From 23b8eeba3c9501e463582f023c4168c06f34c4ae Mon Sep 17 00:00:00 2001 From: Alejandro Rosas Date: Fri, 6 Nov 2026 12:31:03 +0000 Subject: [PATCH 1/4] DID-10140: [VER-PY] sdk-python: API changes release as minor versions, the shared breaking-change detection, Authentication spec synced --- .github/workflows/spec-sync.yml | 77 +- .gitleaks.toml | 7 + AGENTS.md | 2 +- CLAUDE.md | 2 +- CONTRIBUTING.md | 12 +- README.md | 8 +- codegen/spec_sync.py | 762 +- ...57fc69299601e59cc7a64c1db370fea5c7461.json | 109606 ++++++++++++ ...799410f82eee1c25c7ce5abc6caa53846be6b.json | 128151 +++++++++++++++ ...2a815773bbd1599256e2774046ee76520c6c8.json | 128132 ++++++++++++++ ...f57a1dfb6374bfd391750d2044fc7a206e4ea.json | 128151 +++++++++++++++ codegen/tests/test_codegen.py | 148 +- codegen/tests/test_spec_sync.py | 528 + docs/DESIGN.md | 20 +- openapi/SOURCE.json | 5 + openapi/authentication.json | 2634 + pyproject.toml | 2 + release-please-config.json | 15 + tests/test_authentication_contract.py | 224 + 19 files changed, 498154 insertions(+), 332 deletions(-) create mode 100644 codegen/tests/fixtures/verification-9fe57fc69299601e59cc7a64c1db370fea5c7461.json create mode 100644 codegen/tests/fixtures/verification-ccc799410f82eee1c25c7ce5abc6caa53846be6b.json create mode 100644 codegen/tests/fixtures/verification-e002a815773bbd1599256e2774046ee76520c6c8.json create mode 100644 codegen/tests/fixtures/verification-fe9f57a1dfb6374bfd391750d2044fc7a206e4ea.json create mode 100644 codegen/tests/test_spec_sync.py create mode 100644 openapi/authentication.json create mode 100644 tests/test_authentication_contract.py diff --git a/.github/workflows/spec-sync.yml b/.github/workflows/spec-sync.yml index b15efc2..d9987e5 100644 --- a/.github/workflows/spec-sync.yml +++ b/.github/workflows/spec-sync.yml @@ -2,7 +2,17 @@ # # Downloads the upstream OpenAPI documents into openapi/, regenerates the SDK, runs the # checks and opens (or updates) one pull request titled -# "chore(spec): sync API spec YYYY-MM-DD" on the branch chore/spec-sync. +# "(spec): sync API spec YYYY-MM-DD" on the branch chore/spec-sync. +# +# The type comes from codegen/spec_sync.py (DESIGN.md sections 3.1 and 13): feat(spec) when +# a caller can observe the change, a minor release even when it can break typed code; +# chore(spec) when only documentation changed, which releases nothing on its own. Never "!" +# or a BREAKING CHANGE footer. The body opens with "Breaking changes" and ends with a +# BEGIN_COMMIT_OVERRIDE block that release-please reads instead of the merged commit: the +# sync line plus one nested api(spec) commit per breaking change, which +# release-please-config.json lists under "API changes that can break typed code". Merge +# with "Squash and merge": after a merge commit release-please reads the block once per +# commit, and every line would appear twice in the release notes. # # Token: pull requests opened with the default GITHUB_TOKEN do not trigger CI (GitHub does # not start workflows from events caused by GITHUB_TOKEN). To get CI on the sync pull @@ -48,11 +58,12 @@ jobs: UV_FROZEN: "1" steps: - name: Prepare - id: prepare - # The summary lives outside the checkout so that it is never committed. + # The summary, the checks and the commit message live outside the checkout so that + # they are never committed. run: | echo "SUMMARY=$RUNNER_TEMP/spec-sync-summary.md" >> "$GITHUB_ENV" - echo "today=$(date -u +%Y-%m-%d)" >> "$GITHUB_OUTPUT" + echo "CHECKS=$RUNNER_TEMP/checks.md" >> "$GITHUB_ENV" + echo "COMMIT_MESSAGE=$RUNNER_TEMP/commit-message.txt" >> "$GITHUB_ENV" - name: Check out the repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -86,9 +97,9 @@ jobs: - name: Download the upstream OpenAPI documents id: sync if: steps.guard.outputs.skip != 'true' - # Writes changed=true|false to $GITHUB_OUTPUT and a markdown summary of the added, - # removed and changed operations to $SUMMARY. - run: uv run python codegen/spec_sync.py --summary "$SUMMARY" + # Writes changed=true|false, date and type=feat|chore to $GITHUB_OUTPUT, a markdown + # summary (breaking changes first) to $SUMMARY and the commit message to $COMMIT_MESSAGE. + run: uv run python codegen/spec_sync.py --summary "$SUMMARY" --commit-message "$COMMIT_MESSAGE" - name: Regenerate the SDK and run the checks id: checks if: steps.sync.outputs.changed == 'true' @@ -98,15 +109,15 @@ jobs: echo echo "## Checks" echo - } >> "$SUMMARY" + } >> "$CHECKS" run_check() { name="$1" shift echo "::group::$name" if "$@"; then - echo "- [x] $name" >> "$SUMMARY" + echo "- [x] $name" >> "$CHECKS" else - echo "- [ ] $name: **failed**, see the workflow run" >> "$SUMMARY" + echo "- [ ] $name: **failed**, see the workflow run" >> "$CHECKS" echo "::error::$name failed" failed=true fi @@ -122,8 +133,46 @@ jobs: echo echo "Opened by [spec sync run ${GITHUB_RUN_ID}](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})." echo "Generated code comes from codegen/ and openapi/; do not edit it by hand." - } >> "$SUMMARY" + } >> "$CHECKS" echo "failed=$failed" >> "$GITHUB_OUTPUT" + - name: Write the pull request body and the commit message + id: body + if: steps.sync.outputs.changed == 'true' + env: + TYPE: ${{ steps.sync.outputs.type }} + run: | + body="$RUNNER_TEMP/pull-request-body.md" + override="$RUNNER_TEMP/commit-override.md" + { + echo + echo "## Release notes" + echo + echo "release-please reads the block below instead of the merged commit, so the release type (\`${TYPE}(spec)\`, never a major version) and the lines under \"API changes that can break typed code\" do not depend on how the commit is titled. Merge with **Squash and merge** and keep the block when you edit this description: after a merge commit release-please reads the block once per commit, and every line would appear twice." + echo + echo "BEGIN_COMMIT_OVERRIDE" + cat "$COMMIT_MESSAGE" + echo "END_COMMIT_OVERRIDE" + } > "$override" + # A pull request description holds 65536 characters: shorten the report, never the + # checks or the block release-please reads. The full report is in the sync step's log. + touch "$CHECKS" + limit=$((65000 - $(wc -c < "$override") - $(wc -c < "$CHECKS"))) + if [ "$(wc -c < "$SUMMARY")" -gt "$limit" ]; then + { + head -c "$((limit - 200))" "$SUMMARY" | sed '$d' + echo + echo "_Shortened to fit the pull request description; the full report is in the log of this run's sync step._" + } > "$body" + else + cp "$SUMMARY" "$body" + fi + cat "$CHECKS" "$override" >> "$body" + delimiter="EOF_$(openssl rand -hex 16)" + { + echo "message<<$delimiter" + cat "$COMMIT_MESSAGE" + echo "$delimiter" + } >> "$GITHUB_OUTPUT" - name: Open or update the spec sync pull request if: steps.sync.outputs.changed == 'true' uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 @@ -131,9 +180,9 @@ jobs: token: ${{ secrets.SPEC_SYNC_TOKEN || secrets.GITHUB_TOKEN }} branch: chore/spec-sync delete-branch: true - title: "chore(spec): sync API spec ${{ steps.prepare.outputs.today }}" - commit-message: "chore(spec): sync API spec ${{ steps.prepare.outputs.today }}" - body-path: ${{ env.SUMMARY }} + title: "${{ steps.sync.outputs.type }}(spec): sync API spec ${{ steps.sync.outputs.date }}" + commit-message: ${{ steps.body.outputs.message }} + body-path: ${{ runner.temp }}/pull-request-body.md # A sync that fails a check still opens the pull request, as a draft, so the fix # (usually an openapi/overlay.json entry) can be pushed to the same branch. draft: ${{ steps.checks.outputs.failed == 'true' }} diff --git a/.gitleaks.toml b/.gitleaks.toml index a210c06..b07ea4d 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -10,9 +10,16 @@ description = "Upstream copies, synced byte for byte by the spec sync workflow a paths = [ '''^openapi/verification\.json$''', '''^openapi/account\.json$''', + '''^openapi/authentication\.json$''', '''^openapi/webhook-vectors\.json$''', ] +[[allowlists]] +description = "Earlier upstream copies of openapi/verification.json, pinned by commit for the spec sync classification tests" +paths = [ + '''^codegen/tests/fixtures/verification-[0-9a-f]{40}\.json$''', +] + [[allowlists]] description = "Synthetic webhook secrets used by the test suite" regexes = [ diff --git a/AGENTS.md b/AGENTS.md index df8323b..388c014 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,7 +36,7 @@ uv for environments and locking, hatchling for the build, pytest, mypy --strict, ## Rules - Generated code is never edited by hand. Change the generator in `codegen/` or `openapi/overlay.json`, then regenerate; CI fails when the committed generated code is stale. -- `openapi/verification.json`, `openapi/account.json` and `openapi/webhook-vectors.json` are upstream copies; never edit them by hand. Only the spec sync workflow updates them. +- `openapi/verification.json`, `openapi/account.json`, `openapi/authentication.json` and `openapi/webhook-vectors.json` are upstream copies; never edit them by hand. Only the spec sync workflow updates them. `authentication.json` is never generated from: `tests/test_authentication_contract.py` holds the hand-written `authentication` module to it. - Keep the SDKs in sync (HARD RULE). Any public API change in `service-didit-verification` or `service-didit-auth` must reach the OpenAPI documents in mintlify-docs, and from there every server SDK through the spec sync pull request. A change to hand-written core behaviour that DESIGN.md describes (client options, errors, retries, pagination, webhooks, Authentication) is made in all seven SDK repositories, one pull request each, and DESIGN.md is updated in all seven in the same words. - Feature parity across languages: a capability shipped in one server SDK ships in the other six. - Releases publish straight from this repository (Alex, 2026-10-29): merging the release-please pull request is the release approval, and the release workflow then creates the vX.Y.Z tag and publishes to the registry. Never push a release tag, create a release or publish by hand outside that flow, and never store a registry token where trusted publishing works. diff --git a/CLAUDE.md b/CLAUDE.md index df8323b..388c014 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,7 +36,7 @@ uv for environments and locking, hatchling for the build, pytest, mypy --strict, ## Rules - Generated code is never edited by hand. Change the generator in `codegen/` or `openapi/overlay.json`, then regenerate; CI fails when the committed generated code is stale. -- `openapi/verification.json`, `openapi/account.json` and `openapi/webhook-vectors.json` are upstream copies; never edit them by hand. Only the spec sync workflow updates them. +- `openapi/verification.json`, `openapi/account.json`, `openapi/authentication.json` and `openapi/webhook-vectors.json` are upstream copies; never edit them by hand. Only the spec sync workflow updates them. `authentication.json` is never generated from: `tests/test_authentication_contract.py` holds the hand-written `authentication` module to it. - Keep the SDKs in sync (HARD RULE). Any public API change in `service-didit-verification` or `service-didit-auth` must reach the OpenAPI documents in mintlify-docs, and from there every server SDK through the spec sync pull request. A change to hand-written core behaviour that DESIGN.md describes (client options, errors, retries, pagination, webhooks, Authentication) is made in all seven SDK repositories, one pull request each, and DESIGN.md is updated in all seven in the same words. - Feature parity across languages: a capability shipped in one server SDK ships in the other six. - Releases publish straight from this repository (Alex, 2026-10-29): merging the release-please pull request is the release approval, and the release workflow then creates the vX.Y.Z tag and publishes to the registry. Never push a release tag, create a release or publish by hand outside that flow, and never store a registry token where trusted publishing works. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2087317..c74cf90 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,17 +28,21 @@ CI runs `uv run python codegen/generate.py --check`, which regenerates into a te ## The OpenAPI documents -- `openapi/verification.json`, `openapi/account.json` and `openapi/webhook-vectors.json` are upstream copies. Never edit them by hand; only the spec sync workflow (`.github/workflows/spec-sync.yml`) updates them. +- `openapi/verification.json`, `openapi/account.json`, `openapi/authentication.json` and `openapi/webhook-vectors.json` are upstream copies. Never edit them by hand; only the spec sync workflow (`.github/workflows/spec-sync.yml`) updates them. The generator reads `verification.json` and `account.json`; `authentication.json` holds the hand-written Authentication module to its contract (`tests/test_authentication_contract.py`). - `openapi/overlay.json` holds the SDK naming (`x-didit-sdk`, DESIGN section 4) for operations whose upstream definition does not carry it yet. Keys are `" "` for `verification.json` and `"account "` for `account.json`; each value has `resource` (dot path of snake_case names, one nested accessor per segment), `method`, `auth`, `server` and, for application-scoped paths, `scope`. When upstream carries `x-didit-sdk` for an operation, the upstream value wins and the overlay entry is deleted. - A new server operation without SDK naming makes the generator fail with a message that names the operation. The fix is an overlay entry (or `x-didit-sdk` upstream). -The spec sync runs daily. When upstream changed, it regenerates the code, runs the checks and opens a pull request titled `chore(spec): sync API spec YYYY-MM-DD`. Its body lists added, removed, renamed and changed operations and schemas; field-level differences (nested fields, types, enum values, nullability, required fields) that can break code written against the SDK are listed under "Breaking changes". A download that is not an OpenAPI 3 document with paths is refused and nothing is written. +The spec sync runs daily. When upstream changed, it regenerates the code, runs the checks and opens a pull request titled `(spec): sync API spec YYYY-MM-DD`. The type is `feat(spec)` when a caller can observe the change (a minor release, even when it can break typed code) and `chore(spec)` when only descriptions, examples or other documentation changed (no release on its own); never `!` or a `BREAKING CHANGE:` footer (DESIGN sections 3.1 and 13). A download that is not an OpenAPI 3 document with paths is refused and nothing is written. + +The body opens with "Breaking changes", or "None detected": every field or parameter whose type changed or that can now be null, removed operation, field or enum value, and newly required parameter or request field, in parameters, request bodies and 2xx responses, following `$ref`s and array items. Then it lists added, removed, renamed and changed operations and schemas. It ends with a `BEGIN_COMMIT_OVERRIDE` block that release-please reads instead of the merged commit: the sync line plus one nested `api(spec):` commit per breaking change, which `release-please-config.json` lists under "API changes that can break typed code". Merge sync pull requests with **Squash and merge** and keep that block: after a merge commit release-please reads it once per commit and every line appears twice. + +`openapi/authentication.json` is synced like the others, but the `didit.authentication` module stays hand-written (DESIGN section 10). When a sync adds, removes or renames one of its server operations, the body lists it under "Authentication module" and `tests/test_authentication_contract.py` fails until the module follows on the sync branch. When a sync fails a check, the pull request opens as a draft; push the fix (usually an `openapi/overlay.json` entry) to its branch `chore/spec-sync`. While that branch carries a commit that is not from the sync, later syncs leave it alone, so the fix is never overwritten: merge or close the pull request and the next sync picks up anything that changed since. ### Deprecations -The sync never deletes a public method silently (DESIGN section 3.1). When an operation is removed upstream, or its `x-didit-sdk` naming changes (a rename), `codegen/spec_sync.py` records the old method in `openapi/deprecated-operations.json`, keyed by `resource.method`, with a dereferenced copy of the operation and `remove_in`, the next minor version. The generator keeps it as a method marked `@deprecated` (a renamed method's old name calls the same endpoint as the new one). An operation that moves to another path with the same naming is not deprecated; the summary compares the old and new operation. Two cases are breaking and are not kept: a name that now belongs to a different operation (swapped or reused), and an operation that becomes a device or end-user token operation (`server: false`), which server SDKs never expose. An operation that loses its naming makes the generator fail until `openapi/overlay.json` names it again. +The sync never deletes a public method silently (DESIGN section 3.1). When an operation is removed upstream, or its `x-didit-sdk` naming changes (a rename), `codegen/spec_sync.py` records the old method in `openapi/deprecated-operations.json`, keyed by `resource.method`, with a dereferenced copy of the operation and `remove_in`, the next minor version. The generator keeps it as a method marked `@deprecated` (a renamed method's old name calls the same endpoint as the new one). An operation that moves to another path with the same naming is not deprecated; the summary compares the old and new operation. Two cases are not kept: a name that now belongs to a different operation (swapped or reused), which the summary flags for review, and an operation that becomes a device or end-user token operation (`server: false`), which server SDKs never expose and which the summary lists as a breaking change. An operation that loses its naming makes the generator fail until `openapi/overlay.json` names it again. - When the release that reaches `remove_in` is prepared (the release-please pull request), `tests/test_contract.py` fails until the entry is deleted: delete it, run `uv run python codegen/generate.py` and push to the release pull request. - If a deprecated name becomes live again (for example an overlay entry gives a new path the old name), the generator says so; delete the entry. @@ -72,7 +76,7 @@ They never run in the default CI. A maintainer can start them from the CI workfl ## Commits, versions and the changelog -Pull request titles and commits follow [Conventional Commits](https://www.conventionalcommits.org/): `fix:` is a patch, `feat:` a minor version, and `feat!:` or a `BREAKING CHANGE:` footer a major version (a minor version while the SDK is 0.x). Pick the type that matches the semver impact of the change. +Pull request titles and commits follow [Conventional Commits](https://www.conventionalcommits.org/): `fix:`, `perf:`, `deps:` and `docs:` are a patch, `feat:` a minor version, and `chore:`, `ci:`, `test:`, `build:`, `refactor:` and `style:` release nothing on their own. `!` or a `BREAKING CHANGE:` footer (a major version) is only for a change to the SDK's own design: renaming or removing a method or resource the API still has, client configuration, errors, retries, pagination or webhook verification behaviour, or a dropped Python version. A change that mirrors the API is a minor version even when it can break typed code (DESIGN section 13). Pick the type that matches the semver impact of the change. Releases are cut by release-please from those commits. It updates the version and generates `CHANGELOG.md`; never edit the changelog by hand. Squash-merge pull requests so that their conventional title becomes the commit release-please reads. `.release-please-manifest.json` holds the released version. diff --git a/README.md b/README.md index 697cd7b..03f636a 100644 --- a/README.md +++ b/README.md @@ -272,7 +272,13 @@ CPython 3.10, 3.11, 3.12, 3.13 and 3.14 on Linux, macOS and Windows. Dependencie ## Versioning -Semantic versioning. Until 1.0.0 the version stays `0.x` and minor versions may change the public API. Releases are cut by release-please from conventional commits; see [CHANGELOG.md](CHANGELOG.md) once the first release is out. When an endpoint is removed from the API, or its method is renamed, the old method stays for one more minor version, marked deprecated (it emits a `DeprecationWarning` and type checkers flag it); a renamed method's old name keeps working as an alias of the new one. +Semantic versioning, with one rule for API changes: + +- A change that mirrors the Didit API is a minor version, even when it can break typed code (a field whose type changed or that can now be `None`, a removed field, endpoint or enum value). The API already behaves that way for every caller, so staying on an older SDK version does not protect you. The release notes list these changes under "API changes that can break typed code": read that section and run your type checker when you upgrade. +- A major version is only for a change to the SDK's own design: renaming or removing a method or resource the API still has, changing client configuration, errors, retries, pagination or webhook verification behaviour, or dropping a supported Python version. +- A sync that only changes the API documentation releases nothing on its own. + +Releases are cut by release-please from conventional commits; see [CHANGELOG.md](CHANGELOG.md). When an endpoint is removed from the API, or its method is renamed, the old method stays for one more minor version, marked deprecated (it emits a `DeprecationWarning` and type checkers flag it); a renamed method's old name keeps working as an alias of the new one. ## Contributing and security diff --git a/codegen/spec_sync.py b/codegen/spec_sync.py index 08f99c2..4ff7f36 100644 --- a/codegen/spec_sync.py +++ b/codegen/spec_sync.py @@ -3,31 +3,43 @@ uv run python codegen/spec_sync.py --summary spec-sync-summary.md -Downloads https://docs.didit.me/openapi-25.json and openapi-auth.json, and when -either changed: +Downloads https://docs.didit.me/openapi-25.json, openapi-auth.json and +openapi-authentication.json, and when any of them changed: - checks that each download is an OpenAPI 3 document with paths, and writes nothing otherwise; -- writes them byte for byte to ``openapi/verification.json`` / ``account.json`` - and records URL, SHA-256 and fetch time in ``openapi/SOURCE.json``; +- writes them byte for byte to ``openapi/verification.json`` / ``account.json`` / + ``authentication.json`` and records URL, SHA-256 and fetch time in + ``openapi/SOURCE.json``; - drops ``overlay.json`` entries that upstream now carries as ``x-didit-sdk``; - never deletes a public method silently: a server method whose operation was removed, or whose naming changed upstream (a rename), is recorded, fully dereferenced, in ``openapi/deprecated-operations.json``, so the SDK keeps it as a deprecated method until the next minor version (``remove_in``). An - operation whose SDK name reappears on another path counts as moved, not removed; -- writes a Markdown summary of added, removed, renamed and changed operations - and schemas. Changes are compared field by field, nested objects, arrays, - types, enums, nullability and required fields included, and every change - that can break code written against the SDK is listed under "Breaking changes". - -It prints ``changed=true|false`` to ``$GITHUB_OUTPUT`` when that is set. The -generator, not this script, regenerates the code. + operation whose SDK name reappears on another path counts as moved, not removed. + Nothing is generated or deprecated from ``authentication.json``: that module is + hand-written (DESIGN section 10) and a contract test holds it to the document; +- picks the commit type (DESIGN sections 3.1 and 13): ``feat`` when anything a + caller can observe changed, ``chore`` when only documentation did. Never ``!`` + or a ``BREAKING CHANGE:`` footer: a change that mirrors the API is a minor version; +- writes a Markdown summary that opens with "Breaking changes", every change that + can break typed code (a field or parameter whose type changed or that can now be + null, a removed operation, field or enum value, a newly required parameter or + request field) in parameters, request bodies and 2xx responses, following + ``$ref``s and array items, then lists added, removed, renamed and changed + operations and schemas; +- writes the commit message release-please reads (``--commit-message``): the sync + line, then one nested ``api(spec):`` commit per breaking change, which + ``release-please-config.json`` lists under "API changes that can break typed code". + +It prints ``changed``, ``date`` and ``type`` (feat or chore) to ``$GITHUB_OUTPUT`` +when that is set. The generator, not this script, regenerates the code. """ from __future__ import annotations import argparse +import dataclasses import datetime as dt import hashlib import json @@ -42,12 +54,47 @@ UPSTREAM = { "verification.json": "https://docs.didit.me/openapi-25.json", "account.json": "https://docs.didit.me/openapi-auth.json", + "authentication.json": "https://docs.didit.me/openapi-authentication.json", } -PREFIXES = {"verification.json": "", "account.json": "account "} +PREFIXES = { + "verification.json": "", + "account.json": "account ", + "authentication.json": "authentication ", +} +# Documents the generator never reads: their module is hand-written (DESIGN section 10). +HAND_WRITTEN = frozenset({"authentication.json"}) METHODS = ("get", "put", "post", "delete", "patch", "head", "options") -# Keys that document a schema without changing what is sent or received. -DOC_KEYS = frozenset({"description", "summary", "title", "example", "examples", "externalDocs"}) +# Keys that document without changing what is sent or received. In a map of names +# (NAME_MAPS) a key is a name: a property called "description" is a field. +DOC_KEYS = frozenset( + {"description", "summary", "title", "example", "examples", "externalDocs", "tags"} +) +NAME_MAPS = frozenset( + { + "properties", + "patternProperties", + "paths", + "schemas", + "responses", + "parameters", + "requestBodies", + "headers", + "securitySchemes", + "content", + "encoding", + "variables", + "mapping", + "x-webhooks", + } +) +# The extension a caller can observe (the webhook events). Every other x- key documents; +# x-didit-sdk is compared as the effective naming, together with overlay.json. +OBSERVABLE_EXTENSIONS = frozenset({"x-webhooks"}) MAX_NOTES = 25 +MAX_DEPTH = 32 +# Lines a sync adds to the release notes; the pull request lists every breaking change. +MAX_RELEASE_NOTES = 100 +NO_DIFFERENCE = "descriptions, examples or other details changed" Json = Any @@ -121,31 +168,168 @@ def operations(doc: Json, prefix: str) -> dict[str, Json]: return out -# Schema comparison ---------------------------------------------------------------------- +# What a caller can observe ---------------------------------------------------------------- -def _shape(node: Json) -> Json: - """``node`` without documentation, so that only what is sent or received compares.""" +def _shape(node: Json, names: bool = False) -> Json: + """``node`` without documentation, so that only what is sent or received compares. + + ``names`` marks a map whose keys are names (properties, paths, status codes...), + which all stay. ``required`` and ``type`` lists compare in any order. + """ if isinstance(node, dict): - return { - k: _shape(v) - for k, v in node.items() - if k not in DOC_KEYS and not str(k).startswith("x-") - } + out: dict[str, Json] = {} + for k, v in node.items(): + if not names and ( + k in DOC_KEYS or (str(k).startswith("x-") and k not in OBSERVABLE_EXTENSIONS) + ): + continue + if not names and k in ("required", "type") and isinstance(v, list): + out[k] = sorted(v, key=str) + else: + out[k] = _shape(v, not names and k in NAME_MAPS) + return out if isinstance(node, list): return [_shape(v) for v in node] return node -def _types(schema: Json) -> set[str]: +def observable(doc: Json) -> Json: + """What a caller can observe in a document: no documentation, no ``info`` and no + ``x-didit-sdk`` (the sync compares the effective naming instead).""" + return _shape({k: v for k, v in doc.items() if k != "info"}) + + +# Schema comparison ---------------------------------------------------------------------- + + +@dataclasses.dataclass(frozen=True) +class Change: + """One difference of an operation; ``breaking`` when it is in the DESIGN 3.1 list.""" + + where: str # "response", "request body", "parameter `limit` (query)", "operation" + path: str # the field, for example "registry_checks[].company.registry_data"; "" for all + kind: str # "can now be null", "type changed from string to object", "removed", ... + breaking: bool = False + + def text(self) -> str: + target = f"{self.where} `{self.path}`" if self.path else self.where + return f"{target}: {self.kind}" + + +@dataclasses.dataclass(frozen=True) +class BreakingChange: + """A change that can break typed code, as the pull request and the release notes list it.""" + + operation: str + where: str + path: str + kind: str + + def describe(self) -> str: + # No "<" or ">": release-please escapes them in the release notes. + target = " ".join(p for p in (self.where, f"`{self.path}`" if self.path else "") if p) + return f"`{self.operation}`{' ' + target if target else ''}: {self.kind}" + + +def _null_only(schema: Json) -> bool: + return isinstance(schema, dict) and ( + schema.get("type") == "null" or schema.get("enum") == [None] + ) + + +def _variants(schema: Json, keyword: str | None = None) -> list[Json]: + """The oneOf/anyOf variants other than null (of ``keyword``, or of both).""" + keywords = (keyword,) if keyword else ("oneOf", "anyOf") + return [v for k in keywords for v in schema.get(k) or [] if not _null_only(v)] + + +def _nullable(schema: Json, depth: int = 0) -> bool: + """Whether ``schema`` accepts null: a null type, ``nullable``, null in its enum, or a + null or nullable oneOf/anyOf variant.""" + if not isinstance(schema, dict) or depth > MAX_DEPTH: + return False raw = schema.get("type") - types = set(raw) if isinstance(raw, list) else ({raw} if isinstance(raw, str) else set()) + if raw == "null" or (isinstance(raw, list) and "null" in raw): + return True if schema.get("nullable") is True: - types.add("null") - for variant in schema.get("anyOf", []) + schema.get("oneOf", []): - if isinstance(variant, dict) and variant.get("type") == "null": - types.add("null") - return types + return True + if isinstance(schema.get("enum"), list) and None in schema["enum"]: + return True + members = schema.get("allOf") + if "type" not in schema and members and all(_nullable(m, depth + 1) for m in members): + return True + return any(_nullable(v, depth + 1) for k in ("oneOf", "anyOf") for v in schema.get(k) or []) + + +def _single(schema: Json, depth: int = 0) -> Json: + """``schema`` read as one type: ``allOf`` members merged into it, and a oneOf/anyOf + with a single non-null variant replaced by that variant, so that ``anyOf: [X, null]`` + reads as a nullable X rather than as X's fields removed.""" + if not isinstance(schema, dict) or depth > MAX_DEPTH: + return schema + members = schema.get("allOf") + if isinstance(members, list) and all(isinstance(m, dict) for m in members): + merged = {k: v for k, v in schema.items() if k != "allOf"} + for member in members: + for key, value in _single(member, depth + 1).items(): + if key == "properties" and isinstance(value, dict): + merged["properties"] = {**value, **(merged.get("properties") or {})} + elif key == "required" and isinstance(value, list): + merged["required"] = sorted({*(merged.get("required") or []), *value}) + else: + merged.setdefault(key, value) + schema = merged + variants = _variants(schema) + if len(variants) != 1 or not isinstance(variants[0], dict): + return schema + rest = {k: v for k, v in schema.items() if k not in ("oneOf", "anyOf")} + return _single({**variants[0], **rest}, depth + 1) + + +def _json_type(value: Json) -> str: + if isinstance(value, bool): + return "boolean" + if isinstance(value, int): + return "integer" + if isinstance(value, float): + return "number" + if isinstance(value, str): + return "string" + return "array" if isinstance(value, list) else "object" + + +def _kinds(schema: Json, depth: int = 0) -> frozenset[str]: + """The JSON types ``schema`` allows besides null, inferred when it has no ``type`` + (a union allows its variants' types); empty when it does not say.""" + if not isinstance(schema, dict) or depth > MAX_DEPTH: + return frozenset() + raw = schema.get("type") + kinds = set(raw) if isinstance(raw, list) else ({raw} if isinstance(raw, str) else set()) + kinds.discard("null") + if kinds: + return frozenset(kinds) + if any(k in schema for k in ("properties", "additionalProperties", "patternProperties")): + return frozenset({"object"}) + if "items" in schema: + return frozenset({"array"}) + variants = _variants(schema) + if variants: + found = [_kinds(v, depth + 1) for v in variants] + return frozenset().union(*found) if all(found) else frozenset() + for member in schema.get("allOf") or []: + kinds |= _kinds(member, depth + 1) + if not kinds: + values = schema["enum"] if isinstance(schema.get("enum"), list) else [] + if "const" in schema: + values = [schema["const"]] + kinds = {_json_type(v) for v in values if v is not None} + return frozenset(kinds) + + +def _enum(schema: Json) -> list[Json] | None: + values = schema.get("enum") + return [v for v in values if v is not None] if isinstance(values, list) else None def _object_props(schema: Json) -> tuple[dict[str, Json], set[str]]: @@ -157,189 +341,227 @@ def _object_props(schema: Json) -> tuple[dict[str, Json], set[str]]: if not isinstance(cur, dict): continue props.update(cur.get("properties") or {}) - required |= set(cur.get("required") or []) + required |= {name for name in cur.get("required") or [] if isinstance(name, str)} stack.extend(cur.get("allOf") or []) return props, required -def _variants(schema: Json, keyword: str) -> list[Json]: - return [ - v - for v in schema.get(keyword) or [] - if not (isinstance(v, dict) and v.get("type") == "null") - ] +def _join(path: str, name: str) -> str: + return f"{path}.{name}" if path else name def diff_schema( old: Json, new: Json, where: str, + path: str = "", *, request: bool, - notes: list[tuple[str, bool]], + out: list[Change], depth: int = 0, ) -> None: - """Append ``(note, breaking)`` for every difference between two dereferenced schemas. + """Append a :class:`Change` for every difference between two dereferenced schemas. - ``request`` tells which side the schema is on: removing an enum value breaks - callers that send it, while a response enum is open (DESIGN 5.1) and gaining - or losing a value breaks nobody; a field that becomes required breaks callers, - a response field that is no longer always present breaks readers. + Breaking (DESIGN 3.1, the same in requests and responses): a type change in either + direction, a value that can now be null, a removed field, enum value or union + variant, and in a request (``request``) a field that is newly required. Everything + else is listed, not counted. """ - if depth > 16 or _shape(old) == _shape(new): + if depth > MAX_DEPTH or _shape(old) == _shape(new): return + start = len(out) + + def note(kind: str, breaking: bool = False, at: str = path) -> None: + out.append(Change(where, at, kind, breaking)) + if not isinstance(old, dict) or not isinstance(new, dict): - notes.append((f"{where}: schema replaced", True)) + note("schema replaced", True) return - old_types, new_types = _types(old), _types(new) - old_plain, new_plain = old_types - {"null"}, new_types - {"null"} - if old_plain and new_plain and old_plain != new_plain: - widened = old_plain == {"integer"} and new_plain == {"number"} - narrowed = old_plain == {"number"} and new_plain == {"integer"} - harmless = (request and widened) or (not request and narrowed) - notes.append( - ( - f"{where}: type {'/'.join(sorted(old_plain))} -> {'/'.join(sorted(new_plain))}", - not harmless, - ) - ) + if _nullable(new) and not _nullable(old): + note("can now be null", True) + elif _nullable(old) and not _nullable(new): + note("no longer accepts null" if request else "can no longer be null") + # A plain schema is a union of one variant: [X, null] against X loses no variant. + sizes = { + k: (len(_variants(old, k)) or 1, len(_variants(new, k)) or 1) for k in ("oneOf", "anyOf") + } + raw_old, raw_new = old, new + old, new = _single(old), _single(new) + old_kinds, new_kinds = _kinds(old), _kinds(new) + if old_kinds and new_kinds and old_kinds != new_kinds: + before, after = " or ".join(sorted(old_kinds)), " or ".join(sorted(new_kinds)) + note(f"type changed from {before} to {after}", True) return - if "null" in new_types and "null" not in old_types and not request: - notes.append((f"{where}: can now be null", True)) - if "null" in old_types and "null" not in new_types and request: - notes.append((f"{where}: no longer accepts null", True)) - - old_enum, new_enum = old.get("enum"), new.get("enum") - if isinstance(old_enum, list) and isinstance(new_enum, list): - gone = [v for v in old_enum if v not in new_enum] + + old_enum, new_enum = _enum(old), _enum(new) + if old_enum is not None and new_enum is not None: + for value in old_enum: + if value not in new_enum: + note(f"enum value {json.dumps(value, ensure_ascii=False)} removed", True) added = [v for v in new_enum if v not in old_enum] - if gone: - notes.append((f"{where}: values removed {json.dumps(gone)}", request)) if added: - notes.append((f"{where}: values added {json.dumps(added)}", False)) - elif isinstance(new_enum, list) and not isinstance(old_enum, list): - notes.append((f"{where}: now limited to {json.dumps(new_enum)}", request)) - elif isinstance(old_enum, list): - notes.append((f"{where}: no longer limited to fixed values", False)) + note(f"enum values added {json.dumps(added, ensure_ascii=False)}") + elif new_enum is not None: + note(f"now limited to {json.dumps(new_enum, ensure_ascii=False)}") + elif old_enum is not None: + note("no longer limited to fixed values") old_props, old_req = _object_props(old) new_props, new_req = _object_props(new) - for name in sorted(set(old_props) - set(new_props)): - notes.append((f"{where}.{name}: removed", True)) - for name in sorted(set(new_props) - set(old_props)): - required = request and name in new_req - notes.append((f"{where}.{name}: added{' (required)' if required else ''}", required)) - for name in sorted(set(old_props) & set(new_props)): - if request and name in new_req and name not in old_req: - notes.append((f"{where}.{name}: now required", True)) - if not request and name in old_req and name not in new_req: - notes.append((f"{where}.{name}: no longer always present", True)) - diff_schema( - old_props[name], new_props[name], f"{where}.{name}", - request=request, notes=notes, depth=depth + 1, - ) # fmt: skip + # A union's fields live in its variants, compared below. + if (old_props and new_props) or not (_variants(old) or _variants(new)): + for name in sorted(set(old_props) - set(new_props)): + note("removed", True, _join(path, name)) + for name in sorted(set(new_props) - set(old_props)): + required = request and name in new_req + note("added (required)" if required else "added", required, _join(path, name)) + for name in sorted(set(old_props) & set(new_props)): + at = _join(path, name) + if name in new_req and name not in old_req: + note("now required" if request else "now always present", request, at) + elif name in old_req and name not in new_req: + note("no longer required" if request else "no longer always present", False, at) + diff_schema( + old_props[name], new_props[name], where, at, + request=request, out=out, depth=depth + 1, + ) # fmt: skip for key, suffix in (("items", "[]"), ("additionalProperties", "{}")): - if isinstance(old.get(key), dict) or isinstance(new.get(key), dict): + old_sub, new_sub = old.get(key), new.get(key) + if isinstance(old_sub, dict) or isinstance(new_sub, dict): diff_schema( - old.get(key) or {}, new.get(key) or {}, f"{where}{suffix}", - request=request, notes=notes, depth=depth + 1, + old_sub if isinstance(old_sub, dict) else {}, + new_sub if isinstance(new_sub, dict) else {}, + where, path + suffix, request=request, out=out, depth=depth + 1, ) # fmt: skip - for keyword in ("oneOf", "anyOf"): - old_vars, new_vars = _variants(old, keyword), _variants(new, keyword) - if len(new_vars) < len(old_vars): - notes.append( - (f"{where}: {len(old_vars) - len(new_vars)} {keyword} variant(s) removed", True) - ) - elif len(new_vars) > len(old_vars): - notes.append( - (f"{where}: {len(new_vars) - len(old_vars)} {keyword} variant(s) added", False) - ) - else: + for keyword, (old_size, new_size) in sizes.items(): + if new_size < old_size: + note(f"type changed: {old_size - new_size} {keyword} variant(s) removed", True) + elif new_size > old_size: + note(f"{new_size - old_size} {keyword} variant(s) added") + elif old_size > 1: + # One variant each was compared as the schema itself, above. + old_vars, new_vars = _variants(raw_old, keyword), _variants(raw_new, keyword) for index, (a, b) in enumerate(zip(old_vars, new_vars, strict=True)): diff_schema( - a, b, f"{where}<{keyword} {index + 1}>", - request=request, notes=notes, depth=depth + 1, + a, b, where, f"{path}({keyword} {index + 1})", + request=request, out=out, depth=depth + 1, ) # fmt: skip + if len(out) == start: + note("other constraints changed (format, pattern, limits or default)") -def _body(op: Json) -> tuple[set[str], Json]: - content = (op.get("requestBody") or {}).get("content") or {} - media = content.get("application/json") or content.get("multipart/form-data") or {} - return set(content), media.get("schema", {}) +def _parameter_where(key: tuple[str, str]) -> str: + return f"parameter `{key[1]}` ({key[0]})" -def _success(op: Json) -> tuple[set[str], Json]: - for code, response in sorted((op.get("responses") or {}).items()): - if str(code).startswith("2"): - content = response.get("content") or {} - return set(content), (content.get("application/json") or {}).get("schema", {}) - return set(), {} +def _parameters(doc: Json, op: Json) -> dict[tuple[str, str], Json]: + found: dict[tuple[str, str], Json] = {} + for parameter in dereference(doc, op.get("parameters") or []): + if isinstance(parameter, dict): + found[(str(parameter.get("in", "")), str(parameter.get("name", "")))] = parameter + return found -def describe_change(old_doc: Json, old: Json, new_doc: Json, new: Json) -> tuple[list[str], bool]: - """Human-readable differences of one operation, and whether any is breaking.""" - found: list[tuple[str, bool]] = [] - old_params = { - (p.get("in"), p.get("name")): p for p in dereference(old_doc, old.get("parameters", [])) +def _content(doc: Json, node: Json) -> dict[str, Json]: + """The schema of each media type of a request body or response.""" + node = dereference(doc, node) + content = node.get("content") if isinstance(node, dict) else None + if not isinstance(content, dict): + return {} + return { + media: entry.get("schema") or {} + for media, entry in content.items() + if isinstance(entry, dict) } - new_params = { - (p.get("in"), p.get("name")): p for p in dereference(new_doc, new.get("parameters", [])) + + +def _successes(doc: Json, op: Json) -> dict[str, dict[str, Json]]: + responses = op.get("responses") or {} + return { + str(status): _content(doc, responses[status]) + for status in sorted(responses, key=str) + if str(status).startswith("2") } - for key in sorted(set(old_params) - set(new_params), key=str): - found.append((f"parameter removed: {key[1]} ({key[0]})", True)) - for key in sorted(set(new_params) - set(old_params), key=str): - required = bool(new_params[key].get("required")) + + +def _diff_content( + old: dict[str, Json], new: dict[str, Json], where: str, *, request: bool, out: list[Change] +) -> None: + """Compare a body per media type; when none is shared, the first against the first, + so that a body that changes content type, appears or disappears is compared too.""" + if old and new and set(old) != set(new): + out.append( + Change(where, "", f"content type {', '.join(sorted(old))} -> {', '.join(sorted(new))}") + ) + pairs = [(m, m) for m in sorted(set(old) & set(new))] + if not pairs and (old or new): + old = old or {"": {}} + new = new or {"": {}} + pairs = [(min(old), min(new))] + for before, after in pairs: + diff_schema(old[before], new[after], where, request=request, out=out) + + +def describe_change(old_doc: Json, old: Json, new_doc: Json, new: Json) -> list[Change]: + """Every difference of one operation (or of a method that moved to another one).""" + found: list[Change] = [] + old_params, new_params = _parameters(old_doc, old), _parameters(new_doc, new) + for key in sorted(set(old_params) - set(new_params)): + found.append(Change(_parameter_where(key), "", "removed")) + for key in sorted(set(new_params) - set(old_params)): + required = new_params[key].get("required") is True found.append( - (f"parameter added: {key[1]} ({key[0]}{', required' if required else ''})", required) + Change(_parameter_where(key), "", "added (required)" if required else "added", required) ) - for key in sorted(set(old_params) & set(new_params), key=str): - if not old_params[key].get("required") and new_params[key].get("required"): - found.append((f"parameter now required: {key[1]}", True)) + for key in sorted(set(old_params) & set(new_params)): + where = _parameter_where(key) + if old_params[key].get("required") is not True and new_params[key].get("required") is True: + found.append(Change(where, "", "now required", True)) diff_schema( - old_params[key].get("schema") or {}, new_params[key].get("schema") or {}, - f"parameter {key[1]}", request=True, notes=found, + old_params[key].get("schema") or {}, new_params[key].get("schema") or {}, where, + request=True, out=found, ) # fmt: skip - old_types, old_body = _body(old) - new_types, new_body = _body(new) - if old_types != new_types and old_types and new_types: - found.append( - ( - f"request content type {sorted(old_types)} -> {sorted(new_types)}", - not old_types <= new_types, - ) - ) - if (old.get("requestBody") or {}).get("required") is not True and ( - new.get("requestBody") or {} - ).get("required") is True: - found.append(("request body now required", True)) - diff_schema( - dereference(old_doc, old_body), dereference(new_doc, new_body), "body", - request=True, notes=found, + old_body, new_body = old.get("requestBody") or {}, new.get("requestBody") or {} + if dereference(old_doc, old_body).get("required") is not True and ( + dereference(new_doc, new_body).get("required") is True + ): + found.append(Change("request body", "", "now required")) + _diff_content( + _content(old_doc, old_body), _content(new_doc, new_body), "request body", + request=True, out=found, ) # fmt: skip - old_types, old_resp = _success(old) - new_types, new_resp = _success(new) - if old_types != new_types: - found.append((f"response content type {sorted(old_types)} -> {sorted(new_types)}", True)) - diff_schema( - dereference(old_doc, old_resp), dereference(new_doc, new_resp), "response", - request=False, notes=found, - ) # fmt: skip + # Every 2xx response both have; when the only success status changed (200 to 201), + # the old body against the new one. + old_responses, new_responses = _successes(old_doc, old), _successes(new_doc, new) + statuses = [(s, s) for s in old_responses if s in new_responses] + if not statuses and len(old_responses) == 1 and len(new_responses) == 1: + statuses = [(next(iter(old_responses)), next(iter(new_responses)))] + found.append(Change("response", "", f"status {statuses[0][0]} -> {statuses[0][1]}")) + for before, after in statuses: + _diff_content( + old_responses[before], new_responses[after], "response", request=False, out=found + ) if old.get("deprecated") is not True and new.get("deprecated") is True: - found.append(("marked deprecated upstream", False)) + found.append(Change("operation", "", "marked deprecated upstream")) if old.get("x-didit-sdk") != new.get("x-didit-sdk") and new.get("x-didit-sdk") is not None: - found.append((f"x-didit-sdk now {json.dumps(new['x-didit-sdk'], sort_keys=True)}", False)) - if not found: - return ["descriptions, examples or other details changed"], False - breaking = any(b for _, b in found) - # Breaking notes first, then the rest, capped so that one huge change stays readable. - ordered = [n for n, b in found if b] + [n for n, b in found if not b] - notes = [f"{n} (breaking)" if (n, True) in found else n for n in ordered] - if len(notes) > MAX_NOTES: - notes = [*notes[:MAX_NOTES], f"and {len(notes) - MAX_NOTES} more differences"] - return notes, breaking + naming = json.dumps(new["x-didit-sdk"], sort_keys=True) + found.append(Change("x-didit-sdk", "", f"now {naming}")) + # One entry per change, even when several responses or media types share it. + return list(dict.fromkeys(found)) + + +def notes(changes: list[Change]) -> list[str]: + """The summary's notes for one operation: breaking first, capped to stay readable.""" + if not changes: + return [NO_DIFFERENCE] + ordered = [c for c in changes if c.breaking] + [c for c in changes if not c.breaking] + out = [c.text() + (" (breaking)" if c.breaking else "") for c in ordered] + if len(out) > MAX_NOTES: + out = [*out[:MAX_NOTES], f"and {len(out) - MAX_NOTES} more differences"] + return out # Versions ------------------------------------------------------------------------------- @@ -362,10 +584,42 @@ def next_minor(version: str) -> str: # The sync ------------------------------------------------------------------------------- +@dataclasses.dataclass +class SyncResult: + changed: bool + summary: str + date: str = "" + commit_type: str = "chore" # "feat" or "chore" (DESIGN 3.1) + breaking: list[BreakingChange] = dataclasses.field(default_factory=list) + + def commit_message(self) -> str: + """What release-please reads (the pull request's ``BEGIN_COMMIT_OVERRIDE`` block): + the sync, then one nested ``api(spec):`` commit per breaking change. A type + outside release-please's own list only splits off in a nested block.""" + lines = [f"api(spec): {c.describe()}" for c in self.breaking[:MAX_RELEASE_NOTES]] + if len(self.breaking) > MAX_RELEASE_NOTES: + more = len(self.breaking) - MAX_RELEASE_NOTES + lines.append(f"api(spec): {more} more, listed in the spec sync pull request") + message = f"{self.commit_type}(spec): sync API spec {self.date}\n" + if lines: + message += "\n" + "".join( + f"BEGIN_NESTED_COMMIT\n{line}\nEND_NESTED_COMMIT\n" for line in lines + ) + return message + + def _name(naming: Json) -> str: return f"{naming['resource']}.{naming['method']}" +def _hand_written_methods(naming: Json) -> set[str]: + """The methods a hand-written module has for one operation: its own and its variants'.""" + if not naming or not naming.get("server"): + return set() + variants = [v["method"] for v in naming.get("variants") or [] if isinstance(v, dict)] + return {f"{naming['resource']}.{m}" for m in (naming["method"], *variants)} + + def sync( openapi_dir: pathlib.Path, downloads: dict[str, bytes], @@ -373,8 +627,8 @@ def sync( today: dt.date, now: dt.datetime, version: str | None = None, -) -> tuple[bool, str]: - """Apply ``downloads`` (file name -> bytes) to ``openapi_dir``; returns (changed, summary).""" +) -> SyncResult: + """Apply ``downloads`` (file name -> bytes) to ``openapi_dir``.""" old_docs: dict[str, Json] = {} new_docs: dict[str, Json] = {} changed_files: list[str] = [] @@ -386,7 +640,7 @@ def sync( if old_bytes != data: changed_files.append(name) if not changed_files: - return False, "No upstream change.\n" + return SyncResult(False, "No upstream change.\n", today.isoformat()) version = version or _version() remove_in = next_minor(version) @@ -407,6 +661,9 @@ def sync( old_ops[key] = (name, op) for key, op in operations(new_docs[name], PREFIXES[name]).items(): new_ops[key] = (name, op) + hand_written = { + key for key, (name, _) in [*old_ops.items(), *new_ops.items()] if name in HAND_WRITTEN + } def old_naming(key: str) -> Json: return old_ops[key][1].get("x-didit-sdk") or old_overlay.get(key) @@ -417,19 +674,23 @@ def new_naming(key: str) -> Json: added = sorted(set(new_ops) - set(old_ops)) removed = sorted(set(old_ops) - set(new_ops)) common = sorted(set(old_ops) & set(new_ops)) - # Server method name -> the operation that provides it after the sync. + # Generated server method name -> the operation that provides it after the sync. new_server_names = { - _name(n): key for key in new_ops if (n := new_naming(key)) and n.get("server") + _name(n): key + for key in new_ops + if key not in hand_written and (n := new_naming(key)) and n.get("server") } old_server_names = { - _name(n): key for key in old_ops if (n := old_naming(key)) and n.get("server") + _name(n): key + for key in old_ops + if key not in hand_written and (n := old_naming(key)) and n.get("server") } lines: list[str] = [ f"Spec sync {today.isoformat()}. Upstream files changed: {', '.join(changed_files)}.", "", ] - breaking: list[str] = [] + breaking: list[BreakingChange] = [] kept: list[str] = [] def keep(naming: Json, key: str, reason: str, replacement: str | None) -> None: @@ -461,6 +722,9 @@ def keep(naming: Json, key: str, reason: str, replacement: str | None) -> None: deprecated["methods"][method] = entry kept.append(method) + def removed_operation(key: str, detail: str) -> None: + breaking.append(BreakingChange(key, "", "", f"operation removed{detail}")) + lines.append(f"## Added operations ({len(added)})") for key in added: n = new_naming(key) @@ -481,19 +745,21 @@ def keep(naming: Json, key: str, reason: str, replacement: str | None) -> None: lines.append(f"- `{key}`") elif not n.get("server"): lines.append(f"- `{key}` (not exposed by server SDKs)") + elif key in hand_written: + lines.append(f"- `{key}` (`{_name(n)}`, hand-written; see Authentication module)") + removed_operation(key, f" (`{_name(n)}`)") elif _name(n) in new_server_names: lines.append( f"- `{key}` moved to `{new_server_names[_name(n)]}`; `{_name(n)}` keeps its name" ) else: - breaking.append( - f"`{_name(n)}` was removed upstream ({key}); kept as a deprecated method until " - f"version {remove_in}" - ) lines.append( f"- `{key}` (`{_name(n)}`): kept as a deprecated method until version {remove_in}, " "recorded in `openapi/deprecated-operations.json`" ) + removed_operation( + key, f"; `{_name(n)}` stays as a deprecated method until version {remove_in}" + ) keep(n, key, "removed", None) overlay["operations"].pop(key, None) lines.append("") @@ -501,7 +767,7 @@ def keep(naming: Json, key: str, reason: str, replacement: str | None) -> None: renames: list[str] = [] for key in common: before, after = old_naming(key), new_naming(key) - if not before or not before.get("server"): + if key in hand_written or not before or not before.get("server"): continue if after and after.get("server") and _name(after) == _name(before): continue @@ -514,7 +780,6 @@ def keep(naming: Json, key: str, reason: str, replacement: str | None) -> None: f"- `{key}` lost its SDK naming: add `{_name(before)}` back to " "`openapi/overlay.json` (the generator fails until it is named)" ) - breaking.append(f"`{_name(before)}` ({key}) has no SDK naming any more") elif not after.get("server"): # Server SDKs never expose device or end-user token operations (DESIGN 4), # and an API key could not call it any more, so the method goes now. @@ -522,19 +787,16 @@ def keep(naming: Json, key: str, reason: str, replacement: str | None) -> None: f"- `{key}` (`{_name(before)}`) is no longer a server operation " f"(auth {after.get('auth')}); the method is removed" ) - breaking.append( - f"`{_name(before)}` ({key}) is removed: the operation now needs " - f"{after.get('auth')} and server SDKs do not expose it" + removed_operation( + key, + f" from server SDKs: it now needs {after.get('auth')}, " + f"and `{_name(before)}` is removed", ) else: renames.append( f"- `{key}`: `{_name(before)}` renamed to `{_name(after)}`; the old name stays " f"as a deprecated alias until version {remove_in}" ) - breaking.append( - f"`{_name(before)}` was renamed upstream to `{_name(after)}` ({key}); the old " - f"name is a deprecated alias until version {remove_in}" - ) keep(before, key, "renamed", _name(after)) lines.append(f"## Renamed methods ({len(renames)})") lines.extend(renames) @@ -550,26 +812,21 @@ def keep(naming: Json, key: str, reason: str, replacement: str | None) -> None: continue old_name, old = old_ops[old_key] new_name, new = new_ops[new_key] - notes, is_breaking = describe_change(old_docs[old_name], old, new_docs[new_name], new) - if notes == ["descriptions, examples or other details changed"]: - notes = ["same parameters, body and response"] - detail = "; ".join(notes) if old_key in new_ops: moves.append( - f"- `{method}` **(breaking)** now calls `{new_key}` instead of `{old_key}` " - f"(which is still in the API): {detail}" + f"- `{method}` **(review)** now calls `{new_key}` instead of `{old_key}` " + "(which is still in the API): a different operation under the same name" ) - breaking.append( - f"`{method}` now calls `{new_key}` instead of `{old_key}`, a different operation" - ) - else: - marker = " **(breaking)**" if is_breaking else "" - moves.append(f"- `{method}`{marker} moved from `{old_key}` to `{new_key}`: {detail}") - if is_breaking: - breaking.append( - f"`{method}` moved from `{old_key}` to `{new_key}`: " - + "; ".join(n for n in notes if n.endswith("(breaking)")) - ) + continue + changes = describe_change(old_docs[old_name], old, new_docs[new_name], new) + found = notes(changes) if changes else ["same parameters, body and response"] + marker = " **(breaking)**" if any(c.breaking for c in changes) else "" + moves.append( + f"- `{method}`{marker} moved from `{old_key}` to `{new_key}`: " + "; ".join(found) + ) + breaking.extend( + BreakingChange(new_key, c.where, c.path, c.kind) for c in changes if c.breaking + ) lines.append(f"## Moved or reassigned methods ({len(moves)})") lines.extend(moves) lines.append("") @@ -582,7 +839,8 @@ def keep(naming: Json, key: str, reason: str, replacement: str | None) -> None: "its deprecation entry was dropped." ) - changes: list[str] = [] + changes_lines: list[str] = [] + documentation_only: list[str] = [] for key in common: old_name, old = old_ops[key] new_name, new = new_ops[key] @@ -590,27 +848,45 @@ def keep(naming: Json, key: str, reason: str, replacement: str | None) -> None: "components" ): continue - notes, is_breaking = describe_change(old_docs[old_name], old, new_docs[new_name], new) - if old == new and notes == ["descriptions, examples or other details changed"]: + changes = describe_change(old_docs[old_name], old, new_docs[new_name], new) + if not changes: + if old != new: + documentation_only.append(key) continue - marker = " **(breaking)**" if is_breaking else "" - changes.append(f"- `{key}`{marker}: " + "; ".join(notes)) - if is_breaking: - breaking.append(f"`{key}`: " + "; ".join(n for n in notes if n.endswith("(breaking)"))) - lines.append(f"## Changed operations ({len(changes)})") - lines.extend(changes) + marker = " **(breaking)**" if any(c.breaking for c in changes) else "" + changes_lines.append(f"- `{key}`{marker}: " + "; ".join(notes(changes))) + breaking.extend(BreakingChange(key, c.where, c.path, c.kind) for c in changes if c.breaking) + lines.append(f"## Changed operations ({len(changes_lines) + len(documentation_only)})") + lines.extend(changes_lines) + if documentation_only: + shown = ", ".join(f"`{key}`" for key in documentation_only[:MAX_NOTES]) + more = len(documentation_only) - MAX_NOTES + lines.append(f"- {NO_DIFFERENCE}: {shown}" + (f" and {more} more" if more > 0 else "")) lines.append("") + old_hand = set().union( + *(_hand_written_methods(old_naming(k)) for k in old_ops if k in hand_written) + ) + new_hand = set().union( + *(_hand_written_methods(new_naming(k)) for k in new_ops if k in hand_written) + ) + if old_hand != new_hand: + lines.append("## Authentication module") + lines.append( + "The module is hand-written (DESIGN section 10); " + "`tests/test_authentication_contract.py` fails until it follows the document." + ) + lines.extend(f"- `{m}` is new: add it" for m in sorted(new_hand - old_hand)) + lines.extend(f"- `{m}` was removed: remove it" for m in sorted(old_hand - new_hand)) + lines.append("") + lines.append("## Schemas") for name in downloads: old_schemas = old_docs[name].get("components", {}).get("schemas", {}) new_schemas = new_docs[name].get("components", {}).get("schemas", {}) - gone = sorted(set(old_schemas) - set(new_schemas)) - for schema in gone: - breaking.append(f"{name}: schema `{schema}` removed (its type leaves `didit.types`)") for label, names in ( ("added", sorted(set(new_schemas) - set(old_schemas))), - ("removed", gone), + ("removed", sorted(set(old_schemas) - set(new_schemas))), ( "changed", sorted( @@ -638,9 +914,40 @@ def keep(naming: Json, key: str, reason: str, replacement: str | None) -> None: lines.append(f"Dropped {len(dropped)} overlay entries that upstream now carries.") lines.append("") - lines.insert(2, "## Breaking changes") - lines.insert(3, "\n".join(f"- {b}" for b in breaking) if breaking else "None detected.") - lines.insert(4, "") + # feat when a caller can observe anything: the documents without documentation, or + # the effective naming (upstream adopting an overlay entry unchanged is no change). + breaking = list(dict.fromkeys(breaking)) + observable_change = ( + bool(breaking or added or removed) + or any(old_naming(key) != new_naming(key) for key in common) + or any(observable(old_docs[n]) != observable(new_docs[n]) for n in changed_files) + ) + commit_type = "feat" if observable_change else "chore" + if commit_type == "chore": + release = ( + "`chore(spec)`: only descriptions, examples or other documentation changed, so this " + "sync releases nothing on its own; it ships with the next release." + ) + else: + release = ( + "`feat(spec)`: this sync changes what a caller can observe, so merging it releases " + "a minor version, never a major one (DESIGN.md section 13). " + + ( + 'The release notes repeat the breaking changes above under "API changes that ' + 'can break typed code".' + if breaking + else "Nothing in it is on the list of changes that can break typed code." + ) + ) + head = [ + "## Breaking changes", + "\n".join(f"- {b.describe()}" for b in breaking) if breaking else "None detected.", + "", + "## Release", + release, + "", + ] + lines[0:0] = head for name, data in downloads.items(): (openapi_dir / name).write_bytes(data) @@ -656,7 +963,8 @@ def keep(naming: Json, key: str, reason: str, replacement: str | None) -> None: source_path.write_text(json.dumps(source, indent=1, ensure_ascii=False), "utf-8") overlay_path.write_text(json.dumps(overlay, indent=1, ensure_ascii=False), "utf-8") deprecated_path.write_text(json.dumps(deprecated, indent=1, ensure_ascii=False) + "\n", "utf-8") - return True, "\n".join(lines).rstrip() + "\n" + summary = "\n".join(lines).rstrip() + "\n" + return SyncResult(True, summary, today.isoformat(), commit_type, breaking) def main(argv: list[str] | None = None) -> int: @@ -664,6 +972,11 @@ def main(argv: list[str] | None = None) -> int: description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter ) parser.add_argument("--summary", type=pathlib.Path, help="write the Markdown summary here") + parser.add_argument( + "--commit-message", + type=pathlib.Path, + help="write the commit message release-please reads here", + ) parser.add_argument( "--from-dir", type=pathlib.Path, help="read the upstream files from a directory instead" ) @@ -674,18 +987,21 @@ def main(argv: list[str] | None = None) -> int: downloads = {name: fetch(url) for name, url in UPSTREAM.items()} now = dt.datetime.now(dt.timezone.utc) try: - changed, summary = sync(REPO / "openapi", downloads, today=now.date(), now=now) + result = sync(REPO / "openapi", downloads, today=now.date(), now=now) except SpecSyncError as exc: print(f"spec sync refused the download, nothing was written: {exc}", file=sys.stderr) return 1 if args.summary: - args.summary.write_text(summary, "utf-8") - print(summary) + args.summary.write_text(result.summary, "utf-8") + if args.commit_message: + args.commit_message.write_text(result.commit_message(), "utf-8") + print(result.summary) output = os.environ.get("GITHUB_OUTPUT") if output: with open(output, "a", encoding="utf-8") as fh: - fh.write(f"changed={'true' if changed else 'false'}\n") - fh.write(f"date={now.date().isoformat()}\n") + fh.write(f"changed={'true' if result.changed else 'false'}\n") + fh.write(f"date={result.date}\n") + fh.write(f"type={result.commit_type}\n") return 0 diff --git a/codegen/tests/fixtures/verification-9fe57fc69299601e59cc7a64c1db370fea5c7461.json b/codegen/tests/fixtures/verification-9fe57fc69299601e59cc7a64c1db370fea5c7461.json new file mode 100644 index 0000000..7736883 --- /dev/null +++ b/codegen/tests/fixtures/verification-9fe57fc69299601e59cc7a64c1db370fea5c7461.json @@ -0,0 +1,109606 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "3.0.0", + "title": "Didit Verification API", + "description": "Identity verification API. Authenticate with x-api-key header." + }, + "servers": [ + { + "url": "https://verification.didit.me" + } + ], + "tags": [], + "paths": { + "/system/healthcheck": { + "get": { + "summary": "Service healthcheck (public, unauthenticated)", + "description": "Public unauthenticated liveness probe. Returns `HTTP 200` with `{status: \"ok\", timestamp}` when service, DB, cache, and broker are healthy.", + "operationId": "get_system_healthcheck", + "x-didit-sdk": { + "resource": "system", + "method": "healthcheck", + "auth": "public", + "server": false + }, + "tags": [ + "System" + ], + "security": [ + {} + ], + "parameters": [], + "responses": { + "200": { + "description": "Service is healthy and accepting requests. Body is always `{\"status\": \"ok\", \"timestamp\": \"\"}`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "status", + "timestamp" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "ok" + ], + "description": "Liveness marker. Always the literal string `\"ok\"` on a 200 response.", + "example": "ok" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Server time at which the healthcheck was evaluated, in ISO 8601 with millisecond precision and a `Z` (UTC) suffix.", + "example": "2026-04-21T16:42:50.445Z" + } + } + }, + "examples": { + "Healthy": { + "summary": "Service is up", + "value": { + "status": "ok", + "timestamp": "2026-04-21T16:42:50.445Z" + } + } + } + } + } + }, + "503": { + "description": "Service is unhealthy — one of the configured health-check plugins (database, cache, broker) is failing. The response body is intentionally minimal; the HTTP status is the load-bearing signal. Retry with exponential backoff." + }, + "5XX": { + "description": "Verification service is degraded or unreachable. Treat any non-2xx response as \"unhealthy\" and retry with exponential backoff." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -i https://verification.didit.me/system/healthcheck" + }, + { + "lang": "Python", + "label": "Python (requests)", + "source": "import requests\n\nresp = requests.get(\"https://verification.didit.me/system/healthcheck\", timeout=5)\nresp.raise_for_status()\nprint(resp.json()) # {'status': 'ok', 'timestamp': '2026-04-21T16:42:50.445Z'}" + }, + { + "lang": "JavaScript", + "label": "Node.js (fetch)", + "source": "const res = await fetch(\"https://verification.didit.me/system/healthcheck\");\nif (!res.ok) throw new Error(`Didit unhealthy: ${res.status}`);\nconst body = await res.json();\nconsole.log(body); // { status: 'ok', timestamp: '2026-04-21T16:42:50.445Z' }" + } + ] + } + }, + "/v3/email/risk/": { + "post": { + "summary": "Risk-score an email address", + "description": "Return deliverability, disposable-address, breach, cross-session match, and normalized risk-intelligence signals without sending an OTP. The endpoint creates an API session and returns its id as `request_id`.\n\n**Decision.** The top-level `status` mirrors `email.status`. It is normally `Approved`; the risk evaluation can produce `In Review` or `Declined`.\n\n**Compatibility aliases.** Live responses expose the normalized intelligence object as both `email.email_intelligence` and `email.enrichment`. The values are identical. New integrations can read `email.enrichment`. Both keys are absent when no usable intelligence data was returned.\n\n**Billing.** Live calls use Email Risk API pricing. The request is billable once the lookup starts, even when some best-effort fields are unavailable.\n\n**Sandbox.** Sandbox keys validate the request and return deterministic approved data without sending an email, consuming credits, creating a session, or contributing to Networks.\n\n**Authentication.** Send your application's API key in `x-api-key`. Missing or invalid credentials return `403` - this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests per minute per API key across all POST/PATCH/DELETE endpoints.\n\n**Social footprint.** Set `enable_social` to `true` to add a `email_social` block: the online platforms this address is registered on, with per-category counts and any profile details available. `profiles_registered: 0` means the platforms were checked and the address was found on none of them - a valid, billable answer. The block is absent when the add-on was not requested or the check could not be completed, and it is not billed in that case. Billing adds one `email_social` unit per request on top of this endpoint's own price.", + "operationId": "post_v3email_risk", + "x-didit-sdk": { + "resource": "email", + "method": "assess_risk", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/email/risk/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"email\": \"alice@example.com\",\n \"country_code\": \"US\",\n \"vendor_data\": \"lead-123\",\n \"metadata\": {\"source\": \"signup_prescreen\"}\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os, requests\n\nresp = requests.post(\n \"https://verification.didit.me/v3/email/risk/\",\n headers={\"x-api-key\": os.environ[\"DIDIT_API_KEY\"], \"Content-Type\": \"application/json\"},\n json={\n \"email\": \"alice@example.com\",\n \"country_code\": \"US\",\n \"vendor_data\": \"lead-123\",\n \"metadata\": {\"source\": \"signup_prescreen\"},\n },\n timeout=15,\n)\nresp.raise_for_status()\nresult = resp.json()\nprint(result[\"status\"]) # Approved / In Review / Declined\nprint(result[\"email\"][\"is_breached\"], result[\"email\"][\"is_disposable\"])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const res = await fetch('https://verification.didit.me/v3/email/risk/', {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n email: 'alice@example.com',\n country_code: 'US',\n vendor_data: 'lead-123',\n metadata: { source: 'signup_prescreen' },\n }),\n});\nconst data = await res.json();\nconsole.log(data.status, data.email.is_breached, data.email.is_disposable);" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailRiskRequest" + }, + "examples": { + "Minimal": { + "summary": "Score an address with defaults", + "value": { + "email": "alice@example.com" + } + }, + "Full context": { + "summary": "Score with signals, country hint, and correlation ids", + "value": { + "email": "alice@example.com", + "country_code": "US", + "signals": { + "ip": "192.0.2.1", + "device_id": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2", + "device_platform": "web", + "os_version": "14.4", + "app_version": "1.2.34", + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15" + }, + "vendor_data": "lead-123", + "metadata": { + "source": "signup_prescreen" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The risk evaluation completed. Inspect `status` for the decision; HTTP is always `200` for completed lookups.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailRiskResponse" + }, + "examples": { + "Clean address": { + "summary": "Deliverable address with no breach exposure", + "value": { + "request_id": "82397be9-5921-4970-a979-2d5b43e08b33", + "status": "Approved", + "email": { + "status": "Approved", + "email": "alice@example.com", + "is_breached": false, + "breaches": [], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 0, + "verified_at": null, + "warnings": [], + "lifecycle": [], + "matches": [], + "email_intelligence": { + "score": 4.0, + "deliverable": "high", + "breach_risk_level": "Low", + "breach_risk_index": 4, + "is_recent_breach": false, + "is_frequent_breached": false, + "is_disposable": false, + "is_free_domain": false, + "is_breached": false, + "no_of_breaches": 0, + "reason_codes": [] + }, + "enrichment": { + "score": 4.0, + "deliverable": "high", + "breach_risk_level": "Low", + "breach_risk_index": 4, + "is_recent_breach": false, + "is_frequent_breached": false, + "is_disposable": false, + "is_free_domain": false, + "is_breached": false, + "no_of_breaches": 0, + "reason_codes": [] + } + }, + "vendor_data": "lead-123", + "metadata": { + "source": "signup_prescreen" + }, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + }, + "Breached address": { + "summary": "Address found in known breaches", + "value": { + "request_id": "d1e5c2a8-6f30-4c1a-8b2d-3e4f5a6b7c8d", + "status": "In Review", + "email": { + "status": "In Review", + "email": "bob@example.org", + "is_breached": true, + "breaches": [ + { + "name": "ExampleBreach2023", + "domain": "example.org", + "breach_date": "2023-06-01", + "breach_emails_count": 1200000, + "description": "A data breach affecting example.org accounts.", + "logo_path": "https://logos.example.org/example.png", + "data_classes": [ + "email_addresses", + "passwords" + ], + "is_verified": true + } + ], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 0, + "verified_at": null, + "warnings": [], + "lifecycle": [], + "matches": [], + "email_intelligence": { + "score": 58.0, + "deliverable": "high", + "breach_risk_level": "High", + "breach_risk_index": 72, + "is_recent_breach": true, + "is_frequent_breached": true, + "is_disposable": false, + "is_free_domain": true, + "is_breached": true, + "no_of_breaches": 4, + "reason_codes": [ + "BREACH_EXPOSURE", + "FREQUENT_BREACH" + ] + }, + "enrichment": { + "score": 58.0, + "deliverable": "high", + "breach_risk_level": "High", + "breach_risk_index": 72, + "is_recent_breach": true, + "is_frequent_breached": true, + "is_disposable": false, + "is_free_domain": true, + "is_breached": true, + "no_of_breaches": 4, + "reason_codes": [ + "BREACH_EXPOSURE", + "FREQUENT_BREACH" + ] + } + }, + "vendor_data": "lead-125", + "metadata": null, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/EmailRiskValidationError" + }, + "403": { + "$ref": "#/components/responses/RiskPermissionDenied" + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/ip/risk/": { + "post": { + "summary": "Risk-score an IP address", + "description": "Return geolocation, network ownership, VPN/Tor and data-center flags, threat data, and `country_mismatch` for a supplied IP address. The endpoint creates an API session and returns its id as `request_id`.\n\n**Decision.** `status` is `In Review` when the resolved country conflicts with `claimed_country`, or when the address is associated with VPN, Tor, or data-center infrastructure. Otherwise it is `Approved`.\n\n**Best effort.** IP enrichment is best effort. If no enrichment source returns usable data, the endpoint returns an approved response with `ip.ip_address: null`, `ip.country_mismatch: null`, and an empty `ip.threat` object.\n\n**Billing.** Live calls use IP Risk API pricing. The request is billable once the lookup starts.\n\n**Sandbox.** Sandbox keys validate the request and return deterministic approved data without consuming credits, creating a session, or contributing to Networks.\n\n**Authentication.** Send your application's API key in `x-api-key`. Missing or invalid credentials return `403` - this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests per minute per API key across all POST/PATCH/DELETE endpoints.", + "operationId": "post_v3ip_risk", + "x-didit-sdk": { + "resource": "ip", + "method": "assess_risk", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/ip/risk/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"ip_address\": \"8.8.8.8\",\n \"claimed_country\": \"ESP\",\n \"user_agent\": \"Mozilla/5.0\",\n \"vendor_data\": \"lead-123\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os, requests\n\nresp = requests.post(\n \"https://verification.didit.me/v3/ip/risk/\",\n headers={\"x-api-key\": os.environ[\"DIDIT_API_KEY\"], \"Content-Type\": \"application/json\"},\n json={\n \"ip_address\": \"8.8.8.8\",\n \"claimed_country\": \"ESP\",\n \"user_agent\": \"Mozilla/5.0\",\n \"vendor_data\": \"lead-123\",\n },\n timeout=15,\n)\nresp.raise_for_status()\nresult = resp.json()\nprint(result[\"status\"]) # Approved / In Review\nprint(result[\"ip\"][\"ip_country_code\"], result[\"ip\"][\"country_mismatch\"])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const res = await fetch('https://verification.didit.me/v3/ip/risk/', {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n ip_address: '8.8.8.8',\n claimed_country: 'ESP',\n user_agent: 'Mozilla/5.0',\n vendor_data: 'lead-123',\n }),\n});\nconst data = await res.json();\nconsole.log(data.status, data.ip.country_mismatch);" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IPRiskRequest" + }, + "examples": { + "Minimal": { + "summary": "Score an address with defaults", + "value": { + "ip_address": "8.8.8.8" + } + }, + "With claimed country": { + "summary": "Score with a claimed country for mismatch detection", + "value": { + "ip_address": "8.8.8.8", + "claimed_country": "ESP", + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15", + "vendor_data": "lead-123" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The risk evaluation completed. Inspect `status` for the decision; HTTP is always `200` for completed lookups.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IPRiskResponse" + }, + "examples": { + "Clean residential IP": { + "summary": "Residential address, claimed country matches", + "value": { + "request_id": "9a96ed2a-721e-4b86-8cbf-d61eda529279", + "status": "Approved", + "ip": { + "status": "Approved", + "ip_address": "192.0.2.10", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_state": "Madrid", + "ip_city": "Madrid", + "latitude": 40.4168, + "longitude": -3.7038, + "isp": "Example Telecom", + "organization": "Example Telecom SA", + "is_vpn_or_tor": false, + "is_data_center": false, + "time_zone": "Europe/Madrid", + "time_zone_offset": "+0200", + "asn_number": 3352, + "asn_organization": "EXAMPLE-TELECOM", + "connection_type": "broadband", + "carrier": "Example Telecom", + "proxy_type": null, + "claimed_country": "ESP", + "country_mismatch": false, + "threat": {} + }, + "vendor_data": "lead-123", + "metadata": null, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + }, + "Datacenter mismatch": { + "summary": "Data-center address conflicting with the claimed country", + "value": { + "request_id": "5b1d4e9a-3c7b-4f8e-9a2d-1b0c9d8e7f6a", + "status": "In Review", + "ip": { + "status": "In Review", + "ip_address": "8.8.8.8", + "ip_country": "United States", + "ip_country_code": "US", + "ip_state": "California", + "ip_city": "Mountain View", + "latitude": 37.386, + "longitude": -122.0838, + "isp": "Google", + "organization": "Google LLC", + "is_vpn_or_tor": true, + "is_data_center": true, + "time_zone": "America/Los_Angeles", + "time_zone_offset": "-0700", + "asn_number": 15169, + "asn_organization": "GOOGLE, US", + "connection_type": "hosting", + "carrier": null, + "proxy_type": "VPN", + "claimed_country": "ESP", + "country_mismatch": true, + "threat": { + "is_proxy": true, + "is_data_center": true, + "is_tor": false + } + }, + "vendor_data": "lead-126", + "metadata": null, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/IPRiskValidationError" + }, + "403": { + "$ref": "#/components/responses/RiskPermissionDenied" + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/phone/risk/": { + "post": { + "summary": "Risk-score a phone number", + "description": "Resolve a phone number and return carrier, line-type, porting, disposable-number, cross-session match, and normalized risk-intelligence signals without sending an OTP. The endpoint creates an API session and returns its id as `request_id`.\n\n**Decision.** The top-level `status` mirrors `phone.status`. It is normally `Approved`; the risk evaluation can produce `In Review` or `Declined`.\n\n**Compatibility aliases.** Live responses expose the normalized intelligence object as both `phone.phone_intelligence` and `phone.enrichment`. The values are identical. New integrations can read `phone.enrichment`. Both keys are absent when no usable intelligence data was returned.\n\n**Billing.** Live calls use Phone Risk API pricing. The request is billable once the lookup starts, even when some best-effort fields are unavailable.\n\n**Sandbox.** Sandbox keys validate the request and return deterministic approved data without sending an OTP, consuming credits, creating a session, or contributing to Networks.\n\n**Authentication.** Send your application's API key in `x-api-key`. Missing or invalid credentials return `403` - this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests per minute per API key across all POST/PATCH/DELETE endpoints.\n\n**Social footprint.** Set `enable_social` to `true` to add a `phone_social` block: the online platforms this number is registered on, with per-category counts and any profile details available. `profiles_registered: 0` means the platforms were checked and the number was found on none of them - a valid, billable answer. The block is absent when the add-on was not requested or the check could not be completed, and it is not billed in that case. Billing adds one `phone_social` unit per request on top of this endpoint's own price.", + "operationId": "post_v3phone_risk", + "x-didit-sdk": { + "resource": "phone", + "method": "assess_risk", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/phone/risk/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"phone_number\": \"+14155552671\",\n \"country_code\": \"US\",\n \"vendor_data\": \"lead-123\",\n \"metadata\": {\"source\": \"signup_prescreen\"}\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os, requests\n\nresp = requests.post(\n \"https://verification.didit.me/v3/phone/risk/\",\n headers={\"x-api-key\": os.environ[\"DIDIT_API_KEY\"], \"Content-Type\": \"application/json\"},\n json={\n \"phone_number\": \"+14155552671\",\n \"country_code\": \"US\",\n \"vendor_data\": \"lead-123\",\n \"metadata\": {\"source\": \"signup_prescreen\"},\n },\n timeout=15,\n)\nresp.raise_for_status()\nresult = resp.json()\nprint(result[\"status\"]) # Approved / In Review / Declined\nprint(result[\"phone\"][\"phone_intelligence\"][\"score\"])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const res = await fetch('https://verification.didit.me/v3/phone/risk/', {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n phone_number: '+14155552671',\n country_code: 'US',\n vendor_data: 'lead-123',\n metadata: { source: 'signup_prescreen' },\n }),\n});\nconst data = await res.json();\nconsole.log(data.status, data.phone.phone_intelligence?.score);" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PhoneRiskRequest" + }, + "examples": { + "Minimal": { + "summary": "Score a number with defaults", + "value": { + "phone_number": "+14155552671" + } + }, + "Full context": { + "summary": "Score with signals, country hint, and correlation ids", + "value": { + "phone_number": "+14155552671", + "country_code": "US", + "signals": { + "ip": "192.0.2.1", + "device_id": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2", + "device_platform": "ios", + "device_model": "iPhone17,2", + "os_version": "18.0.1", + "app_version": "1.2.34", + "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1" + }, + "vendor_data": "lead-123", + "metadata": { + "source": "signup_prescreen" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The risk evaluation completed. Inspect `status` for the decision; HTTP is always `200` for completed lookups.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PhoneRiskResponse" + }, + "examples": { + "Clean mobile number": { + "summary": "Clean mobile number with intelligence coverage", + "value": { + "request_id": "4fd09d2e-9bc9-4c4e-9c46-7f625ac0ab62", + "status": "Approved", + "phone": { + "status": "Approved", + "phone_number_prefix": "+1", + "phone_number": "4155552671", + "full_number": "+14155552671", + "country_code": "US", + "country_name": "United States", + "carrier": { + "name": "Example Mobile", + "type": "mobile" + }, + "is_disposable": false, + "is_virtual": false, + "verification_method": null, + "verification_attempts": 0, + "verified_at": null, + "warnings": [], + "lifecycle": [], + "matches": [], + "phone_intelligence": { + "score": 3.0, + "phone_type_risk": "Low", + "phone_trust_index": 97, + "network_stability_index": 95, + "is_disposable": false, + "is_ported": false, + "is_recently_ported": false, + "carrier_type": "mobile", + "reason_codes": [] + }, + "enrichment": { + "score": 3.0, + "phone_type_risk": "Low", + "phone_trust_index": 97, + "network_stability_index": 95, + "is_disposable": false, + "is_ported": false, + "is_recently_ported": false, + "carrier_type": "mobile", + "reason_codes": [] + } + }, + "vendor_data": "lead-123", + "metadata": { + "source": "signup_prescreen" + }, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + }, + "Number in review": { + "summary": "Disposable or virtual number flagged for review", + "value": { + "request_id": "8c22e1f0-4b3d-4a3a-9d1e-2f6b7c8d9e0f", + "status": "In Review", + "phone": { + "status": "In Review", + "phone_number_prefix": "+44", + "phone_number": "7700900123", + "full_number": "+447700900123", + "country_code": "GB", + "country_name": "United Kingdom", + "carrier": { + "name": "Example VoIP", + "type": "voip" + }, + "is_disposable": true, + "is_virtual": true, + "verification_method": null, + "verification_attempts": 0, + "verified_at": null, + "warnings": [], + "lifecycle": [], + "matches": [], + "phone_intelligence": { + "score": 72.0, + "phone_type_risk": "High", + "phone_trust_index": 28, + "network_stability_index": 40, + "is_disposable": true, + "is_ported": true, + "is_recently_ported": true, + "carrier_type": "voip", + "reason_codes": [ + "DISPOSABLE_NUMBER", + "RECENT_PORT" + ] + }, + "enrichment": { + "score": 72.0, + "phone_type_risk": "High", + "phone_trust_index": 28, + "network_stability_index": 40, + "is_disposable": true, + "is_ported": true, + "is_recently_ported": true, + "carrier_type": "voip", + "reason_codes": [ + "DISPOSABLE_NUMBER", + "RECENT_PORT" + ] + } + }, + "vendor_data": "lead-124", + "metadata": null, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/PhoneRiskValidationError" + }, + "403": { + "$ref": "#/components/responses/RiskPermissionDenied" + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/": { + "post": { + "summary": "Create a verification session", + "description": "Create a User Verification (KYC) or Business Verification (KYB) session and receive a hosted verification `url` plus a `session_token` to redirect or embed for your end user. The `workflow_id` selects which verification steps run and whether the session is KYC or KYB.\n\nCall this from your backend (never the browser — the API key is a secret) whenever a user or business needs to be verified: at signup, before a sensitive action, or when re-verification is required. Prerequisites: a published workflow (created in the Console [Workflows](https://docs.didit.me/console/workflows) page) and an application API key with available credits. Sandbox applications bypass the credit check.\n\n**Idempotency:** when `vendor_data` is provided and an unfinished session (`Not Started`, `In Progress`, `Resubmitted`, or `Awaiting User`) with the same `vendor_data` already exists on the workflow's **current latest published version** (and that session already has a hosted verification URL), the existing session is returned (still `201`) instead of creating a duplicate — with its `callback` and `metadata` updated to the newly provided values. An unfinished session created on an older version of the workflow (one that has since been republished) is **not** reused; a new session pinned to the latest published version is created instead. Sessions in `Approved`, `Declined`, `In Review`, `Expired`, `Abandoned`, or `Kyc Expired` are never reused.\n\n**Side effects:** the hosted verification URL is generated and stored; if `contact_details.send_notification_emails` is `true` and an email is provided, a verification invite email is sent. The session expires after the workflow's configured session expiration time. For KYB workflows, `contact_details.phone` and `expected_details` are ignored.", + "operationId": "post_v3_session_create", + "x-didit-sdk": { + "resource": "sessions", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "201": { + "description": "Session created (or returned, if an unfinished session with the same `vendor_data` already exists on the workflow's latest published version). The response is the serialized session, including the hosted verification `url` to redirect the user to.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "session_id", + "session_number", + "session_token", + "url", + "vendor_data", + "metadata", + "status", + "workflow_id", + "workflow_version", + "callback" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of the verification session. Use this id when calling `GET /v3/session/{sessionId}/decision/`.", + "example": "11111111-2222-3333-4444-555555555555" + }, + "session_number": { + "type": "integer", + "description": "Sequential, human-friendly number assigned to the session inside your application. Useful for support and dashboards.", + "example": 43762 + }, + "session_token": { + "type": "string", + "description": "12-character URL-safe token that authorizes the end user to access the hosted verification flow at `url`. Valid until the session expires. Treat it as a secret — anyone holding it can open the verification flow for this session.", + "example": "3FaJ9wLqX2Mz" + }, + "url": { + "type": "string", + "format": "uri", + "description": "Hosted verification URL to redirect the end user to. The URL embeds the `session_token` and, if configured, uses your white-label domain. When the request includes `language`, the URL contains a language path segment — `https://verify.didit.me/{language}/session/{session_token}` (e.g. `/es/session/...`); without `language`, the segment is omitted.", + "example": "https://verify.didit.me/session/3FaJ9wLqX2Mz" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Identifier you passed in the request to link the session to a user or business in your own system. Echoed back verbatim. Null when not provided.", + "example": "user-123" + }, + "metadata": { + "description": "Arbitrary JSON payload you stored with the session at creation time. Echoed back verbatim — whatever JSON value you sent (object, string, number, array) is returned as-is, though a JSON object is recommended. Not shown to the end user. Always present in responses; `null` when not provided at creation time.", + "example": { + "user_type": "premium", + "account_id": "ABC123" + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The tags on the session, by name.", + "example": [ + "VIP", + "ONBOARDING Q4" + ] + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Current status of the session. Newly created sessions return `Not Started`. If a non-finished session already existed for the same `vendor_data` on the workflow's latest published version, the returned status reflects that existing session.", + "example": "Not Started" + }, + "callback": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Final redirect URL the user is sent to after completing the flow. Didit appends `?verificationSessionId={session_id}&status={status}` to this URL. Falls back to the workflow's configured callback URL when not provided in the request; `null` when neither is set. When the application has allowed domains and this URL is a web URL outside them (for example a workflow default set before the list changed), the hosted flow does not redirect the user to it.", + "example": "https://example.com/verification/callback" + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier of the workflow this session runs on. Always the workflow's stable group identifier — even if you referenced a specific workflow version UUID in the request (backward-compatible lookup), the response carries the stable `workflow_id`. Use it to correlate the session with the workflow you configured in the Console.", + "example": "11111111-2222-3333-4444-555555555555" + }, + "workflow_version": { + "type": "integer", + "description": "Version number of the published workflow version the session was pinned to at creation. Didit resolves `workflow_id` to the workflow's latest published version when the session is created, and the session keeps running that version even if the workflow is republished later.", + "example": 3 + } + } + }, + "examples": { + "KYC session": { + "summary": "KYC session created", + "value": { + "session_id": "11111111-2222-3333-4444-555555555555", + "session_number": 43762, + "session_token": "3FaJ9wLqX2Mz", + "url": "https://verify.didit.me/en/session/3FaJ9wLqX2Mz", + "vendor_data": "user-123", + "metadata": { + "user_type": "premium", + "account_id": "ABC123" + }, + "status": "Not Started", + "workflow_id": "11111111-2222-3333-4444-555555555555", + "workflow_version": 3, + "callback": "https://example.com/verification/callback" + } + }, + "KYB session": { + "summary": "KYB session created", + "value": { + "session_id": "22222222-3333-4444-5555-666666666666", + "session_number": 43763, + "session_token": "Yk7pQ2vN8aBc", + "url": "https://verify.didit.me/en/session/Yk7pQ2vN8aBc", + "vendor_data": "company-acme-001", + "metadata": { + "tier": "enterprise" + }, + "status": "Not Started", + "workflow_id": "33333333-4444-5555-6666-777777777777", + "workflow_version": 1, + "callback": "https://example.com/kyb/callback" + } + } + } + } + } + }, + "400": { + "description": "Bad request — the payload failed validation (missing or unknown `workflow_id`, invalid `language`, blocklisted `vendor_data`, malformed `portrait_image`, `address` without `country`/`poa_country`, invalid `sandbox_scenario`, `sign` documents, fields or values the workflow's Electronic Signature step refuses, such as `signature_document_not_supported`, `signature_document_conversion_failed`, `field_anchor_not_found` or `field_value_missing`, etc.) or your organization does not have enough credits to start the session.\n\nThe body is an object keyed by the offending field name (or `detail` for non-field errors). Values are an **array of strings** for input-validation failures (e.g. missing required field, invalid language, oversized portrait image) and a **plain string** for failures detected while creating the session (unknown `workflow_id`, blocklisted `vendor_data`, no reference face available for a Face Match workflow). Handle both shapes.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message for non-field errors (e.g. insufficient credits). Field-level failures use the field name as the key instead, with a string or array-of-strings value.", + "example": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + }, + "examples": { + "Missing workflow_id": { + "summary": "Required field omitted (array-of-strings shape)", + "value": { + "workflow_id": [ + "This field is required." + ] + } + }, + "Invalid workflow_id": { + "summary": "Unknown workflow_id (plain-string shape)", + "value": { + "workflow_id": "Invalid workflow_id." + } + }, + "Insufficient credits": { + "summary": "Organization is out of credits", + "value": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + }, + "Blocklisted vendor_data": { + "summary": "vendor_data is blocklisted", + "value": { + "vendor_data": "This user or business has been blocked and cannot create new verification sessions." + } + }, + "Portrait image too large": { + "summary": "portrait_image fails size validation", + "value": { + "portrait_image": [ + "Image size exceeds 2MB." + ] + } + }, + "No stored face for vendor_data": { + "summary": "Face Match workflow without portrait_image and no stored face for the vendor_data user", + "value": { + "portrait_image": "No stored face image was found for this user. Send a portrait_image, or complete an approved verification with face liveness or an ID document for this vendor_data first." + } + }, + "Portrait image required without vendor_data": { + "summary": "Face Match workflow without portrait_image and without vendor_data", + "value": { + "portrait_image": "A portrait image is required to perform face match for this workflow. Send a portrait_image, or provide the vendor_data of a user with a previously stored face image to reuse it." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failed. The endpoint returns `403` whenever the `x-api-key` header is missing, malformed, the API key has expired, or the API key is valid but the client does not have permission to create sessions in this application (for example the API key belongs to a different organization). These failures share the body `{\"detail\": \"You do not have permission to perform this action.\"}`. Check that the `x-api-key` header is present and belongs to the intended application, re-fetch the application API key via the [Auth API](/auth-api/get-credentials) if needed, and retry.\n\n**Deleted application:** for a few minutes after an application is deleted, a request made with one of its API keys can answer `{\"detail\": \"This application was deleted.\", \"code\": \"application_deleted\"}`. Do not retry: the application and its API keys no longer exist. After those few minutes, the key is refused like any other invalid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You do not have permission to perform this action." + }, + "code": { + "type": "string", + "description": "Machine-readable reason, present only on some failures. `application_deleted`: the API key belongs to an application that was deleted.", + "example": "application_deleted" + } + }, + "required": [ + "detail" + ] + }, + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header or invalid API key", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "No permission": { + "summary": "Client cannot create sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Application deleted": { + "summary": "API key of a deleted application", + "value": { + "detail": "This application was deleted.", + "code": "application_deleted" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded — more than 600 session-create requests in a 60-second window from the same API key / IP. Inspect `Retry-After` and back off before retrying.\n\n**Note:** The source code also defines a `FREE_SESSION_RATE_LIMIT` of 10 calls/minute alongside `PAID_SESSION_RATE_LIMIT=600` (`sessions/serializers/session.py`), but that free-tier ceiling is **not applied to this V3 endpoint** — the rate-limit middleware enforces the 600/min `SESSION_CREATE_RATE_LIMIT` regardless of plan. 600/min is the only limit you can hit here.", + "headers": { + "Retry-After": { + "description": "Seconds the caller must wait before retrying the request.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 42 + } + }, + "X-RateLimit-Limit": { + "description": "Maximum number of requests allowed in the current 60-second window for the limit that was breached (600 for session-create). Absent on the sandbox-quota variant.", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Number of requests still allowed in the current window for the breached limit. Absent on the sandbox-quota variant.", + "schema": { + "type": "integer", + "minimum": 0, + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "UTC epoch seconds when the current rate-limit window resets. Absent on the sandbox-quota variant.", + "schema": { + "type": "integer", + "minimum": 0, + "example": 1747497600 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable explanation of the rate-limit breach. Use this message to distinguish which of the three limits was hit.", + "example": "Session creation rate limit exceeded. You can make up to 600 requests per minute." + } + } + }, + "examples": { + "Session-create limit": { + "summary": "More than 600 session-create requests per minute", + "value": { + "detail": "Session creation rate limit exceeded. You can make up to 600 requests per minute." + } + }, + "Sandbox session quota": { + "summary": "Sandbox application exceeded 500 sessions in 24 hours", + "value": { + "detail": "Sandbox session quota exceeded (500 sessions per 24h). Wait for the window to reset. Expected available in 3600 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/session/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"workflow_id\": \"11111111-2222-3333-4444-555555555555\",\n \"vendor_data\": \"user-123\",\n \"callback\": \"https://example.com/verification/callback\",\n \"callback_method\": \"both\",\n \"metadata\": {\"user_type\": \"premium\", \"account_id\": \"ABC123\"},\n \"language\": \"en\",\n \"contact_details\": {\n \"email\": \"john.doe@example.com\",\n \"send_notification_emails\": true,\n \"email_lang\": \"en\",\n \"phone\": \"+14155552671\"\n },\n \"expected_details\": {\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"date_of_birth\": \"1990-05-15\",\n \"id_country\": \"USA\",\n \"expected_document_types\": [\"P\", \"ID\"]\n }\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nurl = \"https://verification.didit.me/v3/session/\"\nheaders = {\n 'x-api-key': 'YOUR_API_KEY',\n \"Content-Type\": \"application/json\",\n}\npayload = {\n \"workflow_id\": \"11111111-2222-3333-4444-555555555555\",\n \"vendor_data\": \"user-123\",\n \"callback\": \"https://example.com/verification/callback\",\n \"callback_method\": \"both\",\n \"metadata\": {\"user_type\": \"premium\", \"account_id\": \"ABC123\"},\n \"language\": \"en\",\n \"contact_details\": {\n \"email\": \"john.doe@example.com\",\n \"send_notification_emails\": True,\n \"email_lang\": \"en\",\n \"phone\": \"+14155552671\",\n },\n \"expected_details\": {\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"date_of_birth\": \"1990-05-15\",\n \"id_country\": \"USA\",\n \"expected_document_types\": [\"P\", \"ID\"],\n },\n}\n\nresponse = requests.post(url, json=payload, headers=headers, timeout=15)\nresponse.raise_for_status()\nsession = response.json()\nprint(session[\"session_id\"], session[\"url\"])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const response = await fetch('https://verification.didit.me/v3/session/', {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n workflow_id: '11111111-2222-3333-4444-555555555555',\n vendor_data: 'user-123',\n callback: 'https://example.com/verification/callback',\n callback_method: 'both',\n metadata: { user_type: 'premium', account_id: 'ABC123' },\n language: 'en',\n contact_details: {\n email: 'john.doe@example.com',\n send_notification_emails: true,\n email_lang: 'en',\n phone: '+14155552671',\n },\n expected_details: {\n first_name: 'John',\n last_name: 'Doe',\n date_of_birth: '1990-05-15',\n id_country: 'USA',\n expected_document_types: ['P', 'ID'],\n },\n }),\n});\n\nif (!response.ok) {\n throw new Error(`Session create failed: ${response.status}`);\n}\nconst session = await response.json();\nconsole.log(session.session_id, session.url);" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "workflow_id" + ], + "properties": { + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier of the workflow that defines which verification steps the session will run. Workflows are created and managed in the [Workflows](/console/workflows) page of the Console. The `workflow_id` also implicitly selects whether the session is KYC or KYB.", + "example": "11111111-2222-3333-4444-555555555555", + "x-readme-id": "0.0" + }, + "vendor_data": { + "type": "string", + "description": "A unique identifier for the user being verified, such as a UUID, email, or internal user ID. This field is used for: (1) **User grouping** — sessions with the same vendor_data are linked to the same user profile in the Users tab. (2) **Cross-session duplicate detection** — when checking for duplicated faces, documents, phone numbers, emails, IP addresses, or device fingerprints, sessions with the same vendor_data are treated as the same user and excluded from matches. Without vendor_data, every session is treated as a unique user and all potential duplicates are surfaced. We strongly recommend always providing a vendor_data to reduce noise in duplicate detection.", + "example": "user-123", + "x-readme-id": "0.1" + }, + "callback": { + "type": "string", + "description": "URL to redirect the user to after verification completes. Didit automatically appends `verificationSessionId` and `status` (Approved, Declined, In Review) as query parameters. Custom URL schemes (e.g. `myapp://`) are supported for mobile callbacks. If omitted, the workflow's default `callback_url` is used. When the application has allowed domains (`allowed_domains` on `PATCH /v3/webhook/`), an `http` or `https` callback must be on one of those origins (same scheme, host and port), otherwise the request fails with 400 on `callback`: `callback must be on one of the application's allowed domains: https://app.example.com`. Custom URL schemes are always accepted.", + "example": "https://example.com/verification/callback", + "x-readme-id": "0.2" + }, + "callback_method": { + "type": "string", + "description": "Determines which device should handle the redirect to the provided callback URL. Use `initiator` to redirect only the device that started the flow, `completer` for the device that finishes it, or `both` to allow either device to trigger the callback. If you ever notice the callback not triggering reliably, we recommend setting this value to `both`.", + "enum": [ + "initiator", + "completer", + "both" + ], + "default": "initiator", + "example": "both", + "x-readme-id": "0.3" + }, + "metadata": { + "description": "Arbitrary JSON stored with the session and echoed back in the response and webhooks. Any JSON value is accepted (object, string, number, array), but a JSON object of key/value pairs is recommended. Not shown to the end user. Use it to pass your own correlation ids, A/B variants, or business context.", + "example": { + "user_type": "premium", + "account_id": "ABC123" + }, + "x-readme-id": "0.4" + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "maxLength": 50 + }, + "maxItems": 10, + "description": "Tags added to the session when it is created, by name. Up to 10, each up to 50 characters. Names are case-insensitive and stored in upper case. A name that does not exist yet in the application is created as a new tag. The tags show on the session in the console, where you can filter by them.", + "example": [ + "VIP", + "ONBOARDING Q4" + ] + }, + "language": { + "type": "string", + "description": "Language code (ISO 639-1) for the verification process interface. Controls the language displayed to the end user during verification. If not provided, the browser's language will be automatically detected and used. Check all the supported languages [here](/integration/supported-languages).", + "enum": [ + "en", + "ar", + "bg", + "bn", + "bs", + "ca", + "cnr", + "cs", + "da", + "de", + "el", + "es", + "et", + "fa", + "fi", + "fr", + "he", + "hi", + "hr", + "hu", + "hy", + "id", + "it", + "ja", + "ka", + "kk", + "ko", + "ky", + "lt", + "lv", + "mk", + "mn", + "ms", + "nl", + "no", + "pl", + "pt-BR", + "pt", + "ro", + "ru", + "sk", + "sl", + "so", + "sq", + "sr", + "sv", + "th", + "tr", + "uk", + "uz", + "vi", + "zh-CN", + "zh-TW", + "zh" + ], + "example": "en", + "x-readme-id": "0.5" + }, + "contact_details": { + "type": "object", + "description": "User contact information that can be used for notifications, prefilling verification forms, and phone verification. This includes email address, preferred language for communications, and phone number.", + "example": { + "email": "john.doe@example.com", + "send_notification_emails": true, + "email_lang": "en", + "phone": "+14155552671" + }, + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "Email address of the user (e.g., \"john.doe@example.com\") that will be used during the [Email Verification](/core-technology/email-verification/overview) step. If not provided, the user must provide it during the verification flow.", + "example": "john.doe@example.com", + "x-readme-id": "1.0" + }, + "send_notification_emails": { + "type": "boolean", + "default": false, + "description": "If true and an email is provided, Didit sends the initial \"Verify your identity\" email asynchronously when the User Verification (KYC) or Business Verification (KYB) session is created. Didit also sends verification status notifications for sessions requiring manual review to the provided email address (e.g., from 'In Review' to 'Approved' or 'Declined'). This helps users return to your application once their verification is complete. If you have white-label activated for the session, the email sent will be white-labeled.", + "example": true, + "x-readme-id": "1.1" + }, + "email_lang": { + "type": "string", + "description": "Language code (ISO 639-1) for email notifications. Controls the language of all email communications (e.g., \"en\", \"es\", \"fr\"). There is no stored default — when omitted, the verification invite email simply falls back to English (`en`) at send time. The enum below is a snapshot; the live source of truth for accepted values is the [supported languages](/integration/supported-languages) doc — always consult it before relying on a specific code.", + "enum": [ + "en", + "ar", + "bg", + "bn", + "bs", + "ca", + "cnr", + "cs", + "da", + "de", + "el", + "es", + "et", + "fa", + "fi", + "fr", + "he", + "hi", + "hr", + "hu", + "hy", + "id", + "it", + "ja", + "ka", + "kk", + "ko", + "ky", + "lt", + "lv", + "mk", + "mn", + "ms", + "nl", + "no", + "pl", + "pt-BR", + "pt", + "ro", + "ru", + "sk", + "sl", + "so", + "sq", + "sr", + "sv", + "th", + "tr", + "uk", + "uz", + "vi", + "zh-CN", + "zh-TW", + "zh" + ], + "example": "en", + "x-readme-id": "1.2" + }, + "phone": { + "type": "string", + "description": "Phone number in E.164 format (e.g., \"+14155552671\") that will be used during the [Phone Verification](/core-technology/phone-verification/overview) step. If not provided, the user must provide it during the verification flow. Ignored for Business Verification (KYB) workflows. **Important:** This phone number is only enforced if it is a valid E.164 phone number. If the provided number is invalid or cannot be parsed, it will be ignored and the user will be able to input any valid phone number during the Phone Verification step.", + "example": "+14155552671", + "x-readme-id": "1.3" + } + }, + "x-readme-id": "0.4" + }, + "expected_details": { + "type": "object", + "description": "Expected user details used to cross-validate against the data extracted from the user's ID document, Proof of Address, and other verification steps. Mismatches are surfaced as warnings on the decision; some fields (e.g. `id_country`, `expected_document_types`) also alter the user-facing flow. For Business Verification (KYB) workflows only the business fields (`company_name`, `registry_country`, `registration_number`) are used — they pre-fill the company registry search for the user — and all person-level fields are ignored.", + "example": { + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1990-05-15", + "nationality": "USA", + "id_country": "USA", + "expected_document_types": [ + "P", + "ID" + ] + }, + "properties": { + "first_name": { + "type": "string", + "description": "User's first name. For example, `John`. The matching uses fuzzy comparison, and you can tune the strictness by configuring the name match score threshold in the Console for both ID Document and Proof of Address workflows.", + "example": "John", + "x-readme-id": "1.0" + }, + "last_name": { + "type": "string", + "description": "User's last name. For example, `Doe`. The matching uses fuzzy comparison, and you can tune the strictness by configuring the name match score threshold in the Console for both ID Document and Proof of Address workflows.", + "example": "Doe", + "x-readme-id": "1.1" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "User's date of birth with format: YYYY-MM-DD. For example, `1990-05-15`.", + "example": "1990-05-15", + "x-readme-id": "1.2" + }, + "gender": { + "type": "string", + "nullable": true, + "enum": [ + "M", + "F" + ], + "default": null, + "description": "User's gender. Must be either 'M', 'F', or null.", + "example": "M", + "x-readme-id": "1.3" + }, + "nationality": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the applicant's country of origin. For example, `USA`. See the [full list of supported country codes](https://docs.didit.me/core-technology/id-verification/supported-documents-id-verification#supported-documents-by-country).", + "example": "USA", + "x-readme-id": "1.4" + }, + "country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code used as a fallback for both ID Verification and Proof of Address country-mismatch checks. Required when `address` is provided and neither `id_country` nor `poa_country` is set.", + "example": "USA", + "x-readme-id": "1.4.1" + }, + "id_country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the expected country of the applicant's ID document, which may differ from nationality. For example, `GBR`. Takes priority over `country` for ID verification country mismatch checks. See the [full list of supported country codes](https://docs.didit.me/core-technology/id-verification/supported-documents-id-verification#supported-documents-by-country).", + "example": "GBR", + "x-readme-id": "1.5" + }, + "poa_country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the expected country of the Proof of Address document. For example, `USA`. Takes priority over `country` for POA country mismatch checks. See the [full list of supported country codes](https://docs.didit.me/core-technology/id-verification/supported-documents-id-verification#supported-documents-by-country).", + "example": "USA", + "x-readme-id": "1.5.1" + }, + "address": { + "type": "string", + "description": "The address in a human readable format, including as much information as possible. For example, `123 Main St, San Francisco, CA 94105, USA`. When provided, you must also pass `country` or `poa_country`, otherwise the request fails with `400`.", + "example": "123 Main St, San Francisco, CA 94105, USA", + "x-readme-id": "1.6" + }, + "identification_number": { + "type": "string", + "description": "The user's document number, personal number, or tax number. For example, `123456789`.", + "example": "123456789", + "x-readme-id": "1.7" + }, + "ip_address": { + "type": "string", + "description": "Expected IP address for the session, in IPv4 or IPv6 format. If the actual IP address differs from this value, a warning will be logged. For example, `192.168.1.100` or `2001:db8::1`.", + "example": "192.168.1.100", + "x-readme-id": "1.8" + }, + "expected_document_types": { + "type": "array", + "description": "Restricts the document types the user can present during the ID verification (OCR) step. When set, the document selection screen only shows the requested types and the returned `documents_allowed` is filtered accordingly. Values are case-insensitive and deduplicated; unknown values are rejected with `400`. Allowed codes: `P` (passport), `ID` (national ID), `DL` (driver's license), `RP` (residence permit), `HIC` (health insurance card), `TC` (tax card), `SSC` (social security card).", + "items": { + "type": "string", + "enum": [ + "P", + "ID", + "DL", + "RP", + "HIC", + "TC", + "SSC" + ] + }, + "example": [ + "P", + "ID" + ], + "x-readme-id": "1.9" + }, + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Expected legal name of the business being verified. For example, `Tesco PLC`. Pre-fills the company-name field of the registry search in Business Verification (KYB) workflows; when `registration_number` is also provided, the registry lookup combines both identifiers. Ignored for KYC workflows.", + "example": "Tesco PLC", + "x-readme-id": "1.10" + }, + "registry_country": { + "type": "string", + "maxLength": 10, + "description": "ISO 3166-1 alpha-2 code of the country whose company registry the business is registered in. For example, `GB`. Where company registries operate at state or province level, append the ISO 3166-2 subdivision code as `XX-YY`: `US-CA` targets the California registry. Same format as `country_code` on the [KYB Registry Search API](https://docs.didit.me/standalone-apis/kyb-registry-search). Pre-selects the registry country in Business Verification (KYB) workflows; ignored for KYC workflows.", + "example": "US-CA", + "x-readme-id": "1.11" + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Expected registration number of the business in the company registry. For example, `00445790`. Pre-fills the registration-number field of the registry search in Business Verification (KYB) workflows; when `company_name` is also provided, the registry lookup combines both identifiers. Ignored for KYC workflows.", + "example": "00445790", + "x-readme-id": "1.12" + } + }, + "x-readme-id": "0.5" + }, + "portrait_image": { + "type": "string", + "format": "byte", + "description": "Base64-encoded portrait image of the end user's face (max 2MB; JPEG, PNG, WebP, or TIFF). Used as the reference image to match against the liveness capture when the workflow is a `Biometric Authentication` workflow with Face Match enabled, or any graph workflow where Face Match runs before ID Verification (OCR). Optional when the `vendor_data` user already has a stored face image: Didit reuses it automatically, resolved in priority order: approved liveness face, ePassport chip photo, ID document portrait, manually enrolled profile face (only faces from approved sessions or faces you enrolled yourself are reused). When provided, `portrait_image` always takes precedence over the stored face. If omitted and no stored face exists (or `vendor_data` is not sent), session creation fails with `400`. Ignored for other workflow types.", + "example": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", + "x-readme-id": "0.6" + }, + "sandbox_scenario": { + "type": "string", + "description": "Sandbox-only scenario slug. Only accepted when the API key belongs to a sandbox-mode application; rejected on live applications. When set, the create flow expands the scenario's bundled magic values onto the session's input fields (contact_details, expected_details) so the mocked providers reproduce the scenario's outcome through the real workflow pipeline. See the scenario catalog at GET /v1/sandbox/scenarios/ or [Sandbox & Test Data](/integration/sandbox-testing). You can also arm (or re-arm) a scenario after create via POST /v3/session/{sessionId}/sandbox/arm/, as long as the session is still `Not Started`. For non-doc lookup, use `lookup_partial_match`, `lookup_no_match`, or `lookup_provider_error` to test the configured attempt budget and fallback. For identity wallets, use `wallet_cancelled`, `wallet_timeout`, or `wallet_provider_error`; `approve` simulates a verified wallet. All catalog wallets can be tested in sandbox, including schemes unavailable on live applications. Simulated attempts are not charged.", + "example": "decline_document_expired" + }, + "sign": { + "type": "object", + "description": "Electronic Signature input for this session. `documents` are the documents to sign, for a workflow whose Electronic Signature step takes documents from the session (`signature_documents_source` `session` or `both`): up to 20 documents and 50 MB in total. `field_values` fills the fields whose `prefill` is `api.`, on workflow and session documents alike. Creating a session again for the same `workflow_id` and `vendor_data` returns the pending session; if that call sends `sign`, its documents, fields and values must be identical to the first ones, and anything different returns `400` with `{\"sign\": \"signature_document_changed\"}`. See [Electronic signature documents](/sessions-api/create-session#electronic-signature-documents).", + "properties": { + "documents": { + "type": "array", + "minItems": 1, + "maxItems": 20, + "items": { + "$ref": "#/components/schemas/SessionSignDocument" + } + }, + "field_values": { + "type": "object", + "description": "Values keyed by the `` of each `api.` pre-fill source: strings of 1 to 500 characters for `text` and `data` fields, booleans for a `checkbox`. `400` with `field_value_unknown` for a key no field uses, `field_value_missing` for a required field without its value, and `field_value_invalid` for a value that fails its field's validation.", + "additionalProperties": { + "oneOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 500 + }, + { + "type": "boolean" + } + ] + }, + "example": { + "credit_limit": "3,000 EUR" + } + } + } + }, + "user_state": { + "type": "string", + "maxLength": 16, + "description": "Where the person lives, when you know it: an ISO 3166-2 subdivision code (`US-IL`, `ES-M`) or a US state code or name (`IL`, `Illinois`). A state whose law requires explicit biometric consent (Illinois, Texas, Colorado) makes the verification flow ask for it before capturing a face, with trigger `declared_state`. An unrecognized value returns `400`. See [Biometric consent](/getting-started/biometric-consent).", + "example": "US-IL" + }, + "biometric_consent": { + "type": "object", + "description": "Explicit biometric consent you already collected from the person. It satisfies the requirement, so the verification flow does not ask again, and the decision records it as `status: \"given\"` with trigger `customer_collected`. An answer the person already gave in the flow is never replaced. See [Biometric consent](/getting-started/biometric-consent).", + "required": [ + "given_at", + "text_version" + ], + "properties": { + "given_at": { + "type": "string", + "format": "date-time", + "description": "When the person gave explicit biometric consent (ISO 8601). Cannot be in the future." + }, + "text_version": { + "type": "string", + "maxLength": 64, + "description": "Version id of the consent text the person accepted, in your own records." + } + } + } + }, + "example": { + "workflow_id": "11111111-2222-3333-4444-555555555555", + "vendor_data": "user-123", + "callback": "https://example.com/verification/callback", + "callback_method": "both", + "metadata": { + "user_type": "premium", + "account_id": "ABC123" + }, + "language": "en", + "contact_details": { + "email": "john.doe@example.com", + "send_notification_emails": true, + "email_lang": "en", + "phone": "+14155552671" + }, + "expected_details": { + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1990-05-15", + "id_country": "USA", + "expected_document_types": [ + "P", + "ID" + ] + } + } + }, + "examples": { + "Create KYC session": { + "summary": "Full happy-path payload for a User Verification (KYC) session", + "description": "Realistic create call that uses every commonly-set field: workflow selection, vendor linkage, callback routing, metadata, locale, contact prefill, and expected user details. Copy-paste this directly into the cURL sample above.", + "value": { + "workflow_id": "11111111-2222-3333-4444-555555555555", + "vendor_data": "user-123", + "callback": "https://example.com/verification/callback", + "callback_method": "both", + "metadata": { + "user_type": "premium", + "account_id": "ABC123" + }, + "language": "en", + "contact_details": { + "email": "john.doe@example.com", + "send_notification_emails": true, + "email_lang": "en", + "phone": "+14155552671" + }, + "expected_details": { + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1990-05-15", + "nationality": "USA", + "id_country": "USA", + "expected_document_types": [ + "P", + "ID" + ] + } + } + }, + "Create KYB session": { + "summary": "Happy-path payload for a Business Verification (KYB) session", + "description": "Same shape as the KYC call — the `workflow_id` is what makes the session KYB. `vendor_data` typically references the company in your system and `contact_details.email` targets the legal representative who will complete the flow. `expected_details` accepts the business fields (`company_name`, `registry_country`, `registration_number`), which pre-fill the company registry search; person-level fields and `contact_details.phone` are ignored for KYB workflows.", + "value": { + "workflow_id": "33333333-4444-5555-6666-777777777777", + "vendor_data": "company-acme-001", + "callback": "https://example.com/kyb/callback", + "callback_method": "both", + "metadata": { + "tier": "enterprise", + "company_id": "ACME-001" + }, + "expected_details": { + "company_name": "Tesco PLC", + "registry_country": "GB", + "registration_number": "00445790" + }, + "language": "en", + "contact_details": { + "email": "legal-rep@acme.example.com", + "send_notification_emails": true, + "email_lang": "en" + } + } + } + } + } + } + } + } + }, + "/v3/session/imports/": { + "post": { + "summary": "Create a verification import job", + "description": "Create an asynchronous import job for historical User Verification sessions, Business Verification sessions, transaction monitoring records, or workflow custom status rules. Set `source=provider` to migrate from another provider: Didit reads Sumsub, Onfido, Persona and ComplyCube with your read-only key, reads Veriff, iDenfy and MetaMap records by the IDs you upload in `file`, and imports an export file in Didit's format for the other providers. Record IDs are namespaced by provider (`sumsub:`) so a re-run skips everything already in Didit. Imports are disabled by default and must be enabled per organization by Didit.", + "operationId": "post_v3_session_imports_create", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "import_type": { + "type": "string", + "enum": [ + "user_verification", + "business_verification", + "status_rules", + "transactions" + ], + "default": "user_verification", + "description": "What to import: historical User Verification sessions, historical Business Verification sessions, workflow custom status rules, or historical transaction monitoring records." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Workflow UUID that imported sessions or status rules should be attached to. Required for user_verification, business_verification, and status_rules. Omit for transactions." + }, + "source_format": { + "type": "string", + "enum": [ + "csv", + "ndjson" + ], + "description": "Optional. Inferred from the uploaded file extension when omitted." + }, + "file": { + "type": "string", + "format": "binary", + "description": "Canonical CSV or NDJSON file. Use NDJSON for large imports. Maximum upload size 25 MB (use `source_file_url` for bigger files). Mutually exclusive with `source_file_url`." + }, + "source_file_url": { + "type": "string", + "format": "uri", + "description": "Alternative to file upload. Must be an HTTPS URL resolving to a public address (private/loopback networks are rejected). Mutually exclusive with `file`." + }, + "source": { + "type": "string", + "enum": [ + "file", + "provider" + ], + "description": "`provider` makes this a migration from another identity verification provider. Omit for a file import." + }, + "provider": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ], + "description": "Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`." + }, + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "options": { + "type": "object", + "properties": { + "region": { + "type": "string", + "description": "The provider region, one of its `regions` (Onfido: `eu`, `us`, `ca`)." + } + } + }, + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + }, + "description": "Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported." + }, + "dry_run": { + "type": "boolean", + "default": false, + "description": "Count what the migration would import (by status, already in Didit, same person, not finished) and write nothing. The job ends `READY`; start it with `POST /v3/session/imports/{importId}/start/`." + }, + "face_templates": { + "type": "boolean", + "default": false, + "description": "Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`." + }, + "lawful_basis_confirmed": { + "type": "boolean", + "default": false, + "description": "Confirms you have a lawful basis to share these selfies with Didit as processor." + } + } + } + }, + "application/json": { + "schema": { + "type": "object", + "required": [ + "source_file_url" + ], + "properties": { + "import_type": { + "type": "string", + "enum": [ + "user_verification", + "business_verification", + "status_rules", + "transactions" + ], + "default": "user_verification", + "description": "What to import: historical User Verification sessions, historical Business Verification sessions, workflow custom status rules, or historical transaction monitoring records." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Workflow UUID that imported sessions or status rules should be attached to. Required for user_verification, business_verification, and status_rules. Omit for transactions." + }, + "source_format": { + "type": "string", + "enum": [ + "csv", + "ndjson" + ], + "description": "Optional. Inferred from the URL's file extension when omitted." + }, + "source_file_url": { + "type": "string", + "format": "uri", + "description": "Alternative to file upload. Must be an HTTPS URL resolving to a public address (private/loopback networks are rejected). Mutually exclusive with `file`." + }, + "source": { + "type": "string", + "enum": [ + "file", + "provider" + ], + "description": "`provider` makes this a migration from another identity verification provider. Omit for a file import." + }, + "provider": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ], + "description": "Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`." + }, + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "options": { + "type": "object", + "properties": { + "region": { + "type": "string", + "description": "The provider region, one of its `regions` (Onfido: `eu`, `us`, `ca`)." + } + } + }, + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + }, + "description": "Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported." + }, + "dry_run": { + "type": "boolean", + "default": false, + "description": "Count what the migration would import (by status, already in Didit, same person, not finished) and write nothing. The job ends `READY`; start it with `POST /v3/session/imports/{importId}/start/`." + }, + "face_templates": { + "type": "boolean", + "default": false, + "description": "Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`." + }, + "lawful_basis_confirmed": { + "type": "boolean", + "default": false, + "description": "Confirms you have a lawful basis to share these selfies with Didit as processor." + } + } + }, + "examples": { + "URL-based import": { + "value": { + "import_type": "user_verification", + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "source_file_url": "https://files.example.com/exports/sessions.ndjson" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Import job accepted. Poll the returned `uuid` until `status` is `COMPLETED` or `FAILED`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + }, + "examples": { + "Job accepted": { + "value": { + "uuid": "3f9c1a2b-4d5e-4f60-8a7b-9c0d1e2f3a4b", + "status": "PENDING", + "import_type": "user_verification", + "source_format": "ndjson", + "source_filename": "sessions.ndjson", + "total_rows": 0, + "processed_rows": 0, + "checkpoint_row": 0, + "created_count": 0, + "updated_count": 0, + "skipped_count": 0, + "failed_count": 0, + "total_media_bytes": 0, + "last_error": null, + "created_at": "2026-05-17T08:42:11Z", + "updated_at": "2026-05-17T08:42:11Z", + "started_at": null, + "completed_at": null + } + } + } + } + } + }, + "400": { + "description": "Invalid workflow, file type, file size, source URL, or selector combination. A migration whose key the provider refuses answers `code` (`invalid_credentials`, `missing_permission`, `wrong_region`, `provider_unavailable`, `rate_limited`, `ids_required`, `record_not_found`), `missing_permissions` and `suggested_region`.", + "content": { + "application/json": { + "examples": { + "Invalid workflow": { + "value": { + "workflow_id": [ + "Invalid workflow_id." + ] + } + }, + "Workflow required": { + "value": { + "workflow_id": [ + "This field is required for this import type." + ] + } + }, + "Wrong file type": { + "value": { + "file": [ + "Only CSV and NDJSON files are supported." + ] + } + }, + "File too large": { + "value": { + "file": [ + "Import file exceeds the maximum allowed size." + ] + } + }, + "No source": { + "value": { + "file": [ + "Upload a file or provide source_file_url." + ] + } + }, + "Both sources": { + "value": { + "file": [ + "Use either file or source_file_url, not both." + ] + } + } + } + } + } + }, + "403": { + "description": "Imports not enabled, sandbox application, or missing/invalid API key (this endpoint returns `403`, never `401`, for authentication failures). Verification imports are disabled by default and must be enabled per organization by Didit.", + "content": { + "application/json": { + "examples": { + "Imports not enabled": { + "value": { + "detail": "Verification imports are not enabled for this organization." + } + }, + "Sandbox": { + "value": { + "detail": "Verification imports are not enabled for sandbox applications." + } + }, + "Bad API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "The application already has an active import job (PENDING/PROCESSING/PAUSED). Wait for it to finish before starting another.", + "content": { + "application/json": { + "examples": { + "Active job": { + "value": { + "detail": "This application already has an active import job. Wait for it to finish before starting another." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/imports/template/": { + "get": { + "summary": "Download verification import template", + "description": "Download the canonical CSV header for verification imports. The column set is determined by `import_type`. The first column of session templates is the idempotency key `external_id`.", + "operationId": "get_v3_session_imports_template", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "retrieve_template", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "import_type", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "user_verification", + "business_verification", + "status_rules", + "transactions" + ], + "default": "user_verification" + } + } + ], + "responses": { + "200": { + "description": "CSV template file.", + "content": { + "text/csv": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot download import templates. This endpoint returns `403` (never `401`) for authentication failures, including requests with no credentials at all.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "summary": "Missing, invalid, or revoked API key", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/imports/{importId}/": { + "get": { + "summary": "Retrieve a verification import job", + "description": "Retrieve import progress counters and terminal status for one job. Poll until `status` is `COMPLETED` or `FAILED`; `failed_count` > 0 means some rows were rejected — list them via the errors endpoint.", + "operationId": "get_v3_session_imports_retrieve", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Import job status.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "404": { + "description": "Import job not found for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read import jobs for this application. This endpoint returns `403` (never `401`) for authentication failures, including requests with no credentials at all.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "summary": "Missing, invalid, or revoked API key", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/imports/{importId}/errors/": { + "get": { + "summary": "List verification import row errors", + "description": "List row-level errors for a verification import job, ordered by `row_number`. Use this endpoint to produce a corrected retry file containing only failed rows.", + "operationId": "get_v3_session_imports_errors", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "list_errors", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size. Defaults to 50 when omitted." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0 + }, + "description": "Zero-based offset of the first record to return." + } + ], + "responses": { + "200": { + "description": "Paginated row errors.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of error rows — exact up to 100, capped at 100 beyond that. Rely on `next` being `null` to detect the last page." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionImportError" + } + } + } + }, + "examples": { + "Row errors": { + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d", + "row_number": 17, + "external_id": "ext-000017", + "error": "status: invalid value 'unknown_status'", + "raw_row": { + "external_id": "ext-000017", + "status": "unknown_status", + "first_name": "Jane" + }, + "created_at": "2026-05-17T08:44:02Z" + } + ] + } + } + } + } + } + }, + "404": { + "description": "Import job not found for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read import jobs for this application. This endpoint returns `403` (never `401`) for authentication failures, including requests with no credentials at all.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "summary": "Missing, invalid, or revoked API key", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/decision/": { + "get": { + "summary": "Retrieve the full decision for a verification session", + "description": "Retrieve the complete decision report for a verification session — the rolled-up session `status` plus one report per verification feature the workflow executed. This is the canonical read endpoint for both User Verification (KYC) and Business Verification (KYB) sessions: the same path serves both, and the top-level `session_kind` discriminator tells you which shape you received (`\"user\"` or `\"business\"`).\n\n**Every feature report is a plural array.** V3 workflows are graphs that can run the same feature more than once, so each block (`id_verifications`, `nfc_verifications`, `liveness_checks`, `face_matches`, `poa_verifications`, `document_ai_documents`, `phone_verifications`, `email_verifications`, `aml_screenings`, `ip_analyses`, `database_validations`, `questionnaire_responses`) is a JSON array whose items each carry a `node_id` identifying the workflow graph node that produced them. Read NFC as `response.nfc_verifications[0]` — never as a singular `nfc` field. A block is `null` until the workflow has run that feature at least once; features the workflow does not include never appear with data. Electronic Signature results are in `signatures`, which is present only when the workflow has an Electronic Signature step.\n\n**When to call.** The recommended pattern is event-driven: wait for the `status.updated` webhook that fires when the session reaches a decision (`Approved`, `Declined`, `In Review`), then call this endpoint once to fetch the full report. Polling also works — the endpoint can be called at any point in the session lifecycle and returns the data produced so far — but webhook-then-fetch is cheaper and faster. Media URLs in the response (document images, videos, PDFs) are short-lived presigned links: fetch them promptly or re-request the decision to get fresh ones, and do not persist them as long-term references.\n\n**Statuses.** The top-level `status` is the session lifecycle status (`Not Started`, `In Progress`, `Awaiting User`, `In Review`, `Approved`, `Declined`, `Resubmitted`, `Expired`, `Kyc Expired`, `Abandoned`). Each feature item additionally carries its own feature-level `status` (`Not Finished`, `Approved`, `Declined`, `In Review`, ...). A session can be `Approved` overall while an individual feature is `In Review`, depending on the workflow's decision rules.\n\n**Authentication and visibility.** Authenticate with the application API key in the `x-api-key` header (a console user Bearer token with the `read:sessions` permission also works). Only sessions owned by the authenticated application are visible. If your workflow restricts response attributes, non-allowed fields inside feature items are returned as `null` while `status`, `warnings`, and `node_id` are always preserved.\n\n**Rate limits.** GET requests are limited to 600 per minute per API key. Exceeding the limit returns `429` with `Retry-After` and `X-RateLimit-*` headers.", + "operationId": "get_v3_session_decision", + "x-didit-sdk": { + "resource": "sessions", + "method": "retrieve_decision", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "200": { + "description": "Full decision report. `session_kind` discriminates the two shapes: `user` sessions carry the KYC feature arrays shown below; `business` sessions instead carry `registry_checks`, `document_verifications` and `key_people_checks` alongside the shared arrays. Feature arrays are `null` until the workflow has run that feature; media URLs are short-lived presigned links.", + "content": { + "application/json": { + "examples": { + "Example": { + "summary": "Full Example", + "value": { + "session_id": "11111111-2222-3333-4444-555555555555", + "session_kind": "user", + "session_number": 43762, + "session_url": "https://verify.didit.me/session/11111111-2222-3333-4444-555555555555", + "status": "In Review", + "environment": "live", + "workflow_id": "11111111-2222-3333-4444-555555555555", + "features": [ + "ID_VERIFICATION", + "NFC", + "LIVENESS", + "FACE_MATCH", + "POA", + "PHONE", + "DATABASE_VALIDATION", + "AML", + "IP_ANALYSIS" + ], + "vendor_data": "11111111-1111-1111-1111-111111111111", + "metadata": { + "user_type": "premium", + "account_id": "ABC123" + }, + "expected_details": { + "first_name": "Carmen", + "last_name": "Española Española" + }, + "contact_details": { + "email": "carmen@example.com", + "email_lang": "es", + "send_notification_emails": false, + "phone": null + }, + "callback": "https://verify.didit.me/", + "id_verifications": [ + { + "node_id": "feature_ocr_1", + "status": "Approved", + "document_type": "Identity Card", + "document_subtype": "ID_CARD_GENERIC", + "document_number": "CAA000000", + "personal_number": "99999999R", + "portrait_image": "https:///portrait.jpg", + "front_image": "https:///front.jpg", + "front_video": "https:///front.mp4", + "back_image": "https:///back.jpg", + "back_video": "https:///back.mp4", + "full_front_image": "https:///full_front.jpg", + "full_back_image": "https:///full_back.jpg", + "front_image_camera_front": "https:///front_camera_front.jpg", + "back_image_camera_front": "https:///back_camera_front.jpg", + "date_of_birth": "1980-01-01", + "age": 45, + "expiration_date": "2031-06-02", + "date_of_issue": "2021-06-02", + "issuing_state": "ESP", + "issuing_state_name": "Spain", + "first_name": "Carmen", + "last_name": "Española Española", + "full_name": "Carmen Española Española", + "gender": "F", + "address": "Avda de Madrid 34, Madrid, Madrid", + "formatted_address": "Avda de Madrid 34, Madrid, Madrid 28822, Spain", + "place_of_birth": "Madrid", + "marital_status": "Single", + "nationality": "ESP", + "extra_fields": { + "dl_categories": [], + "blood_group": null + }, + "mrz": { + "surname": "ESPANOLA ESPANOLA", + "name": "CARMEN", + "country": "ESP", + "nationality": "ESP", + "birth_date": "800101", + "expiry_date": "310602", + "sex": "F", + "document_type": "ID", + "document_number": "CAA000000", + "optional_data": "99999999R", + "optional_data_2": "", + "birth_date_hash": "4", + "expiry_date_hash": "8", + "document_number_hash": "4", + "final_hash": "1", + "personal_number": "99999999R", + "warnings": [], + "errors": [], + "mrz_type": "TD1", + "mrz_string": "IDESPCAA000000499999999R<<<<<<\n8001014F3106028ESP<<<<<<<<<<<1\nESPANOLA/match_face.jpg", + "status": "Approved", + "is_blocklisted": false, + "is_allowlisted": false, + "api_service": null, + "source": "session" + } + ], + "front_image_quality_score": { + "focus_score": 85.3, + "brightness_score": 92.1, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 72.4, + "overall_score": 88.7 + }, + "back_image_quality_score": { + "focus_score": 79.1, + "brightness_score": 90.5, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 72.4, + "overall_score": 85.2 + }, + "extra_files": [ + "https:///extra_id_verification.jpg" + ], + "matches": [ + { + "session_id": "7d4f1f60-9c2e-4b8a-8f3d-2a1b5c6d7e8f", + "session_number": 1234, + "vendor_data": "user_ref_001", + "verification_date": "2024-06-15T10:30:00Z", + "user_details": { + "name": "John Smith", + "document_type": "ID", + "document_number": "CLM148418" + }, + "status": "Approved", + "is_blocklisted": false, + "api_service": null, + "front_image_url": "https:///document_front.jpg" + }, + { + "session_id": "b2c8e4d1-6a3f-4e7b-95c0-1d8f2a6b4c9e", + "session_number": 1235, + "vendor_data": null, + "verification_date": "2024-05-20T14:45:00Z", + "user_details": { + "name": "John Smith", + "document_type": "ID", + "document_number": "CLM148418" + }, + "status": "Declined", + "is_blocklisted": true, + "api_service": "ID_VERIFICATION", + "front_image_url": "https:///document_front_2.jpg" + } + ], + "warnings": [ + { + "feature": "ID_VERIFICATION", + "risk": "QR_NOT_DETECTED", + "additional_data": null, + "log_type": "information", + "short_description": "QR not detected", + "long_description": "The system couldn't find or read the QR code on the document, which is necessary for document verification. This could be due to poor image quality or an unsupported document type.", + "node_id": "feature_ocr_1" + } + ], + "verification_method": "document", + "assurance": "documentary", + "wallet_provider": null, + "fallback_from": null, + "id_lookup": null, + "wallet_verification": null, + "document_liveness": { + "portrait_replace": { + "score": 96.46, + "front": 96.46, + "back": 98.2, + "decline_threshold": 35, + "review_threshold": 70, + "bucket": "approve" + }, + "printed_copy": { + "score": 91.2, + "front": 91.2, + "back": 93.7, + "decline_threshold": 35, + "review_threshold": 77, + "bucket": "approve" + }, + "screen_replay": { + "score": 99.9, + "front": 99.9, + "back": 99.5, + "decline_threshold": 30, + "review_threshold": 70, + "bucket": "approve" + } + } + }, + { + "node_id": "feature_ocr_2", + "status": "Approved", + "issuing_state": "ZAF", + "issuing_state_name": "South Africa", + "first_name": "Thabo", + "last_name": "Mokoena", + "full_name": "Thabo Mokoena", + "date_of_birth": "1991-06-15", + "personal_number": "9106155043088", + "verification_method": "id_lookup", + "assurance": "data_match", + "wallet_provider": null, + "fallback_from": null, + "id_lookup": { + "source": "Department of Home Affairs", + "checked_at": "2026-09-04T09:12:44Z", + "attempts": 1, + "max_attempts": 2, + "outcome": "match", + "comparison": [ + { + "field": "identification_number", + "label": "ID number", + "provided": "9106155043088", + "record": "9106155043088", + "result": "match" + }, + { + "field": "full_name", + "label": "Full name", + "provided": "Thabo Mokoena", + "record": "Thabo Mokoena", + "result": "match" + }, + { + "field": "date_of_birth", + "label": "Date of birth", + "provided": "1991-06-15", + "record": "1991-06-15", + "result": "match" + } + ], + "registry_portrait": "https:///registry_portrait.jpg", + "face_match_score": 96.4, + "source_errors": [] + }, + "wallet_verification": null, + "warnings": [] + }, + { + "node_id": "feature_ocr_3", + "status": "Approved", + "issuing_state": "DNK", + "issuing_state_name": "Denmark", + "first_name": "Freja", + "last_name": "Nielsen", + "full_name": "Freja Nielsen", + "date_of_birth": "1988-03-02", + "verification_method": "wallet", + "assurance": "cryptographic", + "wallet_provider": "mitid", + "fallback_from": null, + "id_lookup": null, + "wallet_verification": { + "provider": "mitid", + "provider_name": "MitID", + "issuing_authority": "Danish Agency for Digital Government", + "issuing_country": "DNK", + "credential_type": "person_identification", + "level_of_assurance": "substantial", + "verified_at": "2026-09-04T09:12:44Z", + "signature_valid": true, + "attributes": { + "full_name": "Freja Nielsen", + "date_of_birth": "1988-03-02", + "cpr_alias": "b0f1c2d3-e4f5-4678-9abc-def012345678" + }, + "portrait": null, + "face_match_score": null + }, + "warnings": [] + } + ], + "nfc_verifications": [ + { + "node_id": "feature_nfc_1", + "status": "In Review", + "is_nfc_skipped": false, + "skip_reason": null, + "chip_protocol": "icao", + "jpki": null, + "portrait_image": "https:///portrait.jpg", + "signature_image": "https:///signature.jpg", + "chip_data": { + "document_type": "ID", + "issuing_country": "ESP", + "document_number": "123456789", + "expiration_date": "2030-01-01", + "first_name": "John", + "last_name": "Smith", + "birth_date": "1990-05-15", + "gender": "M", + "nationality": "ESP", + "address": "CALLE MAYOR 123 4B, MADRID, MADRID", + "place_of_birth": "MADRID, MADRID" + }, + "authenticity": { + "sod_integrity": true, + "dg_integrity": true + }, + "certificate_summary": { + "issuer": "Common Name: CSCA SPAIN, Serial Number: 3, Organization: DIRECCION GENERAL DE LA POLICIA, Country: ES", + "subject": "Common Name: DS n-eID SPAIN 2, Organizational Unit: PASSPORT, Organization: DIRECCION GENERAL DE LA POLICIA, Country: ES", + "serial_number": "118120836164494130086420187336801405660", + "not_valid_after": "2031-02-18 10:21:11", + "not_valid_before": "2020-11-18 10:21:11" + }, + "warnings": [ + { + "feature": "NFC", + "risk": "DATA_INCONSISTENT", + "additional_data": null, + "log_type": "warning", + "short_description": "OCR and NFC mrz code extracted are not the same", + "long_description": "The Optical Character Recognition (OCR) data and the NFC chip data don't match, indicating potential document tampering or data inconsistency.", + "node_id": "feature_nfc_1" + } + ] + } + ], + "liveness_checks": [ + { + "node_id": "feature_liveness_1", + "status": "Approved", + "method": "ACTIVE_3D", + "score": 89.92, + "reference_image": "https:///reference.jpg", + "video_url": "https:///video.mp4", + "age_estimation": 24.3, + "matches": [ + { + "session_id": "7d4f1f60-9c2e-4b8a-8f3d-2a1b5c6d7e8f", + "session_number": 1234, + "vendor_data": "user_ref_001", + "verification_date": "2024-06-15T10:30:00Z", + "user_details": { + "full_name": "John Smith", + "document_type": "ID", + "document_number": "CLM148418" + }, + "status": "Approved", + "is_blocklisted": false, + "is_allowlisted": false, + "api_service": null, + "source": "session", + "match_image_url": "https:///face_match.jpg", + "similarity_percentage": 98.5 + }, + { + "session_id": "3e9a7c21-5b48-4f0e-9c6a-8d2f4b1e0a35", + "session_number": 1100, + "vendor_data": "customer_xyz", + "verification_date": "2024-04-10T09:15:00Z", + "user_details": { + "full_name": "John Smith", + "document_type": "P", + "document_number": "DZ8375806" + }, + "status": "Approved", + "is_blocklisted": false, + "is_allowlisted": false, + "api_service": "PASSIVE_LIVENESS", + "source": "session", + "match_image_url": "https:///face_match_2.jpg", + "similarity_percentage": 95.2 + } + ], + "warnings": [ + { + "feature": "LIVENESS", + "risk": "LOW_LIVENESS_SCORE", + "additional_data": null, + "log_type": "information", + "short_description": "Low liveness score", + "long_description": "The liveness check resulted in a low score, indicating potential use of non-live facial representations or poor-quality biometric data.", + "node_id": "feature_liveness_1" + } + ], + "face_quality": null, + "face_luminance": null + } + ], + "face_matches": [ + { + "node_id": "feature_face_match_1", + "status": "In Review", + "score": 65.43, + "source_image_session_id": null, + "source_image": "https:///source-image.jpg", + "target_image": "https:///target-image.jpg", + "warnings": [ + { + "feature": "FACEMATCH", + "risk": "LOW_FACE_MATCH_SIMILARITY", + "additional_data": null, + "log_type": "warning", + "short_description": "Low face match similarity", + "long_description": "The facial features of the provided image don't closely match the reference image, suggesting a potential identity mismatch.", + "node_id": "feature_face_match_1" + } + ] + } + ], + "phone_verifications": [ + { + "node_id": "feature_phone_1", + "status": "Approved", + "phone_number_prefix": "+34", + "phone_number": "600600600", + "full_number": "+34600600600", + "country_code": "ES", + "country_name": "Spain", + "carrier": { + "name": "Orange", + "type": "mobile" + }, + "is_disposable": false, + "is_virtual": false, + "verification_method": "sms", + "verification_attempts": 1, + "verified_at": "2024-07-28T06:47:35.654321Z", + "lifecycle": [ + { + "type": "PHONE_VERIFICATION_MESSAGE_SENT", + "timestamp": "2025-08-24T09:12:39.580554+00:00", + "details": { + "status": "Success", + "reason": null, + "channel": "sms", + "actual_channel": "sms" + }, + "fee": 0.1 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2025-08-24T09:12:39.662157+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "PHONE_VERIFICATION_APPROVED", + "timestamp": "2025-08-24T09:12:39.684292+00:00", + "details": null, + "fee": 0 + } + ], + "warnings": [], + "matches": [ + { + "session_id": null, + "session_number": null, + "vendor_data": null, + "verification_date": null, + "phone_number": "+34600000000", + "status": null, + "is_blocklisted": true, + "api_service": null, + "source": "list_entry" + } + ] + } + ], + "email_verifications": [ + { + "node_id": "feature_email_1", + "status": "Approved", + "email": "test@example.com", + "is_breached": true, + "breaches": [ + { + "name": "TAPAirPortugal", + "domain": "flytap.com", + "logo_path": "https://logos.example.com/TAPAirPortugal.png", + "breach_date": "2022-08-25", + "description": "In August 2022, the Portuguese airline TAP Air Portugal was the target of a ransomware attack perpetrated by the Ragnar Locker gang who later leaked the compromised data via a public dark web site. Over 5M unique email addresses were exposed alongside other personal data including names, genders, DoBs, phone numbers and physical addresses.", + "is_verified": true, + "data_classes": [ + "dates_of_birth", + "email_addresses", + "genders", + "names", + "nationalities", + "phone_numbers", + "physical_addresses", + "salutations", + "spoken_languages" + ], + "breach_emails_count": 6083479 + } + ], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 1, + "verified_at": "2025-09-15T17:36:19.963451Z", + "warnings": [ + { + "feature": "EMAIL", + "risk": "BREACHED_EMAIL_DETECTED", + "additional_data": null, + "log_type": "information", + "short_description": "Breached email detected", + "long_description": "This email address was found in one or more known data breaches.", + "node_id": "feature_email_1" + } + ], + "lifecycle": [ + { + "type": "EMAIL_VERIFICATION_MESSAGE_SENT", + "timestamp": "2025-09-15T17:36:19.792684+00:00", + "details": { + "status": "Success", + "reason": null + }, + "fee": 0.02 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2025-09-15T17:36:19.963427+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "EMAIL_VERIFICATION_DECLINED", + "timestamp": "2025-09-15T17:36:20.081471+00:00", + "details": { + "reason": "BREACHED_EMAIL_DETECTED" + }, + "fee": 0 + } + ], + "matches": [ + { + "session_id": null, + "session_number": null, + "vendor_data": null, + "verification_date": null, + "email": "test@example.com", + "status": null, + "is_blocklisted": true, + "api_service": null, + "source": "list_entry" + } + ] + } + ], + "poa_verifications": [ + { + "node_id": "feature_poa_1", + "status": "Declined", + "document_file": "https:///poa_document.pdf", + "issuing_state": "AR", + "document_type": "UTILITY_BILL", + "document_subtype": null, + "document_language": "es", + "document_metadata": { + "file_size": 1000, + "content_type": "application/pdf", + "creation_date": "2021-01-01", + "modified_date": "2021-01-01", + "overlay_manipulation": null, + "creator": null, + "producer": "PDF Generator", + "software": null, + "encryption": null, + "is_signed": false, + "is_tampered": false, + "signature_info": null, + "exif_original_date": null, + "exif_digitized_date": null, + "processed_by_known_editor": false, + "has_different_creation_mod_date": true + }, + "issuer": "Aguas del Norte", + "issue_date": "2021-02-01", + "expiration_date": null, + "poa_address": "AVDA. MONSEÑOR TAVELLA N° 3396, SALTA", + "poa_formatted_address": "Av. Monseñor Tavella 3396, A4400 Salta, Argentina", + "poa_parsed_address": { + "address_type": "Avenida", + "street_1": "Avenida Monseñor Tavella 3396", + "street_2": null, + "city": "Salta", + "region": "Salta", + "country": "AR", + "postal_code": "A4400", + "document_location": { + "latitude": -24.8208664, + "longitude": -65.4131 + } + }, + "name_on_document": "Fernando De Lima", + "name_match_score_expected_details": null, + "name_match_score_id_verification": 38.5, + "expected_details_address": null, + "expected_details_formatted_address": null, + "expected_details_parsed_address": {}, + "extra_fields": { + "bank_account_number": "1234567890", + "bank_iban": null, + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": "+5491112345678", + "additional_names": [] + }, + "extra_files": [ + "https:///extra_poa.pdf" + ], + "detected_codes": [ + { + "type": "QR_CODE", + "page_number": 1, + "position": [ + [ + 820, + 1120 + ], + [ + 1002, + 1120 + ], + [ + 1002, + 1302 + ], + [ + 820, + 1302 + ] + ], + "x": 820, + "y": 1120, + "width": 182, + "height": 182, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "aHR0cHM6Ly9hZ3Vhc2RlbG5vcnRlLmNvbS9mYWN0dXJhLzk5ODgxMQ==", + "parsed_payload": "https://aguasdelnorte.com/factura/998811" + }, + { + "type": "CODE_128", + "page_number": 1, + "position": [ + [ + 112, + 1480 + ], + [ + 560, + 1480 + ], + [ + 560, + 1540 + ], + [ + 112, + 1540 + ] + ], + "x": 112, + "y": 1480, + "width": 448, + "height": 60, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "MDA5OTg4MTEwMDA0Mg==", + "parsed_payload": "0099881100042" + } + ], + "warnings": [] + } + ], + "document_ai_documents": [ + { + "status": "Approved", + "node_id": "feature_document_ai_1", + "items": [ + { + "uuid": "c1a2b3c4-d5e6-7f80-9a1b-2c3d4e5f6071", + "node_id": "feature_document_ai_1", + "document_key": "proof_of_funds", + "original_filename": "bank_statement.pdf", + "file_size": 184320, + "file_url": "https:///document_ai/proof_of_funds.pdf", + "status": "Approved", + "extracted_data": { + "account_holder": "John Doe", + "balance": 15230.55, + "currency": "EUR", + "statement_date": "2026-05-31" + }, + "detected_codes": [ + { + "type": "QR_CODE", + "page_number": 2, + "position": [ + [ + 124, + 86 + ], + [ + 306, + 86 + ], + [ + 306, + 268 + ], + [ + 124, + 268 + ] + ], + "x": 124, + "y": 86, + "width": 182, + "height": 182, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "SU5WLTIwMjYtMDA0Mg==", + "parsed_payload": "INV-2026-0042" + } + ], + "fields": [ + { + "key": "account_holder", + "name": "Account holder", + "instruction": "Extract the full name of the account holder.", + "type": "text", + "required": true + }, + { + "key": "balance", + "name": "Closing balance", + "instruction": "Extract the closing balance amount.", + "type": "number", + "required": true + } + ], + "document_metadata": { + "is_tampered": false + }, + "cross_check_result": null, + "created_at": "2026-06-01T12:34:56Z" + } + ], + "warnings": [] + } + ], + "questionnaire_responses": [ + { + "node_id": "feature_questionnaire_1", + "questionnaire_id": "9f7485e0-b00b-4d56-9d3e-7859b68d1213", + "title": "Source of Funds", + "description": "Reply to this questionnaire to help us understand the source of funds.", + "languages": [ + "en" + ], + "default_language": "en", + "is_active": true, + "is_simple_questionnaire": true, + "questionnaire_group_id": "2b6a9d40-13a8-4b1e-9a6c-3f1d2e4b5a60", + "version": 1, + "published_at": "2025-11-02T09:14:55.000000Z", + "sections": [ + { + "title": null, + "description": null, + "items": [ + { + "uuid": "618636d9-7d5c-41ce-b8d0-1ab2a4be20db", + "value": "question_1", + "element_type": "DROPDOWN", + "is_required": true, + "title": "What is your primary source of funds?", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Salary", + "value": "salary" + }, + { + "label": "Business Income", + "value": "business_income" + }, + { + "label": "Savings", + "value": "savings" + }, + { + "label": "Investments", + "value": "investments" + }, + { + "label": "Sale of Asset", + "value": "sale_of_asset" + }, + { + "label": "Inheritance/Gift", + "value": "inheritance_gift" + }, + { + "label": "Pension", + "value": "pension" + }, + { + "label": "Crypto Proceeds", + "value": "crypto_proceeds" + }, + { + "label": "Other", + "value": "other", + "requires_text_input": true + } + ], + "max_files": 1, + "answer": { + "value": "salary" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "2d16b527-72be-42e0-8e96-b164b818386c", + "value": "question_2", + "element_type": "LONG_TEXT", + "is_required": true, + "title": "Provide details of the primary source.", + "description": null, + "placeholder": null, + "choices": null, + "max_files": 1, + "answer": { + "value": "Test answer" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "a9239b7c-fbfb-4ea7-b7e4-c835cd4016e0", + "value": "question_3", + "element_type": "DROPDOWN", + "is_required": true, + "title": "What is your annual income range (pre-tax)?", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Less than $25,000", + "value": "<25k" + }, + { + "label": "$25,000 - $50,000", + "value": "25k-50k" + }, + { + "label": "$50,001 - $100,000", + "value": "50k-100k" + }, + { + "label": "$100,001 - $250,000", + "value": "100k-250k" + }, + { + "label": "More than $250,000", + "value": ">250k" + } + ], + "max_files": 1, + "answer": { + "value": "<25k" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "93dede62-6d0c-4a7f-934d-42175eb94011", + "value": "question_4", + "element_type": "DROPDOWN", + "is_required": true, + "title": "What is your approximate net worth (range)?", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Less than $50,000", + "value": "<50k" + }, + { + "label": "$50,000 - $250,000", + "value": "50k-250k" + }, + { + "label": "$250,001 - $1,000,000", + "value": "250k-1m" + }, + { + "label": "$1,000,001 - $5,000,000", + "value": "1m-5m" + }, + { + "label": "More than $5,000,000", + "value": ">5m" + } + ], + "max_files": 1, + "answer": { + "value": "<50k" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "2af60d6f-6961-4692-9ee7-24b3033610a6", + "value": "question_5", + "element_type": "LONG_TEXT", + "is_required": true, + "title": "For your initial deposit/funding, indicate the exact source and amount.", + "description": null, + "placeholder": null, + "choices": null, + "max_files": 1, + "answer": { + "value": "Test answer" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "0ad9d1aa-736f-46cf-947b-9e1241b53e40", + "value": "question_6", + "element_type": "LONG_TEXT", + "is_required": true, + "title": "Breakdown of sources by percentage.", + "description": null, + "placeholder": null, + "choices": null, + "max_files": 1, + "answer": { + "value": "Test answer" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "1bf8a2a3-14ee-48a8-bb3d-175481aae4aa", + "value": "question_7", + "element_type": "SINGLE_CHOICE", + "is_required": true, + "title": "Will any funds originate from third parties? If yes, please provide details.", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Yes", + "value": "yes", + "requires_text_input": true + }, + { + "label": "No", + "value": "no" + } + ], + "max_files": 1, + "answer": { + "text": "Additional details", + "value": "yes" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "e18d4dd3-b7bf-4883-ba18-d6046adbe313", + "value": "question_8", + "element_type": "SINGLE_CHOICE", + "is_required": true, + "title": "Do you hold or plan to use virtual assets (e.g., crypto) to fund this account? If yes, please provide details.", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Yes", + "value": "yes", + "requires_text_input": true + }, + { + "label": "No", + "value": "no" + } + ], + "max_files": 1, + "answer": { + "text": "Additional details", + "value": "yes" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "a90a073d-e098-404c-bbce-5130d8b12a13", + "value": "question_9", + "element_type": "FILE_UPLOAD", + "is_required": true, + "title": "Upload a proof of funds document", + "description": "You can upload your three (3) most recent bank statements, three (3) most recent payslips, your most recent tax return, proof of company ownership, trading exchange statement, sale of property, or proof of inheritance.", + "placeholder": null, + "choices": null, + "max_files": 3, + "answer": { + "files": [ + "/media/ocr/", + "/media/ocr/" + ] + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "9503b486-7cb8-4c3b-a97d-4b64926a5a47", + "value": "question_10", + "element_type": "LONG_TEXT", + "is_required": false, + "title": "Would you like to add any details to help us expedite our review of the documents you submitted?", + "description": null, + "placeholder": null, + "choices": null, + "max_files": 1, + "answer": { + "value": "Test answer" + }, + "required_if": null, + "repeatable_config": null + } + ] + } + ], + "status": "Approved" + } + ], + "aml_screenings": [ + { + "node_id": "feature_aml_1", + "status": "In Review", + "total_hits": 1, + "entity_type": "person", + "hits": [ + { + "id": "cPtTGRKkyddAcAC4xgsLCm", + "url": "https://news.example.org/articles/sample", + "match": false, + "score": 0.73, + "target": null, + "caption": "Javier Ejemplo Modelo", + "datasets": [ + "PEP" + ], + "features": null, + "rca_name": "", + "last_seen": "2025-06-13T00:00:00", + "risk_view": { + "crimes": { + "score": 0, + "weightage": 20, + "risk_level": "Low", + "risk_scores": {} + }, + "countries": { + "score": 0, + "weightage": 30, + "risk_level": "Low", + "risk_scores": {} + }, + "categories": { + "score": 100, + "weightage": 50, + "risk_level": "High", + "risk_scores": { + "PEP": 100 + } + }, + "custom_list": {} + }, + "first_seen": "2025-01-18T00:00:00", + "properties": { + "name": [ + "Javier Ejemplo Modelo" + ], + "alias": [ + "Javier Ejemplo Modelo", + "David Azagra" + ], + "notes": [ + "Spanish orchestra conductor" + ], + "title": null, + "gender": [ + "male" + ], + "height": null, + "topics": null, + "weight": null, + "address": null, + "country": null, + "website": null, + "eyeColor": null, + "keywords": null, + "lastName": [ + "Ejemplo", + "Modelo" + ], + "position": null, + "religion": null, + "birthDate": [ + "1974" + ], + "education": [ + "Tokyo Arts and Space (2012-2012)", + "Saint Petersburg Conservatory (-2010)", + "Comillas Pontifical University" + ], + "ethnicity": null, + "firstName": [ + "David" + ], + "hairColor": null, + "weakAlias": null, + "birthPlace": [ + "Madrid" + ], + "modifiedAt": null, + "wikidataId": null, + "citizenship": null, + "nationality": null + }, + "match_score": 98, + "risk_score": 73, + "review_status": "Unreviewed", + "score_breakdown": { + "name_score": 95, + "name_weight": 60, + "name_weight_normalized": 70.59, + "name_contribution": 67.06, + "dob_score": 100, + "dob_weight": 25, + "dob_weight_normalized": 29.41, + "dob_contribution": 29.41, + "country_score": 0, + "country_weight": 15, + "country_weight_normalized": 0, + "country_contribution": 0, + "document_number_match_type": "NEUTRAL", + "document_number_effect": "No document number provided for screening", + "total_score": 98 + }, + "pep_matches": [ + { + "aliases": [ + "Javier Ejemplo Modelo", + "David Azagra" + ], + "education": [ + "Tokyo Arts and Space (2012-2012)", + "Saint Petersburg Conservatory (-2010)", + "Comillas Pontifical University" + ], + "list_name": "Wikidata", + "publisher": "Wikidata", + "source_url": "https://news.example.org/articles/sample", + "description": "Wikidata is the structured data project of the Wikipedia community, providing fact-based information edited by humans and machines", + "matched_name": "Javier Ejemplo Modelo", + "pep_position": "", + "date_of_birth": "1974", + "other_sources": [], + "place_of_birth": "Madrid" + } + ], + "linked_entities": [ + { + "name": [ + "Antonio Ejemplo" + ], + "active": [], + "status": [], + "details": [], + "relation": [] + } + ], + "warning_matches": [], + "sanction_matches": [], + "adverse_media_details": { + "sentiment": "Moderately Negative", + "entity_type": "person", + "sentiment_score": -2, + "adverse_keywords": { + "fraud": 3, + "juicio": 1, + "delitos": 2, + "imputado": 1, + "impunidad": 1, + "corruption": 2, + "favoritism": 2, + "privilegio": 1, + "corrupción": 1, + "persecution": 2, + "desconfianza": 1, + "desprestigio": 1, + "fraude de ley": 2, + "manipulación": 1, + "investigación": 3, + "prevaricación": 8, + "trato de favor": 1, + "irregularidades": 2, + "crisis de confianza": 1, + "tráfico de influencias": 8 + } + }, + "adverse_media_matches": [ + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in legal violations related to favoritism and influence trafficking, leading to a negative portrayal in the article.", + "headline": "La Diputación de Villaejemplo consolida la plaza a medida para el ex asesor de Moncloa amigo del \"hermanito\" de Ejemplo", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "fraud", + "prevaricación", + "tráfico de influencias", + "irregularidades", + "imputado" + ], + "sentiment_score": -2, + "publication_date": "2025-06-10T20:46:38" + }, + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in legal issues regarding favoritism and corruption, leading to a negative portrayal in the article.", + "headline": "La Diputación de Villaejemplo consolida la plaza a medida para el ex asesor de Moncloa amigo del \"hermanito\" de Ejemplo", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "fraud", + "prevaricación", + "tráfico de influencias", + "irregularidades", + "trato de favor" + ], + "sentiment_score": -2, + "publication_date": "2025-06-10T20:46:38" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is mentioned in connection with Carlos Ficticio's corruption case, indicating his involvement in alleged influence peddling.", + "headline": "España, paraíso del aforado", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias", + "corrupción", + "impunidad", + "privilegio", + "desconfianza", + "manipulación", + "fraude de ley" + ], + "sentiment_score": -2, + "publication_date": "2025-06-10T05:05:34" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is implicated in the same investigation as Ficticio, which raises questions about his conduct and political ethics.", + "headline": "Aforamientos: esa figura incómoda", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias", + "investigación", + "juicio", + "fraude de ley", + "desprestigio", + "crisis de confianza", + "delitos" + ], + "sentiment_score": -2, + "publication_date": "2025-06-08T06:01:00" + }, + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in a legal investigation regarding irregularities in his hiring, which raises serious concerns about his conduct.", + "headline": "La jueza del 'caso Javier Ejemplo' denuncia un «fraude de ley» en el aforamiento de Ficticio (Partido Ejemplo)", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "fraud", + "irregularities", + "prevaricación", + "tráfico de influencias" + ], + "sentiment_score": -2, + "publication_date": "2025-06-09T10:19:10" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is mentioned in connection with legal allegations of corruption, contributing to a negative perception.", + "headline": "'Caso aforamiento': la letrada mayor de la Asamblea certifica a la juez que Ficticio es diputado desde antes de prometer su cargo en el Pleno", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias" + ], + "sentiment_score": -2, + "publication_date": "2025-06-09T08:08:30" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is implicated in the same investigation as Ficticio, raising concerns about political corruption and influence.", + "headline": "Aforamientos: esa figura incómoda", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias", + "investigación" + ], + "sentiment_score": -2, + "publication_date": "2025-06-08T06:01:00" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is implicated in the same investigation as Ficticio, which raises concerns about political misconduct.", + "headline": "Aforamientos: esa figura incómoda", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias", + "investigación", + "delitos" + ], + "sentiment_score": -2, + "publication_date": "2025-06-08T06:00:59" + }, + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in a case of favoritism and corruption, which casts a negative light on his character and actions.", + "headline": "'Caso Javier Ejemplo': las asociaciones judiciales salen en defensa de la juez Luisa Inventada ante las \"presiones externas\" que sufre", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "favoritism", + "corruption", + "persecution" + ], + "sentiment_score": -2, + "publication_date": "2025-06-06T10:08:15" + }, + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in a case of alleged favoritism and corruption, which casts a negative light on his character and actions.", + "headline": "'Caso Javier Ejemplo': las asociaciones judiciales salen en defensa de la juez Luisa Inventada ante las \"presiones externas\" que sufre", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "favoritism", + "corruption", + "persecution" + ], + "sentiment_score": -2, + "publication_date": "2025-06-06T08:08:15" + } + ], + "additional_information": {} + } + ], + "score": 80, + "screened_data": { + "full_name": "Javier Ejemplo Modelo", + "nationality": "ES", + "date_of_birth": "1974-01-01", + "document_number": null + }, + "is_ongoing_monitoring_enabled": true, + "next_ongoing_monitoring_bill_date": "2026-07-24", + "last_ongoing_screening_at": "2026-09-12T04:03:11Z", + "last_ongoing_screening_datasets_updated_at": "2026-09-11T06:00:00Z", + "warnings": [ + { + "feature": "AML", + "risk": "POSSIBLE_MATCH_FOUND", + "additional_data": null, + "log_type": "warning", + "short_description": "Possible match found in AML screening", + "long_description": "The Anti-Money Laundering (AML) screening process identified potential matches with watchlists or high-risk databases, requiring further review.", + "node_id": "feature_aml_1" + } + ] + } + ], + "ip_analyses": [ + { + "node_id": "feature_ip_1", + "status": "Approved", + "device_brand": "Apple", + "device_model": "iPhone", + "browser_family": "Mobile Safari", + "os_family": "iOS", + "platform": "mobile", + "device_fingerprint": "didit-fp-a1b2c3d4e5f6g7h8", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_state": "Barcelona", + "ip_city": "Barcelona", + "latitude": 41.4022, + "longitude": 2.1407, + "ip_address": "83.50.226.71", + "isp": null, + "organization": null, + "is_vpn_or_tor": false, + "is_data_center": false, + "time_zone": "Europe/Madrid", + "time_zone_offset": "+0100", + "ip": { + "location": { + "latitude": 40.2206327, + "longitude": 1.5770097 + }, + "distance_from_id_document": 23.4, + "distance_from_poa_document": 12.3 + }, + "id_document": { + "location": { + "latitude": 41.2706327, + "longitude": 1.9770097 + }, + "distance_from_ip": 23.4, + "distance_from_poa_document": 18.7 + }, + "poa_document": { + "location": { + "latitude": 41.2706327, + "longitude": 1.9770097 + }, + "distance_from_ip": 12.3, + "distance_from_id_document": 18.7 + }, + "warnings": [], + "matches": [ + { + "session_id": "99999999-8888-7777-6666-555555555555", + "session_number": 41205, + "vendor_data": "22222222-2222-2222-2222-222222222222", + "verification_date": "2024-07-20T10:15:30Z", + "match_type": "ip_address", + "match_source": "ip_address", + "matched_value": "83.50.226.71", + "status": "Approved", + "is_blocklisted": false, + "api_service": null, + "source": "session", + "device_info": { + "device_brand": "Apple", + "device_model": "iPhone", + "browser_family": "Mobile Safari", + "os_family": "iOS", + "platform": "mobile", + "device_fingerprint": "didit-fp-f9e8d7c6b5a40312" + }, + "location_info": { + "ip_address": "83.50.226.71", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_state": "Barcelona", + "ip_city": "Barcelona", + "is_vpn_or_tor": false, + "is_data_center": false + }, + "confidence": 0.0, + "match_mode": "co_occurrence" + }, + { + "session_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "session_number": 42830, + "vendor_data": "33333333-3333-3333-3333-333333333333", + "verification_date": "2024-06-30T18:22:05Z", + "match_type": "device_fingerprint", + "match_source": "recovered_high", + "matched_value": "f47ac10b-58cc-4372-a567-0e02b2c3d479", + "status": "Declined", + "is_blocklisted": false, + "api_service": null, + "source": "session", + "device_info": { + "device_brand": "Samsung", + "device_model": "SM-G991B", + "browser_family": "Chrome Mobile", + "os_family": "Android", + "platform": "mobile", + "device_fingerprint": null + }, + "location_info": { + "ip_address": "83.50.231.18", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_state": "Madrid", + "ip_city": "Madrid", + "is_vpn_or_tor": false, + "is_data_center": false + }, + "confidence": 1.0, + "match_mode": "deterministic", + "recovery_similarity": 0.9874, + "tls_ja4_corroborated": true, + "recovery_gate_reason": "hardware_root_match" + } + ] + } + ], + "database_validations": [ + { + "node_id": "feature_db_validation_1", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "last_name": "De Lima", + "first_name": "Fernando", + "tax_number": "01234567890", + "date_of_birth": "1980-01-01", + "document_type": "ID", + "expiration_date": "2030-01-01" + }, + "validations": [ + { + "service_id": "bra_cpf", + "service_name": "Brazil - CPF status check", + "outcome_code": "MATCH", + "validation": { + "full_name": "full_match", + "date_of_birth": "full_match", + "identification_number": "full_match" + }, + "source_data": { + "identification_number": "01234567890", + "first_name": "FERNANDO", + "last_name": "DE LIMA", + "date_of_birth": "1980-01-01", + "gender": "M", + "nationality": "BRA" + } + } + ], + "match_type": "full_match", + "status": "Approved", + "warnings": [] + } + ], + "reviews": [ + { + "user": "compliance@example.com", + "new_status": "Declined", + "comment": "Possible match found in AML screening", + "created_at": "2024-07-18T13:29:00.366811Z" + } + ], + "created_at": "2024-07-24T08:54:25.443172Z", + "expires_at": "2025-07-24T08:54:25.443172Z", + "biometric_consent": { + "required": false, + "trigger": null, + "status": "not_required", + "given_at": null, + "declined_at": null, + "text_version": null, + "text_hash": null, + "locale": null, + "policy_url": null, + "step": null, + "node_id": null, + "ip_address": null, + "ip_country_code": null, + "ip_state": null, + "ip_city": null, + "user_agent": null, + "detected_ip_address": "83.50.226.71", + "detected_country_code": "ES", + "detected_state": "ES-B", + "detected_city": "Barcelona", + "declared_state": null, + "document_state": null, + "jurisdictions": [], + "retention_months": null, + "reason": null, + "decline_reason": null, + "face_data_deleted_at": null + } + } + }, + "BusinessExample": { + "summary": "Business (KYB) session example", + "description": "A `session_kind = \"business\"` decision: KYB sessions replace the KYC feature arrays with `registry_checks`, `document_verifications` and `key_people_checks`, while sharing `aml_screenings`, `phone_verifications`, `email_verifications`, `questionnaire_responses`, `ip_analyses`, `reviews` and the session envelope.", + "value": { + "session_id": "66666666-7777-8888-9999-000000000000", + "session_kind": "business", + "session_number": 31415, + "session_url": "https://verify.didit.me/session/business123", + "status": "In Review", + "environment": "live", + "workflow_id": "abababab-cdcd-efef-0101-232323232323", + "features": [ + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE" + ], + "vendor_data": "merchant-001234", + "metadata": null, + "callback": "https://example.com/verification/callback", + "registry_checks": [ + { + "status": "Approved", + "node_id": "feature_kyb_registry_1", + "data_resolved": true, + "company": { + "uuid": "dddd4444-eeee-5555-ffff-666677778888", + "node_id": "feature_kyb_registry_1", + "status": "Approved", + "registry_status": "active", + "data_resolved": true, + "company_name": "ACME HOLDINGS LTD", + "registration_number": "12345678", + "country_code": "GB", + "region": null, + "company_type": "Private Limited Company", + "incorporation_date": "2015-03-12", + "registered_address": "1 Example Street, London, EC1A 1AA", + "tax_number": null, + "risk_level": null, + "verification_status": null, + "is_from_registry": true, + "fetch_status": "resolved", + "alternative_names": null, + "nature_of_business": "62012 - Business and domestic software development", + "registered_capital": "100 GBP", + "registered_capital_amount": "100.00", + "registered_capital_currency": "GBP", + "website": null, + "email": null, + "phone": null, + "legal_entity_identifier": null, + "location_of_registration": "England and Wales", + "financial_summary": null, + "officers": [ + { + "uuid": "aaaa1111-bbbb-2222-cccc-333344445555", + "name": "Jane Smith", + "designation": "Director", + "role": "director", + "nationality": "British", + "is_active": true, + "kyc_status": "Approved", + "kyc_session_url": "https://verify.didit.me/session/abc123def456" + } + ], + "beneficial_owners": [ + { + "uuid": "bbbb2222-cccc-3333-dddd-444455556666", + "name": "John Doe", + "first_name": "John", + "last_name": "Doe", + "entity_type": "person", + "roles": [ + "ubo", + "shareholder" + ], + "ownership_min_shares": "50", + "ownership_max_shares": "75", + "is_active": true, + "kyc_status": "Pending", + "kyc_session_url": "https://verify.didit.me/session/def456ghi789", + "effective_ownership_percent": 75.0 + } + ], + "addresses": [ + { + "type": "registered", + "address": "1 Example Street, London, EC1A 1AA", + "country": "GB" + } + ], + "industries": [ + { + "code": "62012", + "description": "Business and domestic software development" + } + ], + "accounts": null, + "registry_data": { + "addresses_detail": [ + { + "type": "registered", + "address": "1 Example Street, London, EC1A 1AA", + "country": "GB" + } + ], + "industries": [ + { + "code": "62012", + "description": "Business and domestic software development" + } + ] + }, + "user_provided_data": { + "company_name": "ACME HOLDINGS", + "registration_number": "12345678" + }, + "confirmed_by_user_at": "2026-06-10T12:01:30.000000Z", + "last_console_edit_at": null, + "is_editable": false + }, + "ownership_structure": { + "nodes": [ + { + "id": "company", + "name": "ACME HOLDINGS LTD", + "type": "company" + }, + { + "id": "ubo-1", + "name": "John Doe", + "type": "person", + "ownership_percent": 75.0 + } + ] + }, + "warnings": [] + } + ], + "aml_screenings": null, + "document_verifications": [ + { + "status": "In Review", + "node_id": "feature_kyb_documents_1", + "items": [ + { + "uuid": "eeee5555-ffff-6666-0000-777788889999", + "document_type": "LEGAL_PRESENCE", + "document_subtype": "CERTIFICATE_OF_INCORPORATION", + "document_group": "LEGAL_PRESENCE", + "file_url": "https:///kyb/certificate-of-incorporation.pdf", + "original_filename": "certificate_of_incorporation.pdf", + "file_size": 482133, + "status": "Approved", + "created_at": "2026-06-10T12:03:11.000000Z", + "cross_check_result": { + "critical": { + "company_name": { + "document": "ACME HOLDINGS LTD", + "registry": "ACME HOLDINGS LTD", + "score": 1.0, + "match": true + }, + "registration_number": { + "document": "12345678", + "registry": "12345678", + "match": true + } + }, + "non_critical": {}, + "people": {} + }, + "document_metadata": { + "file_size": 482133, + "content_type": "application/pdf", + "creation_date": "2015-03-12T09:00:00", + "modified_date": "2015-03-12T09:00:00", + "creator": null, + "producer": null, + "software": null, + "encryption": null, + "is_signed": true, + "is_tampered": false, + "signature_info": null, + "exif_original_date": null, + "exif_digitized_date": null, + "processed_by_known_editor": false, + "has_different_creation_mod_date": false, + "overlay_manipulation": null + }, + "description": null, + "is_requested": false, + "ocr_data": { + "company_name": "ACME HOLDINGS LTD", + "registration_number": "12345678", + "incorporation_date": "2015-03-12" + }, + "is_console_upload": false, + "is_confirmed": true + }, + { + "uuid": "ffff6666-0000-7777-1111-888899990000", + "document_type": "OWNERSHIP_STRUCTURE", + "document_subtype": null, + "document_group": "OWNERSHIP_STRUCTURE", + "file_url": null, + "original_filename": null, + "file_size": null, + "status": "Not Finished", + "created_at": "2026-06-10T15:40:02.000000Z", + "cross_check_result": null, + "document_metadata": null, + "description": "Please upload an up-to-date shareholder registry", + "is_requested": true, + "ocr_data": null, + "is_console_upload": false, + "is_confirmed": true + } + ], + "groups": { + "LEGAL_PRESENCE": { + "total": 1, + "approved": 1, + "pending": 0, + "declined": 0, + "in_review": 0, + "other": 0, + "missing": 0 + }, + "OWNERSHIP_STRUCTURE": { + "total": 1, + "approved": 0, + "pending": 0, + "declined": 0, + "in_review": 0, + "other": 0, + "missing": 1 + } + }, + "required_groups": [ + "LEGAL_PRESENCE", + "OWNERSHIP_STRUCTURE" + ], + "warnings": [] + } + ], + "key_people_checks": [ + { + "status": "In Review", + "node_id": "feature_kyb_key_people_1", + "registry": { + "officers": [ + { + "uuid": "aaaa1111-bbbb-2222-cccc-333344445555", + "name": "Jane Smith", + "designation": "Director", + "role": "director", + "nationality": "British", + "appointment_date": "2015-03-12", + "termination_date": null, + "is_active": true, + "address": "1 Example Street, London, EC1A 1AA", + "aml_status": "Approved", + "kyc_status": "Approved", + "kyc_session_url": "https://verify.didit.me/session/abc123def456", + "kyc_session_id": "77777777-8888-9999-aaaa-bbbbbbbbbbbb", + "verification_workflow_type": "kyc", + "verification_workflow_id": "12121212-3434-5656-7878-909090909090", + "verification_workflow_label": "Key People KYC", + "verification_workflow_features": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "didit_internal_id": "99999999-0000-1111-2222-333333333333", + "kyc_document_type": "P", + "kyc_nationality": "GBR", + "kyc_liveness_passed": true, + "kyc_verified_at": "2026-06-10T14:22:31.123456Z", + "kyc_aml_categories": { + "sanctions": "clear", + "pep": "clear", + "adverse_media": "clear", + "warnings": "clear" + }, + "kyc_notification_sent_at": "2026-06-09T09:00:00.000000Z" + } + ], + "beneficial_owners": [ + { + "uuid": "bbbb2222-cccc-3333-dddd-444455556666", + "name": "John Doe", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-05-04", + "country_of_birth": null, + "email": null, + "company_name": null, + "registration_number": null, + "entity_type": "person", + "nationality": "British", + "designation": null, + "roles": [ + "ubo", + "shareholder" + ], + "ownership_min_shares": "50", + "ownership_max_shares": "75", + "is_active": true, + "aml_status": null, + "kyc_status": "In Progress", + "kyc_session_url": "https://verify.didit.me/session/def456ghi789", + "kyc_session_id": "88888888-9999-aaaa-bbbb-cccccccccccc", + "verification_workflow_type": "kyc", + "verification_workflow_id": "12121212-3434-5656-7878-909090909090", + "verification_workflow_label": "Key People KYC", + "verification_workflow_features": [ + { + "feature": "ID_VERIFICATION", + "status": "Not Finished" + }, + { + "feature": "LIVENESS", + "status": "Not Finished" + }, + { + "feature": "AML", + "status": "Not Finished" + } + ], + "didit_internal_id": null, + "kyc_document_type": null, + "kyc_nationality": null, + "kyc_liveness_passed": null, + "kyc_verified_at": null, + "kyc_aml_categories": null, + "kyc_notification_sent_at": null, + "child_beneficial_owners": [], + "effective_ownership_percent": 75.0 + } + ] + }, + "submitted": { + "parties": [ + { + "uuid": "cccc3333-dddd-4444-eeee-555566667777", + "entity_type": "person", + "source": "USER", + "name": "John Doe", + "first_name": "John", + "last_name": "Doe", + "company_name": null, + "registration_number": null, + "email": "john.doe@example.com", + "nationality": "GBR", + "phone_number": "+447700900123", + "date_of_birth": "1980-05-04", + "position": "Chief Executive Officer", + "custom_fields": {}, + "is_active": true, + "is_skipped": false, + "requires_verification": true, + "roles": [ + { + "role": "ubo", + "ownership_percent": 75.0, + "voting_percent": 75.0 + }, + { + "role": "director", + "ownership_percent": null, + "voting_percent": null + } + ], + "kyc_session_id": "88888888-9999-aaaa-bbbb-cccccccccccc", + "kyc_session_status": "In Progress", + "kyc_session_url": "https://verify.didit.me/session/def456ghi789", + "kyb_sub_session_id": null, + "kyb_sub_session_status": null, + "kyb_sub_session_url": null, + "verification_workflow_type": "kyc", + "verification_workflow_id": "12121212-3434-5656-7878-909090909090", + "verification_workflow_label": "Key People KYC", + "verification_workflow_features": [ + { + "feature": "ID_VERIFICATION", + "status": "Not Finished" + }, + { + "feature": "LIVENESS", + "status": "Not Finished" + }, + { + "feature": "AML", + "status": "Not Finished" + } + ], + "didit_internal_id": null + } + ] + }, + "ubo_kyc_summary": { + "total": 1, + "approved": 0, + "flagged": 0, + "pending": 1 + }, + "warnings": [] + } + ], + "phone_verifications": null, + "email_verifications": null, + "questionnaire_responses": null, + "ip_analyses": null, + "reviews": [], + "contact_details": null, + "expected_details": { + "company_name": "Tesco PLC", + "registry_country": "GB", + "registration_number": "00445790" + }, + "created_at": "2026-06-10T11:58:04.000000Z", + "expires_at": "2026-06-24T11:58:04.000000Z" + } + } + }, + "schema": { + "$ref": "#/components/schemas/SessionDecision" + } + } + } + }, + "403": { + "description": "Authentication or authorization failed. **This endpoint never returns `401`** — Didit's API-key authentication does not emit a `WWW-Authenticate` challenge, so missing or invalid credentials also surface as `403`. Two `detail` strings distinguish the failure modes:\n\n- `\"Authentication credentials were not provided or are invalid.\"` — no `x-api-key` header (and no `Authorization: Bearer` token), or the supplied credential failed introspection (revoked, expired, or malformed).\n- `\"You do not have permission to perform this action.\"` — the credential is valid but not allowed to read this session: a console-user Bearer token lacks the `read:sessions` permission or cannot access the owning application, or an API key belongs to a different organization than the session.\n\nNote that for a completely unknown `sessionId` the endpoint resolves the owning application before validating credentials, so a missing session returns `404` even when the credential is missing or invalid.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Authentication credentials were not provided or are invalid." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed introspection", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Insufficient permissions": { + "summary": "Valid credential lacking read:sessions or access to the owning application", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Session not found. Returned when no live session matches the supplied `sessionId` as either a User Verification (KYC) or a Business Verification (KYB) session — a session that has been deleted also lands here. Body is the standard `{\"detail\": ...}` envelope.\n\n**Two distinct `detail` strings.** If the `sessionId` is unknown as both a KYC and a KYB session, the pre-auth owner lookup fails and you receive `{\"detail\": \"Not found.\"}` — this happens before credential validation, so it is returned even for unauthenticated calls. If the session exists but cannot be retrieved under the authenticated application (for example a stale or just-deleted record), the view returns `{\"detail\": \"Session not found.\"}`. Both indicate the same outcome — the session is not accessible — so match on the `404` status code rather than the string.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Not found." + } + } + }, + "examples": { + "Unknown session id": { + "summary": "sessionId is unknown to this application", + "value": { + "detail": "Not found." + } + }, + "Session not retrievable": { + "summary": "Session exists but is not retrievable for this application", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. GET requests are limited to **600 per minute per credential** (API key, Bearer token, or source IP when unauthenticated). Inspect `Retry-After` / `X-RateLimit-Reset` and back off before retrying. If you hit this limit while polling for decisions, switch to the `status.updated` webhook and fetch the decision once per status change instead.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying the request.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Maximum number of requests allowed in the current window (600 for GET requests).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests remaining in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix timestamp (seconds) at which the current window resets.", + "schema": { + "type": "integer", + "example": 1750000000 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable explanation of the rate-limit breach.", + "example": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + }, + "examples": { + "Throttled": { + "summary": "More than 600 GET requests in one minute from the same credential", + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session whose decision you want to retrieve. This is the `session_id` returned by `POST /v3/session/`. The same path works for both User Verification (KYC) and Business Verification (KYB) sessions — the server resolves the id against both session types.\n\n**Must be a valid UUID.** The route only matches well-formed UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below. Validate the UUID format client-side so you can distinguish an input error from a genuine missing-session `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "include", + "in": "query", + "required": false, + "description": "Comma-separated list of extra payload sections. Supported values: `events` and `resubmissions`, alone or together (`events,resubmissions`). When `include=events` is passed, the response additionally contains the session event timeline (`events`, including the `WORKFLOW_BRANCH_EVALUATED` / `WORKFLOW_STATUS_NODE_REACHED` routing events described on the Retrieve Session page), the full activity log (`activity_reviews`), blocklist flags (`blocklisted`), a per-session `cost_breakdown`, workflow metadata (`workflow_type`, `workflow_version`, `workflow_version_id`, and `workflow_terminal_node_id` - the last recorded workflow status node that set a session status, or `null` if none has been recorded; historical provenance that is not cleared by manual approvals, declines or resubmissions, and is replaced by a later workflow status node), and session metadata (`api_service` — string or `null`, set only for standalone-API sessions; `session_type` — `API`, `HOSTED`, or `MIGRATED`; `has_device_nfc_support` — boolean). Two fields change relative to the plain shape: the top-level `session_kind` discriminator is **omitted** (the events shape only exists for user sessions), and the `features` array switches from plain strings to `{feature, node_id}` objects — where the email feature is reported as `EMAIL` instead of `EMAIL_VERIFICATION`. This expanded shape is primarily used by the Didit Console; most API integrations should omit the parameter. **KYB limitation:** `include=events` only applies to user (KYC) sessions. Business sessions (`session_kind = \"business\"`) always return the standard business decision shape — `events`, `activity_reviews`, `blocklisted`, `cost_breakdown` and the extra `workflow_*`/session metadata are never returned for business sessions, and their `features` array stays a plain string array. With `include=resubmissions` (user and business sessions alike) the response also contains `resubmissions`: one entry per resubmission request, oldest first, each with `number`, `created_at`, `previous_status`, `nodes`, `reasons`, `requested_by` and `results` (what the resubmitted steps returned before the request, under the same keys and in the same shape as the current results). Each `cost_breakdown` item carries `usage_type`, `price`, `details` and `created_at`: a resubmitted step that runs again is billed as a new item.", + "schema": { + "type": "string", + "enum": [ + "events", + "resubmissions", + "events,resubmissions" + ], + "example": "events" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/decision/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Accept: application/json'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nsession_id = \"11111111-2222-3333-4444-555555555555\"\nurl = f\"https://verification.didit.me/v3/session/{session_id}/decision/\"\nheaders = {\n 'x-api-key': 'YOUR_API_KEY',\n \"Accept\": \"application/json\",\n}\n\nresponse = requests.get(url, headers=headers, timeout=15)\nresponse.raise_for_status()\ndecision = response.json()\n\nprint(\"session_kind:\", decision[\"session_kind\"])\nprint(\"top-level status:\", decision[\"status\"])\n\n# nfc_verifications is ALWAYS an array — never a singular `nfc` field.\nfor nfc in decision.get(\"nfc_verifications\") or []:\n print(\"nfc node_id:\", nfc[\"node_id\"], \"status:\", nfc[\"status\"])\n\nfor id_check in decision.get(\"id_verifications\") or []:\n print(\"id node_id:\", id_check[\"node_id\"], \"status:\", id_check[\"status\"])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const sessionId = '11111111-2222-3333-4444-555555555555';\nconst response = await fetch(\n `https://verification.didit.me/v3/session/${sessionId}/decision/`,\n {\n method: 'GET',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n Accept: 'application/json',\n },\n }\n);\n\nif (!response.ok) {\n throw new Error(`Decision fetch failed: ${response.status}`);\n}\nconst decision = await response.json();\n\nconsole.log('session_kind:', decision.session_kind);\nconsole.log('top-level status:', decision.status);\n\n// nfc_verifications is ALWAYS an array — never a singular `nfc` field.\nfor (const nfc of decision.nfc_verifications ?? []) {\n console.log('nfc node_id:', nfc.node_id, 'status:', nfc.status);\n}\n\nfor (const idCheck of decision.id_verifications ?? []) {\n console.log('id node_id:', idCheck.node_id, 'status:', idCheck.status);\n}" + } + ] + } + }, + "/v3/session/{sessionId}/delete/": { + "delete": { + "summary": "Delete a verification session (KYC or KYB)", + "description": "Delete a single verification session, User Verification (KYC) or Business Verification (KYB), by its `session_id`. The id is resolved against KYC sessions first, then KYB sessions, so both kinds are deleted through this one URL.\n\n**What is deleted:**\n- The session and every verification attempt it holds, including results that a resubmission replaced: the decision, extracted data, all related feature records (ID verification, face, liveness, face match, AML, registry checks, IP analysis, and so on), its reviews and its activity log.\n- All stored files owned by the session: document front/back photos (full, cropped, and privacy-blurred variants), document-capture videos, portrait crops, NFC chip portrait and signature images, face reference images, liveness videos, face-match source/target images, Proof of Address documents, and any extra uploaded files (KYC); uploaded company documents and extra files (KYB). Electronic Signature documents, signed PDFs, evidence PDFs and signature images (KYC and KYB): their download paths stop working. Previously issued media URLs (`https:///...`) stop resolving.\n- The session's face data. Its face embedding is deleted too, unless you choose to keep an image-free template (see **Face biometric data**).\n- Webhook deliveries still queued for the session, and their retries. Webhooks already delivered are not affected.\n- For a Business Verification (KYB) session: the verifications it created for its key people (KYC) and its corporate owners (KYB), recursively, reported as `deleted_child_verifications`. A verification it only links to, such as an existing approved KYC it reused, is not deleted.\n\nThe session disappears from every read endpoint immediately: `GET /v3/session/{sessionId}/decision/` returns `404` and `GET /v3/sessions/` stops listing it. Ongoing AML monitoring and KYB registry monitoring started by the deleted verifications stop.\n\n**What is not affected:** blocklist entries created from this session (face or document) are **not** removed; manage those with the blocklist endpoints. Share tokens issued for the session are not revoked, but there is nothing left for them to import: an import with one returns `400` with `{\"share_token\": [\"Original session does not exist.\"]}`. Credits already consumed are **not** refunded. The parent User or Business entity is **not** deleted; use `POST /v3/users/delete/` or `POST /v3/businesses/delete/` for those. If the User's profile portrait came from this session, the portrait is removed with it.\n\n**Face biometric data:** by default the session's face embedding is deleted with the session (`face_retention_outcome: deleted`). An application that opted in to biometric-template retention (`face_retention_policy: retain_until_user_deleted` on `PATCH /v3/webhook/`), or a request with `retain_face_embeddings: true`, keeps one separately managed, image-free face biometric template anchored to the session's User (`retained_with_user`) so that duplicate detection, Face Search, and biometric authentication keep working. The template never contains the face image, media, identity fields, the decision, or the deleted session's id; it is biometric data deleted at `biometric_template_expires_at`, or earlier on User deletion, privacy erasure, or an explicit purge through `/v3/biometric-templates/`. A session without a linked User cannot retain a template (`ineligible_no_vendor_user`). Send `deletion_instruction: privacy_erasure` for data-subject requests: it purges every retained template for that User and never retains a new one.\n\n**Irreversible:** deletion is permanent and there is no restore endpoint. Export anything you need (for example a decision PDF) before you call it.\n\n**Side effects:** no webhook is emitted for deletions.\n\n**Response:** `200 OK` once the deletion has completed, with a JSON body reporting the `face_retention_outcome`, the retained template's expiry and, for a KYB session, `deleted_child_verifications`. Earlier versions of this endpoint returned `204 No Content`; update clients that assert on `204`. Keep your `instruction_id` in your own erasure log.\n\n**Idempotency:** not idempotent at the HTTP level: the first call returns `200`; repeating it returns `404` because the session no longer resolves. After a `503` the session is still in place; repeat the same request to finish deleting it.\n\n**Authentication:** send your application's API key in the `x-api-key` header; the session must belong to that application. Console user access tokens (`Authorization: Bearer ...`) may also call this endpoint when they carry the `delete:sessions` permission. Authentication and permission failures both return `403`; this API never returns `401`.\n\n**Rate limit:** shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.\n\nTo bulk-delete KYC sessions by their numeric `session_number`, use `POST /v3/sessions/delete/` instead.\n\n**Filed regulatory reports:** if the selected data is in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "delete_v3_session_by_id", + "x-didit-sdk": { + "resource": "sessions", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID (`session_id`) of the User Verification (KYC) or Business Verification (KYB) session to delete, as returned when the session was created. Must be a canonical hyphenated UUID — a non-UUID value does not match the route and returns `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "responses": { + "200": { + "description": "Session deleted. The body reports what happened to the session's face biometric data and, for a KYB session, how many verifications it created were deleted with it.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The deleted session." + }, + "session_number": { + "type": "integer", + "nullable": true, + "description": "The deleted session's number." + }, + "face_retention_outcome": { + "type": "string", + "enum": [ + "retained_with_user", + "deleted", + "none", + "ineligible_no_vendor_user" + ], + "description": "What happened to the session's face biometric data. `deleted`: the face embedding was deleted with the session (default). `retained_with_user`: the session is deleted and one image-free biometric template stays anchored to the User; see `biometric_template_uuid`. `none`: the session had no face embedding (for example a KYB session, or a KYC session without a liveness selfie). `ineligible_no_vendor_user`: retention was requested but the session has no linked User, so the embedding was deleted with the session. A biometric-store failure is never reported through this field on this endpoint: it returns `503` and the session is not deleted." + }, + "biometric_template_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Id of the retained biometric template when `face_retention_outcome` is `retained_with_user`; `null` otherwise. Manage it through `/v3/biometric-templates/{template_uuid}/`." + }, + "biometric_template_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the retained biometric template is deleted, when `face_retention_outcome` is `retained_with_user`; `null` when no template is retained." + }, + "deleted_child_verifications": { + "type": "integer", + "description": "For a Business Verification (KYB) session: how many verifications it created (its key people's KYC sessions and its corporate owners' KYB sessions, recursively) were deleted with it. Always `0` for a KYC session." + } + } + }, + "examples": { + "Deleted (default)": { + "value": { + "session_id": "4c5c7f3a-3b1d-4b7e-9c39-2b4f7e6a1d02", + "session_number": 1024, + "face_retention_outcome": "deleted", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + } + }, + "Retained with User": { + "value": { + "session_id": "4c5c7f3a-3b1d-4b7e-9c39-2b4f7e6a1d02", + "session_number": 1024, + "face_retention_outcome": "retained_with_user", + "biometric_template_uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "biometric_template_expires_at": "2027-10-09T10:15:00Z", + "deleted_child_verifications": 0 + } + }, + "Ineligible: no User": { + "summary": "Retention requested on a session created without vendor_data", + "value": { + "session_id": "7a1e9c2d-5f4b-4c3a-8e2d-1b0a9f8e7d6c", + "session_number": 1031, + "face_retention_outcome": "ineligible_no_vendor_user", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + } + }, + "KYB session": { + "value": { + "session_id": "0f3c9b7e-2d1a-4e5f-8c6b-7a9d8e1f2c3b", + "session_number": 88, + "face_retention_outcome": "none", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 3 + }, + "summary": "A business verification deleted with the three verifications it created for its key people" + } + } + } + } + }, + "400": { + "description": "Validation error — nothing is deleted.", + "content": { + "application/json": { + "examples": { + "Privacy erasure cannot retain": { + "value": { + "retain_face_embeddings": [ + "Privacy erasure cannot retain biometric templates." + ] + } + }, + "No finite retention duration": { + "summary": "Retention requested without face_retention_days on the request or the application", + "value": { + "detail": "A finite biometric-template retention duration is required." + } + } + } + } + } + }, + "403": { + "description": "Authentication or permission failure. Missing/invalid credentials also return `403` — this API never returns `401`. Exception: when the `sessionId` does not resolve to a live session, the pre-auth owner lookup returns `404` first, even with missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid API key": { + "summary": "No x-api-key header, or the key is invalid", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Insufficient permission": { + "summary": "User token without delete:sessions, or credentials for an application that does not own the session", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No live KYC or KYB session with this `session_id` exists — the id is unknown or the session was already deleted (repeating a successful delete lands here).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not Found": { + "summary": "Unknown or already-deleted session", + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Shared write rate limit exceeded (300 POST/PATCH/DELETE requests per minute per API key). Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "Write budget exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + }, + "503": { + "description": "The deletion could not finish because a storage service, a biometric store, or a monitoring provider was unavailable. The session is still in place; repeat the same request until it returns `200`. Some of its stored files or its face embedding may already be deleted, so do not rely on them after a `503`. Retries converge on a single template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Session deletion unavailable": { + "summary": "A storage service or a monitoring provider was unavailable", + "value": { + "detail": "The session could not be deleted because a storage service or a monitoring provider is unavailable. The session is still in place; repeat the request to finish deleting it." + } + }, + "Biometric store unavailable": { + "summary": "A biometric store was unavailable while retaining or purging a template", + "value": { + "detail": "The biometric-template operation could not finish because an external store is unavailable. The operation is retryable." + } + } + } + } + } + }, + "409": { + "description": "The session is part of a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl (default policy)", + "source": "curl -X DELETE \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/delete/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "curl", + "label": "curl (retain template)", + "source": "curl -X DELETE \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/delete/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"retain_face_embeddings\": true, \"face_retention_days\": 365}'" + }, + { + "lang": "curl", + "label": "curl (privacy erasure)", + "source": "curl -X DELETE \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/delete/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"deletion_instruction\": \"privacy_erasure\", \"instruction_id\": \"dsar-2026-0142\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.delete(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/delete/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n json={\"retain_face_embeddings\": True, \"face_retention_days\": 365},\n)\nresponse.raise_for_status()\nprint(response.json()[\"face_retention_outcome\"])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch(\n 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/delete/',\n {\n method: 'DELETE',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY, 'Content-Type': 'application/json' },\n body: JSON.stringify({ deletion_instruction: 'privacy_erasure', instruction_id: 'dsar-2026-0142' }),\n },\n);\nconst body = await resp.json();\nconsole.log(body.face_retention_outcome);" + } + ], + "requestBody": { + "required": false, + "description": "Optional. Omit the body entirely to follow the application's retention policy with an operational deletion.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "retain_face_embeddings": { + "type": "boolean", + "nullable": true, + "description": "Override the application's `face_retention_policy` for this deletion. `true` keeps one image-free face biometric template anchored to the session's User after the session is deleted; `false` deletes the face embedding with the session; omit or `null` to follow the application policy. Ignored for KYB sessions and for sessions without a face embedding.", + "example": true + }, + "face_retention_days": { + "type": "integer", + "minimum": 1, + "maximum": 3650, + "description": "Finite retention duration, in days, for a retained template. Required when a template is retained unless the application already sets `face_retention_days`. The template's `expires_at` is the earliest of this duration, the application's general data-retention window, and `face_retention_deadline`.", + "example": 365 + }, + "face_retention_deadline": { + "type": "string", + "format": "date-time", + "description": "Optional hard expiry for a retained template. Must be in the future.", + "example": "2027-08-28T00:00:00Z" + }, + "deletion_instruction": { + "type": "string", + "enum": [ + "operational_session_delete", + "privacy_erasure" + ], + "default": "operational_session_delete", + "description": "The class of instruction you are giving. `operational_session_delete` deletes the session and retains a template only when the policy or `retain_face_embeddings` says so. `privacy_erasure` purges every retained biometric template anchored to the session's User before deleting the session, cannot be overridden by the application policy, and cannot be combined with `retain_face_embeddings: true` (`400`).", + "example": "operational_session_delete" + }, + "instruction_id": { + "type": "string", + "maxLength": 255, + "description": "Your durable reference for this deletion instruction (for example an erasure-ticket id). Recorded on the audit trail and on any retained template. Generated by Didit when omitted.", + "example": "dsar-2026-0142" + } + } + }, + "examples": { + "Retain a biometric template": { + "summary": "Delete the session, keep an image-free template for 365 days", + "value": { + "retain_face_embeddings": true, + "face_retention_days": 365, + "instruction_id": "post-approval-cleanup-2026-08" + } + }, + "Force deletion on an opted-in application": { + "summary": "Delete the face embedding regardless of the application policy", + "value": { + "retain_face_embeddings": false + } + }, + "Privacy erasure": { + "summary": "Data-subject request: delete the session and purge every retained template for that person", + "value": { + "deletion_instruction": "privacy_erasure", + "instruction_id": "dsar-2026-0142" + } + } + } + } + } + } + } + }, + "/v3/session/{sessionId}/generate-pdf/": { + "get": { + "summary": "Download a PDF report for a verification session", + "description": "Render and download a compliance-ready PDF report for a finished verification session. The same path serves both User Verification (KYC) and Business Verification (KYB) sessions — the server resolves the `sessionId` against both session types and picks the matching report template.\n\n**Eligible statuses.** The session must already carry a final or reviewable status:\n\n- KYC sessions: `Approved`, `Declined`, `In Review`, or `Kyc Expired`.\n- KYB sessions: `Approved`, `Declined`, or `In Review`.\n\nAny other status (`Not Started`, `In Progress`, `Expired`, `Abandoned`, …) returns `403` with an explanatory `detail` string — see the `403` response below.\n\n**What the report contains.** The PDF mirrors the console session view at the moment of the request:\n\n- **KYC** — rolled-up session status, the warnings list, and one section per executed feature: ID Verification (document images plus extracted fields), NFC, Liveness, Face Match, Email Verification, Phone Verification, Proof of Address, Questionnaire answers, Database Validation, AML Screening, and IP Analysis — followed by session tags, console review activity (comments and status changes), and the session event timeline.\n- **KYB** — registry checks (company data), AML screenings, business document verifications, key-people checks, questionnaire responses, phone/email verifications, IP analyses, and the session event timeline, followed by tags and review activity.\n\n**Branding.** If your application has white-label customization, the report is rendered with your logo and links to your privacy-policy URL; otherwise it carries Didit branding. Reports are rendered in English — there is no language parameter.\n\n**No caching.** Every call re-renders the PDF from the current session data, so a report generated after a manual review reflects the reviewer’s decision. Two calls for the same session can produce byte-different files — archive the downloaded file if you need an immutable copy.\n\n**Latency.** Rendering downloads every stored image (document sides, selfies, extra files) before composing the PDF, so media-heavy sessions can take several seconds. Use a generous client read timeout (60 s recommended) and stream the body to disk.\n\n**Trailing slash.** The canonical route ends with a trailing slash (`…/generate-pdf/`). A request without it is served directly with the same response — there is no `301` redirect, so no client has to follow redirects and `curl` does not need `-L`. Use the slashed URL as in the samples.\n\nIf you need the underlying data as JSON instead of a rendered report, use `GET /v3/session/{sessionId}/decision/`. For every reportable session of one user in a single document, use `GET /v3/users/{vendor_data}/generate-pdf/`.", + "operationId": "get_v3_session_generate_pdf", + "x-didit-sdk": { + "resource": "sessions", + "method": "generate_pdf", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) or Business Verification (KYB) session to render — the `session_id` returned by `POST /v3/session/`. The same path works for both session kinds; use the `Content-Disposition` filename on the response to tell which report type you received.\n\n**Must be a well-formed, lowercase UUID.** The route only matches valid UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below. The route's UUID converter only matches the canonical lowercase form — uppercase-hex UUIDs are rejected with the HTML 404.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "responses": { + "200": { + "description": "The rendered PDF document, returned directly as binary `application/pdf` — there is no JSON wrapper and no download-URL indirection. The body starts with the `%PDF` magic bytes (PDF 1.7). Save it to a `.pdf` file or stream it through to your caller.", + "headers": { + "Content-Disposition": { + "description": "`attachment; filename=session_{sessionId}.pdf` for KYC sessions, `attachment; filename=business_{sessionId}.pdf` for KYB sessions — the prefix tells you which report template was rendered.", + "schema": { + "type": "string", + "example": "attachment; filename=session_11111111-2222-3333-4444-555555555555.pdf" + } + }, + "Content-Length": { + "description": "Total size of the PDF in bytes; the body is not chunked.", + "schema": { + "type": "integer", + "example": 25191 + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "403": { + "description": "Authentication, authorization, or session-status failure. **This endpoint never returns `401`** — Didit's API-key authentication does not emit a `WWW-Authenticate` challenge, so missing or invalid credentials also surface as `403`. Distinguish the failure modes by the `detail` string:\n\n- `\"Authentication credentials were not provided or are invalid.\"` — no `x-api-key` header (and no `Authorization: Bearer` token), or the supplied credential failed introspection (revoked, expired, or malformed).\n- `\"You do not have permission to perform this action.\"` — the credential is valid but cannot read this session: a console-user Bearer token lacks the `read:sessions` permission or cannot access the owning application, or an API key belongs to a different organization than the session.\n- `\"You can only generate a PDF for sessions in review, declined, approved or kyc expired\"` — the KYC session exists but is not in an eligible status yet (for example `Not Started` or `In Progress`). Wait for the `status.updated` webhook before requesting the report.\n- `\"You can only generate a PDF for sessions in review, declined or approved\"` — same condition for KYB sessions.\n\nNote that for a completely unknown `sessionId` the endpoint resolves the owning application before validating credentials, so a missing session returns `404` even when the credential is missing or invalid.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You can only generate a PDF for sessions in review, declined, approved or kyc expired" + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed introspection", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Insufficient permissions": { + "summary": "Valid credential lacking read:sessions or access to the owning application", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "KYC session not finished": { + "summary": "KYC session still Not Started / In Progress", + "value": { + "detail": "You can only generate a PDF for sessions in review, declined, approved or kyc expired" + } + }, + "KYB session not finished": { + "summary": "KYB session still Not Started / In Progress", + "value": { + "detail": "You can only generate a PDF for sessions in review, declined or approved" + } + } + } + } + } + }, + "404": { + "description": "No active session with this `sessionId` exists — it was never created, or it has been deleted. Owner resolution happens before credential validation, so this `404` is returned even for unauthenticated requests. Depending on which lookup misses, the `detail` string is `\"Not found.\"` (the common case) or `\"Session not found.\"`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Not found." + } + } + }, + "examples": { + "Unknown session": { + "summary": "sessionId does not match any KYC or KYB session", + "value": { + "detail": "Not found." + } + }, + "Recently deleted session": { + "summary": "Session was deleted between lookups", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. PDF generation has a dedicated limit of **50 requests per minute per credential** (API key, Bearer token, or source IP when unauthenticated), in addition to the global 600/min GET limit. Wait `Retry-After` seconds (also reflected in the `X-RateLimit-*` headers), then retry. Reports are not cached server-side, so download each report once and store the file instead of re-fetching it.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying the request.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Maximum number of PDF generations allowed in the current window (50).", + "schema": { + "type": "integer", + "example": 50 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests remaining in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix timestamp (seconds) at which the current window resets.", + "schema": { + "type": "integer", + "example": 1750000000 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable explanation of the rate-limit breach.", + "example": "Session PDF generation rate limit exceeded. You can make up to 50 requests per minute." + } + } + }, + "examples": { + "Throttled": { + "summary": "More than 50 PDF generations in one minute from the same credential", + "value": { + "detail": "Session PDF generation rate limit exceeded. You can make up to 50 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --fail \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/generate-pdf/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n --output report.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/generate-pdf/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n stream=True,\n timeout=60,\n)\nresponse.raise_for_status()\nwith open(\"report.pdf\", \"wb\") as fh:\n for chunk in response.iter_content(chunk_size=8192):\n fh.write(chunk)" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "import { writeFile } from 'node:fs/promises';\n\nconst response = await fetch(\n 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/generate-pdf/',\n { headers: { 'x-api-key': 'YOUR_API_KEY' } },\n);\nif (!response.ok) throw new Error(`PDF generation failed: HTTP ${response.status}`);\nawait writeFile('report.pdf', Buffer.from(await response.arrayBuffer()));" + } + ] + } + }, + "/v3/session/{sessionId}/sandbox/arm/": { + "post": { + "summary": "Arm a sandbox scenario on a session", + "description": "Persist a sandbox scenario on a `Not Started` session and expand its bundled magic values (email, phone, or `expected_details` fields) onto the session, so the mocked providers reproduce the scenario's outcome once the flow runs. This is the endpoint the pre-flow scenario picker in Didit's hosted verification UI calls when a tester chooses an outcome before starting capture. Build your own picker against it, or call it directly from a sandbox test harness.\n\nAuthenticate with the session's own `Session-Token` (returned in `session_token` by [Create Session](/sessions-api/create-session)), not your application `x-api-key`: this endpoint is meant to be called from the client, not your backend. A scenario is only accepted while `status` is `Not Started`. Calling it again while still `Not Started` re-arms the session: it swaps in the new scenario's magic values and clears only the values the *previous* scenario wrote, so any value the user already typed by hand (email, first name, etc.) survives the re-arm. Arming a session that has moved past `Not Started`, or that belongs to a live (non-sandbox) application, is rejected.\n\nSee [Sandbox & Test Data](/integration/sandbox-testing) for the full scenario catalog and the other two ways to drive an outcome: `sandbox_scenario` at session create, and typing magic values directly.", + "operationId": "post_v3_session_sandbox_arm", + "x-didit-sdk": { + "resource": "sessions", + "method": "arm_sandbox", + "auth": "device_token", + "server": false + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "SessionTokenAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the sandbox verification session to arm. Must match the session encoded in the Session-Token used to authenticate.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "scenario" + ], + "properties": { + "scenario": { + "type": "string", + "description": "Slug of the scenario to arm. See the catalog at GET /v1/sandbox/scenarios/ or the scenario table in [Sandbox & Test Data](/integration/sandbox-testing).", + "example": "decline_document_expired" + } + } + }, + "examples": { + "Arm": { + "summary": "Arm the AML PEP-hit scenario", + "value": { + "scenario": "decline_aml_hit" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Scenario armed. Its magic values are now on the session. `status` reflects the session's current status, which this call never changes: it is always `Not Started` on success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid" + }, + "scenario": { + "type": "string", + "description": "The armed scenario slug (echoes the request)." + }, + "status": { + "type": "string", + "example": "Not Started" + } + } + }, + "examples": { + "Armed": { + "summary": "Scenario armed on a Not Started session", + "value": { + "session_id": "11111111-2222-3333-4444-555555555555", + "scenario": "decline_aml_hit", + "status": "Not Started" + } + } + } + } + } + }, + "400": { + "description": "Validation error: an unknown scenario slug, or the session has already progressed past `Not Started`.", + "content": { + "application/json": { + "examples": { + "Unknown Scenario": { + "summary": "scenario is not in the catalog", + "value": { + "scenario": [ + "Unknown sandbox scenario: 'not-a-real-scenario'." + ] + } + }, + "Already Started": { + "summary": "Session moved past Not Started", + "value": [ + "Scenario can only be armed before the verification starts." + ] + } + } + } + } + }, + "404": { + "description": "The Session-Token does not resolve to this `session_id`, or the session belongs to a live (non-sandbox) application.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Live session, or session_id / token mismatch", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/sandbox/arm/ \\\n -H 'Session-Token: YOUR_SESSION_TOKEN' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"scenario\": \"decline_aml_hit\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/sandbox/arm/\",\n headers={\n \"Session-Token\": \"YOUR_SESSION_TOKEN\",\n \"Content-Type\": \"application/json\",\n },\n json={\"scenario\": \"decline_aml_hit\"},\n)\nresponse.raise_for_status()\nprint(response.json()) # {'session_id': '...', 'scenario': 'decline_aml_hit', 'status': 'Not Started'}" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const response = await fetch(\n 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/sandbox/arm/',\n {\n method: 'POST',\n headers: {\n 'Session-Token': sessionToken,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ scenario: 'decline_aml_hit' }),\n },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst { session_id, scenario, status } = await response.json();" + } + ] + } + }, + "/v3/session/{sessionId}/update-status/": { + "patch": { + "summary": "Approve, decline, or request resubmission for a verification session", + "description": "Manually override the final decision of a KYC or KYB verification session. Set `new_status` to `Approved` or `Declined` to record a manual decision, or to `Resubmitted` to clear specific verification steps and let the user redo them through the same verification URL. The endpoint accepts both user (KYC) and business (KYB) session IDs.\n\nCall it once a session has reached a reviewable state: the current status must be `Approved`, `Declined`, `In Review`, `Kyc Expired`, `Abandoned`, or `Resubmitted`. Sessions that are still `Not Started`, `In Progress`, or `Awaiting User`, or that ended as `Expired`, return `400`. Your API key needs the `write:sessions` permission (a scoped API key needs write access to decisions). The eligible-state check runs before body validation, so an ineligible session returns the `Wrong Current Status` error even when the payload is also invalid.\n\nFor `Resubmitted`, pass `nodes_to_resubmit` to choose which workflow steps the user must redo, or omit it to auto-select existing feature attempts in a resubmittable state (`Declined`, `In Review`, `Not Finished`, `Expired`). Never-attempted features are not auto-selected: list them explicitly in `nodes_to_resubmit`, or the request returns `400` when no recorded attempt needs resubmission. The selected steps' previous results move to the resubmission's history (returned by Get Decision with `include=resubmissions`), the steps are reordered to match the workflow graph, the session's expiration window restarts, and the original verification URL becomes usable again. Backend-only steps (`AML`, `DATABASE_VALIDATION`, `IP_ANALYSIS`) at the head of the list run immediately without user interaction; if every resubmitted step is backend-only, the session re-finalizes within the same request. `KYB_REGISTRY` and `KYB_KEY_PEOPLE` cannot be resubmitted directly: resubmit the relevant child KYC sessions instead and the parent business session recomputes automatically.\n\nEvery call that changes the status fires the `status.updated` [webhook](/integration/webhooks) once the change commits and appends an activity entry (with actor attribution and your `comment`) to the session's `reviews`, visible in [Get Decision](#get-/v3/session/-sessionId-/decision/) and in the console activity timeline. The user-profile aggregates linked to the session's `vendor_data` update as well. Declining disables ongoing AML monitoring for the session; re-approving re-enables it (from `Resubmitted`, `Declined`, or `Kyc Expired`) when the workflow has monitoring turned on. Approving or declining a child KYC that belongs to a KYB key-people check recomputes the parent business session's status. Set `send_email: true` (with `email_address`) to notify the user: a status notice for `Approved`/`Declined`, or a resubmission email containing the verification link and per-step reasons for `Resubmitted`. To check the email first, preview it with [Preview Decision Email](/sessions-api/preview-decision-email) (`Approved`, `Declined`) or [Preview Resubmission Email](/sessions-api/preview-resubmission) (`Resubmitted`) and pass its `preview_token` as `email_preview_token`: the update then sends exactly the previewed email, or returns `409` before changing anything when the email or the session changed since the preview or the receipt is older than 15 minutes.\n\nRepeating `Approved` or `Declined` on a session that already has that status changes nothing and returns `200`: no webhook, no activity entry and no email. Repeating `Resubmitted` returns `400` (`The new status is the same as the current status.`). Missing or invalid credentials and missing permissions return `403`; an expired scoped API key returns `401`.", + "operationId": "patch_v3_session_update_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session to update. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "new_status" + ], + "properties": { + "new_status": { + "type": "string", + "description": "Target status. `Approved` and `Declined` record a final manual decision (each can also overturn the other). `Resubmitted` clears the selected steps and sends the session back to the user. Any other value returns `400`.", + "enum": [ + "Approved", + "Declined", + "Resubmitted" + ], + "example": "Approved" + }, + "comment": { + "type": "string", + "description": "Free-text reason for the change, stored on the session's review trail and returned in the `reviews` array of [Get Decision](#get-/v3/session/-sessionId-/decision/). For example `Duplicated user`.", + "example": "All checks passed manual review" + }, + "nodes_to_resubmit": { + "type": "array", + "description": "Workflow steps the user must redo. Only acted on when `new_status` is `Resubmitted`. For `Approved`/`Declined` the entries are still schema-validated (an invalid `feature` value returns `400`) but schema-valid entries are semantically ignored. If omitted, the server auto-selects existing OCR, Liveness, Face Match, POA, Phone, Email, AML, Database Validation, and Questionnaire attempts whose status is `Declined`, `In Review`, `Not Finished`, or `Expired` (NFC, IP analysis, age estimation, face search, and KYB document attempts are never auto-selected; non-face-match face attempts are selected as `LIVENESS`) — features the user never attempted (no recorded attempt) are NOT selected, so a session with zero attempts returns `400` (\"No features found that need resubmission\") even though nothing was approved; pass `nodes_to_resubmit` explicitly in that case. Steps are executed in workflow-graph order regardless of the order you send them. `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and the `KYB` alias are rejected with `400` — those parent checks recompute from their child KYC sessions.", + "items": { + "type": "object", + "required": [ + "node_id", + "feature" + ], + "properties": { + "node_id": { + "type": "string", + "description": "Node identifier as it appears in the session's workflow definition (for example `feature_ocr`, `feature_liveness`).", + "example": "feature_ocr" + }, + "feature": { + "type": "string", + "description": "Feature type of the node. The aliases `ID_VERIFICATION`, `POA`, `PHONE`, and `EMAIL` are normalized server-side to `OCR`, `PROOF_OF_ADDRESS`, `PHONE_VERIFICATION`, and `EMAIL_VERIFICATION`. `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and `KYB` pass schema validation but are always rejected with the business-rule `400` shown in the examples.", + "enum": [ + "OCR", + "OCR_BACK", + "NFC", + "AML", + "FACE", + "LIVENESS", + "FACE_MATCH", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "PROOF_OF_ADDRESS", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "FACE_SEARCH", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_DOCUMENTS", + "ID_VERIFICATION", + "POA", + "PHONE", + "EMAIL", + "KYB_REGISTRY", + "KYB_KEY_PEOPLE", + "KYB" + ], + "example": "OCR" + } + } + } + }, + "send_email": { + "type": "boolean", + "description": "Whether to email the user about the change. Requires `email_address`. For `Approved`/`Declined` the user receives a status notice; for `Resubmitted` the email includes the verification link and the per-step resubmission reasons.", + "default": false, + "example": false + }, + "email_address": { + "type": "string", + "format": "email", + "description": "Recipient for the notification email. **Required when `send_email` is `true`** — omitting it returns `400`.", + "example": "user@example.com" + }, + "email_language": { + "type": "string", + "description": "Language for the notification email. Accepts any string at schema level; unsupported codes silently fall back to English (`en`).", + "default": "en", + "example": "en" + }, + "email_preview_token": { + "type": "string", + "writeOnly": true, + "minLength": 1, + "maxLength": 512, + "description": "Optional receipt from [Preview Decision Email](/sessions-api/preview-decision-email) (for `Approved` and `Declined`) or [Preview Resubmission Email](/sessions-api/preview-resubmission) (for `Resubmitted`): its `preview_token`. Valid only with `send_email: true`; without it the request returns `400`. The recipient, language, session status, sender and email (and, for a resubmission, the selected steps) must still match the preview, and the receipt expires after 15 minutes. Otherwise the request returns `409` before the session changes or an email is sent. Existing integrations may omit this field." + } + } + }, + "examples": { + "Approve Session": { + "summary": "Approve a session after manual review", + "value": { + "new_status": "Approved", + "comment": "All checks passed manual review" + } + }, + "Decline Session": { + "summary": "Decline a session", + "value": { + "new_status": "Declined", + "comment": "Suspected fraud" + } + }, + "Resubmit Specific Steps": { + "summary": "Request resubmission of chosen steps, with email", + "value": { + "new_status": "Resubmitted", + "nodes_to_resubmit": [ + { + "node_id": "feature_ocr", + "feature": "OCR" + }, + { + "node_id": "feature_liveness", + "feature": "LIVENESS" + } + ], + "send_email": true, + "email_address": "user@example.com", + "email_language": "en" + } + }, + "Resubmit Auto-select": { + "summary": "Request resubmission (auto-select non-approved steps)", + "value": { + "new_status": "Resubmitted" + } + }, + "Approve With Previewed Email": { + "summary": "Approve and send the email returned by Preview Decision Email", + "value": { + "new_status": "Approved", + "send_email": true, + "email_address": "person@example.com", + "email_language": "en", + "email_preview_token": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Status updated. The response contains only the `session_id`; fetch the updated session via [Get Decision](#get-/v3/session/-sessionId-/decision/). The `status.updated` webhook fires once the change commits. Repeating `Approved` or `Declined` returns the same body and changes nothing.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the updated session." + } + } + }, + "examples": { + "Updated": { + "summary": "Returned for Approved, Declined, and Resubmitted alike", + "value": { + "session_id": "3472fb7c-8f7c-4d1a-9cf4-cf3d74ce1a60" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field errors arrive as `{\"\": [\"\"]}` (some business-rule rejections use a string value instead: `{\"nodes_to_resubmit\": \"\"}`), cross-state errors as `{\"detail\": \"\"}`, and the same-status error as a bare JSON array.", + "content": { + "application/json": { + "examples": { + "Wrong Current Status": { + "summary": "Session is not in an updatable state", + "value": { + "detail": "Only sessions with status Approved, Declined, In Review, Kyc Expired, Abandoned, Resubmitted can be updated. Current status: In Progress" + } + }, + "Invalid Status Value": { + "summary": "new_status is a real session status but not one of Approved/Declined/Resubmitted", + "value": { + "new_status": [ + "You can only update the status to 'Approved', 'Declined', or 'Resubmitted'." + ] + } + }, + "Missing new_status": { + "summary": "Required field omitted", + "value": { + "new_status": [ + "This field is required." + ] + } + }, + "Same Status": { + "summary": "Resubmitted requested on a session that is already Resubmitted", + "value": [ + "The new status is the same as the current status." + ] + }, + "Email Address Required": { + "summary": "send_email true without email_address", + "value": { + "email_address": [ + "Email address is required when send_email is true." + ] + } + }, + "Non-resubmittable Feature": { + "summary": "Resubmit targets a KYB parent check", + "value": { + "nodes_to_resubmit": "The following features cannot be resubmitted directly: KYB_REGISTRY. Resubmit the relevant child KYC sessions instead; the parent will recompute automatically." + } + }, + "Nothing To Resubmit": { + "summary": "Auto-select found no non-approved features", + "value": { + "nodes_to_resubmit": "No features found that need resubmission. All features are already approved." + } + }, + "Invalid Feature Choice": { + "summary": "Unknown feature in nodes_to_resubmit", + "value": { + "nodes_to_resubmit": [ + { + "feature": [ + "\"BOGUS\" is not a valid choice. Valid choices are: OCR, OCR_BACK, NFC, AML, FACE, LIVENESS, FACE_MATCH, IP_ANALYSIS, AGE_ESTIMATION, PROOF_OF_ADDRESS, PHONE_VERIFICATION, EMAIL_VERIFICATION, FACE_SEARCH, DATABASE_VALIDATION, QUESTIONNAIRE, KYB_REGISTRY, KYB_DOCUMENTS, KYB_KEY_PEOPLE, ID_VERIFICATION, POA, PHONE, EMAIL, KYB" + ] + } + ] + } + }, + "Invalid Status Value (not a status at all)": { + "summary": "new_status is not a valid StatusChoices value", + "value": { + "new_status": [ + "\"Bogus\" is not a valid choice." + ] + } + }, + "Preview Receipt Without Email": { + "summary": "email_preview_token sent without send_email: true", + "value": { + "email_preview_token": [ + "A preview receipt is only valid when the email is sent." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing/invalid credentials or insufficient permissions, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "API key lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists (deleted sessions also return `404`).", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "A resubmit targeting only backend-only features (AML, DATABASE_VALIDATION, IP_ANALYSIS) was repeated within the 30-second cooldown window; an immediate repeat cannot change the outcome. Back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Backend-only resubmit throttled": { + "value": { + "detail": "This session was already resubmitted for backend-only features within the last 30 seconds; repeating it cannot change the outcome. Wait before resubmitting again." + } + } + } + } + } + }, + "409": { + "description": "The email preview changed or expired. Generate a new preview before confirming; no session changes or email delivery occur.", + "content": { + "application/json": { + "examples": { + "Preview Changed": { + "summary": "The receipt no longer matches, or is older than 15 minutes", + "value": { + "detail": "This email preview changed or expired. Preview the message again before confirming." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"new_status\": \"Approved\",\n \"comment\": \"All checks passed manual review\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.patch(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-status/\",\n headers={\n \"x-api-key\": \"YOUR_API_KEY\",\n \"Content-Type\": \"application/json\",\n },\n json={\n \"new_status\": \"Resubmitted\",\n \"nodes_to_resubmit\": [\n {\"node_id\": \"feature_ocr\", \"feature\": \"OCR\"},\n {\"node_id\": \"feature_liveness\", \"feature\": \"LIVENESS\"},\n ],\n \"send_email\": True,\n \"email_address\": \"user@example.com\",\n \"email_language\": \"en\",\n },\n)\nresponse.raise_for_status()\nprint(response.json()) # {'session_id': '11111111-2222-3333-4444-555555555555'}" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const response = await fetch(\n 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-status/',\n {\n method: 'PATCH',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n new_status: 'Declined',\n comment: 'Suspected fraud',\n }),\n },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst { session_id } = await response.json();" + } + ] + } + }, + "/v3/id-verification/": { + "post": { + "summary": "ID Verification (document OCR + fraud checks)", + "description": "OCR an identity document and run document fraud checks in one call — returns the extracted holder fields, the parsed MRZ, an `Approved`/`Declined`/`In Review` `status`, and a `warnings` list explaining every issue found. Supports 14,000+ document types from 220+ countries and territories.\n\n**How it works.** Send the document's `front_image` (required) and, for two-sided documents, the `back_image`. Upload uncropped photos with all four corners of the document visible — the service detects, aligns, and crops the document itself, so do not pre-crop. The document is classified automatically (you never declare the country or document type), the visual zone is read with OCR, and the MRZ and any barcodes are decoded. Cross-checks then validate dates, number formats, and MRZ check digits, and compare the visual zone against the MRZ. With `perform_document_liveness=true`, the images are additionally screened for screen replays, printed copies, and portrait manipulation.\n\n**Decision logic.** `status` is `Approved` unless at least one warning resolves to a decline. Fraud and hard-failure risks always decline: `DOCUMENT_EXPIRED`, `SCREEN_CAPTURE_DETECTED`, `PRINTED_COPY_DETECTED`, `PORTRAIT_MANIPULATION_DETECTED`, `PUBLIC_DOCUMENT_IMAGE_DETECTED`, plus extraction failures (`NAME_NOT_DETECTED`, `DATE_OF_BIRTH_NOT_DETECTED`, `DOCUMENT_NUMBER_NOT_DETECTED`). (`PORTRAIT_IMAGE_NOT_DETECTED`, `COULD_NOT_DETECT_DOCUMENT_TYPE`, `INVALID_DATE`, and `MRZ_NOT_DETECTED` exist in workflow sessions but are never produced by this standalone endpoint.) Three risk groups are configurable per request via `invalid_mrz_action`, `inconsistent_data_action`, and `expiration_date_not_detected_action` (`DECLINE` or `NO_ACTION`). All other warnings (e.g. `POSSIBLE_DUPLICATED_USER`) are informational and never decline on their own. When `perform_document_liveness=true` (the default) and the forgery check returns no usable score — the provider failed, answered non-200, or answered HTTP 200 with `status=\"ERR\"` - the call returns `200` with `status: \"In Review\"` and a `DOCUMENT_LIVENESS_NOT_COMPUTED` warning (`log_type: \"warning\"`) instead of `Approved`: a check that did not run is not evidence that the document is genuine. This escalation is not configurable and never overrides a `Declined`; with `perform_document_liveness=false` the check is skipped and the status follows the remaining warnings as before. A document that cannot be processed at all returns `400` with `{\"error\": \"COULD_NOT_RECOGNIZE_DOCUMENT\"}`; a readable but problematic document returns `200` with `status: \"Declined\"` — always inspect `id_verification.status` and `id_verification.warnings`, not just the HTTP code.\n\n**Billing.** Each `200` response consumes one ID Verification API credit (paid from your plan credit or top-up balance). When the organization's balance cannot cover the call, the endpoint returns `403` with the not-enough-credits error before any image processing.\n\n**Session persistence (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session: it appears in the Business Console, the returned `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/`, the cropped document/portrait images are stored and returned as short-lived media URLs (`https:///ocr/...`), and a `status.updated` webhook is emitted to your configured webhook endpoints. When `false`, nothing is stored, `request_id` is a one-off correlation UUID, and `portrait_image`/`front_image`/`back_image` are returned inline as base64-encoded JPEG strings.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload (a fictional \"Sandbox User\" USA identity card), no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3id-verification", + "x-didit-sdk": { + "resource": "id_verification", + "method": "verify", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "front_image" + ], + "properties": { + "front_image": { + "type": "string", + "format": "binary", + "description": "Front side of the identity document. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp`, `pdf`. Maximum upload size: **10 MB** (larger files are rejected with `400`). PDFs are rendered to an image before OCR; encrypted PDFs require `front_image_password`. Images are automatically compressed to ~0.5 MB and EXIF orientation is applied. Upload the full, uncropped capture with all four corners of the document visible." + }, + "front_image_password": { + "type": "string", + "description": "Password to decrypt `front_image` when it is an encrypted PDF. Sending an encrypted PDF without (or with a wrong) password returns `400`.", + "writeOnly": true + }, + "back_image": { + "type": "string", + "format": "binary", + "description": "Back side of the document — send it whenever the document has one (many ID cards carry the MRZ or a barcode on the back; omitting it simply leaves the MRZ and back-side fields empty in the response — this endpoint does not raise a missing-MRZ warning). Same format and size limits as `front_image`. When omitted, `back_image` and back-side fields are `null` in the response." + }, + "back_image_password": { + "type": "string", + "description": "Password to decrypt `back_image` when it is an encrypted PDF.", + "writeOnly": true + }, + "perform_document_liveness": { + "type": "boolean", + "default": true, + "description": "Screens both images for presentation fraud: screen replays (`SCREEN_CAPTURE_DETECTED`), printed copies (`PRINTED_COPY_DETECTED`), and portrait manipulation (`PORTRAIT_MANIPULATION_DETECTED`). Any of these auto-declines. Enabled by default; set to `false` to skip fraud screening and reduce latency. When enabled and the provider returns no usable score, the response is `In Review` with a `DOCUMENT_LIVENESS_NOT_COMPUTED` warning rather than `Approved`.", + "example": true + }, + "minimum_age": { + "type": "integer", + "nullable": true, + "default": null, + "minimum": 1, + "maximum": 120, + "description": "Accepted and validated (1–120) but **currently not applied** by this endpoint — it never produces a `MINIMUM_AGE_NOT_MET` warning from this field. Enforce age rules from the returned `date_of_birth`/`age`, or use a verification-session workflow with age restrictions." + }, + "expiration_date_not_detected_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when no expiration date can be read (`EXPIRATION_DATE_NOT_DETECTED`). Default `NO_ACTION` because many identity documents print no expiry date. Note: a *detected and past* expiry date always declines (`DOCUMENT_EXPIRED`), regardless of this option." + }, + "invalid_mrz_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "DECLINE", + "description": "What to do when the extracted MRZ fails check-digit validation (`MRZ_VALIDATION_FAILED`). A missing MRZ raises no warning on this endpoint. Only relevant for documents that carry an MRZ. `REVIEW` is not supported on this endpoint and returns `400`." + }, + "inconsistent_data_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "DECLINE", + "description": "What to do when extracted data is internally inconsistent: `DATA_INCONSISTENT`, `MRZ_AND_DATA_EXTRACTED_FROM_OCR_NOT_SAME` (visual zone disagrees with the MRZ), or `DOCUMENT_NAME_DIFFERENT_FROM_OTHER_APPROVED_DOCUMENTS`." + }, + "critical_field_occlusion_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "DECLINE", + "description": "What to do when a critical printed field was physically covered on the capture (`CRITICAL_FIELD_OCCLUSION_DETECTED`) - an identity number, a name, the date of birth, or the expiry date. Declines by default, like this endpoint's other document-integrity risks: covering the numbers a report is reconciled against is what a redacted document looks like. The value read for a covered field is dropped from the extraction either way, so the matching `*_NOT_DETECTED` warning usually accompanies it. `REVIEW` is not supported on this endpoint and returns `400`." + }, + "document_or_personal_number_format_mismatch_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when a number does not match the format its issuing authority uses for that document type (`DOCUMENT_NUMBER_FORMAT_MISMATCH`, `PERSONAL_NUMBER_FORMAT_MISMATCH`). Default `NO_ACTION`, which reports the warning without changing the status. `REVIEW` is not supported on this endpoint and returns `400`." + }, + "preferred_characters": { + "type": "string", + "enum": [ + "latin", + "non_latin" + ], + "default": "latin", + "description": "Preferred script for name/address fields on documents that print both Latin and non-Latin text (Arabic, Cyrillic, CJK, …). `latin` returns transliterated/Latin values; `non_latin` prefers the native script." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, announced through a `status.updated` webhook, and with document images returned as media URLs. When `false`, nothing is stored, `request_id` is a transient UUID, and images are returned inline as base64 JPEG strings.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"onboarding\"}`) — it is parsed into an object.", + "example": { + "flow": "onboarding" + } + } + } + }, + "example": { + "front_image": "(binary JPEG/PNG/PDF of the document front)", + "back_image": "(binary JPEG/PNG/PDF of the document back)", + "perform_document_liveness": true, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "onboarding" + } + } + } + } + }, + "responses": { + "200": { + "description": "Document processed. `id_verification.status` is `Approved`, `Declined`, or `In Review`; every detected issue is itemized in `id_verification.warnings`. `In Review` is returned when a requested document-liveness check produced no usable result (`DOCUMENT_LIVENESS_NOT_COMPUTED`). A problematic document still returns `200` with `status: \"Declined\"` — inspect the body, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id and image fields are short-lived media URLs; with `save_api_request=false` they are inline base64 JPEG strings.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Two-sided ID card read cleanly (saved — images as media URLs)", + "value": { + "request_id": "11d219ed-d59c-4b1d-8d65-9b933f12d5b8", + "id_verification": { + "status": "Approved", + "document_type": "Identity Card", + "document_subtype": "ID_CARD_GENERIC", + "document_number": "CBX164224", + "personal_number": "20446581H", + "portrait_image": "https:///ocr/11d219ed-d59c-4b1d-8d65-9b933f12d5b8-portrait_image-8c2f.jpg?signature=...", + "front_image": "https:///ocr/11d219ed-d59c-4b1d-8d65-9b933f12d5b8-front_image-4a1e.jpg?signature=...", + "back_image": "https:///ocr/11d219ed-d59c-4b1d-8d65-9b933f12d5b8-back_image-77b0.jpg?signature=...", + "front_image_camera_front": null, + "back_image_camera_front": null, + "front_image_camera_front_face_match_score": null, + "back_image_camera_front_face_match_score": null, + "front_image_quality_score": { + "focus_score": 78.0, + "brightness_score": 83.2, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 40.7, + "overall_score": 70.2 + }, + "back_image_quality_score": { + "focus_score": 100.0, + "brightness_score": 95.6, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 43.0, + "overall_score": 84.4 + }, + "date_of_birth": "1980-01-13", + "age": 46, + "expiration_date": "2032-03-31", + "date_of_issue": "2022-03-31", + "issuing_state": "ESP", + "issuing_state_name": "Spain", + "first_name": "Julio Francisco", + "last_name": "Fores Sena", + "full_name": "Julio Francisco Fores Sena", + "gender": "M", + "address": "Brda. Urb. El Cardonal 0052 52 Po3 B,Taco,San Cristobal De La Laguna,Santa Cruz De Tenerife", + "formatted_address": "Av. el Cardonal, 52, b, 38108 La Laguna, Santa Cruz de Tenerife, Spain", + "place_of_birth": "Valencia, Valencia", + "marital_status": "UNKNOWN", + "nationality": "ESP", + "extra_fields": { + "first_surname": "Fores", + "second_surname": "Sena" + }, + "mrz": { + "surname": "FORES SENA", + "name": "JULIO FRANCISCO", + "country": "ESP", + "nationality": "ESP", + "birth_date": "800113", + "expiry_date": "320331", + "sex": "M", + "document_type": "ID", + "document_number": "CBX164224", + "optional_data": "20446581H", + "optional_data_2": "", + "birth_date_hash": "9", + "expiry_date_hash": "8", + "document_number_hash": "3", + "final_hash": "3", + "personal_number": "20446581H", + "warnings": [], + "errors": [], + "mrz_type": "TD1", + "mrz_string": "IDESPCBX164224320446581H<<<<<<\n8001139M3203318ESP<<<<<<<<<<<3\nFORES/ocr/f61681b8-9422-4378-ac3e-04c6a247fb45-portrait_image-1d9a.jpg?signature=...", + "front_image": "https:///ocr/f61681b8-9422-4378-ac3e-04c6a247fb45-front_image-5c2b.jpg?signature=...", + "back_image": null, + "front_image_camera_front": null, + "back_image_camera_front": null, + "front_image_camera_front_face_match_score": null, + "back_image_camera_front_face_match_score": null, + "front_image_quality_score": { + "focus_score": 100.0, + "brightness_score": 95.7, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 22.2, + "overall_score": 79.3 + }, + "back_image_quality_score": null, + "date_of_birth": "1997-01-30", + "age": 29, + "expiration_date": "2025-11-26", + "date_of_issue": null, + "issuing_state": "ESP", + "issuing_state_name": "Spain", + "first_name": "Lucia", + "last_name": "Marin Castro", + "full_name": "Lucia Marin Castro", + "gender": "F", + "address": null, + "formatted_address": null, + "place_of_birth": null, + "marital_status": "UNKNOWN", + "nationality": "ESP", + "extra_fields": { + "first_surname": "Marin", + "second_surname": "Castro" + }, + "mrz": {}, + "parsed_address": null, + "warnings": [ + { + "risk": "DOCUMENT_EXPIRED", + "feature": "ID_VERIFICATION", + "additional_data": null, + "log_type": "error", + "short_description": "Document expired", + "long_description": "The document's expiration date has passed, rendering it no longer valid for use." + } + ], + "barcodes": [] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:25:48.117204+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "id_verification": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "description": "`Approved` when no warning resolves to a decline and the requested document-liveness check produced a usable result; `In Review` when that requested check produced no usable result (`DOCUMENT_LIVENESS_NOT_COMPUTED`); `Declined` when a warning resolves to a decline — the `warnings` list explains why." + }, + "document_type": { + "type": "string", + "nullable": true, + "description": "Detected document type display name: `Identity Card`, `Passport`, `Driver's License`, `Residence Permit`, `Health Insurance Card`, or `Tax Card`. `null` when the document could not be classified.", + "example": "Identity Card" + }, + "document_subtype": { + "type": "string", + "nullable": true, + "description": "Finer-grained subtype when available (e.g. `ID_CARD_GENERIC`, `EPASSPORT`).", + "example": "ID_CARD_GENERIC" + }, + "document_number": { + "type": "string", + "nullable": true, + "example": "CBX164224" + }, + "personal_number": { + "type": "string", + "nullable": true, + "description": "Secondary personal/national number printed on some documents.", + "example": "20446581H" + }, + "portrait_image": { + "type": "string", + "nullable": true, + "description": "Cropped portrait photo from the document. A short-lived media URL (`https:///ocr/...`) when `save_api_request=true` (default); an inline base64-encoded JPEG string when `save_api_request=false`. `null` when no portrait was detected." + }, + "front_image": { + "type": "string", + "nullable": true, + "description": "Aligned crop of the document front. Media URL when saved, inline base64 JPEG otherwise." + }, + "back_image": { + "type": "string", + "nullable": true, + "description": "Aligned crop of the document back. `null` when no `back_image` was uploaded." + }, + "front_image_camera_front": { + "type": "string", + "nullable": true, + "description": "Always `null` on this endpoint (populated only in camera-capture verification sessions)." + }, + "back_image_camera_front": { + "type": "string", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "front_image_camera_front_face_match_score": { + "type": "number", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "back_image_camera_front_face_match_score": { + "type": "number", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "front_image_quality_score": { + "type": "object", + "nullable": true, + "description": "Capture-quality diagnostics for the uploaded image (`null` when the side was not provided or scoring failed). All scores are 0–100.", + "properties": { + "focus_score": { + "type": "number", + "format": "float", + "description": "Sharpness of the document crop (higher is sharper)." + }, + "brightness_score": { + "type": "number", + "format": "float", + "description": "Exposure quality (higher is better)." + }, + "brightness_issue": { + "type": "string", + "nullable": true, + "description": "`ok` when exposure is fine, otherwise the detected issue (e.g. too dark / too bright)." + }, + "is_document_fully_visible": { + "type": "boolean", + "nullable": true, + "description": "Whether all four corners of the document are inside the frame." + }, + "resolution_score": { + "type": "number", + "format": "float", + "description": "Effective resolution of the document area." + }, + "overall_score": { + "type": "number", + "format": "float", + "description": "Aggregate quality score." + } + } + }, + "back_image_quality_score": { + "type": "object", + "nullable": true, + "description": "Capture-quality diagnostics for the uploaded image (`null` when the side was not provided or scoring failed). All scores are 0–100.", + "properties": { + "focus_score": { + "type": "number", + "format": "float", + "description": "Sharpness of the document crop (higher is sharper)." + }, + "brightness_score": { + "type": "number", + "format": "float", + "description": "Exposure quality (higher is better)." + }, + "brightness_issue": { + "type": "string", + "nullable": true, + "description": "`ok` when exposure is fine, otherwise the detected issue (e.g. too dark / too bright)." + }, + "is_document_fully_visible": { + "type": "boolean", + "nullable": true, + "description": "Whether all four corners of the document are inside the frame." + }, + "resolution_score": { + "type": "number", + "format": "float", + "description": "Effective resolution of the document area." + }, + "overall_score": { + "type": "number", + "format": "float", + "description": "Aggregate quality score." + } + } + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "example": "1980-01-13" + }, + "age": { + "type": "integer", + "nullable": true, + "description": "Holder age in years, computed from `date_of_birth`.", + "example": 46 + }, + "expiration_date": { + "type": "string", + "format": "date", + "nullable": true, + "example": "2032-03-31" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "nullable": true, + "example": "2022-03-31" + }, + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Issuing country (ISO 3166-1 alpha-3).", + "example": "ESP" + }, + "issuing_state_name": { + "type": "string", + "nullable": true, + "example": "Spain" + }, + "first_name": { + "type": "string", + "nullable": true, + "example": "Julio Francisco" + }, + "last_name": { + "type": "string", + "nullable": true, + "example": "Fores Sena" + }, + "full_name": { + "type": "string", + "nullable": true, + "example": "Julio Francisco Fores Sena" + }, + "gender": { + "type": "string", + "nullable": true, + "description": "`M`, `F`, or `null` when not printed/detected.", + "example": "M" + }, + "address": { + "type": "string", + "nullable": true, + "description": "Address exactly as printed on the document." + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "Geocoded, normalized version of `address`." + }, + "place_of_birth": { + "type": "string", + "nullable": true, + "example": "Valencia, Valencia" + }, + "marital_status": { + "type": "string", + "nullable": true, + "description": "`UNKNOWN` unless the document prints marital status.", + "example": "UNKNOWN" + }, + "nationality": { + "type": "string", + "nullable": true, + "description": "Holder nationality (ISO 3166-1 alpha-3).", + "example": "ESP" + }, + "extra_fields": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Document-specific extras that have no dedicated column (e.g. `first_surname`/`second_surname` on Spanish IDs, license classes on driving licenses)." + }, + "mrz": { + "type": "object", + "description": "Parsed machine-readable zone. An **empty object** (`{}`) when the document has no MRZ or it could not be read — also check the `warnings` list in that case. Dates inside the MRZ use the raw `YYMMDD` format.", + "properties": { + "surname": { + "type": "string", + "example": "FORES SENA" + }, + "name": { + "type": "string", + "example": "JULIO FRANCISCO" + }, + "country": { + "type": "string", + "description": "Issuing country (ISO 3166-1 alpha-3).", + "example": "ESP" + }, + "nationality": { + "type": "string", + "description": "Holder nationality (ISO 3166-1 alpha-3).", + "example": "ESP" + }, + "birth_date": { + "type": "string", + "description": "Raw MRZ date of birth (`YYMMDD`).", + "example": "800113" + }, + "expiry_date": { + "type": "string", + "description": "Raw MRZ expiry date (`YYMMDD`).", + "example": "320331" + }, + "sex": { + "type": "string", + "example": "M" + }, + "document_type": { + "type": "string", + "description": "MRZ document-type code (e.g. `ID`, `P`).", + "example": "ID" + }, + "document_number": { + "type": "string", + "example": "CBX164224" + }, + "optional_data": { + "type": "string", + "nullable": true, + "example": "20446581H" + }, + "optional_data_2": { + "type": "string", + "nullable": true, + "example": "" + }, + "birth_date_hash": { + "type": "string", + "description": "MRZ check digit for the birth date.", + "example": "9" + }, + "expiry_date_hash": { + "type": "string", + "description": "MRZ check digit for the expiry date.", + "example": "8" + }, + "document_number_hash": { + "type": "string", + "description": "MRZ check digit for the document number.", + "example": "3" + }, + "final_hash": { + "type": "string", + "description": "Composite MRZ check digit.", + "example": "3" + }, + "personal_number": { + "type": "string", + "nullable": true, + "example": "20446581H" + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Non-fatal MRZ parsing warnings." + }, + "errors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "MRZ check-digit/parsing errors. Non-empty errors surface as an `MRZ_VALIDATION_FAILED` warning." + }, + "mrz_type": { + "type": "string", + "description": "MRZ layout: `TD1` (ID cards, 3 lines), `TD2`, or `TD3` (passports, 2 lines).", + "example": "TD1" + }, + "mrz_string": { + "type": "string", + "description": "Raw MRZ text as read, lines separated by `\\n`." + }, + "mrz_key": { + "type": "string", + "description": "Composite key (document number + check digits) used for cross-checks." + } + } + }, + "parsed_address": { + "type": "object", + "nullable": true, + "description": "Structured, geocoded breakdown of `address`. `null` when the document carries no address or it could not be parsed.", + "properties": { + "street_1": { + "type": "string", + "nullable": true, + "example": "Avenida el Cardonal 52" + }, + "street_2": { + "type": "string", + "nullable": true, + "example": "b" + }, + "city": { + "type": "string", + "nullable": true, + "example": "La Laguna" + }, + "region": { + "type": "string", + "nullable": true, + "example": "Canarias" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code.", + "example": "ES" + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "38108" + }, + "address_type": { + "type": "string", + "nullable": true, + "example": "Avenida" + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "Canonical geocoded address string.", + "example": "Av. el Cardonal, 52, b, 38108 La Laguna, Santa Cruz de Tenerife, Spain" + }, + "raw_results": { + "type": "object", + "additionalProperties": true, + "description": "Raw geocoding payload (`address_components`, `geometry.location`, `place_id`, …) for advanced consumers." + }, + "document_location": { + "type": "object", + "nullable": true, + "properties": { + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + } + }, + "description": "Geocoded coordinates of the document address." + }, + "label": { + "type": "string", + "nullable": true, + "description": "Which document field the address was read from.", + "example": "Spain Identity Card Address" + }, + "is_verified": { + "type": "boolean", + "description": "Whether geocoding confirmed the address exists." + }, + "category": { + "type": "string", + "nullable": true, + "description": "Address category (e.g. `Residential`, `Commercial`).", + "example": "Residential" + } + } + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. Every entry explains one detected risk; entries with `log_type: \"error\"` set `status` to `Declined`, and `DOCUMENT_LIVENESS_NOT_COMPUTED` (`log_type: \"warning\"`) sets it to `In Review`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "description": "Machine-readable risk code. Always-decline risks: `DOCUMENT_EXPIRED`, `MINIMUM_AGE_NOT_MET`, `PORTRAIT_IMAGE_NOT_DETECTED`, `SCREEN_CAPTURE_DETECTED`, `PRINTED_COPY_DETECTED`, `PORTRAIT_MANIPULATION_DETECTED`, `PUBLIC_DOCUMENT_IMAGE_DETECTED`, `INVALID_DATE`, `COULD_NOT_RECOGNIZE_DOCUMENT` (400-only, never a warning), `DOCUMENT_NUMBER_NOT_DETECTED`, `COULD_NOT_DETECT_DOCUMENT_TYPE`, `NAME_NOT_DETECTED`, `DATE_OF_BIRTH_NOT_DETECTED`. Governed by request options: `MRZ_NOT_DETECTED` / `MRZ_VALIDATION_FAILED` (`invalid_mrz_action`), `DATA_INCONSISTENT` / `MRZ_AND_DATA_EXTRACTED_FROM_OCR_NOT_SAME` / `DOCUMENT_NAME_DIFFERENT_FROM_OTHER_APPROVED_DOCUMENTS` (`inconsistent_data_action`), `EXPIRATION_DATE_NOT_DETECTED` (`expiration_date_not_detected_action`). Other risks (e.g. `POSSIBLE_DUPLICATED_USER`) are informational. `DOCUMENT_LIVENESS_NOT_COMPUTED` is raised when `perform_document_liveness=true` but the forgery check returned no usable score; it sets `status` to `In Review`.", + "example": "DOCUMENT_EXPIRED" + }, + "feature": { + "type": "string", + "enum": [ + "ID_VERIFICATION" + ], + "description": "Feature that raised the warning. Always `ID_VERIFICATION` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Extra context for some risks (e.g. `POSSIBLE_DUPLICATED_USER` includes `duplicated_session_id`); `null` for most." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `warning` (only `DOCUMENT_LIVENESS_NOT_COMPUTED`, when the requested forgery check produced no usable score) drives `status` to `In Review`; `information` entries are advisory and never change the status." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "barcodes": { + "type": "array", + "description": "Barcodes decoded from the document (PDF417, QR, …). Empty when the document has none or none could be read.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Barcode type (e.g. `PDF417`, `QR_CODE`, `UNKNOWN`)." + }, + "position": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Location of the barcode in the image, when available." + }, + "data": { + "type": "string", + "description": "Decoded payload." + }, + "data_raw": { + "type": "string", + "description": "Raw (undecoded) payload, when different." + }, + "side": { + "type": "string", + "nullable": true, + "description": "`front` or `back`." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated." + } + } + } + } + } + }, + "400": { + "description": "Validation or processing error. Field-level problems return DRF's standard envelope (one array of messages per offending field). Encrypted-PDF problems return `{\"detail\": ...}`. A document image in which no document can be recognized at all returns `{\"error\": \"COULD_NOT_RECOGNIZE_DOCUMENT\"}`. PDF uploads can additionally fail with \"The submitted PDF file is corrupted or invalid.\" or a no-pages error.", + "content": { + "application/json": { + "examples": { + "Missing front image": { + "summary": "`front_image` not included in the form data", + "value": { + "front_image": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp/pdf", + "value": { + "front_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp, pdf." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 10 MB limit", + "value": { + "front_image": [ + "File size should not exceed 10 MB" + ] + } + }, + "Invalid option value": { + "summary": "Options only accept DECLINE or NO_ACTION", + "value": { + "invalid_mrz_action": [ + "\"REVIEW\" is not a valid choice." + ] + } + }, + "Encrypted PDF": { + "summary": "PDF uploaded without the required password", + "value": { + "detail": "The PDF is encrypted. Please upload a decrypted PDF or a photo instead." + } + }, + "Wrong PDF password": { + "summary": "`front_image_password` / `back_image_password` does not decrypt the PDF", + "value": { + "detail": "The PDF password is incorrect. Please provide the correct password." + } + }, + "Document not recognized": { + "summary": "No identity document could be detected in the image", + "value": { + "error": "COULD_NOT_RECOGNIZE_DOCUMENT" + } + }, + "PDF conversion failure": { + "summary": "Uploaded PDF could not be converted to an image", + "value": { + "error": "Failed to convert front image PDF." + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, malformed, revoked, or belongs to another environment — and also when the calling organization's balance cannot cover the call. Authentication failures return `403` with `{\"detail\": ...}`; this API never returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. All POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/id-verification/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'front_image=@./id_front.jpg' \\\n -F 'back_image=@./id_back.jpg' \\\n -F 'perform_document_liveness=true' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nurl = 'https://verification.didit.me/v3/id-verification/'\nheaders = {'x-api-key': 'YOUR_API_KEY'}\n\nwith open('id_front.jpg', 'rb') as front_f, open('id_back.jpg', 'rb') as back_f:\n files = {\n 'front_image': ('id_front.jpg', front_f, 'image/jpeg'),\n 'back_image': ('id_back.jpg', back_f, 'image/jpeg'),\n }\n data = {\n 'perform_document_liveness': 'true',\n 'save_api_request': 'true',\n 'vendor_data': 'user-123',\n }\n resp = requests.post(url, headers=headers, files=files, data=data, timeout=120)\n\nresp.raise_for_status()\nid_v = resp.json()['id_verification']\nprint('status:', id_v['status'])\nprint('name:', id_v.get('full_name'))\nprint('document:', id_v.get('document_type'), id_v.get('document_number'))\nfor w in id_v.get('warnings', []):\n print('warning:', w['risk'], '-', w['log_type'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "import fs from 'node:fs';\n\nconst form = new FormData();\nform.append('front_image', new Blob([fs.readFileSync('./id_front.jpg')]), 'id_front.jpg');\nform.append('back_image', new Blob([fs.readFileSync('./id_back.jpg')]), 'id_back.jpg');\nform.append('perform_document_liveness', 'true');\nform.append('save_api_request', 'true');\nform.append('vendor_data', 'user-123');\n\nconst response = await fetch('https://verification.didit.me/v3/id-verification/', {\n method: 'POST',\n headers: { 'x-api-key': 'YOUR_API_KEY' },\n body: form,\n});\n\nif (!response.ok) throw new Error(`ID verification failed: ${response.status}`);\nconst body = await response.json();\nconsole.log('status:', body.id_verification.status);\nconsole.log('warnings:', body.id_verification.warnings.map((w) => w.risk));" + } + ] + } + }, + "/v3/face-match/": { + "post": { + "summary": "Face Match", + "description": "Compare two face images (1:1) and get back a similarity `score` (0–100) plus an `Approved`/`Declined` `status`.\n\n**How it works.** Both images are decoded and analyzed by a biometric model (set `rotate_image=true` if captures may be sideways or upside down), then a similarity score between the two faces is computed. A score **strictly above** `face_match_score_decline_threshold` (default `30`) returns `status: \"Approved\"`; a score at or below it returns `Declined` with a `LOW_FACE_MATCH_SIMILARITY` warning. If no face pair can be scored at all, the call still returns `200` with `status: \"Declined\"` and a `NO_REFERENCE_IMAGE` warning — `score` is `null` on the default save path, or `0` with `save_api_request=false`. Submit one clear, front-facing face per image for best results.\n\n**Billing.** Each `200` response consumes one Face Match API credit (paid from your plan credit or top-up balance). When the organization's balance cannot cover the call, the endpoint returns `403` with the not-enough-credits error before any image processing.\n\n**Session persistence (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session: it appears in the Business Console, the returned `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/`, both images are stored as the session's face-match source/target images, and a `status.updated` webhook is emitted to your configured webhook endpoints. When `false`, nothing is stored and `request_id` is a one-off correlation UUID that cannot be looked up later.\n\n**Face search index.** This endpoint never enrolls faces into your face search index and never runs blocklist or duplicate screening — use `POST /v3/passive-liveness/` or `POST /v3/face-search/` for those.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload, no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3face-match", + "x-didit-sdk": { + "resource": "face_match", + "method": "compare", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "user_image", + "ref_image" + ], + "properties": { + "user_image": { + "type": "string", + "format": "binary", + "description": "Live or candidate face image to verify (e.g. a selfie). Submit a single front-facing photo with the subject's face clearly visible and well lit. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp`. Maximum upload size: **5 MB** (larger files are rejected with `400`). Images are automatically compressed to ~0.5 MB before processing, so very high resolutions do not improve accuracy." + }, + "ref_image": { + "type": "string", + "format": "binary", + "description": "Reference face image to compare against (e.g. an ID document portrait or a previously enrolled photo). Same format and size limits as `user_image`." + }, + "face_match_score_decline_threshold": { + "type": "number", + "format": "float", + "default": 30, + "minimum": 0, + "maximum": 100, + "description": "Similarity threshold (0–100) that drives the response `status`. A computed score **at or below** this value sets `status` to `Declined` and adds a `LOW_FACE_MATCH_SIMILARITY` warning; a score strictly above it returns `Approved`. Default `30` is permissive — raise it (e.g. `50`–`60`) for stricter verification. Values outside 0–100 return `400`.", + "example": 50 + }, + "rotate_image": { + "type": "boolean", + "default": false, + "description": "When `true`, the service tries the input images in 90-degree increments (0, 90, 180, 270) and uses the orientation that yields the best face detection — useful for mobile captures with missing EXIF orientation. Adds latency, so only enable it if you cannot guarantee upright images.", + "example": false + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, and announced through a `status.updated` webhook. When `false`, nothing is stored and `request_id` is a transient UUID for response correlation only.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"login\"}`) — it is parsed into an object.", + "example": { + "flow": "login", + "device_id": "abc123" + } + } + } + }, + "example": { + "user_image": "(binary JPEG/PNG of the live selfie)", + "ref_image": "(binary JPEG/PNG of the reference photo)", + "face_match_score_decline_threshold": 50, + "rotate_image": false, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "login" + } + } + } + } + }, + "responses": { + "200": { + "description": "Face match computed. `face_match.score` is a 0–100 float (two decimals); `status` is `Approved` when the score is strictly above `face_match_score_decline_threshold`, otherwise `Declined` with an explanatory warning. A low similarity or unscorable pair still returns `200` — inspect `face_match.status` and `face_match.warnings`, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Same person — score above the threshold", + "value": { + "request_id": "f9f5a777-8002-44c6-baaf-128bcfd6e226", + "face_match": { + "status": "Approved", + "score": 99.41, + "user_image": { + "entities": [ + { + "bbox": [ + 661, + 728, + 1688, + 2188 + ], + "confidence": 0.732973, + "age": 26.91, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "ref_image": { + "entities": [ + { + "bbox": [ + 653, + 745, + 1691, + 2185 + ], + "confidence": 0.722082, + "age": 27.0, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": { + "flow": "login" + }, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "Declined - low similarity": { + "summary": "Score at or below the decline threshold", + "value": { + "request_id": "7cdda24e-311a-46d3-a3bb-e5d9b564ce73", + "face_match": { + "status": "Declined", + "score": 24.87, + "user_image": { + "entities": [ + { + "bbox": [ + 661, + 728, + 1688, + 2188 + ], + "confidence": 0.732973, + "age": 26.91, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "ref_image": { + "entities": [ + { + "bbox": [ + 156, + 234, + 679, + 898 + ], + "confidence": 0.717775, + "age": 41.2, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "LOW_FACE_MATCH_SIMILARITY", + "feature": "FACEMATCH", + "additional_data": null, + "log_type": "error", + "short_description": "Low face match similarity", + "long_description": "The facial features of the provided image don't closely match the reference image, suggesting a potential identity mismatch." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:08:19.859116+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "face_match": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Approved` when the similarity score is strictly above `face_match_score_decline_threshold` and a score could be computed; `Declined` otherwise (a warning explains why)." + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Similarity score from 0 to 100 — the model's confidence that both images depict the same person. Rounded to two decimals only when `save_api_request` is true; with `save_api_request=false` the raw float is returned. `null` when no face pair could be scored (with the default `save_api_request=true`); with `save_api_request=false` the same condition can surface as `score: 0`. Either way a `NO_REFERENCE_IMAGE` warning is present and `status` is `Declined`.", + "example": 99.41 + }, + "user_image": { + "type": "object", + "description": "Face-detection results for the analyzed images.", + "properties": { + "entities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box of the detected face as `[x_min, y_min, x_max, y_max]` pixel coordinates in the processed image.", + "example": [ + 661, + 728, + 1688, + 2188 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.732973 + }, + "age": { + "type": "number", + "format": "float", + "description": "Model-estimated age of the detected face, in years. Informational only — it does not affect the face-match decision.", + "example": 26.91 + }, + "gender": { + "type": "string", + "description": "Model-predicted gender of the detected face (`male` or `female`). Informational only.", + "example": "male" + }, + "race": { + "type": "string", + "nullable": true, + "description": "Reserved field — always `null` in responses from this endpoint.", + "example": null + } + } + }, + "description": "One entry per detected face." + }, + "best_angle": { + "type": "integer", + "description": "Rotation (degrees: 0, 90, 180, or 270) that produced the best face detection. Only non-zero when `rotate_image=true` corrected the orientation.", + "example": 0 + } + } + }, + "ref_image": { + "type": "object", + "description": "Face-detection results for the reference image, same shape as `user_image`.", + "properties": { + "entities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box of the detected face as `[x_min, y_min, x_max, y_max]` pixel coordinates in the processed image.", + "example": [ + 661, + 728, + 1688, + 2188 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.732973 + }, + "age": { + "type": "number", + "format": "float", + "description": "Model-estimated age of the detected face, in years. Informational only — it does not affect the face-match decision.", + "example": 26.91 + }, + "gender": { + "type": "string", + "description": "Model-predicted gender of the detected face (`male` or `female`). Informational only.", + "example": "male" + }, + "race": { + "type": "string", + "nullable": true, + "description": "Reserved field — always `null` in responses from this endpoint.", + "example": null + } + } + }, + "description": "One entry per detected face." + }, + "best_angle": { + "type": "integer", + "example": 0 + } + } + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. `NO_REFERENCE_IMAGE` (`error`) — no face pair could be scored; `LOW_FACE_MATCH_SIMILARITY` (`error`) — score at or below the decline threshold. Any of these sets `status` to `Declined`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "NO_REFERENCE_IMAGE", + "LOW_FACE_MATCH_SIMILARITY" + ], + "description": "Machine-readable risk code." + }, + "feature": { + "type": "string", + "enum": [ + "FACEMATCH" + ], + "description": "Feature that raised the warning. Always `FACEMATCH` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Always `null` for face-match warnings." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `warning` and `information` entries are advisory and never decline on their own." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated, e.g. `2026-06-12T01:04:42.763237+00:00`." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when a required image is missing, exceeds 5 MB, has an unsupported extension, or when an option is out of range. The body is DRF's standard field-error envelope: one array of messages per offending field.", + "content": { + "application/json": { + "examples": { + "Missing required image": { + "summary": "`ref_image` (or `user_image`) not included in the form data", + "value": { + "ref_image": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp", + "value": { + "user_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 5 MB limit", + "value": { + "user_image": [ + "File size should not exceed 5 MB" + ] + } + }, + "Threshold out of range": { + "summary": "`face_match_score_decline_threshold` outside 0–100", + "value": { + "face_match_score_decline_threshold": [ + "Ensure this value is less than or equal to 100." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, malformed, revoked, or belongs to another environment — and also when the calling organization's balance cannot cover the call. Authentication failures return `403` with `{\"detail\": ...}`; this API never returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. All POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/face-match/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'user_image=@./selfie.jpg' \\\n -F 'ref_image=@./id_portrait.jpg' \\\n -F 'face_match_score_decline_threshold=50' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nurl = 'https://verification.didit.me/v3/face-match/'\nheaders = {'x-api-key': 'YOUR_API_KEY'}\n\nwith open('selfie.jpg', 'rb') as user_f, open('id_portrait.jpg', 'rb') as ref_f:\n files = {\n 'user_image': ('selfie.jpg', user_f, 'image/jpeg'),\n 'ref_image': ('id_portrait.jpg', ref_f, 'image/jpeg'),\n }\n data = {\n 'face_match_score_decline_threshold': 50,\n 'save_api_request': 'true',\n 'vendor_data': 'user-123',\n }\n resp = requests.post(url, headers=headers, files=files, data=data, timeout=60)\n\nresp.raise_for_status()\nbody = resp.json()\nprint('status:', body['face_match']['status'])\nprint('score:', body['face_match']['score'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "import fs from 'node:fs';\n\nconst form = new FormData();\nform.append('user_image', new Blob([fs.readFileSync('./selfie.jpg')]), 'selfie.jpg');\nform.append('ref_image', new Blob([fs.readFileSync('./id_portrait.jpg')]), 'id_portrait.jpg');\nform.append('face_match_score_decline_threshold', '50');\nform.append('save_api_request', 'true');\nform.append('vendor_data', 'user-123');\n\nconst response = await fetch('https://verification.didit.me/v3/face-match/', {\n method: 'POST',\n headers: { 'x-api-key': 'YOUR_API_KEY' },\n body: form,\n});\n\nif (!response.ok) throw new Error(`Face match failed: ${response.status}`);\nconst body = await response.json();\nconsole.log('status:', body.face_match.status, 'score:', body.face_match.score);" + } + ] + } + }, + "/v3/age-estimation/": { + "post": { + "summary": "Age Estimation (face age + passive liveness)", + "description": "Estimate a person's age from a single face photo and run a passive liveness check in the same call — returns the model-predicted `age_estimation` (in years), a liveness `score` (0–100), and an `Approved`/`Declined` `status`. The age is **model-predicted from the face, not verified against a document** — use it for age gates and low-friction checks, not as legal proof of age.\n\n**How it works.** The image is analyzed for faces (set `rotate_image=true` if captures may be sideways or upside down); each detected face gets an estimated age, and the largest face in the frame drives `age_estimation`. In parallel, a passive liveness model scores whether the photo shows a live person (no user challenge required) and detects presentation attacks (photos of photos, screens, masks). Submit one clear, front-facing, well-lit face per image for best results.\n\n**Decision logic.** Any warning declines: `status` is `Approved` only when the `warnings` array is empty. A liveness `score` at or below `face_liveness_score_decline_threshold` (default `30`) adds `LOW_LIVENESS_SCORE`; a detected attack adds `LIVENESS_FACE_ATTACK`; no face found by the liveness model adds `NO_FACE_DETECTED`; no estimable face age adds `AGE_NOT_DETECTED` (`age_estimation` is `null`); an estimated age below `age_estimation_decline_threshold` (default `18`) adds `AGE_BELOW_MINIMUM`. Borderline ages near your threshold deserve a fallback to document-based verification — the model's error grows for faces close to the limit.\n\n**Billing.** Each `200` response consumes one Age Estimation API credit (paid from your plan credit or top-up balance). When the organization's balance cannot cover the call, the endpoint returns `403` with the not-enough-credits error before any image processing.\n\n**Session persistence (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session: it appears in the Business Console, the returned `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/` (the result is exposed there in `liveness_checks[]`, with `features: [\"AGE_ESTIMATION\"]`), the face crop and reference image are stored, the face embedding is added to your application's face index (used by face search and duplicate detection), and a `status.updated` webhook is emitted. When `false`, nothing is stored and `request_id` is a one-off correlation UUID that cannot be looked up later.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload (`age_estimation: 30`, no warnings), no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3age-estimation", + "x-didit-sdk": { + "resource": "age_estimation", + "method": "estimate", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "user_image" + ], + "properties": { + "user_image": { + "type": "string", + "format": "binary", + "description": "Face photo to analyze (e.g. a selfie). Submit a single front-facing photo with the subject's face clearly visible and well lit. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp` (no PDF). Maximum upload size: **5 MB** (larger files are rejected with `400`). Images are automatically compressed to ~0.3 MB before processing, so very high resolutions do not improve accuracy." + }, + "face_liveness_score_decline_threshold": { + "type": "number", + "format": "float", + "default": 30, + "minimum": 0, + "maximum": 100, + "description": "Liveness threshold (0–100). A liveness `score` **at or below** this value adds a `LOW_LIVENESS_SCORE` warning and declines. Raise it for stricter anti-spoofing; values outside 0–100 return `400`.", + "example": 30 + }, + "age_estimation_decline_threshold": { + "type": "integer", + "default": 18, + "minimum": 0, + "maximum": 100, + "description": "Minimum acceptable estimated age in years. An estimated age **below** this value adds an `AGE_BELOW_MINIMUM` warning and declines. Set `0` to disable the age check (the call then only runs liveness + age estimation without an age-based decision).", + "example": 18 + }, + "rotate_image": { + "type": "boolean", + "default": false, + "description": "When `true`, the service tries the input image in 90-degree increments (0, 90, 180, 270) and uses the orientation that yields the best face detection — useful for mobile captures with missing EXIF orientation. Adds latency, so only enable it if you cannot guarantee upright images.", + "example": false + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, announced through a `status.updated` webhook, and with the detected face added to your application's face index. When `false`, nothing is stored and `request_id` is a transient UUID for response correlation only.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"age-gate\"}`) — it is parsed into an object.", + "example": { + "flow": "age-gate" + } + } + } + }, + "example": { + "user_image": "(binary JPEG/PNG of the selfie)", + "face_liveness_score_decline_threshold": 30, + "age_estimation_decline_threshold": 18, + "rotate_image": false, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "age-gate" + } + } + } + } + }, + "responses": { + "200": { + "description": "Age estimation and passive liveness completed. `age_estimation.age_estimation` is the model-predicted age in years of the largest detected face (`null` if none); `age_estimation.score` is the passive-liveness score (0–100); `status` is `Approved` only when `warnings` is empty. An underage or spoofed capture still returns `200` with `status: \"Declined\"` — inspect the body, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Live adult face — both checks passed", + "value": { + "request_id": "0c40ba43-64ab-4e2e-b4b8-7d1f12f81bc1", + "age_estimation": { + "status": "Approved", + "method": "PASSIVE", + "score": 97.5, + "user_image": { + "entities": [ + { + "age": 27.33, + "bbox": [ + 40, + 40, + 100, + 100 + ], + "confidence": 0.7177750468254089, + "gender": "male" + } + ], + "best_angle": 0 + }, + "age_estimation": 27.33, + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": { + "flow": "age-gate" + }, + "created_at": "2026-06-12T02:24:11.512941+00:00" + } + }, + "Declined - age below threshold": { + "summary": "Estimated age below `age_estimation_decline_threshold`", + "value": { + "request_id": "9be41a6f-2f4e-4f57-92f4-b3a4f6f0a1c2", + "age_estimation": { + "status": "Declined", + "method": "PASSIVE", + "score": 95.0, + "user_image": { + "entities": [ + { + "age": 16.5, + "bbox": [ + 40, + 40, + 100, + 100 + ], + "confidence": 0.7177750468254089, + "gender": "female" + } + ], + "best_angle": 0 + }, + "age_estimation": 16.5, + "warnings": [ + { + "risk": "AGE_BELOW_MINIMUM", + "feature": "LIVENESS", + "additional_data": null, + "log_type": "error", + "short_description": "Age below minimum", + "long_description": "The age of the face is below the minimum age threshold for the application." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:26:40.118332+00:00" + } + }, + "Declined - liveness too low": { + "summary": "Liveness score at or below the decline threshold (possible spoof)", + "value": { + "request_id": "5f6f2f1f-7c4f-43b9-8d62-0a8f4c2d9e77", + "age_estimation": { + "status": "Declined", + "method": "PASSIVE", + "score": 5.0, + "user_image": { + "entities": [ + { + "age": 25.0, + "bbox": [ + 40, + 40, + 100, + 100 + ], + "confidence": 0.7177750468254089, + "gender": "male" + } + ], + "best_angle": 0 + }, + "age_estimation": 25.0, + "warnings": [ + { + "risk": "LOW_LIVENESS_SCORE", + "feature": "LIVENESS", + "additional_data": null, + "log_type": "error", + "short_description": "Low liveness score", + "long_description": "The liveness check resulted in a low score, indicating potential use of non-live facial representations or poor-quality biometric data." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:27:02.901547+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "age_estimation": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Approved` when no warnings were raised; `Declined` otherwise (every warning this endpoint produces declines)." + }, + "method": { + "type": "string", + "enum": [ + "PASSIVE" + ], + "description": "Liveness method. Always `PASSIVE` on this endpoint (single-image, no user challenge)." + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Passive-liveness score from 0 to 100 — the model's confidence that the photo shows a live person. Compared against `face_liveness_score_decline_threshold`; at or below it the call declines with `LOW_LIVENESS_SCORE`. `null` when the liveness model returns no confidence — the call still declines with `LOW_LIVENESS_SCORE` because null is treated as 0 for the threshold comparison.", + "example": 97.5 + }, + "user_image": { + "type": "object", + "description": "Face-detection results for the analyzed image.", + "properties": { + "entities": { + "type": "array", + "description": "One entry per detected face. Empty when no face was found (then `age_estimation` is `null` and an `AGE_NOT_DETECTED` warning is added).", + "items": { + "type": "object", + "properties": { + "age": { + "type": "number", + "format": "float", + "description": "Model-estimated age of this face, in years.", + "example": 27.33 + }, + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box of the detected face as `[x_min, y_min, x_max, y_max]` pixel coordinates in the processed image.", + "example": [ + 40, + 40, + 100, + 100 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.7177750468254089 + }, + "gender": { + "type": "string", + "description": "Model-predicted gender of the detected face (`male` or `female`). Informational only.", + "example": "male" + } + } + } + }, + "best_angle": { + "type": "integer", + "nullable": true, + "description": "Rotation (degrees: 0, 90, 180, or 270) that produced the best face detection. Only non-zero when `rotate_image=true` corrected the orientation.", + "example": 0 + } + } + }, + "age_estimation": { + "type": "number", + "format": "float", + "nullable": true, + "description": "Model-predicted age in years of the **largest** detected face. `null` when no face age could be estimated (an `AGE_NOT_DETECTED` warning is added and the call declines).", + "example": 27.33 + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. Any entry sets `status` to `Declined`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "NO_FACE_DETECTED", + "LOW_LIVENESS_SCORE", + "LIVENESS_FACE_ATTACK", + "AGE_NOT_DETECTED", + "AGE_BELOW_MINIMUM" + ], + "description": "Machine-readable risk code. `NO_FACE_DETECTED` — the liveness model found no face; `LOW_LIVENESS_SCORE` — liveness `score` at or below `face_liveness_score_decline_threshold`; `LIVENESS_FACE_ATTACK` — a presentation attack (photo/screen/mask) was detected; `AGE_NOT_DETECTED` — no face age could be estimated; `AGE_BELOW_MINIMUM` — estimated age below `age_estimation_decline_threshold`. `NO_FACE_DETECTED` and `LOW_LIVENESS_SCORE` are mutually exclusive — when no face is found only `NO_FACE_DETECTED` is emitted." + }, + "feature": { + "type": "string", + "enum": [ + "LIVENESS" + ], + "description": "Feature that raised the warning. Always `LIVENESS` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Always `null` for age-estimation warnings." + }, + "log_type": { + "type": "string", + "enum": [ + "error" + ], + "description": "Severity. Every warning this endpoint produces is an `error` and sets `status` to `Declined`." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when `user_image` is missing, exceeds 5 MB, has an unsupported extension, or when a threshold is out of range. The body is DRF's standard field-error envelope: one array of messages per offending field.", + "content": { + "application/json": { + "examples": { + "Missing user image": { + "summary": "`user_image` not included in the form data", + "value": { + "user_image": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp", + "value": { + "user_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 5 MB limit", + "value": { + "user_image": [ + "File size should not exceed 5 MB" + ] + } + }, + "Threshold out of range": { + "summary": "`face_liveness_score_decline_threshold` outside 0–100", + "value": { + "face_liveness_score_decline_threshold": [ + "Ensure this value is less than or equal to 100." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, malformed, revoked, or belongs to another environment — and also when the calling organization's balance cannot cover the call. Authentication failures return `403` with `{\"detail\": ...}`; this API never returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. All POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/age-estimation/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'user_image=@./selfie.jpg' \\\n -F 'age_estimation_decline_threshold=18' \\\n -F 'face_liveness_score_decline_threshold=30' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nurl = 'https://verification.didit.me/v3/age-estimation/'\nheaders = {'x-api-key': 'YOUR_API_KEY'}\n\nwith open('selfie.jpg', 'rb') as f:\n files = {'user_image': ('selfie.jpg', f, 'image/jpeg')}\n data = {\n 'age_estimation_decline_threshold': 18,\n 'face_liveness_score_decline_threshold': 30,\n 'save_api_request': 'true',\n 'vendor_data': 'user-123',\n }\n resp = requests.post(url, headers=headers, files=files, data=data, timeout=60)\n\nresp.raise_for_status()\nresult = resp.json()['age_estimation']\nprint('status:', result['status'])\nprint('estimated age:', result['age_estimation'])\nprint('liveness score:', result['score'])\nfor w in result['warnings']:\n print('warning:', w['risk'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "import fs from 'node:fs';\n\nconst form = new FormData();\nform.append('user_image', new Blob([fs.readFileSync('./selfie.jpg')]), 'selfie.jpg');\nform.append('age_estimation_decline_threshold', '18');\nform.append('face_liveness_score_decline_threshold', '30');\nform.append('save_api_request', 'true');\nform.append('vendor_data', 'user-123');\n\nconst response = await fetch('https://verification.didit.me/v3/age-estimation/', {\n method: 'POST',\n headers: { 'x-api-key': 'YOUR_API_KEY' },\n body: form,\n});\n\nif (!response.ok) throw new Error(`Age estimation failed: ${response.status}`);\nconst { age_estimation } = await response.json();\nconsole.log('status:', age_estimation.status, 'age:', age_estimation.age_estimation, 'liveness:', age_estimation.score);" + } + ] + } + }, + "/v3/poa/": { + "post": { + "summary": "Proof of Address (document OCR + address checks)", + "description": "Extract and validate a Proof of Address document — utility bills, bank statements, and government-issued documents — in one call. Returns the issuer, issue/expiry dates, the holder name, the raw and parsed/geocoded address, an `Approved`/`Declined` `status`, and a `warnings` list explaining every issue found.\n\n**Latency.** Extraction is LLM-based: typical calls take **5–15 seconds**, multi-page PDFs up to **~30 seconds**. Configure a client timeout of **at least 45 seconds** and do not retry before the call completes.\n\n**How it works.** The document (image or PDF, up to 15 MB; encrypted PDFs require `document_password`) is classified into a document type and subtype (e.g. `UTILITY_BILL` / `WATER_BILL`), and the issuer, dates, holder name, and address are extracted with LLM-based extraction. The address is parsed and geocoded into `poa_parsed_address`. PDF/EXIF forensics screen for tampering — modification after digital signing, known PDF editors, overlay-text manipulation, suspicious re-exports — surfacing `SUSPECTED_DOCUMENT_MANIPULATION` or `DOCUMENT_METADATA_MISMATCH`. Document age is validated against `poa_document_age_months`, and the language against `poa_languages_allowed`. If you pass `expected_address`, `expected_country`, or `expected_first_name`/`expected_last_name`, the extracted values are cross-checked and mismatches are flagged.\n\n**Decision logic.** `status` is `Approved` unless at least one warning resolves to a decline. Hard failures always decline: `MISSING_ADDRESS_INFORMATION`, `POA_DOCUMENT_EXPIRED`, `INVALID_DOCUMENT_TYPE`, `UNABLE_TO_VALIDATE_DOCUMENT_AGE`, plus `UNABLE_TO_EXTRACT_ISSUE_DATE` and `POA_NAME_NOT_DETECTED` (not configurable on this endpoint). Five risk groups are configurable per request via the `poa_*_action` options (`DECLINE` or `NO_ACTION`, all defaulting to `DECLINE`). `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION` and `UNPARSABLE_OR_INVALID_ADDRESS` are informational on this endpoint and never decline. A document that cannot be processed at all returns `400`; a readable but problematic document returns `200` with `status: \"Declined\"` — always inspect `poa.status` and `poa.warnings`, not just the HTTP code.\n\n**Billing.** Each `200` response consumes one Proof of Address API credit (paid from your plan credit or top-up balance). When the organization's balance cannot cover the call, the endpoint returns `403` with the not-enough-credits error before any document processing.\n\n**Session persistence (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session: it appears in the Business Console, the uploaded document is stored, the returned `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/` (the decision additionally exposes `document_file` and the `document_metadata` forensics, which this response omits), and a `status.updated` webhook is emitted. When `false`, nothing is stored and `request_id` is a one-off correlation UUID that cannot be looked up later.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload (a fictional \"Sandbox Power Co.\" electricity bill), no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.\n\nNote: `FUTURE_ISSUE_DATE` and `POOR_DOCUMENT_QUALITY` exist in workflow sessions but are never produced by this standalone endpoint — a future-dated document is not auto-declined here.", + "operationId": "post_v3poa", + "x-didit-sdk": { + "resource": "proof_of_address", + "method": "verify", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "document" + ], + "properties": { + "document": { + "type": "string", + "format": "binary", + "description": "The Proof of Address document. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `pdf`, `webp`. Maximum upload size: **15 MB** (larger files are rejected with `400`). Multi-page PDFs are supported (expect higher latency); encrypted PDFs require `document_password`. Images are automatically compressed before processing." + }, + "document_password": { + "type": "string", + "description": "Password to decrypt `document` when it is an encrypted PDF. Sending an encrypted PDF without (or with a wrong) password returns `400`.", + "writeOnly": true + }, + "expected_address": { + "type": "string", + "description": "Address to verify against the document. It is geocoded and compared with the extracted address; a mismatch (or an expected address that cannot be geocoded/verified) adds `ADDRESS_MISMATCH_WITH_PROVIDED`, governed by `poa_address_mismatch_action`. The geocoded form is echoed back in `expected_details_formatted_address` / `expected_details_parsed_address`.", + "example": "Av. Monseñor Tavella 1291, Salta, Argentina" + }, + "expected_country": { + "type": "string", + "description": "Country to verify against the document (ISO 3166-1 code, e.g. `ARG` or `AR`). A mismatch with the document's country adds `POA_COUNTRY_MISMATCH_WITH_PROVIDED`, governed by `poa_address_mismatch_action`.", + "example": "ARG" + }, + "expected_first_name": { + "type": "string", + "description": "First name to verify against `name_on_document` (fuzzy match, transliteration-aware). A score below the match threshold adds `NAME_MISMATCH_WITH_PROVIDED`, governed by `poa_address_mismatch_action`.", + "example": "Sophia" + }, + "expected_last_name": { + "type": "string", + "description": "Last name to verify against `name_on_document`, combined with `expected_first_name` for the match score.", + "example": "Martinez" + }, + "poa_languages_allowed": { + "type": "string", + "description": "Comma-separated list of accepted document languages as ISO 639-1 codes (e.g. `en,es,fr`). When omitted or blank, **all 51 supported languages** are accepted. A document in a language outside the list adds `UNSUPPORTED_DOCUMENT_LANGUAGE` (governed by `poa_unsupported_language_action`); an unknown code in the list returns `400` with the full set of supported codes.", + "example": "en,es" + }, + "poa_document_age_months": { + "type": "string", + "description": "Comma-separated `key:value` pairs setting the maximum document age in months per document type — keys: `utility_bill`, `bank_statement`, `government_issued_document`, `other_poa_document`; values: `1`–`120`, or `-1` for unlimited (no age check). Defaults when omitted: `utility_bill:3,bank_statement:3,government_issued_document:12,other_poa_document:12`. A document older than its limit declines with `POA_DOCUMENT_EXPIRED`. **Caution:** when you provide this field, list *every* type you accept — omitted types are treated as not accepted (informational `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION`) and their age check is skipped.", + "example": "utility_bill:3,bank_statement:6" + }, + "poa_name_mismatch_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "Accepted but **currently not applied** — name-mismatch warnings (`NAME_MISMATCH_WITH_PROVIDED`) follow `poa_address_mismatch_action` instead. Set `poa_address_mismatch_action` to control both name and address mismatch behavior." + }, + "poa_document_issues_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do on file-integrity problems — in practice only `DOCUMENT_METADATA_MISMATCH` is produced by this endpoint." + }, + "poa_document_authenticity_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when document manipulation is suspected (`SUSPECTED_DOCUMENT_MANIPULATION`) — e.g. the PDF was modified after digital signing, processed by a known PDF editor, shows overlay-text manipulation, or has inconsistent EXIF dates." + }, + "poa_unsupported_language_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when the document language is outside `poa_languages_allowed` (`UNSUPPORTED_DOCUMENT_LANGUAGE`)." + }, + "poa_address_mismatch_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when the extracted details disagree with the `expected_*` fields: `ADDRESS_MISMATCH_WITH_PROVIDED`, `NAME_MISMATCH_WITH_PROVIDED`, and `POA_COUNTRY_MISMATCH_WITH_PROVIDED` all follow this action." + }, + "poa_issuer_not_identified_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when no issuing company/organization can be identified on the document (`ISSUER_NOT_IDENTIFIED`)." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, announced through a `status.updated` webhook, and with the uploaded document stored. When `false`, nothing is stored and `request_id` is a transient UUID for response correlation only.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"onboarding\"}`) — it is parsed into an object.", + "example": { + "flow": "onboarding" + } + } + } + }, + "example": { + "document": "(binary JPEG/PNG/PDF of the utility bill or bank statement)", + "expected_address": "Av. Monseñor Tavella 1291, Salta, Argentina", + "expected_country": "ARG", + "expected_first_name": "Sophia", + "expected_last_name": "Martinez", + "poa_languages_allowed": "en,es", + "save_api_request": true, + "vendor_data": "user-123" + } + } + } + }, + "responses": { + "200": { + "description": "Document processed. `poa.status` is `Approved` or `Declined`; every detected issue is itemized in `poa.warnings`. An expired, mismatched, or suspicious document still returns `200` with `status: \"Declined\"` — inspect the body, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Utility bill read cleanly, no expected-details checks", + "value": { + "request_id": "4b523a8d-ed32-45e4-8aa8-a89eda459d88", + "poa": { + "issuing_state": "ARG", + "document_type": "UTILITY_BILL", + "document_subtype": "WATER_BILL", + "document_language": "es", + "issuer": "Aguas del Norte", + "issue_date": "2026-04-10", + "expiration_date": null, + "poa_address": "AVDA. MONSEÑOR TAVELLA N° 1291, B° VILLA LAVALLE CP: 4400, CAPITAL - SALTA", + "poa_formatted_address": "Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina", + "poa_parsed_address": { + "street_1": "Avenida Monseñor Tavella 1291", + "street_2": "b", + "city": "Salta", + "region": "Salta", + "country": "AR", + "postal_code": "A4400", + "document_location": { + "latitude": -24.8208682, + "longitude": -65.4130954 + } + }, + "expected_details_address": null, + "expected_details_formatted_address": null, + "expected_details_parsed_address": null, + "name_on_document": "OESTE EMBOTELLADORA S.A.", + "extra_fields": { + "bank_account_number": null, + "bank_iban": null, + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": null, + "additional_names": [ + "LA EMBOTELLADORA DEL NORTE SA" + ] + }, + "detected_codes": [ + { + "type": "QR_CODE", + "page_number": 1, + "position": [ + [ + 820, + 1120 + ], + [ + 1002, + 1120 + ], + [ + 1002, + 1302 + ], + [ + 820, + 1302 + ] + ], + "x": 820, + "y": 1120, + "width": 182, + "height": 182, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "aHR0cHM6Ly9hZ3Vhc2RlbG5vcnRlLmNvbS9mYWN0dXJhLzk5ODgxMQ==", + "parsed_payload": "https://aguasdelnorte.com/factura/998811" + }, + { + "type": "CODE_128", + "page_number": 1, + "position": [ + [ + 112, + 1480 + ], + [ + 560, + 1480 + ], + [ + 560, + 1540 + ], + [ + 112, + 1540 + ] + ], + "x": 112, + "y": 1480, + "width": 448, + "height": 60, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "MDA5OTg4MTEwMDA0Mg==", + "parsed_payload": "0099881100042" + } + ], + "status": "Approved", + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:29:02.439655+00:00" + } + }, + "Declined - document too old": { + "summary": "Issue date older than the allowed `poa_document_age_months`", + "value": { + "request_id": "f61681b8-9422-4378-ac3e-04c6a247fb45", + "poa": { + "issuing_state": "ARG", + "document_type": "UTILITY_BILL", + "document_subtype": "WATER_BILL", + "document_language": "es", + "issuer": "Aguas del Norte", + "issue_date": "2021-02-10", + "expiration_date": null, + "poa_address": "AVDA. MONSEÑOR TAVELLA N° 1291, B° VILLA LAVALLE CP: 4400, CAPITAL - SALTA", + "poa_formatted_address": "Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina", + "poa_parsed_address": { + "street_1": "Avenida Monseñor Tavella 1291", + "street_2": "b", + "city": "Salta", + "region": "Salta", + "country": "AR", + "postal_code": "A4400", + "document_location": { + "latitude": -24.8208682, + "longitude": -65.4130954 + } + }, + "expected_details_address": null, + "expected_details_formatted_address": null, + "expected_details_parsed_address": null, + "name_on_document": "OESTE EMBOTELLADORA S.A.", + "extra_fields": { + "bank_account_number": null, + "bank_iban": null, + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": null, + "additional_names": [ + "LA EMBOTELLADORA DEL NORTE SA" + ] + }, + "status": "Declined", + "warnings": [ + { + "risk": "POA_DOCUMENT_EXPIRED", + "feature": "PROOF_OF_ADDRESS", + "additional_data": { + "expiration_reason": "age_window", + "max_age_months": 3, + "document_subtype": "WATER_BILL", + "document_type": "UTILITY_BILL", + "issue_date": "2021-02-10" + }, + "log_type": "error", + "short_description": "Document expired", + "long_description": "The submitted document is older than the maximum accepted age of 3 months from its issue date, which exceeds the acceptable time period for validity." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:23:02.165493+00:00" + } + }, + "Declined - name mismatch": { + "summary": "`expected_first_name`/`expected_last_name` do not match `name_on_document`", + "value": { + "request_id": "0a7f2f9c-58f1-4b8e-b1e3-2f6f9d4f7a21", + "poa": { + "issuing_state": "ARG", + "document_type": "UTILITY_BILL", + "document_subtype": "WATER_BILL", + "document_language": "es", + "issuer": "Aguas del Norte", + "issue_date": "2021-02-10", + "expiration_date": null, + "poa_address": "AVDA. MONSEÑOR TAVELLA N° 1291, B° VILLA LAVALLE CP: 4400, CAPITAL - SALTA", + "poa_formatted_address": "Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina", + "poa_parsed_address": { + "street_1": "Avenida Monseñor Tavella 1291", + "street_2": "b", + "city": "Salta", + "region": "Salta", + "country": "AR", + "postal_code": "A4400", + "document_location": { + "latitude": -24.8208682, + "longitude": -65.4130954 + } + }, + "expected_details_address": null, + "expected_details_formatted_address": null, + "expected_details_parsed_address": null, + "name_on_document": "OESTE EMBOTELLADORA S.A.", + "extra_fields": { + "bank_account_number": null, + "bank_iban": null, + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": null, + "additional_names": [ + "LA EMBOTELLADORA DEL NORTE SA" + ] + }, + "status": "Declined", + "warnings": [ + { + "risk": "NAME_MISMATCH_WITH_PROVIDED", + "feature": "PROOF_OF_ADDRESS", + "additional_data": null, + "log_type": "error", + "short_description": "Name mismatch with provided information", + "long_description": "The full name on the document does not match the name from the user's verified identity documents, or the full name sent by API." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:27:21.330172+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "poa": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Approved` when no warning resolves to a decline; `Declined` otherwise — the `warnings` list explains why." + }, + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Country the document was issued in, as extracted from the document — usually ISO 3166-1 alpha-3 (e.g. `ARG`).", + "example": "ARG" + }, + "document_type": { + "type": "string", + "enum": [ + "UTILITY_BILL", + "BANK_STATEMENT", + "GOVERNMENT_ISSUED_DOCUMENT", + "OTHER_POA_DOCUMENT", + "UNKNOWN" + ], + "description": "Detected document category. `UNKNOWN` means the document could not be classified as an acceptable Proof of Address and auto-declines with `INVALID_DOCUMENT_TYPE`. Never null — classification failures fall back to `UNKNOWN`." + }, + "document_subtype": { + "type": "string", + "description": "Finer-grained classification within `document_type` — e.g. `ELECTRICITY_BILL`, `WATER_BILL`, `GAS_BILL`, `INTERNET_BILL`, `PHONE_BILL` (utility bills); `ACCOUNT_STATEMENT`, `CREDIT_CARD_STATEMENT` (bank statements); `TAX_ASSESSMENT`, `RESIDENCY_CERTIFICATE` (government documents). `UNKNOWN` when no subtype could be determined. Never null — classification failures fall back to `UNKNOWN`.", + "example": "WATER_BILL" + }, + "document_language": { + "type": "string", + "nullable": true, + "description": "Detected document language (ISO 639-1).", + "example": "es" + }, + "issuer": { + "type": "string", + "nullable": true, + "description": "Company or organization that issued the document. `null` adds `ISSUER_NOT_IDENTIFIED`.", + "example": "Aguas del Norte" + }, + "issue_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Document issue date (`YYYY-MM-DD`). `null` when it could not be extracted — that declines with `UNABLE_TO_EXTRACT_ISSUE_DATE` on this endpoint.", + "example": "2021-02-10" + }, + "expiration_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Explicit expiry/validity date printed on the document, when present. Independent of the `poa_document_age_months` age check." + }, + "poa_address": { + "type": "string", + "nullable": true, + "description": "Address exactly as printed on the document. `null` auto-declines with `MISSING_ADDRESS_INFORMATION`." + }, + "poa_formatted_address": { + "type": "string", + "nullable": true, + "description": "Geocoded, normalized version of `poa_address`.", + "example": "Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina" + }, + "poa_parsed_address": { + "type": "object", + "nullable": true, + "description": "Structured, geocoded breakdown of the document address. `null` when the address could not be parsed.", + "properties": { + "street_1": { + "type": "string", + "nullable": true, + "example": "Avenida Monseñor Tavella 1291" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "city": { + "type": "string", + "nullable": true, + "example": "Salta" + }, + "region": { + "type": "string", + "nullable": true, + "example": "Salta" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code.", + "example": "AR" + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "A4400" + }, + "document_location": { + "type": "object", + "nullable": true, + "properties": { + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + } + }, + "description": "Geocoded coordinates of the document address." + } + } + }, + "expected_details_address": { + "type": "string", + "nullable": true, + "description": "Echo of the `expected_address` you sent, or `null`." + }, + "expected_details_formatted_address": { + "type": "string", + "nullable": true, + "description": "Geocoded, normalized version of `expected_address`; `null` when not provided or not geocodable." + }, + "expected_details_parsed_address": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Full geocoding result for `expected_address` (street/city/region/country/postal code plus raw geocoder output and an `is_verified` flag). `null` when `expected_address` was not provided." + }, + "name_on_document": { + "type": "string", + "nullable": true, + "description": "Primary holder name extracted from the document; compared against `expected_first_name`/`expected_last_name` when provided. `null` declines with `POA_NAME_NOT_DETECTED` on this endpoint.", + "example": "OESTE EMBOTELLADORA S.A." + }, + "extra_fields": { + "type": "object", + "description": "Document-specific extras. Bank fields are populated for bank statements when printed on the document; `null` otherwise.", + "properties": { + "bank_account_number": { + "type": "string", + "nullable": true + }, + "bank_iban": { + "type": "string", + "nullable": true + }, + "bank_sort_code": { + "type": "string", + "nullable": true + }, + "bank_routing_number": { + "type": "string", + "nullable": true + }, + "bank_swift_bic": { + "type": "string", + "nullable": true + }, + "bank_branch_name": { + "type": "string", + "nullable": true + }, + "bank_branch_address": { + "type": "string", + "nullable": true + }, + "document_phone_number": { + "type": "string", + "nullable": true, + "description": "Contact phone number printed on the document." + }, + "additional_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Other holder names found on the document (e.g. co-account holders, previous account names)." + } + } + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. Every entry explains one detected risk; entries with `log_type: \"error\"` set `status` to `Declined`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "description": "Machine-readable risk code. Always-decline: `MISSING_ADDRESS_INFORMATION`, `POA_DOCUMENT_EXPIRED`, `INVALID_DOCUMENT_TYPE`, `UNABLE_TO_VALIDATE_DOCUMENT_AGE`, plus `UNABLE_TO_EXTRACT_ISSUE_DATE` and `POA_NAME_NOT_DETECTED` (fixed to decline on this endpoint). Governed by request options: `NAME_MISMATCH_WITH_PROVIDED` / `ADDRESS_MISMATCH_WITH_PROVIDED` / `POA_COUNTRY_MISMATCH_WITH_PROVIDED` (`poa_address_mismatch_action`), `POOR_DOCUMENT_QUALITY` (never produced by this endpoint) / `DOCUMENT_METADATA_MISMATCH` (`poa_document_issues_action`), `SUSPECTED_DOCUMENT_MANIPULATION` (`poa_document_authenticity_action`), `UNSUPPORTED_DOCUMENT_LANGUAGE` (`poa_unsupported_language_action`), `ISSUER_NOT_IDENTIFIED` (`poa_issuer_not_identified_action`). Informational on this endpoint: `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION`, `UNPARSABLE_OR_INVALID_ADDRESS`.", + "example": "POA_DOCUMENT_EXPIRED" + }, + "feature": { + "type": "string", + "enum": [ + "PROOF_OF_ADDRESS" + ], + "description": "Feature that raised the warning. Always `PROOF_OF_ADDRESS` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Extra context for some risks — e.g. `POA_DOCUMENT_EXPIRED` includes `expiration_reason` (`printed_expiration` or `age_window`), `document_type`, `document_subtype`, and `issue_date`, plus `expiration_date` on the `printed_expiration` path or `max_age_months` on the `age_window` path; `SUSPECTED_DOCUMENT_MANIPULATION` includes `reason` and `detection_method`. `null` for most risks." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `information` entries are advisory and never decline on their own. (`warning` is not produced by this endpoint — its options only accept `DECLINE` or `NO_ACTION`.)" + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "detected_codes": { + "type": "array", + "description": "QR codes and barcodes detected across every processed page of the document. Empty when none are found. A located but undecodable QR code has `parsed_payload: null`.", + "items": { + "$ref": "#/components/schemas/DocumentAIDetectedCode" + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated." + } + } + } + } + } + }, + "400": { + "description": "Validation or processing error. Field-level problems return DRF's standard envelope (one array of messages per offending field). Encrypted-PDF problems return `{\"detail\": ...}`. A document the extraction pipeline cannot process returns `{\"error\": [\"Error extracting POA information\"]}`.", + "content": { + "application/json": { + "examples": { + "Missing document": { + "summary": "`document` not included in the form data", + "value": { + "document": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/pdf/webp", + "value": { + "document": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, pdf, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 15 MB limit", + "value": { + "document": [ + "File size should not exceed 15 MB" + ] + } + }, + "Encrypted PDF": { + "summary": "PDF uploaded without the required password", + "value": { + "detail": "The PDF is encrypted. Please upload a decrypted PDF or a photo instead." + } + }, + "Wrong PDF password": { + "summary": "`document_password` does not decrypt the PDF", + "value": { + "detail": "The PDF password is incorrect. Please provide the correct password." + } + }, + "Invalid language code": { + "summary": "`poa_languages_allowed` contains an unsupported code", + "value": { + "poa_languages_allowed": [ + "Invalid language code: 'xx'. Must be one of the supported languages: ['ar', 'bn', 'hy', 'bg', 'bs', 'ca', 'cnr', 'sq', 'zh', 'hr', 'cs', 'da', 'nl', 'en', 'et', 'fi', 'fr', 'ka', 'kk', 'de', 'el', 'he', 'hi', 'hu', 'id', 'it', 'ja', 'ko', 'ky', 'lv', 'lt', 'mk', 'mn', 'ms', 'no', 'fa', 'pl', 'pt', 'ro', 'ru', 'sr', 'sk', 'sl', 'so', 'es', 'sv', 'th', 'tr', 'uk', 'uz', 'vi']." + ] + } + }, + "Invalid document age value": { + "summary": "`poa_document_age_months` value outside 1–120 / -1", + "value": { + "poa_document_age_months": [ + "Invalid integer value for 'utility_bill': '999'. Must be -1 (unlimited) or a positive integer between 1 and 120." + ] + } + }, + "Extraction failure": { + "summary": "The document could not be processed by the extraction pipeline", + "value": { + "error": [ + "Error extracting POA information" + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, malformed, revoked, or belongs to another environment — and also when the calling organization's balance cannot cover the call. Authentication failures return `403` with `{\"detail\": ...}`; this API never returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any document processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. All POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/poa/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'document=@./utility-bill.pdf' \\\n -F 'expected_address=Av. Monseñor Tavella 1291, Salta, Argentina' \\\n -F 'expected_country=ARG' \\\n -F 'expected_first_name=Sophia' \\\n -F 'expected_last_name=Martinez' \\\n -F 'poa_languages_allowed=en,es' \\\n -F 'vendor_data=user-123' \\\n --max-time 45" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nurl = 'https://verification.didit.me/v3/poa/'\nheaders = {'x-api-key': 'YOUR_API_KEY'}\n\nwith open('utility-bill.pdf', 'rb') as f:\n files = {'document': ('utility-bill.pdf', f, 'application/pdf')}\n data = {\n 'expected_address': 'Av. Monseñor Tavella 1291, Salta, Argentina',\n 'expected_country': 'ARG',\n 'expected_first_name': 'Sophia',\n 'expected_last_name': 'Martinez',\n 'poa_languages_allowed': 'en,es',\n 'vendor_data': 'user-123',\n }\n # LLM-based extraction takes 5-15s (multi-page PDFs up to ~30s) - allow headroom\n resp = requests.post(url, headers=headers, files=files, data=data, timeout=45)\n\nresp.raise_for_status()\npoa = resp.json()['poa']\nprint('status:', poa['status'])\nprint('issuer:', poa['issuer'], '| issued:', poa['issue_date'])\nprint('address:', poa['poa_formatted_address'])\nfor w in poa['warnings']:\n print('warning:', w['risk'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "import fs from 'node:fs';\n\nconst form = new FormData();\nform.append('document', new Blob([fs.readFileSync('./utility-bill.pdf')]), 'utility-bill.pdf');\nform.append('expected_address', 'Av. Monseñor Tavella 1291, Salta, Argentina');\nform.append('expected_country', 'ARG');\nform.append('expected_first_name', 'Sophia');\nform.append('expected_last_name', 'Martinez');\nform.append('poa_languages_allowed', 'en,es');\nform.append('vendor_data', 'user-123');\n\n// LLM-based extraction takes 5-15s (multi-page PDFs up to ~30s) - allow >=45s\nconst response = await fetch('https://verification.didit.me/v3/poa/', {\n method: 'POST',\n headers: { 'x-api-key': 'YOUR_API_KEY' },\n body: form,\n signal: AbortSignal.timeout(45_000),\n});\n\nif (!response.ok) throw new Error(`POA verification failed: ${response.status}`);\nconst { poa } = await response.json();\nconsole.log('status:', poa.status);\nconsole.log('warnings:', poa.warnings.map((w) => w.risk));" + } + ] + } + }, + "/v3/document-ai/": { + "post": { + "summary": "Document AI (custom field extraction + tamper checks)", + "description": "Extract the fields **you** define from any supporting document — payslips, proof of funds, source-of-wealth letters, tax statements, contracts, invoices — in one call. Send the file plus a `fields` definition (key, name, extraction instruction, type) and get back typed values (`text`, `number`, `date` as `YYYY-MM-DD`), an optional name-match score against the expected person or company, PDF/EXIF tamper signals, an `Approved`/`Declined`/`In Review` `status`, and a `warnings` list explaining every issue found. This is the server-to-server counterpart of the hosted Document AI workflow step (see `/core-technology/document-ai/overview`); it uses Didit's default extraction model and does not expose provider selection.\n\n**Latency.** Extraction is LLM-based: typical calls take **5–15 seconds**, multi-page PDFs up to **~30 seconds**. Configure a client timeout of **at least 45 seconds** and do not retry before the call completes.\n\n**Request format.** `multipart/form-data` only. `fields` is **one JSON-encoded array string** (1–30 items, at most 16 KB); bracket-style form arrays are rejected with `400`. Each item is `{key, name, instruction, type, required, is_full_name}` — `key` is the JSON key you get back (`^[a-z0-9_]{1,64}$`, unique), `instruction` tells the model what to read, `type` is `text` (default) | `number` | `date`, `required` raises `DOCUMENT_AI_MISSING_REQUIRED_FIELDS` when the value cannot be read, and at most one field may set `is_full_name: true` to mark the value compared against the expected name.\n\n**Name matching.** Pass `expected_first_name` + `expected_last_name` (person) **or** `expected_company_name` (+ optional `expected_company_country`) — not both. The value of the `is_full_name` field (or, when none is marked, the first text field whose key looks like a name) is compared fuzzily; `name_match_score` (0–100) and `name_on_document` are always returned when both sides exist, and a score below `document_ai_name_match_score_threshold` (default 80) adds `DOCUMENT_AI_NAME_MISMATCH`, governed by `document_ai_name_mismatch_action`.\n\n**Decision logic.** Each risk maps to a configurable action: `DECLINE` → `status: \"Declined\"`, `REVIEW` → `status: \"In Review\"`, `NO_ACTION` → informational only. Defaults mirror the workflow step: unreadable document `REVIEW`, missing required fields `REVIEW`, suspected manipulation `DECLINE`, name mismatch `NO_ACTION`, unsupported file `REVIEW`. A document that cannot be processed at all returns `400`; a readable but problematic document returns `200` with `Declined`/`In Review` — always inspect `document_ai.status` and `document_ai.warnings`, not just the HTTP code.\n\n**Billing.** Each `200` response consumes one Document AI API credit — **$0.20 per document** at list, paid from your plan credit or top-up balance. When the organization's balance cannot cover the call, the endpoint returns `403` before any processing. Sandbox API keys return a deterministic mock response and are not charged.\n\n**Persistence.** With `save_api_request=true` (default) the call is stored as an API-type session: the document, the extracted values, the field definitions and the name-match snapshot are kept, the session shows up in the Business Console, and `request_id` works with `GET /v3/session/{sessionId}/decision/`.", + "operationId": "post_v3document-ai", + "x-didit-sdk": { + "resource": "document_ai", + "method": "analyze", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "document", + "fields" + ], + "properties": { + "document": { + "type": "string", + "format": "binary", + "description": "The document to read. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `pdf`, `webp`, `zip`. Maximum upload size: **30 MB** (larger files are rejected with `400`). Multi-page PDFs are supported (expect higher latency); encrypted PDFs are rejected with `400`. Images are automatically compressed before processing." + }, + "fields": { + "type": "string", + "description": "One JSON-encoded array string with **1–30** field definitions (maximum 16 KB). Each item: `key` (required, `^[a-z0-9_]{1,64}$`, unique — the key returned in `extracted_data`), `name` (≤100 chars, shown in messages), `instruction` (≤500 chars, what the model should read), `type` (`text` default | `number` | `date`), `required` (boolean, default `false`), `is_full_name` (boolean, default `false`, at most one per request). Bracket-style form arrays, invalid JSON, duplicate keys, or more than one `is_full_name` return `400` before any processing or billing.", + "example": "[{\"key\": \"employer_name\", \"name\": \"Employer name\", \"instruction\": \"The employer or company issuing the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": false}, {\"key\": \"employee_name\", \"name\": \"Employee name\", \"instruction\": \"The full name of the employee as printed on the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": true}, {\"key\": \"net_pay\", \"name\": \"Net pay\", \"instruction\": \"The net amount paid to the employee for the period.\", \"type\": \"number\", \"required\": true, \"is_full_name\": false}, {\"key\": \"pay_date\", \"name\": \"Pay date\", \"instruction\": \"The payment date shown on the payslip.\", \"type\": \"date\", \"required\": false, \"is_full_name\": false}]" + }, + "expected_first_name": { + "type": "string", + "description": "First name to verify against the document's name field (fuzzy match, transliteration-aware). Use together with `expected_last_name`; cannot be combined with `expected_company_name`.", + "example": "Sophia" + }, + "expected_last_name": { + "type": "string", + "description": "Last name to verify against the document's name field, combined with `expected_first_name` for `name_match_score`.", + "example": "Martinez" + }, + "expected_company_name": { + "type": "string", + "description": "Company name to verify against the document's name field (for business documents). Cannot be combined with `expected_first_name` / `expected_last_name` (no example value on purpose — the generated request sample uses the person fields; swap them for this one when verifying a business document)." + }, + "expected_company_country": { + "type": "string", + "description": "Optional ISO 3166-1 alpha-2 country code (two ASCII letters, e.g. `FR`) that tunes company-name normalization for `expected_company_name`. Only used with `expected_company_name`." + }, + "document_ai_name_match_score_threshold": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 80, + "description": "Minimum `name_match_score` (0–100) to consider the name matched. A lower score adds `DOCUMENT_AI_NAME_MISMATCH`, governed by `document_ai_name_mismatch_action`.", + "example": 80 + }, + "document_ai_unreadable_document_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "REVIEW", + "description": "What to do when no field could be read from the document (`DOCUMENT_AI_UNREADABLE_DOCUMENT`)." + }, + "document_ai_missing_required_fields_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "REVIEW", + "description": "What to do when at least one `required` field could not be read (`DOCUMENT_AI_MISSING_REQUIRED_FIELDS`; `additional_data.missing_fields` lists them)." + }, + "document_ai_document_tampering_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when PDF/EXIF forensics suspect manipulation — modified after digital signing, known PDF editor, overlay-text manipulation, suspicious re-export, image anomalies (`DOCUMENT_AI_SUSPECTED_MANIPULATION`)." + }, + "document_ai_name_mismatch_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "NO_ACTION", + "description": "What to do when `name_match_score` is below the threshold (`DOCUMENT_AI_NAME_MISMATCH`)." + }, + "document_ai_unsupported_file_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "REVIEW", + "description": "What to do when the upload is empty or not a processable file (`DOCUMENT_AI_UNSUPPORTED_FILE`)." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, with the uploaded document, the extracted values, the field definitions and the name-match snapshot stored. When `false`, nothing is stored and `request_id` is a transient UUID for response correlation only; the call is still billed.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"onboarding\"}`) — it is parsed into an object.", + "example": { + "flow": "onboarding" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Document processed. `document_ai.status` is `Approved`, `Declined`, or `In Review`; every detected issue is itemized in `document_ai.warnings`. A problematic document still returns `200` — inspect the body, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Payslip read cleanly, name matched", + "value": { + "request_id": "9d6a3b1e-2f0c-4a8e-9b7d-1c2e3f4a5b6c", + "document_ai": { + "status": "Approved", + "extracted_data": { + "employer_name": "Acme Payments Ltd", + "employee_name": "Sophia Martinez", + "net_pay": 3120.5, + "pay_date": "2026-07-31" + }, + "detected_codes": [ + { + "type": "CODE_128", + "page_number": 1, + "position": [ + [ + 96, + 456 + ], + [ + 96, + 616 + ], + [ + 1012, + 611 + ], + [ + 1015, + 456 + ] + ], + "x": 96, + "y": 456, + "width": 919, + "height": 160, + "page_width": 1200, + "page_height": 1700, + "raw_payload": "SU5WLTIwMjYtMDA0Mg==", + "parsed_payload": "INV-2026-0042" + } + ], + "fields": [ + { + "key": "employer_name", + "name": "Employer name", + "instruction": "The employer or company issuing the payslip.", + "type": "text", + "required": true, + "is_full_name": false + }, + { + "key": "employee_name", + "name": "Employee name", + "instruction": "The full name of the employee as printed on the payslip.", + "type": "text", + "required": true, + "is_full_name": true + }, + { + "key": "net_pay", + "name": "Net pay", + "instruction": "The net amount paid to the employee for the period.", + "type": "number", + "required": true, + "is_full_name": false + }, + { + "key": "pay_date", + "name": "Pay date", + "instruction": "The payment date shown on the payslip.", + "type": "date", + "required": false, + "is_full_name": false + } + ], + "name_match_score": 100.0, + "name_on_document": "Sophia Martinez", + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": { + "flow": "onboarding" + }, + "created_at": "2026-08-19T16:40:12.318Z" + } + }, + "In Review — missing required field": { + "summary": "`net_pay` could not be read; default action REVIEW", + "value": { + "request_id": "3f2a9c7b-5d4e-4b1a-8c6f-0e9d8c7b6a5f", + "document_ai": { + "status": "In Review", + "extracted_data": { + "employer_name": "Acme Payments Ltd", + "employee_name": "Sophia Martinez", + "net_pay": null, + "pay_date": "2026-07-31" + }, + "detected_codes": [], + "fields": [ + { + "key": "employer_name", + "name": "Employer name", + "instruction": "The employer or company issuing the payslip.", + "type": "text", + "required": true, + "is_full_name": false + }, + { + "key": "employee_name", + "name": "Employee name", + "instruction": "The full name of the employee as printed on the payslip.", + "type": "text", + "required": true, + "is_full_name": true + }, + { + "key": "net_pay", + "name": "Net pay", + "instruction": "The net amount paid to the employee for the period.", + "type": "number", + "required": true, + "is_full_name": false + }, + { + "key": "pay_date", + "name": "Pay date", + "instruction": "The payment date shown on the payslip.", + "type": "date", + "required": false, + "is_full_name": false + } + ], + "name_match_score": 100.0, + "name_on_document": "Sophia Martinez", + "warnings": [ + { + "risk": "DOCUMENT_AI_MISSING_REQUIRED_FIELDS", + "feature": "DOCUMENT_AI", + "additional_data": { + "missing_fields": [ + "Net pay" + ] + }, + "log_type": "warning", + "short_description": "Required fields could not be extracted", + "long_description": "One or more fields configured as required could not be extracted from the uploaded document." + } + ] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-08-19T16:41:05.002Z" + } + }, + "Declined — suspected manipulation": { + "summary": "PDF forensics flagged a known editor; default action DECLINE", + "value": { + "request_id": "7c1d2e3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f", + "document_ai": { + "status": "Declined", + "extracted_data": { + "employer_name": "Acme Payments Ltd", + "employee_name": "Sophia Martinez", + "net_pay": 3120.5, + "pay_date": "2026-07-31" + }, + "detected_codes": [], + "fields": [ + { + "key": "employer_name", + "name": "Employer name", + "instruction": "The employer or company issuing the payslip.", + "type": "text", + "required": true, + "is_full_name": false + }, + { + "key": "employee_name", + "name": "Employee name", + "instruction": "The full name of the employee as printed on the payslip.", + "type": "text", + "required": true, + "is_full_name": true + }, + { + "key": "net_pay", + "name": "Net pay", + "instruction": "The net amount paid to the employee for the period.", + "type": "number", + "required": true, + "is_full_name": false + }, + { + "key": "pay_date", + "name": "Pay date", + "instruction": "The payment date shown on the payslip.", + "type": "date", + "required": false, + "is_full_name": false + } + ], + "name_match_score": null, + "name_on_document": "Sophia Martinez", + "warnings": [ + { + "risk": "DOCUMENT_AI_SUSPECTED_MANIPULATION", + "feature": "DOCUMENT_AI", + "additional_data": { + "detection_method": "known_editor", + "processed_by_known_editor": "ilovepdf" + }, + "log_type": "error", + "short_description": "Suspected document manipulation", + "long_description": "The system detected signs of potential manipulation or editing in the uploaded document." + } + ] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-08-19T16:42:40.771Z" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "document_ai": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "description": "`Approved` when no warning resolves to a decline or review; `Declined` when at least one risk maps to `DECLINE`; otherwise `In Review` when at least one maps to `REVIEW`. The `warnings` list explains why." + }, + "extracted_data": { + "type": "object", + "additionalProperties": true, + "description": "One entry per requested field key. Values are typed per the field `type` — strings for `text`, numbers for `number`, `YYYY-MM-DD` strings for `date` — or `null` when the value could not be read.", + "example": { + "employer_name": "Acme Payments Ltd", + "employee_name": "Sophia Martinez", + "net_pay": 3120.5, + "pay_date": "2026-07-31" + } + }, + "detected_codes": { + "type": "array", + "description": "QR codes and barcodes detected across every processed page. Empty when none are found. A located but undecodable QR code has `parsed_payload: null`.", + "items": { + "$ref": "#/components/schemas/DocumentAIDetectedCode" + } + }, + "fields": { + "type": "array", + "description": "The normalized field definitions that were used for this extraction, echoed back (also stored with the session when `save_api_request=true`).", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "instruction": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "text", + "number", + "date" + ] + }, + "required": { + "type": "boolean" + }, + "is_full_name": { + "type": "boolean" + } + } + } + }, + "name_match_score": { + "type": "number", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Fuzzy similarity (0–100) between `name_on_document` and the expected person/company name. Returned whenever both a name value and an expected name exist — on matches **and** mismatches; `null` otherwise.", + "example": 96.0 + }, + "name_on_document": { + "type": "string", + "nullable": true, + "description": "The name value taken from the document (the `is_full_name` field, or the first name-like text field) that was compared against the expected name. `null` when no name value was available.", + "example": "Sophia Martinez" + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. Every entry explains one detected risk; entries with `log_type: \"error\"` set `status` to `Declined`, entries with `log_type: \"warning\"` set it to `In Review`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "DOCUMENT_AI_UNREADABLE_DOCUMENT", + "DOCUMENT_AI_MISSING_REQUIRED_FIELDS", + "DOCUMENT_AI_SUSPECTED_MANIPULATION", + "DOCUMENT_AI_NAME_MISMATCH", + "DOCUMENT_AI_UNSUPPORTED_FILE" + ], + "description": "Machine-readable risk code. Each one is governed by the matching `document_ai_*_action` request option.", + "example": "DOCUMENT_AI_MISSING_REQUIRED_FIELDS" + }, + "feature": { + "type": "string", + "enum": [ + "DOCUMENT_AI" + ], + "description": "Feature that raised the warning. Always `DOCUMENT_AI` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Extra context for the risk: `{\"missing_fields\": [\"Net pay\"]}` for missing required fields, `{\"name_on_document\", \"score\", \"threshold\"}` for a name mismatch, and the forensic finding (`detection_method`, producer/editor details) for suspected manipulation. `null` when there is nothing to add.", + "example": { + "missing_fields": [ + "Net pay" + ] + } + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity derived from the configured action: `error` (DECLINE), `warning` (REVIEW), `information` (NO_ACTION)." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-liner for the risk.", + "example": "Required fields could not be extracted" + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation, interpolated with `additional_data` where relevant." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the request was processed (UTC, ISO 8601)." + } + } + } + } + } + }, + "400": { + "description": "Validation or processing error. Field-level problems return DRF's standard envelope (one array of messages per offending field). Encrypted or unreadable documents and extraction failures return `{\"error\": ...}`.", + "content": { + "application/json": { + "examples": { + "Missing document": { + "summary": "`document` not included in the form data", + "value": { + "document": [ + "No file was submitted." + ] + } + }, + "Invalid fields payload": { + "summary": "`fields` is not one JSON-encoded array string", + "value": { + "fields": [ + "fields must be a valid JSON array string." + ] + } + }, + "Duplicate field keys": { + "summary": "Two items share the same `key`", + "value": { + "fields": [ + "Every fields item must have a unique key." + ] + } + }, + "Conflicting expectations": { + "summary": "Person and company expectations sent together", + "value": { + "non_field_errors": [ + "Provide either person expected-name fields or expected_company_name, not both." + ] + } + }, + "Invalid metadata": { + "summary": "`metadata` is not a JSON object (or contains a NUL character)", + "value": { + "metadata": [ + "Metadata must be a JSON object." + ] + } + }, + "Oversized vendor_data": { + "summary": "`vendor_data` over the 2,000-byte cap", + "value": { + "vendor_data": [ + "vendor_data is too long (maximum 2000 bytes)." + ] + } + }, + "Encrypted PDF": { + "summary": "Password-protected PDF", + "value": { + "error": "The PDF is encrypted. Please upload a decrypted PDF or a photo instead." + } + }, + "Extraction failed": { + "summary": "The extraction pipeline could not process the document", + "value": { + "error": [ + "Error extracting Document AI information" + ] + } + } + }, + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, malformed, revoked, or belongs to another environment — and also when the calling organization's balance cannot cover the call. Authentication failures return `403` with `{\"detail\": ...}`; this API never returns `401`. Credit shortfalls — and contract-billed organizations whose rate card has no Document AI API entry — return `403` with `{\"error\": ...}` before any document processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + }, + "Contract rate missing": { + "summary": "Contract-billed organization whose rate card has no Document AI API entry", + "value": { + "error": "Document AI API is not included in your contract rate card. Contact support@didit.me to enable it." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "error": { + "type": "string" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. All POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/aml/": { + "post": { + "summary": "AML screening (standalone)", + "operationId": "post_v3aml", + "x-didit-sdk": { + "resource": "aml", + "method": "screen", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "Idempotency-Key", + "in": "header", + "required": false, + "description": "Optional retry-safety key, up to 255 characters. Reusing the same key with the same request body returns the original response without running or billing a second screening. Reusing the key with a different body returns `409`.", + "schema": { + "type": "string", + "maxLength": 255 + } + } + ], + "description": "Screen a person or company against PEP, sanctions, warning/watchlist, and adverse-media datasets in one call.\n\n**Two-score model.** Each hit carries a `match_score` (0–100, identity confidence) and a `risk_score` (0–100, entity risk). Hits whose `match_score` is below `aml_match_score_threshold` (default **93**) are tagged `review_status: \"False Positive\"` and ignored for risk purposes; the highest `risk_score` among the remaining hits becomes `aml.score`. The final `aml.status` follows your thresholds: `score` > `aml_score_review_threshold` (default **100**) → `Declined`; `score` > `aml_score_approve_threshold` (default **80**) → `In Review`; otherwise `Approved`. Whenever `score` is non-zero and ≥ the approve threshold a `POSSIBLE_MATCH_FOUND` warning is added (a score of exactly 0 never warns).\n\n**Match-score weights.** `aml_name_weight` + `aml_dob_weight` + `aml_country_weight` must sum to exactly 100 (defaults 60/25/15). Components missing from the request are re-normalized away; a matching `document_number` acts as a golden key that overrides `match_score` to 100.\n\n**Latency.** Plain screenings typically complete in a few seconds. With `include_adverse_media=true` the API waits for adverse-media enrichment (an initial ~5 s pause plus up to ~25 s of polling) — configure a client timeout of **at least 60 seconds**.\n\n**Persistence.** `save_api_request` defaults to **true**: the screening is stored as a session (`request_id` works with `GET /v3/session/{sessionId}/decision/`), it appears in the console, a `status.updated` webhook fires, and hit review statuses can be updated later. With `save_api_request=false` nothing is stored and `request_id` is a transient correlation id. `include_ongoing_monitoring=true` keeps re-screening the profile and requires `save_api_request=true` (enforced with a `400`).\n\n**Idempotency.** Send an `Idempotency-Key` header when retrying after a timeout or transient error. The key is scoped to your application and the exact request body. The first completed request stores its response; later identical retries return that same response and do not run or bill another AML screening. If the original request is still running, retry later with the same key. If the same key is reused with a different body, the API returns `409`.\n\n**Billing.** One AML API credit is charged after the first successful screening. Replayed idempotent responses are not charged again; an insufficient balance returns `403` before any new screening happens.\n\n**Sandbox.** Keys from sandbox applications skip screening and billing entirely and return a mocked result — `Approved` with zero hits unless you pass `sandbox_scenario`. `save_api_request` works the same as on live keys, so a saved sandbox screening is retrievable through `GET /v3/session/{sessionId}/decision/` and `GET /v3/session/{sessionId}/generate-pdf/`.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "full_name" + ], + "properties": { + "full_name": { + "type": "string", + "minLength": 2, + "description": "Full name of the person or company to screen. Minimum **2 characters** - the screening provider's own floor. A shorter or blank name returns `400` on this field before any screening runs, so nothing is billed.", + "example": "Antonio Ejemplo Modelo" + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "default": "person", + "description": "Type of entity to screen. Defaults to `person`." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth (persons) or incorporation date (companies), `YYYY-MM-DD`. Improves match precision via the DOB weight.", + "example": "1972-02-29" + }, + "nationality": { + "type": "string", + "description": "ISO 3166-1 **alpha-2** country code (e.g. `ES`). Invalid codes return `400`. Improves match precision via the country weight.", + "example": "ES" + }, + "document_number": { + "type": "string", + "description": "Identity document number. Not weighted — acts as a golden key: an exact match overrides the hit's `match_score` to 100, a hard mismatch penalizes it. Sent to the screening provider as a search filter only when it is **2-50 characters**; a value outside that range is accepted and stored, and the screening still runs and is billed, just not narrowed by document number." + }, + "aml_score_approve_threshold": { + "type": "number", + "minimum": 0, + "maximum": 100, + "default": 80, + "description": "Risk-score threshold at or below which the result is `Approved`. Must be ≤ `aml_score_review_threshold` (else `400`)." + }, + "aml_score_review_threshold": { + "type": "number", + "minimum": 0, + "maximum": 100, + "default": 100, + "description": "Risk-score threshold above which the result is `Declined`. Scores between the two thresholds produce `In Review`." + }, + "aml_name_weight": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 60, + "description": "Weight of name similarity in the per-hit match score. The three weights must sum to exactly 100 (else `400`)." + }, + "aml_dob_weight": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 25, + "description": "Weight of date of birth in the per-hit match score." + }, + "aml_country_weight": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 15, + "description": "Weight of country/nationality in the per-hit match score." + }, + "aml_match_score_threshold": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 93, + "description": "Per-hit cutoff: hits with `match_score` below this are `False Positive` (excluded from risk assessment); at or above are `Unreviewed` possible matches." + }, + "include_adverse_media": { + "type": "boolean", + "default": false, + "description": "Also search news media for negative coverage. Adds up to ~30 s of latency while results are gathered — use a ≥60 s client timeout." + }, + "include_ongoing_monitoring": { + "type": "boolean", + "default": false, + "description": "Keep re-screening this profile and push changes via webhook. Requires `save_api_request=true` (else `400`)." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "Persist the screening as a session (console visibility, decision endpoint, webhooks, hit review). Set to `false` for a stateless check." + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for the screened user; echoed back and stored with the session." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON stored with the request and echoed back." + }, + "sandbox_scenario": { + "type": "string", + "description": "Sandbox-only scenario slug. Only accepted when the API key belongs to a sandbox-mode application; rejected on live applications. A standalone screening has no session to arm, so the slug goes on the screening request itself: `decline_aml_hit` returns one production-shaped hit and settles the screening as `Declined`, `review_aml_possible_match` settles it `In Review`, and a scenario that arms no AML outcome leaves the screening clean. See the scenario catalog at GET /v1/sandbox/scenarios/ or [Sandbox & Test Data](/integration/sandbox-testing).", + "example": "decline_aml_hit" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/aml/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -H 'Idempotency-Key: aml-user-1234-2026-08-20' \\\n -d '{\n \"full_name\": \"Antonio Ejemplo Modelo\",\n \"entity_type\": \"person\",\n \"date_of_birth\": \"1972-02-29\",\n \"nationality\": \"ES\",\n \"include_adverse_media\": false,\n \"vendor_data\": \"user-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os, requests\n\nresp = requests.post(\n \"https://verification.didit.me/v3/aml/\",\n headers={\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Content-Type\": \"application/json\",\n \"Idempotency-Key\": \"aml-user-1234-2026-08-20\",\n },\n json={\n \"full_name\": \"Antonio Ejemplo Modelo\",\n \"entity_type\": \"person\",\n \"date_of_birth\": \"1972-02-29\",\n \"nationality\": \"ES\",\n \"include_adverse_media\": False,\n \"vendor_data\": \"user-1234\",\n },\n timeout=60, # adverse media enrichment can add ~30s\n)\nresp.raise_for_status()\nprint(resp.json()[\"aml\"][\"status\"]) # Approved / In Review / Declined" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const res = await fetch('https://verification.didit.me/v3/aml/', {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n 'Idempotency-Key': 'aml-user-1234-2026-08-20',\n },\n body: JSON.stringify({\n full_name: 'Antonio Ejemplo Modelo',\n entity_type: 'person',\n date_of_birth: '1972-02-29',\n nationality: 'ES',\n include_adverse_media: false,\n vendor_data: 'user-1234',\n }),\n});\nconst data = await res.json();\nconsole.log(data.aml.status);" + } + ], + "responses": { + "200": { + "description": "Screening completed. `aml.status` reflects your thresholds; every hit at or above the internal inclusion floor is listed in `aml.hits` with its identity `match_score`, entity `risk_score`, and a `score_breakdown` explaining the match.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "aml": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "`Declined` when `score` > `aml_score_review_threshold`; `In Review` when `score` > `aml_score_approve_threshold`; `Approved` otherwise (including when no score is available)." + }, + "total_hits": { + "type": "integer", + "description": "Number of entries in `hits`." + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Echo of the screened entity type." + }, + "hits": { + "type": "array", + "items": { + "type": "object", + "description": "One screening hit. `match_score` (0–100) measures identity confidence — hits below `aml_match_score_threshold` are tagged `review_status: \"False Positive\"` and excluded from the risk assessment; hits at or above it are `\"Unreviewed\"` possible matches. `risk_score` (0–100) measures how risky the matched entity is and drives the top-level `aml.score`.", + "properties": { + "id": { + "type": "string", + "nullable": true, + "description": "Stable identifier of the matched entity in the screening datasets." + }, + "url": { + "type": "string", + "nullable": true, + "description": "Source URL for the matched entity, when available." + }, + "match": { + "type": "boolean", + "nullable": true, + "description": "Legacy exact-match flag. Prefer `match_score` + `review_status`." + }, + "score": { + "type": "number", + "nullable": true, + "description": "DEPRECATED weighted match score in the 0–1 range. Use `match_score` instead." + }, + "match_score": { + "type": "number", + "nullable": true, + "description": "Identity confidence (0–100). Compared against `aml_match_score_threshold` to split False Positives from Possible Matches." + }, + "risk_score": { + "type": "number", + "nullable": true, + "description": "Entity risk score (0–100). The highest `risk_score` among non-false-positive hits becomes the top-level `aml.score`." + }, + "target": { + "type": "boolean", + "nullable": true + }, + "caption": { + "type": "string", + "nullable": true, + "description": "Display name of the matched entity." + }, + "datasets": { + "type": "array", + "nullable": true, + "items": { + "type": "string" + }, + "description": "Datasets the entity appears in (e.g. `PEP`, `PEP Level 1`, sanctions list names)." + }, + "features": { + "type": "object", + "nullable": true + }, + "properties": { + "type": "object", + "nullable": true, + "description": "Entity profile: `name`, `alias`, `notes`, `gender`, `birthDate`, `birthPlace`, `nationality`, `position`, and other biographical arrays (each nullable)." + }, + "rca_name": { + "type": "string", + "nullable": true, + "description": "Name of the related close associate through which the entity was matched, when the hit is an RCA." + }, + "pep_matches": { + "type": "array", + "description": "PEP list entries backing this hit.", + "items": { + "type": "object", + "properties": { + "list_name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "source_url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "matched_name": { + "type": "string" + }, + "pep_position": { + "type": "string" + }, + "date_of_birth": { + "type": "string" + }, + "place_of_birth": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "type": "string" + } + }, + "education": { + "type": "array", + "items": { + "type": "string" + } + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "sanction_matches": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Sanctions list entries backing this hit (same backing-list shape as `pep_matches`, plus sanction-specific fields)." + }, + "warning_matches": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Warning/watchlist entries backing this hit." + }, + "adverse_media_matches": { + "type": "array", + "description": "Adverse-media articles. Populated only when `include_adverse_media=true`.", + "items": { + "type": "object", + "properties": { + "headline": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "sentiment": { + "type": "string" + }, + "sentiment_score": { + "type": "number" + }, + "source_url": { + "type": "string" + }, + "thumbnail": { + "type": "string", + "nullable": true + }, + "country": { + "type": "string", + "nullable": true + }, + "author_name": { + "type": "string", + "nullable": true + }, + "publication_date": { + "type": "string", + "nullable": true + }, + "adverse_keywords": { + "type": "array", + "items": { + "type": "string" + } + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "adverse_media_details": { + "type": "object", + "nullable": true, + "description": "Aggregated adverse-media sentiment for the entity (`sentiment`, `sentiment_score`, `adverse_keywords` frequency map). Populated only when `include_adverse_media=true`." + }, + "first_seen": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the entity first appeared in the datasets." + }, + "last_seen": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Most recent dataset refresh that included the entity." + }, + "linked_entities": { + "type": "array", + "description": "Known relationships of the matched entity (family, associates, companies).", + "items": { + "type": "object", + "properties": { + "name": { + "type": "array", + "items": { + "type": "string" + } + }, + "relation": { + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "type": "array", + "items": { + "type": "string" + } + }, + "details": { + "type": "array", + "items": { + "type": "string" + } + }, + "active": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "risk_view": { + "type": "object", + "nullable": true, + "description": "Per-dimension risk breakdown (`crimes`, `countries`, `categories`, `custom_list`), each with `score`, `weightage`, `risk_level`, and a `risk_scores` map." + }, + "additional_information": { + "type": "object", + "nullable": true, + "description": "Free-form extra context, e.g. a `flag_summary` array explaining why the entity is flagged." + }, + "review_status": { + "type": "string", + "nullable": true, + "enum": [ + "False Positive", + "Unreviewed", + "Confirmed Match", + "Inconclusive" + ], + "description": "`False Positive` — `match_score` below `aml_match_score_threshold`, excluded from risk assessment. `Unreviewed` — possible match awaiting review. `Confirmed Match` / `Inconclusive` — set manually by a reviewer (via the Business Console) on saved sessions; that review capability is not part of this spec." + }, + "score_breakdown": { + "type": "object", + "nullable": true, + "description": "How `match_score` was computed. Name, date of birth, and country each contribute `_score` × `_weight_normalized` points; weights are re-normalized when a component is missing from the screened data. The document number is not weighted — it acts as a 'golden key': `MATCH` overrides the score to 100, `NEUTRAL` leaves it unchanged, `HARD_MISMATCH` applies a penalty.", + "properties": { + "name_score": { + "type": "integer", + "nullable": true, + "description": "Raw name similarity (0–100)." + }, + "name_weight": { + "type": "integer", + "nullable": true, + "description": "Configured name weight (`aml_name_weight`)." + }, + "name_weight_normalized": { + "type": "number", + "nullable": true, + "description": "Name weight after re-normalization (0–100)." + }, + "name_contribution": { + "type": "number", + "nullable": true, + "description": "Points the name contributed to `total_score`." + }, + "dob_score": { + "type": "integer", + "nullable": true, + "description": "Date-of-birth score as a percentage of its weight (−100 to 100)." + }, + "dob_weight": { + "type": "integer", + "nullable": true, + "description": "Configured DOB weight (`aml_dob_weight`)." + }, + "dob_weight_normalized": { + "type": "number", + "nullable": true, + "description": "DOB weight after re-normalization (0–100)." + }, + "dob_contribution": { + "type": "number", + "nullable": true, + "description": "Points the DOB contributed to `total_score`." + }, + "country_score": { + "type": "integer", + "nullable": true, + "description": "Country score as a percentage of its weight (−100 to 100)." + }, + "country_weight": { + "type": "integer", + "nullable": true, + "description": "Configured country weight (`aml_country_weight`)." + }, + "country_weight_normalized": { + "type": "number", + "nullable": true, + "description": "Country weight after re-normalization (0–100)." + }, + "country_contribution": { + "type": "number", + "nullable": true, + "description": "Points the country contributed to `total_score`." + }, + "document_number_match_type": { + "type": "string", + "nullable": true, + "enum": [ + "MATCH", + "NEUTRAL", + "HARD_MISMATCH" + ], + "description": "Golden-key result for the document number comparison." + }, + "document_number_effect": { + "type": "string", + "nullable": true, + "description": "Human-readable description of the document-number effect." + }, + "total_score": { + "type": "integer", + "nullable": true, + "description": "Final weighted match score (0–100) — mirrors `match_score`." + } + } + } + } + } + }, + "score": { + "type": "number", + "nullable": true, + "description": "Overall AML risk score (0–100): the highest `risk_score` among hits that are not False Positives. Drives `status` via the two thresholds." + }, + "screened_data": { + "type": "object", + "description": "Echo of what was screened: `full_name`, `date_of_birth`, `nationality`, `document_number` (absent fields are `null`).", + "properties": { + "full_name": { + "type": "string" + }, + "date_of_birth": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "document_number": { + "type": "string", + "nullable": true + } + } + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature that raised the warning." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk identifier." + }, + "additional_data": { + "type": "object", + "nullable": true, + "description": "Extra context for the warning, when available." + }, + "log_type": { + "type": "string", + "description": "`warning`, `information`, or `error` — how the risk affected the outcome." + }, + "short_description": { + "type": "string", + "description": "One-line human-readable summary." + }, + "long_description": { + "type": "string", + "description": "Full human-readable explanation." + } + } + }, + "description": "Contains a `POSSIBLE_MATCH_FOUND` warning whenever `score` is non-zero and ≥ `aml_score_approve_threshold` (a score of exactly 0 never warns); empty otherwise." + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "No hits (Approved)": { + "summary": "Clean screening — zero hits", + "value": { + "request_id": "d27c5509-7a66-45a9-b05e-d69d9f4c336a", + "aml": { + "status": "Approved", + "total_hits": 0, + "entity_type": "person", + "hits": [], + "score": 0, + "screened_data": { + "full_name": "Maximilian Mustermann-Doe", + "date_of_birth": "1992-03-14", + "nationality": "DE", + "document_number": null + }, + "warnings": [] + }, + "vendor_data": "user-1234", + "metadata": { + "internal_ref": "abc-123" + }, + "created_at": "2026-06-11T10:21:07.013938+00:00" + } + }, + "PEP hit (Approved — risk below approve threshold)": { + "summary": "One PEP possible match; risk score 73 stays under the default approve threshold of 80", + "value": { + "request_id": "0b7e7a3e-65a1-4f3d-9c7a-1de2f9a40111", + "aml": { + "status": "Approved", + "total_hits": 1, + "entity_type": "person", + "hits": [ + { + "id": "kDpfszrbkDdaxpMVZw3NLg", + "url": "https://news.example.org/articles/sample", + "match": false, + "score": 0.93, + "match_score": 93.0, + "risk_score": 73.0, + "target": null, + "caption": "Antonio Ejemplo Modelo", + "datasets": [ + "PEP Level 1", + "PEP" + ], + "features": null, + "rca_name": "", + "first_seen": "2026-01-07T00:00:00", + "last_seen": "2026-01-07T00:00:00", + "review_status": "Unreviewed", + "properties": { + "name": [ + "Antonio Ejemplo Modelo", + "Antonio Ejemplo" + ], + "alias": [ + "Antonio Ejemplo Modelo", + "Ejemplo Modelo, Pedro" + ], + "notes": [ + "Prime Minister of Spain" + ], + "gender": [ + "male" + ], + "birthDate": [ + "1972-02-29" + ], + "birthPlace": [ + "Madrid, Spain" + ], + "nationality": null, + "position": null + }, + "risk_view": { + "crimes": { + "score": 0.0, + "weightage": 20, + "risk_level": "Low", + "risk_scores": {} + }, + "countries": { + "score": 9.64, + "weightage": 30, + "risk_level": "Low", + "risk_scores": { + "Spain": 25.71 + } + }, + "categories": { + "score": 62.5, + "weightage": 50, + "risk_level": "High", + "risk_scores": { + "PEP": 100.0, + "PEP Level 1": 100.0 + } + }, + "custom_list": {} + }, + "score_breakdown": { + "name_score": 89, + "name_weight": 60, + "name_weight_normalized": 60.0, + "name_contribution": 53.4, + "dob_score": 100, + "dob_weight": 25, + "dob_weight_normalized": 25.0, + "dob_contribution": 25.0, + "country_score": 100, + "country_weight": 15, + "country_weight_normalized": 15.0, + "country_contribution": 15.0, + "document_number_match_type": "NEUTRAL", + "document_number_effect": "No document number provided for screening", + "total_score": 93 + }, + "pep_matches": [ + { + "aliases": [], + "education": [], + "list_name": "Presidency of the Government of Spain", + "publisher": "Presidency of the Government of Spain", + "source_url": "https://news.example.org/articles/sample", + "description": "The Presidency of the Government is the central institutional body in Spain that supports the Prime Minister and coordinates the general action of the central government.", + "matched_name": "Antonio Ejemplo Modelo", + "pep_position": "", + "date_of_birth": "1972-02-29", + "other_sources": [], + "place_of_birth": "Madrid, Spain" + } + ], + "sanction_matches": [], + "warning_matches": [], + "adverse_media_matches": [], + "adverse_media_details": null, + "linked_entities": [ + { + "name": [ + "Maria Begona Gomez Fernandez" + ], + "active": [], + "status": [], + "details": [], + "relation": [ + "spouse" + ] + } + ], + "additional_information": { + "flag_summary": [ + "Antonio Ejemplo Modelo has been flagged as a Level 1 Politically Exposed Person (PEP) from Spain, serving as the President of the Government of Spain." + ] + } + } + ], + "score": 73.0, + "screened_data": { + "full_name": "Pedro Sanchez Perez-Castejon", + "date_of_birth": "1972-02-29", + "nationality": "ES", + "document_number": null + }, + "warnings": [] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-11T10:23:44.812000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems return DRF's standard envelope (one array of messages per offending field); cross-field rules (weight sum, threshold ordering, monitoring-without-save) return `{\"error\": [...]}`.", + "content": { + "application/json": { + "examples": { + "Missing full_name": { + "summary": "`full_name` is the only required field", + "value": { + "full_name": [ + "This field is required." + ] + } + }, + "Short full_name": { + "summary": "`full_name` must be at least 2 characters", + "value": { + "full_name": [ + "Ensure this field has at least 2 characters." + ] + } + }, + "Invalid nationality": { + "summary": "`nationality` must be ISO 3166-1 alpha-2", + "value": { + "nationality": [ + "Invalid ISO 3166-1 alpha-2 country code." + ] + } + }, + "Weights do not sum to 100": { + "summary": "`aml_name_weight` + `aml_dob_weight` + `aml_country_weight` ≠ 100", + "value": { + "error": [ + "AML weights must sum to 100. Current sum: 90 (name: 50, dob: 25, country: 15)" + ] + } + }, + "Thresholds out of order": { + "summary": "Approve threshold above review threshold", + "value": { + "error": [ + "Approve threshold (90.0) cannot be greater than review threshold (80.0)" + ] + } + }, + "Monitoring without saving": { + "summary": "`include_ongoing_monitoring=true` with `save_api_request=false`", + "value": { + "error": [ + "Ongoing monitoring can only be enabled when the API request is saved." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, malformed, revoked, or belongs to another environment — and also when the calling organization's balance cannot cover the call. Authentication failures return `403` with `{\"detail\": ...}`; this API never returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. All POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/face-search/": { + "post": { + "summary": "Face Search", + "description": "Search a face image (1:N) against your application's face search index — faces enrolled from verification sessions, saved standalone API calls, direct user-profile face uploads, and your block/allow lists — and get back ranked similarity matches plus blocklist and duplicate warnings.\n\n**How it works.** The largest detected face in `user_image` is embedded and searched against the index, scoped to your application. Up to **5 matches** above the similarity floor are returned in `matches`, each with `similarity_percentage`, the originating session (`session_id`, `session_number`, `status`, `vendor_data`), `user_details` extracted during that session's document verification, `is_blocklisted` / `is_allowlisted` flags, and a `source` discriminator (`session`, `imported`, or `list_entry`).\n\n- `search_type=most_similar` (default) ranks every enrolled face by similarity — use it for deduplication and fraud-ring investigation.\n- `search_type=blocklisted_or_approved` restricts candidates to blocklisted faces, allowlisted faces, faces from approved sessions, and imported user-profile faces, ranking blocklisted entries first — use it when the primary goal is blocklist screening.\n\n`status` is `Declined` only when a `FACE_IN_BLOCKLIST` or `POSSIBLE_FACE_IN_BLOCKLIST` warning fires; `DUPLICATED_FACE` / `POSSIBLE_DUPLICATED_FACE` (`information`) and `MULTIPLE_FACES_DETECTED` (`warning`) never decline. Unlike Passive Liveness, this endpoint does **not** exclude prior sessions with the same `vendor_data` from matching. A passive-liveness analysis also runs internally and is stored with the persisted session, but its result is not part of this response.\n\n**Billing.** Each `200` response consumes one Face Search API credit (paid from your plan credit or top-up balance). Insufficient balance returns `403` before any image processing.\n\n**Session persistence and face enrollment (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session (Business Console, `GET /v3/session/{sessionId}/decision/` via the returned `request_id`, `status.updated` webhook) and the searched face is enrolled into your face search index. Faces enrolled by Face Search calls are excluded from future Face Search results, so repeated searches never match each other. When `false`, the call is one-shot: nothing is stored, the face is not enrolled, and `match_image_url` values are internal storage paths rather than downloadable URLs.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload, no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3face-search", + "x-didit-sdk": { + "resource": "face_search", + "method": "search", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "user_image" + ], + "properties": { + "user_image": { + "type": "string", + "format": "binary", + "description": "Front-facing face image to search with. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp`. Maximum upload size: **5 MB** (larger files are rejected with `400`). Images are automatically compressed to ~0.5 MB before processing, so very high resolutions do not improve accuracy. The image must contain at least one detectable face — otherwise the endpoint returns `400`. When several faces are present, the largest one is searched and a `MULTIPLE_FACES_DETECTED` warning is added." + }, + "search_type": { + "type": "string", + "default": "most_similar", + "enum": [ + "most_similar", + "blocklisted_or_approved" + ], + "description": "Search policy. `most_similar` (default) ranks every enrolled face in your application by similarity. `blocklisted_or_approved` restricts candidates to blocklisted faces, allowlisted faces, faces from approved sessions, and imported user-profile faces — with blocklisted entries ranked first.", + "example": "most_similar" + }, + "rotate_image": { + "type": "boolean", + "default": false, + "description": "When `true`, the service tries 90-degree rotations of the input and uses the orientation that yields the best face detection. Useful when EXIF orientation is missing. Adds latency.", + "example": false + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session, emits a `status.updated` webhook, presigns `match_image_url` values, and enrolls the searched face into your face search index (Face Search enrollments are excluded from future searches). When `false`, the search is one-shot — no session is stored and the face is not enrolled.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque identifier (your user id, email, UUID…) stored on the persisted session and echoed back. It does not exclude same-user faces from the search results.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back. In multipart requests, send it as a JSON-encoded string field — it is parsed into an object.", + "example": { + "flow": "dedup_check" + } + } + } + }, + "example": { + "user_image": "(binary JPEG/PNG selfie)", + "search_type": "most_similar", + "rotate_image": false, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "dedup_check" + } + } + } + } + }, + "responses": { + "200": { + "description": "Face search completed. `face_search.matches` holds up to 5 similar faces ordered by similarity; an empty array means no enrolled face exceeded the similarity floor. `status` is `Declined` only on blocklist hits — duplicate matches alone return `Approved`, so inspect `matches` and `warnings`, not just `status`. When `save_api_request=true`, `request_id` is the persisted session id. Each match carries `source` (`session`, `imported`, `list_entry`, or `retained_template`), `vendor_user_id`, and `biometric_template_id`; a `retained_template` match comes from an image-free biometric template kept after the source session was deleted and has `null` session fields, `match_image_url`, and `user_details`.", + "content": { + "application/json": { + "examples": { + "Blocklist hit (Declined)": { + "summary": "The face matches a blocklisted face", + "value": { + "request_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef", + "face_search": { + "status": "Declined", + "total_matches": 1, + "matches": [ + { + "session_id": "882c42d5-8a4d-4d20-8080-a22f57822c86", + "session_number": 323442, + "similarity_percentage": 99.99, + "source": "session", + "vendor_data": "user-1", + "verification_date": "2025-01-01T00:00:00Z", + "user_details": { + "full_name": "Jane Marie Doe", + "document_type": "ID", + "document_number": "X1234567" + }, + "match_image_url": "https:///face/3f6a1c2e/reference.jpg", + "status": "Approved", + "is_blocklisted": true, + "is_allowlisted": false, + "api_service": null, + "vendor_user_id": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f", + "biometric_template_id": null + } + ], + "user_image": { + "entities": [ + { + "bbox": [ + 40, + 40, + 120, + 120 + ], + "confidence": 0.732973 + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "FACE_IN_BLOCKLIST", + "feature": "LIVENESS", + "additional_data": { + "blocklisted_session_id": "882c42d5-8a4d-4d20-8080-a22f57822c86", + "blocklisted_session_number": 323442, + "api_service": null + }, + "log_type": "error", + "short_description": "Face in blocklist", + "long_description": "The system identified a face in the blocklist, which means the face is not allowed to be verified." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "Duplicate found (Approved)": { + "summary": "The face already appeared in another approved session — informational, no decline", + "value": { + "request_id": "5e0c3a1f-7b2d-4c8e-9f10-2a3b4c5d6e7f", + "face_search": { + "status": "Approved", + "total_matches": 1, + "matches": [ + { + "session_id": "1f2e3d4c-5b6a-7980-1122-334455667788", + "session_number": 1024, + "similarity_percentage": 87.42, + "source": "session", + "vendor_data": "user-9", + "verification_date": "2025-11-20T09:15:00Z", + "user_details": null, + "match_image_url": "https:///face/9c8b7a6d/reference.jpg", + "status": "Approved", + "is_blocklisted": false, + "is_allowlisted": false, + "api_service": "PASSIVE_LIVENESS", + "vendor_user_id": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f", + "biometric_template_id": null + } + ], + "user_image": { + "entities": [ + { + "bbox": [ + 40, + 40, + 120, + 120 + ], + "confidence": 0.732973 + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "DUPLICATED_FACE", + "feature": "LIVENESS", + "additional_data": { + "duplicated_session_id": "1f2e3d4c-5b6a-7980-1122-334455667788", + "duplicated_session_number": 1024, + "api_service": "PASSIVE_LIVENESS" + }, + "log_type": "information", + "short_description": "Duplicated face from other approved session", + "long_description": "The system identified a duplicated face from another approved session, requiring further investigation." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "No matches (Approved)": { + "summary": "No enrolled face exceeded the similarity floor", + "value": { + "request_id": "9d8c7b6a-5f4e-3d2c-1b0a-998877665544", + "face_search": { + "status": "Approved", + "total_matches": 0, + "matches": [], + "user_image": { + "entities": [ + { + "bbox": [ + 40, + 40, + 120, + 120 + ], + "confidence": 0.732973 + } + ], + "best_angle": 0 + }, + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "face_search": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Declined` only when a `FACE_IN_BLOCKLIST` or `POSSIBLE_FACE_IN_BLOCKLIST` warning fired. Duplicate matches and multiple-face warnings never decline." + }, + "total_matches": { + "type": "integer", + "description": "Number of entries in `matches` (0–5).", + "example": 1 + }, + "matches": { + "type": "array", + "maxItems": 5, + "description": "Up to 5 faces from your face search index that exceed the similarity floor, ordered by similarity (with blocklisted entries ranked first when `search_type=blocklisted_or_approved`). Empty when no enrolled face is similar enough.", + "items": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Id of the verification session the matched face belongs to. `null` when `source` is `imported`, `list_entry`, or `retained_template`.", + "example": "882c42d5-8a4d-4d20-8080-a22f57822c86" + }, + "session_number": { + "type": "integer", + "nullable": true, + "description": "Human-friendly session number shown in the Business Console. `null` for non-session matches.", + "example": 323442 + }, + "similarity_percentage": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 100, + "description": "Similarity between the submitted face and the matched face (0–100).", + "example": 99.99 + }, + "source": { + "type": "string", + "enum": [ + "session", + "imported", + "list_entry", + "retained_template" + ], + "description": "Where the matched face was enrolled from: `session` — a verification session or a saved standalone API call; `imported` — a face uploaded directly to a User profile; `list_entry` — a face attached to one of your lists; `retained_template` — an image-free biometric template stored on a User, listed under `/v3/biometric-templates/`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of the matched session or linked User for imported and retained-template faces.", + "example": "user-1" + }, + "vendor_user_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Linked User id. `null` for list entries and session matches without a User.", + "example": "2f7c1a9e-4b28-4a6d-97f1-83576f80e1b4" + }, + "biometric_template_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Id of the retained biometric template. Set only when `source` is `retained_template`; `null` otherwise.", + "example": null + }, + "verification_date": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the matched face was originally captured, or when a retained template was created (`YYYY-MM-DDThh:mm:ssZ`). `null` for `list_entry` matches.", + "example": "2025-01-01T00:00:00Z" + }, + "user_details": { + "type": "object", + "nullable": true, + "description": "Identity data extracted during the matched session's document verification. `null` when the matched session has no document data (e.g. liveness-only sessions) and always `null` for `retained_template` matches. For `source: imported` matches it is populated from the User profile instead: `full_name` set, `document_type`/`document_number` null.", + "properties": { + "full_name": { + "type": "string", + "nullable": true, + "example": "Jane Marie Doe" + }, + "document_type": { + "type": "string", + "nullable": true, + "example": "ID" + }, + "document_number": { + "type": "string", + "nullable": true, + "example": "X1234567" + } + } + }, + "match_image_url": { + "type": "string", + "nullable": true, + "description": "Time-limited URL (`https:///...`) of the matched face's reference image when `save_api_request=true`. With `save_api_request=false` this is an internal storage path that is not directly downloadable. Always `null` for `retained_template` matches because retained templates contain no image.", + "example": "https:///face/3f6a1c2e/reference.jpg" + }, + "status": { + "type": "string", + "nullable": true, + "description": "Status of the matched session (`Approved`, `Declined`, `In Review`, …). `null` for `imported`/`list_entry` matches.", + "example": "Approved" + }, + "is_blocklisted": { + "type": "boolean", + "description": "`true` when the matched face is on your face blocklist. Any sufficiently similar blocklisted match declines the search." + }, + "is_allowlisted": { + "type": "boolean", + "description": "`true` when the matched face is on one of your allowlists. Allowlisted matches suppress duplicate warnings." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "Set when the matched face was enrolled by a standalone API call (e.g. `PASSIVE_LIVENESS`); `null` for regular verification sessions and imported faces.", + "example": null + } + } + } + }, + "user_image": { + "type": "object", + "description": "Face-detection results for the submitted image. Entries here carry only `bbox` and `confidence`.", + "properties": { + "entities": { + "type": "array", + "description": "One entry per detected face. The largest face is the one searched.", + "items": { + "type": "object", + "properties": { + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box `[x_min, y_min, x_max, y_max]` in pixels.", + "example": [ + 40, + 40, + 120, + 120 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.732973 + } + } + } + }, + "best_angle": { + "type": "integer", + "description": "Rotation (degrees) that produced the best face detection; non-zero only when `rotate_image=true` corrected the orientation. Never null on this endpoint (defaults to 0 when the model omits it).", + "example": 0 + } + } + }, + "warnings": { + "type": "array", + "description": "Risk signals. `FACE_IN_BLOCKLIST` / `POSSIBLE_FACE_IN_BLOCKLIST` (`error`) decline the search; `MULTIPLE_FACES_DETECTED` (`warning`) and `DUPLICATED_FACE` / `POSSIBLE_DUPLICATED_FACE` (`information`) are advisory.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "MULTIPLE_FACES_DETECTED", + "FACE_IN_BLOCKLIST", + "POSSIBLE_FACE_IN_BLOCKLIST", + "DUPLICATED_FACE", + "POSSIBLE_DUPLICATED_FACE" + ], + "description": "Machine-readable risk code." + }, + "feature": { + "type": "string", + "enum": [ + "LIVENESS" + ], + "description": "Feature that raised the warning. Always `LIVENESS` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "`null` for `MULTIPLE_FACES_DETECTED`. Blocklist hits carry `{blocklisted_session_id, blocklisted_session_number, api_service}`; duplicate hits carry `{duplicated_session_id, duplicated_session_number, api_service}` pointing at the first matching session." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `warning` and `information` entries are advisory and never decline on their own." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated, e.g. `2026-06-12T01:04:42.763237+00:00`." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when `user_image` is missing, exceeds 5 MB, has an unsupported extension, or when **no face is detected** in the submitted image. Field errors use DRF's `{field: [messages]}` envelope; face-detection failures use `{\"error\": ...}`.", + "content": { + "application/json": { + "examples": { + "No face detected": { + "summary": "The image decodes fine but contains no detectable face", + "value": { + "error": "No face detected in the image" + } + }, + "Missing image": { + "summary": "`user_image` not included in the form data", + "value": { + "user_image": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp", + "value": { + "user_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 5 MB limit", + "value": { + "user_image": [ + "File size should not exceed 5 MB" + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, malformed, revoked, or belongs to another environment — and also when the calling organization's balance cannot cover the call. Authentication failures return `403` with `{\"detail\": ...}`; this API never returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. All POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/face-search/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'user_image=@./selfie.jpg' \\\n -F 'search_type=most_similar' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nurl = 'https://verification.didit.me/v3/face-search/'\nheaders = {'x-api-key': 'YOUR_API_KEY'}\n\nwith open('selfie.jpg', 'rb') as f:\n files = {'user_image': ('selfie.jpg', f, 'image/jpeg')}\n data = {\n 'search_type': 'most_similar',\n 'save_api_request': 'true',\n 'vendor_data': 'user-123',\n }\n resp = requests.post(url, headers=headers, files=files, data=data, timeout=60)\n\nresp.raise_for_status()\nbody = resp.json()\nprint('status:', body['face_search']['status'])\nprint('matches:', body['face_search']['total_matches'])\nfor m in body['face_search']['matches']:\n print(f\" session={m['session_id']} similarity={m['similarity_percentage']} blocklisted={m['is_blocklisted']}\")" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "import fs from 'node:fs';\n\nconst form = new FormData();\nform.append('user_image', new Blob([fs.readFileSync('./selfie.jpg')]), 'selfie.jpg');\nform.append('search_type', 'most_similar');\nform.append('save_api_request', 'true');\nform.append('vendor_data', 'user-123');\n\nconst response = await fetch('https://verification.didit.me/v3/face-search/', {\n method: 'POST',\n headers: { 'x-api-key': 'YOUR_API_KEY' },\n body: form,\n});\n\nif (!response.ok) throw new Error(`Face search failed: ${response.status}`);\nconst body = await response.json();\nconsole.log('status:', body.face_search.status, 'matches:', body.face_search.total_matches);\nbody.face_search.matches.forEach(m =>\n console.log(` session=${m.session_id} similarity=${m.similarity_percentage} blocklisted=${m.is_blocklisted}`)\n);" + } + ] + } + }, + "/v3/sessions/": { + "get": { + "summary": "List verification sessions (KYC and/or KYB) with filters and pagination", + "description": "Returns a paginated list of verification sessions for the application identified by your API key, newest first (descending `session_number`).\n\n**Canonical URL:** the route is `/v3/sessions/` (trailing slash). Requests to `/v3/sessions` without the slash receive a `301` redirect to the slashed URL with the query string preserved; most HTTP clients follow it transparently on GET.\n\n**When to use this endpoint vs webhooks.** Use this endpoint for dashboards, back-office search, reconciliation, and batch exports. Do not poll it to track the progress of individual verifications - configure [webhooks](/integration/webhooks) instead, which push `status.updated` events the moment a session changes. For one session's full result, call `GET /v3/session/{sessionId}/decision/`.\n\n**KYC and KYB sessions.** `session_kind` selects which kinds are listed:\n- `user` (default) - User Verification (KYC) sessions only. Backward-compatible behavior.\n- `business` - Business Verification (KYB) sessions only. Rows have a different, smaller shape (see the response schema).\n- `all` - both kinds in one response: the KYC page and the KYB page for the same `limit`/`offset` are concatenated in `results` (KYC rows first), so a page can contain up to `2 x limit` rows. `count` is the sum of both kinds and `next`/`previous` reflect the KYC-side pagination. For strict pagination of mixed data, call `user` and `business` separately.\n\nEvery row carries a `session_kind` discriminator (`\"user\"` or `\"business\"`).\n\n**Pagination.** Standard limit/offset: `limit` (default 50) and `offset` (default 0), with `next`/`previous` ready-made page URLs. For KYC rows `count` is exact. For KYB rows `count` is computed with a bounded scan and is capped at 100 - when there are more than 100 matches it stays at 100, so iterate using `next` (or by checking whether a full page came back) rather than computing total pages from `count`.\n\n**Filter semantics.** All filters combine with AND and apply to KYC rows. When `session_kind` is `business` or `all`, the KYB side honors the shared parameters `vendor_data`, `status`, `workflow_id`, `country` (registry-company country code), `date_from`, `date_to`, `search` (company name, registration number — type registration numbers without separators, e.g. `DPC001` not `DPC-001` — vendor_data, session number, or session UUID), `warning`, and `organization_name`; the remaining KYC-only filters are ignored for KYB rows. Comma-separated parameters (`status`, `country`, `document_type`, `region`, `workflow_id`, `document_name`, `warning`, `organization_name`, `session_type`) match any of the listed values (OR within the parameter). `vendor_data` is an exact match - use `search` for partial matching. Unknown values in list filters simply return no rows (no error); malformed typed values (`date_from`, `date_to`, `didit_internal_id`, `api_service`, `session_kind`) return `400` with a per-field error object.\n\n**Search.** `search` adapts to the input: a full UUID looks up `session_id`; digits (with optional `+ - . space`) match session-number prefix, document/personal numbers, phone-number prefix, or `vendor_data`; values containing `@` match email prefixes; any other text matches names (accent-insensitive) and `vendor_data`.\n\nNo fixed per-endpoint rate limit is enforced, but keep list polling infrequent and rely on webhooks for real-time updates.", + "operationId": "get_v3_sessions", + "x-didit-sdk": { + "resource": "sessions", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "session_kind", + "in": "query", + "required": false, + "description": "Which kind of sessions to list: `user` (KYC only, default), `business` (KYB only), or `all` (both, concatenated - see the description for merged-pagination semantics). Matching is case-insensitive (`BUSINESS` works); any value other than a case variant of `user`/`business`/`all` returns `400`.", + "schema": { + "type": "string", + "enum": [ + "user", + "business", + "all" + ], + "default": "user" + }, + "example": "all" + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of rows per page (per kind when `session_kind=all`). Defaults to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "example": 20 + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of rows to skip before the first returned row. Defaults to 0. Prefer following the `next`/`previous` URLs from the response.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "example": 20 + }, + { + "name": "status", + "in": "query", + "required": false, + "description": "Comma-separated list of session statuses to include, e.g. `Approved,In Review`. Values are case-sensitive; see [Verification Statuses](/integration/verification-statuses). Unknown values match nothing.", + "schema": { + "type": "string" + }, + "example": "Approved,In Review" + }, + { + "name": "vendor_data", + "in": "query", + "required": false, + "description": "Exact-match filter on the `vendor_data` you sent at session creation (your own user/business identifier). For partial matches use `search` instead.", + "schema": { + "type": "string" + }, + "example": "your-user-id-123" + }, + { + "name": "search", + "in": "query", + "required": false, + "description": "A complete displayed reference such as `S-0001` (user) or `B-0001` (business) matches only that session number and kind; leading zeros and letter case do not affect matching. Smart free-text search. A full UUID matches `session_id`; numeric input matches session-number prefix, document/personal numbers, phone prefix, or `vendor_data`; input containing `@` matches email prefixes; other text matches names (accent-insensitive) and `vendor_data`. Free text containing digits additionally matches document/personal-number search fields and session-number prefixes.", + "schema": { + "type": "string" + }, + "example": "jane doe" + }, + { + "name": "workflow_id", + "in": "query", + "required": false, + "description": "Comma-separated workflow UUIDs (find them on the Workflows page in the Console). Malformed UUIDs in the list are ignored; if none are valid the result is empty.", + "schema": { + "type": "string" + }, + "example": "9f9b1234-aaaa-bbbb-cccc-1234567890ab" + }, + { + "name": "date_from", + "in": "query", + "required": false, + "description": "Only sessions created on or after this date (UTC), format `YYYY-MM-DD`. Malformed dates return `400`.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-06-01" + }, + { + "name": "date_to", + "in": "query", + "required": false, + "description": "Only sessions created up to this date (UTC), format `YYYY-MM-DD`. For KYC rows the entire day is included; for KYB rows the comparison is `created_at <= date_to` (midnight), so use the next day's date to include a full day of KYB sessions.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-06-30" + }, + { + "name": "country", + "in": "query", + "required": false, + "description": "Comma-separated country filter. For KYC rows: ISO 3166-1 alpha-3 codes of the ID document's issuing state (e.g. `ESP,USA`), which may differ from nationality. For KYB rows: the registry company's country code.", + "schema": { + "type": "string" + }, + "example": "ESP,USA" + }, + { + "name": "document_type", + "in": "query", + "required": false, + "description": "Comma-separated ID-document type codes verified in the session (KYC rows only): `P` (Passport), `ID` (Identity Card), `DL` (Driver's License), `RP` (Residence Permit), `SSC` (Social Security Card), `HIC` (Health Insurance Card), `WP` (Work Permit), `TC` (Tax Card), `VISA`, `PSC` (Public Service Card), `BC` (Birth Certificate), `OTHER`. Note the response field `document_type` contains the display name, not the code.", + "schema": { + "type": "string" + }, + "example": "P,ID" + }, + { + "name": "document_name", + "in": "query", + "required": false, + "description": "Exact document names; multiple names can be passed comma-separated, but the value is only split on commas when it contains more than one ` - ` separator (a lone name with an embedded comma is matched whole). Names are as recognized by document verification (front or back side), e.g. `Spain - Id Card (2021)`. KYC rows only.", + "schema": { + "type": "string" + }, + "example": "Spain - Id Card (2021)" + }, + { + "name": "region", + "in": "query", + "required": false, + "description": "Comma-separated document region/state values (for documents issued per region, such as US driver's licenses). KYC rows only.", + "schema": { + "type": "string" + }, + "example": "CA,TX" + }, + { + "name": "didit_internal_id", + "in": "query", + "required": false, + "description": "Filter by Didit's internal user identifier (the `didit_internal_id` response field) to list all sessions of one verified user. Must be a valid UUID; malformed values return `400`. KYC rows only.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147" + }, + { + "name": "session_type", + "in": "query", + "required": false, + "description": "Comma-separated session creation channels: `API`, `HOSTED`, `MIGRATED`. The special value `ALL` (or omitting the parameter) disables the filter. KYC rows only.", + "schema": { + "type": "string" + }, + "example": "HOSTED" + }, + { + "name": "api_service", + "in": "query", + "required": false, + "description": "Filter standalone API sessions by service. Invalid values return `400`. KYC rows only.", + "schema": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "FACE_MATCH", + "AGE_ESTIMATION", + "FACE_SEARCH", + "POA", + "AML", + "KYB_REGISTRY", + "PASSIVE_LIVENESS", + "DATABASE_VALIDATION", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION" + ] + }, + "example": "AML" + }, + { + "name": "screened_full_name", + "in": "query", + "required": false, + "description": "Accent-insensitive name search inside AML and database-validation screened data. When combined with `api_service=AML` or `api_service=DATABASE_VALIDATION`, the search is restricted to that feature's screened data. KYC rows only.", + "schema": { + "type": "string" + }, + "example": "Jane Doe" + }, + { + "name": "warning", + "in": "query", + "required": false, + "description": "Comma-separated warning risk codes; returns sessions that raised any of them (e.g. `POSSIBLE_FRAUD`). Risk codes are listed per feature in the [warnings documentation](/integration/webhooks). Applies to KYC rows and, with business warning codes, to KYB rows.", + "schema": { + "type": "string" + }, + "example": "POSSIBLE_FRAUD" + }, + { + "name": "status_initialized_as_in_review", + "in": "query", + "required": false, + "description": "When `true`, only sessions whose decision initially landed in `In Review` (useful to audit manual-review volume). KYC rows only.", + "schema": { + "type": "boolean" + }, + "example": true + }, + { + "name": "is_kyc_to_be_reviewed", + "in": "query", + "required": false, + "description": "`true` returns sessions whose document verification is flagged for manual review; `false` returns sessions with document verification explicitly not flagged. Omit to disable. KYC rows only.", + "schema": { + "type": "boolean" + }, + "example": true + }, + { + "name": "euuid", + "in": "query", + "required": false, + "description": "Filter by the end user's euuid (UUID assigned to the verifying user, used by Console deep links). Must be a valid UUID; malformed values return a 400 field error. KYC rows only.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "0f8fad5b-d9cb-469f-a165-70867728950e" + }, + { + "name": "organization_name", + "in": "query", + "required": false, + "description": "Comma-separated organization names. Console-oriented; with an API key (already scoped to one application) it only matches your own organization's name.", + "schema": { + "type": "string" + }, + "example": "Acme Inc" + } + ], + "responses": { + "200": { + "description": "Paginated session list. `results` items are KYC rows, KYB rows, or both depending on `session_kind`; discriminate with each row's `session_kind` field.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Number of sessions matching the filters. Exact for KYC rows; for KYB rows it is capped at 100 (use `next` to know whether more pages exist). For `session_kind=all` it is the sum of both kinds.", + "example": 2 + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page.", + "example": "https://verification.didit.me/v3/sessions/?limit=20&offset=20" + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page.", + "example": null + }, + "results": { + "type": "array", + "description": "Session rows, newest first. With `session_kind=all`, KYC rows come first, then KYB rows.", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/UserSessionListItem" + }, + { + "$ref": "#/components/schemas/BusinessSessionListItem" + } + ], + "discriminator": { + "propertyName": "session_kind", + "mapping": { + "user": "#/components/schemas/UserSessionListItem", + "business": "#/components/schemas/BusinessSessionListItem" + } + } + } + } + } + }, + "examples": { + "KYC sessions (default)": { + "summary": "session_kind=user - one completed and one not-started session", + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "session_id": "5b3720ed-d429-42ef-b67f-37ea805f48ee", + "session_kind": "user", + "session_number": 720, + "session_url": "https://verify.didit.me/session/CuXoBYCYVO5Y", + "portrait_image": "https:///portraits/5b3720ed.jpg?X-Amz-Expires=3600&X-Amz-Signature=...", + "document_type": "Passport", + "full_name": "Jane Marie Doe", + "country": "ESP", + "status": "Approved", + "vendor_data": "your-user-id-123", + "didit_internal_id": "d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147", + "created_at": "2026-06-09T12:26:54.328364Z", + "features": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + }, + { + "feature": "IP_ANALYSIS", + "status": "Approved" + } + ], + "phone_number": { + "number": "+14155552671", + "is_verified": true + }, + "email_address": { + "email": "jane@example.com", + "is_verified": true + }, + "additional_details": { + "aml": { + "total_hits": 0, + "entity_type": "person", + "is_adverse_media_fetched": false + }, + "liveness": { + "method": "PASSIVE", + "score": 0.98 + }, + "face_match": { + "score": 0.95 + } + } + }, + { + "session_id": "038ad62f-5ebd-46c2-afe9-4b2956bf687f", + "session_kind": "user", + "session_number": 719, + "session_url": "https://verify.didit.me/session/7RGKiO6ioGH6", + "portrait_image": null, + "document_type": null, + "full_name": null, + "country": null, + "status": "Not Started", + "vendor_data": "your-user-id-124", + "didit_internal_id": "eb4e679d-ca4b-461f-9685-687bac29ebdb", + "created_at": "2026-06-09T08:02:23.343053Z", + "features": [ + { + "feature": "ID_VERIFICATION", + "status": "Not Finished" + }, + { + "feature": "LIVENESS", + "status": "Not Finished" + }, + { + "feature": "FACE_MATCH", + "status": "Not Finished" + }, + { + "feature": "AML", + "status": "Not Finished" + }, + { + "feature": "IP_ANALYSIS", + "status": "Not Finished" + } + ], + "phone_number": null, + "email_address": null, + "additional_details": null + } + ] + } + }, + "Mixed KYC + KYB (session_kind=all)": { + "summary": "session_kind=all - KYC row followed by a KYB row", + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "session_id": "5b3720ed-d429-42ef-b67f-37ea805f48ee", + "session_kind": "user", + "session_number": 720, + "session_url": "https://verify.didit.me/session/CuXoBYCYVO5Y", + "portrait_image": null, + "document_type": "Identity Card", + "full_name": "Jane Marie Doe", + "country": "ESP", + "status": "In Review", + "vendor_data": "your-user-id-123", + "didit_internal_id": "d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147", + "created_at": "2026-06-09T12:26:54.328364Z", + "features": [ + { + "feature": "ID_VERIFICATION", + "status": "In Review" + }, + { + "feature": "LIVENESS", + "status": "Approved" + } + ], + "phone_number": null, + "email_address": null, + "additional_details": { + "liveness": { + "method": "PASSIVE", + "score": 0.98 + } + } + }, + { + "session_id": "8a1f63c2-1b9e-4f3a-9c41-2f6a1f0b7d52", + "session_kind": "business", + "session_number": 42, + "status": "Approved", + "vendor_data": "your-company-id-77", + "created_at": "2026-06-08T09:14:02.118450Z", + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "workflow_type": "kyb", + "workflow_label": "Standard KYB", + "company_name": "Acme Holdings S.L.", + "registration_number": "B12345678", + "country": "ES" + } + ] + } + } + } + } + } + }, + "301": { + "description": "Redirect to the canonical trailing-slash URL. Returned when the request path is `/v3/sessions` (no trailing slash); the `Location` header points to `/v3/sessions/` with the query string preserved. GET clients typically follow it automatically." + }, + "400": { + "description": "Invalid query parameter. The body is an object keyed by the offending parameter.", + "content": { + "application/json": { + "examples": { + "Invalid session_kind": { + "summary": "session_kind not one of user/business/all", + "value": { + "session_kind": "Must be \"user\", \"business\", or \"all\"." + } + }, + "Invalid date": { + "summary": "Malformed date_from/date_to", + "value": { + "date_from": [ + "Enter a valid date." + ] + } + }, + "Invalid didit_internal_id": { + "summary": "didit_internal_id is not a UUID", + "value": { + "didit_internal_id": [ + "Enter a valid UUID." + ] + } + }, + "Invalid api_service": { + "summary": "api_service not in the allowed choices", + "value": { + "api_service": [ + "Select a valid choice. FOO is not one of the available choices." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, malformed, or revoked API key. This API never returns `401`; all authentication failures are `403`.\n\n**Deleted application:** for a few minutes after an application is deleted, a request made with one of its API keys can answer `{\"detail\": \"This application was deleted.\", \"code\": \"application_deleted\"}`. Do not retry: the application and its API keys no longer exist. After those few minutes, the key is refused like any other invalid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You do not have permission to perform this action." + }, + "code": { + "type": "string", + "description": "Machine-readable reason, present only on some failures. `application_deleted`: the API key belongs to an application that was deleted.", + "example": "application_deleted" + } + }, + "required": [ + "detail" + ] + }, + "examples": { + "Forbidden": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "ApplicationDeleted": { + "summary": "API key of a deleted application", + "value": { + "detail": "This application was deleted.", + "code": "application_deleted" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -s 'https://verification.didit.me/v3/sessions/?status=Approved,In%20Review&date_from=2026-06-01&limit=20' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nurl = \"https://verification.didit.me/v3/sessions/\"\nheaders = {\"x-api-key\": \"YOUR_API_KEY\"}\nparams = {\n \"session_kind\": \"user\", # \"user\" (default) | \"business\" | \"all\"\n \"status\": \"Approved,In Review\", # comma-separated, OR within the list\n \"date_from\": \"2026-06-01\",\n \"limit\": 20,\n}\n\nwhile url:\n response = requests.get(url, headers=headers, params=params)\n response.raise_for_status()\n page = response.json()\n for session in page[\"results\"]:\n print(session[\"session_kind\"], session[\"session_id\"], session[\"status\"])\n url, params = page[\"next\"], None # \"next\" already carries the query string" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const params = new URLSearchParams({\n session_kind: 'all',\n status: 'Approved,In Review',\n date_from: '2026-06-01',\n limit: '20',\n});\n\nconst response = await fetch(\n `https://verification.didit.me/v3/sessions/?${params}`,\n { headers: { 'x-api-key': process.env.DIDIT_API_KEY } },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\n\nconst { count, next, results } = await response.json();\nfor (const session of results) {\n // session.session_kind is \"user\" (KYC) or \"business\" (KYB)\n console.log(session.session_kind, session.session_id, session.status);\n}" + } + ] + } + }, + "/v3/session/{sessionId}/share/": { + "post": { + "summary": "Mint a share token for a finished verification session", + "description": "Mint a short-lived JWT that lets a specific Didit application import this finished session. Pair with `POST /v3/session/import-shared/`.", + "operationId": "post_v3_session_share", + "x-didit-sdk": { + "resource": "sessions", + "method": "share", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "200": { + "description": "Share token minted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "share_token": { + "type": "string", + "description": "HS256-signed JWT. Pass this verbatim as `share_token` to [`POST /v3/session/import-shared/`](/sessions-api/import-shared-session) on the target application.", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + }, + "for_application_id": { + "type": "string", + "format": "uuid", + "description": "Echoes the target application UUID that the token is bound to.", + "example": "a5f3bca2-46e2-411e-90ef-a580900a57ee" + }, + "session_kind": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Whether the source session is a User Verification (KYC) or Business Verification (KYB) session." + } + } + }, + "examples": { + "User session": { + "summary": "Share a KYC session", + "value": { + "share_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uX2lkIjoiMjBiYjNjM2ItNjE0Ni00MzlmLTg0YTQtYmQzMGQwMGFjNmEyIiwiZnJvbV9hcHBsaWNhdGlvbl9pZCI6ImRiZDIwZTM0LTQyZTktNGYyYy1iYTkxLWNmMDc2MjAxNmY2NCIsImZvcl9hcHBsaWNhdGlvbl9pZCI6ImE1ZjNiY2EyLTQ2ZTItNDExZS05MGVmLWE1ODA5MDBhNTdlZSIsImlhdCI6MTc1MzYzMDY2NiwiZXhwIjoxNzUzNjM0MjY2fQ.JJ9pNE_hqZsOtbR0XYZIWw4JzidjdEl279iUrsIkhGE", + "for_application_id": "a5f3bca2-46e2-411e-90ef-a580900a57ee", + "session_kind": "user" + } + }, + "Business session": { + "summary": "Share a KYB session", + "value": { + "share_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "for_application_id": "a5f3bca2-46e2-411e-90ef-a580900a57ee", + "session_kind": "business" + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials. Unlike most v3 endpoints, this endpoint authenticates through the permission decorator and returns `401` when no valid token/key is presented.", + "content": { + "application/json": { + "examples": { + "Unauthorized": { + "summary": "Unauthorized", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "404": { + "description": "No session with the given `session_id` exists in your application.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Session not found", + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field errors are keyed by field name; the not-finished error arrives under a `detail` key (as an array).", + "content": { + "application/json": { + "examples": { + "Wrong status": { + "summary": "Source session is not finished", + "value": { + "detail": [ + "Only finished sessions (\"Approved\", \"Declined\", \"In Review\") can be shared." + ] + } + }, + "Target missing": { + "summary": "Target application does not exist", + "value": { + "for_application_id": [ + "Target application does not exist." + ] + } + }, + "Self-share": { + "summary": "Target is the calling application", + "value": { + "for_application_id": [ + "Cannot share a session with the same application." + ] + } + }, + "TTL bounds": { + "summary": "ttl_in_seconds outside bounds", + "value": { + "ttl_in_seconds": [ + "Ensure this value is greater than or equal to 60." + ] + } + } + } + } + } + }, + "403": { + "description": "The credentials are valid but lack the `write:sessions` permission for this application.", + "content": { + "application/json": { + "examples": { + "No Permission": { + "value": { + "detail": "You do not have permission to perform this action." + }, + "summary": "No Permission" + } + } + } + } + } + }, + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the source verification session to mint a token for.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "for_application_id" + ], + "properties": { + "for_application_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the Didit application that will redeem the token. Must exist, must not be deleted, and must differ from the calling application. Find it in the Business Console under **Settings → Application**.", + "example": "a5f3bca2-46e2-411e-90ef-a580900a57ee" + }, + "ttl_in_seconds": { + "type": "integer", + "description": "Token lifetime, in seconds. Minimum `60`, maximum `86400` (24 h). Defaults to `3600` (1 h).", + "minimum": 60, + "maximum": 86400, + "default": 3600, + "example": 3600 + } + } + }, + "example": { + "for_application_id": "a5f3bca2-46e2-411e-90ef-a580900a57ee", + "ttl_in_seconds": 7200 + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/share/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"for_application_id\": \"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n \"ttl_in_seconds\": 7200\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/share/\",\n headers={\n 'x-api-key': 'YOUR_API_KEY',\n \"Content-Type\": \"application/json\",\n },\n json={\n \"for_application_id\": \"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n \"ttl_in_seconds\": 7200,\n },\n)\nresponse.raise_for_status()\nshare_token = response.json()[\"share_token\"]" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const response = await fetch(\n 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/share/',\n {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n for_application_id: 'a5f3bca2-46e2-411e-90ef-a580900a57ee',\n ttl_in_seconds: 7200,\n }),\n },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst { share_token } = await response.json();" + } + ] + } + }, + "/v3/session/import-shared/": { + "post": { + "summary": "Redeem a share token to clone a verification session into your application", + "description": "Redeem a share token to clone a KYC or KYB session into the calling application with a fresh `session_id`. Not idempotent — a token redeems once per receiver.", + "operationId": "post_v3_session_import_shared", + "x-didit-sdk": { + "resource": "sessions", + "method": "import_shared", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "201": { + "description": "Session cloned. The body is the V3 decision payload for both kinds, discriminated by `session_kind`: `\"user\"` (KYC) with list-based feature blocks such as `id_verifications`, `liveness_checks`, `aml_screenings`; `\"business\"` (KYB) with blocks like `registry_checks` and `key_people_checks`. Both include `shared_from_session` — the `session_id` of the original session in the source application. With `trust_review: false` the cloned session's `status` is forced to `In Review`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionDecision" + }, + "examples": { + "User session imported": { + "summary": "Cloned KYC session (truncated — full V3 decision payload in reality)", + "value": { + "session_id": "11111111-2222-3333-4444-555555555555", + "session_kind": "user", + "shared_from_session": "99999999-8888-7777-6666-555555555555", + "session_number": 43762, + "session_url": null, + "status": "In Review", + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "vendor_data": "user-1", + "created_at": "2026-05-17T08:42:11Z", + "expires_at": "2026-05-24T08:42:11Z" + } + }, + "Business session imported": { + "summary": "Cloned KYB session (truncated — full V3 KYB decision payload in reality)", + "value": { + "session_id": "22222222-3333-4444-5555-666666666666", + "session_kind": "business", + "shared_from_session": "88888888-7777-6666-5555-444444444444", + "session_number": 982, + "session_url": null, + "status": "Approved", + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "vendor_data": "company-1" + } + } + } + } + } + }, + "400": { + "description": "Validation error (token invalid, expired, wrong target, or references a session that no longer exists).", + "content": { + "application/json": { + "examples": { + "Invalid Share Token": { + "summary": "Invalid JWT", + "value": { + "share_token": [ + "Invalid share token." + ] + } + }, + "Expired Share Token": { + "summary": "Token past `exp`", + "value": { + "share_token": [ + "Share token has expired." + ] + } + }, + "Wrong audience": { + "summary": "Token not bound to this application", + "value": { + "share_token": [ + "This token is not valid for this application." + ] + } + }, + "Original missing": { + "summary": "Source session was deleted", + "value": { + "share_token": [ + "Original session does not exist." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing/invalid API key (this endpoint returns `403`, never `401`, for authentication failures), or this source session has already been imported into the calling application (one redeem per receiver).", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "summary": "Forbidden", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Already imported": { + "summary": "Source already imported once", + "value": { + "detail": "This session has already been shared with your application." + } + } + } + } + } + }, + "404": { + "description": "`workflow_id` does not belong to the calling application.", + "content": { + "application/json": { + "examples": { + "Workflow not found": { + "summary": "Workflow unknown", + "value": { + "detail": "Workflow does not exist for this application." + } + } + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "share_token", + "trust_review", + "workflow_id" + ], + "properties": { + "share_token": { + "type": "string", + "description": "JWT share token issued by [`POST /v3/session/{sessionId}/share/`](/sessions-api/share-session).", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + }, + "trust_review": { + "type": "boolean", + "description": "If `true`, the cloned session keeps the source's final `status`. If `false`, it is forced into `In Review`.", + "example": false + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "UUID of a workflow in the calling application. Cross-application IDs are rejected with `404`.", + "example": "9f9b1234-aaaa-bbbb-cccc-1234567890ab" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Optional override for the cloned session's `vendor_data`.", + "example": "user-1" + } + } + }, + "example": { + "share_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "trust_review": false, + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "vendor_data": "user-1" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/import-shared/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"share_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n \"trust_review\": false,\n \"workflow_id\": \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n \"vendor_data\": \"user-1\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/session/import-shared/\",\n headers={\n 'x-api-key': 'YOUR_API_KEY',\n \"Content-Type\": \"application/json\",\n },\n json={\n \"share_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n \"trust_review\": False,\n \"workflow_id\": \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n \"vendor_data\": \"user-1\",\n },\n)\nresponse.raise_for_status()\nimported = response.json()" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const response = await fetch(\n 'https://verification.didit.me/v3/session/import-shared/',\n {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n share_token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',\n trust_review: false,\n workflow_id: '9f9b1234-aaaa-bbbb-cccc-1234567890ab',\n vendor_data: 'user-1',\n }),\n },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst imported = await response.json();" + } + ] + } + }, + "/v3/kyb/search/": { + "post": { + "summary": "KYB registry company search (standalone)", + "operationId": "post_v3_kyb_search", + "x-didit-sdk": { + "resource": "kyb", + "method": "search", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Search official company registries for candidate companies by name and/or registration number. This is step 1 of the two-step registry flow: **search** returns lightweight candidates, then `POST /v3/kyb/select/` retrieves the full profile of one candidate.\n\n**Search billing.** Successful searches using a paid company-data source cost USD 0.50 per request, regardless of candidate count. Empty results, failures and results served only by other sources are not charged. The applicable price is reserved before searching; insufficient balance returns 403 with code `insufficient_credits`. Contract rates apply where agreed, and an unconfigured contract search rate returns 403. Sandbox searches are free. Repeating a successful search can incur another charge; callback delivery does not add a charge. Profile retrieval is billed separately. This search fee also applies to v2. Candidates always come back with `fetch_status: \"pending\"`: the full company data is only fetched (and billed) on select.\n\n**`kyb_response_id` is ephemeral.** It is a per-search handle, not a stable company id. Select promptly after searching and re-search to get a fresh handle instead of storing it.\n\n**`tier` picks the provider chain.** `tier` names the registry tier the search runs on: `basic` (the default, the Lite chain), `shareholders` or `ubo`. The search uses the provider chain that serves that tier, and it is not always the same chain the Lite search reads, so an empty Lite search is not proof that the registry does not hold the company — search again at the tier you need. A country that does not offer the tier you name is searched at the highest tier it offers up to that one. Retrieve the candidate from the endpoint that sells that tier: `POST /v3/kyb/select/` (Lite), `POST /v3/kyb/shareholders/` or `POST /v3/kyb/ubo/`.\n\n**Authentication.** `POST /v3/kyb/search/` (and `/v2/`) takes your application's API key in the `x-api-key` header. The hosted `POST /v1/kyb/search/` is the session-authenticated route a hosted verification flow calls: it answers `403` to an API key and takes no `tier` — the workflow's KYB registry configuration picks the chain.\n\n**Two modes.**\n- *Synchronous (default):* the API polls the registry until the search resolves and returns the final candidate list (`search_resolved: true`).\n- *Asynchronous:* pass `webhook_url` and the API returns immediately — usually with `search_status: \"pending\"` and an empty candidate list — then POSTs a `kyb.registry_search.resolved` callback to your URL when the registry finishes. The callback body is `{\"event_id\", \"event_type\": \"kyb.registry_search.resolved\", \"request_id\", \"vendor_data\", \"metadata\", \"search_status\", \"search_resolved\", \"kyb_registry\", \"timestamp\", \"created_at\"}` — note `timestamp` and `created_at` in the callback are epoch integers, unlike this endpoint's ISO `created_at` — and is sent **unsigned** (header `X-Didit-Unsigned-Callback: true`, no `X-Signature`).\n\n**Country codes.** ISO 3166-1 alpha-2 (`GB`, `DE`), plus `XX-YY` subdivision codes where registries are regional — e.g. `US-CA` for California.\n\n**Sandbox.** Keys from sandbox applications return one static candidate without contacting any registry.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "country_code" + ], + "properties": { + "country_code": { + "type": "string", + "maxLength": 10, + "description": "ISO 3166-1 alpha-2 code of the country whose company registry to search. For example, `GB`. Where company registries operate at state or province level, append the ISO 3166-2 subdivision code as `XX-YY`: `US-CA` searches the California registry. United States searches always require the subdivision — a bare `US` is rejected. Case-insensitive; normalized to upper case.", + "example": "GB" + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "Company name to search for. Either `name` or `registration_number` is required.", + "example": "Tesco" + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Registry registration number to search for. Either `name` or `registration_number` is required." + }, + "tier": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "default": "basic", + "description": "Registry tier whose provider chain this search runs on. `basic` (default) searches the country's Lite chain; `shareholders` and `ubo` search that tier's ownership chain, which can be a different source, so an empty Lite search does not prove the registry lacks the company. A country that does not offer the named tier is searched at the highest tier it offers up to that one. Retrieve the candidate from the endpoint that sells the tier you searched: `POST /v3/kyb/select/` (Lite), `POST /v3/kyb/shareholders/` or `POST /v3/kyb/ubo/`.", + "example": "basic" + }, + "search_type": { + "type": "string", + "enum": [ + "contains" + ], + "default": "contains", + "description": "Name-matching strategy applied by the registry search. `contains` is the only accepted value: no registry source behind the search offers a prefix or fuzzy mode, so `start_with` and `fuzzy` are rejected with `400` (`not a valid choice`)." + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for this lookup; echoed back and included in the webhook callback." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON; echoed back and included in the webhook callback." + }, + "webhook_url": { + "type": "string", + "format": "uri", + "maxLength": 500, + "description": "Switches the search to asynchronous mode: the API returns immediately and POSTs an unsigned `kyb.registry_search.resolved` callback to this URL when the candidate list is ready." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/kyb/search/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"country_code\": \"GB\",\n \"name\": \"Tesco\",\n \"search_type\": \"contains\",\n \"vendor_data\": \"company-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os, requests\n\nresp = requests.post(\n \"https://verification.didit.me/v3/kyb/search/\",\n headers={\"x-api-key\": os.environ[\"DIDIT_API_KEY\"]},\n json={\"country_code\": \"GB\", \"name\": \"Tesco\"},\n timeout=60, # synchronous mode waits for the registry to resolve\n)\nresp.raise_for_status()\ncandidates = resp.json()[\"kyb_registry\"][\"companies\"]\nkyb_response_id = candidates[0][\"kyb_response_id\"] # pass to /v3/kyb/select/" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const res = await fetch('https://verification.didit.me/v3/kyb/search/', {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ country_code: 'GB', name: 'Tesco' }),\n});\nconst data = await res.json();\nconst kybResponseId = data.kyb_registry.companies[0].kyb_response_id;" + } + ], + "responses": { + "200": { + "description": "Search accepted. Synchronous mode returns the resolved candidate list; webhook mode returns immediately (often `search_status: \"pending\"`) and delivers the final list via the `kyb.registry_search.resolved` callback.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "With `webhook_url`: the persisted search-request id, echoed as `request_id` in the `kyb.registry_search.resolved` callback. Without `webhook_url`: a transient correlation UUID (the search is not stored)." + }, + "kyb_registry": { + "$ref": "#/components/schemas/KYBRegistrySearchResults" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Resolved search": { + "summary": "Synchronous mode — final candidate list", + "value": { + "request_id": "0c7e9a40-3a13-4f7e-8a44-9d2e2f1c5b6a", + "kyb_registry": { + "companies": [ + { + "kyb_response_id": "69aeeb95febb0f1704042259", + "name": "Tesco PLC", + "registration_number": "00445790", + "status": "active", + "type": "Public Limited Company", + "risk_level": null, + "fetch_status": "pending" + }, + { + "kyb_response_id": "69aeeb95febb0f170404225a", + "name": "E-TESCO LTD", + "registration_number": "15168476", + "status": "active", + "type": "Private Limited Company", + "risk_level": null, + "fetch_status": "pending" + } + ], + "pagination": { + "total": 2, + "page": 1, + "per_page": 25 + }, + "search_status": "resolved", + "search_resolved": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:40:00.000000+00:00" + } + }, + "Pending search (webhook mode)": { + "summary": "`webhook_url` provided — candidates arrive via the kyb.registry_search.resolved callback", + "value": { + "request_id": "5f2d8c1b-7e4a-4b9c-9f0d-3a6e8c2b1d4f", + "kyb_registry": { + "companies": [], + "pagination": { + "total": 0, + "page": 1, + "per_page": 25 + }, + "search_status": "pending", + "search_resolved": false + }, + "vendor_data": "company-1234", + "metadata": { + "source": "onboarding" + }, + "created_at": "2026-06-11T10:41:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems use DRF's standard envelope; the name/registration-number rule reports under `non_field_errors`.", + "content": { + "application/json": { + "examples": { + "Missing search criteria": { + "summary": "Neither `name` nor `registration_number` provided", + "value": { + "non_field_errors": [ + "Either 'name' or 'registration_number' is required." + ] + } + }, + "Invalid country_code": { + "summary": "Must be `XX` (alpha-2) or `XX-YY` subdivision", + "value": { + "country_code": [ + "country_code must be a 2-letter ISO code." + ] + } + }, + "Invalid search_type": { + "summary": "Only `contains` is accepted", + "value": { + "search_type": [ + "\"start_with\" is not a valid choice." + ] + } + }, + "Invalid webhook_url": { + "summary": "`webhook_url` must be a valid URL", + "value": { + "webhook_url": [ + "Enter a valid URL." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, malformed, revoked, or belongs to another environment. Authentication failures return `403` with `{\"detail\": ...}`; this API never returns `401`. Also returned before searching when the applicable search fee cannot be covered (`code: \"insufficient_credits\"`), or when a contract search rate is missing.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. All POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "502": { + "description": "The KYB registry provider is temporarily unavailable - a 5xx response, timeout, or connection failure from the upstream registry. The search is not billed. Retry later, or, in Didit's hosted verification flow, the user is automatically offered manual company entry instead of a retry-only error.", + "content": { + "application/json": { + "examples": { + "Registry provider outage": { + "summary": "The registry provider did not answer the search", + "value": { + "detail": "The KYB registry provider is temporarily unavailable. Please try again shortly.", + "code": "kyb_registry_provider_unavailable" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/kyb/select/": { + "post": { + "summary": "KYB registry company select — Lite tier (standalone)", + "operationId": "post_v3_kyb_select", + "x-didit-sdk": { + "resource": "kyb", + "method": "select", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Retrieve the **Lite** registry profile of one candidate returned by `POST /v3/kyb/search/` — company details, registered address, identifiers, status, activity and the officers, plus the raw registry payload. Shareholders and ultimate beneficial owners are sold by `POST /v3/kyb/shareholders/` and `POST /v3/kyb/ubo/`.\n\n**Pairing with search.** Pass the candidate's `kyb_response_id` exactly as returned by a search of the same application. The handle is ephemeral and scoped to that search — select promptly, and re-search for a fresh handle rather than storing it.\n\n**Billing.** Each select is billed at the country's Lite price (any search fee is separate); see [KYB registry pricing](/getting-started/kyb-registry-pricing). The balance is checked before any registry call (`403` when short) and the charge is recorded against the created business session.\n\n**Persistence is mandatory.** Every select creates a business session (its id is the `request_id`) so billing and retrieval can be tracked; it appears in the console. `save_api_request` is accepted for symmetry but must be `true` (the default) — sending `false` returns `400`.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBRegistryCandidateRequest" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/kyb/select/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"kyb_response_id\": \"69aeeb95febb0f1704042259\",\n \"vendor_data\": \"company-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os, requests\n\nsearch = requests.post(\n \"https://verification.didit.me/v3/kyb/search/\",\n headers={\"x-api-key\": os.environ[\"DIDIT_API_KEY\"]},\n json={\"country_code\": \"GB\", \"name\": \"Tesco\"},\n timeout=60,\n).json()\nkyb_response_id = search[\"kyb_registry\"][\"companies\"][0][\"kyb_response_id\"]\n\nresp = requests.post(\n \"https://verification.didit.me/v3/kyb/select/\",\n headers={\"x-api-key\": os.environ[\"DIDIT_API_KEY\"]},\n json={\"kyb_response_id\": kyb_response_id, \"vendor_data\": \"company-1234\"},\n timeout=90, # waits for the registry to resolve the full profile\n)\nresp.raise_for_status()\ncompany = resp.json()[\"kyb_registry\"]\nprint(company[\"company_name\"], company[\"registry_status\"], company[\"status\"])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const res = await fetch('https://verification.didit.me/v3/kyb/select/', {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n kyb_response_id: '69aeeb95febb0f1704042259',\n vendor_data: 'company-1234',\n }),\n});\nconst data = await res.json();\nconsole.log(data.kyb_registry.company_name, data.kyb_registry.registry_status);" + } + ], + "responses": { + "200": { + "description": "Company retrieved and billed. `kyb_registry` carries the full profile; check `data_resolved` — when `false` the registry was still processing and the stored record completes automatically once it resolves.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the business session created for this retrieval. The stored record (visible in the console) keeps updating if the registry resolves after the response." + }, + "registry_tier_requested": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "The tier this endpoint sells: `basic` (Lite) for select, `shareholders`, `ubo`." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "The tier actually delivered and billed. Lower than requested when the country's registries hold no ownership data, or the registry has no ownership record for this company." + }, + "kyb_registry": { + "type": "object", + "description": "Full registry company profile.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit's id for this company record." + }, + "node_id": { + "type": "string", + "example": "feature_kyb_registry" + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review" + ], + "description": "Didit's assessment of the registry check (e.g. `In Review` when the company is not active in the registry or ownership data could not be derived)." + }, + "registry_status": { + "type": "string", + "nullable": true, + "enum": [ + "active", + "dissolved", + "deregistered", + "see full details", + "authorised", + "appointed representative", + "unauthorised", + "inactive", + "no longer authorised", + "closed", + "struck off" + ], + "description": "Normalized company status as reported by the registry." + }, + "data_resolved": { + "type": "boolean", + "description": "`true` once the registry returned the full profile. `false` when the registry was still processing after polling — the stored record completes automatically once the registry resolves." + }, + "company_name": { + "type": "string", + "example": "Tesco PLC" + }, + "registration_number": { + "type": "string", + "nullable": true, + "example": "00445790" + }, + "country_code": { + "type": "string", + "description": "ISO 3166-1 alpha-2.", + "example": "GB" + }, + "region": { + "type": "string", + "nullable": true, + "description": "Subdivision code when the registry is regional (e.g. `CA` for `US-CA`); empty otherwise." + }, + "company_type": { + "type": "string", + "nullable": true, + "example": "Public Limited Company" + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true, + "example": "1947-11-27" + }, + "registered_address": { + "type": "string", + "nullable": true, + "example": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA" + }, + "tax_number": { + "type": "string", + "nullable": true, + "example": "GB412 8925 93" + }, + "risk_level": { + "type": "string", + "nullable": true, + "description": "Registry-reported risk level, when available." + }, + "verification_status": { + "type": "string", + "nullable": true, + "description": "Registry verification status, when available." + }, + "is_from_registry": { + "type": "boolean", + "description": "Always `true` for this endpoint — the profile was retrieved from an official registry." + }, + "fetch_status": { + "type": "string", + "enum": [ + "pending", + "resolved" + ], + "description": "Registry fetch lifecycle. Mirrored by `data_resolved`." + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true, + "description": "Activity description or classification code, when reported." + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "nullable": true, + "description": "Decimal amount as a string, when reported." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 code, when reported." + }, + "website": { + "type": "string", + "nullable": true, + "example": "https://www.tesco.com/" + }, + "email": { + "type": "string", + "nullable": true, + "example": "customer.service@tesco.co.uk" + }, + "phone": { + "type": "string", + "nullable": true, + "example": "03301231688" + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true, + "description": "LEI, when reported." + }, + "location_of_registration": { + "type": "string", + "nullable": true, + "description": "Registry/jurisdiction the company is registered with, when reported." + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards. At select time this is typically not yet set — VAT and tax ID validation runs when the end user submits the registry step in the hosted flow." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true, + "description": "Registry-reported share/capital details (currency, total shares, share classes), when available." + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "example": "MELISSA BETHELL" + }, + "designation": { + "type": "string", + "nullable": true, + "description": "Raw registry designation (e.g. `Director`, `Secretary`)." + }, + "role": { + "type": "string", + "description": "Normalized role derived from the designation. Full 17-value model set; registry parsing on this endpoint typically emits director, non_executive_director, secretary, chairman, or company_officer.", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "company_officer", + "settlor", + "protector", + "investor", + "other" + ] + }, + "nationality": { + "type": "string", + "nullable": true + }, + "is_active": { + "type": "boolean", + "description": "`false` when the registry marks the appointment as resigned." + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending" + ], + "description": "Status of an associated KYC session, when one was started for this officer. `null` otherwise." + }, + "kyc_session_url": { + "type": "string", + "nullable": true, + "description": "Verification URL of the associated KYC session, when one exists." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + }, + "description": "Directors, secretaries, and other officers extracted from the registry." + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the owner is a natural person or a corporate entity." + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Normalized roles (e.g. `ubo`, `shareholder`)." + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership band reported by the registry (e.g. `25%`)." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership band reported by the registry." + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending" + ] + }, + "kyc_session_url": { + "type": "string", + "nullable": true + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true, + "description": "Parsed ownership percentage (max bound, falling back to min bound)." + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + }, + "shareholding": { + "$ref": "#/components/schemas/KYBRegistryShareholding" + } + } + }, + "description": "Beneficial owners / persons with significant control extracted from the registry. Can be empty when the registry does not expose ownership." + }, + "addresses": { + "type": "array", + "nullable": true, + "description": "Every address the registry states, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryAddress" + } + }, + "industries": { + "type": "array", + "nullable": true, + "description": "The activities the registry classifies the company under, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryIndustry" + } + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "type": "object", + "description": "Raw record returned by the registry source, kept for reviewers. Its shape differs by country and source and is not a stable contract: read the canonical fields (`addresses`, `industries`, `ownership`, `beneficial_owners[].shareholding`) instead." + }, + "user_provided_data": { + "type": "object", + "nullable": true, + "description": "Always `null` on this endpoint (used by the hosted KYB flow when end users edit registry data)." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set when a console reviewer edits the record later." + }, + "is_editable": { + "type": "boolean", + "description": "Whether the registry-sourced data can still be edited in the hosted flow/console." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "Same as the top-level `registry_tier_delivered`." + }, + "registry_ownership_status": { + "type": "string", + "nullable": true, + "enum": [ + "not_requested", + "delivered", + "not_held", + "unavailable" + ], + "description": "`delivered` when the ownership data came back, `not_held` when the ownership registry answered and does not list this company (see `registry_data.ownership_error`), `unavailable` when an ownership tier was requested but the registry had none for this company or could not be asked (see `registry_data.ownership_error`), `not_requested` on the Lite tier." + }, + "ownership": { + "$ref": "#/components/schemas/KYBRegistryOwnership" + }, + "registry_tier": { + "type": "string", + "nullable": true, + "description": "The registry tier the workflow ordered: `basic`, `shareholders` or `ubo`. Also in `ownership.tier_requested`." + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Resolved company": { + "summary": "Full registry profile (officer list truncated for brevity)", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "f1e2d3c4-b5a6-4789-9c0d-1e2f3a4b5c6d", + "node_id": "feature_kyb_registry", + "status": "In Review", + "registry_status": "active", + "data_resolved": true, + "company_name": "Tesco PLC", + "registration_number": "00445790", + "country_code": "GB", + "region": "", + "company_type": "Public Limited Company", + "incorporation_date": "1947-11-27", + "registered_address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "tax_number": "GB412 8925 93", + "risk_level": "low risk", + "verification_status": "verified", + "is_from_registry": true, + "fetch_status": "resolved", + "alternative_names": "TESCO STORES (HOLDINGS) PUBLIC LIMITED COMPANY", + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": "https://www.tesco.com/", + "email": "customer.service@tesco.co.uk", + "phone": "03301231688", + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": { + "currency": "Pound (£)", + "total_number_of_shares": "8174378721", + "meta_detail": { + "Class_Of_Share": "Ordinary" + } + }, + "officers": [ + { + "uuid": "a1b2c3d4-e5f6-4789-9a0b-1c2d3e4f5a6b", + "name": "CHRISTOPHER JON TAYLOR", + "designation": "Secretary", + "role": "secretary", + "nationality": null, + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + }, + { + "uuid": "b2c3d4e5-f6a7-4890-8b1c-2d3e4f5a6b7c", + "name": "MELISSA BETHELL", + "designation": "Director", + "role": "director", + "nationality": "British", + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + } + ], + "beneficial_owners": [], + "addresses": [ + { + "address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "type": "legal_entity_registry_address", + "description": "registered office address" + } + ], + "industries": null, + "accounts": { + "last_account": "26 February 2025", + "next_account": "26 February 2026", + "due_by_date": "26 August 2026" + }, + "registry_data": { + "name": "Tesco PLC", + "registration_number": "00445790", + "status": "active", + "type": "Public Limited Company", + "incorporation_date": "1947-11-27", + "tax_number": "GB412 8925 93", + "country_code": "GB", + "fetch_status": "resolved", + "industries_detail": [ + { + "code": "47110", + "name": "Retail Sale In Non-specialised Stores With Food, Beverages Or Tobacco Predominating", + "description": null, + "meta_detail": [] + } + ] + }, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00" + } + }, + "Pending (registry still resolving)": { + "summary": "Select stayed pending after polling — sparse fields, auto-completes server-side", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "7c0a4f7e-2b1d-4f6a-9c3e-8d5b2a1f0e9d", + "node_id": "feature_kyb_registry", + "status": "Not Finished", + "registry_status": null, + "data_resolved": false, + "company_name": "Tesco PLC", + "registration_number": "00445790", + "country_code": "GB", + "region": null, + "company_type": null, + "incorporation_date": null, + "registered_address": null, + "tax_number": null, + "risk_level": null, + "verification_status": null, + "is_from_registry": true, + "fetch_status": "pending", + "alternative_names": null, + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": null, + "email": null, + "phone": null, + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": {}, + "officers": [], + "beneficial_owners": [], + "addresses": null, + "industries": null, + "accounts": null, + "registry_data": {}, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems use DRF's standard envelope.", + "content": { + "application/json": { + "examples": { + "Missing kyb_response_id": { + "summary": "`kyb_response_id` is required", + "value": { + "kyb_response_id": [ + "This field is required." + ] + } + }, + "save_api_request=false rejected": { + "summary": "Selects are always persisted", + "value": { + "save_api_request": [ + "KYB registry select must be saved so billing and retrieval can be tracked." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, malformed, revoked, or belongs to another environment — and also when the calling organization's balance cannot cover the call. Authentication failures return `403` with `{\"detail\": ...}`; this API never returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. All POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/passive-liveness/": { + "post": { + "summary": "Passive Liveness", + "description": "Detect presentation attacks — printed photos, screen replays, masks, deepfakes — from a single face image. No video, motion, or user interaction required.\n\n**How it works.** The image is analyzed by a biometric model; the largest detected face is evaluated and a liveness `score` (0–100) is computed. `status` is `Declined` when any `error`-severity warning fires:\n- `NO_FACE_DETECTED` — the liveness model found no face,\n- `LOW_LIVENESS_SCORE` — score at or below `face_liveness_score_decline_threshold` (default `30`),\n- `LIVENESS_FACE_ATTACK` — the model detected a presentation attack,\n- `FACE_IN_BLOCKLIST` / `POSSIBLE_FACE_IN_BLOCKLIST` — the face matches an entry on your face blocklist.\n\n`MULTIPLE_FACES_DETECTED` is a `warning`-severity signal (does not decline), and `DUPLICATED_FACE` / `POSSIBLE_DUPLICATED_FACE` are `information`-severity signals that the same face already appeared in another approved session (they never decline). `face_quality` and `face_luminance` (0–100, nullable) describe capture quality.\n\n**Blocklist and duplicate screening.** Runs on **every** call — with or without `save_api_request` — by matching the detected face against your application's face search index. Prior faces with the same `vendor_data` are excluded from the entire index search — so re-running liveness for the same user does not trigger `DUPLICATED_FACE`, and a blocklisted face originating from a session with the caller's own `vendor_data` is also NOT flagged (`FACE_IN_BLOCKLIST` screening is bypassed for same-`vendor_data` faces).\n\n**Billing.** Each `200` response consumes one Passive Liveness API credit (paid from your plan credit or top-up balance). Insufficient balance returns `403` before any image processing.\n\n**Session persistence and face enrollment (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session (Business Console, `GET /v3/session/{sessionId}/decision/` via the returned `request_id`, `status.updated` webhook), the reference image and face crop are stored, and the detected face is **enrolled into your face search index** so later Face Search calls and duplicate checks can match it. When `false`, the call is one-shot: nothing is stored and the face is not enrolled (blocklist/duplicate screening still runs).\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload, no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3passive-liveness", + "x-didit-sdk": { + "resource": "passive_liveness", + "method": "check", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "user_image" + ], + "properties": { + "user_image": { + "type": "string", + "format": "binary", + "description": "Front-facing face image (e.g. a selfie). Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp`. Maximum upload size: **5 MB** (larger files are rejected with `400`). Images are automatically compressed to ~0.5 MB before processing, so very high resolutions do not improve accuracy. The image should contain a single subject — when several faces are present, the largest one is evaluated and a `MULTIPLE_FACES_DETECTED` warning is added." + }, + "face_liveness_score_decline_threshold": { + "type": "number", + "format": "float", + "default": 30, + "minimum": 0, + "maximum": 100, + "description": "Liveness score threshold (0–100). A computed score **at or below** this value emits a `LOW_LIVENESS_SCORE` warning and sets `status` to `Declined`. Default `30`; raise it for a stricter anti-spoofing posture. Values outside 0–100 return `400`.", + "example": 30 + }, + "rotate_image": { + "type": "boolean", + "default": false, + "description": "When `true`, the service tries 90-degree rotations of the input and uses the orientation that yields the best face detection. Useful when EXIF orientation is missing. Adds latency.", + "example": false + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session, stores the face images, emits a `status.updated` webhook, and enrolls the detected face into your face search index. When `false`, the call is one-shot — nothing is stored and the face is not enrolled. Blocklist and duplicate screening run either way.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque identifier (your user id, email, UUID…) stored on the persisted session and echoed back. Also used to exclude prior sessions with the same `vendor_data` from the duplicate-face check, so re-verifying the same user does not raise `DUPLICATED_FACE`.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back. In multipart requests, send it as a JSON-encoded string field — it is parsed into an object.", + "example": { + "flow": "withdrawal" + } + } + } + }, + "example": { + "user_image": "(binary JPEG/PNG selfie)", + "face_liveness_score_decline_threshold": 30, + "rotate_image": false, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "withdrawal" + } + } + } + } + }, + "responses": { + "200": { + "description": "Passive liveness completed. `liveness.score` is the model's 0–100 confidence that the image shows a live person; `status` is `Approved` unless an `error`-severity warning fired. A spoofed or blocklisted face still returns `200` — inspect `liveness.status` and `liveness.warnings`, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Live face — no warnings", + "value": { + "request_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef", + "liveness": { + "status": "Approved", + "method": "PASSIVE", + "score": 95.0, + "user_image": { + "entities": [ + { + "bbox": [ + 661, + 728, + 1688, + 2188 + ], + "confidence": 0.732973, + "age": 26.91, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [], + "face_quality": 84.21, + "face_luminance": 50.33 + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "Declined - presentation attack": { + "summary": "The model flagged a spoof (print, replay, mask…)", + "value": { + "request_id": "f1c8c9b2-d3e4-4f5a-9b8c-7d6e5f4a3b2c", + "liveness": { + "status": "Declined", + "method": "PASSIVE", + "score": 12.5, + "user_image": { + "entities": [ + { + "bbox": [ + 100, + 120, + 420, + 540 + ], + "confidence": 0.81, + "age": 30.1, + "gender": "female", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "LOW_LIVENESS_SCORE", + "feature": "LIVENESS", + "additional_data": null, + "log_type": "error", + "short_description": "Low liveness score", + "long_description": "The liveness check resulted in a low score, indicating potential use of non-live facial representations or poor-quality biometric data." + }, + { + "risk": "LIVENESS_FACE_ATTACK", + "feature": "LIVENESS", + "additional_data": null, + "log_type": "error", + "short_description": "Liveness Face Attack", + "long_description": "The system detected a potential attempt to bypass the liveness check." + } + ], + "face_quality": 72.4, + "face_luminance": 48.2 + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "Declined - face on blocklist": { + "summary": "Live face, but it matches a blocklist entry", + "value": { + "request_id": "0b54a1de-22aa-4e0f-9c84-5a2b1f3c4d5e", + "liveness": { + "status": "Declined", + "method": "PASSIVE", + "score": 93.75, + "user_image": { + "entities": [ + { + "bbox": [ + 40, + 40, + 100, + 100 + ], + "confidence": 0.722082, + "age": 27.0, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "FACE_IN_BLOCKLIST", + "feature": "LIVENESS", + "additional_data": { + "blocklisted_session_id": "882c42d5-8a4d-4d20-8080-a22f57822c86", + "blocklisted_session_number": 3242, + "api_service": null + }, + "log_type": "error", + "short_description": "Face in blocklist", + "long_description": "The system identified a face in the blocklist, which means the face is not allowed to be verified." + } + ], + "face_quality": null, + "face_luminance": null + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "liveness": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Declined` when at least one `error`-severity warning fired (no face, low score, presentation attack, or a blocklist hit); `Approved` otherwise. `warning`/`information` entries (multiple faces, duplicates) never decline on their own." + }, + "method": { + "type": "string", + "enum": [ + "PASSIVE" + ], + "description": "Always `PASSIVE` for this endpoint." + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Liveness confidence (0–100, two decimals) that the image depicts a live person rather than a spoof. `null` when the model returned no confidence.", + "example": 95.0 + }, + "user_image": { + "type": "object", + "description": "Face-detection results for the submitted image.", + "properties": { + "entities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box of the detected face as `[x_min, y_min, x_max, y_max]` pixel coordinates in the processed image.", + "example": [ + 661, + 728, + 1688, + 2188 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.732973 + }, + "age": { + "type": "number", + "format": "float", + "description": "Model-estimated age of the detected face, in years. Informational only — it does not affect the liveness decision.", + "example": 26.91 + }, + "gender": { + "type": "string", + "description": "Model-predicted gender of the detected face (`male` or `female`). Informational only.", + "example": "male" + }, + "race": { + "type": "string", + "nullable": true, + "description": "Reserved field — always `null` in responses from this endpoint.", + "example": null + } + } + }, + "description": "One entry per detected face. The largest face is the one evaluated." + }, + "best_angle": { + "type": "integer", + "nullable": true, + "description": "Rotation (degrees) that produced the best face detection; non-zero only when `rotate_image=true` corrected the orientation.", + "example": 0 + } + } + }, + "warnings": { + "type": "array", + "description": "Risk signals. `error` severity (declines): `NO_FACE_DETECTED`, `LOW_LIVENESS_SCORE` (score at or below the threshold), `LIVENESS_FACE_ATTACK`, `FACE_IN_BLOCKLIST`, `POSSIBLE_FACE_IN_BLOCKLIST`. `warning` severity (review, no decline): `MULTIPLE_FACES_DETECTED`. `information` severity (no decline): `DUPLICATED_FACE`, `POSSIBLE_DUPLICATED_FACE` — the same face already appeared in another approved session.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "NO_FACE_DETECTED", + "LOW_LIVENESS_SCORE", + "LIVENESS_FACE_ATTACK", + "MULTIPLE_FACES_DETECTED", + "FACE_IN_BLOCKLIST", + "POSSIBLE_FACE_IN_BLOCKLIST", + "DUPLICATED_FACE", + "POSSIBLE_DUPLICATED_FACE" + ], + "description": "Machine-readable risk code." + }, + "feature": { + "type": "string", + "enum": [ + "LIVENESS" + ], + "description": "Feature that raised the warning. Always `LIVENESS` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "`null` for most warnings. Blocklist hits carry `{blocklisted_session_id, blocklisted_session_number, api_service}`; duplicate hits carry `{duplicated_session_id, duplicated_session_number, api_service}` pointing at the matching session." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `warning` and `information` entries are advisory and never decline on their own." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "face_quality": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Capture-quality score of the detected face, normalized to 0–100. `null` when the model could not produce a reliable value.", + "example": 84.21 + }, + "face_luminance": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Brightness of the detected face, normalized to 0–100. `null` when unavailable.", + "example": 50.33 + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated, e.g. `2026-06-12T01:04:42.763237+00:00`." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when `user_image` is missing, exceeds 5 MB, has an unsupported extension, fails server-side image decoding, or when an option is out of range. Field errors use DRF's `{field: [messages]}` envelope; image-decoding failures use `{\"error\": ...}`.", + "content": { + "application/json": { + "examples": { + "Missing image": { + "summary": "`user_image` not included in the form data", + "value": { + "user_image": [ + "No file was submitted." + ] + } + }, + "Invalid image format": { + "summary": "Allowed extension but undecodable image content", + "value": { + "error": "Invalid user image format.", + "code": "invalid_image" + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp", + "value": { + "user_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 5 MB limit", + "value": { + "user_image": [ + "File size should not exceed 5 MB" + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, malformed, revoked, or belongs to another environment — and also when the calling organization's balance cannot cover the call. Authentication failures return `403` with `{\"detail\": ...}`; this API never returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. All POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/passive-liveness/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'user_image=@./selfie.jpg' \\\n -F 'face_liveness_score_decline_threshold=30' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nurl = 'https://verification.didit.me/v3/passive-liveness/'\nheaders = {'x-api-key': 'YOUR_API_KEY'}\n\nwith open('selfie.jpg', 'rb') as f:\n files = {'user_image': ('selfie.jpg', f, 'image/jpeg')}\n data = {\n 'face_liveness_score_decline_threshold': 30,\n 'save_api_request': 'true',\n 'vendor_data': 'user-123',\n }\n resp = requests.post(url, headers=headers, files=files, data=data, timeout=60)\n\nresp.raise_for_status()\nbody = resp.json()\nprint('status:', body['liveness']['status'])\nprint('score:', body['liveness']['score'])\nfor w in body['liveness']['warnings']:\n print('warning:', w['risk'], '-', w['log_type'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "import fs from 'node:fs';\n\nconst form = new FormData();\nform.append('user_image', new Blob([fs.readFileSync('./selfie.jpg')]), 'selfie.jpg');\nform.append('face_liveness_score_decline_threshold', '30');\nform.append('save_api_request', 'true');\nform.append('vendor_data', 'user-123');\n\nconst response = await fetch('https://verification.didit.me/v3/passive-liveness/', {\n method: 'POST',\n headers: { 'x-api-key': 'YOUR_API_KEY' },\n body: form,\n});\n\nif (!response.ok) throw new Error(`Passive liveness failed: ${response.status}`);\nconst body = await response.json();\nconsole.log('status:', body.liveness.status, 'score:', body.liveness.score);\nbody.liveness.warnings.forEach(w => console.log('warning:', w.risk, '-', w.log_type));" + } + ] + } + }, + "/v3/database-validation/": { + "post": { + "summary": "Database Validation (standalone)", + "operationId": "post_v3database-validation", + "x-didit-sdk": { + "resource": "database_validation", + "method": "validate", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Validate a person's identity data against official government and registry sources — CPF in Brazil, RENAPER in Argentina, DNI registries in Spain and Peru, INE in Mexico, and 100+ more services across 60+ countries.\n\n**Service selection.** Each country exposes one or more catalog services identified by a `service_id` (e.g. `bra_cpf`, `arg_renaper`, `pan_cedula_sib`). Pin the services you want with the `services` array — if you omit it, exactly **one** default service runs (the longest-established live service for the country). Sending `services` also switches the response to the extended shape with `services_used` and `match_score`. Discover services, their required fields, and prices via `GET /v1/organization/database-validation-countries/` (a catalog endpoint not documented in this spec) or the Business Console workflow editor. Some services are flagged `requires_consent=true` in the catalog and demand `consent=true`; others require onboarding for your organization before they can be called.\n\n**Input fields.** `identification_number` is a universal field that maps to the right country-specific field automatically (see its description). Country format rules are enforced before any provider is called (e.g. BRA CPF must be exactly 11 digits) and format failures are never billed. Biometric services need a `selfie` upload (multipart only); Argentina RENAPER additionally requires `gender`.\n\n**Results.** Each service returns a field-by-field `validation` map plus a vendor-neutral `outcome_code` (`MATCH`, `PARTIAL_MATCH`, `NO_MATCH`, `DOCUMENT_NOT_FOUND`, `INVALID_DOCUMENT_FORMAT`, `INVALID_INPUT`, `MINOR_BLOCKED`, `DECEASED`, `BIOMETRIC_NO_MATCH`, `BIOMETRIC_IMAGE_UNUSABLE`, `INCONCLUSIVE`, `DOCUMENT_SUPERSEDED`, `REGISTRY_UNAVAILABLE`, `REGISTRY_ERROR`). Note the distinctions: `NO_MATCH` is a definitive mismatch, `INCONCLUSIVE` means the registry could not confirm either way (review, not decline), and `BIOMETRIC_IMAGE_UNUSABLE` is a technical selfie problem (retake), unlike the definitive `BIOMETRIC_NO_MATCH`. The overall `match_type` aggregates across services (any full → `full_match`, else any partial → `partial_match`, else `no_match`) and `no_match_action`/`partial_match_action` translate it into the final `status`. `validation_type` is derived: `two_by_two` when two or more distinct services produced a full match (on the identification number, on full name + date of birth together, or on the address without contradictions), else `one_by_one`.\n\n**Persistence.** `save_api_request` defaults to **true**: the result is stored as a session (`request_id` works with `GET /v3/session/{sessionId}/decision/`), appears in the console, and fires a `status.updated` webhook. It also controls the `validations` shape — per-service objects when saved, a merged `{field: match}` map when not.\n\n**Billing.** Per-service: each service that returns a billable result is charged its own catalog price. Failed or skipped services are not billed, and requests rejected with `400` cost nothing. The pre-flight balance check covers the sum of the selected services' prices (`403` when short).\n\n**Failures.** When no selected service returns a usable result, the API returns `400` with `validation_errors` if every unanswered attempt explicitly refused the input (`provider_rejected_input`, `retryable: false`). Correct the input before trying again. Otherwise it returns `502` with `validation_errors` (`empty_provider_response`, `retryable: true`), including mixed input rejection and unavailable fallback attempts; nothing is billed (a session is still recorded with status `Not Finished` when `save_api_request=true`). When only some services fail, the `200` response carries their failures in `database_validation.errors`.\n\n**Sandbox.** Keys from sandbox applications skip the registry calls and billing and return a static `full_match` response — the sandbox payload always includes `services_used` and `match_score: 100` regardless of how `services` was sent, so don't validate the live extended-shape gating or the 0.0–1.0 `match_score` fraction against sandbox responses.\n\nSend the request as `application/json`, or as `multipart/form-data` when uploading a `selfie`.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "issuing_state" + ], + "properties": { + "issuing_state": { + "type": "string", + "description": "ISO 3166-1 **alpha-3** country code of the registry to validate against (e.g. `BRA`, `ESP`, `COL`). Determines which services are available. Unsupported codes return `400` with the full list of valid options.", + "example": "BRA" + }, + "services": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Catalog `service_id`s to run for this country (e.g. `[\"bra_cpf\"]`). Also accepts a single string or a comma-separated/JSON-encoded string. **If omitted or empty, exactly one default service runs** — the longest-established live service for the country; newer, biometric, and pay-as-you-go services must be named explicitly. Sending `services` switches the response to the extended shape (adds `services_used` and `match_score`). Every id must exist and be live for the `issuing_state`; services that require onboarding return `400` until activated for your organization. Discover available services per country via `GET /v1/organization/database-validation-countries/` (a catalog endpoint not documented in this spec) or the Business Console workflow editor.", + "example": [ + "bra_cpf" + ] + }, + "validation_type": { + "type": "string", + "deprecated": true, + "description": "DEPRECATED. Accepted for backward compatibility but ignored — the response `validation_type` is now derived from how many services full-matched. Use `services` to pin specific services." + }, + "consent": { + "type": "boolean", + "default": false, + "description": "Set to `true` when the end user has explicitly consented to the selected validation services. Required for services flagged `requires_consent=true` in the catalog — without it those services return `400` with `services_requiring_consent`." + }, + "identification_number": { + "type": "string", + "description": "Universal identification number — automatically mapped to the correct country-specific field, so you can use it instead of `personal_number`/`tax_number`/`document_number`: ARG→document_number (DNI), BOL→document_number (CI), BRA→tax_number (CPF, 11 digits), CHL→personal_number (RUT), COL→personal_number (Cédula), CRI→personal_number (Cédula), DOM→personal_number (Cédula, 11 digits), ECU→personal_number (Cédula, 10 digits), ESP→personal_number (DNI/NIE), GTM→document_number (DPI), HND→document_number (DNI), MEX→personal_number (CURP, 18 chars), PAN→personal_number (Cédula), PER→personal_number (DNI, 8 digits), PRY→document_number (CI), SLV→document_number (DUI), URY→personal_number (CI), VEN→document_number (Cédula). It never overrides an explicitly provided country-specific field." + }, + "first_name": { + "type": "string", + "description": "The individual's first name. Required by some services/countries.", + "example": "John" + }, + "last_name": { + "type": "string", + "description": "The individual's last name. Required by some services/countries.", + "example": "Doe" + }, + "middle_name": { + "type": "string", + "description": "Middle name, used by some country services (AUS, NZL, …)." + }, + "full_name": { + "type": "string", + "description": "Full name — some services accept this in lieu of first/last name (e.g. CHN services, which match the native-script name)." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth, `YYYY-MM-DD`. Required by many services.", + "example": "1980-01-01" + }, + "personal_number": { + "type": "string", + "description": "Government-issued unique personal identifier. Used by: CHL (RUT), COL (Cédula), CRI, DOM, ECU, ESP (DNI/NIE), MEX (CURP), PAN (Cédula), PER (DNI), URY. Consider `identification_number` instead." + }, + "tax_number": { + "type": "string", + "description": "Tax identification number. Used by: BRA (CPF, 11 digits). Consider `identification_number` instead." + }, + "document_number": { + "type": "string", + "description": "Document number. Used by: ARG (DNI), BOL (CI), GTM (DPI), HND (DNI), PRY (CI), SLV (DUI), VEN (Cédula). Consider `identification_number` instead." + }, + "document_type": { + "type": "string", + "enum": [ + "P", + "DL", + "ID", + "RP", + "SSC", + "HIC", + "WP", + "TC", + "VISA", + "PSC", + "BC", + "OTHER" + ], + "description": "Type of document being validated: `P` passport, `DL` driver license, `ID` national ID, `RP` residence permit (plus `SSC`, `HIC`, `WP`, `TC`, `VISA`, `PSC`, `BC`, `OTHER`). Required by some services (e.g. ESP)." + }, + "expiration_date": { + "type": "string", + "format": "date", + "description": "Document expiration date, `YYYY-MM-DD`. Required for ESP (Spain) validation.", + "example": "2030-01-15" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "description": "Document issue date, `YYYY-MM-DD` (fecha de expedición). Required for COL (Colombia) cédula validation.", + "example": "2015-06-20" + }, + "nationality": { + "type": "string", + "description": "Nationality as ISO 3166-1 alpha-3. Required by some services." + }, + "gender": { + "type": "string", + "enum": [ + "M", + "F", + "X" + ], + "description": "Gender: `M`, `F`, or `X` (other/unknown). Required for Argentina's RENAPER validation (`arg_renaper`)." + }, + "address": { + "description": "Residential address. Prefer the structured object `{\"street_1\":\"123 Main St\",\"street_2\":\"Apt 4B\",\"city\":\"Springfield\",\"region\":\"IL\",\"postal_code\":\"62701\",\"country\":\"US\"}`; a complete single-line string is still accepted. Required by address-verification services; when a service defines address requirements, missing parts return `400` with `address_fields_required_by`.", + "oneOf": [ + { + "type": "object", + "properties": { + "street_1": { + "type": "string" + }, + "street_2": { + "type": "string" + }, + "city": { + "type": "string" + }, + "region": { + "type": "string" + }, + "postal_code": { + "type": "string" + }, + "country": { + "type": "string" + } + } + }, + { + "type": "string", + "description": "Complete single-line address (legacy)." + } + ] + }, + "address_element_1": { + "type": "string", + "description": "Street address including street number and type. If omitted, derived from `address`." + }, + "address_element_2": { + "type": "string", + "description": "Apartment, unit, building, floor, or extra address line. Only send when you have it explicitly." + }, + "address_element_3": { + "type": "string", + "description": "City, suburb, district, locality, or neighborhood. If omitted, derived from `address`." + }, + "address_element_4": { + "type": "string", + "description": "State, province, region, or town. If omitted, derived from `address`." + }, + "address_element_5": { + "type": "string", + "description": "Postcode or postal code. `postal_code` is accepted as an alias." + }, + "postal_code": { + "type": "string", + "description": "Postal code for address-based services (alias of `address_element_5`)." + }, + "driver_license_number": { + "type": "string", + "description": "Driver licence number for government document-verification services (AUS, NZL, IND)." + }, + "driver_license_card_number": { + "type": "string", + "description": "Physical driver licence card number, where required separately from the licence number (AUS)." + }, + "driver_license_state": { + "type": "string", + "description": "Driver licence state or territory of issue (AUS)." + }, + "driver_license_version": { + "type": "string", + "description": "Driver licence version code (NZL)." + }, + "passport_number": { + "type": "string", + "description": "Passport number for government passport-verification services (AUS, NZL, IND, CHN)." + }, + "passport_expiration_date": { + "type": "string", + "format": "date", + "description": "Passport expiry date, `YYYY-MM-DD`, for passport-verification services." + }, + "passport_file_number": { + "type": "string", + "description": "Passport file number (IND)." + }, + "passport_issue_country": { + "type": "string", + "description": "Issuing country of the passport (ISO 3166-1 alpha-3) for passport-verification services." + }, + "medicare_card_number": { + "type": "string", + "description": "Medicare card number (AUS)." + }, + "immi_card_number": { + "type": "string", + "description": "Australian ImmiCard number." + }, + "immi_card_expiry_date": { + "type": "string", + "format": "date", + "description": "Australian ImmiCard expiry date." + }, + "citizenship_certificate_number": { + "type": "string", + "description": "Citizenship certificate number (AUS)." + }, + "birth_registration_number": { + "type": "string", + "description": "Birth-certificate registration number (AUS)." + }, + "birth_registration_date": { + "type": "string", + "format": "date", + "description": "Birth-certificate registration date (AUS)." + }, + "birth_registration_state": { + "type": "string", + "description": "Birth-certificate registration state (AUS)." + }, + "marriage_certificate_number": { + "type": "string", + "description": "Marriage certificate number (AUS)." + }, + "change_of_name_certificate_number": { + "type": "string", + "description": "Change-of-name certificate number (AUS)." + }, + "first_partner_name": { + "type": "string", + "description": "First name of the partner on a marriage certificate (AUS)." + }, + "last_partner_name": { + "type": "string", + "description": "Last name of the partner on a marriage certificate (AUS)." + }, + "voter_id": { + "type": "string", + "description": "Voter registration number (IND EPIC, IRL)." + }, + "epic_card": { + "type": "string", + "description": "India EPIC voter card number." + }, + "pan": { + "type": "string", + "description": "India PAN (Permanent Account Number)." + }, + "national_id": { + "type": "string", + "description": "National ID number (CHN, MYS, KEN, KHM, NGA, ZAF, …)." + }, + "cic": { + "type": "string", + "description": "Mexican INE/IFE Código de Identificación de Credencial (CIC), 9 digits. Used by the MEX INE credential-validity service (`mex_ine_vigencia`)." + }, + "identificador_ciudadano": { + "type": "string", + "description": "Mexican INE Identificador del Ciudadano (9 digits). Used with `cic` for modern INE models (E/F/G/H) in `mex_ine_vigencia`." + }, + "ocr": { + "type": "string", + "description": "Mexican INE OCR number (13 digits, back of card). Used with `cic` for Model D in `mex_ine_vigencia`." + }, + "voter_number": { + "type": "string", + "description": "Mexican INE Clave de Elector (18 chars). Used with `emission_number` for legacy IFE models (A/B/C) in `mex_ine_vigencia`." + }, + "emission_number": { + "type": "string", + "description": "Mexican INE Número de Emisión. Used with `voter_number` for legacy IFE models (A/B/C) in `mex_ine_vigencia`." + }, + "bvn": { + "type": "string", + "description": "Nigerian Bank Verification Number." + }, + "bank_card_number": { + "type": "string", + "description": "Bank card number (CHN bank-card verification)." + }, + "ssn": { + "type": "string", + "description": "Social Security Number (USA)." + }, + "phone": { + "type": "string", + "description": "Phone number for phone-verification services." + }, + "landline": { + "type": "string", + "description": "Landline number for phone-verification services." + }, + "email": { + "type": "string", + "format": "email", + "description": "Email address for identity-verification services." + }, + "country_of_residence": { + "type": "string", + "description": "ISO 3166-1 alpha-2 country of residence. Used by global identity-enrichment services (e.g. `glb_identity_enrichment`) to focus the lookup.", + "example": "US" + }, + "partial_match_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "NO_ACTION", + "description": "What a `partial_match` does to `database_validation.status`. Defaults to `NO_ACTION` (status stays `Approved`, with a warning)." + }, + "no_match_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What a `no_match` does to `database_validation.status`. Defaults to `DECLINE`." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "Persist the validation as a session (console visibility, decision endpoint, `status.updated` webhook). Also changes the `validations` response shape: per-service objects when `true` (default), a merged field map when `false`." + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for the validated user; echoed back and stored with the session." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON stored with the request and echoed back." + } + } + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "issuing_state" + ], + "properties": { + "issuing_state": { + "type": "string", + "description": "ISO 3166-1 **alpha-3** country code of the registry to validate against (e.g. `BRA`, `ESP`, `COL`). Determines which services are available. Unsupported codes return `400` with the full list of valid options.", + "example": "BRA" + }, + "services": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Catalog `service_id`s to run for this country (e.g. `[\"bra_cpf\"]`). Also accepts a single string or a comma-separated/JSON-encoded string. **If omitted or empty, exactly one default service runs** — the longest-established live service for the country; newer, biometric, and pay-as-you-go services must be named explicitly. Sending `services` switches the response to the extended shape (adds `services_used` and `match_score`). Every id must exist and be live for the `issuing_state`; services that require onboarding return `400` until activated for your organization. Discover available services per country via `GET /v1/organization/database-validation-countries/` (a catalog endpoint not documented in this spec) or the Business Console workflow editor.", + "example": [ + "bra_cpf" + ] + }, + "validation_type": { + "type": "string", + "deprecated": true, + "description": "DEPRECATED. Accepted for backward compatibility but ignored — the response `validation_type` is now derived from how many services full-matched. Use `services` to pin specific services." + }, + "consent": { + "type": "boolean", + "default": false, + "description": "Set to `true` when the end user has explicitly consented to the selected validation services. Required for services flagged `requires_consent=true` in the catalog — without it those services return `400` with `services_requiring_consent`." + }, + "identification_number": { + "type": "string", + "description": "Universal identification number — automatically mapped to the correct country-specific field, so you can use it instead of `personal_number`/`tax_number`/`document_number`: ARG→document_number (DNI), BOL→document_number (CI), BRA→tax_number (CPF, 11 digits), CHL→personal_number (RUT), COL→personal_number (Cédula), CRI→personal_number (Cédula), DOM→personal_number (Cédula, 11 digits), ECU→personal_number (Cédula, 10 digits), ESP→personal_number (DNI/NIE), GTM→document_number (DPI), HND→document_number (DNI), MEX→personal_number (CURP, 18 chars), PAN→personal_number (Cédula), PER→personal_number (DNI, 8 digits), PRY→document_number (CI), SLV→document_number (DUI), URY→personal_number (CI), VEN→document_number (Cédula). It never overrides an explicitly provided country-specific field." + }, + "first_name": { + "type": "string", + "description": "The individual's first name. Required by some services/countries.", + "example": "John" + }, + "last_name": { + "type": "string", + "description": "The individual's last name. Required by some services/countries.", + "example": "Doe" + }, + "middle_name": { + "type": "string", + "description": "Middle name, used by some country services (AUS, NZL, …)." + }, + "full_name": { + "type": "string", + "description": "Full name — some services accept this in lieu of first/last name (e.g. CHN services, which match the native-script name)." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth, `YYYY-MM-DD`. Required by many services.", + "example": "1980-01-01" + }, + "personal_number": { + "type": "string", + "description": "Government-issued unique personal identifier. Used by: CHL (RUT), COL (Cédula), CRI, DOM, ECU, ESP (DNI/NIE), MEX (CURP), PAN (Cédula), PER (DNI), URY. Consider `identification_number` instead." + }, + "tax_number": { + "type": "string", + "description": "Tax identification number. Used by: BRA (CPF, 11 digits). Consider `identification_number` instead." + }, + "document_number": { + "type": "string", + "description": "Document number. Used by: ARG (DNI), BOL (CI), GTM (DPI), HND (DNI), PRY (CI), SLV (DUI), VEN (Cédula). Consider `identification_number` instead." + }, + "document_type": { + "type": "string", + "enum": [ + "P", + "DL", + "ID", + "RP", + "SSC", + "HIC", + "WP", + "TC", + "VISA", + "PSC", + "BC", + "OTHER" + ], + "description": "Type of document being validated: `P` passport, `DL` driver license, `ID` national ID, `RP` residence permit (plus `SSC`, `HIC`, `WP`, `TC`, `VISA`, `PSC`, `BC`, `OTHER`). Required by some services (e.g. ESP)." + }, + "expiration_date": { + "type": "string", + "format": "date", + "description": "Document expiration date, `YYYY-MM-DD`. Required for ESP (Spain) validation.", + "example": "2030-01-15" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "description": "Document issue date, `YYYY-MM-DD` (fecha de expedición). Required for COL (Colombia) cédula validation.", + "example": "2015-06-20" + }, + "nationality": { + "type": "string", + "description": "Nationality as ISO 3166-1 alpha-3. Required by some services." + }, + "gender": { + "type": "string", + "enum": [ + "M", + "F", + "X" + ], + "description": "Gender: `M`, `F`, or `X` (other/unknown). Required for Argentina's RENAPER validation (`arg_renaper`)." + }, + "address": { + "description": "Residential address. Prefer the structured object `{\"street_1\":\"123 Main St\",\"street_2\":\"Apt 4B\",\"city\":\"Springfield\",\"region\":\"IL\",\"postal_code\":\"62701\",\"country\":\"US\"}`; a complete single-line string is still accepted. Required by address-verification services; when a service defines address requirements, missing parts return `400` with `address_fields_required_by`.", + "oneOf": [ + { + "type": "object", + "properties": { + "street_1": { + "type": "string" + }, + "street_2": { + "type": "string" + }, + "city": { + "type": "string" + }, + "region": { + "type": "string" + }, + "postal_code": { + "type": "string" + }, + "country": { + "type": "string" + } + } + }, + { + "type": "string", + "description": "Complete single-line address (legacy)." + } + ] + }, + "address_element_1": { + "type": "string", + "description": "Street address including street number and type. If omitted, derived from `address`." + }, + "address_element_2": { + "type": "string", + "description": "Apartment, unit, building, floor, or extra address line. Only send when you have it explicitly." + }, + "address_element_3": { + "type": "string", + "description": "City, suburb, district, locality, or neighborhood. If omitted, derived from `address`." + }, + "address_element_4": { + "type": "string", + "description": "State, province, region, or town. If omitted, derived from `address`." + }, + "address_element_5": { + "type": "string", + "description": "Postcode or postal code. `postal_code` is accepted as an alias." + }, + "postal_code": { + "type": "string", + "description": "Postal code for address-based services (alias of `address_element_5`)." + }, + "driver_license_number": { + "type": "string", + "description": "Driver licence number for government document-verification services (AUS, NZL, IND)." + }, + "driver_license_card_number": { + "type": "string", + "description": "Physical driver licence card number, where required separately from the licence number (AUS)." + }, + "driver_license_state": { + "type": "string", + "description": "Driver licence state or territory of issue (AUS)." + }, + "driver_license_version": { + "type": "string", + "description": "Driver licence version code (NZL)." + }, + "passport_number": { + "type": "string", + "description": "Passport number for government passport-verification services (AUS, NZL, IND, CHN)." + }, + "passport_expiration_date": { + "type": "string", + "format": "date", + "description": "Passport expiry date, `YYYY-MM-DD`, for passport-verification services." + }, + "passport_file_number": { + "type": "string", + "description": "Passport file number (IND)." + }, + "passport_issue_country": { + "type": "string", + "description": "Issuing country of the passport (ISO 3166-1 alpha-3) for passport-verification services." + }, + "medicare_card_number": { + "type": "string", + "description": "Medicare card number (AUS)." + }, + "immi_card_number": { + "type": "string", + "description": "Australian ImmiCard number." + }, + "immi_card_expiry_date": { + "type": "string", + "format": "date", + "description": "Australian ImmiCard expiry date." + }, + "citizenship_certificate_number": { + "type": "string", + "description": "Citizenship certificate number (AUS)." + }, + "birth_registration_number": { + "type": "string", + "description": "Birth-certificate registration number (AUS)." + }, + "birth_registration_date": { + "type": "string", + "format": "date", + "description": "Birth-certificate registration date (AUS)." + }, + "birth_registration_state": { + "type": "string", + "description": "Birth-certificate registration state (AUS)." + }, + "marriage_certificate_number": { + "type": "string", + "description": "Marriage certificate number (AUS)." + }, + "change_of_name_certificate_number": { + "type": "string", + "description": "Change-of-name certificate number (AUS)." + }, + "first_partner_name": { + "type": "string", + "description": "First name of the partner on a marriage certificate (AUS)." + }, + "last_partner_name": { + "type": "string", + "description": "Last name of the partner on a marriage certificate (AUS)." + }, + "voter_id": { + "type": "string", + "description": "Voter registration number (IND EPIC, IRL)." + }, + "epic_card": { + "type": "string", + "description": "India EPIC voter card number." + }, + "pan": { + "type": "string", + "description": "India PAN (Permanent Account Number)." + }, + "national_id": { + "type": "string", + "description": "National ID number (CHN, MYS, KEN, KHM, NGA, ZAF, …)." + }, + "cic": { + "type": "string", + "description": "Mexican INE/IFE Código de Identificación de Credencial (CIC), 9 digits. Used by the MEX INE credential-validity service (`mex_ine_vigencia`)." + }, + "identificador_ciudadano": { + "type": "string", + "description": "Mexican INE Identificador del Ciudadano (9 digits). Used with `cic` for modern INE models (E/F/G/H) in `mex_ine_vigencia`." + }, + "ocr": { + "type": "string", + "description": "Mexican INE OCR number (13 digits, back of card). Used with `cic` for Model D in `mex_ine_vigencia`." + }, + "voter_number": { + "type": "string", + "description": "Mexican INE Clave de Elector (18 chars). Used with `emission_number` for legacy IFE models (A/B/C) in `mex_ine_vigencia`." + }, + "emission_number": { + "type": "string", + "description": "Mexican INE Número de Emisión. Used with `voter_number` for legacy IFE models (A/B/C) in `mex_ine_vigencia`." + }, + "bvn": { + "type": "string", + "description": "Nigerian Bank Verification Number." + }, + "bank_card_number": { + "type": "string", + "description": "Bank card number (CHN bank-card verification)." + }, + "ssn": { + "type": "string", + "description": "Social Security Number (USA)." + }, + "phone": { + "type": "string", + "description": "Phone number for phone-verification services." + }, + "landline": { + "type": "string", + "description": "Landline number for phone-verification services." + }, + "email": { + "type": "string", + "format": "email", + "description": "Email address for identity-verification services." + }, + "country_of_residence": { + "type": "string", + "description": "ISO 3166-1 alpha-2 country of residence. Used by global identity-enrichment services (e.g. `glb_identity_enrichment`) to focus the lookup.", + "example": "US" + }, + "partial_match_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "NO_ACTION", + "description": "What a `partial_match` does to `database_validation.status`. Defaults to `NO_ACTION` (status stays `Approved`, with a warning)." + }, + "no_match_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What a `no_match` does to `database_validation.status`. Defaults to `DECLINE`." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "Persist the validation as a session (console visibility, decision endpoint, `status.updated` webhook). Also changes the `validations` response shape: per-service objects when `true` (default), a merged field map when `false`." + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for the validated user; echoed back and stored with the session." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON stored with the request and echoed back." + }, + "selfie": { + "type": "string", + "format": "binary", + "description": "Selfie image (`jpg`, `jpeg`, `png`, `webp`; max 2 MB — images are compressed to ~1.5 MB before upload to the registry) for biometric validation services, e.g. Argentina RENAPER (`arg_renaper`) and Panama SIB (`pan_cedula_sib`, `pan_cedula_sib_plus`). Required whenever a selected service lists it as a required field. Only available via `multipart/form-data`." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/database-validation/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"issuing_state\": \"BRA\",\n \"services\": [\"bra_cpf\"],\n \"identification_number\": \"12345678900\",\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"date_of_birth\": \"1980-01-01\",\n \"vendor_data\": \"user-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os, requests\n\nresp = requests.post(\n \"https://verification.didit.me/v3/database-validation/\",\n headers={\"x-api-key\": os.environ[\"DIDIT_API_KEY\"]},\n json={\n \"issuing_state\": \"BRA\",\n \"services\": [\"bra_cpf\"],\n \"identification_number\": \"12345678900\",\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"date_of_birth\": \"1980-01-01\",\n \"vendor_data\": \"user-1234\",\n },\n timeout=45,\n)\nresp.raise_for_status()\ndv = resp.json()[\"database_validation\"]\nprint(dv[\"status\"], dv[\"match_type\"]) # e.g. Approved full_match\n\n# Biometric services need a selfie via multipart/form-data:\n# requests.post(..., data={\"issuing_state\": \"ARG\", \"services\": '[\"arg_renaper\"]',\n# \"identification_number\": \"12345678\", \"gender\": \"M\", ...},\n# files={\"selfie\": open(\"selfie.jpg\", \"rb\")})" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const res = await fetch('https://verification.didit.me/v3/database-validation/', {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n issuing_state: 'BRA',\n services: ['bra_cpf'],\n identification_number: '12345678900',\n first_name: 'John',\n last_name: 'Doe',\n date_of_birth: '1980-01-01',\n vendor_data: 'user-1234',\n }),\n});\nconst data = await res.json();\nconsole.log(data.database_validation.status, data.database_validation.match_type);" + } + ], + "responses": { + "200": { + "description": "Validation completed — at least one selected service returned a usable result. Inspect `database_validation.match_type` for the aggregate outcome, `validations` for the per-service field comparisons and `outcome_code`s, and `errors` (when present) for services that failed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "database_validation": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "description": "`Declined` when the overall `match_type` triggers a `DECLINE` action (`no_match_action` defaults to `DECLINE`, `partial_match_action` to `NO_ACTION`); `In Review` when no service produced a usable comparison, for example when the only selected service came back `REGISTRY_UNAVAILABLE` or `INCONCLUSIVE`; `Approved` otherwise. A request that selected several services can be `Approved` on one service's match while another was unavailable, so check `errors` and the per-service `outcome_code`s too." + }, + "issuing_state": { + "type": "string", + "description": "Echo of the ISO 3166-1 alpha-3 country validated against.", + "example": "BRA" + }, + "validation_type": { + "type": "string", + "enum": [ + "one_by_one", + "two_by_two" + ], + "description": "Derived from the results: `two_by_two` when ≥2 distinct services produced a full match (on the identification number, on full name + date of birth together, or on the address without contradictions); `one_by_one` otherwise. (The request field of the same name is deprecated and ignored.)" + }, + "screened_data": { + "type": "object", + "description": "Echo of the input fields that were sent to the selected services (dates normalized to `YYYY-MM-DD`; a `selfie`, when sent, is returned as a pre-signed URL; `consent_obtained: true` is added when consent was given)." + }, + "match_type": { + "type": "string", + "nullable": true, + "enum": [ + "full_match", + "partial_match", + "no_match" + ], + "description": "Overall result aggregated across services with field-level comparisons: any `full_match` → `full_match`; else any `partial_match` → `partial_match`; else `no_match`. Outcome-only services, such as biometric criminal screening, can return `null`; inspect each item in `validations` for its `outcome_code`." + }, + "validations": { + "description": "Shape depends on `save_api_request`. **`true` (default):** an array of per-service objects (`service_id`, `service_name`, `validation`, `outcome_code`, `outcome_detail`, `source_data`). **`false`:** a single flat object merging the field-level results of all services (`{\"full_name\": \"full_match\", ...}`).", + "oneOf": [ + { + "type": "array", + "items": { + "type": "object", + "description": "Per-service validation result (returned when `save_api_request=true`, the default).", + "properties": { + "service_id": { + "type": "string", + "description": "Catalog id of the service that produced this result (e.g. `bra_cpf`).", + "example": "bra_cpf" + }, + "service_name": { + "type": "string", + "description": "Human-readable catalog name of the service.", + "example": "Brazil - CPF status check" + }, + "validation": { + "type": "object", + "description": "Field-by-field comparison between your input and the registry record. Keys are canonical field names (`full_name`, `date_of_birth`, `identification_number`, `address`, …); values are `full_match`, `partial_match`, or `no_match`.", + "additionalProperties": { + "type": "string", + "enum": [ + "full_match", + "partial_match", + "no_match" + ] + } + }, + "outcome_code": { + "type": "string", + "enum": [ + "MATCH", + "PARTIAL_MATCH", + "NO_MATCH", + "DOCUMENT_NOT_FOUND", + "INVALID_DOCUMENT_FORMAT", + "INVALID_INPUT", + "MINOR_BLOCKED", + "DECEASED", + "BIOMETRIC_NO_MATCH", + "BIOMETRIC_IMAGE_UNUSABLE", + "INCONCLUSIVE", + "DOCUMENT_SUPERSEDED", + "REGISTRY_UNAVAILABLE", + "REGISTRY_ERROR" + ], + "description": "Vendor-neutral outcome of the registry lookup. `MATCH`/`PARTIAL_MATCH`/`NO_MATCH` describe the comparison; `DOCUMENT_NOT_FOUND` means the identifier is not in the registry; `INCONCLUSIVE` means the registry could not confirm either way (routed to review — NOT a no-match); `BIOMETRIC_NO_MATCH` is a definitive face mismatch while `BIOMETRIC_IMAGE_UNUSABLE` is a technical image problem (retake the selfie); `DECEASED`/`MINOR_BLOCKED` are registry-policy outcomes; `DOCUMENT_SUPERSEDED` means the identity matched but the edition of the document presented is not the one on file (a newer edition has been issued); `INVALID_DOCUMENT_FORMAT`/`INVALID_INPUT` mean the registry itself rejected the data it was sent; `REGISTRY_UNAVAILABLE`/`REGISTRY_ERROR` indicate upstream failures." + }, + "outcome_detail": { + "type": "string", + "nullable": true, + "description": "Raw upstream status detail backing the outcome code (e.g. an HTTP or registry status code)." + }, + "source_data": { + "type": "object", + "nullable": true, + "description": "Cleaned, normalized view of the registry record using canonical field names (`identification_number`, `first_name`, `last_name`, `date_of_birth`, plus registry-specific flags). Image fields, when present, are returned as pre-signed URLs." + } + } + } + }, + { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "full_match", + "partial_match", + "no_match" + ] + }, + "description": "Merged field-level results (stateless `save_api_request=false` shape)." + } + ] + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature that raised the warning." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk identifier." + }, + "additional_data": { + "type": "object", + "nullable": true, + "description": "Extra context for the warning, when available." + }, + "log_type": { + "type": "string", + "description": "`warning`, `information`, or `error` — how the risk affected the outcome." + }, + "short_description": { + "type": "string", + "description": "One-line human-readable summary." + }, + "long_description": { + "type": "string", + "description": "Full human-readable explanation." + } + } + }, + "description": "`DATABASE_VALIDATION_NO_MATCH` or `DATABASE_VALIDATION_PARTIAL_MATCH` when applicable; empty on a full match." + }, + "errors": { + "type": "array", + "description": "Present only when some (but not all) selected services failed. Each entry carries `service_id`, `code`, `message`, and `retryable`. A service reported here is absent from `validations` and `services_used`, and is not billed. This array is the only place it appears, so inspect it even when `status` is `Approved`.", + "items": { + "type": "object", + "properties": { + "service_id": { + "type": "string" + }, + "code": { + "type": "string", + "example": "empty_provider_response" + }, + "message": { + "type": "string" + }, + "retryable": { + "type": "boolean", + "description": "`true` when the service produced no answer and retrying it later can succeed; `false` when the source refused the data it was sent and the input must be corrected first.", + "example": true + } + } + } + }, + "services_used": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Catalog ids of the services that returned a billable result. **Only present when the request explicitly sent `services`.**" + }, + "match_score": { + "type": "number", + "description": "Fraction (0.0–1.0) of attempted services that produced a full match (on the identification number, on full name + date of birth together, or on the address without contradictions). **Only present when the request explicitly sent `services`.**" + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Full match (Approved)": { + "summary": "Default save_api_request=true — per-service `validations` objects", + "value": { + "request_id": "7f9a1c0e-1f2b-4f6e-9d3a-2b1c0e7f9a1c", + "database_validation": { + "status": "Approved", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "tax_number": "12345678900", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-01-01" + }, + "match_type": "full_match", + "validations": [ + { + "validation": { + "full_name": "full_match", + "date_of_birth": "full_match", + "identification_number": "full_match" + }, + "outcome_code": "MATCH", + "outcome_detail": "200", + "service_id": "bra_cpf", + "service_name": "Brazil - CPF status check", + "source_data": { + "identification_number": "12345678900", + "first_name": "JOHN", + "last_name": "DOE", + "date_of_birth": "1980-01-01", + "lgpd_minor": false, + "minor_under_16": false, + "minor_under_18": false + } + } + ], + "warnings": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-11T10:30:00.000000+00:00" + } + }, + "Explicit services (extended shape)": { + "summary": "Request sent `services` — response adds `services_used` and `match_score`", + "value": { + "request_id": "b3e7c1a2-9d4f-4f0b-8a6c-5e2d1f0a9b3e", + "database_validation": { + "status": "Approved", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "tax_number": "12345678900", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-01-01" + }, + "match_type": "full_match", + "validations": [ + { + "validation": { + "full_name": "full_match", + "date_of_birth": "full_match", + "identification_number": "full_match" + }, + "outcome_code": "MATCH", + "outcome_detail": "200", + "service_id": "bra_cpf", + "service_name": "Brazil - CPF status check", + "source_data": { + "identification_number": "12345678900", + "first_name": "JOHN", + "last_name": "DOE", + "date_of_birth": "1980-01-01", + "lgpd_minor": false, + "minor_under_16": false, + "minor_under_18": false + } + } + ], + "warnings": [], + "services_used": [ + "bra_cpf" + ], + "match_score": 1.0 + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-11T10:30:00.000000+00:00" + } + }, + "No match (Declined)": { + "summary": "Registry record does not match — default `no_match_action=DECLINE`", + "value": { + "request_id": "0a1b2c3d-4e5f-6789-abcd-ef0123456789", + "database_validation": { + "status": "Declined", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "tax_number": "12345678900", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-01-01" + }, + "match_type": "no_match", + "validations": [ + { + "validation": { + "full_name": "no_match", + "date_of_birth": "no_match", + "identification_number": "no_match" + }, + "outcome_code": "NO_MATCH", + "outcome_detail": "200", + "service_id": "bra_cpf", + "service_name": "Brazil - CPF status check", + "source_data": { + "identification_number": "00987654321", + "first_name": "TOTALLY", + "last_name": "DIFFERENT NAME", + "date_of_birth": "1999-01-01", + "lgpd_minor": false, + "minor_under_16": false, + "minor_under_18": false + } + } + ], + "warnings": [ + { + "feature": "DATABASE_VALIDATION", + "risk": "DATABASE_VALIDATION_NO_MATCH", + "additional_data": null, + "log_type": "error", + "short_description": "Database validation no match", + "long_description": "The system identified a no match in the database validation, requiring further investigation." + } + ] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-11T10:31:00.000000+00:00" + } + }, + "Stateless (save_api_request=false)": { + "summary": "`validations` collapses to a merged field map; nothing is stored", + "value": { + "request_id": "c4d5e6f7-8901-2345-6789-abcdef012345", + "database_validation": { + "status": "Approved", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "tax_number": "12345678900", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-01-01" + }, + "match_type": "full_match", + "validations": { + "full_name": "full_match", + "date_of_birth": "full_match", + "identification_number": "full_match" + }, + "warnings": [] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-11T10:32:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error - nothing is billed. Preflight field errors occur before any external call. If every unanswered attempt explicitly refuses the input, the response instead includes `validation_errors` with `code: provider_rejected_input` and `retryable: false`; correct the input before retrying. Field-level problems use DRF's standard envelope; catalog problems return a `services` array (plus `requires_onboarding` or `services_requiring_consent` when relevant); missing-field errors include `fields_required_by` mapping each missing field to the services that demand it; services that need at least one extra identifier (e.g. `usa_states_credit_bureau`, which cannot verify a name alone) return `any_field_required_by` mapping each service to the fields of which at least one must be present; format problems checked per service return `invalid_fields_by_service`.", + "content": { + "application/json": { + "examples": { + "Invalid issuing_state": { + "summary": "Country not supported — message lists every valid alpha-3 code", + "value": { + "error": [ + "Invalid issuing state. Valid options are: ARE, ARG, AUS, AUT, BEL, BGD, BOL, BRA, CAN, CHE, CHL, CHN, CIV, COL, CRI, CZE, DEU, DNK, DOM, ECU, ESP, FIN, FRA, GBR, GHA, GLB, GRC, GTM, HKG, HND, IDN, IND, IRL, ITA, KEN, KHM, MAR, MEX, MYS, NGA, NLD, NOR, NZL, OMN, PAN, PER, PHL, POL, PRT, PRY, QAT, SGP, SLV, SVK, SWE, THA, UGA, URY, USA, VEN, ZAF, ZMB, ZWE" + ] + } + }, + "Missing required fields": { + "summary": "Union of required fields across selected services, with the universal-field hint", + "value": { + "error": [ + "Missing required fields: tax_number (or use 'identification_number' which maps to 'tax_number' for BRA)" + ], + "fields_required_by": { + "tax_number": [ + "bra_cpf" + ] + } + } + }, + "Unknown service id": { + "summary": "`services` contains an id that is not in the catalog", + "value": { + "services": [ + "Unknown service_id(s): ['bogus_service']" + ] + } + }, + "Service not live for country": { + "summary": "`services` names a service from another country (or a stub entry)", + "value": { + "services": [ + "Services not currently live for PER: ['bra_cpf']. Available services: ['per_dni', 'per_residential', 'per_tax_registration']" + ] + } + }, + "Service requires onboarding": { + "summary": "Service exists but is not activated for your organization yet", + "value": { + "services": [ + "Services require onboarding before they can be enabled for AUS: ['aus_australia_driver_licence']. Contact Didit support to activate them." + ], + "requires_onboarding": [ + "aus_australia_driver_licence" + ] + } + }, + "Consent missing": { + "summary": "A selected service is flagged requires_consent=true and `consent` was not `true`", + "value": { + "consent": [ + "Explicit end-user consent is required for these database validation services: ['chn_national_id']. Send `consent=true`." + ], + "services_requiring_consent": [ + "chn_national_id" + ] + } + }, + "Country format rule violation": { + "summary": "Identifier fails the country's format rule (length/digits/regex)", + "value": { + "tax_number": [ + "Invalid tax number for BRA: Brazilian CPF (exactly 11 digits). Must be exactly 11 characters long (got 10)." + ] + } + }, + "Service-specific format violation": { + "summary": "A field fails the selected service's own format requirements", + "value": { + "error": [ + "One or more fields do not match the format required by the selected service." + ], + "invalid_fields_by_service": { + "chn_national_id": [ + "national_id_format" + ] + } + } + }, + "Input refused by every attempt": { + "summary": "Correct the input before retrying", + "value": { + "error": "The source refused to run this query with the supplied input.", + "validation_errors": [ + { + "service_id": "bra_cpf", + "code": "provider_rejected_input", + "message": "The source refused to run this query with the supplied input.", + "retryable": false + } + ] + } + } + }, + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "error": { + "description": "Request-level explanation or preflight field errors.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "validation_errors": { + "type": "array", + "description": "Present when every unanswered attempt refused the input after preflight.", + "items": { + "type": "object", + "properties": { + "service_id": { + "type": "string" + }, + "code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retryable": { + "type": "boolean", + "description": "Whether retrying later can resolve this service failure. False means correct the supplied input first." + } + } + } + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, malformed, revoked, or belongs to another environment — and also when the calling organization's balance cannot cover the call. Authentication failures return `403` with `{\"detail\": ...}`; this API never returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. All POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "502": { + "description": "No selected service returned a usable result. Inspect each validation_errors entry before retrying; HTTP 502 alone does not establish a transient failure. An empty_provider_response with retryable: true indicates unavailable or unexplained results, including a refused primary followed by an unavailable fallback. Legacy provider_invalid_input and provider_validation_error entries instead require corrected input and may omit retryable. Nothing is billed. When save_api_request=true, the failed attempt is recorded as a session with status Not Finished. Partial failures return 200 with database_validation.errors.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "validation_errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "service_id": { + "type": "string" + }, + "code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retryable": { + "type": "boolean", + "description": "Whether retrying later can resolve this service failure. False means correct the supplied input first." + } + } + } + } + } + }, + "examples": { + "Provider returned no result": { + "summary": "Every selected service failed", + "value": { + "error": "Database validation could not be performed. The external validation service did not return a result. Please try again later.", + "validation_errors": [ + { + "service_id": "bra_cpf", + "code": "empty_provider_response", + "message": "The provider did not return a database validation result.", + "retryable": true + } + ] + } + } + } + } + } + } + } + } + }, + "/v3/phone/send/": { + "post": { + "summary": "Send Phone Code", + "description": "Send a one-time passcode (OTP) to a phone number over WhatsApp, SMS, or another supported channel, then verify it with [`POST /v3/phone/check/`](/standalone-apis/phone-check).\n\n**How send and check pair up.** Verification state is keyed by your application plus the E.164 `phone_number` — the check call does not take `request_id`. Each new send creates a pending verification that lives for **5 minutes**: call the check with the same number and the code the user received before the window closes. Calling send again for the same number while a verification is pending re-sends a code for that same verification (`status: \"Retry\"`, same `request_id`). At most one retry is attached this way (two sends total); a further send starts a fresh verification with a new `request_id`. The 5-minute window is measured from the first send and is not extended by retries.\n\n**Delivery channels.** `options.preferred_channel` selects the channel (`whatsapp` by default; `sms`, `telegram`, `voice`, `rcs`, `viber`, and `zalo` are also supported). When the preferred channel is unavailable for the destination country or number, delivery automatically falls back to SMS — the send still reports `Success`, and the channel actually used is reported by the check response (`phone.verification_method` and the `lifecycle` events).\n\n**Branding.** SMS codes carry your application's SMS sender name, set in the Business Console, at no extra cost. Pass `options.sms_sender_name` to sign one code with another name, or `options.use_white_label_customization: true` to fall back to your white-label public name when no sender name is set. WhatsApp codes use a fixed message; connect your own WhatsApp Business number in the Business Console to send them from your business name and number, billed at the Didit fee only.\n\n**Send statuses.** `Success` — OTP sent for a new verification; `Retry` — OTP re-sent for the pending verification; `Blocked` — the anti-fraud layer refused to send (see `reason`, e.g. `repeated_attempts`, `suspicious`, `spam`). A `Blocked` send immediately finalizes the verification as `Declined` with a high-risk warning, so a follow-up check returns `Expired or Not Found`.\n\n**Billing.** The price depends on the destination country and the channel that actually delivers the message — see [Phone Verification Pricing](/getting-started/phone-verification-pricing). Only `Blocked` sends are guaranteed free: any other unbilled send attempt is charged at verification finalization or expiration, even if delivery was never confirmed or the delivery provider reported the message undeliverable. Checks are always free. As an anti-abuse measure, phone verification is disabled until your organization's first top-up (`403`).\n\n**Session persistence.** Every new verification is persisted as an API-type session: `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/`, the verification appears in the Business Console, and `status.updated` webhooks fire as it progresses.\n\n**Sandbox.** Sandbox API keys skip delivery and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Success` payload with a random `request_id`; no message is sent and nothing is persisted. Use code `123456` on the sandbox check.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limits.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints, plus an anti-abuse cap of 4 send attempts per phone number per hour; exceeding either returns `429`.", + "operationId": "post_v3phone_send", + "x-didit-sdk": { + "resource": "phone", + "method": "send_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/phone/send/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"phone_number\": \"+14155552671\",\n \"options\": {\n \"code_size\": 6,\n \"preferred_channel\": \"whatsapp\",\n \"locale\": \"en-US\"\n },\n \"vendor_data\": \"user-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os, requests\n\nresp = requests.post(\n \"https://verification.didit.me/v3/phone/send/\",\n headers={\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Content-Type\": \"application/json\",\n },\n json={\n \"phone_number\": \"+14155552671\",\n \"options\": {\n \"code_size\": 6,\n \"preferred_channel\": \"whatsapp\",\n \"locale\": \"en-US\",\n },\n \"vendor_data\": \"user-1234\",\n },\n timeout=15,\n)\nresp.raise_for_status()\nprint(resp.json()) # {request_id, status, reason, vendor_data, metadata}\n# Then verify with POST /v3/phone/check/ using the same phone_number" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const res = await fetch('https://verification.didit.me/v3/phone/send/', {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n phone_number: '+14155552671',\n options: { code_size: 6, preferred_channel: 'whatsapp', locale: 'en-US' },\n vendor_data: 'user-1234',\n }),\n});\nif (!res.ok) throw new Error(`Phone send failed: ${res.status}`);\nconst data = await res.json();\nconsole.log(data); // { request_id, status, reason, vendor_data, metadata }\n// Then verify with POST /v3/phone/check/ using the same phone_number" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "phone_number": { + "type": "string", + "maxLength": 20, + "description": "Recipient phone number in E.164 format — leading `+` and country code (e.g., `+14155552671`). Numbers are validated and normalized to E.164; unparseable or invalid numbers return `400` with a `phone_number` field error.", + "example": "+14155552671" + }, + "options": { + "type": "object", + "description": "OTP delivery options. All fields are optional.", + "properties": { + "code_size": { + "type": "integer", + "minimum": 4, + "maximum": 8, + "default": 6, + "description": "Number of digits in the OTP." + }, + "locale": { + "type": "string", + "maxLength": 5, + "pattern": "^[a-z]{2,3}(-[A-Z]{2,3})?$", + "description": "BCP-47 locale used to localize the verification message (e.g., `en-US`, `fr`, `es`). Invalid formats return `400`.", + "example": "en-US" + }, + "preferred_channel": { + "type": "string", + "enum": [ + "whatsapp", + "sms", + "telegram", + "voice", + "rcs", + "viber", + "zalo" + ], + "default": "whatsapp", + "description": "Preferred delivery channel. If the channel is unavailable for the destination country or number, the message automatically falls back to SMS — the send still reports `Success`." + }, + "use_white_label_customization": { + "type": "boolean", + "default": false, + "description": "When `true`, the send is white-labelled: if your application has no SMS sender name, the SMS names your white-label public name instead (\"123456 is your verification code for Acme Bank\"). Your SMS sender name, when set, is used with or without this flag. Applies to SMS messages, including an SMS fallback from another channel; WhatsApp codes are sent with a fixed message and are not branded. Defaults to `false`." + }, + "sms_sender_name": { + "type": "string", + "nullable": true, + "maxLength": 30, + "example": "Acme Bank", + "description": "The name this code's SMS carries (\"123456 is your verification code for Acme Bank\"), instead of your application's SMS sender name. Free. 1 to 30 characters: letters, digits, spaces and ' & - ! only, not only digits, with single spaces between words; any other value returns `400` and no code is sent. Omitted, `null` or blank, your application's SMS sender name is used. Each application can start using up to two new names per day (UTC); past that, the code is sent with your application's name. Applies to SMS messages, including an SMS fallback from another channel; WhatsApp codes are not branded.", + "pattern": "^$|^(?![\\d ]+$)[\\p{L}\\p{N}'&!-]+( [\\p{L}\\p{N}'&!-]+)*$" + } + } + }, + "signals": { + "type": "object", + "description": "Optional device and network signals about the end user, forwarded to the anti-fraud layer to improve detection of abusive or automated traffic. All fields are optional.", + "properties": { + "ip": { + "type": "string", + "description": "IP address of the end user's device. IPv4 or IPv6.", + "example": "192.0.2.1" + }, + "device_id": { + "type": "string", + "maxLength": 255, + "description": "Unique identifier of the end user's device. Android: `ANDROID_ID`; iOS: `identifierForVendor`.", + "example": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2" + }, + "device_platform": { + "type": "string", + "enum": [ + "android", + "ios", + "ipados", + "tvos", + "web" + ], + "description": "Platform of the end user's device." + }, + "device_model": { + "type": "string", + "maxLength": 255, + "description": "Model of the end user's device.", + "example": "iPhone17,2" + }, + "os_version": { + "type": "string", + "maxLength": 64, + "description": "Operating-system version of the end user's device.", + "example": "18.0.1" + }, + "app_version": { + "type": "string", + "maxLength": 64, + "description": "Version of your application.", + "example": "1.2.34" + }, + "user_agent": { + "type": "string", + "maxLength": 512, + "description": "User agent of the end user's browser or app.", + "example": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1" + } + } + }, + "vendor_data": { + "type": "string", + "description": "Optional caller-controlled identifier (your internal user id, an email, a UUID, etc.) persisted on the session and echoed back in the send response, the matching check response, webhooks, and the Business Console. Use it to correlate Didit's `request_id` with your user record." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Optional free-form JSON object persisted on the session and echoed back in the send response, the matching check response, webhooks, and the Business Console." + } + }, + "required": [ + "phone_number" + ] + }, + "examples": { + "WhatsApp (default)": { + "summary": "6-digit OTP via WhatsApp, falling back to SMS automatically", + "value": { + "phone_number": "+14155552671", + "options": { + "preferred_channel": "whatsapp", + "locale": "en-US" + }, + "vendor_data": "user-1234" + } + }, + "SMS with custom code length": { + "summary": "Force SMS delivery with a 4-digit code", + "value": { + "phone_number": "+34699999999", + "options": { + "preferred_channel": "sms", + "code_size": 4, + "locale": "es" + } + } + }, + "With fraud signals": { + "summary": "Forward device signals to strengthen anti-fraud screening", + "value": { + "phone_number": "+14155552671", + "signals": { + "ip": "192.0.2.1", + "device_id": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2", + "device_platform": "ios", + "device_model": "iPhone17,2", + "os_version": "18.0.1", + "app_version": "1.2.34" + }, + "vendor_data": "user-1234" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Send acknowledged. Inspect `status`: `Success` and `Retry` mean a code is on its way; `Blocked` means the anti-fraud layer refused and the verification is already finalized as `Declined`. `request_id` is the persisted session id (same id on a `Retry`).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PhoneVerificationSendResponse" + }, + "examples": { + "Success": { + "summary": "OTP sent for a new verification", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Success", + "reason": null, + "vendor_data": "user-1234", + "metadata": null + } + }, + "Retry": { + "summary": "Second send for the same number inside the 5-minute window — same request_id", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Retry", + "reason": null, + "vendor_data": "user-1234", + "metadata": null + } + }, + "Blocked": { + "summary": "Anti-fraud refusal — verification finalized as Declined, nothing billed", + "value": { + "request_id": "0d8af1f4-0c43-46d7-b75c-6e4e22c0a7b3", + "status": "Blocked", + "reason": "repeated_attempts", + "vendor_data": "user-1234", + "metadata": null + } + } + } + } + } + }, + "400": { + "description": "Validation error. Malformed input returns DRF's field-error envelope (one array of messages per offending field, nested under `options` for option errors). A number that parses but is rejected at send time (unreachable, or an ineligible line type such as a short code) returns a `{\"detail\": ...}` envelope instead.", + "content": { + "application/json": { + "examples": { + "Invalid phone number (field error)": { + "summary": "Number is not valid E.164", + "value": { + "phone_number": [ + "Invalid phone number provided." + ] + } + }, + "Missing phone number": { + "summary": "`phone_number` not included in the body", + "value": { + "phone_number": [ + "This field is required." + ] + } + }, + "Invalid options": { + "summary": "Out-of-range `code_size`, malformed `locale`, unknown `preferred_channel`", + "value": { + "options": { + "code_size": [ + "Ensure this value is less than or equal to 8." + ], + "locale": [ + "Ensure this field has no more than 5 characters." + ], + "preferred_channel": [ + "\"carrier_pigeon\" is not a valid choice." + ] + } + } + }, + "Number rejected at send time": { + "summary": "Valid E.164 shape but the number cannot receive an OTP", + "value": { + "detail": "Invalid phone number provided." + } + }, + "Ineligible line type": { + "summary": "Line type cannot receive an OTP (e.g., short code)", + "value": { + "detail": "Invalid phone line type provided." + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, malformed, revoked, or belongs to another environment (`{\"detail\": ...}`) — this API never returns `401`. Also returned before any send when the organization has never topped up (anti-abuse gate) or when its balance cannot cover the destination's send price (`{\"error\": ...}`).", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Top-up required": { + "summary": "Phone verification stays disabled until the organization's first top-up", + "value": { + "detail": "To protect against abuse, phone verification is disabled until your first top-up." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the send price", + "value": { + "error": "You don't have enough credits to perform this request." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. All POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key. Additionally, an anti-abuse cap of 4 verification attempts per phone number per hour applies upstream. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers. Note: the `X-RateLimit-*`/`Retry-After` headers accompany only the 300/min write-limit 429; the per-number cap 429 carries no rate-limit headers.", + "content": { + "application/json": { + "examples": { + "Per-number cap": { + "summary": "More than 4 verification attempts for the same number in the last hour", + "value": { + "detail": "Maximum verification attempts reached for this phone number. Only 4 authentication attempts are allowed per hour. Try again later or use a different number." + } + }, + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "502": { + "description": "The phone verification provider is temporarily unavailable - a 5xx response, timeout, or connection failure that survived the built-in retries. Safe to retry; nothing is billed and no attempt is consumed.", + "content": { + "application/json": { + "examples": { + "Provider outage": { + "summary": "The provider did not answer the send request", + "value": { + "detail": "Phone verification is temporarily unavailable. Please try again shortly.", + "code": "phone_provider_unavailable" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "Unexpected failure while creating the verification that is not a known provider outage. Safe to retry.", + "content": { + "application/json": { + "examples": { + "Provider error": { + "summary": "OTP delivery could not be initiated", + "value": { + "detail": "Error creating phone verification" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/phone/check/": { + "post": { + "summary": "Check Phone Code", + "description": "Verify the OTP delivered by [`POST /v3/phone/send/`](/standalone-apis/phone-send) and get the final verification result plus phone intelligence: carrier name and line type, virtual (VoIP) and disposable flags, and duplicate usage of the number across your sessions.\n\n**How the check finds the verification.** Matching is by your application plus the E.164 `phone_number` — `request_id` is not an input. The most recent pending verification created within the last **5 minutes** is checked. If there is none (never sent, already finalized, or older than 5 minutes) the endpoint returns `200` with `status: \"Expired or Not Found\"`.\n\n**Attempt budget.** Each verification allows **3 code attempts**. The first two wrong codes return `status: \"Failed\"` with the attempts remaining and `phone: null`; the third wrong code finalizes the verification as `Declined` with a `VERIFICATION_CODE_ATTEMPTS_EXCEEDED` warning and returns the full phone report.\n\n**Outcomes.** `Approved` — correct code and no declining risk. `Declined` — terminal: the code was correct but a declining risk matched (a `DECLINE` action below, a blocklisted or high-risk number), or the attempt budget was exhausted. `Failed` — wrong code, attempts remaining. `Expired or Not Found` — nothing to check. On `Approved`/`Declined` the `request_id` equals the send's `request_id` (the session id) and `phone` carries the full report (`carrier`, `is_virtual`, `is_disposable`, `warnings`, `lifecycle`, `matches`); on `Failed` and `Expired or Not Found` the `request_id` is a one-off random UUID.\n\n**Risk actions.** `duplicated_phone_number_action`, `disposable_number_action`, and `voip_number_action` decide what happens when the corresponding risk is detected on a correct code: `DECLINE` flips the final status to `Declined`; `NO_ACTION` (default) records the risk in `phone.warnings` without affecting the status.\n\n**Billing.** Checks are free — delivery is billed on the send side (see [Phone Verification Pricing](/getting-started/phone-verification-pricing)).\n\n**Session persistence.** A finalized check updates the session created by the send (visible in the Business Console, queryable via `GET /v3/session/{sessionId}/decision/`) and fires a `status.updated` webhook.\n\n**Sandbox.** Sandbox API keys skip all processing: any well-formed code (4–8 digits, `123456` included) returns a static `Approved` payload with a simplified flat `phone` object (`status`, `phone_number`, `country_code`, `carrier_name`, `line_type`, `flags`); malformed input still returns `400`. Nothing is persisted.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limits.** Shared write budget of 300 requests/min per API key, plus the upstream anti-abuse cap of 4 attempts per number per hour; exceeding either returns `429`.\n\n**Social footprint.** Set `enable_social` to `true` to add a `phone_social` block: the online platforms this number is registered on, with per-category counts and any profile details available. `profiles_registered: 0` means the platforms were checked and the number was found on none of them - a valid, billable answer. The block is absent when the add-on was not requested or the check could not be completed, and it is not billed in that case. Billing adds one `phone_social` unit per request on top of this endpoint's own price.", + "operationId": "post_v3phone_check", + "x-didit-sdk": { + "resource": "phone", + "method": "check_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/phone/check/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"phone_number\": \"+14155552671\",\n \"code\": \"123456\",\n \"voip_number_action\": \"DECLINE\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os, requests\n\nresp = requests.post(\n \"https://verification.didit.me/v3/phone/check/\",\n headers={\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Content-Type\": \"application/json\",\n },\n json={\n \"phone_number\": \"+14155552671\", # same number as the send call\n \"code\": \"123456\",\n \"voip_number_action\": \"DECLINE\",\n },\n timeout=15,\n)\nresp.raise_for_status()\nresult = resp.json()\nprint(result[\"status\"]) # Approved / Declined / Failed / Expired or Not Found\nif result[\"status\"] in (\"Approved\", \"Declined\"):\n print(result[\"phone\"][\"carrier\"], result[\"phone\"][\"is_virtual\"])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const res = await fetch('https://verification.didit.me/v3/phone/check/', {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n phone_number: '+14155552671', // same number as the send call\n code: '123456',\n voip_number_action: 'DECLINE',\n }),\n});\nconst data = await res.json();\nif (data.status === 'Approved') {\n // full phone report is in data.phone (carrier, is_virtual, matches, ...)\n}" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "phone_number": { + "type": "string", + "maxLength": 20, + "description": "The same phone number used in the matching [`POST /v3/phone/send/`](/standalone-apis/phone-send) call, in E.164 format. This is what links the check to the send.", + "example": "+14155552671" + }, + "code": { + "type": "string", + "minLength": 4, + "maxLength": 8, + "pattern": "^[0-9]{4,8}$", + "description": "The OTP the end user received. Must be 4–8 numeric digits — anything else returns `400` without consuming an attempt. A well-formed but wrong code returns `200` with `status: \"Failed\"` and consumes one of the 3 attempts.", + "example": "123456" + }, + "duplicated_phone_number_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the same number was already used by a different user (different `vendor_data`) of your application. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `phone.warnings` and fills `phone.matches`." + }, + "disposable_number_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the carrier lookup flags the number as a temporary/burner line (`phone.is_disposable`). `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `phone.warnings`." + }, + "voip_number_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the carrier lookup reports a virtual line type — `voip`, `isp`, or `vpn` (`phone.is_virtual`). `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `phone.warnings`." + }, + "enable_social": { + "type": "boolean", + "default": false, + "description": "When true, also look up the social footprint of the number - the messaging, social, e-commerce and professional platforms it is registered on. Billed as one `phone_social` unit per request in addition to this endpoint's own price.", + "example": true + }, + "phone_no_social_presence_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the social footprint check finds the number registered on none of the platforms it covers. Only NO_ACTION and DECLINE are accepted on this endpoint; REVIEW is available on the workflow configuration key of the same name." + } + }, + "required": [ + "phone_number", + "code" + ] + }, + "examples": { + "Default": { + "summary": "Record any risks but never auto-decline", + "value": { + "phone_number": "+14155552671", + "code": "123456" + } + }, + "Strict risk policy": { + "summary": "Auto-decline VoIP and disposable lines", + "value": { + "phone_number": "+14155552671", + "code": "123456", + "voip_number_action": "DECLINE", + "disposable_number_action": "DECLINE" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Check completed — wrong codes and missing verifications also return `200`; inspect `status`, not the HTTP code. `phone` is populated only on finalized outcomes (`Approved`/`Declined`), `null` on `Failed`, and absent on `Expired or Not Found`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PhoneVerificationCheckResponse" + }, + "examples": { + "Approved": { + "summary": "Correct code — full phone report with carrier, lifecycle, and matches", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Approved", + "message": "The verification code is correct.", + "phone": { + "status": "Approved", + "phone_number_prefix": "+1", + "phone_number": "4155552671", + "full_number": "+14155552671", + "country_code": "US", + "country_name": "United States", + "carrier": { + "name": "AT&T", + "type": "mobile" + }, + "is_disposable": false, + "is_virtual": false, + "verification_method": "whatsapp", + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [], + "lifecycle": [ + { + "type": "PHONE_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null, + "channel": "whatsapp", + "actual_channel": "whatsapp" + }, + "fee": 0.0509 + }, + { + "type": "PHONE_DELIVERY_DELIVERED", + "timestamp": "2026-06-12T01:23:43.118220+00:00", + "details": { + "channel": "whatsapp", + "status": "delivered" + }, + "fee": 0 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "PHONE_VERIFICATION_APPROVED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": null, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Failed (attempts remaining)": { + "summary": "Wrong code — the user can retry; request_id here is a one-off UUID", + "value": { + "request_id": "11111111-2222-3333-4444-555555555555", + "status": "Failed", + "message": "The verification code is incorrect. Attempts remaining: 2", + "phone": null, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:21.512340+00:00" + } + }, + "Declined (max attempts)": { + "summary": "Third wrong code — verification finalized as Declined", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Declined", + "message": "The verification code is incorrect. Maximum attempts reached.", + "phone": { + "status": "Declined", + "phone_number_prefix": "+1", + "phone_number": "4155552671", + "full_number": "+14155552671", + "country_code": "US", + "country_name": "United States", + "carrier": { + "name": "AT&T", + "type": "mobile" + }, + "is_disposable": false, + "is_virtual": false, + "verification_method": "whatsapp", + "verification_attempts": 1, + "verified_at": null, + "warnings": [ + { + "feature": "PHONE", + "risk": "VERIFICATION_CODE_ATTEMPTS_EXCEEDED", + "additional_data": null, + "log_type": "error", + "short_description": "Verification code attempts exceeded", + "long_description": "The system detected that the phone number has exceeded the maximum number of verification code attempts." + } + ], + "lifecycle": [ + { + "type": "PHONE_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null, + "channel": "whatsapp", + "actual_channel": "whatsapp" + }, + "fee": 0.0509 + }, + { + "type": "INVALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:01.118220+00:00", + "details": { + "code_tried": "111111", + "status": "Failed" + }, + "fee": 0 + }, + { + "type": "INVALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:21.512340+00:00", + "details": { + "code_tried": "222222", + "status": "Failed" + }, + "fee": 0 + }, + { + "type": "INVALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "333333", + "status": "Failed" + }, + "fee": 0 + }, + { + "type": "PHONE_VERIFICATION_DECLINED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": { + "reason": "VERIFICATION_CODE_ATTEMPTS_EXCEEDED" + }, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Declined (risk policy)": { + "summary": "Correct code, but the line is VoIP and voip_number_action=DECLINE", + "value": { + "request_id": "7f1c9d2e-44a1-4b6e-9a3e-5b8e6f1c2d3a", + "status": "Declined", + "message": "The verification code is correct.", + "phone": { + "status": "Declined", + "phone_number_prefix": "+1", + "phone_number": "5005550006", + "full_number": "+15005550006", + "country_code": "US", + "country_name": "United States", + "carrier": { + "name": "CLEC LLC", + "type": "voip" + }, + "is_disposable": false, + "is_virtual": true, + "verification_method": "sms", + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [ + { + "feature": "PHONE", + "risk": "VOIP_NUMBER_DETECTED", + "additional_data": null, + "log_type": "error", + "short_description": "VoIP number detected", + "long_description": "The system detected that the phone number is a VoIP number, which is not allowed." + } + ], + "lifecycle": [ + { + "type": "PHONE_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null, + "channel": "whatsapp", + "actual_channel": "sms" + }, + "fee": 0.0509 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "PHONE_VERIFICATION_DECLINED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": { + "reason": "VOIP_NUMBER_DETECTED" + }, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Expired or Not Found": { + "summary": "No pending verification for this number in the last 5 minutes", + "value": { + "request_id": "69cc35c6-280a-47a6-ab8c-4f32abc02ed0", + "status": "Expired or Not Found", + "message": "No pending phone verification found in the last 5 minutes.", + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-12T01:24:47.311323+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error — DRF's field-error envelope: one array of messages per offending field.", + "content": { + "application/json": { + "examples": { + "Invalid code format": { + "summary": "`code` is not 4–8 numeric digits", + "value": { + "code": [ + "Invalid code format. Enter 4 to 8 numeric digits." + ] + } + }, + "Invalid phone number": { + "summary": "`phone_number` is not valid E.164", + "value": { + "phone_number": [ + "Invalid phone number provided." + ] + } + }, + "Invalid action": { + "summary": "Risk actions accept only NO_ACTION or DECLINE", + "value": { + "voip_number_action": [ + "\"REVIEW\" is not a valid choice." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, malformed, revoked, or belongs to another environment — this API never returns `401`. Checks are free, so there is no credit check here.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. All POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key. Additionally, an anti-abuse cap of 4 verification attempts per phone number per hour applies upstream. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers. Note: the `X-RateLimit-*`/`Retry-After` headers accompany only the 300/min write-limit 429; the per-number cap 429 carries no rate-limit headers.", + "content": { + "application/json": { + "examples": { + "Per-number cap": { + "summary": "More than 4 verification attempts for the same number in the last hour", + "value": { + "detail": "Maximum verification attempts reached for this phone number. Only 4 authentication attempts are allowed per hour. Try again later or use a different number." + } + }, + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "502": { + "description": "The phone verification provider is temporarily unavailable - a 5xx response, timeout, or connection failure that survived the built-in retries. Safe to retry; the attempt budget is not consumed.", + "content": { + "application/json": { + "examples": { + "Provider outage": { + "summary": "The provider did not answer the check request", + "value": { + "detail": "Phone verification is temporarily unavailable. Please try again shortly.", + "code": "phone_provider_unavailable" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "Unexpected failure while verifying the code upstream that is not a known provider outage. Safe to retry; the attempt budget is only consumed by completed checks.", + "content": { + "application/json": { + "examples": { + "Provider error": { + "summary": "Code verification could not be completed", + "value": { + "detail": "Error checking phone verification" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/bank/account-check/": { + "post": { + "summary": "Check a bank account", + "description": "Check that a bank account belongs to the person or company you name, before you send money to it. No session and no sign-in: send the name and the account, read the result. It is the [account check](/core-technology/bank-verification/account-check) of the Bank Verification step, run from your backend.\n\n**What you send.** The account's `country` (ISO 3166-1 alpha-3), the `account` the way that country writes it, and the `holder` name: `full_name` for a person or `business_name` for a company. A business name checks a business account.\n\n**What you get back.** `result` is `match`, `close_match`, `no_match` or `cannot_verify`. On a `close_match`, `holder_name` is the name the bank holds the account under, when the bank returned it. `account` comes back masked to its last 4 characters.\n\n**Billing.** Each check the bank answers is billed once at the Account check price of the account's country, whatever the result: a `no_match`, an account that does not exist and a `cannot_verify` are billed too. A request answered with an error is never billed.\n\n**Every request is a new check.** There is no idempotency key and no endpoint to read a check back, so a retried request runs, and is billed, again. Store the response.\n\n**Sandbox.** A sandbox key calls no bank and is charged nothing. The last 4 characters of the IBAN, else of the account number, choose the answer: `0001` a `close_match` with a `holder_name`, `0002` a `close_match` without one, `0003` and `0006` a `no_match`, `0004` `cannot_verify`, `0005` a `503` and `0009` a `400`. Any other ending is a `match`.\n\n**API keys.** A scoped API key needs write access to sessions.", + "operationId": "post_v3_bank_account_check", + "tags": [ + "Standalone APIs" + ], + "x-didit-sdk": { + "resource": "bank", + "method": "account_check", + "auth": "api_key", + "server": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckRequest" + }, + "examples": { + "Person, United Kingdom": { + "summary": "A sort code and account number, checked against a person's name", + "value": { + "country": "GBR", + "account": { + "sort_code": "20-45-67", + "account_number": "31926819" + }, + "holder": { + "full_name": "Maria Oliveira" + }, + "vendor_data": "payout_20481" + } + }, + "Company, IBAN": { + "summary": "An IBAN, checked against a company's name", + "value": { + "country": "DEU", + "account": { + "iban": "DE89 3704 0044 0532 0130 00" + }, + "holder": { + "business_name": "Northwind Trading GmbH" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The bank answered. The check is billed, whatever the result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckResponse" + }, + "examples": { + "Close match": { + "summary": "The bank holds the account under a close name", + "value": { + "request_id": "c2f4a9e0-7b1d-4c3a-9e55-1d0a6b8f2a47", + "result": "close_match", + "holder_name": "MARIA R OLIVEIRA", + "account": "****6819", + "vendor_data": "payout_20481", + "created_at": "2026-10-21T14:32:08Z" + } + }, + "Match": { + "summary": "The bank holds the account under the name sent", + "value": { + "request_id": "5b0e2d7c-41a8-4f6e-9c3b-8e7d1a2f6c90", + "result": "match", + "holder_name": null, + "account": "****3000", + "vendor_data": null, + "created_at": "2026-10-21T14:35:41Z" + } + } + } + } + } + }, + "400": { + "description": "Refused as sent, and not billed. `field` names the body field to correct and `reason` says why, with the same reasons the hosted account form shows. Only the first refused field is reported.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Missing field": { + "summary": "The country's check needs a sort code", + "value": { + "detail": "The sort code is not valid (required).", + "code": "invalid_account_input", + "field": "account.sort_code", + "reason": "required" + } + }, + "Invalid IBAN": { + "summary": "The IBAN has the wrong length for its country", + "value": { + "detail": "The iban is not valid (length).", + "code": "invalid_account_input", + "field": "account.iban", + "reason": "length" + } + }, + "Refused by the bank": { + "summary": "The bank refused the account as sent", + "value": { + "detail": "The account was refused as typed (rejected).", + "code": "invalid_account_input", + "field": "account.account_number", + "reason": "rejected" + } + }, + "No name": { + "summary": "`holder` has neither `full_name` nor `business_name`", + "value": { + "detail": "Send full_name for a person or business_name for a company.", + "code": "invalid_account_input", + "field": "holder", + "reason": "required" + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "402": { + "description": "The check cannot be paid for, so it did not run. Top up your balance, then send it again.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Out of credit": { + "summary": "Your balance cannot cover one check", + "value": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me", + "code": "insufficient_credits" + } + }, + "Not in your contract": { + "summary": "Your contract has no price for the Account check API", + "value": { + "detail": "Your contract does not price the Account check API. Contact your account manager.", + "code": "contract_rate_missing" + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header or the bearer token is missing or invalid, the request came from an address outside the key's IP list, or a scoped key has no write access to sessions.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No valid API key or token", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "No sessions write": { + "summary": "A scoped key without write access to sessions", + "value": { + "detail": "This API key has no write access to sessions." + } + } + } + } + } + }, + "404": { + "description": "No account check serves this account yet, so nothing was checked or billed. `country_not_covered`: none serves its country, or its kind of account (person or business) there. `bank_not_covered`: none serves its bank. `bank_verification_not_available`: Bank Verification is not available yet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Country not covered": { + "summary": "No account check serves the country", + "value": { + "detail": "No account check serves this country yet.", + "code": "country_not_covered" + } + }, + "Bank not covered": { + "summary": "No account check serves the account's bank", + "value": { + "detail": "No account check serves this account's bank yet.", + "code": "bank_not_covered" + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + }, + "503": { + "description": "The bank could not answer. Nothing was checked or billed; send the check again later.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Unavailable": { + "summary": "The bank did not answer", + "value": { + "detail": "The account check is temporarily unavailable and this request was not charged. Please try again.", + "code": "account_check_unavailable" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/bank/account-check/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"country\": \"GBR\",\n \"account\": {\"sort_code\": \"20-45-67\", \"account_number\": \"31926819\"},\n \"holder\": {\"full_name\": \"Maria Oliveira\"},\n \"vendor_data\": \"payout_20481\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os, requests\n\nresp = requests.post(\n \"https://verification.didit.me/v3/bank/account-check/\",\n headers={\"x-api-key\": os.environ[\"DIDIT_API_KEY\"]},\n json={\n \"country\": \"GBR\",\n \"account\": {\"sort_code\": \"20-45-67\", \"account_number\": \"31926819\"},\n \"holder\": {\"full_name\": \"Maria Oliveira\"},\n \"vendor_data\": \"payout_20481\",\n },\n timeout=30,\n)\nresp.raise_for_status()\ncheck = resp.json()\nif check[\"result\"] == \"match\":\n pass # send the payout\nelif check[\"result\"] == \"close_match\":\n print(check[\"holder_name\"]) # the name the bank holds, when it returned one" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const res = await fetch('https://verification.didit.me/v3/bank/account-check/', {\n method: 'POST',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n country: 'GBR',\n account: { sort_code: '20-45-67', account_number: '31926819' },\n holder: { full_name: 'Maria Oliveira' },\n vendor_data: 'payout_20481',\n }),\n});\nconst check = await res.json();\n// check.result: match, close_match, no_match or cannot_verify" + } + ] + } + }, + "/v3/email/send/": { + "post": { + "summary": "Send Email Code", + "description": "Send a one-time passcode (OTP) to an email address, then verify it with [`POST /v3/email/check/`](/standalone-apis/email-check).\n\n**How send and check pair up.** Verification state is keyed by your application plus the `email` address — the check call does not take `request_id`. Each new send creates a pending verification that lives for **5 minutes**: call the check with the same address and the code the user received before the window closes. Calling send again for the same address while a verification is pending generates a fresh code for that same verification (`status: \"Retry\"`, same `request_id`); the previous code stops working. At most one retry is attached this way (two sends total); a further send starts a fresh verification with a new `request_id`. The 5-minute window is measured from the first send and is not extended by retries.\n\n**Deliverability pre-check.** Before anything is sent, the address goes through syntax and DNS/MX validation. Addresses that cannot receive mail return `200` with `status: \"Undeliverable\"` and `reason: \"email_can_not_be_delivered\"` — no email is sent, nothing is billed, and the verification is immediately finalized as `Declined` (a follow-up check returns `Expired or Not Found`). A downstream send failure reports the same way.\n\n**Code format and branding.** Codes are 4–8 characters (`options.code_size`, default 6), numeric by default; set `options.alphanumeric_code: true` for uppercase letters and digits (the check comparison is case-insensitive). The email template is localized via `options.locale` (54 supported languages) and can use your application's white-label branding via `options.use_white_label_customization`.\n\n**Billing.** One Email Verification API credit per successful send (`status: \"Success\"`), charged at send time. `Retry` and `Undeliverable` sends are free, and checks are free.\n\n**Session persistence.** Every new verification is persisted as an API-type session: `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/`, the verification appears in the Business Console, and `status.updated` webhooks fire as it progresses.\n\n**Sandbox.** Sandbox API keys skip delivery and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Success` payload with a random `request_id`; no email is sent and nothing is persisted. Use code `123456` on the sandbox check.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3email_send", + "x-didit-sdk": { + "resource": "email", + "method": "send_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/email/send/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"email\": \"alice@example.com\",\n \"options\": { \"code_size\": 6, \"locale\": \"en\" },\n \"vendor_data\": \"user-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os, requests\n\nresp = requests.post(\n \"https://verification.didit.me/v3/email/send/\",\n headers={\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Content-Type\": \"application/json\",\n },\n json={\n \"email\": \"alice@example.com\",\n \"options\": {\"code_size\": 6, \"locale\": \"en\"},\n \"vendor_data\": \"user-1234\",\n },\n timeout=15,\n)\nresp.raise_for_status()\nprint(resp.json()) # {request_id, status, reason, vendor_data, metadata}\n# Then verify with POST /v3/email/check/ using the same email" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const res = await fetch('https://verification.didit.me/v3/email/send/', {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n email: 'alice@example.com',\n options: { code_size: 6, locale: 'en' },\n vendor_data: 'user-1234',\n }),\n});\nif (!res.ok) throw new Error(`Email send failed: ${res.status}`);\nconst data = await res.json();\nconsole.log(data); // { request_id, status, reason, vendor_data, metadata }\n// Then verify with POST /v3/email/check/ using the same email" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "Recipient email address. Malformed addresses return `400`; syntactically valid addresses that cannot receive mail (failed DNS/MX validation) return `200` with `status: \"Undeliverable\"`.", + "example": "alice@example.com" + }, + "options": { + "type": "object", + "description": "OTP format, localization, and branding options. All fields are optional.", + "properties": { + "code_size": { + "type": "integer", + "minimum": 4, + "maximum": 8, + "default": 6, + "description": "Number of characters in the OTP (digits by default; uppercase letters and digits when `alphanumeric_code` is `true`)." + }, + "alphanumeric_code": { + "type": "boolean", + "default": false, + "description": "When `true`, the OTP is composed of uppercase letters `A–Z` and digits `0–9` instead of digits only. The `/v3/email/check/` comparison is case-insensitive." + }, + "locale": { + "type": "string", + "maxLength": 5, + "enum": [ + "en", + "ar", + "bn", + "bg", + "bs", + "ca", + "cs", + "da", + "de", + "el", + "es", + "et", + "fa", + "fi", + "fr", + "he", + "hi", + "hr", + "hu", + "hy", + "id", + "it", + "ja", + "ka", + "kk", + "ko", + "ky", + "lt", + "lv", + "cnr", + "mk", + "mn", + "ms", + "nl", + "no", + "pl", + "pt-BR", + "pt", + "ro", + "ru", + "sk", + "sl", + "so", + "sq", + "sr", + "sv", + "th", + "tr", + "uk", + "uz", + "vi", + "zh-CN", + "zh-TW", + "zh" + ], + "description": "Language of the OTP email template. Defaults to `en`. Unsupported values return `400` listing all supported locales.", + "example": "en" + }, + "use_white_label_customization": { + "type": "boolean", + "default": false, + "description": "When `true`, the OTP email uses your application's white-label customization (logo, colors, sender branding) instead of the default Didit template." + } + } + }, + "signals": { + "type": "object", + "description": "Optional device and network signals about the end user, forwarded to the anti-fraud layer to improve detection of abusive or automated traffic. All fields are optional.", + "properties": { + "ip": { + "type": "string", + "description": "IP address of the end user's device. IPv4 or IPv6.", + "example": "192.0.2.1" + }, + "device_id": { + "type": "string", + "maxLength": 255, + "description": "Unique identifier of the end user's device. Android: `ANDROID_ID`; iOS: `identifierForVendor`.", + "example": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2" + }, + "device_platform": { + "type": "string", + "enum": [ + "android", + "ios", + "ipados", + "tvos", + "web" + ], + "description": "Platform of the end user's device." + }, + "device_model": { + "type": "string", + "maxLength": 255, + "description": "Model of the end user's device.", + "example": "iPhone17,2" + }, + "os_version": { + "type": "string", + "maxLength": 64, + "description": "Operating-system version of the end user's device.", + "example": "18.0.1" + }, + "app_version": { + "type": "string", + "maxLength": 64, + "description": "Version of your application.", + "example": "1.2.34" + }, + "user_agent": { + "type": "string", + "maxLength": 512, + "description": "User agent of the end user's browser or app.", + "example": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1" + } + } + }, + "vendor_data": { + "type": "string", + "description": "Optional caller-controlled identifier (your internal user id, an email, a UUID, etc.) persisted on the session and echoed back in the send response, the matching check response, webhooks, and the Business Console. Use it to correlate Didit's `request_id` with your user record." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Optional free-form JSON object persisted on the session and echoed back in the send response, the matching check response, webhooks, and the Business Console." + } + }, + "required": [ + "email" + ] + }, + "examples": { + "Numeric 6-digit code": { + "summary": "Default — 6-digit numeric code in English", + "value": { + "email": "alice@example.com", + "options": { + "locale": "en" + }, + "vendor_data": "user-1234" + } + }, + "Alphanumeric 8-character code": { + "summary": "Uppercase letters + digits, Spanish template", + "value": { + "email": "bob@example.com", + "options": { + "code_size": 8, + "alphanumeric_code": true, + "locale": "es" + } + } + }, + "White-label branding": { + "summary": "Send the OTP email with your application's white-label customization", + "value": { + "email": "carol@example.com", + "options": { + "locale": "en", + "use_white_label_customization": true + }, + "vendor_data": "user-5678" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Send acknowledged. Inspect `status`: `Success` and `Retry` mean a code is on its way; `Undeliverable` means the address cannot receive mail and the verification is already finalized as `Declined`. `request_id` is the persisted session id (same id on a `Retry`).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailVerificationSendResponse" + }, + "examples": { + "Success": { + "summary": "OTP emailed for a new verification (billed)", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Success", + "reason": null, + "vendor_data": "user-1234", + "metadata": null + } + }, + "Retry": { + "summary": "Second send inside the 5-minute window — fresh code, same request_id, free", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Retry", + "reason": null, + "vendor_data": "user-1234", + "metadata": null + } + }, + "Undeliverable": { + "summary": "Address failed DNS/MX validation — verification finalized as Declined, nothing billed", + "value": { + "request_id": "cb1a5011-344d-444d-bdce-218904745d7f", + "status": "Undeliverable", + "reason": "email_can_not_be_delivered", + "vendor_data": "user-1234", + "metadata": null + } + } + } + } + } + }, + "400": { + "description": "Validation error — DRF's field-error envelope: one array of messages per offending field, nested under `options` for option errors.", + "content": { + "application/json": { + "examples": { + "Invalid email": { + "summary": "Malformed email address", + "value": { + "email": [ + "Enter a valid email address." + ] + } + }, + "Unsupported locale": { + "summary": "`options.locale` outside the supported set", + "value": { + "options": { + "locale": [ + "Invalid locale. Supported locales are en, ar, bn, bg, bs, ca, cs, da, de, el, es, et, fa, fi, fr, he, hi, hr, hu, hy, id, it, ja, ka, kk, ko, ky, lt, lv, cnr, mk, mn, ms, nl, no, pl, pt-BR, pt, ro, ru, sk, sl, so, sq, sr, sv, th, tr, uk, uz, vi, zh-CN, zh-TW, zh." + ] + } + } + }, + "Code size out of range": { + "summary": "`options.code_size` outside 4–8", + "value": { + "options": { + "code_size": [ + "Ensure this value is less than or equal to 8." + ] + } + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, malformed, revoked, or belongs to another environment (`{\"detail\": ...}`) — this API never returns `401`. Also returned before any send when the organization's balance cannot cover one Email Verification API credit (`{\"error\": ...}`).", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the send price", + "value": { + "error": "You don't have enough credits to perform this request." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. All POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "503": { + "description": "The email provider could not be reached (an outage or a timeout), so no code was sent and nothing was billed or counted as a send attempt. Repeat the same request.", + "content": { + "application/json": { + "examples": { + "Email delivery unavailable": { + "summary": "The email provider failed after one automatic retry", + "value": { + "detail": "Email delivery is temporarily unavailable. Please try again." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/email/check/": { + "post": { + "summary": "Check Email Code", + "description": "Verify the OTP delivered by [`POST /v3/email/send/`](/standalone-apis/email-send) and get the final verification result plus email intelligence: deliverability, breach exposure (`is_breached` and the `breaches` list from a breach-intelligence database), disposable-provider detection, and duplicate usage of the address across your sessions.\n\n**How the check finds the verification.** Matching is by your application plus the `email` address — `request_id` is not an input. The most recent pending verification created within the last **5 minutes** is checked. If there is none (never sent, already finalized, undeliverable at send time, or older than 5 minutes) the endpoint returns `200` with `status: \"Expired or Not Found\"`.\n\n**Attempt budget.** Each verification allows **3 code attempts**. The first two wrong codes return `status: \"Failed\"` with the attempts remaining and `email: null`; the third wrong code finalizes the verification as `Declined` with an `EMAIL_CODE_ATTEMPTS_EXCEEDED` warning and returns the full email report. Codes are compared case-insensitively (relevant for `alphanumeric_code` sends); only the most recently sent code is valid.\n\n**Outcomes.** `Approved` — correct code and no declining risk. `Declined` — terminal: the code was correct but a declining risk matched (a `DECLINE` action below, a blocklisted address, or the address found undeliverable at finalization), or the attempt budget was exhausted. `Failed` — wrong code, attempts remaining. `Expired or Not Found` — nothing to check. On `Approved`/`Declined` the `request_id` equals the send's `request_id` (the session id) and `email` carries the full report (`is_breached`, `breaches`, `is_disposable`, `is_undeliverable`, `warnings`, `lifecycle`, `matches`); on `Failed` and `Expired or Not Found` the `request_id` is a one-off random UUID.\n\n**Risk actions.** `duplicated_email_action`, `breached_email_action`, and `disposable_email_action` decide what happens when the corresponding risk is detected on a correct code: `DECLINE` flips the final status to `Declined`; `NO_ACTION` (default) records the risk in `email.warnings` without affecting the status.\n\n**Billing.** Checks are free — the credit is consumed by the successful send.\n\n**Session persistence.** A finalized check updates the session created by the send (visible in the Business Console, queryable via `GET /v3/session/{sessionId}/decision/`) and fires a `status.updated` webhook.\n\n**Sandbox.** Sandbox API keys skip all processing: any well-formed code (`123456` included) returns a static `Approved` payload with a simplified `email` object (`status`, `email`, `is_breached`, `is_disposable`, `is_undeliverable`); malformed input still returns `400`. Nothing is persisted.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.\n\n**Social footprint.** Set `enable_social` to `true` to add a `email_social` block: the online platforms this address is registered on, with per-category counts and any profile details available. `profiles_registered: 0` means the platforms were checked and the address was found on none of them - a valid, billable answer. The block is absent when the add-on was not requested or the check could not be completed, and it is not billed in that case. Billing adds one `email_social` unit per request on top of this endpoint's own price.", + "operationId": "post_v3email_check", + "x-didit-sdk": { + "resource": "email", + "method": "check_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/email/check/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"email\": \"alice@example.com\",\n \"code\": \"123456\",\n \"disposable_email_action\": \"DECLINE\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os, requests\n\nresp = requests.post(\n \"https://verification.didit.me/v3/email/check/\",\n headers={\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Content-Type\": \"application/json\",\n },\n json={\n \"email\": \"alice@example.com\", # same address as the send call\n \"code\": \"123456\",\n \"disposable_email_action\": \"DECLINE\",\n },\n timeout=15,\n)\nresp.raise_for_status()\nresult = resp.json()\nprint(result[\"status\"]) # Approved / Declined / Failed / Expired or Not Found\nif result[\"status\"] in (\"Approved\", \"Declined\"):\n print(result[\"email\"][\"is_breached\"], result[\"email\"][\"is_disposable\"])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const res = await fetch('https://verification.didit.me/v3/email/check/', {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n email: 'alice@example.com', // same address as the send call\n code: '123456',\n disposable_email_action: 'DECLINE',\n }),\n});\nconst data = await res.json();\nif (data.status === 'Approved') {\n // full email report is in data.email (breaches, is_disposable, matches, ...)\n}" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "The same email address used in the matching [`POST /v3/email/send/`](/standalone-apis/email-send) call. This is what links the check to the send.", + "example": "alice@example.com" + }, + "code": { + "type": "string", + "maxLength": 10, + "description": "The OTP the end user received: 4–8 digits, or 4–8 letters/digits when `alphanumeric_code: true` was used at send time. Comparison is case-insensitive.", + "example": "123456" + }, + "duplicated_email_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the same address was already used and approved by a different user (only previously Approved verifications count) (different `vendor_data`) of your application. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `email.warnings` and fills `email.matches`." + }, + "breached_email_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the address appears in known data breaches (`email.is_breached`). `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `email.warnings`." + }, + "disposable_email_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the domain belongs to a disposable/temporary-mail provider (`email.is_disposable`). `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `email.warnings`." + }, + "enable_social": { + "type": "boolean", + "default": false, + "description": "When true, also look up the social footprint of the address - the email, social, professional, e-commerce and entertainment platforms it is registered on. Billed as one `email_social` unit per request in addition to this endpoint's own price.", + "example": true + }, + "email_no_social_presence_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the social footprint check finds the address registered on none of the platforms it covers. Only NO_ACTION and DECLINE are accepted on this endpoint; REVIEW is available on the workflow configuration key of the same name." + } + }, + "required": [ + "email", + "code" + ] + }, + "examples": { + "Default": { + "summary": "Record any risks but never auto-decline", + "value": { + "email": "alice@example.com", + "code": "123456" + } + }, + "Strict risk policy": { + "summary": "Auto-decline disposable and breached inboxes", + "value": { + "email": "alice@example.com", + "code": "123456", + "disposable_email_action": "DECLINE", + "breached_email_action": "DECLINE" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Check completed — wrong codes and missing verifications also return `200`; inspect `status`, not the HTTP code. `email` is populated only on finalized outcomes (`Approved`/`Declined`), `null` on `Failed`, and absent on `Expired or Not Found`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailVerificationCheckResponse" + }, + "examples": { + "Approved": { + "summary": "Correct code — clean address, full email report", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Approved", + "message": "The verification code is correct.", + "email": { + "status": "Approved", + "email": "alice@example.com", + "is_breached": false, + "breaches": [], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [], + "lifecycle": [ + { + "type": "EMAIL_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null + }, + "fee": 0.02 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "EMAIL_VERIFICATION_APPROVED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": null, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Approved with breach exposure": { + "summary": "Correct code; breach exposure recorded as a warning (breached_email_action=NO_ACTION)", + "value": { + "request_id": "3a1f2b6c-9d4e-4f7a-8b2c-1e5d6f7a8b9c", + "status": "Approved", + "message": "The verification code is correct.", + "email": { + "status": "Approved", + "email": "bob@example.com", + "is_breached": true, + "breaches": [ + { + "name": "ExampleApp", + "domain": "exampleapp.com", + "breach_date": "2019-03-21", + "breach_emails_count": 763117, + "description": "In March 2019, the social platform ExampleApp suffered a data breach that exposed email addresses and passwords.", + "logo_path": "https:///logos/ExampleApp.png", + "data_classes": [ + "email_addresses", + "passwords" + ], + "is_verified": true + } + ], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [ + { + "feature": "EMAIL", + "risk": "BREACHED_EMAIL_DETECTED", + "additional_data": null, + "log_type": "information", + "short_description": "Breached email detected", + "long_description": "This email address was found in one or more known data breaches." + } + ], + "lifecycle": [ + { + "type": "EMAIL_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null + }, + "fee": 0.02 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "835126", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "EMAIL_VERIFICATION_APPROVED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": null, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-5678", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Failed (attempts remaining)": { + "summary": "Wrong code — the user can retry; request_id here is a one-off UUID", + "value": { + "request_id": "11111111-2222-3333-4444-555555555555", + "status": "Failed", + "message": "The verification code is incorrect. Attempts remaining: 2", + "email": null, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:21.512340+00:00" + } + }, + "Declined (risk policy)": { + "summary": "Correct code, but the address is disposable and disposable_email_action=DECLINE", + "value": { + "request_id": "8c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "status": "Declined", + "message": "The verification code is correct.", + "email": { + "status": "Declined", + "email": "tempuser42@mailinator.com", + "is_breached": false, + "breaches": [], + "is_disposable": true, + "is_undeliverable": false, + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [ + { + "feature": "EMAIL", + "risk": "DISPOSABLE_EMAIL_DETECTED", + "additional_data": null, + "log_type": "error", + "short_description": "Disposable email detected", + "long_description": "The system detected that the email is disposable, which is not allowed." + } + ], + "lifecycle": [ + { + "type": "EMAIL_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null + }, + "fee": 0.02 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "EMAIL_VERIFICATION_DECLINED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": { + "reason": "DISPOSABLE_EMAIL_DETECTED" + }, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Expired or Not Found": { + "summary": "No pending verification for this address in the last 5 minutes", + "value": { + "request_id": "88808a77-06e9-4cb0-85f5-9c052ddfd987", + "status": "Expired or Not Found", + "message": "No pending email verification found in the last 5 minutes.", + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-12T01:24:59.887904+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error — DRF's field-error envelope: one array of messages per offending field.", + "content": { + "application/json": { + "examples": { + "Invalid email": { + "summary": "Malformed email address", + "value": { + "email": [ + "Enter a valid email address." + ] + } + }, + "Missing code": { + "summary": "`code` not included in the body", + "value": { + "code": [ + "This field is required." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, malformed, revoked, or belongs to another environment — this API never returns `401`. Checks are free, so there is no credit check here.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. All POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/workflows/": { + "get": { + "summary": "List workflows", + "description": "List your workflows, newest first, in a limit/offset pagination envelope (`{count, next, previous, results}`, default page size 50). Each row is one version; `workflow_id` is the stable group ID and new sessions use the latest `published` version.", + "operationId": "list_workflows", + "x-didit-sdk": { + "resource": "workflows", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50 + }, + "description": "Page size (number of workflows per page)." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0 + }, + "description": "Number of rows to skip before the first returned workflow." + } + ], + "responses": { + "200": { + "description": "One page of workflows ordered by `created_at` descending, wrapped in the pagination envelope. Each item in `results` is a `WorkflowListItem`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of workflow versions for the application." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkflowListItem" + } + } + } + }, + "examples": { + "Two workflows": { + "summary": "A default KYC workflow and a draft KYC + AML workflow", + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "uuid": "a1b2c3d4-5678-90ab-cdef-111111111111", + "workflow_id": "a1b2c3d4-5678-90ab-cdef-111111111111", + "workflow_label": "Standard KYC", + "workflow_type": "kyc", + "is_default": true, + "is_archived": false, + "is_white_label_enabled": false, + "total_price": 0.15, + "min_price": 0.15, + "max_price": 0.15, + "features": "OCR + LIVENESS + FACE_MATCH", + "is_simple_workflow": true, + "is_editable": false, + "workflow_url": "https://verify.didit.me/u/_yljPOx8RWG3UAz5ZRQGVQ", + "max_retry_attempts": 3, + "retry_window_days": 7, + "session_expiration_time": 604800, + "version": 2, + "status": "published", + "has_draft": false, + "created_by": { + "name": "Alex Mateo", + "email": "alex@example.com", + "type": "user" + }, + "updated_at": "2026-04-12T10:30:00Z" + }, + { + "uuid": "b2c3d4e5-6789-01bc-defa-222222222222", + "workflow_id": "b2c3d4e5-6789-01bc-defa-222222222222", + "workflow_label": "Full Verification + AML", + "workflow_type": "kyc", + "is_default": false, + "is_archived": false, + "is_white_label_enabled": false, + "total_price": 0.25, + "min_price": 0.25, + "max_price": 0.25, + "features": "OCR + LIVENESS + FACE_MATCH + AML", + "is_simple_workflow": true, + "is_editable": true, + "workflow_url": "https://verify.didit.me/u/Aq3kPOx8RWG3UAz5ZRQGVQ", + "max_retry_attempts": 3, + "retry_window_days": 7, + "session_expiration_time": 604800, + "version": 1, + "status": "draft", + "has_draft": true, + "created_by": null, + "updated_at": "2026-04-15T18:05:21Z" + } + ] + } + } + } + } + } + }, + "403": { + "description": "The API key is missing, malformed, expired, or belongs to a different application. Body: `{\"detail\": \"You do not have permission to perform this action.\"}`. Refresh the token via the [Auth API](/auth-api/get-credentials) and retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff and honour any `Retry-After` header." + }, + "500": { + "description": "Unexpected server error while building the list. The request can be safely retried." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/workflows/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Accept: application/json'" + }, + { + "lang": "Python", + "label": "Python (requests)", + "source": "import requests\n\nresp = requests.get(\n \"https://verification.didit.me/v3/workflows/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n timeout=10,\n)\nresp.raise_for_status()\npage = resp.json()\nfor workflow in page[\"results\"]:\n print(workflow[\"uuid\"], workflow[\"workflow_label\"], workflow[\"status\"])" + }, + { + "lang": "JavaScript", + "label": "Node.js (fetch)", + "source": "const res = await fetch(\"https://verification.didit.me/v3/workflows/\", {\n headers: { 'x-api-key': 'YOUR_API_KEY' },\n});\nif (!res.ok) throw new Error(`Didit ${res.status}`);\nconst page = await res.json();\npage.results.forEach((w) => console.log(w.uuid, w.workflow_label, w.status));" + } + ] + }, + "post": { + "summary": "Create workflow", + "description": "Create a simple workflow. Send `features` in execution order (ordering rules apply); defaults to `published` v1. The body accepts ONLY the fields listed in `SimpleWorkflowRequest` — any other key (including `workflow_type`) is rejected with a 400. Not idempotent — each call creates a new workflow. Maximum 50 workflows per application.", + "operationId": "create_workflow", + "x-didit-sdk": { + "resource": "workflows", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleWorkflowRequest" + }, + "examples": { + "Simple KYC": { + "summary": "Linear ID, liveness, and face match workflow", + "value": { + "workflow_label": "Standard KYC", + "features": [ + { + "feature": "OCR", + "config": { + "duplicated_user_action": "review" + } + }, + { + "feature": "LIVENESS", + "config": { + "face_liveness_method": "passive" + } + }, + { + "feature": "FACE_MATCH", + "config": { + "face_match_score_decline_threshold": 40, + "face_match_score_review_threshold": 60 + } + } + ] + } + }, + "KYC + AML": { + "summary": "Run AML after ID verification", + "value": { + "workflow_label": "KYC with AML", + "features": [ + { + "feature": "OCR" + }, + { + "feature": "AML", + "config": { + "aml_score_review_threshold": 70, + "aml_score_approve_threshold": 30 + } + } + ] + } + }, + "Questionnaire workflow": { + "summary": "Run a questionnaire created with POST /v3/questionnaires/", + "value": { + "workflow_label": "Source of Funds", + "features": [ + { + "feature": "QUESTIONNAIRE", + "config": { + "questionnaire_uuid": "11111111-2222-3333-4444-555555555555", + "review_questionnaire_manually": true + } + } + ] + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Workflow created. The response body is the FULL workflow configuration — the same raw `VerificationSettings` serialization returned by `GET /v3/workflows/{settings_uuid}/` (~190 fields: every feature toggle, threshold, action and the generated `workflow_graph`), not the compact list item. Store the `uuid` (also exposed as `workflow_id`) so you can reference it from later session-creation calls. Note: `features` is a `\" + \"`-joined string and prices are JSON numbers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowDetail" + }, + "examples": { + "Created (abbreviated)": { + "summary": "Key fields of the full configuration object", + "value": { + "uuid": "c3d4e5f6-7890-12cd-ef34-333333333333", + "workflow_id": "c3d4e5f6-7890-12cd-ef34-333333333333", + "workflow_label": "Standard KYC", + "workflow_type": null, + "is_default": false, + "is_archived": false, + "total_price": 0.15, + "min_price": 0.15, + "max_price": 0.15, + "features": "OCR + LIVENESS + FACE_MATCH", + "is_simple_workflow": false, + "is_editable": false, + "workflow_url": "https://verify.didit.me/u/w9TkPOx8RWG3UAz5ZRQGVQ", + "workflow_graph": { + "start_node": "ocr", + "nodes": "... one node per feature plus a final status node ..." + }, + "max_retry_attempts": 3, + "retry_window_days": 7, + "session_expiration_time": 604800, + "version": 1, + "status": "published", + "published_at": "2026-05-17T10:30:00Z", + "created_at": "2026-05-17T10:30:00Z", + "updated_at": "2026-05-17T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Common causes: a top-level key outside the accepted whitelist (e.g. `workflow_type`), an unknown `feature` value, a feature placed before its dependency (e.g., `FACE_MATCH` before `OCR`), a passive feature (`AML`, `IP_ANALYSIS`, `DATABASE_VALIDATION`, `KYB_KEY_PEOPLE`) used as the first step, a `QUESTIONNAIRE` feature whose `config.questionnaire_uuid` does not exist for this application, or an out-of-range threshold. Body is a DRF validation error object — keys are field names, values are arrays of human-readable messages.", + "content": { + "application/json": { + "examples": { + "Missing feature": { + "value": { + "features": [ + "At least one feature is required." + ] + } + }, + "Unknown top-level field": { + "value": { + "workflow_type": [ + "The v3 workflow API only accepts `workflow_label`, `is_default`, `status`, `features`, `is_white_label_enabled`, `is_desktop_allowed`, `max_retry_attempts`, `retry_window_days`, `session_expiration_time`, `face_liveness_max_attempts`, `face_match_max_attempts`." + ] + } + }, + "Unknown feature": { + "value": { + "features": [ + "Invalid feature 'FOO'. Allowed values: AGE_ESTIMATION, AML, DATABASE_VALIDATION, EMAIL_VERIFICATION, FACE_MATCH, IP_ANALYSIS, KYB_DOCUMENTS, KYB_KEY_PEOPLE, KYB_REGISTRY, LIVENESS, NFC, OCR, PHONE_VERIFICATION, PROOF_OF_ADDRESS, QUESTIONNAIRE." + ] + } + }, + "Ordering": { + "value": { + "features": [ + "Feature 'FACE_MATCH' must appear after one of: OCR." + ] + } + }, + "Questionnaire not found": { + "value": { + "features": "Questionnaire '11111111-2222-3333-4444-555555555555' was not found for this application. Create the questionnaire first, then use its questionnaire_id." + } + } + } + } + } + }, + "403": { + "description": "The API key is missing, malformed, expired, or scoped to a different application. Refresh via the [Auth API](/auth-api/get-credentials) and retry.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "415": { + "description": "Unsupported media type. Set `Content-Type: application/json` and send a JSON body." + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error while creating the workflow. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/workflows/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"workflow_label\": \"Standard KYC\",\n \"features\": [\n {\"feature\": \"OCR\"},\n {\"feature\": \"LIVENESS\", \"config\": {\"face_liveness_method\": \"passive\"}},\n {\"feature\": \"FACE_MATCH\", \"config\": {\"face_match_score_decline_threshold\": 40, \"face_match_score_review_threshold\": 60}}\n ]\n }'" + }, + { + "lang": "Python", + "label": "Python (requests)", + "source": "import requests\n\npayload = {\n \"workflow_label\": \"Standard KYC\",\n \"features\": [\n {\"feature\": \"OCR\"},\n {\"feature\": \"LIVENESS\", \"config\": {\"face_liveness_method\": \"passive\"}},\n {\"feature\": \"FACE_MATCH\"},\n ],\n}\nresp = requests.post(\n \"https://verification.didit.me/v3/workflows/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n json=payload,\n timeout=10,\n)\nresp.raise_for_status()\nworkflow = resp.json()\nprint(\"Workflow id:\", workflow[\"uuid\"])" + }, + { + "lang": "JavaScript", + "label": "Node.js (fetch)", + "source": "const res = await fetch(\"https://verification.didit.me/v3/workflows/\", {\n method: \"POST\",\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n workflow_label: \"Standard KYC\",\n features: [\n { feature: \"OCR\" },\n { feature: \"LIVENESS\", config: { face_liveness_method: \"passive\" } },\n { feature: \"FACE_MATCH\" },\n ],\n }),\n});\nif (!res.ok) throw new Error(`Didit ${res.status}: ${await res.text()}`);\nconst workflow = await res.json();\nconsole.log(\"Workflow id:\", workflow.uuid);" + } + ] + } + }, + "/v3/workflows/{settings_uuid}/": { + "get": { + "summary": "Get workflow", + "description": "Fetch full configuration for one workflow version by `settings_uuid`: feature toggles, thresholds, allowed documents, graph, retry policy, callbacks, and pricing.", + "operationId": "get_workflow", + "x-didit-sdk": { + "resource": "workflows", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "parameters": [ + { + "name": "settings_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the workflow (the `uuid` value returned by the list endpoint). Each version of a workflow has its own `settings_uuid`." + } + ], + "responses": { + "200": { + "description": "Full workflow configuration. The shape is the raw `VerificationSettings` serialization (~190 fields: every feature toggle, threshold, warning action, allowed-documents config, the `workflow_graph`, retry policy, callbacks and pricing). The example below is abbreviated. Notes: `features` is a `\" + \"`-joined string (e.g. `\"OCR + LIVENESS\"`), prices are JSON numbers, and `is_editable` is `true` only for `draft` versions.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowDetail" + }, + "examples": { + "KYC workflow with face match (abbreviated)": { + "value": { + "uuid": "a1b2c3d4-5678-90ab-cdef-111111111111", + "workflow_id": "a1b2c3d4-5678-90ab-cdef-111111111111", + "workflow_label": "Standard KYC", + "workflow_type": "kyc", + "is_default": true, + "is_archived": false, + "is_liveness_enabled": true, + "face_liveness_method": "passive", + "face_liveness_score_decline_threshold": 50, + "face_liveness_score_review_threshold": 70, + "is_face_match_enabled": true, + "face_match_score_decline_threshold": 40, + "face_match_score_review_threshold": 60, + "is_aml_enabled": false, + "is_phone_verification_enabled": false, + "is_email_verification_enabled": false, + "is_database_validation_enabled": false, + "is_ip_analysis_enabled": false, + "is_nfc_enabled": false, + "duplicated_user_action": "no_action", + "documents_allowed": { + "ESP": { + "P": { + "sides": 1, + "enabled": 1, + "subtypes": [ + "EPASSPORT", + "PASSPORT_GENERIC" + ], + "preferred_characters": "latin", + "expiration_check_mode": "strict" + } + } + }, + "max_retry_attempts": 3, + "retry_window_days": 7, + "session_expiration_time": 604800, + "version": 2, + "status": "published", + "published_at": "2026-04-12T10:30:00Z", + "is_simple_workflow": false, + "is_editable": false, + "workflow_url": "https://verify.didit.me/u/_yljPOx8RWG3UAz5ZRQGVQ", + "total_price": 0.15, + "min_price": 0.15, + "max_price": 0.15, + "features": "OCR + LIVENESS + FACE_MATCH", + "created_at": "2025-01-15T10:30:00Z", + "updated_at": "2026-04-12T10:30:00Z" + } + } + } + } + } + }, + "403": { + "description": "API key is missing, malformed, expired, or scoped to a different application.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No workflow with this `settings_uuid` exists for the authenticated application. Either the UUID is wrong or it belongs to another application — Didit deliberately does not distinguish the two cases to avoid leaking the existence of cross-application records.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/workflows/a1b2c3d4-5678-90ab-cdef-111111111111/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "Python", + "label": "Python (requests)", + "source": "import requests\n\nresp = requests.get(\n f\"https://verification.didit.me/v3/workflows/{settings_uuid}/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n timeout=10,\n)\nresp.raise_for_status()\nworkflow = resp.json()\nprint(workflow[\"workflow_label\"], workflow[\"status\"])" + }, + { + "lang": "JavaScript", + "label": "Node.js (fetch)", + "source": "const res = await fetch(`https://verification.didit.me/v3/workflows/${settingsUuid}/`, {\n headers: { 'x-api-key': 'YOUR_API_KEY' },\n});\nif (!res.ok) throw new Error(`Didit ${res.status}`);\nconst workflow = await res.json();\nconsole.log(workflow.workflow_label, workflow.status);" + } + ] + }, + "patch": { + "summary": "Update workflow", + "description": "Update a workflow. Versioning depends on the `status` field you send and the current state of the version: (1) no `status` on a **published** version → the published version is updated in place (same `uuid`, same `version`); (2) no `status` on a **draft** → the draft is updated and immediately published; (3) `status: \"published\"` on a **published** version → a NEW published version is created under the same `workflow_id` (new `uuid`, `version` + 1) and returned, and any existing draft in the group is deleted; (4) `status: \"draft\"` keeps a draft as a draft (autosave) but is rejected with 400 on a published version (`\"Cannot revert a published version to draft.\"`). The body accepts the same strict field whitelist as create — unknown keys are rejected with 400.", + "operationId": "update_workflow", + "x-didit-sdk": { + "resource": "workflows", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "parameters": [ + { + "name": "settings_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the workflow to update." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleWorkflowUpdateRequest" + }, + "examples": { + "Replace features": { + "summary": "Send the full desired feature order", + "value": { + "features": [ + { + "feature": "OCR" + }, + { + "feature": "AML" + } + ] + } + }, + "Rename": { + "summary": "Rename a workflow", + "value": { + "workflow_label": "Premium KYC" + } + }, + "Set as default": { + "summary": "Make this workflow the application's default", + "value": { + "is_default": true + } + }, + "Update config": { + "summary": "Change feature configuration", + "value": { + "features": [ + { + "feature": "OCR" + }, + { + "feature": "LIVENESS", + "config": { + "face_liveness_method": "passive" + } + } + ] + } + }, + "Publish a draft": { + "summary": "Promote a draft to published", + "value": { + "status": "published" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Workflow updated. The body is the FULL configuration of the resulting version (same raw `VerificationSettings` shape as `GET /v3/workflows/{settings_uuid}/`). When the update created a new version (`status: \"published\"` sent on a published row) the returned `uuid` and `version` differ from the ones you patched — use the returned `uuid` for subsequent calls.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowDetail" + } + } + } + }, + "400": { + "description": "Validation error — a top-level key outside the accepted whitelist, an unknown feature, invalid ordering, out-of-range threshold, referencing a non-existent questionnaire, or `status: \"draft\"` sent for a published version (`\"Cannot revert a published version to draft.\"`). The response body is a DRF validation error keyed by field name." + }, + "403": { + "description": "API key missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No workflow with this `settings_uuid` exists for the authenticated application." + }, + "415": { + "description": "Unsupported media type. Set `Content-Type: application/json`." + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X PATCH 'https://verification.didit.me/v3/workflows/a1b2c3d4-5678-90ab-cdef-111111111111/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"workflow_label\": \"Premium KYC\"}'" + }, + { + "lang": "Python", + "label": "Python (requests)", + "source": "import requests\n\nresp = requests.patch(\n f\"https://verification.didit.me/v3/workflows/{settings_uuid}/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n json={\"workflow_label\": \"Premium KYC\"},\n timeout=10,\n)\nresp.raise_for_status()\nprint(resp.json()[\"workflow_label\"])" + }, + { + "lang": "JavaScript", + "label": "Node.js (fetch)", + "source": "const res = await fetch(`https://verification.didit.me/v3/workflows/${settingsUuid}/`, {\n method: \"PATCH\",\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ workflow_label: \"Premium KYC\" }),\n});\nif (!res.ok) throw new Error(`Didit ${res.status}`);\nconst workflow = await res.json();\nconsole.log(workflow.workflow_label);" + } + ] + }, + "delete": { + "summary": "Delete workflow", + "description": "Permanently delete one workflow version (no undo). Existing sessions keep their verification results but their workflow reference is cleared; other versions in the same `workflow_id` group are not deleted.", + "operationId": "delete_workflow", + "x-didit-sdk": { + "resource": "workflows", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "parameters": [ + { + "name": "settings_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the workflow to delete." + } + ], + "responses": { + "204": { + "description": "Workflow deleted. The response body is empty." + }, + "403": { + "description": "API key missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No workflow with this `settings_uuid` exists for the authenticated application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X DELETE 'https://verification.didit.me/v3/workflows/a1b2c3d4-5678-90ab-cdef-111111111111/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "Python", + "label": "Python (requests)", + "source": "import requests\n\nresp = requests.delete(\n f\"https://verification.didit.me/v3/workflows/{settings_uuid}/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n timeout=10,\n)\nresp.raise_for_status() # raises on 4xx/5xx; 204 is success" + }, + { + "lang": "JavaScript", + "label": "Node.js (fetch)", + "source": "const res = await fetch(`https://verification.didit.me/v3/workflows/${settingsUuid}/`, {\n method: \"DELETE\",\n headers: { 'x-api-key': 'YOUR_API_KEY' },\n});\nif (res.status !== 204) throw new Error(`Didit ${res.status}`);" + } + ] + } + }, + "/v3/questionnaires/": { + "get": { + "summary": "List questionnaires", + "description": "List your questionnaires, newest first, in a limit/offset pagination envelope (`{count, next, previous, results}`, default page size 50). Each row is one version; `questionnaire_group_id` is the stable group ID resolved to the latest `published` version.", + "operationId": "list_questionnaires", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50 + }, + "description": "Page size (number of questionnaires per page)." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0 + }, + "description": "Number of rows to skip before the first returned questionnaire." + } + ], + "responses": { + "200": { + "description": "One page of questionnaires ordered by `created_at` descending, wrapped in the pagination envelope. Each item in `results` is a `QuestionnaireListItem`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of questionnaire versions for the application." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionnaireListItem" + } + } + } + }, + "examples": { + "One questionnaire": { + "summary": "A simple bilingual questionnaire", + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "11111111-2222-3333-4444-555555555555", + "title": "KYC Questionnaire", + "description": "Additional verification questions", + "languages": [ + "en", + "es" + ], + "default_language": "en", + "is_active": true, + "is_simple_questionnaire": true, + "created_at": "2026-04-23T09:55:00Z", + "updated_at": "2026-04-23T10:00:00Z", + "question_types": [ + "SHORT_TEXT", + "MULTIPLE_CHOICE", + "FILE_UPLOAD" + ], + "workflow_names": [], + "questionnaire_group_id": "11111111-2222-3333-4444-555555555555", + "version": 1, + "status": "published", + "published_at": "2026-04-23T10:00:00Z", + "is_editable": false + } + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error while building the list. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/questionnaires/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "Python", + "label": "Python (requests)", + "source": "import requests\n\nresp = requests.get(\n \"https://verification.didit.me/v3/questionnaires/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n timeout=10,\n)\nresp.raise_for_status()\npage = resp.json()\nfor q in page[\"results\"]:\n print(q[\"uuid\"], q[\"title\"], q[\"status\"])" + }, + { + "lang": "JavaScript", + "label": "Node.js (fetch)", + "source": "const res = await fetch(\"https://verification.didit.me/v3/questionnaires/\", {\n headers: { 'x-api-key': 'YOUR_API_KEY' },\n});\nif (!res.ok) throw new Error(`Didit ${res.status}`);\nconst page = await res.json();\npage.results.forEach((q) => console.log(q.uuid, q.title, q.status));" + } + ] + }, + "post": { + "summary": "Create questionnaire", + "description": "Create a simple linear questionnaire. Send `form_elements` in display order; branching and repeatable groups require the Console. Defaults to `published` v1.", + "operationId": "create_questionnaire", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleQuestionnaireRequest" + }, + "examples": { + "Simple": { + "summary": "Basic questionnaire", + "value": { + "title": "Customer Onboarding", + "description": "Additional information needed for verification", + "default_language": "en", + "languages": [ + "en" + ], + "is_active": true, + "form_elements": [ + { + "id": "occupation", + "element_type": "short_text", + "label": { + "en": "What is your occupation?" + }, + "is_required": true, + "placeholder": { + "en": "Software engineer" + } + }, + { + "id": "source_of_funds", + "element_type": "multiple_choice", + "label": { + "en": "Source of funds" + }, + "is_required": true, + "options": [ + { + "value": "employment", + "label": { + "en": "Employment" + } + }, + { + "value": "business", + "label": { + "en": "Business" + } + }, + { + "value": "investments", + "label": { + "en": "Investments" + } + }, + { + "value": "other", + "label": { + "en": "Other" + } + } + ] + } + ] + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Questionnaire created. The response includes the `questionnaire_id` — store it to reference this questionnaire from workflows or future update/delete calls.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QuestionnaireDetail" + }, + "examples": { + "Created": { + "value": { + "questionnaire_id": "11111111-2222-3333-4444-555555555555", + "title": "Customer Onboarding", + "description": "Additional information needed for verification", + "languages": [ + "en" + ], + "default_language": "en", + "is_active": true, + "is_simple_questionnaire": true, + "graph": { + "start_node": "occupation", + "nodes": { + "occupation": { + "element_type": "SHORT_TEXT", + "is_required": true, + "title": { + "en": "What is your occupation?" + }, + "next": "source_of_funds" + }, + "source_of_funds": { + "element_type": "MULTIPLE_CHOICE", + "is_required": true, + "title": { + "en": "Source of funds" + }, + "choices": [ + { + "value": "employment", + "label": { + "en": "Employment" + } + }, + { + "value": "business", + "label": { + "en": "Business" + } + } + ], + "next": null + } + } + }, + "sections": [], + "questionnaire_group_id": "11111111-2222-3333-4444-555555555555", + "version": 1, + "status": "published", + "published_at": "2026-04-23T10:00:00Z", + "is_editable": false + } + } + } + } + } + }, + "400": { + "description": "Validation error. Common causes: an unknown `element_type`, a translatable field missing a translation for one of `languages`, `languages` missing `en`, `default_language` not in `languages`, an option without a `label`, a question that has `branches`/`required_if`/`repeatable_config` (not supported by this endpoint), or sending a raw `graph` instead of `form_elements`. The body is a DRF validation error keyed by field name.", + "content": { + "application/json": { + "examples": { + "Graph not accepted": { + "value": { + "graph": "The v3 questionnaire API only accepts simple `form_elements`, not `graph`." + } + }, + "Missing label": { + "value": { + "form_elements": [ + { + "label": [ + "This field is required." + ] + } + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "415": { + "description": "Unsupported media type. Set `Content-Type: application/json` and send a JSON body." + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error while creating the questionnaire. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/questionnaires/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"title\": \"Customer Onboarding\",\n \"default_language\": \"en\",\n \"languages\": [\"en\"],\n \"form_elements\": [\n {\n \"id\": \"occupation\",\n \"element_type\": \"short_text\",\n \"label\": {\"en\": \"What is your occupation?\"},\n \"is_required\": true\n }\n ]\n }'" + }, + { + "lang": "Python", + "label": "Python (requests)", + "source": "import requests\n\npayload = {\n \"title\": \"Customer Onboarding\",\n \"default_language\": \"en\",\n \"languages\": [\"en\"],\n \"form_elements\": [\n {\n \"id\": \"occupation\",\n \"element_type\": \"short_text\",\n \"label\": {\"en\": \"What is your occupation?\"},\n \"is_required\": True,\n },\n ],\n}\nresp = requests.post(\n \"https://verification.didit.me/v3/questionnaires/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n json=payload,\n timeout=10,\n)\nresp.raise_for_status()\nquestionnaire = resp.json()\nprint(\"Questionnaire id:\", questionnaire[\"questionnaire_id\"])" + }, + { + "lang": "JavaScript", + "label": "Node.js (fetch)", + "source": "const res = await fetch(\"https://verification.didit.me/v3/questionnaires/\", {\n method: \"POST\",\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n title: \"Customer Onboarding\",\n default_language: \"en\",\n languages: [\"en\"],\n form_elements: [\n {\n id: \"occupation\",\n element_type: \"short_text\",\n label: { en: \"What is your occupation?\" },\n is_required: true,\n },\n ],\n }),\n});\nif (!res.ok) throw new Error(`Didit ${res.status}: ${await res.text()}`);\nconst questionnaire = await res.json();\nconsole.log(\"Questionnaire id:\", questionnaire.questionnaire_id);" + } + ] + } + }, + "/v3/questionnaires/{questionnaire_uuid}/": { + "get": { + "summary": "Get questionnaire", + "description": "Fetch full questionnaire detail for one version by `questionnaire_uuid`: title, `languages`, canonical `graph`, derived `sections`, and version metadata.", + "operationId": "get_questionnaire", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "parameters": [ + { + "name": "questionnaire_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the questionnaire (the `uuid` value returned by the list endpoint, also returned as `questionnaire_id` on detail endpoints)." + } + ], + "responses": { + "200": { + "description": "Full questionnaire detail including the canonical `graph`, the derived `sections` view, version metadata, and `is_editable`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QuestionnaireDetail" + }, + "examples": { + "Published questionnaire": { + "value": { + "questionnaire_id": "11111111-2222-3333-4444-555555555555", + "title": "Customer Onboarding", + "description": "Additional information needed for verification", + "languages": [ + "en" + ], + "default_language": "en", + "is_active": true, + "is_simple_questionnaire": true, + "graph": { + "start_node": "occupation", + "nodes": { + "occupation": { + "element_type": "SHORT_TEXT", + "is_required": true, + "title": { + "en": "What is your occupation?" + }, + "next": "source_of_funds" + }, + "source_of_funds": { + "element_type": "MULTIPLE_CHOICE", + "is_required": true, + "title": { + "en": "Source of funds" + }, + "choices": [ + { + "value": "employment", + "label": { + "en": "Employment" + } + }, + { + "value": "business", + "label": { + "en": "Business" + } + } + ], + "next": null + } + } + }, + "sections": [ + { + "title": {}, + "description": {}, + "items": [ + { + "uuid": "11111111-2222-3333-4444-666666666666", + "value": "occupation", + "element_type": "SHORT_TEXT", + "is_required": true, + "title": { + "en": "What is your occupation?" + } + } + ] + } + ], + "questionnaire_group_id": "11111111-2222-3333-4444-555555555555", + "version": 1, + "status": "published", + "published_at": "2026-04-23T10:00:00Z", + "is_editable": false + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No questionnaire with this `questionnaire_uuid` exists for the authenticated application. Either the UUID is wrong or it belongs to another application — the API deliberately does not distinguish the two cases.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/questionnaires/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "Python", + "label": "Python (requests)", + "source": "import requests\n\nresp = requests.get(\n f\"https://verification.didit.me/v3/questionnaires/{questionnaire_uuid}/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n timeout=10,\n)\nresp.raise_for_status()\nquestionnaire = resp.json()\nprint(questionnaire[\"title\"], questionnaire[\"status\"])" + }, + { + "lang": "JavaScript", + "label": "Node.js (fetch)", + "source": "const res = await fetch(`https://verification.didit.me/v3/questionnaires/${questionnaireUuid}/`, {\n headers: { 'x-api-key': 'YOUR_API_KEY' },\n});\nif (!res.ok) throw new Error(`Didit ${res.status}`);\nconst questionnaire = await res.json();\nconsole.log(questionnaire.title, questionnaire.status);" + } + ] + }, + "patch": { + "summary": "Update questionnaire", + "description": "Update a questionnaire. Versioning depends on the `status` field you send and the current state of the version: (1) no `status` on a **published** version → the published version is updated in place (same `questionnaire_id`, same `version`); (2) no `status` on a **draft** → the draft is updated and immediately published; (3) `status: \"published\"` on a **published** version → a NEW published version is created under the same `questionnaire_group_id` (new `questionnaire_id`, `version` + 1) and returned, and any existing draft in the group is deleted; (4) `status: \"draft\"` keeps a draft as a draft (autosave) but is rejected with 400 on a published version (`\"Cannot revert a published version to draft.\"`).", + "operationId": "update_questionnaire", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "parameters": [ + { + "name": "questionnaire_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the questionnaire to update." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleQuestionnaireUpdateRequest" + }, + "examples": { + "Rename": { + "summary": "Rename a questionnaire", + "value": { + "title": "Updated Customer Onboarding" + } + }, + "Replace questions": { + "summary": "Replace the full linear question list", + "value": { + "form_elements": [ + { + "id": "occupation", + "element_type": "short_text", + "label": { + "en": "What is your occupation?" + }, + "is_required": true + }, + { + "id": "source_of_funds", + "element_type": "multiple_choice", + "label": { + "en": "Source of funds" + }, + "options": [ + { + "value": "employment", + "label": { + "en": "Employment" + } + }, + { + "value": "business", + "label": { + "en": "Business" + } + } + ] + } + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Questionnaire updated. The body is the latest state of the resulting version — the in-place updated version, or the newly created published version when you sent `status: \"published\"` on a published row (in that case `questionnaire_id` and `version` differ from the ones you patched). `questionnaire_id` in the response is the per-version UUID; use `questionnaire_group_id` if you need the stable group identifier.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QuestionnaireDetail" + } + } + } + }, + "400": { + "description": "Validation error — unknown `element_type`, missing translation, unsupported branching/conditional fields, `languages` missing `en`, `default_language` not in `languages`, option without a label, sending a raw `graph`, or `status: \"draft\"` on a published version (`\"Cannot revert a published version to draft.\"`). Response body is a DRF validation error keyed by field name." + }, + "403": { + "description": "API key missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No questionnaire with this `questionnaire_uuid` exists for the authenticated application." + }, + "415": { + "description": "Unsupported media type. Set `Content-Type: application/json`." + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X PATCH 'https://verification.didit.me/v3/questionnaires/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"title\": \"Updated Customer Onboarding\"}'" + }, + { + "lang": "Python", + "label": "Python (requests)", + "source": "import requests\n\nresp = requests.patch(\n f\"https://verification.didit.me/v3/questionnaires/{questionnaire_uuid}/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n json={\"title\": \"Updated Customer Onboarding\"},\n timeout=10,\n)\nresp.raise_for_status()\nprint(resp.json()[\"questionnaire_id\"], resp.json()[\"status\"])" + }, + { + "lang": "JavaScript", + "label": "Node.js (fetch)", + "source": "const res = await fetch(`https://verification.didit.me/v3/questionnaires/${questionnaireUuid}/`, {\n method: \"PATCH\",\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ title: \"Updated Customer Onboarding\" }),\n});\nif (!res.ok) throw new Error(`Didit ${res.status}`);\nconst q = await res.json();\nconsole.log(q.questionnaire_id, q.status);" + } + ] + }, + "delete": { + "summary": "Delete questionnaire", + "description": "Permanently delete one questionnaire version (no undo). Stored questionnaire responses linked to this version are deleted with it, and sessions referencing it lose the link; other versions in the same group are not deleted. Workflows that referenced it keep running but the questionnaire step can no longer load it.", + "operationId": "delete_questionnaire", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "parameters": [ + { + "name": "questionnaire_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the questionnaire to delete." + } + ], + "responses": { + "204": { + "description": "Questionnaire deleted. The response body is empty." + }, + "403": { + "description": "API key missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No questionnaire with this `questionnaire_uuid` exists for the authenticated application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X DELETE 'https://verification.didit.me/v3/questionnaires/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "Python", + "label": "Python (requests)", + "source": "import requests\n\nresp = requests.delete(\n f\"https://verification.didit.me/v3/questionnaires/{questionnaire_uuid}/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n timeout=10,\n)\nresp.raise_for_status() # raises on 4xx/5xx; 204 is success" + }, + { + "lang": "JavaScript", + "label": "Node.js (fetch)", + "source": "const res = await fetch(`https://verification.didit.me/v3/questionnaires/${questionnaireUuid}/`, {\n method: \"DELETE\",\n headers: { 'x-api-key': 'YOUR_API_KEY' },\n});\nif (res.status !== 204) throw new Error(`Didit ${res.status}`);" + } + ] + } + }, + "/v3/users/": { + "get": { + "summary": "List users", + "description": "List [users](/entities/users) Didit assembles from KYC sessions sharing the same `vendor_data` (free-form string, NOT a UUID), plus users created via the API. Paginated, ordered by `last_session_at` desc. This endpoint does not support filtering or search parameters — fetch pages and filter client-side.", + "operationId": "list_users", + "x-didit-sdk": { + "resource": "users", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size. Defaults to 50 when omitted. There is no enforced maximum, but keep pages small for latency." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset of the first record to return. Prefer following the `next` URL from the previous page over computing this by hand." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/users/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.get(\n 'https://verification.didit.me/v3/users/',\n headers={'x-api-key': 'YOUR_API_KEY'},\n params={'limit': 50},\n)\nresp.raise_for_status()\npage = resp.json()\nfor user in page['results']:\n print(user['vendor_data'], user['status'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const url = new URL('https://verification.didit.me/v3/users/');\nurl.searchParams.set('limit', '50');\n\nconst page = await fetch(url, {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n}).then((r) => r.json());\n\nconsole.log(page.count, page.results.length);" + } + ], + "responses": { + "200": { + "description": "Paginated list of users with verification status, session counts, and feature breakdown.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows — exact up to 100, capped at 100 beyond that for performance. Do not use it to detect the last page; rely on `next` being `null` instead." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserListItem" + } + } + } + }, + "examples": { + "Example": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": null, + "full_name": "John Michael Doe", + "date_of_birth": "1990-05-15", + "effective_name": "John Michael Doe", + "status": "ACTIVE", + "portrait_image_url": "https:///...", + "session_count": 3, + "approved_count": 2, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [ + "USA" + ], + "approved_emails": [ + "john@example.com" + ], + "approved_phones": [ + "+14155551234" + ], + "features": { + "ID_VERIFICATION": "Approved", + "LIVENESS": "Approved", + "FACE_MATCH": "Approved", + "AML": "Approved" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-01T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2025-06-01T08:00:00Z" + }, + { + "didit_internal_id": "6ed99d53-e8f5-4cf8-9ac1-4a506cb40f6b", + "vendor_data": null, + "display_name": "Jane S.", + "full_name": "Jane Elizabeth Smith", + "date_of_birth": "1985-11-22", + "effective_name": "Jane S.", + "status": "FLAGGED", + "portrait_image_url": null, + "session_count": 1, + "approved_count": 0, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [], + "approved_emails": [], + "approved_phones": [], + "features": { + "ID_VERIFICATION": "In Review" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "In Review" + } + ], + "last_session_at": "2025-06-20T14:00:00Z", + "last_activity_at": "2025-06-20T14:05:00Z", + "first_session_at": "2025-06-20T14:00:00Z", + "tags": [], + "created_at": "2025-06-20T14:00:00Z" + } + ] + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "Invalid or revoked API key, or the key cannot list users for this application. Note: on this endpoint an *invalid* key returns `403` (not `401`).", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/users/{vendor_data}/": { + "get": { + "summary": "Get user", + "description": "Fetch one [user](/entities/users) by `vendor_data` (exact match). Adds `metadata`, `comments`, and `updated_at` to the list view, and switches `tags` to the detailed tag-link shape.", + "operationId": "get_user", + "x-didit-sdk": { + "resource": "users", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the user — a free-form string (NOT a UUID). This is the same value you passed as `vendor_data` when creating the session that first introduced this user, matched exactly as sent.", + "example": "user-abc-123" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/users/user-abc-123/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.get(\n 'https://verification.didit.me/v3/users/user-abc-123/',\n headers={'x-api-key': 'YOUR_API_KEY'},\n)\nresp.raise_for_status()\nuser = resp.json()\nprint(user['didit_internal_id'], user['status'], user['session_count'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const vendorData = encodeURIComponent('user-abc-123');\nconst user = await fetch(\n `https://verification.didit.me/v3/users/${vendorData}/`,\n { headers: { 'x-api-key': process.env.DIDIT_API_KEY } },\n).then((r) => r.json());\n\nconsole.log(user.didit_internal_id, user.status);" + } + ], + "responses": { + "200": { + "description": "Full user detail including metadata, comments/activity log, and aggregated session data.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDetailItem" + }, + "examples": { + "Active user": { + "value": { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": null, + "full_name": "John Michael Doe", + "date_of_birth": "1990-05-15", + "effective_name": "John Michael Doe", + "status": "ACTIVE", + "metadata": { + "tier": "premium", + "source": "website" + }, + "portrait_image_url": "https:///...", + "session_count": 3, + "approved_count": 2, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [ + "USA" + ], + "approved_emails": [ + "john@example.com" + ], + "approved_phones": [ + "+14155551234" + ], + "features": { + "ID_VERIFICATION": "Approved", + "LIVENESS": "Approved", + "FACE_MATCH": "Approved", + "AML": "Approved" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-01T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "tag": { + "uuid": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b", + "name": "VIP", + "color": "#2567FF", + "description": null, + "source": "custom", + "created_at": "2025-05-01T09:00:00Z", + "updated_at": "2025-05-01T09:00:00Z" + }, + "added_by_email": "analyst@acme.com", + "added_by_name": "Jane Analyst", + "created_at": "2025-06-02T11:00:00Z" + } + ], + "comments": [ + { + "uuid": "c1111111-2222-4333-8444-555555555555", + "comment_type": "STATUS_CHANGED", + "comment": null, + "actor_email": "analyst@acme.com", + "actor_name": "Jane Analyst", + "previous_status": "FLAGGED", + "new_status": "ACTIVE", + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "mentioned_emails": [], + "created_at": "2025-06-01T10:30:00Z" + } + ], + "created_at": "2025-06-01T08:00:00Z", + "updated_at": "2025-06-15T10:30:00Z" + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "Invalid or revoked API key, or the key cannot read this application's users. Note: on this endpoint an *invalid* key returns `403` (not `401`).", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No user with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update user", + "description": "Partial update of a user: `display_name`, `full_name`, `date_of_birth`, `status`, `metadata`, `approved_emails`/`approved_phones`/`issuing_states`. `metadata` fully replaces. Changing `status` here works like [Update User Status](#patch-/v3/users/-vendor_data-/update-status/): it records a `STATUS_CHANGED` activity with the optional `reason`, and `BLOCKED` adds the `vendor_data` to the system blocklist (moving away from `BLOCKED` removes that entry). Changes to `display_name`, `full_name`, `date_of_birth` or `metadata` are recorded as a `PROFILE_UPDATED` activity.", + "operationId": "update_user", + "x-didit-sdk": { + "resource": "users", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the user (free-form string, NOT a UUID).", + "example": "user-abc-123" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/users/user-abc-123/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"display_name\": \"Jane S.\", \"status\": \"FLAGGED\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.patch(\n 'https://verification.didit.me/v3/users/user-abc-123/',\n headers={'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json'},\n json={'display_name': 'Jane S.', 'status': 'FLAGGED'},\n)\nresp.raise_for_status()\nprint(resp.json()['status'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/users/user-abc-123/', {\n method: 'PATCH',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY, 'Content-Type': 'application/json' },\n body: JSON.stringify({ display_name: 'Jane S.', status: 'FLAGGED' }),\n});\nconst user = await resp.json();\nconsole.log(user.status);" + } + ], + "responses": { + "200": { + "description": "User updated. The full updated user record (same shape as Get User) is returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDetailItem" + }, + "examples": { + "Updated": { + "value": { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": "Jane S.", + "full_name": "Jane Elizabeth Smith", + "date_of_birth": "1985-11-22", + "effective_name": "Jane S.", + "status": "FLAGGED", + "metadata": { + "tier": "premium" + }, + "portrait_image_url": "https:///...", + "session_count": 3, + "approved_count": 2, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [ + "USA" + ], + "approved_emails": [ + "john@example.com" + ], + "approved_phones": [ + "+14155551234" + ], + "features": { + "ID_VERIFICATION": "Approved", + "LIVENESS": "Approved", + "FACE_MATCH": "Approved", + "AML": "Approved" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-01T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "tag": { + "uuid": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b", + "name": "VIP", + "color": "#2567FF", + "description": null, + "source": "custom", + "created_at": "2025-05-01T09:00:00Z", + "updated_at": "2025-05-01T09:00:00Z" + }, + "added_by_email": "analyst@acme.com", + "added_by_name": "Jane Analyst", + "created_at": "2025-06-02T11:00:00Z" + } + ], + "comments": [], + "created_at": "2025-06-01T08:00:00Z", + "updated_at": "2025-06-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Invalid body — typically an unknown `status` or a `date_of_birth` not in `YYYY-MM-DD`.", + "content": { + "application/json": { + "examples": { + "Bad status": { + "value": { + "status": [ + "\"INVALID\" is not a valid choice." + ] + } + }, + "Bad date_of_birth": { + "value": { + "date_of_birth": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + }, + "Reason too long": { + "value": { + "reason": [ + "Ensure this field has no more than 1000 characters." + ] + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "Invalid or revoked API key, or the key cannot update users for this application. Note: on this endpoint an *invalid* key returns `403` (not `401`).", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No user with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "full_name": { + "type": "string", + "nullable": true, + "maxLength": 512, + "description": "Full name of the user" + }, + "display_name": { + "type": "string", + "nullable": true, + "description": "Custom display name for this user" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth in YYYY-MM-DD format" + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "User lifecycle status. A change is recorded as a `STATUS_CHANGED` activity, and `BLOCKED` adds the `vendor_data` to the system blocklist (moving away from `BLOCKED` removes that entry)." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Arbitrary JSON object you attach to the user. **Fully replaces** the existing metadata on update — merge client-side if you only want to add keys." + }, + "approved_emails": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Verified email addresses for this user, e.g. `[\"john@example.com\"]`. Fully replaces the stored list." + }, + "approved_phones": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Verified phone numbers for this user. Fully replaces the stored list." + }, + "issuing_states": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Issuing countries (ISO 3166-1 alpha-3), e.g. `[\"USA\"]`. Fully replaces the stored list." + }, + "reason": { + "type": "string", + "nullable": true, + "maxLength": 1000, + "description": "Why the status changed. Recorded on the status-change entry of the user's activity (`comments[].comment`, and **Activity** in the Business Console), only when the status actually changes. Webhooks do not carry it." + } + } + }, + "examples": { + "Rename": { + "summary": "Set display name", + "value": { + "display_name": "Jane S." + } + }, + "Override status": { + "summary": "Flag the user for review", + "value": { + "status": "FLAGGED" + } + }, + "Add metadata": { + "summary": "Replace metadata JSON", + "value": { + "metadata": { + "tier": "premium", + "risk_level": "low" + } + } + }, + "Fix extracted DOB": { + "summary": "Correct extracted profile fields", + "value": { + "full_name": "Jane Elizabeth Smith", + "date_of_birth": "1985-11-22" + } + }, + "Flag with reason": { + "summary": "Flag the user and record why", + "value": { + "status": "FLAGGED", + "reason": "Address does not match the billing country" + } + } + } + } + } + } + } + }, + "/v3/users/{vendor_data}/generate-pdf/": { + "get": { + "summary": "Download the full user history PDF report", + "description": "Render and download one compliance-ready PDF containing the full verification history of the user identified by `vendor_data`. It is the same document the Console produces from **User detail → Actions → Download PDF**, so periodic compliance exports no longer need a person clicking in the Console.\n\n**What the report contains.** A cover page with the profile summary (name, date of birth, issuing states, approved emails and phones), the latest status of every feature, the session counters and an index of the sessions included — followed by the standard report of every included session, in chronological order (oldest first). Each session report is the same document `GET /v3/session/{sessionId}/generate-pdf/` returns for that session.\n\n**Eligible sessions.** Only User Verification (KYC) sessions in `Approved`, `Declined`, `In Review` or `Kyc Expired` are included; sessions in any other status are skipped. If the user has no session in one of those statuses the endpoint returns `403` — see below.\n\n**Cap.** The report is capped at the **20 most recent** reportable sessions. When a user has more, the newest 20 are included and the cover page states how many older sessions were left out. Use `GET /v3/sessions/?vendor_data=…` for the complete list as JSON.\n\n**Branding.** If your application has white-label customization, the cover and every session report are rendered with your logo and privacy-policy URL; otherwise they carry Didit branding. Reports are rendered in English — there is no language parameter.\n\n**No caching.** Every call re-renders the PDF from the current data, so a report generated after a manual review reflects the reviewer's decision. Two calls for the same user can produce byte-different files — archive the downloaded file if you need an immutable copy.\n\n**Latency.** Rendering is synchronous and downloads every stored image of every included session, so a user with many media-heavy sessions can take tens of seconds. Use a generous client read timeout (120 s recommended) and stream the body to disk. Generation stops at a server-side time budget instead of running until your client gives up: if that budget is reached, the response is still a valid PDF containing the most recent sessions, and the cover states how many were omitted.\n\n**Scope.** The user is resolved inside the application that owns the API key. A key can never export a user of another application — such a user does not exist for it and the endpoint returns `404`.\n\n**Trailing slash.** The canonical route ends with a trailing slash (`…/generate-pdf/`). A request without it is served directly with the same response — there is no `301` redirect, so no client has to follow redirects and `curl` does not need `-L`. Use the slashed URL as in the samples.", + "operationId": "get_user_generate_pdf", + "x-didit-sdk": { + "resource": "users", + "method": "generate_pdf", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the user — a free-form string (NOT a UUID). This is the same value you passed as `vendor_data` when creating the user's sessions, matched exactly as sent. URL-encode it; a value containing `/` cannot be addressed by this route.", + "example": "user-abc-123" + } + ], + "responses": { + "200": { + "description": "The rendered PDF document, returned directly as binary `application/pdf` — there is no JSON wrapper and no download-URL indirection. The body starts with the `%PDF` magic bytes. Save it to a `.pdf` file or stream it through to your caller.", + "headers": { + "Content-Disposition": { + "description": "`attachment; filename=user_{didit_internal_id}.pdf` — the file is named after the user's stable Didit identifier (`didit_internal_id` on `GET /v3/users/{vendor_data}/`), not after `vendor_data`.", + "schema": { + "type": "string", + "example": "attachment; filename=user_f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418.pdf" + } + }, + "Content-Length": { + "description": "Total size of the PDF in bytes; the body is not chunked.", + "schema": { + "type": "integer", + "example": 148213 + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "Authorization or eligibility failure. Distinguish the two by the `detail` string:\n\n- `\"You do not have permission to perform this action.\"` — the API key is invalid or revoked, or the request carries a Console user token instead of an application API key.\n- `\"This user has no sessions in review, declined, approved or kyc expired to report.\"` — the user exists in your application but none of its sessions is in a reportable status yet (for example every session is still `In Progress`, or the user was created through `POST /v3/users/create/` and has never verified). Wait for a `status.updated` webhook before requesting the report.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "This user has no sessions in review, declined, approved or kyc expired to report." + } + } + }, + "examples": { + "Invalid credentials": { + "summary": "Revoked or invalid API key, or a Console user token", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "No reportable sessions": { + "summary": "User exists but has no Approved / Declined / In Review / Kyc Expired session", + "value": { + "detail": "This user has no sessions in review, declined, approved or kyc expired to report." + } + } + } + } + } + }, + "404": { + "description": "No user with the supplied `vendor_data` exists in the application behind the API key. Users of other applications are invisible to this key and return the same `404`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "User not found." + } + } + }, + "examples": { + "Unknown user": { + "summary": "vendor_data does not match any user of this application", + "value": { + "detail": "User not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. PDF generation has a dedicated limit of **50 requests per minute per credential** (API key, Bearer token, or source IP when unauthenticated), shared between this endpoint and `GET /v3/session/{sessionId}/generate-pdf/`, in addition to the global 600/min GET limit. Wait `Retry-After` seconds (also reflected in the `X-RateLimit-*` headers), then retry. Reports are not cached server-side, so download each report once and store the file instead of re-fetching it.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying the request.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Maximum number of PDF generations allowed in the current window (50).", + "schema": { + "type": "integer", + "example": 50 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests remaining in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix timestamp (seconds) at which the current window resets.", + "schema": { + "type": "integer", + "example": 1750000000 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable explanation of the rate-limit breach.", + "example": "Session PDF generation rate limit exceeded. You can make up to 50 requests per minute." + } + } + }, + "examples": { + "Throttled": { + "summary": "More than 50 PDF generations in one minute from the same credential", + "value": { + "detail": "Session PDF generation rate limit exceeded. You can make up to 50 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --fail \\\n 'https://verification.didit.me/v3/users/user-abc-123/generate-pdf/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n --output user-history.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"https://verification.didit.me/v3/users/user-abc-123/generate-pdf/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n stream=True,\n timeout=120,\n)\nresponse.raise_for_status()\nwith open(\"user-history.pdf\", \"wb\") as fh:\n for chunk in response.iter_content(chunk_size=8192):\n fh.write(chunk)" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "import { writeFile } from 'node:fs/promises';\n\nconst vendorData = encodeURIComponent('user-abc-123');\nconst response = await fetch(\n `https://verification.didit.me/v3/users/${vendorData}/generate-pdf/`,\n { headers: { 'x-api-key': process.env.DIDIT_API_KEY } },\n);\nif (!response.ok) throw new Error(`PDF generation failed: HTTP ${response.status}`);\nawait writeFile('user-history.pdf', Buffer.from(await response.arrayBuffer()));" + } + ] + } + }, + "/v3/users/delete/": { + "post": { + "summary": "Batch delete users", + "description": "**Permanently delete** users: the user records are removed permanently. For everyday blocking prefer [Update User Status](#patch-/v3/users/-vendor_data-/update-status/) with `BLOCKED`. Select users with `vendor_data_list`, with `didit_internal_id_list`, or with both at once; or pass `delete_all: true`. This endpoint never narrows `delete_all` with filters. The Console's People filters are refused with a `400` (see the `400` response), so a narrowed intent does not turn into a full wipe. Two exceptions: `filter_has_approved_sessions` and `filter_has_declined_sessions` are not refused and not applied, so with `delete_all: true` they still delete every user; do not send them.\n\n**Biometric templates:** every image-free biometric template retained for these users after session deletion (see `/v3/biometric-templates/`) is purged before the user is deleted, so the person stops matching in duplicate detection and Face Search. If a template cannot be purged because a biometric store is unavailable, the call returns `503`, no user in the request is deleted, and repeating the request finishes the job.\n\n**Filed regulatory reports:** if the selected data is in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "batch_delete_users", + "x-didit-sdk": { + "resource": "users", + "method": "batch_delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Provide `vendor_data_list`, `didit_internal_id_list`, or `delete_all: true`. The two lists can be combined and are OR'd. When `delete_all` is `true` it takes precedence and both lists are ignored.", + "properties": { + "vendor_data_list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Your own identifiers to delete, matched exactly as sent." + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit internal user IDs to delete - the `didit_internal_id` returned by the create and get endpoints." + }, + "delete_all": { + "type": "boolean", + "default": false, + "description": "If `true`, deletes every user in the application." + } + } + }, + "examples": { + "Specific users": { + "summary": "Delete users by vendor_data", + "value": { + "vendor_data_list": [ + "user-123", + "user-456" + ] + } + }, + "By Didit internal ID": { + "summary": "Delete users by didit_internal_id", + "value": { + "didit_internal_id_list": [ + "3f1c0d3e-8f0a-4c2e-9a3d-9b1f5f4a2c11" + ] + } + }, + "All users": { + "summary": "Wipe every user in this application", + "value": { + "delete_all": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/users/delete/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vendor_data_list\": [\"user-123\", \"user-456\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/users/delete/',\n headers={'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json'},\n json={'vendor_data_list': ['user-123', 'user-456']},\n)\nresp.raise_for_status()\nprint('deleted', resp.json()['deleted'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/users/delete/', {\n method: 'POST',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY, 'Content-Type': 'application/json' },\n body: JSON.stringify({ vendor_data_list: ['user-123', 'user-456'] }),\n});\nconst body = await resp.json();\nconsole.log('deleted', body.deleted);" + } + ], + "responses": { + "200": { + "description": "Users deleted. Returns the number of users actually deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deleted": { + "type": "integer", + "description": "Number of users deleted (excludes vendor_data values that didn't match anything)." + } + } + }, + "examples": { + "Deleted some": { + "value": { + "deleted": 2 + } + }, + "Nothing matched": { + "value": { + "deleted": 0 + } + } + } + } + } + }, + "400": { + "description": "Validation error; nothing is deleted. Errors are keyed by field name. With no selector (neither list and `delete_all` not `true`) the body is `{\"vendor_data_list\": [\"This field is required unless delete_all is true.\"]}`; an empty list is refused under its own key with `\"This list may not be empty.\"`. A Console-only filter (`filter_search`, `filter_status`, `filter_country`, `filter_date_from`, `filter_date_to`, `filter_tag`, `filter_latest_verification_status`, `filter_risk`, `filter_has_sessions`, `filter_has_open_alerts`, `filter_aml_monitoring`, `filter_can_sign_in`, `filter_auth_status`, `filter_auth_method` or `filter_auth_mfa_enabled`) is refused whenever it carries a value, even `false`, with an object keyed by each such field. Any other unknown `filter_*` key is refused with `\"Unknown filter.\"`.", + "content": { + "application/json": { + "examples": { + "Missing selector": { + "summary": "Neither list nor delete_all provided", + "value": { + "vendor_data_list": [ + "This field is required unless delete_all is true." + ] + } + }, + "Console-only filter": { + "value": { + "filter_has_sessions": "This filter is Console-only and is not supported by this endpoint." + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "Invalid or revoked API key, or the key cannot delete users for this application. Note: on this endpoint an *invalid* key returns `403` (not `401`).", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + }, + "503": { + "description": "A biometric store was unavailable while retaining or purging a biometric template. Nothing is deleted; repeat the same request. Retries are idempotent and converge on a single template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Retryable": { + "value": { + "detail": "The biometric-template operation could not finish because an external store is unavailable. The operation is retryable." + } + } + } + } + } + }, + "409": { + "description": "The selection includes a person in a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/sessions/delete/": { + "post": { + "summary": "Bulk delete KYC sessions by session_number", + "description": "Bulk delete User Verification (KYC) sessions by their numeric `session_number`, or every KYC session in your application with `delete_all: true`.\n\n`session_number` is the human-friendly incrementing counter shown in the Console session list and returned as `session_number` by the create-session and decision endpoints; it is **not** the `session_id` UUID. To delete a session by UUID, or to delete a Business Verification (KYB) session, which this endpoint never touches, use `DELETE /v3/session/{sessionId}/delete/` instead.\n\n**Up to 20 sessions are deleted in the request, more run as a job.** The number of matching sessions decides, whether they are selected by `session_numbers` or with `delete_all`:\n- Up to 20 matching sessions: they are deleted before the response, which is `200` with one entry per session in `results[]`.\n- More than 20: the response is `202` with a deletion job, and the sessions are deleted in the background. Poll `GET /v3/sessions/delete/{job_id}/` until `status` is `COMPLETED` or `FAILED`. A job reports counts per outcome, never which sessions it deleted. A `delete_all` job deletes only the sessions created before the request; sessions started while it runs are not deleted.\n\nEach deleted session gets exactly the same treatment as the single-session delete endpoint: the session, every verification attempt it holds, its decision and extracted data, its related feature records (ID verification, face, liveness, face match, and so on) and all of its stored files are deleted, it disappears from every read endpoint, and previously issued media URLs stop resolving. Webhook deliveries still queued for a deleted session, and their retries, are dropped; webhooks already delivered are not affected. Blocklist entries are **not** removed, credits already consumed are **not** refunded, and the parent User entities are **not** deleted (use `POST /v3/users/delete/` for those); no webhook is emitted for the deletion. Deletion is permanent and irreversible.\n\n**Selection semantics:**\n- With `delete_all: true` you can narrow the selection with the filters of `GET /v3/sessions/`, each prefixed with `filter_` (for example `filter_status`, `filter_workflow_id`, `filter_date_from`). Filters that take several values in List Sessions (`status`, `workflow_id`, `country`, `region`, `document_type`, `document_name`, `session_type`, `warning`, `organization_name`) accept a comma-separated string or an array; the others take one value. An empty value is ignored. Filters apply only together with `delete_all`, and a deletion job applies the same filters.\n- An unknown `filter_*` key, or a filter value that cannot be read, rejects the whole request with `400` before anything is deleted, so a mistyped filter never widens the deletion to every session.\n- Entries in `session_numbers` that do not match a live KYC session in your application (unknown numbers, already-deleted sessions, or sessions owned by another application) are **silently skipped**: they do not appear in `results` and are not counted in a job's `total`.\n- Validation failures (a non-numeric entry, an empty list, neither `session_numbers` nor `delete_all` provided, or a template retention the application cannot honour) reject the **whole request** with `400` and delete nothing.\n- Sessions are deleted in groups. If a group cannot be deleted (for example because a storage service is unavailable), every session in that group is reported as `face_retention_outcome: failed_retryable` and stays in place; the other groups are unaffected. Repeat the request for those sessions. A job retries such a group by itself.\n- There is no documented cap on the list length; the shared write rate limit (below) is the practical bound.\n\n**Face biometric data:** the optional `retain_face_embeddings`, `face_retention_days`, `face_retention_deadline`, `deletion_instruction`, and `instruction_id` fields have the same meaning as on `DELETE /v3/session/{sessionId}/delete/` and apply to every matched session. Each result reports its own `face_retention_outcome`.\n\n**Response:** `200 OK` with `results[]` for up to 20 matching sessions, or `202 Accepted` with a deletion job for more. Every result confirms deletion except `face_retention_outcome: failed_retryable`, which reports a session that was NOT deleted and should be retried. Earlier versions of this endpoint returned `204 No Content`; update clients that assert on `204`.\n\n**Idempotency:** repeating the same request deletes nothing more: numbers that no longer match a session are skipped.\n\n**`delete_all: true` (destructive):** deletes **every** remaining KYC session in the application; `session_numbers` is ignored when set. There is no confirmation step and no undo.\n\n**Authentication:** API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`); this API never returns `401`.\n\n**Rate limit:** shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.\n\nThis endpoint accepts API keys only. Business Console user Bearer tokens are rejected with `403` (unlike the single-session delete, which accepts both).\n\n**Filed regulatory reports:** if the selected data is in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "batch_delete_sessions", + "x-didit-sdk": { + "resource": "sessions", + "method": "batch_delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_numbers": { + "type": "array", + "description": "Numeric `session_number` values of the KYC sessions to delete, as digit-only strings (JSON numbers are also accepted and coerced). Required unless `delete_all` is `true`; an empty list returns `400`. Any non-digit entry rejects the whole request with `400` (unless `delete_all` is `true`, in which case validation is skipped and the entries are ignored). Numbers that do not match a live session in your application are silently skipped.", + "items": { + "type": "string", + "pattern": "^[0-9]+$", + "example": "1001" + }, + "minItems": 1, + "example": [ + "1001", + "1002", + "1003" + ] + }, + "delete_all": { + "type": "boolean", + "description": "When `true`, every remaining KYC session in the calling application is deleted and `session_numbers` is ignored, unless you narrow the selection with `filter_*` keys. More than 20 matching sessions run as a deletion job (`202`) that covers only the sessions created before the request. Defaults to `false`. Irreversible; there is no confirmation step.", + "default": false, + "example": false + }, + "retain_face_embeddings": { + "type": "boolean", + "nullable": true, + "description": "Override the application's `face_retention_policy` for this deletion. `true` keeps one image-free face biometric template anchored to the session's User after the session is deleted; `false` deletes the face embedding with the session; omit or `null` to follow the application policy. Ignored for KYB sessions and for sessions without a face embedding.", + "example": true + }, + "face_retention_days": { + "type": "integer", + "minimum": 1, + "maximum": 3650, + "description": "Finite retention duration, in days, for a retained template. Required when a template is retained unless the application already sets `face_retention_days`. The template's `expires_at` is the earliest of this duration, the application's general data-retention window, and `face_retention_deadline`.", + "example": 365 + }, + "face_retention_deadline": { + "type": "string", + "format": "date-time", + "description": "Optional hard expiry for a retained template. Must be in the future.", + "example": "2027-08-28T00:00:00Z" + }, + "deletion_instruction": { + "type": "string", + "enum": [ + "operational_session_delete", + "privacy_erasure" + ], + "default": "operational_session_delete", + "description": "The class of instruction you are giving. `operational_session_delete` deletes the session and retains a template only when the policy or `retain_face_embeddings` says so. `privacy_erasure` purges every retained biometric template anchored to the session's User before deleting the session, cannot be overridden by the application policy, and cannot be combined with `retain_face_embeddings: true` (`400`).", + "example": "operational_session_delete" + }, + "instruction_id": { + "type": "string", + "maxLength": 255, + "description": "Your durable reference for this deletion instruction (for example an erasure-ticket id). Recorded on the audit trail and on any retained template. Generated by Didit when omitted.", + "example": "dsar-2026-0142" + }, + "filter_status": { + "description": "Narrows a `delete_all` to the sessions that match the `status` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_vendor_data": { + "description": "Narrows a `delete_all` to the sessions that match the `vendor_data` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated text, so several values are not matched as alternatives. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string" + }, + "filter_search": { + "description": "Narrows a `delete_all` to the sessions that match the `search` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated text, so several values are not matched as alternatives. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string" + }, + "filter_workflow_id": { + "description": "Narrows a `delete_all` to the sessions that match the `workflow_id` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_date_from": { + "description": "Narrows a `delete_all` to the sessions that match the `date_from` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string", + "format": "date" + }, + "filter_date_to": { + "description": "Narrows a `delete_all` to the sessions that match the `date_to` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string", + "format": "date" + }, + "filter_country": { + "description": "Narrows a `delete_all` to the sessions that match the `country` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_document_type": { + "description": "Narrows a `delete_all` to the sessions that match the `document_type` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_document_name": { + "description": "Narrows a `delete_all` to the sessions that match the `document_name` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_region": { + "description": "Narrows a `delete_all` to the sessions that match the `region` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_didit_internal_id": { + "description": "Narrows a `delete_all` to the sessions that match the `didit_internal_id` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string", + "format": "uuid" + }, + "filter_session_type": { + "description": "Narrows a `delete_all` to the sessions that match the `session_type` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_api_service": { + "description": "Narrows a `delete_all` to the sessions that match the `api_service` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string" + }, + "filter_screened_full_name": { + "description": "Narrows a `delete_all` to the sessions that match the `screened_full_name` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated text, so several values are not matched as alternatives. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string" + }, + "filter_warning": { + "description": "Narrows a `delete_all` to the sessions that match the `warning` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_status_initialized_as_in_review": { + "description": "Narrows a `delete_all` to the sessions that match the `status_initialized_as_in_review` filter of [List Sessions](/sessions-api/list-sessions), with the same values. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "boolean" + }, + "filter_is_kyc_to_be_reviewed": { + "description": "Narrows a `delete_all` to the sessions that match the `is_kyc_to_be_reviewed` filter of [List Sessions](/sessions-api/list-sessions), with the same values. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "boolean" + }, + "filter_euuid": { + "description": "Narrows a `delete_all` to the sessions that match the `euuid` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string", + "format": "uuid" + }, + "filter_organization_name": { + "description": "Narrows a `delete_all` to the sessions that match the `organization_name` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. With an API key it can only match your own organization's name, so it selects all of your sessions or none. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + } + }, + "examples": { + "Specific sessions": { + "summary": "Delete specific sessions by number", + "value": { + "session_numbers": [ + "1001", + "1002", + "1003" + ] + } + }, + "All sessions": { + "summary": "Delete every KYC session in the application", + "value": { + "delete_all": true + } + }, + "Delete and retain templates": { + "summary": "Delete sessions but keep image-free biometric templates for 365 days", + "value": { + "session_numbers": [ + "1001", + "1002" + ], + "retain_face_embeddings": true, + "face_retention_days": 365, + "instruction_id": "post-approval-cleanup-2026-08" + } + }, + "Privacy erasure": { + "summary": "Delete sessions and purge every retained template for their Users", + "value": { + "session_numbers": [ + "1001" + ], + "deletion_instruction": "privacy_erasure", + "instruction_id": "dsar-2026-0142" + } + }, + "Filtered delete_all": { + "summary": "Delete every Declined session of one workflow created in September 2026", + "value": { + "delete_all": true, + "filter_status": [ + "Declined" + ], + "filter_workflow_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "filter_date_from": "2026-09-01", + "filter_date_to": "2026-09-30" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Up to 20 sessions matched and were processed in the request. One result per matched session; numbers that matched nothing (unknown or already deleted) are skipped and do not appear. A result confirms deletion except when `face_retention_outcome` is `failed_retryable`: that session was NOT deleted and should be retried.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The deleted session." + }, + "session_number": { + "type": "integer", + "nullable": true, + "description": "The deleted session's number." + }, + "face_retention_outcome": { + "type": "string", + "enum": [ + "retained_with_user", + "deleted", + "none", + "ineligible_no_vendor_user", + "failed_retryable" + ], + "description": "What happened to the session's face biometric data. `deleted`: the face embedding was deleted with the session (default). `retained_with_user`: the session is deleted and one image-free biometric template stays anchored to the User; see `biometric_template_uuid`. `none`: the session had no face embedding (for example a document-only KYC session without a liveness selfie; KYB sessions are never selected by this endpoint). `ineligible_no_vendor_user`: retention was requested but the session has no linked User, so the embedding was deleted with the session. `failed_retryable` (batch endpoint only): the group this session was deleted with could not be deleted (a storage service, a biometric store, or a monitoring provider was unavailable); this session is still in place, repeat the request for it." + }, + "biometric_template_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Id of the retained biometric template when `face_retention_outcome` is `retained_with_user`; `null` otherwise. Manage it through `/v3/biometric-templates/{template_uuid}/`." + }, + "biometric_template_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the retained biometric template is deleted, when `face_retention_outcome` is `retained_with_user`; `null` when no template is retained." + }, + "deleted_child_verifications": { + "type": "integer", + "description": "Always `0` on this endpoint, which deletes KYC sessions only. Deleting a Business Verification (KYB) session with `DELETE /v3/session/{sessionId}/delete/` reports the verifications it created that were deleted with it." + } + } + } + } + } + }, + "examples": { + "Mixed outcomes": { + "value": { + "results": [ + { + "session_id": "4c5c7f3a-3b1d-4b7e-9c39-2b4f7e6a1d02", + "session_number": 1001, + "face_retention_outcome": "deleted", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + }, + { + "session_id": "b8e1d2c3-4f5a-4b6c-8d7e-9f0a1b2c3d4e", + "session_number": 1002, + "face_retention_outcome": "retained_with_user", + "biometric_template_uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "biometric_template_expires_at": "2027-10-09T10:15:00Z", + "deleted_child_verifications": 0 + }, + { + "session_id": "7a1e9c2d-5f4b-4c3a-8e2d-1b0a9f8e7d6c", + "session_number": 1003, + "face_retention_outcome": "ineligible_no_vendor_user", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + }, + { + "session_id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f", + "session_number": 1004, + "face_retention_outcome": "failed_retryable", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + } + ] + } + } + } + } + } + }, + "202": { + "description": "More than 20 sessions matched. They are deleted in the background; poll `GET /v3/sessions/delete/{job_id}/` until `status` is `COMPLETED` or `FAILED`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionDeletionJob" + }, + "examples": { + "Job accepted": { + "value": { + "job_id": "5b8f2c1e-7d3a-4f6b-9e2c-1a0d8f7e6c5b", + "status": "PENDING", + "total": 250, + "processed": 0, + "outcomes": { + "deleted": 0, + "none": 0, + "retained_with_user": 0, + "ineligible_no_vendor_user": 0, + "failed_retryable": 0, + "already_deleted": 0 + }, + "deleted_child_verifications": 0, + "error": null, + "created_at": "2026-10-09T10:15:00Z", + "completed_at": null + } + } + } + } + } + }, + "400": { + "description": "Validation error — nothing was deleted. Error values are arrays of message strings keyed by field name.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_numbers": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": { + "description": "Other keys: a `filter_*` key that is not a List Sessions filter (`\"Unknown filter.\"`), or a `filter_*` key whose value cannot be read (keyed by that `filter_*` key, with an array of messages)." + } + }, + "examples": { + "Missing selector": { + "summary": "Neither session_numbers nor delete_all provided", + "value": { + "session_numbers": [ + "This field is required unless delete_all is true." + ] + } + }, + "Empty list": { + "summary": "session_numbers is an empty array", + "value": { + "session_numbers": [ + "This list may not be empty." + ] + } + }, + "Non-numeric entry": { + "summary": "session_numbers contains a non-digit string", + "value": { + "session_numbers": [ + "All session_numbers must be numeric." + ] + } + }, + "Privacy erasure cannot retain": { + "summary": "deletion_instruction privacy_erasure combined with retain_face_embeddings true", + "value": { + "retain_face_embeddings": [ + "Privacy erasure cannot retain biometric templates." + ] + } + }, + "No finite retention duration": { + "summary": "Retention requested without face_retention_days on the request or the application", + "value": { + "detail": "A finite biometric-template retention duration is required." + } + }, + "Unknown filter": { + "summary": "A filter_* key that List Sessions does not have", + "value": { + "filter_reviewer": "Unknown filter." + } + }, + "Unreadable filter value": { + "summary": "A filter value that cannot be read", + "value": { + "filter_date_from": [ + "Enter a valid date." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing or invalid API key. This API returns `403` for authentication failures — never `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid API key": { + "summary": "No x-api-key header, or the key is invalid", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Shared write rate limit exceeded (300 POST/PATCH/DELETE requests per minute per API key). Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "Write budget exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + }, + "409": { + "description": "A selected session is part of a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/sessions/delete/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"session_numbers\": [\"1001\", \"1002\", \"1003\"]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/sessions/delete/\",\n headers={\n \"x-api-key\": \"YOUR_API_KEY\",\n \"Content-Type\": \"application/json\",\n },\n json={\"session_numbers\": [\"1001\", \"1002\", \"1003\"]},\n)\nresponse.raise_for_status()\nif response.status_code == 202: # more than 20 sessions: poll the job\n job_id = response.json()[\"job_id\"]\nelse: # 200: one result per matched session\n print(response.json()[\"results\"])\n\n# Destructive: delete every KYC session in the application\n# requests.post(\n# \"https://verification.didit.me/v3/sessions/delete/\",\n# headers={\"x-api-key\": \"YOUR_API_KEY\"},\n# json={\"delete_all\": True},\n# )" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const response = await fetch(\n 'https://verification.didit.me/v3/sessions/delete/',\n {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ session_numbers: ['1001', '1002', '1003'] }),\n },\n);\nif (response.status !== 200 && response.status !== 202) throw new Error(`HTTP ${response.status}`);\nconst body = await response.json();\n// 200: body.results, one per matched session. 202: poll /v3/sessions/delete/${body.job_id}/" + } + ] + } + }, + "/v3/sessions/delete/{job_id}/": { + "get": { + "summary": "Get a bulk session deletion job", + "description": "Progress of a bulk session deletion that `POST /v3/sessions/delete/` answered with `202` (more than 20 matching sessions, selected by `session_numbers` or with `delete_all`). Poll every few seconds while `status` is `PENDING` or `PROCESSING`; stop when it is `COMPLETED` or `FAILED`.\n\nThe job reports how many sessions ended in each outcome and how many it has processed, never which sessions it deleted. A `FAILED` job carries the reason in `error`; the sessions it had not reached are still in place, so repeat the deletion request to delete them.\n\n**Authentication:** API key in the `x-api-key` header. A job is only visible to the application that created it; any other `job_id` returns `404`. Missing or invalid credentials return `403`; this API never returns `401`.", + "operationId": "get_session_deletion_job", + "x-didit-sdk": { + "resource": "sessions", + "method": "retrieve_deletion_job", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The `job_id` returned with the `202` from `POST /v3/sessions/delete/`." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl 'https://verification.didit.me/v3/sessions/delete/JOB_ID/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import time\nimport requests\n\nurl = 'https://verification.didit.me/v3/sessions/delete/JOB_ID/'\nwhile True:\n job = requests.get(url, headers={'x-api-key': 'YOUR_API_KEY'}).json()\n if job['status'] in ('COMPLETED', 'FAILED'):\n break\n time.sleep(3)\nprint(job['status'], job['outcomes'], job['error'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const url = 'https://verification.didit.me/v3/sessions/delete/JOB_ID/';\nlet job;\ndo {\n await new Promise((r) => setTimeout(r, 3000));\n job = await (await fetch(url, { headers: { 'x-api-key': 'YOUR_API_KEY' } })).json();\n} while (job.status === 'PENDING' || job.status === 'PROCESSING');\nconsole.log(job.status, job.outcomes, job.error);" + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionDeletionJob" + }, + "examples": { + "Processing": { + "value": { + "job_id": "5b8f2c1e-7d3a-4f6b-9e2c-1a0d8f7e6c5b", + "status": "PROCESSING", + "total": 250, + "processed": 100, + "outcomes": { + "deleted": 82, + "none": 18, + "retained_with_user": 0, + "ineligible_no_vendor_user": 0, + "failed_retryable": 0, + "already_deleted": 0 + }, + "deleted_child_verifications": 0, + "error": null, + "created_at": "2026-10-09T10:15:00Z", + "completed_at": null + } + }, + "Completed": { + "value": { + "job_id": "5b8f2c1e-7d3a-4f6b-9e2c-1a0d8f7e6c5b", + "status": "COMPLETED", + "total": 250, + "processed": 250, + "outcomes": { + "deleted": 204, + "none": 44, + "retained_with_user": 0, + "ineligible_no_vendor_user": 0, + "failed_retryable": 0, + "already_deleted": 2 + }, + "deleted_child_verifications": 0, + "error": null, + "created_at": "2026-10-09T10:15:00Z", + "completed_at": "2026-10-09T10:16:40Z" + } + }, + "Failed": { + "value": { + "job_id": "5b8f2c1e-7d3a-4f6b-9e2c-1a0d8f7e6c5b", + "status": "FAILED", + "total": 250, + "processed": 100, + "outcomes": { + "deleted": 82, + "none": 18, + "retained_with_user": 0, + "ineligible_no_vendor_user": 0, + "failed_retryable": 0, + "already_deleted": 0 + }, + "deleted_child_verifications": 0, + "error": "The deletion stopped before it finished. The sessions it had not reached are still in place; repeat the request to delete them.", + "created_at": "2026-10-09T10:15:00Z", + "completed_at": "2026-10-09T10:15:52Z" + } + } + } + } + } + }, + "403": { + "description": "Missing or invalid API key. This API returns `403` for authentication failures, never `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid API key": { + "summary": "No x-api-key header, or the key is invalid", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No deletion job with this `job_id` exists for the calling application. Jobs are only visible to the application that created them." + } + } + } + }, + "/v3/sessions/{session_id}/reviews/": { + "get": { + "summary": "List session reviews and activity log", + "description": "Return the audit trail / activity feed for a User Verification (KYC) session, newest first, in a paginated envelope (`count` / `next` / `previous` / `results`, 50 entries per page by default).\n\nFeed entries are written by every actor that touches the session: manual decisions made through `PATCH /v3/session/{session_id}/update-status/` (which records `previous_status` → `new_status`), Console reviewer activity (comments with `@email` mentions, KYC/POA data edits, file uploads/removals, tag changes, detail-page views), AML ongoing-monitoring matches, and notes appended via `POST` on this same path. Reading this feed is the API-side equivalent of the **Activity** panel in the Didit Console session view.\n\nOnly User Verification (KYC) sessions are addressable here; Business Verification (KYB) session IDs return `404`.", + "operationId": "list_session_reviews", + "x-didit-sdk": { + "resource": "sessions.reviews", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session. Must belong to the same application as the API key — cross-application UUIDs, deleted sessions, and Business Verification (KYB) session IDs all return `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Page size. Defaults to `50`.", + "schema": { + "type": "integer", + "default": 50, + "example": 50 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of entries to skip from the start of the (ordered) feed. Defaults to `0`. The `next` / `previous` URLs in the response carry the right values for walking the feed.", + "schema": { + "type": "integer", + "default": 0, + "example": 0 + } + }, + { + "name": "ordering", + "in": "query", + "required": false, + "description": "Sort order. The feed defaults to newest first (`-created_at`); pass `created_at` to read it oldest first. Unknown values are silently ignored (default order is used); other serializer field names are also accepted.", + "schema": { + "type": "string", + "default": "-created_at", + "example": "created_at" + } + } + ], + "responses": { + "200": { + "description": "Paginated activity feed, newest first by default. `count` is the total number of entries; `next` / `previous` are ready-made page URLs (`null` at the ends). A `count` of `0` with empty `results` means no activity has been recorded yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of feed entries for this session (across all pages).", + "example": 3 + }, + "next": { + "type": "string", + "nullable": true, + "description": "URL of the next page, or `null` on the last page.", + "example": null + }, + "previous": { + "type": "string", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page.", + "example": null + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ReviewItem" + }, + "description": "Feed entries for this page." + } + } + }, + "examples": { + "DecisionTrail": { + "summary": "Manual approval plus API-added notes", + "value": { + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "uuid": "de0561bc-5583-4409-9171-1a31a4c8a5f4", + "activity_type": "STATUS_UPDATED", + "actor_display": "API Client", + "actor_email": null, + "actor_type": "API_KEY", + "new_status": "Approved", + "previous_status": "In Review", + "comment": "Document re-checked manually; address matches.", + "mentioned_emails": [], + "previous_value": { + "status": "In Review" + }, + "new_value": { + "status": "Approved" + }, + "changed_fields": [ + "status" + ], + "metadata": null, + "created_at": "2026-06-12T00:19:31.918735Z" + }, + { + "uuid": "35370f58-5098-44ee-8a06-8c9af80b2455", + "activity_type": "STATUS_UPDATED", + "actor_display": "Unknown", + "actor_email": null, + "actor_type": "CONSOLE_USER", + "new_status": null, + "previous_status": null, + "comment": "Note without status.", + "mentioned_emails": [], + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "created_at": "2026-06-12T00:18:44.504157Z" + }, + { + "uuid": "00338f82-f1c5-4f57-8614-c49bb258af1a", + "activity_type": "STATUS_UPDATED", + "actor_display": "Unknown", + "actor_email": null, + "actor_type": "CONSOLE_USER", + "new_status": "In Review", + "previous_status": null, + "comment": "Flagging for manual document re-check.", + "mentioned_emails": [], + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "created_at": "2026-06-12T00:18:44.466537Z" + } + ] + } + }, + "PaginatedFirstPage": { + "summary": "First page with limit=2 — next holds the follow-up URL", + "value": { + "count": 4, + "next": "https://verification.didit.me/v3/sessions/8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d/reviews/?limit=2&offset=2", + "previous": null, + "results": [ + { + "uuid": "de0561bc-5583-4409-9171-1a31a4c8a5f4", + "activity_type": "STATUS_UPDATED", + "actor_display": "API Client", + "actor_email": null, + "actor_type": "API_KEY", + "new_status": "Approved", + "previous_status": "In Review", + "comment": "Document re-checked manually; address matches.", + "mentioned_emails": [], + "previous_value": { + "status": "In Review" + }, + "new_value": { + "status": "Approved" + }, + "changed_fields": [ + "status" + ], + "metadata": null, + "created_at": "2026-06-12T00:19:31.918735Z" + }, + { + "uuid": "bd4e6227-bdd2-42b2-a726-1d12f785a457", + "activity_type": "STATUS_UPDATED", + "actor_display": "Unknown", + "actor_email": null, + "actor_type": "CONSOLE_USER", + "new_status": null, + "previous_status": null, + "comment": null, + "mentioned_emails": [], + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "created_at": "2026-06-12T00:18:44.540238Z" + } + ] + } + }, + "Empty": { + "summary": "Session with no recorded activity yet", + "value": { + "count": 0, + "next": null, + "previous": null, + "results": [] + } + } + } + } + } + }, + "403": { + "description": "Missing, malformed, expired, or revoked `x-api-key`. Authentication is checked before the session lookup, so an invalid key returns `403` even when the session UUID does not exist.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You do not have permission to perform this action." + } + } + }, + "examples": { + "InvalidKey": { + "summary": "Missing or invalid x-api-key header", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No User Verification (KYC) session with this UUID is visible to your application: the UUID does not exist, the session was deleted, or it belongs to a different application. Business Verification (KYB) session IDs also return `404` here — the reviews feed is only addressable for KYC sessions.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Session not found." + } + } + }, + "examples": { + "NotFound": { + "summary": "Unknown, deleted, or cross-application session UUID", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Inspect `Retry-After` and `X-RateLimit-*` response headers. See [Rate limiting](/integration/rate-limiting)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -s 'https://verification.didit.me/v3/sessions/8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d/reviews/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "Python", + "label": "Python (requests)", + "source": "import os, requests\n\nsession_id = \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\"\nurl = f\"https://verification.didit.me/v3/sessions/{session_id}/reviews/\"\n\nwhile url: # walk every page of the feed\n resp = requests.get(url, headers={\"x-api-key\": os.environ[\"DIDIT_API_KEY\"]}, timeout=10)\n resp.raise_for_status()\n page = resp.json()\n for entry in page[\"results\"]:\n print(entry[\"created_at\"], entry[\"activity_type\"], entry[\"actor_display\"], entry.get(\"new_status\"))\n url = page[\"next\"]" + }, + { + "lang": "JavaScript", + "label": "Node.js (fetch)", + "source": "const sessionId = \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\";\nconst res = await fetch(\n `https://verification.didit.me/v3/sessions/${sessionId}/reviews/`,\n { headers: { 'x-api-key': 'YOUR_API_KEY' } },\n);\nif (!res.ok) throw new Error(`Didit ${res.status}`);\nconst { count, results } = await res.json();\nconsole.log(`${count} feed entries`);\nfor (const entry of results) {\n console.log(entry.created_at, entry.activity_type, entry.actor_display, entry.new_status ?? \"\");\n}" + } + ] + }, + "post": { + "summary": "Add a review note to a session's audit trail", + "description": "Append a note to a User Verification (KYC) session's review / activity feed. **This endpoint only records an entry — it never changes the session.** The session's `status` is left untouched, no status-transition validation runs, and no webhook fires. To actually approve, decline, or re-open a session (which also writes a fully-attributed `STATUS_UPDATED` entry to this feed), use `PATCH /v3/session/{session_id}/update-status/` instead.\n\nBoth body fields are optional and an empty JSON object is accepted. `new_status` is stored purely as a label on the entry and accepts any of the ten session status values (case-sensitive). `@email` mentions inside `comment` are stored as plain text only — they trigger no notifications on this endpoint, and the entry's `mentioned_emails` stays empty.\n\nNote on attribution: entries created here carry no actor information. In the feed they show `actor_display: \"Unknown\"`, `actor_type: \"CONSOLE_USER\"`, and `activity_type: \"STATUS_UPDATED\"` (storage defaults), and the `201` body's `user` field is `\"Unknown\"`.", + "operationId": "create_session_review", + "x-didit-sdk": { + "resource": "sessions.reviews", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "Content-Type", + "in": "header", + "required": false, + "description": "Set to `application/json` when sending a JSON body.", + "schema": { + "type": "string", + "example": "application/json" + } + }, + { + "name": "session_id", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session. Must belong to the same application as the API key — cross-application UUIDs, deleted sessions, and Business Verification (KYB) session IDs all return `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "new_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Optional status label to record on the entry. Accepts any session status value, case-sensitive (`\"approved\"` is rejected with `400`). Recording a status here does **not** change the session's actual status — use the update-status endpoint for that.", + "example": "In Review" + }, + "comment": { + "type": "string", + "nullable": true, + "description": "Optional free-text note (no enforced length limit). Stored verbatim; `@email` mentions are not processed on this endpoint.", + "example": "Flagging for manual document re-check." + } + } + }, + "examples": { + "NoteWithStatusLabel": { + "summary": "Note carrying a status label (session status unchanged)", + "value": { + "new_status": "In Review", + "comment": "Flagging for manual document re-check." + } + }, + "CommentOnly": { + "summary": "Plain note without a status label", + "value": { + "comment": "Customer emailed an updated utility bill; awaiting upload." + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Entry recorded on the session's audit trail. The session itself is unchanged — its `status` keeps its previous value and no webhook is sent. The new entry also appears at the top of `GET …/reviews/` (with `actor_display: \"Unknown\"`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "user": { + "type": "string", + "description": "Display name of the entry's author. Always `\"Unknown\"` for entries created through this endpoint, because it records no actor attribution.", + "example": "Unknown" + }, + "new_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "The status label recorded on the entry, or `null` if none was sent. The session's actual status is not affected.", + "example": "In Review" + }, + "comment": { + "type": "string", + "nullable": true, + "description": "The note recorded on the entry, or `null` if none was sent.", + "example": "Flagging for manual document re-check." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the entry was written, ISO 8601 UTC.", + "example": "2026-06-12T00:18:44.466537Z" + } + } + }, + "examples": { + "NoteWithStatusLabel": { + "summary": "Note with a status label", + "value": { + "user": "Unknown", + "new_status": "In Review", + "comment": "Flagging for manual document re-check.", + "created_at": "2026-06-12T00:18:44.466537Z" + } + }, + "CommentOnly": { + "summary": "Plain note", + "value": { + "user": "Unknown", + "new_status": null, + "comment": "Note without status.", + "created_at": "2026-06-12T00:18:44.504157Z" + } + } + } + } + } + }, + "400": { + "description": "Validation error, returned as a per-field map of messages. The usual cause is a `new_status` value that is not one of the ten allowed statuses — the check is case-sensitive.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "new_status": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "\"approved\" is not a valid choice." + ] + } + } + }, + "examples": { + "InvalidStatusChoice": { + "summary": "Lowercase / unknown status value", + "value": { + "new_status": [ + "\"approved\" is not a valid choice." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, malformed, expired, or revoked `x-api-key`. Authentication is checked before the session lookup, so an invalid key returns `403` even when the session UUID does not exist.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You do not have permission to perform this action." + } + } + }, + "examples": { + "InvalidKey": { + "summary": "Missing or invalid x-api-key header", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No User Verification (KYC) session with this UUID is visible to your application: the UUID does not exist, the session was deleted, or it belongs to a different application. Business Verification (KYB) session IDs also return `404` here — the reviews feed is only addressable for KYC sessions.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Session not found." + } + } + }, + "examples": { + "NotFound": { + "summary": "Unknown, deleted, or cross-application session UUID", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Inspect `Retry-After` and `X-RateLimit-*` response headers. See [Rate limiting](/integration/rate-limiting)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "# Records an audit-trail note only -- the session's status does NOT change.\ncurl -s -X POST https://verification.didit.me/v3/sessions/8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d/reviews/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"new_status\": \"In Review\",\n \"comment\": \"Flagging for manual document re-check.\"\n }'" + }, + { + "lang": "Python", + "label": "Python (requests)", + "source": "import os, requests\n\n# Records an audit-trail note only -- the session's status does NOT change.\n# To approve/decline a session use PATCH /v3/session/{session_id}/update-status/.\nsession_id = \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\"\nresp = requests.post(\n f\"https://verification.didit.me/v3/sessions/{session_id}/reviews/\",\n headers={\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Content-Type\": \"application/json\",\n },\n json={\n \"new_status\": \"In Review\",\n \"comment\": \"Flagging for manual document re-check.\",\n },\n timeout=10,\n)\nresp.raise_for_status()\nprint(resp.json()) # {'user': 'Unknown', 'new_status': 'In Review', ...}" + }, + { + "lang": "JavaScript", + "label": "Node.js (fetch)", + "source": "// Records an audit-trail note only -- the session's status does NOT change.\n// To approve/decline a session use PATCH /v3/session/{session_id}/update-status/.\nconst sessionId = \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\";\nconst res = await fetch(\n `https://verification.didit.me/v3/sessions/${sessionId}/reviews/`,\n {\n method: \"POST\",\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n new_status: \"In Review\",\n comment: \"Flagging for manual document re-check.\",\n }),\n },\n);\nif (!res.ok) throw new Error(`Didit ${res.status}: ${await res.text()}`);\nconsole.log(await res.json()); // { user: 'Unknown', new_status: 'In Review', ... }" + } + ] + } + }, + "/v3/billing/balance/": { + "get": { + "summary": "Get credit balance", + "description": "Get current pre-paid credit balance in USD (organization-wide) plus auto-refill config. Amounts are decimal strings — parse with a decimal library.", + "operationId": "get_billing_balance", + "x-didit-sdk": { + "resource": "billing", + "method": "retrieve_balance", + "auth": "api_key", + "server": true + }, + "tags": [ + "Billing" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/billing/balance/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nfrom decimal import Decimal\n\nimport requests\n\nresp = requests.get(\n 'https://verification.didit.me/v3/billing/balance/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status()\ndata = resp.json()\nbalance = Decimal(data['balance'])\nif balance < Decimal('50'):\n print(f'Low balance: ${balance} - top up before launching new sessions.')\nelse:\n print(f'Balance OK: ${balance}')" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/billing/balance/', {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (!resp.ok) throw new Error(`Billing balance failed: ${resp.status}`);\nconst { balance, auto_refill_enabled, auto_refill_threshold } = await resp.json();\nconsole.log({ balance, auto_refill_enabled, auto_refill_threshold });" + } + ], + "responses": { + "200": { + "description": "Current credit balance plus the organization's auto-refill configuration.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "balance", + "auto_refill_enabled" + ], + "properties": { + "balance": { + "type": "string", + "description": "Current pre-paid credit balance in USD, as a fixed-point decimal string. Decreases as verifications are billed and increases when a top-up settles." + }, + "auto_refill_enabled": { + "type": "boolean", + "description": "When `true`, Didit will automatically charge the saved payment method to add credits once the balance crosses `auto_refill_threshold`. Configured from Console → Billing." + }, + "auto_refill_amount": { + "type": "string", + "nullable": true, + "description": "USD amount that will be charged and added to the balance on each auto-refill, at face value. Set from \\$50 to \\$10,000 in Console → Billing. `null` when auto-refill is disabled or unset." + }, + "auto_refill_threshold": { + "type": "string", + "nullable": true, + "description": "Trigger threshold in USD. Auto-refill fires when `balance` falls below this value. `null` when auto-refill is disabled or unset." + } + } + }, + "examples": { + "Healthy balance with auto-refill": { + "value": { + "balance": "1234.5600", + "auto_refill_enabled": true, + "auto_refill_amount": "500.00", + "auto_refill_threshold": "100.00" + } + }, + "Low balance, manual top-up only": { + "value": { + "balance": "12.0000", + "auto_refill_enabled": false, + "auto_refill_amount": null, + "auto_refill_threshold": null + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read billing data for this organization. This endpoint returns `403` (never `401`) for authentication failures, including requests with no credentials at all.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/billing/top-up/": { + "post": { + "summary": "Top up credits", + "description": "Add credits via a hosted Stripe Checkout session (**\\$50 to \\$10,000** per top-up). Top-ups are available on the Free, Growth and Scale plans, added at face value with no discount or bonus, and last 12 months; Enterprise annual contracts cannot top up. Redirect the customer to the returned `checkout_session_url`.", + "operationId": "billing_top_up", + "x-didit-sdk": { + "resource": "billing", + "method": "top_up", + "auth": "api_key", + "server": true + }, + "tags": [ + "Billing" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "amount_in_dollars" + ], + "properties": { + "amount_in_dollars": { + "type": "number", + "minimum": 50, + "maximum": 10000, + "description": "Amount to charge, in USD. **Must be from 50 to 10,000.** The balance receives the same amount, with no discount or bonus. Tax is added automatically at checkout based on the customer's billing address." + }, + "success_url": { + "type": "string", + "format": "uri", + "description": "URL the hosted checkout redirects the customer to after a successful payment. Currently defaults to `https://console.didit.me`. Pass a working URL explicitly; the default host is unavailable. Append the literal placeholder `{CHECKOUT_SESSION_ID}` if you want the hosted checkout to substitute the session ID." + }, + "cancel_url": { + "type": "string", + "format": "uri", + "description": "URL the hosted checkout redirects the customer to when they abort or close the Checkout window. Currently defaults to `https://console.didit.me`. Pass a working URL explicitly; the default host is unavailable." + } + } + }, + "examples": { + "Minimum top-up": { + "value": { + "amount_in_dollars": 50 + } + }, + "Custom amount with redirects": { + "value": { + "amount_in_dollars": 500, + "success_url": "https://yourapp.com/billing/top-up/success", + "cancel_url": "https://yourapp.com/billing/top-up/cancel" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/billing/top-up/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"amount_in_dollars\": 500,\n \"success_url\": \"https://yourapp.com/billing/success\",\n \"cancel_url\": \"https://yourapp.com/billing/cancel\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/billing/top-up/',\n headers={\n 'x-api-key': os.environ['DIDIT_API_KEY'],\n 'Content-Type': 'application/json',\n },\n json={\n 'amount_in_dollars': 500,\n 'success_url': 'https://yourapp.com/billing/success',\n 'cancel_url': 'https://yourapp.com/billing/cancel',\n },\n timeout=10,\n)\nresp.raise_for_status()\nsession = resp.json()\nprint('Redirect customer to:', session['checkout_session_url'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/billing/top-up/', {\n method: 'POST',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n amount_in_dollars: 500,\n success_url: 'https://yourapp.com/billing/success',\n cancel_url: 'https://yourapp.com/billing/cancel',\n }),\n});\nif (!resp.ok) throw new Error(`Top-up failed: ${resp.status}`);\nconst { checkout_session_url } = await resp.json();\nwindow.location.assign(checkout_session_url);" + } + ], + "responses": { + "200": { + "description": "Hosted checkout session created. Redirect the user to `checkout_session_url` to complete payment.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "checkout_session_id", + "checkout_session_url" + ], + "properties": { + "checkout_session_id": { + "type": "string", + "description": "Hosted checkout session identifier (e.g. `cs_test_a1B2c3...`). Useful for server-side reconciliation." + }, + "checkout_session_url": { + "type": "string", + "format": "uri", + "description": "Single-use hosted payment page URL. Redirect the customer here to complete payment." + } + } + }, + "examples": { + "Created": { + "value": { + "checkout_session_id": "cs_test_a1B2c3D4e5F6g7H8i9J0", + "checkout_session_url": "https://checkout.example.com/c/pay/cs_test_a1B2c3D4e5F6g7H8i9J0" + } + } + } + } + } + }, + "400": { + "description": "Invalid payload: the amount is missing, not a number, below the \\$50 minimum or above the \\$10,000 maximum, or the organization is on an Enterprise annual contract. The error body is a JSON array.", + "content": { + "application/json": { + "examples": { + "Missing amount": { + "value": [ + "amount_in_dollars is required." + ] + }, + "Not a number": { + "value": [ + "amount_in_dollars must be a number." + ] + }, + "Below minimum": { + "value": [ + "Amount must be at least $50." + ] + }, + "Above maximum": { + "value": [ + "Amount must be at most $10,000." + ] + }, + "Annual plan": { + "value": [ + "You have an annual plan. Contact your account manager to top up." + ] + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot create top-up sessions for this organization. This endpoint returns `403` (never `401`) for authentication failures, including requests with no credentials at all.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/": { + "get": { + "summary": "List webhook destinations", + "description": "List all webhook destinations (unpaginated array, oldest first). Each item carries the destination's configuration, its delivery metrics for the last 30 days (`metrics_window_days`) with one `daily_points` entry per day, and its health (`health_status`, `failing_streak`, `failing_since`, `last_success_at`). The list has no `summary` object and never includes the signing secret: each item shows only `secret_last4`. Fetch a single destination (`GET /v3/webhook/destinations/{destination_uuid}/`) to read `secret_shared_key`.", + "operationId": "list_webhook_destinations", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.get(\n 'https://verification.didit.me/v3/webhook/destinations/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status()\nfor dest in resp.json():\n print(f\"{dest['label']:30s} {dest['url']:50s} {dest['health_status']}\")" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/webhook/destinations/', {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nconst destinations = await resp.json();\nfor (const d of destinations) {\n console.log(d.label, d.url, d.health_status, `${d.error_rate_percentage}% errors`);\n}" + } + ], + "responses": { + "200": { + "description": "List of webhook destinations (unpaginated, oldest first).", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Stable destination identifier; use as `{destination_uuid}` for update/delete." + }, + "label": { + "type": "string", + "description": "Human-readable name shown in the Console." + }, + "url": { + "type": "string", + "format": "uri", + "description": "Destination URL that receives `POST` requests with the webhook payload." + }, + "enabled": { + "type": "boolean", + "description": "When `false`, Didit silently skips delivery to this destination." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload schema version sent to this destination. `v3` is the current/recommended format." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + }, + "subscribed_events_count": { + "type": "integer", + "description": "Cached length of `subscribed_events`, useful for UI rendering." + }, + "deliveries_count": { + "type": "integer", + "description": "Delivery attempts (retries included) made to this destination in the last 30 days." + }, + "failed_deliveries_count": { + "type": "integer", + "description": "Delivery attempts in the last 30 days that the destination answered with HTTP 400 or above, or did not answer (timeout, refused connection or blocked address)." + }, + "average_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Average response time in milliseconds over the last 30 days, rounded to an integer. `null` when there were no deliveries in that window." + }, + "error_rate_percentage": { + "type": "integer", + "description": "`round(failed_deliveries_count / deliveries_count * 100)` over the last 30 days; `0` when there are no deliveries." + }, + "last_delivery_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent delivery attempt (success or failure). `null` if never delivered." + }, + "metrics_window_days": { + "type": "integer", + "description": "Length in days of the window that `deliveries_count`, `failed_deliveries_count`, `average_response_time_ms`, `error_rate_percentage` and `daily_points` cover. Always `30`." + }, + "health_status": { + "type": "string", + "enum": [ + "healthy", + "degraded", + "failing" + ], + "description": "Delivery health. `failing`: 3 or more consecutive events failed their first delivery attempt since the destination last answered successfully (see `failing_streak`). `degraded`: not failing, but a delivery attempt failed in the last 24 hours and no later attempt has delivered that event; a retry or resend that delivers it clears this. `healthy`: neither. Independent of `enabled`. A failed attempt is one answered with HTTP 400 or above, or not answered at all." + }, + "failing_streak": { + "type": "integer", + "description": "How many consecutive events failed their first delivery attempt since the destination last answered successfully, within the last 30 days. Retries are not counted, and any successful attempt, including a retry or a resend, ends the streak. `0` when no event has failed since the last success." + }, + "failing_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the first failed attempt of the current `failing_streak` was made. `null` when `failing_streak` is `0`." + }, + "last_success_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the destination last answered a delivery with an HTTP status under 400, within the last 30 days. `null` if it has not in that window." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the destination." + }, + "daily_points": { + "type": "array", + "description": "One entry per UTC day of the last 30 days, oldest first, including days with no deliveries. Counts every attempt, retries included.", + "items": { + "type": "object", + "properties": { + "date": { + "type": "string", + "format": "date", + "description": "The UTC day." + }, + "total_deliveries": { + "type": "integer", + "description": "Delivery attempts made that day." + }, + "failed_deliveries": { + "type": "integer", + "description": "Attempts made that day that were answered with HTTP 400 or above, or not answered at all." + } + } + } + }, + "secret_last4": { + "type": "string", + "description": "Last four characters of the destination's current signing secret, so you can tell secrets apart. Not enough to verify a signature." + }, + "created_by_email": { + "type": "string", + "nullable": true, + "description": "Email of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_by_name": { + "type": "string", + "nullable": true, + "description": "Name of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + }, + "examples": { + "Two destinations": { + "value": [ + { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "subscribed_events_count": 2, + "deliveries_count": 12453, + "failed_deliveries_count": 14, + "average_response_time_ms": 187, + "error_rate_percentage": 0, + "last_delivery_at": "2026-05-17T09:21:18Z", + "metrics_window_days": 30, + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": "2026-05-17T09:21:18Z", + "environment": "live", + "daily_points": [ + { + "date": "2026-04-18", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-19", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-20", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-21", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-22", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-23", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-24", + "total_deliveries": 415, + "failed_deliveries": 9 + }, + { + "date": "2026-04-25", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-26", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-27", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-28", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-29", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-30", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-01", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-02", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-03", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-04", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-05", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-06", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-07", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-08", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-09", + "total_deliveries": 415, + "failed_deliveries": 5 + }, + { + "date": "2026-05-10", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-11", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-12", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-13", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-14", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-15", + "total_deliveries": 416, + "failed_deliveries": 0 + }, + { + "date": "2026-05-16", + "total_deliveries": 416, + "failed_deliveries": 0 + }, + { + "date": "2026-05-17", + "total_deliveries": 416, + "failed_deliveries": 0 + } + ], + "secret_last4": "0xnQ", + "created_by_email": null, + "created_by_name": null, + "created_at": "2026-01-04T08:00:00Z", + "updated_at": "2026-04-22T14:09:11Z" + }, + { + "uuid": "22222222-3333-4444-5555-666666666666", + "label": "Staging mirror", + "url": "https://staging.yourapp.com/webhooks/didit", + "enabled": false, + "webhook_version": "v3", + "subscribed_events": [ + "status.updated" + ], + "subscribed_events_count": 1, + "deliveries_count": 0, + "failed_deliveries_count": 0, + "average_response_time_ms": null, + "error_rate_percentage": 0, + "last_delivery_at": null, + "metrics_window_days": 30, + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": null, + "environment": "live", + "daily_points": [ + { + "date": "2026-04-18", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-19", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-20", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-21", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-22", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-23", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-24", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-25", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-26", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-27", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-28", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-29", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-30", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-01", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-02", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-03", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-04", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-05", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-06", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-07", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-08", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-09", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-10", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-11", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-12", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-13", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-14", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-15", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-16", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-17", + "total_deliveries": 0, + "failed_deliveries": 0 + } + ], + "secret_last4": "k9Qe", + "created_by_email": "reviewer@example.com", + "created_by_name": "Example Reviewer", + "created_at": "2026-03-12T10:15:02Z", + "updated_at": "2026-03-12T10:15:02Z" + } + ] + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. Didit returns 403 (never 401) for all authentication failures on this endpoint.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create webhook destination", + "description": "Create a webhook destination. The response includes the auto-generated `secret_shared_key` HMAC signing secret (also readable later via `GET /v3/webhook/destinations/{destination_uuid}/`). Always send `subscribed_events` with at least one event (no wildcards): an explicit empty list is rejected with 400, and omitting the field creates a destination subscribed to nothing, which never receives a webhook. The `(application, url)` pair must be unique among the application's destinations.", + "operationId": "create_webhook_destination", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/webhook/destinations/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"label\": \"Production session webhooks\",\n \"url\": \"https://yourapp.com/webhooks/didit\",\n \"webhook_version\": \"v3\",\n \"subscribed_events\": [\"status.updated\", \"data.updated\"]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/webhook/destinations/',\n headers={\n 'x-api-key': os.environ['DIDIT_API_KEY'],\n 'Content-Type': 'application/json',\n },\n json={\n 'label': 'Production session webhooks',\n 'url': 'https://yourapp.com/webhooks/didit',\n 'webhook_version': 'v3',\n 'subscribed_events': ['status.updated', 'data.updated'],\n },\n timeout=10,\n)\nresp.raise_for_status()\ndestination = resp.json()\n# Store secret_shared_key in your secret manager.\nprint('Destination UUID:', destination['uuid'])\nprint('Signing secret:', destination['secret_shared_key'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/webhook/destinations/', {\n method: 'POST',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n label: 'Production session webhooks',\n url: 'https://yourapp.com/webhooks/didit',\n webhook_version: 'v3',\n subscribed_events: ['status.updated', 'data.updated'],\n }),\n});\nif (!resp.ok) throw new Error(await resp.text());\nconst destination = await resp.json();\nconsole.log('Store this secret securely:', destination.secret_shared_key);" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "label", + "url" + ], + "properties": { + "label": { + "type": "string", + "maxLength": 255, + "description": "Human-readable name displayed in Console → Webhooks. Helps distinguish destinations when you have several (e.g. \"Prod\", \"Staging mirror\")." + }, + "url": { + "type": "string", + "format": "uri", + "description": "HTTPS URL that will receive `POST` requests. Must be unique among the application's destinations. Use a stable, hardened endpoint: Didit retries failed deliveries on a backoff schedule." + }, + "enabled": { + "type": "boolean", + "default": true, + "description": "When `false`, the destination is saved but Didit will not push any webhooks to it. Useful for staging configs you want to keep parked." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload schema version Didit will send. `v3` is the current/recommended format; older versions are kept for backward compatibility with legacy integrations." + }, + "subscribed_events": { + "allOf": [ + { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + } + ], + "description": "Events to deliver. Effectively required: an explicit `[]` is rejected with 400, and omitting the field creates a destination with no subscriptions that receives nothing." + } + } + }, + "examples": { + "session_updates": { + "summary": "Session status and data updates", + "value": { + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "webhook_version": "v3", + "subscribed_events": [ + "status.updated", + "data.updated" + ] + } + }, + "transaction_updates": { + "summary": "Transaction monitoring updates", + "value": { + "label": "Transaction monitoring webhooks", + "url": "https://yourapp.com/webhooks/didit-transactions", + "webhook_version": "v3", + "subscribed_events": [ + "transaction.created", + "transaction.status.updated" + ] + } + }, + "entity_updates": { + "summary": "User and Business entity updates", + "value": { + "label": "Entity lifecycle webhooks", + "url": "https://yourapp.com/webhooks/didit-entities", + "webhook_version": "v3", + "subscribed_events": [ + "user.status.updated", + "user.data.updated", + "business.status.updated", + "business.data.updated", + "activity.created" + ] + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Destination created. `secret_shared_key` is the HMAC signing secret — store it now.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Stable destination identifier. Use as `{destination_uuid}` in subsequent calls." + }, + "label": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "enabled": { + "type": "boolean" + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ] + }, + "secret_shared_key": { + "type": "string", + "description": "Auto-generated HMAC signing secret bound to this destination (43-character URL-safe token). Use it to verify the signature headers on incoming webhooks. Always returned in full to API-key callers; Console users need the `read:webhooks` permission, otherwise they see an empty string." + }, + "secret_last4": { + "type": "string", + "description": "Last four characters of the destination's current signing secret, so you can tell secrets apart. Not enough to verify a signature." + }, + "previous_secret_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set for 24 hours after the destination's signing secret is rotated in the Business Console. Until this time, every delivery to the destination also carries `X-Signature-Previous`, `X-Signature-V2-Previous` and `X-Signature-Simple-Previous`, made with the replaced secret (see [Signature verification](/integration/webhooks#signature-verification)). `null` when no rotation happened in the last 24 hours." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + }, + "created_by_email": { + "type": "string", + "nullable": true, + "description": "Email of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_by_name": { + "type": "string", + "nullable": true, + "description": "Name of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "health_status": { + "type": "string", + "enum": [ + "healthy", + "degraded", + "failing" + ], + "description": "Delivery health. `failing`: 3 or more consecutive events failed their first delivery attempt since the destination last answered successfully (see `failing_streak`). `degraded`: not failing, but a delivery attempt failed in the last 24 hours and no later attempt has delivered that event; a retry or resend that delivers it clears this. `healthy`: neither. Independent of `enabled`. A failed attempt is one answered with HTTP 400 or above, or not answered at all." + }, + "failing_streak": { + "type": "integer", + "description": "How many consecutive events failed their first delivery attempt since the destination last answered successfully, within the last 30 days. Retries are not counted, and any successful attempt, including a retry or a resend, ends the streak. `0` when no event has failed since the last success." + }, + "failing_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the first failed attempt of the current `failing_streak` was made. `null` when `failing_streak` is `0`." + }, + "last_success_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the destination last answered a delivery with an HTTP status under 400, within the last 30 days. `null` if it has not in that window." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the destination." + }, + "summary": { + "type": "object", + "description": "Delivery metrics for the last `metrics_window_days` (30) days. `last_delivery_at` is the most recent attempt, whenever it was made.", + "properties": { + "total_deliveries": { + "type": "integer" + }, + "failed_deliveries": { + "type": "integer" + }, + "error_rate_percentage": { + "type": "integer" + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true + }, + "last_delivery_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "metrics_window_days": { + "type": "integer", + "description": "Length in days of the window the other summary metrics cover. Always `30`." + } + } + } + } + }, + "examples": { + "Created": { + "value": { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "secret_shared_key": "tno2NTeRC1ZK3sBOYlBJDyEzBVENl3pQ1AcZyAW0xnQ", + "secret_last4": "0xnQ", + "previous_secret_expires_at": null, + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_by_email": null, + "created_by_name": null, + "created_at": "2026-05-17T10:00:00Z", + "updated_at": "2026-05-17T10:00:00Z", + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": null, + "environment": "live", + "summary": { + "total_deliveries": 0, + "failed_deliveries": 0, + "error_rate_percentage": 0, + "min_response_time_ms": null, + "avg_response_time_ms": null, + "max_response_time_ms": null, + "last_delivery_at": null, + "metrics_window_days": 30 + } + } + } + } + } + } + }, + "400": { + "description": "Validation failed — usually a duplicate URL, missing/invalid `subscribed_events`, or unknown event name.", + "content": { + "application/json": { + "examples": { + "Duplicate URL": { + "value": { + "url": [ + "This webhook destination already exists for the application." + ] + } + }, + "Empty events list": { + "value": { + "subscribed_events": [ + "At least one subscribed event is required." + ] + } + }, + "Unknown event": { + "value": { + "subscribed_events": [ + "Unsupported webhook events: foo.bar" + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. Didit returns 403 (never 401) for all authentication failures on this endpoint.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/": { + "get": { + "summary": "Get webhook destination", + "description": "Retrieve one webhook destination with its `secret_shared_key` signing secret, its health (`health_status`, `failing_streak`, `failing_since`, `last_success_at`) and a delivery `summary` for the last 30 days. API-key callers always receive the secret in full; Console users without `read:webhooks` see an empty string.", + "operationId": "get_webhook_destination", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination (the `uuid` returned from create / list).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nuuid = '11111111-2222-3333-4444-555555555555'\nresp = requests.get(\n f'https://verification.didit.me/v3/webhook/destinations/{uuid}/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status()\nprint(resp.json())" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const uuid = '11111111-2222-3333-4444-555555555555';\nconst resp = await fetch(`https://verification.didit.me/v3/webhook/destinations/${uuid}/`, {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (!resp.ok) throw new Error(`Lookup failed: ${resp.status}`);\nconst destination = await resp.json();\nconsole.log(destination);" + } + ], + "responses": { + "200": { + "description": "Webhook destination details.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "enabled": { + "type": "boolean" + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ] + }, + "secret_shared_key": { + "type": "string", + "description": "HMAC signing secret (43-character URL-safe token). Always returned in full to API-key callers; Console users need `read:webhooks`, otherwise an empty string." + }, + "secret_last4": { + "type": "string", + "description": "Last four characters of the destination's current signing secret, so you can tell secrets apart. Not enough to verify a signature." + }, + "previous_secret_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set for 24 hours after the destination's signing secret is rotated in the Business Console. Until this time, every delivery to the destination also carries `X-Signature-Previous`, `X-Signature-V2-Previous` and `X-Signature-Simple-Previous`, made with the replaced secret (see [Signature verification](/integration/webhooks#signature-verification)). `null` when no rotation happened in the last 24 hours." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + }, + "created_by_email": { + "type": "string", + "nullable": true, + "description": "Email of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_by_name": { + "type": "string", + "nullable": true, + "description": "Name of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "health_status": { + "type": "string", + "enum": [ + "healthy", + "degraded", + "failing" + ], + "description": "Delivery health. `failing`: 3 or more consecutive events failed their first delivery attempt since the destination last answered successfully (see `failing_streak`). `degraded`: not failing, but a delivery attempt failed in the last 24 hours and no later attempt has delivered that event; a retry or resend that delivers it clears this. `healthy`: neither. Independent of `enabled`. A failed attempt is one answered with HTTP 400 or above, or not answered at all." + }, + "failing_streak": { + "type": "integer", + "description": "How many consecutive events failed their first delivery attempt since the destination last answered successfully, within the last 30 days. Retries are not counted, and any successful attempt, including a retry or a resend, ends the streak. `0` when no event has failed since the last success." + }, + "failing_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the first failed attempt of the current `failing_streak` was made. `null` when `failing_streak` is `0`." + }, + "last_success_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the destination last answered a delivery with an HTTP status under 400, within the last 30 days. `null` if it has not in that window." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the destination." + }, + "summary": { + "type": "object", + "description": "Delivery metrics for the last `metrics_window_days` (30) days. `last_delivery_at` is the most recent attempt, whenever it was made.", + "properties": { + "total_deliveries": { + "type": "integer" + }, + "failed_deliveries": { + "type": "integer" + }, + "error_rate_percentage": { + "type": "integer" + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true + }, + "last_delivery_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "metrics_window_days": { + "type": "integer", + "description": "Length in days of the window the other summary metrics cover. Always `30`." + } + } + } + } + }, + "examples": { + "Healthy destination": { + "value": { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "secret_shared_key": "tno2NTeRC1ZK3sBOYlBJDyEzBVENl3pQ1AcZyAW0xnQ", + "secret_last4": "0xnQ", + "previous_secret_expires_at": null, + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_by_email": null, + "created_by_name": null, + "created_at": "2026-01-04T08:00:00Z", + "updated_at": "2026-04-22T14:09:11Z", + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": "2026-05-17T09:21:18Z", + "environment": "live", + "summary": { + "total_deliveries": 12453, + "failed_deliveries": 14, + "error_rate_percentage": 0, + "min_response_time_ms": 41, + "avg_response_time_ms": 187, + "max_response_time_ms": 2103, + "last_delivery_at": "2026-05-17T09:21:18Z", + "metrics_window_days": 30 + } + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. Didit returns 403 (never 401) for all authentication failures on this endpoint.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No destination with this UUID exists, or it belongs to a different application, or it has been deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update webhook destination", + "description": "Update a webhook destination. **`subscribed_events` is REPLACED wholesale, not merged**: fetch first, append, resend. Secret is not rotated. A `uuid` in the body is ignored: the path names the destination.", + "operationId": "update_webhook_destination", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination to update.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "label": { + "type": "string", + "description": "New human-readable name." + }, + "url": { + "type": "string", + "format": "uri", + "description": "New target URL. Must be unique within the application." + }, + "enabled": { + "type": "boolean", + "description": "Set to `false` to pause deliveries to this destination without deleting it." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "New payload schema version for future deliveries." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + } + } + }, + "examples": { + "Pause destination": { + "value": { + "enabled": false + } + }, + "Add transaction events": { + "value": { + "subscribed_events": [ + "status.updated", + "data.updated", + "transaction.created", + "transaction.status.updated" + ] + } + }, + "Move target URL": { + "value": { + "label": "Production session webhooks v2", + "url": "https://api.yourapp.com/webhooks/didit/v2" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"subscribed_events\": [\n \"status.updated\",\n \"data.updated\",\n \"transaction.created\"\n ]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nuuid = '11111111-2222-3333-4444-555555555555'\nresp = requests.patch(\n f'https://verification.didit.me/v3/webhook/destinations/{uuid}/',\n headers={\n 'x-api-key': os.environ['DIDIT_API_KEY'],\n 'Content-Type': 'application/json',\n },\n json={'enabled': False},\n timeout=10,\n)\nresp.raise_for_status()\nprint('Paused destination:', resp.json()['label'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const uuid = '11111111-2222-3333-4444-555555555555';\nconst resp = await fetch(`https://verification.didit.me/v3/webhook/destinations/${uuid}/`, {\n method: 'PATCH',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ enabled: false }),\n});\nif (!resp.ok) throw new Error(await resp.text());\nconsole.log(await resp.json());" + } + ], + "responses": { + "200": { + "description": "Updated webhook destination. Body has the same shape as `GET /v3/webhook/destinations/{destination_uuid}/`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "enabled": { + "type": "boolean" + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ] + }, + "secret_shared_key": { + "type": "string" + }, + "secret_last4": { + "type": "string", + "description": "Last four characters of the destination's current signing secret, so you can tell secrets apart. Not enough to verify a signature." + }, + "previous_secret_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set for 24 hours after the destination's signing secret is rotated in the Business Console. Until this time, every delivery to the destination also carries `X-Signature-Previous`, `X-Signature-V2-Previous` and `X-Signature-Simple-Previous`, made with the replaced secret (see [Signature verification](/integration/webhooks#signature-verification)). `null` when no rotation happened in the last 24 hours." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + }, + "created_by_email": { + "type": "string", + "nullable": true, + "description": "Email of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_by_name": { + "type": "string", + "nullable": true, + "description": "Name of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "health_status": { + "type": "string", + "enum": [ + "healthy", + "degraded", + "failing" + ], + "description": "Delivery health. `failing`: 3 or more consecutive events failed their first delivery attempt since the destination last answered successfully (see `failing_streak`). `degraded`: not failing, but a delivery attempt failed in the last 24 hours and no later attempt has delivered that event; a retry or resend that delivers it clears this. `healthy`: neither. Independent of `enabled`. A failed attempt is one answered with HTTP 400 or above, or not answered at all." + }, + "failing_streak": { + "type": "integer", + "description": "How many consecutive events failed their first delivery attempt since the destination last answered successfully, within the last 30 days. Retries are not counted, and any successful attempt, including a retry or a resend, ends the streak. `0` when no event has failed since the last success." + }, + "failing_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the first failed attempt of the current `failing_streak` was made. `null` when `failing_streak` is `0`." + }, + "last_success_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the destination last answered a delivery with an HTTP status under 400, within the last 30 days. `null` if it has not in that window." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the destination." + }, + "summary": { + "type": "object", + "properties": { + "total_deliveries": { + "type": "integer" + }, + "failed_deliveries": { + "type": "integer" + }, + "error_rate_percentage": { + "type": "integer" + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true + }, + "last_delivery_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "metrics_window_days": { + "type": "integer", + "description": "Length in days of the window the other summary metrics cover. Always `30`." + } + }, + "description": "Delivery metrics for the last `metrics_window_days` (30) days. `last_delivery_at` is the most recent attempt, whenever it was made." + } + } + }, + "examples": { + "Paused": { + "value": { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": false, + "webhook_version": "v3", + "secret_shared_key": "tno2NTeRC1ZK3sBOYlBJDyEzBVENl3pQ1AcZyAW0xnQ", + "secret_last4": "0xnQ", + "previous_secret_expires_at": null, + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_by_email": null, + "created_by_name": null, + "created_at": "2026-01-04T08:00:00Z", + "updated_at": "2026-05-17T11:02:55Z", + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": "2026-05-17T09:21:18Z", + "environment": "live", + "summary": { + "total_deliveries": 12453, + "failed_deliveries": 14, + "error_rate_percentage": 0, + "min_response_time_ms": 41, + "avg_response_time_ms": 187, + "max_response_time_ms": 2103, + "last_delivery_at": "2026-05-17T09:21:18Z", + "metrics_window_days": 30 + } + } + } + } + } + } + }, + "400": { + "description": "Validation failed (duplicate URL, empty/unknown events list, malformed body).", + "content": { + "application/json": { + "examples": { + "Duplicate URL": { + "value": { + "url": [ + "This webhook destination already exists for the application." + ] + } + }, + "Empty events": { + "value": { + "subscribed_events": [ + "At least one subscribed event is required." + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. Didit returns 403 (never 401) for all authentication failures on this endpoint.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Destination with this UUID does not exist (or is deleted, or belongs to another application).", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete webhook destination", + "description": "Permanently delete a webhook destination, its signing secret and its entire delivery log. This cannot be undone. No new webhook is sent to the destination's URL; other destinations subscribed to the same events keep receiving them. Afterwards the destination no longer appears in `GET /v3/webhook/destinations/`, and every route under `/v3/webhook/destinations/{destination_uuid}/`, including its deliveries, a delivery's detail and its resend, answers 404. The application's legacy `webhook_url` is set to the URL of the oldest remaining enabled destination, or cleared when no enabled destination is left. Calling DELETE again returns 404.", + "operationId": "delete_webhook_destination", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination to delete.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nuuid = '11111111-2222-3333-4444-555555555555'\nresp = requests.delete(\n f'https://verification.didit.me/v3/webhook/destinations/{uuid}/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status()\nassert resp.status_code == 204" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const uuid = '11111111-2222-3333-4444-555555555555';\nconst resp = await fetch(`https://verification.didit.me/v3/webhook/destinations/${uuid}/`, {\n method: 'DELETE',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (resp.status !== 204) throw new Error(`Delete failed: ${resp.status}`);" + } + ], + "responses": { + "204": { + "description": "Destination, signing secret and delivery log deleted permanently. No response body." + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. Didit returns 403 (never 401) for all authentication failures on this endpoint.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No destination with this UUID exists for the application (already deleted or never existed).", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/wallet-screening/": { + "post": { + "summary": "Screen a wallet address", + "description": "Run on-demand AML screening for a single crypto wallet/address without creating a transaction. Resolves your application's configured blockchain analytics provider, screens the address synchronously, and returns the normalised risk result. LIGHTNING is available only with an Elliptic or Chainalysis BYOK connection added through Marketplace. Nothing is written to the transactions table. One AML monitoring usage is billed per successful screening (sandbox applications are not billed).", + "operationId": "screenWallet", + "x-didit-sdk": { + "resource": "wallet_screening", + "method": "screen", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "wallet_address", + "blockchain" + ], + "properties": { + "wallet_address": { + "type": "string", + "description": "The crypto address or network identifier to screen. Must match the selected network (e.g. a `0x`-prefixed 40-hex-char address for EVM chains). Alias: `address`.", + "example": "0x94f1b9b64e2932f6a2db338f616844400cd58e8a" + }, + "blockchain": { + "type": "string", + "description": "Network identifier. Managed screening supports BTC, ETH, LTC, XRP, BCH, DOGE, TRX, SOL, MATIC, BNB, BASE (Base), OP (Optimism), and ARB (Arbitrum One). USDT and USDC are legacy Ethereum aliases, not multi-network searches. Connected-provider coverage varies. LIGHTNING requires a supported connected provider. CELO (Celo), MODE (Mode) and PLUME (Plume) are screened through a connected TRM Labs account; managed screening answers 400 for them. Alias: `currency`.", + "enum": [ + "BTC", + "LIGHTNING", + "ETH", + "LTC", + "XRP", + "BCH", + "DOGE", + "TRX", + "SOL", + "MATIC", + "BNB", + "USDT", + "USDC", + "BASE", + "OP", + "ARB", + "CELO", + "MODE", + "PLUME" + ], + "example": "ETH" + }, + "direction": { + "type": "string", + "description": "Optional. Whether the address is being screened as an inbound (deposit) or outbound (withdrawal) counterparty. Accepts `inbound`, `outbound`, `deposit`, or `withdrawal`. Defaults to a neutral pre-transfer screen when omitted. This is passed to the provider as context only; it does not change the returned `risk_score`, `severity`, or the `source_of_funds`/`destination_of_funds` breakdowns, which are always both returned regardless of `direction`. Changing or omitting `direction` therefore does not require re-screening an address.", + "enum": [ + "inbound", + "outbound", + "deposit", + "withdrawal" + ] + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/wallet-screening/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"wallet_address\": \"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\",\n \"blockchain\": \"ETH\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/wallet-screening/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={\n 'wallet_address': '0x94f1b9b64e2932f6a2db338f616844400cd58e8a',\n 'blockchain': 'ETH',\n },\n timeout=20,\n)\nresp.raise_for_status()\nresult = resp.json()\nprint(result['risk_score'], result['severity'], result['sanctions_hit'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const result = await fetch('https://verification.didit.me/v3/wallet-screening/', {\n method: 'POST',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n wallet_address: '0x94f1b9b64e2932f6a2db338f616844400cd58e8a',\n blockchain: 'ETH',\n }),\n}).then((r) => r.json());\nconsole.log(result.risk_score, result.severity, result.sanctions_hit);" + } + ], + "responses": { + "200": { + "description": "The normalised wallet screening result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletScreeningResult" + }, + "example": { + "provider": "merklescience", + "screening_type": "WALLET_SCREENING", + "risk_score": 95, + "severity": "CRITICAL", + "status": "SCREENED", + "summary": "Merkle Science risk: Critical (5/5). SANCTIONS HIT. Dominant risk: sanctioned. Driven by: the address is attributed to Sanctions / OFAC (Example Sanctions)", + "wallet_address": "0x94f1b9b64e2932f6a2db338f616844400cd58e8a", + "transaction_hash": null, + "blockchain": "ETH", + "block_timestamp": null, + "total_value_usd": null, + "fee_usd": null, + "sender_rows": [], + "receiver_rows": [], + "transaction_flows": [], + "transaction_flows_status": null, + "transaction_flows_error": null, + "source_of_funds": [ + { + "category": "sanctioned", + "percentage": 100.0, + "amount_usd": 9813695.0, + "entity_name": "Example Sanctions", + "entity_type": "Sanctions", + "entity_subtype": "OFAC", + "hops": null, + "description": "Sanctions / OFAC", + "is_direct": true, + "exposure_direction": "incoming", + "exposure_type": "direct", + "provider_entity_id": "118885", + "provider_entity_type_id": "30", + "provider_entity_subtype_id": "125", + "country": "", + "raw_amount": null, + "raw_currency": null + } + ], + "destination_of_funds": [ + { + "category": "defi", + "percentage": 84.824453, + "amount_usd": 7783414.38, + "entity_name": "Example DeFi 7", + "entity_type": "DeFi", + "entity_subtype": "Bridges", + "hops": null, + "description": "DeFi / Bridges", + "is_direct": false, + "exposure_direction": "outgoing", + "exposure_type": "indirect", + "provider_entity_id": "118459", + "provider_entity_type_id": "33", + "provider_entity_subtype_id": "143", + "country": "", + "raw_amount": null, + "raw_currency": null + }, + { + "category": "defi", + "percentage": 15.175547, + "amount_usd": 1392494.37, + "entity_name": "Example DeFi 2", + "entity_type": "DeFi", + "entity_subtype": "Yield Aggregator", + "hops": null, + "description": "DeFi / Yield Aggregator", + "is_direct": false, + "exposure_direction": "outgoing", + "exposure_type": "indirect", + "provider_entity_id": "90192", + "provider_entity_type_id": "33", + "provider_entity_subtype_id": "142", + "country": "", + "raw_amount": null, + "raw_currency": null + } + ], + "counterparty_connections": [ + { + "entity_name": "Example Sanctions", + "entity_type": "Sanctions", + "entity_subtype": "OFAC", + "risk_level": "critical", + "categories": [ + "sanctioned" + ], + "received_usd": null, + "sent_usd": null, + "received_hops": null, + "sent_hops": null, + "percentage": 0.0, + "is_direct": true, + "country": null, + "provider_entity_id": null + }, + { + "entity_name": "Example Sanctions", + "entity_type": "Sanctions", + "entity_subtype": "OFAC", + "risk_level": "critical", + "categories": [ + "sanctioned" + ], + "received_usd": null, + "sent_usd": null, + "received_hops": null, + "sent_hops": null, + "percentage": 0.0, + "is_direct": true, + "country": null, + "provider_entity_id": null + } + ], + "network_nodes": [], + "network_edges": [], + "sanctions_hit": true, + "pep_counterparty": null, + "dominant_risk_category": "sanctioned", + "risk_factors": [ + { + "source": "attribution", + "category": "sanctioned", + "label": "Sanctions / OFAC", + "entity_name": "Example Sanctions", + "direction": "attribution", + "exposure_type": "attribution", + "amount_usd": null, + "percentage": 0.0, + "is_high_risk": true, + "description": "the address is attributed to Sanctions / OFAC (Example Sanctions)" + }, + { + "source": "exposure", + "category": "sanctioned", + "label": "Sanctions / OFAC", + "entity_name": "Example Sanctions", + "direction": "incoming", + "exposure_type": "direct", + "amount_usd": 9813695.0, + "percentage": 51.679304, + "is_high_risk": true, + "description": "51.6793% direct incoming exposure to Sanctions / OFAC (Example Sanctions)" + }, + { + "source": "exposure", + "category": "defi", + "label": "DeFi / Bridges", + "entity_name": "Example DeFi 7", + "direction": "outgoing", + "exposure_type": "indirect", + "amount_usd": 7783414.38, + "percentage": 40.987766, + "is_high_risk": false, + "description": "40.9878% indirect outgoing exposure to DeFi / Bridges (Example DeFi 7)" + }, + { + "source": "exposure", + "category": "defi", + "label": "DeFi / Yield Aggregator", + "entity_name": "Example DeFi 2", + "direction": "outgoing", + "exposure_type": "indirect", + "amount_usd": 1392494.37, + "percentage": 7.33293, + "is_high_risk": false, + "description": "7.33293% indirect outgoing exposure to DeFi / Yield Aggregator (Example DeFi 2)" + } + ], + "raw_response": { + "...": "provider-specific details" + }, + "report_signature": "" + } + } + } + }, + "400": { + "description": "Validation error — malformed wallet address, missing `wallet_address`, unsupported `blockchain`, or LIGHTNING without an Elliptic or Chainalysis BYOK Marketplace connection." + }, + "401": { + "description": "Missing or invalid `x-api-key`." + }, + "409": { + "description": "No AML provider is configured for the application, or the configured provider does not support wallet screening yet. Configure transaction monitoring first." + }, + "502": { + "description": "The AML provider could not complete the screening. Retry later." + } + } + } + }, + "/v3/wallet-screening/pdf/": { + "post": { + "summary": "Render a wallet-screening result as a PDF", + "description": "Stateless companion to `POST /v3/wallet-screening/`: repost the exact JSON body that endpoint returned — including the `report_signature` it added — and get back a Didit-branded PDF of the same result. No provider is called and nothing is billed or persisted; this only re-renders a result Didit already produced. The signature ties the PDF to an unmodified result: editing `risk_score`, `severity`, `sanctions_hit`, or any other signed field before reposting fails with `400`.", + "operationId": "screenWalletPdf", + "x-didit-sdk": { + "resource": "wallet_screening", + "method": "screen_pdf", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "description": "The unmodified JSON object returned by `POST /v3/wallet-screening/`, including `report_signature`.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/WalletScreeningResult" + } + ], + "required": [ + "report_signature" + ], + "description": "Same shape as the `POST /v3/wallet-screening/` response — pass that response body back verbatim, including `report_signature`." + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "# $RESULT is the saved JSON body from POST /v3/wallet-screening/\ncurl -X POST 'https://verification.didit.me/v3/wallet-screening/pdf/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d \"$RESULT\" \\\n -o wallet_screening_report.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\n# `result` is the dict returned by POST /v3/wallet-screening/, unmodified\nresponse = requests.post(\n 'https://verification.didit.me/v3/wallet-screening/pdf/',\n headers={'x-api-key': 'YOUR_API_KEY'},\n json=result,\n timeout=30,\n)\nresponse.raise_for_status()\nwith open('wallet_screening_report.pdf', 'wb') as fh:\n fh.write(response.content)" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "// `result` is the object returned by POST /v3/wallet-screening/, unmodified\nconst response = await fetch('https://verification.didit.me/v3/wallet-screening/pdf/', {\n method: 'POST',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(result),\n});\nif (!response.ok) throw new Error(`PDF generation failed: HTTP ${response.status}`);\nconst pdfBuffer = Buffer.from(await response.arrayBuffer());" + } + ], + "responses": { + "200": { + "description": "The rendered PDF document, returned directly as binary `application/pdf` — there is no JSON wrapper.", + "headers": { + "Content-Disposition": { + "description": "`attachment; filename=wallet_screening_{address}.pdf`, derived from the `wallet_address` (or `transaction_hash`) in the reposted result.", + "schema": { + "type": "string", + "example": "attachment; filename=wallet_screening_0x28c6c06298d514db0899.pdf" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "400": { + "description": "The body is not a JSON object matching the wallet-screening result shape, or `report_signature` is missing or does not match the reposted fields (edited or truncated before repost).", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "examples": { + "Not an object": { + "value": { + "non_field_errors": [ + "Expected a JSON object matching a wallet-screening result." + ] + } + }, + "Missing or invalid signature": { + "value": { + "report_signature": [ + "This wallet-screening result is missing or fails its report signature - repost the exact JSON returned by the wallet-screening endpoint, unmodified." + ] + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid `x-api-key`." + } + } + } + }, + "/v3/transactions/": { + "get": { + "summary": "List transactions", + "description": "Paginated list of monitored transactions, newest first (`txn_date` desc). `count` is capped at 100; use `total_transactions` for the true row count. This endpoint does not support filtering or search parameters.", + "operationId": "listTransactions", + "x-didit-sdk": { + "resource": "transactions", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size. Defaults to 50 when omitted. There is no enforced maximum, but keep pages small for latency." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset of the first record to return. Prefer following the `next` URL from the previous page." + }, + { + "name": "travel_rule_status", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Return only transactions whose Travel Rule transfer is in this exact status (for example `ON_HOLD` or `AWAITING_COUNTERPARTY`). `count` follows this filter; `total_transactions` still counts every transaction in the application." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.get(\n 'https://verification.didit.me/v3/transactions/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n params={'limit': 50},\n timeout=10,\n)\nresp.raise_for_status()\npage = resp.json()\nfor tx in page['results']:\n print(\n tx['transaction_number'],\n tx['status'],\n tx['severity'],\n f\"{tx['amount']} {tx['currency']}\",\n tx.get('subject_name') or tx.get('subject_vendor_data'),\n )" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const url = new URL('https://verification.didit.me/v3/transactions/');\nurl.searchParams.set('limit', '50');\nconst page = await fetch(url, {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n}).then((r) => r.json());\nfor (const tx of page.results) {\n console.log(tx.transaction_number, tx.status, tx.severity, tx.amount, tx.currency);\n}" + } + ], + "responses": { + "200": { + "description": "Paginated list of transaction summaries, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Capped count of matching transactions (exact up to 100, capped thereafter). Use `total_transactions` for the true count when paginating." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "total_transactions": { + "type": "integer", + "description": "Exact total count of (non-deleted) transactions for the application. Present on paginated responses." + }, + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Stable Didit transaction identifier. Use as `{transaction_id}` in detail/notes/related calls." + }, + "transaction_number": { + "type": "integer", + "description": "Application-scoped sequential transaction number shown in Console (e.g. `4123`)." + }, + "txn_id": { + "type": "string", + "description": "Customer-provided identifier from the create call (max 128 chars). Unique per application." + }, + "transaction_type": { + "type": "string", + "description": "Top-level category as stored: `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet`, `gamblingLimitChange`, `gamblingBonusChange`, `auditTrailEvent`. Note multi-word values are echoed back in camelCase even when submitted in snake_case." + }, + "action_type": { + "type": "string", + "description": "Sub-type within the category (e.g. `deposit`, `withdrawal`, `transfer`)." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Current monitoring verdict." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject. Stored uppercase regardless of the casing submitted." + }, + "amount": { + "type": "string", + "description": "Transaction amount as a decimal string with trailing zeros stripped (e.g. `\"1500\"`, `\"0.5\"`, `\"0.123456789012345678\"`). Up to 18 decimal places." + }, + "currency": { + "type": "string", + "description": "Currency code of `amount`. Any ISO 4217 alphabetic code is accepted (e.g. `EUR`, `USD`, `NGN`), as is a crypto asset symbol (e.g. `BTC`). Prefer a currently-circulating code. Not every accepted code has an exchange rate: when none is available for the code (for example the ISO accounting unit `XSU`, or a crypto symbol Didit does not price), automatic conversion fails and `preferred_currency_amount` is returned null unless the request supplied a pre-converted `amount_in_default_currency`." + }, + "preferred_currency_amount": { + "type": "string", + "nullable": true, + "description": "`amount` converted to the application's preferred currency, when available." + }, + "preferred_currency_code": { + "type": "string", + "nullable": true, + "description": "Preferred currency code used for `preferred_currency_amount` - the application's base currency, any currently-circulating ISO 4217 code. A code with no available exchange rate leaves `preferred_currency_amount` null unless the request supplied a pre-converted `amount_in_default_currency`." + }, + "payment_details": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference or memo from the submission." + }, + "score": { + "type": "integer", + "description": "Risk score (typically 0–100). Higher = riskier." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ], + "description": "Categorical risk severity set by rules/providers (`UNKNOWN`, `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`). `null` until something sets it." + }, + "subject_name": { + "type": "string", + "nullable": true, + "description": "Full name of the `APPLICANT` party." + }, + "subject_vendor_data": { + "type": "string", + "nullable": true, + "description": "Your internal user/business identifier for the applicant." + }, + "subject_country": { + "type": "string", + "nullable": true, + "description": "Country code of the applicant, from the submitted `address.country` (typically ISO 3166-1 alpha-3)." + }, + "counterparty_name": { + "type": "string", + "nullable": true, + "description": "Full name of the `COUNTERPARTY` party, if any." + }, + "counterparty_vendor_data": { + "type": "string", + "nullable": true, + "description": "Your internal identifier for the counterparty." + }, + "counterparty_country": { + "type": "string", + "nullable": true, + "description": "Country code of the counterparty, from the submitted `address.country`." + }, + "tags": { + "type": "array", + "description": "Tags attached to the transaction (manually or by rules).", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ], + "description": "Whether the tag is a Didit preset or a custom tag." + } + } + } + }, + "txn_date": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred (from `transaction_at` at create time)." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When Didit recorded the submission." + }, + "decision_reason_code": { + "type": "string", + "nullable": true, + "description": "Machine-readable reason for the current `status` (e.g. `WALLET_HIGH_RISK`)." + }, + "decision_reason_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label for `decision_reason_code`." + }, + "assigned_to_name": { + "type": "string", + "nullable": true, + "description": "Display name of the Console reviewer assigned to this transaction." + }, + "travel_rule_status": { + "type": "string", + "nullable": true, + "description": "Status of the transaction's Travel Rule transfer (for example `COMPLETED`), or `null` when it has none." + }, + "travel_rule": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TransactionTravelRule" + } + ], + "description": "The transaction's Travel Rule transfer, the same block as `travel_rule` on `GET /v3/transactions/{transaction_id}/`, or `null` when the transaction has none." + } + } + } + } + } + }, + "examples": { + "Single transaction": { + "value": { + "count": 1, + "next": null, + "previous": null, + "total_transactions": 1, + "results": [ + { + "uuid": "abcdef12-3456-4890-abcd-ef1234567890", + "transaction_number": 4123, + "txn_id": "your-txn-2026-05-17-001", + "transaction_type": "finance", + "action_type": "withdrawal", + "status": "IN_REVIEW", + "direction": "OUTBOUND", + "amount": "1500", + "currency": "EUR", + "preferred_currency_amount": "1620.45", + "preferred_currency_code": "USD", + "payment_details": "Withdrawal to crypto wallet", + "txn_date": "2026-05-17T08:42:00Z", + "created_at": "2026-05-17T08:42:11Z", + "score": 78, + "severity": "HIGH", + "decision_reason_code": "WALLET_HIGH_RISK", + "decision_reason_label": "Destination wallet flagged as high risk", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "Manual review", + "color": "#F59E0B", + "description": null, + "source": "custom" + } + ], + "subject_name": "Maria Garcia", + "subject_vendor_data": "user-12345", + "counterparty_name": null, + "counterparty_vendor_data": null, + "counterparty_country": null, + "subject_country": null, + "assigned_to_name": "Alex Reviewer", + "travel_rule_status": null, + "travel_rule": null + } + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot list transactions for this application. This endpoint returns `403` (never `401`) for authentication failures, including requests with no credentials at all.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create transaction", + "description": "Submit a transaction for synchronous AML/risk monitoring. The transaction is created `APPROVED`, then your rules (and crypto screening, when applicable) run inline and may flip the returned `status` to `IN_REVIEW` or `DECLINED`. **`transaction_id` is your idempotency key** (max 128 chars, unique per app); reusing it returns 400. A `transaction.created` webhook is dispatched on success for live applications. Sandbox applications are not billed and do not emit `transaction.created` or `transaction.status.updated` webhooks. Sandbox Travel Rule exchanges do emit `travel_rule.status.updated` webhooks with `environment: sandbox`. A Travel Rule transfer is stored and returned before any counterparty is contacted: `travel_rule.status` comes back `AWAITING_COUNTERPARTY` (or `NOT_APPLICABLE` below your threshold, `UNCONFIRMED_OWNERSHIP` for a declared self-hosted wallet, `COMPLETED` when the other wallet is your own verified wallet) and `travel_rule.protocol` is null. Didit then chooses the rail and sends the exchange, and every later status change of the transfer, including `COUNTERPARTY_VASP_NOT_FOUND` when no rail reaches the counterparty, arrives as a `travel_rule.status.updated` webhook. Your rules run again when the rail or the status changes.", + "operationId": "createTransaction", + "tags": [ + "Transactions" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"transaction_id\": \"your-txn-2026-05-17-001\",\n \"transaction_category\": \"finance\",\n \"transaction_details\": {\n \"direction\": \"outbound\",\n \"amount\": 1500,\n \"currency\": \"EUR\",\n \"action_type\": \"withdrawal\"\n },\n \"subject\": {\n \"entity_type\": \"individual\",\n \"vendor_data\": \"user-12345\",\n \"full_name\": \"Maria Garcia\"\n }\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/transactions/',\n headers={\n 'x-api-key': os.environ['DIDIT_API_KEY'],\n 'Content-Type': 'application/json',\n },\n json={\n 'transaction_id': 'your-txn-2026-05-17-001',\n 'transaction_category': 'finance',\n 'transaction_details': {\n 'direction': 'outbound',\n 'amount': 1500,\n 'currency': 'EUR',\n 'action_type': 'withdrawal',\n },\n 'subject': {\n 'entity_type': 'individual',\n 'vendor_data': 'user-12345',\n 'full_name': 'Maria Garcia',\n },\n },\n timeout=15,\n)\nresp.raise_for_status()\ntx = resp.json()\nprint(tx['txn_id'], '->', tx['status'], tx['severity'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/transactions/', {\n method: 'POST',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n transaction_id: 'your-txn-2026-05-17-001',\n transaction_category: 'finance',\n transaction_details: {\n direction: 'outbound',\n amount: 1500,\n currency: 'EUR',\n action_type: 'withdrawal',\n },\n subject: {\n entity_type: 'individual',\n vendor_data: 'user-12345',\n full_name: 'Maria Garcia',\n },\n }),\n});\nif (!resp.ok) throw new Error(await resp.text());\nconst tx = await resp.json();\nconsole.log(tx.txn_id, tx.status, tx.severity);" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "transaction_id": { + "type": "string", + "description": "Your unique identifier for this transaction (max 128 characters).", + "example": "your-txn-2026-05-17-001" + }, + "transaction_at": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred. Defaults to now if omitted." + }, + "time_zone": { + "type": "string", + "description": "IANA time zone identifier, e.g. `Europe/Madrid`." + }, + "transaction_category": { + "type": "string", + "description": "Category of the transaction. CamelCase aliases (`travelRule`, `userPlatformEvent`, `gamblingBet`, …) are also accepted; the stored/echoed value is camelCase for multi-word categories.", + "example": "finance", + "enum": [ + "finance", + "kyc", + "travel_rule", + "user_event", + "audit_trail_event", + "gambling_bet", + "gambling_limit_change", + "gambling_bonus_change", + "travelRule" + ] + }, + "transaction_details": { + "type": "object", + "description": "Core financial details of the transaction.", + "required": [ + "direction", + "amount", + "currency" + ], + "properties": { + "direction": { + "type": "string", + "description": "Direction of the transaction relative to the subject. Case-insensitive; `in`/`out` are accepted as aliases. Echoed back uppercase (`INBOUND`/`OUTBOUND`).", + "example": "outbound", + "enum": [ + "inbound", + "outbound", + "in", + "out" + ] + }, + "amount": { + "type": "number", + "description": "Transaction amount (up to 18 decimal places). Send it as a decimal string (e.g. `\"0.123456789012345678\"`) when exact precision matters: a JSON number is parsed as a binary float first, so digits beyond about 15 significant digits are rounded before the amount is stored.", + "example": 1500 + }, + "currency": { + "type": "string", + "description": "Currency code. Any ISO 4217 alphabetic code is accepted (e.g. `EUR`, `USD`, `NGN`), as is a crypto asset symbol (e.g. `BTC`). Prefer a currently-circulating code. Not every accepted code has an exchange rate: when none is available for the code (for example the ISO accounting unit `XSU`, or a crypto symbol Didit does not price), automatic conversion fails and `preferred_currency_amount` is returned null unless the request supplied a pre-converted `amount_in_default_currency`.", + "example": "EUR" + }, + "currency_kind": { + "type": "string", + "description": "Whether the currency is `fiat` or `crypto`." + }, + "amount_in_default_currency": { + "type": "number", + "nullable": true, + "description": "Pre-converted amount in the default currency. If omitted, automatic FX conversion is attempted." + }, + "default_currency": { + "type": "string", + "description": "The default currency code for the converted amount. Any currently-circulating ISO 4217 code." + }, + "payment_details": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference or memo." + }, + "payment_reference_id": { + "type": "string", + "nullable": true, + "description": "External payment system reference. For crypto AML transaction-hash screening, send the blockchain transaction hash and also provide `subject.payment_method.account_id` with the related address." + }, + "action_type": { + "type": "string", + "nullable": true, + "description": "Sub-type of the transaction, e.g. `deposit`, `withdrawal`, `transfer`." + }, + "crypto_params": { + "type": "object", + "description": "Crypto network selection, separate from currency (the asset symbol). Recording a custom token symbol does not guarantee transaction-hash coverage for its contract.", + "properties": { + "crypto_chain": { + "type": "string", + "description": "Actual network used for screening. Managed screening supports BTC, ETH, LTC, XRP, BCH, DOGE, TRX, SOL, MATIC, BNB, BASE (Base), OP (Optimism) and ARB (Arbitrum One). Always specify this for multi-network tokens. When it is missing for USDT, USDC or ETH sent with a 0x transaction hash, Didit looks the hash up on Ethereum, BNB Chain, Polygon, Base, Arbitrum and Optimism and screens it on the network where it is found; if no network has it, the transaction is not screened and custom_values.aml_screening_status is FAILED with the reason in custom_values.aml_screening_error. When it is missing for USDT or USDC screened by wallet (no transaction hash), a Tron address is screened on Tron, a Solana address on Solana, and a 0x address on the first of those six networks where it has sent a transaction or holds the token, or on Ethereum when it is active on none of them. Connected-provider coverage varies; LIGHTNING requires a supported connected provider.", + "example": "BASE" + } + } + } + } + }, + "subject": { + "type": "object", + "description": "The subject (applicant) of the transaction.", + "required": [ + "vendor_data", + "full_name" + ], + "properties": { + "entity_type": { + "type": "string", + "description": "Entity type: `individual` or `company`." + }, + "vendor_data": { + "type": "string", + "description": "Your internal user/business identifier.", + "example": "user-12345" + }, + "full_name": { + "type": "string", + "description": "Full name of the subject.", + "example": "Maria Garcia" + }, + "first_name": { + "type": "string", + "description": "First name of the subject." + }, + "last_name": { + "type": "string", + "description": "Last name of the subject." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth (YYYY-MM-DD)." + }, + "address": { + "type": "object", + "description": "Address object.", + "properties": { + "country": { + "type": "string", + "description": "Country code (ISO 3166-1 alpha-3 recommended). Copied to the party's `country_code`." + }, + "town": { + "type": "string" + }, + "state": { + "type": "string" + }, + "street": { + "type": "string" + }, + "post_code": { + "type": "string" + } + } + }, + "institution_details": { + "type": "object", + "description": "Institution details (for institutional subjects).", + "properties": { + "name": { + "type": "string" + }, + "code": { + "type": "string" + }, + "address": { + "type": "object" + } + } + }, + "device_context": { + "type": "object", + "description": "Device context captured at transaction time. browser_family and platform must be sent as explicit top-level keys — they are not derived from user_agent. The camelCase alias device is also accepted on the request.", + "properties": { + "fingerprint": { + "type": "string", + "description": "Unique device fingerprint." + }, + "persistent_device_id": { + "type": "string", + "description": "Stable cross-session device identifier (v2 fingerprint schema)." + }, + "user_agent": { + "type": "string", + "description": "Browser or app user agent string. CamelCase alias: userAgent." + }, + "browser_family": { + "type": "string", + "description": "Browser family (e.g. Chrome, Firefox, Safari). Must be sent explicitly — it is not derived from user_agent. CamelCase alias: browserFamily." + }, + "browser_version": { + "type": "string", + "description": "Browser version string. Must be sent explicitly. CamelCase alias: browserVersion." + }, + "platform": { + "type": "string", + "description": "Platform identifier (web, ios, android, macos, windows, linux). Must be sent explicitly — it is not derived from user_agent." + }, + "os_family": { + "type": "string", + "description": "Operating system family (e.g. iOS, Android, Windows). CamelCase alias: osFamily." + }, + "os_version": { + "type": "string", + "description": "Operating system version string. CamelCase alias: osVersion." + }, + "accept_language": { + "type": "string", + "description": "Accept-Language header value. CamelCase alias: acceptLang." + }, + "session_id": { + "type": "string", + "description": "Session identifier of the user current session. Stored in metadata. CamelCase alias: sessionId." + }, + "session_age_ms": { + "type": "number", + "description": "Age of the user session in milliseconds at transaction time. CamelCase alias: sessionAgeMs." + }, + "network_context": { + "type": "object", + "description": "Network context with IP address and geo-location. A top-level ipInfo block is also folded into this object.", + "properties": { + "ip_address": { + "type": "string", + "description": "Client IP address. CamelCase alias: ip." + }, + "ip_country_code": { + "type": "string", + "description": "Geo-resolved country of the IP (ISO 3166-1 alpha-3). CamelCase alias: countryCode3." + }, + "latitude": { + "type": "number", + "description": "Geographic latitude of the IP. CamelCase alias: lat." + }, + "longitude": { + "type": "number", + "description": "Geographic longitude of the IP. CamelCase alias: lon." + } + } + } + } + }, + "payment_method": { + "type": "object", + "description": "Payment method used by the subject.", + "properties": { + "method_type": { + "type": "string", + "description": "Type of payment method: `bank_card`, `bank_account`, `crypto_wallet`, `ewallet`, `unhosted_wallet`, `other`." + }, + "account_id": { + "type": "string", + "description": "Account identifier (e.g. IBAN, wallet address). For crypto screening, this should contain the wallet address for this participant. Inbound transaction-hash screening requires the service/customer deposit address on the subject. Outbound screening uses the destination wallet on the counterparty. On a crypto transfer on bitcoin or Lightning it may be a Lightning payment request (BOLT11 invoice, BOLT12 offer, LNURL) or a Lightning Address: Didit stores the payee it names as the account (\"\" when it names none) and what the request says on the Travel Rule transfer's `chain_details.lightning`. At most 255 characters once stored. On a crypto transfer on bitcoin or Lightning it may be a Lightning payment request (BOLT11 invoice, BOLT12 offer, LNURL) or a Lightning Address: Didit stores the payee it names as the account (\"\" when it names none) and what the request says on the Travel Rule transfer's `chain_details.lightning`. At most 255 characters once stored." + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "The destination tag or memo that is part of the wallet on a memo network (XRP, XLM, EOS, ATOM, HBAR). On one shared exchange address each memo is its own payment method. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`. Alias: `destinationTag` or `destination_tag`." + }, + "issuing_country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code of the issuer." + } + } + } + } + }, + "counterparty": { + "type": "object", + "description": "The counterparty of the transaction (optional).", + "properties": { + "entity_type": { + "type": "string", + "description": "Entity type: `individual` or `company`." + }, + "vendor_data": { + "type": "string", + "description": "Your internal identifier for the counterparty." + }, + "full_name": { + "type": "string", + "description": "Full name of the counterparty." + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "address": { + "type": "object", + "description": "Address object with `country` (ISO 3166-1 alpha-3)." + }, + "institution_details": { + "type": "object" + }, + "device_context": { + "type": "object", + "description": "Device context for the counterparty. browser_family and platform must be sent as explicit top-level keys — they are not derived from user_agent.", + "properties": { + "fingerprint": { + "type": "string", + "description": "Unique device fingerprint." + }, + "persistent_device_id": { + "type": "string", + "description": "Stable cross-session device identifier (v2 fingerprint schema)." + }, + "user_agent": { + "type": "string", + "description": "Browser or app user agent string. CamelCase alias: userAgent." + }, + "browser_family": { + "type": "string", + "description": "Browser family (e.g. Chrome, Firefox, Safari). Must be sent explicitly — it is not derived from user_agent. CamelCase alias: browserFamily." + }, + "browser_version": { + "type": "string", + "description": "Browser version string. Must be sent explicitly. CamelCase alias: browserVersion." + }, + "platform": { + "type": "string", + "description": "Platform identifier (web, ios, android, macos, windows, linux). Must be sent explicitly — it is not derived from user_agent." + }, + "os_family": { + "type": "string", + "description": "Operating system family (e.g. iOS, Android, Windows). CamelCase alias: osFamily." + }, + "os_version": { + "type": "string", + "description": "Operating system version string. CamelCase alias: osVersion." + }, + "accept_language": { + "type": "string", + "description": "Accept-Language header value. CamelCase alias: acceptLang." + }, + "session_id": { + "type": "string", + "description": "Session identifier for the counterparty session. Stored in metadata. CamelCase alias: sessionId." + }, + "session_age_ms": { + "type": "number", + "description": "Age of the user session in milliseconds at transaction time. CamelCase alias: sessionAgeMs." + }, + "network_context": { + "type": "object", + "description": "Network context with IP address and geo-location. A top-level ipInfo block is also folded into this object.", + "properties": { + "ip_address": { + "type": "string", + "description": "Client IP address. CamelCase alias: ip." + }, + "ip_country_code": { + "type": "string", + "description": "Geo-resolved country of the IP (ISO 3166-1 alpha-3). CamelCase alias: countryCode3." + }, + "latitude": { + "type": "number", + "description": "Geographic latitude of the IP. CamelCase alias: lat." + }, + "longitude": { + "type": "number", + "description": "Geographic longitude of the IP. CamelCase alias: lon." + } + } + } + } + }, + "payment_method": { + "type": "object", + "description": "Payment method used by the counterparty. On travelRule transactions, `method_type: \"unhosted_wallet\"` declares the counterparty wallet as self-hosted: the transfer skips counterparty-VASP routing, is created directly in `UNCONFIRMED_OWNERSHIP`, and (when `auto_wallet_verification` is enabled) a wallet-ownership widget session is auto-minted and returned in the transaction's `action_required` block.", + "properties": { + "method_type": { + "type": "string", + "description": "Type of payment method: `bank_card`, `bank_account`, `crypto_wallet`, `ewallet`, `unhosted_wallet`, `other`. Use `unhosted_wallet` to declare a self-hosted wallet for the Travel Rule." + }, + "account_id": { + "type": "string", + "description": "Account identifier (e.g. IBAN or wallet address). On a crypto transfer on bitcoin or Lightning it may be a Lightning payment request (BOLT11 invoice, BOLT12 offer, LNURL) or a Lightning Address: Didit stores the payee it names as the account (\"\" when it names none) and what the request says on the Travel Rule transfer's `chain_details.lightning`. At most 255 characters once stored. On a crypto transfer on bitcoin or Lightning it may be a Lightning payment request (BOLT11 invoice, BOLT12 offer, LNURL) or a Lightning Address: Didit stores the payee it names as the account (\"\" when it names none) and what the request says on the Travel Rule transfer's `chain_details.lightning`. At most 255 characters once stored." + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "The destination tag or memo that is part of the wallet on a memo network (XRP, XLM, EOS, ATOM, HBAR). On one shared exchange address each memo is its own payment method. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`. Alias: `destinationTag` or `destination_tag`." + }, + "issuing_country": { + "type": "string" + } + } + } + } + }, + "custom_properties": { + "type": "object", + "description": "Arbitrary key-value pairs attached to the transaction. Each key can be referenced in rule conditions with the field path `custom_values.`.", + "additionalProperties": true, + "example": { + "merchant_id": "test", + "bank_name": "test_2" + } + }, + "travel_rule_details": { + "type": "object", + "description": "Travel Rule data. When Travel Rule is enabled and transaction_category is travel_rule, Didit runs the exchange itself: it ignores status, protocol, required and obligations_count, and uses originator_data, beneficiary_data and metadata. Otherwise Didit stores these values as a passive Travel Rule record. While Travel Rule is on, an outbound transfer at or above your threshold must carry what its regime requires (EU TFR or FATF Recommendation 16), checked on the sides you send: a missing field answers 400 under `travel_rule_details.originator_data` or `travel_rule_details.beneficiary_data`, for example \"EU TFR requires the originator name.\"", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "description": "Required whenever you send travel_rule_details. One of the Travel Rule transfer statuses (for example AWAITING_COUNTERPARTY, COMPLETED, NOT_APPLICABLE); lowercase and camelCase spellings and the legacy values UNKNOWN, COMPLIANT, PENDING_ACTION, PENDING_COUNTERPARTY, FAILED and EXEMPT are also accepted and mapped. Any other value returns 400." + }, + "protocol": { + "type": "string" + }, + "required": { + "type": "boolean" + }, + "obligations_count": { + "type": "integer" + }, + "originator_data": { + "type": "object", + "description": "Originator party, as flat fields or one IVMS 101 person. For your user with an approved KYC verification, Didit replaces the fields the verification holds with the verified values (see `metadata.originator_provenance`). It must be an object: anything else is a `400`. On a memo network a `memo` key names the originator wallet's memo or tag." + }, + "beneficiary_data": { + "type": "object", + "description": "Beneficiary party, as flat fields or one IVMS 101 person. It must be an object: anything else is a `400`. To route the transfer, Didit reads `wallet_address` (an empty one is filled from `counterparty.payment_method.account_id`), `chain` (filled from `transaction_details.crypto_params.crypto_chain` or the `currency` when that names a supported chain), `name` (falling back to `counterparty.full_name`), `travel_address`, `counterparty_vasp_name`, `counterparty_vasp_lei` and `counterparty_vasp_email`. On a memo network `memo` names the beneficiary wallet's memo or tag; `wallet_address` and `memo` win over the counterparty's payment method." + }, + "metadata": { + "type": "object" + }, + "utxo": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleUtxo" + } + ], + "description": "Optional, UTXO chains only (BTC, LTC, BCH, DOGE). The transaction's inputs and outputs, from which Didit infers the originator wallet and the beneficiary output when you do not declare them." + }, + "originator": { + "type": "object", + "description": "An IVMS 101 originator block: `originatorPersons` (one `naturalPerson` or `legalPerson`; a legal person makes the party a company) and `accountNumber` (wallet addresses). Used for `originator_data` when that object is empty. A malformed block answers 400 keyed by its path, for example `originator.originatorPersons`." + }, + "beneficiary": { + "type": "object", + "description": "An IVMS 101 beneficiary block: `beneficiaryPersons` (one `naturalPerson` or `legalPerson`; a legal person makes the party a company) and `accountNumber` (wallet addresses). Used for `beneficiary_data` when that object is empty. A malformed block answers 400 keyed by its path, for example `beneficiary.beneficiaryPersons`." + }, + "ivms101": { + "type": "object", + "description": "The `originator` and `beneficiary` blocks wrapped in one IVMS 101 object (`IVMS101` is accepted too). Top-level blocks win over the wrapper's." + } + } + }, + "network_snapshot": { + "type": "object", + "description": "Pre-computed network graph snapshot.", + "properties": { + "nodes": { + "type": "array", + "items": { + "type": "object" + } + }, + "edges": { + "type": "array", + "items": { + "type": "object" + } + }, + "metrics": { + "type": "object" + } + } + }, + "include_crypto_screening": { + "type": "boolean", + "nullable": true, + "description": "Per-transaction override for crypto blockchain analytics screening. When `true`, crypto screening is performed regardless of the application default. When `false`, crypto screening is skipped even if enabled in the application settings. When omitted or `null`, the application-level default configured in the Console is used. Crypto screening requires `transaction_details.direction`, `currency_kind: \"crypto\"`, a chain/currency, and the wallet address selected by the direction: inbound pre-transfer screening uses `counterparty.payment_method.account_id`, inbound transaction-hash screening uses `subject.payment_method.account_id`, and outbound screening uses `counterparty.payment_method.account_id`. When `transaction_details.payment_reference_id` contains a blockchain transaction hash, Didit performs transaction screening and returns transaction summary enrichment when available." + } + }, + "required": [ + "transaction_id", + "transaction_category", + "transaction_details", + "subject" + ] + }, + "examples": { + "Minimal fiat withdrawal": { + "summary": "Smallest acceptable payload for a fiat outbound transfer", + "value": { + "transaction_id": "your-txn-2026-05-17-001", + "transaction_category": "finance", + "transaction_details": { + "direction": "outbound", + "amount": 1500, + "currency": "EUR", + "action_type": "withdrawal" + }, + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia" + } + } + }, + "Crypto outbound with screening": { + "summary": "Outbound crypto withdrawal to an unhosted wallet, opt-in screening", + "value": { + "transaction_id": "your-txn-2026-05-17-002", + "transaction_category": "finance", + "include_crypto_screening": true, + "transaction_details": { + "direction": "outbound", + "amount": 0.5, + "currency": "BTC", + "currency_kind": "crypto", + "amount_in_default_currency": 32500, + "default_currency": "USD", + "action_type": "withdrawal" + }, + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "payment_method": { + "method_type": "crypto_wallet", + "account_id": "bc1qexampleexchangewalletaddressxyz", + "issuing_country": "ESP" + } + }, + "counterparty": { + "entity_type": "individual", + "payment_method": { + "method_type": "unhosted_wallet", + "account_id": "bc1qexampleexternalwalletaddressabc" + } + } + } + }, + "Travel Rule transfer": { + "summary": "Outbound crypto transfer with Travel Rule originator/beneficiary data", + "value": { + "transaction_id": "your-txn-2026-05-17-003", + "transaction_category": "travel_rule", + "transaction_details": { + "direction": "outbound", + "amount": 25000, + "currency": "USDT", + "currency_kind": "crypto", + "action_type": "transfer" + }, + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "date_of_birth": "1992-03-14", + "address": { + "country": "ESP", + "town": "Madrid", + "street": "Calle Mayor 1", + "post_code": "28013" + } + }, + "counterparty": { + "entity_type": "individual", + "full_name": "John Beneficiary", + "institution_details": { + "name": "Beneficiary VASP, Inc.", + "code": "BVASP-US" + } + }, + "travel_rule_details": { + "status": "AWAITING_COUNTERPARTY", + "protocol": "IVMS101", + "required": true, + "originator_data": { + "name": "Maria Garcia" + }, + "beneficiary_data": { + "name": "John Beneficiary" + } + } + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Transaction submitted and monitored. The body is the full transaction detail (same shape as `GET /v3/transactions/{transaction_id}/`), reflecting any rule outcomes already applied.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionDetail" + }, + "examples": { + "Approved with no rule hits": { + "value": { + "uuid": "abcdef12-3456-4890-abcd-ef1234567890", + "environment": "live", + "transaction_number": 4123, + "txn_id": "your-txn-2026-05-17-001", + "txn_date": "2026-05-17T08:42:00Z", + "zone_id": "Europe/Madrid", + "transaction_type": "finance", + "action_type": "withdrawal", + "direction": "OUTBOUND", + "status": "APPROVED", + "source": "LIVE", + "amount": "1500", + "currency": "EUR", + "currency_type": "fiat", + "amount_in_default_currency": "1620.45", + "default_currency_code": "USD", + "preferred_currency_amount": "1620.45", + "preferred_currency_code": "USD", + "payment_details": "Withdrawal to crypto wallet", + "payment_txn_id": null, + "score": 0, + "severity": null, + "decision_reason_code": null, + "decision_reason_label": null, + "vendor_data": "user-12345", + "metadata": { + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "address": {}, + "institution_details": {}, + "device_context": {} + }, + "counterparty": {} + }, + "props": {}, + "tags": [], + "parties": [ + { + "uuid": "b1111111-2222-4333-8444-555555555555", + "role": "APPLICANT", + "entity_type": "individual", + "kind": "USER", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "first_name": null, + "last_name": null, + "country_code": null, + "dob": null, + "address": {}, + "institution_info": {}, + "device": {}, + "external_party_snapshot": null + } + ], + "payment_methods": [], + "documents": [], + "activities": [ + { + "uuid": "d4e5f6a7-8901-4bcd-9ef0-123456789abc", + "activity_type": "TRANSACTION_CREATED", + "source": "TRANSACTION", + "status": "APPROVED", + "title": "withdrawal", + "description": "Transaction recorded.", + "metadata": {}, + "occurred_at": "2026-05-17T08:42:11Z" + } + ], + "alerts": [], + "rule_runs": [], + "provider_results": [], + "travel_rule": null, + "network_snapshot": null, + "remediation": null, + "action_required": null, + "cost_breakdown": null + } + }, + "Flagged for review": { + "value": { + "uuid": "abcdef12-3456-4890-abcd-ef1234567891", + "environment": "live", + "transaction_number": 4124, + "txn_id": "your-txn-2026-05-17-002", + "txn_date": "2026-05-17T08:42:00Z", + "zone_id": "Europe/Madrid", + "transaction_type": "finance", + "action_type": "withdrawal", + "direction": "OUTBOUND", + "status": "IN_REVIEW", + "source": "LIVE", + "amount": "0.5", + "currency": "BTC", + "currency_type": "crypto", + "amount_in_default_currency": "32500", + "default_currency_code": "USD", + "preferred_currency_amount": "32500", + "preferred_currency_code": "USD", + "payment_details": "Withdrawal to crypto wallet", + "payment_txn_id": null, + "score": 78, + "severity": "HIGH", + "decision_reason_code": "WALLET_HIGH_RISK", + "decision_reason_label": "Destination wallet flagged as high risk", + "vendor_data": "user-12345", + "metadata": { + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "address": {}, + "institution_details": {}, + "device_context": {} + }, + "counterparty": {} + }, + "props": { + "order_id": "ord-9988" + }, + "tags": [], + "parties": [ + { + "uuid": "b1111111-2222-4333-8444-555555555555", + "role": "APPLICANT", + "entity_type": "individual", + "kind": "USER", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "first_name": null, + "last_name": null, + "country_code": null, + "dob": null, + "address": {}, + "institution_info": {}, + "device": {}, + "external_party_snapshot": null + } + ], + "payment_methods": [], + "documents": [], + "activities": [], + "alerts": [ + { + "uuid": "a7b8c9d0-1234-4abc-9def-567890abcdef", + "title": "Destination wallet flagged as high risk", + "description": "Provider risk score exceeded the configured threshold.", + "severity": "HIGH", + "status": "OPEN", + "source": "RULE", + "metadata": {}, + "created_at": "2026-05-17T08:42:12Z", + "due_at": null + } + ], + "rule_runs": [ + { + "uuid": "e5f6a7b8-9012-4cde-af01-23456789abcd", + "rule": "f6a7b8c9-0123-4def-b012-3456789abcde", + "rule_title": "High-risk wallet screening", + "rule_description": null, + "rule_category": "crypto", + "matched": true, + "is_test": false, + "mode": "ACTIVE", + "severity": "HIGH", + "score_delta": 78, + "status_target": "IN_REVIEW", + "action_summary": "score +78; change_status:IN_REVIEW", + "metadata": {}, + "created_at": "2026-05-17T08:42:12Z" + } + ], + "provider_results": [], + "travel_rule": null, + "network_snapshot": null, + "remediation": null, + "action_required": null, + "cost_breakdown": null + } + } + } + } + } + }, + "400": { + "description": "Validation failed (missing required fields, duplicate `transaction_id`, unknown enum value, invalid currency, etc.).", + "content": { + "application/json": { + "examples": { + "Missing required field": { + "value": { + "transaction_details": { + "amount": [ + "This field is required." + ] + } + } + }, + "Duplicate transaction_id": { + "value": { + "transaction_id": [ + "A transaction with this transaction_id already exists." + ] + } + }, + "Invalid category": { + "value": { + "non_field_errors": [ + "Unsupported transaction category." + ] + } + }, + "Missing subject identity": { + "value": { + "subject": [ + "subject.vendor_data and subject.full_name are required." + ] + } + }, + "Blocklisted participant": { + "value": { + "vendor_data": [ + "The individual 'user-12345' is in the blocklist and cannot participate in transactions." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot submit transactions for this application. This endpoint returns `403` (never `401`) for authentication failures, including requests with no credentials at all.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "409": { + "description": "Another request with the same `transaction_id` created the transaction while this one was being processed, so only one of two simultaneous submissions succeeds. Read the existing transaction instead of resubmitting.", + "content": { + "application/json": { + "examples": { + "Concurrent duplicate": { + "value": { + "detail": "A transaction with this transaction_id already exists." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-didit-sdk": { + "resource": "transactions", + "method": "create", + "auth": "api_key", + "server": true + } + } + }, + "/v3/transactions/{transaction_id}/": { + "get": { + "summary": "Get transaction", + "description": "Retrieve the full monitoring record for one transaction: parties, score/severity, rule runs, alerts, Travel Rule outcome, remediation session.", + "operationId": "getTransaction", + "x-didit-sdk": { + "resource": "transactions", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID (the `uuid` returned from create / list, **not** your `txn_id`).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/abcdef12-3456-7890-abcd-ef1234567890/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nuuid = 'abcdef12-3456-7890-abcd-ef1234567890'\nresp = requests.get(\n f'https://verification.didit.me/v3/transactions/{uuid}/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status()\ntx = resp.json()\nfor alert in tx['alerts']:\n print(alert.get('code'), alert.get('severity'))" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const uuid = 'abcdef12-3456-7890-abcd-ef1234567890';\nconst resp = await fetch(`https://verification.didit.me/v3/transactions/${uuid}/`, {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (!resp.ok) throw new Error(`Lookup failed: ${resp.status}`);\nconst tx = await resp.json();\nconsole.log(tx.status, tx.severity, tx.alerts.length, 'alerts');" + } + ], + "responses": { + "200": { + "description": "Full transaction detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionDetail" + }, + "examples": { + "In review with an alert": { + "value": { + "uuid": "abcdef12-3456-4890-abcd-ef1234567890", + "environment": "live", + "transaction_number": 4123, + "txn_id": "your-txn-2026-05-17-001", + "txn_date": "2026-05-17T08:42:00Z", + "zone_id": "Europe/Madrid", + "transaction_type": "finance", + "action_type": "withdrawal", + "direction": "OUTBOUND", + "status": "IN_REVIEW", + "source": "LIVE", + "amount": "1500", + "currency": "EUR", + "currency_type": "fiat", + "amount_in_default_currency": "1620.45", + "default_currency_code": "USD", + "preferred_currency_amount": "1620.45", + "preferred_currency_code": "USD", + "payment_details": "Withdrawal to crypto wallet", + "payment_txn_id": null, + "score": 78, + "severity": "HIGH", + "decision_reason_code": "WALLET_HIGH_RISK", + "decision_reason_label": "Destination wallet flagged as high risk", + "vendor_data": "user-12345", + "metadata": { + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "address": {}, + "institution_details": {}, + "device_context": {} + }, + "counterparty": {} + }, + "props": { + "order_id": "ord-9988" + }, + "tags": [], + "parties": [ + { + "uuid": "b1111111-2222-4333-8444-555555555555", + "role": "APPLICANT", + "entity_type": "individual", + "kind": "USER", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "first_name": null, + "last_name": null, + "country_code": null, + "dob": null, + "address": {}, + "institution_info": {}, + "device": {}, + "external_party_snapshot": null + } + ], + "payment_methods": [], + "documents": [], + "activities": [], + "alerts": [ + { + "uuid": "a7b8c9d0-1234-4abc-9def-567890abcdef", + "title": "Destination wallet flagged as high risk", + "description": "Provider risk score exceeded the configured threshold.", + "severity": "HIGH", + "status": "OPEN", + "source": "RULE", + "metadata": {}, + "created_at": "2026-05-17T08:42:12Z", + "due_at": null + } + ], + "rule_runs": [], + "provider_results": [], + "travel_rule": null, + "network_snapshot": null, + "remediation": null, + "action_required": null, + "cost_breakdown": null + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read this application's transactions. This endpoint returns `403` (never `401`) for authentication failures, including requests with no credentials at all.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists for the application (deleted, never created, belongs to another application, or the path segment is not a valid UUID).", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/transactions/sdk-token/": { + "post": { + "summary": "Mint an SDK transaction token", + "description": "Mint a short-lived scoped token your app passes to the Didit SDKs so they can submit transactions directly from the end-user's device via `POST /v1/transactions/`. The token is bound to one `vendor_data` (your user id): every transaction submitted with it has its subject identity enforced server-side from that binding, so a tampered client cannot submit on behalf of another user. Mint tokens from your backend - never ship the API key itself in an app. camelCase aliases (`vendorData`, `ttlSeconds`, `maxUses`) are also accepted.", + "operationId": "createTransactionSdkToken", + "x-didit-sdk": { + "resource": "transactions", + "method": "create_sdk_token", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "vendor_data": { + "type": "string", + "description": "Your internal identifier for the end user this token is scoped to. Enforced as the subject identity of every transaction submitted with the token.", + "example": "user-042" + }, + "ttl_seconds": { + "type": "integer", + "default": 900, + "maximum": 86400, + "description": "Token lifetime in seconds. Defaults to 900 (15 minutes); maximum 86400 (24 hours)." + }, + "max_uses": { + "type": "integer", + "nullable": true, + "description": "Maximum number of successful submissions allowed with this token. Omit or null for unlimited uses within the TTL." + } + }, + "required": [ + "vendor_data" + ] + }, + "example": { + "vendor_data": "user-042", + "ttl_seconds": 900, + "max_uses": 3 + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/transactions/sdk-token/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"vendor_data\": \"user-042\", \"ttl_seconds\": 900, \"max_uses\": 3}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/transactions/sdk-token/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={'vendor_data': 'user-042', 'ttl_seconds': 900, 'max_uses': 3},\n timeout=10,\n)\nresp.raise_for_status()\ntoken = resp.json()\nprint(token['sdk_token'], token['expires_at'])" + } + ], + "responses": { + "201": { + "description": "The minted token. Hand `sdk_token` to your app; it authenticates the device-facing `/v1/transactions/` endpoints via the `X-Transaction-Token` header until `expires_at`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sdk_token": { + "type": "string", + "description": "The scoped transaction token to pass to the SDK." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the token stops being accepted." + } + } + }, + "example": { + "sdk_token": "q7Zt...urlsafe", + "expires_at": "2026-07-07T12:15:00Z" + } + } + } + }, + "400": { + "description": "Validation failed: missing `vendor_data`, or `ttl_seconds` outside the allowed range.", + "content": { + "application/json": { + "examples": { + "Missing vendor_data": { + "value": { + "vendor_data": [ + "This field is required." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key.", + "content": { + "application/json": { + "examples": { + "Invalid API Key": { + "value": { + "detail": "Invalid API Key." + } + } + } + } + } + } + } + } + }, + "/v1/transactions/": { + "post": { + "summary": "Submit a transaction from an SDK", + "description": "Device-facing endpoint the Didit SDKs call to submit a transaction directly from the end-user's device. Authentication is a scoped token from `POST /v3/transactions/sdk-token/` sent in the `X-Transaction-Token` header instead of an API key, and the endpoint is CORS-enabled so browsers can call it cross-origin. The payload uses the camelCase wire aliases shown below (the SDK `submitTransaction` methods build it for you). Three server-side guarantees: the application is always taken from the token; the subject identity (`externalUserId`) is always enforced from the token's `vendor_data`, so spoofed payload values are ignored; and device intelligence (the SDK device fingerprint plus server-derived IP and user agent) is attached to the subject automatically.", + "operationId": "submitTransactionFromSdk", + "x-didit-sdk": { + "resource": "transactions", + "method": "submit_from_device", + "auth": "device_token", + "server": false + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "TransactionTokenAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "txnId": { + "type": "string", + "description": "Your unique identifier for this transaction (maps to `transaction_id`, max 128 characters).", + "example": "wd-2026-07-07-0042" + }, + "txnDate": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred (maps to `transaction_at`). Defaults to now if omitted." + }, + "zoneId": { + "type": "string", + "description": "IANA time zone identifier (maps to `time_zone`), e.g. `Europe/Madrid`." + }, + "type": { + "type": "string", + "description": "Transaction category (maps to `transaction_category`), e.g. `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet`.", + "example": "travelRule" + }, + "info": { + "type": "object", + "description": "Core financial details of the transaction (maps to `transaction_details`).", + "required": [ + "direction", + "amount", + "currency" + ], + "properties": { + "direction": { + "type": "string", + "description": "Direction relative to the subject. Case-insensitive; `in`/`out` are accepted as aliases. Echoed back uppercase (`INBOUND`/`OUTBOUND`).", + "example": "out" + }, + "amount": { + "type": "number", + "description": "Transaction amount (up to 18 decimal places). A decimal string is also accepted and is the only way to preserve more than about 15 significant digits: a JSON number is parsed as a binary float first, so `0.123456789012345678` is stored as `0.12345678901234568`.", + "example": 0.25 + }, + "currency": { + "type": "string", + "description": "Currency code. Any ISO 4217 alphabetic code is accepted (e.g. `EUR`, `USD`, `NGN`), as is a crypto asset symbol (e.g. `BTC`). Prefer a currently-circulating code. Not every accepted code has an exchange rate: when none is available for the code (for example the ISO accounting unit `XSU`, or a crypto symbol Didit does not price), automatic conversion fails and `preferred_currency_amount` is returned null unless the request supplied a pre-converted `amountInDefaultCurrency`.", + "example": "ETH" + }, + "currencyType": { + "type": "string", + "description": "Whether the currency is `fiat` or `crypto` (maps to `currency_kind`)." + }, + "amountInDefaultCurrency": { + "type": "number", + "nullable": true, + "description": "Pre-converted amount in the default currency (maps to `amount_in_default_currency`). If omitted, automatic FX conversion is attempted." + }, + "defaultCurrencyCode": { + "type": "string", + "description": "The default currency code for the converted amount (maps to `default_currency`). Any currently-circulating ISO 4217 code." + }, + "paymentDetails": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference or memo (maps to `payment_details`)." + }, + "paymentTxnId": { + "type": "string", + "nullable": true, + "description": "External payment reference (maps to `payment_reference_id`); the on-chain transaction hash for crypto transactions." + }, + "type": { + "type": "string", + "nullable": true, + "description": "Sub-type of the transaction (maps to `action_type`), e.g. `deposit`, `withdrawal`, `transfer`." + }, + "cryptoParams": { + "type": "object", + "description": "Crypto-specific parameters, e.g. `{\"crypto_chain\": \"ETH\"}`." + } + } + }, + "subject": { + "type": "object", + "description": "The subject (applicant) of the transaction. `externalUserId` is ignored here: the subject identity is always enforced from the token's `vendor_data`.", + "required": [ + "fullName" + ], + "properties": { + "type": { + "type": "string", + "description": "Entity type (maps to `entity_type`): `individual` (default) or `company`." + }, + "externalUserId": { + "type": "string", + "description": "Ignored on the subject - enforced server-side from the token's `vendor_data`." + }, + "fullName": { + "type": "string", + "example": "Jane Doe" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "dob": { + "type": "string", + "format": "date", + "description": "Date of birth (YYYY-MM-DD; maps to `date_of_birth`)." + }, + "address": { + "type": "object", + "description": "Address object with `country` (ISO 3166-1 alpha-3), `town`, `state`, `street`, `post_code`." + }, + "institutionInfo": { + "type": "object", + "description": "Institution details for institutional participants (maps to `institution_details`)." + }, + "device": { + "type": "object", + "description": "Optional explicit device context. Merged with the automatically captured device intelligence; server-derived fields win on conflict." + }, + "paymentMethod": { + "type": "object", + "description": "Payment method used by the subject (maps to `payment_method`).", + "properties": { + "type": { + "type": "string", + "description": "Payment method kind (maps to `method_type`), e.g. `bank_card`, `bank_account`, `crypto_wallet`, `ewallet`, `unhosted_wallet`, `other`." + }, + "accountId": { + "type": "string", + "description": "Account identifier (maps to `account_id`): IBAN, card fingerprint, or wallet address." + }, + "issuingCountry": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code of the issuer (maps to `issuing_country`)." + } + } + } + } + }, + "counterparty": { + "type": "object", + "description": "The counterparty of the transaction (optional). Same shape as `subject`; `externalUserId` is meaningful here.", + "properties": { + "type": { + "type": "string", + "description": "Entity type (maps to `entity_type`): `individual` or `company`." + }, + "externalUserId": { + "type": "string", + "description": "Your internal identifier for the counterparty (maps to `vendor_data`)." + }, + "fullName": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "dob": { + "type": "string", + "format": "date" + }, + "address": { + "type": "object" + }, + "institutionInfo": { + "type": "object" + }, + "device": { + "type": "object" + }, + "paymentMethod": { + "type": "object", + "description": "Payment method used by the counterparty (maps to `payment_method`). On travelRule transactions, `type: \"unhosted_wallet\"` declares the counterparty wallet as self-hosted: the transfer skips counterparty-VASP routing, is created directly in `UNCONFIRMED_OWNERSHIP`, and (when `auto_wallet_verification` is enabled) a wallet-ownership widget session is auto-minted and returned in `action_required` so the SDK can launch it.", + "properties": { + "type": { + "type": "string", + "description": "Payment method kind (maps to `method_type`), e.g. `bank_card`, `bank_account`, `crypto_wallet`, `ewallet`, `unhosted_wallet`, `other`. Use `unhosted_wallet` to declare a self-hosted wallet for the Travel Rule." + }, + "accountId": { + "type": "string", + "description": "Account identifier (maps to `account_id`): IBAN, card fingerprint, or wallet address." + }, + "issuingCountry": { + "type": "string" + } + } + } + } + }, + "props": { + "type": "object", + "additionalProperties": true, + "description": "Custom key-value pairs (maps to `custom_properties`). Each key can be referenced in rule conditions as `custom_values.`." + }, + "travelRule": { + "type": "object", + "description": "Travel Rule details (maps to `travel_rule_details`). When Travel Rule is enabled for the application, Didit ignores any `status` you send and drives the exchange itself.", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string" + }, + "protocol": { + "type": "string" + }, + "required": { + "type": "boolean" + }, + "obligationsCount": { + "type": "integer" + }, + "originatorData": { + "type": "object" + }, + "beneficiaryData": { + "type": "object" + }, + "metadata": { + "type": "object" + } + } + }, + "includeCryptoScreening": { + "type": "boolean", + "nullable": true, + "description": "Per-transaction override for crypto blockchain analytics screening (maps to `include_crypto_screening`)." + }, + "fingerprint_v2": { + "type": "object", + "description": "The `didit-fp-v2` device fingerprint payload. Injected automatically by the SDKs; only relevant when calling the wire API directly." + } + }, + "required": [ + "txnId", + "type", + "info", + "subject" + ] + }, + "example": { + "txnId": "wd-2026-07-07-0042", + "type": "travelRule", + "info": { + "direction": "out", + "amount": "0.25", + "currency": "ETH", + "currencyType": "crypto", + "cryptoParams": { + "crypto_chain": "ETH" + } + }, + "subject": { + "fullName": "Jane Doe" + }, + "counterparty": { + "fullName": "Ana Diaz", + "paymentMethod": { + "type": "unhosted_wallet", + "accountId": "0xBeneficiaryWallet01" + } + }, + "travelRule": { + "status": "AWAITING_COUNTERPARTY", + "beneficiaryData": { + "wallet_address": "0xBeneficiaryWallet01", + "name": "Ana Diaz" + } + }, + "includeCryptoScreening": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v1/transactions/ \\\n -H 'X-Transaction-Token: SDK_TOKEN' -H 'Content-Type: application/json' \\\n -d '{\"txnId\": \"wd-2026-07-07-0042\", \"type\": \"travelRule\", \"info\": {\"direction\": \"out\", \"amount\": \"0.25\", \"currency\": \"ETH\", \"currencyType\": \"crypto\"}, \"subject\": {\"fullName\": \"Jane Doe\"}, \"counterparty\": {\"fullName\": \"Ana Diaz\", \"paymentMethod\": {\"type\": \"unhosted_wallet\", \"accountId\": \"0xBeneficiaryWallet01\"}}, \"travelRule\": {\"status\": \"AWAITING_COUNTERPARTY\", \"beneficiaryData\": {\"wallet_address\": \"0xBeneficiaryWallet01\", \"name\": \"Ana Diaz\"}}, \"includeCryptoScreening\": true}'" + }, + { + "lang": "javascript", + "label": "Web SDK", + "source": "import { DiditSdk } from '@didit-protocol/sdk-web';\n\nconst result = await DiditSdk.shared.submitTransaction({\n transactionToken: sdkToken,\n transaction: {\n txnId: 'wd-2026-07-07-0042',\n category: 'travelRule',\n details: { direction: 'out', amount: '0.25', currency: 'ETH', currencyType: 'crypto' },\n subject: { fullName: 'Jane Doe' },\n counterparty: { fullName: 'Ana Diaz', paymentMethod: { type: 'unhosted_wallet', accountId: '0xBeneficiaryWallet01' } },\n travelRule: { status: 'AWAITING_COUNTERPARTY', beneficiaryData: { wallet_address: '0xBeneficiaryWallet01', name: 'Ana Diaz' } },\n includeCryptoScreening: true\n },\n onActionCompleted: (refreshed) => console.log(refreshed.status)\n});" + } + ], + "responses": { + "201": { + "description": "Transaction submitted and monitored. Same detail shape as `GET /v3/transactions/{transaction_id}/`, including the `action_required` block when the end user must complete a follow-up action (the SDKs auto-launch it by default).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionDetail" + }, + "examples": { + "Wallet ownership required": { + "value": { + "uuid": "22222222-3333-4444-5555-666666666666", + "txn_id": "wd-2026-07-07-0042", + "transaction_type": "travelRule", + "direction": "OUTBOUND", + "status": "APPROVED", + "amount": "0.25", + "currency": "ETH", + "travel_rule": { + "uuid": "33333333-4444-5555-6666-777777777777", + "status": "UNCONFIRMED_OWNERSHIP", + "required": true + }, + "action_required": { + "type": "wallet_ownership", + "url": "https://verify.didit.me/wallet-ownership/3s9x...urlsafe", + "widget_session_id": "77777777-8888-9999-aaaa-bbbbbbbbbbbb", + "expires_at": "2026-07-07T13:00:00Z" + } + } + } + } + } + } + }, + "400": { + "description": "Validation failed (missing required fields, duplicate `txnId`, unknown category).", + "content": { + "application/json": { + "examples": { + "Missing fields": { + "value": { + "transaction_details": { + "currency": [ + "This field is required." + ] + } + } + }, + "Duplicate txnId": { + "value": { + "transaction_id": [ + "A transaction with this transaction_id already exists." + ] + } + } + } + } + } + }, + "401": { + "description": "The transaction token was rejected: missing, unknown, revoked, over its `max_uses`, or expired. Expired-token responses always contain the word `expired` in the detail, which is how the SDKs distinguish `expired_token` from `invalid_token` errors.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "Invalid transaction token." + } + }, + "Expired token": { + "value": { + "detail": "Transaction token expired." + } + } + } + } + } + }, + "409": { + "description": "Another request with the same `txnId` created the transaction while this one was being processed, so only one of two simultaneous submissions succeeds. Read the existing transaction instead of resubmitting.", + "content": { + "application/json": { + "examples": { + "Concurrent duplicate": { + "value": { + "detail": "A transaction with this transaction_id already exists." + } + } + } + } + } + } + } + } + }, + "/v1/transactions/{transaction_id}/": { + "get": { + "summary": "Get a transaction from an SDK", + "description": "Device-facing read endpoint, authenticated with the same `X-Transaction-Token` used to submit. The SDKs poll it after an auto-launched action flow completes or its window closes, so the final state never depends on a single notification. Returns the same transaction detail as the submit response, including the current `action_required` block (`null` once the action is resolved).", + "operationId": "getTransactionFromSdk", + "x-didit-sdk": { + "resource": "transactions", + "method": "retrieve_from_device", + "auth": "device_token", + "server": false + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID (the `uuid` from the submit response, **not** your `txnId`).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + } + ], + "security": [ + { + "TransactionTokenAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v1/transactions/22222222-3333-4444-5555-666666666666/' \\\n -H 'X-Transaction-Token: SDK_TOKEN'" + } + ], + "responses": { + "200": { + "description": "The full transaction detail, including `action_required` (`null` once the pending action is resolved).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionDetail" + } + } + } + }, + "401": { + "description": "The transaction token was rejected: missing, unknown, revoked, over its `max_uses`, or expired.", + "content": { + "application/json": { + "examples": { + "Expired token": { + "value": { + "detail": "Transaction token expired." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists for the token's application, or the path segment is not a valid UUID.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + } + } + } + }, + "/v3/businesses/": { + "get": { + "summary": "List businesses", + "description": "List [businesses](/entities/businesses) Didit assembles from KYB sessions sharing the same `vendor_data` (free-form string, NOT a UUID), plus businesses created via the API. Paginated, ordered by `last_session_at` desc. This endpoint does not support filtering or search parameters — fetch pages and filter client-side.", + "operationId": "list_businesses", + "x-didit-sdk": { + "resource": "businesses", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size. Defaults to 50 when omitted. There is no enforced maximum, but keep pages small for latency." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset of the first record to return. Prefer following the `next` URL from the previous page over computing this by hand." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/businesses/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.get(\n 'https://verification.didit.me/v3/businesses/',\n headers={'x-api-key': 'YOUR_API_KEY'},\n params={'limit': 50},\n)\nresp.raise_for_status()\npage = resp.json()\nfor biz in page['results']:\n print(biz['vendor_data'], biz['status'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const url = new URL('https://verification.didit.me/v3/businesses/');\nurl.searchParams.set('limit', '50');\n\nconst page = await fetch(url, {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n}).then((r) => r.json());\nconsole.log(page.count, page.results.length);" + } + ], + "responses": { + "200": { + "description": "Paginated list of businesses with status, session counts, and feature breakdown.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows — exact up to 100, capped at 100 beyond that for performance. Do not use it to detect the last page; rely on `next` being `null` instead." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BusinessListItem" + } + } + } + }, + "examples": { + "Example": { + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-123", + "display_name": null, + "legal_name": "Acme Trading Ltd", + "registration_number": "12345678", + "country_code": "GB", + "effective_name": "Acme Trading Ltd", + "status": "ACTIVE", + "session_count": 2, + "approved_count": 1, + "declined_count": 0, + "in_review_count": 1, + "features": { + "KYB": "Approved" + }, + "features_list": [ + { + "feature": "KYB", + "status": "Approved" + } + ], + "last_session_at": "2026-03-15T10:30:00Z", + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": "2026-01-10T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2026-01-10T08:00:00Z" + } + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot list businesses for this application. This endpoint returns `403` (never `401`) for authentication failures, including requests with no credentials at all.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/customization/": { + "get": { + "summary": "Get branding customization", + "description": "Returns the white-label branding for the application resolved from the API key: colors, fonts, border radii, logo URLs and UI/UX options. Custom domain and white-label email settings are managed only in the Business Console and are not part of this response.", + "operationId": "get_customization", + "x-didit-sdk": { + "resource": "customization", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Customization" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/customization/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.get(\n 'https://verification.didit.me/v3/customization/',\n headers={'x-api-key': 'YOUR_API_KEY'},\n)\nresp.raise_for_status()\nbranding = resp.json()\nprint(branding['font_family'], branding['color_primary'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const branding = await fetch(\n 'https://verification.didit.me/v3/customization/',\n { headers: { 'x-api-key': process.env.DIDIT_API_KEY } },\n).then((r) => r.json());\nconsole.log(branding.font_family, branding.color_primary);" + } + ], + "responses": { + "200": { + "description": "Current branding customization for the application.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrandingCustomization" + }, + "examples": { + "Default branding": { + "value": { + "color_primary": "#000000", + "color_secondary": "#9DA1A1", + "color_background": "#FFFFFF", + "color_panel": "#2567FF", + "color_panel_10": "#1A1A1A", + "color_on_panel_1": "#9DA1A1", + "color_on_panel_2": "#F4F4F6", + "color_on_background": "#000000", + "color_button_1": "#2567FF", + "color_button_2": "#F8F8F8", + "color_button_text_1": "#FFFFFF", + "color_button_text_2": "#000000", + "color_pill_text": "#111827", + "border_radius_panel": 10, + "border_radius_buttons": 12, + "font_family": "Inter", + "font_weight": "regular", + "font_url": "https://fonts.googleapis.com/css2?family=Inter", + "logo_square": null, + "logo_rectangular": null, + "logo_rectangular_dark": null, + "favicon": null, + "app_public_name": "Acme Verify", + "privacy_policy_url": "https://acme.com/privacy", + "skip_welcome_screen": false, + "disable_login_with_didit": false, + "hide_progress_bar": false, + "callback_seconds": 3 + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. Didit returns 403 (never 401) for all authentication failures on this endpoint.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update branding customization", + "description": "Partial update of the application's white-label branding. Send `application/json` to change colors, fonts, radii and UI options, or `multipart/form-data` to upload logo images. Only the fields you include are changed. Custom domain and white-label email cannot be set through this endpoint — use the Business Console for those.", + "operationId": "update_customization", + "x-didit-sdk": { + "resource": "customization", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Customization" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrandingCustomizationUpdate" + }, + "examples": { + "Update colors and font": { + "summary": "Change branding colors and font", + "value": { + "color_primary": "#123456", + "color_button_1": "#2567FF", + "font_family": "AR One Sans", + "border_radius_buttons": 16 + } + }, + "Update UI options": { + "summary": "Change public name and flow options", + "value": { + "app_public_name": "Acme Verify", + "privacy_policy_url": "https://acme.com/privacy", + "skip_welcome_screen": true, + "hide_progress_bar": true, + "callback_seconds": 5 + } + }, + "Remove a logo": { + "summary": "Delete the current rectangular logo", + "value": { + "delete_logo_rectangular": true + } + }, + "Remove the dark-mode logo": { + "summary": "Delete the current dark-mode logo", + "value": { + "delete_logo_rectangular_dark": true + } + } + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "description": "Use multipart/form-data only to upload logo images. Branding fields above may also be sent as form fields in the same request.", + "properties": { + "image_square": { + "type": "string", + "format": "binary", + "description": "Square logo (1:1 aspect ratio). PNG, JPG or WebP, at least 512 px on the long side, at most **1 MB**." + }, + "image_rectangular": { + "type": "string", + "format": "binary", + "description": "Rectangular logo (2:1 aspect ratio). PNG, JPG or WebP, at least 512 px on the long side, at most **1 MB**." + }, + "image_rectangular_dark": { + "type": "string", + "format": "binary", + "description": "Rectangular logo for dark mode (2:1 aspect ratio), shown on a dark background. PNG, JPG or WebP, at least 512 px on the long side, at most **1 MB**. Returned as `logo_rectangular_dark`." + }, + "image_favicon": { + "type": "string", + "format": "binary", + "description": "Favicon image. Max **500 KB**." + } + } + }, + "example": { + "image_rectangular": "(binary PNG logo, 2:1)", + "image_rectangular_dark": "(binary PNG logo for dark mode, 2:1)", + "image_favicon": "(binary favicon)" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/customization/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"color_primary\": \"#123456\", \"font_family\": \"AR One Sans\"}'" + }, + { + "lang": "curl", + "label": "curl (logo upload)", + "source": "curl -X PATCH 'https://verification.didit.me/v3/customization/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'image_rectangular=@logo.png' \\\n -F 'image_rectangular_dark=@logo-dark.png' \\\n -F 'image_favicon=@favicon.ico'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.patch(\n 'https://verification.didit.me/v3/customization/',\n headers={'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json'},\n json={'color_primary': '#123456', 'font_family': 'AR One Sans'},\n)\nresp.raise_for_status()\nprint(resp.json()['color_primary'])\n\n# Upload a logo (multipart):\nwith open('logo.png', 'rb') as logo:\n requests.patch(\n 'https://verification.didit.me/v3/customization/',\n headers={'x-api-key': 'YOUR_API_KEY'},\n files={'image_rectangular': logo},\n )" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/customization/', {\n method: 'PATCH',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY, 'Content-Type': 'application/json' },\n body: JSON.stringify({ color_primary: '#123456', font_family: 'AR One Sans' }),\n});\nconst branding = await resp.json();\nconsole.log(branding.color_primary);" + } + ], + "responses": { + "200": { + "description": "Branding updated. The FULL branding object is returned (all fields, not just the ones you changed).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrandingCustomization" + }, + "examples": { + "Updated": { + "value": { + "color_primary": "#123456", + "color_secondary": "#9DA1A1", + "color_background": "#FFFFFF", + "color_panel": "#2567FF", + "color_panel_10": "#1A1A1A", + "color_on_panel_1": "#9DA1A1", + "color_on_panel_2": "#F4F4F6", + "color_on_background": "#000000", + "color_button_1": "#2567FF", + "color_button_2": "#F8F8F8", + "color_button_text_1": "#FFFFFF", + "color_button_text_2": "#000000", + "color_pill_text": "#111827", + "border_radius_panel": 10, + "border_radius_buttons": 16, + "font_family": "AR One Sans", + "font_weight": "400", + "font_url": "https://fonts.googleapis.com/css2?family=AR+One+Sans", + "logo_square": null, + "logo_rectangular": "https://cdn.didit.me/white_label/logo_rectangular/...", + "logo_rectangular_dark": null, + "favicon": null, + "app_public_name": "Acme Verify", + "privacy_policy_url": "https://acme.com/privacy", + "skip_welcome_screen": true, + "disable_login_with_didit": false, + "hide_progress_bar": false, + "callback_seconds": 3 + } + } + } + } + } + }, + "400": { + "description": "Invalid body. For example: a malformed hex color, an unknown font family or weight, a Business-Console-only field (`domain`, `from_email`, `verify_domain`, `force_remove_domain`, `verify_email_domain`, `force_remove_email_domain`, and their read-only companions), or a logo upload that is not a PNG, JPG or WebP image, is under 512 px on its long side, is over 1 MB, or has the wrong aspect ratio. Also returned when setting `disable_login_with_didit` to `false` while a custom domain is configured.", + "content": { + "application/json": { + "examples": { + "Bad color": { + "value": { + "color_primary": [ + "Invalid color format. Use '#RRGGBB' or '#RRGGBBAA' format." + ] + } + }, + "Bad logo": { + "value": { + "image_rectangular": [ + "Image aspect ratio must be 2:1. Got 1024x1024 image dimensions" + ] + } + }, + "Unsupported logo format": { + "summary": "Not a PNG, JPG or WebP image (an SVG, for example)", + "value": { + "image_rectangular_dark": [ + "Upload a PNG, JPG or WebP image." + ] + } + }, + "Logo too small": { + "summary": "Under 512 px on the long side", + "value": { + "image_rectangular": [ + "Image must be at least 512 px on its long side. Got 400x200 image dimensions" + ] + } + }, + "Logo too large": { + "summary": "Over 1 MB", + "value": { + "image_square": [ + "File size should not exceed 1 MB" + ] + } + }, + "Console-only field": { + "value": { + "domain": [ + "Manage this field in the Business Console." + ] + } + }, + "Bad font weight": { + "value": { + "font_weight": [ + "Font weight 950 is not available for Inter. Available weights are: 100, 200, 300, 400, 500, 600, 700, 800, 900" + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. Didit returns 403 (never 401) for all authentication failures on this endpoint.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/businesses/{vendor_data}/": { + "get": { + "summary": "Get business", + "description": "Fetch one [business](/entities/businesses) by `vendor_data` (exact match). Adds `metadata` and `updated_at` to the list view. Deleted businesses return 404.", + "operationId": "get_business", + "x-didit-sdk": { + "resource": "businesses", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the business — a free-form string (NOT a UUID), matched exactly as sent.", + "example": "company-123" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/businesses/company-123/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.get(\n 'https://verification.didit.me/v3/businesses/company-123/',\n headers={'x-api-key': 'YOUR_API_KEY'},\n)\nresp.raise_for_status()\nbiz = resp.json()\nprint(biz['didit_internal_id'], biz['status'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const vendorData = encodeURIComponent('company-123');\nconst biz = await fetch(\n `https://verification.didit.me/v3/businesses/${vendorData}/`,\n { headers: { 'x-api-key': process.env.DIDIT_API_KEY } },\n).then((r) => r.json());\nconsole.log(biz.didit_internal_id, biz.status);" + } + ], + "responses": { + "200": { + "description": "Full business detail including metadata and aggregated session data.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDetailItem" + }, + "examples": { + "Active business": { + "value": { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-123", + "display_name": null, + "legal_name": "Acme Trading Ltd", + "registration_number": "12345678", + "country_code": "GB", + "effective_name": "Acme Trading Ltd", + "status": "ACTIVE", + "session_count": 2, + "approved_count": 1, + "declined_count": 0, + "in_review_count": 1, + "features": { + "KYB": "Approved" + }, + "features_list": [ + { + "feature": "KYB", + "status": "Approved" + } + ], + "last_session_at": "2026-03-15T10:30:00Z", + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": "2026-01-10T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2026-01-10T08:00:00Z", + "metadata": { + "industry": "fintech" + }, + "updated_at": "2026-03-15T10:30:00Z" + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read this application's businesses. This endpoint returns `403` (never `401`) for authentication failures, including requests with no credentials at all.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No (non-deleted) business with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update business", + "description": "Partial update of a business: `display_name`, `legal_name`, `registration_number`, `country_code`, `status`, `metadata`. `metadata` fully replaces. Setting `status` to `BLOCKED` adds the `vendor_data` to the system blocklist; moving away from `BLOCKED` removes that entry. Every edit is recorded in the company's activity: a status change with the optional `reason`, and a change to `display_name`, `legal_name`, `registration_number`, `country_code` or `metadata` as a profile update.", + "operationId": "update_business", + "x-didit-sdk": { + "resource": "businesses", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the business (free-form string, NOT a UUID).", + "example": "company-123" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Partial-update body. Provide only the fields you want to change.", + "properties": { + "display_name": { + "type": "string", + "nullable": true, + "description": "Friendly display name shown in the console (takes precedence over `legal_name` for UI display)." + }, + "legal_name": { + "type": "string", + "nullable": true, + "description": "Official legal name from registry or manual entry." + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Company registration or incorporation number." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "Country of incorporation (ISO 3166-1 alpha-2, e.g. `GB`, `US`)." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "Business lifecycle status. Setting `BLOCKED` automatically adds the `vendor_data` to the system blocklist; flipping away from `BLOCKED` removes that blocklist entry." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Arbitrary JSON object you attach to the business. **Fully replaces** the existing metadata on update — merge client-side if you only want to add keys." + }, + "reason": { + "type": "string", + "nullable": true, + "maxLength": 1000, + "description": "Why the status changed. Recorded on the status-change entry of the company's **Activity** in the Business Console, only when the status actually changes. Webhooks do not carry it." + } + } + }, + "examples": { + "Update name": { + "summary": "Update display name", + "value": { + "display_name": "Acme Corp" + } + }, + "Flag for review": { + "summary": "Flip status", + "value": { + "status": "FLAGGED" + } + }, + "Add metadata": { + "summary": "Replace metadata JSON", + "value": { + "metadata": { + "industry": "fintech", + "risk_level": "low" + } + } + }, + "Fix registry data": { + "summary": "Correct extracted fields", + "value": { + "legal_name": "Acme Trading International Ltd", + "registration_number": "12345678", + "country_code": "GB" + } + }, + "Flag with reason": { + "summary": "Flag the business and record why", + "value": { + "status": "FLAGGED", + "reason": "Ownership structure under review" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/businesses/company-123/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"display_name\": \"Acme Corp\", \"status\": \"FLAGGED\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.patch(\n 'https://verification.didit.me/v3/businesses/company-123/',\n headers={'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json'},\n json={'display_name': 'Acme Corp', 'status': 'FLAGGED'},\n)\nresp.raise_for_status()\nprint(resp.json()['status'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/businesses/company-123/', {\n method: 'PATCH',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY, 'Content-Type': 'application/json' },\n body: JSON.stringify({ display_name: 'Acme Corp', status: 'FLAGGED' }),\n});\nconst biz = await resp.json();\nconsole.log(biz.status);" + } + ], + "responses": { + "200": { + "description": "Business updated. The full updated business record is returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDetailItem" + }, + "examples": { + "Updated": { + "value": { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-123", + "display_name": "Acme Corp", + "legal_name": "Acme Trading Ltd", + "registration_number": "12345678", + "country_code": "GB", + "effective_name": "Acme Corp", + "status": "FLAGGED", + "session_count": 2, + "approved_count": 1, + "declined_count": 0, + "in_review_count": 1, + "features": { + "KYB": "Approved" + }, + "features_list": [ + { + "feature": "KYB", + "status": "Approved" + } + ], + "last_session_at": "2026-03-15T10:30:00Z", + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": "2026-01-10T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2026-01-10T08:00:00Z", + "metadata": { + "industry": "fintech", + "risk_level": "low" + }, + "updated_at": "2026-03-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Invalid body: typically an unknown `status`, malformed `metadata` or a `reason` over 1000 characters.", + "content": { + "application/json": { + "examples": { + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Bad metadata": { + "value": { + "metadata": [ + "Metadata must be a JSON object." + ] + } + }, + "Reason too long": { + "value": { + "reason": [ + "Ensure this field has no more than 1000 characters." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot update businesses for this application. This endpoint returns `403` (never `401`) for authentication failures, including requests with no credentials at all.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No (non-deleted) business with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/businesses/{vendor_data}/update-status/": { + "patch": { + "summary": "Update business status", + "description": "Flip only the lifecycle `status` of a business: `ACTIVE`, `FLAGGED` or `BLOCKED` (not session statuses). `BLOCKED` adds the `vendor_data` to the system blocklist; moving away from `BLOCKED` removes that entry. Each change is recorded in the company's activity, with the optional `reason`. Sending the current status changes nothing and records no `reason`. The `business.status.updated` webhook does not carry `reason`.", + "operationId": "update_business_status", + "x-didit-sdk": { + "resource": "businesses", + "method": "update_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the business (free-form string, NOT a UUID).", + "example": "company-123" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/EntityStatusUpdateRequest" + }, + { + "type": "object", + "properties": { + "reason": { + "type": "string", + "nullable": true, + "maxLength": 1000, + "description": "Why the status changed. Recorded on the status-change entry of the company's **Activity** in the Business Console, only when the status actually changes. Webhooks do not carry it." + } + } + } + ] + }, + "examples": { + "Flag": { + "summary": "Flag the business for review", + "value": { + "status": "FLAGGED" + } + }, + "Block": { + "summary": "Block the business (adds to system blocklist)", + "value": { + "status": "BLOCKED" + } + }, + "Reactivate": { + "summary": "Reactivate the business", + "value": { + "status": "ACTIVE" + } + }, + "Block with reason": { + "summary": "Block the business and record why", + "value": { + "status": "BLOCKED", + "reason": "Registry shows the company dissolved" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/businesses/company-123/update-status/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"status\": \"BLOCKED\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.patch(\n 'https://verification.didit.me/v3/businesses/company-123/update-status/',\n headers={'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json'},\n json={'status': 'BLOCKED'},\n)\nresp.raise_for_status()\nprint(resp.json()['status'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/businesses/company-123/update-status/', {\n method: 'PATCH',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY, 'Content-Type': 'application/json' },\n body: JSON.stringify({ status: 'BLOCKED' }),\n});\nconst biz = await resp.json();\nconsole.log(biz.status);" + } + ], + "responses": { + "200": { + "description": "Business status updated. Full business record returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDetailItem" + }, + "examples": { + "Blocked": { + "value": { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-123", + "display_name": null, + "legal_name": "Acme Trading Ltd", + "registration_number": "12345678", + "country_code": "GB", + "effective_name": "Acme Trading Ltd", + "status": "BLOCKED", + "session_count": 2, + "approved_count": 1, + "declined_count": 0, + "in_review_count": 1, + "features": { + "KYB": "Approved" + }, + "features_list": [ + { + "feature": "KYB", + "status": "Approved" + } + ], + "last_session_at": "2026-03-15T10:30:00Z", + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": "2026-01-10T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2026-01-10T08:00:00Z", + "metadata": { + "industry": "fintech" + }, + "updated_at": "2026-03-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Missing or invalid `status`, or a `reason` over 1000 characters.", + "content": { + "application/json": { + "examples": { + "Missing status": { + "value": { + "status": [ + "This field is required." + ] + } + }, + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Reason too long": { + "value": { + "reason": [ + "Ensure this field has no more than 1000 characters." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot update businesses for this application. This endpoint returns `403` (never `401`) for authentication failures, including requests with no credentials at all.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No (non-deleted) business with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/businesses/delete/": { + "post": { + "summary": "Batch delete businesses", + "description": "**Permanently delete** businesses — this cannot be undone. For blocking prefer [Update Business Status](#patch-/v3/businesses/-vendor_data-/update-status/) with `BLOCKED`.\n\n**Filed regulatory reports:** if the selected data is in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "batch_delete_businesses", + "x-didit-sdk": { + "resource": "businesses", + "method": "batch_delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Provide `vendor_data_list`, `didit_internal_id_list`, or `delete_all: true`. Precedence when several are sent: `delete_all` > `vendor_data_list` > `didit_internal_id_list` (only the highest-precedence selector is used).", + "properties": { + "vendor_data_list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Your own identifiers to delete, matched exactly as sent." + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit's internal UUIDs (`didit_internal_id`) to delete." + }, + "delete_all": { + "type": "boolean", + "default": false, + "description": "If `true`, deletes every business in the application. Cannot be combined with the list selectors." + } + } + }, + "examples": { + "Specific businesses": { + "summary": "Delete by vendor_data", + "value": { + "vendor_data_list": [ + "company-123", + "company-456" + ] + } + }, + "Specific internal IDs": { + "summary": "Delete by didit_internal_id", + "value": { + "didit_internal_id_list": [ + "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "b2c3d4e5-f6a7-8901-bcde-f12345678901" + ] + } + }, + "All businesses": { + "summary": "Wipe every business in this application", + "value": { + "delete_all": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/businesses/delete/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vendor_data_list\": [\"company-123\", \"company-456\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/businesses/delete/',\n headers={'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json'},\n json={'vendor_data_list': ['company-123', 'company-456']},\n)\nresp.raise_for_status()\nprint('deleted', resp.json()['deleted'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/businesses/delete/', {\n method: 'POST',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY, 'Content-Type': 'application/json' },\n body: JSON.stringify({ vendor_data_list: ['company-123', 'company-456'] }),\n});\nconst body = await resp.json();\nconsole.log('deleted', body.deleted);" + } + ], + "responses": { + "200": { + "description": "Businesses deleted. Returns the number of rows actually removed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deleted": { + "type": "integer", + "description": "Number of businesses deleted (excludes vendor_data values that didn't match anything)." + } + } + }, + "examples": { + "Deleted some": { + "value": { + "deleted": 2 + } + }, + "Nothing matched": { + "value": { + "deleted": 0 + } + } + } + } + } + }, + "400": { + "description": "No selector supplied (all of `vendor_data_list`, `didit_internal_id_list`, and `delete_all` missing or empty). The error body is a JSON array.", + "content": { + "application/json": { + "examples": { + "Missing selector": { + "value": [ + "Provide vendor_data_list, didit_internal_id_list, or set delete_all=true." + ] + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot delete businesses for this application. This endpoint returns `403` (never `401`) for authentication failures, including requests with no credentials at all.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + }, + "409": { + "description": "The selection includes a company in a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/users/aml-monitoring/": { + "post": { + "summary": "Enable or disable AML ongoing monitoring for users", + "description": "Turns AML ongoing monitoring on or off for the selected users, with the same semantics as the Business Console. Exactly one monitored AML screening is kept per user, so a profile that verified several times is monitored and billed once; disabling turns monitoring off across all of the profile's screenings. Users without any AML screening are screened first (billed as a regular AML screening) when they have the full name, date of birth and country needed; otherwise they are reported as `skipped_no_identity_data`.\n\nSet `estimate_only: true` to preview counts and costs without writing anything. Selections of 20 profiles or fewer resolve inline and return `200` with the completed job; larger selections return `202` and run in the background - poll [Get AML monitoring job](#get-/v3/aml-monitoring/jobs/-job_id-/) with the returned `job_id`. Enabling is refused with `403` when the organization balance does not cover the estimate. Identifiers that match nothing are listed in `unmatched` and never fail the request: a selection that matches nothing completes as an empty job (`total` 0), the same convention as the batch delete endpoints.", + "operationId": "toggle_users_aml_monitoring", + "x-didit-sdk": { + "resource": "users", + "method": "set_aml_monitoring", + "auth": "api_key", + "server": true + }, + "tags": [ + "AML Ongoing Monitoring" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_enabled" + ], + "description": "Select users with `vendor_data_list`, `didit_internal_id_list`, or both (the two lists are OR'd). At most 1000 identifiers per request across both lists.", + "properties": { + "is_enabled": { + "type": "boolean", + "description": "true to enable monitoring, false to disable it." + }, + "estimate_only": { + "type": "boolean", + "default": false, + "description": "When true, returns the cost estimate and performs no writes - no screening, no billing, no job." + }, + "vendor_data_list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Your own identifiers of the users to toggle, matched exactly as sent." + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit internal ids of the users to toggle - the `didit_internal_id` returned by the create, get and list endpoints." + } + } + }, + "examples": { + "Estimate first": { + "summary": "Preview the cost without writing", + "value": { + "is_enabled": true, + "estimate_only": true, + "vendor_data_list": [ + "user-123", + "user-456" + ] + } + }, + "Enable": { + "summary": "Enable monitoring for two users", + "value": { + "is_enabled": true, + "vendor_data_list": [ + "user-123", + "user-456" + ] + } + }, + "Disable": { + "summary": "Disable monitoring by Didit internal id", + "value": { + "is_enabled": false, + "didit_internal_id_list": [ + "3f1c0d3e-8f0a-4c2e-9a3d-9b1f5f4a2c11" + ] + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/users/aml-monitoring/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"is_enabled\": true, \"vendor_data_list\": [\"user-123\", \"user-456\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/users/aml-monitoring/',\n headers={'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json'},\n json={'is_enabled': True, 'vendor_data_list': ['user-123', 'user-456']},\n)\nresp.raise_for_status()\njob = resp.json()\nprint(job['status'], job['outcomes'])\nfor row in job['results']:\n print(row['vendor_data'], row['outcome'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/users/aml-monitoring/', {\n method: 'POST',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY, 'Content-Type': 'application/json' },\n body: JSON.stringify({ is_enabled: true, vendor_data_list: ['user-123', 'user-456'] }),\n});\nconst job = await resp.json();\nconsole.log(job.status, job.outcomes);\nfor (const row of job.results) console.log(row.vendor_data, row.outcome);" + } + ], + "responses": { + "200": { + "description": "The estimate (when `estimate_only` is true) or the completed job for a selection processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AMLMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger selections run in the background. Poll the job endpoint with `job_id`; `results` fills in as profiles are processed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AMLMonitoringJob" + } + } + } + }, + "400": { + "description": "Validation error: `is_enabled` missing, no selector, or more than 1000 identifiers across both lists." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Insufficient balance to enable monitoring for this selection. The response carries the `estimate` so you can top up the right amount.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + } + } + } + } + } + } + } + } + } + } + }, + "/v3/businesses/aml-monitoring/": { + "post": { + "summary": "Enable or disable AML ongoing monitoring for businesses", + "description": "Turns AML ongoing monitoring on or off for the selected businesses, with the same semantics as the Business Console. Exactly one monitored AML screening is kept per business, so a profile that verified several times is monitored and billed once; disabling turns monitoring off across all of the profile's screenings. Businesses without any AML screening are screened first (billed as a regular AML screening) when they have the legal or display name needed; otherwise they are reported as `skipped_no_identity_data`.\n\nSet `estimate_only: true` to preview counts and costs without writing anything. Selections of 20 profiles or fewer resolve inline and return `200` with the completed job; larger selections return `202` and run in the background - poll [Get AML monitoring job](#get-/v3/aml-monitoring/jobs/-job_id-/) with the returned `job_id`. Enabling is refused with `403` when the organization balance does not cover the estimate. Identifiers that match nothing are listed in `unmatched` and never fail the request: a selection that matches nothing completes as an empty job (`total` 0), the same convention as the batch delete endpoints.", + "operationId": "toggle_businesses_aml_monitoring", + "x-didit-sdk": { + "resource": "businesses", + "method": "set_aml_monitoring", + "auth": "api_key", + "server": true + }, + "tags": [ + "AML Ongoing Monitoring" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_enabled" + ], + "description": "Select businesses with `vendor_data_list`, `didit_internal_id_list`, or both (the two lists are OR'd). At most 1000 identifiers per request across both lists.", + "properties": { + "is_enabled": { + "type": "boolean", + "description": "true to enable monitoring, false to disable it." + }, + "estimate_only": { + "type": "boolean", + "default": false, + "description": "When true, returns the cost estimate and performs no writes - no screening, no billing, no job." + }, + "vendor_data_list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Your own identifiers of the businesses to toggle, matched exactly as sent." + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit internal ids of the businesses to toggle - the `didit_internal_id` returned by the create, get and list endpoints." + } + } + }, + "examples": { + "Estimate first": { + "summary": "Preview the cost without writing", + "value": { + "is_enabled": true, + "estimate_only": true, + "vendor_data_list": [ + "company-123", + "company-456" + ] + } + }, + "Enable": { + "summary": "Enable monitoring for two businesses", + "value": { + "is_enabled": true, + "vendor_data_list": [ + "company-123", + "company-456" + ] + } + }, + "Disable": { + "summary": "Disable monitoring by Didit internal id", + "value": { + "is_enabled": false, + "didit_internal_id_list": [ + "3f1c0d3e-8f0a-4c2e-9a3d-9b1f5f4a2c11" + ] + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/businesses/aml-monitoring/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"is_enabled\": true, \"vendor_data_list\": [\"company-123\", \"company-456\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/businesses/aml-monitoring/',\n headers={'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json'},\n json={'is_enabled': True, 'vendor_data_list': ['company-123', 'company-456']},\n)\nresp.raise_for_status()\njob = resp.json()\nprint(job['status'], job['outcomes'])\nfor row in job['results']:\n print(row['vendor_data'], row['outcome'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/businesses/aml-monitoring/', {\n method: 'POST',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY, 'Content-Type': 'application/json' },\n body: JSON.stringify({ is_enabled: true, vendor_data_list: ['company-123', 'company-456'] }),\n});\nconst job = await resp.json();\nconsole.log(job.status, job.outcomes);\nfor (const row of job.results) console.log(row.vendor_data, row.outcome);" + } + ], + "responses": { + "200": { + "description": "The estimate (when `estimate_only` is true) or the completed job for a selection processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AMLMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger selections run in the background. Poll the job endpoint with `job_id`; `results` fills in as profiles are processed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AMLMonitoringJob" + } + } + } + }, + "400": { + "description": "Validation error: `is_enabled` missing, no selector, or more than 1000 identifiers across both lists." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Insufficient balance to enable monitoring for this selection. The response carries the `estimate` so you can top up the right amount.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + } + } + } + } + } + } + } + } + } + } + }, + "/v3/aml-monitoring/jobs/{job_id}/": { + "get": { + "summary": "Get AML ongoing monitoring job", + "description": "Status, outcome counters and per-record results of a monitoring toggle job started from `POST /v3/users/aml-monitoring/` or `POST /v3/businesses/aml-monitoring/`. Poll every couple of seconds while `status` is `PENDING` or `PROCESSING`; `results` is complete once it is `COMPLETED`.", + "operationId": "get_aml_monitoring_job", + "x-didit-sdk": { + "resource": "aml_monitoring", + "method": "retrieve_job", + "auth": "api_key", + "server": true + }, + "tags": [ + "AML Ongoing Monitoring" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The `job_id` returned by the toggle endpoint." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl 'https://verification.didit.me/v3/aml-monitoring/jobs/JOB_ID/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import time\nimport requests\n\nurl = 'https://verification.didit.me/v3/aml-monitoring/jobs/JOB_ID/'\nwhile True:\n job = requests.get(url, headers={'x-api-key': 'YOUR_API_KEY'}).json()\n if job['status'] in ('COMPLETED', 'FAILED'):\n break\n time.sleep(2)\nprint(job['outcomes'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const url = 'https://verification.didit.me/v3/aml-monitoring/jobs/JOB_ID/';\nlet job;\ndo {\n await new Promise((r) => setTimeout(r, 2000));\n job = await (await fetch(url, { headers: { 'x-api-key': process.env.DIDIT_API_KEY } })).json();\n} while (job.status === 'PENDING' || job.status === 'PROCESSING');\nconsole.log(job.outcomes);" + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ] + }, + "is_enabled": { + "type": "boolean", + "description": "true when the job enables monitoring, false when it disables it." + }, + "target_type": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Which profile family the job toggles." + }, + "total": { + "type": "integer", + "description": "Profiles the job processes." + }, + "processed": { + "type": "integer", + "description": "Profiles processed so far." + }, + "outcomes": { + "type": "object", + "description": "Per-record outcome counters. Every selected profile resolves to exactly one counter; nothing is silently skipped.", + "properties": { + "enabled": { + "type": "integer", + "description": "Monitoring enabled on an existing AML screening." + }, + "screened_and_enabled": { + "type": "integer", + "description": "A new AML screening was created (billed) and monitoring enabled on it." + }, + "already_enabled": { + "type": "integer", + "description": "Monitoring was already active; nothing changed and nothing was billed." + }, + "disabled": { + "type": "integer", + "description": "Monitoring turned off across every AML screening of the profile." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Skipped: the profile lacks the full name, date of birth and country (users) or a name (businesses) needed to screen." + }, + "skipped_blocked": { + "type": "integer", + "description": "Skipped: the user is blocked." + }, + "skipped_sandbox": { + "type": "integer", + "description": "Skipped: sandbox application, no screening is created." + }, + "failed_provider": { + "type": "integer", + "description": "The screening could not be completed; retry later." + }, + "failed_insufficient_balance": { + "type": "integer", + "description": "Balance ran out while the job was running." + } + } + }, + "results": { + "type": "array", + "description": "One entry per processed profile, in processing order. Grows while the job runs; complete once status is COMPLETED. Recorded for jobs started through the API; a job started from the Business Console reports counters only.", + "items": { + "type": "object", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit internal id of the profile." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the profile." + }, + "outcome": { + "type": "string", + "enum": [ + "enabled", + "screened_and_enabled", + "already_enabled", + "disabled", + "skipped_no_identity_data", + "skipped_blocked", + "skipped_sandbox", + "failed_provider", + "failed_insufficient_balance" + ] + } + } + } + }, + "error": { + "type": "string", + "nullable": true, + "description": "Generic failure message when status is FAILED; the detailed cause is kept on the platform side." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "404": { + "description": "No such job for this application." + } + } + } + }, + "/v3/users/{vendor_data}/update-status/": { + "patch": { + "summary": "Update user status", + "description": "Flip only the lifecycle `status` of a user: `ACTIVE`, `FLAGGED` or `BLOCKED` (not session statuses). `BLOCKED` adds the `vendor_data` to the system blocklist; moving away from `BLOCKED` removes that entry. Each change is recorded as a `STATUS_CHANGED` activity on the user, with the optional `reason` as its `comment`. Sending the current status changes nothing and records no `reason`. The `user.status.updated` webhook does not carry `reason`.", + "operationId": "update_user_status", + "x-didit-sdk": { + "resource": "users", + "method": "update_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the user." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/EntityStatusUpdateRequest" + }, + { + "type": "object", + "properties": { + "reason": { + "type": "string", + "nullable": true, + "maxLength": 1000, + "description": "Why the status changed. Recorded on the status-change entry of the user's activity (`comments[].comment`, and **Activity** in the Business Console), only when the status actually changes. Webhooks do not carry it." + } + } + } + ] + }, + "examples": { + "Activate": { + "summary": "Reactivate the user", + "value": { + "status": "ACTIVE" + } + }, + "Flag": { + "summary": "Flag the user for review", + "value": { + "status": "FLAGGED" + } + }, + "Block": { + "summary": "Block the user (adds to system blocklist)", + "value": { + "status": "BLOCKED" + } + }, + "Block with reason": { + "summary": "Block the user and record why", + "value": { + "status": "BLOCKED", + "reason": "Chargeback fraud pattern confirmed" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/users/user-abc-123/update-status/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"status\": \"BLOCKED\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.patch(\n 'https://verification.didit.me/v3/users/user-abc-123/update-status/',\n headers={'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json'},\n json={'status': 'BLOCKED'},\n)\nresp.raise_for_status()\nprint(resp.json()['status'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/users/user-abc-123/update-status/', {\n method: 'PATCH',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY, 'Content-Type': 'application/json' },\n body: JSON.stringify({ status: 'BLOCKED' }),\n});\nconst user = await resp.json();\nconsole.log(user.status);" + } + ], + "responses": { + "200": { + "description": "User status updated. Full user record returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDetailItem" + }, + "examples": { + "Blocked": { + "value": { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": null, + "full_name": "John Michael Doe", + "date_of_birth": "1990-05-15", + "effective_name": "John Michael Doe", + "status": "BLOCKED", + "metadata": { + "tier": "premium", + "source": "website" + }, + "portrait_image_url": "https:///...", + "session_count": 3, + "approved_count": 2, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [ + "USA" + ], + "approved_emails": [ + "john@example.com" + ], + "approved_phones": [ + "+14155551234" + ], + "features": { + "ID_VERIFICATION": "Approved", + "LIVENESS": "Approved", + "FACE_MATCH": "Approved", + "AML": "Approved" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-01T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "tag": { + "uuid": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b", + "name": "VIP", + "color": "#2567FF", + "description": null, + "source": "custom", + "created_at": "2025-05-01T09:00:00Z", + "updated_at": "2025-05-01T09:00:00Z" + }, + "added_by_email": "analyst@acme.com", + "added_by_name": "Jane Analyst", + "created_at": "2025-06-02T11:00:00Z" + } + ], + "comments": [ + { + "uuid": "c1111111-2222-4333-8444-555555555555", + "comment_type": "STATUS_CHANGED", + "comment": "Chargeback fraud pattern confirmed", + "actor_email": null, + "actor_name": null, + "previous_status": "ACTIVE", + "new_status": "BLOCKED", + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "mentioned_emails": [], + "created_at": "2025-06-15T10:30:00Z" + } + ], + "created_at": "2025-06-01T08:00:00Z", + "updated_at": "2025-06-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Missing or invalid `status`, or a `reason` over 1000 characters.", + "content": { + "application/json": { + "examples": { + "Missing status": { + "value": { + "status": [ + "This field is required." + ] + } + }, + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Reason too long": { + "value": { + "reason": [ + "Ensure this field has no more than 1000 characters." + ] + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "Invalid or revoked API key, or the key cannot update users for this application. Note: on this endpoint an *invalid* key returns `403` (not `401`).", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No (non-deleted) user with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/businesses/create/": { + "post": { + "summary": "Create business", + "description": "Pre-create a [business](/entities/businesses) *without* a KYB session. All fields are optional, but you should always send `vendor_data` — without it the record cannot be linked to sessions or transactions. When provided, `vendor_data` must be unique among non-deleted businesses (exact match; conflicts return 400). Not idempotent.", + "operationId": "create_business", + "x-didit-sdk": { + "resource": "businesses", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your unique identifier for this business (free-form string, NOT a UUID). Optional but strongly recommended. When provided, must be unique among non-deleted businesses for the application; matched exactly as sent." + }, + "display_name": { + "type": "string", + "nullable": true, + "maxLength": 255, + "description": "Friendly display name shown in the console (takes precedence over `legal_name` for UI display)." + }, + "legal_name": { + "type": "string", + "nullable": true, + "maxLength": 255, + "description": "Official legal name of the company." + }, + "registration_number": { + "type": "string", + "nullable": true, + "maxLength": 100, + "description": "Company registration or incorporation number." + }, + "country_code": { + "type": "string", + "nullable": true, + "maxLength": 10, + "description": "Country of incorporation (ISO 3166-1 alpha-2, e.g. `GB`, `US`)." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "default": "ACTIVE", + "description": "Initial lifecycle status. Defaults to `ACTIVE`." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Arbitrary JSON object you attach to the business. Defaults to `{}`." + } + } + }, + "examples": { + "Basic": { + "summary": "Recommended minimum", + "value": { + "vendor_data": "company-456", + "legal_name": "New Corp Ltd", + "country_code": "US" + } + }, + "Full": { + "summary": "All fields", + "value": { + "vendor_data": "company-789", + "display_name": "New Corp", + "legal_name": "New Corp International Ltd", + "registration_number": "98765432", + "country_code": "DE", + "status": "ACTIVE", + "metadata": { + "industry": "fintech", + "tier": "enterprise" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/businesses/create/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vendor_data\": \"company-456\", \"legal_name\": \"New Corp Ltd\", \"country_code\": \"US\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/businesses/create/',\n headers={'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json'},\n json={\n 'vendor_data': 'company-456',\n 'legal_name': 'New Corp Ltd',\n 'country_code': 'US',\n },\n)\nresp.raise_for_status()\nbiz = resp.json()\nprint(biz['didit_internal_id'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/businesses/create/', {\n method: 'POST',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n vendor_data: 'company-456',\n legal_name: 'New Corp Ltd',\n country_code: 'US',\n }),\n});\nconst biz = await resp.json();\nconsole.log(biz.didit_internal_id);" + } + ], + "responses": { + "201": { + "description": "Business created. Full business record returned (same shape as Get Business).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDetailItem" + }, + "examples": { + "Created": { + "value": { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-456", + "display_name": null, + "legal_name": "New Corp Ltd", + "registration_number": null, + "country_code": "US", + "effective_name": "New Corp Ltd", + "status": "ACTIVE", + "session_count": 0, + "approved_count": 0, + "declined_count": 0, + "in_review_count": 0, + "features": {}, + "features_list": [], + "last_session_at": null, + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": null, + "tags": [], + "created_at": "2026-03-15T10:30:00Z", + "metadata": {}, + "updated_at": "2026-03-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Invalid body — typically a duplicate `vendor_data`, an unknown `status`, or malformed `metadata`.", + "content": { + "application/json": { + "examples": { + "Duplicate": { + "value": { + "vendor_data": [ + "A business with this vendor_data already exists." + ] + } + }, + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Bad metadata": { + "value": { + "metadata": [ + "Metadata must be a JSON object." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot create businesses for this application. This endpoint returns `403` (never `401`) for authentication failures, including requests with no credentials at all.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/users/create/": { + "post": { + "summary": "Create user", + "description": "Create a [user](/entities/users) by `vendor_data`, before or without a verification session. `vendor_data` must be unique among the application's users (a duplicate returns `400`). Not idempotent.\n\n**Contact details:** `email` and `phone_number` are stored in `metadata.contact`, and `country` in `metadata.address`. When you send `first_name` or `last_name` without `full_name`, Didit joins them into `full_name`.\n\n**Verification link:** with `send_verification_link: true`, Didit also creates a verification session for the person on `workflow_id` and emails them the link at `email`. The session runs the same checks as [Create Session](/sessions-api/create-session), such as the blocklist and your credit balance. The person and the session are created together or not at all: if any check fails, nothing is written and no email is sent. The response's `verification_link` describes the session, and is `null` when you don't ask for a link.\n\n**API key access:** a scoped API key needs `users` write. With `send_verification_link: true` it also needs `sessions` write, or the call answers `403` before anything is written. Full-access keys are unaffected. See [API keys](/console/api-keys).", + "operationId": "create_user", + "x-didit-sdk": { + "resource": "users", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "vendor_data" + ], + "properties": { + "vendor_data": { + "type": "string", + "description": "Your unique identifier for this user (free-form string, NOT a UUID). Must be unique among the application's users; matched exactly as sent." + }, + "full_name": { + "type": "string", + "nullable": true, + "maxLength": 512, + "description": "Full legal name of the user." + }, + "display_name": { + "type": "string", + "nullable": true, + "description": "Friendly display name shown in the console (takes precedence over `full_name` for UI display).", + "maxLength": 255 + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth in `YYYY-MM-DD` format." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "default": "ACTIVE", + "description": "Initial lifecycle status. Defaults to `ACTIVE`." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Arbitrary JSON object you attach to the user. Defaults to `{}`." + }, + "approved_emails": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Pre-trusted email addresses for this user, e.g. `[\"john@example.com\"]`." + }, + "approved_phones": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Pre-trusted phone numbers for this user, e.g. `[\"+14155551234\"]`." + }, + "issuing_states": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Pre-recorded issuing countries (ISO 3166-1 alpha-3), e.g. `[\"USA\"]`." + }, + "first_name": { + "type": "string", + "maxLength": 255, + "description": "Given name. When `full_name` is not sent, Didit joins `first_name` and `last_name` with a space into `full_name`. Not returned as a separate field." + }, + "last_name": { + "type": "string", + "maxLength": 255, + "description": "Family name. Joined with `first_name` into `full_name` when `full_name` is not sent. Not returned as a separate field." + }, + "email": { + "type": "string", + "format": "email", + "description": "Contact email, stored in `metadata.contact.email` (it replaces an `email` you send inside `metadata.contact`). Required when `send_verification_link` is `true`: the link is sent to this address. If `metadata.contact` is not a JSON object, the call answers `400` on this field." + }, + "phone_number": { + "type": "string", + "maxLength": 32, + "description": "Contact phone number, stored in `metadata.contact.phone_number`. If `metadata.contact` is not a JSON object, the call answers `400` on this field." + }, + "country": { + "type": "string", + "maxLength": 3, + "description": "Country as an ISO 3166 code, alpha-2 or alpha-3 (`ES` or `ESP`). Stored uppercased in `metadata.address.country`. If `metadata.address` is not a JSON object, the call answers `400` on this field." + }, + "send_verification_link": { + "type": "boolean", + "default": false, + "description": "When `true`, Didit creates a verification session for the person on `workflow_id` and emails them the link at `email`. Requires `email` and `workflow_id`. A scoped API key also needs `sessions` write." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "The workflow of the verification session. Required when `send_verification_link` is `true`. It must verify a person: a Business Verification (KYB) workflow is refused. Ignored otherwise." + }, + "language": { + "type": "string", + "maxLength": 10, + "description": "Language of the verification email, as a [supported language](/integration/supported-languages) code. English when omitted. Ignored when `send_verification_link` is not `true`." + } + } + }, + "examples": { + "Basic": { + "summary": "Create with basic info", + "value": { + "vendor_data": "user-abc-123", + "full_name": "John Doe" + } + }, + "Full": { + "summary": "All fields", + "value": { + "vendor_data": "user-abc-456", + "full_name": "Jane Smith", + "display_name": "Jane S.", + "date_of_birth": "1990-05-15", + "status": "ACTIVE", + "metadata": { + "tier": "premium", + "source": "api" + }, + "approved_emails": [ + "jane@example.com" + ] + } + }, + "Contact details": { + "summary": "Name, email, phone and country", + "value": { + "vendor_data": "user-abc-789", + "first_name": "Julia", + "last_name": "Martin", + "email": "julia.martin@example.com", + "phone_number": "+34600000000", + "country": "ES" + } + }, + "With verification link": { + "summary": "Create the person and email them a verification link", + "value": { + "vendor_data": "user-abc-789", + "first_name": "Julia", + "last_name": "Martin", + "email": "julia.martin@example.com", + "send_verification_link": true, + "workflow_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "language": "es" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/users/create/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vendor_data\": \"user-abc-123\", \"full_name\": \"John Doe\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/users/create/',\n headers={'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json'},\n json={'vendor_data': 'user-abc-123', 'full_name': 'John Doe'},\n)\nresp.raise_for_status()\nuser = resp.json()\nprint(user['didit_internal_id'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/users/create/', {\n method: 'POST',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY, 'Content-Type': 'application/json' },\n body: JSON.stringify({ vendor_data: 'user-abc-123', full_name: 'John Doe' }),\n});\nconst user = await resp.json();\nconsole.log(user.didit_internal_id);" + } + ], + "responses": { + "201": { + "description": "User created. The full user record (same shape as Get User), plus `verification_link`: the verification session Didit emailed when `send_verification_link` was `true`, otherwise `null`.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UserDetailItem" + }, + { + "type": "object", + "required": [ + "verification_link" + ], + "properties": { + "verification_link": { + "type": "object", + "nullable": true, + "description": "The verification session Didit created and emailed to the person. Always present: `null` when `send_verification_link` was not `true`.", + "required": [ + "session_id", + "session_number", + "status", + "url", + "workflow_id", + "email" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "ID of the verification session." + }, + "session_number": { + "type": "integer", + "description": "Sequential number of the session inside your application." + }, + "status": { + "type": "string", + "description": "Status of the session, `Not Started` for a session that was just created." + }, + "url": { + "type": "string", + "format": "uri", + "description": "The verification link Didit emailed to the person." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The workflow the session runs." + }, + "email": { + "type": "string", + "format": "email", + "description": "The address the link was sent to." + } + } + } + } + } + ] + }, + "examples": { + "Created": { + "value": { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": null, + "full_name": "John Doe", + "date_of_birth": null, + "effective_name": "John Doe", + "status": "ACTIVE", + "metadata": {}, + "portrait_image_url": null, + "session_count": 0, + "approved_count": 0, + "declined_count": 0, + "in_review_count": 0, + "issuing_states": [], + "approved_emails": [], + "approved_phones": [], + "features": {}, + "features_list": [], + "last_session_at": null, + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": null, + "tags": [], + "comments": [], + "created_at": "2025-06-15T10:30:00Z", + "updated_at": "2025-06-15T10:30:00Z", + "verification_link": null + } + }, + "Created with verification link": { + "value": { + "didit_internal_id": "9b2f6c1e-3d4a-4e5f-8a7b-1c2d3e4f5a6b", + "vendor_data": "user-abc-789", + "display_name": null, + "full_name": "Julia Martin", + "date_of_birth": null, + "effective_name": "Julia Martin", + "status": "ACTIVE", + "metadata": { + "contact": { + "email": "julia.martin@example.com" + } + }, + "portrait_image_url": null, + "session_count": 1, + "approved_count": 0, + "declined_count": 0, + "in_review_count": 0, + "issuing_states": [], + "approved_emails": [], + "approved_phones": [], + "features": {}, + "features_list": [], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-15T10:30:00Z", + "tags": [], + "comments": [], + "created_at": "2025-06-15T10:30:00Z", + "updated_at": "2025-06-15T10:30:00Z", + "verification_link": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa7", + "session_number": 43762, + "status": "Not Started", + "url": "https://verify.didit.me/en/session/3FaJ9wLqX2Mz", + "workflow_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "email": "julia.martin@example.com" + } + } + } + } + } + } + }, + "400": { + "description": "Invalid body, keyed by field: typically a duplicate `vendor_data`, a missing required field or a malformed value. With `send_verification_link: true`, also a missing `email` or `workflow_id`, an unknown or Business Verification (KYB) workflow, an unsupported `language` (keyed `contact_details.email_lang`), a blocklisted `vendor_data`, or too few credits to start the session. Nothing is written.", + "content": { + "application/json": { + "examples": { + "Duplicate": { + "value": { + "vendor_data": [ + "A user with this vendor_data already exists." + ] + } + }, + "Missing vendor_data": { + "value": { + "vendor_data": [ + "This field is required." + ] + } + }, + "Bad metadata": { + "value": { + "metadata": [ + "Metadata must be a JSON object." + ] + } + }, + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Bad country": { + "value": { + "country": [ + "Use an ISO 3166 country code, like ES or ESP." + ] + } + }, + "Contact not an object": { + "value": { + "email": [ + "metadata.contact must be a JSON object to store this value." + ] + } + }, + "Address not an object": { + "value": { + "country": [ + "metadata.address must be a JSON object to store this value." + ] + } + }, + "Link without email or workflow": { + "value": { + "email": [ + "An email address is required to send a verification link." + ], + "workflow_id": [ + "Pick the workflow to send a verification link for." + ] + } + }, + "Unknown workflow": { + "value": { + "workflow_id": [ + "Invalid workflow_id." + ] + } + }, + "Business workflow": { + "value": { + "workflow_id": [ + "This workflow verifies a business. Pick a workflow that verifies a person to send them a verification link." + ] + } + }, + "Blocklisted vendor_data": { + "value": { + "vendor_data": "This user or business has been blocked and cannot create new verification sessions." + } + }, + "Insufficient credits": { + "value": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "Invalid or revoked API key, or the key cannot create users for this application. A scoped API key needs `users` write, and also `sessions` write when `send_verification_link` is `true`; nothing is written. Note: on this endpoint an *invalid* key returns `403` (not `401`).", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "No sessions write": { + "summary": "Scoped key asks for a verification link without sessions write", + "value": { + "detail": "This API key has no write access to sessions." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-users/by-id/{didit_internal_id}/faces/upload/": { + "post": { + "summary": "Upload user face", + "description": "Attach a trusted imported face image to an existing User profile. Use this after creating or retrieving a User by `vendor_data` and reading its `didit_internal_id`. The uploaded face is stored on the User profile and indexed for duplicate detection and face search. It is not added to a face blocklist.", + "operationId": "upload_user_face", + "x-didit-sdk": { + "resource": "users", + "method": "upload_face", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID that owns the application." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID that owns the User profile." + }, + { + "name": "didit_internal_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Didit's internal User UUID, returned as `didit_internal_id` by `GET /v3/users/{vendor_data}/` and `POST /v3/users/create/`." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "image" + ], + "properties": { + "image": { + "type": "string", + "format": "byte", + "description": "Raw base64-encoded JPG or PNG face image. Do not include a `data:image/...;base64,` prefix. Max decoded size: 2 MB." + }, + "comment": { + "type": "string", + "description": "Optional operator-visible note for the uploaded face.", + "default": "" + } + } + }, + "examples": { + "Imported face": { + "summary": "Attach a face imported from a previous provider", + "value": { + "image": "/9j/4AAQSkZJRgABAQEA...", + "comment": "Imported from previous KYC provider" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/vendor-users/by-id/f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418/faces/upload/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"image\": \"/9j/4AAQSkZJRgABAQEA...\", \"comment\": \"Imported from previous KYC provider\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/vendor-users/by-id/f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418/faces/upload/',\n headers={'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json'},\n json={\n 'image': '/9j/4AAQSkZJRgABAQEA...',\n 'comment': 'Imported from previous KYC provider',\n },\n)\nresp.raise_for_status()\nface = resp.json()\nprint(face['uuid'], face['image_url'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/vendor-users/by-id/f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418/faces/upload/', {\n method: 'POST',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n image: '/9j/4AAQSkZJRgABAQEA...',\n comment: 'Imported from previous KYC provider',\n }),\n});\nconst face = await resp.json();\nconsole.log(face.uuid, face.image_url);" + } + ], + "responses": { + "201": { + "description": "Face uploaded and attached to the User profile.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "uuid", + "image_url", + "comment", + "uploaded_by", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the uploaded User face record." + }, + "image_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Signed URL for the uploaded face image." + }, + "comment": { + "type": "string", + "nullable": true, + "description": "Comment supplied when the face was uploaded." + }, + "uploaded_by": { + "type": "string", + "nullable": true, + "description": "Email or actor identifier of the uploader when available." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Upload timestamp." + } + } + }, + "examples": { + "Uploaded": { + "value": { + "uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "image_url": "https://signed-url.example/face.jpg", + "comment": "Imported from previous KYC provider", + "uploaded_by": "api", + "created_at": "2026-05-18T12:00:00Z" + } + } + } + } + } + }, + "400": { + "description": "Invalid request body or image. Common causes: invalid base64, invalid image, no detectable face, image over 2 MB, missing embedding, or the User already has 5 uploaded faces.", + "content": { + "application/json": { + "examples": { + "No face": { + "value": { + "image": "No face detected in the image. Please upload a clear photo with a visible face." + } + }, + "Too many faces": { + "value": { + "detail": "Maximum of 5 faces per user reached." + } + }, + "Too large": { + "value": { + "image": "Image exceeds maximum size of 2 MB." + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "examples": { + "Missing key": { + "value": { + "detail": "Authentication credentials were not provided." + } + } + } + } + } + }, + "403": { + "description": "The credential is valid but cannot write User profiles for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No User exists for that `didit_internal_id` in the application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/update-data/": { + "patch": { + "summary": "Update KYC data extracted from the ID document on a verification session", + "description": "Correct the identity fields that were extracted (OCR / NFC) from the user's ID document — misread names, dates, document numbers, and so on — after the verification has finished. Typical callers are a compliance analyst working from your own back office, or an automated reconciliation job. Every field in the body is optional: only the keys you send are written, everything else is left untouched (`PATCH` semantics).\n\nThe session must belong to your application and be in `Approved`, `Declined`, `In Review`, or `Kyc Expired` status. Sessions in any other state (e.g. still `In Progress`) return `404` with `\"No Session matches the given query.\"` — distinct from the `\"Not found.\"` returned for an unknown or deleted `sessionId`. The session must also carry an ID-verification (KYC) record: sessions whose workflow never ran an ID-verification step return `404` (`\"The session does not have a KYC.\"`). For graph workflows with multiple ID-verification steps, target a specific step with `node_id` (query parameter or body field); when omitted, the most recent KYC record is patched.\n\nTwo derived fields are recomputed on every call: `full_name` (from `first_name` + `last_name`) and `issuing_state_name` (from `issuing_state`). If the free-text `address` changes, the server may also re-geocode it and refresh `parsed_address` (skipped for sandbox sessions). `extra_fields` and `parsed_address` are merged key-by-key rather than replaced — see the per-field descriptions for the `null`-to-delete semantics.\n\nSubmitting at least one valid field fires the `data.updated` [webhook](/integration/webhooks) (trigger `manual_review`) — even when the submitted values equal the stored ones. When values actually change, a `KYC_DATA_UPDATED` activity entry recording the changed fields with previous and new values is appended to the session's `reviews`, visible in [Get Decision](#get-/v3/session/-sessionId-/decision/) and the console activity timeline. The session's status and decision are **not** re-evaluated — use [Update Status](#patch-/v3/session/-sessionId-/update-status/) to change the outcome. An empty JSON body (`{}`) is accepted and simply returns the current KYC data without firing the webhook.\n\nAccepts an API key or a console user token; either way the credential needs the `write:sessions` permission. Authentication and permission failures both return `403` — this API never responds `401`. Note that the session lookup runs before credential checks, so an unknown `sessionId` returns `404` even with missing credentials. This endpoint has a dedicated rate limit of 10 requests per minute per API key (invalid requests count too); the shared write budget of 300 requests per minute also applies.", + "operationId": "patch_v3_session_update_data", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_data", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session whose extracted document data you are correcting. Business (KYB) session IDs are not valid here — for company data use [Update KYB Company Data](#patch-/v3/session/-sessionId-/kyb/-companyUuid-/update-data/).", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node whose KYC record to patch, for graph workflows with multiple ID-verification steps (also accepted as a `node_id` body field). When omitted, the most recent KYC record on the session is patched. If no KYC exists for the given node, the call returns `404` (`\"The session does not have a KYC for node ''.\"`).", + "schema": { + "type": "string", + "example": "feature_ocr" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "All fields optional — send only what you want to change.", + "properties": { + "document_type": { + "type": "string", + "description": "Document type code. Accepts the short codes listed in the enum, plus six long-form aliases normalized server-side before validation: `Passport` → `P`, `Identity Card` → `ID`, `Driver's License` → `DL`, `Residence Permit` → `RP`, `Health Insurance Card` → `HIC`, `Tax Card` → `TC`. Any other value (including `PASSPORT` in caps) returns `400`.", + "enum": [ + "P", + "DL", + "ID", + "RP", + "SSC", + "HIC", + "WP", + "TC", + "VISA", + "PSC", + "BC", + "OTHER" + ], + "nullable": true, + "example": "P" + }, + "document_subtype": { + "type": "string", + "maxLength": 128, + "nullable": true, + "description": "Document subtype from the document registry, including the region prefix when the registry distinguishes regional variants." + }, + "document_number": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Document number as printed on the ID.", + "example": "PAB123456" + }, + "personal_number": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Personal / national identification number where the document carries one (e.g. Spanish DNI on a passport).", + "example": "99999999R" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth in `YYYY-MM-DD`. Any other format returns `400`.", + "example": "1980-01-01" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Issue date of the document in `YYYY-MM-DD`." + }, + "expiration_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Expiration date of the document in `YYYY-MM-DD`." + }, + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Issuing country as an ISO 3166-1 alpha-3 code. Case-insensitive (`esp` is stored as `ESP`); the human-readable `issuing_state_name` shown in [Get Decision](#get-/v3/session/-sessionId-/decision/) is recomputed from this value. Invalid codes return `400`.", + "example": "ESP" + }, + "first_name": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Given name(s). The stored `full_name` is recomputed as `first_name + ' ' + last_name` on every call to this endpoint.", + "example": "Carmen" + }, + "last_name": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Family name(s). Also feeds the recomputed `full_name`.", + "example": "Espanola Espanola" + }, + "gender": { + "type": "string", + "enum": [ + "M", + "F", + "U" + ], + "nullable": true, + "description": "Sex as printed on the document: `M` (male), `F` (female), or `U` (unknown). Any other value — including `X` — returns `400`." + }, + "address": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it synchronously before responding — the 200 body already reflects the refreshed `parsed_address`. The re-geocode also requires `issuing_state` to be present on the KYC record." + }, + "place_of_birth": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Place of birth as printed on the document." + }, + "nationality": { + "type": "string", + "maxLength": 32, + "nullable": true, + "description": "Nationality as printed on the document. Free text up to 32 characters — typically an ISO 3166-1 alpha-3 code (e.g. `ESP`), but not validated as one." + }, + "marital_status": { + "type": "string", + "enum": [ + "SINGLE", + "MARRIED", + "DIVORCED", + "WIDOWED", + "UNKNOWN" + ], + "description": "Marital status where the document carries it. Any other value returns `400`." + }, + "extra_fields": { + "type": "object", + "additionalProperties": true, + "nullable": true, + "description": "Per-key overrides for document-specific extra fields (e.g. `blood_group`, `dl_categories`, `mrz_string`). Keys are merged into the existing override map: send a key with a value to set it, or with `null` / `\"\"` to remove that key from the merged view. Send the whole field as `null` to clear all previously applied overrides. Must be a JSON object, otherwise `400`. The response always returns the merged view (OCR-extracted values with your overrides applied)." + }, + "parsed_address": { + "type": "object", + "nullable": true, + "description": "Structured address. Only these nested keys are accepted: `address_type`, `city`, `region`, `street_1`, `street_2`, `postal_code`, `country` — any other key returns `400`. Per-key semantics: a value sets the key, `null` removes it. `formatted_address` is recomputed server-side by joining `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country` and is returned in the response — you cannot set it directly.", + "properties": { + "address_type": { + "type": "string", + "nullable": true, + "description": "Free-form address classification (e.g. `home`)." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Madrid" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State / province / autonomous community." + }, + "street_1": { + "type": "string", + "nullable": true, + "example": "Avda de Madrid 34" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "28013" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`.", + "example": "ES" + } + } + } + } + }, + "examples": { + "Correct name and DOB": { + "summary": "Fix OCR-misread name, date of birth, and structured address", + "value": { + "first_name": "Carmen", + "last_name": "Espanola Espanola", + "date_of_birth": "1980-01-01", + "issuing_state": "ESP", + "marital_status": "MARRIED", + "extra_fields": { + "blood_group": "O+" + }, + "parsed_address": { + "street_1": "Avda de Madrid 34", + "city": "Madrid", + "postal_code": "28013", + "country": "ES" + } + } + }, + "Remove an extra field": { + "summary": "Delete one extra-field key, keep the rest", + "value": { + "extra_fields": { + "blood_group": null + } + } + }, + "Long-form document type": { + "summary": "Long-form alias is normalized to the short code (here P)", + "value": { + "document_type": "Passport" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "KYC data updated. Returns the **full current KYC record** (every updatable field with its stored value, plus the merged `extra_fields` and the current `parsed_address`) — not just the fields you submitted. The `data.updated` webhook fires when at least one valid field was in the payload.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "document_type": { + "type": "string", + "description": "Document type code. Accepts the short codes listed in the enum, plus six long-form aliases normalized server-side before validation: `Passport` → `P`, `Identity Card` → `ID`, `Driver's License` → `DL`, `Residence Permit` → `RP`, `Health Insurance Card` → `HIC`, `Tax Card` → `TC`. Any other value (including `PASSPORT` in caps) returns `400`.", + "enum": [ + "P", + "DL", + "ID", + "RP", + "SSC", + "HIC", + "WP", + "TC", + "VISA", + "PSC", + "BC", + "OTHER" + ], + "nullable": true, + "example": "P" + }, + "document_subtype": { + "type": "string", + "maxLength": 128, + "nullable": true, + "description": "Document subtype from the document registry, including the region prefix when the registry distinguishes regional variants." + }, + "document_number": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Document number as printed on the ID.", + "example": "PAB123456" + }, + "personal_number": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Personal / national identification number where the document carries one (e.g. Spanish DNI on a passport).", + "example": "99999999R" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth in `YYYY-MM-DD`. Any other format returns `400`.", + "example": "1980-01-01" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Issue date of the document in `YYYY-MM-DD`." + }, + "expiration_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Expiration date of the document in `YYYY-MM-DD`." + }, + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Issuing country as an ISO 3166-1 alpha-3 code. Case-insensitive (`esp` is stored as `ESP`); the human-readable `issuing_state_name` shown in [Get Decision](#get-/v3/session/-sessionId-/decision/) is recomputed from this value. Invalid codes return `400`.", + "example": "ESP" + }, + "first_name": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Given name(s). The stored `full_name` is recomputed as `first_name + ' ' + last_name` on every call to this endpoint.", + "example": "Carmen" + }, + "last_name": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Family name(s). Also feeds the recomputed `full_name`.", + "example": "Espanola Espanola" + }, + "gender": { + "type": "string", + "enum": [ + "M", + "F", + "U" + ], + "nullable": true, + "description": "Sex as printed on the document: `M` (male), `F` (female), or `U` (unknown). Any other value — including `X` — returns `400`." + }, + "address": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it in the background of the same request and may overwrite `parsed_address` with the geocoder result." + }, + "place_of_birth": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Place of birth as printed on the document." + }, + "nationality": { + "type": "string", + "maxLength": 32, + "nullable": true, + "description": "Nationality as printed on the document. Free text up to 32 characters — typically an ISO 3166-1 alpha-3 code (e.g. `ESP`), but not validated as one." + }, + "marital_status": { + "type": "string", + "enum": [ + "SINGLE", + "MARRIED", + "DIVORCED", + "WIDOWED", + "UNKNOWN" + ], + "description": "Marital status where the document carries it. Any other value returns `400`." + }, + "extra_fields": { + "type": "object", + "additionalProperties": true, + "nullable": true, + "description": "Merged view of the document's extra fields: the OCR-extracted values with all operator overrides applied. Keys removed via a `null` override are absent from this map." + }, + "parsed_address": { + "type": "object", + "nullable": true, + "description": "Current structured address (with internal bookkeeping keys stripped), or `null` when none exists. Includes the recomputed `formatted_address` alongside the seven editable keys.", + "properties": { + "address_type": { + "type": "string", + "nullable": true, + "description": "Free-form address classification (e.g. `home`)." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Madrid" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State / province / autonomous community." + }, + "street_1": { + "type": "string", + "nullable": true, + "example": "Avda de Madrid 34" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "28013" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`.", + "example": "ES" + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "Joined, human-readable form of the structured address. Recomputed server-side after every `parsed_address` edit.", + "example": "Avda de Madrid 34, Madrid, 28013, ES" + } + } + } + } + }, + "examples": { + "Updated KYC": { + "summary": "Full KYC record after the correction", + "value": { + "document_type": "P", + "document_subtype": null, + "document_number": "PAB123456", + "personal_number": "99999999R", + "date_of_birth": "1980-01-01", + "date_of_issue": "2021-02-09", + "expiration_date": "2031-02-08", + "issuing_state": "ESP", + "first_name": "Carmen", + "last_name": "Espanola Espanola", + "gender": "F", + "address": "AVDA DE MADRID 34, MADRID, MADRID 28013", + "place_of_birth": "MADRID", + "nationality": "ESP", + "marital_status": "MARRIED", + "extra_fields": { + "mrz_string": "P\": [\"\"]}`; errors on nested `parsed_address` / `extra_fields` keys use an object value: `{\"parsed_address\": {\"\": \"\"}}`.", + "content": { + "application/json": { + "examples": { + "Invalid gender": { + "summary": "gender must be M, F, or U", + "value": { + "gender": [ + "\"X\" is not a valid choice." + ] + } + }, + "Invalid document type": { + "summary": "Not a short code or long-form alias", + "value": { + "document_type": [ + "\"PASSPORT\" is not a valid choice." + ] + } + }, + "Invalid issuing state": { + "summary": "issuing_state is not ISO 3166-1 alpha-3", + "value": { + "issuing_state": [ + "Invalid ISO 3166-1 alpha-3 country code for issuing_state." + ] + } + }, + "Invalid marital status": { + "summary": "marital_status not in the enum", + "value": { + "marital_status": [ + "\"ENGAGED\" is not a valid choice." + ] + } + }, + "Bad date format": { + "summary": "Dates must be YYYY-MM-DD", + "value": { + "date_of_birth": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + }, + "Unsupported parsed_address key": { + "summary": "Only the seven documented keys are allowed", + "value": { + "parsed_address": { + "zipcode": "Unsupported key. Allowed keys: address_type, city, region, street_1, street_2, postal_code, country" + } + } + }, + "Invalid parsed_address country": { + "summary": "Nested country must be alpha-2, not alpha-3", + "value": { + "parsed_address": { + "country": "Invalid ISO 3166-1 alpha-2 country code." + } + } + }, + "extra_fields not an object": { + "summary": "extra_fields must be a JSON object", + "value": { + "extra_fields": [ + "This field must be a JSON object." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing/invalid credentials or insufficient permissions. This API returns `403` for authentication failures — never `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Credential lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown session, session in a non-editable state, or no KYC record to patch. Each case has a distinct `detail` message (see examples). The unknown-session lookup runs before credential checks.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "summary": "No session with this ID in your application (or it was deleted)", + "value": { + "detail": "Not found." + } + }, + "Wrong session state": { + "summary": "Session exists but is not Approved / Declined / In Review / Kyc Expired", + "value": { + "detail": "No Session matches the given query." + } + }, + "No KYC record": { + "summary": "The workflow never ran an ID-verification step", + "value": { + "detail": "The session does not have a KYC." + } + }, + "No KYC for node": { + "summary": "node_id given but no KYC exists for it", + "value": { + "detail": "The session does not have a KYC for node 'feature_missing'." + } + } + } + } + } + }, + "429": { + "description": "Dedicated rate limit exceeded — 10 KYC data updates per minute per API key (rejected requests count toward the budget). The shared 300/min write budget can also trigger with its own message. Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "KYC update budget exhausted", + "value": { + "detail": "Session KYC data update rate limit exceeded. You can make up to 10 requests per minute." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-data/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"first_name\": \"Carmen\",\n \"last_name\": \"Espanola Espanola\",\n \"date_of_birth\": \"1980-01-01\",\n \"issuing_state\": \"ESP\",\n \"parsed_address\": {\n \"street_1\": \"Avda de Madrid 34\",\n \"city\": \"Madrid\",\n \"postal_code\": \"28013\",\n \"country\": \"ES\"\n }\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.patch(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-data/\",\n headers={\n \"x-api-key\": \"YOUR_API_KEY\",\n \"Content-Type\": \"application/json\",\n },\n json={\n \"first_name\": \"Carmen\",\n \"last_name\": \"Espanola Espanola\",\n \"date_of_birth\": \"1980-01-01\",\n \"issuing_state\": \"ESP\",\n \"extra_fields\": {\"blood_group\": \"O+\"},\n \"parsed_address\": {\"city\": \"Madrid\", \"postal_code\": \"28013\", \"country\": \"ES\"},\n },\n)\nresponse.raise_for_status()\nkyc = response.json() # full current KYC record\nprint(kyc[\"first_name\"], kyc[\"parsed_address\"][\"formatted_address\"])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const response = await fetch(\n 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-data/',\n {\n method: 'PATCH',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n first_name: 'Carmen',\n last_name: 'Espanola Espanola',\n date_of_birth: '1980-01-01',\n issuing_state: 'ESP',\n parsed_address: { city: 'Madrid', postal_code: '28013', country: 'ES' },\n }),\n },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst kyc = await response.json(); // full current KYC record" + } + ] + } + }, + "/v3/session/{sessionId}/features/{nodeId}/update-status/": { + "patch": { + "summary": "Update an individual feature (step) status", + "description": "Set the status of a single verification feature (step) within a session. Target the feature by its workflow graph `nodeId`. On non-graph workflows, whose steps have no node, use `default` and also pass `feature_type` to say which step you mean.\n\nWorks for both User Verification (KYC) and Business Verification (KYB) sessions and every step type: ID/OCR, Liveness, Face Match, NFC, AML, Proof of Address, Phone, Email, Database Validation, Questionnaire, and the KYB registry / documents / key-people checks. This endpoint is **not** available for standalone API sessions (`session_type = API`).\n\n`new_status` must be one of `Approved`, `Declined`, or `In Review`, and must differ from the feature's current status.\n\n**When the override applies.** Without `defer_until_flow_finishes` (or with `false`) the override applies at once, as it always did; when the person is still in the flow it is recorded as `forced`. With `defer_until_flow_finishes: true` while the person is still in the flow (the session is `Not Started`, `In Progress` or `Resubmitted` and has not finished), the override is stored as `pending_until_flow_finishes` and applies when the flow finishes, after the flow reaches its own decision. On a session whose flow already ended it applies at once either way. A waiting override is cancelled instead when the person redoes its step before finishing, when a newer override on the same check replaces it, when the session leaves the flow unfinished (abandoned, expired, or decided mid-flow), or when you cancel it with `POST /v3/session/{sessionId}/features/overrides/{overrideId}/cancel/`. List a session's overrides with `GET /v3/session/{sessionId}/features/overrides/`.\n\n**Effect on the session.** A session that already has a decision keeps it: only the check changes. When the session has no decision yet and its workflow is waiting on exactly this step, applying the override completes the step: the workflow moves on, later steps run, and a status node can set the session decision and fire `status.updated`.\n\n**Webhooks and activity.** A `data.updated` webhook fires when the override applies (at once, or when the flow finishes), never while it waits or when it is cancelled. Every override is recorded in the session's activity as `FEATURE_OVERRIDE_PENDING`, `FEATURE_OVERRIDE_APPLIED` or `FEATURE_OVERRIDE_CANCELLED`, with who made it; the request's entry carries your `comment`.\n\nAuthenticate with your API key (`x-api-key`) or a user authorization header carrying the `write:sessions` privilege; a scoped API key needs write access to decisions.", + "operationId": "patch_v3_session_update_feature_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_feature_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "in": "path", + "name": "nodeId", + "required": true, + "description": "Workflow graph node ID of the feature to update (for example `feature_ocr_1`). Use the literal value `default` for non-graph (single-step) workflows where the feature has no node ID.", + "schema": { + "type": "string", + "example": "feature_ocr_1" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "new_status" + ], + "properties": { + "new_status": { + "type": "string", + "description": "Target status for this feature. Must differ from the current value.", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "example": "Approved" + }, + "comment": { + "type": "string", + "description": "Optional free-text note recorded on the session's review trail for this feature change.", + "example": "Document manually verified by compliance." + }, + "feature_type": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "OCR", + "OCR_BACK", + "NFC", + "LIVENESS", + "FACE", + "AGE_ESTIMATION", + "AGE_VERIFICATION", + "FACE_SEARCH", + "FACE_MATCH", + "IP_ANALYSIS", + "AML", + "PHONE", + "PHONE_VERIFICATION", + "EMAIL", + "EMAIL_VERIFICATION", + "POA", + "PROOF_OF_ADDRESS", + "DATABASE_VALIDATION", + "DATA_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "BANK_VERIFICATION", + "GEOLOCATION" + ], + "description": "Which kind of step to target. Needed on non-graph workflows (`nodeId` = `default`), where every step shares no node; optional otherwise. An unknown value returns `400`.", + "example": "LIVENESS" + }, + "defer_until_flow_finishes": { + "type": "boolean", + "default": false, + "description": "While the person is still in the flow, store the override as `pending_until_flow_finishes` and apply it when the flow finishes, instead of at once. Omitted or `false`, the override applies at once. On a session whose flow already ended it applies at once either way.", + "example": false + } + } + }, + "examples": { + "Apply Now": { + "summary": "Approve the ID step at once", + "value": { + "new_status": "Approved", + "comment": "Document manually verified by compliance." + } + }, + "Wait For The Flow": { + "summary": "Approve the liveness step once the person finishes the flow", + "value": { + "new_status": "Approved", + "comment": "Liveness retried with a reviewer present.", + "defer_until_flow_finishes": true + } + }, + "Non-graph Workflow": { + "summary": "Target the liveness step of a workflow without nodes (nodeId = default)", + "value": { + "new_status": "In Review", + "feature_type": "LIVENESS" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The override was recorded. `new_status` is the check's status as stored: the requested one once the override applied, the unchanged one while it waits for the flow to finish. `override` says which happened.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid" + }, + "node_id": { + "type": "string", + "nullable": true, + "description": "The updated step's workflow node, or `null` for a step without a node (the `default` path in a non-graph workflow).", + "example": "feature_ocr_1" + }, + "feature_type": { + "type": "string", + "description": "Model name of the updated feature (e.g. `KYC`, `Face`, `FaceMatch`, `AML`, `POA`).", + "example": "KYC" + }, + "new_status": { + "type": "string", + "description": "The check's stored status: the requested one once the override applied, the unchanged one while the override waits for the flow to finish.", + "example": "Approved" + }, + "override": { + "$ref": "#/components/schemas/FeatureStatusOverride" + } + }, + "required": [ + "session_id", + "node_id", + "feature_type", + "new_status", + "override" + ] + }, + "examples": { + "Applied": { + "summary": "The override applied at once", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "node_id": "feature_ocr_1", + "feature_type": "KYC", + "new_status": "Approved", + "override": { + "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "node_id": "feature_ocr_1", + "feature_type": "", + "feature_model": "KYC", + "previous_status": "In Review", + "new_status": "Approved", + "state": "applied", + "forced": false, + "reason": "Document manually verified by compliance.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": "2026-10-13T09:15:00Z", + "cancelled_at": null, + "cancel_reason": null + } + } + }, + "Waiting For The Flow": { + "summary": "defer_until_flow_finishes while the person is still in the flow", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "node_id": "feature_liveness_1", + "feature_type": "Face", + "new_status": "Declined", + "override": { + "id": "9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3", + "node_id": "feature_liveness_1", + "feature_type": "", + "feature_model": "Face", + "previous_status": "Declined", + "new_status": "Approved", + "state": "pending_until_flow_finishes", + "forced": false, + "reason": "Liveness retried with a reviewer present.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": null, + "cancelled_at": null, + "cancel_reason": null + } + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when `new_status` is invalid or unchanged, when `feature_type` is unknown, or when the session is a standalone API session.", + "content": { + "application/json": { + "examples": { + "Same Status": { + "summary": "new_status equals the current status", + "value": { + "new_status": [ + "The new status is the same as the current status." + ] + } + }, + "API Session": { + "summary": "Feature status cannot be changed on standalone API sessions", + "value": [ + "Cannot change feature status for API sessions." + ] + }, + "Unknown feature_type": { + "summary": "feature_type is not a known step type", + "value": { + "feature_type": [ + "Unknown feature_type 'SELFIE'." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing/invalid credentials or insufficient permissions.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "API key lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without write access to decisions", + "value": { + "detail": "This API key has no write access to decisions." + } + } + } + } + } + }, + "404": { + "description": "The session was not found, or it has no feature with the given `nodeId`.", + "content": { + "application/json": { + "examples": { + "Session Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Session not found." + } + }, + "Feature Not Found": { + "summary": "No feature for this node_id", + "value": { + "detail": "No feature found with node_id 'feature_ocr_1' in this session." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/features/feature_ocr_1/update-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"new_status\": \"Approved\",\n \"comment\": \"Document manually verified by compliance.\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.patch(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/features/feature_ocr_1/update-status/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\", \"Content-Type\": \"application/json\"},\n json={\"new_status\": \"Approved\", \"comment\": \"Document manually verified by compliance.\"},\n)\nprint(response.json())" + } + ] + } + }, + "/v3/session/{sessionId}/update-aml-hit-status/": { + "patch": { + "summary": "Update an AML hit review status", + "description": "Set the review status of a **single AML hit** (a potential sanction / PEP / watchlist / adverse-media match) returned by AML screening, without changing the overall session decision. Identify the hit by its `hit_id` (the `id` of an entry in the AML check's `hits` array — see the session decision).\n\n`review_status` is one of `Unreviewed`, `Confirmed Match`, `False Positive`, or `Inconclusive`. Reviewer decisions are preserved across ongoing-monitoring updates, so a hit you mark `False Positive` stays that way when the provider re-screens.\n\nA successful call fires a `data.updated` webhook and appends an activity entry (with actor attribution) to the session's review trail. For graph workflows with multiple AML checks, pass `node_id` to target a specific check.\n\nAuthenticate with your API key (`x-api-key`) or a user authorization header — either must carry the `write:sessions` privilege.", + "operationId": "patch_v3_session_update_aml_hit_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_aml_hit_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "in": "query", + "name": "node_id", + "required": false, + "description": "Workflow graph node ID. Required only when the session has multiple AML checks (one per node); omit it for single-AML sessions.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "hit_id", + "review_status" + ], + "properties": { + "hit_id": { + "type": "string", + "description": "The `id` of the hit in the AML check's `hits` array.", + "example": "abc123" + }, + "review_status": { + "type": "string", + "description": "The new review status for the hit.", + "enum": [ + "Unreviewed", + "Confirmed Match", + "False Positive", + "Inconclusive" + ], + "example": "False Positive" + }, + "node_id": { + "type": "string", + "description": "Optional graph node ID. Use it (here or as a query parameter) when the session has multiple AML checks.", + "example": "feature_aml_1" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Hit review status updated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message", + "hit_id", + "review_status" + ], + "properties": { + "message": { + "type": "string", + "description": "Always `Hit status updated successfully`." + }, + "hit_id": { + "type": "string", + "description": "The updated hit, with surrounding whitespace removed." + }, + "review_status": { + "type": "string", + "enum": [ + "Unreviewed", + "Confirmed Match", + "False Positive", + "Inconclusive" + ], + "description": "The review status now stored on the hit." + } + } + }, + "example": { + "message": "Hit status updated successfully", + "hit_id": "abc123", + "review_status": "False Positive" + } + } + } + }, + "400": { + "description": "Validation error — `hit_id` empty or `review_status` not one of the allowed values.", + "content": { + "application/json": { + "example": { + "review_status": [ + "\"Maybe\" is not a valid choice." + ] + } + } + } + }, + "403": { + "description": "Missing/invalid credentials or insufficient permissions. This API returns `403` for authentication failures — never `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "API key lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "The session was not found, the session has no AML check (optionally for the given `node_id`), or no hit matches `hit_id`.", + "content": { + "application/json": { + "examples": { + "No AML Check": { + "summary": "Session has no AML check", + "value": { + "detail": "The session does not have an AML check." + } + }, + "Hit Not Found": { + "summary": "Unknown hit_id", + "value": { + "detail": "Hit with ID 'abc123' not found in the AML check." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-aml-hit-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"hit_id\": \"abc123\",\n \"review_status\": \"False Positive\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.patch(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-aml-hit-status/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\", \"Content-Type\": \"application/json\"},\n json={\"hit_id\": \"abc123\", \"review_status\": \"False Positive\"},\n)\nprint(response.json())" + } + ] + } + }, + "/v3/session/{sessionId}/bulk-update-aml-hit-status/": { + "patch": { + "summary": "Bulk update AML hit review statuses", + "description": "Update the review status of **multiple AML hits** in one call. Provide `hit_updates`, a list of `{ hit_id, review_status }` pairs. `review_status` is one of `Unreviewed`, `Confirmed Match`, `False Positive`, or `Inconclusive`.\n\nThe update is all-or-nothing: if any `hit_id` is not found in the AML check, the request returns `404` and no hit is changed. A successful call fires a single `data.updated` webhook and records one activity entry covering all changes. For graph workflows with multiple AML checks, pass `node_id` to target a specific check.\n\nAuthenticate with your API key (`x-api-key`) or a user authorization header — either must carry the `write:sessions` privilege.", + "operationId": "patch_v3_session_bulk_update_aml_hit_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "bulk_update_aml_hit_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "in": "query", + "name": "node_id", + "required": false, + "description": "Workflow graph node ID. Required only when the session has multiple AML checks (one per node); omit it for single-AML sessions.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "hit_updates" + ], + "properties": { + "hit_updates": { + "type": "array", + "description": "Non-empty list of hit updates.", + "items": { + "type": "object", + "required": [ + "hit_id", + "review_status" + ], + "properties": { + "hit_id": { + "type": "string", + "example": "abc123" + }, + "review_status": { + "type": "string", + "enum": [ + "Unreviewed", + "Confirmed Match", + "False Positive", + "Inconclusive" + ], + "example": "Confirmed Match" + } + } + } + }, + "node_id": { + "type": "string", + "description": "Optional graph node ID. Use it (here or as a query parameter) when the session has multiple AML checks.", + "example": "feature_aml_1" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "All hits updated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message", + "updated_hits" + ], + "properties": { + "message": { + "type": "string", + "description": "`Updated {n} hit(s) successfully`, where `n` is the number of entries in `hit_updates`." + }, + "updated_hits": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The `hit_id` of every entry in `hit_updates`, in request order, including hits whose status did not change." + } + } + }, + "example": { + "message": "Updated 2 hit(s) successfully", + "updated_hits": [ + "abc123", + "def456" + ] + } + } + } + }, + "400": { + "description": "Validation error — `hit_updates` empty, or an entry is missing `hit_id` / `review_status` or carries an invalid value.", + "content": { + "application/json": { + "example": { + "hit_updates": [ + "hit_updates cannot be empty" + ] + } + } + } + }, + "403": { + "description": "Missing/invalid credentials or insufficient permissions. This API returns `403` for authentication failures — never `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "API key lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "The session was not found, the session has no AML check, or one or more `hit_id`s do not exist (no hit is changed in that case).", + "content": { + "application/json": { + "example": { + "detail": "Hits not found: zzz999" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/bulk-update-aml-hit-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"hit_updates\": [\n { \"hit_id\": \"abc123\", \"review_status\": \"Confirmed Match\" },\n { \"hit_id\": \"def456\", \"review_status\": \"False Positive\" }\n ]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.patch(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/bulk-update-aml-hit-status/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\", \"Content-Type\": \"application/json\"},\n json={\n \"hit_updates\": [\n {\"hit_id\": \"abc123\", \"review_status\": \"Confirmed Match\"},\n {\"hit_id\": \"def456\", \"review_status\": \"False Positive\"},\n ]\n },\n)\nprint(response.json())" + } + ] + } + }, + "/v3/session/{sessionId}/update-poa-data/": { + "patch": { + "summary": "Update Proof of Address data extracted from the POA document on a verification session", + "description": "Correct the fields extracted from the user's Proof of Address document — utility bill, bank statement, or other address evidence — after the verification has finished: issuer, dates, the printed name, the address itself, and banking details. Every field in the body is optional: only the keys you send are written, everything else is left untouched (`PATCH` semantics).\n\nThe session must belong to your application and be in `Approved`, `Declined`, `In Review`, or `Kyc Expired` status. Sessions in any other state (e.g. still `In Progress`) return `404` with `\"No Session matches the given query.\"` — distinct from the `\"Not found.\"` returned for an unknown or deleted `sessionId`. The session must also carry a POA record: sessions whose workflow never ran a Proof of Address step return `404` (`\"The session does not have a POA.\"`). For graph workflows with multiple POA steps, target a specific step with `node_id` (query parameter or body field); when omitted, the most recent POA record is patched.\n\n`extra_fields` is merged key-by-key: nine known keys (`bank_iban`, `additional_names`, …) write through to dedicated POA columns, any other key is kept as a custom extra, and `null` / `\"\"` deletes a key. `poa_parsed_address` accepts only the seven documented keys and recomputes `formatted_address` / `poa_formatted_address` after every edit. If the free-text `poa_address` changes, the server may also re-geocode it and refresh the structured address (skipped for sandbox sessions).\n\nUnlike the KYC variant of this endpoint, the `data.updated` [webhook](/integration/webhooks) (trigger `manual_review`) fires only when at least one stored value **actually changed** — re-sending identical values is a silent no-op. When values change, a `POA_DATA_UPDATED` activity entry recording the changed fields with previous and new values is appended to the session's `reviews`, visible in [Get Decision](#get-/v3/session/-sessionId-/decision/) and the console activity timeline. The session's status and decision are **not** re-evaluated — use [Update Status](#patch-/v3/session/-sessionId-/update-status/) to change the outcome.\n\nAccepts an API key or a console user token; either way the credential needs the `write:sessions` permission. Authentication and permission failures both return `403` — this API never responds `401`. Note that the session lookup runs before credential checks, so an unknown `sessionId` returns `404` even with missing credentials. This endpoint has a dedicated rate limit of 10 requests per minute per API key (invalid requests count too); the shared write budget of 300 requests per minute also applies.\n\nAn empty JSON body `{}` is a no-op: it returns `200` echoing the current POA record without firing the webhook (`has_changes` stays false).", + "operationId": "patch_v3_session_update_poa_data", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_poa_data", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session whose attached POA record you are correcting.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node whose POA record to patch, for graph workflows with multiple Proof of Address steps (also accepted as a `node_id` body field). When omitted, the most recent POA record on the session is patched. If no POA exists for the given node, the call returns `404` (`\"The session does not have a POA for node ''.\"`).", + "schema": { + "type": "string", + "example": "feature_poa" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "All fields optional — send only what you want to change.", + "properties": { + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Country that issued the POA document, ISO 3166-1 alpha-3. Case-insensitive (normalized to uppercase); invalid codes return `400`.", + "example": "ESP" + }, + "document_type": { + "type": "string", + "enum": [ + "UTILITY_BILL", + "BANK_STATEMENT", + "GOVERNMENT_ISSUED_DOCUMENT", + "OTHER_POA_DOCUMENT", + "UNKNOWN" + ], + "description": "POA document category. Values are uppercase — lowercase variants like `utility_bill` return `400`.", + "example": "UTILITY_BILL" + }, + "document_language": { + "type": "string", + "maxLength": 50, + "nullable": true, + "description": "Language of the document, typically an ISO 639-1 code (e.g. `es`). Free text — not validated as a language code.", + "example": "es" + }, + "issuer": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Organization that issued the document (utility company, bank, government agency).", + "example": "Energy Provider S.A." + }, + "issue_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Issue date of the POA document in `YYYY-MM-DD`. Any other format returns `400`.", + "example": "2026-04-12" + }, + "poa_address": { + "type": "string", + "nullable": true, + "description": "Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it in the background of the same request and may overwrite `poa_parsed_address` / `poa_formatted_address` with the geocoder result. The re-geocode also requires `issuing_state` to be present on the POA record.", + "example": "Avda de Madrid 34, 28013 Madrid" + }, + "name_on_document": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Recipient name as printed on the document.", + "example": "Carmen Espanola" + }, + "extra_fields": { + "type": "object", + "additionalProperties": true, + "nullable": true, + "description": "Document-specific extras, merged key-by-key. Nine keys map to dedicated POA columns: `bank_account_number`, `bank_iban`, `bank_sort_code`, `bank_routing_number`, `bank_swift_bic`, `bank_branch_name`, `bank_branch_address`, `document_phone_number`, and `additional_names` (must be a list of strings or `null`, otherwise `400`). Any other key is stored as a custom extra field. Per-key semantics: a value sets the key; `null` or `\"\"` deletes it. Must be a JSON object, otherwise `400`." + }, + "poa_parsed_address": { + "type": "object", + "nullable": true, + "description": "Structured address. Only these nested keys are accepted: `address_type`, `city`, `region`, `street_1`, `street_2`, `postal_code`, `country` — any other key returns `400`. Per-key semantics: a value sets the key, `null` removes it. `formatted_address` (and the top-level `poa_formatted_address`) is recomputed server-side by joining `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country` — you cannot set it directly.", + "properties": { + "address_type": { + "type": "string", + "nullable": true, + "description": "Free-form address classification (e.g. `home`)." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Madrid" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State / province / autonomous community." + }, + "street_1": { + "type": "string", + "nullable": true, + "example": "Avda de Madrid 34" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "28013" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`.", + "example": "ES" + } + } + } + } + }, + "examples": { + "Correct issuer and address": { + "summary": "Fix document classification, issuer, and the structured address", + "value": { + "issuing_state": "ESP", + "document_type": "BANK_STATEMENT", + "issuer": "Banco Example S.A.", + "issue_date": "2026-05-02", + "name_on_document": "Carmen Espanola", + "poa_address": "Avda de Madrid 34, 28013 Madrid", + "extra_fields": { + "bank_iban": "ES9121000418450200051332", + "additional_names": [ + "Carmen E. Espanola" + ] + }, + "poa_parsed_address": { + "street_1": "Avda de Madrid 34", + "city": "Madrid", + "postal_code": "28013", + "country": "ES" + } + } + }, + "Remove a banking detail": { + "summary": "Delete one extra-field key, keep the rest", + "value": { + "extra_fields": { + "bank_iban": null + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "POA data updated. Returns the **full current POA record** (every updatable field with its stored value, plus the complete `extra_fields` map, `poa_parsed_address`, and `poa_formatted_address`) — not just the fields you submitted. The `data.updated` webhook fires only when at least one value actually changed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Country that issued the POA document, ISO 3166-1 alpha-3. Case-insensitive (normalized to uppercase); invalid codes return `400`.", + "example": "ESP" + }, + "document_type": { + "type": "string", + "enum": [ + "UTILITY_BILL", + "BANK_STATEMENT", + "GOVERNMENT_ISSUED_DOCUMENT", + "OTHER_POA_DOCUMENT", + "UNKNOWN" + ], + "description": "POA document category. Values are uppercase — lowercase variants like `utility_bill` return `400`.", + "example": "UTILITY_BILL" + }, + "document_language": { + "type": "string", + "maxLength": 50, + "nullable": true, + "description": "Language of the document, typically an ISO 639-1 code (e.g. `es`). Free text — not validated as a language code.", + "example": "es" + }, + "issuer": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Organization that issued the document (utility company, bank, government agency).", + "example": "Energy Provider S.A." + }, + "issue_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Issue date of the POA document in `YYYY-MM-DD`. Any other format returns `400`.", + "example": "2026-04-12" + }, + "poa_address": { + "type": "string", + "nullable": true, + "description": "Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it in the background of the same request and may overwrite `poa_parsed_address` / `poa_formatted_address` with the geocoder result.", + "example": "Avda de Madrid 34, 28013 Madrid" + }, + "name_on_document": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Recipient name as printed on the document.", + "example": "Carmen Espanola" + }, + "extra_fields": { + "type": "object", + "additionalProperties": true, + "nullable": true, + "description": "Always present in the response: the nine known keys (with `null` for unset ones, `additional_names` defaulting to `[]`) plus any custom keys previously stored." + }, + "poa_parsed_address": { + "type": "object", + "nullable": true, + "description": "Current structured address, or `null` when none exists. Includes the recomputed `formatted_address` alongside the seven editable keys. Returned as stored — after a server-side geocode it can contain bookkeeping keys (e.g. `raw_results`) alongside the editable keys.", + "properties": { + "address_type": { + "type": "string", + "nullable": true, + "description": "Free-form address classification (e.g. `home`)." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Madrid" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State / province / autonomous community." + }, + "street_1": { + "type": "string", + "nullable": true, + "example": "Avda de Madrid 34" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "28013" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`.", + "example": "ES" + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "Joined, human-readable form of the structured address. Recomputed server-side after every `poa_parsed_address` edit.", + "example": "Avda de Madrid 34, Madrid, 28013, ES" + } + } + }, + "poa_formatted_address": { + "type": "string", + "nullable": true, + "description": "Top-level copy of the recomputed `formatted_address`, kept in sync when the structured address changes.", + "example": "Avda de Madrid 34, Madrid, 28013, ES" + } + } + }, + "examples": { + "Updated POA": { + "summary": "Full POA record after the correction", + "value": { + "issuing_state": "ESP", + "document_type": "BANK_STATEMENT", + "document_language": "es", + "issuer": "Banco Example S.A.", + "issue_date": "2026-05-02", + "poa_address": "Avda de Madrid 34, 28013 Madrid", + "name_on_document": "Carmen Espanola", + "extra_fields": { + "bank_account_number": null, + "bank_iban": "ES9121000418450200051332", + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": null, + "additional_names": [ + "Carmen E. Espanola" + ] + }, + "poa_parsed_address": { + "city": "Madrid", + "street_1": "Avda de Madrid 34", + "postal_code": "28013", + "country": "ES", + "formatted_address": "Avda de Madrid 34, Madrid, 28013, ES" + }, + "poa_formatted_address": "Avda de Madrid 34, Madrid, 28013, ES" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field errors arrive as `{\"\": [\"\"]}`; errors on nested `poa_parsed_address` / `extra_fields` keys use an object value: `{\"extra_fields\": {\"\": \"\"}}`.", + "content": { + "application/json": { + "examples": { + "Invalid document type": { + "summary": "Enum is uppercase — lowercase variants are rejected", + "value": { + "document_type": [ + "\"utility_bill\" is not a valid choice." + ] + } + }, + "Invalid issuing state": { + "summary": "issuing_state is not ISO 3166-1 alpha-3", + "value": { + "issuing_state": [ + "Invalid ISO 3166-1 alpha-3 country code for issuing_state." + ] + } + }, + "additional_names not a list": { + "summary": "additional_names accepts a list of strings or null", + "value": { + "extra_fields": { + "additional_names": "Must be a list or null." + } + } + }, + "Unsupported poa_parsed_address key": { + "summary": "Only the seven documented keys are allowed", + "value": { + "poa_parsed_address": { + "zipcode": "Unsupported key. Allowed keys: address_type, city, region, street_1, street_2, postal_code, country" + } + } + }, + "Bad date format": { + "summary": "Dates must be YYYY-MM-DD", + "value": { + "issue_date": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing/invalid credentials or insufficient permissions. This API returns `403` for authentication failures — never `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Credential lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown session, session in a non-editable state, or no POA record to patch. Each case has a distinct `detail` message (see examples). The unknown-session lookup runs before credential checks.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "summary": "No session with this ID in your application (or it was deleted)", + "value": { + "detail": "Not found." + } + }, + "Wrong session state": { + "summary": "Session exists but is not Approved / Declined / In Review / Kyc Expired", + "value": { + "detail": "No Session matches the given query." + } + }, + "No POA record": { + "summary": "The workflow never ran a Proof of Address step", + "value": { + "detail": "The session does not have a POA." + } + }, + "No POA for node": { + "summary": "node_id given but no POA exists for it", + "value": { + "detail": "The session does not have a POA for node 'feature_missing'." + } + } + } + } + } + }, + "429": { + "description": "Dedicated rate limit exceeded — 10 POA data updates per minute per API key (rejected requests count toward the budget). The shared 300/min write budget can also trigger with its own message. Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "POA update budget exhausted", + "value": { + "detail": "Session POA data update rate limit exceeded. You can make up to 10 requests per minute." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-poa-data/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"issuing_state\": \"ESP\",\n \"document_type\": \"BANK_STATEMENT\",\n \"issuer\": \"Banco Example S.A.\",\n \"issue_date\": \"2026-05-02\",\n \"name_on_document\": \"Carmen Espanola\",\n \"poa_address\": \"Avda de Madrid 34, 28013 Madrid\",\n \"extra_fields\": {\n \"bank_iban\": \"ES9121000418450200051332\"\n },\n \"poa_parsed_address\": {\n \"city\": \"Madrid\",\n \"postal_code\": \"28013\",\n \"country\": \"ES\"\n }\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.patch(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-poa-data/\",\n headers={\n \"x-api-key\": \"YOUR_API_KEY\",\n \"Content-Type\": \"application/json\",\n },\n json={\n \"issuing_state\": \"ESP\",\n \"document_type\": \"BANK_STATEMENT\",\n \"issuer\": \"Banco Example S.A.\",\n \"issue_date\": \"2026-05-02\",\n \"name_on_document\": \"Carmen Espanola\",\n \"poa_address\": \"Avda de Madrid 34, 28013 Madrid\",\n \"extra_fields\": {\"bank_iban\": \"ES9121000418450200051332\"},\n \"poa_parsed_address\": {\"city\": \"Madrid\", \"postal_code\": \"28013\", \"country\": \"ES\"},\n },\n)\nresponse.raise_for_status()\npoa = response.json() # full current POA record\nprint(poa[\"issuer\"], poa[\"poa_formatted_address\"])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const response = await fetch(\n 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-poa-data/',\n {\n method: 'PATCH',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n issuing_state: 'ESP',\n document_type: 'BANK_STATEMENT',\n issuer: 'Banco Example S.A.',\n issue_date: '2026-05-02',\n name_on_document: 'Carmen Espanola',\n poa_address: 'Avda de Madrid 34, 28013 Madrid',\n poa_parsed_address: { city: 'Madrid', postal_code: '28013', country: 'ES' },\n }),\n },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst poa = await response.json(); // full current POA record" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/{companyUuid}/update-data/": { + "patch": { + "summary": "Update registry-extracted company data on a Business Verification (KYB) session", + "description": "Correct the registry-extracted company data on a Business Verification (KYB) session — company name, registration number, addresses, status, contact details — identified by `companyUuid`. Typical callers are a compliance analyst fixing a registry misread, or your back office syncing corrected company data. Every field in the body is optional (`PATCH` semantics); an empty JSON body (`{}`) is a no-op that simply echoes the current company record.\n\nEdits are written in two places at once: fields with a canonical company column (name, registration number, country, incorporation date, tax number, type, statuses, capital fields (`registered_capital`, `registered_capital_amount`, `registered_capital_currency`), alternative names, nature of business, website, email, phone, LEI, location, and `legal_address` → `registered_address`) update that column, and **every** submitted key is also merged into the `registry_data` snapshot (dates as ISO strings) so the \"extracted from registry\" view reflects the correction. Fields without a column — `region`, `registration_date`, `dissolution_date`, `control_scheme` — land in `registry_data` only. `user_provided_data` (what the end user confirmed in the hosted flow) is never touched, and `last_console_edit_at` is stamped on every non-empty PATCH.\n\nUnlike the KYC/POA update endpoints, there is **no session-status gate**: the company can be edited at any point in the business session's lifecycle. The company must belong to the session in the path (`404` with `\"KYB company does not belong to the given session.\"` otherwise) and to your application. Every non-empty PATCH fires the `data.updated` [webhook](/integration/webhooks) on the business session (trigger `console_registry_edit`) — even when the submitted values equal the stored ones. The session's status and decision are **not** re-evaluated, and no review/audit entry is recorded.\n\nAccepts an API key or a console user token; either way the credential needs the `write:businesses` permission. Authentication and permission failures both return `403` — this API never responds `401`. Note that the session lookup runs before credential checks, so an unknown `sessionId` returns `404` even with missing credentials. The shared write budget of 300 requests per minute per API key applies.\n\nFor User Verification sessions, the KYC counterpart is `PATCH /v3/session/{sessionId}/update-data/`; to change the session decision itself use `PATCH /v3/session/{sessionId}/update-status/`.", + "operationId": "patch_v3_session_kyb_company_update_data", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_kyb_company_data", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the parent Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "companyUuid", + "in": "path", + "required": true, + "description": "UUID of the company record to update (`registry_checks[].company.uuid` on the business session decision). Must belong to the session referenced by `sessionId`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "88888888-9999-aaaa-bbbb-cccccccccccc" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "All fields optional — send only what you want to change. Provided fields update the canonical company columns (where one exists) and are merged into the `registry_data` snapshot.", + "properties": { + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Legal name of the company. Written to the canonical column and the registry snapshot.", + "example": "Docs Probe Corp" + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Official registration number with the company registry.", + "example": "DPC-001" + }, + "country_code": { + "type": "string", + "maxLength": 2, + "description": "Country of incorporation, ISO 3166-1 alpha-2. Case-insensitive (`us` is stored as `US`). Alpha-3 codes fail the 2-character limit with `400`.", + "example": "US" + }, + "region": { + "type": "string", + "maxLength": 10, + "nullable": true, + "description": "Subdivision / region tag where the registry has one (e.g. US state). Stored in the registry snapshot only — it has no canonical column." + }, + "incorporation_date": { + "type": "string", + "format": "date", + "description": "Incorporation date in `YYYY-MM-DD`. Optional like every other field — the `PATCH` is partial. Any other date format returns `400`.", + "example": "2015-04-12" + }, + "registration_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Registration date where the registry distinguishes it from incorporation. Stored in the registry snapshot only." + }, + "dissolution_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Dissolution date for companies that are no longer active. Stored in the registry snapshot only." + }, + "tax_number": { + "type": "string", + "maxLength": 100, + "description": "Tax identification number." + }, + "legal_address": { + "type": "string", + "description": "Legal / registered address. Aliased server-side to the canonical `registered_address` column — the response returns it under `registered_address`, while the registry snapshot keeps the `legal_address` key you sent.", + "example": "1 Example Plaza, Wilmington, DE 19801" + }, + "company_type": { + "type": "string", + "maxLength": 100, + "description": "Legal entity type (e.g. `Limited Liability Company`, `PLC`, `GmbH`).", + "example": "Limited Liability Company" + }, + "registry_status": { + "type": "string", + "enum": [ + "active", + "dissolved", + "deregistered", + "see full details", + "authorised", + "appointed representative", + "unauthorised", + "inactive", + "no longer authorised", + "closed", + "struck off" + ], + "nullable": true, + "description": "Registry-reported company status. Lowercase, some values contain spaces (e.g. `struck off`, `no longer authorised`). Any other value returns `400`.", + "example": "active" + }, + "verification_status": { + "type": "string", + "enum": [ + "verified", + "failed", + "unknown" + ], + "nullable": true, + "description": "Outcome of the registry verification. Any other value returns `400`.", + "example": "verified" + }, + "alternative_names": { + "type": "string", + "description": "Trade names, former legal names, DBA names (free text)." + }, + "nature_of_business": { + "type": "string", + "description": "Short description of the company's activity.", + "example": "Software development" + }, + "registered_capital": { + "type": "string", + "maxLength": 100, + "description": "Human-readable registered capital (e.g. `USD 50000`). Use `registered_capital_amount` + `registered_capital_currency` for structured values, or this field for free-form text.", + "example": "USD 50000" + }, + "registered_capital_amount": { + "type": "number", + "format": "double", + "nullable": true, + "description": "Numeric registered-capital amount (up to 20 digits, 2 decimal places). Sets the `registered_capital_amount` column and is mirrored into `registry_data` as a decimal string; the response echoes it as a decimal string (e.g. `\"1500000.50\"`)." + }, + "registered_capital_currency": { + "type": "string", + "maxLength": 3, + "nullable": true, + "description": "ISO 4217 currency code for `registered_capital_amount`. Case-insensitive (`usd` is stored as `USD`); values that are not exactly 3 letters return `400` with the ISO 4217 message. Sets the canonical `registered_capital_currency` column and is mirrored into `registry_data`.", + "example": "USD" + }, + "website": { + "type": "string", + "maxLength": 500, + "description": "Company website. Bare domains are accepted and normalized with an `https://` prefix (`docsprobe.example.com` is stored as `https://docsprobe.example.com`); values that still fail URL validation return `400`.", + "example": "https://docsprobe.example.com" + }, + "email": { + "type": "string", + "format": "email", + "description": "Contact email on file with the registry.", + "example": "legal@docsprobe.example.com" + }, + "phone": { + "type": "string", + "maxLength": 50, + "description": "Contact phone. Must match `+`, digits, spaces, dashes, dots, or parentheses, with 7-15 digits total — otherwise `400`.", + "example": "+1 302 555 0143" + }, + "legal_entity_identifier": { + "type": "string", + "maxLength": 50, + "description": "LEI, where the company has one." + }, + "location_of_registration": { + "type": "string", + "maxLength": 255, + "description": "City / subdivision of the registry of record." + }, + "vat_number": { + "type": "string", + "maxLength": 64, + "description": "EU VAT number. Merged into the `registry_data` snapshot only — editing it here does not re-run VAT and tax ID validation, so `vat_validation_status` and the other `vat_*` result fields are unchanged." + }, + "control_scheme": { + "type": "string", + "description": "Control / governance scheme when the registry exposes it. Stored in the registry snapshot only." + } + } + }, + "examples": { + "Correct registry data": { + "summary": "Fix the company's registry-extracted fields", + "value": { + "company_name": "Docs Probe Corp", + "registration_number": "DPC-001", + "country_code": "US", + "incorporation_date": "2015-04-12", + "company_type": "Limited Liability Company", + "legal_address": "1 Example Plaza, Wilmington, DE 19801", + "registry_status": "active", + "verification_status": "verified", + "website": "docsprobe.example.com", + "email": "legal@docsprobe.example.com", + "phone": "+1 302 555 0143", + "registered_capital": "USD 50000", + "nature_of_business": "Software development" + } + }, + "Single field": { + "summary": "Only the keys you send are written", + "value": { + "registry_status": "dissolved" + } + }, + "No-op": { + "summary": "Empty body echoes the current record without firing the webhook", + "value": {} + } + } + } + } + }, + "responses": { + "200": { + "description": "Company data updated (or echoed unchanged for an empty body). Returns the full KYB company record — the same shape as `registry_checks[].company` on the business session decision. Note how `legal_address` comes back as `registered_address`. `registered_capital_amount`/`registered_capital_currency` populate their canonical columns and are mirrored into `registry_data`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Full KYB company record — the same shape as the `company` object inside `registry_checks[]` on the business session decision.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Company record identifier (the `companyUuid` path parameter)." + }, + "node_id": { + "type": "string", + "nullable": true, + "description": "Workflow graph node this company record belongs to, when the workflow is graph-based." + }, + "status": { + "type": "string", + "description": "Feature lifecycle status of the registry check (e.g. `Not Finished`, `Approved`, `Declined`, `In Review`)." + }, + "registry_status": { + "type": "string", + "nullable": true, + "description": "Registry-reported company status (e.g. `active`, `dissolved`, `struck off`)." + }, + "data_resolved": { + "type": "boolean", + "description": "`true` when the company data is complete — always `true` for manual-entry companies; for registry-sourced companies, `true` once the registry fetch resolved." + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of incorporation." + }, + "region": { + "type": "string", + "nullable": true + }, + "company_type": { + "type": "string", + "nullable": true + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "registered_address": { + "type": "string", + "nullable": true, + "description": "Canonical registered address — updated when you send `legal_address`." + }, + "tax_number": { + "type": "string", + "nullable": true + }, + "risk_level": { + "type": "string", + "nullable": true + }, + "verification_status": { + "type": "string", + "nullable": true, + "description": "`verified`, `failed`, or `unknown`." + }, + "is_from_registry": { + "type": "boolean", + "description": "`true` when the company was pre-filled from a registry search rather than entered manually." + }, + "fetch_status": { + "type": "string", + "nullable": true, + "description": "Registry fetch lifecycle for registry-sourced companies (`pending` / `resolved`); `null` for manual entries." + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "nullable": true, + "description": "Canonical column. Returned as a decimal string (e.g. `\"2750000.00\"`)." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "Canonical column. Set by the registry flow or by console/API edits via this endpoint." + }, + "website": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "phone": { + "type": "string", + "nullable": true + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true + }, + "location_of_registration": { + "type": "string", + "nullable": true + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true, + "additionalProperties": true + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Directors / officers attached to the company, including per-person KYC progress." + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Beneficial owners attached to the company, including per-person KYC progress." + }, + "addresses": { + "type": "array", + "nullable": true, + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Registry address detail, when the registry returned any." + }, + "industries": { + "type": "array", + "nullable": true, + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Registry industry classification, when available." + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Raw registry payload snapshot. Every key you PATCH is merged into this map (dates serialized as ISO strings), so the \"extracted from registry\" view reflects operator corrections." + }, + "user_provided_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "What the end user typed during the hosted registry-confirmation step. Never modified by this endpoint." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the end user confirmed the company data in the hosted flow." + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Stamped on every successful non-empty PATCH, so UIs can surface \"edited by operator\" context." + }, + "is_editable": { + "type": "boolean", + "description": "`true` while the end user can still edit the registry-sourced data in the hosted flow (registry-sourced and not yet confirmed)." + } + } + }, + "examples": { + "Updated company": { + "summary": "Full company record after the correction", + "value": { + "uuid": "6b9479f5-6769-46c8-baf1-df63bbbc5f8c", + "node_id": null, + "status": "Not Finished", + "registry_status": "active", + "data_resolved": true, + "company_name": "Docs Probe Corp", + "registration_number": "DPC-001", + "country_code": "US", + "region": null, + "company_type": "Limited Liability Company", + "incorporation_date": "2015-04-12", + "registered_address": "1 Example Plaza, Wilmington, DE 19801", + "tax_number": null, + "risk_level": null, + "verification_status": "verified", + "is_from_registry": false, + "fetch_status": null, + "alternative_names": null, + "nature_of_business": "Software development", + "registered_capital": "USD 50000", + "registered_capital_amount": null, + "registered_capital_currency": "USD", + "website": "https://docsprobe.example.com", + "email": "legal@docsprobe.example.com", + "phone": "+1 302 555 0143", + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": null, + "officers": [], + "beneficial_owners": [], + "addresses": null, + "industries": null, + "accounts": null, + "registry_data": { + "company_name": "Docs Probe Corp", + "registration_number": "DPC-001", + "country_code": "US", + "incorporation_date": "2015-04-12", + "legal_address": "1 Example Plaza, Wilmington, DE 19801", + "company_type": "Limited Liability Company", + "registry_status": "active", + "verification_status": "verified", + "nature_of_business": "Software development", + "registered_capital": "USD 50000", + "registered_capital_currency": "USD", + "website": "https://docsprobe.example.com", + "email": "legal@docsprobe.example.com", + "phone": "+1 302 555 0143" + }, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": "2026-06-12T00:02:34.276061Z", + "is_editable": false + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field errors arrive as `{\"\": [\"\"]}`. Max-length checks run before format checks, so `country_code: \"USA\"` reports the 2-character limit rather than the ISO message.", + "content": { + "application/json": { + "examples": { + "Alpha-3 country code": { + "summary": "country_code must be alpha-2 — length check fires first", + "value": { + "country_code": [ + "Ensure this field has no more than 2 characters." + ] + } + }, + "Non-alphabetic country code": { + "summary": "2 characters but not letters", + "value": { + "country_code": [ + "country_code must be a 2-letter ISO 3166-1 alpha-2 code." + ] + } + }, + "Invalid currency": { + "summary": "3 characters but not an ISO 4217 code", + "value": { + "registered_capital_currency": [ + "Currency must be a 3-letter ISO 4217 code (e.g. USD, EUR, GBP)." + ] + } + }, + "Invalid phone": { + "summary": "Phone fails the format / digit-count check", + "value": { + "phone": [ + "Invalid phone number format." + ] + } + }, + "Invalid registry status": { + "summary": "registry_status not in the enum", + "value": { + "registry_status": [ + "\"liquidated\" is not a valid choice." + ] + } + }, + "Invalid email": { + "summary": "Malformed email address", + "value": { + "email": [ + "Enter a valid email address." + ] + } + }, + "Invalid website": { + "summary": "Value fails URL validation even after https:// normalization", + "value": { + "website": [ + "Enter a valid URL." + ] + } + }, + "Bad date format": { + "summary": "Dates must be YYYY-MM-DD", + "value": { + "incorporation_date": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing/invalid credentials or insufficient permissions. This API returns `403` for authentication failures — never `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Credential lacks write:businesses", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown session, unknown company, or a company that belongs to a different session. Each case has a distinct `detail` message (see examples). The unknown-session lookup runs before credential checks.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "summary": "No session with this ID in your application (or it was deleted)", + "value": { + "detail": "Not found." + } + }, + "Unknown company": { + "summary": "No company with this UUID in your application", + "value": { + "detail": "No KYBCompany matches the given query." + } + }, + "Company / session mismatch": { + "summary": "companyUuid exists but belongs to another session", + "value": { + "detail": "KYB company does not belong to the given session." + } + } + } + } + } + }, + "429": { + "description": "Shared write rate limit exceeded (300 POST/PATCH/DELETE requests per minute per API key). Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "Write budget exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/33333333-4444-5555-6666-777777777777/kyb/88888888-9999-aaaa-bbbb-cccccccccccc/update-data/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"company_name\": \"Docs Probe Corp\",\n \"registration_number\": \"DPC-001\",\n \"country_code\": \"US\",\n \"incorporation_date\": \"2015-04-12\",\n \"company_type\": \"Limited Liability Company\",\n \"legal_address\": \"1 Example Plaza, Wilmington, DE 19801\",\n \"registry_status\": \"active\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.patch(\n \"https://verification.didit.me/v3/session/33333333-4444-5555-6666-777777777777/kyb/\"\n \"88888888-9999-aaaa-bbbb-cccccccccccc/update-data/\",\n headers={\n \"x-api-key\": \"YOUR_API_KEY\",\n \"Content-Type\": \"application/json\",\n },\n json={\n \"company_name\": \"Docs Probe Corp\",\n \"registration_number\": \"DPC-001\",\n \"country_code\": \"US\",\n \"incorporation_date\": \"2015-04-12\",\n \"legal_address\": \"1 Example Plaza, Wilmington, DE 19801\",\n \"registry_status\": \"active\",\n },\n)\nresponse.raise_for_status()\ncompany = response.json() # full company record\nprint(company[\"company_name\"], company[\"registered_address\"])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const response = await fetch(\n 'https://verification.didit.me/v3/session/33333333-4444-5555-6666-777777777777/kyb/' +\n '88888888-9999-aaaa-bbbb-cccccccccccc/update-data/',\n {\n method: 'PATCH',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n company_name: 'Docs Probe Corp',\n registration_number: 'DPC-001',\n country_code: 'US',\n incorporation_date: '2015-04-12',\n legal_address: '1 Example Plaza, Wilmington, DE 19801',\n registry_status: 'active',\n }),\n },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst company = await response.json(); // full company record" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/parties/{partyUuid}/": { + "patch": { + "summary": "Skip (or re-require) a Key People party on a Business Verification (KYB) session", + "description": "Exempt a Key People party from verification, or put it back in scope. This is the operator escape hatch for a party that can never finish its own check - most often because the individual KYC session handed to a UBO or director was deleted, which freezes that child's status and leaves the KYB parked in `Awaiting User` with nothing left that could move it.\n\nSetting `is_skipped: true` drops the party out of the Key People aggregation and immediately recomputes the `KYBKeyPeopleCheck` and the parent session status, so the business session can settle and then be approved or declined. Sending `is_skipped: false` puts the party back in scope and the check goes back to waiting on it.\n\nUnlike the applicant-facing skip in the hosted Key People screen, this endpoint is **not** gated by the role's `allow_skip` setting - that flag limits what the applicant may decide, not what your analyst may override. `requires_verification` is deliberately left untouched, so an overridden party reads as `requires_verification: true` **and** `is_skipped: true` and the record still shows what the rules asked for.\n\nThe party must belong to the session in the path (`404` with `\"Party does not belong to the given session.\"` otherwise) and to your application. A PATCH that actually changes the flag fires the `data.updated` [webhook](/integration/webhooks) on the business session with trigger `console_key_people_skip`; a PATCH that sets the flag to its current value is a no-op that echoes the party back.\n\nAccepts an API key or a console user token; either way the credential needs the `write:businesses` permission.", + "operationId": "patch_v3_session_kyb_party_update", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_kyb_party", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the parent Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "partyUuid", + "in": "path", + "required": true, + "description": "UUID of the Key People party to update (`key_people_checks[].submitted.parties[].uuid` on the business session decision). Must belong to the session referenced by `sessionId`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_skipped" + ], + "properties": { + "is_skipped": { + "type": "boolean", + "description": "`true` exempts the party from verification so the Key People check can settle without it; `false` puts it back in scope.", + "example": true + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Party updated (or echoed unchanged when the flag already had this value). Returns the party - the same shape as an entry of `key_people_checks[].submitted.parties[]` on the business session decision.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Key People party record - the same shape as an entry of `key_people_checks[].submitted.parties[]` on the business session decision.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Party identifier." + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "name": { + "type": "string", + "description": "Display name of the party." + }, + "is_skipped": { + "type": "boolean", + "description": "Whether the party is exempt from verification." + }, + "requires_verification": { + "type": "boolean", + "description": "What the role and threshold rules asked for. Left untouched by this endpoint." + }, + "kyc_session_status": { + "type": "string", + "nullable": true, + "description": "Status of the linked individual KYC session, frozen at deletion time when the session was deleted." + }, + "kyc_session_url": { + "type": "string", + "nullable": true, + "description": "Link to the linked individual KYC session. `null` when there is none or when it was deleted." + }, + "kyc_session_deleted": { + "type": "boolean", + "description": "The linked individual KYC session was deleted, so it can never settle." + }, + "kyb_sub_session_deleted": { + "type": "boolean", + "description": "Corporate-party counterpart of `kyc_session_deleted`." + } + } + } + } + } + }, + "400": { + "description": "Validation error on the request body. `is_skipped` is the only field this endpoint reads, and it is required. Field errors arrive as `{\"\": [\"\"]}`. Body validation runs *after* the party lookup, so a malformed `is_skipped` sent to a party that does not exist reports `404`, not `400`.", + "content": { + "application/json": { + "examples": { + "Missing is_skipped": { + "summary": "Empty body, or a body that omits `is_skipped`", + "value": { + "is_skipped": [ + "This field is required." + ] + } + }, + "Non-boolean is_skipped": { + "summary": "Value is not a boolean — `\"maybe\"`, `2`, `[]`. Booleans plus `\"true\"`/`\"false\"`, `1`/`0` and `\"yes\"`/`\"no\"` are all accepted", + "value": { + "is_skipped": [ + "Must be a valid boolean." + ] + } + }, + "Null is_skipped": { + "summary": "`is_skipped` is present but `null`", + "value": { + "is_skipped": [ + "This field may not be null." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing/invalid credentials or insufficient permissions. This API returns `403` for authentication failures — never `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Credential lacks write:businesses", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown party, or a party that belongs to a different session. Each case has a distinct `detail` message (see examples). Credential and permission checks run before the party lookup, so an unauthorised call reports `403` rather than `404`. There is no separate session lookup: an unknown `sessionId` paired with a party of yours reports the mismatch message.", + "content": { + "application/json": { + "examples": { + "Unknown party": { + "summary": "No party with this UUID in your application", + "value": { + "detail": "Not found." + } + }, + "Party / session mismatch": { + "summary": "partyUuid exists but hangs off another session", + "value": { + "detail": "Party does not belong to the given session." + } + } + } + } + } + }, + "429": { + "description": "Shared write rate limit exceeded (300 POST/PATCH/DELETE requests per minute per API key). Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "Write budget exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/settings/": { + "get": { + "summary": "Get Travel Rule settings", + "description": "Read the application's Travel Rule settings and VASP profile. Auto-creates a default (disabled) settings row and an empty profile on first read.", + "operationId": "getTravelRuleSettings", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_settings", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/settings/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Current Travel Rule settings and VASP profile.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleSettingsDetail" + }, + "examples": { + "Disabled (default)": { + "value": { + "is_enabled": false, + "jurisdiction": "EU", + "name_matching_strictness": "DEFAULT", + "confirmation_timeout_hours": 48, + "timeout_outcome": "HOLD", + "threshold_amount": "0.00", + "inbound_auto_accept": true, + "allow_self_declaration": true, + "allow_screenshot_proof": true, + "auto_wallet_verification": true, + "vasp_attribution_enabled": true, + "legal_name": "", + "lei": "", + "compliance_email": "", + "is_discoverable": true, + "counterparty_screening_enabled": false, + "sunrise_policy": "", + "sunrise_jurisdictions": [ + "CO", + "KH", + "VN" + ], + "non_responsive_policy": "", + "travel_address": "", + "disabled_networks": [], + "networks": { + "gtr": { + "rail": "GTR", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "trust": { + "rail": "TRUST", + "mode": "unavailable", + "state": "unavailable", + "reason": "own_membership_only", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "verifyvasp": { + "rail": "VERIFYVASP", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "sygna": { + "rail": "SYGNA", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + } + }, + "didit_network": { + "listed": false, + "missing": [ + "travel_rule_off", + "legal_name_missing", + "compliance_email_missing" + ], + "compliance_email_confirmed": false + } + } + } + } + } + } + } + } + }, + "put": { + "summary": "Update Travel Rule settings", + "description": "Upsert the application's Travel Rule settings and VASP profile. Partial update — only send the fields you want to change. Setting `is_enabled: true` requires a non-blank `legal_name` (already stored or included in the same request), otherwise returns 400.", + "operationId": "updateTravelRuleSettings", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "update_settings", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleSettingsUpdate" + }, + "example": { + "is_enabled": true, + "legal_name": "Origin CASP SL", + "jurisdiction": "EU", + "name_matching_strictness": "DEFAULT", + "confirmation_timeout_hours": 48, + "timeout_outcome": "HOLD", + "is_discoverable": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PUT https://verification.didit.me/v3/travel-rule/settings/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"is_enabled\": true, \"legal_name\": \"Origin CASP SL\", \"jurisdiction\": \"EU\"}'" + } + ], + "responses": { + "200": { + "description": "Updated settings and profile, in the same shape as GET.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleSettingsDetail" + } + } + } + }, + "400": { + "description": "Validation error, e.g. enabling without a legal_name, a sunrise_policy or non_responsive_policy other than HOLD, REJECT, PROCEED or empty, an unknown field in `matching_rules`, or a `data_sharing_fields` choice that cannot meet the originator obligations of your jurisdiction's regime (the errors are keyed by entity type under `data_sharing_fields`).", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "legal_name": [ + "legal_name is required to enable travel rule." + ] + } + } + } + } + } + } + }, + "/v3/travel-rule/settings/compliance-email/confirmation/": { + "post": { + "summary": "Send the compliance email confirmation", + "description": "Emails a single-use confirmation link to your VASP profile's compliance_email. Opening the link and pressing its confirm button confirms the address, which the Didit network needs before it lists your profile. A new request replaces the previous link; the link stops working when the compliance email changes. One email per minute per address.", + "operationId": "sendTravelRuleComplianceEmailConfirmation", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "send_compliance_email_confirmation", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/settings/compliance-email/confirmation/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "202": { + "description": "The confirmation email was sent.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sent_to": { + "type": "string", + "format": "email" + } + } + }, + "example": { + "sent_to": "compliance@your-vasp.example" + } + } + } + }, + "400": { + "description": "No compliance email is set, or it is already confirmed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "No compliance email": { + "value": { + "compliance_email": [ + "Set a compliance email before confirming it." + ] + } + }, + "Already confirmed": { + "value": { + "compliance_email": [ + "This compliance email is already confirmed." + ] + } + } + } + } + } + }, + "429": { + "description": "A confirmation went to this address less than a minute ago." + }, + "503": { + "description": "The email could not be sent. Nothing changed; try again in a moment." + } + } + } + }, + "/v3/travel-rule/wallet-addresses/": { + "get": { + "summary": "List wallet address book entries", + "description": "Paginated list of your application's Travel Rule wallet address book, newest first.", + "operationId": "listTravelRuleWalletAddresses", + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/wallet-addresses/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Paginated wallet address book entries.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WalletAddressEntry" + } + } + } + } + } + } + } + } + }, + "post": { + "summary": "Register a wallet address", + "description": "Add an entry to your Travel Rule wallet address book so inbound INTERNAL-rail transfers can resolve against it.", + "operationId": "createTravelRuleWalletAddress", + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressEntryCreate" + }, + "example": { + "address": "0xBeneficiaryWallet01", + "chain": "ETH", + "holder_name": "Ana Diaz", + "holder_vendor_data": "user-042", + "self_declared": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/wallet-addresses/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"address\": \"0xBeneficiaryWallet01\", \"chain\": \"ETH\", \"holder_name\": \"Ana Diaz\", \"self_declared\": true}'" + } + ], + "responses": { + "201": { + "description": "The created entry.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressEntry" + } + } + } + }, + "400": { + "description": "Duplicate wallet (address, chain and memo) for this application, a memo or X-address the chain cannot carry, a missing `address`, an invalid `travel_address`, `self_declared: true` while `allow_self_declaration` is off, or `self_declared: true` while `ownership_validation_mode` is `FULL`.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "examples": { + "Duplicate entry": { + "value": { + "address": [ + "A wallet address entry with this address and chain already exists." + ] + } + }, + "Invalid travel address": { + "value": { + "travel_address": [ + "Invalid travel address." + ] + } + }, + "Self-declaration disabled": { + "value": { + "self_declared": [ + "Self-declared ownership is disabled for this application." + ] + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/wallet-addresses/{entry_uuid}/": { + "patch": { + "summary": "Update wallet address holder metadata", + "description": "Partial update of holder_name, holder_vendor_data, entity_type, and travel_address. Setting travel_address attaches a TRP routing address to the entry; invalid values are rejected with 400. The response echoes only these four fields.", + "operationId": "updateTravelRuleWalletAddress", + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "entry_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressEntryUpdate" + }, + "example": { + "holder_name": "Ana Updated", + "travel_address": "ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH https://verification.didit.me/v3/travel-rule/wallet-addresses/{entry_uuid}/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"holder_name\": \"Ana Updated\", \"travel_address\": \"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\"}'" + } + ], + "responses": { + "200": { + "description": "The updated fields.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressEntryUpdate" + } + } + } + }, + "400": { + "description": "`travel_address` is not a valid travel address.", + "content": { + "application/json": { + "examples": { + "Invalid travel address": { + "value": { + "travel_address": [ + "Invalid travel address." + ] + } + } + } + } + } + }, + "404": { + "description": "Unknown or deleted entry." + } + } + }, + "delete": { + "summary": "Delete a wallet address", + "description": "Deletes the entry together with its ownership proofs and the screenshots they stored. It stops matching new transfers immediately and the address is freed up for re-registration. Transfers that already used the wallet keep their own record of it and read the same. Deletion is irreversible.", + "operationId": "deleteTravelRuleWalletAddress", + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "entry_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE https://verification.didit.me/v3/travel-rule/wallet-addresses/{entry_uuid}/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "204": { + "description": "Deleted." + }, + "404": { + "description": "Unknown or already deleted entry." + }, + "503": { + "description": "`wallet_address_deletion_incomplete`: a stored file could not be deleted, so the deletion stopped before it finished. The address-book entry is still there; retry the same request to finish it.", + "content": { + "application/json": { + "example": { + "detail": "A stored file could not be deleted, so the deletion stopped before it finished. Try again.", + "code": "wallet_address_deletion_incomplete" + } + } + } + } + } + } + }, + "/v3/travel-rule/vasps/": { + "get": { + "summary": "Search the VASP directory", + "description": "Paginated, name-sorted search of the VASP directory: the shared directory entries, which Didit builds from the ESMA interim MiCA register, the public TRISA directory, the global LEI register and its own curation, plus the members of the Didit network. Each row carries the routes your application has to it today. Your own application's profile is never listed. Rows also carry the shared due-diligence assessment and your application's own decision, entity screening and questionnaires.", + "operationId": "listTravelRuleVasps", + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "search", + "in": "query", + "schema": { + "type": "string", + "maxLength": 255 + }, + "description": "A name or alias fragment (case-insensitive), an exact LEI, or a domain (example.com or a URL on it). Didit network members match on their legal name, LEI or the domain of their compliance email." + }, + { + "name": "network", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "TRISA" + ] + }, + "description": "Only directory entries a network's member directory currently lists." + }, + { + "name": "jurisdiction", + "in": "query", + "schema": { + "type": "string" + }, + "description": "ISO 3166 alpha-2 or alpha-3 country code. 400 for anything else." + }, + { + "name": "reachable", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "true: only VASPs your application has an available route to (Didit network members included). false: only directory entries it cannot reach today." + }, + { + "name": "kind", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "directory", + "didit" + ] + }, + "description": "Only directory entries, or only Didit network members." + }, + { + "name": "risk", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "description": "Only directory entries whose risk_level is this band. Didit network members, which are not scored, never match." + }, + { + "name": "responsiveness", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "RESPONSIVE", + "UNRESPONSIVE", + "UNKNOWN" + ] + }, + "description": "Only directory entries whose responsiveness.status is this value. Didit network members never match." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/travel-rule/vasps/?search=counterparty&reachable=true' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Paginated, name-sorted VASP directory results.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VaspDirectoryEntry" + } + } + } + } + } + } + }, + "400": { + "description": "A filter is not valid: the body names each field, for example an unknown network, country code, risk band or responsiveness status.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "jurisdiction": [ + "Use an ISO 3166 alpha-2 or alpha-3 country code." + ], + "network": [ + "\"SWIFT\" is not a valid choice." + ] + } + } + } + } + }, + "x-didit-sdk": { + "resource": "travel_rule", + "method": "list_vasps", + "auth": "api_key", + "server": true + } + } + }, + "/v3/travel-rule/vasps/{vasp_id}/": { + "get": { + "summary": "Get a VASP directory entry", + "description": "One directory entry or Didit network member by the id the search returned, with the same fields and the routes your application has to it.", + "operationId": "getTravelRuleVasp", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_vasp", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "The entry.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VaspDirectoryEntry" + } + } + } + }, + "404": { + "description": "No entry or listed Didit network member has this id.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry has this id." + } + } + } + } + } + } + }, + "/v3/travel-rule/vasps/{vasp_id}/decision/": { + "get": { + "summary": "Get your decision on a VASP", + "description": "Your application's decision on a counterparty (a directory entry or a Didit network member): approved, restricted or blocked. An entity of a brand with no decision of its own follows the brand's decision, named in inherited_from. Private to your application.", + "operationId": "getTravelRuleVaspDecision", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_vasp_decision", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/decision/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "The decision that applies, or null.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecisionResponse" + }, + "examples": { + "Own decision": { + "value": { + "decision": { + "decision": "APPROVED", + "reason": "Licensed in DE, questionnaire reviewed", + "decided_by": "", + "updated_at": "2026-10-14T10:05:00Z", + "inherited_from": null + } + } + }, + "Inherited from the brand": { + "value": { + "decision": { + "decision": "BLOCKED", + "reason": "Group refused after review", + "decided_by": "", + "updated_at": "2026-10-14T10:05:00Z", + "inherited_from": { + "id": "5e4d3c2b-1a0f-4e9d-8c7b-6a5f4e3d2c1b", + "name": "Example Group" + } + } + } + }, + "No decision": { + "value": { + "decision": null + } + } + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + } + } + }, + "put": { + "summary": "Set your decision on a VASP", + "description": "Make or change your application's decision on a counterparty: APPROVED, RESTRICTED or BLOCKED, with an optional reason. It never changes the shared directory entry and acts only through your transaction rules (counterparty_dd_status). A decision on a brand also covers each of its entities that has no decision of its own.", + "operationId": "updateTravelRuleVaspDecision", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "update_vasp_decision", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecisionRequest" + }, + "example": { + "decision": "BLOCKED", + "reason": "Unlicensed in its home jurisdiction" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PUT https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/decision/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"decision\": \"BLOCKED\", \"reason\": \"Unlicensed in its home jurisdiction\"}'" + } + ], + "responses": { + "200": { + "description": "The decision as it now applies.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecisionResponse" + }, + "example": { + "decision": { + "decision": "BLOCKED", + "reason": "Unlicensed in its home jurisdiction", + "decided_by": "", + "updated_at": "2026-10-14T10:05:00Z", + "inherited_from": null + } + } + } + } + }, + "400": { + "description": "The decision is missing or not one of APPROVED, RESTRICTED or BLOCKED, or the reason is longer than 255 characters.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "decision": [ + "\"MAYBE\" is not a valid choice." + ] + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + } + } + }, + "delete": { + "summary": "Clear your decision on a VASP", + "description": "Clear your application's own decision on a counterparty. The response shows the decision that applies afterwards: null, or for an entity of a brand the brand's decision, which the entity follows again.", + "operationId": "clearTravelRuleVaspDecision", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "clear_vasp_decision", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/decision/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "The decision cleared; the body shows the decision that applies now.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecisionResponse" + }, + "example": { + "decision": null + } + } + } + }, + "404": { + "description": "No counterparty has this id, or your application has no decision of its own on it (a decision it only inherits from its brand cannot be cleared here: clear the brand's).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Unknown id": { + "value": { + "detail": "No directory entry or Didit network member has this id." + } + }, + "No decision of its own": { + "value": { + "detail": "This application has no decision of its own on this counterparty." + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/vasps/{vasp_id}/screening/": { + "get": { + "summary": "Get a VASP's entity screening", + "description": "Whether counterparty screening is on for your application, what one screening costs your organization, how long a result stays fresh, and your latest sanctions and adverse-media screening of the counterparty. Reading it is free.", + "operationId": "getTravelRuleVaspScreening", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_vasp_screening", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/screening/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Your screening setting, its price and your latest screening.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreeningStatus" + }, + "example": { + "enabled": true, + "price": "0.20", + "fresh_days": 30, + "screening": { + "id": "7a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d", + "status": "Approved", + "total_hits": 0, + "score": null, + "screened_at": "2026-10-14T09:58:00Z", + "fresh": true, + "flagged": false + } + } + } + } + }, + "403": { + "description": "Your organization's contract has no rate configured for counterparty screening.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No contract rate is configured for this usage type." + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + } + } + }, + "post": { + "summary": "Screen a VASP", + "description": "Screen the counterparty entity against sanctions and adverse media for your application. Opt-in: counterparty_screening_enabled must be on in your Travel Rule settings. Each new screening is billed at your organization's Company AML Screening price; a screening younger than 30 days is returned instead (200, not billed) unless you send refresh: true. One screening of a counterparty runs at a time per application. The result is private to your application. Sandbox applications are never screened.", + "operationId": "screenTravelRuleVasp", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "screen_vasp", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreeningRequest" + }, + "example": { + "refresh": false + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/screening/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"refresh\": false}'" + } + ], + "responses": { + "200": { + "description": "A fresh screening already existed and was returned. Nothing was billed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreeningResult" + }, + "example": { + "outcome": "reused", + "screening": { + "id": "7a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d", + "status": "Approved", + "total_hits": 0, + "score": null, + "screened_at": "2026-10-14T09:58:00Z", + "fresh": true, + "flagged": false + } + } + } + } + }, + "201": { + "description": "A new screening ran and was billed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreeningResult" + }, + "example": { + "outcome": "screened", + "screening": { + "id": "7a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d", + "status": "In Review", + "total_hits": 2, + "score": 85.0, + "screened_at": "2026-10-14T09:58:00Z", + "fresh": true, + "flagged": true + } + } + } + } + }, + "400": { + "description": "The body is not valid, or the counterparty has no name to screen.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "detail": [ + "This counterparty has no name to screen." + ] + } + } + } + }, + "403": { + "description": "Your organization's balance cannot cover the screening (code insufficient_credits; nothing ran and nothing was billed), or your contract has no rate configured for it.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me", + "code": "insufficient_credits" + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + }, + "409": { + "description": "Nothing ran and nothing was billed: counterparty screening is off for your application, the application is a sandbox one, or a screening of this counterparty is already running.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Screening off": { + "value": { + "detail": "Counterparty screening is turned off. Enable it in the Travel Rule settings first." + } + }, + "Sandbox application": { + "value": { + "detail": "Counterparty screening does not run for sandbox applications." + } + }, + "Already running": { + "value": { + "detail": "A screening of this counterparty is already running. Try again in a moment." + } + } + } + } + } + }, + "502": { + "description": "The screening could not be completed. Nothing was billed; try again in a moment.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "The screening provider did not answer. Try again in a moment." + } + } + } + } + } + } + }, + "/v3/travel-rule/vasps/{vasp_id}/questionnaires/": { + "get": { + "summary": "List due-diligence questionnaires with a VASP", + "description": "Every due-diligence questionnaire your application sent the counterparty, newest first, with its status and, once answered, the answers. Private to your application.", + "operationId": "listTravelRuleVaspQuestionnaires", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "list_vasp_questionnaires", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/questionnaires/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Your questionnaires with this counterparty.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaireList" + }, + "example": { + "questionnaires": [ + { + "id": "3f9e8d7c-6b5a-4c3d-9e2f-1a0b9c8d7e6f", + "status": "SENT", + "form_version": "1", + "sent_to": "travelrule@counterparty.example", + "sent_at": "2026-10-14T10:00:00Z", + "expires_at": "2026-11-13T10:00:00Z", + "answered_at": null, + "valid_until": null, + "current": false, + "answers": {} + }, + { + "id": "8b7a6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d", + "status": "ANSWERED", + "form_version": "1", + "sent_to": "travelrule@counterparty.example", + "sent_at": "2026-09-02T08:00:00Z", + "expires_at": "2026-10-02T08:00:00Z", + "answered_at": "2026-09-04T15:20:00Z", + "valid_until": "2027-09-04T15:20:00Z", + "current": true, + "answers": { + "legal_name": "Example Counterparty VASP, incorporated in the example jurisdiction.", + "licensing": "Crypto-asset service provider authorised under MiCA by its national regulator, licence active.", + "jurisdictions": "EU and EEA. We do not serve the United States.", + "aml_program": "Written AML/CFT policy, annual independent review, last completed in March 2026.", + "compliance_officer": "Head of Compliance, reachable at compliance@counterparty.example.", + "sanctions_screening": "Customers at onboarding and daily; counterparties on every transfer.", + "transaction_monitoring": "Rule-based monitoring with analyst review; suspicious activity reports filed with the FIU.", + "travel_rule": "TRP and email; we answer requests within one business day.", + "custody": "Segregated customer wallets held with a qualified custodian, insured.", + "ownership": "No shareholder holds 25 percent or more; parent company Example Holding Group.", + "record_keeping": "Five-year record-keeping for Travel Rule and transaction data, as our regulator requires.", + "data_protection": "Encrypted at rest and in transit, stored in the EU." + } + } + ] + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + } + } + }, + "post": { + "summary": "Send a due-diligence questionnaire to a VASP", + "description": "Email Didit's standard due-diligence questionnaire to a compliance address on record for the counterparty, with a single-use answer link. The counterparty has 30 days to answer every question; the answers stay current for 365 days. A new send replaces your earlier unanswered questionnaire to the same counterparty, whose link stops working. One email per minute per address.", + "operationId": "sendTravelRuleVaspQuestionnaire", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "send_vasp_questionnaire", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaireRequest" + }, + "example": { + "sent_to": "travelrule@counterparty.example" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/questionnaires/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"sent_to\": \"travelrule@counterparty.example\"}'" + } + ], + "responses": { + "202": { + "description": "The questionnaire was emailed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaire" + }, + "example": { + "id": "3f9e8d7c-6b5a-4c3d-9e2f-1a0b9c8d7e6f", + "status": "SENT", + "form_version": "1", + "sent_to": "travelrule@counterparty.example", + "sent_at": "2026-10-14T10:00:00Z", + "expires_at": "2026-11-13T10:00:00Z", + "answered_at": null, + "valid_until": null, + "current": false, + "answers": {} + } + } + } + }, + "400": { + "description": "sent_to is not a valid email address or not a compliance address on record for the counterparty, or the counterparty has no compliance address on record.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "Not on record": { + "value": { + "sent_to": [ + "Send the questionnaire to a compliance address on record for this counterparty." + ] + } + }, + "No address": { + "value": { + "sent_to": [ + "No compliance address is on record for this counterparty. Add a contact for it first." + ] + } + } + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + }, + "429": { + "description": "A questionnaire went to this address less than a minute ago." + }, + "503": { + "description": "The email could not be sent and no questionnaire was created. Try again in a moment." + } + } + } + }, + "/v3/transactions/{transaction_id}/travel-rule/": { + "patch": { + "summary": "Finish, cancel, resend, or update the originator wallet of a Travel Rule transfer", + "description": "Provide `payment_txn_id` to report the on-chain hash and move an outbound COMPLETED transfer to FINISHED (its INTERNAL-rail counterpart also moves to FINISHED), `{\"action\": \"cancel\"}` to cancel a non-terminal transfer, or `{\"action\": \"resend\"}` to route an outbound transfer stuck in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE, or NOT_ENOUGH_COUNTERPARTY_DATA again (improve the counterparty data first, e.g. add a travel_address to the destination wallet entry). resend applies to outbound transfers only. `{\"action\": \"update_compliance_email\", \"compliance_email\": \"desk@example.com\"}` replaces the compliance email of an outbound transfer in one of those three statuses, the fix for `reason: email_undeliverable`: the status does not change, and the next `resend` sends the request to that address. A resend is queued, not run inside the request: the response shows the transfer back in AWAITING_COUNTERPARTY with a `resend queued` timeline entry and a queued `delivery`, and where it went arrives as a `travel_rule.status.updated` webhook. When the transfer went over GTR, VerifyVASP or Sygna Bridge, the network learns the on-chain hash or the cancellation after the response, with retries; `delivery` shows how that message stands. `{\"action\": \"update_originator_wallet\", \"wallet_address\": \"...\"}` records the wallet the funds actually left from on an outbound transfer, for example the address a UTXO withdrawal was broadcast from rather than the main wallet you declared. It changes the record only: nothing is sent to the counterparty again and the status stays. The originator becomes `chain_details.originator` with `source: updated`, the change is added to the `timeline` with the previous and the new wallet, and the INTERNAL-rail counterpart of the transfer is updated the same way. `{\"action\": \"beneficiary_vasp\", \"beneficiary_vasp\": \"\"}` names the counterparty yourself when discovery could not, on an outbound transfer in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE or NOT_ENOUGH_COUNTERPARTY_DATA: the transfer is routed to that VASP and sent again.", + "operationId": "patchTravelRuleTransfer", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "update_transfer", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID that owns the transfer.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTransferPatchRequest" + }, + "examples": { + "Finish": { + "value": { + "payment_txn_id": "0xchainhash" + } + }, + "Cancel": { + "value": { + "action": "cancel" + } + }, + "Resend": { + "value": { + "action": "resend" + } + }, + "Update compliance email": { + "value": { + "action": "update_compliance_email", + "compliance_email": "desk@example.com" + } + }, + "Update originator wallet": { + "value": { + "action": "update_originator_wallet", + "wallet_address": "bc1qOriginatorInputAddress01" + } + }, + "Name the counterparty": { + "value": { + "action": "beneficiary_vasp", + "beneficiary_vasp": "9f1c2b34-5d6e-4f70-8a91-b2c3d4e5f607" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH https://verification.didit.me/v3/transactions/{transaction_id}/travel-rule/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"payment_txn_id\": \"0xchainhash\"}'" + } + ], + "responses": { + "200": { + "description": "The updated transfer. After `update_compliance_email` its status is unchanged and it keeps `reason: email_undeliverable` until the next `resend`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTransferDetail" + } + } + } + }, + "400": { + "description": "Neither payment_txn_id nor a supported action was provided, `compliance_email` is missing or not an email address on `update_compliance_email`, or, for `update_originator_wallet`, no wallet_address, a wallet or memo the transfer's chain cannot carry, or a wallet that is not one of the transfer's UTXO inputs, or `beneficiary_vasp` is missing or not a valid directory VASP id.", + "content": { + "application/json": { + "examples": { + "No action": { + "value": { + "detail": [ + "Provide payment_txn_id, action=cancel, action=resend, action=beneficiary_vasp, action=update_originator_wallet or action=update_compliance_email." + ] + } + }, + "Invalid compliance email": { + "value": { + "compliance_email": [ + "Enter a valid email address." + ] + } + } + } + } + } + }, + "404": { + "description": "Unknown transaction, the transaction has no Travel Rule transfer, or (action `beneficiary_vasp`) no directory entry has that id." + }, + "409": { + "description": "The transfer is not in a state that allows this operation (e.g. not COMPLETED/outbound for finish, already terminal for cancel, or not an outbound transfer in a resendable status for resend). `update_compliance_email` answers `409` for an inbound transfer, and for an outbound one outside COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE and NOT_ENOUGH_COUNTERPARTY_DATA. `update_originator_wallet` answers `409` for an inbound, cancelled or expired transfer. `beneficiary_vasp` answers `409` unless the transfer is outbound and in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE or NOT_ENOUGH_COUNTERPARTY_DATA.", + "content": { + "application/json": { + "examples": { + "Cancel from a terminal status": { + "value": { + "detail": "Transfer is already in a terminal state." + } + }, + "Resend not allowed": { + "value": { + "detail": "Transfer cannot be resent from its current state." + } + }, + "Finish not allowed": { + "value": { + "detail": "Transfer must be an outbound transfer in COMPLETED status to finish." + } + }, + "Compliance email cannot change": { + "value": { + "detail": "The compliance email of this transfer cannot change from its current state." + } + } + } + } + } + } + } + } + }, + "/v3/transactions/{transaction_id}/travel-rule/ownership/": { + "post": { + "summary": "Confirm or deny wallet ownership", + "description": "Resolve a transfer that waits on wallet ownership: call it on your own side of the transfer when it is UNCONFIRMED_OWNERSHIP, or when it is an inbound transfer in ON_HOLD. Confirming marks the matching wallet address book entry as ownership-verified (for an inbound ON_HOLD transfer with no entry, Didit creates one from beneficiary_data.wallet_address) and runs the beneficiary name match on both sides of the transfer; denying declines both sides. An inbound transfer whose deposit already settled moves on to FINISHED when the match succeeds. When the counterparty wallet was declared self-hosted (counterparty.payment_method.method_type: unhosted_wallet), confirming records that the customer verified who controls that wallet and completes the transfer (COMPLETED) in both directions, or FINISHED for an inbound deposit whose on-chain hash is already known. The wallet verified is the counterparty's: the destination of an outbound withdrawal, or the source (originator's wallet) of an inbound deposit. If it is not in your wallet address book yet, Didit adds it as ownership-verified under the counterparty's name; if the book already holds it under a different holder name, the transfer moves to COUNTERPARTY_MISMATCHED_DATA. For an inbound TRP transfer in UNCONFIRMED_OWNERSHIP, ON_HOLD or COUNTERPARTY_MISMATCHED_DATA, Didit sends your decision to the originating VASP instead: approving returns the payment address (wallet_address, or the matched entry's address) and moves the transfer to COMPLETED, and denying declines it. The same call also decides an inbound transfer that a GTR or Sygna Bridge counterparty sent and that waits for your review (`UNCONFIRMED_OWNERSHIP`, `ON_HOLD` or `COUNTERPARTY_MISMATCHED_DATA`): `confirmed: true` approves it (pass `wallet_address` when the transfer names none) and `confirmed: false` declines it, and the network is answered after the response, with retries. VerifyVASP takes its answer when it asks, so a VerifyVASP transfer that would need your review is declined at once and cannot be decided later.", + "operationId": "confirmTravelRuleOwnership", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "confirm_ownership", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable UUID of the transaction your application holds for this transfer: the beneficiary side's transaction on an INTERNAL-rail transfer, or the transaction you created (outbound or inbound) when the counterparty wallet was declared self-hosted.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleOwnershipConfirmRequest" + }, + "example": { + "confirmed": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/transactions/{transaction_id}/travel-rule/ownership/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"confirmed\": true}'" + } + ], + "responses": { + "200": { + "description": "The updated transfer. For an inbound transfer the counterparty's originator_data/beneficiary_data stay masked (`data_masked: true`) until ownership is confirmed, then `data_unmasked` records when, why and by whom.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTransferDetail" + } + } + } + }, + "409": { + "description": "The transfer is not waiting on ownership confirmation, no wallet address book entry matches its counterparty wallet, an INTERNAL transfer from another Didit customer needs a control proof (a signed message or a test deposit) before it can be confirmed, or a network transfer was already answered over its network (a VerifyVASP transfer, or a GTR or Sygna Bridge transfer you or Didit already decided).", + "content": { + "application/json": { + "examples": { + "Not awaiting confirmation": { + "value": { + "detail": "Transfer is not awaiting ownership confirmation." + } + }, + "No matching address book entry": { + "value": { + "detail": "wallet address entry no longer available" + } + } + } + } + } + }, + "400": { + "description": "Validation error: confirmed is missing, an inbound TRP approval has no wallet_address and no address to fall back on, or `confirmed: true` on a declared self-hosted wallet transfer that names no counterparty wallet and no `wallet_address` was sent.", + "content": { + "application/json": { + "examples": { + "No payment address": { + "value": { + "wallet_address": [ + "wallet_address is required to approve." + ] + } + } + } + } + } + }, + "404": { + "description": "Unknown transaction, or the transaction has no Travel Rule transfer." + }, + "502": { + "description": "Inbound TRP transfer only: Didit could not deliver your decision to the originating VASP, so the transfer status does not change.", + "content": { + "application/json": { + "examples": { + "Callback not delivered": { + "value": { + "detail": "Counterparty inquiry-resolution callback could not be delivered." + } + } + } + } + } + } + } + } + }, + "/v3/transactions/{transaction_id}/travel-rule/messages/": { + "get": { + "summary": "List Travel Rule messages", + "description": "Messages and document requests exchanged with the counterparty VASP about this transfer.", + "operationId": "listTravelRuleMessages", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "list_messages", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID that owns the transfer.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/transactions/{transaction_id}/travel-rule/messages/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "The messages, oldest first.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleMessage" + } + } + } + } + }, + "404": { + "description": "Unknown transaction or token, or no Travel Rule transfer on the transaction." + } + } + }, + "post": { + "summary": "Send a Travel Rule message or document request", + "description": "Sends a message or a document request to the counterparty VASP. Works on INTERNAL transfers (the other Didit customer sees it on its side of the pair) and EMAIL transfers (the counterparty is emailed and answers in the pickup portal). A transfer over TRP, GTR, TRUST, VerifyVASP or Sygna carries no messages, because those protocols have none. Limits: 100 messages per transfer, 5000 characters per body, 10 requested documents, 5 files of 10 MB each.", + "operationId": "createTravelRuleMessage", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "create_message", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID that owns the transfer.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessageCreateRequest" + }, + "example": { + "kind": "DOCUMENT_REQUEST", + "body": "Please confirm the date of birth of the beneficiary.", + "requested_documents": [ + "Proof of address" + ] + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessageCreateRequest" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/transactions/{transaction_id}/travel-rule/messages/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"kind\": \"DOCUMENT_REQUEST\", \"body\": \"Please confirm the date of birth of the beneficiary.\", \"requested_documents\": [\"Proof of address\"]}'" + } + ], + "responses": { + "201": { + "description": "The stored message.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessage" + } + } + } + }, + "400": { + "description": "Invalid body, a file type or size that is not accepted, more than 100 messages on the transfer, or a `reply_to` that is not a message of this transfer.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "requested_documents": [ + "Name at least one document the counterparty should send." + ] + } + } + } + }, + "404": { + "description": "Unknown transaction or token, or no Travel Rule transfer on the transaction." + }, + "409": { + "description": "No message can reach the counterparty. `detail` gives the reason: the transfer runs over a network rail (TRP, GTR, TRUST, VerifyVASP or Sygna), it has no counterparty yet, an INTERNAL transfer's data did not match the wallet holder so nothing reached the recipient, or an EMAIL transfer's pickup link has expired or the transfer is closed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/pickup/{token}/": { + "get": { + "summary": "Get email-rail pickup info", + "description": "Public, unauthenticated. Lets a non-Didit counterparty VASP preview the pending exchange from the emailed pickup link. The token itself is the authentication.", + "operationId": "getTravelRulePickup", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_pickup", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "contact", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "The link's `?contact=` value, the contact the link was sent to. Echoed back as `contact` unchanged; it is not validated." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/pickup/{token}/" + } + ], + "responses": { + "200": { + "description": "Pickup preview.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRulePickupInfo" + }, + "example": { + "reference": "33333333-4444-5555-6666-777777777777", + "requested_at": "2026-07-05T10:00:00Z", + "originator_vasp_name": "Origin CASP SL", + "originator_vasp_lei": "529900EXAMPLEORIG001", + "recipient_vasp_name": "Example Counterparty VASP", + "language": "es", + "amount": "0.250000000000000000", + "currency": "ETH", + "fiat_amount": "", + "fiat_currency": "", + "chain": "ETH", + "beneficiary_wallet": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb", + "status": "AWAITING_COUNTERPARTY", + "ivms_version": "ivms101.2023", + "deadline_at": "2026-07-07T10:00:00Z", + "reminders_sent": 0, + "contact": "", + "decline_reasons": [ + "data_mismatch", + "not_our_customer", + "wallet_not_hosted", + "sanctions", + "risk_appetite", + "jurisdiction", + "insufficient_data", + "other" + ], + "join_invitation": { + "eligible": false, + "url": "" + }, + "originator_data": { + "name": "Jane Doe", + "wallet_address": "0x77ccff0cc624a53cd8b8d8703ced826710211ec7" + }, + "beneficiary_data": { + "name": "Ana Diaz", + "wallet_address": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb" + }, + "ivms_request": { + "originator": { + "originatorPersons": [ + { + "naturalPerson": { + "name": { + "nameIdentifier": [ + { + "primaryIdentifier": "Doe", + "secondaryIdentifier": "Jane", + "nameIdentifierType": "LEGL" + } + ] + } + } + } + ], + "accountNumber": [ + "0x77ccff0cc624a53cd8b8d8703ced826710211ec7" + ] + }, + "beneficiary": { + "beneficiaryPersons": [ + { + "naturalPerson": { + "name": { + "nameIdentifier": [ + { + "primaryIdentifier": "Diaz", + "secondaryIdentifier": "Ana", + "nameIdentifierType": "LEGL" + } + ] + } + } + } + ], + "accountNumber": [ + "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb" + ] + } + }, + "data_masked": false + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The window has closed: the counterparty already answered, the transfer left AWAITING_COUNTERPARTY, or the deadline passed." + } + } + } + }, + "/v3/travel-rule/pickup/{token}/respond/": { + "post": { + "summary": "Respond to an email-rail pickup", + "description": "Public, unauthenticated. Accepts or declines the exchange from the emailed pickup link. On accept, beneficiary_name is checked against the originator's name_matching_strictness policy.", + "operationId": "respondTravelRulePickup", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "respond_pickup", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "contact", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "The link's `?contact=` value. Used when the body sends no `contact`." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRulePickupRespondRequest" + }, + "example": { + "decision": "accept", + "beneficiary_name": "Carlos Ruiz" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/pickup/{token}/respond/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"decision\": \"accept\", \"beneficiary_name\": \"Carlos Ruiz\"}'" + } + ], + "responses": { + "200": { + "description": "The resulting exchange status: COMPLETED or COUNTERPARTY_MISMATCHED_DATA after an accept, COUNTERPARTY_VASP_GENERAL_DECLINE after a decline, which also echoes `decline_reason`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "decline_reason": { + "type": "string", + "description": "Present after a decline." + } + } + }, + "examples": { + "accepted": { + "value": { + "status": "COMPLETED" + } + }, + "declined": { + "value": { + "status": "COUNTERPARTY_VASP_GENERAL_DECLINE", + "decline_reason": "not_our_customer" + } + } + } + } + } + }, + "400": { + "description": "A field is invalid: for example `decision` missing or not `accept` or `decline`, `decline_detail` missing on a decline with `decline_reason: other`, an `ivms_response` that is not an IVMS 101 object or is longer than 16384 characters, or a `contact` in the body that is not a UUID (`{\"contact\": [\"Must be a valid UUID.\"]}`).", + "content": { + "application/json": { + "example": { + "decline_detail": [ + "Say what the reason is when you choose another reason." + ] + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The window has closed: the counterparty already answered, the transfer left AWAITING_COUNTERPARTY, or the deadline passed." + } + } + } + }, + "/v3/travel-rule/pickup/{token}/messages/": { + "get": { + "summary": "List pickup messages", + "description": "Public, unauthenticated. The messages of an EMAIL-rail transfer as the counterparty VASP reads them: what the customer sent is `INBOUND` and is signed with the customer's VASP name. The token is the authentication.", + "operationId": "listTravelRulePickupMessages", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "list_pickup_messages", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/pickup/{token}/messages/" + } + ], + "responses": { + "200": { + "description": "The messages, oldest first, from the counterparty's side.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleMessage" + } + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The pickup link has expired or the transfer is closed." + } + } + }, + "post": { + "summary": "Reply in the pickup portal", + "description": "Public, unauthenticated. The counterparty VASP writes back on an EMAIL-rail transfer. Same body as the transaction's messages; attachments must be PDF, PNG, JPEG or WebP. The token is the authentication.", + "operationId": "createTravelRulePickupMessage", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "create_pickup_message", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessageCreateRequest" + }, + "example": { + "kind": "MESSAGE", + "body": "Attached is the proof of address of the wallet holder." + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessageCreateRequest" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/pickup/{token}/messages/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"kind\": \"MESSAGE\", \"body\": \"Attached is the proof of address of the wallet holder.\"}'" + } + ], + "responses": { + "201": { + "description": "The stored message.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessage" + } + } + } + }, + "400": { + "description": "Invalid body, a file type or size that is not accepted, more than 100 messages on the transfer, or a `reply_to` that is not a message of this transfer.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "requested_documents": [ + "Name at least one document the counterparty should send." + ] + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The pickup link has expired or the transfer is closed." + } + } + } + }, + "/v3/travel-rule/inbound/": { + "post": { + "summary": "Register an after-deposit (sunrise) inbound transfer", + "description": "Register Travel Rule data for a crypto deposit that already settled on-chain before any exchange took place. Creates an inbound travelRule transaction (or reuses an unclaimed deposit) and resolves the transfer against your wallet address book. For a newly created transaction, Didit sends transaction.created first and the travel_rule.status.updated webhooks after it.", + "operationId": "registerTravelRuleInbound", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "register_inbound", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleInboundRequest" + }, + "example": { + "chain": "ETH", + "txid": "0xdeposit-hash", + "wallet_address": "0xYourDepositWallet", + "amount": "500.00", + "currency": "USDC", + "originator_data": { + "name": "Origin CASP SL" + }, + "beneficiary_data": { + "name": "Ana Diaz" + }, + "originating_vasp": { + "name": "Origin CASP SL", + "lei": "", + "travel_address": "ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/inbound/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"chain\":\"ETH\",\"txid\":\"0xdeposit-hash\",\"wallet_address\":\"0xYourDepositWallet\",\"amount\":\"500.00\",\"currency\":\"USDC\"}'" + } + ], + "responses": { + "200": { + "description": "A transfer for this txid and wallet_address was already registered; the existing one is returned unchanged (created is false).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleInboundResponse" + } + } + } + }, + "201": { + "description": "A new Travel Rule transfer was registered, on a new inbound transaction or on an existing unclaimed deposit (created is true).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleInboundResponse" + } + } + } + }, + "400": { + "description": "A required field (`chain`, `txid`, `wallet_address`, `amount`, `currency`) is missing, `originating_vasp.lei` is not a valid LEI, `originating_vasp.travel_address` is not a valid travel address, a memo, X-address or `utxo` the chain cannot carry, or `originator_data` / `beneficiary_data` that is not an object.", + "content": { + "application/json": { + "examples": { + "Missing field": { + "value": { + "txid": [ + "This field is required." + ] + } + }, + "Invalid LEI": { + "value": { + "originating_vasp": { + "lei": [ + "Invalid LEI: expected 20 alphanumeric characters (ISO 17442)." + ] + } + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/test-vasp/inbound/": { + "post": { + "summary": "Trigger an inbound transfer from the Didit Test VASP (sandbox)", + "description": "Sandbox applications only. The Didit Test VASP sends your application an inbound Travel Rule transfer, so you can exercise the receiving side of an exchange end to end before going live. The transfer is booked and decided by the same logic a real inbound transfer from a network runs against your wallet address book, so the inbox, the decision and the wallet ownership flow behave as they do live. Nothing leaves Didit. A live application receives 400. Send the same `reference` again to get the transfer the first call booked instead of a second one.", + "operationId": "triggerTravelRuleTestVaspInbound", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "trigger_test_vasp_inbound", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTestVaspInboundRequest" + }, + "example": { + "beneficiary_wallet_address": "0xYourSandboxWallet", + "scenario": "accept", + "amount": "1", + "reference": "demo-001" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/test-vasp/inbound/ \\\n -H 'x-api-key: YOUR_SANDBOX_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"beneficiary_wallet_address\":\"0xYourSandboxWallet\",\"scenario\":\"accept\",\"reference\":\"demo-001\"}'" + } + ], + "responses": { + "201": { + "description": "The inbound transfer was booked and decided. A repeat with the same `reference` returns the same body.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTestVaspInboundResponse" + } + } + } + }, + "400": { + "description": "Invalid body, an unknown or unsupported `scenario`, or the application is a live application.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "409": { + "description": "The transfer could not be booked, for example because Travel Rule is not enabled for the application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/widget-session/": { + "post": { + "summary": "Mint a wallet-ownership widget session", + "description": "Mint a hosted wallet-ownership widget URL/token. Send the customer to the returned url to prove control of a wallet (message signing, Satoshi test, screenshot, or self-declaration). The widget is web-only in this release - open the url in the system browser or a custom tab, never inside a mobile SDK webview.", + "operationId": "createTravelRuleWidgetSession", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "create_widget_session", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetSessionCreate" + }, + "example": { + "wallet_address": "0xd3b62acb76fb27a7f8961ec40fa11746b113aca9", + "chain": "ETH", + "holder_name": "Jane Doe", + "vendor_data": "user-042", + "transaction_id": "99999999-aaaa-bbbb-cccc-dddddddddddd", + "expires_in_minutes": 60 + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/widget-session/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"wallet_address\":\"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\",\"chain\":\"ETH\",\"holder_name\":\"Jane Doe\",\"transaction_id\":\"99999999-aaaa-bbbb-cccc-dddddddddddd\"}'" + } + ], + "responses": { + "201": { + "description": "The minted widget session.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetSessionResponse" + } + } + } + }, + "400": { + "description": "Invalid body: `callback_url` is not an absolute http(s) URL, `expires_in_minutes` is outside 5 to 10080, `transaction_id` is not a UUID, or a field is longer than its limit.", + "content": { + "application/json": { + "examples": { + "Invalid callback_url": { + "value": { + "callback_url": [ + "callback_url must be an absolute http(s) URL." + ] + } + } + } + } + } + }, + "404": { + "description": "`transaction_id` does not name a Travel Rule transfer in this application." + } + } + } + }, + "/v3/travel-rule/widget/{token}/": { + "get": { + "summary": "Get wallet-ownership widget context", + "description": "Public read endpoint keyed by the widget session token - no API key required. Returns the widget context plus the status of every proof, so you can poll it to detect completion; completed_at becomes non-null once a proof is verified. A SATOSHI_TEST proof can sit at status PENDING while its deposit is seen on-chain but unconfirmed - a background job automatically rechecks it and completes it once the deposit confirms, so no client action is needed while it waits. Each proof carries the fields its method needs: deposit_address, expected_amount, and its own expires_at for SATOSHI_TEST; challenge for MESSAGE_SIGNING. Prefer the travel_rule.status.updated webhook over polling when the session is linked to a transfer.", + "operationId": "getTravelRuleWidgetContext", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_widget_context", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/widget/{token}/" + } + ], + "responses": { + "200": { + "description": "The widget context and proof statuses.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetContext" + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The session expired without completing." + } + } + } + }, + "/v3/travel-rule/widget/{token}/vasps/": { + "get": { + "summary": "Search VASPs from the wallet widget", + "description": "Public read endpoint keyed by the widget session token, no API key required: the VASP picker of the end user's widget. Same search and filters as GET /v3/travel-rule/vasps/ for the widget's application, answering only who each VASP is and whether it can be reached.", + "operationId": "listTravelRuleWidgetVasps", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "list_widget_vasps", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + }, + { + "name": "search", + "in": "query", + "schema": { + "type": "string", + "maxLength": 255 + }, + "description": "A name or alias fragment (case-insensitive), an exact LEI, or a domain (example.com or a URL on it). Didit network members match on their legal name, LEI or the domain of their compliance email." + }, + { + "name": "network", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "TRISA" + ] + }, + "description": "Only directory entries a network's member directory currently lists." + }, + { + "name": "jurisdiction", + "in": "query", + "schema": { + "type": "string" + }, + "description": "ISO 3166 alpha-2 or alpha-3 country code. 400 for anything else." + }, + { + "name": "reachable", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "true: only VASPs your application has an available route to (Didit network members included). false: only directory entries it cannot reach today." + }, + { + "name": "kind", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "directory", + "didit" + ] + }, + "description": "Only directory entries, or only Didit network members." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl 'https://verification.didit.me/v3/travel-rule/widget/{token}/vasps/?search=counterparty'" + } + ], + "responses": { + "200": { + "description": "Paginated, name-sorted results.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VaspDirectoryWidgetEntry" + } + } + } + } + } + } + }, + "400": { + "description": "A filter is not valid: the body names each field, for example an unknown network or country code.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "jurisdiction": [ + "Use an ISO 3166 alpha-2 or alpha-3 country code." + ], + "network": [ + "\"SWIFT\" is not a valid choice." + ] + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The session expired." + } + } + }, + "post": { + "summary": "Pick the counterparty VASP in the wallet widget", + "description": "Public write endpoint keyed by the widget session token, no API key required: the end user chose the destination VASP that automatic discovery could not name. Only an outbound transfer in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE or NOT_ENOUGH_COUNTERPARTY_DATA accepts it. The transfer is routed to that VASP and sent again; automatic discovery never overrides the pick.", + "operationId": "selectTravelRuleWidgetVasp", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "select_widget_vasp", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "vasp" + ], + "properties": { + "vasp": { + "type": "string", + "format": "uuid", + "description": "The `id` of a VASP from `GET /v3/travel-rule/widget/{token}/vasps/`." + } + } + }, + "example": { + "vasp": "9f1c2b34-5d6e-4f70-8a91-b2c3d4e5f607" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/widget/{token}/vasps/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"vasp\": \"9f1c2b34-5d6e-4f70-8a91-b2c3d4e5f607\"}'" + } + ], + "responses": { + "200": { + "description": "The pick was recorded and the transfer is being sent again.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "transfer_status": { + "type": "string", + "description": "The transfer status after the pick." + }, + "counterparty_vasp": { + "type": "string", + "description": "The name of the VASP the end user picked." + } + } + }, + "example": { + "detail": "Counterparty VASP selected; the transfer is being resent.", + "transfer_status": "AWAITING_COUNTERPARTY", + "counterparty_vasp": "Northwind Exchange" + } + } + } + }, + "400": { + "description": "`vasp` is missing, or no live directory entry has that id.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "vasp": [ + "No directory entry has this id." + ] + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "409": { + "description": "The widget session is not linked to a Travel Rule transfer, or the transfer is not an outbound one in a status that accepts a counterparty." + }, + "410": { + "description": "The session expired." + } + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-users/toggle-ongoing-monitoring/": { + "patch": { + "summary": "Toggle AML ongoing monitoring for users", + "description": "Enables or disables AML ongoing monitoring at the user level. Exactly one monitored AML screening is kept per user; users without any AML screening (for example imported users) get one created and screened first, billed as a regular AML screening. Set estimate_only to preview counts and costs without writing. Batches of 20 or fewer resolve inline (200, COMPLETED); larger batches return 202 and run asynchronously.", + "operationId": "toggle_vendor_users_ongoing_monitoring", + "x-didit-sdk": { + "resource": "users", + "method": "set_ongoing_monitoring", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Estimate (when estimate_only) or the completed job for small batches processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger batches are processed asynchronously; poll the job endpoint with the returned job_id.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "403": { + "description": "Missing write:sessions privilege, or insufficient balance to enable (the estimate is included in the error body)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-users/toggle-ongoing-monitoring/{job_id}/": { + "get": { + "summary": "Get AML ongoing monitoring job", + "description": "Progress and per-outcome counters of a monitoring toggle job. Poll every couple of seconds while status is PENDING or PROCESSING.", + "operationId": "get_vendor_users_ongoing_monitoring_job", + "x-didit-sdk": { + "resource": "users", + "method": "retrieve_ongoing_monitoring_job", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Monitoring job UUID returned by the toggle endpoint." + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "404": { + "description": "Unknown job for this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-businesses/toggle-ongoing-monitoring/": { + "patch": { + "summary": "Toggle AML ongoing monitoring for businesses", + "description": "Business-level equivalent of the user toggle: one monitored AML screening per business, with businesses lacking any AML screening screened first (company entity). Same estimate_only, inline fast-path and async job contract.", + "operationId": "toggle_vendor_businesses_ongoing_monitoring", + "x-didit-sdk": { + "resource": "businesses", + "method": "set_ongoing_monitoring", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Estimate (when estimate_only) or the completed job for small batches processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger batches are processed asynchronously; poll the job endpoint with the returned job_id.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "403": { + "description": "Missing write:sessions privilege, or insufficient balance to enable (the estimate is included in the error body)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-businesses/toggle-ongoing-monitoring/{job_id}/": { + "get": { + "summary": "Get AML ongoing monitoring job", + "description": "Progress and per-outcome counters of a monitoring toggle job. Poll every couple of seconds while status is PENDING or PROCESSING.", + "operationId": "get_vendor_businesses_ongoing_monitoring_job", + "x-didit-sdk": { + "resource": "businesses", + "method": "retrieve_ongoing_monitoring_job", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Monitoring job UUID returned by the toggle endpoint." + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "404": { + "description": "Unknown job for this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/businesses/toggle-ongoing-monitoring/": { + "patch": { + "summary": "Toggle AML ongoing monitoring for business sessions", + "description": "Session-level toggle for KYB verifications, addressed by session number. Sessions without an AML screening get one created and attached to the session itself. Same estimate_only, inline fast-path and async job contract as the user-level endpoints. Replaces the legacy synchronous behavior that skipped sessions without an AML screening.", + "operationId": "toggle_business_sessions_ongoing_monitoring", + "x-didit-sdk": { + "resource": "businesses", + "method": "set_session_ongoing_monitoring", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_enabled" + ], + "properties": { + "is_enabled": { + "type": "boolean", + "description": "true to enable monitoring, false to disable it." + }, + "estimate_only": { + "type": "boolean", + "default": false, + "description": "When true, returns the cost estimate and performs no writes." + }, + "update_all": { + "type": "boolean", + "default": false, + "description": "Apply to every entity of the application (scoped by the filter_* params when present)." + }, + "filter_search": { + "type": "string" + }, + "filter_status": { + "type": "array", + "items": { + "type": "string" + } + }, + "filter_country": { + "type": "string" + }, + "filter_date_from": { + "type": "string", + "format": "date" + }, + "filter_date_to": { + "type": "string", + "format": "date" + }, + "session_numbers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Business session numbers to toggle. Required unless update_all (or the legacy toggle_all alias) is true." + }, + "toggle_all": { + "type": "boolean", + "default": false, + "description": "Legacy alias of update_all." + }, + "filter_workflow_id": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Estimate (when estimate_only) or the completed job for small batches processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger batches are processed asynchronously; poll the job endpoint with the returned job_id.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "403": { + "description": "Missing write:sessions privilege, or insufficient balance to enable (the estimate is included in the error body)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/businesses/toggle-ongoing-monitoring/{job_id}/": { + "get": { + "summary": "Get AML ongoing monitoring job", + "description": "Progress and per-outcome counters of a monitoring toggle job. Poll every couple of seconds while status is PENDING or PROCESSING.", + "operationId": "get_business_sessions_ongoing_monitoring_job", + "x-didit-sdk": { + "resource": "businesses", + "method": "retrieve_session_ongoing_monitoring_job", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Monitoring job UUID returned by the toggle endpoint." + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "404": { + "description": "Unknown job for this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/": { + "get": { + "summary": "List cases", + "description": "List cases for the application, newest first. Supports filtering, search, and sorting; see query parameters. Paginated with `limit`/`offset`.", + "operationId": "list_cases", + "x-didit-sdk": { + "resource": "cases", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size. Defaults to 50." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset of the first record." + }, + { + "name": "status__in", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated statuses to include, e.g. OPEN,AWAITING_USER." + }, + { + "name": "priority", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated priorities to include." + }, + { + "name": "source__in", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated sources to include." + }, + { + "name": "blueprint", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter to cases on this blueprint." + }, + { + "name": "assigned_to", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter to cases assigned to this user." + }, + { + "name": "unassigned", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "true for unassigned cases, false for assigned cases." + }, + { + "name": "tag", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Filter to cases carrying this exact tag." + }, + { + "name": "search", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Matches title, case_number, tag, subject name/UUID/external ID, linked session UUID, or linked transaction UUID." + }, + { + "name": "date_from", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Created-at lower bound (inclusive)." + }, + { + "name": "date_to", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Created-at upper bound (inclusive)." + }, + { + "name": "due_before", + "in": "query", + "schema": { + "type": "string", + "format": "date-time" + }, + "description": "due_at upper bound." + }, + { + "name": "overdue", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "true to return only overdue open/awaiting-user cases." + }, + { + "name": "ordering", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "created_at", + "-created_at", + "due_at", + "-due_at" + ], + "default": "-created_at" + }, + "description": "Sort order. due_at sorts nulls last." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/?status__in=OPEN,AWAITING_USER&ordering=-due_at' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.get(\n 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n params={'status__in': 'OPEN,AWAITING_USER', 'ordering': '-due_at'},\n timeout=10,\n)\nresp.raise_for_status()\nfor case in resp.json()['results']:\n print(case['case_number'], case['title'], case['priority'], case['due_at'])" + } + ], + "responses": { + "200": { + "description": "Paginated case list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows. Exact up to 100, capped at 100 beyond that for performance. Rely on `next` being null to detect the last page." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or null." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or null." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseListItem" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create case", + "description": "Create a case anchored to exactly one subject (a user or a business), directly or by deriving the subject from an attached session/transaction link. Source is always `MANUAL` for API-created cases.", + "operationId": "create_case", + "x-didit-sdk": { + "resource": "cases", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string", + "maxLength": 255 + }, + "blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "CaseBlueprint UUID. Omit for no blueprint." + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ], + "default": "MEDIUM" + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "subject": { + "type": "object", + "description": "Required unless links is provided; the case's exactly-one subject.", + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "business" + ] + }, + "uuid": { + "type": "string", + "format": "uuid" + } + } + }, + "links": { + "type": "array", + "description": "Sessions/business sessions/transactions to attach on creation, e.g. [{\"entity_type\": \"transaction\", \"entity_uuid\": \"...\"}]. When subject is omitted, the subject is derived from the first link's owner.", + "items": { + "type": "object" + } + }, + "assigned_to": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "User UUID to assign immediately." + }, + "review_duplicates": { + "type": "boolean", + "default": false, + "description": "Recheck existing investigations before creating the case. When true, a subject with existing cases requires a nonblank separate_incident_reason. The console always enables this protection. Omitting it preserves existing API behavior." + }, + "separate_incident_reason": { + "type": "string", + "maxLength": 1000 + }, + "source_context": { + "$ref": "#/components/schemas/CaseDuplicateCaseFraudSourceContextRequest" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"title\": \"Manual - Jane Doe\", \"blueprint\": \"b1de0000-0000-4000-8000-000000000002\", \"priority\": \"HIGH\", \"subject\": {\"type\": \"user\", \"uuid\": \"5b9a0000-0000-4000-8000-00000000f00d\"}, \"tags\": [\"review\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={\n 'title': 'Manual - Jane Doe',\n 'blueprint': 'b1de0000-0000-4000-8000-000000000002',\n 'priority': 'HIGH',\n 'subject': {'type': 'user', 'uuid': '5b9a0000-0000-4000-8000-00000000f00d'},\n },\n timeout=10,\n)\nresp.raise_for_status()\ncase = resp.json()\nprint(case['case_number'], case['uuid'])" + } + ], + "responses": { + "201": { + "description": "Case created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "400": { + "description": "Neither subject nor links provided, or the subject/blueprint/assignee was not found.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "409": { + "description": "Duplicate review is enabled and an existing case was found without a separate-incident reason. No case was created." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/": { + "get": { + "summary": "Get case", + "description": "Retrieve a case with its subject, blueprint, notes, events, links, and checklist.", + "operationId": "get_case", + "x-didit-sdk": { + "resource": "cases", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Case detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "404": { + "description": "Case (or nested resource) not found in this application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update case", + "description": "Update the case's title, priority, due date, tags, or owning review team. Send only the fields you want to change; fields you omit keep their value. `tags` replaces the whole tag list. `team` takes the uuid of an active review team of this application (list them with `GET .../cases/teams/`), or null to clear the team. Requires a full-access API key, or `write:cases` for a console member: a key with restricted access gets 403. Logs PRIORITY_CHANGED, DUE_DATE_CHANGED, TAG_ADDED, TAG_REMOVED and TEAM_CHANGED events as applicable; a title change is saved without a timeline event. Returns the updated case.", + "operationId": "update_case", + "x-didit-sdk": { + "resource": "cases", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string", + "maxLength": 255, + "description": "New case title." + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "New deadline. Send null to clear it." + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The complete tag list. It replaces the current tags." + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "UUID of the review team that owns the case. It must be an active team of this application. Send null to clear the team." + } + } + }, + "examples": { + "Change priority and deadline": { + "value": { + "priority": "HIGH", + "due_at": "2026-11-01T00:00:00Z" + } + }, + "Move the case to a team": { + "value": { + "team": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + }, + "Clear the team": { + "value": { + "team": null + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"priority\": \"HIGH\", \"due_at\": \"2026-08-01T00:00:00Z\"}'" + }, + { + "lang": "curl", + "label": "curl (change team)", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"team\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\"}'" + } + ], + "responses": { + "200": { + "description": "Updated case.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "400": { + "description": "A field failed validation. The body is keyed by field name. `team` is rejected when it is not an active review team of this application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "Team not active in this application": { + "value": { + "team": [ + "Choose an active team of this application." + ] + } + }, + "Invalid priority": { + "value": { + "priority": [ + "\"URGENT\" is not a valid choice." + ] + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Cases endpoints need a full-access key. A console member token needs `write:cases`.", + "content": { + "application/json": { + "examples": { + "Restricted API Key": { + "summary": "API key with restricted access", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "Missing Privilege": { + "summary": "Console member without write:cases, or a key of another application", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Case not found in this application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No Case matches the given query." + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete case", + "description": "Delete a case permanently. Its draft regulatory reports are deleted with it. Linked verifications, people, companies and transactions are not deleted. There is no undelete endpoint.\n\nIf the case has a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "delete_case", + "x-didit-sdk": { + "resource": "cases", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "204": { + "description": "Case deleted." + }, + "403": { + "description": "The key cannot delete cases. It requires a full-access key with `write:cases`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "The case does not exist in this application or was already deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "409": { + "description": "The case has a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/assign/": { + "post": { + "summary": "Assign or unassign case", + "description": "Assign the case to a user, or unassign it by sending assignee_user_id: null. assignee_email/assignee_name auto-provision a User row when the assignee does not exist yet.", + "operationId": "assign_case", + "x-didit-sdk": { + "resource": "cases", + "method": "assign", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "assignee_user_id" + ], + "properties": { + "assignee_user_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "null to unassign." + }, + "assignee_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "assignee_name": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/assign/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"assignee_user_id\": \"9a000000-0000-4000-8000-0000000000aa\"}'" + } + ], + "responses": { + "200": { + "description": "Case assigned/unassigned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/resolve/": { + "post": { + "summary": "Resolve case", + "description": "Resolve the case with one of five resolutions: FALSE_POSITIVE (no concern found), VALID_THREAT (concern confirmed), INCONCLUSIVE, REQUIREMENTS_NOT_MET or REQUIREMENTS_SATISFIED. Blocked with 400 if the blueprint's checklist is not optional and items remain incomplete, or if the blueprint requires a resolution note and none is given. When the case's blueprint has a four-eyes checker step (four_eyes_checker_blueprint), the resolution is staged (case.pending_resolution) and the case moves to the checker blueprint instead of resolving immediately; a different officer must call approve or reject-approval. Submit a four-eyes resolution as a console member: a resolution an API key submits has no recorded maker, so approve and reject-approval return 403 for every caller. On a four-eyes blueprint you can name the member who must approve with `checker`: then only that member can approve or reject the resolution, and the response's `checker` names them. Without `checker`, the resolution waits on the checker blueprint's queue. Sending `checker` for a blueprint without a checker step returns 400.", + "operationId": "resolve_case", + "x-didit-sdk": { + "resource": "cases", + "method": "resolve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "resolution" + ], + "properties": { + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ] + }, + "note": { + "type": "string", + "nullable": true, + "description": "Required when the blueprint sets require_resolution_note." + }, + "checker": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Four-eyes blueprints only. The user uuid of the member who must approve this resolution (the same id as `assigned_to.uuid` on a case, or `members[].user_id` on a review team). The member must have case access to this application and cannot be the officer who submits the resolution. Omit it or send null to leave the resolution to the checker queue." + } + } + }, + "examples": { + "Resolve": { + "value": { + "resolution": "FALSE_POSITIVE", + "note": "Confirmed legitimate activity after reviewing documents." + } + }, + "Four-eyes blueprint with a named checker": { + "value": { + "resolution": "VALID_THREAT", + "note": "Document tampering confirmed.", + "checker": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/resolve/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"resolution\": \"FALSE_POSITIVE\", \"note\": \"Confirmed legitimate activity after reviewing documents.\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/resolve/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={'resolution': 'FALSE_POSITIVE', 'note': 'Confirmed legitimate activity.'},\n timeout=10,\n)\nresp.raise_for_status()\ncase = resp.json()\nprint(case['status'], case['resolution'], case.get('pending_resolution'))" + } + ], + "responses": { + "200": { + "description": "Resolved, or staged for 4-eyes approval.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "400": { + "description": "The resolution was refused. Non-field errors come as a list of strings: an incomplete checklist, a missing required note, or a `checker` that could not be checked or is not an active reviewer with case access to this application. `checker` errors come keyed by the field: the named member is the officer submitting the resolution, or the case's blueprint has no checker step.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + } + ] + }, + "examples": { + "Incomplete checklist": { + "value": [ + "Case has incomplete checklist items: Verify source of funds" + ] + }, + "Resolution note required": { + "value": [ + "A resolution note is required to resolve this case." + ] + }, + "Reviewer access could not be verified": { + "value": [ + "Reviewer access could not be verified. Try again." + ] + }, + "Checker is not an active reviewer": { + "value": [ + "Choose an active reviewer with case access to this application." + ] + }, + "Checker is the submitting officer": { + "value": { + "checker": [ + "The officer who submitted the resolution cannot be its checker." + ] + } + }, + "Policy has no checker step": { + "value": { + "checker": [ + "This review policy has no checker step, so the case resolves directly." + ] + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/reopen/": { + "post": { + "summary": "Reopen case", + "description": "Move an awaiting-user or resolved case back to open, clearing resolution, resolution_notes, resolved_at, and resolved_by_email.", + "operationId": "reopen_case", + "x-didit-sdk": { + "resource": "cases", + "method": "reopen", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/reopen/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Reopen case.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/request-info/": { + "post": { + "summary": "Request info (awaiting user)", + "description": "Move an open case to awaiting_user while more information is collected from the verified user. Fails with 400 if a resolution is currently pending 4-eyes approval.", + "operationId": "request_case_info", + "x-didit-sdk": { + "resource": "cases", + "method": "request_info", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/request-info/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Request info (awaiting user).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/escalate/": { + "post": { + "summary": "Escalate case", + "description": "Move the case to its blueprint's configured escalation_blueprint. Requires the current blueprint to have escalation_enabled and an escalation_blueprint set, otherwise 400.", + "operationId": "escalate_case", + "x-didit-sdk": { + "resource": "cases", + "method": "escalate", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/escalate/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Escalate case.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/transfer/": { + "post": { + "summary": "Transfer case", + "description": "Move the case to a different, active blueprint of the same application. Requires the current blueprint to have transfer_enabled, otherwise 400.", + "operationId": "transfer_case", + "x-didit-sdk": { + "resource": "cases", + "method": "transfer", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "target_blueprint" + ], + "properties": { + "target_blueprint": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/transfer/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"target_blueprint\": \"b1de0000-0000-4000-8000-000000000002\"}'" + } + ], + "responses": { + "200": { + "description": "Case transferred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/approve/": { + "post": { + "summary": "Approve staged resolution (4-eyes)", + "description": "Apply a resolution staged by resolve on a four-eyes blueprint. The caller must be a different officer than the one who submitted it, otherwise 403. When the resolution names a checker (the case's `checker`), only that member can approve it: any other caller, including an API key, gets 403.", + "operationId": "approve_case_resolution", + "x-didit-sdk": { + "resource": "cases", + "method": "approve_resolution", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/approve/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Resolution applied.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "409": { + "description": "Case has no resolution pending approval.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "403": { + "description": "The caller cannot approve this resolution: they submitted it, the case has no recorded maker, or the resolution names a different checker. An API key always gets this 403 when a checker is named.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Caller submitted the resolution": { + "value": { + "detail": "The officer who submitted the resolution cannot approve it." + } + }, + "No recorded maker": { + "value": { + "detail": "Case has no recorded maker for this pending resolution; approve is not permitted." + } + }, + "Caller is not the named checker": { + "value": { + "detail": "Only the named checker can approve this resolution." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/reject-approval/": { + "post": { + "summary": "Reject staged resolution (4-eyes)", + "description": "Reject a resolution staged by resolve and return the case to its maker blueprint. The caller must be a different officer than the one who submitted it, otherwise 403. When the resolution names a checker (the case's `checker`), only that member can reject it: any other caller, including an API key, gets 403.", + "operationId": "reject_case_resolution", + "x-didit-sdk": { + "resource": "cases", + "method": "reject_resolution", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "notes": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/reject-approval/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"notes\": \"Needs a clearer rationale before this can be approved.\"}'" + } + ], + "responses": { + "200": { + "description": "Resolution rejected, case returned to the maker blueprint.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "409": { + "description": "Case has no resolution pending approval.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "403": { + "description": "The caller cannot reject this resolution: they submitted it, the case has no recorded maker, or the resolution names a different checker. An API key always gets this 403 when a checker is named.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Caller submitted the resolution": { + "value": { + "detail": "The officer who submitted the resolution cannot reject it." + } + }, + "No recorded maker": { + "value": { + "detail": "Case has no recorded maker for this pending resolution; reject is not permitted." + } + }, + "Caller is not the named checker": { + "value": { + "detail": "Only the named checker can reject this resolution." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/entity-actions/": { + "post": { + "operationId": "apply_case_entity_action", + "x-didit-sdk": { + "resource": "cases", + "method": "apply_entity_action", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseEntityActionRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseEntityActionRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseEntityActionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseEntityActionResult" + } + } + }, + "description": "" + }, + "409": { + "description": "The reference belongs to a different action payload." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Apply one explicit action to the case subject. Supply a unique client_reference to obtain an action receipt and make retries idempotent. Reusing that reference with a different payload returns 409. With a reference, inspect receipt.status even when HTTP status is 200: FAILED means no action was applied. Repeating a failed request does not execute it again unless retry is true. Omitting client_reference preserves the legacy response." + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/print/": { + "get": { + "summary": "Print case (PDF)", + "description": "Render the full case (summary, subject, checklist state, notes, attached sessions/transactions, events, FIU report list) as a PDF and return a presigned download URL.", + "operationId": "print_case", + "x-didit-sdk": { + "resource": "cases", + "method": "print", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/print/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Presigned PDF URL.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/notes/": { + "get": { + "operationId": "list_case_notes", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "name": "ordering", + "required": false, + "in": "query", + "description": "Which field to use when ordering the results.", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "offset", + "required": false, + "in": "query", + "description": "The initial index from which to return the results.", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationPaginatedCaseNoteList" + } + } + }, + "description": "" + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The case or note is unavailable for this caller." + } + }, + "tags": [ + "Case Management" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "operationId": "create_case_note", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNoteCreateRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNoteCreateRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNoteCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNote" + } + } + }, + "description": "" + }, + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNote" + } + } + }, + "description": "" + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The case or note is unavailable for this caller." + }, + "409": { + "description": "This client reference was already used for another note or for a note that has been deleted." + } + }, + "tags": [ + "Case Management" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Add a case-scoped note. An optional client_reference makes an unchanged retry return the same note without duplicate activity or notifications. reply_to must identify a live note in this case. The response includes a bounded parent excerpt without copying that excerpt into the saved reply." + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/notes/{note_uuid}/": { + "patch": { + "operationId": "root_partial_update", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "note_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationPatchedCaseNoteUpdateRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationPatchedCaseNoteUpdateRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationPatchedCaseNoteUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNote" + } + } + }, + "description": "" + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The case or note is unavailable for this caller." + } + }, + "tags": [ + "Case Management" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Pin or unpin an existing note. Pinned notes appear first. Pin changes are recorded in case activity and do not change evidence or source decisions." + }, + "delete": { + "operationId": "delete_case_note", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "note_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "The note is permanently deleted. Replies remain, without the deleted parent excerpt. Attachments are deleted unless another note still uses them." + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The case or note is unavailable for this caller." + } + }, + "tags": [ + "Case Management" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/notes/presign/": { + "post": { + "summary": "Upload note attachment", + "description": "Upload a file (JPEG, PNG, WebP, PDF, plain text, or CSV; max 5 MB) to attach to a note. Returns the s3_key to pass in the note's attachments array.", + "operationId": "upload_case_note_attachment", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "create_attachment_upload", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "file" + ], + "properties": { + "file": { + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/notes/presign/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'file=@evidence.pdf'" + } + ], + "responses": { + "201": { + "description": "File uploaded.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "s3_key": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "filename": { + "type": "string" + }, + "file_size": { + "type": "integer" + } + } + } + } + } + }, + "400": { + "description": "No file, unsupported content type, or file exceeds 5 MB.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "get": { + "summary": "Get note attachment URL", + "description": "Mint a fresh presigned download URL for an attachment already stored on one of the case's notes.", + "operationId": "get_case_note_attachment_url", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "retrieve_attachment_url", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "s3_key", + "in": "query", + "schema": { + "type": "string" + }, + "description": "The attachment's s3_key, as returned by upload or listed on the note.", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/notes/presign/?s3_key=case-notes/c1a5e000-0000-4000-8000-000000000001/evidence.pdf' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Presigned URL.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "s3_key": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "filename": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "s3_key missing or does not belong to this case.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/links/": { + "get": { + "summary": "List case links", + "description": "List sessions, business sessions, and transactions attached to the case.", + "operationId": "list_case_links", + "x-didit-sdk": { + "resource": "cases.links", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/links/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Case links.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseLink" + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Add case link", + "description": "Attach a session, business session, or transaction to the case. Rejected with 400 if the entity belongs to a different subject than the case.", + "operationId": "create_case_link", + "x-didit-sdk": { + "resource": "cases.links", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "entity_type", + "entity_uuid" + ], + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "session", + "business_session", + "transaction" + ] + }, + "entity_uuid": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/links/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"entity_type\": \"transaction\", \"entity_uuid\": \"77000000-0000-4000-8000-000000000077\"}'" + } + ], + "responses": { + "201": { + "description": "Link created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseLink" + } + } + } + }, + "400": { + "description": "Entity belongs to a different profile than the case subject.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/links/{link_uuid}/": { + "delete": { + "summary": "Remove case link", + "description": "Detach a linked session, business session, or transaction from the case.", + "operationId": "delete_case_link", + "x-didit-sdk": { + "resource": "cases.links", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "link_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "11000000-0000-4000-8000-000000000004" + }, + "description": "Link UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/links/11000000-0000-4000-8000-000000000004/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "204": { + "description": "Link removed." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/events/": { + "get": { + "summary": "List case events", + "description": "Read-only audit timeline of the case (created, status changes, assignment, resolution, escalation, transfers, tag/priority/due-date changes, entity actions, and more).", + "operationId": "list_case_events", + "x-didit-sdk": { + "resource": "cases", + "method": "list_events", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/events/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Paginated events, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows. Exact up to 100, capped at 100 beyond that for performance. Rely on `next` being null to detect the last page." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or null." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or null." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseEvent" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/checklist/": { + "get": { + "summary": "List case checklist", + "description": "List the case's checklist items, copied from the blueprint's content_config.checklist.items at creation, ordered.", + "operationId": "list_case_checklist", + "x-didit-sdk": { + "resource": "cases.checklist", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/checklist/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Checklist items.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseChecklistItem" + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/checklist/{item_uuid}/": { + "patch": { + "summary": "Toggle checklist item", + "description": "Mark a checklist item complete or incomplete. Resolution is blocked while any item is incomplete, unless the blueprint marks the checklist optional.", + "operationId": "toggle_case_checklist_item", + "x-didit-sdk": { + "resource": "cases.checklist", + "method": "toggle_item", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "item_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c4ec0000-0000-4000-8000-000000000005" + }, + "description": "Checklist item UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_completed" + ], + "properties": { + "is_completed": { + "type": "boolean" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/checklist/c4ec0000-0000-4000-8000-000000000005/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"is_completed\": true}'" + } + ], + "responses": { + "200": { + "description": "Updated checklist item.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseChecklistItem" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/aml/": { + "get": { + "summary": "List case AML hits", + "description": "List AML screening hits for the case's subject. Same shape as the hits returned by POST /v3/aml/.", + "operationId": "list_case_aml_hits", + "x-didit-sdk": { + "resource": "cases", + "method": "list_aml_hits", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/aml/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "AML hits.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AMLScreening" + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/statistics/": { + "get": { + "summary": "Case analytics", + "description": "Aggregate case analytics for the date range (defaults to the last 30 days; max 731 days). Charts, tables, totals, assignment events, and current queue counts use the same application-scoped filters. Filters use current case values. Dates apply to each metric event, so older cases assigned or resolved during the period remain included. Queue depth is independent of the date range. Invalid filters return 400; an unknown blueprint or assignee returns an empty result.", + "operationId": "get_case_analytics", + "x-didit-sdk": { + "resource": "cases", + "method": "retrieve_statistics", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "date_from", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Range start (inclusive). Defaults to 30 days ago." + }, + { + "name": "date_to", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Range end (inclusive). Defaults to today." + }, + { + "name": "blueprint", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter by the current blueprint UUID." + }, + { + "name": "assigned_to", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter by the current assigned reviewer user UUID." + }, + { + "name": "priority", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "description": "Filter by current case priority." + }, + { + "name": "source", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ] + }, + "description": "Filter by case creation source." + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ] + }, + "description": "Filter by current case status." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/statistics/?date_from=2026-06-01&date_to=2026-06-30' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Analytics payload.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "series": { + "type": "array", + "items": { + "type": "object", + "properties": { + "date": { + "type": "string", + "format": "date" + }, + "created": { + "type": "integer" + }, + "reviewed": { + "type": "integer" + } + } + } + }, + "tables": { + "type": "object", + "properties": { + "officer": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Dimension value: officer email, \"blueprint_name:uuid\", source, or status. \"unassigned\"/\"none\" for missing dimensions." + }, + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + } + }, + "blueprint": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Dimension value: officer email, \"blueprint_name:uuid\", source, or status. \"unassigned\"/\"none\" for missing dimensions." + }, + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + } + }, + "source": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Dimension value: officer email, \"blueprint_name:uuid\", source, or status. \"unassigned\"/\"none\" for missing dimensions." + }, + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + } + }, + "status": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Dimension value: officer email, \"blueprint_name:uuid\", source, or status. \"unassigned\"/\"none\" for missing dimensions." + }, + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + } + } + } + }, + "totals": { + "type": "object", + "properties": { + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + }, + "queue": { + "type": "object", + "description": "Live snapshot, not scoped to the date range.", + "properties": { + "open": { + "type": "integer" + }, + "overdue": { + "type": "integer" + } + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/bulk/status/": { + "post": { + "summary": "Bulk update case status", + "description": "Apply the same status transition to several cases in one call: OPEN reopens, AWAITING_USER requests info, RESOLVED resolves (resolution required). Each case is evaluated independently; gate failures (incomplete checklist, missing required note) are reported per case without aborting the batch.", + "operationId": "bulk_update_case_status", + "x-didit-sdk": { + "resource": "cases", + "method": "bulk_update_status", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "case_uuids", + "status" + ], + "properties": { + "case_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1 + }, + "status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ] + }, + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ], + "description": "Required when status is RESOLVED." + }, + "note": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/bulk/status/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"case_uuids\": [\"c1a5e000-0000-4000-8000-000000000001\"], \"status\": \"RESOLVED\", \"resolution\": \"FALSE_POSITIVE\"}'" + } + ], + "responses": { + "200": { + "description": "Per-case results.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBulkUpdateResult" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/bulk/assign/": { + "post": { + "summary": "Bulk assign cases", + "description": "Assign (or unassign, with assignee_user_id: null) several cases at once. All cases must share the same blueprint; mismatches are reported per case without aborting the batch.", + "operationId": "bulk_assign_cases", + "x-didit-sdk": { + "resource": "cases", + "method": "bulk_assign", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "case_uuids", + "assignee_user_id" + ], + "properties": { + "case_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1 + }, + "assignee_user_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "assignee_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "assignee_name": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/bulk/assign/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"case_uuids\": [\"c1a5e000-0000-4000-8000-000000000001\"], \"assignee_user_id\": \"9a000000-0000-4000-8000-0000000000aa\"}'" + } + ], + "responses": { + "200": { + "description": "Per-case results.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBulkUpdateResult" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/": { + "get": { + "summary": "List blueprints", + "description": "List case blueprints for the application.", + "operationId": "list_case_blueprints", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Paginated blueprint list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows, capped at 100 for performance." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseBlueprintListItem" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create blueprint", + "description": "Create a case blueprint: general settings (assignment, escalation, transfer, 4-eyes pairing) plus content_config, the six sections that drive the case page. Set publish=false to create an unpublished draft; Publish creates its first live version. Discarding a never-published draft deletes it.", + "operationId": "create_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "assignee_pool" + ], + "properties": { + "name": { + "type": "string", + "maxLength": 24, + "description": "Max 24 characters, e.g. \"AML investigation\"." + }, + "description": { + "type": "string", + "nullable": true + }, + "assignee_pool": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1, + "description": "List of User UUIDs. Empty arrays are rejected with \"Pick at least one assignee\"." + }, + "assignment_method": { + "type": "string", + "enum": [ + "MANUAL", + "AUTOMATIC" + ], + "default": "MANUAL" + }, + "max_active_cases": { + "type": "integer", + "nullable": true, + "minimum": 1, + "description": "Automatic-mode threshold: \"fewer than N cases of this blueprint assigned\"." + }, + "escalation_enabled": { + "type": "boolean", + "default": false + }, + "escalation_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Target blueprint UUID. Not enforced at save time, but the escalate action fails with 400 until both escalation_enabled and escalation_blueprint are set." + }, + "transfer_enabled": { + "type": "boolean", + "default": false + }, + "four_eyes_checker_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Setting this enables 4-eyes review: resolutions on this blueprint stage on the checker blueprint's queue." + }, + "require_resolution_note": { + "type": "boolean", + "default": false + }, + "content_config": { + "$ref": "#/components/schemas/CaseContentConfig" + }, + "is_active": { + "type": "boolean", + "default": true + }, + "default_deadline_hours": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 8760, + "description": "Default deadline for newly opened cases. Existing case deadlines are unchanged." + }, + "publish": { + "type": "boolean", + "writeOnly": true, + "default": true, + "description": "Set false to save a never-published draft. The record remains inactive at version 0 until the draft is explicitly published. Omitting this field preserves immediate creation for existing integrations." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"name\": \"AML investigation\", \"assignee_pool\": [\"9a000000-0000-4000-8000-0000000000aa\"], \"assignment_method\": \"AUTOMATIC\", \"max_active_cases\": 15, \"require_resolution_note\": true, \"content_config\": {\"aml_control\": {\"enabled\": true, \"financial\": true, \"identity\": false}}}'" + } + ], + "responses": { + "201": { + "description": "Blueprint created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintDetail" + } + } + } + }, + "400": { + "description": "Name over 24 chars, empty assignee_pool, or a cross-referenced blueprint (escalation/checker) not found.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/{blueprint_uuid}/": { + "get": { + "summary": "Get blueprint", + "description": "Retrieve a blueprint, including its connected creation sources (AML config, transaction rules, workflow nodes) and which blueprints escalate to it.", + "operationId": "get_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "blueprint_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "b1de0000-0000-4000-8000-000000000002" + }, + "description": "Blueprint UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/b1de0000-0000-4000-8000-000000000002/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Blueprint detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update blueprint", + "description": "Partially update a blueprint's general settings or content_config.", + "operationId": "update_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "blueprint_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "b1de0000-0000-4000-8000-000000000002" + }, + "description": "Blueprint UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 24, + "description": "Max 24 characters, e.g. \"AML investigation\"." + }, + "description": { + "type": "string", + "nullable": true + }, + "assignee_pool": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1, + "description": "List of User UUIDs. Empty arrays are rejected with \"Pick at least one assignee\"." + }, + "assignment_method": { + "type": "string", + "enum": [ + "MANUAL", + "AUTOMATIC" + ] + }, + "max_active_cases": { + "type": "integer", + "nullable": true, + "minimum": 1, + "description": "Automatic-mode threshold: \"fewer than N cases of this blueprint assigned\"." + }, + "escalation_enabled": { + "type": "boolean" + }, + "escalation_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Target blueprint UUID. Not enforced at save time, but the escalate action fails with 400 until both escalation_enabled and escalation_blueprint are set." + }, + "transfer_enabled": { + "type": "boolean" + }, + "four_eyes_checker_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Setting this enables 4-eyes review: resolutions on this blueprint stage on the checker blueprint's queue." + }, + "require_resolution_note": { + "type": "boolean" + }, + "content_config": { + "$ref": "#/components/schemas/CaseContentConfig" + }, + "is_active": { + "type": "boolean" + }, + "default_deadline_hours": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 8760, + "description": "Default deadline for newly opened cases. Existing case deadlines are unchanged." + }, + "expected_version": { + "type": "integer", + "minimum": 1, + "description": "The version opened by the editor. A stale version returns 409 without changing the review policy." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/b1de0000-0000-4000-8000-000000000002/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"transfer_enabled\": true}'" + } + ], + "responses": { + "200": { + "description": "Updated blueprint.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintDetail" + } + } + } + }, + "409": { + "description": "The review policy changed since expected_version. Review the latest version before retrying." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete blueprint", + "description": "Delete a blueprint. Deletion always succeeds and does not touch the blueprint's cases: existing cases keep their blueprint reference and its settings continue to apply to them. A deleted blueprint no longer appears in blueprint lists and can no longer be chosen as a transfer target; if a maker blueprint is deleted while a resolution is pending 4-eyes approval, a rejected resolution leaves the case on the checker blueprint.", + "operationId": "delete_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "blueprint_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "b1de0000-0000-4000-8000-000000000002" + }, + "description": "Blueprint UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/b1de0000-0000-4000-8000-000000000002/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "204": { + "description": "Blueprint deleted." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/presets/": { + "get": { + "summary": "List blueprint presets", + "description": "List the four built-in presets (Screening hit, AML investigation, Suspicious activity, KYC review) with their content_config and whether each is already installed on this application.", + "operationId": "list_case_blueprint_presets", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "list_presets", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/presets/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Preset catalogue.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "preset_key": { + "type": "string", + "enum": [ + "screening_hit", + "aml_investigation", + "suspicious_activity", + "kyc" + ] + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "content_config": { + "$ref": "#/components/schemas/CaseContentConfig" + }, + "installed": { + "type": "boolean" + } + } + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/presets/install/": { + "post": { + "summary": "Install blueprint presets", + "description": "Copy one or more presets into the application as editable blueprints. Idempotent per preset_key; omit preset_keys to install every preset not yet installed.", + "operationId": "install_case_blueprint_presets", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "install_presets", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "preset_keys": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Subset of screening_hit, aml_investigation, suspicious_activity, kyc. Omit to install all." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/presets/install/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"preset_keys\": [\"aml_investigation\", \"kyc\"]}'" + } + ], + "responses": { + "201": { + "description": "One or more presets installed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintPresetInstallResult" + } + } + } + }, + "200": { + "description": "No new presets to install (all requested presets already exist).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintPresetInstallResult" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/report-templates/": { + "get": { + "summary": "List report templates", + "description": "List FIU report templates for the application.", + "operationId": "list_report_templates", + "x-didit-sdk": { + "resource": "report_templates", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Paginated templates.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows, capped at 100 for performance." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ReportTemplateDetail" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create report template", + "description": "Create a reusable per-jurisdiction, per-report-type goAML header template. Rejected with 400 if any of the template-level obligatory fields (rentity_id, submission_code, currency_code_local) is missing from field_config; an invalid template cannot be saved or used to create a report. Per-filing header fields (action, submission_date, transactions) are enforced at report validate/finalize time instead.", + "operationId": "create_report_template", + "x-didit-sdk": { + "resource": "report_templates", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "jurisdiction", + "report_type" + ], + "properties": { + "name": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code, upper-cased on save." + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "field_config": { + "type": "object", + "description": "Reusable goAML header defaults. Template-level obligatory fields: rentity_id, submission_code, and currency_code_local (3-letter). Optional: enabled_fields (which optional report blocks to include) plus reporting_person, location, reason, and action defaults. Per-filing header fields (action, submission_date, transactions, jurisdiction) are enforced when the report is validated or finalized, not on the template." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"name\": \"US SAR default\", \"jurisdiction\": \"US\", \"report_type\": \"SAR\", \"field_config\": {\"rentity_id\": \"FIU-US-00123\", \"submission_code\": \"E\", \"currency_code_local\": \"USD\"}}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={\n 'name': 'US SAR default',\n 'jurisdiction': 'US',\n 'report_type': 'SAR',\n 'field_config': {'rentity_id': 'FIU-US-00123', 'submission_code': 'E', 'currency_code_local': 'USD'},\n },\n timeout=10,\n)\nresp.raise_for_status()\nprint(resp.json()['uuid'])" + } + ], + "responses": { + "201": { + "description": "Template created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReportTemplateDetail" + } + } + } + }, + "400": { + "description": "Unrecognized jurisdiction, or field_config fails obligatory-field validation.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/report-templates/{template_uuid}/": { + "get": { + "summary": "Get report template", + "description": "Retrieve a report template.", + "operationId": "get_report_template", + "x-didit-sdk": { + "resource": "report_templates", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "template_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "7ec00000-0000-4000-8000-000000000006" + }, + "description": "Report template UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/7ec00000-0000-4000-8000-000000000006/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Template detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReportTemplateDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update report template", + "description": "Partially update a report template. Re-validates obligatory goAML fields on save.", + "operationId": "update_report_template", + "x-didit-sdk": { + "resource": "report_templates", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "template_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "7ec00000-0000-4000-8000-000000000006" + }, + "description": "Report template UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code, upper-cased on save." + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "field_config": { + "type": "object", + "description": "Reusable goAML header defaults. Template-level obligatory fields: rentity_id, submission_code, and currency_code_local (3-letter). Optional: enabled_fields (which optional report blocks to include) plus reporting_person, location, reason, and action defaults. Per-filing header fields (action, submission_date, transactions, jurisdiction) are enforced when the report is validated or finalized, not on the template." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/7ec00000-0000-4000-8000-000000000006/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"field_config\": {\"rentity_id\": \"FIU-US-00124\"}}'" + } + ], + "responses": { + "200": { + "description": "Updated template.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReportTemplateDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete report template", + "description": "Delete a report template. Reports already created from it are unaffected.", + "operationId": "delete_report_template", + "x-didit-sdk": { + "resource": "report_templates", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "template_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "7ec00000-0000-4000-8000-000000000006" + }, + "description": "Report template UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/7ec00000-0000-4000-8000-000000000006/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "204": { + "description": "Template deleted." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/regulatory-reports/": { + "get": { + "summary": "List reports for case", + "description": "List FIU reports generated for the case.", + "operationId": "list_case_regulatory_reports", + "x-didit-sdk": { + "resource": "cases.regulatory_reports", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/regulatory-reports/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Paginated reports for the case.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportList" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create report from case", + "description": "Create a draft FIU report for the case: DRAFT status, auto-populated from the optional template plus the case's subject, linked transactions, and narrative. Validate then finalize to produce the goAML XML and PDF.", + "operationId": "create_case_regulatory_report", + "x-didit-sdk": { + "resource": "cases.regulatory_reports", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ], + "description": "Required unless a template is given; the template's report_type is used otherwise." + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "Required unless a template is given." + }, + "template": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Report template UUID; its field_config seeds the draft before case-derived data is merged in." + }, + "transactions": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "nullable": true, + "description": "Transactions to attach; defaults to the case's linked transactions." + }, + "rentity_id": { + "type": "string", + "nullable": true + }, + "rentity_branch": { + "type": "string", + "nullable": true + }, + "submission_code": { + "type": "string", + "enum": [ + "E", + "M" + ], + "default": "E" + }, + "entity_reference": { + "type": "string", + "nullable": true + }, + "fiu_ref_number": { + "type": "string", + "nullable": true + }, + "currency_code_local": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "nullable": true + }, + "submission_date": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reporting_person": { + "type": "object" + }, + "location": { + "type": "object" + }, + "reason": { + "type": "string", + "nullable": true + }, + "action": { + "type": "string", + "nullable": true + }, + "activity": { + "type": "string", + "nullable": true + }, + "narrative": { + "type": "object", + "properties": { + "who": { + "type": "string" + }, + "what": { + "type": "string" + }, + "when": { + "type": "string" + }, + "where": { + "type": "string" + }, + "why": { + "type": "string" + }, + "how": { + "type": "string" + } + } + }, + "report_indicators": { + "type": "array", + "items": { + "type": "string" + } + }, + "included_fields": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/regulatory-reports/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"template\": \"7ec00000-0000-4000-8000-000000000006\"}'" + } + ], + "responses": { + "201": { + "description": "Draft report created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "400": { + "description": "The referenced template was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/": { + "get": { + "summary": "List reports", + "description": "Application-wide FIU report list, optionally filtered by status, report_type, or case.", + "operationId": "list_regulatory_reports", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + }, + { + "name": "status", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "DRAFT", + "VALIDATED", + "FINALIZED" + ] + }, + "description": "Filter by status." + }, + { + "name": "report_type", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "description": "Filter by report type." + }, + { + "name": "case", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter to reports on this case." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Paginated reports across the application.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportList" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create standalone report", + "description": "Create a draft FIU report from the standalone Reports page: both case and template are required (unlike the case-scoped create, which can omit template).", + "operationId": "create_standalone_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "case", + "template" + ], + "properties": { + "case": { + "type": "string", + "format": "uuid" + }, + "template": { + "type": "string", + "format": "uuid" + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ], + "description": "Required unless a template is given; the template's report_type is used otherwise." + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "Required unless a template is given." + }, + "transactions": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "nullable": true, + "description": "Transactions to attach; defaults to the case's linked transactions." + }, + "rentity_id": { + "type": "string", + "nullable": true + }, + "rentity_branch": { + "type": "string", + "nullable": true + }, + "submission_code": { + "type": "string", + "enum": [ + "E", + "M" + ], + "default": "E" + }, + "entity_reference": { + "type": "string", + "nullable": true + }, + "fiu_ref_number": { + "type": "string", + "nullable": true + }, + "currency_code_local": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "nullable": true + }, + "submission_date": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reporting_person": { + "type": "object" + }, + "location": { + "type": "object" + }, + "reason": { + "type": "string", + "nullable": true + }, + "action": { + "type": "string", + "nullable": true + }, + "activity": { + "type": "string", + "nullable": true + }, + "narrative": { + "type": "object", + "properties": { + "who": { + "type": "string" + }, + "what": { + "type": "string" + }, + "when": { + "type": "string" + }, + "where": { + "type": "string" + }, + "why": { + "type": "string" + }, + "how": { + "type": "string" + } + } + }, + "report_indicators": { + "type": "array", + "items": { + "type": "string" + } + }, + "included_fields": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"case\": \"c1a5e000-0000-4000-8000-000000000001\", \"template\": \"7ec00000-0000-4000-8000-000000000006\"}'" + } + ], + "responses": { + "201": { + "description": "Draft report created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "400": { + "description": "The referenced template was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/{report_uuid}/": { + "get": { + "summary": "Get report", + "description": "Retrieve a regulatory report.", + "operationId": "get_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Report detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update report", + "description": "Edit a draft or validated report's fields. Finalized reports cannot be edited (409).", + "operationId": "update_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2 + }, + "rentity_id": { + "type": "string", + "nullable": true + }, + "rentity_branch": { + "type": "string", + "nullable": true + }, + "submission_code": { + "type": "string", + "enum": [ + "E", + "M" + ], + "default": "E" + }, + "entity_reference": { + "type": "string", + "nullable": true + }, + "fiu_ref_number": { + "type": "string", + "nullable": true + }, + "currency_code_local": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "nullable": true + }, + "submission_date": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reporting_person": { + "type": "object" + }, + "location": { + "type": "object" + }, + "reason": { + "type": "string", + "nullable": true + }, + "action": { + "type": "string", + "nullable": true + }, + "activity": { + "type": "string", + "nullable": true + }, + "narrative": { + "type": "object", + "properties": { + "who": { + "type": "string" + }, + "what": { + "type": "string" + }, + "when": { + "type": "string" + }, + "where": { + "type": "string" + }, + "why": { + "type": "string" + }, + "how": { + "type": "string" + } + } + }, + "report_indicators": { + "type": "array", + "items": { + "type": "string" + } + }, + "included_fields": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"rentity_id\": \"FIU-US-00123\", \"currency_code_local\": \"USD\"}'" + } + ], + "responses": { + "200": { + "description": "Updated report.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "409": { + "description": "Report is already finalized.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete report", + "description": "Delete a regulatory report permanently. The report, its copied narrative and reasons, its transaction links, and its generated goAML XML and PDF are erased, including every stored version of those files. The report disappears from lists and download URLs stop working. Other records, including the case, transactions, people and companies, are not deleted. There is no undo.\n\nA draft or validated report can be deleted without a request body. Deleting a finalized report requires `confirm_delete: true` in the request body. If the report is missing, the request returns `404`; if a finalized report is sent without confirmation, it returns `400` and nothing is deleted.", + "operationId": "delete_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl (draft report)", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "curl", + "label": "curl (finalized report)", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"confirm_delete\": true}'" + } + ], + "responses": { + "204": { + "description": "Report deleted, including its generated files." + }, + "400": { + "description": "A finalized report was sent without `confirm_delete: true`. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "confirm_delete": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "example": { + "confirm_delete": [ + "This field is required to delete a finalized report." + ] + } + } + } + }, + "403": { + "description": "The API key cannot delete reports. The endpoint requires `write:cases`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Report not found in this application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "409": { + "description": "The report is already being deleted. Repeat the request to finish erasing its files.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "This report is being deleted. Retry deleting it to finish erasing its files.", + "code": "report_deletion_pending" + } + } + } + }, + "503": { + "description": "The report files could not be erased right now. The report is still in place. Repeat the request.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "The report files could not be erased. Please retry deleting the report.", + "code": "report_storage_unavailable" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": false, + "description": "Required only for a finalized report.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "confirm_delete": { + "type": "boolean", + "description": "Set to true to confirm that a finalized report and its generated files are deleted permanently." + } + }, + "example": { + "confirm_delete": true + } + } + } + } + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/{report_uuid}/validate/": { + "post": { + "summary": "Validate report", + "description": "Check the report against the obligatory goAML header fields (reporting entity ID, submission code, local currency, action taken, submission date), plus at least one attached transaction and a 2-letter jurisdiction, without generating any files. Also stores the result on the report: validation_errors and validated_at are updated, and a non-finalized report's status moves to VALIDATED (no errors) or back to DRAFT (errors).", + "operationId": "validate_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "validate", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/validate/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Validation result.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "DRAFT", + "VALIDATED", + "FINALIZED" + ] + }, + "is_valid": { + "type": "boolean" + }, + "errors": { + "type": "array", + "description": "Empty when valid.", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/{report_uuid}/finalize/": { + "post": { + "summary": "Finalize report", + "description": "Re-validate, then render the goAML XML and PDF and upload both to S3. On SAR/STR/TFR reports, this also moves the case's transaction alerts from Pending SAR to SAR filed.", + "operationId": "finalize_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "finalize", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/finalize/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/finalize/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=30,\n)\nif resp.status_code == 400:\n print('Validation failed:', resp.json()['errors'])\nelse:\n resp.raise_for_status()\n report = resp.json()\n print(report['status'], report['report_number'])" + } + ], + "responses": { + "200": { + "description": "Report finalized; xml_s3_key and pdf_s3_key are set.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "400": { + "description": "Report failed validation.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "409": { + "description": "Report is not in a finalizable state.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/{report_uuid}/download-url/": { + "get": { + "summary": "Get report download URL", + "description": "Mint a presigned URL for a finalized report's goAML XML or PDF.", + "operationId": "get_regulatory_report_download_url", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "retrieve_download_url", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + }, + { + "name": "file", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "pdf", + "xml" + ], + "default": "pdf" + }, + "description": "Which file to download." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/download-url/?file=xml' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Presigned download URL.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "download_url": { + "type": "string", + "format": "uri" + }, + "file": { + "type": "string", + "enum": [ + "pdf", + "xml" + ] + } + } + } + } + } + }, + "409": { + "description": "Report is not finalized yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "status": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/transactions/rules/": { + "get": { + "summary": "List rules", + "description": "List the application's transaction-monitoring rules: your custom rules plus any library presets you have installed. Filter by source, category, mode, bundle or free-text search; order with `ordering` (run_count, approved_pct, reviewed_pct, declined_pct, latest_triggered_at, title, created_at, source, each with an optional `-` prefix).", + "operationId": "listTransactionRules", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "source", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "PRESET", + "CUSTOM" + ] + }, + "description": "PRESET or CUSTOM" + }, + { + "name": "category", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Filter by rule category" + }, + { + "name": "mode", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ] + }, + "description": "ACTIVE, DISABLED or TEST" + }, + { + "name": "bundle", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Filter presets by bundle" + }, + { + "name": "search", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Case-insensitive match against title and description" + }, + { + "name": "ordering", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated ordering fields, default source,-run_count" + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "description": "Page size" + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "description": "Page offset" + } + ], + "responses": { + "200": { + "description": "Paginated rules.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleDetail" + } + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/rules/?source=CUSTOM&ordering=-run_count' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ] + }, + "post": { + "summary": "Create rule", + "description": "Create a CUSTOM transaction-monitoring rule. Titles are unique per application (duplicate -> 400). A `change_status` action targeting `AWAITING_USER` must carry `workflow_id`. Rules created in ACTIVE mode apply to new transactions immediately - the recommended workflow is create in TEST mode, backtest, then switch mode to ACTIVE.", + "operationId": "createTransactionRule", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleCreateRequest" + }, + "example": { + "title": "Structuring - inbound (custom)", + "description": "20+ inbound EUR transfers, all under 10,000 EUR, 30 days", + "category": "finance", + "mode": "TEST", + "evaluation_mode": "ALL", + "scope": { + "transaction_types": [ + "finance" + ], + "directions": [ + "INBOUND" + ] + }, + "conditions": [ + { + "field": "currency", + "operator": "eq", + "value": "EUR" + }, + { + "field": "amount", + "operator": "lt", + "value": 10000 + } + ], + "aggregation": [ + { + "metric": "count", + "operator": "gte", + "value": 20, + "window": "30d", + "filters": { + "direction": "INBOUND", + "transaction_type": "finance", + "subject_vendor_data": "__current__", + "currency": "EUR" + } + }, + { + "metric": "max", + "field": "amount", + "operator": "lt", + "value": 10000, + "window": "30d", + "filters": { + "direction": "INBOUND", + "transaction_type": "finance", + "subject_vendor_data": "__current__", + "currency": "EUR" + } + } + ], + "actions": [ + { + "type": "add_score", + "value": 35 + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "The created rule.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleDetail" + } + } + } + }, + "400": { + "description": "Validation error - duplicate title, unknown operator/metric, malformed window, invalid scope direction, or AWAITING_USER without workflow_id.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "title": [ + "A rule with this name already exists." + ] + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/rules/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"title\": \"Structuring - inbound (custom)\", \"description\": \"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\", \"category\": \"finance\", \"mode\": \"TEST\", \"evaluation_mode\": \"ALL\", \"scope\": {\"transaction_types\": [\"finance\"], \"directions\": [\"INBOUND\"]}, \"conditions\": [{\"field\": \"currency\", \"operator\": \"eq\", \"value\": \"EUR\"}, {\"field\": \"amount\", \"operator\": \"lt\", \"value\": 10000}], \"aggregation\": [{\"metric\": \"count\", \"operator\": \"gte\", \"value\": 20, \"window\": \"30d\", \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}}, {\"metric\": \"max\", \"field\": \"amount\", \"operator\": \"lt\", \"value\": 10000, \"window\": \"30d\", \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}}], \"actions\": [{\"type\": \"add_score\", \"value\": 35}]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nrule = {\n \"title\": \"Structuring - inbound (custom)\",\n \"description\": \"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\",\n \"category\": \"finance\",\n \"mode\": \"TEST\",\n \"evaluation_mode\": \"ALL\",\n \"scope\": {\n \"transaction_types\": [\n \"finance\"\n ],\n \"directions\": [\n \"INBOUND\"\n ]\n },\n \"conditions\": [\n {\n \"field\": \"currency\",\n \"operator\": \"eq\",\n \"value\": \"EUR\"\n },\n {\n \"field\": \"amount\",\n \"operator\": \"lt\",\n \"value\": 10000\n }\n ],\n \"aggregation\": [\n {\n \"metric\": \"count\",\n \"operator\": \"gte\",\n \"value\": 20,\n \"window\": \"30d\",\n \"filters\": {\n \"direction\": \"INBOUND\",\n \"transaction_type\": \"finance\",\n \"subject_vendor_data\": \"__current__\",\n \"currency\": \"EUR\"\n }\n },\n {\n \"metric\": \"max\",\n \"field\": \"amount\",\n \"operator\": \"lt\",\n \"value\": 10000,\n \"window\": \"30d\",\n \"filters\": {\n \"direction\": \"INBOUND\",\n \"transaction_type\": \"finance\",\n \"subject_vendor_data\": \"__current__\",\n \"currency\": \"EUR\"\n }\n }\n ],\n \"actions\": [\n {\n \"type\": \"add_score\",\n \"value\": 35\n }\n ]\n}\n\nresp = requests.post(\n 'https://verification.didit.me/v3/transactions/rules/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json=rule,\n timeout=15,\n)\nresp.raise_for_status()\nprint(resp.json()['uuid'], resp.json()['mode'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const rule = {\n \"title\": \"Structuring - inbound (custom)\",\n \"description\": \"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\",\n \"category\": \"finance\",\n \"mode\": \"TEST\",\n \"evaluation_mode\": \"ALL\",\n \"scope\": {\n \"transaction_types\": [\n \"finance\"\n ],\n \"directions\": [\n \"INBOUND\"\n ]\n },\n \"conditions\": [\n {\n \"field\": \"currency\",\n \"operator\": \"eq\",\n \"value\": \"EUR\"\n },\n {\n \"field\": \"amount\",\n \"operator\": \"lt\",\n \"value\": 10000\n }\n ],\n \"aggregation\": [\n {\n \"metric\": \"count\",\n \"operator\": \"gte\",\n \"value\": 20,\n \"window\": \"30d\",\n \"filters\": {\n \"direction\": \"INBOUND\",\n \"transaction_type\": \"finance\",\n \"subject_vendor_data\": \"__current__\",\n \"currency\": \"EUR\"\n }\n },\n {\n \"metric\": \"max\",\n \"field\": \"amount\",\n \"operator\": \"lt\",\n \"value\": 10000,\n \"window\": \"30d\",\n \"filters\": {\n \"direction\": \"INBOUND\",\n \"transaction_type\": \"finance\",\n \"subject_vendor_data\": \"__current__\",\n \"currency\": \"EUR\"\n }\n }\n ],\n \"actions\": [\n {\n \"type\": \"add_score\",\n \"value\": 35\n }\n ]\n};\n\nconst resp = await fetch('https://verification.didit.me/v3/transactions/rules/', {\n method: 'POST',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(rule),\n});\nif (!resp.ok) throw new Error(await resp.text());\nconst created = await resp.json();\nconsole.log(created.uuid, created.mode);" + } + ] + } + }, + "/v3/transactions/rules/{rule_uuid}/": { + "parameters": [ + { + "name": "rule_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The rule's uuid from list or create." + } + ], + "get": { + "summary": "Get rule", + "description": "Retrieve one rule with its full configuration and execution counters.", + "operationId": "getTransactionRule", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "The rule.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleDetail" + } + } + } + }, + "404": { + "description": "No such rule on this application.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/rules/RULE_UUID/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ] + }, + "patch": { + "summary": "Update rule", + "description": "Partially update a rule. Custom rules accept every configuration field. Library (PRESET) rules accept only `mode` - use it to activate, disable, or switch a preset to TEST; any other field returns 400. An installed copy of a withheld library rule is switched off: setting its `mode` to anything but `DISABLED` answers 400 (\"This library rule cannot run yet: its conditions read data the rules engine cannot evaluate.\").", + "operationId": "updateTransactionRule", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleUpdateRequest" + }, + "example": { + "mode": "ACTIVE" + } + } + } + }, + "responses": { + "200": { + "description": "The updated rule.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleDetail" + } + } + } + }, + "400": { + "description": "Validation error, a non-mode field on a library rule, or a mode other than DISABLED on a withheld library rule.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/transactions/rules/RULE_UUID/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"mode\": \"ACTIVE\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.patch(\n 'https://verification.didit.me/v3/transactions/rules/RULE_UUID/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={'mode': 'ACTIVE'},\n timeout=15,\n)\nresp.raise_for_status()\nprint(resp.json()['mode'])" + } + ] + }, + "delete": { + "summary": "Delete rule", + "description": "Delete a CUSTOM rule. Deletion is immediate and permanent: the rule stops evaluating for new transactions and disappears from listings, while past rule runs on already-evaluated transactions keep their recorded results. Library rules cannot be deleted - uninstall them via the install endpoint instead (they can always be reinstalled from the library).", + "operationId": "deleteTransactionRule", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "204": { + "description": "Deleted." + }, + "400": { + "description": "The rule is a library preset - uninstall instead.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "detail": "Library rules cannot be deleted. Uninstall them via the rules install endpoint instead." + } + } + } + }, + "404": { + "description": "No such rule on this application.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/transactions/rules/RULE_UUID/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ] + } + }, + "/v3/transactions/rules/backtest/": { + "post": { + "summary": "Backtest rule configuration", + "description": "Evaluate a hypothetical rule configuration against your recent transactions (most recent first, up to the backtest cap) without creating a rule or touching any transaction. Use it before activating a rule to see how often it would have matched and how many subjects it would have affected.", + "operationId": "backtestTransactionRules", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "backtest", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleBacktestRequest" + }, + "example": { + "conditions": [ + { + "field": "currency", + "operator": "eq", + "value": "EUR" + }, + { + "field": "amount", + "operator": "lt", + "value": 10000 + } + ], + "aggregation": [ + { + "metric": "count", + "operator": "gte", + "value": 20, + "window": "30d", + "filters": { + "direction": "INBOUND", + "transaction_type": "finance", + "subject_vendor_data": "__current__", + "currency": "EUR" + } + }, + { + "metric": "max", + "field": "amount", + "operator": "lt", + "value": 10000, + "window": "30d", + "filters": { + "direction": "INBOUND", + "transaction_type": "finance", + "subject_vendor_data": "__current__", + "currency": "EUR" + } + } + ], + "evaluation_mode": "ALL", + "scope": { + "transaction_types": [ + "finance" + ], + "directions": [ + "INBOUND" + ] + }, + "period_days": 90 + } + } + } + }, + "responses": { + "200": { + "description": "Backtest statistics.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleBacktestResult" + }, + "example": { + "evaluated": 4820, + "matched": 12, + "affected_entities": 4, + "period_days": 90 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/rules/backtest/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"conditions\": [{\"field\": \"currency\", \"operator\": \"eq\", \"value\": \"EUR\"}, {\"field\": \"amount\", \"operator\": \"lt\", \"value\": 10000}], \"aggregation\": [{\"metric\": \"count\", \"operator\": \"gte\", \"value\": 20, \"window\": \"30d\", \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}}, {\"metric\": \"max\", \"field\": \"amount\", \"operator\": \"lt\", \"value\": 10000, \"window\": \"30d\", \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}}], \"evaluation_mode\": \"ALL\", \"scope\": {\"transaction_types\": [\"finance\"], \"directions\": [\"INBOUND\"]}, \"period_days\": 90}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/transactions/rules/backtest/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={\n \"conditions\": [\n {\n \"field\": \"currency\",\n \"operator\": \"eq\",\n \"value\": \"EUR\"\n },\n {\n \"field\": \"amount\",\n \"operator\": \"lt\",\n \"value\": 10000\n }\n ],\n \"aggregation\": [\n {\n \"metric\": \"count\",\n \"operator\": \"gte\",\n \"value\": 20,\n \"window\": \"30d\",\n \"filters\": {\n \"direction\": \"INBOUND\",\n \"transaction_type\": \"finance\",\n \"subject_vendor_data\": \"__current__\",\n \"currency\": \"EUR\"\n }\n },\n {\n \"metric\": \"max\",\n \"field\": \"amount\",\n \"operator\": \"lt\",\n \"value\": 10000,\n \"window\": \"30d\",\n \"filters\": {\n \"direction\": \"INBOUND\",\n \"transaction_type\": \"finance\",\n \"subject_vendor_data\": \"__current__\",\n \"currency\": \"EUR\"\n }\n }\n ],\n \"evaluation_mode\": \"ALL\",\n \"scope\": {\n \"transaction_types\": [\n \"finance\"\n ],\n \"directions\": [\n \"INBOUND\"\n ]\n },\n \"period_days\": 90\n},\n timeout=30,\n)\nresp.raise_for_status()\nstats = resp.json()\nprint(stats['matched'], 'matches across', stats['affected_entities'], 'subjects')" + } + ] + } + }, + "/v3/transactions/rules/library/": { + "get": { + "summary": "List rule library", + "description": "Browse the full preset rule library (150+ rules) with each entry's complete configuration and an `is_installed` flag for this application. Filter by bundle, category, or free-text search. A library rule whose conditions read data the rules engine cannot evaluate yet is withheld until it can run. Withheld rules are not listed.", + "operationId": "listTransactionRuleLibrary", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "list_library", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "bundle", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Filter by bundle, e.g. finance, aml_ctf, crypto_monitoring, travel_rule" + }, + { + "name": "category", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Filter by category" + }, + { + "name": "search", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Case-insensitive match against title and description" + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "description": "Page size" + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "description": "Page offset" + } + ], + "responses": { + "200": { + "description": "Paginated library entries.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleLibraryEntry" + } + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/rules/library/?bundle=finance' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ] + } + }, + "/v3/transactions/rules/install/": { + "post": { + "summary": "Install library rules", + "description": "Install one or more presets onto the application, by `library_keys` or a whole `bundle`. An installed preset is active at once while transaction monitoring is on, and disabled otherwise; already-installed keys are skipped. A library rule whose conditions read data the rules engine cannot evaluate yet is withheld until it can run. A request that names one in `library_keys` answers 400 and installs nothing (\"These library rules cannot run yet: their conditions read data the rules engine cannot evaluate.\" followed by the keys), and a `bundle` install skips it. A withheld rule comes back on its own once its conditions can be evaluated. A `library_keys` entry that is not in the library is skipped, not refused.", + "operationId": "installTransactionRuleLibrary", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "install_library", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleInstallRequest" + }, + "example": { + "library_keys": [ + "structuring-inbound", + "structuring-outbound" + ] + } + } + } + }, + "responses": { + "200": { + "description": "What was installed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleInstallResult" + }, + "example": { + "installed_library_keys": [ + "structuring-inbound", + "structuring-outbound" + ], + "installed_count": 2 + } + } + } + }, + "400": { + "description": "Neither library_keys nor bundle provided, `library_keys` is not a list, or a `library_keys` entry is withheld. An unknown entry is skipped and answers 200 without it.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/rules/install/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"library_keys\": [\"structuring-inbound\", \"structuring-outbound\"]}'" + } + ] + }, + "delete": { + "summary": "Uninstall library rules", + "description": "Remove installed presets from the application, by `library_keys` or a whole `bundle`. Uninstalled presets stop evaluating immediately and can be reinstalled from the library at any time.", + "operationId": "uninstallTransactionRuleLibrary", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "uninstall_library", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleInstallRequest" + }, + "example": { + "bundle": "responsible_gaming" + } + } + } + }, + "responses": { + "200": { + "description": "How many presets were removed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleInstallResult" + }, + "example": { + "uninstalled_count": 5 + } + } + } + }, + "400": { + "description": "Neither library_keys nor bundle provided.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/transactions/rules/install/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"bundle\": \"responsible_gaming\"}'" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/": { + "get": { + "summary": "List networks", + "description": "List fraud networks detected within this application's organization, highest risk first by default. By default dismissed networks are excluded; pass status to include them. Sandbox applications never have networks: clustering excludes sandbox data entirely. Every response carries the organization's `sharing_mode` and whether cross-organization signals apply to it (`cross_org_available`, `cross_org_unavailable_reason`). `kpis` aggregate the networks that match your filters and `counts` aggregate every network the application can see; pass `summary=false` to skip both when you only need the page. Requires a full-access API key, or `list:networks` for a console member.", + "operationId": "list_networks", + "x-didit-sdk": { + "resource": "networks", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "status", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated statuses to include: active,in_review,resolved,dismissed. Omit to exclude dismissed." + }, + { + "name": "scope", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "identity", + "transactions" + ] + }, + "description": "`transactions` keeps networks whose members are all transaction parties; `identity` keeps every other network. Omit for both. It also narrows `counts.views` and `counts.facets`. Any other value returns 400." + }, + { + "name": "signal_type", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated signal types: ip_address,device,face,document_number,phone,email,address." + }, + { + "name": "pattern_type", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated pattern types, e.g. exact_same_device,same_email." + }, + { + "name": "risk_band", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated bands: low,medium,high." + }, + { + "name": "min_size", + "in": "query", + "schema": { + "type": "integer" + }, + "description": "Minimum member count." + }, + { + "name": "max_size", + "in": "query", + "schema": { + "type": "integer" + }, + "description": "Maximum member count." + }, + { + "name": "assignee", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated assignees: `unassigned`, member emails (matched case-insensitively), or `me` for the calling console member. A network matches any value. `me` needs a console member token: an API key gets 400 `{\"assignee\": [\"\\\"me\\\" needs a user token; an API key has no assignee identity.\"]}`." + }, + { + "name": "priority", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated priorities: `low`, `medium`, `high`, or `none` for networks without a priority. Any other value returns 400 `{\"priority\": \"Unknown priority: .\"}`." + }, + { + "name": "risk_min", + "in": "query", + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "description": "Minimum risk score, a whole number from 0 to 100. A value that is not a whole number returns 400 `{\"risk_min\": \"Use a whole number from 0 to 100.\"}`." + }, + { + "name": "has_case", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "`true` keeps the networks your application opened a case for, `false` the others. Any other value is ignored." + }, + { + "name": "date_from", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Lower bound on last_activity_at (ISO 8601 date or datetime)." + }, + { + "name": "date_to", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Upper bound on first_activity_at (ISO 8601 date or datetime)." + }, + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Free-text search across network name, member names, the network_id (N-0001), and exact pattern_type tokens." + }, + { + "name": "ordering", + "in": "query", + "schema": { + "type": "string" + }, + "description": "One of risk_score, -risk_score, last_activity_at, -last_activity_at, first_activity_at, -first_activity_at, network_number, -network_number. Defaults to -risk_score,-last_activity_at." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 100, + "minimum": 1, + "maximum": 500 + }, + "description": "Page size, 1-500. Defaults to 100." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset of the first record." + }, + { + "name": "summary", + "in": "query", + "schema": { + "type": "boolean", + "default": true + }, + "description": "`false` returns the page and its `count` only, without `kpis` and `counts`. Defaults to `true`. Any value other than `true` or `false` returns 400." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/?status=active,in_review&ordering=-risk_score' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Paginated network list, with the organization's sharing mode and cross-organization availability, plus `kpis` and `counts` unless `summary=false`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "integer", + "nullable": true, + "description": "Offset of the next page, or null on the last page." + }, + "previous": { + "type": "integer", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkListItem" + } + }, + "kpis": { + "type": "object", + "description": "Each key is {value, delta} over the results matching the current filters. Omitted when `summary=false`.", + "properties": { + "active": { + "type": "object" + }, + "in_review": { + "type": "object" + }, + "applicants_involved": { + "type": "object" + }, + "detected_this_period": { + "type": "object" + }, + "false_positive_rate": { + "type": "object" + } + } + }, + "counts": { + "$ref": "#/components/schemas/NetworkListCounts" + }, + "sharing_mode": { + "type": "string", + "enum": [ + "off", + "private", + "global" + ], + "description": "The organization's cross-organization sharing mode. Only `global` compares networks across organizations; `private` and `off` keep them to the organization's own history.", + "example": "global" + }, + "cross_org_available": { + "type": "boolean", + "description": "Whether cross-organization signals apply to this organization. They need both the Enterprise plan and the `global` sharing mode.", + "example": true + }, + "cross_org_unavailable_reason": { + "type": "string", + "enum": [ + "plan", + "sharing_mode" + ], + "nullable": true, + "description": "Why cross-organization signals do not apply, checked in this order. `plan`: the organization's plan does not include the cross-organization fraud network (it needs the Enterprise plan), whatever the sharing mode. `sharing_mode`: the plan includes it, but the sharing mode is `private` or `off`. Null when `cross_org_available` is `true`.", + "example": null + } + }, + "required": [ + "count", + "next", + "previous", + "results", + "sharing_mode", + "cross_org_available", + "cross_org_unavailable_reason" + ] + } + } + } + }, + "400": { + "description": "An invalid filter, ordering, pagination or `summary` value.", + "content": { + "application/json": { + "examples": { + "Invalid summary": { + "summary": "Invalid summary", + "value": { + "summary": [ + "Must be \"true\" or \"false\"." + ] + } + }, + "Invalid scope": { + "summary": "Invalid scope", + "value": { + "scope": [ + "Use identity or transactions." + ] + } + }, + "Unsupported ordering": { + "summary": "Unsupported ordering", + "value": [ + "Unsupported ordering." + ] + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `list:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/": { + "get": { + "summary": "Get network", + "description": "Retrieve a single network's full detail: stats, fraud tags and risk signals, plus the organization's `sharing_mode` and cross-organization availability, your application's `tags` on the network, member `outcomes` and pattern scores (`patterns`). Requires a full-access API key, or `read:networks` for a console member.", + "operationId": "get_network", + "x-didit-sdk": { + "resource": "networks", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Network detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkDetailResponse" + }, + "example": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "network_id": "N-0042", + "name": null, + "status": "in_review", + "risk": { + "score": 82, + "band": "high", + "bar": 0.82 + }, + "pattern_types": [ + "exact_same_device", + "same_email" + ], + "signal_types": [ + "device", + "email" + ], + "member_counts": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signal_coverage": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + }, + "first_activity_at": "2026-10-01T09:12:44Z", + "last_activity_at": "2026-10-12T16:03:10Z", + "stats": { + "members": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signals": { + "total": 2, + "by_type": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + } + } + }, + "fraud_tags": [], + "risk_signals": { + "residential_proxy_reuse": { + "detected": false, + "signal_count": 0, + "max_subject_count": 0 + }, + "email_intelligence": { + "enriched_signal_count": 1, + "high_risk_count": 0, + "social_profile_count": 0 + }, + "phone_intelligence": { + "enriched_signal_count": 0, + "high_risk_count": 0 + } + }, + "reviewed_at": "2026-10-12T16:30:00Z", + "reviewed_by": "reviewer@example.com", + "review_reason": "Shared device across three new accounts.", + "notes_count": 0, + "sharing_mode": "global", + "cross_org_available": true, + "cross_org_unavailable_reason": null, + "tags": [ + { + "uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "name": "SUSPICIOUS", + "color": "#6366F1", + "source": "custom", + "added_by": null, + "added_at": "2026-10-13T08:15:00Z" + } + ], + "outcomes": { + "approved": 1, + "declined": 1, + "in_review": 0, + "total": 3 + }, + "patterns": [ + { + "type": "background", + "score": 0.8125, + "members": 3, + "reason": null, + "computed_at": "2026-10-13T08:00:00Z" + } + ] + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `read:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/graph/": { + "get": { + "summary": "Get network graph", + "description": "Retrieve the network as a graph of subject nodes (vendor users, vendor businesses, transaction parties) and signal nodes (the shared device/IP/face/document/phone/email/address values linking them), with edges representing memberships.", + "operationId": "get_network_graph", + "x-didit-sdk": { + "resource": "networks", + "method": "retrieve_graph", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + }, + { + "name": "depth", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "1", + "2", + "3", + "all" + ] + }, + "description": "Hops from the focus subject to include. Defaults to all (unbounded)." + }, + { + "name": "focus_kind", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "vendor_user", + "vendor_business", + "transaction_party" + ] + }, + "description": "Subject type to center the graph on. Must be supplied together with focus_id; defaults to an arbitrary member when depth is bounded and neither is supplied." + }, + { + "name": "focus_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Subject UUID to center the graph on." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/graph/?depth=2' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Network graph.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkGraph" + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/members/": { + "get": { + "summary": "List network members", + "description": "List the subjects (vendor users, vendor businesses, or transaction parties) that belong to this network.", + "operationId": "list_network_members", + "x-didit-sdk": { + "resource": "networks", + "method": "list_members", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/members/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Network members. Not paginated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkMember" + } + } + } + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/signals/": { + "get": { + "summary": "List network signals", + "description": "List the signals (shared device, IP, face, document number, phone, email, or address values) that link this network's members.", + "operationId": "list_network_signals", + "x-didit-sdk": { + "resource": "networks", + "method": "list_signals", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/signals/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Network signals. Not paginated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkSignal" + } + } + } + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/timeline/": { + "get": { + "summary": "Get network timeline", + "description": "Without `contract`: the network's own events (detection, each signal's latest observation and its review), oldest first and not paginated. `limit`, `cursor` and `event_type` are ignored. With `contract=2`: the typed timeline, newest first and paginated by cursor. It adds what the member accounts did (verification attempts, transactions, sign-ins you post through the events API and the live rules they hit) and the cases opened for the network. `note_added` events are returned to console members only, never to an API key. Requires a full-access API key, or `read:networks` for a console member.", + "operationId": "get_network_timeline", + "x-didit-sdk": { + "resource": "networks", + "method": "retrieve_timeline", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + }, + { + "name": "contract", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "2" + ] + }, + "description": "`2` returns the typed timeline. Omit it for the default timeline. Any other value returns 400." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 500, + "default": 100 + }, + "description": "Typed timeline only. Events per page, 1 to 500. Defaults to 100." + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Typed timeline only. The previous page's `next_cursor`. An invalid or empty cursor returns 400." + }, + { + "name": "event_type", + "in": "query", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "array", + "maxItems": 20, + "items": { + "type": "string", + "enum": [ + "network_detected", + "signal_observed", + "network_active", + "network_in_review", + "network_resolved", + "network_dismissed", + "verification_attempt", + "transaction", + "login", + "rule_hit", + "case_linked", + "note_added" + ] + } + }, + "description": "Typed timeline only. Event types to keep, repeated or comma-separated, at most 20 values. Defaults to every type." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/timeline/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "curl", + "label": "curl (typed timeline)", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/3fa85f64-5717-4562-b3fc-2c963f66afa6/timeline/?contract=2&limit=50&event_type=verification_attempt,login' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Without `contract`: `{results}`, oldest first, not paginated. With `contract=2`: `{results, next_cursor}`, newest first.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/NetworkTimeline" + }, + { + "$ref": "#/components/schemas/NetworkTimelinePage" + } + ] + }, + "examples": { + "Default timeline": { + "summary": "Without contract", + "value": { + "results": [ + { + "event_type": "network_detected", + "occurred_at": "2026-10-01T09:12:44Z", + "label": "Network detected", + "metadata": { + "network_id": "N-0042" + } + }, + { + "event_type": "signal_observed", + "occurred_at": "2026-10-12T16:03:10Z", + "label": "device observed", + "metadata": { + "signal_type": "device", + "subject_count": 3, + "is_hub": false + } + }, + { + "event_type": "network_in_review", + "occurred_at": "2026-10-12T16:30:00Z", + "label": "Network in_review", + "metadata": { + "reason": "Shared device across three new accounts." + } + } + ] + } + }, + "Typed timeline": { + "summary": "With contract=2", + "value": { + "results": [ + { + "id": "monitoring_event:9b2f1c3e-4d5a-4b6c-8d7e-0f1a2b3c4d5e", + "event_type": "login", + "occurred_at": "2026-10-12T18:41:02Z", + "ingested_at": "2026-10-12T18:41:03Z", + "label": "Login", + "metadata": {}, + "account": { + "node_id": "vendor_user:5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "node_type": "account", + "uuid": "5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "vendor_data": "user-1042", + "display_name": "Alex Example", + "ownership": "owned", + "route": { + "kind": "user", + "id": "5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "application_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + } + }, + "source_ref": { + "kind": "monitoring_event", + "id": "9b2f1c3e-4d5a-4b6c-8d7e-0f1a2b3c4d5e", + "ownership": "owned" + }, + "verification_attempt": null, + "transaction": null, + "login": { + "action": "auth.login", + "succeeded": true, + "status": "APPROVED", + "ip": { + "address": "203.0.113.24", + "country_code": "ES", + "city": "Madrid", + "connection_type": null, + "is_vpn_or_tor": false, + "is_data_center": false + }, + "device": { + "fingerprint": "fp_0a1b2c3d", + "label": "Chrome · Android" + }, + "is_first_login": false, + "is_new_device": true + }, + "rule_hit": null, + "case_linked": null, + "note_added": null + }, + { + "id": "session:1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "event_type": "verification_attempt", + "occurred_at": "2026-10-12T16:03:10Z", + "ingested_at": "2026-10-12T16:03:10Z", + "label": "Verification attempt 2 of 2", + "metadata": {}, + "account": { + "node_id": "vendor_user:5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "node_type": "account", + "uuid": "5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "vendor_data": "user-1042", + "display_name": "Alex Example", + "ownership": "owned", + "route": { + "kind": "user", + "id": "5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "application_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + } + }, + "source_ref": { + "kind": "session", + "id": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "ownership": "owned", + "route": { + "kind": "session", + "id": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "application_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + } + }, + "verification_attempt": { + "session_id": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "session_number": 1287, + "attempt": 2, + "attempt_count": 2, + "status": "Approved", + "status_changed_at": "2026-10-12T16:05:41Z", + "decline_reasons": [] + }, + "transaction": null, + "login": null, + "rule_hit": null, + "case_linked": null, + "note_added": null + }, + { + "id": "signal_observed:8d7c6b5a-4e3f-4a2b-9c1d-0e9f8a7b6c5d", + "event_type": "signal_observed", + "occurred_at": "2026-10-12T16:03:10Z", + "ingested_at": "2026-10-01T09:12:44Z", + "label": "device observed", + "metadata": { + "signal_type": "device", + "subject_count": 3, + "is_hub": false + }, + "account": null, + "source_ref": null, + "verification_attempt": null, + "transaction": null, + "login": null, + "rule_hit": null, + "case_linked": null, + "note_added": null + } + ], + "next_cursor": "eyJ0IjoiMjAyNi0xMC0xMlQxNjowMzoxMCswMDowMCIsImMiOiIyMDI2LTEwLTAxVDA5OjEyOjQ0KzAwOjAwIiwiciI6MSwidSI6IjhkN2M2YjVhLTRlM2YtNGEyYi05YzFkLTBlOWY4YTdiNmM1ZCJ9" + } + } + } + } + } + }, + "400": { + "description": "With `contract` present: an invalid `contract`, `limit`, `cursor` or `event_type` value.", + "content": { + "application/json": { + "examples": { + "Unsupported contract": { + "summary": "Unsupported contract", + "value": { + "contract": [ + "\"3\" is not a valid choice." + ] + } + }, + "Invalid cursor": { + "summary": "Invalid cursor", + "value": { + "cursor": [ + "This cursor is not valid. Reload the feed." + ] + } + }, + "Limit too large": { + "summary": "Limit too large", + "value": { + "limit": [ + "Ensure this value is less than or equal to 500." + ] + } + }, + "Unknown event type": { + "summary": "Unknown event type", + "value": { + "event_type": { + "0": [ + "\"logout\" is not a valid choice." + ] + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `read:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/map/": { + "get": { + "summary": "Get network map", + "description": "Geographic points where this network's members' addresses/locations converge, sourced from the sessions, business sessions, and transactions backing its memberships.", + "operationId": "get_network_map", + "x-didit-sdk": { + "resource": "networks", + "method": "retrieve_map", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/map/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Network map.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkMap" + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/sessions/{session_id}/networks/": { + "get": { + "summary": "List networks for a session", + "description": "List the networks the session's vendor user belongs to. Resolves through the session's vendor_data-linked user.", + "operationId": "list_session_network_memberships", + "x-didit-sdk": { + "resource": "sessions", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Session UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/sessions/aaaaaaaa-0000-4000-8000-000000000001/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/business-sessions/{business_session_id}/networks/": { + "get": { + "summary": "List networks for a business session", + "description": "List the networks the business session's vendor business belongs to.", + "operationId": "list_business_session_network_memberships", + "x-didit-sdk": { + "resource": "business_sessions", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "business_session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Business session UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/business-sessions/aaaaaaaa-0000-4000-8000-000000000002/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/users/{vendor_user_uuid}/networks/": { + "get": { + "summary": "List networks for a user", + "description": "List the networks a vendor user belongs to.", + "operationId": "list_user_network_memberships", + "x-didit-sdk": { + "resource": "users", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "vendor_user_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Vendor user UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/users/aaaaaaaa-0000-4000-8000-000000000003/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/businesses/{vendor_business_uuid}/networks/": { + "get": { + "summary": "List networks for a business", + "description": "List the networks a vendor business belongs to.", + "operationId": "list_business_network_memberships", + "x-didit-sdk": { + "resource": "businesses", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "vendor_business_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Vendor business UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/businesses/aaaaaaaa-0000-4000-8000-000000000004/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/transactions/{transaction_uuid}/networks/": { + "get": { + "summary": "List networks for a transaction", + "description": "List the networks the transaction's backing KYC/KYB subject belongs to (resolved through the transaction's session or business session).", + "operationId": "list_transaction_network_memberships", + "x-didit-sdk": { + "resource": "transactions", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "transaction_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Transaction UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/transactions/aaaaaaaa-0000-4000-8000-000000000005/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/transactions/{transaction_uuid}/parties/{transaction_party_uuid}/networks/": { + "get": { + "summary": "List networks for a transaction party", + "description": "List the networks a transaction party (remitter, beneficiary, or counterparty) belongs to, resolved through the party itself rather than through the transaction's backing session.", + "operationId": "list_transaction_party_network_memberships", + "x-didit-sdk": { + "resource": "transactions", + "method": "list_party_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "transaction_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Transaction UUID." + }, + { + "name": "transaction_party_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Transaction party UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/transactions/aaaaaaaa-0000-4000-8000-000000000005/parties/bbbbbbbb-0000-4000-8000-000000000006/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/biometric-templates/": { + "get": { + "summary": "List retained biometric templates", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nReturns a paginated list of the templates the application currently holds. Purged templates are not listed.", + "operationId": "list_biometric_templates", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "query", + "name": "vendor_data", + "required": false, + "description": "Case-insensitive substring match on the owning User's current `vendor_data`.", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "vendor_user_uuid", + "required": false, + "description": "Exact match on the owning User.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "query", + "name": "status", + "required": false, + "description": "Lifecycle status. Purged templates are never listed.", + "schema": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed" + ] + } + }, + { + "in": "query", + "name": "source_type", + "required": false, + "description": "How the template was created.", + "schema": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ] + } + }, + { + "in": "query", + "name": "retained_from", + "required": false, + "description": "Templates retained at or after this time.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "retained_to", + "required": false, + "description": "Templates retained at or before this time. Must not be earlier than `retained_from` (`400`).", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "ordering", + "required": false, + "description": "Sort order.", + "schema": { + "type": "string", + "enum": [ + "retained_at", + "-retained_at", + "expires_at", + "-expires_at" + ], + "default": "-retained_at" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "description": "Page size.", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "required": false, + "description": "Pagination offset.", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Paginated templates.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Template id. Randomly generated; not derived from the deleted session or its face." + }, + "vendor_user_uuid": { + "type": "string", + "format": "uuid", + "description": "The owning User." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "The User's current `vendor_data`, resolved when you read the template (never copied)." + }, + "source_type": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ], + "description": "How the template was created: a session deletion (API or Console), the application's automatic data-retention window, or a Didit-operated backfill you instructed." + }, + "provenance_reference": { + "type": "string", + "format": "uuid", + "description": "Opaque reference tying the template to its audit trail. It is not the deleted session's id and cannot be resolved to it." + }, + "retention_policy": { + "type": "string", + "enum": [ + "delete_with_session", + "retain_until_user_deleted" + ], + "description": "The application policy at retention time." + }, + "retention_override": { + "type": "boolean", + "nullable": true, + "description": "The `retain_face_embeddings` value sent on the deletion, or `null` when the policy applied." + }, + "retained_at": { + "type": "string", + "format": "date-time" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the template expires and is purged automatically. Always set." + }, + "retained_by": { + "type": "string", + "nullable": true, + "description": "Principal that gave the instruction: the application id for API keys, the Console member id for Console actions, or a `service:` identifier for automatic retention. Never an email address." + }, + "retained_by_type": { + "type": "string", + "enum": [ + "API_KEY", + "CONSOLE_USER", + "SYSTEM" + ] + }, + "instruction_class": { + "type": "string", + "enum": [ + "operational_session_delete", + "privacy_erasure" + ] + }, + "instruction_source": { + "type": "string", + "enum": [ + "api", + "console", + "automatic_retention", + "backfill", + "privacy_erasure", + "explicit_template_purge", + "vendor_user_delete", + "expiry", + "reconciliation", + "migration", + "system" + ] + }, + "instruction_id": { + "type": "string", + "description": "The `instruction_id` sent on the deletion, or a Didit-generated id." + }, + "held_out_of_use": { + "type": "boolean", + "description": "`true` only while a legal hold applies. A held template is excluded from all matching." + }, + "status": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed", + "purged" + ], + "description": "Only `active` templates that are not held, not past `expires_at`, and whose User still exists take part in matching." + }, + "purged_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + } + } + } + }, + "examples": { + "One active template": { + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "vendor_user_uuid": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f", + "vendor_data": "user-42", + "source_type": "session_delete", + "provenance_reference": "6b1e8d2f-4a3c-4f9e-8c7d-1a2b3c4d5e6f", + "retention_policy": "retain_until_user_deleted", + "retention_override": null, + "retained_at": "2026-08-28T10:15:42.318402Z", + "expires_at": "2027-08-28T10:15:42.318402Z", + "retained_by": "3c9e2b1f-7d4a-4e8b-9c1d-5f6a7b8c9d0e", + "retained_by_type": "API_KEY", + "instruction_class": "operational_session_delete", + "instruction_source": "api", + "instruction_id": "post-approval-cleanup-2026-08", + "held_out_of_use": false, + "status": "active", + "purged_at": null + } + ] + } + } + } + } + } + }, + "400": { + "description": "Invalid filter value, or `retained_to` earlier than `retained_from`. Field-keyed validation errors.", + "content": { + "application/json": { + "examples": { + "Bad range": { + "value": { + "retained_to": [ + "Must be on or after retained_from." + ] + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/biometric-templates/?vendor_data=user-42&status=active' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ] + } + }, + "/v3/biometric-templates/count/": { + "get": { + "summary": "Count retained biometric templates", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nReturns the number of templates matching the same filters as the list endpoint. Use it to check what a filtered bulk purge with `delete_all: true` would touch.", + "operationId": "count_biometric_templates", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "count", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "query", + "name": "vendor_data", + "required": false, + "description": "Case-insensitive substring match on the owning User's current `vendor_data`.", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "vendor_user_uuid", + "required": false, + "description": "Exact match on the owning User.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "query", + "name": "status", + "required": false, + "description": "Lifecycle status. Purged templates are never listed.", + "schema": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed" + ] + } + }, + { + "in": "query", + "name": "source_type", + "required": false, + "description": "How the template was created.", + "schema": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ] + } + }, + { + "in": "query", + "name": "retained_from", + "required": false, + "description": "Templates retained at or after this time.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "retained_to", + "required": false, + "description": "Templates retained at or before this time. Must not be earlier than `retained_from` (`400`).", + "schema": { + "type": "string", + "format": "date-time" + } + } + ], + "responses": { + "200": { + "description": "Count.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "minimum": 0 + } + } + }, + "examples": { + "Count": { + "value": { + "count": 128 + } + } + } + } + } + }, + "400": { + "description": "Invalid filter value, or `retained_to` earlier than `retained_from`. Field-keyed validation errors.", + "content": { + "application/json": { + "examples": { + "Bad range": { + "value": { + "retained_to": [ + "Must be on or after retained_from." + ] + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/biometric-templates/count/?status=active' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ] + } + }, + "/v3/biometric-templates/{template_uuid}/": { + "get": { + "summary": "Get a retained biometric template", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nReturns one template. The response never includes an image, a vector, the deleted session's id, or identity data.", + "operationId": "get_biometric_template", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "template_uuid", + "required": true, + "description": "Template id, as returned in `biometric_template_uuid` by the session deletion endpoints, `biometric_template_id` on a Face Search match, or the list endpoint.", + "schema": { + "type": "string", + "format": "uuid", + "example": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b" + } + } + ], + "responses": { + "200": { + "description": "The template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Template id. Randomly generated; not derived from the deleted session or its face." + }, + "vendor_user_uuid": { + "type": "string", + "format": "uuid", + "description": "The owning User." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "The User's current `vendor_data`, resolved when you read the template (never copied)." + }, + "source_type": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ], + "description": "How the template was created: a session deletion (API or Console), the application's automatic data-retention window, or a Didit-operated backfill you instructed." + }, + "provenance_reference": { + "type": "string", + "format": "uuid", + "description": "Opaque reference tying the template to its audit trail. It is not the deleted session's id and cannot be resolved to it." + }, + "retention_policy": { + "type": "string", + "enum": [ + "delete_with_session", + "retain_until_user_deleted" + ], + "description": "The application policy at retention time." + }, + "retention_override": { + "type": "boolean", + "nullable": true, + "description": "The `retain_face_embeddings` value sent on the deletion, or `null` when the policy applied." + }, + "retained_at": { + "type": "string", + "format": "date-time" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the template expires and is purged automatically. Always set." + }, + "retained_by": { + "type": "string", + "nullable": true, + "description": "Principal that gave the instruction: the application id for API keys, the Console member id for Console actions, or a `service:` identifier for automatic retention. Never an email address." + }, + "retained_by_type": { + "type": "string", + "enum": [ + "API_KEY", + "CONSOLE_USER", + "SYSTEM" + ] + }, + "instruction_class": { + "type": "string", + "enum": [ + "operational_session_delete", + "privacy_erasure" + ] + }, + "instruction_source": { + "type": "string", + "enum": [ + "api", + "console", + "automatic_retention", + "backfill", + "privacy_erasure", + "explicit_template_purge", + "vendor_user_delete", + "expiry", + "reconciliation", + "migration", + "system" + ] + }, + "instruction_id": { + "type": "string", + "description": "The `instruction_id` sent on the deletion, or a Didit-generated id." + }, + "held_out_of_use": { + "type": "boolean", + "description": "`true` only while a legal hold applies. A held template is excluded from all matching." + }, + "status": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed", + "purged" + ], + "description": "Only `active` templates that are not held, not past `expires_at`, and whose User still exists take part in matching." + }, + "purged_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "examples": { + "Active template": { + "value": { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "vendor_user_uuid": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f", + "vendor_data": "user-42", + "source_type": "session_delete", + "provenance_reference": "6b1e8d2f-4a3c-4f9e-8c7d-1a2b3c4d5e6f", + "retention_policy": "retain_until_user_deleted", + "retention_override": null, + "retained_at": "2026-08-28T10:15:42.318402Z", + "expires_at": "2027-08-28T10:15:42.318402Z", + "retained_by": "3c9e2b1f-7d4a-4e8b-9c1d-5f6a7b8c9d0e", + "retained_by_type": "API_KEY", + "instruction_class": "operational_session_delete", + "instruction_source": "api", + "instruction_id": "post-approval-cleanup-2026-08", + "held_out_of_use": false, + "status": "active", + "purged_at": null + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown template, a template of another application, or (on `GET`) a purged template.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Biometric template not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/biometric-templates/9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ] + }, + "delete": { + "summary": "Purge a retained biometric template", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nPurges one template: it is removed from every kind of matching and the stored template is deleted. Irreversible and recorded on the audit trail with the acting principal.\n\n**Retry semantics:** Didit deletes the stored template first and only then marks the template purged. If a biometric store is unavailable the call returns `503`, the template moves to `status: purge_failed` and is already excluded from matching; repeat the same call to finish. Repeating a purge of an already purged template returns `already_purged`.\n\nTemplates are also purged by `POST /v3/users/delete/` for the owning User, by any deletion of that User's sessions with `deletion_instruction: privacy_erasure`, and automatically at `expires_at`.", + "operationId": "purge_biometric_template", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "template_uuid", + "required": true, + "description": "Template id, as returned in `biometric_template_uuid` by the session deletion endpoints, `biometric_template_id` on a Face Search match, or the list endpoint.", + "schema": { + "type": "string", + "format": "uuid", + "example": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b" + } + } + ], + "responses": { + "200": { + "description": "Purge outcome.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "outcome": { + "type": "string", + "enum": [ + "purged", + "already_purged" + ] + } + } + }, + "examples": { + "Purged": { + "value": { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "outcome": "purged" + } + }, + "Already purged": { + "value": { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "outcome": "already_purged" + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown template, a template of another application, or (on `GET`) a purged template.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Biometric template not found." + } + } + } + } + } + }, + "503": { + "description": "A biometric store was unavailable while retaining or purging a biometric template. Nothing is deleted; repeat the same request. Retries are idempotent and converge on a single template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Retryable": { + "value": { + "detail": "The biometric-template operation could not finish because an external store is unavailable. The operation is retryable." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE https://verification.didit.me/v3/biometric-templates/9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ] + } + }, + "/v3/biometric-templates/delete/": { + "post": { + "summary": "Bulk purge retained biometric templates", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nPurges many templates in one call, by explicit id list or with `delete_all: true` optionally narrowed by the same filters as the list endpoint. Templates are purged one by one and each result reports `purged` or `already_purged`. If a biometric store is unavailable the call returns `503` at the first failing template: templates purged earlier in the request stay purged, the failing one moves to `purge_failed` (already excluded from matching), and repeating the request finishes the job.\n\n**`delete_all: true` without filters purges every biometric template in the application and cannot be undone.** Run the count endpoint with the same filters first.", + "operationId": "bulk_purge_biometric_templates", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "batch_delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "template_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1, + "description": "Templates to purge. Required unless `delete_all` is `true`. Already purged ids are reported as `already_purged`." + }, + "delete_all": { + "type": "boolean", + "default": false, + "description": "When `true`, purges every template matching the filters below (all of the application's templates when no filter is given). `template_uuids` is ignored. Irreversible." + }, + "vendor_data": { + "type": "string", + "description": "Only with `delete_all`: case-insensitive substring match on the owning User's `vendor_data`." + }, + "vendor_user_uuid": { + "type": "string", + "format": "uuid", + "description": "Only with `delete_all`: exact match on the owning User." + }, + "status": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed" + ], + "description": "Only with `delete_all`." + }, + "source_type": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ], + "description": "Only with `delete_all`." + }, + "retained_from": { + "type": "string", + "format": "date-time", + "description": "Only with `delete_all`." + }, + "retained_to": { + "type": "string", + "format": "date-time", + "description": "Only with `delete_all`. Must not be earlier than `retained_from`." + } + } + }, + "examples": { + "By id": { + "value": { + "template_uuids": [ + "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" + ] + } + }, + "Every template of one User": { + "value": { + "delete_all": true, + "vendor_user_uuid": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f" + } + }, + "Every template in the application": { + "value": { + "delete_all": true + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Per-template purge outcomes.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "outcome": { + "type": "string", + "enum": [ + "purged", + "already_purged" + ] + } + } + } + } + } + }, + "examples": { + "Mixed": { + "value": { + "results": [ + { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "outcome": "purged" + }, + { + "uuid": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", + "outcome": "already_purged" + } + ] + } + } + } + } + } + }, + "400": { + "description": "Validation error — nothing is purged.", + "content": { + "application/json": { + "examples": { + "Missing selector": { + "value": { + "template_uuids": [ + "This field is required unless delete_all is true." + ] + } + }, + "Bad range": { + "value": { + "retained_to": [ + "Must be on or after retained_from." + ] + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "503": { + "description": "A biometric store was unavailable while retaining or purging a biometric template. Nothing is deleted; repeat the same request. Retries are idempotent and converge on a single template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Retryable": { + "value": { + "detail": "The biometric-template operation could not finish because an external store is unavailable. The operation is retryable." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/biometric-templates/delete/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"delete_all\": true, \"vendor_user_uuid\": \"2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f\"}'" + } + ] + } + }, + "/v3/kyb/shareholders/": { + "post": { + "summary": "KYB registry company with shareholders (standalone)", + "operationId": "post_v3_kyb_shareholders", + "x-didit-sdk": { + "resource": "kyb", + "method": "list_shareholders", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Retrieve the Lite profile **plus the direct shareholders** (natural persons and corporate entities, with their ownership bands) of one candidate returned by `POST /v3/kyb/search/`.\n\n**Pairing with search.** Pass the candidate's `kyb_response_id` exactly as returned by a search of the same application. The search runs on the Lite registry chain; when the country's ownership data lives at another registry Didit re-finds the company there by registration number, at no extra cost.\n\n**Billing.** Billed at the country's Shareholders price - see [KYB registry pricing](/getting-started/kyb-registry-pricing). When the registry holds no shareholder record for the company (or the country offers no ownership data at all) the Lite profile is delivered and billed instead: read `registry_tier_delivered` and `registry_ownership_status`. The balance is checked against the Shareholders price before any registry call (`403` when short).\n\n**Availability.** v3 only. Every call creates a business session (its id is the `request_id`).\n\n**Pending reports.** When a report is still being prepared, or the ownership section has not been retrieved from the registry yet, the endpoint returns 202 Accepted, kyb_registry.fetch_status=pending, kyb_registry.registry_ownership_status=pending, and registry_tier_delivered=null. Keep request_id and retrieve GET /v3/session/{request_id}/decision/ until registry_checks[0].company.fetch_status is resolved. Didit completes the report automatically and charges the delivered tier once it is ready. Do not repeat the purchase while it is pending; every new POST creates a separate request.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBRegistryCandidateRequest" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/kyb/shareholders/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"kyb_response_id\": \"69aeeb95febb0f1704042259\",\n \"vendor_data\": \"company-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os, time, requests\n\nsearch = requests.post(\n \"https://verification.didit.me/v3/kyb/search/\",\n headers={\"x-api-key\": os.environ[\"DIDIT_API_KEY\"]},\n json={\"country_code\": \"GB\", \"name\": \"Tesco\"},\n timeout=60,\n).json()\nkyb_response_id = search[\"kyb_registry\"][\"companies\"][0][\"kyb_response_id\"]\n\nresp = requests.post(\n \"https://verification.didit.me/v3/kyb/shareholders/\",\n headers={\"x-api-key\": os.environ[\"DIDIT_API_KEY\"]},\n json={\"kyb_response_id\": kyb_response_id, \"vendor_data\": \"company-1234\"},\n timeout=90,\n)\nresp.raise_for_status()\ndata = resp.json()\nrequest_id = data[\"request_id\"]\nprint(\"Keep this request ID:\", request_id)\ncompany = data[\"kyb_registry\"]\nif resp.status_code == 202 or company.get(\"fetch_status\") == \"pending\":\n # This client wait limit does not cancel the report or create a new purchase.\n deadline = time.monotonic() + 300\n while company.get(\"fetch_status\") != \"resolved\" and time.monotonic() < deadline:\n time.sleep(5)\n decision = requests.get(\n f\"https://verification.didit.me/v3/session/{request_id}/decision/\",\n headers={\"x-api-key\": os.environ[\"DIDIT_API_KEY\"]},\n timeout=30,\n )\n decision.raise_for_status()\n checks = decision.json().get(\"registry_checks\") or []\n if checks:\n company = checks[0].get(\"company\") or company\nif company.get(\"fetch_status\") == \"resolved\":\n print(company[\"company_name\"], company[\"registry_tier_delivered\"])\nelse:\n print(\"Still processing; retrieve this request later:\", request_id)" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const res = await fetch('https://verification.didit.me/v3/kyb/shareholders/', {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n kyb_response_id: '69aeeb95febb0f1704042259',\n vendor_data: 'company-1234',\n }),\n});\nif (!res.ok) throw new Error(`Request failed: ${res.status}`);\nconst data = await res.json();\nconst requestId = data.request_id;\nconsole.log('Keep this request ID:', requestId);\nlet company = data.kyb_registry;\nif (res.status === 202 || company.fetch_status === 'pending') {\n // This client wait limit does not cancel the report or create a new purchase.\n const deadline = Date.now() + 300000;\n while (company.fetch_status !== 'resolved' && Date.now() < deadline) {\n await new Promise(resolve => setTimeout(resolve, 5000));\n const decisionResponse = await fetch(\n `https://verification.didit.me/v3/session/${requestId}/decision/`,\n { headers: { 'x-api-key': 'YOUR_API_KEY' }, signal: AbortSignal.timeout(30000) },\n );\n if (!decisionResponse.ok) throw new Error(`Retrieval failed: ${decisionResponse.status}`);\n const decision = await decisionResponse.json();\n company = decision.registry_checks?.[0]?.company ?? company;\n }\n}\nif (company.fetch_status === 'resolved') {\n console.log(company.company_name, company.registry_tier_delivered);\n} else {\n console.log('Still processing; retrieve this request later:', requestId);\n}" + } + ], + "responses": { + "200": { + "description": "Company report returned and billed at the delivered tier. Ownership reports still being prepared can return 202; keep request_id to retrieve their saved decision.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the business session created for this retrieval. The stored record (visible in the console) keeps updating if the registry resolves after the response." + }, + "registry_tier_requested": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "The tier this endpoint sells: `basic` (Lite) for select, `shareholders`, `ubo`." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "The tier actually delivered and billed. Lower than requested when the country's registries hold no ownership data, or the registry has no ownership record for this company.", + "nullable": true + }, + "kyb_registry": { + "type": "object", + "description": "Full registry company profile.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit's id for this company record." + }, + "node_id": { + "type": "string", + "example": "feature_kyb_registry" + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review" + ], + "description": "Didit's assessment of the registry check (e.g. `In Review` when the company is not active in the registry or ownership data could not be derived)." + }, + "registry_status": { + "type": "string", + "nullable": true, + "enum": [ + "active", + "dissolved", + "deregistered", + "see full details", + "authorised", + "appointed representative", + "unauthorised", + "inactive", + "no longer authorised", + "closed", + "struck off", + null + ], + "description": "Normalized company status as reported by the registry." + }, + "data_resolved": { + "type": "boolean", + "description": "`true` once the registry returned the full profile. `false` when the registry was still processing after polling — the stored record completes automatically once the registry resolves." + }, + "company_name": { + "type": "string", + "example": "Tesco PLC" + }, + "registration_number": { + "type": "string", + "nullable": true, + "example": "00445790" + }, + "country_code": { + "type": "string", + "description": "ISO 3166-1 alpha-2.", + "example": "GB" + }, + "region": { + "type": "string", + "nullable": true, + "description": "Subdivision code when the registry is regional (e.g. `CA` for `US-CA`); empty otherwise." + }, + "company_type": { + "type": "string", + "nullable": true, + "example": "Public Limited Company" + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true, + "example": "1947-11-27" + }, + "registered_address": { + "type": "string", + "nullable": true, + "example": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA" + }, + "tax_number": { + "type": "string", + "nullable": true, + "example": "GB412 8925 93" + }, + "risk_level": { + "type": "string", + "nullable": true, + "description": "Registry-reported risk level, when available." + }, + "verification_status": { + "type": "string", + "nullable": true, + "description": "Registry verification status, when available." + }, + "is_from_registry": { + "type": "boolean", + "description": "Always `true` for this endpoint — the profile was retrieved from an official registry." + }, + "fetch_status": { + "type": "string", + "enum": [ + "pending", + "resolved" + ], + "description": "Registry fetch lifecycle. Mirrored by `data_resolved`." + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true, + "description": "Activity description or classification code, when reported." + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "nullable": true, + "description": "Decimal amount as a string, when reported." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 code, when reported." + }, + "website": { + "type": "string", + "nullable": true, + "example": "https://www.tesco.com/" + }, + "email": { + "type": "string", + "nullable": true, + "example": "customer.service@tesco.co.uk" + }, + "phone": { + "type": "string", + "nullable": true, + "example": "03301231688" + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true, + "description": "LEI, when reported." + }, + "location_of_registration": { + "type": "string", + "nullable": true, + "description": "Registry/jurisdiction the company is registered with, when reported." + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards. At select time this is typically not yet set — VAT and tax ID validation runs when the end user submits the registry step in the hosted flow." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true, + "description": "Registry-reported share/capital details (currency, total shares, share classes), when available." + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "example": "MELISSA BETHELL" + }, + "designation": { + "type": "string", + "nullable": true, + "description": "Raw registry designation (e.g. `Director`, `Secretary`)." + }, + "role": { + "type": "string", + "description": "Normalized role derived from the designation. Full 17-value model set; registry parsing on this endpoint typically emits director, non_executive_director, secretary, chairman, or company_officer.", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "company_officer", + "settlor", + "protector", + "investor", + "other" + ] + }, + "nationality": { + "type": "string", + "nullable": true + }, + "is_active": { + "type": "boolean", + "description": "`false` when the registry marks the appointment as resigned." + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending", + null + ], + "description": "Status of an associated KYC session, when one was started for this officer. `null` otherwise." + }, + "kyc_session_url": { + "type": "string", + "nullable": true, + "description": "Verification URL of the associated KYC session, when one exists." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + }, + "description": "Directors, secretaries, and other officers extracted from the registry." + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the owner is a natural person or a corporate entity." + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Normalized roles (e.g. `ubo`, `shareholder`)." + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership band reported by the registry (e.g. `25%`)." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership band reported by the registry." + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending", + null + ] + }, + "kyc_session_url": { + "type": "string", + "nullable": true + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true, + "description": "Parsed ownership percentage (max bound, falling back to min bound)." + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + }, + "shareholding": { + "$ref": "#/components/schemas/KYBRegistryShareholding" + } + } + }, + "description": "Beneficial owners / persons with significant control extracted from the registry. Can be empty when the registry does not expose ownership." + }, + "addresses": { + "type": "array", + "nullable": true, + "description": "Every address the registry states, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryAddress" + } + }, + "industries": { + "type": "array", + "nullable": true, + "description": "The activities the registry classifies the company under, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryIndustry" + } + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "type": "object", + "description": "Raw record returned by the registry source, kept for reviewers. Its shape differs by country and source and is not a stable contract: read the canonical fields (`addresses`, `industries`, `ownership`, `beneficial_owners[].shareholding`) instead." + }, + "user_provided_data": { + "type": "object", + "nullable": true, + "description": "Always `null` on this endpoint (used by the hosted KYB flow when end users edit registry data)." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set when a console reviewer edits the record later." + }, + "is_editable": { + "type": "boolean", + "description": "Whether the registry-sourced data can still be edited in the hosted flow/console." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "Same as the top-level `registry_tier_delivered`.", + "nullable": true + }, + "registry_ownership_status": { + "type": "string", + "nullable": true, + "enum": [ + "not_requested", + "delivered", + "not_held", + "unavailable", + "pending", + null + ], + "description": "`delivered` when the ownership data came back, `not_held` when the ownership registry answered and does not list this company (see `registry_data.ownership_error`), `unavailable` when an ownership tier was requested but the registry had none for this company or could not be asked (see `registry_data.ownership_error`), `pending` when the ownership section has not been retrieved from the registry yet and Didit is still completing it, `not_requested` on the Lite tier." + }, + "ownership": { + "$ref": "#/components/schemas/KYBRegistryOwnership" + }, + "registry_tier": { + "type": "string", + "nullable": true, + "description": "The registry tier the workflow ordered: `basic`, `shareholders` or `ubo`. Also in `ownership.tier_requested`." + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Resolved company": { + "summary": "Full registry profile (officer list truncated for brevity)", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "f1e2d3c4-b5a6-4789-9c0d-1e2f3a4b5c6d", + "node_id": "feature_kyb_registry", + "status": "In Review", + "registry_status": "active", + "data_resolved": true, + "company_name": "Tesco PLC", + "registration_number": "00445790", + "country_code": "GB", + "region": "", + "company_type": "Public Limited Company", + "incorporation_date": "1947-11-27", + "registered_address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "tax_number": "GB412 8925 93", + "risk_level": "low risk", + "verification_status": "verified", + "is_from_registry": true, + "fetch_status": "resolved", + "alternative_names": "TESCO STORES (HOLDINGS) PUBLIC LIMITED COMPANY", + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": "https://www.tesco.com/", + "email": "customer.service@tesco.co.uk", + "phone": "03301231688", + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": { + "currency": "Pound (£)", + "total_number_of_shares": "8174378721", + "meta_detail": { + "Class_Of_Share": "Ordinary" + } + }, + "officers": [ + { + "uuid": "a1b2c3d4-e5f6-4789-9a0b-1c2d3e4f5a6b", + "name": "CHRISTOPHER JON TAYLOR", + "designation": "Secretary", + "role": "secretary", + "nationality": null, + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + }, + { + "uuid": "b2c3d4e5-f6a7-4890-8b1c-2d3e4f5a6b7c", + "name": "MELISSA BETHELL", + "designation": "Director", + "role": "director", + "nationality": "British", + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + } + ], + "beneficial_owners": [], + "addresses": [ + { + "address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "type": "legal_entity_registry_address", + "description": "registered office address" + } + ], + "industries": null, + "accounts": { + "last_account": "26 February 2025", + "next_account": "26 February 2026", + "due_by_date": "26 August 2026" + }, + "registry_data": { + "name": "Tesco PLC", + "registration_number": "00445790", + "status": "active", + "type": "Public Limited Company", + "incorporation_date": "1947-11-27", + "tax_number": "GB412 8925 93", + "country_code": "GB", + "fetch_status": "resolved", + "industries_detail": [ + { + "code": "47110", + "name": "Retail Sale In Non-specialised Stores With Food, Beverages Or Tobacco Predominating", + "description": null, + "meta_detail": [] + } + ] + }, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems use DRF's standard envelope.", + "content": { + "application/json": { + "examples": { + "Missing kyb_response_id": { + "summary": "`kyb_response_id` is required", + "value": { + "kyb_response_id": [ + "This field is required." + ] + } + }, + "save_api_request=false rejected": { + "summary": "Selects are always persisted", + "value": { + "save_api_request": [ + "KYB registry select must be saved so billing and retrieval can be tracked." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, malformed, revoked, or belongs to another environment — and also when the calling organization's balance cannot cover the call. Authentication failures return `403` with `{\"detail\": ...}`; this API never returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. All POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "202": { + "description": "Report accepted and still processing. When a report is still being prepared, the endpoint returns 202 Accepted, kyb_registry.fetch_status=pending, and registry_tier_delivered=null. Keep request_id and retrieve GET /v3/session/{request_id}/decision/ until registry_checks[0].company.fetch_status is resolved. Didit completes the report automatically and charges the delivered tier once it is ready. Do not repeat the purchase while it is pending; every new POST creates a separate request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/paths/~1v3~1kyb~1shareholders~1/post/responses/200/content/application~1json/schema" + }, + "examples": { + "Pending (registry still resolving)": { + "summary": "Ownership report is still processing; retrieve the saved decision", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "7c0a4f7e-2b1d-4f6a-9c3e-8d5b2a1f0e9d", + "node_id": "feature_kyb_registry", + "status": "Not Finished", + "registry_status": null, + "data_resolved": false, + "company_name": "Example Holdings Limited", + "registration_number": "12345678", + "country_code": "HK", + "region": null, + "company_type": null, + "incorporation_date": null, + "registered_address": null, + "tax_number": null, + "risk_level": null, + "verification_status": null, + "is_from_registry": true, + "fetch_status": "pending", + "alternative_names": null, + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": null, + "email": null, + "phone": null, + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": {}, + "officers": [], + "beneficial_owners": [], + "addresses": null, + "industries": null, + "accounts": null, + "registry_data": {}, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true, + "registry_tier_delivered": null, + "registry_ownership_status": "pending" + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00", + "registry_tier_delivered": null, + "registry_tier_requested": "shareholders" + } + } + } + } + } + } + } + } + }, + "/v3/kyb/ubo/": { + "post": { + "summary": "KYB registry company with ultimate beneficial owners (standalone)", + "operationId": "post_v3_kyb_ubo", + "x-didit-sdk": { + "resource": "kyb", + "method": "list_ubos", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Retrieve the Lite profile, the direct shareholders **and the ultimate beneficial owners** resolved through the ownership chain (`registry_data.ubo_resolution` carries the corporate parent chain) of one candidate returned by `POST /v3/kyb/search/`.\n\n**Pairing with search.** Pass the candidate's `kyb_response_id` exactly as returned by a search of the same application. The search runs on the Lite registry chain; when the country's ownership data lives at another registry Didit re-finds the company there by registration number, at no extra cost.\n\n**Billing.** Billed at the country's UBO price - see [KYB registry pricing](/getting-started/kyb-registry-pricing). The tier delivered is the highest the country and the registry can serve: Shareholders when the resolved report names no natural person as a beneficial owner, Lite when there is no ownership record at all; `registry_tier_delivered` names what was billed. The balance is checked against the UBO price before any registry call (`403` when short).\n\n**Availability.** v3 only. Every call creates a business session (its id is the `request_id`).\n\n**Pending reports.** When a report is still being prepared, or the ownership section has not been retrieved from the registry yet, the endpoint returns 202 Accepted, kyb_registry.fetch_status=pending, kyb_registry.registry_ownership_status=pending, and registry_tier_delivered=null. Keep request_id and retrieve GET /v3/session/{request_id}/decision/ until registry_checks[0].company.fetch_status is resolved. Didit completes the report automatically and charges the delivered tier once it is ready. Do not repeat the purchase while it is pending; every new POST creates a separate request.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBRegistryCandidateRequest" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/kyb/ubo/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"kyb_response_id\": \"69aeeb95febb0f1704042259\",\n \"vendor_data\": \"company-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os, time, requests\n\nsearch = requests.post(\n \"https://verification.didit.me/v3/kyb/search/\",\n headers={\"x-api-key\": os.environ[\"DIDIT_API_KEY\"]},\n json={\"country_code\": \"GB\", \"name\": \"Tesco\"},\n timeout=60,\n).json()\nkyb_response_id = search[\"kyb_registry\"][\"companies\"][0][\"kyb_response_id\"]\n\nresp = requests.post(\n \"https://verification.didit.me/v3/kyb/ubo/\",\n headers={\"x-api-key\": os.environ[\"DIDIT_API_KEY\"]},\n json={\"kyb_response_id\": kyb_response_id, \"vendor_data\": \"company-1234\"},\n timeout=90,\n)\nresp.raise_for_status()\ndata = resp.json()\nrequest_id = data[\"request_id\"]\nprint(\"Keep this request ID:\", request_id)\ncompany = data[\"kyb_registry\"]\nif resp.status_code == 202 or company.get(\"fetch_status\") == \"pending\":\n # This client wait limit does not cancel the report or create a new purchase.\n deadline = time.monotonic() + 300\n while company.get(\"fetch_status\") != \"resolved\" and time.monotonic() < deadline:\n time.sleep(5)\n decision = requests.get(\n f\"https://verification.didit.me/v3/session/{request_id}/decision/\",\n headers={\"x-api-key\": os.environ[\"DIDIT_API_KEY\"]},\n timeout=30,\n )\n decision.raise_for_status()\n checks = decision.json().get(\"registry_checks\") or []\n if checks:\n company = checks[0].get(\"company\") or company\nif company.get(\"fetch_status\") == \"resolved\":\n print(company[\"company_name\"], company[\"registry_tier_delivered\"])\nelse:\n print(\"Still processing; retrieve this request later:\", request_id)" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const res = await fetch('https://verification.didit.me/v3/kyb/ubo/', {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n kyb_response_id: '69aeeb95febb0f1704042259',\n vendor_data: 'company-1234',\n }),\n});\nif (!res.ok) throw new Error(`Request failed: ${res.status}`);\nconst data = await res.json();\nconst requestId = data.request_id;\nconsole.log('Keep this request ID:', requestId);\nlet company = data.kyb_registry;\nif (res.status === 202 || company.fetch_status === 'pending') {\n // This client wait limit does not cancel the report or create a new purchase.\n const deadline = Date.now() + 300000;\n while (company.fetch_status !== 'resolved' && Date.now() < deadline) {\n await new Promise(resolve => setTimeout(resolve, 5000));\n const decisionResponse = await fetch(\n `https://verification.didit.me/v3/session/${requestId}/decision/`,\n { headers: { 'x-api-key': 'YOUR_API_KEY' }, signal: AbortSignal.timeout(30000) },\n );\n if (!decisionResponse.ok) throw new Error(`Retrieval failed: ${decisionResponse.status}`);\n const decision = await decisionResponse.json();\n company = decision.registry_checks?.[0]?.company ?? company;\n }\n}\nif (company.fetch_status === 'resolved') {\n console.log(company.company_name, company.registry_tier_delivered);\n} else {\n console.log('Still processing; retrieve this request later:', requestId);\n}" + } + ], + "responses": { + "200": { + "description": "Company report returned and billed at the delivered tier. Ownership reports still being prepared can return 202; keep request_id to retrieve their saved decision.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the business session created for this retrieval. The stored record (visible in the console) keeps updating if the registry resolves after the response." + }, + "registry_tier_requested": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "The tier this endpoint sells: `basic` (Lite) for select, `shareholders`, `ubo`." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "The tier actually delivered and billed. Lower than requested when the country's registries hold no ownership data, when the registry has no ownership record for this company, or when the resolved report of a `ubo` request names no natural person as a beneficial owner.", + "nullable": true + }, + "kyb_registry": { + "type": "object", + "description": "Full registry company profile.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit's id for this company record." + }, + "node_id": { + "type": "string", + "example": "feature_kyb_registry" + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review" + ], + "description": "Didit's assessment of the registry check (e.g. `In Review` when the company is not active in the registry or ownership data could not be derived)." + }, + "registry_status": { + "type": "string", + "nullable": true, + "enum": [ + "active", + "dissolved", + "deregistered", + "see full details", + "authorised", + "appointed representative", + "unauthorised", + "inactive", + "no longer authorised", + "closed", + "struck off", + null + ], + "description": "Normalized company status as reported by the registry." + }, + "data_resolved": { + "type": "boolean", + "description": "`true` once the registry returned the full profile. `false` when the registry was still processing after polling — the stored record completes automatically once the registry resolves." + }, + "company_name": { + "type": "string", + "example": "Tesco PLC" + }, + "registration_number": { + "type": "string", + "nullable": true, + "example": "00445790" + }, + "country_code": { + "type": "string", + "description": "ISO 3166-1 alpha-2.", + "example": "GB" + }, + "region": { + "type": "string", + "nullable": true, + "description": "Subdivision code when the registry is regional (e.g. `CA` for `US-CA`); empty otherwise." + }, + "company_type": { + "type": "string", + "nullable": true, + "example": "Public Limited Company" + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true, + "example": "1947-11-27" + }, + "registered_address": { + "type": "string", + "nullable": true, + "example": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA" + }, + "tax_number": { + "type": "string", + "nullable": true, + "example": "GB412 8925 93" + }, + "risk_level": { + "type": "string", + "nullable": true, + "description": "Registry-reported risk level, when available." + }, + "verification_status": { + "type": "string", + "nullable": true, + "description": "Registry verification status, when available." + }, + "is_from_registry": { + "type": "boolean", + "description": "Always `true` for this endpoint — the profile was retrieved from an official registry." + }, + "fetch_status": { + "type": "string", + "enum": [ + "pending", + "resolved" + ], + "description": "Registry fetch lifecycle. Mirrored by `data_resolved`." + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true, + "description": "Activity description or classification code, when reported." + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "nullable": true, + "description": "Decimal amount as a string, when reported." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 code, when reported." + }, + "website": { + "type": "string", + "nullable": true, + "example": "https://www.tesco.com/" + }, + "email": { + "type": "string", + "nullable": true, + "example": "customer.service@tesco.co.uk" + }, + "phone": { + "type": "string", + "nullable": true, + "example": "03301231688" + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true, + "description": "LEI, when reported." + }, + "location_of_registration": { + "type": "string", + "nullable": true, + "description": "Registry/jurisdiction the company is registered with, when reported." + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards. At select time this is typically not yet set — VAT and tax ID validation runs when the end user submits the registry step in the hosted flow." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true, + "description": "Registry-reported share/capital details (currency, total shares, share classes), when available." + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "example": "MELISSA BETHELL" + }, + "designation": { + "type": "string", + "nullable": true, + "description": "Raw registry designation (e.g. `Director`, `Secretary`)." + }, + "role": { + "type": "string", + "description": "Normalized role derived from the designation. Full 17-value model set; registry parsing on this endpoint typically emits director, non_executive_director, secretary, chairman, or company_officer.", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "company_officer", + "settlor", + "protector", + "investor", + "other" + ] + }, + "nationality": { + "type": "string", + "nullable": true + }, + "is_active": { + "type": "boolean", + "description": "`false` when the registry marks the appointment as resigned." + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending", + null + ], + "description": "Status of an associated KYC session, when one was started for this officer. `null` otherwise." + }, + "kyc_session_url": { + "type": "string", + "nullable": true, + "description": "Verification URL of the associated KYC session, when one exists." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + }, + "description": "Directors, secretaries, and other officers extracted from the registry." + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the owner is a natural person or a corporate entity." + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Normalized roles (e.g. `ubo`, `shareholder`)." + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership band reported by the registry (e.g. `25%`)." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership band reported by the registry." + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending", + null + ] + }, + "kyc_session_url": { + "type": "string", + "nullable": true + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true, + "description": "Parsed ownership percentage (max bound, falling back to min bound)." + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + }, + "shareholding": { + "$ref": "#/components/schemas/KYBRegistryShareholding" + } + } + }, + "description": "Beneficial owners / persons with significant control extracted from the registry. Can be empty when the registry does not expose ownership." + }, + "addresses": { + "type": "array", + "nullable": true, + "description": "Every address the registry states, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryAddress" + } + }, + "industries": { + "type": "array", + "nullable": true, + "description": "The activities the registry classifies the company under, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryIndustry" + } + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "type": "object", + "description": "Raw record returned by the registry source, kept for reviewers. Its shape differs by country and source and is not a stable contract: read the canonical fields (`addresses`, `industries`, `ownership`, `beneficial_owners[].shareholding`) instead." + }, + "user_provided_data": { + "type": "object", + "nullable": true, + "description": "Always `null` on this endpoint (used by the hosted KYB flow when end users edit registry data)." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set when a console reviewer edits the record later." + }, + "is_editable": { + "type": "boolean", + "description": "Whether the registry-sourced data can still be edited in the hosted flow/console." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "Same as the top-level `registry_tier_delivered`.", + "nullable": true + }, + "registry_ownership_status": { + "type": "string", + "nullable": true, + "enum": [ + "not_requested", + "delivered", + "not_held", + "unavailable", + "pending", + null + ], + "description": "`delivered` when the ownership data came back, `not_held` when the ownership registry answered and does not list this company (see `registry_data.ownership_error`), `unavailable` when an ownership tier was requested but the registry had none for this company or could not be asked (see `registry_data.ownership_error`), `pending` when the ownership section has not been retrieved from the registry yet and Didit is still completing it, `not_requested` on the Lite tier." + }, + "ownership": { + "$ref": "#/components/schemas/KYBRegistryOwnership" + }, + "registry_tier": { + "type": "string", + "nullable": true, + "description": "The registry tier the workflow ordered: `basic`, `shareholders` or `ubo`. Also in `ownership.tier_requested`." + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Resolved company": { + "summary": "Full registry profile (officer list truncated for brevity)", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "f1e2d3c4-b5a6-4789-9c0d-1e2f3a4b5c6d", + "node_id": "feature_kyb_registry", + "status": "In Review", + "registry_status": "active", + "data_resolved": true, + "company_name": "Tesco PLC", + "registration_number": "00445790", + "country_code": "GB", + "region": "", + "company_type": "Public Limited Company", + "incorporation_date": "1947-11-27", + "registered_address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "tax_number": "GB412 8925 93", + "risk_level": "low risk", + "verification_status": "verified", + "is_from_registry": true, + "fetch_status": "resolved", + "alternative_names": "TESCO STORES (HOLDINGS) PUBLIC LIMITED COMPANY", + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": "https://www.tesco.com/", + "email": "customer.service@tesco.co.uk", + "phone": "03301231688", + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": { + "currency": "Pound (£)", + "total_number_of_shares": "8174378721", + "meta_detail": { + "Class_Of_Share": "Ordinary" + } + }, + "officers": [ + { + "uuid": "a1b2c3d4-e5f6-4789-9a0b-1c2d3e4f5a6b", + "name": "CHRISTOPHER JON TAYLOR", + "designation": "Secretary", + "role": "secretary", + "nationality": null, + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + }, + { + "uuid": "b2c3d4e5-f6a7-4890-8b1c-2d3e4f5a6b7c", + "name": "MELISSA BETHELL", + "designation": "Director", + "role": "director", + "nationality": "British", + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + } + ], + "beneficial_owners": [], + "addresses": [ + { + "address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "type": "legal_entity_registry_address", + "description": "registered office address" + } + ], + "industries": null, + "accounts": { + "last_account": "26 February 2025", + "next_account": "26 February 2026", + "due_by_date": "26 August 2026" + }, + "registry_data": { + "name": "Tesco PLC", + "registration_number": "00445790", + "status": "active", + "type": "Public Limited Company", + "incorporation_date": "1947-11-27", + "tax_number": "GB412 8925 93", + "country_code": "GB", + "fetch_status": "resolved", + "industries_detail": [ + { + "code": "47110", + "name": "Retail Sale In Non-specialised Stores With Food, Beverages Or Tobacco Predominating", + "description": null, + "meta_detail": [] + } + ] + }, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems use DRF's standard envelope.", + "content": { + "application/json": { + "examples": { + "Missing kyb_response_id": { + "summary": "`kyb_response_id` is required", + "value": { + "kyb_response_id": [ + "This field is required." + ] + } + }, + "save_api_request=false rejected": { + "summary": "Selects are always persisted", + "value": { + "save_api_request": [ + "KYB registry select must be saved so billing and retrieval can be tracked." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, malformed, revoked, or belongs to another environment — and also when the calling organization's balance cannot cover the call. Authentication failures return `403` with `{\"detail\": ...}`; this API never returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. All POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "202": { + "description": "Report accepted and still processing. When a report is still being prepared, the endpoint returns 202 Accepted, kyb_registry.fetch_status=pending, and registry_tier_delivered=null. Keep request_id and retrieve GET /v3/session/{request_id}/decision/ until registry_checks[0].company.fetch_status is resolved. Didit completes the report automatically and charges the delivered tier once it is ready. Do not repeat the purchase while it is pending; every new POST creates a separate request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/paths/~1v3~1kyb~1ubo~1/post/responses/200/content/application~1json/schema" + }, + "examples": { + "Pending (registry still resolving)": { + "summary": "Ownership report is still processing; retrieve the saved decision", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "7c0a4f7e-2b1d-4f6a-9c3e-8d5b2a1f0e9d", + "node_id": "feature_kyb_registry", + "status": "Not Finished", + "registry_status": null, + "data_resolved": false, + "company_name": "Example Holdings Limited", + "registration_number": "12345678", + "country_code": "HK", + "region": null, + "company_type": null, + "incorporation_date": null, + "registered_address": null, + "tax_number": null, + "risk_level": null, + "verification_status": null, + "is_from_registry": true, + "fetch_status": "pending", + "alternative_names": null, + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": null, + "email": null, + "phone": null, + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": {}, + "officers": [], + "beneficial_owners": [], + "addresses": null, + "industries": null, + "accounts": null, + "registry_data": {}, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true, + "registry_tier_delivered": null, + "registry_ownership_status": "pending" + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00", + "registry_tier_delivered": null, + "registry_tier_requested": "ubo" + } + } + } + } + } + } + } + } + }, + "/v3/id-verification/wallets/": { + "get": { + "operationId": "list_wallets", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "list", + "auth": "api_key", + "server": true + }, + "description": "Every Digital ID wallet with its start endpoint, countries, required fields and whether this application can start it in this environment now. Listed does not mean live.", + "summary": "List Digital ID wallets", + "tags": [ + "Digital ID wallets" + ], + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletCatalogResponse" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "Missing or invalid credentials (x-api-key or Authorization: Bearer)." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request GET \\\n --url https://verification.didit.me/v3/id-verification/wallets/ \\\n --header 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"]\n}\n\nresponse = requests.get(\n \"https://verification.didit.me/v3/id-verification/wallets/\",\n headers=headers,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ] + } + }, + "/v3/id-verification/wallets/verifications/{request_id}/": { + "get": { + "operationId": "get_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "retrieve_verification", + "auth": "api_key", + "server": true + }, + "description": "Status and result of a wallet verification: the normalized identity and the wallet's own attributes once verified, the error code once declined, the next action while pending. A verification past expires_at is decided timeout.", + "summary": "Get a wallet verification", + "parameters": [ + { + "in": "path", + "name": "request_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The request_id the start endpoint returned.", + "required": true + } + ], + "tags": [ + "Digital ID wallets" + ], + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "ftn", + "name": "Finnish Trust Network", + "issuing_authority": "Finnish banks and mobile operators (FTN)" + }, + "country": "FIN", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "Missing or invalid credentials (x-api-key or Authorization: Bearer)." + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error not_found: no wallet verification of this application has this request_id." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request GET \\\n --url https://verification.didit.me/v3/id-verification/wallets/verifications/REQUEST_ID/ \\\n --header 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"]\n}\n\nresponse = requests.get(\n \"https://verification.didit.me/v3/id-verification/wallets/verifications/REQUEST_ID/\",\n headers=headers,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ] + } + }, + "/v3/id-verification/wallets/verifications/{request_id}/poll/": { + "post": { + "operationId": "poll_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "poll_verification", + "auth": "api_key", + "server": true + }, + "description": "Smart-ID and Mobile-ID: ask the provider whether the user confirmed, refused or let the prompt lapse, and verify the signed identity when they confirmed. Redirect wallets complete through the user's browser, so for them this only reads the verification back. Answers the verification as it is afterwards.", + "summary": "Poll a wallet verification", + "parameters": [ + { + "in": "path", + "name": "request_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The request_id the start endpoint returned.", + "required": true + } + ], + "tags": [ + "Digital ID wallets" + ], + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "smart_id", + "name": "Smart-ID", + "issuing_authority": "SK ID Solutions" + }, + "country": "EST", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "in_app", + "simulated": true, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "confirm_in_app", + "verification_code": "1234", + "poll_interval_seconds": 2 + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "Missing or invalid credentials (x-api-key or Authorization: Bearer)." + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error not_found: no wallet verification of this application has this request_id." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/verifications/REQUEST_ID/poll/ \\\n --header 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"]\n}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/verifications/REQUEST_ID/poll/\",\n headers=headers,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ] + } + }, + "/v3/id-verification/wallets/verifications/{request_id}/cancel/": { + "post": { + "operationId": "cancel_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "cancel_verification", + "auth": "api_key", + "server": true + }, + "description": "Withdraw a pending verification: it is declined as cancelled and never billed. A decided one is returned unchanged. Smart-ID and Mobile-ID: a cancel that arrives while a poll is checking the sign-in waits a few seconds for that poll, then declines it, or returns it verified if the user had already confirmed. A verification the user is completing right now cannot be cancelled (409 verification_in_progress with Retry-After): retry after that many seconds to read its outcome.", + "summary": "Cancel a wallet verification", + "parameters": [ + { + "in": "path", + "name": "request_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The request_id the start endpoint returned.", + "required": true + } + ], + "tags": [ + "Digital ID wallets" + ], + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "ftn", + "name": "Finnish Trust Network", + "issuing_authority": "Finnish banks and mobile operators (FTN)" + }, + "country": "FIN", + "status": "Declined", + "outcome": "cancelled", + "error": "cancelled_by_customer", + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": null, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "Missing or invalid credentials (x-api-key or Authorization: Bearer)." + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error not_found: no wallet verification of this application has this request_id." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error verification_in_progress: the user is completing this sign-in right now. The Retry-After header says when to retry; the retry returns the decided verification." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/verifications/REQUEST_ID/cancel/ \\\n --header 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"]\n}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/verifications/REQUEST_ID/cancel/\",\n headers=headers,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ] + } + }, + "/v3/id-verification/wallets/mitid/": { + "post": { + "operationId": "start_mitid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_mitid", + "auth": "api_key", + "server": true + }, + "description": "Start a MitID sign-in (Danish Agency for Digital Government; DNK) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start MitID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMitidWalletVerificationStartRequest" + }, + "example": { + "country": "DNK", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMitidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMitidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "mitid", + "name": "MitID", + "issuing_authority": "Danish Agency for Digital Government" + }, + "country": "DNK", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/mitid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"DNK\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'DNK',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/mitid/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "mitid" + } + }, + "/v3/id-verification/wallets/bankid-se/": { + "post": { + "operationId": "start_bankid_se_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_bankid_se", + "auth": "api_key", + "server": true + }, + "description": "Start a BankID (SWE) sign-in (Finansiell ID-Teknik / bank consortium; SWE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start BankID (SWE) verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidSeWalletVerificationStartRequest" + }, + "example": { + "country": "SWE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidSeWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidSeWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "bankid_se", + "name": "BankID", + "issuing_authority": "Finansiell ID-Teknik / bank consortium" + }, + "country": "SWE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/bankid-se/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"SWE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'SWE',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/bankid-se/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "bankid_se" + } + }, + "/v3/id-verification/wallets/bankid-no/": { + "post": { + "operationId": "start_bankid_no_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_bankid_no", + "auth": "api_key", + "server": true + }, + "description": "Start a BankID (NOR) sign-in (BankID BankAxept AS; NOR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start BankID (NOR) verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidNoWalletVerificationStartRequest" + }, + "example": { + "country": "NOR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidNoWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidNoWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "bankid_no", + "name": "BankID", + "issuing_authority": "BankID BankAxept AS" + }, + "country": "NOR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/bankid-no/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"NOR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'NOR',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/bankid-no/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "bankid_no" + } + }, + "/v3/id-verification/wallets/vipps/": { + "post": { + "operationId": "start_vipps_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_vipps", + "auth": "api_key", + "server": true + }, + "description": "Start a Vipps sign-in (Vipps MobilePay / BankID NO; NOR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Vipps verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiVippsWalletVerificationStartRequest" + }, + "example": { + "country": "NOR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiVippsWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiVippsWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "vipps", + "name": "Vipps", + "issuing_authority": "Vipps MobilePay / BankID NO" + }, + "country": "NOR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/vipps/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"NOR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'NOR',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/vipps/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "vipps" + } + }, + "/v3/id-verification/wallets/buypass/": { + "post": { + "operationId": "start_buypass_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_buypass", + "auth": "api_key", + "server": true + }, + "description": "Start a Buypass ID sign-in (Buypass AS; NOR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Buypass ID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBuypassWalletVerificationStartRequest" + }, + "example": { + "country": "NOR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBuypassWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBuypassWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "buypass", + "name": "Buypass ID", + "issuing_authority": "Buypass AS" + }, + "country": "NOR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/buypass/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"NOR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'NOR',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/buypass/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "buypass" + } + }, + "/v3/id-verification/wallets/itsme/": { + "post": { + "operationId": "start_itsme_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_itsme", + "auth": "api_key", + "server": true + }, + "description": "Start a itsme sign-in (Belgian Mobile ID; BEL, LUX, NLD) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start itsme verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiItsmeWalletVerificationStartRequest" + }, + "example": { + "country": "BEL", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiItsmeWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiItsmeWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "itsme", + "name": "itsme", + "issuing_authority": "Belgian Mobile ID" + }, + "country": "BEL", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/itsme/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"BEL\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'BEL',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/itsme/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "itsme" + } + }, + "/v3/id-verification/wallets/idin/": { + "post": { + "operationId": "start_idin_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_idin", + "auth": "api_key", + "server": true + }, + "description": "Start a iDIN sign-in (Dutch banks (Currence iDIN); NLD) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start iDIN verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdinWalletVerificationStartRequest" + }, + "example": { + "country": "NLD", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdinWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdinWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "idin", + "name": "iDIN", + "issuing_authority": "Dutch banks (Currence iDIN)" + }, + "country": "NLD", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/idin/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"NLD\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'NLD',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/idin/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "idin" + } + }, + "/v3/id-verification/wallets/ftn/": { + "post": { + "operationId": "start_ftn_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_ftn", + "auth": "api_key", + "server": true + }, + "description": "Start a Finnish Trust Network sign-in (Finnish banks and mobile operators (FTN); FIN) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Finnish Trust Network verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFtnWalletVerificationStartRequest" + }, + "example": { + "country": "FIN", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFtnWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFtnWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "ftn", + "name": "Finnish Trust Network", + "issuing_authority": "Finnish banks and mobile operators (FTN)" + }, + "country": "FIN", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/ftn/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"FIN\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'FIN',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/ftn/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "ftn" + } + }, + "/v3/id-verification/wallets/personalausweis/": { + "post": { + "operationId": "start_personalausweis_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_personalausweis", + "auth": "api_key", + "server": true + }, + "description": "Start a Personalausweis sign-in (Bundesministerium des Innern (eID); DEU) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Personalausweis verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest" + }, + "example": { + "country": "DEU", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "personalausweis", + "name": "Personalausweis", + "issuing_authority": "Bundesministerium des Innern (eID)" + }, + "country": "DEU", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/personalausweis/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"DEU\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'DEU',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/personalausweis/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "personalausweis" + } + }, + "/v3/id-verification/wallets/frejaid/": { + "post": { + "operationId": "start_frejaid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_frejaid", + "auth": "api_key", + "server": true + }, + "description": "Start a Freja eID sign-in (Freja eID Group; SWE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Freja eID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFrejaidWalletVerificationStartRequest" + }, + "example": { + "country": "SWE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFrejaidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFrejaidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "frejaid", + "name": "Freja eID", + "issuing_authority": "Freja eID Group" + }, + "country": "SWE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/frejaid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"SWE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'SWE',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/frejaid/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "frejaid" + } + }, + "/v3/id-verification/wallets/uae-pass/": { + "post": { + "operationId": "start_uae_pass_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_uae_pass", + "auth": "api_key", + "server": true + }, + "description": "Start a UAE PASS sign-in (UAE Digital Government Authority; ARE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start UAE PASS verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiUaePassWalletVerificationStartRequest" + }, + "example": { + "country": "ARE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiUaePassWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiUaePassWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "uae_pass", + "name": "UAE PASS", + "issuing_authority": "UAE Digital Government Authority" + }, + "country": "ARE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/uae-pass/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"ARE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'ARE',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/uae-pass/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "uae_pass" + } + }, + "/v3/id-verification/wallets/govbr/": { + "post": { + "operationId": "start_govbr_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_govbr", + "auth": "api_key", + "server": true + }, + "description": "Start a gov.br sign-in (Governo Federal do Brasil; BRA) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start gov.br verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovbrWalletVerificationStartRequest" + }, + "example": { + "country": "BRA", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovbrWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovbrWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "govbr", + "name": "gov.br", + "issuing_authority": "Governo Federal do Brasil" + }, + "country": "BRA", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/govbr/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"BRA\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'BRA',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/govbr/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "govbr" + } + }, + "/v3/id-verification/wallets/oneid/": { + "post": { + "operationId": "start_oneid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_oneid", + "auth": "api_key", + "server": true + }, + "description": "Start a OneID sign-in (OneID (UK bank-verified identity); GBR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start OneID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiOneidWalletVerificationStartRequest" + }, + "example": { + "country": "GBR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiOneidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiOneidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "oneid", + "name": "OneID", + "issuing_authority": "OneID (UK bank-verified identity)" + }, + "country": "GBR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/oneid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"GBR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'GBR',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/oneid/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "oneid" + } + }, + "/v3/id-verification/wallets/govuk-wallet/": { + "post": { + "operationId": "start_govuk_wallet_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_govuk_wallet", + "auth": "api_key", + "server": true + }, + "description": "Start a GOV.UK Wallet sign-in (UK Government Digital Service; GBR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start GOV.UK Wallet verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovukWalletWalletVerificationStartRequest" + }, + "example": { + "country": "GBR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovukWalletWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovukWalletWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "govuk_wallet", + "name": "GOV.UK Wallet", + "issuing_authority": "UK Government Digital Service" + }, + "country": "GBR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/govuk-wallet/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"GBR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'GBR',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/govuk-wallet/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "govuk_wallet" + } + }, + "/v3/id-verification/wallets/smart-id/": { + "post": { + "operationId": "start_smart_id_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_smart_id", + "auth": "api_key", + "server": true + }, + "description": "Start a Smart-ID sign-in (SK ID Solutions; EST, LVA, LTU, BEL) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=confirm_in_app with the comparison code to show the user; the user confirms it and enters their PIN in the app (never collect the PIN). Poll POST /v3/id-verification/wallets/verifications/{request_id}/poll/ every poll_interval_seconds, or rely on the webhook: Didit polls in the background too. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Smart-ID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiSmartIdWalletVerificationStartRequest" + }, + "example": { + "country": "EST", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "personal_code": "00000000000" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiSmartIdWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiSmartIdWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "smart_id", + "name": "Smart-ID", + "issuing_authority": "SK ID Solutions" + }, + "country": "EST", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "in_app", + "simulated": true, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "confirm_in_app", + "verification_code": "1234", + "poll_interval_seconds": 2 + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/smart-id/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"EST\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"personal_code\": \"00000000000\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'EST',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'personal_code': '00000000000'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/smart-id/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "smart_id" + } + }, + "/v3/id-verification/wallets/mobile-id/": { + "post": { + "operationId": "start_mobile_id_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_mobile_id", + "auth": "api_key", + "server": true + }, + "description": "Start a Mobile-ID sign-in (SK ID Solutions with the national mobile operators; EST, LTU) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=confirm_in_app with the comparison code to show the user; the user confirms it and enters their PIN in the app (never collect the PIN). Poll POST /v3/id-verification/wallets/verifications/{request_id}/poll/ every poll_interval_seconds, or rely on the webhook: Didit polls in the background too. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Mobile-ID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMobileIdWalletVerificationStartRequest" + }, + "example": { + "country": "EST", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "personal_code": "00000000000", + "phone_number": "+37200000000" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMobileIdWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMobileIdWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "mobile_id", + "name": "Mobile-ID", + "issuing_authority": "SK ID Solutions with the national mobile operators" + }, + "country": "EST", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "in_app", + "simulated": true, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "confirm_in_app", + "verification_code": "1234", + "poll_interval_seconds": 2 + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/mobile-id/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"EST\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"personal_code\": \"00000000000\",\n \"phone_number\": \"+37200000000\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'EST',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'personal_code': '00000000000',\n 'phone_number': '+37200000000'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/mobile-id/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "mobile_id" + } + }, + "/v3/id-verification/wallets/idcard-ee/": { + "post": { + "operationId": "start_idcard_ee_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_idcard_ee", + "auth": "api_key", + "server": true + }, + "description": "Start a Estonian ID-card sign-in (Police and Border Guard Board (Estonia); EST) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Estonian ID-card verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdcardEeWalletVerificationStartRequest" + }, + "example": { + "country": "EST", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdcardEeWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdcardEeWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "idcard_ee", + "name": "Estonian ID-card", + "issuing_authority": "Police and Border Guard Board (Estonia)" + }, + "country": "EST", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/idcard-ee/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"EST\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'EST',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/idcard-ee/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "idcard_ee" + } + }, + "/v3/id-verification/wallets/eparaksts-mobile/": { + "post": { + "operationId": "start_eparaksts_mobile_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_eparaksts_mobile", + "auth": "api_key", + "server": true + }, + "description": "Start a eParaksts mobile sign-in (LVRTC (Latvian State Radio and Television Centre); LVA) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start eParaksts mobile verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest" + }, + "example": { + "country": "LVA", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "eparaksts_mobile", + "name": "eParaksts mobile", + "issuing_authority": "LVRTC (Latvian State Radio and Television Centre)" + }, + "country": "LVA", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/eparaksts-mobile/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"LVA\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'LVA',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/eparaksts-mobile/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "eparaksts_mobile" + } + }, + "/v3/id-verification/wallets/bankid-cz/": { + "post": { + "operationId": "start_bankid_cz_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_bankid_cz", + "auth": "api_key", + "server": true + }, + "description": "Start a Bank iD sign-in (Bankovní identita, a.s.; CZE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Bank iD verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidCzWalletVerificationStartRequest" + }, + "example": { + "country": "CZE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidCzWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidCzWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "bankid_cz", + "name": "Bank iD", + "issuing_authority": "Bankovní identita, a.s." + }, + "country": "CZE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/bankid-cz/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"CZE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'CZE',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/bankid-cz/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "bankid_cz" + } + }, + "/v3/id-verification/wallets/mojeid/": { + "post": { + "operationId": "start_mojeid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_mojeid", + "auth": "api_key", + "server": true + }, + "description": "Start a MojeID sign-in (CZ.NIC; CZE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start MojeID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMojeidWalletVerificationStartRequest" + }, + "example": { + "country": "CZE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMojeidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMojeidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "mojeid", + "name": "MojeID", + "issuing_authority": "CZ.NIC" + }, + "country": "CZE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/mojeid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"CZE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'CZE',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/mojeid/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "mojeid" + } + }, + "/v3/id-verification/wallets/diia/": { + "post": { + "operationId": "start_diia_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_diia", + "auth": "api_key", + "server": true + }, + "description": "Start a Diia sign-in (Ministry of Digital Transformation of Ukraine; UKR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Diia verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiDiiaWalletVerificationStartRequest" + }, + "example": { + "country": "UKR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiDiiaWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiDiiaWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "diia", + "name": "Diia", + "issuing_authority": "Ministry of Digital Transformation of Ukraine" + }, + "country": "UKR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/diia/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"UKR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'UKR',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/diia/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "diia" + } + }, + "/v3/id-verification/wallets/franceconnect/": { + "post": { + "operationId": "start_franceconnect_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_franceconnect", + "auth": "api_key", + "server": true + }, + "description": "Start a FranceConnect sign-in (DINUM (French state); FRA) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start FranceConnect verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFranceconnectWalletVerificationStartRequest" + }, + "example": { + "country": "FRA", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFranceconnectWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFranceconnectWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "franceconnect", + "name": "FranceConnect", + "issuing_authority": "DINUM (French state)" + }, + "country": "FRA", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/franceconnect/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"FRA\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'FRA',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/franceconnect/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "franceconnect" + } + }, + "/v3/id-verification/wallets/audkenni/": { + "post": { + "operationId": "start_audkenni_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_audkenni", + "auth": "api_key", + "server": true + }, + "description": "Start a Auðkenni sign-in (Auðkenni (Icelandic electronic ID); ISL) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Auðkenni verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiAudkenniWalletVerificationStartRequest" + }, + "example": { + "country": "ISL", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiAudkenniWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiAudkenniWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "audkenni", + "name": "Auðkenni", + "issuing_authority": "Auðkenni (Icelandic electronic ID)" + }, + "country": "ISL", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/audkenni/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"ISL\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'ISL',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/audkenni/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "audkenni" + } + }, + "/v3/id-verification/wallets/connectid/": { + "post": { + "operationId": "start_connectid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_connectid", + "auth": "api_key", + "server": true + }, + "description": "Start a ConnectID sign-in (Australian Payments Plus; AUS) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start ConnectID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiConnectidWalletVerificationStartRequest" + }, + "example": { + "country": "AUS", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiConnectidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiConnectidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "connectid", + "name": "ConnectID", + "issuing_authority": "Australian Payments Plus" + }, + "country": "AUS", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/connectid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"AUS\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'AUS',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/connectid/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "connectid" + } + }, + "/v3/id-verification/wallets/eudi/": { + "post": { + "operationId": "start_eudi_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_eudi", + "auth": "api_key", + "server": true + }, + "description": "Start a EUDI Wallet sign-in (The user's own member state; the issuer differs per country; AUT, BEL, BGR, HRV, CYP, CZE, DNK, EST, FIN, FRA, DEU, GRC, HUN, IRL, ITA, LVA, LTU, LUX, MLT, NLD, POL, PRT, ROU, SVK, SVN, ESP, SWE, ISL, LIE, NOR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start EUDI Wallet verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEudiWalletVerificationStartRequest" + }, + "example": { + "country": "AUT", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEudiWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEudiWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "eudi", + "name": "EUDI Wallet", + "issuing_authority": "The user's own member state; the issuer differs per country" + }, + "country": "AUT", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing or invalid credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/eudi/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"AUT\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nimport requests\n\nheaders = {\n \"x-api-key\": os.environ[\"DIDIT_API_KEY\"],\n \"Idempotency-Key\": \"your-unique-attempt-key\"\n}\npayload = {'country': 'AUT',\n 'vendor_data': 'your-customer-reference',\n 'metadata': {'reference': 'example'},\n 'return_url': 'https://app.example.com/identity/complete'}\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/id-verification/wallets/eudi/\",\n headers=headers,\n json=payload,\n timeout=30,\n)\nresponse.raise_for_status()\nprint(response.json())\n" + } + ], + "x-didit-wallet-id": "eudi" + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/teams/": { + "get": { + "summary": "List review teams", + "description": "Requires `list:cases` for the requested application.", + "operationId": "list_case_teams", + "x-didit-sdk": { + "resource": "cases.teams", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000000, + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseReviewTeam" + } + }, + "count": { + "type": "integer", + "description": "Matching count, capped at 100. Follow next to continue beyond the cap." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create review team", + "description": "Requires `write:cases` for the requested application.", + "operationId": "create_case_teams", + "x-didit-sdk": { + "resource": "cases.teams", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "responses": { + "201": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeam" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeamCreate" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/fraud/checks/{check_id}/": { + "get": { + "operationId": "root_retrieve", + "x-didit-sdk": { + "resource": "fraud_checks", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "description": "Fetch a previously created check by its `check_id`.", + "summary": "Retrieve a Fraud Check", + "parameters": [ + { + "in": "path", + "name": "check_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckResponse" + } + } + }, + "description": "" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudClientCredentialsErrorMessage" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "tags": [ + "Fraud Check" + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/": { + "get": { + "operationId": "organization_application_cases_requests_list", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "name": "ordering", + "required": false, + "in": "query", + "description": "Which field to use when ordering the results.", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "offset", + "required": false, + "in": "query", + "description": "The initial index from which to return the results.", + "schema": { + "type": "integer" + } + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingPaginatedCaseRequestList" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "operationId": "organization_application_cases_requests_create", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestConfirmRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestConfirmRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestConfirmRequest" + } + } + }, + "required": true + }, + "responses": { + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "400": { + "description": "Invalid inputs, unsupported purpose, unavailable verified contact or an existing immutable response." + }, + "409": { + "description": "The preview changed or expired. Preview again before confirming." + }, + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequest" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Confirm the exact signed preview within 15 minutes. Only fixed templates and an OTP-verified contact belonging to the case subject are accepted. The request ID makes retries idempotent. Delivery is queued; SENT, SIMULATED and UNKNOWN are distinct outcomes. An actionable request sets the case to Awaiting user. A review update leaves its status unchanged." + } + }, + "/v3/fraud/checks/": { + "post": { + "operationId": "create_fraud_check", + "x-didit-sdk": { + "resource": "fraud_checks", + "method": "create", + "auth": "api_key", + "server": true + }, + "description": "Submit one or more typed identifiers and receive normalization, fresh enrichment, authoritative validation where the country and identifier type are covered, independent risk dimensions and a recommendation in one response.\n\nThese profiles support identifier checks. Selfie-based identity binding (`biometric_identity`) requires profile version `2026-09-14.2` and a Brazilian CPF; it runs only with the user's consent and when your organization is entitled to it. Earlier versions report it as `not_available_in_profile_version`. Modules that cannot run are reported as skipped with a deterministic reason; a source that could not answer is never reported as an identity mismatch.", + "summary": "Run a Fraud Check", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckResponse" + } + } + }, + "description": "" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudClientCredentialsErrorMessage" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "" + }, + "409": { + "description": "Conflict, returned without a charge: the `client_reference` was already used with a different request body; a check with the same `client_reference` is still running (retry after `Retry-After` seconds with the same reference); or the verification this check records into was deleted before the check completed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + }, + "examples": { + "different_body": { + "summary": "Reference reused with a different body", + "value": { + "error": "client_reference has already been used with a different request body." + } + }, + "still_running": { + "summary": "A check with this reference is still running", + "value": { + "error": "A check with this client_reference is already running. Retry with the same reference later." + } + }, + "verification_deleted": { + "summary": "The verification was deleted before the check completed", + "value": { + "error": "This verification was deleted before the check completed." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying, sent when a check with the same `client_reference` is still running.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "tags": [ + "Fraud Check" + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/{request_uuid}/cancel/": { + "post": { + "operationId": "organization_application_cases_requests_cancel_create", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "cancel", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequest" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "400": { + "description": "Invalid inputs, unsupported purpose, unavailable verified contact or an existing immutable response." + }, + "409": { + "description": "The preview changed or expired. Preview again before confirming." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/options/": { + "get": { + "operationId": "organization_application_cases_requests_options_retrieve", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "retrieve_options", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestOptions" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/{request_uuid}/attachment/": { + "get": { + "operationId": "organization_application_cases_requests_attachment_retrieve", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "retrieve_attachment", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "image/jpeg": { + "schema": { + "type": "string", + "format": "binary" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/{record_uuid}/history/": { + "get": { + "operationId": "list_case_routing_rule_history", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "list_history", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "name": "ordering", + "required": false, + "in": "query", + "description": "Which field to use when ordering the results.", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "offset", + "required": false, + "in": "query", + "description": "The initial index from which to return the results.", + "schema": { + "type": "integer" + } + } + ], + "tags": [ + "Case Assignment" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPaginatedPolicyRevisionList" + } + } + }, + "description": "" + }, + "403": { + "description": "Case access is required." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/resubmission-preview/": { + "post": { + "summary": "Preview a resubmission email", + "description": "Render the email that would accompany a KYC or KYB resubmission without changing the session, resetting checks, extending its expiry, running checks or sending mail. Requires the `write:sessions` privilege (a scoped API key needs write access to decisions) and a session in `Approved`, `Declined`, `In Review`, `Kyc Expired` or `Abandoned` status. The server validates the selected workflow steps and renders its fixed translated template with the applicable verified white-label sender and branding. The request cannot set a subject, message body or action URL.\n\n`email_address` is optional. Without it, the email goes to the person's own address: the one they verified in an email verification step of the flow, else the one given in `contact_details` when the session was created. An address you pass wins (`recipient_source: request`). When no address is known and you pass none, `recipient` and `preview_token` are empty strings and no email can be sent.\n\nThe response includes the recipient and where it comes from, the sender, subject, HTML, language, resolved steps, whether confirming sends the email (`will_be_sent`, `not_sent_reason`) and a signed `preview_token`. `requires_user_action` is `false` when every selected step runs without the person: the resubmission then finishes on its own and no email goes out (`not_sent_reason: no_user_action`).\n\nThis preview does not reserve session state or authorize delivery. After reviewing it, call [Update Session Status](/sessions-api/update-status) with `new_status: Resubmitted`, the same `nodes_to_resubmit` and `email_language` you previewed, `send_email: true`, `email_address` set to `recipient` and `email_preview_token` set to `preview_token`. The receipt expires after 15 minutes. When a receipt is supplied, changed inputs, message content, sender or session state return `409` before any mutation or send. Existing integrations may omit the receipt. Regenerate the preview after a conflict. Treat the response as sensitive verification content; responses use `Cache-Control: no-store`. Render HTML in a sandboxed frame, never directly in your application DOM.", + "operationId": "post_v3_session_resubmission_preview", + "x-didit-sdk": { + "resource": "sessions", + "method": "preview_resubmission", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session to update. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "nodes_to_resubmit": { + "type": "array", + "description": "Steps to preview, using the same validation, selection and workflow ordering as a resubmission. Omit to auto-select recorded attempts needing resubmission.", + "items": { + "type": "object", + "required": [ + "node_id", + "feature" + ], + "properties": { + "node_id": { + "type": "string", + "description": "Node identifier as it appears in the session's workflow definition (for example `feature_ocr`, `feature_liveness`).", + "example": "feature_ocr" + }, + "feature": { + "type": "string", + "description": "Feature type of the node. The aliases `ID_VERIFICATION`, `POA`, `PHONE`, and `EMAIL` are normalized server-side to `OCR`, `PROOF_OF_ADDRESS`, `PHONE_VERIFICATION`, and `EMAIL_VERIFICATION`. `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and `KYB` pass schema validation but are always rejected with the business-rule `400` shown in the examples.", + "enum": [ + "OCR", + "OCR_BACK", + "NFC", + "AML", + "FACE", + "LIVENESS", + "FACE_MATCH", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "PROOF_OF_ADDRESS", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "FACE_SEARCH", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_DOCUMENTS", + "ID_VERIFICATION", + "POA", + "PHONE", + "EMAIL", + "KYB_REGISTRY", + "KYB_KEY_PEOPLE", + "KYB" + ], + "example": "OCR" + } + } + } + }, + "email_address": { + "type": "string", + "format": "email", + "description": "Optional. Send the email to this address instead of the person's own. Without it, the recipient is the address the person verified in the flow, else the one given in `contact_details`. No email is sent by this call.", + "example": "user@example.com" + }, + "email_language": { + "type": "string", + "description": "Language for the notification email. Accepts any string at schema level; unsupported codes silently fall back to English (`en`).", + "default": "en", + "example": "en" + } + } + }, + "examples": { + "Person's Own Address": { + "summary": "Preview a proof-of-address resubmission for the person's own address", + "value": { + "nodes_to_resubmit": [ + { + "node_id": "feature_poa_1", + "feature": "POA" + } + ], + "email_language": "en" + } + }, + "Named Recipient": { + "summary": "Preview the email for an address you name", + "value": { + "nodes_to_resubmit": [ + { + "node_id": "feature_ocr", + "feature": "OCR" + } + ], + "email_address": "person@example.com", + "email_language": "en" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Preview only. The session is unchanged and no email is sent.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "recipient", + "recipient_source", + "sender", + "sender_name", + "sender_email", + "sender_kind", + "subject", + "html", + "language", + "will_be_sent", + "not_sent_reason", + "preview_token", + "nodes_to_resubmit", + "requires_user_action" + ], + "properties": { + "recipient": { + "type": "string", + "description": "The address the email goes to. An empty string when no address is known for the person: then no email can be sent and `preview_token` is empty too. Pass it as `email_address` when you confirm.", + "example": "person@example.com" + }, + "recipient_source": { + "type": "string", + "enum": [ + "email_verification", + "contact_details", + "request", + "" + ], + "description": "Where `recipient` comes from. `request`: the `email_address` you passed. `email_verification`: the address the person verified in an email verification step of the flow. `contact_details`: the address given in `contact_details` when the session was created. An empty string when there is no address.", + "example": "email_verification" + }, + "sender": { + "type": "string", + "description": "The `From` header the email goes out with.", + "example": "Example App " + }, + "sender_name": { + "type": "string", + "description": "The display name part of `sender`.", + "example": "Example App" + }, + "sender_email": { + "type": "string", + "format": "email", + "description": "The address part of `sender`.", + "example": "no-reply@mail.example.com" + }, + "sender_kind": { + "type": "string", + "enum": [ + "customer", + "didit" + ], + "description": "`customer`: the email goes out from your application's verified sender domain (white label). `didit`: it goes out from Didit's own sender.", + "example": "customer" + }, + "subject": { + "type": "string", + "description": "The subject line exactly as it is sent.", + "example": "Action Required: Complete your verification in Example App" + }, + "html": { + "type": "string", + "description": "The email's HTML exactly as it is sent, with its logo embedded as a data URL. Render it in a sandboxed frame, never directly in your application DOM.", + "example": "..." + }, + "language": { + "type": "string", + "description": "The language the email is written in. English (`en`) when the `email_language` you asked for is not offered.", + "example": "en" + }, + "will_be_sent": { + "type": "boolean", + "description": "Whether confirming the change with `send_email: true` sends this email. `false` when `not_sent_reason` is set.", + "example": true + }, + "not_sent_reason": { + "type": "string", + "nullable": true, + "enum": [ + "no_user_action", + "no_recipient", + "sandbox", + null + ], + "description": "Why confirming would send no email, or `null` when it will be sent. `no_user_action`: every selected step runs without the person, so the resubmission finishes on its own and no email goes out. `no_recipient`: no address is known for the person and you passed none. `sandbox`: a sandbox session whose recipient has not opted into sandbox email delivery, so the send is only simulated.", + "example": null + }, + "nodes_to_resubmit": { + "type": "array", + "items": { + "type": "object", + "required": [ + "node_id", + "feature" + ], + "properties": { + "node_id": { + "type": "string", + "description": "Node identifier as it appears in the session's workflow definition (for example `feature_ocr`, `feature_liveness`).", + "example": "feature_ocr" + }, + "feature": { + "type": "string", + "description": "Feature type of the node. The aliases `ID_VERIFICATION`, `POA`, `PHONE`, and `EMAIL` are normalized server-side to `OCR`, `PROOF_OF_ADDRESS`, `PHONE_VERIFICATION`, and `EMAIL_VERIFICATION`. `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and `KYB` pass schema validation but are always rejected with the business-rule `400` shown in the examples.", + "enum": [ + "OCR", + "OCR_BACK", + "NFC", + "AML", + "FACE", + "LIVENESS", + "FACE_MATCH", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "PROOF_OF_ADDRESS", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "FACE_SEARCH", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_DOCUMENTS", + "ID_VERIFICATION", + "POA", + "PHONE", + "EMAIL", + "KYB_REGISTRY", + "KYB_KEY_PEOPLE", + "KYB" + ], + "example": "OCR" + } + } + } + }, + "requires_user_action": { + "type": "boolean", + "description": "Whether at least one selected check requires user interaction. This does not predict the final workflow outcome." + }, + "preview_token": { + "type": "string", + "description": "Signed receipt for this exact email, valid for 15 minutes. Pass it as `email_preview_token` to [Update Session Status](/sessions-api/update-status). An empty string when there is no recipient.", + "example": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + } + }, + "examples": { + "Will Be Sent": { + "summary": "Default recipient: the address verified in the flow", + "value": { + "recipient": "person@example.com", + "recipient_source": "email_verification", + "sender": "Example App ", + "sender_name": "Example App", + "sender_email": "no-reply@mail.example.com", + "sender_kind": "customer", + "subject": "Action Required: Complete your verification in Example App", + "html": "...", + "language": "en", + "will_be_sent": true, + "not_sent_reason": null, + "nodes_to_resubmit": [ + { + "node_id": "feature_poa_1", + "feature": "PROOF_OF_ADDRESS" + } + ], + "requires_user_action": true, + "preview_token": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + }, + "No User Action": { + "summary": "Only backend steps are selected, so no email goes out", + "value": { + "recipient": "person@example.com", + "recipient_source": "contact_details", + "sender": "Example App ", + "sender_name": "Example App", + "sender_email": "no-reply@mail.example.com", + "sender_kind": "customer", + "subject": "Action Required: Complete your verification in Example App", + "html": "...", + "language": "en", + "will_be_sent": false, + "not_sent_reason": "no_user_action", + "nodes_to_resubmit": [ + { + "node_id": "feature_aml", + "feature": "AML" + } + ], + "requires_user_action": false, + "preview_token": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + } + } + } + }, + "headers": { + "Cache-Control": { + "description": "Always `no-store`: the preview carries verification content and must not be cached.", + "schema": { + "type": "string", + "example": "no-store" + } + } + } + }, + "400": { + "description": "The session is not in a status this endpoint accepts, it is already `Resubmitted`, the selected steps cannot be resubmitted, or a field is invalid.", + "content": { + "application/json": { + "examples": { + "Wrong Current Status": { + "summary": "Session is not in a status this endpoint accepts", + "value": { + "detail": "Only sessions with status Approved, Declined, In Review, Kyc Expired, Abandoned, Resubmitted can be updated. Current status: In Progress" + } + }, + "Already Resubmitted": { + "summary": "The session is already Resubmitted", + "value": [ + "The new status is the same as the current status." + ] + }, + "Nothing To Resubmit": { + "summary": "Auto-select found no step that needs resubmission", + "value": { + "nodes_to_resubmit": "No features found that need resubmission. All features are already approved." + } + }, + "Invalid Email": { + "summary": "email_address is not an email address", + "value": { + "email_address": [ + "Enter a valid email address." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing or invalid credentials, a caller without the `write:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no write access to decisions." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "The steps to resubmit are all backend-only (AML, DATABASE_VALIDATION, IP_ANALYSIS), and the session was already resubmitted for backend-only steps within the last 30 seconds, so Update Session Status would refuse the resubmission. Retry after the interval in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Backend-only resubmit throttled": { + "value": { + "detail": "This session was already resubmitted for backend-only features within the last 30 seconds; repeating it cannot change the outcome. Wait before resubmitting again." + } + } + } + } + } + } + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/{record_uuid}/": { + "get": { + "summary": "Retrieve routing rule", + "description": "Requires `read:cases` for the requested application.", + "operationId": "retrieve_case_routing_rules", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "record_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRule" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "404": { + "description": "Record not found in this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update routing rule", + "description": "Requires `write:cases` for the requested application.", + "operationId": "update_case_routing_rules", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "record_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRule" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "404": { + "description": "Record not found in this application." + }, + "409": { + "description": "The routing rule changed since expected_version. Review the latest rule before retrying." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRuleUpdate" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/case-requests/{request_uuid}/": { + "get": { + "operationId": "case_requests_retrieve", + "x-didit-sdk": { + "resource": "case_requests", + "method": "retrieve", + "auth": "device_token", + "server": false + }, + "parameters": [ + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPublic" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + } + }, + "security": [ + { + "CaseRequestToken": [] + } + ], + "description": "Access one customer request using its request-specific token. No private case findings, original identity data or recipient address are returned. Actionable requests expire after seven days. Cancellation or case resolution invalidates the link. Responses are immutable and remain evidence for reviewer assessment; they never approve or update a source verification." + }, + "post": { + "operationId": "case_requests_create", + "x-didit-sdk": { + "resource": "case_requests", + "method": "respond", + "auth": "device_token", + "server": false + }, + "parameters": [ + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseCustomerResponseRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseCustomerResponseRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseCustomerResponseResult" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "400": { + "description": "Invalid inputs, unsupported purpose, unavailable verified contact or an existing immutable response." + }, + "409": { + "description": "The preview changed or expired. Preview again before confirming." + } + }, + "security": [ + { + "CaseRequestToken": [] + } + ], + "description": "Access one customer request using its request-specific token. No private case findings, original identity data or recipient address are returned. Actionable requests expire after seven days. Cancellation or case resolution invalidates the link. Responses are immutable and remain evidence for reviewer assessment; they never approve or update a source verification." + } + }, + "/v3/fraud/checks/plan/": { + "post": { + "operationId": "plan_create", + "x-didit-sdk": { + "resource": "fraud_checks", + "method": "plan", + "auth": "api_key", + "server": true + }, + "description": "Validate selected checks and estimate their price without executing, creating a session or billing.", + "summary": "Plan a Fraud Check", + "tags": [ + "Fraud Check" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckPlan" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudBadRequestErrorMessage" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/": { + "get": { + "summary": "List routing rules", + "description": "Requires `list:cases` for the requested application.", + "operationId": "list_case_routing_rules", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000000, + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseRoutingRule" + } + }, + "count": { + "type": "integer", + "description": "Matching count, capped at 100. Follow next to continue beyond the cap." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create routing rule", + "description": "Requires `write:cases` for the requested application. Set publish=false to create an unpublished draft; Publish creates its first live version. Discarding a never-published draft deletes it.", + "operationId": "create_case_routing_rules", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "responses": { + "201": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRule" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRuleCreate" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/{record_uuid}/publish/": { + "post": { + "operationId": "publish_case_routing_rule", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "publish", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Assignment" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftPreconditionRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftPreconditionRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftPreconditionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationCaseRoutingRule" + } + } + }, + "description": "" + }, + "403": { + "description": "Case access is required." + }, + "409": { + "description": "The published version or draft revision has changed. Reload before retrying." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/{blueprint_uuid}/publish/": { + "post": { + "operationId": "publish_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "publish", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftPreconditionRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftPreconditionRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftPreconditionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationCaseBlueprint" + } + } + }, + "description": "" + }, + "409": { + "description": "Published version or saved draft revision changed. Reload before retrying." + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/preview/": { + "post": { + "operationId": "organization_application_cases_requests_preview_create", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "preview", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPreviewRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPreviewRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPreviewRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPreviewResponse" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "400": { + "description": "Invalid inputs, unsupported purpose, unavailable verified contact or an existing immutable response." + }, + "409": { + "description": "The preview changed or expired. Preview again before confirming." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Render the production email without sending or changing the case. Returns the exact recipient, sender, subject, body and a signed preview_token. Send the same contact_id, purpose, language, request_id, expires_at and preview_token to confirm within 15 minutes." + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/action-receipts/": { + "get": { + "operationId": "action_receipts_retrieve", + "x-didit-sdk": { + "resource": "cases", + "method": "list_action_receipts", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "query", + "name": "offset", + "schema": { + "type": "integer", + "maximum": 1000000, + "minimum": 0, + "default": 0 + } + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseActionReceiptPage" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "List subject-action receipts, newest first, with at most 20 results per page. Use next_offset for the next page. Input note text is not exposed in the history." + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/notes/{note_uuid}/": { + "delete": { + "operationId": "delete_network_note", + "x-didit-sdk": { + "resource": "networks.notes", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "summary": "Delete your network note", + "description": "Requires write:networks and the authenticated author of the note. Deletion is irreversible. Connected account and verification data are unaffected.", + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Network UUID." + }, + { + "name": "note_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Console user access token in the form Bearer ." + } + ], + "responses": { + "204": { + "description": "Note deleted." + }, + "401": { + "description": "Authentication is required." + }, + "403": { + "description": "Missing permission or Networks access." + }, + "404": { + "description": "Network or note is not visible to this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/action-receipts/{receipt_uuid}/retry/": { + "post": { + "operationId": "action_receipts_retry_create", + "x-didit-sdk": { + "resource": "cases", + "method": "retry_action_receipt", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "receipt_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseActionReceipt" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Retry only this failed subject action using its saved inputs. Current write permission is checked. Applied actions return their receipt without executing again. Case closure is independent." + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/{blueprint_uuid}/draft/": { + "get": { + "operationId": "get_case_blueprint_draft", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "retrieve_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftResponse" + } + } + }, + "description": "" + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "put": { + "operationId": "save_case_blueprint_draft", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "save_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftWriteRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftWriteRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftWriteRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraft" + } + } + }, + "description": "" + }, + "409": { + "description": "Published version or saved draft revision changed. Reload before retrying." + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "operationId": "discard_case_blueprint_draft", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "discard_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "204": { + "description": "No response body" + }, + "409": { + "description": "Published version or saved draft revision changed. Reload before retrying." + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftPreconditionRequest" + } + } + } + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/teams/{record_uuid}/": { + "get": { + "summary": "Retrieve review team", + "description": "Requires `read:cases` for the requested application.", + "operationId": "retrieve_case_teams", + "x-didit-sdk": { + "resource": "cases.teams", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "record_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeam" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "404": { + "description": "Record not found in this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update review team", + "description": "Requires `write:cases` for the requested application.", + "operationId": "update_case_teams", + "x-didit-sdk": { + "resource": "cases.teams", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "record_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeam" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "404": { + "description": "Record not found in this application." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeamUpdate" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/report/": { + "get": { + "operationId": "download_network_report", + "x-didit-sdk": { + "resource": "networks", + "method": "download_report", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "summary": "Download a network evidence report", + "description": "Download a PDF of the current organization network, its observed account connections, signal references and review status. Requires read:networks. Global intelligence and private analyst notes are not included. Each account and signal list is capped at 500 entries and the report states when it is incomplete. The request does not change network status. Errors are returned as JSON.", + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Network UUID." + }, + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Console user access token in the form Bearer ." + } + ], + "responses": { + "200": { + "description": "Network evidence report. Cache-Control: no-store.", + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "description": "Authentication is required." + }, + "403": { + "description": "Missing permission or Networks access." + }, + "404": { + "description": "Network or note is not visible to this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/notes/": { + "get": { + "operationId": "list_network_notes", + "x-didit-sdk": { + "resource": "networks.notes", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "summary": "List private network notes", + "description": "Console users with read:networks can read notes on a network visible to the application. Notes stay within the organization and are not contributed to shared intelligence. Newest notes appear first.", + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Network UUID." + }, + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Console user access token in the form Bearer ." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 30 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000000, + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "A page of notes. Count is capped at 100; use next to continue.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkNote" + } + } + } + } + } + } + }, + "401": { + "description": "Authentication is required." + }, + "403": { + "description": "Missing permission or Networks access." + }, + "404": { + "description": "Network or note is not visible to this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "operationId": "create_network_note", + "x-didit-sdk": { + "resource": "networks.notes", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "summary": "Add a private network note", + "description": "Requires a console user with write:networks. The author is assigned from the authenticated user. Supply a new random UUID client_reference for each note; retry the same body and reference after an uncertain response to avoid duplicates. Reusing a reference for another body, author or network returns 409. Notes follow the surviving investigation when networks merge.", + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Network UUID." + }, + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Console user access token in the form Bearer ." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "comment", + "client_reference" + ], + "properties": { + "comment": { + "type": "string", + "minLength": 1, + "maxLength": 10000 + }, + "client_reference": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Saved private note.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkNote" + } + } + } + }, + "200": { + "description": "Saved private note.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkNote" + } + } + } + }, + "400": { + "description": "Invalid or blank note." + }, + "409": { + "description": "The reference was already used for another note." + }, + "401": { + "description": "Authentication is required." + }, + "403": { + "description": "Missing permission or Networks access." + }, + "404": { + "description": "Network or note is not visible to this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/{record_uuid}/draft/": { + "get": { + "operationId": "get_case_routing_rule_draft", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "retrieve_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Assignment" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftResponse" + } + } + }, + "description": "" + }, + "403": { + "description": "Case access is required." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "put": { + "operationId": "save_case_routing_rule_draft", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "save_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Assignment" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftWriteRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftWriteRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftWriteRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraft" + } + } + }, + "description": "" + }, + "403": { + "description": "Case access is required." + }, + "409": { + "description": "The published version or draft revision has changed. Reload before retrying." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "operationId": "discard_case_routing_rule_draft", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "discard_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Assignment" + ], + "responses": { + "204": { + "description": "No response body" + }, + "403": { + "description": "Case access is required." + }, + "409": { + "description": "The published version or draft revision has changed. Reload before retrying." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftPreconditionRequest" + } + } + } + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/duplicates/": { + "post": { + "operationId": "duplicates_create", + "x-didit-sdk": { + "resource": "cases", + "method": "find_duplicates", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicateQueryRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicateQueryRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicateQueryRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicatePage" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Review existing cases for a subject or owned source record before manual case creation. Both open and resolved investigations are returned. Each page contains at most 20 cases; use next_offset to request another page." + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/preview/": { + "post": { + "summary": "Test case routing", + "description": "Requires `read:cases` for the requested application. Evaluates current rules or a supplied draft against current reviewer access, availability, skills and capacity. It does not save the draft or reserve reviewer capacity.", + "operationId": "preview_case_routing", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "preview", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingPreview" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingPreviewRequest" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/reviewers/": { + "get": { + "summary": "List eligible case reviewers", + "description": "Requires `list:cases` for the requested application.", + "operationId": "list_case_reviewers", + "x-didit-sdk": { + "resource": "cases", + "method": "list_reviewers", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "format": "uuid" + }, + "email": { + "type": "string", + "format": "email" + }, + "name": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "503": { + "description": "Reviewer access could not be verified. Retry later." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/{blueprint_uuid}/history/": { + "get": { + "operationId": "list_case_blueprint_history", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "list_history", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "name": "ordering", + "required": false, + "in": "query", + "description": "Which field to use when ordering the results.", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "offset", + "required": false, + "in": "query", + "description": "The initial index from which to return the results.", + "schema": { + "type": "integer" + } + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPaginatedPolicyRevisionList" + } + } + }, + "description": "" + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/signatures/{nodeId}/documents/{documentId}/file/": { + "get": { + "summary": "Download a signed document", + "description": "Download the signed PDF of one document of an Electronic Signature step. Use the `signed_file` path of `signatures[].documents[]` in the decision. The PDF holds the document the signer was shown with their field values stamped, the evidence page (unless `signature_evidence_page` is off) and the PAdES signature with its timestamp. Didit checks the file against `sha256_signed` before returning it.\n\nWorks for User Verification (KYC) and Business Verification (KYB) sessions. Needs `read:sessions`; your API key works. See the [Electronic Signature report](/core-technology/electronic-signature/report-electronic-signature#download-the-signed-documents).", + "operationId": "get_v3_session_signature_document_file", + "x-didit-sdk": { + "resource": "sessions.signatures", + "method": "download_document", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "The User Verification (KYC) or Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "nodeId", + "in": "path", + "required": true, + "description": "The Electronic Signature step: `node_id` of the item in `signatures[]`.", + "schema": { + "type": "string", + "example": "feature_signature" + } + }, + { + "name": "documentId", + "in": "path", + "required": true, + "description": "`id` of the document in `signatures[].documents[]`.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "The signed PDF.", + "headers": { + "Content-Disposition": { + "description": "Attachment with the file name.", + "schema": { + "type": "string", + "example": "attachment; filename=\"agreement-signed.pdf\"" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "403": { + "description": "Missing or invalid credentials, or a credential that can't read this session. Didit's API-key authentication answers `403`, never `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "You do not have permission to perform this action." + } + } + } + }, + "404": { + "description": "No signed document matches: the session, step or document doesn't exist, was deleted, or isn't signed yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not signed yet": { + "summary": "The signer hasn't signed", + "value": { + "code": "signature_not_signed", + "detail": "signature_not_signed" + } + }, + "Unknown document": { + "summary": "No such document in this step", + "value": { + "code": "signature_not_found", + "detail": "signature_not_found" + } + } + } + } + } + }, + "409": { + "description": "The stored file no longer matches its recorded SHA-256. Nothing is returned.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Changed file": { + "summary": "Stored file doesn't match its hash", + "value": { + "code": "signature_document_changed", + "detail": "signature_document_changed" + } + } + } + } + } + } + } + } + }, + "/v3/session/{sessionId}/signatures/{nodeId}/evidence/": { + "get": { + "summary": "Download the signature evidence", + "description": "Download a signer's evidence PDF: the level and format, the signer and their verified identity session, the code confirmation, the IP address, each document with its original SHA-256 and field values, the certificate, the timestamp and the SHA-256 of every signed file. Use the `evidence_file` path of the signer's item in `signatures[]`.\n\nAlways pass `signature_id` when a Business Verification step has several signers: without it, the route picks one of the step's signers. Needs `read:sessions`; your API key works.", + "operationId": "get_v3_session_signature_evidence", + "x-didit-sdk": { + "resource": "sessions.signatures", + "method": "download_evidence", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "The User Verification (KYC) or Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "nodeId", + "in": "path", + "required": true, + "description": "The Electronic Signature step: `node_id` of the item in `signatures[]`.", + "schema": { + "type": "string", + "example": "feature_signature" + } + }, + { + "name": "signature_id", + "in": "query", + "required": false, + "description": "`id` of the signer's item in `signatures[]`.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "The evidence PDF.", + "headers": { + "Content-Disposition": { + "description": "Attachment with the file name.", + "schema": { + "type": "string", + "example": "attachment; filename=\"signature-evidence.pdf\"" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "403": { + "description": "Missing or invalid credentials, or a credential that can't read this session. Didit's API-key authentication answers `403`, never `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "You do not have permission to perform this action." + } + } + } + }, + "404": { + "description": "No evidence matches: the session, step or signer doesn't exist, was deleted, or hasn't signed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not signed yet": { + "summary": "The signer hasn't signed", + "value": { + "code": "signature_not_signed", + "detail": "signature_not_signed" + } + }, + "Unknown signer": { + "summary": "No such signature in this step", + "value": { + "code": "signature_not_found", + "detail": "signature_not_found" + } + } + } + } + } + }, + "409": { + "description": "The stored file no longer matches its recorded SHA-256. Nothing is returned.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Changed file": { + "summary": "Stored file doesn't match its hash", + "value": { + "code": "signature_document_changed", + "detail": "signature_document_changed" + } + } + } + } + } + } + } + } + }, + "/v3/session/{sessionId}/signatures/{nodeId}/fields/{fieldId}/image/": { + "get": { + "summary": "Download a signature image", + "description": "Download the signature or initials a signer applied to a field, as PNG. Use the `image_url` of the field in `signatures[].documents[].fields[]`. Needs `read:sessions`; your API key works.", + "operationId": "get_v3_session_signature_field_image", + "x-didit-sdk": { + "resource": "sessions.signatures", + "method": "download_field_image", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "The User Verification (KYC) or Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "nodeId", + "in": "path", + "required": true, + "description": "The Electronic Signature step: `node_id` of the item in `signatures[]`.", + "schema": { + "type": "string", + "example": "feature_signature" + } + }, + { + "name": "fieldId", + "in": "path", + "required": true, + "description": "The field on this signer's copy, as in the field's `image_url`.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "The image.", + "content": { + "image/png": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "400": { + "description": "The stored image no longer matches its recorded SHA-256.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "example": { + "code": "signature_document_changed", + "detail": "signature_document_changed" + } + } + } + }, + "403": { + "description": "Missing or invalid credentials, or a credential that can't read this session. Didit's API-key authentication answers `403`, never `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "You do not have permission to perform this action." + } + } + } + }, + "404": { + "description": "No image matches: the session, step or field doesn't exist, was deleted, or has no applied signature or initials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Unknown field": { + "summary": "No such field", + "value": { + "code": "signature_field_not_found", + "detail": "signature_field_not_found" + } + } + } + } + } + } + } + } + }, + "/v3/session/{sessionId}/signatures/{nodeId}/validate/": { + "post": { + "summary": "Validate signatures again", + "description": "Re-check the signed documents of an Electronic Signature step. Didit re-reads every signed PDF, checks its SHA-256 and validates the PAdES signature, the certificate, the revocation data and the timestamp, then saves the result in each signer's `validation` and returns the updated items. Nothing is signed again. Send `signature_id` to check one signer; without it, every signer of the step is checked, and the call fails if any of them hasn't signed.\n\nNeeds `write:sessions`; your API key works. In the console it's the **Validate again** button.", + "operationId": "post_v3_session_signature_validate", + "x-didit-sdk": { + "resource": "sessions.signatures", + "method": "validate", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "The User Verification (KYC) or Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "nodeId", + "in": "path", + "required": true, + "description": "The Electronic Signature step: `node_id` of the item in `signatures[]`.", + "schema": { + "type": "string", + "example": "feature_signature" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "signature_id": { + "type": "string", + "format": "uuid", + "description": "`id` of one signer's item in `signatures[]`." + } + } + }, + "example": { + "signature_id": "0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10" + } + } + } + }, + "responses": { + "200": { + "description": "The checked signers, with their updated `validation`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "signatures": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SignatureResult" + } + } + } + } + } + } + }, + "400": { + "description": "A stored signed PDF no longer matches the SHA-256 recorded when it was signed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Document changed": { + "summary": "A signed file no longer matches its hash", + "value": { + "code": "signature_document_changed", + "detail": "signature_document_changed" + } + } + } + } + } + }, + "403": { + "description": "Missing or invalid credentials, or a credential that can't read this session. Didit's API-key authentication answers `403`, never `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "You do not have permission to perform this action." + } + } + } + }, + "404": { + "description": "No signature matches: the session, step or signer doesn't exist or was deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Unknown signer": { + "summary": "No such signature in this step", + "value": { + "code": "signature_not_found", + "detail": "signature_not_found" + } + } + } + } + } + }, + "409": { + "description": "A signer of the step hasn't signed, so there's nothing to validate.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not signed": { + "summary": "A signer hasn't signed yet", + "value": { + "code": "signature_not_signed", + "detail": "signature_not_signed" + } + } + } + } + } + } + } + } + }, + "/v3/document-ai/{sessionId}/documents/{documentUuid}/": { + "patch": { + "summary": "Update a Document AI document", + "description": "Correct the fields Document AI extracted from one uploaded document, set its review status, or both, on a KYC or KYB session. `extracted_data` must be a JSON object and is merged key by key: a key you send replaces the stored value (a key sent as `null` is stored as `null`) and keys you leave out keep their values. The document status feeds the Document AI result of its workflow node in the session decision; the session status itself is not recomputed. Every successful call, including one that changes nothing, sends a `data.updated` [webhook](https://docs.didit.me/integration/webhooks) with trigger `manual_review`. A scoped API key needs `decisions: write`; see [Document AI](https://docs.didit.me/core-technology/document-ai/overview).", + "operationId": "patch_v3_document_ai_document", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_document_ai_data", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "documentUuid", + "in": "path", + "required": true, + "description": "UUID of the Document AI document (`document_ai_documents[].items[].uuid` in `GET /v3/session/{sessionId}/decision/`). It must belong to the session in the path.", + "schema": { + "type": "string", + "format": "uuid", + "example": "44444444-5555-6666-7777-888888888888" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "extracted_data": { + "type": "object", + "additionalProperties": true, + "description": "Field keys (the keys you configured for the document) and their corrected values. Must be a JSON object; merged key by key into the stored data. Leave it out rather than sending `null`, which returns `400`.", + "example": { + "invoice_number": "INV-2026-0042" + } + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Not Finished" + ], + "description": "New review status of the document.", + "example": "Approved" + } + } + }, + "example": { + "extracted_data": { + "invoice_number": "INV-2026-0042" + }, + "status": "Approved" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/document-ai/11111111-2222-3333-4444-555555555555/documents/44444444-5555-6666-7777-888888888888/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"extracted_data\": {\"invoice_number\": \"INV-2026-0042\"}, \"status\": \"Approved\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nsession_id = '11111111-2222-3333-4444-555555555555'\ndocument_uuid = '44444444-5555-6666-7777-888888888888'\nresp = requests.patch(\n f'https://verification.didit.me/v3/document-ai/{session_id}/documents/{document_uuid}/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={'extracted_data': {'invoice_number': 'INV-2026-0042'}, 'status': 'Approved'},\n timeout=10,\n)\nresp.raise_for_status()\ndocument = resp.json()\nprint(document['status'], document['extracted_data'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const sessionId = '11111111-2222-3333-4444-555555555555';\nconst documentUuid = '44444444-5555-6666-7777-888888888888';\nconst resp = await fetch(`https://verification.didit.me/v3/document-ai/${sessionId}/documents/${documentUuid}/`, {\n method: 'PATCH',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY, 'Content-Type': 'application/json' },\n body: JSON.stringify({ extracted_data: { invoice_number: 'INV-2026-0042' }, status: 'Approved' }),\n});\nif (!resp.ok) throw new Error(`Update failed: ${resp.status}`);\nconst document = await resp.json();\nconsole.log(document.status, document.extracted_data);" + } + ], + "responses": { + "200": { + "description": "Document updated. Returns the document after the merge.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "uuid", + "document_key", + "original_filename", + "file_url", + "status", + "extracted_data" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Document identifier." + }, + "document_key": { + "type": "string", + "description": "The configured document this upload corresponds to." + }, + "original_filename": { + "type": "string", + "nullable": true, + "description": "File name the document was uploaded with." + }, + "file_url": { + "type": "string", + "nullable": true, + "description": "Storage path of the uploaded file. It is not a download link: read the presigned `file_url` from the session decision to download the file." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Review status of the document." + }, + "extracted_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "All extracted fields after the merge, keyed by field key." + } + } + }, + "example": { + "uuid": "44444444-5555-6666-7777-888888888888", + "document_key": "invoice", + "original_filename": "invoice-2026-09.pdf", + "file_url": "document_ai/11111111-2222-3333-4444-555555555555-66666666-7777-8888-9999-000000000000.pdf", + "status": "Approved", + "extracted_data": { + "invoice_number": "INV-2026-0042", + "total_amount": 1250.0, + "issue_date": "2026-09-01" + } + } + } + } + }, + "400": { + "description": "Validation error, checked before the document is looked up: `status` not one of the allowed values, or `extracted_data` sent as `null`.", + "content": { + "application/json": { + "examples": { + "Invalid status": { + "summary": "status not in the enum", + "value": { + "status": [ + "\"Rejected\" is not a valid choice." + ] + } + }, + "Null extracted_data": { + "summary": "extracted_data is null", + "value": { + "extracted_data": [ + "This field may not be null." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. Missing, invalid and revoked keys return `403` (not `401`); so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits, or no current document with this UUID belongs to it. The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's limits (also returned for a session deleted moments ago)", + "value": { + "detail": "Session not found." + } + }, + "Unknown document": { + "summary": "documentUuid does not belong to the session", + "value": { + "detail": "Document AI document not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/aml/dataset-freshness/": { + "get": { + "summary": "Get AML dataset freshness", + "description": "Report how current the datasets behind AML screening are, per source family (`sanctions`, `pep`, `warnings`, `adverse_media`), as audit evidence for [ongoing AML monitoring](https://docs.didit.me/core-technology/aml-screening/continuous-monitoring-aml-screening#proving-that-monitoring-ran). The answer is platform-wide: no session in the path, no customer data in the body, the same answer for every application.\n\nAlways `200` for an accepted key. Check `availability` first: when it is not `available`, `dataset_freshness` is `null`. Dates are never estimated: a family whose datasets reported no date is absent from `sources`, and a family where only some datasets reported one has `oldest_updated_at: null`. Readings are taken while ongoing monitoring runs, not on each call; `captured_at` says when.\n\nA scoped API key needs `sessions: read`; a key limited to some workflows or to approved sessions cannot call this endpoint. Nothing is billed.", + "operationId": "get_aml_dataset_freshness", + "x-didit-sdk": { + "resource": "aml", + "method": "retrieve_dataset_freshness", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/aml/dataset-freshness/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.get(\n 'https://verification.didit.me/v3/aml/dataset-freshness/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status()\nbody = resp.json()\nif body['availability'] == 'available':\n for family, info in body['dataset_freshness']['sources'].items():\n print(family, info['last_updated_at'], info['oldest_updated_at'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/aml/dataset-freshness/', {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (!resp.ok) throw new Error(`Freshness lookup failed: ${resp.status}`);\nconst body = await resp.json();\nif (body.availability === 'available') {\n for (const [family, info] of Object.entries(body.dataset_freshness.sources)) {\n console.log(family, info.last_updated_at, info.oldest_updated_at);\n }\n}" + } + ], + "responses": { + "200": { + "description": "The latest dataset freshness reading, or `null` with the reason.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "dataset_freshness", + "availability" + ], + "properties": { + "dataset_freshness": { + "allOf": [ + { + "$ref": "#/components/schemas/AMLDatasetFreshness" + } + ], + "nullable": true, + "description": "The latest reading, or `null` when `availability` is not `available`." + }, + "availability": { + "type": "string", + "enum": [ + "available", + "not_configured", + "not_yet_collected" + ], + "description": "`available` when a reading exists; `not_configured` when dataset dates are not collected in this environment; `not_yet_collected` when collection is set up but no reading exists yet." + } + } + }, + "examples": { + "Available": { + "value": { + "dataset_freshness": { + "captured_at": "2026-10-17T06:00:12.345678+00:00", + "provider": "aml_data_source", + "catalog_version": "20261017054500-abc", + "sources": { + "sanctions": { + "last_updated_at": "2026-10-17T04:10:00+00:00", + "oldest_updated_at": "2026-10-15T22:30:00+00:00", + "dataset_count": 48, + "dated_dataset_count": 48 + }, + "pep": { + "last_updated_at": "2026-10-16T23:05:00+00:00", + "oldest_updated_at": null, + "dataset_count": 12, + "dated_dataset_count": 11 + } + } + }, + "availability": "available" + } + }, + "Not collected yet": { + "value": { + "dataset_freshness": null, + "availability": "not_yet_collected" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing or invalid API key gets the generic message (never `401`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions read": { + "value": { + "detail": "This API key has no read access to sessions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/": { + "get": { + "summary": "List lists", + "description": "Returns the application's lists, newest first: the system lists Didit provisions (one blocklist per entry type, the **High Risk Countries** custom list, and any system allowlist) and the allowlists and custom lists you created. Filter with `list_type` and `entry_type`; deleted lists never appear. Results are paginated with `limit` and `offset`. A scoped API key needs `workflows: read`. See [Lists](https://docs.didit.me/management-api/lists/overview) for what each list type does.", + "operationId": "list_lists", + "x-didit-sdk": { + "resource": "lists", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_type", + "in": "query", + "required": false, + "description": "Only lists of this type.", + "schema": { + "type": "string", + "enum": [ + "blocklist", + "allowlist", + "custom" + ] + } + }, + { + "name": "entry_type", + "in": "query", + "required": false, + "description": "Only lists of this entry type.", + "schema": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email", + "ip_address", + "device_fingerprint", + "wallet_address", + "bank_account", + "country", + "user", + "business", + "key" + ] + } + }, + { + "name": "ordering", + "in": "query", + "required": false, + "description": "Sort order: `-created_at` (newest first, the default) or `created_at` (oldest first).", + "schema": { + "type": "string", + "enum": [ + "-created_at", + "created_at" + ], + "default": "-created_at" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Page size. Defaults to 50. A value above 1000000 returns `400`; zero, a negative or a non-numeric value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Zero-based index of the first item to return. Prefer following `next`. A value above 1000000 returns `400`; a negative or non-numeric value falls back to 0.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/lists/?list_type=blocklist' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.get(\n 'https://verification.didit.me/v3/lists/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n params={'list_type': 'blocklist'},\n timeout=10,\n)\nresp.raise_for_status()\nfor item in resp.json()['results']:\n print(item['uuid'], item['entry_type'], item['name'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/lists/?list_type=blocklist', {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (!resp.ok) throw new Error(`Request failed: ${resp.status}`);\nconst page = await resp.json();\nconsole.log(page.results.map((list) => [list.uuid, list.entry_type, list.name]));" + } + ], + "responses": { + "200": { + "description": "A page of lists.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "description": "Total number of lists matching the filters." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ComplianceList" + } + } + } + }, + "examples": { + "Lists": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "uuid": "22222222-3333-4444-5555-666666666666", + "name": "Trusted office IPs", + "description": "Office egress ranges", + "list_type": "allowlist", + "entry_type": "ip_address", + "is_system": false, + "entry_count": 3, + "last_entry_added_at": "2026-10-02T08:15:27.501334Z", + "created_at": "2026-09-30T13:20:45.889120Z", + "updated_at": "2026-09-30T13:20:45.889120Z" + }, + { + "uuid": "11111111-2222-3333-4444-555555555555", + "name": "Face Blocklist", + "description": "Blocked biometric faces. When a face matching an entry in this list is detected during liveness verification, the session will be automatically declined.", + "list_type": "blocklist", + "entry_type": "face", + "is_system": true, + "entry_count": 12, + "last_entry_added_at": "2026-10-09T16:42:03.118204Z", + "created_at": "2026-01-14T10:02:11.402917Z", + "updated_at": "2026-01-14T10:02:11.402917Z" + } + ] + } + } + } + } + } + }, + "400": { + "description": "A query parameter is invalid.", + "content": { + "application/json": { + "examples": { + "Unknown list type": { + "value": { + "list_type": [ + "\"denylist\" is not a valid choice." + ] + } + }, + "Unsupported ordering": { + "value": { + "ordering": [ + "\"name\" is not a valid choice." + ] + } + }, + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing or invalid, or a scoped API key lacks `workflows: read` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no read access to workflows." + } + } + } + } + } + }, + "429": { + "description": "The shared read limit of 600 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create a list", + "description": "Creates an allowlist or a custom list in the application the API key belongs to. Blocklists cannot be created: Didit provisions one system blocklist per entry type, and you add to it with [Create a list entry](https://docs.didit.me/management-api/lists/create-entry). `entry_type` cannot change after creation and decides how entry values are validated and matched. Allowlist entries mark matching items as trusted (for example, a face allowlist skips the duplicated-face action) and never override a blocklist match; reference any list from a workflow or transaction rule condition with `value_type: \"list\"`. A scoped API key needs `workflows: write`.", + "operationId": "create_list", + "x-didit-sdk": { + "resource": "lists", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "list_type", + "entry_type" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "List name. Must differ from the name of every other list of the application, system lists included." + }, + "description": { + "type": "string", + "default": "", + "description": "Optional free-text description." + }, + "list_type": { + "type": "string", + "enum": [ + "allowlist", + "custom" + ], + "description": "`allowlist` or `custom`. `blocklist` is refused with `400`." + }, + "entry_type": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email", + "ip_address", + "device_fingerprint", + "wallet_address", + "bank_account", + "country", + "user", + "business", + "key" + ], + "description": "What the list holds: `face`, `document` (document number), `phone`, `email`, `ip_address` (IP address or CIDR range), `device_fingerprint`, `wallet_address`, `bank_account`, `country` (ISO 3166-1 alpha-3), `user` or `business` (your `vendor_data`), or `key` (any string). It decides how entry values are validated and matched. Cannot be changed later." + } + } + }, + "examples": { + "IP allowlist": { + "value": { + "name": "Trusted office IPs", + "description": "Office egress ranges", + "list_type": "allowlist", + "entry_type": "ip_address" + } + }, + "Custom country list": { + "value": { + "name": "Enhanced due diligence countries", + "list_type": "custom", + "entry_type": "country" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/lists/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"name\": \"Trusted office IPs\",\n \"description\": \"Office egress ranges\",\n \"list_type\": \"allowlist\",\n \"entry_type\": \"ip_address\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/lists/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={\n 'name': 'Trusted office IPs',\n 'description': 'Office egress ranges',\n 'list_type': 'allowlist',\n 'entry_type': 'ip_address',\n },\n timeout=10,\n)\nresp.raise_for_status()\nprint('Created list', resp.json()['uuid'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/lists/', {\n method: 'POST',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n name: 'Trusted office IPs',\n description: 'Office egress ranges',\n list_type: 'allowlist',\n entry_type: 'ip_address',\n }),\n});\nif (!resp.ok) throw new Error(`Create failed: ${resp.status}`);\nconst list = await resp.json();\nconsole.log('Created list', list.uuid);" + } + ], + "responses": { + "201": { + "description": "The list was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ComplianceList" + }, + "examples": { + "New allowlist": { + "value": { + "uuid": "22222222-3333-4444-5555-666666666666", + "name": "Trusted office IPs", + "description": "Office egress ranges", + "list_type": "allowlist", + "entry_type": "ip_address", + "is_system": false, + "entry_count": 0, + "last_entry_added_at": null, + "created_at": "2026-10-12T09:15:44.031876Z", + "updated_at": "2026-10-12T09:15:44.031876Z" + } + } + } + } + } + }, + "400": { + "description": "The body is invalid.", + "content": { + "application/json": { + "examples": { + "Blocklist refused": { + "value": { + "list_type": [ + "Cannot create additional blocklist lists. System blocklists are auto-created for each application." + ] + } + }, + "Duplicate name": { + "value": { + "name": [ + "A list with this name already exists for this application." + ] + } + }, + "Missing fields": { + "value": { + "name": [ + "This field is required." + ], + "list_type": [ + "This field is required." + ], + "entry_type": [ + "This field is required." + ] + } + }, + "Unknown entry type": { + "value": { + "entry_type": [ + "\"iban\" is not a valid choice." + ] + } + }, + "Name too long": { + "value": { + "name": [ + "Ensure this field has no more than 255 characters." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing or invalid, or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/{list_uuid}/": { + "get": { + "summary": "Get a list", + "description": "Returns one list of the application with its entry count and when its newest entry was added. A list of another application, or one that was deleted, returns `404`. This returns the list only: read its values with [List list entries](https://docs.didit.me/management-api/lists/list-entries). A scoped API key needs `workflows: read`.", + "operationId": "get_list", + "x-didit-sdk": { + "resource": "lists", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list, as returned by List lists or Create a list.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nlist_uuid = '11111111-2222-3333-4444-555555555555'\nresp = requests.get(\n f'https://verification.didit.me/v3/lists/{list_uuid}/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status()\nprint(resp.json())" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const listUuid = '11111111-2222-3333-4444-555555555555';\nconst resp = await fetch(`https://verification.didit.me/v3/lists/${listUuid}/`, {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (!resp.ok) throw new Error(`Lookup failed: ${resp.status}`);\nconst list = await resp.json();\nconsole.log(list.name, list.entry_count);" + } + ], + "responses": { + "200": { + "description": "The list.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ComplianceList" + }, + "examples": { + "System face blocklist": { + "value": { + "uuid": "11111111-2222-3333-4444-555555555555", + "name": "Face Blocklist", + "description": "Blocked biometric faces. When a face matching an entry in this list is detected during liveness verification, the session will be automatically declined.", + "list_type": "blocklist", + "entry_type": "face", + "is_system": true, + "entry_count": 12, + "last_entry_added_at": "2026-10-09T16:42:03.118204Z", + "created_at": "2026-01-14T10:02:11.402917Z", + "updated_at": "2026-01-14T10:02:11.402917Z" + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing or invalid, or a scoped API key lacks `workflows: read` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no read access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No List matches the given query." + } + } + } + } + } + }, + "429": { + "description": "The shared read limit of 600 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update a list", + "description": "Renames a list or changes its description. Only `name` and `description` are read; any other field is ignored, so `list_type` and `entry_type` keep the values they were created with. System lists cannot be modified and return `400`. The response contains only `name` and `description`: call [Get a list](https://docs.didit.me/management-api/lists/get) for the full list. A scoped API key needs `workflows: write`.", + "operationId": "update_list", + "x-didit-sdk": { + "resource": "lists", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list, as returned by List lists or Create a list.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "New name. Must differ from the name of every other list of the application." + }, + "description": { + "type": "string", + "description": "New description. Send an empty string to clear it." + } + } + }, + "examples": { + "Rename": { + "value": { + "name": "Trusted office and VPN IPs" + } + }, + "Rename and describe": { + "value": { + "name": "Trusted office and VPN IPs", + "description": "Office egress ranges and the corporate VPN" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"name\": \"Trusted office and VPN IPs\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nlist_uuid = '11111111-2222-3333-4444-555555555555'\nresp = requests.patch(\n f'https://verification.didit.me/v3/lists/{list_uuid}/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={'name': 'Trusted office and VPN IPs'},\n timeout=10,\n)\nresp.raise_for_status()\nprint(resp.json())" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const listUuid = '11111111-2222-3333-4444-555555555555';\nconst resp = await fetch(`https://verification.didit.me/v3/lists/${listUuid}/`, {\n method: 'PATCH',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ name: 'Trusted office and VPN IPs' }),\n});\nif (!resp.ok) throw new Error(`Update failed: ${resp.status}`);\nconsole.log(await resp.json());" + } + ], + "responses": { + "200": { + "description": "The list was updated. Only the editable fields are returned.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "description" + ], + "properties": { + "name": { + "type": "string", + "description": "The list's name after the update." + }, + "description": { + "type": "string", + "description": "The list's description after the update." + } + } + }, + "examples": { + "Renamed": { + "value": { + "name": "Trusted office and VPN IPs", + "description": "Office egress ranges" + } + } + } + } + } + }, + "400": { + "description": "The list is a system list, or the body is invalid.", + "content": { + "application/json": { + "examples": { + "System list": { + "value": { + "non_field_errors": [ + "System lists cannot be modified." + ] + } + }, + "Duplicate name": { + "value": { + "name": [ + "A list with this name already exists for this application." + ] + } + }, + "Blank name": { + "value": { + "name": [ + "This field may not be blank." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing or invalid, or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No List matches the given query." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete a list", + "description": "Deletes an allowlist or custom list together with its entries. Deletion is irreversible: the list and its entries no longer appear in any read, and workflow and transaction rule conditions that reference the list evaluate it as an empty list from then on. Sessions and transactions already evaluated keep their results, and the name can be reused for a new list. System lists cannot be deleted and return `400`; to empty one, [delete its entries](https://docs.didit.me/management-api/lists/delete-entry). A scoped API key needs `workflows: write`.", + "operationId": "delete_list", + "x-didit-sdk": { + "resource": "lists", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list, as returned by List lists or Create a list.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nlist_uuid = '11111111-2222-3333-4444-555555555555'\nresp = requests.delete(\n f'https://verification.didit.me/v3/lists/{list_uuid}/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status()\nprint('Deleted:', resp.status_code == 204)" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const listUuid = '11111111-2222-3333-4444-555555555555';\nconst resp = await fetch(`https://verification.didit.me/v3/lists/${listUuid}/`, {\n method: 'DELETE',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (resp.status !== 204) throw new Error(`Delete failed: ${resp.status}`);\nconsole.log('List deleted');" + } + ], + "responses": { + "204": { + "description": "The list and its entries were deleted. No body." + }, + "400": { + "description": "The list is a system list.", + "content": { + "application/json": { + "examples": { + "System list": { + "value": { + "detail": "System lists cannot be deleted." + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing or invalid, or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was already deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No List matches the given query." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/{list_uuid}/entries/": { + "get": { + "summary": "List list entries", + "description": "Returns the current entries of one list, newest first. `search` matches part of an entry's `value` or `display_label` without regard to case, and `reference_session_id` narrows the results to the entries taken from one person verification session. Image URLs in `metadata` are short-lived signed URLs issued again on every call; a scoped API key without `media: read` receives `null` in their place. A scoped API key needs `workflows: read`.", + "operationId": "list_list_entries", + "x-didit-sdk": { + "resource": "lists.entries", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list, as returned by List lists or Create a list.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "search", + "in": "query", + "required": false, + "description": "Case-insensitive text that the entry's `value` or `display_label` must contain.", + "schema": { + "type": "string" + } + }, + { + "name": "reference_session_id", + "in": "query", + "required": false, + "description": "Only the entries taken from this person verification session, for example to see which of its faces are already listed.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Page size. Defaults to 50. A value above 1000000 returns `400`; zero, a negative or a non-numeric value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Zero-based index of the first item to return. Prefer following `next`. A value above 1000000 returns `400`; a negative or non-numeric value falls back to 0.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/entries/?search=example.com' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nlist_uuid = '11111111-2222-3333-4444-555555555555'\nresp = requests.get(\n f'https://verification.didit.me/v3/lists/{list_uuid}/entries/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n params={'search': 'example.com', 'limit': 100},\n timeout=10,\n)\nresp.raise_for_status()\nfor entry in resp.json()['results']:\n print(entry['uuid'], entry['value'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const listUuid = '11111111-2222-3333-4444-555555555555';\nconst resp = await fetch(\n `https://verification.didit.me/v3/lists/${listUuid}/entries/?search=example.com`,\n { headers: { 'x-api-key': process.env.DIDIT_API_KEY } },\n);\nif (!resp.ok) throw new Error(`Request failed: ${resp.status}`);\nconst page = await resp.json();\nconsole.log(page.count, page.results.map((entry) => entry.value));" + } + ], + "responses": { + "200": { + "description": "A page of entries, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "description": "Total number of entries matching the filters." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ComplianceListEntry" + } + } + } + }, + "examples": { + "Entries": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "uuid": "bbbbbbbb-cccc-dddd-eeee-ffffffffffff", + "value": "X1234567", + "display_label": "Jane Doe - X1234567", + "comment": "", + "added_by": "api", + "reference_session_id": "33333333-4444-5555-6666-777777777777", + "reference_session_number": 1042, + "reference_session_type": "kyc", + "reference_object_uuid": "66666666-7777-8888-9999-000000000000", + "metadata": { + "full_name": "Jane Doe", + "issuing_state": "ESP", + "document_type": "ID", + "region": "", + "document_number": "X1234567" + }, + "created_at": "2026-10-12T10:06:12.774915Z" + }, + { + "uuid": "eeeeeeee-ffff-0000-1111-222222222222", + "value": "y7654321", + "display_label": "", + "comment": "Reported by a partner", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": null, + "metadata": null, + "created_at": "2026-10-03T07:48:30.552810Z" + } + ] + } + } + } + } + } + }, + "400": { + "description": "A query parameter is invalid.", + "content": { + "application/json": { + "examples": { + "Invalid session id": { + "value": { + "reference_session_id": [ + "Must be a valid UUID." + ] + } + }, + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing or invalid, or a scoped API key lacks `workflows: read` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no read access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "List not found." + } + } + } + } + } + }, + "429": { + "description": "The shared read limit of 600 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create a list entry", + "description": "Adds one entry to a list, in one of three ways: by `value`; from a verification session with `reference_session_id`, where Didit reads the value from the session's most recent record of the list's entry type and links the entry to it; or by `value` with a `reference_object_uuid` and `metadata.reference_type` pointing back to a transaction, user or business. On the system face, document, phone and email blocklists, the record read from the session is also flagged as blocklisted, so new matching sessions are declined; on the system user and business blocklists, matching users or businesses are set to `BLOCKED`. Sessions and transactions that already finished are not changed. A scoped API key needs `workflows: write`. See [Create a list entry](https://docs.didit.me/management-api/lists/create-entry) for each mode.", + "operationId": "create_list_entry", + "x-didit-sdk": { + "resource": "lists.entries", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list to add to. For a face you only have as an image, use Upload a face to a list instead.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Send `value`, `reference_session_id`, or both. Which fields apply depends on the list's `entry_type`.", + "properties": { + "value": { + "type": "string", + "description": "The value to list, validated against the list's `entry_type`: `email` a valid email address; `phone` a valid international number, stored in E.164; `ip_address` an IP address or CIDR range (for example `10.0.0.0/8`); `device_fingerprint` at least 8 characters among letters, digits, `-`, `_`, `:` and `.` (a blocklist refuses a fingerprint that several distinct devices have reported); `country` an ISO 3166-1 alpha-2 or alpha-3 code, stored as alpha-3; `user` and `business` the `vendor_data`; any other type a non-empty string. Stored in lower case except phone numbers and country codes, and a list holds each value once (case-insensitive). Required unless `reference_session_id` is sent; must be left empty with a document-capture `face_image_source`." + }, + "reference_session_id": { + "type": "string", + "format": "uuid", + "description": "A person or business verification session of your application to take the value from. Didit reads the session's most recent record of the list's entry type (`face`, `document`, `phone`, `email`, `ip_address` or `device_fingerprint`), links the entry to the session and to that record, and fills `display_label` and `metadata` for documents. For a `device_fingerprint` blocklist, a session that only has a device-class fingerprint, or was completed on more than one device, is refused: send the exact fingerprint as `value`. For other entry types, or when the session has no such record, send `value` too; your `value` is then stored and the entry is still linked to the session." + }, + "reference_object_uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the record the entry stands for when it does not come from a session: a transaction UUID, or the `didit_internal_id` of a user or business. Pair it with `metadata.reference_type`. Replaced by the session's record when `reference_session_id` provides one." + }, + "face_image_source": { + "type": "string", + "enum": [ + "liveness", + "front_image_camera_front", + "back_image_camera_front" + ], + "default": "liveness", + "description": "Face lists with `reference_session_id`: which of the session's images the face is taken from. `liveness` (default) is the liveness selfie. `front_image_camera_front` and `back_image_camera_front` are the selfies the front camera took while the user photographed the front or back of their document: accepted only on the system face blocklist, with `reference_session_id` and an empty `value`. Each selfie can be added once and must show one clear face." + }, + "reference_node_id": { + "type": "string", + "nullable": true, + "maxLength": 100, + "description": "With a document-capture `face_image_source`: the ID verification node whose selfie to use, for workflows with more than one. Defaults to the most recent ID verification that has that image. Refused with `liveness`." + }, + "display_label": { + "type": "string", + "maxLength": 512, + "description": "Human-readable label. When empty, Didit derives one from the session where it can (for example the holder's name and document number)." + }, + "comment": { + "type": "string", + "description": "Free-text note, such as the reason for listing." + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Free-form JSON stored with the entry and returned as stored. Use `reference_type` and `reference_id` so the console links back to the source. `reference_image_url` and `front_image_url` are set by Didit only and are dropped if you send them. For an entry read from a session, Didit's keys are merged with yours.", + "properties": { + "reference_type": { + "type": "string", + "enum": [ + "transaction", + "vendor_user", + "vendor_business" + ], + "description": "What `reference_object_uuid` points to." + }, + "reference_id": { + "type": "string", + "description": "Identifier of the source record." + } + } + } + } + }, + "examples": { + "By value": { + "value": { + "value": "+14155552671", + "comment": "Reported fraud" + } + }, + "From a session": { + "value": { + "reference_session_id": "33333333-4444-5555-6666-777777777777" + } + }, + "Face from a document-capture selfie": { + "value": { + "reference_session_id": "33333333-4444-5555-6666-777777777777", + "face_image_source": "front_image_camera_front" + } + }, + "From a transaction": { + "value": { + "value": "0x52908400098527886E0F7030069857D2E4169EE7", + "reference_object_uuid": "55555555-6666-7777-8888-999999999999", + "comment": "Linked to a chargeback", + "metadata": { + "reference_type": "transaction", + "reference_id": "55555555-6666-7777-8888-999999999999" + } + } + }, + "Country": { + "value": { + "value": "PRK", + "comment": "Sanctioned jurisdiction" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/entries/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"value\": \"+14155552671\", \"comment\": \"Reported fraud\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nlist_uuid = '11111111-2222-3333-4444-555555555555'\nresp = requests.post(\n f'https://verification.didit.me/v3/lists/{list_uuid}/entries/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={'reference_session_id': '33333333-4444-5555-6666-777777777777'},\n timeout=10,\n)\nresp.raise_for_status()\nentry = resp.json()\nprint('Listed', entry['value'], 'as', entry['uuid'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const listUuid = '11111111-2222-3333-4444-555555555555';\nconst resp = await fetch(`https://verification.didit.me/v3/lists/${listUuid}/entries/`, {\n method: 'POST',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ value: '+14155552671', comment: 'Reported fraud' }),\n});\nif (!resp.ok) throw new Error(`Create failed: ${resp.status} ${await resp.text()}`);\nconst entry = await resp.json();\nconsole.log('Listed', entry.value, 'as', entry.uuid);" + } + ], + "responses": { + "201": { + "description": "The entry was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ComplianceListEntry" + }, + "examples": { + "By value": { + "value": { + "uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "value": "+14155552671", + "display_label": "", + "comment": "Reported fraud", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": null, + "metadata": null, + "created_at": "2026-10-12T10:04:51.220391Z" + } + }, + "Document from a session": { + "value": { + "uuid": "bbbbbbbb-cccc-dddd-eeee-ffffffffffff", + "value": "X1234567", + "display_label": "Jane Doe - X1234567", + "comment": "", + "added_by": "api", + "reference_session_id": "33333333-4444-5555-6666-777777777777", + "reference_session_number": 1042, + "reference_session_type": "kyc", + "reference_object_uuid": "66666666-7777-8888-9999-000000000000", + "metadata": { + "full_name": "Jane Doe", + "issuing_state": "ESP", + "document_type": "ID", + "region": "", + "document_number": "X1234567" + }, + "created_at": "2026-10-12T10:06:12.774915Z" + } + }, + "From a transaction": { + "value": { + "uuid": "dddddddd-eeee-ffff-0000-111111111111", + "value": "0x52908400098527886e0f7030069857d2e4169ee7", + "display_label": "", + "comment": "Linked to a chargeback", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": "55555555-6666-7777-8888-999999999999", + "metadata": { + "reference_type": "transaction", + "reference_id": "55555555-6666-7777-8888-999999999999" + }, + "created_at": "2026-10-12T10:20:40.117062Z" + } + } + } + } + } + }, + "400": { + "description": "The body is invalid, the value is already in the list, or the session cannot provide the value.", + "content": { + "application/json": { + "examples": { + "Nothing to list": { + "value": { + "value": [ + "Either 'value' or 'reference_session_id' must be provided." + ] + } + }, + "Duplicate value": { + "value": { + "value": [ + "An entry with this value already exists in this list." + ] + } + }, + "Invalid phone": { + "value": { + "value": [ + "Invalid phone number. Provide a valid international number (e.g. +1234567890)." + ] + } + }, + "Invalid IP": { + "value": { + "value": [ + "Invalid IP address or CIDR range (e.g. 192.168.1.1 or 10.0.0.0/24)." + ] + } + }, + "Invalid country": { + "value": { + "value": [ + "Invalid country code: XX. Provide a valid ISO 3166-1 alpha-2 or alpha-3 country code." + ] + } + }, + "Device-class fingerprint": { + "value": { + "value": [ + "This value is a device-class fingerprint: several distinct devices have reported it, so blocklisting it would decline unrelated people. Block the user by document, face, email or phone instead." + ] + } + }, + "Unknown session": { + "value": { + "reference_session_id": [ + "Session not found or does not belong to this application." + ] + } + }, + "No data in session": { + "value": { + "value": [ + "Either provide a 'value' or ensure the session contains data for this entry type." + ] + } + }, + "Selfie on another list": { + "value": { + "face_image_source": [ + "A document-capture selfie can only be added to the face blocklist." + ] + } + }, + "No clear face in selfie": { + "value": { + "face_image_source": [ + "No clear face was found in this selfie, so it cannot be blocklisted." + ] + } + }, + "Selfie already listed": { + "value": { + "face_image_source": [ + "This selfie is already in the blocklist." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing or invalid, or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "List not found." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + }, + "503": { + "description": "Face analysis of a document-capture selfie is temporarily unavailable. Nothing was added; retry later.", + "content": { + "application/json": { + "examples": { + "Unavailable": { + "value": { + "detail": "Service temporarily unavailable. Please try again later." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/{list_uuid}/entries/{entry_uuid}/": { + "delete": { + "summary": "Delete a list entry", + "description": "Deletes one entry from a list, system lists included. The entry stops matching at once, and deletion is irreversible: to list the value again, create a new entry. On a system face, document, phone or email blocklist, the record the entry stands for is no longer flagged as blocklisted; on the system user or business blocklist, users or businesses of the application whose `vendor_data` equals the entry's value (or whose `didit_internal_id` equals its `reference_object_uuid`) and that are `BLOCKED` return to `ACTIVE`. Sessions and transactions that already finished keep their status. A scoped API key needs `workflows: write`.", + "operationId": "delete_list_entry", + "x-didit-sdk": { + "resource": "lists.entries", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list that holds the entry.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "entry_uuid", + "in": "path", + "required": true, + "description": "UUID of the entry, as returned by List list entries or Create a list entry.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/entries/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nlist_uuid = '11111111-2222-3333-4444-555555555555'\nentry_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'\nresp = requests.delete(\n f'https://verification.didit.me/v3/lists/{list_uuid}/entries/{entry_uuid}/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status()\nprint('Deleted:', resp.status_code == 204)" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const listUuid = '11111111-2222-3333-4444-555555555555';\nconst entryUuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee';\nconst resp = await fetch(\n `https://verification.didit.me/v3/lists/${listUuid}/entries/${entryUuid}/`,\n { method: 'DELETE', headers: { 'x-api-key': process.env.DIDIT_API_KEY } },\n);\nif (resp.status !== 204) throw new Error(`Delete failed: ${resp.status}`);\nconsole.log('Entry deleted');" + } + ], + "responses": { + "204": { + "description": "The entry was deleted. No body." + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing or invalid, or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No entry with this UUID exists in this list, the list is not in the API key's application or was deleted, or the entry was already deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No ListEntry matches the given query." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/{list_uuid}/entries/face-upload/": { + "post": { + "summary": "Upload a face to a list", + "description": "Adds a face to a face blocklist or face allowlist from a base64-encoded JPEG or PNG image, for when you have no verification session to reference. Didit detects the faces in the image, uses the largest one, and creates an entry whose `value` and `reference_object_uuid` are the UUID of the new face. A blocklisted face declines new sessions whose face matches; an allowlisted face skips the duplicated-face action and never overrides a blocklist match. In a sandbox application the image is not analyzed and the entry matches nothing. A scoped API key needs `workflows: write`. To list a face from an existing session, use [Create a list entry](https://docs.didit.me/management-api/lists/create-entry) with `reference_session_id` instead.", + "operationId": "upload_list_entry_face", + "x-didit-sdk": { + "resource": "lists.entries", + "method": "upload_face", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of a list with `entry_type: face` and `list_type: blocklist` or `allowlist`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "image" + ], + "properties": { + "image": { + "type": "string", + "format": "byte", + "description": "Base64-encoded JPEG or PNG, at most 2 MB once decoded, showing a clearly visible face. Send the bare base64 string, without a `data:image/...;base64,` prefix." + }, + "comment": { + "type": "string", + "default": "", + "description": "Optional free-text note, such as the reason for listing." + } + } + }, + "examples": { + "Face image": { + "value": { + "image": "/9j/4AAQSkZJRgABAQAAAQABAAD...", + "comment": "Known fraud attempt" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/entries/face-upload/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d \"{\\\"image\\\": \\\"$(base64 < face.jpg | tr -d '\\n')\\\", \\\"comment\\\": \\\"Known fraud attempt\\\"}\"" + }, + { + "lang": "python", + "label": "Python", + "source": "import base64\nimport os\n\nimport requests\n\nlist_uuid = '11111111-2222-3333-4444-555555555555'\nwith open('face.jpg', 'rb') as image_file:\n image_b64 = base64.b64encode(image_file.read()).decode('ascii')\n\nresp = requests.post(\n f'https://verification.didit.me/v3/lists/{list_uuid}/entries/face-upload/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={'image': image_b64, 'comment': 'Known fraud attempt'},\n timeout=10,\n)\nresp.raise_for_status()\nprint('Face entry', resp.json()['uuid'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "import { readFile } from 'node:fs/promises';\n\nconst listUuid = '11111111-2222-3333-4444-555555555555';\nconst image = (await readFile('face.jpg')).toString('base64');\nconst resp = await fetch(`https://verification.didit.me/v3/lists/${listUuid}/entries/face-upload/`, {\n method: 'POST',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ image, comment: 'Known fraud attempt' }),\n});\nif (!resp.ok) throw new Error(`Upload failed: ${resp.status} ${await resp.text()}`);\nconst entry = await resp.json();\nconsole.log('Face entry', entry.uuid);" + } + ], + "responses": { + "201": { + "description": "The face entry was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ComplianceListEntry" + }, + "examples": { + "Uploaded face": { + "value": { + "uuid": "cccccccc-dddd-eeee-ffff-000000000000", + "value": "44444444-5555-6666-7777-888888888888", + "display_label": "Manually uploaded face", + "comment": "Known fraud attempt", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": "44444444-5555-6666-7777-888888888888", + "metadata": { + "reference_image_url": "https://media.example.com/faces/reference.jpg?expires=1760263938&signature=3f9a1c" + }, + "created_at": "2026-10-12T10:12:18.904467Z" + } + }, + "Sandbox application": { + "value": { + "uuid": "cccccccc-dddd-eeee-ffff-000000000000", + "value": "44444444-5555-6666-7777-888888888888", + "display_label": "Sandbox face entry", + "comment": "Known fraud attempt", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": "44444444-5555-6666-7777-888888888888", + "metadata": { + "sandbox": true + }, + "created_at": "2026-10-12T10:12:18.904467Z" + } + } + } + } + } + }, + "400": { + "description": "The list does not take face uploads, or the image is missing, invalid, too large or shows no usable face. No entry was created.", + "content": { + "application/json": { + "examples": { + "Not a face list": { + "value": { + "detail": "Face upload is only available for face-type lists." + } + }, + "Custom face list": { + "value": { + "detail": "Face upload is only available for face blocklists and allowlists." + } + }, + "Missing image": { + "value": { + "image": [ + "This field is required." + ] + } + }, + "Invalid base64": { + "value": { + "image": [ + "Invalid base64 encoding." + ] + } + }, + "Too large": { + "value": { + "image": [ + "Image exceeds maximum size of 2 MB." + ] + } + }, + "Unreadable image": { + "value": { + "image": "Invalid image. Provide a valid base64-encoded JPG or PNG." + } + }, + "No face": { + "value": { + "image": "No face detected in the image. Please upload a clear photo with a visible face." + } + }, + "No usable face": { + "value": { + "image": "Could not extract face embedding. Try a different image." + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing or invalid, or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "List not found." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + }, + "503": { + "description": "Face analysis is temporarily unavailable. No entry was created; retry later.", + "content": { + "application/json": { + "examples": { + "Unavailable": { + "value": { + "detail": "Service temporarily unavailable. Please try again later." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/blocklist/": { + "get": { + "summary": "List blocklisted items", + "description": "Returns one paginated feed of the faces, identity documents, phone numbers and email addresses of the application's verification sessions that are currently blocklisted, newest `blocklisted_at` first; filter with `item_type`. Items of deleted sessions are not included. The feed covers records blocklisted from a session (with [Add session items to the blocklist](https://docs.didit.me/management-api/blocklist/add), from the console, or by a list entry created from a session); values added to a list by hand, uploaded faces, faces from a document-capture selfie and the other entry types are read with [List list entries](https://docs.didit.me/management-api/lists/list-entries). `face_image` and `front_image` are short-lived signed URLs, and a scoped API key without `media: read` receives `null`. A scoped API key needs `workflows: read`.", + "operationId": "list_blocklist_items", + "x-didit-sdk": { + "resource": "blocklist", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Blocklist" + ], + "parameters": [ + { + "name": "item_type", + "in": "query", + "required": false, + "description": "Only items of this type. When omitted, every type is returned.", + "schema": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email" + ] + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Page size. Defaults to 50. A value above 1000000 returns `400`; zero, a negative or a non-numeric value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Zero-based index of the first item to return. Prefer following `next`. A value above 1000000 returns `400`; a negative or non-numeric value falls back to 0.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/blocklist/?item_type=document' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.get(\n 'https://verification.didit.me/v3/blocklist/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n params={'item_type': 'document'},\n timeout=10,\n)\nresp.raise_for_status()\nfor item in resp.json()['results']:\n print(item['session_id'], item['document_type'], item['document_number'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/blocklist/?item_type=document', {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (!resp.ok) throw new Error(`Request failed: ${resp.status}`);\nconst page = await resp.json();\nconsole.log(page.count, page.results);" + } + ], + "responses": { + "200": { + "description": "A page of blocklisted items, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "description": "Total number of blocklisted items matching the filters." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BlocklistedItem" + } + } + } + }, + "examples": { + "Mixed feed": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "type": "document", + "session_id": "33333333-4444-5555-6666-777777777777", + "session_number": 1042, + "blocklisted_at": "2026-10-12T10:06:12.701553Z", + "face_image": null, + "document_type": "ID", + "document_number": "X1234567", + "front_image": "https://media.example.com/documents/front.jpg?expires=1760263938&signature=8b2e47", + "phone_number": null, + "country_code": null, + "email": null + }, + { + "type": "phone", + "session_id": "77777777-8888-9999-0000-111111111111", + "session_number": 1017, + "blocklisted_at": "2026-10-08T15:31:09.400127Z", + "face_image": null, + "document_type": null, + "document_number": null, + "front_image": null, + "phone_number": "+14155552671", + "country_code": "US", + "email": null + } + ] + } + } + } + } + } + }, + "400": { + "description": "A query parameter is invalid.", + "content": { + "application/json": { + "examples": { + "Unknown item type": { + "value": { + "item_type": [ + "\"wallet\" is not a valid choice." + ] + } + }, + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing or invalid, or a scoped API key lacks `workflows: read` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no read access to workflows." + } + } + } + } + } + }, + "429": { + "description": "The shared read limit of 600 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/blocklist/add/": { + "post": { + "summary": "Add session items to the blocklist", + "description": "Blocklists the face, identity document, phone number or email address of one person verification session of the application. For each selected type, Didit takes the session's most recent record, flags it as blocklisted and adds an entry linked to the session to the matching system blocklist (`added_by: \"api\"`), so new sessions with a matching face, document number, phone number or email address are declined. Every selected item is checked first, so if one is missing nothing is blocklisted; repeating the call for an item already listed keeps its single entry and refreshes `blocklisted_at`. The session itself and sessions that already finished are not changed. A scoped API key needs `workflows: write`.", + "operationId": "add_to_blocklist", + "x-didit-sdk": { + "resource": "blocklist", + "method": "add", + "auth": "api_key", + "server": true + }, + "tags": [ + "Blocklist" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "session_id" + ], + "description": "At least one `blocklist_*` flag must be `true`.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "A person verification session of your application. Deleted sessions are refused." + }, + "blocklist_face": { + "type": "boolean", + "default": false, + "description": "Blocklist the session's face." + }, + "blocklist_document": { + "type": "boolean", + "default": false, + "description": "Blocklist the session's identity document. The document needs its issuing state, document type, document number and full name." + }, + "blocklist_phone": { + "type": "boolean", + "default": false, + "description": "Blocklist the session's phone number." + }, + "blocklist_email": { + "type": "boolean", + "default": false, + "description": "Blocklist the session's email address." + } + } + }, + "examples": { + "Face and document": { + "value": { + "session_id": "33333333-4444-5555-6666-777777777777", + "blocklist_face": true, + "blocklist_document": true + } + }, + "Phone only": { + "value": { + "session_id": "33333333-4444-5555-6666-777777777777", + "blocklist_phone": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/blocklist/add/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"session_id\": \"33333333-4444-5555-6666-777777777777\",\n \"blocklist_face\": true,\n \"blocklist_document\": true\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/blocklist/add/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={\n 'session_id': '33333333-4444-5555-6666-777777777777',\n 'blocklist_face': True,\n 'blocklist_document': True,\n },\n timeout=10,\n)\nresp.raise_for_status()\nprint(resp.json()['message'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/blocklist/add/', {\n method: 'POST',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n session_id: '33333333-4444-5555-6666-777777777777',\n blocklist_face: true,\n blocklist_document: true,\n }),\n});\nif (!resp.ok) throw new Error(`Blocklist failed: ${resp.status} ${await resp.text()}`);\nconsole.log((await resp.json()).message);" + } + ], + "responses": { + "200": { + "description": "The selected items were blocklisted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string", + "description": "Always `Successfully updated blocklist.`" + } + } + }, + "examples": { + "Blocklisted": { + "value": { + "message": "Successfully updated blocklist." + } + } + } + } + } + }, + "400": { + "description": "The body is invalid, the session is not a verification session of this application, or it has no record of a selected type. Nothing was blocklisted.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "value": { + "non_field_errors": [ + "Session not found" + ] + } + }, + "No item selected": { + "value": { + "non_field_errors": [ + "Must specify at least one item to blocklist" + ] + } + }, + "No face record": { + "value": { + "non_field_errors": [ + "Session does not have a face record" + ] + } + }, + "Incomplete document": { + "value": { + "non_field_errors": [ + "To blocklist a document, the document must have an issuing state, document type, document number and full name" + ] + } + }, + "Invalid session id": { + "value": { + "session_id": [ + "Must be a valid UUID." + ] + } + }, + "Missing session id": { + "value": { + "session_id": [ + "This field is required." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing or invalid, or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/blocklist/remove/": { + "post": { + "summary": "Remove session items from the blocklist", + "description": "Lifts the block on the face, identity document, phone number or email address of one person verification session of the application. For each selected type, Didit takes the session's most recent record, clears its blocklisted flag and deletes the system blocklist entries created from that record, so it no longer declines new sessions. Entries added to a list by value are not touched: find them with [List list entries](https://docs.didit.me/management-api/lists/list-entries) and delete them with [Delete a list entry](https://docs.didit.me/management-api/lists/delete-entry). Unblocking a record that is not blocklisted succeeds and changes nothing, and sessions that already finished are not changed. A scoped API key needs `workflows: write`.", + "operationId": "remove_from_blocklist", + "x-didit-sdk": { + "resource": "blocklist", + "method": "remove", + "auth": "api_key", + "server": true + }, + "tags": [ + "Blocklist" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "session_id" + ], + "description": "At least one `unblock_*` flag must be `true`.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "A person verification session of your application. Deleted sessions are refused." + }, + "unblock_face": { + "type": "boolean", + "default": false, + "description": "Lift the block on the session's face." + }, + "unblock_document": { + "type": "boolean", + "default": false, + "description": "Lift the block on the session's identity document." + }, + "unblock_phone": { + "type": "boolean", + "default": false, + "description": "Lift the block on the session's phone number." + }, + "unblock_email": { + "type": "boolean", + "default": false, + "description": "Lift the block on the session's email address." + } + } + }, + "examples": { + "Face and document": { + "value": { + "session_id": "33333333-4444-5555-6666-777777777777", + "unblock_face": true, + "unblock_document": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/blocklist/remove/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"session_id\": \"33333333-4444-5555-6666-777777777777\",\n \"unblock_face\": true,\n \"unblock_document\": true\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/blocklist/remove/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={\n 'session_id': '33333333-4444-5555-6666-777777777777',\n 'unblock_face': True,\n 'unblock_document': True,\n },\n timeout=10,\n)\nresp.raise_for_status()\nprint(resp.json()['message'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/blocklist/remove/', {\n method: 'POST',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n session_id: '33333333-4444-5555-6666-777777777777',\n unblock_face: true,\n unblock_document: true,\n }),\n});\nif (!resp.ok) throw new Error(`Unblock failed: ${resp.status} ${await resp.text()}`);\nconsole.log((await resp.json()).message);" + } + ], + "responses": { + "200": { + "description": "The block on the selected items was lifted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string", + "description": "Always `Successfully removed from blocklist.`" + } + } + }, + "examples": { + "Unblocked": { + "value": { + "message": "Successfully removed from blocklist." + } + } + } + } + } + }, + "400": { + "description": "The body is invalid, the session is not a verification session of this application, or it has no record of a selected type. Nothing was changed.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "value": { + "non_field_errors": [ + "Session not found" + ] + } + }, + "No item selected": { + "value": { + "non_field_errors": [ + "Must specify at least one item to unblock" + ] + } + }, + "No email record": { + "value": { + "non_field_errors": [ + "Session does not have an email record" + ] + } + }, + "Invalid session id": { + "value": { + "session_id": [ + "Must be a valid UUID." + ] + } + }, + "Missing session id": { + "value": { + "session_id": [ + "This field is required." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing or invalid, or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/": { + "get": { + "summary": "Get webhook configuration", + "description": "Return the application's webhook settings in one call: every [webhook destination](https://docs.didit.me/management-api/webhook/list-destinations) with its signing secret, the event types a destination can subscribe to, the application-level (legacy) webhook URL, version and secret, the data and biometric-template retention policy, and the Digital ID wallet return hosts. Didit delivers webhooks to destinations, each signed with its own `secret_shared_key`; the top-level `webhook_url`, `webhook_version` and `secret_shared_key` are used only while no destination is enabled, and then only for `status.updated` and `data.updated`. The response contains signing secrets, so keep it out of logs and client-side code. A scoped API key needs `webhooks: read`.", + "operationId": "get_webhook_config", + "x-didit-sdk": { + "resource": "webhooks", + "method": "retrieve_config", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.get(\n 'https://verification.didit.me/v3/webhook/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status()\nconfig = resp.json()\nfor destination in config['endpoints']:\n print(destination['label'], destination['url'], destination['subscribed_events'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/webhook/', {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (!resp.ok) throw new Error(`Request failed: ${resp.status}`);\nconst config = await resp.json();\nfor (const destination of config.endpoints) {\n console.log(destination.label, destination.url, destination.subscribed_events);\n}" + } + ], + "responses": { + "200": { + "description": "The webhook configuration.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookConfig" + }, + "examples": { + "One destination": { + "value": { + "webhook_url": "https://yourapp.com/webhooks/didit", + "webhook_version": "v3", + "secret_shared_key": "dGhpcy1pcy10aGUtYXBwLWxldmVsLXNlY3JldC0wMDA", + "capture_method": "MOBILE", + "data_retention_months": 24, + "face_retention_policy": "delete_with_session", + "face_retention_days": null, + "biometric_consent_mode": "auto", + "available_events": [ + { + "value": "status.updated", + "label": "Session status updated" + }, + { + "value": "data.updated", + "label": "Session data updated" + }, + { + "value": "user.status.updated", + "label": "User status updated" + }, + { + "value": "user.data.updated", + "label": "User data updated" + }, + { + "value": "business.status.updated", + "label": "Business status updated" + }, + { + "value": "business.data.updated", + "label": "Business data updated" + }, + { + "value": "activity.created", + "label": "Activity created" + }, + { + "value": "transaction.created", + "label": "Transaction created" + }, + { + "value": "transaction.status.updated", + "label": "Transaction status updated" + }, + { + "value": "travel_rule.status.updated", + "label": "Travel Rule Status Updated" + }, + { + "value": "workflow.compliance_changed", + "label": "Workflow Compliance Changed" + } + ], + "allowed_return_hosts": [ + "shop.example.com" + ], + "endpoints": [ + { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "secret_shared_key": "dGhpcy1pcy1hbi1leGFtcGxlLXNlY3JldC0wMDAwMDA", + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_at": "2026-01-04T08:00:00.000000Z", + "updated_at": "2026-04-22T14:09:11.523671Z" + } + ] + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A missing, invalid or revoked key gets `403`, not `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks read": { + "value": { + "detail": "This API key has no read access to webhooks." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update webhook configuration", + "description": "Partially update application-level settings: the legacy webhook URL, version and secret, the data and biometric-template retention policy, and the Digital ID wallet return hosts. Send only the fields to change; the response is the full configuration, the same shape as `GET /v3/webhook/`. Destinations are not managed here: create, update and delete them with the `/v3/webhook/destinations/` endpoints, and do not send an `endpoints` field to this endpoint. A `data_retention_months` change also applies to existing sessions, and sessions it deletes cannot be restored; read [Data retention](https://docs.didit.me/console/data-retention) first. A scoped API key needs `webhooks: write`.", + "operationId": "update_webhook_config", + "x-didit-sdk": { + "resource": "webhooks", + "method": "update_config", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Send only the fields to change. Destinations are not managed here.", + "properties": { + "webhook_url": { + "type": "string", + "format": "uri", + "nullable": true, + "maxLength": 2000, + "description": "Legacy application-level webhook URL. Didit uses it only while the application has no enabled destination, and then only for `status.updated` and `data.updated`. It does not create a destination, and the next create, update or delete of a destination replaces it. To receive webhooks, create a destination with `POST /v3/webhook/destinations/` instead." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload version of the legacy `webhook_url`." + }, + "rotate_secret_key": { + "type": "boolean", + "default": false, + "description": "`true` replaces the application-level `secret_shared_key` with a new random value at once; the new value is in the response. Destination secrets are not changed. Every call with `true` generates a new secret, so it is not idempotent." + }, + "capture_method": { + "type": "string", + "enum": [ + "MOBILE", + "MOBILE_AND_DESKTOP" + ], + "description": "Legacy application setting. The verification flow does not read it." + }, + "data_retention_months": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 120, + "description": "Automatic deletion window in months (a month counts as 30 days), or `null` for no automatic deletion. A change applies to existing sessions too: every user verification session older than the new window is deleted automatically, and that deletion is irreversible." + }, + "face_retention_policy": { + "type": "string", + "enum": [ + "delete_with_session", + "retain_until_user_deleted" + ], + "description": "What happens to a session's face embedding when the session is deleted. `retain_until_user_deleted` needs `face_retention_days` in the same request or already set. A change applies to session deletions from then on; it creates or purges no existing biometric template." + }, + "face_retention_days": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 3650, + "description": "Retention period, in days, of biometric templates under `retain_until_user_deleted`." + }, + "biometric_consent_mode": { + "type": "string", + "enum": [ + "auto", + "always", + "never" + ], + "description": "When the hosted flow and the native SDKs ask the person for explicit biometric consent before a face capture. `auto` (default) asks only where a trigger applies: a declared state, the person's IP, or the identity document's issuer is in Illinois, Texas or Colorado, or the person looks to be in the US without a known state. `always` asks every person whose workflow captures a face. `never` never shows the step, and you collect consent yourself before the selfie. Each change is recorded in the audit log. See [Biometric consent](https://docs.didit.me/getting-started/biometric-consent)." + }, + "allowed_return_hosts": { + "type": "array", + "maxItems": 20, + "description": "Replaces the whole list of Digital ID wallet return hosts. Bare hostnames only, such as `shop.example.com`: no scheme, port, path or wildcard. Values are lowercased and duplicates are dropped. Send `[]` to clear the list.", + "items": { + "type": "string", + "maxLength": 253 + } + } + } + }, + "examples": { + "Enable biometric template retention": { + "value": { + "face_retention_policy": "retain_until_user_deleted", + "face_retention_days": 365, + "biometric_consent_mode": "auto" + } + }, + "Rotate the legacy secret": { + "value": { + "rotate_secret_key": true + } + }, + "Set data retention": { + "value": { + "data_retention_months": 24 + } + }, + "Register return hosts": { + "value": { + "allowed_return_hosts": [ + "shop.example.com", + "app.example.com" + ] + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/webhook/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"face_retention_policy\": \"retain_until_user_deleted\", \"face_retention_days\": 365}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.patch(\n 'https://verification.didit.me/v3/webhook/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={'face_retention_policy': 'retain_until_user_deleted', 'face_retention_days': 365},\n timeout=10,\n)\nresp.raise_for_status()\nconfig = resp.json()\nprint(config['face_retention_policy'], config['face_retention_days'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/webhook/', {\n method: 'PATCH',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ face_retention_policy: 'retain_until_user_deleted', face_retention_days: 365 }),\n});\nif (!resp.ok) throw new Error(await resp.text());\nconst config = await resp.json();\nconsole.log(config.face_retention_policy, config.face_retention_days);" + } + ], + "responses": { + "200": { + "description": "The full configuration after the update, the same shape as `GET /v3/webhook/`. When you rotated the secret, store the new `secret_shared_key` before you discard the response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookConfig" + }, + "examples": { + "Retention enabled": { + "value": { + "webhook_url": "https://yourapp.com/webhooks/didit", + "webhook_version": "v3", + "secret_shared_key": "dGhpcy1pcy10aGUtYXBwLWxldmVsLXNlY3JldC0wMDA", + "capture_method": "MOBILE", + "data_retention_months": 24, + "face_retention_policy": "retain_until_user_deleted", + "face_retention_days": 365, + "biometric_consent_mode": "auto", + "available_events": [ + { + "value": "status.updated", + "label": "Session status updated" + }, + { + "value": "data.updated", + "label": "Session data updated" + }, + { + "value": "user.status.updated", + "label": "User status updated" + }, + { + "value": "user.data.updated", + "label": "User data updated" + }, + { + "value": "business.status.updated", + "label": "Business status updated" + }, + { + "value": "business.data.updated", + "label": "Business data updated" + }, + { + "value": "activity.created", + "label": "Activity created" + }, + { + "value": "transaction.created", + "label": "Transaction created" + }, + { + "value": "transaction.status.updated", + "label": "Transaction status updated" + }, + { + "value": "travel_rule.status.updated", + "label": "Travel Rule Status Updated" + }, + { + "value": "workflow.compliance_changed", + "label": "Workflow Compliance Changed" + } + ], + "allowed_return_hosts": [ + "shop.example.com" + ], + "endpoints": [ + { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "secret_shared_key": "dGhpcy1pcy1hbi1leGFtcGxlLXNlY3JldC0wMDAwMDA", + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_at": "2026-01-04T08:00:00.000000Z", + "updated_at": "2026-04-22T14:09:11.523671Z" + } + ] + } + } + } + } + } + }, + "400": { + "description": "Validation failed: each invalid field is mapped to a list of messages, and nothing is changed. A body that is not valid JSON returns `{\"detail\": \"JSON parse error - ...\"}`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Each invalid field mapped to a list of messages.", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "Retention days missing": { + "value": { + "face_retention_days": [ + "A finite retention duration is required when face retention is enabled." + ], + "biometric_consent_mode": "auto" + } + }, + "Retention window out of range": { + "value": { + "data_retention_months": [ + "Ensure this value is less than or equal to 120." + ] + } + }, + "Invalid return host": { + "value": { + "allowed_return_hosts": [ + "'https://shop.example.com' is not a hostname. Register the bare host (e.g. shop.example.com): no scheme, port, path or wildcard." + ] + } + }, + "Invalid choice": { + "value": { + "face_retention_policy": [ + "\"forever\" is not a valid choice." + ] + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A missing, invalid or revoked key gets `403`, not `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks write": { + "value": { + "detail": "This API key has no write access to webhooks." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 300 write requests (POST, PATCH, PUT, DELETE) in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (300).", + "schema": { + "type": "integer", + "example": 300 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-write" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/analytics/": { + "get": { + "summary": "Get webhook destination analytics", + "description": "Delivery totals, failure rate and response times for one webhook destination, over the whole range (`summary`) and per UTC day (`points`). Every attempt counts as a delivery: the first send of an event, each automatic [retry](https://docs.didit.me/integration/webhooks#retry-policy) and each resend. A delivery is failed when its recorded HTTP status is 400 or above, so timeouts (recorded as `504`) and connection failures (`503`) count as failed; a delivery still queued counts in `total_deliveries` only. Delivery records are available for about 30 days. A scoped API key needs `webhooks: read`.", + "operationId": "get_webhook_destination_analytics", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "retrieve_analytics", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination (the `uuid` returned by `GET /v3/webhook/destinations/`).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "date_from", + "in": "query", + "required": false, + "description": "First UTC day to include, as `YYYY-MM-DD` (inclusive). Omit to start from the oldest delivery available. `dateFrom` is accepted as an alias.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-10-01" + }, + { + "name": "date_to", + "in": "query", + "required": false, + "description": "Last UTC day to include, as `YYYY-MM-DD` (inclusive). Must not be earlier than `date_from`. `dateTo` is accepted as an alias.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-10-16" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/analytics/?date_from=2026-10-01&date_to=2026-10-16' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\ndestination_uuid = '11111111-2222-3333-4444-555555555555'\nresp = requests.get(\n f'https://verification.didit.me/v3/webhook/destinations/{destination_uuid}/analytics/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n params={'date_from': '2026-10-01', 'date_to': '2026-10-16'},\n timeout=10,\n)\nresp.raise_for_status()\nsummary = resp.json()['summary']\nprint(f\"{summary['failed_deliveries']} of {summary['total_deliveries']} failed ({summary['error_rate_percentage']}%)\")" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const destinationUuid = '11111111-2222-3333-4444-555555555555';\nconst params = new URLSearchParams({ date_from: '2026-10-01', date_to: '2026-10-16' });\nconst resp = await fetch(`https://verification.didit.me/v3/webhook/destinations/${destinationUuid}/analytics/?${params}`, {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (!resp.ok) throw new Error(`Request failed: ${resp.status}`);\nconst { summary, points } = await resp.json();\nconsole.log(summary.error_rate_percentage, points.length);" + } + ], + "responses": { + "200": { + "description": "Delivery statistics for the destination.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "summary", + "points" + ], + "properties": { + "summary": { + "type": "object", + "description": "Totals over the requested range.", + "required": [ + "total_deliveries", + "failed_deliveries", + "min_response_time_ms", + "avg_response_time_ms", + "max_response_time_ms", + "error_rate_percentage" + ], + "properties": { + "total_deliveries": { + "type": "integer", + "minimum": 0, + "description": "Delivery attempts, including queued ones." + }, + "failed_deliveries": { + "type": "integer", + "minimum": 0, + "description": "Attempts whose recorded HTTP status is 400 or above." + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Fastest attempt in milliseconds, or `null` when no attempt has a recorded time." + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Mean attempt time in milliseconds, rounded, or `null` when no attempt has a recorded time." + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Slowest attempt in milliseconds, or `null` when no attempt has a recorded time." + }, + "error_rate_percentage": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "`failed_deliveries` / `total_deliveries` x 100, rounded to a whole number. `0` when there are no deliveries." + } + } + }, + "points": { + "type": "array", + "description": "One entry per UTC day that had deliveries, oldest first. Days without deliveries are omitted, so fill the gaps with zeros when you chart them.", + "items": { + "type": "object", + "required": [ + "date", + "total_deliveries", + "failed_deliveries", + "min_response_time_ms", + "avg_response_time_ms", + "max_response_time_ms" + ], + "properties": { + "date": { + "type": "string", + "format": "date", + "description": "UTC day, `YYYY-MM-DD`." + }, + "total_deliveries": { + "type": "integer", + "minimum": 0, + "description": "Delivery attempts, including queued ones." + }, + "failed_deliveries": { + "type": "integer", + "minimum": 0, + "description": "Attempts whose recorded HTTP status is 400 or above." + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Fastest attempt in milliseconds, or `null` when no attempt has a recorded time." + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Mean attempt time in milliseconds, rounded, or `null` when no attempt has a recorded time." + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Slowest attempt in milliseconds, or `null` when no attempt has a recorded time." + } + } + } + } + } + }, + "examples": { + "Three days of deliveries": { + "value": { + "summary": { + "total_deliveries": 1250, + "failed_deliveries": 12, + "min_response_time_ms": 38, + "avg_response_time_ms": 164, + "max_response_time_ms": 5004, + "error_rate_percentage": 1 + }, + "points": [ + { + "date": "2026-10-14", + "total_deliveries": 410, + "failed_deliveries": 0, + "min_response_time_ms": 38, + "avg_response_time_ms": 152, + "max_response_time_ms": 911 + }, + { + "date": "2026-10-15", + "total_deliveries": 395, + "failed_deliveries": 12, + "min_response_time_ms": 41, + "avg_response_time_ms": 190, + "max_response_time_ms": 5004 + }, + { + "date": "2026-10-16", + "total_deliveries": 445, + "failed_deliveries": 0, + "min_response_time_ms": 40, + "avg_response_time_ms": 151, + "max_response_time_ms": 1220 + } + ] + } + }, + "No deliveries in range": { + "value": { + "summary": { + "total_deliveries": 0, + "failed_deliveries": 0, + "min_response_time_ms": null, + "avg_response_time_ms": null, + "max_response_time_ms": null, + "error_rate_percentage": 0 + }, + "points": [] + } + } + } + } + } + }, + "400": { + "description": "`date_from` or `date_to` is not a `YYYY-MM-DD` date, or `date_to` is earlier than `date_from`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Each invalid field mapped to a list of messages.", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "Malformed date": { + "value": { + "date_from": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + }, + "Reversed range": { + "value": { + "date_to": [ + "date_to must be greater than or equal to date_from." + ] + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A missing, invalid or revoked key gets `403`, not `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks read": { + "value": { + "detail": "This API key has no read access to webhooks." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No destination with this UUID exists for the application, or it has been deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Destination not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/deliveries/": { + "get": { + "summary": "List webhook destination deliveries", + "description": "Page through every delivery attempt to one webhook destination, newest first. The first send of each event, each automatic [retry](https://docs.didit.me/integration/webhooks#retry-policy) and each resend are separate entries. Entries carry the HTTP status and response time but not the payload: fetch one delivery to read its headers and body. `count` is exact up to 100 and reads 100 above that, so follow `next` until it is `null`. Delivery records are available for about 30 days. A scoped API key needs `webhooks: read` and `decisions: read`; a key limited to some workflows or to approved sessions cannot use this endpoint.", + "operationId": "list_webhook_destination_deliveries", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "list_deliveries", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination (the `uuid` returned by `GET /v3/webhook/destinations/`).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of deliveries per page. Defaults to 50. A value that is not a positive integer falls back to 50; a value above 1000000 returns `400`.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "example": 50 + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of deliveries to skip. Defaults to 0. Prefer following the `next` and `previous` URLs from the response.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "example": 0 + }, + { + "name": "date_from", + "in": "query", + "required": false, + "description": "First UTC day to include, as `YYYY-MM-DD` (inclusive). Omit to start from the oldest delivery available. `dateFrom` is accepted as an alias.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-10-01" + }, + { + "name": "date_to", + "in": "query", + "required": false, + "description": "Last UTC day to include, as `YYYY-MM-DD` (inclusive). Must not be earlier than `date_from`. `dateTo` is accepted as an alias.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-10-16" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/deliveries/?limit=50&date_from=2026-10-01' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\ndestination_uuid = '11111111-2222-3333-4444-555555555555'\nurl = f'https://verification.didit.me/v3/webhook/destinations/{destination_uuid}/deliveries/'\nparams = {'limit': 50, 'date_from': '2026-10-01'}\n\nwhile url:\n resp = requests.get(\n url,\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n params=params,\n timeout=10,\n )\n resp.raise_for_status()\n page = resp.json()\n for delivery in page['results']:\n if delivery['response_status'] is None or delivery['response_status'] >= 400:\n print(delivery['uuid'], delivery['webhook_type'], delivery['response_status'])\n url, params = page['next'], None # next already carries the query string" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const destinationUuid = '11111111-2222-3333-4444-555555555555';\nlet url = `https://verification.didit.me/v3/webhook/destinations/${destinationUuid}/deliveries/?limit=50&date_from=2026-10-01`;\nwhile (url) {\n const resp = await fetch(url, { headers: { 'x-api-key': process.env.DIDIT_API_KEY } });\n if (!resp.ok) throw new Error(`Request failed: ${resp.status}`);\n const page = await resp.json();\n for (const delivery of page.results) {\n if (delivery.response_status === null || delivery.response_status >= 400) {\n console.log(delivery.uuid, delivery.webhook_type, delivery.response_status);\n }\n }\n url = page.next;\n}" + } + ], + "responses": { + "200": { + "description": "One page of deliveries, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "description": "Matching deliveries, exact up to 100 and capped at 100 above that. Use `next` to know whether more pages exist." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebhookDestinationDelivery" + } + } + } + }, + "examples": { + "Failed delivery, its retry and a queued event": { + "value": { + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "uuid": "66666666-7777-8888-9999-000000000003", + "session_id": null, + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": null, + "response_time_ms": null, + "status": "APPROVED", + "webhook_type": "transaction.created", + "created_at": "2026-10-15T09:23:02.871190Z" + }, + { + "uuid": "66666666-7777-8888-9999-000000000002", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 200, + "response_time_ms": 143, + "status": "In Progress", + "webhook_type": "status.updated", + "created_at": "2026-10-15T09:22:19.104233Z" + }, + { + "uuid": "66666666-7777-8888-9999-000000000001", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 503, + "response_time_ms": 412, + "status": "In Progress", + "webhook_type": "status.updated", + "created_at": "2026-10-15T09:21:18.412345Z" + } + ] + } + } + } + } + } + }, + "400": { + "description": "`date_from` or `date_to` is not a `YYYY-MM-DD` date, `date_to` is earlier than `date_from`, or `limit` or `offset` is above 1000000 (that error is a JSON array of messages).", + "content": { + "application/json": { + "examples": { + "Malformed date": { + "value": { + "date_from": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + }, + "Reversed range": { + "value": { + "date_to": [ + "date_to must be greater than or equal to date_from." + ] + } + }, + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A missing, invalid or revoked key gets `403`, not `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` A key limited to some workflows or to approved sessions gets `This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks read": { + "value": { + "detail": "This API key has no read access to webhooks." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No destination with this UUID exists for the application, or it has been deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Destination not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/deliveries/{delivery_uuid}/": { + "get": { + "summary": "Get webhook destination delivery", + "description": "Retrieve one delivery attempt to a webhook destination with the exact request headers and JSON body Didit sent, the HTTP status your endpoint returned and the response time. `request_header` and `request_body` are JSON text: parse them to read the signature headers and the payload, for example to check your [signature verification](https://docs.didit.me/integration/webhooks#signature-verification) against the destination's `secret_shared_key`. A delivery never contains the signing secret, and Didit does not record your endpoint's response body. A scoped API key needs `webhooks: read` and `decisions: read`; a key limited to some workflows or to approved sessions cannot use this endpoint.", + "operationId": "get_webhook_destination_delivery", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "retrieve_delivery", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination the delivery was sent to.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "delivery_uuid", + "in": "path", + "required": true, + "description": "UUID of the delivery: the `uuid` of an entry returned by `GET /v3/webhook/destinations/{destination_uuid}/deliveries/`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "66666666-7777-8888-9999-000000000001" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/deliveries/66666666-7777-8888-9999-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import json\nimport os\n\nimport requests\n\ndestination_uuid = '11111111-2222-3333-4444-555555555555'\ndelivery_uuid = '66666666-7777-8888-9999-000000000001'\nresp = requests.get(\n f'https://verification.didit.me/v3/webhook/destinations/{destination_uuid}/deliveries/{delivery_uuid}/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status()\ndelivery = resp.json()\nheaders = json.loads(delivery['request_header'])\npayload = json.loads(delivery['request_body'])\nprint(delivery['response_status'], headers['X-Signature-V2'], payload['event_id'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const destinationUuid = '11111111-2222-3333-4444-555555555555';\nconst deliveryUuid = '66666666-7777-8888-9999-000000000001';\nconst resp = await fetch(\n `https://verification.didit.me/v3/webhook/destinations/${destinationUuid}/deliveries/${deliveryUuid}/`,\n { headers: { 'x-api-key': process.env.DIDIT_API_KEY } },\n);\nif (!resp.ok) throw new Error(`Request failed: ${resp.status}`);\nconst delivery = await resp.json();\nconst headers = JSON.parse(delivery.request_header);\nconst payload = JSON.parse(delivery.request_body);\nconsole.log(delivery.response_status, headers['X-Signature-V2'], payload.event_id);" + } + ], + "responses": { + "200": { + "description": "The delivery, with its request headers and body.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookDestinationDeliveryDetail" + }, + "examples": { + "Connection failure": { + "value": { + "uuid": "66666666-7777-8888-9999-000000000001", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 503, + "response_time_ms": 412, + "status": "In Progress", + "webhook_type": "status.updated", + "created_at": "2026-10-15T09:21:18.412345Z", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"a5e02a03404e1337a0198e6be15d34abbd053c658a0e51e5d0e78554213ef29b\", \"X-Signature-V2\": \"927e7cb770c94ce745939bff73f04aa200015f3bc64795ba46c2aaf78a6cedc5\", \"X-Signature-Simple\": \"e37b0d4c716c72bb5ae7127b9cc455d28ab4686e9e22312b4ec2327409a73302\", \"X-Timestamp\": \"1792056078\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792056078,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A missing, invalid or revoked key gets `403`, not `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` A key limited to some workflows or to approved sessions gets `This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks read": { + "value": { + "detail": "This API key has no read access to webhooks." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The destination does not exist for the application or has been deleted, or the delivery does not exist, belongs to another destination, or is no longer available.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Destination not found": { + "value": { + "detail": "Webhook destination not found." + } + }, + "Delivery not found": { + "value": { + "detail": "Webhook delivery not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/deliveries/{delivery_uuid}/resend/": { + "post": { + "summary": "Resend webhook destination delivery", + "description": "Send a past delivery again, for example after your endpoint was down. Didit takes the stored payload, sets a fresh `timestamp`, signs it again with the destination's current `secret_shared_key` (`X-Signature`, `X-Signature-V2`, `X-Signature-Simple` and `X-Timestamp`) and posts it to the delivery's `request_url`, even if the destination's URL has changed since or the destination is disabled. `event_id` and the rest of the payload are unchanged, so deduplicate on `event_id`. The call waits up to 5 seconds for your endpoint and returns the new delivery with `200` whatever your endpoint answered: read `response_status`. A resend is not retried automatically, every call sends again and creates a new delivery, and the request has no body. A scoped API key needs `webhooks: write` and `decisions: read`; a key limited to some workflows or to approved sessions cannot use this endpoint.", + "operationId": "resend_webhook_destination_delivery", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "resend_delivery", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination the delivery was sent to.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "delivery_uuid", + "in": "path", + "required": true, + "description": "UUID of the delivery to resend: the `uuid` of an entry returned by `GET /v3/webhook/destinations/{destination_uuid}/deliveries/`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "66666666-7777-8888-9999-000000000001" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/deliveries/66666666-7777-8888-9999-000000000001/resend/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\ndestination_uuid = '11111111-2222-3333-4444-555555555555'\ndelivery_uuid = '66666666-7777-8888-9999-000000000001'\nresp = requests.post(\n f'https://verification.didit.me/v3/webhook/destinations/{destination_uuid}/deliveries/{delivery_uuid}/resend/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status()\nresent = resp.json()\nif resent['response_status'] is None or resent['response_status'] >= 400:\n print('Your endpoint still rejects it:', resent['response_status'])\nelse:\n print('Delivered as', resent['uuid'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const destinationUuid = '11111111-2222-3333-4444-555555555555';\nconst deliveryUuid = '66666666-7777-8888-9999-000000000001';\nconst resp = await fetch(\n `https://verification.didit.me/v3/webhook/destinations/${destinationUuid}/deliveries/${deliveryUuid}/resend/`,\n { method: 'POST', headers: { 'x-api-key': process.env.DIDIT_API_KEY } },\n);\nif (!resp.ok) throw new Error(await resp.text());\nconst resent = await resp.json();\nconsole.log(resent.uuid, resent.response_status);" + } + ], + "responses": { + "200": { + "description": "The new delivery, already attempted. `response_status` is what your endpoint answered this time (`504` timeout, `503` connection failure). The original delivery is unchanged.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookDestinationDeliveryDetail" + }, + "examples": { + "Accepted on resend": { + "value": { + "uuid": "66666666-7777-8888-9999-000000000004", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 200, + "response_time_ms": 151, + "status": "In Progress", + "webhook_type": "status.updated", + "created_at": "2026-10-16T14:05:42.330981Z", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"b8eb1e376517ee0cad8ba98ff7f8fd35fc72eca231000963a96a335f82328835\", \"X-Signature-V2\": \"9efd2ce9ed1303e1d05273ef9608a59363ed9789a05253db4748b3e8e50cb109\", \"X-Signature-Simple\": \"bf3a9dc04b2c20ab539203b0cd5cca2a10f630bf62d8feb6d68d82af80f64774\", \"X-Timestamp\": \"1792159542\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792159542,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}" + } + } + } + } + } + }, + "400": { + "description": "The stored delivery could not be signed again. Nothing is sent.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string", + "description": "Stable machine-readable error code. Branch on this, not on `detail`." + } + } + }, + "examples": { + "Unsignable delivery": { + "value": { + "detail": "Invalid webhook data format", + "code": "invalid_webhook_payload" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A missing, invalid or revoked key gets `403`, not `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` A key limited to some workflows or to approved sessions gets `This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks write": { + "value": { + "detail": "This API key has no write access to webhooks." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The destination does not exist for the application or has been deleted, or the delivery does not exist, belongs to another destination, or is no longer available.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Destination not found": { + "value": { + "detail": "Webhook destination not found." + } + }, + "Delivery not found": { + "value": { + "detail": "Webhook delivery not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 300 write requests (POST, PATCH, PUT, DELETE) in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (300).", + "schema": { + "type": "integer", + "example": 300 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-write" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/": { + "get": { + "summary": "List monitoring events", + "description": "List the monitoring events stored for your application, newest `occurred_at` first, with optional filters that combine with AND. Multi-value filters (`event_category`, `action_type`, `status`, `retention_class`) repeat the parameter and match any of the values; the other filters are exact matches, and unknown values simply return no events. Each result is a summary: call `GET /v3/events/{event_uuid}/` for the payload, device context, IP enrichment and rule evidence, and prefer `GET /v3/events/subjects/{vendor_data}/` for one subject's history. Reads work whether or not event ingestion is switched on. A scoped API key needs `transactions: read`. See [Event ingestion](https://docs.didit.me/transaction-monitoring/events).", + "operationId": "list_monitoring_events", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of events per page. Defaults to 50. Values above 1000000 return `400`; a non-numeric, zero or negative value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "example": 50 + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of events to skip. Defaults to 0. Prefer following the `next` URL from the response.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "example": 0 + }, + { + "name": "event_category", + "in": "query", + "required": false, + "description": "Only events in these categories. Repeat the parameter for several values (`?event_category=payment&event_category=dispute`); an event matching any of them is returned.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ] + } + }, + "example": [ + "payment", + "dispute" + ] + }, + { + "name": "action_type", + "in": "query", + "required": false, + "description": "Only events with these action types (exact, case-sensitive). Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "example": [ + "deposit_completed", + "chargeback_opened" + ] + }, + { + "name": "status", + "in": "query", + "required": false, + "description": "Only events with these statuses. Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ] + } + }, + "example": [ + "IN_REVIEW", + "DECLINED" + ] + }, + { + "name": "retention_class", + "in": "query", + "required": false, + "description": "Only events in these retention classes. Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ] + } + }, + "example": [ + "risk_relevant" + ] + }, + { + "name": "subject_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the subject's `vendor_data`. The same id can name an individual and a company; add `subject_entity_type` to pick one, or use `GET /v3/events/subjects/{vendor_data}/`.", + "schema": { + "type": "string" + }, + "example": "user-42" + }, + { + "name": "subject_entity_type", + "in": "query", + "required": false, + "description": "`company` returns only events of company subjects; any other non-empty value (for example `individual`) returns only events of individuals. Omit it to return both.", + "schema": { + "type": "string", + "enum": [ + "individual", + "company" + ] + }, + "example": "individual" + }, + { + "name": "business_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the `business.vendor_data` sent with the event.", + "schema": { + "type": "string" + }, + "example": "merchant-7" + }, + { + "name": "session_id", + "in": "query", + "required": false, + "description": "Exact match on `session.session_id`.", + "schema": { + "type": "string" + }, + "example": "web-session-abc" + }, + { + "name": "device_fingerprint", + "in": "query", + "required": false, + "description": "Exact match on `device_context.fingerprint`.", + "schema": { + "type": "string" + }, + "example": "fp-a8c1" + }, + { + "name": "ip_address", + "in": "query", + "required": false, + "description": "Exact match on the event's IP address.", + "schema": { + "type": "string" + }, + "example": "203.0.113.42" + }, + { + "name": "game_id", + "in": "query", + "required": false, + "description": "Exact match on the promoted `game_id`.", + "schema": { + "type": "string" + }, + "example": "poker-holdem-01" + }, + { + "name": "round_id", + "in": "query", + "required": false, + "description": "Exact match on the promoted `round_id`.", + "schema": { + "type": "string" + }, + "example": "round-88231" + }, + { + "name": "affiliate_code", + "in": "query", + "required": false, + "description": "Exact match on the promoted `affiliate_code`.", + "schema": { + "type": "string" + }, + "example": "aff-summer" + }, + { + "name": "payment_reference_id", + "in": "query", + "required": false, + "description": "Exact match on the payment reference (from `transaction_context.payment_reference_id` or `payload.payment_reference_id`).", + "schema": { + "type": "string" + }, + "example": "dep_735" + }, + { + "name": "counterparty_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the promoted `counterparty_vendor_data`.", + "schema": { + "type": "string" + }, + "example": "user-77" + }, + { + "name": "matched_only", + "in": "query", + "required": false, + "description": "`true` (also `1` or `yes`, case-insensitive) returns only events at least one rule matched (`rules_matched_count` above 0). Any other value is ignored.", + "schema": { + "type": "boolean", + "default": false + }, + "example": true + }, + { + "name": "occurred_from", + "in": "query", + "required": false, + "description": "Only events with `occurred_at` at or after this instant (inclusive). Send an ISO 8601 value: `2026-10-01T00:00:00Z`, an explicit offset with the `+` percent-encoded (`2026-10-01T02:00:00%2B02:00`; an unencoded `+` is decoded as a space and the value no longer parses), or a date alone (`2026-10-01`), which means 00:00:00 UTC of that day. A value without an offset is read as UTC. Values in any other format are not accepted.", + "schema": { + "type": "string", + "format": "date-time" + }, + "example": "2026-10-01T00:00:00Z" + }, + { + "name": "occurred_to", + "in": "query", + "required": false, + "description": "Only events with `occurred_at` at or before this instant (inclusive). A date alone means the start of that day, so `occurred_to=2026-10-15` excludes the rest of 15 October; send `2026-10-15T23:59:59Z` to include it. Send an ISO 8601 value: `2026-10-01T00:00:00Z`, an explicit offset with the `+` percent-encoded (`2026-10-01T02:00:00%2B02:00`; an unencoded `+` is decoded as a space and the value no longer parses), or a date alone (`2026-10-01`), which means 00:00:00 UTC of that day. A value without an offset is read as UTC. Values in any other format are not accepted.", + "schema": { + "type": "string", + "format": "date-time" + }, + "example": "2026-10-17T23:59:59Z" + }, + { + "name": "search", + "in": "query", + "required": false, + "description": "Case-insensitive substring match on `event_id`, `action_type`, `subject_vendor_data` or `counterparty_vendor_data`.", + "schema": { + "type": "string" + }, + "example": "chargeback" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/events/?event_category=payment&event_category=dispute&matched_only=true&occurred_from=2026-10-01T00:00:00Z&limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nurl = 'https://verification.didit.me/v3/events/'\nparams = {\n 'event_category': ['payment', 'dispute'],\n 'matched_only': 'true',\n 'occurred_from': '2026-10-01T00:00:00Z',\n 'limit': 50,\n}\nwhile url:\n resp = requests.get(\n url,\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n params=params,\n timeout=10,\n )\n resp.raise_for_status()\n page = resp.json()\n for event in page['results']:\n print(event['event_uuid'], event['action_type'], event['status'])\n url, params = page['next'], None" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const params = new URLSearchParams([\n ['event_category', 'payment'],\n ['event_category', 'dispute'],\n ['matched_only', 'true'],\n ['occurred_from', '2026-10-01T00:00:00Z'],\n ['limit', '50'],\n]);\nlet url = `https://verification.didit.me/v3/events/?${params}`;\nwhile (url) {\n const resp = await fetch(url, {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n });\n if (!resp.ok) throw new Error(`Request failed: ${resp.status}`);\n const page = await resp.json();\n for (const event of page.results) console.log(event.event_uuid, event.action_type, event.status);\n url = page.next;\n}" + } + ], + "responses": { + "200": { + "description": "One page of event summaries.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventPage" + }, + "examples": { + "Payment and dispute events": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "event_uuid": "11111111-2222-3333-4444-555555555555", + "event_id": "chargeback-2026-10-16-000318", + "event_category": "dispute", + "action_type": "chargeback_opened", + "occurred_at": "2026-10-16T14:02:11Z", + "received_at": "2026-10-16T14:02:12.418230Z", + "status": "IN_REVIEW", + "score": 60, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": null, + "device_fingerprint": null, + "ip_address": null, + "ip_country": null, + "amount": "250.000000000000000000", + "currency": "EUR", + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": "dep_735", + "retention_class": "monetary_or_compliance", + "rules_matched_count": 1, + "correlation_pending": false, + "is_droppable": false + }, + { + "event_uuid": "22222222-3333-4444-5555-666666666666", + "event_id": "login-2026-10-15-000912", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-15T08:30:00.120000Z", + "received_at": "2026-10-15T08:30:00.734512Z", + "status": "ACCEPTED", + "score": 0, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": "web-session-abc", + "device_fingerprint": "fp-a8c1", + "ip_address": "203.0.113.42", + "ip_country": "ES", + "amount": null, + "currency": null, + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": null, + "retention_class": "standard", + "rules_matched_count": 0, + "correlation_pending": false, + "is_droppable": true + } + ] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is larger than 1000000. The body is a JSON array with one message. A non-numeric or non-positive `limit` falls back to the default of 50 instead of failing.", + "content": { + "application/json": { + "examples": { + "limit too large": { + "summary": "limit above 1000000", + "value": [ + "limit must not exceed 1000000." + ] + }, + "offset too large": { + "summary": "offset above 1000000", + "value": [ + "offset must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing or invalid `x-api-key` (this endpoint answers `403`, never `401`, for a missing or invalid key), a scoped key without `transactions: read`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: `GET` requests share a budget of 600 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create monitoring event", + "description": "Send one non-monetary event (login, session, gameplay, bonus, affiliate, payment, dispute, security, responsible gambling or custom) from your backend. The event is validated against its category contract, stored, IP-enriched once per unique public address, and evaluated by your real-time event rules before the response returns; cross-subject and long-window rules run asynchronously (`correlation_pending: true`), and in a sandbox application no rules run. `event_id` is the idempotency key: the same `event_id` with the same `event_category`, `action_type`, `occurred_at`, `subject.vendor_data` and `payload` returns `200` with `idempotent_replay: true` and the stored outcome (other envelope fields are not compared), while different values return `409`. Event ingestion is opt-in per application: until it is switched on this endpoint returns `403` with `code: event_monitoring_disabled` and stores nothing. A scoped API key needs `transactions: write`. See [Event ingestion](https://docs.didit.me/transaction-monitoring/events) and [Rules on events](https://docs.didit.me/transaction-monitoring/event-rules); to send events from a browser or app, use [SDK event submission](https://docs.didit.me/transaction-monitoring/sdk-event-submission).", + "operationId": "create_monitoring_event", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventCreateRequest" + }, + "examples": { + "Login": { + "value": { + "event_id": "login-2026-10-17-000001", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-17T09:15:02.481Z", + "time_zone": "Europe/Madrid", + "subject": { + "vendor_data": "user-42", + "entity_type": "individual" + }, + "session": { + "session_id": "web-session-abc", + "sequence": 142 + }, + "device_context": { + "fingerprint": "fp-a8c1", + "network_context": { + "ip_address": "203.0.113.42" + } + }, + "payload": { + "method": "password" + }, + "custom_values": { + "vip_tier": "gold" + } + } + }, + "Deposit completed": { + "value": { + "event_id": "deposit-2026-10-17-000002", + "event_category": "payment", + "action_type": "deposit_completed", + "occurred_at": "2026-10-17T09:16:40Z", + "subject": { + "vendor_data": "user-42" + }, + "transaction_context": { + "amount": "100.00", + "currency": "EUR", + "direction": "inbound", + "payment_reference_id": "dep_736" + }, + "payload": { + "payment_method": "card", + "status": "completed" + } + } + }, + "Chargeback opened": { + "value": { + "event_id": "chargeback-2026-10-16-000318", + "event_category": "dispute", + "action_type": "chargeback_opened", + "occurred_at": "2026-10-16T14:02:11Z", + "subject": { + "vendor_data": "user-42" + }, + "payload": { + "payment_reference_id": "dep_735", + "chargeback_id": "cb_5521", + "amount": "250.00", + "currency": "EUR", + "reason_code": "fraud" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/events/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"event_id\": \"login-2026-10-17-000001\",\n \"event_category\": \"auth\",\n \"action_type\": \"login\",\n \"occurred_at\": \"2026-10-17T09:15:02.481Z\",\n \"subject\": {\"vendor_data\": \"user-42\", \"entity_type\": \"individual\"},\n \"session\": {\"session_id\": \"web-session-abc\", \"sequence\": 142},\n \"device_context\": {\n \"fingerprint\": \"fp-a8c1\",\n \"network_context\": {\"ip_address\": \"203.0.113.42\"}\n },\n \"payload\": {\"method\": \"password\"}\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/events/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={\n 'event_id': 'login-2026-10-17-000001',\n 'event_category': 'auth',\n 'action_type': 'login',\n 'occurred_at': '2026-10-17T09:15:02.481Z',\n 'subject': {'vendor_data': 'user-42', 'entity_type': 'individual'},\n 'session': {'session_id': 'web-session-abc', 'sequence': 142},\n 'device_context': {\n 'fingerprint': 'fp-a8c1',\n 'network_context': {'ip_address': '203.0.113.42'},\n },\n 'payload': {'method': 'password'},\n },\n timeout=10,\n)\nresp.raise_for_status()\nresult = resp.json()\nprint(result['event_uuid'], result['status'], result['idempotent_replay'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/events/', {\n method: 'POST',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n event_id: 'login-2026-10-17-000001',\n event_category: 'auth',\n action_type: 'login',\n occurred_at: '2026-10-17T09:15:02.481Z',\n subject: { vendor_data: 'user-42', entity_type: 'individual' },\n session: { session_id: 'web-session-abc', sequence: 142 },\n device_context: {\n fingerprint: 'fp-a8c1',\n network_context: { ip_address: '203.0.113.42' },\n },\n payload: { method: 'password' },\n }),\n});\nif (!resp.ok) throw new Error(await resp.text());\nconst result = await resp.json();\nconsole.log(result.event_uuid, result.status, result.idempotent_replay);" + } + ], + "responses": { + "201": { + "description": "The event was stored and evaluated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventIngestResult" + }, + "examples": { + "Stored, no rule matched": { + "value": { + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "standard", + "expires_at": "2026-11-16T09:15:02.481000Z", + "idempotent_replay": false, + "rule_matches": [] + } + }, + "Rule matched, async rule pending": { + "value": { + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "IN_REVIEW", + "score": 40, + "rules_evaluated_count": 6, + "rules_matched_count": 1, + "correlation_pending": true, + "retention_class": "risk_relevant", + "expires_at": "2027-01-15T09:15:02.481000Z", + "idempotent_replay": false, + "rule_matches": [ + { + "rule_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "library_key": null, + "mode": "ACTIVE", + "is_async": false + } + ] + } + } + } + } + } + }, + "200": { + "description": "Idempotent replay: an event with this `event_id` and the same content is already stored. Nothing was stored or evaluated again; the body describes the stored event.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventIngestResult" + }, + "examples": { + "Replay": { + "value": { + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "standard", + "expires_at": "2026-11-16T09:15:02.481000Z", + "idempotent_replay": true, + "rule_matches": [] + } + } + } + } + } + }, + "400": { + "description": "Validation failed. The body is keyed by field; nested fields are nested objects. Nothing is stored.", + "content": { + "application/json": { + "examples": { + "Missing fields": { + "value": { + "event_id": [ + "This field is required." + ], + "subject": [ + "This field is required." + ] + } + }, + "Unknown category": { + "value": { + "event_category": [ + "\"login\" is not a valid choice." + ] + } + }, + "occurred_at too old": { + "value": { + "occurred_at": [ + "occurred_at is more than 7 days in the past. Use the batch import lane for backfills." + ] + } + }, + "occurred_at in the future": { + "value": { + "occurred_at": [ + "occurred_at is more than 5 minutes in the future." + ] + } + }, + "Missing subject vendor_data": { + "value": { + "subject": { + "vendor_data": [ + "This field is required." + ] + } + } + }, + "Missing required payload keys": { + "value": { + "payload": [ + "payment.deposit_completed requires: payment_method, status." + ] + } + }, + "Missing one-of payload key": { + "value": { + "payload": [ + "dispute.chargeback_opened requires one of: dispute_id, chargeback_id." + ] + } + }, + "Security change without changed_field": { + "value": { + "payload": [ + "security.*_changed requires 'changed_field'." + ] + } + }, + "Payload too large": { + "value": { + "payload": [ + "payload exceeds 16384 bytes. Summarise telemetry client-side." + ] + } + }, + "Too many custom_values": { + "value": { + "custom_values": [ + "custom_values accepts at most 100 keys." + ] + } + }, + "Nested custom value": { + "value": { + "custom_values": [ + "custom_values['limits'] must be a scalar - rule conditions cannot address nested values." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing or invalid `x-api-key` (this endpoint answers `403`, never `401`, for a missing or invalid key), a scoped key without `transactions: write`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist. Also returned with `code: event_monitoring_disabled` while event ingestion is switched off for the application; nothing is stored in that case, and the check runs before the body is validated.", + "content": { + "application/json": { + "examples": { + "Event monitoring disabled": { + "value": { + "detail": "Event monitoring is not enabled for this application. Turn on event ingestion in the transaction monitoring settings before submitting events.", + "code": "event_monitoring_disabled" + } + }, + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "409": { + "description": "`event_id` was already used for an event with different content (category, action type, `occurred_at`, subject `vendor_data` or payload). Send the event under a new `event_id`.", + "content": { + "application/json": { + "examples": { + "event_id reused": { + "value": { + "event_id": "event_id 'login-2026-10-17-000001' already exists with a different payload." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: write requests (`POST`, `PATCH`, `DELETE`) share a budget of 300 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/batch/": { + "post": { + "summary": "Create monitoring events in batch", + "description": "Submit 1 to 500 events in one request, each in the same envelope as `POST /v3/events/`. Every item is validated, stored and evaluated on its own and gets its own outcome (`accepted`, `replayed` or `rejected`), so one malformed event never rolls back the others: the response is `201` when no item was rejected and `207` when at least one was, and both mean the request was processed. Send `{\"events\": [...]}`; a bare JSON array of events is also accepted. Event ingestion must be switched on for the application, otherwise the whole request returns `403` with `code: event_monitoring_disabled` and nothing is stored. A scoped API key needs `transactions: write`. See [Batches](https://docs.didit.me/transaction-monitoring/events#batches).", + "operationId": "create_monitoring_events_batch", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "create_batch", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "events" + ], + "properties": { + "events": { + "type": "array", + "minItems": 1, + "maxItems": 500, + "description": "1 to 500 events. Each item follows `MonitoringEventCreateRequest`, including the 7-day past and 5-minute future window on `occurred_at`.", + "items": { + "$ref": "#/components/schemas/MonitoringEventCreateRequest" + } + } + } + }, + "example": { + "events": [ + { + "event_id": "login-2026-10-17-000001", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-17T09:15:02.481Z", + "subject": { + "vendor_data": "user-42" + } + }, + { + "event_id": "deposit-2026-10-17-000002", + "event_category": "payment", + "action_type": "deposit_completed", + "occurred_at": "2026-10-17T09:16:40Z", + "subject": { + "vendor_data": "user-42" + }, + "transaction_context": { + "amount": "100.00", + "currency": "EUR", + "payment_reference_id": "dep_736" + }, + "payload": { + "payment_method": "card", + "status": "completed" + } + } + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/events/batch/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"events\": [\n {\"event_id\": \"login-2026-10-17-000001\", \"event_category\": \"auth\", \"action_type\": \"login\",\n \"occurred_at\": \"2026-10-17T09:15:02.481Z\", \"subject\": {\"vendor_data\": \"user-42\"}},\n {\"event_id\": \"deposit-2026-10-17-000002\", \"event_category\": \"payment\", \"action_type\": \"deposit_completed\",\n \"occurred_at\": \"2026-10-17T09:16:40.000Z\", \"subject\": {\"vendor_data\": \"user-42\"},\n \"transaction_context\": {\"amount\": \"100.00\", \"currency\": \"EUR\", \"payment_reference_id\": \"dep_736\"},\n \"payload\": {\"payment_method\": \"card\", \"status\": \"completed\"}}\n ]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nevents = [\n {\n 'event_id': 'login-2026-10-17-000001',\n 'event_category': 'auth',\n 'action_type': 'login',\n 'occurred_at': '2026-10-17T09:15:02.481Z',\n 'subject': {'vendor_data': 'user-42'},\n },\n {\n 'event_id': 'deposit-2026-10-17-000002',\n 'event_category': 'payment',\n 'action_type': 'deposit_completed',\n 'occurred_at': '2026-10-17T09:16:40.000Z',\n 'subject': {'vendor_data': 'user-42'},\n 'transaction_context': {'amount': '100.00', 'currency': 'EUR', 'payment_reference_id': 'dep_736'},\n 'payload': {'payment_method': 'card', 'status': 'completed'},\n },\n]\nresp = requests.post(\n 'https://verification.didit.me/v3/events/batch/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={'events': events},\n timeout=10,\n)\nresp.raise_for_status() # 201 and 207 are both success\nbatch = resp.json()\nfor item in batch['results']:\n if item['outcome'] == 'rejected':\n print('fix and resend', events[item['index']]['event_id'], item['errors'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const events = [\n {\n event_id: 'login-2026-10-17-000001',\n event_category: 'auth',\n action_type: 'login',\n occurred_at: '2026-10-17T09:15:02.481Z',\n subject: { vendor_data: 'user-42' },\n },\n {\n event_id: 'deposit-2026-10-17-000002',\n event_category: 'payment',\n action_type: 'deposit_completed',\n occurred_at: '2026-10-17T09:16:40.000Z',\n subject: { vendor_data: 'user-42' },\n transaction_context: { amount: '100.00', currency: 'EUR', payment_reference_id: 'dep_736' },\n payload: { payment_method: 'card', status: 'completed' },\n },\n];\nconst resp = await fetch('https://verification.didit.me/v3/events/batch/', {\n method: 'POST',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ events }),\n});\nif (!resp.ok) throw new Error(await resp.text()); // 201 and 207 are both ok\nconst batch = await resp.json();\nfor (const item of batch.results) {\n if (item.outcome === 'rejected') console.log('fix and resend', events[item.index].event_id, item.errors);\n}" + } + ], + "responses": { + "201": { + "description": "No item was rejected: every item is `accepted` or `replayed`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "accepted_count", + "replayed_count", + "rejected_count", + "results" + ], + "properties": { + "accepted_count": { + "type": "integer", + "minimum": 0, + "description": "Items stored and evaluated." + }, + "replayed_count": { + "type": "integer", + "minimum": 0, + "description": "Items already stored with the same content." + }, + "rejected_count": { + "type": "integer", + "minimum": 0, + "description": "Items not stored." + }, + "results": { + "type": "array", + "description": "One entry per item, in request order.", + "items": { + "type": "object", + "description": "Outcome of one item. Accepted and replayed items also carry every field of `MonitoringEventIngestResult`; rejected items carry `event_id` and `errors` instead.", + "required": [ + "index", + "outcome" + ], + "properties": { + "index": { + "type": "integer", + "minimum": 0, + "description": "Position of the item in your `events` array." + }, + "outcome": { + "type": "string", + "enum": [ + "accepted", + "replayed", + "rejected" + ], + "description": "`accepted`: stored and evaluated. `replayed`: already stored with the same content. `rejected`: not stored; see `errors`." + }, + "event_id": { + "type": "string", + "nullable": true, + "description": "Your `event_id`. On items rejected by validation it is read from the item's snake_case `event_id` key, so it is null when the item omitted it or sent the `eventId` alias." + }, + "errors": { + "type": "object", + "additionalProperties": true, + "description": "Rejected items only. Validation errors keyed by field, in the same shape as the `400` of `POST /v3/events/`; a reused `event_id` with different content is reported as a string under `errors.event_id`." + }, + "event_uuid": { + "type": "string", + "format": "uuid", + "description": "Accepted and replayed items only." + }, + "status": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ], + "description": "Accepted and replayed items only." + }, + "score": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "rules_evaluated_count": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "rules_matched_count": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "correlation_pending": { + "type": "boolean", + "description": "Accepted and replayed items only." + }, + "retention_class": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ], + "description": "Accepted and replayed items only." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Accepted and replayed items only." + }, + "idempotent_replay": { + "type": "boolean", + "description": "Accepted and replayed items only." + }, + "rule_matches": { + "type": "array", + "description": "Accepted and replayed items only.", + "items": { + "type": "object", + "required": [ + "rule_id", + "library_key", + "mode", + "is_async" + ], + "properties": { + "rule_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the rule." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Library key when the rule was installed from the rules library, otherwise null." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "TEST" + ], + "description": "`TEST` rules record the match but apply no actions." + }, + "is_async": { + "type": "boolean", + "description": "`true` when the match came from the asynchronous pass." + } + } + } + } + } + } + } + } + }, + "examples": { + "All landed": { + "value": { + "accepted_count": 1, + "replayed_count": 1, + "rejected_count": 0, + "results": [ + { + "index": 0, + "outcome": "accepted", + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "standard", + "expires_at": "2026-11-16T09:15:02.481000Z", + "idempotent_replay": false, + "rule_matches": [] + }, + { + "index": 1, + "outcome": "replayed", + "event_uuid": "11111111-2222-3333-4444-555555555555", + "event_id": "deposit-2026-10-17-000002", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "monetary_or_compliance", + "expires_at": null, + "idempotent_replay": true, + "rule_matches": [] + } + ] + } + } + } + } + } + }, + "207": { + "description": "At least one item was rejected. The other items were stored; fix and resend only the rejected ones (resending an accepted item is safe, it comes back `replayed`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "accepted_count", + "replayed_count", + "rejected_count", + "results" + ], + "properties": { + "accepted_count": { + "type": "integer", + "minimum": 0, + "description": "Items stored and evaluated." + }, + "replayed_count": { + "type": "integer", + "minimum": 0, + "description": "Items already stored with the same content." + }, + "rejected_count": { + "type": "integer", + "minimum": 0, + "description": "Items not stored." + }, + "results": { + "type": "array", + "description": "One entry per item, in request order.", + "items": { + "type": "object", + "description": "Outcome of one item. Accepted and replayed items also carry every field of `MonitoringEventIngestResult`; rejected items carry `event_id` and `errors` instead.", + "required": [ + "index", + "outcome" + ], + "properties": { + "index": { + "type": "integer", + "minimum": 0, + "description": "Position of the item in your `events` array." + }, + "outcome": { + "type": "string", + "enum": [ + "accepted", + "replayed", + "rejected" + ], + "description": "`accepted`: stored and evaluated. `replayed`: already stored with the same content. `rejected`: not stored; see `errors`." + }, + "event_id": { + "type": "string", + "nullable": true, + "description": "Your `event_id`. On items rejected by validation it is read from the item's snake_case `event_id` key, so it is null when the item omitted it or sent the `eventId` alias." + }, + "errors": { + "type": "object", + "additionalProperties": true, + "description": "Rejected items only. Validation errors keyed by field, in the same shape as the `400` of `POST /v3/events/`; a reused `event_id` with different content is reported as a string under `errors.event_id`." + }, + "event_uuid": { + "type": "string", + "format": "uuid", + "description": "Accepted and replayed items only." + }, + "status": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ], + "description": "Accepted and replayed items only." + }, + "score": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "rules_evaluated_count": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "rules_matched_count": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "correlation_pending": { + "type": "boolean", + "description": "Accepted and replayed items only." + }, + "retention_class": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ], + "description": "Accepted and replayed items only." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Accepted and replayed items only." + }, + "idempotent_replay": { + "type": "boolean", + "description": "Accepted and replayed items only." + }, + "rule_matches": { + "type": "array", + "description": "Accepted and replayed items only.", + "items": { + "type": "object", + "required": [ + "rule_id", + "library_key", + "mode", + "is_async" + ], + "properties": { + "rule_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the rule." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Library key when the rule was installed from the rules library, otherwise null." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "TEST" + ], + "description": "`TEST` rules record the match but apply no actions." + }, + "is_async": { + "type": "boolean", + "description": "`true` when the match came from the asynchronous pass." + } + } + } + } + } + } + } + } + }, + "examples": { + "Partial success": { + "value": { + "accepted_count": 1, + "replayed_count": 0, + "rejected_count": 2, + "results": [ + { + "index": 0, + "outcome": "accepted", + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "standard", + "expires_at": "2026-11-16T09:15:02.481000Z", + "idempotent_replay": false, + "rule_matches": [] + }, + { + "index": 1, + "event_id": "deposit-2026-10-17-000002", + "outcome": "rejected", + "errors": { + "payload": [ + "payment.deposit_completed requires: payment_method, status." + ] + } + }, + { + "index": 2, + "event_id": "login-2026-10-16-000777", + "outcome": "rejected", + "errors": { + "event_id": "event_id 'login-2026-10-16-000777' already exists with a different payload." + } + } + ] + } + } + } + } + } + }, + "400": { + "description": "The batch itself is malformed: `events` is missing or not an array, is empty, or has more than 500 items. Nothing is stored; split large batches.", + "content": { + "application/json": { + "examples": { + "Not a list": { + "value": { + "events": "Expected a list of events." + } + }, + "Empty": { + "value": { + "events": "At least one event is required." + } + }, + "Too many": { + "value": { + "events": "A batch accepts at most 500 events; received 612." + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing or invalid `x-api-key` (this endpoint answers `403`, never `401`, for a missing or invalid key), a scoped key without `transactions: write`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist. Also returned with `code: event_monitoring_disabled` while event ingestion is switched off for the application; nothing is stored in that case, and the check runs before the body is validated.", + "content": { + "application/json": { + "examples": { + "Event monitoring disabled": { + "value": { + "detail": "Event monitoring is not enabled for this application. Turn on event ingestion in the transaction monitoring settings before submitting events.", + "code": "event_monitoring_disabled" + } + }, + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: write requests (`POST`, `PATCH`, `DELETE`) share a budget of 300 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/sdk-token/": { + "post": { + "summary": "Mint monitoring SDK token", + "description": "Mint a short-lived device credential bound to one subject, so your web or mobile app can send that user's events straight to Didit without an API key. The app sends the returned `monitoring_token` in the `X-Monitoring-Token` header to the device lane (`POST /v1/events/` and `POST /v1/events/batch/`), where the subject is always overwritten with this `vendor_data`, events outside `allowed_event_categories` are rejected, each submitted event spends one use, and a request whose `Origin` does not equal `bind_to_origin` is refused. Call it from your backend only; minting creates the credential and nothing else (no user is created). Event ingestion must be switched on for the application, otherwise this endpoint returns `403` with `code: event_monitoring_disabled` instead of minting a credential that could never write. A scoped API key needs `transactions: write`. See [SDK event submission](https://docs.didit.me/transaction-monitoring/sdk-event-submission).", + "operationId": "create_monitoring_event_sdk_token", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "create_sdk_token", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "camelCase aliases are accepted: `vendorData`, `entityType`, `allowedEventCategories`, `ttlSeconds`, `maxUses`, `bindToOrigin`.", + "required": [ + "vendor_data" + ], + "properties": { + "vendor_data": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "Your id for the subject every event submitted with this credential belongs to." + }, + "entity_type": { + "type": "string", + "maxLength": 32, + "default": "individual", + "description": "`company` binds a company subject; any other value (default `individual`) binds an individual." + }, + "allowed_event_categories": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ] + }, + "description": "Categories the credential may submit. Omit it or send `[]` to allow every category." + }, + "ttl_seconds": { + "type": "integer", + "minimum": 60, + "maximum": 86400, + "default": 900, + "description": "Lifetime of the credential in seconds." + }, + "max_uses": { + "type": "integer", + "minimum": 1, + "nullable": true, + "default": 1000, + "description": "Number of events the credential may submit (a batch spends one use per event). Send `null` for no cap within the lifetime." + }, + "bind_to_origin": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "When set, the browser `Origin` header of every device request must equal this value exactly; a request without `Origin` is refused." + } + } + }, + "example": { + "vendor_data": "user-42", + "allowed_event_categories": [ + "auth", + "session", + "gameplay" + ], + "ttl_seconds": 900, + "max_uses": 1000, + "bind_to_origin": "https://app.example.com" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/events/sdk-token/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"vendor_data\": \"user-42\",\n \"allowed_event_categories\": [\"auth\", \"session\", \"gameplay\"],\n \"ttl_seconds\": 900,\n \"max_uses\": 1000,\n \"bind_to_origin\": \"https://app.example.com\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.post(\n 'https://verification.didit.me/v3/events/sdk-token/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={\n 'vendor_data': 'user-42',\n 'allowed_event_categories': ['auth', 'session', 'gameplay'],\n 'ttl_seconds': 900,\n 'max_uses': 1000,\n 'bind_to_origin': 'https://app.example.com',\n },\n timeout=10,\n)\nresp.raise_for_status()\ncredential = resp.json()\n# Hand only monitoring_token to the app, never the API key.\nprint(credential['monitoring_token'], credential['expires_at'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/events/sdk-token/', {\n method: 'POST',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n vendor_data: 'user-42',\n allowed_event_categories: ['auth', 'session', 'gameplay'],\n ttl_seconds: 900,\n max_uses: 1000,\n bind_to_origin: 'https://app.example.com',\n }),\n});\nif (!resp.ok) throw new Error(await resp.text());\nconst { monitoring_token, expires_at } = await resp.json();\n// Hand only monitoring_token to the app, never the API key.\nconsole.log(monitoring_token, expires_at);" + } + ], + "responses": { + "201": { + "description": "The credential was minted. Hand `monitoring_token` to the app and treat it as a secret until `expires_at`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "monitoring_token", + "expires_at", + "allowed_event_categories", + "max_uses", + "bind_to_origin" + ], + "properties": { + "monitoring_token": { + "type": "string", + "description": "The device credential (a 43-character URL-safe token). Send it in the `X-Monitoring-Token` header." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the credential stops being accepted." + }, + "allowed_event_categories": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ] + }, + "description": "Categories the credential may submit; `[]` means every category." + }, + "max_uses": { + "type": "integer", + "nullable": true, + "description": "Event budget of the credential; null means no cap." + }, + "bind_to_origin": { + "type": "string", + "nullable": true, + "description": "Origin the credential is bound to, or null." + } + } + }, + "example": { + "monitoring_token": "q7ZtR2xKp9VbN4mWc8LdH1sYfJ3gT6uEaQ0oPiXzBkC", + "expires_at": "2026-10-17T09:30:02.481000Z", + "allowed_event_categories": [ + "auth", + "session", + "gameplay" + ], + "max_uses": 1000, + "bind_to_origin": "https://app.example.com" + } + } + } + }, + "400": { + "description": "Validation failed: missing `vendor_data`, `ttl_seconds` outside 60 to 86400, `max_uses` below 1, or an unknown category (keyed by its position in the array).", + "content": { + "application/json": { + "examples": { + "Missing vendor_data": { + "value": { + "vendor_data": [ + "This field is required." + ] + } + }, + "ttl_seconds too short": { + "value": { + "ttl_seconds": [ + "Ensure this value is greater than or equal to 60." + ] + } + }, + "Unknown category": { + "value": { + "allowed_event_categories": { + "1": [ + "\"login\" is not a valid choice." + ] + } + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing or invalid `x-api-key` (this endpoint answers `403`, never `401`, for a missing or invalid key), a scoped key without `transactions: write`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist. Also returned with `code: event_monitoring_disabled` while event ingestion is switched off for the application; nothing is stored in that case, and the check runs before the body is validated.", + "content": { + "application/json": { + "examples": { + "Event monitoring disabled": { + "value": { + "detail": "Event monitoring is not enabled for this application. Turn on event ingestion in the transaction monitoring settings before submitting events.", + "code": "event_monitoring_disabled" + } + }, + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: write requests (`POST`, `PATCH`, `DELETE`) share a budget of 300 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/{event_uuid}/": { + "get": { + "summary": "Get monitoring event", + "description": "Retrieve one monitoring event with everything the list returns plus the payload, custom values, device context, field provenance (`field_sources`), the promoted domain fields, `expires_at`, every matching rule run with its evidence, the IP enrichment and the tags rules attached. While `correlation_pending` is `true` an asynchronous rule still has to run; read the event again a few minutes later for the final `status`, `score` and `rule_runs`. Reads work whether or not event ingestion is switched on. A scoped API key needs `transactions: read`. See [Evidence](https://docs.didit.me/transaction-monitoring/event-rules#evidence) and [IP enrichment](https://docs.didit.me/transaction-monitoring/ip-enrichment).", + "operationId": "get_monitoring_event", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "event_uuid", + "in": "path", + "required": true, + "description": "Didit identifier of the event (`event_uuid` from the create, batch, list or timeline response), not your own `event_id`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/events/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nevent_uuid = '11111111-2222-3333-4444-555555555555'\nresp = requests.get(\n f'https://verification.didit.me/v3/events/{event_uuid}/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status()\nevent = resp.json()\nprint(event['status'], event['score'])\nfor run in event['rule_runs']:\n print(run['rule_title'], run['score_delta'], run['action_summary'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const eventUuid = '11111111-2222-3333-4444-555555555555';\nconst resp = await fetch(`https://verification.didit.me/v3/events/${eventUuid}/`, {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (!resp.ok) throw new Error(`Request failed: ${resp.status}`);\nconst event = await resp.json();\nconsole.log(event.status, event.score, event.rule_runs.length, 'rule runs');" + } + ], + "responses": { + "200": { + "description": "The event with its evidence.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventDetail" + }, + "examples": { + "Login from a VPN, in review": { + "value": { + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-17T09:15:02.481000Z", + "received_at": "2026-10-17T09:15:02.903114Z", + "status": "IN_REVIEW", + "score": 40, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": "web-session-abc", + "device_fingerprint": "fp-a8c1", + "ip_address": "203.0.113.42", + "ip_country": "ES", + "amount": null, + "currency": null, + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": null, + "retention_class": "risk_relevant", + "rules_matched_count": 1, + "correlation_pending": false, + "is_droppable": true, + "payload": { + "method": "password" + }, + "custom_values": { + "vip_tier": "gold", + "vpn_or_proxy_detected": true + }, + "device_context": { + "fingerprint": "fp-a8c1", + "network_context": { + "ip_address": "203.0.113.42", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_city": "Madrid", + "asn_number": 64500, + "asn_organization": "Example Networks", + "is_vpn_or_tor": true, + "is_data_center": false + } + }, + "field_sources": { + "subject.vendor_data": "customer", + "event_category": "customer", + "action_type": "customer", + "occurred_at": "customer", + "received_at": "server", + "payload.method": "customer", + "session.session_id": "customer", + "session.sequence": "customer", + "device_context.fingerprint": "customer", + "device_context.network_context.ip_address": "customer", + "device_context.network_context.ip_country": "server", + "device_context.network_context.ip_country_code": "server", + "device_context.network_context.ip_city": "server", + "device_context.network_context.asn_number": "server", + "device_context.network_context.asn_organization": "server", + "device_context.network_context.is_vpn_or_tor": "server", + "device_context.network_context.is_data_center": "server" + }, + "session_sequence": 142, + "table_id": null, + "hand_id": null, + "match_id": null, + "market_id": null, + "side": null, + "role": null, + "net_result_amount": null, + "payment_fingerprint": null, + "instrument_reference_hash": null, + "withdrawal_address_hash": null, + "dispute_id": null, + "chargeback_id": null, + "changed_field": null, + "identity_match_ref": null, + "schema_version": 1, + "expires_at": "2027-01-15T09:15:02.481000Z", + "rules_evaluated_count": 6, + "rule_runs": [ + { + "rule_uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "rule_title": "Login from VPN or Tor", + "library_key": null, + "matched": true, + "mode": "ACTIVE", + "is_async": false, + "severity": "MEDIUM", + "score_delta": 40, + "action_summary": "score +40; add_tag:VPN_LOGIN", + "matched_payload": [ + { + "field": "event.ip.is_vpn_or_tor", + "operator": "eq", + "expected_value": true, + "current_value": true, + "matched": true, + "group_index": null, + "group_logic": "AND", + "field_source": "server" + } + ], + "created_at": "2026-10-17T09:15:02.951207Z" + } + ], + "ip_enrichment": { + "ip_country_code": "ES", + "ip_city": "Madrid", + "asn_number": 64500, + "asn_organization": "Example Networks", + "is_vpn_or_tor": true, + "is_data_center": false, + "proxy_type": null, + "last_enriched_at": "2026-10-17T09:15:02.910442Z" + }, + "subject_profile": null, + "tags": [ + "VPN_LOGIN" + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing or invalid `x-api-key` (this endpoint answers `403`, never `401`, for a missing or invalid key), a scoped key without `transactions: read`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No event with this `event_uuid` exists in the application: it was never stored, it was deleted, or it belongs to another application. A path segment that is not a UUID does not match the route and also returns `404`.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No MonitoringEvent matches the given query." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: `GET` requests share a budget of 600 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/subjects/{vendor_data}/": { + "get": { + "summary": "Get subject event timeline", + "description": "List every monitoring event of one subject, newest `occurred_at` first. A `vendor_data` is unique only within an entity type, so the same id can name an individual and a company: `entity_type` picks which timeline you get, and it defaults to `individual`, so a company's events need `entity_type=company`. The filters of `GET /v3/events/` apply on top, and a `vendor_data` with no events returns an empty page rather than `404`. A scoped API key needs `transactions: read`. See [Reading events back](https://docs.didit.me/transaction-monitoring/events#reading-events-back).", + "operationId": "get_monitoring_event_subject_timeline", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "retrieve_subject_timeline", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "description": "Your id for the subject (`subject.vendor_data` of its events). URL-encode it; a value containing `/` cannot be addressed by this endpoint.", + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "example": "user-42" + }, + { + "name": "entity_type", + "in": "query", + "required": false, + "description": "`company` returns the company with this `vendor_data`; any other value, or none, returns the individual. `subject_entity_type` is accepted as an alias when `entity_type` is absent.", + "schema": { + "type": "string", + "enum": [ + "individual", + "company" + ], + "default": "individual" + }, + "example": "individual" + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of events per page. Defaults to 50. Values above 1000000 return `400`; a non-numeric, zero or negative value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "example": 50 + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of events to skip. Defaults to 0. Prefer following the `next` URL from the response.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "example": 0 + }, + { + "name": "event_category", + "in": "query", + "required": false, + "description": "Only events in these categories. Repeat the parameter for several values (`?event_category=payment&event_category=dispute`); an event matching any of them is returned.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ] + } + }, + "example": [ + "payment", + "dispute" + ] + }, + { + "name": "action_type", + "in": "query", + "required": false, + "description": "Only events with these action types (exact, case-sensitive). Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "example": [ + "deposit_completed", + "chargeback_opened" + ] + }, + { + "name": "status", + "in": "query", + "required": false, + "description": "Only events with these statuses. Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ] + } + }, + "example": [ + "IN_REVIEW", + "DECLINED" + ] + }, + { + "name": "retention_class", + "in": "query", + "required": false, + "description": "Only events in these retention classes. Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ] + } + }, + "example": [ + "risk_relevant" + ] + }, + { + "name": "business_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the `business.vendor_data` sent with the event.", + "schema": { + "type": "string" + }, + "example": "merchant-7" + }, + { + "name": "session_id", + "in": "query", + "required": false, + "description": "Exact match on `session.session_id`.", + "schema": { + "type": "string" + }, + "example": "web-session-abc" + }, + { + "name": "device_fingerprint", + "in": "query", + "required": false, + "description": "Exact match on `device_context.fingerprint`.", + "schema": { + "type": "string" + }, + "example": "fp-a8c1" + }, + { + "name": "ip_address", + "in": "query", + "required": false, + "description": "Exact match on the event's IP address.", + "schema": { + "type": "string" + }, + "example": "203.0.113.42" + }, + { + "name": "game_id", + "in": "query", + "required": false, + "description": "Exact match on the promoted `game_id`.", + "schema": { + "type": "string" + }, + "example": "poker-holdem-01" + }, + { + "name": "round_id", + "in": "query", + "required": false, + "description": "Exact match on the promoted `round_id`.", + "schema": { + "type": "string" + }, + "example": "round-88231" + }, + { + "name": "affiliate_code", + "in": "query", + "required": false, + "description": "Exact match on the promoted `affiliate_code`.", + "schema": { + "type": "string" + }, + "example": "aff-summer" + }, + { + "name": "payment_reference_id", + "in": "query", + "required": false, + "description": "Exact match on the payment reference (from `transaction_context.payment_reference_id` or `payload.payment_reference_id`).", + "schema": { + "type": "string" + }, + "example": "dep_735" + }, + { + "name": "counterparty_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the promoted `counterparty_vendor_data`.", + "schema": { + "type": "string" + }, + "example": "user-77" + }, + { + "name": "matched_only", + "in": "query", + "required": false, + "description": "`true` (also `1` or `yes`, case-insensitive) returns only events at least one rule matched (`rules_matched_count` above 0). Any other value is ignored.", + "schema": { + "type": "boolean", + "default": false + }, + "example": true + }, + { + "name": "occurred_from", + "in": "query", + "required": false, + "description": "Only events with `occurred_at` at or after this instant (inclusive). Send an ISO 8601 value: `2026-10-01T00:00:00Z`, an explicit offset with the `+` percent-encoded (`2026-10-01T02:00:00%2B02:00`; an unencoded `+` is decoded as a space and the value no longer parses), or a date alone (`2026-10-01`), which means 00:00:00 UTC of that day. A value without an offset is read as UTC. Values in any other format are not accepted.", + "schema": { + "type": "string", + "format": "date-time" + }, + "example": "2026-10-01T00:00:00Z" + }, + { + "name": "occurred_to", + "in": "query", + "required": false, + "description": "Only events with `occurred_at` at or before this instant (inclusive). A date alone means the start of that day, so `occurred_to=2026-10-15` excludes the rest of 15 October; send `2026-10-15T23:59:59Z` to include it. Send an ISO 8601 value: `2026-10-01T00:00:00Z`, an explicit offset with the `+` percent-encoded (`2026-10-01T02:00:00%2B02:00`; an unencoded `+` is decoded as a space and the value no longer parses), or a date alone (`2026-10-01`), which means 00:00:00 UTC of that day. A value without an offset is read as UTC. Values in any other format are not accepted.", + "schema": { + "type": "string", + "format": "date-time" + }, + "example": "2026-10-17T23:59:59Z" + }, + { + "name": "search", + "in": "query", + "required": false, + "description": "Case-insensitive substring match on `event_id`, `action_type`, `subject_vendor_data` or `counterparty_vendor_data`.", + "schema": { + "type": "string" + }, + "example": "chargeback" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/events/subjects/user-42/?entity_type=individual&limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\nfrom urllib.parse import quote\n\nimport requests\n\nvendor_data = 'user-42'\nresp = requests.get(\n f'https://verification.didit.me/v3/events/subjects/{quote(vendor_data, safe=\"\")}/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n params={'entity_type': 'individual', 'limit': 50},\n timeout=10,\n)\nresp.raise_for_status()\nfor event in resp.json()['results']:\n print(event['occurred_at'], event['event_category'], event['action_type'], event['status'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const vendorData = 'user-42';\nconst url = `https://verification.didit.me/v3/events/subjects/${encodeURIComponent(vendorData)}/?entity_type=individual&limit=50`;\nconst resp = await fetch(url, {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (!resp.ok) throw new Error(`Request failed: ${resp.status}`);\nconst page = await resp.json();\nfor (const event of page.results) console.log(event.occurred_at, event.event_category, event.action_type, event.status);" + } + ], + "responses": { + "200": { + "description": "One page of the subject's events.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventPage" + }, + "examples": { + "Individual timeline": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "event_uuid": "11111111-2222-3333-4444-555555555555", + "event_id": "chargeback-2026-10-16-000318", + "event_category": "dispute", + "action_type": "chargeback_opened", + "occurred_at": "2026-10-16T14:02:11Z", + "received_at": "2026-10-16T14:02:12.418230Z", + "status": "IN_REVIEW", + "score": 60, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": null, + "device_fingerprint": null, + "ip_address": null, + "ip_country": null, + "amount": "250.000000000000000000", + "currency": "EUR", + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": "dep_735", + "retention_class": "monetary_or_compliance", + "rules_matched_count": 1, + "correlation_pending": false, + "is_droppable": false + }, + { + "event_uuid": "22222222-3333-4444-5555-666666666666", + "event_id": "login-2026-10-15-000912", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-15T08:30:00.120000Z", + "received_at": "2026-10-15T08:30:00.734512Z", + "status": "ACCEPTED", + "score": 0, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": "web-session-abc", + "device_fingerprint": "fp-a8c1", + "ip_address": "203.0.113.42", + "ip_country": "ES", + "amount": null, + "currency": null, + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": null, + "retention_class": "standard", + "rules_matched_count": 0, + "correlation_pending": false, + "is_droppable": true + } + ] + } + }, + "No events": { + "value": { + "count": 0, + "next": null, + "previous": null, + "results": [] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is larger than 1000000. The body is a JSON array with one message. A non-numeric or non-positive `limit` falls back to the default of 50 instead of failing.", + "content": { + "application/json": { + "examples": { + "limit too large": { + "summary": "limit above 1000000", + "value": [ + "limit must not exceed 1000000." + ] + }, + "offset too large": { + "summary": "offset above 1000000", + "value": [ + "offset must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing or invalid `x-api-key` (this endpoint answers `403`, never `401`, for a missing or invalid key), a scoped key without `transactions: read`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: `GET` requests share a budget of 600 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/enrichment-metrics/": { + "get": { + "summary": "Get event enrichment metrics", + "description": "Return the event ingestion and IP-enrichment counters of your application for a period, summed over every lane (server, batch and device), so you can watch event volume and confirm that enrichment is resolved once per unique address rather than once per event. The period covers today plus the previous `days` days, counted by UTC calendar date. The three ratios are null when their denominator is zero. A scoped API key needs `transactions: read`. See [IP enrichment](https://docs.didit.me/transaction-monitoring/events#ip-enrichment).", + "operationId": "get_monitoring_event_enrichment_metrics", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "retrieve_enrichment_metrics", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "days", + "in": "query", + "required": false, + "description": "Length of the period in days. Defaults to 30; values are clamped to 1 to 365, and a non-integer value falls back to 30. The value applied is returned as `period_days`.", + "schema": { + "type": "integer", + "default": 30, + "minimum": 1, + "maximum": 365 + }, + "example": 30 + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/events/enrichment-metrics/?days=30' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nresp = requests.get(\n 'https://verification.didit.me/v3/events/enrichment-metrics/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n params={'days': 30},\n timeout=10,\n)\nresp.raise_for_status()\nmetrics = resp.json()\nprint(metrics['events_ingested'], metrics['cache_hit_rate'], metrics['provider_calls_per_1k_events'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const resp = await fetch('https://verification.didit.me/v3/events/enrichment-metrics/?days=30', {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (!resp.ok) throw new Error(`Request failed: ${resp.status}`);\nconst metrics = await resp.json();\nconsole.log(metrics.events_ingested, metrics.cache_hit_rate, metrics.provider_calls_per_1k_events);" + } + ], + "responses": { + "200": { + "description": "Counters and ratios for the period.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "events_ingested", + "events_rejected", + "enrichment_lookups", + "enrichment_cache_hits", + "enrichment_provider_calls", + "enrichment_skipped_low_value", + "unique_ips_seen", + "period_days", + "cache_hit_rate", + "unique_ip_ratio", + "provider_calls_per_1k_events" + ], + "properties": { + "events_ingested": { + "type": "integer", + "description": "New events stored. Idempotent replays are not counted." + }, + "events_rejected": { + "type": "integer", + "description": "Items rejected inside batch requests (server and device lanes). A rejected single-event request is not counted." + }, + "enrichment_lookups": { + "type": "integer", + "description": "Events whose public IP address was resolved, from the cache or freshly." + }, + "enrichment_cache_hits": { + "type": "integer", + "description": "Lookups answered from the cache." + }, + "enrichment_provider_calls": { + "type": "integer", + "description": "Lookups that needed a fresh resolution of the address." + }, + "enrichment_skipped_low_value": { + "type": "integer", + "description": "Low-value telemetry events whose address was not already known. They never trigger a fresh resolution of their own." + }, + "unique_ips_seen": { + "type": "integer", + "description": "Addresses that needed a fresh resolution." + }, + "period_days": { + "type": "integer", + "description": "The `days` value applied after clamping." + }, + "cache_hit_rate": { + "type": "number", + "nullable": true, + "description": "`enrichment_cache_hits / enrichment_lookups`, rounded to 4 decimals. Null when there were no lookups." + }, + "unique_ip_ratio": { + "type": "number", + "nullable": true, + "description": "`unique_ips_seen / events_ingested`, rounded to 4 decimals. Null when no events were ingested." + }, + "provider_calls_per_1k_events": { + "type": "number", + "nullable": true, + "description": "Fresh resolutions per 1000 ingested events, rounded to 2 decimals. Null when no events were ingested." + } + } + }, + "examples": { + "Last 30 days": { + "value": { + "events_ingested": 128450, + "events_rejected": 214, + "enrichment_lookups": 41230, + "enrichment_cache_hits": 40118, + "enrichment_provider_calls": 1112, + "enrichment_skipped_low_value": 74320, + "unique_ips_seen": 1112, + "period_days": 30, + "cache_hit_rate": 0.973, + "unique_ip_ratio": 0.0087, + "provider_calls_per_1k_events": 8.66 + } + }, + "No events yet": { + "value": { + "events_ingested": 0, + "events_rejected": 0, + "enrichment_lookups": 0, + "enrichment_cache_hits": 0, + "enrichment_provider_calls": 0, + "enrichment_skipped_low_value": 0, + "unique_ips_seen": 0, + "period_days": 30, + "cache_hit_rate": null, + "unique_ip_ratio": null, + "provider_calls_per_1k_events": null + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing or invalid `x-api-key` (this endpoint answers `403`, never `401`, for a missing or invalid key), a scoped key without `transactions: read`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: `GET` requests share a budget of 600 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/{event_uuid}/delete/": { + "delete": { + "summary": "Delete monitoring event", + "description": "Permanently delete one monitoring event together with its rule runs, notes and tag links, for example an event sent by mistake. After the `204` the event is gone from every read (retrieve returns `404`, and it no longer appears in the list or the subject timeline), the deletion is irreversible, and its `event_id` is free to be used for a new event. Not affected: the user or company the event resolved to, cases a rule opened, list entries a rule added, and the scores and statuses already applied to other events. To erase everything about a person or a company, delete the user or the company instead, which also deletes their events. A scoped API key needs `transactions: write`.", + "operationId": "delete_monitoring_event", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "event_uuid", + "in": "path", + "required": true, + "description": "Didit identifier of the event (`event_uuid` from the create, batch, list or timeline response), not your own `event_id`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/events/11111111-2222-3333-4444-555555555555/delete/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nevent_uuid = '11111111-2222-3333-4444-555555555555'\nresp = requests.delete(\n f'https://verification.didit.me/v3/events/{event_uuid}/delete/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status() # 204 No Content\nprint('deleted', event_uuid)" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const eventUuid = '11111111-2222-3333-4444-555555555555';\nconst resp = await fetch(`https://verification.didit.me/v3/events/${eventUuid}/delete/`, {\n method: 'DELETE',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (resp.status !== 204) throw new Error(`Delete failed: ${resp.status}`);\nconsole.log('deleted', eventUuid);" + } + ], + "responses": { + "204": { + "description": "The event was deleted. No body." + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing or invalid `x-api-key` (this endpoint answers `403`, never `401`, for a missing or invalid key), a scoped key without `transactions: write`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No event with this `event_uuid` exists in the application: it was never stored, it was deleted, or it belongs to another application. A path segment that is not a UUID does not match the route and also returns `404`.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No MonitoringEvent matches the given query." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: write requests (`POST`, `PATCH`, `DELETE`) share a budget of 300 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/kyb/parties/{partyUuid}/regenerate/": { + "post": { + "summary": "Regenerate a Key People party's KYC session", + "description": "Give a Key People party of a Business Verification (KYB) session a new individual KYC session after its previous one was deleted or expired before the person started it, so the Key People check can finish again; send no request body. It works when the party's KYC session was deleted (`kyc_session_deleted: true`) or expired before the person started it (`Expired`, never started). A session still in progress or one that ended in any other status (`Approved`, `Declined`, `In Review`, `Abandoned`, ...) returns `409` unless it was deleted. A party that never had one or a company party always returns `409`. Didit creates a new hosted KYC session with the workflow the party was verified with (its latest published version) or, when that is no longer known, the workflow your Key People settings assign to the party's role, so its link lasts that KYC workflow's session expiration time; it emails the link to the party when an email is on file, clears `is_skipped`, and recomputes the Key People check and the business session status. It then sends a `data.updated` [webhook](https://docs.didit.me/integration/webhooks) for the business session with trigger `console_key_people_regenerate`; to exempt the party instead, use `PATCH /v3/session/{sessionId}/kyb/parties/{partyUuid}/`. A scoped API key needs `decisions: write`, and without `sessions: write` it receives `kyc_session_url` as `null`. Deleted sessions qualify even if the person had already started or finished verification. Imported sessions do not qualify through expiry; they must have been deleted.", + "operationId": "post_v3_session_kyb_party_regenerate", + "x-didit-sdk": { + "resource": "sessions", + "method": "regenerate_kyb_party", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the parent Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "partyUuid", + "in": "path", + "required": true, + "description": "UUID of the Key People party (`key_people_checks[].submitted.parties[].uuid` on the business session decision). Must belong to the session referenced by `sessionId`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/session/33333333-4444-5555-6666-777777777777/kyb/parties/0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41/regenerate/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nsession_id = '33333333-4444-5555-6666-777777777777'\nparty_uuid = '0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41'\nresp = requests.post(\n f'https://verification.didit.me/v3/session/{session_id}/kyb/parties/{party_uuid}/regenerate/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status()\nparty = resp.json()\nprint(party['kyc_session_id'], party['kyc_session_status'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const sessionId = '33333333-4444-5555-6666-777777777777';\nconst partyUuid = '0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41';\nconst resp = await fetch(`https://verification.didit.me/v3/session/${sessionId}/kyb/parties/${partyUuid}/regenerate/`, {\n method: 'POST',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (!resp.ok) throw new Error(`Regenerate failed: ${resp.status}`);\nconst party = await resp.json();\nconsole.log(party.kyc_session_id, party.kyc_session_status);" + } + ], + "responses": { + "200": { + "description": "New KYC session created. Returns the updated party with its new child session, which expires after its KYC workflow's session expiration time.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBKeyPeopleParty" + }, + "example": { + "uuid": "0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41", + "entity_type": "person", + "source": "USER", + "name": "Jane Smith", + "first_name": "Jane", + "last_name": "Smith", + "company_name": null, + "registration_number": null, + "email": "user@example.com", + "nationality": "GB", + "phone_number": null, + "date_of_birth": "1980-05-04", + "position": "Director", + "custom_fields": {}, + "is_active": true, + "is_skipped": false, + "requires_verification": true, + "roles": [ + { + "role": "director", + "ownership_percent": null, + "voting_percent": null + } + ], + "kyc_session_id": "55555555-6666-7777-8888-999999999999", + "kyc_session_status": "Not Started", + "kyc_session_url": "https://verify.didit.me/session/abc123def456", + "kyc_session_deleted": false, + "kyb_sub_session_id": null, + "kyb_sub_session_status": null, + "kyb_sub_session_url": null, + "kyb_sub_session_deleted": false, + "verification_workflow_type": "kyc", + "verification_workflow_id": "12121212-3434-5656-7878-909090909090", + "verification_workflow_label": "Key People KYC", + "verification_workflow_features": [ + { + "feature": "ID_VERIFICATION", + "status": "Not Finished" + }, + { + "feature": "LIVENESS", + "status": "Not Finished" + }, + { + "feature": "AML", + "status": "Not Finished" + } + ], + "didit_internal_id": null + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. Missing, invalid and revoked keys return `403` (not `401`); so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits; no party with this UUID exists in your application; or the party belongs to another session. Each case has its own `detail`. The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's workflow or approved-only limits", + "value": { + "detail": "Session not found." + } + }, + "Unknown party": { + "summary": "No party with this UUID in your application", + "value": { + "detail": "No BusinessParty matches the given query." + } + }, + "Party of another session": { + "summary": "partyUuid exists but belongs to a different session", + "value": { + "detail": "Party does not belong to the given session." + } + } + } + } + } + }, + "409": { + "description": "The party has no eligible KYC session to replace. Unless deleted, a session still in progress or one that ended in another status (`Approved`, `Declined`, `In Review`, `Abandoned`, ...) cannot be regenerated. A party that never had one or a company party also returns `409`. An eligible session was deleted or expired before the person started it. Deleted sessions qualify even if the person had already started or finished verification. Imported sessions do not qualify through expiry; they must have been deleted. Branch on `code`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "detail", + "code" + ], + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string", + "enum": [ + "kyb_party_kyc_session_not_deleted" + ], + "description": "Stable machine-readable error code." + } + } + }, + "example": { + "detail": "This party's KYC session is still live (it has neither been deleted nor expired without starting), so it cannot be regenerated.", + "code": "kyb_party_kyc_session_not_deleted" + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/transactions/{transaction_id}/notes/": { + "get": { + "summary": "List transaction notes", + "description": "List the notes on one monitored transaction, newest first (`created_at` descending): notes you add through the API, notes and status changes reviewers make in the Console, and notes Didit adds automatically (a rule's `add_note` action, or a problem creating the user-action verification session), which carry `actor_name` `Rule Engine`. `metadata` tells the note types apart.\n\nPaginated with `limit` and `offset`. `count` is exact up to 100 and capped at 100 above that, so follow `next` until it is `null` instead of computing pages from `count`. This is a read with no side effects.\n\nA scoped API key needs `transactions: read`; a key limited to some workflows or to approved sessions cannot call this endpoint. Reviewers see the same list under **Notes** on the [transaction detail](https://docs.didit.me/transaction-monitoring/console#transaction-detail).", + "operationId": "list_transaction_notes", + "x-didit-sdk": { + "resource": "transactions.notes", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit transaction UUID: the `uuid` returned by `POST /v3/transactions/` and `GET /v3/transactions/`. Your own `txn_id` is not accepted.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "description": "Number of notes per page. Defaults to 50. A value that is not a positive integer falls back to 50; a value above 1000000 returns `400`." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "description": "Zero-based position of the first note to return. Prefer following `next`. A value that is not a whole number falls back to 0; a value above 1000000 returns `400`." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/abcdef12-3456-7890-abcd-ef1234567890/notes/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\ntransaction_id = 'abcdef12-3456-7890-abcd-ef1234567890'\nurl = f'https://verification.didit.me/v3/transactions/{transaction_id}/notes/'\nparams = {'limit': 50}\nwhile url:\n resp = requests.get(\n url,\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n params=params,\n timeout=10,\n )\n resp.raise_for_status()\n page = resp.json()\n for note in page['results']:\n print(note['created_at'], note['actor_name'], note['note'])\n url, params = page['next'], None" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const transactionId = 'abcdef12-3456-7890-abcd-ef1234567890';\nlet url = `https://verification.didit.me/v3/transactions/${transactionId}/notes/?limit=50`;\nwhile (url) {\n const resp = await fetch(url, {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n });\n if (!resp.ok) throw new Error(`List notes failed: ${resp.status}`);\n const page = await resp.json();\n for (const note of page.results) console.log(note.created_at, note.actor_name, note.note);\n url = page.next;\n}" + } + ], + "responses": { + "200": { + "description": "A page of notes, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Number of notes on the transaction: exact up to 100, capped at 100 above that. Follow `next` instead of computing pages from it." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionNote" + } + } + } + }, + "examples": { + "API, Console and rule notes": { + "value": { + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "uuid": "c1111111-2222-4333-8444-555555555555", + "note": "Customer confirmed the withdrawal by phone.", + "actor_email": null, + "actor_name": null, + "metadata": { + "mentioned_emails": [ + "analyst@example.com" + ] + }, + "created_at": "2026-10-16T10:05:12.481203Z" + }, + { + "uuid": "c2222222-3333-4444-8555-666666666666", + "note": "Status changed from IN_REVIEW to APPROVED. Reason: Source of funds verified", + "actor_email": "reviewer@example.com", + "actor_name": "Alex Reviewer", + "metadata": { + "type": "status_change", + "previous_status": "IN_REVIEW", + "new_status": "APPROVED", + "reason_code": "SOF_VERIFIED", + "reason_label": "Source of funds verified" + }, + "created_at": "2026-10-15T17:22:40.103911Z" + }, + { + "uuid": "c3333333-4444-4555-8666-777777777777", + "note": "Large withdrawal to a new wallet: check the source of funds.", + "actor_email": null, + "actor_name": "Rule Engine", + "metadata": { + "rule_uuid": "d1111111-2222-4333-8444-555555555555", + "source": "rule_action" + }, + "created_at": "2026-10-15T08:42:12.004518Z" + } + ] + } + }, + "No notes yet": { + "value": { + "count": 0, + "next": null, + "previous": null, + "results": [] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is above 1000000. The body is a JSON array of messages, not an object.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "examples": { + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + }, + "Offset too large": { + "value": [ + "offset must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing or invalid API key gets the generic message (never `401`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without transactions read": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists in the application (never created, deleted, or owned by another application). A `transaction_id` that is not a UUID does not reach the API: it returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "No Transaction matches the given query." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create transaction note", + "description": "Add a plain-text note to a monitored transaction's review timeline, where Console reviewers see it. A note is context, not a decision: the transaction's `status`, `score` and alerts do not change, no webhook is sent, and nothing is billed. The transaction's `activities` gain a `TRANSACTION_NOTE_ADDED` entry.\n\nNotes created with an API key have no author: `actor_email` and `actor_name` are `null`, so put the author in the text if you need it. Every address in `mentioned_emails` must belong to a member of your organization, otherwise the whole request fails with `400`; mentions are stored lower-cased and de-duplicated, and this endpoint does not notify the mentioned members.\n\nEach call creates a new note, so retrying a timed-out request can add it twice: list the notes before retrying. There is no API endpoint to edit or remove a note. A scoped API key needs `transactions: write`; a key limited to some workflows or to approved sessions cannot call this endpoint.", + "operationId": "create_transaction_note", + "x-didit-sdk": { + "resource": "transactions.notes", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit transaction UUID: the `uuid` returned by `POST /v3/transactions/` and `GET /v3/transactions/`. Your own `txn_id` is not accepted.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "note" + ], + "properties": { + "note": { + "type": "string", + "minLength": 1, + "description": "Plain-text note. Leading and trailing whitespace is trimmed and a blank note is rejected. No length limit." + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + }, + "default": [], + "description": "Emails of members of your organization to mention. Each must be a valid email of an organization member." + } + } + }, + "examples": { + "Note with a mention": { + "value": { + "note": "Customer confirmed the withdrawal by phone.", + "mentioned_emails": [ + "analyst@example.com" + ] + } + }, + "Plain note": { + "value": { + "note": "Source of funds document requested by email." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/abcdef12-3456-7890-abcd-ef1234567890/notes/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"note\": \"Customer confirmed the withdrawal by phone.\", \"mentioned_emails\": [\"analyst@example.com\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\ntransaction_id = 'abcdef12-3456-7890-abcd-ef1234567890'\nresp = requests.post(\n f'https://verification.didit.me/v3/transactions/{transaction_id}/notes/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={\n 'note': 'Customer confirmed the withdrawal by phone.',\n 'mentioned_emails': ['analyst@example.com'],\n },\n timeout=10,\n)\nresp.raise_for_status()\nnote = resp.json()\nprint(note['uuid'], note['created_at'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const transactionId = 'abcdef12-3456-7890-abcd-ef1234567890';\nconst resp = await fetch(`https://verification.didit.me/v3/transactions/${transactionId}/notes/`, {\n method: 'POST',\n headers: {\n 'x-api-key': process.env.DIDIT_API_KEY,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n note: 'Customer confirmed the withdrawal by phone.',\n mentioned_emails: ['analyst@example.com'],\n }),\n});\nif (!resp.ok) throw new Error(`Create note failed: ${resp.status}`);\nconst note = await resp.json();\nconsole.log(note.uuid, note.created_at);" + } + ], + "responses": { + "201": { + "description": "The note was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionNote" + }, + "examples": { + "Created": { + "value": { + "uuid": "c1111111-2222-4333-8444-555555555555", + "note": "Customer confirmed the withdrawal by phone.", + "actor_email": null, + "actor_name": null, + "metadata": { + "mentioned_emails": [ + "analyst@example.com" + ] + }, + "created_at": "2026-10-16T10:05:12.481203Z" + } + } + } + } + } + }, + "400": { + "description": "The body is invalid: `note` missing, null or blank, an invalid address in `mentioned_emails` (errors keyed by list position), or a mentioned address that is not a member of your organization. Nothing is created.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Field errors keyed by field name.", + "additionalProperties": {} + }, + "examples": { + "Missing note": { + "value": { + "note": [ + "This field is required." + ] + } + }, + "Blank note": { + "value": { + "note": [ + "This field may not be blank." + ] + } + }, + "Invalid email": { + "value": { + "mentioned_emails": { + "0": [ + "Enter a valid email address." + ] + } + } + }, + "Not an organization member": { + "value": { + "mentioned_emails": [ + "Mentioned users must belong to this organization and have access to this application." + ] + } + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing or invalid API key gets the generic message (never `401`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without transactions write": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists in the application (never created, deleted, or owned by another application). A `transaction_id` that is not a UUID does not reach the API: it returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "No Transaction matches the given query." + } + } + } + } + } + }, + "429": { + "description": "More than 300 write requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/transactions/{transaction_id}/related/": { + "get": { + "summary": "List related transactions", + "description": "List the other transactions in your application that involve the same people or companies as this one. A transaction is related when any of its parties, in any role, has the same `vendor_data` as one of this transaction's parties. The transaction itself is never returned, and parties without `vendor_data` are ignored: when none of this transaction's parties has one, the result is always empty.\n\nResults use the summary shape of `GET /v3/transactions/`, newest first (`txn_date`, then `created_at`). Paginated with `limit` and `offset`; `count` is exact up to 100 and capped at 100 above that, so follow `next` until it is `null`.\n\nA scoped API key needs `transactions: read`; a key limited to some workflows or to approved sessions cannot call this endpoint. Send the same `vendor_data` for the same person or company on every [transaction](https://docs.didit.me/transaction-monitoring/transactions#subject-and-counterparty) to make this list complete.", + "operationId": "list_related_transactions", + "x-didit-sdk": { + "resource": "transactions", + "method": "list_related", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit transaction UUID: the `uuid` returned by `POST /v3/transactions/` and `GET /v3/transactions/`. Your own `txn_id` is not accepted.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "description": "Number of transactions per page. Defaults to 50. A value that is not a positive integer falls back to 50; a value above 1000000 returns `400`." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "description": "Zero-based position of the first transaction to return. Prefer following `next`. A value that is not a whole number falls back to 0; a value above 1000000 returns `400`." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/abcdef12-3456-7890-abcd-ef1234567890/related/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\ntransaction_id = 'abcdef12-3456-7890-abcd-ef1234567890'\nresp = requests.get(\n f'https://verification.didit.me/v3/transactions/{transaction_id}/related/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n params={'limit': 50},\n timeout=10,\n)\nresp.raise_for_status()\nfor tx in resp.json()['results']:\n print(tx['uuid'], tx['txn_date'], tx['status'], tx['amount'], tx['currency'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const transactionId = 'abcdef12-3456-7890-abcd-ef1234567890';\nconst resp = await fetch(\n `https://verification.didit.me/v3/transactions/${transactionId}/related/?limit=50`,\n { headers: { 'x-api-key': process.env.DIDIT_API_KEY } },\n);\nif (!resp.ok) throw new Error(`Related lookup failed: ${resp.status}`);\nconst page = await resp.json();\nfor (const tx of page.results) console.log(tx.uuid, tx.status, tx.amount, tx.currency);" + } + ], + "responses": { + "200": { + "description": "A page of related transactions, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Number of related transactions: exact up to 100, capped at 100 above that. Follow `next` instead of computing pages from it." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionSummary" + } + } + } + }, + "examples": { + "One related transaction": { + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "abcdef99-3456-4890-abcd-ef1234567890", + "transaction_number": 4087, + "txn_id": "your-txn-2026-10-02-017", + "transaction_type": "finance", + "action_type": "deposit", + "status": "APPROVED", + "source": "LIVE", + "direction": "INBOUND", + "amount": "250", + "currency": "EUR", + "currency_type": "fiat", + "preferred_currency_amount": "270.08", + "preferred_currency_code": "USD", + "payment_details": "Card top-up", + "txn_date": "2026-10-02T14:10:00Z", + "created_at": "2026-10-02T14:10:03.512244Z", + "score": 12, + "severity": "LOW", + "decision_reason_code": null, + "decision_reason_label": null, + "tags": [], + "subject_name": "Maria Garcia", + "subject_vendor_data": "user-12345", + "subject_type": "user", + "subject_uuid": "e1111111-2222-4333-8444-555555555555", + "counterparty_name": null, + "counterparty_vendor_data": null, + "counterparty_country": null, + "subject_country": "ESP", + "assigned_to_name": null, + "travel_rule_status": null, + "application_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "application_name": "Production app", + "organization_id": "11111111-2222-3333-4444-555555555555", + "organization_name": "Example Org" + } + ] + } + }, + "No party has vendor_data": { + "value": { + "count": 0, + "next": null, + "previous": null, + "results": [] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is above 1000000. The body is a JSON array of messages, not an object.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "examples": { + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + }, + "Offset too large": { + "value": [ + "offset must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing or invalid API key gets the generic message (never `401`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without transactions read": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists in the application (never created, deleted, or owned by another application). A `transaction_id` that is not a UUID does not reach the API: it returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "No Transaction matches the given query." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/businesses/{business_session_id}/pdf/": { + "get": { + "summary": "Download a business session PDF report", + "description": "Render the PDF report of one Business Verification (KYB) session: company registry data, key people and beneficial owners, documents, AML results and the other workflow features, plus the session's tags and review activity. With white-label customization on, the report carries your logo and privacy policy URL. The body is the PDF itself (`application/pdf`), not JSON.\n\nThe session must be `Approved`, `Declined` or `In Review`; any other status returns `403`, so wait for the `status.updated` [webhook](https://docs.didit.me/integration/webhooks) first. The report is rendered on every call and nothing is billed, so store the file instead of fetching it repeatedly. `GET /v3/session/{sessionId}/generate-pdf/` renders the same kind of report by session ID alone.\n\nA PDF report counts as media: a scoped API key needs `businesses: read` and `media: read`, and a key limited to some workflows or to approved sessions cannot call it.", + "operationId": "get_business_session_pdf", + "x-didit-sdk": { + "resource": "businesses", + "method": "generate_session_pdf", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID.", + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID. Must be the application the API key belongs to: a key used on any other application gets `403`.", + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + }, + { + "name": "business_session_id", + "in": "path", + "required": true, + "description": "`session_id` of the Business Verification (KYB) session, as returned when the session was created. A session of another application returns `404`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl --fail -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/businesses/22222222-3333-4444-5555-666666666666/pdf/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n --output business_report.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\norg_id = '11111111-2222-3333-4444-555555555555'\napp_id = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'\nbusiness_session_id = '22222222-3333-4444-5555-666666666666'\nresp = requests.get(\n f'https://verification.didit.me/v3/organization/{org_id}/application/{app_id}'\n f'/businesses/{business_session_id}/pdf/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=60,\n)\nresp.raise_for_status()\nwith open(f'business_{business_session_id}.pdf', 'wb') as fh:\n fh.write(resp.content)" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "import { writeFile } from 'node:fs/promises';\n\nconst orgId = '11111111-2222-3333-4444-555555555555';\nconst appId = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee';\nconst businessSessionId = '22222222-3333-4444-5555-666666666666';\nconst resp = await fetch(\n `https://verification.didit.me/v3/organization/${orgId}/application/${appId}/businesses/${businessSessionId}/pdf/`,\n { headers: { 'x-api-key': process.env.DIDIT_API_KEY } },\n);\nif (!resp.ok) throw new Error(`PDF generation failed: ${resp.status}`);\nawait writeFile(`business_${businessSessionId}.pdf`, Buffer.from(await resp.arrayBuffer()));" + } + ], + "responses": { + "200": { + "description": "The rendered PDF, returned directly as binary `application/pdf`.", + "headers": { + "Content-Disposition": { + "description": "Always `attachment; filename=business_{business_session_id}.pdf`.", + "schema": { + "type": "string", + "example": "attachment; filename=business_22222222-3333-4444-5555-666666666666.pdf" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed or the session is not finished. Read `detail`: a session that is not `Approved`, `Declined` or `In Review`, a missing or invalid API key (never `401`), an `application_id` that is not the key's application, a scoped key without `media: read` or `businesses: read`, a key limited to workflows or approved sessions, or a request from outside the key's IP list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Session not finished": { + "value": { + "detail": "You can only generate a PDF for sessions in review, declined or approved." + } + }, + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without media read": { + "value": { + "detail": "This API key has no read access to media." + } + }, + "Scoped key without businesses read": { + "value": { + "detail": "This API key has no read access to companies." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No business session with this ID exists in the application (never created, deleted, or owned by another application). A value that is not a UUID returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "Business session not found." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/transactions/{transaction_uuid}/pdf/": { + "get": { + "summary": "Download a transaction PDF report", + "description": "Render one monitored transaction as a PDF report: overview and verdict, parties and payment methods, custom properties, alerts and rule runs, screening and Travel Rule results, the network summary, the cost breakdown, up to 50 related transactions and the 20 most recent activity entries. The body is the PDF itself (`application/pdf`), not JSON.\n\nThe report is rendered from the transaction's current data on every call, so request it again after a status change. Nothing is stored and nothing is billed. Rendering can take a few seconds, so use a generous client timeout.\n\nThis endpoint lives on an application-scoped path: put your organization ID and the ID of the application your API key belongs to in the URL ([find your organization ID](https://docs.didit.me/getting-started/api-authentication#find-your-organization-id)). A PDF report counts as media: a scoped API key needs `media: read` and `transactions: read`, and a key limited to some workflows or to approved sessions cannot call it.", + "operationId": "get_transaction_pdf", + "x-didit-sdk": { + "resource": "transactions", + "method": "generate_pdf", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID.", + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID. Must be the application the API key belongs to: a key used on any other application gets `403`.", + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + }, + { + "name": "transaction_uuid", + "in": "path", + "required": true, + "description": "Didit transaction UUID: the `uuid` returned by `POST /v3/transactions/` and `GET /v3/transactions/`. Your own `txn_id` is not accepted.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl --fail -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/transactions/abcdef12-3456-7890-abcd-ef1234567890/pdf/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n --output transaction_report.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\norg_id = '11111111-2222-3333-4444-555555555555'\napp_id = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'\ntransaction_uuid = 'abcdef12-3456-7890-abcd-ef1234567890'\nresp = requests.get(\n f'https://verification.didit.me/v3/organization/{org_id}/application/{app_id}'\n f'/transactions/{transaction_uuid}/pdf/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=60,\n)\nresp.raise_for_status()\nwith open(f'transaction_{transaction_uuid}.pdf', 'wb') as fh:\n fh.write(resp.content)" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "import { writeFile } from 'node:fs/promises';\n\nconst orgId = '11111111-2222-3333-4444-555555555555';\nconst appId = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee';\nconst transactionUuid = 'abcdef12-3456-7890-abcd-ef1234567890';\nconst resp = await fetch(\n `https://verification.didit.me/v3/organization/${orgId}/application/${appId}/transactions/${transactionUuid}/pdf/`,\n { headers: { 'x-api-key': process.env.DIDIT_API_KEY } },\n);\nif (!resp.ok) throw new Error(`PDF generation failed: ${resp.status}`);\nawait writeFile(`transaction_${transactionUuid}.pdf`, Buffer.from(await resp.arrayBuffer()));" + } + ], + "responses": { + "200": { + "description": "The rendered PDF, returned directly as binary `application/pdf`.", + "headers": { + "Content-Disposition": { + "description": "Always `attachment; filename=transaction_{transaction_uuid}.pdf`.", + "schema": { + "type": "string", + "example": "attachment; filename=transaction_abcdef12-3456-7890-abcd-ef1234567890.pdf" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. Read `detail`: a missing or invalid API key (never `401`), an `application_id` that is not the key's application, a scoped key without `media: read` or `transactions: read`, a key limited to workflows or approved sessions, or a request from outside the key's IP list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without media read": { + "value": { + "detail": "This API key has no read access to media." + } + }, + "Scoped key without transactions read": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists in the application (never created, deleted, or owned by another application). A `transaction_uuid` that is not a UUID does not reach the API: it returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "No Transaction matches the given query." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/analytics/": { + "get": { + "summary": "Get application analytics", + "description": "Read the numbers behind the Business Console [analytics dashboard](https://docs.didit.me/console/analytics) for a date range. Without `scope` and `widget_keys` the response is the user verification (KYC) summary (`ApplicationAnalyticsSummary`): daily session counts, status totals, conversion rate, the same figures for the preceding period of equal length, locations, demographics, devices, workflow funnels, active verification time, resubmissions, warnings and time series. With `scope` or `widget_keys` it is the widgets of one dashboard tab (`ApplicationAnalyticsWidgetResponse`): `kyc`, `kyb`, `transactions` or `home`.\n\nSession counts are by session creation date (UTC) and reflect each session's current status. They come from daily rollups refreshed in the background, so the newest activity can take a short while to appear. Invalid workflow IDs and versions are ignored rather than rejected. Nothing is billed.\n\nOnly a full-access API key can call this endpoint: every scoped key gets `403`, whatever access it holds, and so does a key of another application. See [API keys](https://docs.didit.me/console/api-keys).", + "operationId": "get_application_analytics", + "x-didit-sdk": { + "resource": "analytics", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Analytics" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID.", + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID. Must be the application the API key belongs to: a key used on any other application gets `403`.", + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + }, + { + "name": "start_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date", + "example": "2026-10-01" + }, + "description": "First day of the range, `YYYY-MM-DD`, inclusive (UTC). Send it together with `end_date`: when either is missing, both are ignored and the range runs from 30 days before today through today." + }, + { + "name": "end_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date", + "example": "2026-10-15" + }, + "description": "Last day of the range, `YYYY-MM-DD`, inclusive (UTC). A future date counts as today. The range may not exceed 730 days." + }, + { + "name": "scope", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "home", + "kyc", + "kyb", + "transactions" + ] + }, + "description": "Dashboard tab to return widgets for (case-insensitive). Omit it, together with `widget_keys`, for the KYC summary. Sending only `widget_keys` implies `kyc`. `home` accepts any KYC, KYB or transaction widget and defaults to the application's saved home layout." + }, + { + "name": "widget_keys", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "tx_count_total,tx_count_daily" + }, + "description": "Comma-separated widget keys, answered in this order. Keys the scope does not accept are dropped; when none is left you get the scope's defaults. KYC: `kyc_sessions_volume`, `kyc_warning_insights`, `kyc_id_locations`, `kyc_ip_locations`, `kyc_workflow_funnel`, `kyc_average_verification_time`, `kyc_conversion_rate`, `kyc_conversion_by_country`, `kyc_resubmission_insights`, `kyc_original_status_distribution`, `kyc_database_validation_coverage`, `kyc_database_validation_latency`, `kyc_demographics`, `kyc_device_details`. KYB: `kyb_sessions_volume`, `kyb_workflow_funnel`, `kyb_average_verification_time`, `kyb_ip_locations`, `kyb_business_country_distribution`, `kyb_warning_insights`, `kyb_conversion_rate`, `kyb_conversion_by_country`, `kyb_industry_breakdown`, `kyb_resubmission_insights`, `kyb_original_status_distribution`, `kyb_registry_coverage`, `kyb_registry_data_completeness`, `kyb_device_details`. Transactions: `tx_count_total`, `tx_count_daily`, `tx_volume_total_preferred_currency`, `tx_volume_daily_preferred_currency`, `tx_average_amount`, `tx_average_amount_daily`, `tx_transaction_category_breakdown`, `tx_action_type_breakdown`, `tx_status_volume_trend`, `tx_user_ip_or_country_distribution`, `tx_tag_breakdown`, `tx_top_rules_triggered`, `tx_team_member_performance`, `tx_score_histogram`." + }, + { + "name": "workflow_ids", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "33333333-4444-5555-6666-777777777777" + }, + "description": "Comma-separated workflow IDs. Limits the KYC summary and the KYC and KYB widgets to those workflows. Values that are not UUIDs are ignored; when none is valid the filter is dropped. The summary also accepts `workflow_id`." + }, + { + "name": "versions", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "3,4" + }, + "description": "Comma-separated workflow version numbers. Applies only together with `workflow_ids`; values that are not whole numbers are ignored. `version` is accepted as an alias." + }, + { + "name": "branch_path_ids", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "default" + }, + "description": "Comma-separated branch path IDs. Applies only together with `workflow_ids`, and only to KYC figures. Figures are currently recorded only for `default` (all paths combined), so any other value returns empty figures. `branch_path_id` is accepted as an alias." + }, + { + "name": "tag", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Transaction widgets only: comma-separated tag UUIDs or tag names." + }, + { + "name": "transaction_type", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "finance" + }, + "description": "Transaction widgets only: comma-separated transaction categories." + }, + { + "name": "action_type", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "withdrawal" + }, + "description": "Transaction widgets only: comma-separated action types (case-insensitive)." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/analytics/?start_date=2026-10-01&end_date=2026-10-15' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\norg_id = '11111111-2222-3333-4444-555555555555'\napp_id = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'\nresp = requests.get(\n f'https://verification.didit.me/v3/organization/{org_id}/application/{app_id}/analytics/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n params={'start_date': '2026-10-01', 'end_date': '2026-10-15'},\n timeout=10,\n)\nresp.raise_for_status()\ndata = resp.json()\nprint(data['request_breakdown']['total_approved'], data['conversion_rate'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const orgId = '11111111-2222-3333-4444-555555555555';\nconst appId = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee';\nconst params = new URLSearchParams({ start_date: '2026-10-01', end_date: '2026-10-15' });\nconst resp = await fetch(\n `https://verification.didit.me/v3/organization/${orgId}/application/${appId}/analytics/?${params}`,\n { headers: { 'x-api-key': process.env.DIDIT_API_KEY } },\n);\nif (!resp.ok) throw new Error(`Analytics failed: ${resp.status}`);\nconst data = await resp.json();\nconsole.log(data.request_breakdown.total_approved, data.conversion_rate);" + } + ], + "responses": { + "200": { + "description": "The KYC summary, or the requested widgets when `scope` or `widget_keys` is sent.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/ApplicationAnalyticsSummary" + }, + { + "$ref": "#/components/schemas/ApplicationAnalyticsWidgetResponse" + } + ] + }, + "examples": { + "KYC summary": { + "value": { + "grouping_mode": "day", + "data_points": [ + { + "rounded_date": "2026-10-14", + "total": 12, + "approved": 8, + "declined": 1, + "canceled": 1, + "pending": 2, + "in_review": 1, + "expired": 1, + "abandoned": 0, + "in_progress": 1, + "not_started": 0, + "manually_approved": 1, + "manually_declined": 0 + }, + { + "rounded_date": "2026-10-15", + "total": 10, + "approved": 6, + "declined": 2, + "canceled": 0, + "pending": 2, + "in_review": 2, + "expired": 0, + "abandoned": 0, + "in_progress": 0, + "not_started": 0, + "manually_approved": 0, + "manually_declined": 1 + } + ], + "request_breakdown": { + "total_approved": 14, + "total_in_review": 3, + "total_declined": 3, + "total_canceled": 1, + "total_pending": 4, + "total_expired": 1, + "total_abandoned": 0, + "total_in_progress": 1, + "total_not_started": 0, + "total_manually_approved": 1, + "total_manually_declined": 1 + }, + "conversion_rate": 70.0, + "location_stats": { + "countries": [ + { + "session_count": 15, + "country": "Spain", + "country_code": "ES" + }, + { + "session_count": 7, + "country": "Portugal", + "country_code": "PT" + } + ], + "ip_countries": [ + { + "session_count": 15, + "country": "Spain", + "country_code": "ES" + }, + { + "session_count": 7, + "country": "Portugal", + "country_code": "PT" + } + ], + "id_countries": [ + { + "country": "Spain", + "country_code": "ES", + "session_count": 14 + }, + { + "country": "Portugal", + "country_code": "PT", + "session_count": 6 + } + ], + "conversion_by_country": [ + { + "country": "Spain", + "country_code": "ES", + "finished": 13, + "approved": 10 + }, + { + "country": "Portugal", + "country_code": "PT", + "finished": 6, + "approved": 4 + } + ], + "cities": [ + { + "session_count": 9, + "location": "Madrid, Spain", + "latitude": 40.4168, + "longitude": -3.7038 + } + ] + }, + "demographic_stats": { + "age_distribution": { + "under_18": 0, + "18_24": 5, + "25_34": 8, + "35_44": 4, + "45_64": 2, + "65_plus": 1 + }, + "gender_distribution": { + "M": 11, + "F": 9 + } + }, + "device_details": { + "device_models": [ + { + "device": "Apple", + "count": 12 + }, + { + "device": "Android", + "count": 10 + } + ], + "browsers": [ + { + "browser": "Safari", + "count": 12 + }, + { + "browser": "Chrome", + "count": 10 + } + ], + "operating_systems": [ + { + "os": "iOS", + "count": 12 + }, + { + "os": "Android", + "count": 10 + } + ] + }, + "workflow_funnel_stats": { + "33333333-4444-5555-6666-777777777777": { + "total": 22, + "steps": { + "OCR": { + "reached": 21 + }, + "LIVENESS": { + "reached": 19 + }, + "FACE_MATCH": { + "reached": 19 + }, + "AML": { + "reached": 18 + } + } + } + }, + "verification_time_stats": { + "overall_avg_seconds": 64.0, + "overall_count": 18, + "per_feature": { + "OCR": { + "avg_seconds": 41.2, + "total_seconds": 741.6, + "count": 18 + }, + "LIVENESS": { + "avg_seconds": 22.8, + "total_seconds": 410.4, + "count": 18 + } + }, + "per_sub_step": { + "OCR": { + "front_id_photo": { + "avg_seconds": 18.3, + "total_seconds": 329.4, + "count": 18 + } + } + } + }, + "resubmission_stats": { + "first_pass": 17, + "second": 2, + "third_plus": 1, + "abandoned": 0 + }, + "warning_stats": { + "ID_VERIFICATION": { + "total": 21, + "risks": { + "DOCUMENT_EXPIRED": { + "error": 1, + "warning": 0, + "information": 0 + } + } + }, + "LIVENESS": { + "total": 19, + "risks": { + "LOW_LIVENESS_SCORE": { + "error": 0, + "warning": 2, + "information": 0 + } + } + } + }, + "original_status_stats": { + "approved": 13, + "in_review": 5, + "declined": 2 + }, + "demographic_time_series": [ + { + "date": "2026-10-14", + "age_distribution": { + "under_18": 0, + "18_24": 3, + "25_34": 4, + "35_44": 2, + "45_64": 1, + "65_plus": 0 + }, + "gender_distribution": { + "M": 6, + "F": 4 + } + }, + { + "date": "2026-10-15", + "age_distribution": { + "under_18": 0, + "18_24": 2, + "25_34": 4, + "35_44": 2, + "45_64": 1, + "65_plus": 1 + }, + "gender_distribution": { + "M": 5, + "F": 5 + } + } + ], + "device_time_series": [ + { + "date": "2026-10-14", + "devices": { + "Apple": 7, + "Android": 5 + }, + "browsers": { + "Safari": 7, + "Chrome": 5 + }, + "operating_systems": { + "iOS": 7, + "Android": 5 + } + }, + { + "date": "2026-10-15", + "devices": { + "Apple": 5, + "Android": 5 + }, + "browsers": { + "Safari": 5, + "Chrome": 5 + }, + "operating_systems": { + "iOS": 5, + "Android": 5 + } + } + ], + "verification_time_time_series": [ + { + "date": "2026-10-14", + "overall_avg": 66.1, + "per_feature": { + "OCR": 42.0, + "LIVENESS": 24.1 + } + }, + { + "date": "2026-10-15", + "overall_avg": 61.5, + "per_feature": { + "OCR": 40.2, + "LIVENESS": 21.3 + } + } + ], + "resubmission_time_series": [ + { + "date": "2026-10-14", + "first_pass": 9, + "second": 1, + "third_plus": 0, + "abandoned": 0 + }, + { + "date": "2026-10-15", + "first_pass": 8, + "second": 1, + "third_plus": 1, + "abandoned": 0 + } + ], + "original_status_time_series": [ + { + "date": "2026-10-14", + "approved": 7, + "in_review": 3, + "declined": 1 + }, + { + "date": "2026-10-15", + "approved": 6, + "in_review": 2, + "declined": 1 + } + ], + "previous_data_points": [ + { + "rounded_date": "2026-10-12", + "total": 9, + "approved": 6, + "declined": 1, + "canceled": 1, + "pending": 1 + }, + { + "rounded_date": "2026-10-13", + "total": 11, + "approved": 7, + "declined": 2, + "canceled": 0, + "pending": 2 + } + ], + "previous_request_breakdown": { + "total_approved": 13, + "total_in_review": 2, + "total_declined": 3, + "total_canceled": 1, + "total_pending": 3 + } + } + }, + "Transaction widgets": { + "value": { + "scope": "transactions", + "available_widget_keys": [ + "tx_count_total", + "tx_count_daily", + "tx_volume_total_preferred_currency", + "tx_volume_daily_preferred_currency", + "tx_average_amount", + "tx_average_amount_daily", + "tx_transaction_category_breakdown", + "tx_action_type_breakdown", + "tx_status_volume_trend", + "tx_user_ip_or_country_distribution", + "tx_tag_breakdown", + "tx_top_rules_triggered", + "tx_team_member_performance", + "tx_score_histogram" + ], + "requested_widget_keys": [ + "tx_count_total", + "tx_count_daily" + ], + "widgets": { + "tx_count_total": { + "kind": "metric", + "title": "Transaction count", + "value": 42 + }, + "tx_count_daily": { + "kind": "time_series", + "title": "Transaction count", + "items": [ + { + "date": "2026-10-14", + "value": 20 + }, + { + "date": "2026-10-15", + "value": 22 + } + ], + "previous_items": [ + { + "date": "2026-10-12", + "value": 18 + }, + { + "date": "2026-10-13", + "value": 15 + } + ] + } + } + } + } + } + } + } + }, + "400": { + "description": "Invalid date range or scope. The body is an object keyed by the offending parameter, with a string message.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "examples": { + "Bad date format": { + "value": { + "start_date": "Invalid date format. Use YYYY-MM-DD (e.g., 2024-01-15)." + } + }, + "Start after end": { + "value": { + "start_date": "Start date must be before or equal to end date." + } + }, + "Range too long": { + "value": { + "date_range": "Date range cannot exceed 730 days (2 years)." + } + }, + "Unknown scope": { + "value": { + "scope": "Unsupported analytics scope." + } + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. Read `detail`: a missing or invalid API key (never `401`), a key of another application, any scoped key, or a request from outside the key's IP list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key": { + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/webhooks/": { + "get": { + "summary": "List session webhook deliveries", + "description": "List every webhook delivery Didit recorded for one verification session, user (KYC) or business (KYB), newest first, as a plain JSON array (not paginated). Each entry is one attempt to one destination: the first send, each automatic [retry](https://docs.didit.me/integration/webhooks#retry-policy) and each resend are separate entries with their own `uuid`, and retries and resends keep the payload's `event_id`. `request_header` and `request_body` are JSON text; parse them to read the signatures and the payload. Delivery records are available for about 30 days, so an empty array means no webhook has been sent for the session yet or every delivery is older than that. A scoped API key needs `sessions: read` and `decisions: read`.", + "operationId": "get_v3_session_webhooks", + "x-didit-sdk": { + "resource": "sessions.webhooks", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session, user (KYC) or business (KYB): the `session_id` returned by `POST /v3/session/`. The server resolves it against both session types.\n\n**Must be a valid UUID.** The route only matches well-formed UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/22222222-3333-4444-5555-666666666666/webhooks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import json\nimport os\n\nimport requests\n\nsession_id = '22222222-3333-4444-5555-666666666666'\nresp = requests.get(\n f'https://verification.didit.me/v3/session/{session_id}/webhooks/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status()\nfor delivery in resp.json():\n payload = json.loads(delivery['request_body'])\n print(delivery['created_at'], delivery['webhook_type'], delivery['response_status'], payload['event_id'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const sessionId = '22222222-3333-4444-5555-666666666666';\nconst resp = await fetch(`https://verification.didit.me/v3/session/${sessionId}/webhooks/`, {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (!resp.ok) throw new Error(`Request failed: ${resp.status}`);\nconst deliveries = await resp.json();\nfor (const delivery of deliveries) {\n const payload = JSON.parse(delivery.request_body);\n console.log(delivery.created_at, delivery.webhook_type, delivery.response_status, payload.event_id);\n}" + } + ], + "responses": { + "200": { + "description": "Every delivery recorded for the session, newest first.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionWebhookDelivery" + } + }, + "examples": { + "Failed delivery and its successful retry": { + "value": [ + { + "uuid": "66666666-7777-8888-9999-000000000002", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 200, + "status": "In Progress", + "webhook_type": "status.updated", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"9188851d160b5b3aacfc946e4dde1f061b7e568662518942c927ddaf507f533d\", \"X-Signature-V2\": \"381236af514d3fb538579365ac5a24f2464ce1b8224c6e770c60d8201c43ecc5\", \"X-Signature-Simple\": \"4740131dc38f29e78a8508371979be74967d5307464494bd3148a03b4d6c8716\", \"X-Timestamp\": \"1792056139\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792056139,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}", + "created_at": "2026-10-15T09:22:19.104233Z" + }, + { + "uuid": "66666666-7777-8888-9999-000000000001", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 503, + "status": "In Progress", + "webhook_type": "status.updated", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"a5e02a03404e1337a0198e6be15d34abbd053c658a0e51e5d0e78554213ef29b\", \"X-Signature-V2\": \"927e7cb770c94ce745939bff73f04aa200015f3bc64795ba46c2aaf78a6cedc5\", \"X-Signature-Simple\": \"e37b0d4c716c72bb5ae7127b9cc455d28ab4686e9e22312b4ec2327409a73302\", \"X-Timestamp\": \"1792056078\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792056078,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}", + "created_at": "2026-10-15T09:21:18.412345Z" + } + ] + }, + "No webhook sent yet": { + "value": [] + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A missing, invalid or revoked key gets `403`, not `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. No `x-api-key`, or an invalid or revoked key, gets `Authentication credentials were not provided or are invalid.` A key of another application gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` An unknown `sessionId` returns `404` before the credential is checked.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Key of another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions read": { + "value": { + "detail": "This API key has no read access to sessions." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist or has been deleted (`Not found.`, returned before the credential is checked), or it is outside the workflow or status limits of a scoped key (`Session not found.`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "value": { + "detail": "Not found." + } + }, + "Outside the key's limits": { + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/webhook/{webhook_uuid}": { + "get": { + "summary": "Get session webhook delivery", + "description": "Retrieve one webhook delivery of a verification session: the URL it went to, the HTTP status your endpoint returned, and the exact headers and JSON body Didit sent. `request_header` and `request_body` are JSON text; to check your [signature verification](https://docs.didit.me/integration/webhooks#signature-verification), recompute `X-Signature-V2` from the parsed body with the secret of the destination it was sent to (or the application-level secret for a delivery to the legacy webhook URL). The path has no trailing slash: with one it returns `404`. A scoped API key needs `sessions: read` and `decisions: read`.", + "operationId": "get_v3_session_webhook", + "x-didit-sdk": { + "resource": "sessions.webhooks", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session, user (KYC) or business (KYB): the `session_id` returned by `POST /v3/session/`. The server resolves it against both session types.\n\n**Must be a valid UUID.** The route only matches well-formed UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + }, + { + "name": "webhook_uuid", + "in": "path", + "required": true, + "description": "UUID of the delivery: the `uuid` of an entry returned by `GET /v3/session/{sessionId}/webhooks/`. It must belong to the session in the path.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "66666666-7777-8888-9999-000000000001" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/22222222-3333-4444-5555-666666666666/webhook/66666666-7777-8888-9999-000000000001' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import json\nimport os\n\nimport requests\n\nsession_id = '22222222-3333-4444-5555-666666666666'\nwebhook_uuid = '66666666-7777-8888-9999-000000000001'\nresp = requests.get(\n f'https://verification.didit.me/v3/session/{session_id}/webhook/{webhook_uuid}', # no trailing slash\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status()\ndelivery = resp.json()\nheaders = json.loads(delivery['request_header'])\npayload = json.loads(delivery['request_body'])\nprint(delivery['response_status'], headers['X-Signature-V2'], payload['status'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const sessionId = '22222222-3333-4444-5555-666666666666';\nconst webhookUuid = '66666666-7777-8888-9999-000000000001';\n// No trailing slash on this path.\nconst resp = await fetch(`https://verification.didit.me/v3/session/${sessionId}/webhook/${webhookUuid}`, {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (!resp.ok) throw new Error(`Request failed: ${resp.status}`);\nconst delivery = await resp.json();\nconst payload = JSON.parse(delivery.request_body);\nconsole.log(delivery.response_status, payload.status);" + } + ], + "responses": { + "200": { + "description": "The delivery, with its request headers and body.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionWebhookDelivery" + }, + "examples": { + "Connection failure": { + "value": { + "uuid": "66666666-7777-8888-9999-000000000001", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 503, + "status": "In Progress", + "webhook_type": "status.updated", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"a5e02a03404e1337a0198e6be15d34abbd053c658a0e51e5d0e78554213ef29b\", \"X-Signature-V2\": \"927e7cb770c94ce745939bff73f04aa200015f3bc64795ba46c2aaf78a6cedc5\", \"X-Signature-Simple\": \"e37b0d4c716c72bb5ae7127b9cc455d28ab4686e9e22312b4ec2327409a73302\", \"X-Timestamp\": \"1792056078\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792056078,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}", + "created_at": "2026-10-15T09:21:18.412345Z" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A missing, invalid or revoked key gets `403`, not `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. No `x-api-key`, or an invalid or revoked key, gets `Authentication credentials were not provided or are invalid.` A key of another application gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` An unknown `sessionId` returns `404` before the credential is checked.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Key of another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions read": { + "value": { + "detail": "This API key has no read access to sessions." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist or has been deleted (`Not found.`, returned before the credential is checked), or it is outside the workflow or status limits of a scoped key (`Session not found.`). A delivery that does not exist, belongs to another session or is no longer available returns `No WebhookLog matches the given query.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "value": { + "detail": "Not found." + } + }, + "Outside the key's limits": { + "value": { + "detail": "Session not found." + } + }, + "Delivery not in this session": { + "value": { + "detail": "No WebhookLog matches the given query." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/webhook/{webhook_uuid}/resend": { + "post": { + "summary": "Resend session webhook delivery", + "description": "Send a recorded session webhook again to the URL it first went to, with the same payload and `event_id` and a fresh `timestamp`, `X-Timestamp` header and signatures. Didit signs it with the current secret of the destination the delivery belongs to (or the application-level secret for a delivery to the legacy webhook URL), waits up to 5 seconds for your endpoint, records the attempt as a new delivery and returns it with `200` whatever your endpoint answered: read `response_status`. A resend is not retried automatically, and every call sends again, so make your endpoint idempotent on `event_id`. The path has no trailing slash and the request has no body. A scoped API key needs `webhooks: write` and `decisions: read`.", + "operationId": "post_v3_session_webhook_resend", + "x-didit-sdk": { + "resource": "sessions.webhooks", + "method": "resend", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session, user (KYC) or business (KYB): the `session_id` returned by `POST /v3/session/`. The server resolves it against both session types.\n\n**Must be a valid UUID.** The route only matches well-formed UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + }, + { + "name": "webhook_uuid", + "in": "path", + "required": true, + "description": "UUID of the delivery to resend: the `uuid` of an entry returned by `GET /v3/session/{sessionId}/webhooks/`. It must belong to the session in the path.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "66666666-7777-8888-9999-000000000001" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/session/22222222-3333-4444-5555-666666666666/webhook/66666666-7777-8888-9999-000000000001/resend' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nsession_id = '22222222-3333-4444-5555-666666666666'\nwebhook_uuid = '66666666-7777-8888-9999-000000000001'\nresp = requests.post(\n f'https://verification.didit.me/v3/session/{session_id}/webhook/{webhook_uuid}/resend', # no trailing slash\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status()\nresent = resp.json()\nprint(resent['uuid'], resent['response_status'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const sessionId = '22222222-3333-4444-5555-666666666666';\nconst webhookUuid = '66666666-7777-8888-9999-000000000001';\n// No trailing slash on this path.\nconst resp = await fetch(`https://verification.didit.me/v3/session/${sessionId}/webhook/${webhookUuid}/resend`, {\n method: 'POST',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (!resp.ok) throw new Error(await resp.text());\nconst resent = await resp.json();\nconsole.log(resent.uuid, resent.response_status);" + } + ], + "responses": { + "200": { + "description": "The new delivery, already attempted. `response_status` is what your endpoint answered this time. The original delivery is unchanged.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionWebhookDelivery" + }, + "examples": { + "Accepted on resend": { + "value": { + "uuid": "66666666-7777-8888-9999-000000000004", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 200, + "status": "In Progress", + "webhook_type": "status.updated", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"b8eb1e376517ee0cad8ba98ff7f8fd35fc72eca231000963a96a335f82328835\", \"X-Signature-V2\": \"9efd2ce9ed1303e1d05273ef9608a59363ed9789a05253db4748b3e8e50cb109\", \"X-Signature-Simple\": \"bf3a9dc04b2c20ab539203b0cd5cca2a10f630bf62d8feb6d68d82af80f64774\", \"X-Timestamp\": \"1792159542\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792159542,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}", + "created_at": "2026-10-16T14:05:42.330981Z" + } + } + } + } + } + }, + "400": { + "description": "The delivery could not be resent. Branch on `code`: `webhook_url_missing` means the delivery has no recorded URL and nothing is sent; `webhook_resend_error` means the stored delivery could not be signed again or the attempt could not be completed. After `webhook_resend_error`, `detail` starts with `Error processing webhook resend:` followed by a diagnostic text that can change.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string", + "description": "Stable machine-readable error code. Branch on this, not on `detail`." + } + } + }, + "examples": { + "No recorded URL": { + "value": { + "detail": "The webhook delivery does not have a request URL configured", + "code": "webhook_url_missing" + } + }, + "Resend failed": { + "value": { + "detail": "Error processing webhook resend: Invalid webhook data format", + "code": "webhook_resend_error" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A missing, invalid or revoked key gets `403`, not `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. No `x-api-key`, or an invalid or revoked key, gets `Authentication credentials were not provided or are invalid.` A key of another application gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` An unknown `sessionId` returns `404` before the credential is checked.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Key of another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks write": { + "value": { + "detail": "This API key has no write access to webhooks." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist or has been deleted (`Not found.`, returned before the credential is checked), or it is outside the workflow or status limits of a scoped key (`Session not found.`). A delivery that does not exist, belongs to another session or is no longer available returns `Webhook log not found`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "value": { + "detail": "Not found." + } + }, + "Outside the key's limits": { + "value": { + "detail": "Session not found." + } + }, + "Delivery not in this session": { + "value": { + "detail": "Webhook log not found" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 300 write requests (POST, PATCH, PUT, DELETE) in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (300).", + "schema": { + "type": "integer", + "example": 300 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-write" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/events/": { + "get": { + "summary": "List session events", + "description": "Read the event timeline of one verification session, newest first by `sent_at`: the screens the user saw, buttons and uploads, failed attempts, status and feature-step changes, and the workflow routing trace. Works for User Verification (KYC) and Business Verification (KYB) session IDs. Events are stored asynchronously, so a new event can take a few seconds to appear. Results are paginated with `limit` and `offset`; follow `next` until it is `null`. A scoped API key needs `sessions: read` and `decisions: read`, and without `media: read` it receives `file_url` as `null`. The routing events are described in [Retrieve session](https://docs.didit.me/sessions-api/retrieve-session).", + "operationId": "get_v3_session_events", + "x-didit-sdk": { + "resource": "sessions", + "method": "list_events", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of events per page. Defaults to 50. A value below 1 or a non-numeric value falls back to 50; a value above 1,000,000 returns `400`.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000, + "example": 50 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of results to skip before the first returned one. Defaults to 0; a negative or non-numeric value falls back to 0, a value above 1,000,000 returns `400`. Prefer following the `next` and `previous` URLs.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000, + "example": 0 + } + }, + { + "name": "ordering", + "in": "query", + "required": false, + "description": "Optional sort override. The default order is `-sent_at`. Pass a result field name (for example `sent_at`, `created_at`, `event_type`, `feature_step` or `step`) to sort ascending, prefix it with `-` to sort descending, and separate several fields with commas. Unknown fields are ignored.", + "schema": { + "type": "string", + "example": "sent_at" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/events/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nsession_id = '11111111-2222-3333-4444-555555555555'\nresp = requests.get(\n f'https://verification.didit.me/v3/session/{session_id}/events/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n params={'limit': 50},\n timeout=10,\n)\nresp.raise_for_status()\nfor event in resp.json()['results']:\n print(event['sent_at'], event['event_type'], event['step'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const sessionId = '11111111-2222-3333-4444-555555555555';\nconst resp = await fetch(`https://verification.didit.me/v3/session/${sessionId}/events/?limit=50`, {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (!resp.ok) throw new Error(`Request failed: ${resp.status}`);\nconst page = await resp.json();\nfor (const event of page.results) console.log(event.sent_at, event.event_type, event.step);" + } + ], + "responses": { + "200": { + "description": "One page of the session's events, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Total number of events of the session." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionEvent" + } + } + } + }, + "examples": { + "Completed session": { + "value": { + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "uuid": "aaaaaaaa-1111-2222-3333-444444444444", + "event_type": "SESSION_STATUS_CHANGED", + "created_at": "2026-09-14T10:02:13.904512Z", + "component": null, + "feature_step": null, + "step": null, + "details": { + "old_status": "In Progress", + "current_status": "Approved" + }, + "ip_address": null, + "user_agent": null, + "device_info": null, + "file_url": null, + "sent_at": 1789380131482 + }, + { + "uuid": "bbbbbbbb-1111-2222-3333-444444444444", + "event_type": "FEATURE_STEP_CHANGED", + "created_at": "2026-09-14T10:01:49.210377Z", + "component": null, + "feature_step": null, + "step": null, + "details": { + "old_feature_step": "OCR", + "current_feature_step": "LIVENESS" + }, + "ip_address": null, + "user_agent": null, + "device_info": null, + "file_url": null, + "sent_at": 1789380107103 + }, + { + "uuid": "cccccccc-1111-2222-3333-444444444444", + "event_type": "STEP_RENDERED", + "created_at": "2026-09-14T10:01:00.581930Z", + "component": "DocumentCapture", + "feature_step": "OCR", + "step": "DOCUMENT_CAPTURE_FRONT", + "details": {}, + "ip_address": "203.0.113.24", + "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", + "device_info": { + "device_model": "iPhone" + }, + "file_url": null, + "sent_at": 1789380058776 + } + ] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is above 1,000,000. The body is a JSON array with one message.", + "content": { + "application/json": { + "examples": { + "Limit too large": { + "summary": "limit=2000000", + "value": [ + "limit must not exceed 1000000." + ] + }, + "Offset too large": { + "summary": "offset=2000000", + "value": [ + "offset must not exceed 1000000." + ] + } + }, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. Missing, invalid and revoked keys return `403` (not `401`); so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions read": { + "summary": "Scoped key lacks sessions: read", + "value": { + "detail": "This API key has no read access to sessions." + } + }, + "Scoped key without decisions read": { + "summary": "Scoped key lacks decisions: read", + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted, or is outside a scoped key's workflow or approved-only limits. The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's workflow or approved-only limits", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 600 GET requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/aml-screening-history/": { + "get": { + "summary": "List AML screening history", + "description": "List every completed ongoing-monitoring re-screen of a session's AML check, newest first by `completed_at`, including the runs where nothing changed: alerts, activity entries and webhooks fire only when a result moves, so this history is the evidence that a clean subject was still being screened. Works for KYC and KYB session IDs; pass `node_id` when the session ran more than one AML step. Only ongoing-monitoring re-screens are listed (the first screening is part of the session decision) and runs from before this history existed are not reconstructed, so a session without ongoing monitoring returns an empty list. `count` is exact up to 100 and capped at 100 above that, so follow `next` until it is `null`. A scoped API key needs `decisions: read`; see [Continuous AML monitoring](https://docs.didit.me/core-technology/aml-screening/continuous-monitoring-aml-screening).", + "operationId": "get_v3_session_aml_screening_history", + "x-didit-sdk": { + "resource": "sessions", + "method": "list_aml_screening_history", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node ID of the AML check. Needed only when the session ran more than one AML step; without it the most recent AML check is used. An unknown node returns `404`.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of screening runs per page. Defaults to 50. A value below 1 or a non-numeric value falls back to 50; a value above 1,000,000 returns `400`.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000, + "example": 50 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of results to skip before the first returned one. Defaults to 0; a negative or non-numeric value falls back to 0, a value above 1,000,000 returns `400`. Prefer following the `next` and `previous` URLs.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000, + "example": 0 + } + }, + { + "name": "ordering", + "in": "query", + "required": false, + "description": "Optional sort override. The default order is `-completed_at`. Pass a result field name (for example `completed_at`, `started_at`, `outcome` or `score`) to sort ascending, prefix it with `-` to sort descending, and separate several fields with commas. Unknown fields are ignored.", + "schema": { + "type": "string", + "example": "completed_at" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/aml-screening-history/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nsession_id = '11111111-2222-3333-4444-555555555555'\nresp = requests.get(\n f'https://verification.didit.me/v3/session/{session_id}/aml-screening-history/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status()\nfor run in resp.json()['results']:\n print(run['completed_at'], run['outcome'], run['status'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const sessionId = '11111111-2222-3333-4444-555555555555';\nconst resp = await fetch(`https://verification.didit.me/v3/session/${sessionId}/aml-screening-history/`, {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (!resp.ok) throw new Error(`Request failed: ${resp.status}`);\nconst page = await resp.json();\nfor (const run of page.results) console.log(run.completed_at, run.outcome, run.status);" + } + ], + "responses": { + "200": { + "description": "One page of screening runs, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "maximum": 100, + "description": "Number of runs, exact up to 100 and capped at 100 above that." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AMLScreeningRun" + } + } + } + }, + "examples": { + "Monitored subject": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "uuid": "dddddddd-1111-2222-3333-444444444444", + "completed_at": "2026-09-14T04:12:33.481207Z", + "started_at": "2026-09-14T04:00:01.002311Z", + "outcome": "NO_CHANGE", + "trigger": "ONGOING_MONITORING", + "provider": "aml_data_source", + "status": "Approved", + "score": 0.0, + "total_hits": 0, + "dataset_freshness": { + "captured_at": "2026-09-14T03:58:02.114820+00:00", + "provider": "aml_data_source", + "catalog_version": "2026-09-13", + "sources": { + "sanctions": { + "last_updated_at": "2026-09-13T21:10:00+00:00", + "oldest_updated_at": "2026-09-11T06:00:00+00:00", + "dataset_count": 120, + "dated_dataset_count": 120 + }, + "pep": { + "last_updated_at": "2026-09-13T08:00:00+00:00", + "oldest_updated_at": null, + "dataset_count": 45, + "dated_dataset_count": 44 + }, + "warnings": { + "last_updated_at": "2026-09-12T18:45:00+00:00", + "oldest_updated_at": "2026-09-01T00:00:00+00:00", + "dataset_count": 30, + "dated_dataset_count": 30 + } + } + } + }, + { + "uuid": "eeeeeeee-1111-2222-3333-444444444444", + "completed_at": "2026-09-13T04:11:58.020914Z", + "started_at": "2026-09-13T04:00:00.871022Z", + "outcome": "FAILED", + "trigger": "ONGOING_MONITORING", + "provider": "aml_data_source", + "status": "Approved", + "score": 0.0, + "total_hits": 0, + "dataset_freshness": null + } + ] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is above 1,000,000. The body is a JSON array with one message.", + "content": { + "application/json": { + "examples": { + "Limit too large": { + "summary": "limit=2000000", + "value": [ + "limit must not exceed 1000000." + ] + }, + "Offset too large": { + "summary": "offset=2000000", + "value": [ + "offset must not exceed 1000000." + ] + } + }, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. Missing, invalid and revoked keys return `403` (not `401`); so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions read": { + "summary": "Scoped key lacks decisions: read", + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits, or it has no AML check (for the given `node_id`). The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's limits (also returned for a session deleted moments ago)", + "value": { + "detail": "Session not found." + } + }, + "No AML check": { + "summary": "The session never ran an AML step", + "value": { + "detail": "The session does not have an AML check." + } + }, + "No AML check for node": { + "summary": "node_id does not match an AML check of the session", + "value": { + "detail": "The session does not have an AML check for node 'feature_aml_2'." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 600 GET requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/aml-screening-history/export/": { + "get": { + "summary": "Export AML screening history as CSV", + "description": "Download the same ongoing-monitoring history as `GET /v3/session/{sessionId}/aml-screening-history/` as a CSV file, one line per completed re-screen, newest first, ready to hand to an auditor. The file is streamed whole (no pagination) and starts with a header line; an empty cell means unknown, dataset dates are never estimated, and a value that would start a spreadsheet formula is prefixed with an apostrophe. Works for KYC and KYB session IDs; pass `node_id` when the session ran more than one AML step. Errors are returned as JSON, not CSV. A scoped API key needs `decisions: read` and `media: read`: the response is a file, so a key without `media: read` gets `403`.", + "operationId": "get_v3_session_aml_screening_history_export", + "x-didit-sdk": { + "resource": "sessions", + "method": "export_aml_screening_history", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node ID of the AML check. Needed only when the session ran more than one AML step; without it the most recent AML check is used. An unknown node returns `404`.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/aml-screening-history/export/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -o aml-screening-history.csv" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nsession_id = '11111111-2222-3333-4444-555555555555'\nresp = requests.get(\n f'https://verification.didit.me/v3/session/{session_id}/aml-screening-history/export/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n timeout=10,\n)\nresp.raise_for_status()\nwith open('aml-screening-history.csv', 'wb') as fh:\n fh.write(resp.content)" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "import { writeFile } from 'node:fs/promises';\n\nconst sessionId = '11111111-2222-3333-4444-555555555555';\nconst resp = await fetch(`https://verification.didit.me/v3/session/${sessionId}/aml-screening-history/export/`, {\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n});\nif (!resp.ok) throw new Error(`Export failed: ${resp.status}`);\nawait writeFile('aml-screening-history.csv', await resp.text());" + } + ], + "responses": { + "200": { + "description": "The CSV file, streamed as `text/csv` with CRLF line endings. Columns, in order: `screened_at` (when the re-screen finished), `outcome` (`NO_CHANGE`, `CHANGED` or `FAILED`), `provider` (opaque identifier of the AML data source; do not branch on it), `aml_status`, `aml_score`, `total_hits`, `datasets_captured_at`, `datasets_sanctions_last_updated_at`, `datasets_pep_last_updated_at`, `datasets_warnings_last_updated_at`, `datasets_adverse_media_last_updated_at` and `datasets_catalog_version`. Dates are ISO 8601 with a UTC offset. A session with no re-screens yet returns the header line only.", + "headers": { + "Content-Disposition": { + "description": "`attachment; filename=\"aml-screening-history-{sessionId}.csv\"`.", + "schema": { + "type": "string", + "example": "attachment; filename=\"aml-screening-history-11111111-2222-3333-4444-555555555555.csv\"" + } + } + }, + "content": { + "text/csv": { + "schema": { + "type": "string", + "format": "binary" + }, + "example": "screened_at,outcome,provider,aml_status,aml_score,total_hits,datasets_captured_at,datasets_sanctions_last_updated_at,datasets_pep_last_updated_at,datasets_warnings_last_updated_at,datasets_adverse_media_last_updated_at,datasets_catalog_version\r\n2026-09-14T04:12:33.481207+00:00,NO_CHANGE,aml_data_source,Approved,0.0,0,2026-09-14T03:58:02.114820+00:00,2026-09-13T21:10:00+00:00,2026-09-13T08:00:00+00:00,2026-09-12T18:45:00+00:00,,2026-09-13\r\n2026-09-13T04:11:58.020914+00:00,FAILED,aml_data_source,Approved,0.0,0,,,,,,\r\n" + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. Missing, invalid and revoked keys return `403` (not `401`); so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions read": { + "summary": "Scoped key lacks decisions: read", + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Scoped key without media read": { + "summary": "Scoped key lacks media: read (the response is a file)", + "value": { + "detail": "This API key has no read access to media." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits, or it has no AML check (for the given `node_id`). The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's limits (also returned for a session deleted moments ago)", + "value": { + "detail": "Session not found." + } + }, + "No AML check": { + "summary": "The session never ran an AML step", + "value": { + "detail": "The session does not have an AML check." + } + }, + "No AML check for node": { + "summary": "node_id does not match an AML check of the session", + "value": { + "detail": "The session does not have an AML check for node 'feature_aml_2'." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 600 GET requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/add-images/": { + "patch": { + "summary": "Add an image to a session", + "description": "Attach one extra image or PDF to the ID verification (`model_type=kyc`) or Proof of Address (`model_type=poa`) result of a finished User Verification (KYC) session, for example evidence collected outside the flow. The session must be `Approved`, `Declined`, `In Review` or `Kyc Expired` and carry the result named by `model_type`; Business Verification (KYB) sessions are not supported. Each call appends one file (to the most recent result when the step ran more than once), keeps the existing files and records a `FILE_ADDED` entry in the session activity log; the upload sends no webhook and does not change the session status. The file is compressed on upload and is returned in `extra_files` of the session decision. This endpoint is limited to 10 requests per minute per API key. A scoped API key needs `sessions: write`, and without `media: read` it receives the `extra_files` links as `null`.", + "operationId": "patch_v3_session_add_images", + "x-didit-sdk": { + "resource": "sessions", + "method": "add_images", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session to add the file to (the `session_id` returned by `POST /v3/session/`). A Business Verification (KYB) session ID returns `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "file", + "model_type" + ], + "properties": { + "file": { + "type": "string", + "format": "binary", + "description": "The image or PDF. Allowed extensions: `jpg`, `jpeg`, `png`, `webp`, `tiff`, `pdf`. At most 15 MB as uploaded; the file is then compressed and rejected with `400` if it is still larger than 2 MB (2,097,152 bytes). Empty files are rejected." + }, + "model_type": { + "type": "string", + "enum": [ + "kyc", + "poa" + ], + "description": "`kyc` adds the file to the ID verification result, `poa` to the Proof of Address result.", + "example": "kyc" + }, + "file_password": { + "type": "string", + "description": "Password of an encrypted PDF. Without it, or with a wrong one, an encrypted PDF returns `400`." + } + } + }, + "example": { + "file": "(binary)", + "model_type": "kyc" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/add-images/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'file=@./back-of-id.jpg' \\\n -F 'model_type=kyc'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nsession_id = '11111111-2222-3333-4444-555555555555'\nwith open('back-of-id.jpg', 'rb') as fh:\n resp = requests.patch(\n f'https://verification.didit.me/v3/session/{session_id}/add-images/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n files={'file': ('back-of-id.jpg', fh, 'image/jpeg')},\n data={'model_type': 'kyc'},\n timeout=10,\n )\nresp.raise_for_status()\nprint(resp.json()['extra_files'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "import { readFile } from 'node:fs/promises';\n\nconst sessionId = '11111111-2222-3333-4444-555555555555';\nconst form = new FormData();\nform.append('file', new Blob([await readFile('./back-of-id.jpg')], { type: 'image/jpeg' }), 'back-of-id.jpg');\nform.append('model_type', 'kyc');\n\nconst resp = await fetch(`https://verification.didit.me/v3/session/${sessionId}/add-images/`, {\n method: 'PATCH',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY },\n body: form,\n});\nif (!resp.ok) throw new Error(`Upload failed: ${resp.status}`);\nconst { extra_files: extraFiles } = await resp.json();\nconsole.log(extraFiles);" + } + ], + "responses": { + "200": { + "description": "File added. Returns every extra file of that result, including the new one.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "extra_files" + ], + "properties": { + "extra_files": { + "type": "array", + "description": "Short-lived presigned links to all extra files of the result named by `model_type`, oldest first. Each link is `null` for a scoped API key without `media: read`. Read fresh links from the session decision later.", + "items": { + "type": "string", + "format": "uri", + "nullable": true + } + } + } + }, + "example": { + "extra_files": [ + "https://media.example.com/session/11111111-2222-3333-4444-555555555555/kyc-extra-files/66666666-7777-8888-9999-000000000000.jpg?expires=1789383731&signature=abc123" + ] + } + } + } + }, + "400": { + "description": "Validation error. Field errors arrive as `{\"\": [\"\"]}`; encrypted-PDF errors as `{\"detail\": \"...\"}`.", + "content": { + "application/json": { + "examples": { + "Missing file": { + "summary": "No file part in the form", + "value": { + "file": [ + "No file was submitted." + ] + } + }, + "Missing model_type": { + "summary": "model_type omitted", + "value": { + "model_type": [ + "This field is required." + ] + } + }, + "Invalid model_type": { + "summary": "model_type is not kyc or poa", + "value": { + "model_type": [ + "\"passport\" is not a valid choice." + ] + } + }, + "File too large": { + "summary": "Upload over 15 MB", + "value": { + "file": [ + "File size should not exceed 15 MB" + ] + } + }, + "Too large after compression": { + "summary": "Still over 2 MB after compression", + "value": { + "file": [ + "File size is too big. Max allowed size is 2097152 bytes." + ] + } + }, + "Encrypted PDF": { + "summary": "Encrypted PDF without file_password", + "value": { + "detail": "The PDF is encrypted. Please upload a decrypted PDF or a photo instead." + } + }, + "Wrong PDF password": { + "summary": "file_password does not open the PDF", + "value": { + "detail": "The PDF password is incorrect. Please provide the correct password." + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. Missing, invalid and revoked keys return `403` (not `401`); so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions write": { + "summary": "Scoped key lacks sessions: write", + "value": { + "detail": "This API key has no write access to sessions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits; it is a KYB session or not in `Approved`, `Declined`, `In Review` or `Kyc Expired`; or it has no result of the requested `model_type` (checked before the file is validated). The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's workflow or approved-only limits", + "value": { + "detail": "Session not found." + } + }, + "Not eligible": { + "summary": "Session not finished yet, or a KYB session ID", + "value": { + "detail": "No Session matches the given query." + } + }, + "No ID verification": { + "summary": "model_type=kyc on a session without ID verification", + "value": { + "detail": "The session does not have KYC data." + } + }, + "No Proof of Address": { + "summary": "model_type=poa on a session without Proof of Address", + "value": { + "detail": "The session does not have POA data." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 10 add-images requests per minute per API key (rejected requests count too). The shared budget of 300 write requests per minute also applies. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Session add-images rate limit exceeded. You can make up to 10 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/update-aml-status/": { + "patch": { + "summary": "Update an AML check status", + "description": "Set the status of a session's AML screening check as a whole (the `status` of an `aml_screenings[]` entry in the session decision), for example `Approved` after your team cleared every hit. Works for KYC and KYB session IDs; pass `node_id` (query parameter or body field) when the session ran more than one AML step, otherwise the most recent AML check is updated. When the status actually changes, an `AML_STATUS_UPDATED` activity entry is recorded and a `data.updated` [webhook](https://docs.didit.me/integration/webhooks) is sent; sending the current status returns `200` and changes nothing. The session status is not recomputed: use `PATCH /v3/session/{sessionId}/update-status/` for the session decision and `PATCH /v3/session/{sessionId}/update-aml-hit-status/` for single hits. A scoped API key needs `decisions: write`.", + "operationId": "patch_v3_session_update_aml_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_aml_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node ID of the AML check to update. Needed only when the session ran more than one AML step. Takes precedence over `node_id` in the body.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "New status of the AML check.", + "example": "Approved" + }, + "node_id": { + "type": "string", + "description": "Workflow graph node ID of the AML check, as an alternative to the query parameter.", + "example": "feature_aml_1" + } + } + }, + "example": { + "status": "Approved" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-aml-status/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"status\": \"Approved\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os\n\nimport requests\n\nsession_id = '11111111-2222-3333-4444-555555555555'\nresp = requests.patch(\n f'https://verification.didit.me/v3/session/{session_id}/update-aml-status/',\n headers={'x-api-key': os.environ['DIDIT_API_KEY']},\n json={'status': 'Approved'},\n timeout=10,\n)\nresp.raise_for_status()\nbody = resp.json()\nprint(body['previous_status'], '->', body['status'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const sessionId = '11111111-2222-3333-4444-555555555555';\nconst resp = await fetch(`https://verification.didit.me/v3/session/${sessionId}/update-aml-status/`, {\n method: 'PATCH',\n headers: { 'x-api-key': process.env.DIDIT_API_KEY, 'Content-Type': 'application/json' },\n body: JSON.stringify({ status: 'Approved' }),\n});\nif (!resp.ok) throw new Error(`Update failed: ${resp.status}`);\nconst body = await resp.json();\nconsole.log(body.previous_status, '->', body.status);" + } + ], + "responses": { + "200": { + "description": "AML check status set (or unchanged when it already had this value).", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message", + "status", + "previous_status" + ], + "properties": { + "message": { + "type": "string", + "description": "Always `AML status updated successfully`." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Status of the AML check now." + }, + "previous_status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Status before the call. Equal to `status` when nothing changed." + } + } + }, + "example": { + "message": "AML status updated successfully", + "status": "Approved", + "previous_status": "In Review" + } + } + } + }, + "400": { + "description": "Validation error: `status` missing or not one of the allowed values. The AML check lookup runs first, so a session without an AML check returns `404` even with an invalid body.", + "content": { + "application/json": { + "examples": { + "Missing status": { + "summary": "Empty body or no status", + "value": { + "status": [ + "This field is required." + ] + } + }, + "Invalid status": { + "summary": "status not in the enum", + "value": { + "status": [ + "\"Cleared\" is not a valid choice." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. Missing, invalid and revoked keys return `403` (not `401`); so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits, or it has no AML check (for the given `node_id`). The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's limits (also returned for a session deleted moments ago)", + "value": { + "detail": "Session not found." + } + }, + "No AML check": { + "summary": "The session never ran an AML step", + "value": { + "detail": "The session does not have an AML check." + } + }, + "No AML check for node": { + "summary": "node_id does not match an AML check of the session", + "value": { + "detail": "The session does not have an AML check for node 'feature_aml_2'." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/status/": { + "get": { + "summary": "Get service status (public, unauthenticated)", + "description": "Returns the state of each Didit service (`operational`, `degraded` or `down`), the overall state (the worst of them), and the incidents that are ongoing or were resolved in the last 7 days. Use it to power a status widget or to check Didit's health from your integration.\n\nPublic: send no auth header. The response contains no organization data and is the same for every caller. Responses are cached for 30 seconds, so poll no more than once every 30 seconds.", + "operationId": "get_v3_status", + "tags": [ + "Status" + ], + "security": [ + {} + ], + "parameters": [], + "responses": { + "200": { + "description": "Current service status.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServiceStatusFeed" + }, + "examples": { + "One service degraded": { + "summary": "An ongoing incident and one resolved this week", + "value": { + "status": "degraded", + "updated_at": "2026-10-13T09:41:07.512345Z", + "services": [ + { + "key": "service_id_verification", + "name": "ID verification", + "status": "operational" + }, + { + "key": "service_liveness_face_match", + "name": "Liveness and face match", + "status": "operational" + }, + { + "key": "service_aml_screening", + "name": "AML screening", + "status": "operational" + }, + { + "key": "service_kyb_registries", + "name": "KYB registries", + "status": "operational" + }, + { + "key": "service_database_validation", + "name": "Database validation", + "status": "degraded" + }, + { + "key": "service_phone_delivery", + "name": "Phone verification delivery", + "status": "operational" + }, + { + "key": "service_email_delivery", + "name": "Email verification delivery", + "status": "operational" + }, + { + "key": "service_transaction_monitoring", + "name": "Transaction monitoring", + "status": "operational" + } + ], + "incidents": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "service": "service_database_validation", + "service_name": "Database validation", + "state": "degraded", + "status": "ongoing", + "title": "Database validation is degraded in Brazil.", + "body": "Checks against Brazil (CPF) may be slow or fail since 08:55 UTC.", + "affected": "Brazil (CPF)", + "country_name": "Brazil", + "started_at": "2026-10-13T08:55:00Z", + "resolved_at": null, + "duration_minutes": null + }, + { + "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "service": "service_aml_screening", + "service_name": "AML screening", + "state": "down", + "status": "resolved", + "title": "AML screening is back to normal.", + "body": "Recovered at 14:32 UTC after 17 minutes.", + "affected": "", + "country_name": "", + "started_at": "2026-10-11T14:15:00Z", + "resolved_at": "2026-10-11T14:32:00Z", + "duration_minutes": 17 + } + ] + } + }, + "All operational": { + "summary": "No incident in the last 7 days", + "value": { + "status": "operational", + "updated_at": "2026-10-13T09:41:07.512345Z", + "services": [ + { + "key": "service_id_verification", + "name": "ID verification", + "status": "operational" + }, + { + "key": "service_liveness_face_match", + "name": "Liveness and face match", + "status": "operational" + }, + { + "key": "service_aml_screening", + "name": "AML screening", + "status": "operational" + }, + { + "key": "service_kyb_registries", + "name": "KYB registries", + "status": "operational" + }, + { + "key": "service_database_validation", + "name": "Database validation", + "status": "operational" + }, + { + "key": "service_phone_delivery", + "name": "Phone verification delivery", + "status": "operational" + }, + { + "key": "service_email_delivery", + "name": "Email verification delivery", + "status": "operational" + }, + { + "key": "service_transaction_monitoring", + "name": "Transaction monitoring", + "status": "operational" + } + ], + "incidents": [] + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests in a minute from the same IP address (or the same API key, if you send one). Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "detail" + ], + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -fsS https://verification.didit.me/v3/status/" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.get('https://verification.didit.me/v3/status/', timeout=5)\nresp.raise_for_status()\nfeed = resp.json()\nprint(feed['status'])\nfor service in feed['services']:\n if service['status'] != 'operational':\n print(service['key'], service['status'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const feed = await fetch('https://verification.didit.me/v3/status/').then((r) => r.json());\nconsole.log(feed.status);\nconst affected = feed.services.filter((s) => s.status !== 'operational');\nconsole.log(affected.map((s) => `${s.key}: ${s.status}`));" + } + ], + "x-didit-sdk": { + "resource": "system", + "method": "retrieve_status", + "auth": "public", + "server": false + } + } + }, + "/v3/organization/bank-verification-pricing/": { + "get": { + "summary": "Get bank verification prices (public, unauthenticated)", + "description": "Returns the list price of each Bank Verification method in every country: bank sign-in with its balance and transactions add-ons, and the account check. Use it to quote a bank step before you build a workflow, or to show bank prices in your own product.\n\nPublic: send no auth header. The response contains no organization data and is the same for every caller. It is cached publicly for one hour and carries an `ETag`, so send `If-None-Match` to get a `304 Not Modified` when nothing changed.\n\nA method is `available` only where Didit serves it in production today, and its `price_usd` is `null` everywhere else, so the feed never shows a price you cannot buy. `available: false` covers a country with no approved price, a method the country does not support, and a route that is not switched on yet.\n\nThese are list prices before your plan discount. Your plan discount applies to the whole bank price, and a negotiated or contract price replaces the list price for your organization.", + "operationId": "get_v3_bank_verification_pricing", + "tags": [ + "Pricing" + ], + "security": [ + {} + ], + "parameters": [], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -fsS https://verification.didit.me/v3/organization/bank-verification-pricing/" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.get('https://verification.didit.me/v3/organization/bank-verification-pricing/', timeout=10)\nresp.raise_for_status()\nfeed = resp.json()\nfor iso3, country in feed['countries'].items():\n sign_in = country['methods']['open_banking']\n if sign_in['available']:\n print(iso3, country['name'], sign_in['price_usd'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const feed = await fetch('https://verification.didit.me/v3/organization/bank-verification-pricing/').then((r) => r.json());\nconst gbr = feed.countries.GBR.methods;\nconsole.log(gbr.open_banking.available ? gbr.open_banking.price_usd : 'Not available');\nconsole.log(gbr.account_lookup.available ? gbr.account_lookup.price_usd : 'Not available');" + } + ], + "x-didit-sdk": { + "resource": "pricing", + "method": "bank_verification", + "auth": "none", + "server": true + }, + "responses": { + "200": { + "description": "Bank Verification list prices by country.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankVerificationPricing" + }, + "examples": { + "Two countries": { + "summary": "One country with both methods, one with neither", + "value": { + "currency": "USD", + "statement_upload": { + "price_usd": "0.0000", + "included_after_charged_bank_attempt": true, + "standalone_available": false + }, + "own_provider_fee_usd": "0.0200", + "countries": { + "GBR": { + "name": "United Kingdom", + "methods": { + "open_banking": { + "available": true, + "market": "UK and EEA", + "price_usd": "0.7900", + "add_ons": { + "balance": "0.3500", + "transactions": "1.3000" + } + }, + "account_lookup": { + "available": true, + "price_usd": "0.4000" + } + } + }, + "CAN": { + "name": "Canada", + "methods": { + "open_banking": { + "available": false, + "market": null, + "price_usd": null, + "add_ons": {} + }, + "account_lookup": { + "available": false, + "price_usd": null + } + } + } + } + } + } + } + } + } + }, + "304": { + "description": "Not modified: the `ETag` you sent in `If-None-Match` still matches. No body." + } + } + } + }, + "/v3/session/{sessionId}/simulate/": { + "post": { + "summary": "Simulate a sandbox session's status", + "description": "Force a terminal status on a sandbox session after the fact, from your backend, without running the flow. Sandbox applications only: a live session returns `404`. Works for User Verification (KYC) and Business Verification (KYB) sessions.\n\nSet `new_status`, or let `scenario` or `risk` supply it. `risk` (or the first risk the scenario's magic values carry) adds that warning to the session. The change is recorded in the session's activity.\n\n`send_webhooks` (default `true`) sends exactly the webhooks a real status change sends. With `false` the status still changes and the console still updates live, but no webhook is logged or sent, the missed-webhook recovery never re-sends that decision, and the next status change sends webhooks as usual. The response echoes the value applied.\n\nRequires the `write:sessions` privilege; a scoped API key needs write access to sessions. An unknown `session_id` returns `404` and another application's session returns `403`, both before the body is read; a live session returns `404` only after the body is validated. To simulate from the hosted flow with the session's own token, use `POST /v3/session/{sessionId}/sandbox/simulate/`. See [Sandbox & Test Data](/integration/sandbox-testing) for the scenario catalog.", + "operationId": "post_v3_session_simulate", + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the sandbox verification session. Accepts both user (KYC) and business (KYB) session IDs.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSimulateRequest" + }, + "examples": { + "Scenario Without Webhooks": { + "summary": "Decline through a scenario without sending webhooks", + "value": { + "scenario": "decline_aml_hit", + "send_webhooks": false + } + }, + "Status Only": { + "summary": "Approve and send the usual webhooks", + "value": { + "new_status": "Approved" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The status changed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSimulateResponse" + }, + "examples": { + "Simulated": { + "summary": "Declined through a scenario, webhooks not sent", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "previous_status": "In Progress", + "status": "Declined", + "scenario": "decline_aml_hit", + "risk": "POSSIBLE_MATCH_FOUND", + "send_webhooks": false + } + } + } + } + } + }, + "400": { + "description": "Validation error: an unknown scenario or risk, no `new_status` when neither `scenario` nor `risk` supplies one, a status this endpoint does not set, or the session already has the requested status (a bare JSON array).", + "content": { + "application/json": { + "examples": { + "Unknown Scenario": { + "summary": "scenario is not in the catalog", + "value": { + "scenario": [ + "Unknown sandbox scenario: 'not-a-scenario'." + ] + } + }, + "Unknown Risk": { + "summary": "risk is not in the catalog", + "value": { + "risk": [ + "Unknown sandbox risk case: 'NOT_A_RISK'." + ] + } + }, + "Missing new_status": { + "summary": "No new_status, scenario or risk", + "value": { + "new_status": [ + "This field is required." + ] + } + }, + "Invalid new_status": { + "summary": "A status the simulation does not set", + "value": { + "new_status": [ + "\"Resubmitted\" is not a valid choice." + ] + } + }, + "Same Status": { + "summary": "The session already has this status", + "value": [ + "The new status is the same as the current status." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing or invalid credentials, a caller without the `write:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no write access to sessions." + } + } + } + } + } + }, + "404": { + "description": "No session with this `session_id` exists, or it is a live (non-sandbox) session.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown or live session", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/simulate/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"scenario\": \"decline_aml_hit\", \"send_webhooks\": false}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/simulate/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n json={\"scenario\": \"decline_aml_hit\", \"send_webhooks\": False},\n)\nresponse.raise_for_status()\nprint(response.json())" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "simulate", + "auth": "api_key", + "server": true + } + } + }, + "/v3/session/{sessionId}/sandbox/simulate/": { + "post": { + "summary": "Simulate a sandbox session's status from the client", + "description": "The same simulation as `POST /v3/session/{sessionId}/simulate/`, authenticated with the session's own `Session-Token` (returned in `session_token` by [Create Session](/sessions-api/create-session)) instead of your API key, so a test harness or the hosted flow can call it from the client. The body, the response and `send_webhooks` behave exactly as on that endpoint.\n\nA `Session-Token` of a live (non-sandbox) application, or one that does not match the `sessionId` in the path, returns `404`. See [Sandbox & Test Data](/integration/sandbox-testing).", + "operationId": "post_v3_session_sandbox_simulate", + "tags": [ + "Sessions" + ], + "security": [ + { + "SessionTokenAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the sandbox verification session. Must match the session encoded in the Session-Token used to authenticate.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSimulateRequest" + }, + "examples": { + "Scenario Without Webhooks": { + "summary": "Decline through a scenario without sending webhooks", + "value": { + "scenario": "decline_aml_hit", + "send_webhooks": false + } + }, + "Status Only": { + "summary": "Approve and send the usual webhooks", + "value": { + "new_status": "Approved" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The status changed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSimulateResponse" + }, + "examples": { + "Simulated": { + "summary": "Declined through a scenario, webhooks not sent", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "previous_status": "In Progress", + "status": "Declined", + "scenario": "decline_aml_hit", + "risk": "POSSIBLE_MATCH_FOUND", + "send_webhooks": false + } + } + } + } + } + }, + "400": { + "description": "Validation error: an unknown scenario or risk, no `new_status` when neither `scenario` nor `risk` supplies one, a status this endpoint does not set, or the session already has the requested status (a bare JSON array).", + "content": { + "application/json": { + "examples": { + "Unknown Scenario": { + "summary": "scenario is not in the catalog", + "value": { + "scenario": [ + "Unknown sandbox scenario: 'not-a-scenario'." + ] + } + }, + "Unknown Risk": { + "summary": "risk is not in the catalog", + "value": { + "risk": [ + "Unknown sandbox risk case: 'NOT_A_RISK'." + ] + } + }, + "Missing new_status": { + "summary": "No new_status, scenario or risk", + "value": { + "new_status": [ + "This field is required." + ] + } + }, + "Invalid new_status": { + "summary": "A status the simulation does not set", + "value": { + "new_status": [ + "\"Resubmitted\" is not a valid choice." + ] + } + }, + "Same Status": { + "summary": "The session already has this status", + "value": [ + "The new status is the same as the current status." + ] + } + } + } + } + }, + "403": { + "description": "The `Session-Token` header is missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid Session Token": { + "summary": "Missing or invalid Session-Token", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "The `Session-Token` belongs to a live (non-sandbox) application, or does not match the `sessionId` in the path.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Live session, or session_id / token mismatch", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/sandbox/simulate/ \\\n -H 'Session-Token: YOUR_SESSION_TOKEN' \\\n -H 'Content-Type: application/json' \\\n -d '{\"new_status\": \"Approved\", \"send_webhooks\": false}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/sandbox/simulate/\",\n headers={\"Session-Token\": \"YOUR_SESSION_TOKEN\"},\n json={\"new_status\": \"Approved\", \"send_webhooks\": False},\n)\nresponse.raise_for_status()\nprint(response.json())" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "simulate_sandbox", + "auth": "device_token", + "server": false + } + } + }, + "/v3/session/{sessionId}/features/overrides/": { + "get": { + "summary": "List a session's check overrides", + "description": "Every override of an individual feature (step) status on a User Verification (KYC) or Business Verification (KYB) session, newest first and not paginated: applied ones, ones waiting for the person to finish the flow (`pending_until_flow_finishes`) and cancelled ones, each with who asked, their reason, and whether it applied while the person was still in the flow (`forced`).\n\n`person_in_flow` tells you whether a new override with `defer_until_flow_finishes: true` would wait. `flow_finished_at` is when the person last finished the flow; it is `null` while they are still in it (a resubmission included) and when the session closed before they finished.\n\nRequires the `read:sessions` privilege; a scoped API key needs read access to decisions.", + "operationId": "get_v3_session_feature_overrides", + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + ], + "responses": { + "200": { + "description": "The session's check overrides, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "session_id", + "person_in_flow", + "flow_finished_at", + "overrides" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "person_in_flow": { + "type": "boolean", + "description": "Whether the person is still in the flow, so a new override with `defer_until_flow_finishes: true` would wait.", + "example": true + }, + "flow_finished_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the person last finished the flow. `null` while they are still in it (a resubmission included) and when the session closed before they finished (abandoned, expired or decided mid-flow).", + "example": null + }, + "overrides": { + "type": "array", + "description": "Every override of the session, newest first.", + "items": { + "$ref": "#/components/schemas/FeatureStatusOverride" + } + } + } + }, + "examples": { + "Overrides": { + "summary": "One override waiting, one applied", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "person_in_flow": true, + "flow_finished_at": null, + "overrides": [ + { + "id": "9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3", + "node_id": "feature_liveness_1", + "feature_type": "", + "feature_model": "Face", + "previous_status": "Declined", + "new_status": "Approved", + "state": "pending_until_flow_finishes", + "forced": false, + "reason": "Liveness retried with a reviewer present.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": null, + "cancelled_at": null, + "cancel_reason": null + }, + { + "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "node_id": "feature_ocr_1", + "feature_type": "", + "feature_model": "KYC", + "previous_status": "In Review", + "new_status": "Approved", + "state": "applied", + "forced": true, + "reason": "Document manually verified by compliance.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": "2026-10-13T09:15:00Z", + "cancelled_at": null, + "cancel_reason": null + } + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing or invalid credentials, a caller without the `read:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks read:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no read access to decisions." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/features/overrides/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.get(\n \"https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/features/overrides/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n)\nresponse.raise_for_status()\nprint(response.json())" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "list_feature_overrides", + "auth": "api_key", + "server": true + } + } + }, + "/v3/session/{sessionId}/features/overrides/{overrideId}/cancel/": { + "post": { + "summary": "Cancel a check override that waits for the flow to finish", + "description": "Cancel an override of a feature (step) status that is still `pending_until_flow_finishes`, so it never applies. The check keeps its status, the cancellation is recorded in the session's activity as `FEATURE_OVERRIDE_CANCELLED`, and no webhook is sent. The response is the override with `state: cancelled` and `cancel_reason: cancelled_by_reviewer`.\n\nAn override that already applied or was cancelled returns `409` with the code `feature_override_not_pending`. Find override IDs with `GET /v3/session/{sessionId}/features/overrides/` or in the `override` returned by the feature status update.\n\nRequires the `write:sessions` privilege; a scoped API key needs write access to decisions.", + "operationId": "post_v3_session_feature_override_cancel", + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + }, + { + "in": "path", + "name": "overrideId", + "required": true, + "description": "The `id` of the override to cancel.", + "schema": { + "type": "string", + "format": "uuid", + "example": "9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3" + } + } + ], + "responses": { + "200": { + "description": "The override is cancelled and will never apply.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FeatureStatusOverride" + }, + "examples": { + "Cancelled": { + "summary": "The cancelled override", + "value": { + "id": "9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3", + "node_id": "feature_liveness_1", + "feature_type": "", + "feature_model": "Face", + "previous_status": "Declined", + "new_status": "Approved", + "state": "cancelled", + "forced": false, + "reason": "Liveness retried with a reviewer present.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": null, + "cancelled_at": "2026-10-13T09:20:00Z", + "cancel_reason": "cancelled_by_reviewer" + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing or invalid credentials, a caller without the `write:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no write access to decisions." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists, or the session has no override with this `overrideId`.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + }, + "Override Not Found": { + "summary": "No override with this id on the session", + "value": { + "detail": "Override not found." + } + } + } + } + } + }, + "409": { + "description": "The override already applied or was cancelled.", + "content": { + "application/json": { + "examples": { + "Not Pending": { + "summary": "Only a waiting override can be cancelled", + "value": { + "detail": "This override is no longer pending: it already applied or was cancelled.", + "code": "feature_override_not_pending" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/features/overrides/9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3/cancel/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/features/overrides/9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3/cancel/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n)\nresponse.raise_for_status()\nprint(response.json())" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "cancel_feature_override", + "auth": "api_key", + "server": true + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/tags/add/": { + "post": { + "summary": "Add a tag to a network", + "description": "Put one of your application's tags on the network. Returns the network detail, whose `tags` lists your application's tags on the network, with `201` when the tag was added and `200` when it was already on the network. A tag that belongs to another application, or does not exist, returns 404 `Tag not found.`. Requires a full-access API key, or `write:networks` for a console member.", + "operationId": "add_network_tag", + "tags": [ + "Networks" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "description": "Network UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkTagRequest" + }, + "example": { + "tag_uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/3fa85f64-5717-4562-b3fc-2c963f66afa6/tags/add/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"tag_uuid\": \"7c9e6679-7425-40de-944b-e07fc1f90ae7\"}'" + } + ], + "responses": { + "201": { + "description": "The tag was added. Returns the network detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkDetailResponse" + }, + "example": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "network_id": "N-0042", + "name": null, + "status": "in_review", + "risk": { + "score": 82, + "band": "high", + "bar": 0.82 + }, + "pattern_types": [ + "exact_same_device", + "same_email" + ], + "signal_types": [ + "device", + "email" + ], + "member_counts": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signal_coverage": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + }, + "first_activity_at": "2026-10-01T09:12:44Z", + "last_activity_at": "2026-10-12T16:03:10Z", + "stats": { + "members": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signals": { + "total": 2, + "by_type": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + } + } + }, + "fraud_tags": [], + "risk_signals": { + "residential_proxy_reuse": { + "detected": false, + "signal_count": 0, + "max_subject_count": 0 + }, + "email_intelligence": { + "enriched_signal_count": 1, + "high_risk_count": 0, + "social_profile_count": 0 + }, + "phone_intelligence": { + "enriched_signal_count": 0, + "high_risk_count": 0 + } + }, + "reviewed_at": "2026-10-12T16:30:00Z", + "reviewed_by": "reviewer@example.com", + "review_reason": "Shared device across three new accounts.", + "notes_count": 0, + "sharing_mode": "global", + "cross_org_available": true, + "cross_org_unavailable_reason": null, + "tags": [ + { + "uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "name": "SUSPICIOUS", + "color": "#6366F1", + "source": "custom", + "added_by": null, + "added_at": "2026-10-13T08:15:00Z" + } + ], + "outcomes": { + "approved": 1, + "declined": 1, + "in_review": 0, + "total": 3 + }, + "patterns": [ + { + "type": "background", + "score": 0.8125, + "members": 3, + "reason": null, + "computed_at": "2026-10-13T08:00:00Z" + } + ] + } + } + } + }, + "200": { + "description": "The tag was already on the network and is left as it is. Returns the network detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkDetailResponse" + }, + "example": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "network_id": "N-0042", + "name": null, + "status": "in_review", + "risk": { + "score": 82, + "band": "high", + "bar": 0.82 + }, + "pattern_types": [ + "exact_same_device", + "same_email" + ], + "signal_types": [ + "device", + "email" + ], + "member_counts": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signal_coverage": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + }, + "first_activity_at": "2026-10-01T09:12:44Z", + "last_activity_at": "2026-10-12T16:03:10Z", + "stats": { + "members": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signals": { + "total": 2, + "by_type": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + } + } + }, + "fraud_tags": [], + "risk_signals": { + "residential_proxy_reuse": { + "detected": false, + "signal_count": 0, + "max_subject_count": 0 + }, + "email_intelligence": { + "enriched_signal_count": 1, + "high_risk_count": 0, + "social_profile_count": 0 + }, + "phone_intelligence": { + "enriched_signal_count": 0, + "high_risk_count": 0 + } + }, + "reviewed_at": "2026-10-12T16:30:00Z", + "reviewed_by": "reviewer@example.com", + "review_reason": "Shared device across three new accounts.", + "notes_count": 0, + "sharing_mode": "global", + "cross_org_available": true, + "cross_org_unavailable_reason": null, + "tags": [ + { + "uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "name": "SUSPICIOUS", + "color": "#6366F1", + "source": "custom", + "added_by": null, + "added_at": "2026-10-13T08:15:00Z" + } + ], + "outcomes": { + "approved": 1, + "declined": 1, + "in_review": 0, + "total": 3 + }, + "patterns": [ + { + "type": "background", + "score": 0.8125, + "members": 3, + "reason": null, + "computed_at": "2026-10-13T08:00:00Z" + } + ] + } + } + } + }, + "400": { + "description": "Invalid body: `tag_uuid` is missing or is not a UUID.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tag_uuid": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "examples": { + "Missing tag_uuid": { + "summary": "Missing tag_uuid", + "value": { + "tag_uuid": [ + "This field is required." + ] + } + }, + "Invalid tag_uuid": { + "summary": "Invalid tag_uuid", + "value": { + "tag_uuid": [ + "Must be a valid UUID." + ] + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `write:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Network not found": { + "summary": "Network not found", + "value": { + "detail": "Network not found." + } + }, + "Tag not found": { + "summary": "Tag not found", + "value": { + "detail": "Tag not found." + } + } + } + } + } + } + }, + "x-didit-sdk": { + "resource": "networks", + "method": "add_tag", + "auth": "api_key", + "server": true, + "scope": "application" + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/tags/remove/": { + "post": { + "summary": "Remove a tag from a network", + "description": "Remove one of your application's tags from the network. Returns the network detail. 404 when the tag is not on the network, or does not belong to your application. Requires a full-access API key, or `write:networks` for a console member.", + "operationId": "remove_network_tag", + "tags": [ + "Networks" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "description": "Network UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkTagRequest" + }, + "example": { + "tag_uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/3fa85f64-5717-4562-b3fc-2c963f66afa6/tags/remove/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"tag_uuid\": \"7c9e6679-7425-40de-944b-e07fc1f90ae7\"}'" + } + ], + "responses": { + "200": { + "description": "The tag was removed from the network. Returns the network detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkDetailResponse" + }, + "example": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "network_id": "N-0042", + "name": null, + "status": "in_review", + "risk": { + "score": 82, + "band": "high", + "bar": 0.82 + }, + "pattern_types": [ + "exact_same_device", + "same_email" + ], + "signal_types": [ + "device", + "email" + ], + "member_counts": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signal_coverage": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + }, + "first_activity_at": "2026-10-01T09:12:44Z", + "last_activity_at": "2026-10-12T16:03:10Z", + "stats": { + "members": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signals": { + "total": 2, + "by_type": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + } + } + }, + "fraud_tags": [], + "risk_signals": { + "residential_proxy_reuse": { + "detected": false, + "signal_count": 0, + "max_subject_count": 0 + }, + "email_intelligence": { + "enriched_signal_count": 1, + "high_risk_count": 0, + "social_profile_count": 0 + }, + "phone_intelligence": { + "enriched_signal_count": 0, + "high_risk_count": 0 + } + }, + "reviewed_at": "2026-10-12T16:30:00Z", + "reviewed_by": "reviewer@example.com", + "review_reason": "Shared device across three new accounts.", + "notes_count": 0, + "sharing_mode": "global", + "cross_org_available": true, + "cross_org_unavailable_reason": null, + "tags": [], + "outcomes": { + "approved": 1, + "declined": 1, + "in_review": 0, + "total": 3 + }, + "patterns": [ + { + "type": "background", + "score": 0.8125, + "members": 3, + "reason": null, + "computed_at": "2026-10-13T08:00:00Z" + } + ] + } + } + } + }, + "400": { + "description": "Invalid body: `tag_uuid` is missing or is not a UUID.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tag_uuid": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "examples": { + "Missing tag_uuid": { + "summary": "Missing tag_uuid", + "value": { + "tag_uuid": [ + "This field is required." + ] + } + }, + "Invalid tag_uuid": { + "summary": "Invalid tag_uuid", + "value": { + "tag_uuid": [ + "Must be a valid UUID." + ] + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `write:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Network not found": { + "summary": "Network not found", + "value": { + "detail": "Network not found." + } + }, + "Tag not found": { + "summary": "Tag not found", + "value": { + "detail": "Tag not found." + } + }, + "Tag not on the network": { + "summary": "Tag not on the network", + "value": { + "detail": "This tag is not on the network." + } + } + } + } + } + } + }, + "x-didit-sdk": { + "resource": "networks", + "method": "remove_tag", + "auth": "api_key", + "server": true, + "scope": "application" + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/{request_uuid}/remind/": { + "post": { + "summary": "Remind the customer of a request", + "description": "Send a waiting customer request's email again, to the same verified recipient, with the same content. You can remind a request at most once every 24 hours. A reminder changes nothing else: the request keeps its deadline and the case keeps its status. Each reminder is logged on the case as a CUSTOMER_REQUEST event with action `reminded`. The response's `delivery_status` is how this reminder's delivery ended: SENT, SIMULATED (the email was not sent), or UNKNOWN (the email might have been accepted; it is not sent again automatically). `next_reminder_at` is when you can remind this request again. Returns 409 with a `reason` when the customer no longer has the request to answer, and 429 with `retry_at` and a `Retry-After` header when the request was reminded in the last 24 hours. Requires `write:cases`.", + "operationId": "organization_application_cases_requests_remind_create", + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "description": "Organization UUID." + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "description": "Application UUID." + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "description": "Case UUID." + }, + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "description": "Customer request UUID, from the case's request list." + } + ], + "tags": [ + "Case Management" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/requests/3fa85f64-5717-4562-b3fc-2c963f66afa6/remind/' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "The reminder was recorded and its email sent.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "request", + "reminded_at", + "delivery_status", + "next_reminder_at" + ], + "properties": { + "request": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequest" + }, + "reminded_at": { + "type": "string", + "format": "date-time", + "description": "When this reminder was recorded." + }, + "delivery_status": { + "type": "string", + "enum": [ + "SENT", + "SIMULATED", + "UNKNOWN" + ], + "description": "How this reminder's delivery ended. SIMULATED: the email was not sent. UNKNOWN: the email might have been accepted; it is not sent again automatically." + }, + "next_reminder_at": { + "type": "string", + "format": "date-time", + "description": "When you can remind this request again: `reminded_at` plus 24 hours." + } + } + }, + "example": { + "request": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "purpose": "confirm_details", + "language": "en", + "recipient": "customer@example.com", + "expires_at": "2026-10-20T09:00:00Z", + "actor_email": "reviewer@example.com", + "delivery_status": "SENT", + "delivered_at": "2026-10-13T09:00:04Z", + "cancelled_at": null, + "responded_at": null, + "response": null, + "has_attachment": false, + "created_at": "2026-10-13T09:00:00Z" + }, + "reminded_at": "2026-10-14T10:30:00Z", + "delivery_status": "SENT", + "next_reminder_at": "2026-10-15T10:30:00Z" + } + } + } + }, + "400": { + "description": "The case has a resolution pending approval. The body is a list of strings.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "example": [ + "Customer messages require an active case without a pending resolution." + ] + } + } + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "409": { + "description": "The request no longer waits on the customer, so there is nothing to remind them of. `reason` says why: `answered` (the customer responded), `cancelled`, `expired`, `closed` (the case was resolved after the request went out, even if it was reopened since), `not_actionable` (a review-in-progress update asks nothing), `not_delivered` (the first email has not gone out), or `contact_unavailable` (the verified email is no longer available).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "case_request_not_waiting" + ] + }, + "reason": { + "type": "string", + "enum": [ + "answered", + "cancelled", + "expired", + "closed", + "not_actionable", + "not_delivered", + "contact_unavailable" + ] + } + } + }, + "example": { + "detail": "This request no longer waits on the customer, so there is nothing to remind them of.", + "code": "case_request_not_waiting", + "reason": "answered" + } + } + } + }, + "429": { + "description": "The request was already reminded in the last 24 hours. `retry_at` and the `Retry-After` header say when you can remind it again.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before you can remind this request again.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 3600 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "case_request_reminder_too_soon" + ] + }, + "retry_at": { + "type": "string", + "format": "date-time" + } + } + }, + "example": { + "detail": "This request was already reminded in the last 24 hours.", + "code": "case_request_reminder_too_soon", + "retry_at": "2026-10-15T10:30:00Z" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-didit-sdk": { + "resource": "cases.requests", + "method": "remind", + "auth": "api_key", + "server": true, + "scope": "application" + } + } + }, + "/v3/session/{sessionId}/decision-email-preview/": { + "post": { + "summary": "Preview a session's decision email", + "description": "Render the email the person receives when you set a session to `Approved` or `Declined` with `send_email: true`, without changing the session or sending anything. The response shows the subject and HTML exactly as they are sent, the recipient, the sender, the language, and whether confirming the change sends the email at all (`will_be_sent`, `not_sent_reason`).\n\nThe recipient is the person's own address: the one they verified in an email verification step of the flow, else the one given in `contact_details` when the session was created. The sender is your application's verified sender domain when white label applies (`sender_kind: customer`), else Didit's sender (`sender_kind: didit`).\n\nTo send exactly what you previewed, call [Update Session Status](/sessions-api/update-status) with the same `new_status`, `send_email: true`, `email_address` set to `recipient`, `email_language` set to `language` and `email_preview_token` set to `preview_token`. The receipt expires after 15 minutes. When the recipient, language, session status, sender or email changed since the preview, the update returns `409` before anything changes; preview again and confirm with the new receipt.\n\nWorks for User Verification (KYC) and Business Verification (KYB) sessions in `Approved`, `Declined`, `In Review`, `Kyc Expired`, `Abandoned` or `Resubmitted` status. The status check runs before body validation. Requires the `write:sessions` privilege; a scoped API key needs write access to decisions. Responses use `Cache-Control: no-store`: treat them as sensitive verification content.", + "operationId": "post_v3_session_decision_email_preview", + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "new_status" + ], + "properties": { + "new_status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "The decision whose email you want to preview.", + "example": "Approved" + }, + "email_language": { + "type": "string", + "default": "en", + "description": "Language of the email, the same default as Update Session Status. A language that is not offered falls back to English; `language` in the response says which one is used.", + "example": "en" + } + } + }, + "examples": { + "Approved": { + "summary": "Preview the Approved email in English", + "value": { + "new_status": "Approved" + } + }, + "Declined in Spanish": { + "summary": "Preview the Declined email in Spanish", + "value": { + "new_status": "Declined", + "email_language": "es" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Preview only. The session is unchanged and no email is sent.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: the preview carries verification content and must not be cached.", + "schema": { + "type": "string", + "example": "no-store" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "recipient", + "recipient_source", + "sender", + "sender_name", + "sender_email", + "sender_kind", + "subject", + "html", + "language", + "will_be_sent", + "not_sent_reason", + "preview_token" + ], + "properties": { + "recipient": { + "type": "string", + "description": "The address the email goes to. An empty string when no address is known for the person: then no email can be sent and `preview_token` is empty too. Pass it as `email_address` when you confirm.", + "example": "person@example.com" + }, + "recipient_source": { + "type": "string", + "enum": [ + "email_verification", + "contact_details", + "" + ], + "description": "Where `recipient` comes from. `email_verification`: the address the person verified in an email verification step of the flow. `contact_details`: the address given in `contact_details` when the session was created. An empty string when there is no address.", + "example": "email_verification" + }, + "sender": { + "type": "string", + "description": "The `From` header the email goes out with.", + "example": "Example App " + }, + "sender_name": { + "type": "string", + "description": "The display name part of `sender`.", + "example": "Example App" + }, + "sender_email": { + "type": "string", + "format": "email", + "description": "The address part of `sender`.", + "example": "no-reply@mail.example.com" + }, + "sender_kind": { + "type": "string", + "enum": [ + "customer", + "didit" + ], + "description": "`customer`: the email goes out from your application's verified sender domain (white label). `didit`: it goes out from Didit's own sender.", + "example": "customer" + }, + "subject": { + "type": "string", + "description": "The subject line exactly as it is sent.", + "example": "Your verification has been approved in Example App" + }, + "html": { + "type": "string", + "description": "The email's HTML exactly as it is sent, with its logo embedded as a data URL. Render it in a sandboxed frame, never directly in your application DOM.", + "example": "..." + }, + "language": { + "type": "string", + "description": "The language the email is written in. English (`en`) when the `email_language` you asked for is not offered.", + "example": "en" + }, + "will_be_sent": { + "type": "boolean", + "description": "Whether confirming the change with `send_email: true` sends this email. `false` when `not_sent_reason` is set.", + "example": true + }, + "not_sent_reason": { + "type": "string", + "nullable": true, + "enum": [ + "status_unchanged", + "notification_emails_disabled", + "no_recipient", + "sandbox", + null + ], + "description": "Why confirming would send no email, or `null` when it will be sent. `status_unchanged`: the session already has this status, so the update changes nothing and sends nothing. `notification_emails_disabled`: the session was created without `contact_details.send_notification_emails: true`, so no decision email goes to the address given in `contact_details`. `no_recipient`: no address is known for the person. `sandbox`: a sandbox session whose recipient has not opted into sandbox email delivery, so the send is only simulated.", + "example": null + }, + "preview_token": { + "type": "string", + "description": "Signed receipt for this exact email, valid for 15 minutes. Pass it as `email_preview_token` to [Update Session Status](/sessions-api/update-status). An empty string when there is no recipient.", + "example": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + } + }, + "examples": { + "Will Be Sent": { + "summary": "The Approved email goes to the address verified in the flow", + "value": { + "recipient": "person@example.com", + "recipient_source": "email_verification", + "sender": "Example App ", + "sender_name": "Example App", + "sender_email": "no-reply@mail.example.com", + "sender_kind": "customer", + "subject": "Your verification has been approved in Example App", + "html": "...", + "language": "en", + "will_be_sent": true, + "not_sent_reason": null, + "preview_token": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + }, + "No Recipient": { + "summary": "No address is known for the person", + "value": { + "recipient": "", + "recipient_source": "", + "sender": "Example App ", + "sender_name": "Example App", + "sender_email": "no-reply@mail.example.com", + "sender_kind": "customer", + "subject": "Your verification has been declined in Example App", + "html": "...", + "language": "en", + "will_be_sent": false, + "not_sent_reason": "no_recipient", + "preview_token": "" + } + } + } + } + } + }, + "400": { + "description": "The session is not in a status this endpoint accepts (`{\"detail\": ...}`), or the body is invalid (`{\"\": [\"\"]}`).", + "content": { + "application/json": { + "examples": { + "Wrong Current Status": { + "summary": "Session is not in a status this endpoint accepts", + "value": { + "detail": "Only sessions with status Approved, Declined, In Review, Kyc Expired, Abandoned, Resubmitted can be updated. Current status: In Progress" + } + }, + "Invalid new_status": { + "summary": "new_status is not Approved or Declined", + "value": { + "new_status": [ + "\"Resubmitted\" is not a valid choice." + ] + } + }, + "Missing new_status": { + "summary": "Required field omitted", + "value": { + "new_status": [ + "This field is required." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing or invalid credentials, a caller without the `write:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no write access to decisions." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/decision-email-preview/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"new_status\": \"Approved\", \"email_language\": \"en\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/decision-email-preview/\",\n headers={\"x-api-key\": \"YOUR_API_KEY\"},\n json={\"new_status\": \"Approved\", \"email_language\": \"en\"},\n)\nresponse.raise_for_status()\nprint(response.json())" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "preview_decision_email", + "auth": "api_key", + "server": true + } + } + }, + "/v3/travel-rule/join/{token}/": { + "get": { + "summary": "Get a free-join invitation", + "description": "Public, unauthenticated. Shows the invitation a join link is for while it is open: who asked, the address it went to, and the directory values that will be prefilled. The token is the authentication.", + "operationId": "getTravelRuleJoinInvitation", + "x-didit-sdk": { + "resource": "travel_rule.join", + "method": "get_invitation", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The invitation token from the join link." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/join/{token}/" + } + ], + "responses": { + "200": { + "description": "The invitation.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleJoinInvitation" + } + } + } + }, + "404": { + "description": "No invitation has this link." + }, + "410": { + "description": "The invitation is no longer open: expired, already used, or withdrawn." + } + } + }, + "post": { + "summary": "Register a free Travel Rule account", + "description": "Public, unauthenticated. Creates a free Travel Rule account for a counterparty VASP that received a request. The email must be the address the invitation went to. A 6-character code is emailed to it; send it to the verify endpoint to finish. Registration attempts are rate limited per address and per IP.", + "operationId": "registerTravelRuleJoin", + "x-didit-sdk": { + "resource": "travel_rule.join", + "method": "register", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The invitation token from the join link." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleJoinRegisterRequest" + }, + "example": { + "email": "compliance@example.com", + "password": "a-long-passphrase" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/join/{token}/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"email\": \"compliance@example.com\", \"password\": \"a-long-passphrase\"}'" + } + ], + "responses": { + "202": { + "description": "The account was created and a code was emailed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Tells the counterparty to check their email for the verification code." + }, + "email": { + "type": "string", + "format": "email", + "description": "The address the code was sent to." + } + } + } + } + } + }, + "400": { + "description": "The email is not the invited address, or the password or address was refused." + }, + "404": { + "description": "No invitation has this link." + }, + "410": { + "description": "The invitation is no longer open: expired, already used, or withdrawn." + }, + "429": { + "description": "Too many attempts to join. The `X-RateLimit-*` headers name the budget that was spent." + } + } + } + }, + "/v3/travel-rule/join/{token}/verify/": { + "post": { + "summary": "Verify a free Travel Rule account", + "description": "Public, unauthenticated. Spends the emailed code, creates the free organization and answers with the credentials its application uses on the workspace endpoints. Receiving Travel Rule data is free.", + "operationId": "verifyTravelRuleJoin", + "x-didit-sdk": { + "resource": "travel_rule.join", + "method": "verify", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The invitation token from the join link." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleJoinVerifyRequest" + }, + "example": { + "email": "compliance@example.com", + "code": "123456" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/join/{token}/verify/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"email\": \"compliance@example.com\", \"code\": \"123456\"}'" + } + ], + "responses": { + "201": { + "description": "The organization and application credentials.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleJoinCredentials" + } + } + } + }, + "400": { + "description": "The code or email was refused." + }, + "404": { + "description": "No invitation has this link." + }, + "410": { + "description": "The invitation is no longer open: expired, already used, or withdrawn." + }, + "429": { + "description": "Too many attempts to join. The `X-RateLimit-*` headers name the budget that was spent." + } + } + } + }, + "/v3/travel-rule/workspace/": { + "get": { + "summary": "Get the free Travel Rule workspace", + "description": "Reads the free workspace of an application that joined free: directory claim, domain proof, billing and Didit network listing.", + "operationId": "getTravelRuleWorkspace", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "get", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/workspace/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "The workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/claim/": { + "post": { + "summary": "Claim a directory entry", + "description": "Claims the directory entry this workspace is. When the work email is at a domain the entry lists, the claim is proved and applies at once: the VASP profile and Travel Rule settings are filled from the entry and Travel Rule is turned on, so the next transfer to it routes over the Didit network. Otherwise the claim waits for review (`pending_review`).", + "operationId": "claimTravelRuleWorkspace", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "claim", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspaceClaimRequest" + }, + "example": { + "vasp_id": "0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/claim/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vasp_id\": \"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\"}'" + } + ], + "responses": { + "200": { + "description": "The workspace with its claim status.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "400": { + "description": "No directory entry has this id." + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/domain/code/": { + "post": { + "summary": "Email a domain verification code", + "description": "Emails a single-use code to the workspace's work address to prove it controls the email domain. Throttled, and a limited number of wrong attempts is accepted per code. Also returns the TXT record for the DNS alternative.", + "operationId": "sendTravelRuleWorkspaceDomainCode", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "send_domain_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/domain/code/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "The domain, its status and the TXT record.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspaceDomainCodeResponse" + } + } + } + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/domain/confirm/": { + "post": { + "summary": "Confirm the domain verification code", + "description": "Spends the emailed code and marks the email domain verified.", + "operationId": "confirmTravelRuleWorkspaceDomain", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "confirm_domain", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspaceDomainConfirmRequest" + }, + "example": { + "code": "123456" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/domain/confirm/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"code\": \"123456\"}'" + } + ], + "responses": { + "200": { + "description": "The workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "400": { + "description": "The code is wrong, expired or already used." + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/domain/dns/": { + "post": { + "summary": "Verify the domain from its TXT record", + "description": "Checks for the `didit-tr-verify.` TXT record returned by the code endpoint and marks the domain verified when it matches. One of the two proofs, the emailed code or the TXT record, is enough.", + "operationId": "verifyTravelRuleWorkspaceDomainDns", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "verify_domain_dns", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/domain/dns/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "The workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "400": { + "description": "The TXT record was not found or does not match." + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/billing/": { + "post": { + "summary": "Enable outbound billing", + "description": "Turns outbound Travel Rule billing on for the workspace. Receiving stays free. This cannot be turned back off through the API: contact Didit support.", + "operationId": "enableTravelRuleWorkspaceBilling", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "enable_billing", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspaceBillingRequest" + }, + "example": { + "enabled": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/billing/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"enabled\": true}'" + } + ], + "responses": { + "200": { + "description": "The workspace with `billing_enabled` true.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "400": { + "description": "`enabled` was false." + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/investigator/summary/": { + "get": { + "operationId": "investigator_summary", + "summary": "Investigator summary", + "description": "Stage, queue counts, reports prepared and decided today, 30-day agreement, prices and the autonomy level of each queue. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "stage": { + "type": "string" + }, + "last_report_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "prepared_today": { + "type": "integer" + }, + "ready_to_accept": { + "type": "integer" + }, + "open": { + "type": "integer" + }, + "decided_today": { + "type": "integer" + }, + "automation_rate_month": { + "type": "number", + "nullable": true + }, + "preparing": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "timezone", + "schema": { + "type": "string" + }, + "description": "IANA time zone (for example `Europe/Madrid`) that \"today\" counts from. UTC by default." + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_summary", + "auth": "api_key", + "server": true + } + } + }, + "/v3/investigator/queue/": { + "get": { + "operationId": "investigator_queue", + "summary": "List the Investigator queue", + "description": "Open reports, **Ready to accept** first, then by deadline, risk and wait. `counts` covers every kind in scope. `count` is capped at 100. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "counts": { + "type": "object", + "additionalProperties": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/InvestigatorQueueRow" + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "kind", + "schema": { + "type": "string" + }, + "description": "`session`, `business_session`, `transaction` or `case`, or several separated by commas." + }, + { + "in": "query", + "name": "queue", + "schema": { + "type": "string", + "enum": [ + "kyc", + "kyb", + "aml", + "transactions", + "cases" + ] + } + }, + { + "in": "query", + "name": "recommendation", + "schema": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ] + } + }, + { + "in": "query", + "name": "confidence", + "schema": { + "type": "string", + "enum": [ + "high", + "moderate", + "low" + ] + } + }, + { + "in": "query", + "name": "ready", + "schema": { + "type": "boolean" + } + }, + { + "in": "query", + "name": "search", + "schema": { + "type": "string" + }, + "description": "Name, company, case title, number, transaction ID or item ID." + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "list_queue", + "auth": "api_key", + "server": true + } + } + }, + "/v3/investigator/runs/{run_id}/": { + "get": { + "operationId": "investigator_run_retrieve", + "summary": "Get an Investigator report", + "description": "The full report of one run. 404 when the run has no completed report, or its item was deleted. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorRunDetail" + } + } + } + }, + "404": { + "description": "Not found, or no report." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "path", + "name": "run_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_run", + "auth": "api_key", + "server": true + } + } + }, + "/v3/investigator/runs/{run_id}/accept/": { + "post": { + "operationId": "investigator_run_accept", + "summary": "Accept a recommendation", + "description": "Applies the recommended action through the item's normal path, so your usual webhooks fire. A `narrative` makes it edited and accepted. Restricted keys need the `decisions` scope (write); transaction alerts also need `transactions` (write). Escalating a verification or transaction opens a case.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorDecision" + } + } + } + }, + "409": { + "description": "Already decided, report not ready, or a guardrail holds the item." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "path", + "name": "run_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorAcceptRequest" + } + } + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "accept_run", + "auth": "api_key", + "server": true + } + } + }, + "/v3/investigator/runs/{run_id}/override/": { + "post": { + "operationId": "investigator_run_override", + "summary": "Override a recommendation", + "description": "Takes another action with a category and a reason. Also overrides a decision the Investigator took on its own while it still stands. Restricted keys need the `decisions` scope (write); transaction alerts also need `transactions` (write). Escalating a verification or transaction opens a case.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorDecision" + } + } + } + }, + "409": { + "description": "Already decided, or a guardrail holds the item." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "path", + "name": "run_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorOverrideRequest" + } + } + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "override_run", + "auth": "api_key", + "server": true + } + } + }, + "/v3/investigator/runs/batch-accept/": { + "post": { + "operationId": "investigator_batch_accept", + "summary": "Batch accept recommendations", + "description": "Accepts up to 50 reports marked Ready to accept. Each is its own decision: one that cannot be accepted is skipped with its reason. Restricted keys need the `decisions` scope (write); transaction alerts also need `transactions` (write). Escalating a verification or transaction opens a case.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorBatchAcceptResponse" + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorBatchAcceptRequest" + } + } + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "batch_accept_runs", + "auth": "api_key", + "server": true + } + } + }, + "/v3/investigator/transactions/{transaction_id}/": { + "get": { + "operationId": "investigator_transaction_retrieve", + "summary": "Get a transaction's Investigator review", + "description": "The transaction's latest review, or `investigator: null` when it never had one. Restricted keys need the `decisions` and `transactions` scopes (read).", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "transaction_id": { + "type": "string", + "format": "uuid" + }, + "investigator": { + "$ref": "#/components/schemas/InvestigatorBlock" + } + } + } + } + } + }, + "404": { + "description": "Not this application's transaction." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "path", + "name": "transaction_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_transaction", + "auth": "api_key", + "server": true + } + } + }, + "/v3/investigator/guardrails/": { + "get": { + "operationId": "investigator_guardrails", + "summary": "List the locked guardrails", + "description": "The five guardrails that apply to every queue. They cannot be switched off. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "enum": [ + "sanctions_to_person", + "no_tipping_off", + "no_protected_characteristics", + "never_files_reports", + "every_decision_logged" + ] + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "locked": { + "type": "boolean" + } + } + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "list_guardrails", + "auth": "api_key", + "server": true + } + } + }, + "/v3/investigator/dashboard/": { + "get": { + "operationId": "investigator_dashboard", + "summary": "Investigator dashboard", + "description": "The last 30 days: reviews, decisions, automation rate, hours saved, spend, overrides, quality samples since go-live, per-queue and weekly figures. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "stage": { + "type": "string" + }, + "window_days": { + "type": "integer" + }, + "reviews": { + "type": "integer" + }, + "decided": { + "type": "integer" + }, + "automated": { + "type": "integer" + }, + "automation_rate": { + "type": "number", + "nullable": true + }, + "hours_saved": { + "type": "number", + "nullable": true + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_dashboard", + "auth": "api_key", + "server": true + } + } + }, + "/v3/investigator/performance/": { + "get": { + "operationId": "investigator_performance", + "summary": "Investigator performance", + "description": "Agreement with your team, volume, time saved, confidence calibration, per-queue results, override reasons and disagreements for a period. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "period": { + "type": "integer" + }, + "start": { + "type": "string", + "format": "date" + }, + "end": { + "type": "string", + "format": "date" + }, + "investigated": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "period", + "schema": { + "type": "string", + "enum": [ + "7", + "30", + "90" + ], + "default": "30" + }, + "description": "Days, ending today." + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_performance", + "auth": "api_key", + "server": true + } + } + }, + "/v3/investigator/activity/": { + "get": { + "operationId": "investigator_activity", + "summary": "List Investigator activity", + "description": "The append-only audit trail, newest first. A batch accept is one entry. Adds `counts` per group and `actors`. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "counts": { + "type": "object", + "additionalProperties": true + }, + "actors": { + "type": "array", + "items": {} + }, + "results": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true, + "description": "`id`, `at`, `kind`, `group`, `queue`, `run_id`, `item`, `actor`, `agent_version`, `data`, `report_version`, `steps`, `sources`, `batch`." + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "group", + "schema": { + "type": "string", + "enum": [ + "agent", + "decisions", + "overrides", + "autonomy" + ] + } + }, + { + "in": "query", + "name": "actor", + "schema": { + "type": "string" + }, + "description": "`agent` for the Investigator, or a person's email." + }, + { + "in": "query", + "name": "since", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "until", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "list_activity", + "auth": "api_key", + "server": true + } + } + }, + "/v3/investigator/activity/export/": { + "get": { + "operationId": "investigator_activity_export", + "summary": "Export Investigator activity", + "description": "The same entries as CSV, one row per decision, newest first, up to 10,000 entries. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "text/csv": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "group", + "schema": { + "type": "string", + "enum": [ + "agent", + "decisions", + "overrides", + "autonomy" + ] + } + }, + { + "in": "query", + "name": "actor", + "schema": { + "type": "string" + }, + "description": "`agent` for the Investigator, or a person's email." + }, + { + "in": "query", + "name": "since", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "until", + "schema": { + "type": "string", + "format": "date-time" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "export_activity", + "auth": "api_key", + "server": true + } + } + }, + "/v3/investigator/settings/": { + "get": { + "operationId": "investigator_settings_retrieve", + "summary": "Get Investigator settings", + "description": "The stage (`none`, `shadow` or `live`) and the monthly spend limit with this month's spend. Restricted keys need the `applications` scope (read).", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorSettings" + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_settings", + "auth": "api_key", + "server": true + } + }, + "patch": { + "operationId": "investigator_settings_update", + "summary": "Update Investigator settings", + "description": "Change the stage, the monthly spend limit, or both. Moving past `none` needs a plan that includes the Investigator (Growth or higher); `live` also needs a contract that prices it, if you have one. Restricted keys need the `applications` scope (write).", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorSettings" + } + } + } + }, + "400": { + "description": "Neither field sent, or an invalid value." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorSettings" + } + } + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "update_settings", + "auth": "api_key", + "server": true + } + } + }, + "/v3/travel-rule/wallet-addresses/import/": { + "post": { + "summary": "Import wallet addresses in bulk", + "description": "Add up to 10,000 wallets to your Travel Rule address book in one call, as JSON or as a CSV file. A wallet can be sent by its address, by the SHA-256 of its lowercased address (`address_hash`) to keep the address private, or both. The import is idempotent: a wallet already in the book is reported as `existing` and left untouched, so the same file can be sent again. Each row is validated on its own, so one bad row never fails the others. A wallet imported by its hash only matches transfers to that address and can be proven with the wallet-ownership widget like any other entry.", + "operationId": "importTravelRuleWalletAddresses", + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "bulk_import", + "auth": "api_key", + "server": true + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "required": [ + "entries" + ], + "properties": { + "entries": { + "type": "array", + "minItems": 1, + "maxItems": 10000, + "items": { + "$ref": "#/components/schemas/WalletAddressImportRow" + } + } + } + }, + { + "type": "array", + "minItems": 1, + "maxItems": 10000, + "items": { + "$ref": "#/components/schemas/WalletAddressImportRow" + } + } + ] + }, + "example": { + "entries": [ + { + "address": "0xBeneficiaryWallet01", + "chain": "ETH", + "holder_name": "Ana Diaz", + "holder_vendor_data": "user-042" + }, + { + "address_hash": "4f7d2e1c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e", + "chain": "bitcoin", + "holder_vendor_data": "user-043" + }, + { + "address": "rExchangeSharedAddress01", + "chain": "XRP", + "memo": "104729", + "holder_name": "Ana Diaz" + } + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "file" + ], + "properties": { + "file": { + "type": "string", + "format": "binary", + "description": "A UTF-8 CSV file of at most 5 MB with a header row naming its columns (the row fields or their aliases). Empty cells count as not sent; columns the import does not read are ignored." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl (JSON)", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/wallet-addresses/import/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"entries\": [{\"address\": \"0xBeneficiaryWallet01\", \"chain\": \"ETH\", \"holder_name\": \"Ana Diaz\"}, {\"address\": \"rExchangeSharedAddress01\", \"chain\": \"XRP\", \"memo\": \"104729\"}]}'" + }, + { + "lang": "curl", + "label": "curl (CSV)", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/wallet-addresses/import/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'file=@wallets.csv;type=text/csv'" + } + ], + "responses": { + "200": { + "description": "Every row was created, already in the book, or a repeat of an earlier row.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressImportReport" + }, + "example": { + "total": 3, + "created": 2, + "existing": 1, + "duplicate": 0, + "failed": 0, + "rows": [ + { + "row": 1, + "status": "existing", + "entry_id": "11111111-2222-3333-4444-555555555555", + "errors": {}, + "duplicate_of_row": null + }, + { + "row": 2, + "status": "created", + "entry_id": "22222222-3333-4444-5555-666666666666", + "errors": {}, + "duplicate_of_row": null + }, + { + "row": 3, + "status": "created", + "entry_id": "33333333-4444-5555-6666-777777777777", + "errors": {}, + "duplicate_of_row": null + } + ] + } + } + } + }, + "207": { + "description": "At least one row failed; the others were imported. Each failed row names its errors.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressImportReport" + }, + "example": { + "total": 2, + "created": 1, + "existing": 0, + "duplicate": 0, + "failed": 1, + "rows": [ + { + "row": 1, + "status": "created", + "entry_id": "44444444-5555-6666-7777-888888888888", + "errors": {}, + "duplicate_of_row": null + }, + { + "row": 2, + "status": "failed", + "entry_id": null, + "errors": { + "address": [ + "Send address, address_hash or both." + ] + }, + "duplicate_of_row": null + } + ] + } + } + } + }, + "400": { + "description": "The request itself cannot be read: both `entries` and a `file`, or neither; more than 10,000 rows; a file over 5 MB, not UTF-8 CSV, without a header row or without rows.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "file": [ + "At most 10000 rows per import." + ] + } + } + } + } + } + } + }, + "/v3/travel-rule/counterparty-contacts/": { + "get": { + "summary": "List counterparty contacts", + "description": "The compliance addresses the EMAIL rail may write to for your application: Didit's platform contacts plus your application's own private ones, bounced ones included, newest first. Paginated with `limit` (default 50) and `offset`; `count` stops at 100, so 100 means 100 or more.", + "operationId": "listTravelRuleCounterpartyContacts", + "x-didit-sdk": { + "resource": "travel_rule.counterparty_contacts", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "counterparty", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Only the contacts of this directory entry. Must be a UUID; an empty value means no filter." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1 + }, + "description": "Page size, default 50." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + }, + "description": "Rows to skip." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/travel-rule/counterparty-contacts/?counterparty={vasp_id}' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "The contacts.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleCounterpartyContact" + } + } + } + }, + "example": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "6c1f9b8e-2d4a-4f7e-8b3c-5a9d0e1f2a3b", + "counterparty": "0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91", + "counterparty_name": "Example Counterparty VASP", + "email": "compliance@counterparty.example", + "name": "Compliance desk", + "source": "application", + "scope": "application", + "created_by": "", + "verified_at": null, + "is_invalid": false, + "invalid_reason": "", + "invalid_detail": "", + "invalid_at": null, + "created_at": "2026-10-19T09:00:00Z" + } + ] + } + } + } + }, + "400": { + "description": "`counterparty` is not a UUID.", + "content": { + "application/json": { + "example": { + "counterparty": [ + "Must be a valid UUID." + ] + } + } + } + } + } + }, + "post": { + "summary": "Add a counterparty contact", + "description": "Adds a private compliance address for one directory entry. Only your application sees and uses it, and the EMAIL rail writes to it before any platform contact. Adding an address the entry already has among your own contacts returns that contact.", + "operationId": "createTravelRuleCounterpartyContact", + "x-didit-sdk": { + "resource": "travel_rule.counterparty_contacts", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyContactCreate" + }, + "example": { + "counterparty": "0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91", + "email": "compliance@counterparty.example", + "name": "Compliance desk" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/counterparty-contacts/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"counterparty\": \"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\", \"email\": \"compliance@counterparty.example\", \"name\": \"Compliance desk\"}'" + } + ], + "responses": { + "201": { + "description": "The contact was added.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyContact" + }, + "example": { + "uuid": "6c1f9b8e-2d4a-4f7e-8b3c-5a9d0e1f2a3b", + "counterparty": "0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91", + "counterparty_name": "Example Counterparty VASP", + "email": "compliance@counterparty.example", + "name": "Compliance desk", + "source": "application", + "scope": "application", + "created_by": "", + "verified_at": null, + "is_invalid": false, + "invalid_reason": "", + "invalid_detail": "", + "invalid_at": null, + "created_at": "2026-10-19T09:00:00Z" + } + } + } + }, + "400": { + "description": "A field is missing or invalid, or no directory entry has this id.", + "content": { + "application/json": { + "example": { + "counterparty": [ + "No directory entry with this id." + ] + } + } + } + } + } + } + }, + "/v3/travel-rule/counterparty-contacts/{contact_uuid}/": { + "delete": { + "summary": "Delete a counterparty contact", + "description": "Deletes one of your application's own contacts. The EMAIL rail stops writing to it at once. Platform contacts cannot be deleted.", + "operationId": "deleteTravelRuleCounterpartyContact", + "x-didit-sdk": { + "resource": "travel_rule.counterparty_contacts", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "contact_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE https://verification.didit.me/v3/travel-rule/counterparty-contacts/{contact_uuid}/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "204": { + "description": "Deleted." + }, + "404": { + "description": "No contact of your application has this id. A platform contact answers 404 too." + } + } + } + }, + "/v3/travel-rule/transfers/": { + "get": { + "summary": "List Travel Rule transfers", + "description": "Your application's Travel Rule transfers, newest first, paginated with `limit` (default 50) and `offset`. Rows carry VASP names, wallets and memos only, never person data. Comma-separated values select several. An unknown value answers 400 naming the parameter, for example `{\"direction\": [\"Unknown value(s): inbound.\"]}`.", + "operationId": "listTravelRuleTransfers", + "x-didit-sdk": { + "resource": "travel_rule.transfers", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "direction", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "INBOUND, OUTBOUND, or both comma-separated. Case-sensitive." + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "One or more transfer statuses, comma-separated." + }, + { + "name": "rail", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "One or more of INTERNAL, TRP, GTR, TRUST, VERIFYVASP, SYGNA, EMAIL, TEST_VASP, comma-separated." + }, + { + "name": "kind", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "withdrawal, deposit or message, comma-separated." + }, + { + "name": "currency", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "The transaction's currency, case-insensitive." + }, + { + "name": "start_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date" + }, + "description": "Transfers created on or after this day (UTC), YYYY-MM-DD." + }, + { + "name": "end_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date" + }, + "description": "Transfers created on or before this day (UTC), YYYY-MM-DD." + }, + { + "name": "counterparty", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The `id` of a directory entry or Didit network member from GET /v3/travel-rule/vasps/: your transfers with that counterparty. A value that is not a valid id answers 400." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1 + }, + "description": "Page size, default 50." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + }, + "description": "Rows to skip." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/travel-rule/transfers/?direction=OUTBOUND&status=AWAITING_COUNTERPARTY' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "A page of transfers.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleTransferRow" + } + } + } + }, + "example": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "id": "5b0f6c2e-2a4d-4f0e-9a41-0c3f9a7e1d22", + "transaction_id": "a3c1e8f4-7b62-4d1a-8e0b-2f9d6c4b7a10", + "txn_id": "wd-2026-10-19-0042", + "txn_date": "2026-10-19T08:41:12Z", + "kind": "withdrawal", + "direction": "OUTBOUND", + "status": "AWAITING_COUNTERPARTY", + "rail": "TRP", + "asset": "ETH", + "amount": "1.25", + "originator_vasp": "Origin CASP SL", + "beneficiary_vasp": "Example Counterparty VASP", + "originator_wallet": "0x77ccff0cc624a53cd8b8d8703ced826710211ec7", + "beneficiary_wallet": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb", + "originator_memo": "", + "beneficiary_memo": "", + "originator_wallet_type": "vasp", + "beneficiary_wallet_type": "vasp", + "matched_transaction_id": null, + "deadline_at": "2026-10-21T08:41:13Z", + "created_at": "2026-10-19T08:41:13Z" + } + ] + } + } + } + }, + "400": { + "description": "A filter value is not valid.", + "content": { + "application/json": { + "example": { + "start_date": [ + "Use the YYYY-MM-DD format." + ] + } + } + } + } + } + } + }, + "/v3/session/imports/providers/": { + "get": { + "summary": "List migration providers", + "description": "The 15 providers you can migrate verifications from: what each needs to connect, whether its API can list records, the permissions its key needs and its default status mapping.", + "operationId": "get_v3_session_imports_providers_list", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "list_providers", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "200": { + "description": "Migration providers.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "providers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImportProvider" + } + }, + "mapping_targets": { + "type": "array", + "items": { + "type": "string" + } + }, + "skipped_statuses": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/imports/providers/check/": { + "post": { + "summary": "Check a provider key", + "description": "Read the provider with your key before creating a migration. Nothing is stored.", + "operationId": "post_v3_session_imports_providers_check", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "check_provider", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "provider": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ], + "description": "Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`." + }, + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "options": { + "type": "object", + "properties": { + "region": { + "type": "string", + "description": "The provider region, one of its `regions` (Onfido: `eu`, `us`, `ca`)." + } + } + }, + "record_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "For `api_ids` providers: the first record ID of your export." + } + }, + "required": [ + "provider", + "credentials" + ] + } + } + } + }, + "responses": { + "200": { + "description": "The key can read records.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean" + }, + "provider": { + "type": "string" + }, + "credential_hint": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "400": { + "description": "The provider refused the key: `code`, `detail`, `missing_permissions`, `suggested_region`." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/imports/{importId}/start/": { + "post": { + "summary": "Start a dry run", + "description": "Start a migration whose dry run finished (`READY`): the same job imports for real, with the status mapping and face templates choice as sent now.", + "operationId": "post_v3_session_imports_start", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "start", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + }, + "description": "Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported." + }, + "face_templates": { + "type": "boolean", + "default": false, + "description": "Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`." + }, + "lawful_basis_confirmed": { + "type": "boolean", + "default": false, + "description": "Confirms you have a lawful basis to share these selfies with Didit as processor." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/imports/{importId}/pause/": { + "post": { + "summary": "Pause an import job", + "description": "Pause a queued or running import.", + "operationId": "post_v3_session_imports_pause", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "pause", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/imports/{importId}/resume/": { + "post": { + "summary": "Resume an import job", + "description": "Resume an import you paused. A job paused by the provider's rate limit resumes by itself.", + "operationId": "post_v3_session_imports_resume", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "resume", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/imports/{importId}/cancel/": { + "post": { + "summary": "Cancel an import job", + "description": "Cancel an import. What was imported stays; the uploaded file and the key are erased.", + "operationId": "post_v3_session_imports_cancel", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "cancel", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/imports/{importId}/retry/": { + "post": { + "summary": "Retry a migration", + "description": "Run a migration that ended again as a new job, with a new key (the old one was erased). It keeps the provider, workflow, region, status mapping and face templates choice; everything already in Didit is skipped.", + "operationId": "post_v3_session_imports_retry", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "retry", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": false, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "file": { + "type": "string", + "format": "binary", + "description": "The record IDs or export file again, for `api_ids` and `export_file` providers." + } + } + } + } + } + }, + "responses": { + "201": { + "description": "The new import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "Not a migration, not ended, or the provider refused the key." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/import-jobs/": { + "get": { + "summary": "List import jobs", + "description": "Every import of the application in one shape: verification imports and migrations, people and businesses.", + "operationId": "get_v3_import_jobs_list", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "list_jobs", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "scope", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "all", + "sessions", + "users", + "businesses" + ], + "default": "all" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + } + } + ], + "responses": { + "200": { + "description": "Import jobs, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImportJobSummary" + } + }, + "counts": { + "type": "object", + "properties": { + "all": { + "type": "integer" + }, + "running_and_waiting": { + "type": "integer" + }, + "needs_attention": { + "type": "integer" + }, + "done": { + "type": "integer" + } + } + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/import-jobs/active/": { + "get": { + "summary": "List active import jobs", + "description": "What is running or waiting, plus what just ended, for a progress indicator.", + "operationId": "get_v3_import_jobs_active", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "list_active_jobs", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "200": { + "description": "Active import jobs.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImportJobSummary" + } + }, + "running": { + "type": "integer" + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/transactions/delete/": { + "delete": { + "summary": "Batch delete transactions", + "description": "Delete one or more transactions permanently. Select them with `transaction_uuids`, or delete every transaction in the application with `delete_all: true`.\n\nA deleted transaction goes with the records that are only its own: its parties, tags, notes, payment method links, documents and every stored version of their files, rule runs, alerts, provider results, network snapshot and Travel Rule data. Cases, people and companies stay; their link to the deleted transaction is cleared. Draft regulatory reports stop listing it and are rebuilt without it. Deletion is permanent and irreversible; there is no undo endpoint.\n\nIf any selected transaction is listed in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each transaction and the reports that list it. Delete those reports, then repeat the request.", + "operationId": "batch_delete_transactions", + "x-didit-sdk": { + "resource": "transactions", + "method": "batch_delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "oneOf": [ + { + "title": "Selected transactions", + "required": [ + "transaction_uuids" + ], + "properties": { + "transaction_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit transaction UUIDs to delete. Required unless `delete_all` is true.", + "minItems": 1 + }, + "delete_all": { + "type": "boolean", + "enum": [ + false + ], + "default": false, + "description": "Leave out or send `false` when you select transactions with `transaction_uuids`." + } + } + }, + { + "title": "All transactions", + "required": [ + "delete_all" + ], + "properties": { + "delete_all": { + "type": "boolean", + "enum": [ + true + ], + "description": "Delete every transaction in the application. `transaction_uuids` is ignored." + }, + "transaction_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Ignored when `delete_all` is true." + } + } + } + ] + }, + "examples": { + "Selected transactions": { + "value": { + "transaction_uuids": [ + "abcdef12-3456-7890-abcd-ef1234567890", + "12345678-90ab-cdef-1234-567890abcdef" + ] + } + }, + "All transactions": { + "value": { + "delete_all": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl (selected transactions)", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/transactions/delete/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"transaction_uuids\": [\"abcdef12-3456-7890-abcd-ef1234567890\"]}'" + } + ], + "responses": { + "204": { + "description": "Selected transactions deleted." + }, + "400": { + "description": "No transaction selector was supplied.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "transaction_uuids": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "example": { + "transaction_uuids": [ + "This field is required unless delete_all is true." + ] + } + } + } + }, + "403": { + "description": "The API key cannot delete transactions. The endpoint requires `write:transactions`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "409": { + "description": "At least one selected transaction is listed in a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/travel-rule/networks/": { + "get": { + "summary": "Get Travel Rule networks", + "description": "Read, for this application, every network's true state: whether it sends through your own membership or through Didit's, and why it cannot yet. Also returns the due-diligence questionnaire and your participants. Reading creates nothing.", + "operationId": "getTravelRuleNetworks", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/networks/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Network states, questionnaire and participants.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworksOverview" + }, + "examples": { + "Before the questionnaire": { + "value": { + "platform_networks_enabled": false, + "questionnaire": { + "status": "draft", + "answers": {}, + "submitted_at": null, + "reviewed_at": null, + "review_note": "", + "history": [], + "updated_at": null + }, + "participants": [], + "networks": { + "gtr": { + "rail": "GTR", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "trust": { + "rail": "TRUST", + "mode": "unavailable", + "state": "unavailable", + "reason": "own_membership_only", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "verifyvasp": { + "rail": "VERIFYVASP", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "sygna": { + "rail": "SYGNA", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + } + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + } + } + } + }, + "/v3/travel-rule/networks/questionnaire/": { + "get": { + "summary": "Get the network questionnaire", + "description": "Read Didit's network due-diligence questionnaire for this application, its status and Didit's review note. An application that never saved one reads an empty draft.", + "operationId": "getTravelRuleNetworkQuestionnaire", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "retrieve_questionnaire", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/networks/questionnaire/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "The questionnaire.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaire" + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + } + } + }, + "put": { + "summary": "Save network questionnaire answers", + "description": "Save answers to the questionnaire while it is draft or changes_requested. Saves are partial: answers you do not send are kept.", + "operationId": "updateTravelRuleNetworkQuestionnaire", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "update_questionnaire", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PUT https://verification.didit.me/v3/travel-rule/networks/questionnaire/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"legal_name\": \"Example VASP Ltd\", \"incorporation_country\": \"ES\", \"aml_ctf_policy_attested\": true}'" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaireAnswers" + } + } + } + }, + "responses": { + "200": { + "description": "The questionnaire with the merged answers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaire" + } + } + } + }, + "400": { + "description": "Validation error, keyed by field." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Network access through Didit's membership is not open yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_platform_networks_disabled" + ] + } + } + } + } + } + }, + "409": { + "description": "The questionnaire is with Didit or decided: it can only change while draft or changes_requested.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_questionnaire_not_editable" + ] + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/networks/questionnaire/submit/": { + "post": { + "summary": "Submit the network questionnaire", + "description": "Send the saved questionnaire to Didit's review. The saved answers must be complete: every field except lei and data_protection_contact_email, with the three attestations true. Didit approves it, asks for changes (edit and submit again) or rejects it. Networks you requested while it was pending are handed to their onboarding when it is approved.", + "operationId": "submitTravelRuleNetworkQuestionnaire", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "submit_questionnaire", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/networks/questionnaire/submit/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "The submitted questionnaire.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaire" + } + } + } + }, + "400": { + "description": "The saved answers are incomplete or invalid, keyed by field." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Network access through Didit's membership is not open yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_platform_networks_disabled" + ] + } + } + } + } + } + }, + "409": { + "description": "The questionnaire is with Didit or decided: it can only change while draft or changes_requested.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_questionnaire_not_editable" + ] + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/networks/{network}/request/": { + "post": { + "summary": "Request a Travel Rule network", + "description": "Ask for this application's own participant on a network Didit's membership carries (GTR or Sygna). Without an approved questionnaire the participant waits for it (questionnaire_pending); with one it goes to the network's onboarding (submitted_to_network) and becomes active once the network gives it a code. Idempotent: a pending, onboarding or live participant is returned unchanged. If the network refused the registration (last_error is set), requesting again retries. TRUST and VerifyVASP are reachable only through your own membership: TRUST admits members per VASP, and a VerifyVASP Enclave serves one VASP id.", + "operationId": "requestTravelRuleNetworkAccess", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "request_access", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "network", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "gtr", + "sygna" + ] + }, + "description": "The network to join through Didit's membership." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/networks/gtr/request/ \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Your participant on the network.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkParticipant" + } + } + } + }, + "400": { + "description": "Unknown network, a network Didit does not carry (connect your own membership), or a sandbox application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_network_own_membership_only", + "travel_rule_network_sandbox_application" + ] + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Network access through Didit's membership is not open yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_platform_networks_disabled" + ] + } + } + } + } + } + } + } + } + }, + "/v3/address-validation/": { + "post": { + "summary": "Validate an address", + "description": "Confirm that an address exists and get it back normalised: its delivery lines, components and coordinates. It confirms the address only; it never links the address to a person.\n\n**Request.** Send the address on one line (`address`) or as structured fields (`address_line_1` and, optionally, `address_line_2`, `city`, `region`, `postal_code`), never both, and always its `country` (ISO 3166-1 alpha-2 or alpha-3).\n\n**Result.** `address_validation.status` is `verified` (confirmed to the building), `partially_verified` (confirmed only to the street or above), `not_verified` (only the region or city matched, or nothing did) or `unavailable` (the validation could not run). HTTP is `200` for every completed request, `unavailable` included; retry an `unavailable` result later.\n\n**Billing.** A live call costs $0.10 per validated address. An `unavailable` result is not charged. Nothing is stored apart from the usage record: no session is created and the result is returned only in this response.\n\n**Sandbox.** Sandbox keys validate the request and always return a deterministic `verified` result that echoes the address sent, without consuming credits.\n\n**Authentication.** Send your application's API key in `x-api-key`. Missing or invalid credentials return `403` - this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests per minute per API key across all POST/PATCH/DELETE endpoints.", + "operationId": "post_v3_address_validation", + "x-didit-sdk": { + "resource": "address_validation", + "method": "validate", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/address-validation/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"address_line_1\": \"1600 Amphitheatre Pkwy\",\n \"city\": \"Mountain View\",\n \"region\": \"CA\",\n \"postal_code\": \"94043\",\n \"country\": \"US\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import os, requests\n\nresp = requests.post(\n \"https://verification.didit.me/v3/address-validation/\",\n headers={\"x-api-key\": os.environ[\"DIDIT_API_KEY\"], \"Content-Type\": \"application/json\"},\n json={\n \"address_line_1\": \"1600 Amphitheatre Pkwy\",\n \"city\": \"Mountain View\",\n \"region\": \"CA\",\n \"postal_code\": \"94043\",\n \"country\": \"US\",\n },\n timeout=15,\n)\nresp.raise_for_status()\nresult = resp.json()[\"address_validation\"]\nprint(result[\"status\"], result[\"formatted_address\"])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const res = await fetch('https://verification.didit.me/v3/address-validation/', {\n method: 'POST',\n headers: {\n 'x-api-key': 'YOUR_API_KEY',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ address: 'Paseo de la Castellana 259, 28046 Madrid', country: 'ES' }),\n});\nconst { address_validation } = await res.json();\nconsole.log(address_validation.status, address_validation.formatted_address);" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddressValidationRequest" + }, + "examples": { + "Structured": { + "summary": "Structured address fields", + "value": { + "address_line_1": "1600 Amphitheatre Pkwy", + "city": "Mountain View", + "region": "CA", + "postal_code": "94043", + "country": "US" + } + }, + "One line": { + "summary": "The whole address on one line", + "value": { + "address": "Paseo de la Castellana 259, 28046 Madrid", + "country": "ES" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The validation completed. Read `address_validation.status`; HTTP is always `200` for a completed request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddressValidationResponse" + }, + "examples": { + "Verified": { + "summary": "A real address, confirmed to the building", + "value": { + "request_id": "6f7d2c1e-1c5a-4a1e-9b8f-2a1f0f4b9c3d", + "address_validation": { + "status": "verified", + "verification_level": "premise", + "address": { + "address_line_1": "1600 Amphitheatre Pkwy", + "address_line_2": null, + "city": "Mountain View", + "region": "CA", + "postal_code": "94043-1351", + "country": "US" + }, + "formatted_address": "1600 Amphitheatre Pkwy, Mountain View CA 94043-1351", + "latitude": 37.422022, + "longitude": -122.08429 + }, + "created_at": "2026-10-27T11:20:00.000000+00:00" + } + }, + "Not verified": { + "summary": "An invented street: only the city matched", + "value": { + "request_id": "0b9e3f4a-6c2d-4e8b-a1f7-5d3c2b1a0e9f", + "address_validation": { + "status": "not_verified", + "verification_level": "locality", + "address": { + "address_line_1": "Calle Inventada Que No Existe 99999 28046", + "address_line_2": null, + "city": "Madrid", + "region": "Madrid", + "postal_code": null, + "country": "ES" + }, + "formatted_address": "Calle Inventada Que No Existe 99999 28046, Madrid, MADRID, SPAIN", + "latitude": 40.42603054, + "longitude": -3.68645331 + }, + "created_at": "2026-10-27T11:21:00.000000+00:00" + } + }, + "Unavailable": { + "summary": "The validation could not run; not charged", + "value": { + "request_id": "3c1b2a0f-9e8d-4c7b-b6a5-1f0e9d8c7b6a", + "address_validation": { + "status": "unavailable", + "verification_level": null, + "address": null, + "formatted_address": null, + "latitude": null, + "longitude": null + }, + "created_at": "2026-10-27T11:22:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/AddressValidationValidationError" + }, + "403": { + "$ref": "#/components/responses/AddressValidationPermissionDenied" + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + } + } + }, + "/v3/travel-rule/wallet-proofs/": { + "get": { + "summary": "List screenshot wallet proofs to review", + "description": "Your review queue for screenshot wallet-ownership proofs. A screenshot is the one proof Didit cannot check itself: once the end user uploads it from the widget it waits PENDING until you approve or reject it with POST /v3/travel-rule/wallet-proofs/{proof_uuid}/decision/. Oldest first, so the queue is worked from the proof that has waited longest. Only uploaded screenshots appear, never a proof whose end user has not uploaded a file yet, and never one on a wallet you deleted from your address book (Didit closes those as OWNERSHIP_CHANGED).", + "operationId": "listTravelRuleWalletProofs", + "x-didit-sdk": { + "resource": "travel_rule.wallet_proofs", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Which proofs to list. Defaults to PENDING, the ones still to decide.", + "schema": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ], + "default": "PENDING" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/wallet-proofs/?status=PENDING \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "responses": { + "200": { + "description": "Paginated screenshot proofs. count is exact up to 100 and capped at 100 above it.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleWalletProofReview" + } + } + } + } + } + } + }, + "400": { + "description": "Unknown status." + }, + "401": { + "description": "Missing or invalid x-api-key." + }, + "403": { + "description": "The caller lacks the transactions permission." + } + } + } + }, + "/v3/travel-rule/wallet-proofs/{proof_uuid}/decision/": { + "post": { + "summary": "Approve or reject a screenshot wallet proof", + "description": "Decide a screenshot proof from your review queue. approve verifies the proof and the address book entry and applies the ownership to the transfer the widget session was minted for, exactly as a verified signature does. A screenshot never confirms a transfer that another Didit customer sent over the INTERNAL rail: that takes a signed message or a test deposit. reject closes the proof as REJECTED with failure_reason REVIEW_REJECTED; the end user can start a new proof from the widget. The note is kept with the proof.", + "operationId": "decideTravelRuleWalletProof", + "x-didit-sdk": { + "resource": "travel_rule.wallet_proofs", + "method": "decide", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "proof_uuid", + "in": "path", + "required": true, + "description": "proof_id from the review queue.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWalletProofDecision" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/wallet-proofs/{proof_uuid}/decision/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"decision\": \"approve\", \"note\": \"Address matches the wallet app.\"}'" + } + ], + "responses": { + "200": { + "description": "The decided proof.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWalletProofReview" + } + } + } + }, + "400": { + "description": "Invalid decision, or a note longer than 512 characters." + }, + "401": { + "description": "Missing or invalid x-api-key." + }, + "403": { + "description": "The caller lacks the transactions permission." + }, + "404": { + "description": "No submitted screenshot proof with this id in your application." + }, + "409": { + "description": "The proof is already decided (the body carries its status), or code OWNERSHIP_CHANGED: the wallet's holder changed or the wallet was deleted after the widget session was minted, so the proof is closed and the current holder needs a new one.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + } + } + } + }, + "/v3/travel-rule/widget/{token}/proofs/": { + "post": { + "summary": "Start a wallet-ownership proof", + "description": "Public endpoint keyed by the widget session token, no API key required: the hosted widget calls it when the end user picks a proof method. A proof exists, and counts in your proof funnel, only from this call. Picking the same method again returns the proof already open (200, same challenge or deposit amount) instead of starting a second one; picking another method closes the unfinished one as METHOD_SWITCHED, except a submitted screenshot or a test deposit still being checked. MESSAGE_SIGNING returns the challenge to sign; SATOSHI_TEST the deposit address, exact amount and window; SELF_DECLARATION is verified at once (status VERIFIED). Signature formats verified per chain family: EVM chains take a personal_sign (EIP-191) signature, hex encoded; Bitcoin takes a BIP-137 signmessage signature (P2PKH, P2SH-P2WPKH and P2WPKH addresses) or a BIP-322 simple signature (P2WPKH and P2TR addresses), base64 encoded; Solana takes an Ed25519 signature over the challenge, base58 encoded; Tron takes a signMessageV2 (TIP-191) signature, hex encoded. A BIP-322 full or proof-of-funds signature, or a multisig address, fails with SIGNATURE_UNSUPPORTED_SCHEME.", + "operationId": "createTravelRuleWidgetProof", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "create_widget_proof", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofCreate" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/widget/{token}/proofs/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"method\": \"MESSAGE_SIGNING\"}'" + } + ], + "responses": { + "201": { + "description": "A new PENDING proof (VERIFIED for SELF_DECLARATION).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofSummary" + } + } + } + }, + "200": { + "description": "The proof already open for this method, resumed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofSummary" + } + } + } + }, + "400": { + "description": "Invalid body, a wallet that does not match the transfer, SCREENSHOT not allowed, or code CHAIN_UNSUPPORTED when the method is not available on the chain.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + }, + "403": { + "description": "SELF_DECLARATION is not allowed for this application." + }, + "404": { + "description": "Unknown token." + }, + "409": { + "description": "code OWNERSHIP_CHANGED: the wallet's holder changed or the wallet was deleted after the session was minted; mint a new session for the current holder. Also returned for SATOSHI_TEST when the session has no satoshi_deposit_address, and when the session has no attempts left (no code; the body carries max_attempts, attempts_used and attempts_remaining 0): mint a new session.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + }, + "410": { + "description": "The session expired." + } + } + } + }, + "/v3/travel-rule/widget/{token}/proofs/{proof_id}/verify/": { + "post": { + "summary": "Submit wallet-ownership proof evidence", + "description": "Public endpoint keyed by the widget session token, no API key required. Send the evidence the proof's method needs: signature for MESSAGE_SIGNING, txid for SATOSHI_TEST (JSON), or a file for SCREENSHOT (multipart). Every submission counts as an attempt. A signature that does not verify, or a deposit that does not match, answers 400 with a code and leaves the proof PENDING so the end user can try again. A deposit seen but not confirmed yet answers 200 with status PENDING and is rechecked automatically until the window closes. A screenshot answers 200 with status PENDING and waits in your review queue (GET /v3/travel-rule/wallet-proofs/). An EXPIRED or REJECTED proof never verifies later: start a new one. Signature formats verified per chain family: EVM chains take a personal_sign (EIP-191) signature, hex encoded; Bitcoin takes a BIP-137 signmessage signature (P2PKH, P2SH-P2WPKH and P2WPKH addresses) or a BIP-322 simple signature (P2WPKH and P2TR addresses), base64 encoded; Solana takes an Ed25519 signature over the challenge, base58 encoded; Tron takes a signMessageV2 (TIP-191) signature, hex encoded. A BIP-322 full or proof-of-funds signature, or a multisig address, fails with SIGNATURE_UNSUPPORTED_SCHEME.", + "operationId": "verifyTravelRuleWidgetProof", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "verify_widget_proof", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + }, + { + "name": "proof_id", + "in": "path", + "required": true, + "description": "proof_id returned when the proof was started.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "signature": { + "type": "string", + "description": "MESSAGE_SIGNING: the wallet's signature over the proof's challenge." + }, + "txid": { + "type": "string", + "description": "SATOSHI_TEST: the transaction id of the test deposit." + } + } + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "file" + ], + "properties": { + "file": { + "type": "string", + "format": "binary", + "description": "SCREENSHOT: an image showing the wallet in the end user's wallet app." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/widget/{token}/proofs/{proof_id}/verify/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"signature\": \"0x...\"}'" + } + ], + "responses": { + "200": { + "description": "VERIFIED, or still PENDING (a screenshot awaiting review, a deposit not indexed or not confirmed yet, with code DEPOSIT_NOT_FOUND or DEPOSIT_UNCONFIRMED).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofVerifyResult" + } + } + } + }, + "400": { + "description": "Missing evidence; a failed attempt that keeps the proof PENDING so the end user can try again (code SIGNATURE_INVALID, SIGNATURE_UNSUPPORTED_SCHEME or DEPOSIT_LOOKUP_FAILED); or a test deposit that rejects the proof (code DEPOSIT_MISMATCH, DEPOSIT_STALE or TXID_REUSED, status REJECTED, with the session's attempt counts): picking a method again starts a new proof while attempts_remaining is above 0.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + }, + "404": { + "description": "Unknown token, or no such proof on this session." + }, + "409": { + "description": "code OWNERSHIP_CHANGED (the proof is closed with it); a closed proof (REJECTED or EXPIRED), with its status, the code it closed with and the session's attempt counts: pick a method again to start a new proof; or a SELF_DECLARATION proof, which is verified when started.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + }, + "410": { + "description": "The session expired, or code WINDOW_EXPIRED when the satoshi test window closed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "DocumentAIDetectedCode": { + "type": "object", + "required": [ + "type", + "page_number", + "position", + "x", + "y", + "width", + "height", + "page_width", + "page_height", + "raw_payload", + "parsed_payload" + ], + "properties": { + "type": { + "type": "string", + "description": "Detected symbology, for example `QR_CODE`, `CODE_128`, `EAN_13`, `DATA_MATRIX`, or `PDF_417`." + }, + "page_number": { + "type": "integer", + "minimum": 1, + "description": "1-based page number." + }, + "position": { + "type": "array", + "nullable": true, + "description": "Detected polygon in rendered-page pixel coordinates. `null` when no reliable location is available.", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "type": "integer" + } + } + }, + "x": { + "type": "integer", + "nullable": true, + "description": "Left edge of the bounding box in rendered-page pixels." + }, + "y": { + "type": "integer", + "nullable": true, + "description": "Top edge of the bounding box in rendered-page pixels." + }, + "width": { + "type": "integer", + "nullable": true, + "description": "Bounding-box width in rendered-page pixels." + }, + "height": { + "type": "integer", + "nullable": true, + "description": "Bounding-box height in rendered-page pixels." + }, + "page_width": { + "type": "integer", + "description": "Rendered page width in pixels." + }, + "page_height": { + "type": "integer", + "description": "Rendered page height in pixels." + }, + "raw_payload": { + "type": "string", + "description": "Raw decoder payload as a base64 string when available; otherwise an empty string." + }, + "parsed_payload": { + "nullable": true, + "description": "Decoded string or structured value. `null` when the code was located but could not be decoded." + } + } + }, + "SessionImportJob": { + "type": "object", + "required": [ + "uuid", + "status", + "import_type", + "source_format", + "source_filename", + "total_rows", + "processed_rows", + "created_count", + "updated_count", + "skipped_count", + "failed_count" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Import job id. Use this value to poll progress and fetch row errors." + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED", + "PAUSED", + "CANCELLED", + "READY" + ] + }, + "import_type": { + "type": "string", + "enum": [ + "user_verification", + "business_verification", + "status_rules", + "transactions" + ] + }, + "source_format": { + "type": "string", + "enum": [ + "csv", + "ndjson" + ] + }, + "source_filename": { + "type": "string" + }, + "total_rows": { + "type": "integer", + "format": "int64" + }, + "processed_rows": { + "type": "integer", + "format": "int64" + }, + "checkpoint_row": { + "type": "integer", + "format": "int64" + }, + "created_count": { + "type": "integer", + "format": "int64" + }, + "updated_count": { + "type": "integer", + "format": "int64" + }, + "skipped_count": { + "type": "integer", + "format": "int64" + }, + "failed_count": { + "type": "integer", + "format": "int64" + }, + "total_media_bytes": { + "type": "integer", + "format": "int64" + }, + "last_error": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "started_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "state": { + "type": "string", + "enum": [ + "ready", + "queued", + "running", + "paused", + "completed", + "errors", + "failed", + "cancelled" + ], + "description": "The job's state as the console shows it; `errors` is a completed job with rejected rows." + }, + "source": { + "type": "string", + "enum": [ + "file", + "api", + "provider" + ] + }, + "provider": { + "type": "string", + "description": "Migration source; empty for a file or API import." + }, + "provider_options": { + "type": "object" + }, + "credential_hint": { + "type": "string", + "description": "The first characters of the key the migration holds. The key itself is never returned." + }, + "credentials_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resumes_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When a job paused by the provider's rate limit resumes by itself." + }, + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "is_dry_run": { + "type": "boolean" + }, + "dry_run_summary": { + "type": "object", + "description": "What a dry run found: `total`, `by_status`, `already_in_didit`, `same_person`, `not_finished`, `failed`, `unmapped_statuses`." + }, + "face_templates_enabled": { + "type": "boolean" + }, + "lawful_basis_confirmed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "not_finished_count": { + "type": "integer", + "format": "int64" + }, + "same_person_count": { + "type": "integer", + "format": "int64" + }, + "face_templates_count": { + "type": "integer", + "format": "int64" + }, + "face_duplicates_count": { + "type": "integer", + "format": "int64" + }, + "face_blocklist_hits_count": { + "type": "integer", + "format": "int64" + }, + "created_by_name": { + "type": "string", + "nullable": true + }, + "created_by_email": { + "type": "string", + "nullable": true + } + } + }, + "SessionImportError": { + "type": "object", + "required": [ + "uuid", + "row_number", + "error", + "raw_row" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "row_number": { + "type": "integer", + "format": "int64" + }, + "external_id": { + "type": "string", + "nullable": true + }, + "error": { + "type": "string" + }, + "raw_row": { + "type": "object", + "additionalProperties": true + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BrandingCustomizationUpdate": { + "type": "object", + "description": "Partial branding update body. Provide only the fields you want to change. Hex colors accept `#RRGGBB` or `#RRGGBBAA`. Custom domain and white-label email are not accepted here.", + "properties": { + "color_primary": { + "type": "string", + "description": "Headers / primary text color." + }, + "color_secondary": { + "type": "string", + "description": "Paragraph / secondary text color." + }, + "color_background": { + "type": "string", + "description": "Page background color." + }, + "color_panel": { + "type": "string", + "description": "Panel / accent color." + }, + "color_panel_10": { + "type": "string", + "description": "Panel color at 10% opacity." + }, + "color_on_panel_1": { + "type": "string", + "description": "Support text color on panels." + }, + "color_on_panel_2": { + "type": "string", + "description": "Pill / badge color on panels." + }, + "color_on_background": { + "type": "string", + "description": "Text color on background." + }, + "color_button_1": { + "type": "string", + "description": "Primary button color." + }, + "color_button_2": { + "type": "string", + "description": "Secondary button color." + }, + "color_button_text_1": { + "type": "string", + "description": "Primary button text color." + }, + "color_button_text_2": { + "type": "string", + "description": "Secondary button text color." + }, + "color_pill_text": { + "type": "string", + "description": "Text color for pills, inputs and selectors." + }, + "border_radius_panel": { + "type": "integer", + "minimum": 0, + "description": "Panel corner radius in pixels." + }, + "border_radius_buttons": { + "type": "integer", + "minimum": 0, + "description": "Button corner radius in pixels." + }, + "font_family": { + "type": "string", + "description": "Google Font family name (e.g. `Inter`, `AR One Sans`)." + }, + "font_weight": { + "type": "string", + "description": "Font weight available for the selected family (e.g. `regular`, `500`, `700`)." + }, + "app_public_name": { + "type": "string", + "nullable": true, + "description": "Public name shown in the verification flow." + }, + "privacy_policy_url": { + "type": "string", + "nullable": true, + "description": "Privacy policy URL shown to the end user." + }, + "skip_welcome_screen": { + "type": "boolean", + "description": "Skip the initial welcome screen." + }, + "disable_login_with_didit": { + "type": "boolean", + "description": "Continue as guest instead of showing the login-with-Didit screen. Cannot be set back to `false` while a custom domain is configured (400)." + }, + "hide_progress_bar": { + "type": "boolean", + "description": "Hide the progress bar during the flow." + }, + "callback_seconds": { + "type": "integer", + "minimum": 0, + "description": "Delay (seconds) before redirecting to the callback URL." + }, + "delete_logo_square": { + "type": "boolean", + "description": "When `true`, removes the current square logo." + }, + "delete_logo_rectangular": { + "type": "boolean", + "description": "When `true`, removes the current rectangular logo." + }, + "delete_logo_rectangular_dark": { + "type": "boolean", + "description": "When `true`, removes the current dark-mode logo, and `logo_rectangular_dark` then reads `null`. If the same multipart request also uploads `image_rectangular_dark`, the upload wins." + }, + "delete_favicon": { + "type": "boolean", + "description": "When `true`, removes the current favicon." + } + } + }, + "BrandingCustomization": { + "type": "object", + "description": "White-label branding for an application: colors, fonts, radii, logo URLs and UI/UX options. Logo URLs are read-only (set them by uploading images). Custom domain and white-label email are not included.", + "properties": { + "color_primary": { + "type": "string" + }, + "color_secondary": { + "type": "string" + }, + "color_background": { + "type": "string" + }, + "color_panel": { + "type": "string" + }, + "color_panel_10": { + "type": "string" + }, + "color_on_panel_1": { + "type": "string" + }, + "color_on_panel_2": { + "type": "string" + }, + "color_on_background": { + "type": "string" + }, + "color_button_1": { + "type": "string" + }, + "color_button_2": { + "type": "string" + }, + "color_button_text_1": { + "type": "string" + }, + "color_button_text_2": { + "type": "string" + }, + "color_pill_text": { + "type": "string" + }, + "border_radius_panel": { + "type": "integer" + }, + "border_radius_buttons": { + "type": "integer" + }, + "font_family": { + "type": "string" + }, + "font_weight": { + "type": "string" + }, + "font_url": { + "type": "string", + "nullable": true, + "description": "Read-only. Resolved Google Fonts stylesheet URL for the selected family." + }, + "logo_square": { + "type": "string", + "nullable": true, + "description": "Read-only URL of the square logo. Upload via `image_square`." + }, + "logo_rectangular": { + "type": "string", + "nullable": true, + "description": "Read-only URL of the rectangular logo. Upload via `image_rectangular`." + }, + "logo_rectangular_dark": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Read-only. Temporary signed URL of the rectangular logo for dark mode, shown on a dark background. `null` when no dark logo is set or rectangular logos are not shown for your account. Upload it with `image_rectangular_dark`." + }, + "favicon": { + "type": "string", + "nullable": true, + "description": "Read-only URL of the favicon. Upload via `image_favicon`." + }, + "app_public_name": { + "type": "string", + "nullable": true + }, + "privacy_policy_url": { + "type": "string", + "nullable": true + }, + "skip_welcome_screen": { + "type": "boolean" + }, + "disable_login_with_didit": { + "type": "boolean" + }, + "hide_progress_bar": { + "type": "boolean" + }, + "callback_seconds": { + "type": "integer" + } + } + }, + "WebhookEvent": { + "type": "string", + "description": "Supported webhook event name. Use these exact strings in `subscribed_events`; unsupported values are rejected.", + "enum": [ + "status.updated", + "data.updated", + "user.status.updated", + "user.data.updated", + "business.status.updated", + "business.data.updated", + "activity.created", + "transaction.created", + "transaction.status.updated", + "travel_rule.status.updated", + "workflow.compliance_changed", + "auth.signup", + "auth.login", + "auth.login_failed", + "auth.password_reset_requested", + "auth.password_reset_completed", + "auth.mfa_changed", + "auth.new_device", + "auth.account_locked", + "investigator.report.ready", + "investigator.decision.automated", + "investigator.decision.overridden", + "investigator.decision.contested", + "investigator.question.raised", + "investigator.automation.paused", + "investigator.rfi.answered", + "investigator.rfi.overdue", + "investigator.drift.detected", + "investigator.sla.breached", + "investigator.qa.failed", + "investigator.external.report.ready" + ] + }, + "WebhookSubscribedEvents": { + "type": "array", + "description": "Event filter for this webhook destination. Didit delivers only webhooks whose event type exactly matches one of these values — there is no wildcard subscription. When sent, the list must contain at least one valid event (an explicit `[]` is rejected with 400); a destination whose list is empty (field omitted at create) receives nothing. On update the list is replaced wholesale, never merged.", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "example": [ + "status.updated", + "data.updated" + ] + }, + "PhoneVerificationSendResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the verification. A `Retry` send returns the same `request_id` as the original send. This id appears in the Business Console, is returned again by a finalized `POST /v3/phone/check/`, and can be passed to `GET /v3/session/{sessionId}/decision/`." + }, + "status": { + "type": "string", + "enum": [ + "Success", + "Retry", + "Blocked" + ], + "description": "`Success` — OTP sent to a new verification. `Retry` — OTP re-sent for the pending verification created by a previous send. `Blocked` — the anti-fraud layer refused to send; the verification is immediately finalized as `Declined` and nothing is billed." + }, + "reason": { + "type": "string", + "nullable": true, + "description": "Why a send was `Blocked` (e.g., `repeated_attempts`, `suspicious`, `spam`). `null` on `Success` and `Retry`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` stored on the session (from the first send)." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` stored on the session (from the first send)." + } + } + }, + "PhoneVerificationCheckResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "On `Approved`/`Declined`: the session id of the matched verification — identical to the `request_id` returned by `POST /v3/phone/send/`. On `Failed` and `Expired or Not Found`: a random one-off UUID that cannot be looked up later." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "Failed", + "Expired or Not Found" + ], + "description": "`Approved` — correct code, no declining risk. `Declined` — terminal: a declining risk matched or the attempt budget (3) was exhausted. `Failed` — wrong code, attempts remaining. `Expired or Not Found` — no pending verification for this number in the last 5 minutes." + }, + "message": { + "type": "string", + "description": "Human-readable explanation of the outcome, including the number of attempts remaining after a wrong code." + }, + "phone": { + "type": "object", + "nullable": true, + "description": "Full phone report. Present (non-null) only on finalized outcomes (`Approved`/`Declined`); `null` on `Failed` and absent on `Expired or Not Found`.", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Not Finished", + "Expired" + ], + "description": "Final status of the phone verification. Matches the top-level `status` for finalized checks." + }, + "phone_number_prefix": { + "type": "string", + "description": "Country calling code with leading `+` (e.g., `+1`).", + "example": "+1" + }, + "phone_number": { + "type": "string", + "description": "National number without the country calling code.", + "example": "4155552671" + }, + "full_number": { + "type": "string", + "description": "Complete number in E.164 format.", + "example": "+14155552671" + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of the number.", + "example": "US" + }, + "country_name": { + "type": "string", + "description": "Full country name derived from `country_code`.", + "example": "United States" + }, + "carrier": { + "type": "object", + "description": "Current network serving the number, from the carrier lookup run at finalization.", + "properties": { + "name": { + "type": "string", + "description": "Carrier name. Empty string when the lookup could not identify it." + }, + "type": { + "type": "string", + "description": "Line type reported by the lookup: `mobile`, `fixed_line`, `voip`, `isp`, `vpn`, `toll_free`, `premium_rate`, `pager`, `payphone`, `satellite`, `service`, `shared_cost`, `short_codes_commercial`, `calling_cards`, `local_rate`, `universal_access`, `voice_mail`, `other`, or `unknown`." + } + } + }, + "is_disposable": { + "type": "boolean", + "description": "`true` when the number belongs to a temporary/burner number service." + }, + "is_virtual": { + "type": "boolean", + "description": "`true` when the line type is virtual (`voip`, `isp`, or `vpn`)." + }, + "verification_method": { + "type": "string", + "description": "Channel that actually delivered the OTP once delivery is confirmed (e.g., `whatsapp`, `sms`); until then, the requested channel." + }, + "verification_attempts": { + "type": "integer", + "description": "Number of OTP sends for this verification (1, or 2 after a retry)." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the correct code was entered. `null` if the verification was never approved." + }, + "warnings": { + "type": "array", + "description": "Risk warnings recorded during the verification (duplicate, disposable, VoIP, attempts exceeded, blocklist, etc.). Empty array when there are none.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature that produced the warning. Always `PHONE` here." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk code." + }, + "additional_data": { + "type": "object", + "nullable": true, + "description": "Risk-specific context (e.g., the duplicated session id for duplicate risks). `null` when the risk carries no extra data." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` risks decline the verification, `warning` flags it for review, `information` is recorded without affecting the status." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "lifecycle": { + "type": "array", + "description": "Chronological audit trail of the verification. Event `type` is one of `PHONE_VERIFICATION_MESSAGE_SENT`, `PHONE_VERIFICATION_RETRY_MESSAGE_SENT`, `PHONE_VERIFICATION_BLOCKED`, `PHONE_DELIVERY_DELIVERED`, `PHONE_DELIVERY_UNDELIVERABLE`, `VALID_CODE_ENTERED`, `INVALID_CODE_ENTERED`, `PHONE_VERIFICATION_APPROVED`, `PHONE_VERIFICATION_DECLINED`, `PHONE_VERIFICATION_IN_REVIEW`, `PHONE_VERIFICATION_EXPIRED`.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Event type." + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the event happened." + }, + "details": { + "type": "object", + "nullable": true, + "description": "Event-specific payload. Send events: `{status, reason, channel, actual_channel}` (`actual_channel` is filled once delivery is confirmed). Delivery events: `{channel, status}`. Code-entry events: `{code_tried, status}`. Final-status events: `null`, or `{reason}` when declined or in review." + }, + "fee": { + "type": "number", + "description": "USD amount billed for this event. Non-zero only on send events (the delivery price); 0 for code entries, delivery confirmations, and status events." + } + } + } + }, + "matches": { + "type": "array", + "description": "Other sessions of your application (KYC, KYB, or API) where the same phone number was used by a different user (different `vendor_data`). Up to 5 entries, a synthetic list-entry blocklist match first when present (session-derived matches keep query order). Empty array when there are no matches.", + "items": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the other verification that used this phone number." + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of that session." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of that session." + }, + "verification_date": { + "type": "string", + "description": "When that session was created (`YYYY-MM-DDTHH:MM:SSZ`)." + }, + "phone_number": { + "type": "string", + "description": "The matched phone number." + }, + "status": { + "type": "string", + "description": "Status of that session (e.g., `Approved`, `Declined`, `In Progress`)." + }, + "is_blocklisted": { + "type": "boolean", + "description": "Whether the phone number is on your application's blocklist." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "For API-type sessions, the standalone API that created it (e.g., `PHONE_VERIFICATION`); `null` for regular verification sessions." + }, + "source": { + "type": "string", + "description": "Where the match was found. Usually `session`; a blocklist hit via an organization list entry prepends a synthetic match with `source: \"list_entry\"` and null `session_id`/`session_number`." + } + } + } + }, + "phone_social": { + "$ref": "#/components/schemas/PhoneSocialFootprint" + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of the matched verification's session. `null` on `Expired or Not Found`." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "`metadata` of the matched verification's session. `null` on `Expired or Not Found`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this check response." + } + } + }, + "EmailVerificationSendResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the verification. A `Retry` send returns the same `request_id` as the original send. This id appears in the Business Console, is returned again by a finalized `POST /v3/email/check/`, and can be passed to `GET /v3/session/{sessionId}/decision/`." + }, + "status": { + "type": "string", + "enum": [ + "Success", + "Retry", + "Undeliverable" + ], + "description": "`Success` — OTP emailed to a new verification (billed). `Retry` — fresh OTP emailed for the pending verification created by a previous send (free). `Undeliverable` — the address failed deliverability validation or the message could not be sent; the verification is immediately finalized as `Declined` and nothing is billed." + }, + "reason": { + "type": "string", + "nullable": true, + "enum": [ + "email_can_not_be_delivered", + null + ], + "description": "`email_can_not_be_delivered` when `status` is `Undeliverable`; `null` otherwise." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` stored on the session (from the first send)." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` stored on the session (from the first send)." + } + } + }, + "EmailVerificationCheckResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "On `Approved`/`Declined`: the session id of the matched verification — identical to the `request_id` returned by `POST /v3/email/send/`. On `Failed` and `Expired or Not Found`: a random one-off UUID that cannot be looked up later." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "Failed", + "Expired or Not Found" + ], + "description": "`Approved` — correct code, no declining risk. `Declined` — terminal: a declining risk matched or the attempt budget (3) was exhausted. `Failed` — wrong code, attempts remaining. `Expired or Not Found` — no pending verification for this address in the last 5 minutes." + }, + "message": { + "type": "string", + "description": "Human-readable explanation of the outcome, including the number of attempts remaining after a wrong code." + }, + "email": { + "type": "object", + "nullable": true, + "description": "Full email report. Present (non-null) only on finalized outcomes (`Approved`/`Declined`); `null` on `Failed` and absent on `Expired or Not Found`.", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Not Finished", + "Expired" + ], + "description": "Final status of the email verification. Matches the top-level `status` for finalized checks." + }, + "email": { + "type": "string", + "format": "email", + "description": "The verified email address.", + "example": "alice@example.com" + }, + "is_breached": { + "type": "boolean", + "description": "`true` when the address appears in known data breaches (breach intelligence lookup run at finalization)." + }, + "breaches": { + "type": "array", + "description": "Up to 5 most recent known breaches containing this address. Empty array (or omitted) when `is_breached` is `false`.", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Breach identifier." + }, + "domain": { + "type": "string", + "description": "Domain of the breached service." + }, + "breach_date": { + "type": "string", + "format": "date", + "description": "Date the breach occurred." + }, + "breach_emails_count": { + "type": "integer", + "description": "Total number of accounts exposed in the breach." + }, + "description": { + "type": "string", + "description": "Human-readable description of the breach (may contain HTML markup)." + }, + "logo_path": { + "type": "string", + "description": "URL of the breached service's logo." + }, + "data_classes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Categories of data exposed, in snake_case (e.g., `email_addresses`, `passwords`)." + }, + "is_verified": { + "type": "boolean", + "description": "Whether the breach has been verified as legitimate." + } + } + } + }, + "is_disposable": { + "type": "boolean", + "description": "`true` when the domain belongs to a known disposable/temporary-mail provider." + }, + "is_undeliverable": { + "type": "boolean", + "description": "`true` when the address cannot receive mail (failed syntax or DNS/MX validation, or the message bounced)." + }, + "verification_attempts": { + "type": "integer", + "description": "Number of OTP sends for this verification (1, or 2 after a retry)." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the correct code was entered. `null` if the verification was never approved." + }, + "warnings": { + "type": "array", + "description": "Risk warnings recorded during the verification (duplicate, breached, disposable, undeliverable, attempts exceeded, blocklist, etc.). Empty array when there are none.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature that produced the warning. Always `EMAIL` here." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk code." + }, + "additional_data": { + "type": "object", + "nullable": true, + "description": "Risk-specific context (e.g., the duplicated session id for duplicate risks). `null` when the risk carries no extra data." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` risks decline the verification, `warning` flags it for review, `information` is recorded without affecting the status." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "lifecycle": { + "type": "array", + "description": "Chronological audit trail of the verification. Event `type` is one of `EMAIL_VERIFICATION_MESSAGE_SENT`, `EMAIL_VERIFICATION_RETRY_MESSAGE_SENT`, `VALID_CODE_ENTERED`, `INVALID_CODE_ENTERED`, `EMAIL_VERIFICATION_APPROVED`, `EMAIL_VERIFICATION_DECLINED`, `EMAIL_VERIFICATION_IN_REVIEW`, `EMAIL_VERIFICATION_EXPIRED`.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Event type." + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the event happened." + }, + "details": { + "type": "object", + "nullable": true, + "description": "Event-specific payload. Send events: `{status, reason}`. Code-entry events: `{code_tried, status}`. Final-status events: `null`, or `{reason}` when declined or in review." + }, + "fee": { + "type": "number", + "description": "USD amount billed for this event. Non-zero only on the first successful send; 0 for retries, code entries, and status events." + } + } + } + }, + "matches": { + "type": "array", + "description": "Other sessions of your application (KYC, KYB, or API) where the same email address was used by a different user (different `vendor_data`). Up to 5 entries; a synthetic list-entry blocklist match is prepended when present (and only when no session-derived blocklisted email exists) — session-derived matches keep created_at order. Empty array when there are no matches.", + "items": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the other verification that used this email address." + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of that session." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of that session." + }, + "verification_date": { + "type": "string", + "description": "When that session was created (`YYYY-MM-DDTHH:MM:SSZ`)." + }, + "email": { + "type": "string", + "description": "The matched email address." + }, + "status": { + "type": "string", + "description": "Status of that session (e.g., `Approved`, `Declined`, `In Progress`)." + }, + "is_blocklisted": { + "type": "boolean", + "description": "Whether the email address is on your application's blocklist." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "For API-type sessions, the standalone API that created it (e.g., `PHONE_VERIFICATION`); `null` for regular verification sessions." + }, + "source": { + "type": "string", + "description": "Where the match was found. Usually `session`; a blocklist hit via an organization list entry prepends a synthetic match with `source: \"list_entry\"` and null `session_id`/`session_number`." + } + } + } + }, + "email_social": { + "$ref": "#/components/schemas/EmailSocialFootprint" + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of the matched verification's session. `null` on `Expired or Not Found`." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "`metadata` of the matched verification's session. `null` on `Expired or Not Found`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this check response." + } + } + }, + "WorkflowListItem": { + "type": "object", + "description": "One workflow version in the list view.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Per-version UUID. Use as `{settings_uuid}` for get/update/delete, and as `workflow_id` when creating sessions." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable group ID shared by every version of the same workflow. Equals `uuid` for version 1." + }, + "workflow_label": { + "type": "string", + "description": "Display name for the workflow." + }, + "workflow_type": { + "type": "string", + "nullable": true, + "description": "Base type for simple workflows: `kyc`, `kyb`, `adaptive_age_verification`, `biometric_authentication`, etc. `null` for graph-based workflows without a KYB feature." + }, + "is_default": { + "type": "boolean", + "description": "Whether this is the default workflow for new sessions." + }, + "is_archived": { + "type": "boolean" + }, + "is_white_label_enabled": { + "type": "boolean", + "description": "Whether white-label customization applies to sessions created with this workflow." + }, + "total_price": { + "type": "number", + "description": "Fixed price per verification in USD (JSON number, excludes variable-priced features such as DATABASE_VALIDATION and PHONE_VERIFICATION)." + }, + "min_price": { + "type": "number", + "description": "Cheapest fixed price across workflow-graph paths, in USD." + }, + "max_price": { + "type": "number", + "description": "Most expensive fixed price across workflow-graph paths, in USD." + }, + "features": { + "type": "string", + "description": "Enabled features as a single `\" + \"`-joined string, e.g. `\"OCR + LIVENESS + FACE_MATCH\"`. Split on `\" + \"` to get a list." + }, + "is_simple_workflow": { + "type": "boolean", + "description": "True when the workflow has no custom workflow graph." + }, + "is_editable": { + "type": "boolean", + "description": "True when this version is a `draft` (drafts are editable in place; published versions are immutable in the Console)." + }, + "workflow_url": { + "type": "string", + "format": "uri", + "description": "Public shareable verification URL for this workflow (`https://verify.didit.me/u/`)." + }, + "max_retry_attempts": { + "type": "integer", + "description": "Maximum retry attempts allowed after a declined verification (0-10)." + }, + "retry_window_days": { + "type": "integer", + "nullable": true, + "description": "Rolling window in days used to count retries (1-365). `null` = all-time limit." + }, + "session_expiration_time": { + "type": "integer", + "description": "Seconds before an unfinished session expires (3600-2419200)." + }, + "version": { + "type": "integer", + "description": "Version number within the `workflow_id` group, starting at 1." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Version status. New sessions use the latest `published` version of a group." + }, + "has_draft": { + "type": "boolean", + "description": "True when any version in this `workflow_id` group is currently a draft." + }, + "created_by": { + "type": "object", + "nullable": true, + "description": "Who created this version: `{name, email, type}` where `type` is `user` (Console user, `email` set) or `api_key` (`name` is the client id, `email` is null). `null` when no creator was recorded.", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string", + "nullable": true + }, + "type": { + "type": "string", + "enum": [ + "user", + "api_key" + ] + } + } + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "SimpleWorkflowRequest": { + "type": "object", + "description": "Create a simple linear workflow. The API converts this feature list into a node-based workflow graph internally. STRICT field whitelist: any key not listed here (including `workflow_type`) is rejected with a 400.", + "required": [ + "features" + ], + "properties": { + "workflow_label": { + "type": "string", + "maxLength": 50, + "default": "ID Verification", + "description": "Display name for the workflow (max 50 characters). Defaults to `\"ID Verification\"` when omitted — always set it so workflows stay distinguishable.", + "example": "Standard KYC" + }, + "is_default": { + "type": "boolean", + "description": "Set this workflow as the default for new sessions." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Omit this field to create a published workflow ready for sessions. Use `draft` only when you want to save without publishing." + }, + "features": { + "type": "array", + "minItems": 1, + "description": "Verification features in execution order. The API links each item to the next one and adds one final status node automatically.", + "items": { + "$ref": "#/components/schemas/SimpleWorkflowFeature" + } + }, + "is_white_label_enabled": { + "type": "boolean", + "description": "Enable white-label customization for sessions created with this workflow.", + "default": false + }, + "is_desktop_allowed": { + "type": "boolean", + "description": "Allow the verification flow to run on desktop browsers.", + "default": false + }, + "max_retry_attempts": { + "type": "integer", + "minimum": 0, + "maximum": 10, + "default": 3, + "description": "Maximum retry attempts allowed after a declined verification. `0` blocks the user after the first decline." + }, + "retry_window_days": { + "type": "integer", + "minimum": 1, + "maximum": 365, + "nullable": true, + "default": 7, + "description": "Rolling window in days used to count retries. `null` enforces an all-time limit." + }, + "face_liveness_max_attempts": { + "type": "integer", + "minimum": 1, + "maximum": 3, + "default": 3, + "description": "Maximum liveness submissions per session. Default 3: one initial attempt plus two retries. This value is also copied into generated liveness and age-estimation workflow nodes unless the feature-level config overrides it." + }, + "face_match_max_attempts": { + "type": "integer", + "minimum": 1, + "maximum": 3, + "default": 3, + "description": "Maximum face-match submissions per session. Default 3: one initial attempt plus two retries. This value is also copied into generated face-match workflow nodes unless the feature-level config overrides it." + }, + "session_expiration_time": { + "type": "integer", + "minimum": 3600, + "maximum": 2419200, + "default": 604800, + "description": "Seconds before an unfinished session expires. Minimum 1 hour (3600), maximum 4 weeks (2419200)." + } + } + }, + "SimpleWorkflowUpdateRequest": { + "type": "object", + "description": "Update workflow metadata or replace the full linear feature list. STRICT field whitelist: any key not listed here is rejected with a 400.", + "properties": { + "workflow_label": { + "type": "string", + "maxLength": 50, + "description": "Display name for the workflow (max 50 characters)." + }, + "is_default": { + "type": "boolean", + "description": "Set this workflow as the default for new sessions." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Controls versioning. Omit to update the current version in place (drafts auto-publish). `published` on an already-published version creates and returns a NEW version. `draft` is only valid while the version is still a draft." + }, + "features": { + "type": "array", + "minItems": 1, + "description": "If provided, this replaces the full feature order and configuration.", + "items": { + "$ref": "#/components/schemas/SimpleWorkflowFeature" + } + }, + "is_white_label_enabled": { + "type": "boolean", + "description": "Enable white-label customization for sessions created with this workflow." + }, + "is_desktop_allowed": { + "type": "boolean", + "description": "Allow the verification flow to run on desktop browsers." + }, + "max_retry_attempts": { + "type": "integer", + "minimum": 0, + "maximum": 10, + "description": "Maximum retry attempts allowed after a declined verification. `0` blocks the user after the first decline." + }, + "retry_window_days": { + "type": "integer", + "minimum": 1, + "maximum": 365, + "nullable": true, + "description": "Rolling window in days used to count retries. `null` enforces an all-time limit." + }, + "face_liveness_max_attempts": { + "type": "integer", + "minimum": 1, + "maximum": 3, + "description": "Maximum liveness submissions per session. This value is also copied into generated liveness and age-estimation workflow nodes unless the feature-level config overrides it." + }, + "face_match_max_attempts": { + "type": "integer", + "minimum": 1, + "maximum": 3, + "description": "Maximum face-match submissions per session. This value is also copied into generated face-match workflow nodes unless the feature-level config overrides it." + }, + "session_expiration_time": { + "type": "integer", + "minimum": 3600, + "maximum": 2419200, + "description": "Seconds before an unfinished session expires. Minimum 1 hour (3600), maximum 4 weeks (2419200)." + } + } + }, + "SimpleWorkflowFeature": { + "type": "object", + "required": [ + "feature" + ], + "properties": { + "feature": { + "type": "string", + "description": "Feature to run. Use the uppercase values shown here.", + "enum": [ + "OCR", + "NFC", + "LIVENESS", + "FACE_MATCH", + "PROOF_OF_ADDRESS", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "DATABASE_VALIDATION", + "AML", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "SIGNATURE" + ] + }, + "config": { + "type": "object", + "description": "Feature-specific settings. For `QUESTIONNAIRE`, include `questionnaire_uuid` with the `questionnaire_id` returned by the questionnaire create endpoint. For `OCR`, `methods` selects which ID Verification methods each country may use (document capture, non-doc lookup against a government or other authoritative source, and digital identity wallets); omit it and every country is document capture only. For `SIGNATURE`, see [Electronic Signature settings](/management-api/workflows/feature-configs#electronic-signature): `signature_level` accepts `aes` only for now, and workflow documents and their fields are managed in the console, so through the API send documents and fields per session with `signature_documents_source: \"session\"`.", + "additionalProperties": true + }, + "label": { + "type": "string", + "description": "Optional internal label for this workflow node." + } + } + }, + "ReviewItem": { + "type": "object", + "description": "One entry in a session's review / activity feed. Status changes carry `previous_status`, `new_status`, `previous_value`, `new_value`, and `changed_fields`; comments carry `comment` and (for Console comments) `mentioned_emails`; the remaining fields are `null` or empty when they do not apply to the activity type.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of this feed entry.", + "example": "de0561bc-5583-4409-9171-1a31a4c8a5f4" + }, + "activity_type": { + "type": "string", + "enum": [ + "COMMENT", + "STATUS_UPDATED", + "KYC_DATA_UPDATED", + "POA_DATA_UPDATED", + "FILE_ADDED", + "FILE_REMOVED", + "AML_ONGOING_MATCH", + "AML_STATUS_UPDATED", + "AML_HIT_STATUS_UPDATED", + "KYB_DATA_UPDATED", + "KYB_DOCUMENT_UPLOADED", + "TAG_ADDED", + "TAG_REMOVED", + "FEATURE_OVERRIDE_PENDING", + "FEATURE_OVERRIDE_APPLIED", + "FEATURE_OVERRIDE_CANCELLED" + ], + "description": "What kind of activity this entry records. `STATUS_UPDATED` rows come from manual decisions (`PATCH /v3/session/{session_id}/update-status/`) and from entries added via `POST …/reviews/` (which default to this type even when no status is recorded). `COMMENT` rows come from Console reviewers; Console detail-page views are also stored as `COMMENT` rows flagged with `metadata.system_event = \"DETAIL_VIEWED\"`. The remaining types track data edits, file changes, tags, and AML monitoring activity. STATUS_UPDATED rows are also written by system actors (e.g. AML ongoing monitoring with actor_type SYSTEM, and automatic feature-status updates), not only by the update-status endpoint and manual review posts. `FEATURE_OVERRIDE_PENDING`, `FEATURE_OVERRIDE_APPLIED` and `FEATURE_OVERRIDE_CANCELLED` record an override of one check's status (`PATCH /v3/session/{session_id}/features/{node_id}/update-status/`): requested to wait for the flow to finish, applied, or cancelled. They carry the check's status in `previous_value` / `new_value` (`{\"status\": ...}`), leave `previous_status` and `new_status` `null`, and name the override in `metadata` (`feature_override_id`, `node_id`, `feature_model`, `forced`, `state`, `cancel_reason`). The entry for the request carries the reviewer's `comment`; an override applied when the flow finishes is recorded with `actor_type` `SYSTEM`.", + "example": "STATUS_UPDATED" + }, + "actor_display": { + "type": "string", + "description": "Human-readable author label, resolved in order: actor's full name → Console account identifier → local part of `actor_email` → `\"API Client\"` (for `API_KEY` actors) → `\"System\"` (for `SYSTEM` actors) → `\"Unknown\"`. Entries created via `POST …/reviews/` always display `\"Unknown\"` because that endpoint records no actor attribution.", + "example": "API Client" + }, + "actor_email": { + "type": "string", + "nullable": true, + "description": "Email of the Console user who performed the action. `null` for API-key and system actors.", + "example": null + }, + "actor_type": { + "type": "string", + "enum": [ + "API_KEY", + "CONSOLE_USER", + "SYSTEM" + ], + "description": "Who performed the action: an API key, a Console user, or Didit itself (`SYSTEM`, e.g. AML ongoing monitoring). Entries created via `POST …/reviews/` carry the default `CONSOLE_USER` regardless of the caller.", + "example": "API_KEY" + }, + "new_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Session status recorded by this entry (for status updates). `null` for comments and data-change entries.", + "example": "Approved" + }, + "previous_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Status the session had before the change. Populated on `STATUS_UPDATED` entries written by the update-status endpoint; `null` on entries added via `POST …/reviews/`. Also populated on system-written STATUS_UPDATED rows.", + "example": "In Review" + }, + "comment": { + "type": "string", + "nullable": true, + "description": "Free-text note attached to the entry, or `null`.", + "example": "Document re-checked manually; address matches." + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + }, + "description": "Console users `@email`-mentioned in the comment. Always empty for entries created via this API — mention processing is a Console-only feature.", + "example": [] + }, + "previous_value": { + "type": "object", + "nullable": true, + "description": "Snapshot of the changed data before the action, e.g. `{\"status\": \"In Review\"}` on a status change. `null` when nothing was changed.", + "example": { + "status": "In Review" + } + }, + "new_value": { + "type": "object", + "nullable": true, + "description": "Snapshot of the changed data after the action, e.g. `{\"status\": \"Approved\"}`. `null` when nothing was changed.", + "example": { + "status": "Approved" + } + }, + "changed_fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the fields modified by this activity (e.g. `[\"status\"]`). Empty for comments and for entries added via `POST …/reviews/`.", + "example": [ + "status" + ] + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Extra context for system-generated entries. Console detail-page views carry `{\"system_event\": \"DETAIL_VIEWED\", \"detail_type\": \"verification\"}` so they can be filtered out of notification logic. `null` for ordinary entries.", + "example": null + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the entry was written, ISO 8601 UTC.", + "example": "2026-06-12T00:19:31.918735Z" + } + } + }, + "QuestionnaireListItem": { + "type": "object", + "description": "One questionnaire version in the list view.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Per-version UUID. Use as `{questionnaire_uuid}` for get/update/delete and as `questionnaire_uuid` in a workflow's QUESTIONNAIRE feature config." + }, + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, + "default_language": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "is_simple_questionnaire": { + "type": "boolean", + "description": "True for linear questionnaires created through this API; false for graph-based questionnaires built in the Console." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "question_types": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Uppercase element types of the answerable questions, in creation order (e.g. `SHORT_TEXT`, `MULTIPLE_CHOICE`, `FILE_UPLOAD`). Structural `SECTION_HEADER` and `SEPARATOR` elements are excluded. One entry per question, so values can repeat." + }, + "workflow_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of workflows using this questionnaire. Currently always `[]` on this endpoint (populated only in the Console listing)." + }, + "questionnaire_group_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier that groups all versions of the same questionnaire." + }, + "version": { + "type": "integer", + "description": "Version number within the group, starting at 1." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ] + }, + "published_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this version was published. `null` for drafts." + }, + "is_editable": { + "type": "boolean", + "description": "True when this version is a `draft`." + } + } + }, + "SimpleQuestionnaireRequest": { + "type": "object", + "description": "Create a simple linear questionnaire. The API converts `form_elements` into the internal questionnaire graph.", + "required": [ + "title", + "languages", + "form_elements" + ], + "properties": { + "title": { + "type": "string", + "description": "Internal name for the questionnaire." + }, + "description": { + "type": "string", + "nullable": true + }, + "languages": { + "type": "array", + "description": "Languages included in every label. Must include `en`.", + "items": { + "type": "string" + }, + "example": [ + "en" + ] + }, + "default_language": { + "type": "string", + "description": "Default language. Must be included in `languages`.", + "example": "en" + }, + "is_active": { + "type": "boolean", + "default": true + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Omit this field to create a published questionnaire ready for workflows." + }, + "form_elements": { + "type": "array", + "minItems": 1, + "description": "Questions in the exact order users should see them. Branching and conditional fields are not supported.", + "items": { + "$ref": "#/components/schemas/SimpleQuestionnaireElement" + } + } + } + }, + "SimpleQuestionnaireUpdateRequest": { + "type": "object", + "description": "Update questionnaire metadata or replace the full linear `form_elements` list.", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, + "default_language": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ] + }, + "form_elements": { + "type": "array", + "minItems": 1, + "description": "If provided, this replaces the full linear question list.", + "items": { + "$ref": "#/components/schemas/SimpleQuestionnaireElement" + } + } + } + }, + "SimpleQuestionnaireElement": { + "type": "object", + "required": [ + "element_type", + "label" + ], + "properties": { + "id": { + "type": "string", + "description": "Optional stable question id. If omitted, the API generates `q1`, `q2`, etc. Use lowercase letters, numbers, and underscores for readability.", + "example": "source_of_funds" + }, + "element_type": { + "type": "string", + "description": "Question type. The API accepts these lowercase values and their uppercase equivalents.", + "enum": [ + "short_text", + "long_text", + "dropdown", + "single_choice", + "multiple_choice", + "number", + "image", + "file_upload", + "time", + "email", + "address", + "phone", + "country", + "date_picker", + "consent", + "section_header", + "separator", + "heading", + "paragraph" + ] + }, + "label": { + "type": "object", + "description": "Question label by language code, for example `{ \"en\": \"Source of funds\" }`.", + "additionalProperties": { + "type": "string" + } + }, + "description": { + "type": "object", + "nullable": true, + "additionalProperties": { + "type": "string" + } + }, + "placeholder": { + "type": "object", + "nullable": true, + "additionalProperties": { + "type": "string" + } + }, + "is_required": { + "type": "boolean", + "default": false + }, + "options": { + "type": "array", + "description": "Use for `dropdown`, `single_choice`, and `multiple_choice`. If `value` is omitted, the API generates one from the option label.", + "items": { + "type": "object", + "required": [ + "label" + ], + "properties": { + "value": { + "type": "string" + }, + "label": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "max_files": { + "type": "integer", + "minimum": 1, + "maximum": 5, + "description": "Use with `file_upload` or `image`." + }, + "validation": { + "type": "object", + "nullable": true, + "description": "Optional format validation for `short_text`, `long_text`, or `number`. Enforced while the user fills the field (they cannot proceed until it passes) and re-checked by the API on submission.", + "properties": { + "pattern": { + "type": "string", + "description": "Regular expression (ECMA-262 subset) the value must fully match. For `short_text` and `long_text`. Do not add ^/$ anchors; the whole value is matched." + }, + "min_length": { + "type": "integer", + "minimum": 0, + "description": "Minimum number of characters. For `short_text` and `long_text`." + }, + "max_length": { + "type": "integer", + "minimum": 0, + "description": "Maximum number of characters. For `short_text` and `long_text`." + }, + "min": { + "type": "number", + "description": "Minimum numeric value. For `number`." + }, + "max": { + "type": "number", + "description": "Maximum numeric value. For `number`." + }, + "error_message": { + "type": "object", + "description": "Translated message shown when validation fails, keyed by language code.", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "QuestionnaireDetail": { + "type": "object", + "description": "Full questionnaire detail returned from GET, POST (create) and PATCH (update) endpoints.", + "properties": { + "questionnaire_id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of the questionnaire. Use this id when referencing the questionnaire from a workflow or in subsequent update/delete requests." + }, + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, + "default_language": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "is_simple_questionnaire": { + "type": "boolean" + }, + "graph": { + "type": "object", + "description": "Graph structure with start_node and nodes map." + }, + "sections": { + "type": "array", + "description": "Questionnaire content grouped into sections (derived from the graph).", + "items": { + "type": "object" + } + }, + "questionnaire_group_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier that groups all versions of the same questionnaire." + }, + "version": { + "type": "integer" + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ] + }, + "published_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "is_editable": { + "type": "boolean", + "description": "True when the questionnaire can still be edited in place (draft versions)." + } + } + }, + "UserListItem": { + "type": "object", + "description": "A verified user.", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit's stable internal UUID for this user." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your unique identifier for this user (passed when creating sessions). This can be null when no vendor identifier was supplied." + }, + "display_name": { + "type": "string", + "nullable": true, + "description": "Custom display name set by you" + }, + "full_name": { + "type": "string", + "nullable": true, + "description": "Full name extracted from verified documents" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "effective_name": { + "type": "string", + "nullable": true, + "description": "Best available name: display_name if set, otherwise full_name" + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "Lifecycle status of the user record (NOT a session status). `ACTIVE` is the default, `FLAGGED` marks the user for manual attention, `BLOCKED` prevents new sessions for this `vendor_data`." + }, + "portrait_image_url": { + "type": "string", + "nullable": true, + "description": "Presigned URL of the user's portrait photo (expires after a few hours)" + }, + "session_count": { + "type": "integer", + "description": "Total number of verification sessions for this user" + }, + "approved_count": { + "type": "integer", + "description": "Number of approved sessions" + }, + "declined_count": { + "type": "integer", + "description": "Number of declined sessions" + }, + "in_review_count": { + "type": "integer", + "description": "Number of sessions in review" + }, + "issuing_states": { + "type": "array", + "items": { + "type": "string" + }, + "description": "ISO 3166-1 alpha-3 codes of issuing countries seen on this user's approved ID documents, e.g. `[\"USA\", \"ESP\"]`. Empty array when none." + }, + "approved_emails": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Verified email addresses collected from this user's approved sessions, e.g. `[\"john@example.com\"]`." + }, + "approved_phones": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Verified phone numbers collected from this user's approved sessions, e.g. `[\"+14155551234\"]`." + }, + "features": { + "type": "object", + "description": "Aggregated per-feature status across all of this user's sessions. Possible keys: `ID_VERIFICATION`, `NFC`, `LIVENESS`, `FACE_MATCH`, `POA`, `QUESTIONNAIRE`, `EMAIL_VERIFICATION`, `PHONE`, `AML`, `IP_ANALYSIS`, `AGE_ESTIMATION`, `DATABASE_VALIDATION`. Possible values: `Approved`, `Declined`, `In Review`, `Not Finished`, `Resub Requested`." + }, + "features_list": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "description": "Same data as `features`, as an ordered array of `{feature, status}` objects." + }, + "last_session_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent session" + }, + "first_session_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the first session" + }, + "last_activity_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent activity on this user (session, transaction, status change, data edit, etc.)." + }, + "tags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + } + } + }, + "description": "Tags assigned to this user" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BlocklistItem": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email" + ] + }, + "session_id": { + "type": "string", + "format": "uuid" + }, + "session_number": { + "type": "integer" + }, + "blocklisted_at": { + "type": "string", + "format": "date-time" + }, + "face_image": { + "type": "string", + "nullable": true + }, + "document_type": { + "type": "string", + "nullable": true + }, + "document_number": { + "type": "string", + "nullable": true + }, + "phone_number": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + } + } + }, + "UserDetailItem": { + "type": "object", + "description": "Full user detail. Extends UserListItem with metadata, comments, and updated_at.", + "allOf": [ + { + "$ref": "#/components/schemas/UserListItem" + }, + { + "type": "object", + "properties": { + "metadata": { + "type": "object", + "description": "Custom metadata JSON you attached to this user. Defaults to `{}`." + }, + "tags": { + "type": "array", + "description": "Tag assignments. NOTE: on detail responses each entry is a tag *link* object (`{uuid, tag: {...}, added_by_email, added_by_name, created_at}`), unlike the flat `{uuid, name, color}` shape used on list responses.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the tag assignment (not the tag itself)." + }, + "tag": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ] + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "added_by_email": { + "type": "string", + "nullable": true + }, + "added_by_name": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + } + }, + "comments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "comment_type": { + "type": "string", + "enum": [ + "COMMENT", + "STATUS_CHANGED", + "METADATA_UPDATED", + "TAG_ADDED", + "TAG_REMOVED", + "PROFILE_UPDATED" + ] + }, + "comment": { + "type": "string", + "nullable": true + }, + "actor_email": { + "type": "string", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "previous_status": { + "type": "string", + "nullable": true, + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED", + null + ] + }, + "new_status": { + "type": "string", + "nullable": true, + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED", + null + ] + }, + "previous_value": { + "type": "object", + "nullable": true, + "description": "Previous field values for PROFILE_UPDATED entries." + }, + "new_value": { + "type": "object", + "nullable": true, + "description": "New field values for PROFILE_UPDATED entries." + }, + "changed_fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the fields changed by a PROFILE_UPDATED entry." + }, + "metadata": { + "type": "object", + "nullable": true + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string" + } + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "description": "Activity log and comments for this user (status changes, profile edits, manual notes)." + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + ] + }, + "BusinessListItem": { + "type": "object", + "description": "A verified business.", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit's stable internal UUID for this business." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your unique identifier for this business (passed when creating sessions). This can be null when no vendor identifier was supplied." + }, + "display_name": { + "type": "string", + "nullable": true, + "description": "Custom display name set by you" + }, + "legal_name": { + "type": "string", + "nullable": true, + "description": "Official legal name from registry or manual entry" + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Company registration or incorporation number" + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "Country of incorporation (ISO 3166-1 alpha-2, e.g. `GB`, `US`)." + }, + "effective_name": { + "type": "string", + "nullable": true, + "description": "Best available name: display_name if set, otherwise legal_name" + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "Lifecycle status of the business record (NOT a session status). `ACTIVE` is the default, `FLAGGED` marks it for manual attention, `BLOCKED` prevents new sessions for this `vendor_data`." + }, + "session_count": { + "type": "integer", + "description": "Total number of verification sessions for this business" + }, + "approved_count": { + "type": "integer", + "description": "Number of approved sessions" + }, + "declined_count": { + "type": "integer", + "description": "Number of declined sessions" + }, + "in_review_count": { + "type": "integer", + "description": "Number of sessions in review" + }, + "features": { + "type": "object", + "description": "Aggregated per-feature status across all of this business's KYB sessions. Currently the only key is `KYB` (the registry company check status). Possible values: `Approved`, `Declined`, `In Review`, `Not Finished`, `Resub Requested`." + }, + "features_list": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "description": "Same data as `features`, as an ordered array of `{feature, status}` objects." + }, + "last_session_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent session" + }, + "first_session_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the first session" + }, + "last_activity_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent activity (status change, session update, etc.)" + }, + "tags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + } + } + }, + "description": "Tags assigned to this business" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BusinessDetailItem": { + "type": "object", + "description": "Full business detail. Extends BusinessListItem with metadata and updated_at.", + "allOf": [ + { + "$ref": "#/components/schemas/BusinessListItem" + }, + { + "type": "object", + "properties": { + "metadata": { + "type": "object", + "description": "Custom metadata JSON you attached to this business. Defaults to `{}`." + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + ] + }, + "TransactionDetail": { + "type": "object", + "description": "Full monitoring record for one transaction, as returned by `GET /v3/transactions/{transaction_id}/` and `POST /v3/transactions/`.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit-stable transaction identifier. Use as `{transaction_id}` for follow-up calls." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the transaction. A `sandbox` transaction is stored but never evaluated, screened, notified or billed, so its empty `rule_runs` means \"not evaluated\"." + }, + "transaction_number": { + "type": "integer", + "description": "Application-scoped sequential transaction number shown in Console (e.g. `4123`)." + }, + "txn_id": { + "type": "string", + "description": "The `transaction_id` you supplied at create time (max 128 chars). Unique per application." + }, + "txn_date": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred (from `transaction_at`; defaults to submission time)." + }, + "zone_id": { + "type": "string", + "nullable": true, + "description": "IANA time zone identifier provided at create time." + }, + "transaction_type": { + "type": "string", + "description": "Top-level category as stored: `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet`, `gamblingLimitChange`, `gamblingBonusChange`, `auditTrailEvent`. Note multi-word values are echoed back in camelCase even when submitted in snake_case." + }, + "action_type": { + "type": "string", + "description": "Sub-type within the category (e.g. `deposit`, `withdrawal`, `transfer`). Defaults to the category when not supplied." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject. Stored uppercase regardless of the casing submitted (`in`/`out`/`inbound`/`outbound` are accepted on input)." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Current monitoring verdict. Transactions are created `APPROVED`; rules may flip them to `IN_REVIEW`/`DECLINED` synchronously." + }, + "source": { + "type": "string", + "enum": [ + "LIVE", + "IMPORTED" + ], + "description": "How the record originated: `LIVE` for transactions submitted through the API or SDK, `IMPORTED` for transactions loaded through the console import." + }, + "amount": { + "type": "string", + "description": "Transaction amount as a decimal string with trailing zeros stripped (e.g. `\"1500\"`, `\"0.5\"`, `\"0.123456789012345678\"`). Up to 18 decimal places." + }, + "currency": { + "type": "string", + "description": "Currency code of `amount`. Any ISO 4217 alphabetic code is accepted (e.g. `EUR`, `USD`, `NGN`), as is a crypto asset symbol (e.g. `BTC`). Prefer a currently-circulating code. Not every accepted code has an exchange rate: when none is available for the code (for example the ISO accounting unit `XSU`, or a crypto symbol Didit does not price), automatic conversion fails and `preferred_currency_amount` is returned null unless the request supplied a pre-converted `amount_in_default_currency`." + }, + "currency_type": { + "type": "string", + "nullable": true, + "description": "`fiat` or `crypto`, as submitted in `currency_kind`." + }, + "amount_in_default_currency": { + "type": "string", + "nullable": true, + "description": "Pre-converted amount you supplied, as a decimal string with trailing zeros stripped." + }, + "default_currency_code": { + "type": "string", + "nullable": true + }, + "preferred_currency_amount": { + "type": "string", + "nullable": true, + "description": "`amount` converted to the application's preferred currency, when available." + }, + "preferred_currency_code": { + "type": "string", + "nullable": true + }, + "payment_details": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference or memo from the submission." + }, + "payment_txn_id": { + "type": "string", + "nullable": true, + "description": "External payment system reference (e.g. blockchain transaction hash) from `payment_reference_id`." + }, + "score": { + "type": "integer", + "description": "Risk score accumulated by rules (typically 0–100). Higher = riskier." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ], + "description": "Categorical risk severity set by rules/providers (`UNKNOWN`, `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`). `null` until something sets it." + }, + "decision_reason_code": { + "type": "string", + "nullable": true, + "description": "Machine-readable reason for the current `status`." + }, + "decision_reason_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label for `decision_reason_code`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Convenience copy of the applicant's `vendor_data`." + }, + "metadata": { + "type": "object", + "description": "Snapshot of the `subject` and `counterparty` payloads as submitted at create time." + }, + "props": { + "type": "object", + "description": "The `custom_properties` you supplied at create time. Each key is addressable in rule conditions as `custom_values.`." + }, + "tags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ], + "description": "Whether the tag is a Didit preset or a custom tag." + } + } + }, + "description": "Tags attached to the transaction (manually or by rules)." + }, + "parties": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "role": { + "type": "string", + "enum": [ + "APPLICANT", + "REMITTER", + "BENEFICIARY", + "COUNTERPARTY" + ], + "description": "Party role. The create endpoint assigns `APPLICANT` (the subject) and `COUNTERPARTY`." + }, + "entity_type": { + "type": "string", + "enum": [ + "individual", + "company", + "external", + "unhostedWallet" + ] + }, + "kind": { + "type": "string", + "enum": [ + "USER", + "BUSINESS", + "EXTERNAL" + ], + "description": "`USER`/`BUSINESS` parties link back to a Didit-owned entity via `vendor_data`. `EXTERNAL` parties have no Didit entity behind them." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your internal user/business identifier." + }, + "full_name": { + "type": "string", + "nullable": true + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "Country code from the submitted `address.country` (typically ISO 3166-1 alpha-3)." + }, + "dob": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth from the submitted `date_of_birth`." + }, + "address": { + "type": "object", + "description": "Address object as submitted. `{}` when omitted." + }, + "institution_info": { + "type": "object", + "description": "Institution details as submitted. `{}` when omitted." + }, + "device": { + "type": "object", + "description": "Normalized device context, including server-side IP enrichment under network_context (geolocation, VPN/data-center flags) when an IP was provided.", + "properties": { + "fingerprint": { + "type": "string", + "description": "Unique device fingerprint." + }, + "persistent_device_id": { + "type": "string", + "description": "Stable cross-session device identifier (v2 fingerprint schema)." + }, + "user_agent": { + "type": "string", + "description": "Browser or app user agent string." + }, + "browser_family": { + "type": "string", + "description": "Browser family (e.g. Chrome, Firefox, Safari)." + }, + "browser_version": { + "type": "string", + "description": "Browser version string." + }, + "platform": { + "type": "string", + "description": "Platform identifier (web, ios, android, macos, windows, linux)." + }, + "os_family": { + "type": "string", + "description": "Operating system family (e.g. iOS, Android, Windows)." + }, + "os_version": { + "type": "string", + "description": "Operating system version string." + }, + "accept_language": { + "type": "string", + "description": "Accept-Language header value." + }, + "session_id": { + "type": "string", + "description": "Session identifier (stored in metadata)." + }, + "session_age_ms": { + "type": "number", + "description": "Session age in milliseconds." + }, + "network_context": { + "type": "object", + "description": "Network context with IP address and geo-location.", + "properties": { + "ip_address": { + "type": "string", + "description": "Client IP address." + }, + "ip_country_code": { + "type": "string", + "description": "Geo-resolved country of the IP (ISO 3166-1 alpha-3)." + }, + "latitude": { + "type": "number", + "description": "Geographic latitude of the IP." + }, + "longitude": { + "type": "number", + "description": "Geographic longitude of the IP." + } + } + } + } + }, + "external_party_snapshot": { + "type": "object", + "nullable": true, + "description": "Frozen copy of the party data for `EXTERNAL` parties. `null` while the party is linked to a Didit user or business." + } + } + }, + "description": "Transaction parties (applicant, counterparty)." + }, + "payment_methods": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "payment_method_type": { + "type": "string", + "enum": [ + "bank_card", + "bank_account", + "e_wallet", + "crypto_wallet", + "unhosted_wallet", + "other" + ] + }, + "label": { + "type": "string", + "nullable": true + }, + "fingerprint": { + "type": "string", + "nullable": true, + "description": "SHA-256 over `type:account_id:issuing_country` — stable identifier to correlate reuse of the same instrument." + }, + "account_id": { + "type": "string", + "nullable": true, + "description": "Account identifier (IBAN, wallet address, card token, …)." + }, + "issuing_country": { + "type": "string", + "nullable": true + }, + "owner_kind": { + "type": "string", + "enum": [ + "USER", + "BUSINESS", + "EXTERNAL" + ] + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your identifier for the owning user/business." + }, + "owner_name": { + "type": "string", + "nullable": true + }, + "institution_info": { + "type": "object" + }, + "details": { + "type": "object", + "description": "Raw `payment_method` object as submitted." + }, + "external_owner_snapshot": { + "type": "object", + "nullable": true + }, + "role": { + "type": "string", + "enum": [ + "SOURCE", + "DESTINATION", + "FUNDING", + "BENEFICIARY" + ], + "description": "How this method was used in the transaction. The create endpoint assigns `SOURCE`/`DESTINATION` from the direction (outbound: subject = SOURCE; inbound: subject = DESTINATION)." + } + } + }, + "description": "Payment methods linked to the transaction." + }, + "documents": { + "type": "array", + "description": "Files attached to the transaction from the console. `[]` when none.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string", + "nullable": true + }, + "note": { + "type": "string", + "nullable": true + }, + "uploaded_by": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "files": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "file_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived signed download URL; fetch it when needed rather than storing it." + }, + "original_filename": { + "type": "string", + "nullable": true + }, + "file_size": { + "type": "integer", + "nullable": true + } + } + } + } + } + } + }, + "activities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "activity_type": { + "type": "string", + "description": "E.g. `TRANSACTION_CREATED`, `TRANSACTION_NOTE_ADDED`, `TRANSACTION_STATUS_CHANGED`." + }, + "source": { + "type": "string" + }, + "status": { + "type": "string", + "nullable": true + }, + "title": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object" + }, + "occurred_at": { + "type": "string", + "format": "date-time" + } + } + }, + "description": "Activity log entries (creation, notes, manual reviews, status changes)." + }, + "alerts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "severity": { + "type": "string", + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ] + }, + "status": { + "type": "string", + "enum": [ + "OPEN", + "INVESTIGATING", + "AWAITING_USER", + "PENDING_SAR", + "SAR_FILED", + "RESOLVED", + "DISMISSED" + ] + }, + "source": { + "type": "string", + "enum": [ + "RULE", + "PROVIDER", + "MANUAL" + ] + }, + "metadata": { + "type": "object" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "description": "Alerts raised by rules and providers." + }, + "rule_runs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "rule": { + "type": "string", + "format": "uuid", + "description": "UUID of the rule that ran." + }, + "rule_title": { + "type": "string" + }, + "rule_description": { + "type": "string", + "nullable": true + }, + "rule_category": { + "type": "string" + }, + "matched": { + "type": "boolean", + "description": "Whether the rule's conditions matched this transaction. A run is recorded only for matched rules, so live evaluations always carry `true`." + }, + "is_test": { + "type": "boolean", + "description": "True when the rule ran in TEST mode (no effect on status/score)." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ] + }, + "severity": { + "type": "string", + "nullable": true + }, + "score_delta": { + "type": "integer", + "description": "Score added by this rule's `add_score` actions." + }, + "status_target": { + "type": "string", + "nullable": true, + "description": "Status set by a `change_status` action, if any." + }, + "action_summary": { + "type": "string", + "nullable": true, + "description": "Summary of the actions the rule executed, joined with `; ` (e.g. `score +30; change_status:IN_REVIEW; add_tag:high_risk_country`). Parts: `score +N`, `change_status:`, `add_tag:`, `add_note`, `add_to_list`, `open_case`. `null` when the rule executed no action." + }, + "metadata": { + "type": "object", + "description": "Evaluation detail: `scope_match`, `conditions[]` and `aggregations[]`. Each condition entry carries `field`, `operator`, `expected_value`, the `current_value` read from the transaction, `matched`, `group_index` and `group_logic`. Each aggregation entry (velocity / count / sum) carries `metric`, `field`, `operator`, `expected_value`, the `computed_value` the aggregate evaluated to, `window`, `filters`, `matched` and `sample_references`; deferred async aggregations have `computed_value: null` and `deferred: true`. A nested condition group is one `conditions[]` entry with `operator` `any_of` or `all_of`, `field` null, and its members' results under its own `conditions`." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "description": "Per-rule execution results — which rule fired, with what score impact." + }, + "provider_results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "provider": { + "type": "string" + }, + "result_type": { + "type": "string", + "enum": [ + "FIAT_MONITORING", + "WALLET_SCREENING", + "TRANSACTION_SCREENING", + "TRAVEL_RULE", + "NETWORK_GRAPH", + "CUSTOM" + ] + }, + "status": { + "type": "string" + }, + "severity": { + "type": "string", + "nullable": true + }, + "score": { + "type": "integer" + }, + "summary": { + "type": "string", + "nullable": true + }, + "payload": { + "type": "object", + "description": "Raw provider response payload." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "description": "Raw provider payloads (AML screening, blockchain analytics, etc.)." + }, + "travel_rule": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TransactionTravelRule" + } + ], + "description": "Travel Rule transfer or check. Populated when `travel_rule_details` was submitted, and also when Travel Rule is enabled and the transaction was created with `transaction_category: travelRule` (Didit initiates the exchange itself, even without `travel_rule_details`). `null` when no transfer or check exists. On an inbound transfer, the counterparty's `originator_data` and `beneficiary_data` stay masked (`data_masked: true`) until wallet ownership is confirmed." + }, + "network_snapshot": { + "type": "object", + "nullable": true, + "description": "Graph snapshot of related transactions/parties used by the rules engine. Either the `network_snapshot` submitted with the transaction or the graph Didit builds from a successful AML wallet screening (which replaces a submitted one). `null` when neither exists.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "nodes": { + "type": "array", + "items": { + "type": "object" + } + }, + "edges": { + "type": "array", + "items": { + "type": "object" + } + }, + "metrics": { + "type": "object" + } + } + }, + "remediation": { + "type": "object", + "nullable": true, + "description": "Remediation session offered to the user when re-verification is required. Superseded by `action_required`, which is the canonical block; kept for backward compatibility.", + "properties": { + "session_id": { + "type": "string" + }, + "session_token": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "status": { + "type": "string" + } + } + }, + "action_required": { + "type": "object", + "nullable": true, + "description": "Pending end-user action required to complete this transaction, or null when there is none. The Didit SDKs auto-launch the action by default and refresh the transaction when it finishes. Two variants, discriminated by `type`: `verification_session` (a hosted verification session created by a rule action) and `wallet_ownership` (a wallet-ownership widget session auto-minted for a Travel Rule transfer that needs proof of wallet control, gated by the `auto_wallet_verification` Travel Rule setting). When both could apply, `wallet_ownership` takes precedence because it blocks the Travel Rule exchange.", + "oneOf": [ + { + "$ref": "#/components/schemas/TransactionVerificationSessionAction" + }, + { + "$ref": "#/components/schemas/TransactionWalletOwnershipAction" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "verification_session": "#/components/schemas/TransactionVerificationSessionAction", + "wallet_ownership": "#/components/schemas/TransactionWalletOwnershipAction" + } + } + }, + "cost_breakdown": { + "type": "object", + "nullable": true, + "description": "Cost of this transaction: `total_price` and `items`, one per charge, each with `usage_type`, `price`, `details` and `created_at`. A re-screen of the same transaction is a new item." + } + } + }, + "TravelRuleSettingsDetail": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean", + "description": "Whether the managed Travel Rule negotiation flow is active for outbound `travelRule` transactions on this application." + }, + "jurisdiction": { + "type": "string", + "maxLength": 8, + "description": "Your VASP's operating jurisdiction code. Default `\"EU\"`. Didit maps it to the regime it stamps on your Travel Rule transactions as `travel_rule_regime` (for example `EU` or an EU member code becomes `eu`), which transaction rules can key on. Other Didit customers see it as your jurisdiction in the VASP directory once the Didit network lists your profile." + }, + "name_matching_strictness": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "How strictly beneficiary names must match before a transfer can reach COMPLETED. `CONTAINS` accepts one name whose words all appear in the other." + }, + "confirmation_timeout_hours": { + "type": "integer", + "minimum": 1, + "description": "Hours an AWAITING_COUNTERPARTY transfer may remain unconfirmed before the periodic sweeper expires it. Default 48." + }, + "timeout_outcome": { + "type": "string", + "enum": [ + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "What happens to the underlying transaction when a transfer expires. Default HOLD. sunrise_policy and non_responsive_policy take precedence when they apply and are set." + }, + "threshold_amount": { + "type": "string", + "description": "Decimal string, in your application's default currency. Minimum transfer amount that triggers a managed exchange; transfers below it resolve straight to NOT_APPLICABLE. Didit compares the transaction amount converted to your default currency, or the `amount_in_default_currency` you send; when no converted amount is available, the threshold does not apply. Defaults to \"0.00\" (EU TFR applies no minimum amount)." + }, + "ownership_validation_mode": { + "type": "string", + "enum": [ + "FULL", + "SIMPLIFIED", + "AUTOMATED" + ], + "description": "How much proof a wallet's ownership takes. `FULL` always needs a proof (signed message, test deposit or reviewed screenshot), never the end user's word. `SIMPLIFIED` accepts a self-declaration below `simplified_validation_threshold` and a proof above it. `AUTOMATED` lets the address book and earlier proofs answer on their own, self-declarations included while `allow_self_declaration` is true. Default `AUTOMATED`." + }, + "simplified_validation_threshold": { + "type": "string", + "description": "Decimal string, default \"0.00\". In `SIMPLIFIED` mode a transfer below this amount (in the threshold currency) accepts a self-declaration. A transfer with no amount in that currency counts as above it." + }, + "data_sharing_fields": { + "$ref": "#/components/schemas/TravelRuleDataSharingFields" + }, + "matching_rules": { + "$ref": "#/components/schemas/TravelRuleMatchingRules" + }, + "legal_name": { + "type": "string", + "description": "Your VASP's legal name. Required (non-blank) to set is_enabled to true." + }, + "lei": { + "type": "string", + "description": "Your Legal Entity Identifier, surfaced in the VASP directory." + }, + "compliance_email": { + "type": "string", + "format": "email", + "description": "Compliance contact email for your VASP profile. The Didit network lists your profile only once this address is confirmed through an emailed link (POST /v3/travel-rule/settings/compliance-email/confirmation/); changing it needs a new confirmation." + }, + "is_discoverable": { + "type": "boolean", + "description": "Whether your VASP profile can be listed in the Didit network: found in GET /v3/travel-rule/vasps/ by other Didit customers and reached on the INTERNAL rail. Listing also needs a live application, Travel Rule on, a legal name and a confirmed compliance email; didit_network says which are missing. Default true." + }, + "inbound_auto_accept": { + "type": "boolean", + "description": "When true, an inbound TRP inquiry whose beneficiary wallet is a verified address-book entry with a matching name is approved automatically. When false, that inquiry waits in ON_HOLD for you to approve or deny it with the ownership endpoint. INTERNAL-rail and after-deposit transfers are not affected. Default true." + }, + "allow_self_declaration": { + "type": "boolean", + "description": "When true, the wallet-ownership widget offers self-declaration as a fallback proof method. Default true." + }, + "allow_screenshot_proof": { + "type": "boolean", + "description": "When true, the wallet-ownership widget offers screenshot upload as an ownership evidence method. Default true." + }, + "auto_wallet_verification": { + "type": "boolean", + "description": "When true, submitting an outbound Travel Rule transfer that needs end-user proof of wallet control auto-mints a wallet-ownership widget session bound to the transaction and exposes it in the transaction's action_required block. Default true." + }, + "vasp_attribution_enabled": { + "type": "boolean", + "description": "When true, an outbound Travel Rule transfer that no rail can route is attributed through blockchain analytics: the destination wallet is resolved to its owning VASP, the entity is recorded on the transfer and in the VASP directory, and the rails are re-run once with the attributed name. Default true." + }, + "counterparty_screening_enabled": { + "type": "boolean", + "description": "Opt-in sanctions and adverse-media screening of counterparty VASPs, billed per screening at the Company AML Screening price. When on, Didit screens the counterparty an outbound exchange reaches (reusing a screening younger than 30 days) and you can screen one with POST /v3/travel-rule/vasps/{vasp_id}/screening/. Default false: nothing is screened or billed." + }, + "sunrise_policy": { + "type": "string", + "enum": [ + "", + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "What an expired transfer does to its transaction when the counterparty is a directory entry in a jurisdiction on sunrise_jurisdictions, which does not enforce the Travel Rule yet: HOLD moves it to review, REJECT declines it, PROCEED leaves its status untouched. Empty (the default) follows timeout_outcome. Applies before non_responsive_policy." + }, + "sunrise_jurisdictions": { + "type": "array", + "items": { + "type": "string" + }, + "readOnly": true, + "description": "Read-only. The countries (ISO 3166 alpha-2) Didit treats as in a Travel Rule sunrise period, where sunrise_policy applies. Currently CO, KH and VN. Any other jurisdiction, and an empty or unknown one, follows timeout_outcome." + }, + "non_responsive_policy": { + "type": "string", + "enum": [ + "", + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "What an expired transfer does to its transaction when the counterparty is a directory entry whose responsiveness is UNRESPONSIVE: HOLD, REJECT or PROCEED, as for timeout_outcome. Empty (the default) follows timeout_outcome. sunrise_policy applies first when both fit." + }, + "travel_address": { + "type": "string", + "description": "Read-only. Your VASP's own travel address - the encoded inbound TRP inquiry endpoint other VASPs use to reach you. Empty while `is_enabled` is false." + }, + "disabled_networks": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA" + ] + }, + "description": "Networks opted out of platform membership. Does not disable your connected own memberships or provision unavailable networks.", + "default": [] + }, + "networks": { + "type": "object", + "readOnly": true, + "description": "Per network, how this application would send and why it cannot. No network, including GTR, is included by default: a network is reachable through your own connected membership, or through Didit's membership once Didit's membership of that network is live and your own participant on it is active. Not a live health or delivery check.", + "properties": { + "gtr": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "trust": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "verifyvasp": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "sygna": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + } + } + }, + "didit_network": { + "type": "object", + "readOnly": true, + "description": "Whether the Didit network lists your VASP profile, so other Didit customers find it in the directory and reach it on the INTERNAL rail, and each condition it still misses.", + "properties": { + "listed": { + "type": "boolean" + }, + "missing": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "travel_rule_off", + "sandbox_application", + "not_discoverable", + "legal_name_missing", + "compliance_email_missing", + "compliance_email_unconfirmed", + "excluded_by_staff", + "internal_organization" + ] + }, + "description": "Empty when listed. compliance_email_unconfirmed: send the confirmation with POST /v3/travel-rule/settings/compliance-email/confirmation/ and open the emailed link. excluded_by_staff: Didit marked the application as a test, demo or internal one." + }, + "compliance_email_confirmed": { + "type": "boolean", + "description": "Whether the current compliance_email was confirmed through the emailed link." + } + } + } + } + }, + "TravelRuleSettingsUpdate": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean" + }, + "legal_name": { + "type": "string", + "maxLength": 255 + }, + "lei": { + "type": "string", + "maxLength": 20 + }, + "jurisdiction": { + "type": "string", + "maxLength": 8 + }, + "name_matching_strictness": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "`CONTAINS` accepts one name whose words all appear in the other." + }, + "confirmation_timeout_hours": { + "type": "integer", + "minimum": 1 + }, + "timeout_outcome": { + "type": "string", + "enum": [ + "HOLD", + "REJECT", + "PROCEED" + ] + }, + "compliance_email": { + "type": "string", + "format": "email", + "description": "Compliance contact email for your VASP profile. The Didit network lists your profile only once this address is confirmed through an emailed link (POST /v3/travel-rule/settings/compliance-email/confirmation/); changing it needs a new confirmation." + }, + "is_discoverable": { + "type": "boolean", + "description": "Whether your VASP profile can be listed in the Didit network: found in GET /v3/travel-rule/vasps/ by other Didit customers and reached on the INTERNAL rail. Listing also needs a live application, Travel Rule on, a legal name and a confirmed compliance email; didit_network says which are missing. Default true." + }, + "threshold_amount": { + "type": "string", + "description": "Decimal string, zero or greater, in your application's default currency. Minimum transfer amount that triggers a managed exchange." + }, + "ownership_validation_mode": { + "type": "string", + "enum": [ + "FULL", + "SIMPLIFIED", + "AUTOMATED" + ], + "description": "See the response field of the same name." + }, + "simplified_validation_threshold": { + "type": "string", + "description": "Decimal string, 0 or more." + }, + "data_sharing_fields": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleDataSharingFields" + } + ], + "description": "Merged per entity type: an entity type you leave out keeps its current choice. A choice that cannot meet the originator obligations of your `jurisdiction` regime (EU TFR or FATF Recommendation 16), an empty list included, is refused with `400`." + }, + "matching_rules": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleMatchingRules" + } + ], + "description": "Merged per entity type, and per field inside it. A field that entity type does not compare (for example `date_of_birth` for `company`) is refused with `400`." + }, + "inbound_auto_accept": { + "type": "boolean" + }, + "allow_self_declaration": { + "type": "boolean" + }, + "allow_screenshot_proof": { + "type": "boolean", + "description": "When true, the wallet-ownership widget offers screenshot upload as an ownership evidence method. Default true." + }, + "auto_wallet_verification": { + "type": "boolean" + }, + "vasp_attribution_enabled": { + "type": "boolean" + }, + "counterparty_screening_enabled": { + "type": "boolean", + "description": "Opt-in sanctions and adverse-media screening of counterparty VASPs, billed per screening at the Company AML Screening price. When on, Didit screens the counterparty an outbound exchange reaches (reusing a screening younger than 30 days) and you can screen one with POST /v3/travel-rule/vasps/{vasp_id}/screening/. Default false: nothing is screened or billed." + }, + "sunrise_policy": { + "type": "string", + "enum": [ + "", + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "HOLD, REJECT, PROCEED, or empty to follow timeout_outcome. See the response field of the same name." + }, + "non_responsive_policy": { + "type": "string", + "enum": [ + "", + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "HOLD, REJECT, PROCEED, or empty to follow timeout_outcome. See the response field of the same name." + }, + "disabled_networks": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA" + ] + }, + "description": "Networks opted out of platform membership. Does not disable your connected own memberships or provision unavailable networks.", + "default": [] + } + }, + "description": "All fields optional; PUT applies a partial update." + }, + "WalletAddressEntry": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "address": { + "type": "string", + "description": "The wallet address; `\"\"` for an entry imported by its hash only. Unique per application, chain and memo, comparing the address without regard to case. After you delete an entry, you can register the same address again." + }, + "chain": { + "type": "string", + "description": "Chain token for the address, for example `ETH` or `BTC`. When Didit routes or registers a transfer, it matches address book entries on the wallet address and, when the transfer carries a chain, on this value too, ignoring case. A transfer's chain is `beneficiary_data.chain`, filled from the transaction's crypto chain or currency symbol (for example `ETH`) when you do not send it, or the `chain` of an after-deposit registration, so use the same token everywhere. An entry with an empty chain matches any chain. A Lightning payee (a node id or a Lightning Address) registered on bitcoin is stored lowercased on `LIGHTNING`, where Lightning transfers are matched. Known chains are stored under one canonical identifier whatever spelling you send: \"ethereum\", \"Ethereum\" and \"ERC20\" are all stored as ETH, \"BNB Smart Chain\" and \"BEP20\" as BNB, \"tron\" as TRX. An unknown chain name is kept as given." + }, + "holder_vendor_data": { + "type": "string", + "description": "Your internal identifier for the holder." + }, + "holder_name": { + "type": "string", + "description": "Name of the wallet holder, used for the beneficiary name match." + }, + "entity_type": { + "type": "string", + "description": "Free text, defaults to \"individual\"." + }, + "is_ownership_verified": { + "type": "boolean", + "description": "Read-only. Whether ownership of this address has been proven." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "travel_address": { + "type": "string", + "description": "The counterparty VASP's travel address for this wallet, if known. Lets Didit route this beneficiary's transfers over the TRP rail, for the assets TRP carries (BTC, ETH, and USDC on a named network)." + }, + "address_hash": { + "type": "string", + "description": "Read-only. SHA-256 of the lowercased address, in lowercase hex. An entry imported by its hash only is matched on it." + }, + "memo": { + "type": "string", + "description": "The destination tag or memo that is part of the wallet on a memo network (XRP, XLM, EOS, ATOM, HBAR); `\"\"` for the whole address. An entry with a memo matches only transfers naming that memo; one without matches the whole address." + }, + "receiving_travel_address": { + "type": "string", + "readOnly": true, + "description": "Your per-wallet travel address for this wallet: give it to the sending VASP so its TRP inquiry names this wallet. Empty for a wallet recorded as another VASP's, while Travel Rule is off, and before your VASP profile is saved." + } + } + }, + "WalletAddressEntryCreate": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string", + "maxLength": 128, + "description": "The wallet address. An XRP X-address is stored as its classic address and its tag, so it matches transfers that name either form." + }, + "chain": { + "type": "string", + "maxLength": 32, + "description": "Chain token for the address, for example `ETH` or `BTC`. When Didit routes or registers a transfer, it matches address book entries on the wallet address and, when the transfer carries a chain, on this value too, ignoring case. A transfer's chain is `beneficiary_data.chain`, filled from the transaction's crypto chain or currency symbol (for example `ETH`) when you do not send it, or the `chain` of an after-deposit registration, so use the same token everywhere. An entry with an empty chain matches any chain. Known chains are stored under one canonical identifier whatever spelling you send: \"ethereum\", \"Ethereum\" and \"ERC20\" are all stored as ETH, \"BNB Smart Chain\" and \"BEP20\" as BNB, \"tron\" as TRX. An unknown chain name is kept as given." + }, + "holder_vendor_data": { + "type": "string", + "maxLength": 255 + }, + "holder_name": { + "type": "string", + "maxLength": 255 + }, + "entity_type": { + "type": "string", + "default": "individual", + "maxLength": 16 + }, + "self_declared": { + "type": "boolean", + "default": false, + "description": "Write-only. When true, the entry is created already ownership-verified via a SELF_DECLARATION proof. Returns 400 when `allow_self_declaration` is off in your Travel Rule settings." + }, + "travel_address": { + "type": "string", + "description": "Optional. The counterparty VASP's travel address for this wallet, enabling TRP routing for the assets TRP carries (BTC, ETH, and USDC on a named network). An invalid travel address returns 400.", + "maxLength": 512 + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "Optional. The destination tag or memo that is part of the wallet. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`. Alias: `destinationTag` or `destination_tag`." + } + } + }, + "WalletAddressEntryUpdate": { + "type": "object", + "properties": { + "holder_name": { + "type": "string", + "maxLength": 255 + }, + "holder_vendor_data": { + "type": "string", + "maxLength": 255 + }, + "entity_type": { + "type": "string", + "maxLength": 16 + }, + "travel_address": { + "type": "string", + "maxLength": 512 + } + }, + "description": "PATCH is a partial update; the response echoes only holder_name, holder_vendor_data, entity_type, travel_address, receiving_travel_address, label, and owner_type." + }, + "TravelRuleTransferDetail": { + "type": "object", + "description": "Full transfer object returned by the ownership-confirmation and finish/cancel endpoints. While an inbound transfer's wallet ownership is unconfirmed, the counterparty's `originator_data` and `beneficiary_data` come back masked (the first letter of each word is kept; the wallet, chain and VASP routing stay readable) and `data_masked` is true.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "AWAITING_COUNTERPARTY", + "ON_HOLD", + "COMPLETED", + "FINISHED", + "CANCELLED", + "EXPIRED", + "COUNTERPARTY_MISMATCHED_DATA", + "COUNTERPARTY_VASP_NOT_FOUND", + "COUNTERPARTY_VASP_NOT_REACHABLE", + "UNCONFIRMED_OWNERSHIP", + "COUNTERPARTY_VASP_GENERAL_DECLINE", + "NOT_ENOUGH_COUNTERPARTY_DATA", + "NOT_APPLICABLE" + ] + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ] + }, + "rail": { + "type": "string", + "nullable": true, + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL", + "TEST_VASP", + null + ], + "description": "The rail the transfer was routed over, or null when no rail was selected (for example below the threshold, a self-hosted counterparty wallet, an after-deposit registration, or no reachable counterparty). INTERNAL means both sides are Didit applications; EMAIL means the counterparty was reached by an emailed pickup link. TEST_VASP means the Didit Test VASP answered the sandbox transfer. Over GTR, VERIFYVASP and SYGNA, Didit reads the counterparty's answer from the network's response or by polling it every 5 minutes. TRUST only sends, so a TRUST transfer stays AWAITING_COUNTERPARTY until it expires. A network rail is used only when you have connected your own membership for that network. It is chosen after the transaction create returns, so it is null in that response." + }, + "protocol": { + "type": "string", + "nullable": true + }, + "ivms_version": { + "type": "string", + "example": "ivms101.2023" + }, + "required": { + "type": "boolean" + }, + "ownership_confirmed": { + "type": "boolean", + "nullable": true + }, + "deadline_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "timeline": { + "type": "array", + "items": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "at": { + "type": "string", + "format": "date-time" + }, + "detail": { + "type": "string" + }, + "name_match": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleNameMatch" + } + ], + "nullable": true, + "description": "The name comparison this entry decided, next to `detail`. Absent on entries that compared no name." + }, + "reason": { + "type": "string", + "description": "Present only on the entry of a transition a failed delivery caused: `email_undeliverable` or `delivery_failed`, the value the transfer's `reason` reports." + } + } + } + }, + "counterparty_vasp": { + "type": "object", + "nullable": true, + "properties": { + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "dd_score": { + "type": "integer", + "description": "Due-diligence score from 0 to 100, where higher is better. Same scale as in the VASP directory." + }, + "dd_score_breakdown": { + "type": "object", + "description": "Per-component contributions to dd_score, one object per component as in the VASP directory (jurisdiction, verification, protocol, lei, history, essentials, responsiveness), each {points, max, reason, detail}. {} until the counterparty has been assessed.", + "additionalProperties": { + "type": "object", + "properties": { + "points": { + "type": "number", + "description": "Points awarded for this component." + }, + "max": { + "type": "integer", + "description": "Maximum points this component can award." + }, + "reason": { + "type": "string", + "description": "Machine-readable code of the component's verdict." + }, + "detail": { + "type": "string", + "description": "Human-readable reason for the awarded points." + } + } + } + }, + "dd_assessed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the due-diligence assessment last ran. Null until the counterparty has been assessed." + } + } + }, + "originator_data": { + "type": "object", + "description": "Originator party. For an inbound transfer the counterparty's values are masked until ownership is confirmed." + }, + "beneficiary_data": { + "type": "object", + "description": "Beneficiary party. For an inbound transfer the counterparty's values are masked until ownership is confirmed." + }, + "name_match": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleNameMatch" + } + ], + "nullable": true, + "description": "The name comparison that decided the transfer, or null until one ran. While the parties are masked, and on the other customer's copy of an INTERNAL exchange, `transfer_name`, `compared_name`, `matched_names` and `mismatched_names` are hidden. `score` and `matched` can disagree: `matched` follows the strictness, `score` is the similarity of the two full names." + }, + "data_match": { + "$ref": "#/components/schemas/TravelRuleDataMatch" + }, + "mismatched_fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Shortcut for `data_match.mismatched_fields`. Empty when nothing differs." + }, + "data_unmasked": { + "$ref": "#/components/schemas/TravelRuleDataUnmasked" + }, + "data_masked": { + "type": "boolean", + "description": "True while the counterparty's personal data is masked in this response." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "delivery": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleDelivery" + } + ], + "description": "Where the latest message this transfer owes the other side stands. Null when the transfer never owed one (for example below `threshold_amount`, a declared self-hosted wallet, or a sunrise registration)." + }, + "discovery": { + "type": "array", + "description": "How Didit looked for the counterparty, one entry per step that ran. Empty when no discovery ran, for example on an inbound transfer.", + "items": { + "$ref": "#/components/schemas/TravelRuleDiscoveryStep" + } + }, + "reason": { + "type": "string", + "description": "Why a `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE` transfer is stuck; `\"\"` for any other status. A failed delivery decides it first, on `COUNTERPARTY_VASP_NOT_REACHABLE` only: `email_undeliverable` (the mail provider refused the compliance email and no address is left to try: when it was sent, on a reminder, or as a permanent bounce reported later) or `delivery_failed` (the rail that was delivering failed for good: a network rail refused the message or ran out of retries, a `TRP` identity host did not answer, or the counterparty's `TRP` host did not accept the inquiry). Otherwise the `discovery` trace decides it: `awaiting_end_user` (no step named a counterparty Didit can reach), `directory_match_no_route` (a step named a directory VASP that has no route), `picked_member_without_proven_wallet` (the end user picked a Didit customer that has not proven control of the wallet), or `no_directory_match` when the trace gives no other reason. A resend moves the transfer on, so the reason clears. See [Why a transfer is stuck](/transaction-monitoring/travel-rule#why-a-transfer-is-stuck)." + }, + "next_actions": { + "type": "array", + "description": "What moves the transfer on; `[]` for any status but `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE`. `email_undeliverable` offers `update_compliance_email`, then `resend`. `delivery_failed` offers `resend`. A discovery reason offers `select_counterparty_vasp`, `declare_self_hosted`, `add_vasp_hint` and `resend`.", + "items": { + "$ref": "#/components/schemas/TravelRuleNextAction" + } + }, + "chain_details": { + "$ref": "#/components/schemas/TravelRuleChainDetails" + } + } + }, + "TravelRuleOwnershipConfirmRequest": { + "type": "object", + "required": [ + "confirmed" + ], + "properties": { + "confirmed": { + "type": "boolean", + "description": "true to confirm ownership, false to deny it." + }, + "wallet_address": { + "type": "string", + "default": "", + "description": "Inbound TRP transfers: the payment address Didit returns to the originating VASP when you approve. Defaults to the matched address book entry's address, then beneficiary_data.wallet_address. On a declared self-hosted wallet transfer that names no counterparty wallet, the wallet whose ownership you verified; without it the request is rejected with 400. Ignored for other transfers." + } + } + }, + "TravelRuleTransferPatchRequest": { + "type": "object", + "description": "Provide exactly one of: payment_txn_id (finish), action=cancel, action=resend, action=update_compliance_email with compliance_email, action=update_originator_wallet with wallet_address, or action=beneficiary_vasp with beneficiary_vasp.", + "properties": { + "payment_txn_id": { + "type": "string", + "description": "The on-chain transaction hash. Requires the transfer to be an outbound transfer in COMPLETED status." + }, + "action": { + "type": "string", + "enum": [ + "cancel", + "resend", + "update_compliance_email", + "update_originator_wallet", + "beneficiary_vasp" + ], + "description": "\"cancel\" cancels a non-terminal transfer. \"resend\" re-runs routing for an outbound transfer stuck in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE, or NOT_ENOUGH_COUNTERPARTY_DATA. \"update_compliance_email\" replaces the compliance email the request goes to, on an outbound transfer in one of those three statuses; send \"resend\" afterwards. \"update_originator_wallet\" records the wallet the funds actually left from (`wallet_address`, `memo`). \"beneficiary_vasp\" names the counterparty VASP yourself (requires `beneficiary_vasp`) for an outbound transfer in one of those statuses: it is routed to that VASP and sent again." + }, + "compliance_email": { + "type": "string", + "format": "email", + "maxLength": 254, + "description": "Required with `action: update_compliance_email`. Kept as this transfer's `beneficiary_data.counterparty_vasp_email`: it belongs to this transfer only and joins no directory entry and no contact list." + }, + "wallet_address": { + "type": "string", + "maxLength": 128, + "description": "With `action: update_originator_wallet`: the wallet the funds left from. When the transfer's UTXO inputs are known it must be one of them." + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "With `action: update_originator_wallet`: the wallet's memo or tag on a memo network. Optional." + }, + "beneficiary_vasp": { + "type": "string", + "format": "uuid", + "description": "Required with action=beneficiary_vasp: the `id` of a directory VASP, from `GET /v3/travel-rule/vasps/`. Your choice outranks every automatic discovery step, which never overrides it." + } + } + }, + "VaspDirectoryEntry": { + "type": "object", + "description": "A VASP of the directory or a member of the Didit network, with the routes your application has to it. A customer is a member of the Didit network only while its application is live, its Travel Rule is on, its profile is discoverable, has a legal name and a confirmed compliance email, and Didit has not marked the application as a test, demo or internal one. Each row also carries the shared due-diligence assessment (dd_score, risk_level, responsiveness, essential_checks) and your application's own decision, entity screening and questionnaires, which no other customer sees.", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The entry's id, for GET /v3/travel-rule/vasps/{vasp_id}/." + }, + "kind": { + "type": "string", + "enum": [ + "directory", + "didit" + ], + "description": "directory for an entry of the shared directory; didit for a member of the Didit network." + }, + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "description": "ISO 3166 country code, as the source gives it." + }, + "domains": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The VASP's web domains. Always empty for Didit network members." + }, + "reachable": { + "type": "boolean", + "description": "Whether your application has an available route to this VASP, or to one of its entities for a brand. Always true for a Didit network member, except on a sandbox application, where every row is false." + }, + "aliases": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "trading_name", + "other_legal_name", + "network_name", + "attribution", + "merged", + "staff" + ], + "description": "trading_name, other_legal_name (from the global LEI register), network_name (the name on a Travel Rule network), attribution (the entity name blockchain analytics gives the VASP's wallets), merged (the name of a duplicate entry merged into this one) or staff." + } + } + }, + "description": "Other names the VASP is found by." + }, + "networks": { + "type": "array", + "items": { + "type": "object", + "description": "A current listing of this VASP in a Travel Rule network's member directory. A TRISA listing says who the VASP is on TRISA; Didit has no TRISA rail, so it is never a route.", + "properties": { + "network": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "TRISA" + ] + }, + "code": { + "type": "string", + "description": "The VASP's identifier on that network." + }, + "listed_name": { + "type": "string", + "description": "The name the network lists the VASP under." + }, + "country": { + "type": "string", + "description": "The country the network lists, when it gives one." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When Didit staff verified the listing. Null for listings read from a public directory." + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the source last showed the listing. A listing the source has not shown for three weeks is removed." + }, + "source": { + "type": "string", + "enum": [ + "trisa_directory", + "staff" + ], + "description": "Where the listing comes from: the public TRISA directory, or Didit staff." + } + } + } + }, + "routes": { + "type": "array", + "items": { + "type": "object", + "description": "One way your application can deliver Travel Rule data to this VASP, in the order dispatch tries them. A network route needs a membership on that network: your own (connected in the Marketplace) or Didit's once Didit holds one. Without it the route is listed as unavailable with the reason, never as a way to reach the VASP.", + "properties": { + "rail": { + "type": "string", + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL" + ] + }, + "status": { + "type": "string", + "enum": [ + "available", + "unavailable" + ] + }, + "reason": { + "type": "string", + "enum": [ + "", + "no_membership", + "network_switched_off", + "sandbox_application" + ], + "description": "Why an unavailable route cannot be used: no_membership (neither you nor Didit holds a membership on that network), network_switched_off (you turned that network off in your settings) or sandbox_application (a sandbox application sends only to the Didit Test VASP, so no route is available to it). Empty when the route is available." + }, + "via": { + "type": "string", + "enum": [ + "", + "own_membership", + "didit_membership", + "directory", + "contact", + "didit_network" + ], + "description": "How an available route is reached: own_membership or didit_membership for a network rail, directory for a TRP endpoint the directory verified, contact for the EMAIL rail, didit_network for a Didit network member. Empty when the route is unavailable." + } + } + } + }, + "umbrella": { + "type": "object", + "nullable": true, + "description": "The brand this entry is a regulated entity of. Null when it is not part of one.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + } + } + }, + "entities": { + "type": "array", + "items": { + "type": "object", + "description": "A regulated entity of a brand: the legal entity that receives a transfer.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "jurisdiction": { + "type": "string" + }, + "reachable": { + "type": "boolean", + "description": "Whether your application has an available route to it." + } + } + }, + "description": "For a brand, the regulated entities that receive its transfers. A transfer naming the brand goes to the reachable entity in the jurisdiction it states (beneficiary_data.counterparty_vasp_jurisdiction), else to the entity blockchain analytics attribution linked to the destination wallet, else to the only reachable entity when no jurisdiction is stated. Two entities that fit equally are never guessed between." + }, + "contacts": { + "type": "array", + "items": { + "type": "object", + "description": "A compliance contact the EMAIL rail can write to.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "email": { + "type": "string", + "format": "email" + }, + "name": { + "type": "string" + }, + "source": { + "type": "string", + "enum": [ + "staff", + "application", + "learned" + ], + "description": "staff: curated by Didit; application: added by your application; learned: an address that answered a Travel Rule request through its response link." + }, + "scope": { + "type": "string", + "enum": [ + "platform", + "application" + ], + "description": "platform contacts are used for every Didit customer's transfers; application contacts are private to your application, which sees only its own." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When someone at this address answered a request. Null when it has not yet." + } + } + }, + "description": "The platform's contacts for this VASP and your application's own." + }, + "dd_status": { + "type": "string", + "enum": [ + "NOT_STARTED", + "IN_PROGRESS", + "COMPLETED", + "REJECTED" + ], + "description": "Didit's verification status of the entry, scored by the verification component of dd_score. Always COMPLETED for a Didit network member." + }, + "dd_score": { + "type": "integer", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "The shared due-diligence score of a directory entry, 0 to 100, where higher is safer (see dd_score_semantic): the sum of the points in dd_score_breakdown. Null for a Didit network member, which is not scored. An entry not assessed yet (dd_assessed_at null) shows 0 with an empty risk_level, so read risk_level rather than the raw score." + }, + "reachable_rails": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL" + ] + }, + "description": "Rails recorded on the entry. Read routes for what your application can use today." + }, + "is_didit_customer": { + "type": "boolean", + "description": "true for a member of the Didit network (dd_status is always COMPLETED, dd_score null and risk_level empty for these) and for a directory entry linked to a Didit customer." + }, + "dd_score_breakdown": { + "type": "object", + "description": "One object per component of dd_score. Components, their weight (max) and their reason codes: jurisdiction, 25 (jurisdiction_not_high_risk, jurisdiction_high_risk, jurisdiction_unknown); verification, 15 (dd_status_completed, dd_status_in_progress, dd_status_not_started, dd_status_rejected); protocol, 15 (native_rail, email_only, no_rail); lei, 10 (lei_valid, lei_missing); history, 10 (history, no_history); essentials, 15 (essential_checks, no_essential_checks); responsiveness, 10 (responsive, unresponsive, responsiveness_unknown). Empty for a Didit network member.", + "additionalProperties": { + "type": "object", + "properties": { + "points": { + "type": "number", + "description": "Points the component contributes, rounded to one decimal." + }, + "max": { + "type": "integer", + "description": "The component's weight: the points it contributes at best." + }, + "reason": { + "type": "string", + "description": "Machine-readable code of the component's verdict." + }, + "detail": { + "type": "string", + "description": "One-line English explanation." + } + } + } + }, + "dd_assessed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When dd_score, risk_level and responsiveness were last computed: after an exchange with the entry completes, finishes, expires, mismatches or is declined, when Didit refreshes or curates the entry, and in the weekly directory refresh. Null for a Didit network member." + }, + "dd_score_semantic": { + "type": "string", + "enum": [ + "higher_is_safer" + ], + "description": "Which way dd_score points: always higher_is_safer." + }, + "risk_level": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH", + "" + ], + "description": "The band dd_score falls in: LOW (70 or more), MEDIUM (40 to 69) or HIGH (below 40). Empty for a Didit network member and for an entry not assessed yet. Filter on it with risk. Your own flagged screening never changes this shared value: it makes counterparty_risk_level HIGH in your rules instead." + }, + "responsiveness": { + "type": "object", + "description": "Whether the VASP answers Travel Rule requests: the share of the requests Didit customers started towards it in the last 90 days that it answered. Only requests sent over a network or protocol rail, or by email to a shared address (the entry's compliance address or a platform contact), count; a request sent to an address a customer typed, or to a customer's private contact, never does. An expired request is unanswered; a completed exchange, a decline or a data mismatch is an answer; a cancelled request or an unreachable rail is neither. Always UNKNOWN with zero counts for a Didit network member.", + "properties": { + "status": { + "type": "string", + "enum": [ + "RESPONSIVE", + "UNRESPONSIVE", + "UNKNOWN" + ], + "description": "UNKNOWN with fewer than 5 decided requests in the window; UNRESPONSIVE when less than 10 percent of them were answered; RESPONSIVE otherwise." + }, + "answered": { + "type": "integer", + "description": "Decided requests in the window that the VASP answered." + }, + "total": { + "type": "integer", + "description": "Decided requests in the window: answered plus expired." + }, + "computed_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "essential_checks": { + "type": "object", + "description": "The essential-checks report of a directory entry, built only from the platform's verified sources: registration (an official register lists the VASP: the EU's interim MiCA register, the public TRISA directory or the global LEI register), licence (a MiCA authorisation is on record and current), domain (the entry's domain matches its TRP endpoint or the website its register gives) and lei (its LEI record is active, issued and consistent with the entry's names and country). The essentials component of dd_score is the share of decided checks that pass. Empty until the weekly directory refresh first checks the entry, and always empty for a Didit network member.", + "properties": { + "level": { + "type": "string", + "enum": [ + "essential" + ] + }, + "checked_at": { + "type": "string", + "format": "date-time" + }, + "checks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "enum": [ + "registration", + "licence", + "domain", + "lei" + ] + }, + "status": { + "type": "string", + "enum": [ + "pass", + "fail", + "unknown" + ], + "description": "unknown when no source on record can decide the check." + }, + "reason": { + "type": "string", + "description": "Machine-readable code of the verdict." + }, + "detail": { + "type": "string", + "description": "One-line English explanation." + } + } + } + }, + "passed": { + "type": "integer" + }, + "failed": { + "type": "integer" + }, + "unknown": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "decision": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecision" + } + ], + "nullable": true, + "description": "Your application's decision on this VASP, or for an entity with no decision of its own its brand's (inherited_from names the brand). Null when there is none. Private to your application." + }, + "entity_screening": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreening" + } + ], + "nullable": true, + "description": "Your application's latest sanctions and adverse-media screening of this VASP. Null when you never screened it. Private to your application." + }, + "questionnaires": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaire" + }, + "description": "Your application's due-diligence questionnaires with this VASP, newest first. Private to your application." + } + }, + "example": { + "id": "0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91", + "kind": "directory", + "name": "Example Counterparty VASP", + "lei": "529900EXAMPLECPTY001", + "jurisdiction": "DE", + "domains": [ + "counterparty.example" + ], + "reachable": true, + "aliases": [ + { + "name": "Example Counterparty", + "kind": "trading_name" + } + ], + "networks": [ + { + "network": "GTR", + "code": "EXAMPLE01", + "listed_name": "Example Counterparty VASP", + "country": "DE", + "verified_at": "2026-10-12T09:30:00Z", + "last_seen_at": "2026-10-12T09:30:00Z", + "source": "staff" + } + ], + "routes": [ + { + "rail": "GTR", + "status": "unavailable", + "reason": "no_membership", + "via": "" + }, + { + "rail": "EMAIL", + "status": "available", + "reason": "", + "via": "contact" + } + ], + "umbrella": null, + "entities": [], + "contacts": [ + { + "id": "6c1f9b8e-2d4a-4f7e-8b3c-5a9d0e1f2a3b", + "email": "travelrule@counterparty.example", + "name": "Travel Rule desk", + "source": "staff", + "scope": "platform", + "verified_at": null + } + ], + "dd_status": "NOT_STARTED", + "dd_score": 62, + "dd_score_breakdown": { + "jurisdiction": { + "points": 25.0, + "max": 25, + "reason": "jurisdiction_not_high_risk", + "detail": "DEU is not on the high-risk jurisdiction list" + }, + "verification": { + "points": 3.8, + "max": 15, + "reason": "dd_status_not_started", + "detail": "due diligence status NOT_STARTED" + }, + "protocol": { + "points": 6.0, + "max": 15, + "reason": "email_only", + "detail": "reachable over the email fallback only" + }, + "lei": { + "points": 10.0, + "max": 10, + "reason": "lei_valid", + "detail": "valid LEI on record" + }, + "history": { + "points": 5.0, + "max": 10, + "reason": "no_history", + "detail": "no exchange history" + }, + "essentials": { + "points": 7.5, + "max": 15, + "reason": "no_essential_checks", + "detail": "no decided essential check yet" + }, + "responsiveness": { + "points": 5.0, + "max": 10, + "reason": "responsiveness_unknown", + "detail": "0 decided recent requests, too few to judge" + } + }, + "dd_assessed_at": "2026-10-12T09:30:00Z", + "dd_score_semantic": "higher_is_safer", + "risk_level": "MEDIUM", + "responsiveness": { + "status": "UNKNOWN", + "answered": 0, + "total": 0, + "computed_at": "2026-10-12T09:30:00Z" + }, + "essential_checks": {}, + "reachable_rails": [ + "GTR", + "EMAIL" + ], + "is_didit_customer": false, + "decision": { + "decision": "APPROVED", + "reason": "Licensed in DE, questionnaire reviewed", + "decided_by": "", + "updated_at": "2026-10-14T10:05:00Z", + "inherited_from": null + }, + "entity_screening": { + "id": "7a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d", + "status": "Approved", + "total_hits": 0, + "score": null, + "screened_at": "2026-10-14T09:58:00Z", + "fresh": true, + "flagged": false + }, + "questionnaires": [ + { + "id": "8b7a6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d", + "status": "ANSWERED", + "form_version": "1", + "sent_to": "travelrule@counterparty.example", + "sent_at": "2026-09-02T08:00:00Z", + "expires_at": "2026-10-02T08:00:00Z", + "answered_at": "2026-09-04T15:20:00Z", + "valid_until": "2027-09-04T15:20:00Z", + "current": true, + "answers": { + "legal_name": "Example Counterparty VASP, incorporated in the example jurisdiction.", + "licensing": "Crypto-asset service provider authorised under MiCA by its national regulator, licence active.", + "jurisdictions": "EU and EEA. We do not serve the United States.", + "aml_program": "Written AML/CFT policy, annual independent review, last completed in March 2026.", + "compliance_officer": "Head of Compliance, reachable at compliance@counterparty.example.", + "sanctions_screening": "Customers at onboarding and daily; counterparties on every transfer.", + "transaction_monitoring": "Rule-based monitoring with analyst review; suspicious activity reports filed with the FIU.", + "travel_rule": "TRP and email; we answer requests within one business day.", + "custody": "Segregated customer wallets held with a qualified custodian, insured.", + "ownership": "No shareholder holds 25 percent or more; parent company Example Holding Group.", + "record_keeping": "Five-year record-keeping for Travel Rule and transaction data, as our regulator requires.", + "data_protection": "Encrypted at rest and in transit, stored in the EU." + } + } + ] + } + }, + "TravelRuleCounterpartyDecision": { + "type": "object", + "description": "Your application's decision on a counterparty VASP. Private to your application: it never changes the shared directory entry and another customer never sees it. It acts only through your transaction rules (the counterparty_dd_status rule field and the travel-rule-counterparty-blocked and travel-rule-counterparty-restricted presets); it never changes how a transfer is routed.", + "properties": { + "decision": { + "type": "string", + "enum": [ + "APPROVED", + "RESTRICTED", + "BLOCKED" + ], + "description": "APPROVED: your team cleared the counterparty. RESTRICTED: transfers to it may go ahead after review. BLOCKED: your team refused it." + }, + "reason": { + "type": "string", + "maxLength": 255, + "description": "Free-text reason you gave, empty when none." + }, + "decided_by": { + "type": "string", + "description": "The console user who made or last changed the decision. Empty when an API key made it." + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "inherited_from": { + "type": "object", + "nullable": true, + "description": "The brand whose decision this entity follows because you made none on the entity itself. Null when the decision is the counterparty's own.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + } + } + } + } + }, + "TravelRuleCounterpartyDecisionResponse": { + "type": "object", + "properties": { + "decision": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecision" + } + ], + "nullable": true, + "description": "The decision that applies to the counterparty for your application: its own, else (for an entity of a brand) the brand's. Null when there is none." + } + } + }, + "TravelRuleCounterpartyDecisionRequest": { + "type": "object", + "required": [ + "decision" + ], + "properties": { + "decision": { + "type": "string", + "enum": [ + "APPROVED", + "RESTRICTED", + "BLOCKED" + ] + }, + "reason": { + "type": "string", + "maxLength": 255, + "description": "Optional. Replaces the previous reason; omit it to clear it." + } + } + }, + "TravelRuleCounterpartyScreening": { + "type": "object", + "description": "One sanctions and adverse-media screening of the counterparty entity, run for and billed to your application. Private to your application: another customer never sees it.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Approved: no possible match needs attention. In Review or Declined: a possible sanctions or adverse-media match." + }, + "total_hits": { + "type": "integer", + "description": "How many possible matches the screening returned." + }, + "score": { + "type": "number", + "nullable": true, + "description": "The highest risk score among the possible matches. Null when nothing matched." + }, + "screened_at": { + "type": "string", + "format": "date-time" + }, + "fresh": { + "type": "boolean", + "description": "Whether the screening is younger than fresh_days (30 days). A fresh screening is reused instead of billed again." + }, + "flagged": { + "type": "boolean", + "description": "true while the screening is fresh and its status is In Review or Declined. While your latest screening is flagged and counterparty screening stays on, counterparty_risk_level is HIGH in your rules." + } + } + }, + "TravelRuleCounterpartyScreeningStatus": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Your counterparty_screening_enabled Travel Rule setting. Off by default; nothing is screened or billed while it is off." + }, + "price": { + "type": "string", + "description": "Decimal string, in USD: what one screening costs your organization, the Company AML Screening price." + }, + "fresh_days": { + "type": "integer", + "description": "How many days a screening stays fresh and is reused." + }, + "screening": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreening" + } + ], + "nullable": true, + "description": "Your latest screening of the counterparty. Null when you never screened it." + } + } + }, + "TravelRuleCounterpartyScreeningRequest": { + "type": "object", + "properties": { + "refresh": { + "type": "boolean", + "default": false, + "description": "true screens again (and bills again) even when a fresh screening exists." + } + } + }, + "TravelRuleCounterpartyScreeningResult": { + "type": "object", + "properties": { + "outcome": { + "type": "string", + "enum": [ + "screened", + "reused" + ], + "description": "screened: a new screening ran and was billed (201). reused: a fresh screening was returned and nothing was billed (200)." + }, + "screening": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreening" + } + } + }, + "TravelRuleCounterpartyQuestionnaire": { + "type": "object", + "description": "One due-diligence questionnaire your application sent the counterparty: Didit's standard questionnaire, emailed to a compliance address on record with a single-use answer link. Private to your application.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "SENT", + "ANSWERED", + "EXPIRED" + ], + "description": "SENT: waiting for answers. ANSWERED: the counterparty answered every question. EXPIRED: the answer link closed unanswered (its deadline passed, or a newer questionnaire replaced it), or answers passed valid_until." + }, + "form_version": { + "type": "string", + "description": "Version of Didit's standard questionnaire the answers belong to. Currently 1." + }, + "sent_to": { + "type": "string", + "format": "email" + }, + "sent_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "The answer-by deadline, 30 days after sending. The link stops working after it." + }, + "answered_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "valid_until": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "How long the answers stay current: 365 days after answering. Null until answered." + }, + "current": { + "type": "boolean", + "description": "true while the questionnaire is ANSWERED and valid_until has not passed." + }, + "answers": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The answers keyed by question id: legal_name, licensing, jurisdictions, aml_program, compliance_officer, sanctions_screening, transaction_monitoring, travel_rule, custody, ownership, record_keeping, data_protection. Each answer is up to 2,000 characters. Empty until answered." + } + } + }, + "TravelRuleCounterpartyQuestionnaireRequest": { + "type": "object", + "properties": { + "sent_to": { + "type": "string", + "format": "email", + "description": "Optional. A compliance address on record for the counterparty: for a directory entry its compliance address or a contact your application sees, for a Didit network member its confirmed compliance email. Default: the address a Travel Rule email to the counterparty goes to (your own contact first, then a platform contact, then the entry's compliance address)." + } + } + }, + "TravelRuleCounterpartyQuestionnaireList": { + "type": "object", + "properties": { + "questionnaires": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaire" + }, + "description": "Newest first." + } + } + }, + "TravelRulePickupInfo": { + "type": "object", + "description": "The pending exchange behind an emailed pickup link, as the counterparty's response page shows it.", + "properties": { + "reference": { + "type": "string", + "format": "uuid", + "description": "The transfer's uuid, as the sending VASP's console shows it." + }, + "requested_at": { + "type": "string", + "format": "date-time" + }, + "originator_vasp_name": { + "type": "string" + }, + "originator_vasp_lei": { + "type": "string" + }, + "recipient_vasp_name": { + "type": "string", + "description": "The counterparty VASP the request is addressed to." + }, + "language": { + "type": "string", + "description": "The language of the request email and the response page, from the counterparty's jurisdiction." + }, + "amount": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "fiat_amount": { + "type": "string", + "description": "The converted value the sender gave, or empty." + }, + "fiat_currency": { + "type": "string" + }, + "chain": { + "type": "string" + }, + "beneficiary_wallet": { + "type": "string" + }, + "status": { + "type": "string" + }, + "ivms_version": { + "type": "string" + }, + "deadline_at": { + "type": "string", + "format": "date-time", + "description": "When the link stops answering." + }, + "reminders_sent": { + "type": "integer" + }, + "contact": { + "type": "string", + "description": "The `?contact=` value of the link, echoed back; empty when the link has none." + }, + "decline_reasons": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "data_mismatch", + "not_our_customer", + "wallet_not_hosted", + "sanctions", + "risk_appetite", + "jurisdiction", + "insufficient_data", + "other" + ] + }, + "description": "The values `decline_reason` accepts." + }, + "join_invitation": { + "type": "object", + "description": "Whether the counterparty holds a live invitation to join Didit for free. The invitation link travels only in the email to the invited address, so `url` is empty here.", + "properties": { + "eligible": { + "type": "boolean" + }, + "url": { + "type": "string" + } + } + }, + "originator_data": { + "type": "object", + "description": "The originator fields the sending VASP shares (`data_sharing_fields`). The name and the wallet are always included." + }, + "beneficiary_data": { + "type": "object" + }, + "ivms_request": { + "type": "object", + "description": "The same request as an IVMS 101 object. Left out while `data_masked` is true." + }, + "data_masked": { + "type": "boolean", + "description": "Always false on this endpoint: an email request is outbound, and the sender's own user is never masked." + } + } + }, + "TravelRulePickupRespondRequest": { + "type": "object", + "required": [ + "decision" + ], + "properties": { + "decision": { + "type": "string", + "enum": [ + "accept", + "decline" + ] + }, + "beneficiary_name": { + "type": "string", + "description": "The beneficiary's name as your records hold it, run through the sending VASP's name rule. `ivms_response` takes precedence when it names the beneficiary.", + "maxLength": 255 + }, + "decline_reason": { + "type": "string", + "enum": [ + "", + "data_mismatch", + "not_our_customer", + "wallet_not_hosted", + "sanctions", + "risk_appetite", + "jurisdiction", + "insufficient_data", + "other" + ], + "description": "Why you decline. Recorded on the sending VASP's transfer timeline. Empty (the default) when you give no reason." + }, + "decline_detail": { + "type": "string", + "maxLength": 512, + "description": "Free text; required when `decision` is `decline` and `decline_reason` is `other`." + }, + "ivms_response": { + "type": "object", + "description": "Your full IVMS 101 response. Its beneficiary name wins over `beneficiary_name`." + }, + "contact": { + "type": "string", + "format": "uuid", + "description": "The contact the link was sent to, from the link's `?contact=` value. When the body leaves it out, the `contact` query parameter is used." + } + } + }, + "TravelRuleInboundRequest": { + "type": "object", + "required": [ + "chain", + "txid", + "wallet_address", + "amount", + "currency" + ], + "properties": { + "chain": { + "type": "string", + "description": "Chain the deposit settled on, for example `ETH`. Stored lowercase and compared case-insensitively with your wallet address book entries, so use the same chain token there.", + "maxLength": 32 + }, + "txid": { + "type": "string", + "description": "On-chain transaction hash. Deduped together with wallet_address.", + "maxLength": 128 + }, + "wallet_address": { + "type": "string", + "description": "The destination wallet you received the deposit on. An XRP X-address is split into its classic address and tag.", + "maxLength": 128 + }, + "amount": { + "type": "string", + "description": "Deposit amount as a decimal string, zero or greater, with up to 18 decimal places." + }, + "currency": { + "type": "string", + "description": "Deposit asset, e.g. \"USDC\".", + "maxLength": 16 + }, + "originator_data": { + "type": "object", + "description": "Originator details from the sending VASP, if known, as a flat object such as `{\"name\": \"...\"}`. Didit reads name (or full_name), first_name, last_name and vendor_data from it.", + "additionalProperties": true + }, + "beneficiary_data": { + "type": "object", + "description": "Beneficiary details (your customer), as a flat object such as `{\"name\": \"...\"}`. Didit matches name against the holder_name of the matched address book entry, and sets wallet_address and chain from the request.", + "additionalProperties": true + }, + "originating_vasp": { + "type": "object", + "description": "Optional description of the sending VASP. It stays on this transfer only (`metadata.originating_vasp`, and the transfer's originator VASP in GET /v3/travel-rule/transfers/); it never creates, edits or links a shared VASP directory entry.", + "properties": { + "name": { + "type": "string", + "maxLength": 255 + }, + "lei": { + "type": "string", + "maxLength": 20, + "description": "Optional. A valid 20-character ISO 17442 LEI, or empty." + }, + "travel_address": { + "type": "string", + "description": "The sender's travel address, recorded on this transfer.", + "maxLength": 512 + } + } + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "Optional. The deposit's destination tag or memo on a memo network: on a shared deposit address it is what tells your customers apart, and the address-book entry for that memo is matched. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`. Alias: `destinationTag` or `destination_tag`." + }, + "utxo": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleUtxo" + } + ], + "description": "Optional, UTXO chains only. The deposit transaction's inputs and outputs: the sender's wallet is the declared `originator_data.wallet_address`, else the largest input." + } + } + }, + "TravelRuleInboundResponse": { + "type": "object", + "properties": { + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Didit transaction UUID for the inbound deposit." + }, + "txn_id": { + "type": "string", + "description": "txn_id of the inbound transaction: Didit-generated when Didit created the transaction, or your own when an unclaimed deposit was reused." + }, + "created": { + "type": "boolean", + "description": "true when this call registered the transfer (HTTP 201); false when a transfer for the same txid and wallet_address already existed (HTTP 200)." + }, + "travel_rule": { + "$ref": "#/components/schemas/TravelRuleTransferDetail" + } + } + }, + "TravelRuleTestVaspInboundRequest": { + "type": "object", + "required": [ + "beneficiary_wallet_address" + ], + "properties": { + "beneficiary_wallet_address": { + "type": "string", + "description": "The destination wallet address. The Didit Test VASP sends to it, matched against your wallet address book on the chain of the asset. A wallet with no matching entry is accepted and the inbound transaction is booked ON_HOLD." + }, + "scenario": { + "type": "string", + "enum": [ + "accept", + "name_mismatch" + ], + "default": "accept", + "description": "What the Didit Test VASP declares the beneficiary to be. `accept` declares the holder's own name; `name_mismatch` declares a name no holder agrees with. The decision itself is always yours." + }, + "chain": { + "type": "string", + "default": "ETH", + "description": "Chain of the asset." + }, + "amount": { + "type": "string", + "default": "1", + "description": "Amount as a decimal string." + }, + "currency": { + "type": "string", + "default": "ETH", + "description": "Asset code." + }, + "reference": { + "type": "string", + "description": "Optional idempotency key. A repeat with the same value returns the transfer the first call booked." + } + } + }, + "TravelRuleTestVaspInboundResponse": { + "type": "object", + "properties": { + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Didit transaction UUID of the inbound transfer." + }, + "txn_id": { + "type": "string", + "description": "Didit-generated txn_id." + }, + "scenario": { + "type": "string", + "description": "The scenario that ran." + }, + "travel_rule_status": { + "type": "string", + "description": "The status your application's own decision resolved to." + }, + "detail": { + "type": "string", + "description": "Why the transfer resolved to that status." + }, + "test_vasp": { + "type": "boolean", + "description": "Always true: the sender was the Didit Test VASP." + }, + "travel_rule": { + "$ref": "#/components/schemas/TravelRuleTransferDetail" + } + } + }, + "TravelRuleWidgetSessionCreate": { + "type": "object", + "properties": { + "wallet_address": { + "type": "string", + "description": "The wallet address to verify. Optional; when empty, the address must be supplied when a proof is started.", + "maxLength": 128 + }, + "chain": { + "type": "string", + "description": "Chain token for the address, for example `ETH`, `BTC` or `SOL`. Use the same token as in your wallet address book. Message signing and the Satoshi test are offered only for a supported chain. The address book entry the proof verifies stores it under its canonical identifier (ETH, BTC, SOL, TRX).", + "maxLength": 32 + }, + "holder_name": { + "type": "string", + "description": "Name of the wallet holder, used for the beneficiary name match.", + "maxLength": 255 + }, + "vendor_data": { + "type": "string", + "description": "Your internal identifier for the holder, carried onto the created address-book entry.", + "maxLength": 255 + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Optional. Didit transaction UUID of a Travel Rule transfer to advance when the proof verifies.", + "nullable": true + }, + "satoshi_deposit_address": { + "type": "string", + "description": "Optional. A deposit address you control; supplying it enables the SATOSHI_TEST proof method.", + "maxLength": 128 + }, + "callback_url": { + "type": "string", + "description": "Optional. Absolute http(s) URL where the widget returns the customer after completion.", + "maxLength": 512 + }, + "expires_in_minutes": { + "type": "integer", + "description": "Optional. Lifetime of the minted link, in minutes. Automatically extended to cover the Satoshi test window when SATOSHI_TEST is offered.", + "default": 60, + "minimum": 5, + "maximum": 10080 + } + } + }, + "TravelRuleWidgetSessionResponse": { + "type": "object", + "properties": { + "widget_session_id": { + "type": "string", + "format": "uuid" + }, + "token": { + "type": "string", + "description": "Opaque session token embedded in the widget URL." + }, + "url": { + "type": "string", + "description": "The hosted wallet-ownership widget URL. Open it verbatim in the system browser (never inside a mobile SDK webview)." + }, + "expires_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleWidgetContext": { + "type": "object", + "properties": { + "vasp_name": { + "type": "string", + "description": "Legal name from your VASP profile, shown to the customer in the widget. Empty when no profile exists." + }, + "wallet_address": { + "type": "string" + }, + "chain": { + "type": "string", + "description": "The chain the session was minted with, as you sent it." + }, + "chain_family": { + "type": "string", + "nullable": true, + "enum": [ + "EVM", + "BTC", + "SOL", + "TRON", + null + ], + "description": "The key family behind chain: EVM (Ethereum, BNB Chain, Polygon, Arbitrum, Base, Optimism, Avalanche, Linea and the other EVM chains), BTC, SOL or TRON. One EVM key controls the same address on every EVM chain. Null when no signature can be verified on the chain." + }, + "holder_name": { + "type": "string" + }, + "allowed_methods": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SCREENSHOT", + "SATOSHI_TEST", + "SELF_DECLARATION" + ] + }, + "description": "Proof methods available for this session. MESSAGE_SIGNING requires a chain with a chain_family, SATOSHI_TEST requires a satoshi_deposit_address on the session and a chain whose deposits Didit can read back, SCREENSHOT requires the allow_screenshot_proof setting, and SELF_DECLARATION requires the allow_self_declaration setting." + }, + "transfer_status": { + "type": "string", + "nullable": true, + "description": "Status of the linked Travel Rule transfer. Null when the session was minted without a transaction_id." + }, + "callback_url": { + "type": "string", + "description": "Where the widget returns the customer after completion. Empty when not set." + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Non-null once a proof has been verified." + }, + "proofs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleWidgetProofSummary" + } + }, + "max_attempts": { + "type": "integer", + "description": "Rejected wallet proofs this session allows." + }, + "attempts_used": { + "type": "integer", + "description": "Rejected proofs so far." + }, + "attempts_remaining": { + "type": "integer", + "description": "When 0, starting another proof answers 409 and you mint a new widget session." + }, + "customization": { + "type": "object", + "nullable": true, + "description": "The application's whitelabel customization (colors, logo, font family) when white label is enabled - the widget renders with the same branding as the verification flow. Null otherwise." + } + } + }, + "TravelRuleWidgetProofSummary": { + "type": "object", + "properties": { + "proof_id": { + "type": "string", + "format": "uuid" + }, + "method": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SATOSHI_TEST", + "SCREENSHOT", + "SELF_DECLARATION" + ] + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ], + "description": "PENDING: the end user picked this method and the proof is not decided yet. A failed signature or a test deposit that is not readable or confirmed yet keeps the proof PENDING with a failure_reason, so the end user can try again; a submitted SATOSHI_TEST deposit is rechecked automatically until its window closes, and a submitted SCREENSHOT waits for your review. VERIFIED: ownership is proven. REJECTED: the test deposit is genuinely wrong (another sender, not the expected amount to the deposit address, older than the challenge, or a transaction that already proved another wallet) or your reviewer rejected the screenshot; failure_reason says which. A rejected proof is final and uses one of the session's attempts. EXPIRED: the proof can no longer be decided (its window closed, the end user switched method, the widget session lapsed, or the wallet's holder changed); failure_reason says which." + }, + "wallet_address": { + "type": "string" + }, + "chain": { + "type": "string", + "description": "Canonical chain identifier of the address book entry the proof is for, e.g. ETH, BTC, SOL, TRX." + }, + "deposit_address": { + "type": "string", + "nullable": true, + "description": "Deposit address the customer must send the Satoshi test amount to. Present only on a SATOSHI_TEST proof." + }, + "expected_amount": { + "type": "string", + "nullable": true, + "description": "Exact on-chain amount, in the deposit's native currency, the Satoshi test deposit must match. Present only on a SATOSHI_TEST proof." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Deadline for this proof's own Satoshi test window. Present only on a SATOSHI_TEST proof; the widget session's own expires_at is extended to cover it when SATOSHI_TEST is offered." + }, + "challenge": { + "type": "string", + "nullable": true, + "description": "Didit-issued message the customer signs with their wallet. Present only on a MESSAGE_SIGNING proof." + }, + "rejection_reason": { + "type": "string", + "description": "Why the proof was rejected, as an English sentence for the end user. Present only when `status` is REJECTED; empty when your reviewer rejected a screenshot (your note stays with you)." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "attempts": { + "type": "integer", + "description": "How many times evidence was submitted for this proof (signatures, transaction ids, uploads)." + }, + "failure_reason": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleWalletProofFailureReason" + } + ], + "description": "Why the last attempt fell short, or why the proof closed. Empty when nothing failed." + } + } + }, + "CaseSubject": { + "type": "object", + "nullable": true, + "description": "The single user or business the case is anchored to.", + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "business" + ] + }, + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "external_id": { + "type": "string", + "nullable": true, + "description": "The subject's vendor_data." + }, + "country": { + "type": "string", + "nullable": true, + "description": "Three-letter country code of the subject (ISO 3166-1 alpha-3), or null when it is not known. For a business, its country of incorporation. For a person, the issuing country of an identity document from their verifications; a person with documents from several countries shows one of them, and ICAO document codes such as `XXA` can appear.", + "example": "ESP" + }, + "portrait": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived signed URL of the person's portrait image. Always null for a business, and null for a person without a portrait. The URL expires, so download the image when you read the case instead of storing the URL." + } + } + }, + "CaseAssignee": { + "type": "object", + "nullable": true, + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "full_name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + } + } + }, + "CaseBlueprintSummary": { + "type": "object", + "nullable": true, + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "maxLength": 24 + } + } + }, + "CaseContentConfig": { + "type": "object", + "description": "The six togglable sections that drive which tabs and fields appear on the case page. Unknown sections or keys are rejected.", + "properties": { + "checklist": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "optional": { + "type": "boolean", + "description": "When true, a case can be resolved with incomplete checklist items." + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "maxLength": 255 + }, + "order": { + "type": "integer" + } + } + } + } + } + }, + "aml_control": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "financial": { + "type": "boolean" + }, + "identity": { + "type": "boolean" + } + } + }, + "identity": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "personal_information": { + "type": "boolean" + }, + "applicant_tags": { + "type": "boolean" + }, + "contact_information": { + "type": "boolean" + }, + "risk_overview": { + "type": "boolean" + }, + "poi_documents": { + "type": "boolean" + }, + "poa_documents": { + "type": "boolean" + } + } + }, + "verifications": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "summary": { + "type": "boolean" + } + } + }, + "reporting": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "fiu_reports": { + "type": "boolean" + } + } + }, + "financial": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "payment_methods": { + "type": "boolean" + }, + "transactions": { + "type": "boolean" + } + } + } + } + }, + "CaseNote": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "comment": { + "type": "string" + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + } + }, + "is_internal": { + "type": "boolean" + }, + "attachments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "s3_key": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "file_size": { + "type": "integer" + }, + "url": { + "type": "string", + "format": "uri" + } + } + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "CaseEvent": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "event_type": { + "type": "string", + "enum": [ + "CREATED", + "STATUS_CHANGED", + "ASSIGNED", + "UNASSIGNED", + "RESOLVED", + "REOPENED", + "LINK_ADDED", + "LINK_REMOVED", + "NOTE_ADDED", + "NOTE_PINNED", + "NOTE_UNPINNED", + "PRIORITY_CHANGED", + "DUE_DATE_CHANGED", + "TAG_ADDED", + "TAG_REMOVED", + "ESCALATED", + "TRANSFERRED", + "CHECKLIST_COMPLETED", + "REPORT_CREATED", + "REPORT_FINALIZED", + "SUBMITTED_FOR_APPROVAL", + "APPROVAL_GRANTED", + "APPROVAL_REJECTED", + "ENTITY_ACTION", + "APPROVAL_INVALIDATED", + "CUSTOMER_REQUEST", + "STAGE_CHANGED", + "TEAM_CHANGED", + "CHECKER_ASSIGNED", + "CHECKER_UNASSIGNED" + ] + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "previous_status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ], + "nullable": true + }, + "new_status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ], + "nullable": true + }, + "metadata": { + "type": "object" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "CaseLink": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "linked_entity_type": { + "type": "string", + "enum": [ + "session", + "business_session", + "transaction" + ], + "nullable": true + }, + "linked_entity_uuid": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "linked_entity_summary": { + "type": "object", + "nullable": true, + "description": "session_id/session_number/status for session and business_session links; uuid/txn_id/status for transaction links." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "CaseChecklistItem": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string", + "maxLength": 255 + }, + "is_completed": { + "type": "boolean" + }, + "completed_by": { + "type": "string", + "format": "email", + "nullable": true + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "order": { + "type": "integer" + } + } + }, + "CaseBlueprintListItem": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "maxLength": 24 + }, + "description": { + "type": "string", + "nullable": true + }, + "assignment_method": { + "type": "string", + "enum": [ + "MANUAL", + "AUTOMATIC" + ] + }, + "assignee_pool": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "max_active_cases": { + "type": "integer", + "nullable": true + }, + "escalation_enabled": { + "type": "boolean" + }, + "transfer_enabled": { + "type": "boolean" + }, + "require_resolution_note": { + "type": "boolean" + }, + "is_active": { + "type": "boolean" + }, + "preset_key": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "version": { + "type": "integer", + "readOnly": true + }, + "default_deadline_hours": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 8760 + } + } + }, + "CaseBlueprintDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "maxLength": 24 + }, + "description": { + "type": "string", + "nullable": true + }, + "assignment_method": { + "type": "string", + "enum": [ + "MANUAL", + "AUTOMATIC" + ] + }, + "assignee_pool": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "max_active_cases": { + "type": "integer", + "nullable": true + }, + "escalation_enabled": { + "type": "boolean" + }, + "transfer_enabled": { + "type": "boolean" + }, + "require_resolution_note": { + "type": "boolean" + }, + "is_active": { + "type": "boolean" + }, + "preset_key": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "escalation_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "escalation_blueprint_name": { + "type": "string", + "nullable": true + }, + "four_eyes_checker_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "four_eyes_checker_blueprint_name": { + "type": "string", + "nullable": true + }, + "content_config": { + "type": "object", + "description": "The six togglable sections that drive which tabs and fields appear on the case page. Unknown sections or keys are rejected.", + "properties": { + "checklist": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "optional": { + "type": "boolean", + "description": "When true, a case can be resolved with incomplete checklist items." + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "maxLength": 255 + }, + "order": { + "type": "integer" + } + } + } + } + } + }, + "aml_control": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "financial": { + "type": "boolean" + }, + "identity": { + "type": "boolean" + } + } + }, + "identity": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "personal_information": { + "type": "boolean" + }, + "applicant_tags": { + "type": "boolean" + }, + "contact_information": { + "type": "boolean" + }, + "risk_overview": { + "type": "boolean" + }, + "poi_documents": { + "type": "boolean" + }, + "poa_documents": { + "type": "boolean" + } + } + }, + "verifications": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "summary": { + "type": "boolean" + } + } + }, + "reporting": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "fiu_reports": { + "type": "boolean" + } + } + }, + "financial": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "payment_methods": { + "type": "boolean" + }, + "transactions": { + "type": "boolean" + } + } + } + } + }, + "creation_sources": { + "type": "array", + "description": "Connectors currently routing new cases into this blueprint.", + "items": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "aml", + "rule", + "workflow" + ] + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + }, + "receives_escalation_from": { + "type": "array", + "description": "Other blueprints whose Escalation target points at this one.", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "version": { + "type": "integer", + "readOnly": true + }, + "default_deadline_hours": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 8760 + } + } + }, + "CaseListItem": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "case_number": { + "type": "string", + "nullable": true, + "description": "Auto-assigned, e.g. CS-0001." + }, + "title": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ] + }, + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ], + "nullable": true + }, + "pending_resolution": { + "type": "object", + "nullable": true, + "description": "Set only while a 4-eyes resolution is staged on the checker blueprint, awaiting approve or reject-approval.", + "properties": { + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ] + }, + "resolution_notes": { + "type": "string", + "nullable": true + }, + "maker_email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email of the officer who submitted the resolution. Null when an API key submitted it: that resolution has no recorded maker, so approve and reject-approval return 403 for every caller." + }, + "maker_name": { + "type": "string", + "nullable": true + }, + "maker_user_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Member user uuid of the officer who submitted the resolution. Null when an API key submitted it. That member cannot be named as `checker`." + }, + "maker_blueprint_id": { + "type": "string", + "format": "uuid" + }, + "staged_at": { + "type": "string", + "format": "date-time" + } + } + }, + "checker": { + "$ref": "#/components/schemas/CaseChecker" + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "source": { + "type": "string", + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ] + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "subject": { + "$ref": "#/components/schemas/CaseSubject" + }, + "assigned_to": { + "$ref": "#/components/schemas/CaseAssignee" + }, + "assigned_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "team": { + "$ref": "#/components/schemas/CaseTeamSummary" + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "created_by_email": { + "type": "string", + "nullable": true + }, + "created_by_name": { + "type": "string", + "nullable": true + }, + "blueprint": { + "$ref": "#/components/schemas/CaseBlueprintSummary" + }, + "links_count": { + "type": "integer" + }, + "notes_count": { + "type": "integer" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "application_id": { + "type": "string", + "format": "uuid" + }, + "application_name": { + "type": "string" + }, + "organization_id": { + "type": "string", + "format": "uuid" + }, + "organization_name": { + "type": "string" + } + } + }, + "CaseDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "case_number": { + "type": "string", + "nullable": true + }, + "title": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ] + }, + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ], + "nullable": true + }, + "resolution_notes": { + "type": "string", + "nullable": true + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "source": { + "type": "string", + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ] + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "subject": { + "$ref": "#/components/schemas/CaseSubject" + }, + "assigned_to": { + "$ref": "#/components/schemas/CaseAssignee" + }, + "assigned_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "team": { + "$ref": "#/components/schemas/CaseTeamSummary" + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resolved_by_email": { + "type": "string", + "nullable": true + }, + "pending_resolution": { + "type": "object", + "nullable": true, + "description": "Set only while a 4-eyes resolution is staged on the checker blueprint, awaiting approve or reject-approval.", + "properties": { + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ] + }, + "resolution_notes": { + "type": "string", + "nullable": true + }, + "maker_email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email of the officer who submitted the resolution. Null when an API key submitted it: that resolution has no recorded maker, so approve and reject-approval return 403 for every caller." + }, + "maker_name": { + "type": "string", + "nullable": true + }, + "maker_user_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Member user uuid of the officer who submitted the resolution. Null when an API key submitted it. That member cannot be named as `checker`." + }, + "maker_blueprint_id": { + "type": "string", + "format": "uuid" + }, + "staged_at": { + "type": "string", + "format": "date-time" + } + } + }, + "checker": { + "$ref": "#/components/schemas/CaseChecker" + }, + "metadata": { + "type": "object" + }, + "blueprint": { + "$ref": "#/components/schemas/CaseBlueprintDetail" + }, + "notes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseNote" + }, + "description": "Newest 50 notes." + }, + "events": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseEvent" + }, + "description": "Newest 100 events." + }, + "links": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseLink" + } + }, + "links_count": { + "type": "integer" + }, + "notes_count": { + "type": "integer" + }, + "checklist_items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseChecklistItem" + } + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "ReportTemplateDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code." + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "report_type_display": { + "type": "string" + }, + "field_config": { + "type": "object", + "description": "Reusable goAML header defaults. Template-level obligatory fields: rentity_id, submission_code, and currency_code_local (3-letter). Optional: enabled_fields (which optional report blocks to include) plus reporting_person, location, reason, and action defaults. Per-filing header fields (action, submission_date, transactions, jurisdiction) are enforced when the report is validated or finalized, not on the template." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "RegulatoryReportDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "case_uuid": { + "type": "string", + "format": "uuid" + }, + "case_number": { + "type": "string", + "nullable": true + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "report_type_display": { + "type": "string" + }, + "report_code": { + "type": "string", + "description": "goAML report code derived from report_type, e.g. STR, CTR, BCR." + }, + "report_number": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "enum": [ + "DRAFT", + "VALIDATED", + "FINALIZED" + ] + }, + "status_display": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2 + }, + "rentity_id": { + "type": "string", + "nullable": true, + "description": "Reporting entity ID issued by the FIU." + }, + "rentity_branch": { + "type": "string", + "nullable": true + }, + "submission_code": { + "type": "string", + "enum": [ + "E", + "M" + ] + }, + "entity_reference": { + "type": "string", + "nullable": true + }, + "fiu_ref_number": { + "type": "string", + "nullable": true + }, + "currency_code_local": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "nullable": true + }, + "submission_date": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reporting_person": { + "type": "object" + }, + "location": { + "type": "object" + }, + "reason": { + "type": "string", + "nullable": true + }, + "action": { + "type": "string", + "nullable": true + }, + "activity": { + "type": "string", + "nullable": true + }, + "narrative": { + "type": "object", + "description": "Five Ws narrative sections.", + "properties": { + "who": { + "type": "string" + }, + "what": { + "type": "string" + }, + "when": { + "type": "string" + }, + "where": { + "type": "string" + }, + "why": { + "type": "string" + }, + "how": { + "type": "string" + } + } + }, + "report_indicators": { + "type": "array", + "items": { + "type": "string" + } + }, + "included_fields": { + "type": "array", + "items": { + "type": "string" + } + }, + "transactions": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "validation_errors": { + "type": "array", + "description": "Result of the most recent validation; each entry is a {field, message} object.", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + }, + "validated_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "finalized_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "created_by_email": { + "type": "string", + "nullable": true + }, + "created_by_name": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TransactionRuleCondition": { + "type": "object", + "required": [ + "field", + "operator" + ], + "properties": { + "field": { + "type": "string", + "description": "Transaction field path to evaluate. Core fields: amount, currency, direction, action_type, score, severity, transaction_type, payment_details, preferred_currency_amount (falls back to amount_in_default_currency), default_currency_code, subject_country, counterparty_country, subject_vendor_data, counterparty_vendor_data, subject_device_fingerprint, counterparty_device_fingerprint, subject_browser_family, subject_browser_version, subject_platform, subject_accept_language, subject_session_age_ms, subject_ip_country, counterparty_ip_country, subject_ip_address, counterparty_ip_address, subject_payment_method_type, subject_payment_method_country, subject_payment_method_fingerprint, subject_payment_method_account_id (and the counterparty_payment_method_* equivalents), travel_rule_status, travel_rule_required, travel_rule_obligations_count, counterparty_dd_status (your decision on the Travel Rule counterparty: APPROVED, RESTRICTED or BLOCKED, an entity following its brand's), counterparty_risk_level (LOW, MEDIUM or HIGH; HIGH while your own counterparty screening is flagged), counterparty_jurisdiction (the counterparty VASP's country), counterparty_responsiveness (RESPONSIVE, UNRESPONSIVE or UNKNOWN), subject_days_since_previous_transaction, tags, and custom_values. for any key submitted in custom_properties on transaction create. Country-code fields accept ISO-2 or ISO-3; both sides are normalized to ISO-3 before comparison." + }, + "operator": { + "type": "string", + "enum": [ + "eq", + "ne", + "gt", + "gte", + "lt", + "lte", + "in", + "not_in", + "contains", + "not_contains", + "contains_any", + "regex", + "fuzzy_match", + "exists", + "is_not_empty", + "is_not_null", + "not_exists", + "is_empty", + "is_null" + ], + "description": "Comparison operator. in/not_in test membership (value may be a list or a single scalar). contains/not_contains are case-insensitive substring checks against a string value. contains_any takes a list of strings and matches when any is a case-insensitive substring of the field value. regex matches the value pattern against the field. fuzzy_match compares against the value string with the required `score` threshold (0-100). gt/gte/lt/lte never match when either side is missing. exists/is_not_empty/is_not_null (aliases) and not_exists/is_empty/is_null (aliases) are presence checks that ignore `value`." + }, + "value": { + "description": "Comparison value. Its shape depends on the operator and value_type; omit for the presence-check operators." + }, + "value_type": { + "type": "string", + "enum": [ + "list", + "field", + "relative_date" + ], + "description": "Optional value interpretation. `list`: value is a List UUID and the list's entries become the comparison value. `field`: value is another field path, compared field-to-field. `relative_date`: value is a relative-date object, e.g. {\"direction\": \"past\", \"unit\": \"days\", \"amount\": 30}." + }, + "score": { + "type": "number", + "minimum": 0, + "maximum": 100, + "description": "Required when operator is fuzzy_match: the 0-100 similarity threshold." + }, + "group_index": { + "type": "integer", + "description": "Optional grouped logic. When any condition carries group_index, conditions sharing an index form a group and grouped evaluation replaces evaluation_mode." + }, + "group_logic": { + "type": "string", + "enum": [ + "AND", + "OR" + ], + "default": "AND", + "description": "How conditions combine WITHIN a group." + }, + "groups_logic": { + "type": "string", + "enum": [ + "AND", + "OR" + ], + "default": "OR", + "description": "How group outcomes combine ACROSS groups. Read from the first condition." + } + }, + "description": "A condition reads only the transaction being evaluated - it never filters the aggregation window." + }, + "TransactionRuleAggregation": { + "type": "object", + "required": [ + "value" + ], + "properties": { + "metric": { + "type": "string", + "enum": [ + "count", + "sum", + "max", + "min", + "avg", + "distinct_count" + ], + "default": "count", + "description": "How the matching historical transactions are aggregated. unique_count is accepted as an alias of distinct_count." + }, + "field": { + "type": "string", + "default": "amount", + "description": "Field aggregated by sum/max/min/avg/distinct_count. Ignored for count." + }, + "operator": { + "type": "string", + "enum": [ + "eq", + "ne", + "gt", + "gte", + "lt", + "lte" + ], + "default": "eq", + "description": "Comparison applied to the computed metric." + }, + "value": { + "type": "number", + "description": "Threshold the computed metric is compared against." + }, + "window": { + "type": "string", + "pattern": "^\\d+(m|h|d)$", + "default": "1d", + "description": "Look-back window ending at the transaction's txn_date: m (MINUTES, not months), h (hours) or d (days), e.g. 30m, 24h, 7d, 30d." + }, + "filters": { + "type": "object", + "additionalProperties": true, + "description": "Narrows which historical transactions are aggregated. Keys are field paths from the condition field catalog; a value of \"__current__\" resolves to the same field's value on the transaction being evaluated (e.g. {\"subject_vendor_data\": \"__current__\"} scopes the window to the same subject), and a list value means membership. Filters compare by equality or membership ONLY - there is no lt/gte filter, so a range such as \"historical transactions under 10,000\" cannot be expressed here. Put the range on the metric instead (metric max with operator lt keeps every transaction in the window below the threshold)." + } + }, + "description": "Velocity check over the application's historical transactions inside the window. Every aggregation entry must match for the rule to match, regardless of evaluation_mode. An aggregation constrains only the window; the transaction being evaluated is constrained by scope and conditions, so repeat direction, currency or transaction_type on both halves when the rule promises them." + }, + "TransactionRuleScope": { + "type": "object", + "properties": { + "transaction_types": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Restrict to transaction types, e.g. finance, kyc, travel_rule, user_event. Empty or missing = no restriction." + }, + "directions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ] + }, + "description": "Restrict to transaction directions (case-insensitive on write)." + }, + "action_types": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Restrict to your action_type values, e.g. withdrawal, deposit." + } + }, + "description": "Which transactions the rule applies to. Only these three keys are accepted." + }, + "TransactionRuleAction": { + "oneOf": [ + { + "$ref": "#/components/schemas/TransactionRuleActionAddScore" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionChangeStatus" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionAddTags" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionAddNote" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionAddToList" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionOpenCase" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "add_score": "#/components/schemas/TransactionRuleActionAddScore", + "change_status": "#/components/schemas/TransactionRuleActionChangeStatus", + "add_tags": "#/components/schemas/TransactionRuleActionAddTags", + "add_note": "#/components/schemas/TransactionRuleActionAddNote", + "add_to_list": "#/components/schemas/TransactionRuleActionAddToList", + "open_case": "#/components/schemas/TransactionRuleActionOpenCase" + } + }, + "description": "One rule action. A rule can carry several; all are applied together when it matches. Rules in TEST mode record what they would have done without applying any action." + }, + "TransactionRuleCreateRequest": { + "type": "object", + "required": [ + "title", + "category", + "mode" + ], + "properties": { + "title": { + "type": "string", + "description": "Rule name. Unique per application among non-deleted rules; duplicates return 400." + }, + "description": { + "type": "string", + "nullable": true + }, + "category": { + "type": "string", + "description": "Risk theme used for grouping and reporting, e.g. finance, aml_ctf, anomaly_detection, fatf, device_intelligence, crypto_monitoring, travel_rule, responsible_gaming, e_commerce." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ], + "description": "ACTIVE evaluates live. TEST evaluates and records rule runs but never touches score or status - the recommended starting mode. DISABLED is skipped." + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ], + "default": "ALL", + "description": "Whether every condition must match (ALL) or at least one (ANY). Ignored when conditions use group_index." + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + } + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + } + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAction" + } + }, + "metadata": { + "type": "object", + "description": "Free-form metadata stored on the rule." + } + }, + "description": "Creates a CUSTOM rule. Rules in ACTIVE mode take effect immediately for new transactions - create in TEST mode and backtest first." + }, + "TransactionRuleUpdateRequest": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Rule name. Unique per application among non-deleted rules; duplicates return 400." + }, + "description": { + "type": "string", + "nullable": true + }, + "category": { + "type": "string", + "description": "Risk theme used for grouping and reporting, e.g. finance, aml_ctf, anomaly_detection, fatf, device_intelligence, crypto_monitoring, travel_rule, responsible_gaming, e_commerce." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ], + "description": "ACTIVE evaluates live. TEST evaluates and records rule runs but never touches score or status - the recommended starting mode. DISABLED is skipped." + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ], + "default": "ALL", + "description": "Whether every condition must match (ALL) or at least one (ANY). Ignored when conditions use group_index." + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + } + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + } + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAction" + } + }, + "metadata": { + "type": "object", + "description": "Free-form metadata stored on the rule." + } + }, + "description": "Partial update. Custom rules accept every field; library (PRESET) rules accept only mode - anything else returns 400." + }, + "TransactionRuleDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "title": { + "type": "string", + "description": "Rule name. Unique per application among non-deleted rules; duplicates return 400." + }, + "description": { + "type": "string", + "nullable": true + }, + "category": { + "type": "string", + "description": "Risk theme used for grouping and reporting, e.g. finance, aml_ctf, anomaly_detection, fatf, device_intelligence, crypto_monitoring, travel_rule, responsible_gaming, e_commerce." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ], + "description": "ACTIVE evaluates live. TEST evaluates and records rule runs but never touches score or status - the recommended starting mode. DISABLED is skipped." + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ], + "default": "ALL", + "description": "Whether every condition must match (ALL) or at least one (ANY). Ignored when conditions use group_index." + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + } + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + } + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAction" + } + }, + "metadata": { + "type": "object", + "description": "Free-form metadata stored on the rule." + }, + "severity": { + "type": "string", + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ], + "nullable": true, + "description": "Informational severity carried by library rules; not settable on custom rules." + }, + "source": { + "type": "string", + "enum": [ + "PRESET", + "CUSTOM" + ], + "description": "PRESET rules come from the library (install/uninstall, mode-only edits); CUSTOM rules are fully yours." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Stable library identifier; null for custom rules." + }, + "latest_triggered_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "run_count": { + "type": "integer", + "description": "How many transactions matched this rule." + }, + "approved_count": { + "type": "integer" + }, + "reviewed_count": { + "type": "integer" + }, + "declined_count": { + "type": "integer" + }, + "awaiting_user_count": { + "type": "integer" + }, + "approved_pct": { + "type": "number" + }, + "reviewed_pct": { + "type": "number" + }, + "declined_pct": { + "type": "number" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TransactionRuleBacktestRequest": { + "type": "object", + "properties": { + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + }, + "default": [] + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + }, + "default": [] + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ], + "default": "ALL" + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "period_days": { + "type": "integer", + "minimum": 1, + "maximum": 365, + "default": 90 + } + }, + "description": "Hypothetical rule configuration to evaluate against recent transactions. Read-only and free: nothing is created or modified. Send the same conditions, aggregation, evaluation_mode AND scope as the rule you intend to activate. Omitted conditions, aggregation and scope add no constraints of their own; dropping scope can include directions or transaction types the saved rule skips. Omitted evaluation_mode defaults to ALL (which can reduce matches for an ANY rule), and omitted period_days defaults to 90 days, not unlimited history." + }, + "TransactionRuleBacktestResult": { + "type": "object", + "properties": { + "evaluated": { + "type": "integer", + "description": "Transactions evaluated (most recent first, capped)." + }, + "matched": { + "type": "integer", + "description": "Transactions the configuration would have matched." + }, + "affected_entities": { + "type": "integer", + "description": "Distinct subjects (by vendor_data) among the matches." + }, + "period_days": { + "type": "integer" + } + } + }, + "TransactionRuleLibraryEntry": { + "type": "object", + "properties": { + "library_key": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "category": { + "type": "string" + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ] + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + } + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + } + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAction" + } + }, + "metadata": { + "type": "object" + }, + "bundle": { + "type": "string", + "nullable": true, + "description": "Functional family, e.g. finance, aml_ctf, crypto_monitoring, travel_rule, responsible_gaming." + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "industries": { + "type": "array", + "items": { + "type": "string" + } + }, + "source": { + "type": "string" + }, + "is_installed": { + "type": "boolean", + "description": "Whether this preset is currently installed on the application." + } + } + }, + "TransactionRuleInstallRequest": { + "type": "object", + "properties": { + "library_keys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Specific presets to install or uninstall." + }, + "bundle": { + "type": "string", + "description": "Install or uninstall every preset in this bundle instead." + } + }, + "description": "Provide library_keys OR bundle." + }, + "TransactionRuleInstallResult": { + "type": "object", + "properties": { + "installed_library_keys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "POST (install) only: presets installed by this call." + }, + "installed_count": { + "type": "integer", + "description": "POST (install) only." + }, + "uninstalled_count": { + "type": "integer", + "description": "DELETE (uninstall) only: presets removed by this call." + } + } + }, + "WalletScreeningResult": { + "type": "object", + "properties": { + "provider": { + "type": "string", + "description": "Provider that performed the screening (e.g. `merklescience`, `crystal`)." + }, + "screening_type": { + "type": "string", + "enum": [ + "WALLET_SCREENING" + ], + "description": "Always `WALLET_SCREENING` for this endpoint." + }, + "risk_score": { + "type": "integer", + "description": "Normalised 0-100 risk score. Higher means greater exposure to risky entities. Merkle Science grades an address 0-5, which maps to 0, 15, 35, 55, 75 or 95." + }, + "severity": { + "type": "string", + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ], + "description": "Risk bucket derived from `risk_score`: `0-9` UNKNOWN, `10-39` LOW, `40-69` MEDIUM, `70-89` HIGH, `90-100` CRITICAL. `UNKNOWN` is the lowest band, not a separate no-data state: `risk_score` 0 (the common clean-address case) means no adverse assessment, while a non-zero score in the 1-9 range is a real but sub-LOW signal - read the `risk_score`, not just the band. Never treat `UNKNOWN` as an affirmative low-risk or clear rating; do not display it as a pass." + }, + "status": { + "type": "string", + "enum": [ + "SCREENED", + "PENDING", + "ERROR" + ], + "description": "Screening outcome status." + }, + "summary": { + "type": "string", + "description": "Human-readable summary of the screening result." + }, + "wallet_address": { + "type": "string", + "description": "The screened address, echoed back." + }, + "transaction_hash": { + "type": "string", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "blockchain": { + "type": "string", + "description": "The blockchain that was screened." + }, + "block_timestamp": { + "type": "string", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "total_value_usd": { + "type": "number", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "fee_usd": { + "type": "number", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "sender_rows": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Transaction screening field, shared with the result shape. Always empty on a wallet screening result." + }, + "receiver_rows": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Transaction screening field, shared with the result shape. Always empty on a wallet screening result." + }, + "transaction_flows": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Transaction screening field, shared with the result shape. Always empty on a wallet screening result." + }, + "transaction_flows_status": { + "type": "string", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "transaction_flows_error": { + "type": "string", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "source_of_funds": { + "type": "array", + "description": "Where the address received funds from, attributed by entity. Each entry is an exposure breakdown.", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "description": "Normalised risk category, e.g. `exchange_licensed`, `dex`, `defi`, `payment_processor`, `mixer`, `darknet_market`, `sanctioned`, `stolen_funds`, `high_risk_exchange`." + }, + "percentage": { + "type": "number", + "description": "Share of the direction's total exposure, 0-100." + }, + "amount_usd": { + "type": "number", + "nullable": true, + "description": "USD value attributed to this entity." + }, + "entity_name": { + "type": "string", + "nullable": true, + "description": "Name of the attributed entity, for example an exchange, a mixer or a sanctioned group." + }, + "entity_type": { + "type": "string", + "nullable": true, + "description": "Provider entity type (e.g. `Exchange`, `DeFi`, `Sanctions`, `Theft`)." + }, + "entity_subtype": { + "type": "string", + "nullable": true, + "description": "More granular subtype (e.g. `Mandatory KYC and AML`, `OFAC`, `Yield Aggregator`)." + }, + "hops": { + "type": "integer", + "nullable": true, + "description": "Number of hops to the entity (1 for a direct counterparty), when the provider reports one. Always `null` for Merkle Science, which reports only whether exposure is direct or indirect: use `exposure_type`." + }, + "description": { + "type": "string", + "nullable": true, + "description": "`entity_type / entity_subtype`, as the provider labels the entity." + }, + "is_direct": { + "type": "boolean", + "description": "True when `exposure_type` is `direct`." + }, + "exposure_direction": { + "type": "string", + "enum": [ + "incoming", + "outgoing", + "connected" + ], + "description": "Whether the funds came from (`incoming`) or went to (`outgoing`) this entity." + }, + "exposure_type": { + "type": "string", + "enum": [ + "direct", + "indirect" + ], + "description": "`direct` (1 hop) or `indirect` (multi-hop) exposure." + }, + "provider_entity_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for the entity." + }, + "provider_entity_type_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for `entity_type`." + }, + "provider_entity_subtype_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for `entity_subtype`." + }, + "country": { + "type": "string", + "nullable": true, + "description": "Primary ISO 3166-1 alpha-2 country of the entity. Empty or `null` when the provider does not know it." + }, + "raw_amount": { + "type": "number", + "nullable": true, + "description": "Amount in the asset's own unit, when the provider reports it." + }, + "raw_currency": { + "type": "string", + "nullable": true, + "description": "Unit of `raw_amount`, when the provider reports it." + } + } + } + }, + "destination_of_funds": { + "type": "array", + "description": "Where the address sent funds to, attributed by entity. Same item shape as `source_of_funds` with `exposure_direction` = `outgoing`.", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "description": "Normalised risk category, e.g. `exchange_licensed`, `dex`, `defi`, `payment_processor`, `mixer`, `darknet_market`, `sanctioned`, `stolen_funds`, `high_risk_exchange`." + }, + "percentage": { + "type": "number", + "description": "Share of the direction's total exposure, 0-100." + }, + "amount_usd": { + "type": "number", + "nullable": true, + "description": "USD value attributed to this entity." + }, + "entity_name": { + "type": "string", + "nullable": true, + "description": "Name of the attributed entity, for example an exchange, a mixer or a sanctioned group." + }, + "entity_type": { + "type": "string", + "nullable": true, + "description": "Provider entity type (e.g. `Exchange`, `DeFi`, `Sanctions`, `Theft`)." + }, + "entity_subtype": { + "type": "string", + "nullable": true, + "description": "More granular subtype (e.g. `Mandatory KYC and AML`, `OFAC`, `Yield Aggregator`)." + }, + "hops": { + "type": "integer", + "nullable": true, + "description": "Number of hops to the entity (1 for a direct counterparty), when the provider reports one. Always `null` for Merkle Science, which reports only whether exposure is direct or indirect: use `exposure_type`." + }, + "description": { + "type": "string", + "nullable": true, + "description": "`entity_type / entity_subtype`, as the provider labels the entity." + }, + "is_direct": { + "type": "boolean", + "description": "True when `exposure_type` is `direct`." + }, + "exposure_direction": { + "type": "string", + "enum": [ + "incoming", + "outgoing", + "connected" + ], + "description": "Whether the funds came from (`incoming`) or went to (`outgoing`) this entity." + }, + "exposure_type": { + "type": "string", + "enum": [ + "direct", + "indirect" + ], + "description": "`direct` (1 hop) or `indirect` (multi-hop) exposure." + }, + "provider_entity_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for the entity." + }, + "provider_entity_type_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for `entity_type`." + }, + "provider_entity_subtype_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for `entity_subtype`." + }, + "country": { + "type": "string", + "nullable": true, + "description": "Primary ISO 3166-1 alpha-2 country of the entity. Empty or `null` when the provider does not know it." + }, + "raw_amount": { + "type": "number", + "nullable": true, + "description": "Amount in the asset's own unit, when the provider reports it." + }, + "raw_currency": { + "type": "string", + "nullable": true, + "description": "Unit of `raw_amount`, when the provider reports it." + } + } + } + }, + "counterparty_connections": { + "type": "array", + "description": "Entities the provider attributes the screened address to. For Merkle Science these come from the address's own owner and user tags, so the same entity can appear twice, and they carry no fund flows: `received_usd`, `sent_usd`, `received_hops` and `sent_hops` are `null` and `percentage` is 0. Do not route on `risk_level` or `is_direct` here: use `sanctions_hit`, `dominant_risk_category` and `risk_factors[].is_high_risk` for the verdict, and `exposure_type` in `source_of_funds`/`destination_of_funds` for directness.", + "items": { + "type": "object", + "properties": { + "entity_name": { + "type": "string", + "nullable": true, + "description": "Attributed entity name." + }, + "entity_type": { + "type": "string", + "nullable": true, + "description": "Provider entity type." + }, + "entity_subtype": { + "type": "string", + "nullable": true, + "description": "Provider entity subtype." + }, + "risk_level": { + "type": "string", + "description": "Lowercase and provider-dependent. For Merkle Science it is the address's own 0-5 risk grade (`no_risk`, `low`, `medium_low`, `medium`, `high`, `critical`), raised to `high` when the entity's category is high risk; it is not a separate grade of the entity." + }, + "categories": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Normalised risk categories of the entity." + }, + "received_usd": { + "type": "number", + "nullable": true, + "description": "USD received from the entity, when the provider reports it." + }, + "sent_usd": { + "type": "number", + "nullable": true, + "description": "USD sent to the entity, when the provider reports it." + }, + "received_hops": { + "type": "integer", + "nullable": true, + "description": "Hops to the entity on the receiving side, when the provider reports them." + }, + "sent_hops": { + "type": "integer", + "nullable": true, + "description": "Hops to the entity on the sending side, when the provider reports them." + }, + "percentage": { + "type": "number", + "description": "Share of the address's exposure, when the provider reports it; otherwise 0." + }, + "is_direct": { + "type": "boolean", + "description": "Always `true` for Merkle Science, whose connections are attributions rather than counterparties." + }, + "country": { + "type": "string", + "nullable": true, + "description": "Country of the entity, when known." + }, + "provider_entity_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for the entity." + } + } + } + }, + "network_nodes": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Exposure graph nodes for transaction screening. Empty on a wallet screening result." + }, + "network_edges": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Exposure graph edges for transaction screening. Empty on a wallet screening result." + }, + "sanctions_hit": { + "type": "boolean", + "description": "True if the address has direct or indirect sanctions exposure." + }, + "pep_counterparty": { + "type": "boolean", + "description": "Politically exposed person exposure. `null` when no PEP finding is available, which is every result today because no provider reports one; `false` only for an explicit negative screening result. Do not read `null` as a completed PEP check.", + "nullable": true + }, + "dominant_risk_category": { + "type": "string", + "nullable": true, + "description": "Highest-weighted high-risk category, or `null` when none is dominant (e.g. `sanctioned`, `mixer`, `stolen_funds`)." + }, + "risk_factors": { + "type": "array", + "description": "Ranked explanation of `risk_score`: what produced this verdict. Providers grade an address on their own model and return only a verdict (Merkle Science returns an integer 0-5), so the score alone is not actionable. Ordered high-risk first, then by the value that moved through each driver, capped at the five strongest; `source_of_funds` and `destination_of_funds` always carry the complete breakdown. A high `risk_score` with no `is_high_risk` entry means the provider reported no sanctions or attributed-entity match and graded the address purely on its own exposure model - `summary` states this explicitly.", + "items": { + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": [ + "attribution", + "exposure" + ], + "description": "`attribution` is what the provider says the address itself is (its own tag or cluster); `exposure` is where its funds actually moved." + }, + "category": { + "type": "string", + "description": "Normalised risk category, same vocabulary as the exposure breakdown." + }, + "label": { + "type": "string", + "description": "The provider's own wording for the driver (e.g. `Gambling / Crypto only`), which is what you would also see in the provider's console." + }, + "entity_name": { + "type": "string", + "nullable": true, + "description": "Attributed entity behind the driver, for example a gambling site or an exchange." + }, + "direction": { + "type": "string", + "enum": [ + "incoming", + "outgoing", + "connected", + "unknown", + "attribution" + ], + "description": "Direction of the fund flow, `attribution` when the driver is the address's own tag, or `unknown` when the provider did not report a direction." + }, + "exposure_type": { + "type": "string", + "enum": [ + "direct", + "indirect", + "unknown", + "attribution" + ], + "description": "`direct` (1 hop) or `indirect` (multi-hop), `attribution` for the address's own tag, or `unknown` when the provider did not state how many hops away the counterparty is. Treat `unknown` as unclassified, not as `direct`." + }, + "amount_usd": { + "type": "number", + "format": "float", + "nullable": true, + "description": "USD value attributed to this driver." + }, + "percentage": { + "type": "number", + "format": "float", + "description": "Share of the address's TOTAL screened volume, both directions combined - unlike `percentage` inside `source_of_funds`/`destination_of_funds`, which is normalised per direction. Comparable and summable across the whole list." + }, + "is_high_risk": { + "type": "boolean", + "description": "Whether the category is one Didit treats as high risk (`sanctioned`, `mixer`, `stolen_funds`, `ransomware`, `scam`, `darknet_market`, `terrorist_financing`, `child_exploitation`, `hacked_exchange`, `high_risk_exchange`, `seized_funds`)." + }, + "description": { + "type": "string", + "description": "One-line rendering of the driver, e.g. `9.01% indirect outgoing exposure to Gambling / Crypto only (Example Gambling)`." + } + } + } + }, + "raw_response": { + "type": "object", + "additionalProperties": true, + "description": "The provider's own payload. Its shape is provider-specific and can change without notice: read the normalised fields instead of parsing it." + }, + "report_signature": { + "type": "string", + "description": "Opaque HMAC-SHA256 signature over this exact result, scoped to your application. Not meaningful on its own — repost the full response body, unmodified and including this field, to `POST /v3/wallet-screening/pdf/` to render it as a PDF. The PDF endpoint rejects the request with `400` if `report_signature` is missing or if any signed field (e.g. `risk_score`, `severity`, `sanctions_hit`) was edited before repost." + } + }, + "required": [ + "provider", + "screening_type", + "risk_score", + "severity", + "status", + "summary", + "wallet_address", + "blockchain", + "sanctions_hit", + "dominant_risk_category", + "source_of_funds", + "destination_of_funds", + "counterparty_connections", + "report_signature" + ] + }, + "NetworkRiskDisplay": { + "type": "object", + "properties": { + "score": { + "type": "integer", + "description": "Risk score, 0-100." + }, + "band": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ], + "description": "Band the score renders as. high >= 70, medium >= 40, otherwise low." + }, + "bar": { + "type": "number", + "format": "float", + "description": "Normalized 0-1 value for rendering a risk bar." + } + } + }, + "NetworkNodeTag": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string", + "nullable": true, + "description": "Null for guard tags, whose color token the renderer owns." + }, + "source": { + "type": "string", + "enum": [ + "subject_tag", + "session_tag", + "transaction_tag", + "signal_guard" + ], + "description": "Where the tag came from: a tag on the user/business, a tag on a session in this network, a tag on a transaction a party belongs to, or a signal guard condition (e.g. hub, datacenter IP)." + }, + "category": { + "type": "string", + "enum": [ + "customer", + "quality" + ], + "nullable": true, + "description": "customer for customer-defined tags, quality for signal-quality guard tags. Null when not applicable." + } + } + }, + "NetworkListItem": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "network_id": { + "type": "string", + "description": "Human-readable per-organization sequential id, e.g. N-0001." + }, + "name": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "enum": [ + "active", + "in_review", + "resolved", + "dismissed" + ], + "description": "Network lifecycle status. Distinct from the per-applicant outcome_status inside a network (approved/in_review/declined/not_completed)." + }, + "risk": { + "$ref": "#/components/schemas/NetworkRiskDisplay" + }, + "pattern_types": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "exact_same_device", + "similar_device", + "same_ip_address", + "same_address", + "similar_selfie_backgrounds", + "similar_poa_documents", + "same_document_number", + "same_phone_number", + "same_email" + ] + }, + "description": "The clustering patterns detected in this network." + }, + "signal_types": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ip_address", + "device", + "face", + "document_number", + "phone", + "email", + "address" + ] + }, + "description": "The distinct signal types present in this network." + }, + "member_counts": { + "type": "object", + "description": "Counts of members by category (e.g. total, per outcome_status)." + }, + "member_status": { + "type": "object", + "description": "Breakdown of members by outcome_status." + }, + "signal_coverage": { + "type": "object", + "description": "Per signal_type coverage of this network's members." + }, + "first_activity_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_activity_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "NetworkDetail": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkListItem" + }, + { + "type": "object", + "properties": { + "stats": { + "type": "object", + "description": "Aggregate stats for the network (e.g. member totals)." + }, + "fraud_tags": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Fraud-pattern tags surfaced on the network." + }, + "risk_signals": { + "type": "object", + "description": "Risk-signal summaries: residential_proxy_reuse, email_intelligence, phone_intelligence." + }, + "reviewed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reviewed_by": { + "type": "string", + "nullable": true + }, + "review_reason": { + "type": "string", + "nullable": true + } + } + } + ], + "properties": { + "notes_count": { + "type": "integer", + "minimum": 0, + "readOnly": true, + "description": "Number of private analyst notes in this investigation." + } + } + }, + "NetworkGraphNode": { + "type": "object", + "description": "A subject node or a signal node, distinguished by kind. Subject nodes (kind: vendor_user | vendor_business | transaction_party) carry id, kind, subject, node_status (approved | in_review | declined | not_completed), and tags. Signal nodes (kind: signal) carry id, kind, uuid, signal_type, subject_count, coverage, is_hub, is_datacenter_ip, is_high_collision_device, node_status (signal_active | signal_guarded), tags, risk_evidence, first_seen_at, and last_seen_at.", + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "vendor_user", + "vendor_business", + "transaction_party", + "signal" + ] + } + } + }, + "NetworkGraphEdge": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "source": { + "type": "string", + "description": "Subject node id." + }, + "target": { + "type": "string", + "description": "Signal node id." + }, + "subject_kind": { + "type": "string", + "enum": [ + "vendor_user", + "vendor_business", + "transaction_party" + ] + }, + "signal_type": { + "type": "string", + "enum": [ + "ip_address", + "device", + "face", + "document_number", + "phone", + "email", + "address" + ] + }, + "confidence": { + "type": "number", + "format": "float", + "description": "1.0 for an exact match." + }, + "observation_count": { + "type": "integer" + }, + "first_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "NetworkGraph": { + "type": "object", + "properties": { + "network": { + "$ref": "#/components/schemas/NetworkDetail" + }, + "depth": { + "type": "integer", + "nullable": true, + "description": "Effective depth applied, or null when unbounded (depth=all)." + }, + "truncated": { + "type": "boolean", + "description": "True if the neighborhood was cut off by depth." + }, + "nodes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkGraphNode" + } + }, + "edges": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkGraphEdge" + } + }, + "metrics": { + "type": "object", + "properties": { + "node_count": { + "type": "integer" + }, + "edge_count": { + "type": "integer" + }, + "hub_signal_count": { + "type": "integer" + } + } + } + } + }, + "NetworkMember": { + "type": "object", + "properties": { + "subject": { + "type": "object", + "description": "The vendor_user, vendor_business, or transaction_party this member represents." + }, + "outcome_status": { + "type": "string", + "description": "The subject's verification/decision outcome." + }, + "node_status": { + "type": "string", + "enum": [ + "approved", + "in_review", + "declined", + "not_completed", + "signal_active", + "signal_guarded" + ], + "description": "For a member row this is always one of the subject-outcome values: approved, in_review, declined, or not_completed." + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkNodeTag" + } + }, + "risk": { + "$ref": "#/components/schemas/NetworkRiskDisplay" + }, + "signal_coverage": { + "type": "object" + }, + "shared_infrastructure": { + "type": "object" + }, + "joined_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "provenance": { + "type": "object", + "properties": { + "session_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "business_session_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "transaction_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "NetworkSignal": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "signal_type": { + "type": "string", + "enum": [ + "ip_address", + "device", + "face", + "document_number", + "phone", + "email", + "address" + ] + }, + "subject_count": { + "type": "integer" + }, + "coverage": { + "type": "number", + "format": "float" + }, + "is_hub": { + "type": "boolean", + "description": "True when this signal links an unusually large number of subjects." + }, + "is_datacenter_ip": { + "type": "boolean" + }, + "is_high_collision_device": { + "type": "boolean" + }, + "node_status": { + "type": "string", + "enum": [ + "signal_active", + "signal_guarded" + ] + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkNodeTag" + } + }, + "risk_evidence": { + "type": "object" + }, + "first_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "NetworkTimelineEvent": { + "type": "object", + "properties": { + "event_type": { + "type": "string", + "description": "e.g. network_detected, signal_observed, network_in_review, network_resolved, network_dismissed." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "label": { + "type": "string" + }, + "metadata": { + "type": "object" + } + } + }, + "NetworkMap": { + "type": "object", + "properties": { + "network": { + "$ref": "#/components/schemas/NetworkDetail" + }, + "convergence_count": { + "type": "integer", + "description": "Number of distinct address/location points members converge on." + }, + "countries": { + "type": "array", + "items": { + "type": "string" + } + }, + "points": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "country": { + "type": "string", + "nullable": true + }, + "country_code": { + "type": "string", + "nullable": true + }, + "city": { + "type": "string", + "nullable": true + }, + "latitude": { + "type": "number", + "format": "float", + "nullable": true + }, + "longitude": { + "type": "number", + "format": "float", + "nullable": true + }, + "observed_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + } + } + } + }, + "EntityNetworkMembership": { + "type": "object", + "properties": { + "network": { + "$ref": "#/components/schemas/NetworkListItem" + }, + "subject": { + "type": "object" + }, + "signal_types": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ip_address", + "device", + "face", + "document_number", + "phone", + "email", + "address" + ] + } + }, + "signal_count": { + "type": "integer" + }, + "joined_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "RiskSignals": { + "type": "object", + "description": "Optional device and network context observed by your application. All fields are optional. Passing them feeds cross-session match detection in Networks.", + "properties": { + "ip": { + "type": "string", + "format": "ip", + "description": "IPv4 or IPv6 address observed for the end user.", + "example": "192.0.2.1" + }, + "device_id": { + "type": "string", + "maxLength": 255, + "description": "Stable device identifier collected by your application (Android `ANDROID_ID`, iOS `identifierForVendor`)." + }, + "device_platform": { + "type": "string", + "enum": [ + "android", + "ios", + "ipados", + "tvos", + "web" + ], + "description": "Platform of the end user's device." + }, + "device_model": { + "type": "string", + "maxLength": 255, + "description": "Device model reported by your application." + }, + "os_version": { + "type": "string", + "maxLength": 64, + "description": "Operating-system version." + }, + "app_version": { + "type": "string", + "maxLength": 64, + "description": "Version of your application." + }, + "user_agent": { + "type": "string", + "maxLength": 512, + "description": "Browser or app user agent." + } + } + }, + "PhoneRiskRequest": { + "type": "object", + "required": [ + "phone_number" + ], + "properties": { + "phone_number": { + "type": "string", + "maxLength": 20, + "description": "Phone number to score. It is validated and normalized to E.164.", + "example": "+14155552671" + }, + "country_code": { + "type": "string", + "maxLength": 3, + "description": "Optional ISO 3166-1 alpha-2 or alpha-3 country hint for intelligence coverage.", + "example": "US" + }, + "signals": { + "$ref": "#/components/schemas/RiskSignals" + }, + "vendor_data": { + "type": "string", + "description": "Your stable reference for this request. Stored on the API session and echoed in the response." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Your non-sensitive JSON metadata. Stored on the API session and echoed in the response." + }, + "enable_social": { + "type": "boolean", + "default": false, + "description": "When true, also look up the social footprint of the number - the messaging, social, e-commerce and professional platforms it is registered on. Billed as one `phone_social` unit per request in addition to this endpoint's own price.", + "example": true + } + } + }, + "EmailRiskRequest": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "Email address to score.", + "example": "alice@example.com" + }, + "country_code": { + "type": "string", + "maxLength": 3, + "description": "Optional ISO 3166-1 alpha-2 or alpha-3 country hint for intelligence coverage.", + "example": "US" + }, + "signals": { + "$ref": "#/components/schemas/RiskSignals" + }, + "vendor_data": { + "type": "string", + "description": "Your stable reference for this request. Stored on the API session and echoed in the response." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Your non-sensitive JSON metadata. Stored on the API session and echoed in the response." + }, + "enable_social": { + "type": "boolean", + "default": false, + "description": "When true, also look up the social footprint of the address - the email, social, professional, e-commerce and entertainment platforms it is registered on. Billed as one `email_social` unit per request in addition to this endpoint's own price.", + "example": true + } + } + }, + "IPRiskRequest": { + "type": "object", + "required": [ + "ip_address" + ], + "properties": { + "ip_address": { + "type": "string", + "format": "ip", + "description": "IPv4 or IPv6 address to score.", + "example": "8.8.8.8" + }, + "claimed_country": { + "type": "string", + "maxLength": 3, + "description": "Optional ISO 3166-1 alpha-2 or alpha-3 country claimed by the user or session. Used to calculate `country_mismatch`.", + "example": "ESP" + }, + "user_agent": { + "type": "string", + "maxLength": 512, + "description": "User agent observed by your backend. Stored as request context; it does not provide device fingerprinting." + }, + "vendor_data": { + "type": "string", + "description": "Your stable reference for this request. Stored on the API session and echoed in the response." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Your non-sensitive JSON metadata. Stored on the API session and echoed in the response." + } + } + }, + "RiskWarning": { + "type": "object", + "required": [ + "feature", + "risk", + "log_type", + "short_description", + "long_description" + ], + "properties": { + "feature": { + "type": "string", + "description": "Feature that produced the warning." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk code. New values can be added over time." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Risk-specific context, or `null`." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity used by the decision rules." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation." + } + } + }, + "RiskLifecycleEvent": { + "type": "object", + "required": [ + "type", + "timestamp", + "details", + "fee" + ], + "properties": { + "type": { + "type": "string", + "description": "Machine-readable event type. New values can be added over time." + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the event happened." + }, + "details": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Event-specific payload, or `null`." + }, + "fee": { + "type": "number", + "description": "USD amount billed for this event." + } + } + }, + "PhoneMatchItem": { + "type": "object", + "description": "Another session of your application that used the same phone number with a different `vendor_data`.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the other verification that used this phone number." + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of that session." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of that session." + }, + "verification_date": { + "type": "string", + "description": "When that session was created (`YYYY-MM-DDTHH:MM:SSZ`)." + }, + "phone_number": { + "type": "string", + "description": "The matched phone number." + }, + "status": { + "type": "string", + "description": "Status of that session (e.g., `Approved`, `Declined`, `In Progress`)." + }, + "is_blocklisted": { + "type": "boolean", + "description": "Whether the phone number is on your application's blocklist." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "For API-type sessions, the standalone API that created it; `null` for regular verification sessions." + }, + "source": { + "type": "string", + "description": "Where the match was found. Usually `session`; a blocklist hit via an organization list entry uses `list_entry`." + } + } + }, + "EmailMatchItem": { + "type": "object", + "description": "Another session of your application that used the same email address with a different `vendor_data`.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the other verification that used this email address." + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of that session." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of that session." + }, + "verification_date": { + "type": "string", + "description": "When that session was created (`YYYY-MM-DDTHH:MM:SSZ`)." + }, + "email": { + "type": "string", + "description": "The matched email address." + }, + "status": { + "type": "string", + "description": "Status of that session (e.g., `Approved`, `Declined`, `In Progress`)." + }, + "is_blocklisted": { + "type": "boolean", + "description": "Whether the email address is on your application's blocklist." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "For API-type sessions, the standalone API that created it; `null` for regular verification sessions." + }, + "source": { + "type": "string", + "description": "Where the match was found. Usually `session`; a blocklist hit via an organization list entry uses `list_entry`." + } + } + }, + "PhoneIntelligence": { + "type": "object", + "description": "Normalized phone-intelligence block. Present only when the intelligence add-on ran for this lookup; when present it is also exposed under the `enrichment` alias with identical values.", + "properties": { + "score": { + "type": "number", + "nullable": true, + "description": "Normalized 0-100 risk score. Higher is riskier." + }, + "phone_type_risk": { + "type": "string", + "nullable": true, + "description": "`Low`, `Medium` or `High`, derived from the score." + }, + "phone_trust_index": { + "type": "number", + "nullable": true, + "description": "Inverse of the score, for dashboards that prefer higher is better." + }, + "network_stability_index": { + "type": "number", + "nullable": true, + "description": "Stability of the line's network." + }, + "is_disposable": { + "type": "boolean", + "nullable": true, + "description": "`true` when the number looks disposable or virtual." + }, + "is_ported": { + "type": "boolean", + "nullable": true, + "description": "`true` when the number has moved carrier." + }, + "is_recently_ported": { + "type": "boolean", + "nullable": true, + "description": "`true` when the port happened recently." + }, + "carrier_type": { + "type": "string", + "nullable": true, + "description": "Line classification reported by the lookup (e.g., `mobile`)." + }, + "reason_codes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Machine-readable codes explaining the score. Values can grow over time, so do not reject unknown codes." + } + } + }, + "EmailIntelligence": { + "type": "object", + "description": "Normalized email-intelligence block. Present only when the intelligence add-on ran for this email; when present it is also exposed as the `enrichment` alias with identical values.", + "properties": { + "score": { + "type": "number", + "nullable": true, + "description": "Normalized 0-100 risk score. Higher is riskier." + }, + "deliverable": { + "type": "string", + "nullable": true, + "description": "Deliverability band (e.g., `high`)." + }, + "breach_risk_level": { + "type": "string", + "nullable": true, + "description": "`Low`, `Medium` or `High`." + }, + "breach_risk_index": { + "type": "number", + "nullable": true, + "description": "Numeric breach-risk index." + }, + "is_recent_breach": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address appears in a recent breach." + }, + "is_frequent_breached": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address appears in many breaches." + }, + "is_disposable": { + "type": "boolean", + "nullable": true, + "description": "`true` when the domain is a throwaway provider." + }, + "is_free_domain": { + "type": "boolean", + "nullable": true, + "description": "`true` when the domain is a free mail provider." + }, + "is_breached": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address appears in known breaches." + }, + "no_of_breaches": { + "type": "number", + "nullable": true, + "description": "Number of known breaches containing the address." + }, + "reason_codes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Machine-readable codes explaining the score. Values can grow over time, so do not reject unknown codes." + } + } + }, + "IPRiskResult": { + "type": "object", + "description": "IP risk result. Individual enrichment fields can be `null` when the lookup returned no usable data.", + "required": [ + "status", + "threat" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review" + ], + "description": "`In Review` when the resolved country conflicts with `claimed_country`, or when the address is associated with VPN, Tor, or data-center infrastructure. Otherwise `Approved`." + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "The scored address. `null` when no enrichment source returned usable data." + }, + "ip_country": { + "type": "string", + "nullable": true, + "description": "Resolved country name." + }, + "ip_country_code": { + "type": "string", + "nullable": true, + "description": "Resolved country code. Compare countries through `country_mismatch` instead of comparing raw country strings yourself." + }, + "ip_state": { + "type": "string", + "nullable": true, + "description": "Resolved state or region." + }, + "ip_city": { + "type": "string", + "nullable": true, + "description": "Resolved city." + }, + "latitude": { + "type": "number", + "nullable": true, + "description": "Approximate network geolocation, not a precise device location." + }, + "longitude": { + "type": "number", + "nullable": true, + "description": "Approximate network geolocation, not a precise device location." + }, + "isp": { + "type": "string", + "nullable": true, + "description": "Internet service provider serving the address." + }, + "organization": { + "type": "string", + "nullable": true, + "description": "Organization owning the address block." + }, + "is_vpn_or_tor": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address is associated with VPN or Tor infrastructure." + }, + "is_data_center": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address belongs to a hosting or data-center network." + }, + "time_zone": { + "type": "string", + "nullable": true, + "description": "Time zone of the resolved location." + }, + "time_zone_offset": { + "type": "string", + "nullable": true, + "description": "UTC offset of the resolved location." + }, + "asn_number": { + "type": "number", + "nullable": true, + "description": "Autonomous system number." + }, + "asn_organization": { + "type": "string", + "nullable": true, + "description": "Organization operating the autonomous system." + }, + "connection_type": { + "type": "string", + "nullable": true, + "description": "Connection classification (e.g., `cellular`, `hosting`)." + }, + "carrier": { + "type": "string", + "nullable": true, + "description": "Mobile carrier when the address is on a mobile network." + }, + "proxy_type": { + "type": "string", + "nullable": true, + "description": "Proxy classification when the address is behind a proxy." + }, + "claimed_country": { + "type": "string", + "nullable": true, + "description": "Echo of the `claimed_country` you sent." + }, + "country_mismatch": { + "type": "boolean", + "nullable": true, + "description": "`true` when the resolved country differs from `claimed_country`. `null` when no enrichment data or no claimed country is available." + }, + "threat": { + "type": "object", + "additionalProperties": true, + "description": "Free-form network-threat evidence from the enrichment source (e.g., `is_proxy`, `is_tor`). Empty object when no enrichment data is available. Treat as diagnostic detail, not a stable contract." + } + } + }, + "PhoneRiskResponse": { + "type": "object", + "required": [ + "request_id", + "status", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the API session created for this request. Store it with `vendor_data` if you need to correlate the result with later webhooks or console activity." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Final decision for this API session. Normally `Approved`; the risk evaluation can produce `In Review` or `Declined`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, `null` when you did not send one." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` you sent, `null` when you did not send one." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this response." + }, + "phone": { + "type": "object", + "description": "Normalized phone report, the same block used by Phone Verification responses.", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Decision for this phone number. Mirrors the top-level `status`." + }, + "phone_number_prefix": { + "type": "string", + "description": "Country calling code with leading `+` (e.g., `+1`).", + "example": "+1" + }, + "phone_number": { + "type": "string", + "description": "National number without the country calling code.", + "example": "4155552671" + }, + "full_number": { + "type": "string", + "description": "Complete number in E.164 format.", + "example": "+14155552671" + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of the number.", + "example": "US" + }, + "country_name": { + "type": "string", + "description": "Full country name derived from `country_code`.", + "example": "United States" + }, + "carrier": { + "type": "object", + "nullable": true, + "description": "Current network serving the number. `null` when the lookup could not resolve a carrier.", + "properties": { + "name": { + "type": "string", + "description": "Carrier name. Empty string when the lookup could not identify it." + }, + "type": { + "type": "string", + "description": "Line type reported by the lookup: `mobile`, `fixed_line`, `voip`, `isp`, `vpn`, `toll_free`, `premium_rate`, or similar." + } + } + }, + "is_disposable": { + "type": "boolean", + "description": "`true` when the number belongs to a temporary or burner number service." + }, + "is_virtual": { + "type": "boolean", + "description": "`true` when the line type is virtual (`voip`, `isp`, or `vpn`)." + }, + "verification_method": { + "type": "string", + "nullable": true, + "description": "OTP channel of the underlying verification. `null` for risk lookups, which never send a code." + }, + "verification_attempts": { + "type": "integer", + "description": "OTP sends for this number. `0` for risk lookups." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the number was last verified by an OTP. `null` for risk lookups." + }, + "warnings": { + "type": "array", + "description": "Risk warnings recorded for this request. Empty array when there are none.", + "items": { + "$ref": "#/components/schemas/RiskWarning" + } + }, + "lifecycle": { + "type": "array", + "description": "Chronological audit trail of the request. Empty array for risk lookups without send events.", + "items": { + "$ref": "#/components/schemas/RiskLifecycleEvent" + } + }, + "matches": { + "type": "array", + "description": "Other sessions of your application where the same phone number was used by a different user (different `vendor_data`). Up to 5 entries. Empty array when there are no matches.", + "items": { + "$ref": "#/components/schemas/PhoneMatchItem" + } + }, + "phone_intelligence": { + "$ref": "#/components/schemas/PhoneIntelligence" + }, + "enrichment": { + "$ref": "#/components/schemas/PhoneIntelligence" + }, + "phone_social": { + "$ref": "#/components/schemas/PhoneSocialFootprint" + } + } + } + } + }, + "EmailRiskResponse": { + "type": "object", + "required": [ + "request_id", + "status", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the API session created for this request. Store it with `vendor_data` if you need to correlate the result with later webhooks or console activity." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Final decision for this API session. Normally `Approved`; the risk evaluation can produce `In Review` or `Declined`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, `null` when you did not send one." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` you sent, `null` when you did not send one." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this response." + }, + "email": { + "type": "object", + "description": "Normalized email report, the same block used by Email Verification responses.", + "required": [ + "status", + "email", + "is_breached", + "breaches", + "is_disposable", + "is_undeliverable" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Decision for this email address. Mirrors the top-level `status`." + }, + "email": { + "type": "string", + "description": "The scored address.", + "example": "alice@example.com" + }, + "is_breached": { + "type": "boolean", + "description": "Whether the address appears in a known credential breach." + }, + "breaches": { + "type": "array", + "description": "Up to 5 most recent known breaches containing this address. Empty array when `is_breached` is `false`.", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Breach identifier." + }, + "domain": { + "type": "string", + "description": "Domain of the breached service." + }, + "breach_date": { + "type": "string", + "format": "date", + "description": "Date the breach occurred." + }, + "breach_emails_count": { + "type": "integer", + "description": "Total number of accounts exposed in the breach." + }, + "description": { + "type": "string", + "description": "Human-readable description of the breach (may contain HTML markup)." + }, + "logo_path": { + "type": "string", + "description": "URL of the breached service's logo." + }, + "data_classes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Categories of data exposed, in snake_case (e.g., `email_addresses`, `passwords`)." + }, + "is_verified": { + "type": "boolean", + "description": "Whether the breach has been verified as legitimate." + } + } + } + }, + "is_disposable": { + "type": "boolean", + "description": "Whether the domain belongs to a throwaway provider." + }, + "is_undeliverable": { + "type": "boolean", + "description": "Whether the address was found undeliverable." + }, + "verification_attempts": { + "type": "integer", + "description": "OTP sends for this address. `0` for risk lookups." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the address was last verified by an OTP. `null` for risk lookups." + }, + "warnings": { + "type": "array", + "description": "Risk warnings recorded for this request. Empty array when there are none.", + "items": { + "$ref": "#/components/schemas/RiskWarning" + } + }, + "lifecycle": { + "type": "array", + "description": "Chronological audit trail of the request. Empty array for risk lookups without sends.", + "items": { + "$ref": "#/components/schemas/RiskLifecycleEvent" + } + }, + "matches": { + "type": "array", + "description": "Other sessions of your application where the same email address was used by a different user (different `vendor_data`). Up to 5 entries. Empty array when there are no matches.", + "items": { + "$ref": "#/components/schemas/EmailMatchItem" + } + }, + "email_intelligence": { + "$ref": "#/components/schemas/EmailIntelligence" + }, + "enrichment": { + "$ref": "#/components/schemas/EmailIntelligence" + }, + "email_social": { + "$ref": "#/components/schemas/EmailSocialFootprint" + } + } + } + } + }, + "IPRiskResponse": { + "type": "object", + "required": [ + "request_id", + "status", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the API session created for this request. Store it with `vendor_data` if you need to correlate the result with later webhooks or console activity." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review" + ], + "description": "Decision for this API session: `In Review` on network or country-mismatch risk, otherwise `Approved`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, `null` when you did not send one." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` you sent, `null` when you did not send one." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this response." + }, + "ip": { + "type": "object", + "description": "IP risk result. Individual enrichment fields can be `null` when the lookup returned no usable data.", + "required": [ + "status", + "threat" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review" + ], + "description": "`In Review` when the resolved country conflicts with `claimed_country`, or when the address is associated with VPN, Tor, or data-center infrastructure. Otherwise `Approved`." + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "The scored address. `null` when no enrichment source returned usable data." + }, + "ip_country": { + "type": "string", + "nullable": true, + "description": "Resolved country name." + }, + "ip_country_code": { + "type": "string", + "nullable": true, + "description": "Resolved country code. Compare countries through `country_mismatch` instead of comparing raw country strings yourself." + }, + "ip_state": { + "type": "string", + "nullable": true, + "description": "Resolved state or region." + }, + "ip_city": { + "type": "string", + "nullable": true, + "description": "Resolved city." + }, + "latitude": { + "type": "number", + "nullable": true, + "description": "Approximate network geolocation, not a precise device location." + }, + "longitude": { + "type": "number", + "nullable": true, + "description": "Approximate network geolocation, not a precise device location." + }, + "isp": { + "type": "string", + "nullable": true, + "description": "Internet service provider serving the address." + }, + "organization": { + "type": "string", + "nullable": true, + "description": "Organization owning the address block." + }, + "is_vpn_or_tor": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address is associated with VPN or Tor infrastructure." + }, + "is_data_center": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address belongs to a hosting or data-center network." + }, + "time_zone": { + "type": "string", + "nullable": true, + "description": "Time zone of the resolved location." + }, + "time_zone_offset": { + "type": "string", + "nullable": true, + "description": "UTC offset of the resolved location." + }, + "asn_number": { + "type": "number", + "nullable": true, + "description": "Autonomous system number." + }, + "asn_organization": { + "type": "string", + "nullable": true, + "description": "Organization operating the autonomous system." + }, + "connection_type": { + "type": "string", + "nullable": true, + "description": "Connection classification (e.g., `cellular`, `hosting`)." + }, + "carrier": { + "type": "string", + "nullable": true, + "description": "Mobile carrier when the address is on a mobile network." + }, + "proxy_type": { + "type": "string", + "nullable": true, + "description": "Proxy classification when the address is behind a proxy." + }, + "claimed_country": { + "type": "string", + "nullable": true, + "description": "Echo of the `claimed_country` you sent." + }, + "country_mismatch": { + "type": "boolean", + "nullable": true, + "description": "`true` when the resolved country differs from `claimed_country`. `null` when no enrichment data or no claimed country is available." + }, + "threat": { + "type": "object", + "additionalProperties": true, + "description": "Free-form network-threat evidence from the enrichment source (e.g., `is_proxy`, `is_tor`). Empty object when no enrichment data is available. Treat as diagnostic detail, not a stable contract." + } + } + } + } + }, + "SocialFootprintPlatform": { + "type": "object", + "properties": { + "platform": { + "type": "string", + "description": "Platform identifier.", + "example": "whatsapp" + }, + "category": { + "type": "string", + "description": "Platform category, e.g. MESSAGING, SOCIAL_MEDIA, ECOMMERCE, PROFESSIONAL, EMAIL_PROVIDER.", + "example": "MESSAGING" + }, + "scope": { + "type": "string", + "enum": [ + "global", + "local" + ], + "description": "`global` for worldwide platforms, `local` for region-specific ones.", + "example": "global" + } + } + }, + "SocialFootprintCategorySummary": { + "type": "object", + "properties": { + "registered": { + "type": "integer", + "nullable": true, + "description": "Platforms in this category where registration is confirmed.", + "example": 2 + }, + "ratio": { + "type": "number", + "format": "float", + "nullable": true, + "description": "Share of the category's checked platforms with a registration, 0.0-1.0.", + "example": 0.6667 + } + } + }, + "SocialFootprintProfile": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "description": "Profile name, when the platform exposes one." + }, + "photo": { + "type": "string", + "nullable": true, + "description": "Profile photo URL, when the platform exposes one." + }, + "about": { + "type": "string", + "nullable": true, + "description": "Profile about/bio text, when the platform exposes one." + }, + "privacy_status": { + "type": "string", + "nullable": true, + "enum": [ + "PUBLIC", + "PRIVATE", + null + ], + "description": "Profile visibility, when the platform exposes it." + }, + "business_account": { + "type": "boolean", + "nullable": true, + "description": "Whether the profile is a business account, when the platform exposes it." + } + } + }, + "PhoneSocialFootprint": { + "type": "object", + "nullable": true, + "description": "Social footprint of the phone number: the online platforms it is registered on. Present only when `enable_social` was set and the check completed. Every count may be null when no data was returned for it. `profiles_registered: 0` means the platforms were checked and the phone number was found on none of them - a valid, billable answer and a common synthetic-identity signal.", + "properties": { + "profiles_registered": { + "type": "integer", + "nullable": true, + "description": "Platforms where the phone number is confirmed registered.", + "example": 5 + }, + "profiles_checked": { + "type": "integer", + "nullable": true, + "description": "Platforms evaluated for registration.", + "example": 14 + }, + "names_found": { + "type": "integer", + "nullable": true, + "description": "Platforms that returned an associated name.", + "example": 0 + }, + "photos_found": { + "type": "integer", + "nullable": true, + "description": "Platforms that returned a profile photo.", + "example": 0 + }, + "communication_breadth": { + "type": "integer", + "nullable": true, + "description": "Communication platforms with detected presence.", + "example": 2 + }, + "digital_footprint_diversity": { + "type": "integer", + "nullable": true, + "description": "Distinct platform categories with detected presence.", + "example": 5 + }, + "digital_sophistication_index": { + "type": "integer", + "nullable": true, + "description": "Composite digital-maturity indicator, 0-10.", + "example": 5 + }, + "engagement_depth_index": { + "type": "integer", + "nullable": true, + "description": "Profile-richness indicator, 0-10.", + "example": 3 + }, + "subscription_affordability_index": { + "type": "integer", + "nullable": true, + "description": "Free vs paid platform mix, 0-10.", + "example": 2 + }, + "free_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on free platforms.", + "example": 4 + }, + "full_paid_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on fully paid platforms.", + "example": 0 + }, + "partial_paid_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on freemium platforms.", + "example": 1 + }, + "whatsapp_profile_completeness": { + "type": "string", + "nullable": true, + "enum": [ + "Basic", + "Partial", + "Complete", + "Not Present", + null + ], + "description": "Completeness of the WhatsApp profile. Phone only; null for email.", + "example": "Partial" + }, + "registered_platforms": { + "type": "array", + "description": "Platforms where registration is confirmed.", + "items": { + "$ref": "#/components/schemas/SocialFootprintPlatform" + } + }, + "not_registered_platforms": { + "type": "array", + "description": "Platforms checked with a negative result.", + "items": { + "$ref": "#/components/schemas/SocialFootprintPlatform" + } + }, + "categories": { + "type": "object", + "description": "Registered count and ratio per platform category.", + "additionalProperties": { + "$ref": "#/components/schemas/SocialFootprintCategorySummary" + } + }, + "profiles": { + "type": "object", + "description": "Per-platform profile details, keyed by platform, when the platform exposes them.", + "additionalProperties": { + "$ref": "#/components/schemas/SocialFootprintProfile" + } + } + } + }, + "EmailSocialFootprint": { + "type": "object", + "nullable": true, + "description": "Social footprint of the email address: the online platforms it is registered on. Present only when `enable_social` was set and the check completed. Every count may be null when no data was returned for it. `profiles_registered: 0` means the platforms were checked and the email address was found on none of them - a valid, billable answer and a common synthetic-identity signal.", + "properties": { + "profiles_registered": { + "type": "integer", + "nullable": true, + "description": "Platforms where the email address is confirmed registered.", + "example": 5 + }, + "profiles_checked": { + "type": "integer", + "nullable": true, + "description": "Platforms evaluated for registration.", + "example": 14 + }, + "names_found": { + "type": "integer", + "nullable": true, + "description": "Platforms that returned an associated name.", + "example": 0 + }, + "photos_found": { + "type": "integer", + "nullable": true, + "description": "Platforms that returned a profile photo.", + "example": 0 + }, + "communication_breadth": { + "type": "integer", + "nullable": true, + "description": "Communication platforms with detected presence.", + "example": 2 + }, + "digital_footprint_diversity": { + "type": "integer", + "nullable": true, + "description": "Distinct platform categories with detected presence.", + "example": 5 + }, + "digital_sophistication_index": { + "type": "integer", + "nullable": true, + "description": "Composite digital-maturity indicator, 0-10.", + "example": 5 + }, + "engagement_depth_index": { + "type": "integer", + "nullable": true, + "description": "Profile-richness indicator, 0-10.", + "example": 3 + }, + "subscription_affordability_index": { + "type": "integer", + "nullable": true, + "description": "Free vs paid platform mix, 0-10.", + "example": 2 + }, + "free_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on free platforms.", + "example": 4 + }, + "full_paid_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on fully paid platforms.", + "example": 0 + }, + "partial_paid_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on freemium platforms.", + "example": 1 + }, + "whatsapp_profile_completeness": { + "type": "string", + "nullable": true, + "enum": [ + "Basic", + "Partial", + "Complete", + "Not Present", + null + ], + "description": "Completeness of the WhatsApp profile. Phone only; null for email.", + "example": "Partial" + }, + "registered_platforms": { + "type": "array", + "description": "Platforms where registration is confirmed.", + "items": { + "$ref": "#/components/schemas/SocialFootprintPlatform" + } + }, + "not_registered_platforms": { + "type": "array", + "description": "Platforms checked with a negative result.", + "items": { + "$ref": "#/components/schemas/SocialFootprintPlatform" + } + }, + "categories": { + "type": "object", + "description": "Registered count and ratio per platform category.", + "additionalProperties": { + "$ref": "#/components/schemas/SocialFootprintCategorySummary" + } + }, + "profiles": { + "type": "object", + "description": "Per-platform profile details, keyed by platform, when the platform exposes them.", + "additionalProperties": { + "$ref": "#/components/schemas/SocialFootprintProfile" + } + } + } + }, + "KYBRegistryAddress": { + "type": "object", + "description": "An address the registry states, in the same shape for every country and registry source. Every key is always present; a part the registry does not publish is null.", + "properties": { + "type": { + "type": "string", + "nullable": true, + "description": "Address type in lower case: `registered` for the registered office; other types as the registry names them (for example `postal`, `office`). `null` when the registry does not say what the address is." + }, + "address": { + "type": "string", + "description": "The full address as one line. Always present on every address." + }, + "street": { + "type": "string", + "nullable": true, + "description": "Street and number." + }, + "city": { + "type": "string", + "nullable": true, + "description": "City or locality." + }, + "postal_code": { + "type": "string", + "nullable": true, + "description": "Postal code." + }, + "region": { + "type": "string", + "nullable": true, + "description": "Region, state or province." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code." + }, + "description": { + "type": "string", + "nullable": true, + "description": "The registry's own label for the address, when it gives one." + } + }, + "required": [ + "type", + "address", + "street", + "city", + "postal_code", + "region", + "country_code", + "description" + ] + }, + "KYBRegistryIndustry": { + "type": "object", + "description": "An activity the registry classifies the company under, in the same shape for every country and registry source.", + "properties": { + "code": { + "type": "string", + "nullable": true, + "description": "Activity code as the registry prints it (for example `73110`)." + }, + "description": { + "type": "string", + "nullable": true, + "description": "Activity label, when the registry publishes one." + }, + "scheme": { + "type": "string", + "nullable": true, + "description": "Classification system, one name per system whichever source reported it: `UK SIC 2007`, `US SIC`, `NACE Rev. 2`, `NACE Rev. 2.1`, `NAF Rev. 2`, `CNAE`, `SBI`, `NOGA 2008`, `CAE Rev. 4`, `Canada SIC 1977`; other national systems keep the registry's own name. Null when the registry does not name the system." + }, + "is_primary": { + "type": "boolean", + "description": "True for the company's main activity. Exactly one entry is primary when the list is not empty." + } + }, + "required": [ + "code", + "description", + "scheme", + "is_primary" + ] + }, + "KYBRegistryShareholding": { + "type": "object", + "nullable": true, + "description": "What an owner holds, in the same shape for every country and registry source. Null when the registry states no holding. A band (for example a UK PSC's 25-50%) is not a stake: `percentage` is null and the band stays in `ownership_min_shares` / `ownership_max_shares`.", + "properties": { + "share_class": { + "type": "string", + "nullable": true, + "description": "Share class or type as the registry names it." + }, + "number_of_shares": { + "type": "number", + "nullable": true, + "description": "Number of shares held." + }, + "currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 currency of the values below." + }, + "nominal_value": { + "type": "number", + "nullable": true, + "description": "Nominal value per share." + }, + "total_value": { + "type": "number", + "nullable": true, + "description": "Total value of the holding." + }, + "percentage": { + "type": "number", + "nullable": true, + "description": "Exact stake in percent, to four decimal places." + } + }, + "required": [ + "share_class", + "number_of_shares", + "currency", + "nominal_value", + "total_value", + "percentage" + ] + }, + "KYBRegistryOwnership": { + "type": "object", + "description": "The outcome of the ownership (Shareholders / UBO) tier, in the same shape for every country and registry source.", + "properties": { + "status": { + "type": "string", + "nullable": true, + "enum": [ + "not_requested", + "pending", + "delivered", + "not_held", + "unavailable", + null + ], + "description": "`delivered` when the registry published ownership; `not_held` when the ownership registry answered and does not list the company at all (see `unavailable_reason`); `unavailable` when it did not serve the ownership data for any other reason (see `unavailable_reason`); `pending` while the registry is still fetching it; `not_requested` for the Basic tier." + }, + "tier_requested": { + "type": "string", + "nullable": true, + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "The tier the workflow ordered." + }, + "tier_delivered": { + "type": "string", + "nullable": true, + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "The tier the registry delivered (and that is billed). A UBO request answered with direct shareholders only is `shareholders`." + }, + "unavailable_reason": { + "type": "string", + "nullable": true, + "enum": [ + "no_owners_on_record", + "no_natural_person_owner", + "ownership_section_unavailable", + "company_not_found", + "ambiguous_company_match", + "missing_registration_number", + "entity_type_not_covered", + "provider_unavailable", + "other", + null + ], + "description": "Why a requested ownership tier was not delivered. Null unless `status` is `unavailable` or `pending`." + }, + "direct_ownership_complete": { + "type": "boolean", + "nullable": true, + "description": "True when the direct shareholders' stated stakes add up to 100% (within one percentage point of rounding); false when they do not; null when ownership was not delivered or a stake is not stated." + }, + "unresolved": { + "type": "array", + "description": "Parts of the ownership chain the registry could not resolve, for example no group structure on record above the direct owners.", + "items": { + "type": "object", + "properties": { + "reason": { + "type": "string" + }, + "section": { + "type": "string", + "nullable": true + } + } + } + } + }, + "required": [ + "status", + "tier_requested", + "tier_delivered", + "unavailable_reason", + "direct_ownership_complete", + "unresolved" + ] + }, + "DigitalIdWalletApiWalletVerificationError": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Stable machine-readable code, e.g. wallet_unavailable." + }, + "detail": { + "type": "string" + } + }, + "required": [ + "detail", + "error" + ] + }, + "DigitalIdWalletApiWalletVerificationStartForbidden": { + "oneOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + }, + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + ] + }, + "DigitalIdWalletApiClientCredentialsForbiddenErrorMessage": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "default": "Forbidden - User doesn't have permission" + } + } + }, + "DigitalIdWalletApiWalletVerificationStartBadRequest": { + "oneOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + }, + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationFieldErrors" + } + ] + }, + "DigitalIdWalletApiWalletVerificationFieldErrors": { + "type": "object", + "description": "Request validation errors: each rejected field with its messages.", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "DigitalIdWalletApiWalletVerificationResponse": { + "type": "object", + "description": "One standalone wallet verification, as every wallet endpoint answers it.", + "properties": { + "request_id": { + "type": "string", + "format": "uuid" + }, + "wallet_verification": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResult" + }, + "vendor_data": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "The metadata object sent at start, or null." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "created_at", + "metadata", + "request_id", + "vendor_data", + "wallet_verification" + ] + }, + "DigitalIdWalletApiWalletVerificationResult": { + "type": "object", + "properties": { + "wallet": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWallet" + } + ], + "readOnly": true + }, + "country": { + "type": "string" + }, + "status": { + "type": "string", + "description": "Not Finished while pending, then Approved (verified) or Declined." + }, + "outcome": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResultOutcomeEnum" + }, + "error": { + "type": "string", + "nullable": true, + "readOnly": true + }, + "interaction": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiInteractionEnum" + } + ], + "readOnly": true + }, + "simulated": { + "type": "boolean", + "readOnly": true + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "next_action": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletNextAction" + } + ], + "nullable": true, + "readOnly": true + }, + "identity": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletIdentity" + } + ], + "nullable": true, + "readOnly": true + }, + "attributes": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "readOnly": true + }, + "level_of_assurance": { + "type": "string", + "nullable": true + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "signature_valid": { + "type": "boolean", + "nullable": true, + "description": "True once the credential's signature was verified; null for a simulated one." + }, + "credential_type": { + "type": "string", + "nullable": true + } + }, + "required": [ + "attributes", + "country", + "credential_type", + "error", + "expires_at", + "identity", + "interaction", + "level_of_assurance", + "next_action", + "outcome", + "signature_valid", + "simulated", + "status", + "verified_at", + "wallet" + ] + }, + "DigitalIdWalletApiWalletIdentity": { + "type": "object", + "properties": { + "full_name": { + "type": "string", + "nullable": true + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true, + "description": "ISO3 nationality, only when the wallet asserts one (never inferred)." + }, + "issuing_state": { + "type": "string", + "description": "ISO3 country whose scheme issued the identity." + } + }, + "required": [ + "date_of_birth", + "first_name", + "full_name", + "issuing_state", + "last_name", + "nationality" + ] + }, + "DigitalIdWalletApiWalletNextAction": { + "type": "object", + "properties": { + "type": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletNextActionTypeEnum" + } + ], + "description": "redirect: send the user's browser to url. confirm_in_app: show verification_code to the user, who checks it matches the code in their wallet app and confirms there; then poll.\n\n* `redirect` - redirect\n* `confirm_in_app` - confirm_in_app" + }, + "url": { + "type": "string", + "format": "uri", + "description": "redirect only: where the user signs in with the wallet." + }, + "verification_code": { + "type": "string", + "description": "confirm_in_app only: the 4-digit comparison code the wallet app shows too." + }, + "poll_interval_seconds": { + "type": "integer", + "description": "confirm_in_app only: how often to call POST .../verifications/{request_id}/poll/ for prompt feedback. Didit also polls pending sign-ins in the background, so the webhook reports the outcome even when you do not poll." + } + }, + "required": [ + "type" + ] + }, + "DigitalIdWalletApiWalletNextActionTypeEnum": { + "enum": [ + "redirect", + "confirm_in_app" + ], + "type": "string", + "description": "* `redirect` - redirect\n* `confirm_in_app` - confirm_in_app" + }, + "DigitalIdWalletApiInteractionEnum": { + "enum": [ + "redirect", + "in_app" + ], + "type": "string", + "description": "* `redirect` - redirect\n* `in_app` - in_app" + }, + "DigitalIdWalletApiWalletVerificationResultOutcomeEnum": { + "enum": [ + "pending", + "verified", + "cancelled", + "timeout", + "failed" + ], + "type": "string", + "description": "* `pending` - Pending\n* `verified` - Verified\n* `cancelled` - Cancelled by the user\n* `timeout` - Timed out\n* `failed` - Failed" + }, + "DigitalIdWalletApiWallet": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Catalog id of the wallet, e.g. bankid_se." + }, + "name": { + "type": "string" + }, + "issuing_authority": { + "type": "string", + "nullable": true + } + }, + "required": [ + "id", + "issuing_authority", + "name" + ] + }, + "DigitalIdWalletApiEudiWalletVerificationStartRequest": { + "type": "object", + "description": "Start a EUDI Wallet verification (AUT, BEL, BGR, HRV, CYP, CZE, DNK, EST, FIN, FRA, DEU, GRC, HUN, IRL, ITA, LVA, LTU, LUX, MLT, NLD, POL, PRT, ROU, SVK, SVN, ESP, SWE, ISL, LIE, NOR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiEudiWalletVerificationStartCountryEnum" + } + ], + "description": "ISO 3166-1 alpha-3 country of the EUDI Wallet identity.\n\n* `AUT` - AUT\n* `BEL` - BEL\n* `BGR` - BGR\n* `HRV` - HRV\n* `CYP` - CYP\n* `CZE` - CZE\n* `DNK` - DNK\n* `EST` - EST\n* `FIN` - FIN\n* `FRA` - FRA\n* `DEU` - DEU\n* `GRC` - GRC\n* `HUN` - HUN\n* `IRL` - IRL\n* `ITA` - ITA\n* `LVA` - LVA\n* `LTU` - LTU\n* `LUX` - LUX\n* `MLT` - MLT\n* `NLD` - NLD\n* `POL` - POL\n* `PRT` - PRT\n* `ROU` - ROU\n* `SVK` - SVK\n* `SVN` - SVN\n* `ESP` - ESP\n* `SWE` - SWE\n* `ISL` - ISL\n* `LIE` - LIE\n* `NOR` - NOR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "country", + "return_url" + ] + }, + "DigitalIdWalletApiDeviceEnum": { + "enum": [ + "same_device", + "cross_device" + ], + "type": "string", + "description": "* `same_device` - same_device\n* `cross_device` - cross_device" + }, + "DigitalIdWalletApiEudiWalletVerificationStartCountryEnum": { + "enum": [ + "AUT", + "BEL", + "BGR", + "HRV", + "CYP", + "CZE", + "DNK", + "EST", + "FIN", + "FRA", + "DEU", + "GRC", + "HUN", + "IRL", + "ITA", + "LVA", + "LTU", + "LUX", + "MLT", + "NLD", + "POL", + "PRT", + "ROU", + "SVK", + "SVN", + "ESP", + "SWE", + "ISL", + "LIE", + "NOR" + ], + "type": "string", + "description": "* `AUT` - AUT\n* `BEL` - BEL\n* `BGR` - BGR\n* `HRV` - HRV\n* `CYP` - CYP\n* `CZE` - CZE\n* `DNK` - DNK\n* `EST` - EST\n* `FIN` - FIN\n* `FRA` - FRA\n* `DEU` - DEU\n* `GRC` - GRC\n* `HUN` - HUN\n* `IRL` - IRL\n* `ITA` - ITA\n* `LVA` - LVA\n* `LTU` - LTU\n* `LUX` - LUX\n* `MLT` - MLT\n* `NLD` - NLD\n* `POL` - POL\n* `PRT` - PRT\n* `ROU` - ROU\n* `SVK` - SVK\n* `SVN` - SVN\n* `ESP` - ESP\n* `SWE` - SWE\n* `ISL` - ISL\n* `LIE` - LIE\n* `NOR` - NOR" + }, + "DigitalIdWalletApiConnectidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a ConnectID verification (AUS).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiConnectidWalletVerificationStartCountryEnum" + } + ], + "default": "AUS", + "description": "ISO 3166-1 alpha-3 country of the ConnectID identity. Defaults to AUS, the only country it serves.\n\n* `AUS` - AUS" + }, + "authorisation_server_id": { + "type": "string", + "minLength": 1, + "description": "Authorisation server ID selected with ConnectID's bank selector. Sandbox defaults to Mock Bank A.", + "maxLength": 36 + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiConnectidWalletVerificationStartCountryEnum": { + "enum": [ + "AUS" + ], + "type": "string", + "description": "* `AUS` - AUS" + }, + "DigitalIdWalletApiAudkenniWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Auðkenni verification (ISL).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiAudkenniWalletVerificationStartCountryEnum" + } + ], + "default": "ISL", + "description": "ISO 3166-1 alpha-3 country of the Auðkenni identity. Defaults to ISL, the only country it serves.\n\n* `ISL` - ISL" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiAudkenniWalletVerificationStartCountryEnum": { + "enum": [ + "ISL" + ], + "type": "string", + "description": "* `ISL` - ISL" + }, + "DigitalIdWalletApiFranceconnectWalletVerificationStartRequest": { + "type": "object", + "description": "Start a FranceConnect verification (FRA).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiFranceconnectWalletVerificationStartCountryEnum" + } + ], + "default": "FRA", + "description": "ISO 3166-1 alpha-3 country of the FranceConnect identity. Defaults to FRA, the only country it serves.\n\n* `FRA` - FRA" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiFranceconnectWalletVerificationStartCountryEnum": { + "enum": [ + "FRA" + ], + "type": "string", + "description": "* `FRA` - FRA" + }, + "DigitalIdWalletApiDiiaWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Diia verification (UKR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDiiaWalletVerificationStartCountryEnum" + } + ], + "default": "UKR", + "description": "ISO 3166-1 alpha-3 country of the Diia identity. Defaults to UKR, the only country it serves.\n\n* `UKR` - UKR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiDiiaWalletVerificationStartCountryEnum": { + "enum": [ + "UKR" + ], + "type": "string", + "description": "* `UKR` - UKR" + }, + "DigitalIdWalletApiMojeidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a MojeID verification (CZE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryE2dEnum" + } + ], + "default": "CZE", + "description": "ISO 3166-1 alpha-3 country of the MojeID identity. Defaults to CZE, the only country it serves.\n\n* `CZE` - CZE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiCountryE2dEnum": { + "enum": [ + "CZE" + ], + "type": "string", + "description": "* `CZE` - CZE" + }, + "DigitalIdWalletApiBankidCzWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Bank iD verification (CZE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryE2dEnum" + } + ], + "default": "CZE", + "description": "ISO 3166-1 alpha-3 country of the Bank iD identity. Defaults to CZE, the only country it serves.\n\n* `CZE` - CZE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest": { + "type": "object", + "description": "Start a eParaksts mobile verification (LVA).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiEparakstsMobileWalletVerificationStartCountryEnum" + } + ], + "default": "LVA", + "description": "ISO 3166-1 alpha-3 country of the eParaksts mobile identity. Defaults to LVA, the only country it serves.\n\n* `LVA` - LVA" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiEparakstsMobileWalletVerificationStartCountryEnum": { + "enum": [ + "LVA" + ], + "type": "string", + "description": "* `LVA` - LVA" + }, + "DigitalIdWalletApiIdcardEeWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Estonian ID-card verification (EST).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiIdcardEeWalletVerificationStartCountryEnum" + } + ], + "default": "EST", + "description": "ISO 3166-1 alpha-3 country of the Estonian ID-card identity. Defaults to EST, the only country it serves.\n\n* `EST` - EST" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiIdcardEeWalletVerificationStartCountryEnum": { + "enum": [ + "EST" + ], + "type": "string", + "description": "* `EST` - EST" + }, + "DigitalIdWalletApiMobileIdWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Mobile-ID verification (EST, LTU).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiMobileIdWalletVerificationStartCountryEnum" + } + ], + "description": "ISO 3166-1 alpha-3 country of the Mobile-ID identity.\n\n* `EST` - EST\n* `LTU` - LTU" + }, + "personal_code": { + "type": "string", + "minLength": 1, + "description": "The user's national personal identification code. It addresses the user's Mobile-ID app; the user confirms the comparison code and enters their PIN in the app. Never collect the PIN.", + "pattern": "^[0-9-]{6,20}$" + }, + "phone_number": { + "type": "string", + "minLength": 1, + "description": "The Mobile-ID phone number in E.164 format (+372... or +370...).", + "maxLength": 16 + } + }, + "required": [ + "country", + "personal_code", + "phone_number" + ] + }, + "DigitalIdWalletApiMobileIdWalletVerificationStartCountryEnum": { + "enum": [ + "EST", + "LTU" + ], + "type": "string", + "description": "* `EST` - EST\n* `LTU` - LTU" + }, + "DigitalIdWalletApiSmartIdWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Smart-ID verification (EST, LVA, LTU, BEL).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiSmartIdWalletVerificationStartCountryEnum" + } + ], + "description": "ISO 3166-1 alpha-3 country of the Smart-ID identity.\n\n* `EST` - EST\n* `LVA` - LVA\n* `LTU` - LTU\n* `BEL` - BEL" + }, + "personal_code": { + "type": "string", + "minLength": 1, + "description": "The user's national personal identification code. It addresses the user's Smart-ID app; the user confirms the comparison code and enters their PIN in the app. Never collect the PIN.", + "pattern": "^[0-9-]{6,20}$" + } + }, + "required": [ + "country", + "personal_code" + ] + }, + "DigitalIdWalletApiSmartIdWalletVerificationStartCountryEnum": { + "enum": [ + "EST", + "LVA", + "LTU", + "BEL" + ], + "type": "string", + "description": "* `EST` - EST\n* `LVA` - LVA\n* `LTU` - LTU\n* `BEL` - BEL" + }, + "DigitalIdWalletApiGovukWalletWalletVerificationStartRequest": { + "type": "object", + "description": "Start a GOV.UK Wallet verification (GBR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountry520Enum" + } + ], + "default": "GBR", + "description": "ISO 3166-1 alpha-3 country of the GOV.UK Wallet identity. Defaults to GBR, the only country it serves.\n\n* `GBR` - GBR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiCountry520Enum": { + "enum": [ + "GBR" + ], + "type": "string", + "description": "* `GBR` - GBR" + }, + "DigitalIdWalletApiOneidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a OneID verification (GBR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountry520Enum" + } + ], + "default": "GBR", + "description": "ISO 3166-1 alpha-3 country of the OneID identity. Defaults to GBR, the only country it serves.\n\n* `GBR` - GBR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiGovbrWalletVerificationStartRequest": { + "type": "object", + "description": "Start a gov.br verification (BRA).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiGovbrWalletVerificationStartCountryEnum" + } + ], + "default": "BRA", + "description": "ISO 3166-1 alpha-3 country of the gov.br identity. Defaults to BRA, the only country it serves.\n\n* `BRA` - BRA" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiGovbrWalletVerificationStartCountryEnum": { + "enum": [ + "BRA" + ], + "type": "string", + "description": "* `BRA` - BRA" + }, + "DigitalIdWalletApiUaePassWalletVerificationStartRequest": { + "type": "object", + "description": "Start a UAE PASS verification (ARE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiUaePassWalletVerificationStartCountryEnum" + } + ], + "default": "ARE", + "description": "ISO 3166-1 alpha-3 country of the UAE PASS identity. Defaults to ARE, the only country it serves.\n\n* `ARE` - ARE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiUaePassWalletVerificationStartCountryEnum": { + "enum": [ + "ARE" + ], + "type": "string", + "description": "* `ARE` - ARE" + }, + "DigitalIdWalletApiFrejaidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Freja eID verification (SWE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryEe9Enum" + } + ], + "default": "SWE", + "description": "ISO 3166-1 alpha-3 country of the Freja eID identity. Defaults to SWE, the only country it serves.\n\n* `SWE` - SWE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiCountryEe9Enum": { + "enum": [ + "SWE" + ], + "type": "string", + "description": "* `SWE` - SWE" + }, + "DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Personalausweis verification (DEU).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiPersonalausweisWalletVerificationStartCountryEnum" + } + ], + "default": "DEU", + "description": "ISO 3166-1 alpha-3 country of the Personalausweis identity. Defaults to DEU, the only country it serves.\n\n* `DEU` - DEU" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiPersonalausweisWalletVerificationStartCountryEnum": { + "enum": [ + "DEU" + ], + "type": "string", + "description": "* `DEU` - DEU" + }, + "DigitalIdWalletApiFtnWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Finnish Trust Network verification (FIN).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiFtnWalletVerificationStartCountryEnum" + } + ], + "default": "FIN", + "description": "ISO 3166-1 alpha-3 country of the Finnish Trust Network identity. Defaults to FIN, the only country it serves.\n\n* `FIN` - FIN" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiFtnWalletVerificationStartCountryEnum": { + "enum": [ + "FIN" + ], + "type": "string", + "description": "* `FIN` - FIN" + }, + "DigitalIdWalletApiIdinWalletVerificationStartRequest": { + "type": "object", + "description": "Start a iDIN verification (NLD).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiIdinWalletVerificationStartCountryEnum" + } + ], + "default": "NLD", + "description": "ISO 3166-1 alpha-3 country of the iDIN identity. Defaults to NLD, the only country it serves.\n\n* `NLD` - NLD" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiIdinWalletVerificationStartCountryEnum": { + "enum": [ + "NLD" + ], + "type": "string", + "description": "* `NLD` - NLD" + }, + "DigitalIdWalletApiItsmeWalletVerificationStartRequest": { + "type": "object", + "description": "Start a itsme verification (BEL, LUX, NLD).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiItsmeWalletVerificationStartCountryEnum" + } + ], + "description": "ISO 3166-1 alpha-3 country of the itsme identity.\n\n* `BEL` - BEL\n* `LUX` - LUX\n* `NLD` - NLD" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "country", + "return_url" + ] + }, + "DigitalIdWalletApiItsmeWalletVerificationStartCountryEnum": { + "enum": [ + "BEL", + "LUX", + "NLD" + ], + "type": "string", + "description": "* `BEL` - BEL\n* `LUX` - LUX\n* `NLD` - NLD" + }, + "DigitalIdWalletApiBuypassWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Buypass ID verification (NOR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryAfcEnum" + } + ], + "default": "NOR", + "description": "ISO 3166-1 alpha-3 country of the Buypass ID identity. Defaults to NOR, the only country it serves.\n\n* `NOR` - NOR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiCountryAfcEnum": { + "enum": [ + "NOR" + ], + "type": "string", + "description": "* `NOR` - NOR" + }, + "DigitalIdWalletApiVippsWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Vipps verification (NOR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryAfcEnum" + } + ], + "default": "NOR", + "description": "ISO 3166-1 alpha-3 country of the Vipps identity. Defaults to NOR, the only country it serves.\n\n* `NOR` - NOR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiBankidNoWalletVerificationStartRequest": { + "type": "object", + "description": "Start a BankID verification (NOR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryAfcEnum" + } + ], + "default": "NOR", + "description": "ISO 3166-1 alpha-3 country of the BankID identity. Defaults to NOR, the only country it serves.\n\n* `NOR` - NOR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiBankidSeWalletVerificationStartRequest": { + "type": "object", + "description": "Start a BankID verification (SWE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryEe9Enum" + } + ], + "default": "SWE", + "description": "ISO 3166-1 alpha-3 country of the BankID identity. Defaults to SWE, the only country it serves.\n\n* `SWE` - SWE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiMitidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a MitID verification (DNK).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiMitidWalletVerificationStartCountryEnum" + } + ], + "default": "DNK", + "description": "ISO 3166-1 alpha-3 country of the MitID identity. Defaults to DNK, the only country it serves.\n\n* `DNK` - DNK" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiMitidWalletVerificationStartCountryEnum": { + "enum": [ + "DNK" + ], + "type": "string", + "description": "* `DNK` - DNK" + }, + "DigitalIdWalletApiWalletCatalogResponse": { + "type": "object", + "properties": { + "wallets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletCatalogEntry" + } + } + }, + "required": [ + "wallets" + ] + }, + "DigitalIdWalletApiWalletCatalogEntry": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string", + "description": "The wallet's own start endpoint." + }, + "countries": { + "type": "array", + "items": { + "type": "string" + } + }, + "issuing_authority": { + "type": "string" + }, + "level_of_assurance": { + "type": "string", + "nullable": true + }, + "interaction": { + "$ref": "#/components/schemas/DigitalIdWalletApiInteractionEnum" + }, + "required_fields": { + "type": "array", + "items": { + "type": "string" + } + }, + "available": { + "type": "boolean", + "description": "Whether this application can start it in this environment now. Listed does not mean live." + } + }, + "required": [ + "available", + "countries", + "id", + "interaction", + "issuing_authority", + "level_of_assurance", + "name", + "path", + "required_fields" + ] + }, + "CaseDuplicateCaseDuplicate": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "case_number": { + "type": "string", + "nullable": true, + "maxLength": 32 + }, + "title": { + "type": "string", + "maxLength": 255 + }, + "status": { + "$ref": "#/components/schemas/CaseDuplicateStatusEnum" + }, + "priority": { + "$ref": "#/components/schemas/CaseDuplicatePriorityEnum" + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "created_at", + "title", + "uuid" + ] + }, + "CaseDuplicateCaseDuplicatePage": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next_offset": { + "type": "integer", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicate" + } + } + }, + "required": [ + "count", + "next_offset", + "results" + ] + }, + "RoutingPublicationPolicyDraftResponse": { + "type": "object", + "properties": { + "draft": { + "allOf": [ + { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraft" + } + ], + "nullable": true + } + }, + "required": [ + "draft" + ] + }, + "RoutingPublicationPolicyDraftWriteRequest": { + "type": "object", + "properties": { + "expected_version": { + "type": "integer", + "minimum": 0 + }, + "expected_revision": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "changes": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "changes", + "expected_revision", + "expected_version" + ] + }, + "CasePublicationPolicyDraft": { + "type": "object", + "properties": { + "revision": { + "type": "string", + "format": "uuid" + }, + "base_version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "changes": {}, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "base_version", + "updated_at" + ] + }, + "CasePublicationPolicyDraftPreconditionRequest": { + "type": "object", + "properties": { + "expected_version": { + "type": "integer", + "minimum": 0 + }, + "expected_revision": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "required": [ + "expected_revision", + "expected_version" + ] + }, + "CaseCollaborationCaseNote": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "comment": { + "type": "string" + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 255 + }, + "actor_name": { + "type": "string", + "nullable": true, + "maxLength": 255 + }, + "mentioned_emails": {}, + "is_internal": { + "type": "boolean" + }, + "attachments": {}, + "tags": {}, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "reply_to": { + "allOf": [ + { + "$ref": "#/components/schemas/CaseCollaborationCaseNoteReply" + } + ], + "nullable": true, + "readOnly": true + }, + "is_pinned": { + "type": "boolean" + }, + "client_reference": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "required": [ + "comment", + "created_at", + "reply_to", + "uuid" + ] + }, + "CustomerMessagingPaginatedCaseRequestList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=400&limit=100" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=200&limit=100" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequest" + } + } + } + }, + "FraudCheckResponse": { + "type": "object", + "properties": { + "check_id": { + "type": "string", + "format": "uuid" + }, + "request_id": { + "type": "string", + "format": "uuid", + "description": "Native verification session created for this check." + }, + "client_reference": { + "type": "string", + "nullable": true + }, + "processing_status": { + "$ref": "#/components/schemas/FraudProcessingStatusEnum" + }, + "profile_id": { + "type": "string" + }, + "profile_version": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "inputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckInput" + } + }, + "modules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckModuleReport" + } + }, + "attribute_evidence": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "compound_evidence": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "risk": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckRisk" + } + }, + "network": { + "type": "object", + "additionalProperties": {} + }, + "enrichment": { + "type": "object", + "additionalProperties": {} + }, + "identity_relationship": { + "allOf": [ + { + "$ref": "#/components/schemas/FraudCheckIdentityRelationship" + } + ], + "nullable": true + }, + "recommendation": { + "$ref": "#/components/schemas/FraudRecommendationEnum" + }, + "next_action": { + "$ref": "#/components/schemas/FraudNextActionEnum" + }, + "decision_reasons": { + "type": "array", + "items": { + "type": "string" + } + }, + "coverage": { + "type": "object", + "additionalProperties": {} + }, + "usage": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "vendor_data": { + "type": "string", + "nullable": true + }, + "metadata": { + "nullable": true + } + }, + "required": [ + "attribute_evidence", + "check_id", + "client_reference", + "completed_at", + "compound_evidence", + "coverage", + "created_at", + "decision_reasons", + "inputs", + "modules", + "network", + "next_action", + "processing_status", + "profile_id", + "profile_version", + "recommendation", + "risk", + "usage" + ] + }, + "CaseReviewTeam": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseReviewTeamMember" + }, + "maxItems": 250 + }, + "is_active": { + "type": "boolean" + }, + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + } + }, + "CaseActionCaseEntityActionRequest": { + "type": "object", + "properties": { + "client_reference": { + "type": "string", + "format": "uuid" + }, + "retry": { + "type": "boolean", + "default": false + }, + "action": { + "$ref": "#/components/schemas/CaseActionActionEnum" + }, + "status": { + "type": "string", + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "note": { + "type": "string", + "nullable": true + } + }, + "required": [ + "action" + ] + }, + "RoutingPublicationPaginatedPolicyRevisionList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=400&limit=100" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=200&limit=100" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RoutingPublicationPolicyRevision" + } + } + } + }, + "FraudRecommendationEnum": { + "enum": [ + "approve", + "review", + "reject" + ], + "type": "string", + "description": "* `approve` - Approve\n* `review` - Review\n* `reject` - Reject" + }, + "CustomerMessagingCaseRequestPreviewResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid" + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "recipient": { + "type": "string", + "format": "email" + }, + "sender": { + "type": "string" + }, + "subject": { + "type": "string" + }, + "html": { + "type": "string" + }, + "requires_user_action": { + "type": "boolean" + }, + "preview_token": { + "type": "string" + } + }, + "required": [ + "expires_at", + "html", + "preview_token", + "recipient", + "request_id", + "requires_user_action", + "sender", + "subject" + ] + }, + "CustomerMessagingCaseCustomerResponseRequest": { + "type": "object", + "properties": { + "full_name": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "file": { + "type": "string", + "format": "binary", + "writeOnly": true + } + } + }, + "CaseCollaborationCaseNoteReply": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "comment": { + "type": "string", + "maxLength": 300 + } + }, + "required": [ + "actor_email", + "actor_name", + "comment", + "uuid" + ] + }, + "FraudCheckModuleReport": { + "type": "object", + "properties": { + "module": { + "$ref": "#/components/schemas/FraudExcludeEnum" + }, + "status": { + "$ref": "#/components/schemas/FraudStatusEnum" + }, + "reason": { + "type": "string", + "nullable": true + }, + "latency_ms": { + "type": "integer" + }, + "freshness": { + "type": "string", + "nullable": true + }, + "billable_units": { + "type": "integer" + } + }, + "required": [ + "billable_units", + "freshness", + "latency_ms", + "module", + "reason", + "status" + ] + }, + "CaseDuplicateEntityTypeEnum": { + "enum": [ + "session", + "business_session", + "transaction" + ], + "type": "string", + "description": "* `session` - Session\n* `business_session` - Business Session\n* `transaction` - Transaction" + }, + "FraudClientCredentialsErrorMessage": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "default": "Authentication failed: Invalid client access token." + } + } + }, + "FraudAvailabilityEnum": { + "enum": [ + "available", + "not_requested", + "no_reference", + "insufficient_history", + "unsupported_region", + "source_unavailable" + ], + "type": "string", + "description": "* `available` - Available\n* `not_requested` - Not Requested\n* `no_reference` - No Reference\n* `insufficient_history` - Insufficient History\n* `unsupported_region` - Unsupported Region\n* `source_unavailable` - Source Unavailable" + }, + "CasePublicationPolicyDraftResponse": { + "type": "object", + "properties": { + "draft": { + "allOf": [ + { + "$ref": "#/components/schemas/CasePublicationPolicyDraft" + } + ], + "nullable": true + } + }, + "required": [ + "draft" + ] + }, + "FraudCheckAttributesRequest": { + "type": "object", + "description": "Corroborating attributes. All optional; they widen module coverage.", + "properties": { + "first_name": { + "type": "string", + "maxLength": 128 + }, + "last_name": { + "type": "string", + "maxLength": 128 + }, + "full_name": { + "type": "string", + "maxLength": 256 + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "nationality": { + "type": "string", + "maxLength": 3 + }, + "gender": { + "type": "string", + "maxLength": 16 + }, + "address": { + "type": "string", + "maxLength": 512 + }, + "city": { + "type": "string", + "maxLength": 128 + }, + "state": { + "type": "string", + "maxLength": 128 + }, + "postal_code": { + "type": "string", + "maxLength": 32 + } + } + }, + "NetworkNote": { + "type": "object", + "required": [ + "uuid", + "actor_email", + "comment", + "client_reference", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "actor_email": { + "type": "string", + "format": "email", + "readOnly": true, + "description": "Authenticated author, assigned by the server." + }, + "comment": { + "type": "string", + "minLength": 1, + "maxLength": 10000 + }, + "client_reference": { + "type": "string", + "format": "uuid" + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + } + }, + "CaseActionEntityTypeEnum": { + "enum": [ + "user", + "business" + ], + "type": "string", + "description": "* `user` - user\n* `business` - business" + }, + "CaseRoutingCondition": { + "type": "object", + "properties": { + "field": { + "type": "string", + "enum": [ + "source", + "priority", + "tags" + ] + }, + "operator": { + "type": "string", + "enum": [ + "equals", + "in", + "contains" + ] + }, + "value": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": [ + "field", + "operator", + "value" + ], + "description": "Source and priority support equals or in with supported values. Tags support contains with one non-empty tag." + }, + "RoutingPublicationPolicyRevision": { + "type": "object", + "properties": { + "version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "snapshot": {}, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "published_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "published_at", + "snapshot", + "version" + ] + }, + "CasePublicationPolicyRevision": { + "type": "object", + "properties": { + "version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "snapshot": {}, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "published_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "published_at", + "snapshot", + "version" + ] + }, + "FraudCheckIdentityRelationship": { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/FraudResultEnum" + }, + "reference_type": { + "type": "string" + }, + "country": { + "type": "string" + }, + "availability": { + "$ref": "#/components/schemas/FraudAvailabilityEnum" + }, + "reason_codes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "availability", + "country", + "reason_codes", + "reference_type", + "result" + ] + }, + "CasePublicationPaginatedPolicyRevisionList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=400&limit=100" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=200&limit=100" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CasePublicationPolicyRevision" + } + } + } + }, + "CaseDuplicateBlankEnum": { + "enum": [ + "" + ] + }, + "FraudCheckPlan": { + "type": "object", + "properties": { + "profile_id": { + "type": "string" + }, + "profile_version": { + "type": "string" + }, + "modules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckPlannedModule" + } + }, + "estimated_price": { + "type": "string", + "nullable": true + }, + "currency": { + "type": "string", + "nullable": true + }, + "is_sandbox": { + "type": "boolean" + }, + "can_afford": { + "type": "boolean" + } + }, + "required": [ + "can_afford", + "currency", + "estimated_price", + "is_sandbox", + "modules", + "profile_id", + "profile_version" + ] + }, + "CaseReviewTeamUpdate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseReviewTeamMember" + }, + "maxItems": 250 + }, + "is_active": { + "type": "boolean" + } + } + }, + "RoutingPublicationPolicyDraft": { + "type": "object", + "properties": { + "revision": { + "type": "string", + "format": "uuid" + }, + "base_version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "changes": {}, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "base_version", + "updated_at" + ] + }, + "FraudCheckRequestRequest": { + "type": "object", + "properties": { + "client_reference": { + "type": "string", + "minLength": 1, + "description": "Your own reference for this check. Doubles as the idempotency key: reusing it with the same body returns the original check without running or billing the modules again; reusing it with a different body returns 409.", + "maxLength": 255 + }, + "profile": { + "type": "string", + "minLength": 1, + "default": "standard", + "description": "Orchestration profile id. Defaults to the Didit `standard` profile.", + "maxLength": 64 + }, + "profile_version": { + "type": "string", + "minLength": 1, + "description": "Pin a frozen profile version. Defaults to the current version of the profile.", + "maxLength": 32 + }, + "subject": { + "$ref": "#/components/schemas/FraudCheckSubjectRequest" + }, + "context": { + "$ref": "#/components/schemas/FraudCheckContextRequest" + }, + "checks": { + "$ref": "#/components/schemas/FraudCheckModuleSelectionRequest" + }, + "vendor_data": { + "type": "string", + "maxLength": 1000 + }, + "metadata": {} + }, + "required": [ + "subject" + ] + }, + "CaseDuplicateCaseSubjectInputRequest": { + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/CaseDuplicateTypeEnum" + }, + "uuid": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "uuid" + ] + }, + "CaseReviewTeamMember": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "format": "uuid" + }, + "availability": { + "type": "string", + "enum": [ + "available", + "unavailable" + ], + "default": "available" + }, + "capacity": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 10 + }, + "skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "available_from": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "available_until": { + "type": "string", + "format": "date-time", + "nullable": true + } + }, + "required": [ + "user_id" + ] + }, + "CaseRoutingPreviewRequest": { + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ] + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ], + "default": "MEDIUM" + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 50 + }, + "rule_uuid": { + "type": "string", + "format": "uuid" + }, + "draft": { + "$ref": "#/components/schemas/CaseRoutingRuleCreate" + } + }, + "required": [ + "source" + ] + }, + "FraudExcludeEnum": { + "enum": [ + "normalization", + "email_risk", + "email_social", + "phone_social", + "phone_risk", + "ip_risk", + "database_validation", + "networks_same_org", + "networks_cross_org", + "biometric_identity" + ], + "type": "string", + "description": "* `normalization` - Normalization\n* `email_risk` - Email Risk\n* `email_social` - Email Social\n* `phone_social` - Phone Social\n* `phone_risk` - Phone Risk\n* `ip_risk` - Ip Risk\n* `database_validation` - Database Validation\n* `networks_same_org` - Networks Same Org\n* `networks_cross_org` - Networks Cross Org\n* `biometric_identity` - Biometric Identity" + }, + "CaseDuplicateCaseLinkCreateRequest": { + "type": "object", + "properties": { + "entity_type": { + "$ref": "#/components/schemas/CaseDuplicateEntityTypeEnum" + }, + "entity_uuid": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "entity_type", + "entity_uuid" + ] + }, + "CaseDuplicateStatusEnum": { + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ], + "type": "string", + "description": "* `OPEN` - Open\n* `AWAITING_USER` - Awaiting user\n* `RESOLVED` - Resolved" + }, + "FraudBandEnum": { + "enum": [ + "low", + "medium", + "high", + "unknown" + ], + "type": "string", + "description": "* `low` - Low\n* `medium` - Medium\n* `high` - High\n* `unknown` - Unknown" + }, + "CaseDuplicatePriorityEnum": { + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ], + "type": "string", + "description": "* `LOW` - Low\n* `MEDIUM` - Medium\n* `HIGH` - High" + }, + "FraudStatusEnum": { + "enum": [ + "planned", + "completed", + "skipped", + "failed" + ], + "type": "string", + "description": "* `planned` - Planned\n* `completed` - Completed\n* `skipped` - Skipped\n* `failed` - Failed" + }, + "FraudCheckInput": { + "type": "object", + "properties": { + "reference": { + "type": "string", + "description": "Redacted handle for the submitted identifier." + }, + "type": { + "$ref": "#/components/schemas/FraudTypeEnum" + }, + "namespace": { + "type": "object", + "additionalProperties": {} + }, + "quality": { + "type": "string" + }, + "normalization": { + "type": "object", + "additionalProperties": {} + }, + "availability": { + "$ref": "#/components/schemas/FraudAvailabilityEnum" + } + }, + "required": [ + "availability", + "namespace", + "normalization", + "quality", + "reference", + "type" + ] + }, + "CustomerMessagingPurposesEnum": { + "enum": [ + "identity_document", + "proof_of_address", + "business_document", + "confirm_details", + "review_in_progress" + ], + "type": "string", + "description": "* `identity_document` - identity_document\n* `proof_of_address` - proof_of_address\n* `business_document` - business_document\n* `confirm_details` - confirm_details\n* `review_in_progress` - review_in_progress" + }, + "FraudTypeEnum": { + "enum": [ + "email", + "phone", + "national_id", + "tax_number", + "passport_number", + "driving_licence_number", + "residence_permit_number", + "voter_id", + "external_id" + ], + "type": "string", + "description": "* `email` - Email\n* `phone` - Phone\n* `national_id` - National Id\n* `tax_number` - Tax Number\n* `passport_number` - Passport Number\n* `driving_licence_number` - Driving Licence Number\n* `residence_permit_number` - Residence Permit Number\n* `voter_id` - Voter Id\n* `external_id` - External Id" + }, + "CaseDuplicateCaseSubjectInput": { + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/CaseDuplicateTypeEnum" + }, + "uuid": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "uuid" + ] + }, + "CaseReviewTeamCreate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseReviewTeamMember" + }, + "maxItems": 250 + }, + "is_active": { + "type": "boolean" + } + }, + "required": [ + "name", + "members" + ] + }, + "CaseDuplicateCaseDuplicateQueryRequest": { + "type": "object", + "properties": { + "subject": { + "$ref": "#/components/schemas/CaseDuplicateCaseSubjectInputRequest" + }, + "links": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseDuplicateCaseLinkCreateRequest" + } + }, + "offset": { + "type": "integer", + "maximum": 1000000, + "minimum": 0, + "default": 0 + } + } + }, + "CaseDuplicateResolutionEnum": { + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT" + ], + "type": "string", + "description": "* `FALSE_POSITIVE` - False positive\n* `VALID_THREAT` - Valid threat" + }, + "CasePublicationPolicyDraftWriteRequest": { + "type": "object", + "properties": { + "expected_version": { + "type": "integer", + "minimum": 0 + }, + "expected_revision": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "changes": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "changes", + "expected_revision", + "expected_version" + ] + }, + "FraudCheckSubjectRequest": { + "type": "object", + "properties": { + "selfie": { + "type": "string", + "minLength": 1, + "description": "Base64 JPEG, PNG or WebP selfie, up to 6 MB. No reference image or URL.", + "maxLength": 8388672 + }, + "identifiers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckIdentifierRequest" + }, + "description": "One or more typed identifiers. One is enough.", + "maxItems": 10, + "minItems": 1 + }, + "attributes": { + "$ref": "#/components/schemas/FraudCheckAttributesRequest" + } + }, + "required": [ + "identifiers" + ] + }, + "RoutingPublicationRoutingCondition": { + "type": "object", + "properties": { + "field": { + "$ref": "#/components/schemas/RoutingPublicationFieldEnum" + }, + "operator": { + "$ref": "#/components/schemas/RoutingPublicationOperatorEnum" + }, + "value": {} + }, + "required": [ + "field", + "operator", + "value" + ] + }, + "CaseActionCaseActionReceipt": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "client_reference": { + "type": "string", + "format": "uuid" + }, + "action": { + "type": "string", + "maxLength": 32 + }, + "status": { + "$ref": "#/components/schemas/CaseActionStatusEnum" + }, + "result": {}, + "error": { + "type": "string", + "maxLength": 255 + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "attempts": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "action", + "client_reference", + "created_at", + "status", + "updated_at", + "uuid" + ] + }, + "FraudCheckContextRequest": { + "type": "object", + "properties": { + "ip_address": { + "type": "string", + "minLength": 1 + }, + "user_agent": { + "type": "string", + "maxLength": 512 + }, + "country": { + "type": "string", + "description": "Country the subject claims to be in. Contextual only - never proof of identity ownership.", + "maxLength": 3 + }, + "purpose": { + "type": "string", + "maxLength": 128 + }, + "consent_obtained": { + "type": "boolean" + } + } + }, + "CustomerMessagingCaseCustomerResponseResult": { + "type": "object", + "properties": { + "responded_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "responded_at" + ] + }, + "CustomerMessagingCaseRequestContact": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "email": { + "type": "string", + "format": "email" + }, + "verified_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "email", + "uuid", + "verified_at" + ] + }, + "FraudClientCredentialsForbiddenErrorMessage": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "default": "Forbidden - User doesn't have permission" + } + } + }, + "CasePublicationAssignmentMethodEnum": { + "enum": [ + "MANUAL", + "AUTOMATIC" + ], + "type": "string", + "description": "* `MANUAL` - Manual\n* `AUTOMATIC` - Automatic" + }, + "CaseDuplicateNullEnum": { + "enum": [ + null + ] + }, + "CustomerMessagingLanguageEnum": { + "enum": [ + "en", + "es", + "pt" + ], + "type": "string", + "description": "* `en` - en\n* `es` - es\n* `pt` - pt" + }, + "CaseDuplicateCaseFraudSourceContextRequest": { + "type": "object", + "properties": { + "check_id": { + "type": "string", + "format": "uuid" + }, + "request_id": { + "type": "string", + "format": "uuid" + }, + "source_section": { + "type": "string", + "minLength": 1, + "pattern": "^[A-Z][A-Z0-9_:-]{0,63}$" + } + }, + "required": [ + "check_id", + "request_id", + "source_section" + ] + }, + "FraudCheckPlannedModule": { + "type": "object", + "properties": { + "module": { + "$ref": "#/components/schemas/FraudExcludeEnum" + }, + "available": { + "type": "boolean" + }, + "availability_reason": { + "type": "string", + "nullable": true + }, + "will_run": { + "type": "boolean" + }, + "selection_reason": { + "type": "string", + "nullable": true + }, + "estimated_price": { + "type": "string", + "nullable": true + } + }, + "required": [ + "availability_reason", + "available", + "estimated_price", + "module", + "selection_reason", + "will_run" + ] + }, + "CustomerMessagingCaseRequestOptions": { + "type": "object", + "properties": { + "contacts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestContact" + } + }, + "purposes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerMessagingPurposesEnum" + } + }, + "languages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerMessagingLanguagesEnum" + } + } + }, + "required": [ + "contacts", + "languages", + "purposes" + ] + }, + "CaseCollaborationPaginatedCaseNoteList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=400&limit=100" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=200&limit=100" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseCollaborationCaseNote" + } + } + } + }, + "FraudProcessingStatusEnum": { + "enum": [ + "pending", + "running", + "completed", + "failed" + ], + "type": "string", + "description": "* `pending` - Pending\n* `running` - Running\n* `completed` - Completed\n* `failed` - Failed" + }, + "CasePublicationCaseBlueprint": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "name": { + "type": "string", + "maxLength": 24 + }, + "description": { + "type": "string", + "nullable": true + }, + "assignment_method": { + "$ref": "#/components/schemas/CasePublicationAssignmentMethodEnum" + }, + "assignee_pool": {}, + "max_active_cases": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true + }, + "escalation_enabled": { + "type": "boolean" + }, + "escalation_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "escalation_blueprint_name": { + "type": "string", + "readOnly": true + }, + "transfer_enabled": { + "type": "boolean" + }, + "four_eyes_checker_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "four_eyes_checker_blueprint_name": { + "type": "string", + "readOnly": true + }, + "require_resolution_note": { + "type": "boolean" + }, + "content_config": {}, + "version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "default_deadline_hours": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "preset_key": { + "type": "string", + "nullable": true, + "maxLength": 64 + }, + "creation_sources": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + }, + "readOnly": true + }, + "receives_escalation_from": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + }, + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "created_at", + "creation_sources", + "escalation_blueprint_name", + "four_eyes_checker_blueprint_name", + "name", + "receives_escalation_from", + "updated_at", + "uuid" + ] + }, + "CaseRoutingRuleUpdate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "priority": { + "type": "integer", + "minimum": 0 + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseRoutingCondition" + }, + "maxItems": 20 + }, + "required_skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "blueprint": { + "type": "string", + "format": "uuid" + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "expected_version": { + "type": "integer", + "minimum": 1, + "writeOnly": true, + "description": "Version opened by the editor. When supplied, stale writes return 409 without changing the rule." + } + } + }, + "CustomerMessagingCaseRequestPreviewRequest": { + "type": "object", + "properties": { + "contact_id": { + "type": "string", + "format": "uuid" + }, + "purpose": { + "$ref": "#/components/schemas/CustomerMessagingPurposeEnum" + }, + "language": { + "allOf": [ + { + "$ref": "#/components/schemas/CustomerMessagingLanguageEnum" + } + ], + "default": "en" + } + }, + "required": [ + "contact_id", + "purpose" + ] + }, + "CaseDuplicateCaseFraudSourceContext": { + "type": "object", + "properties": { + "check_id": { + "type": "string", + "format": "uuid" + }, + "request_id": { + "type": "string", + "format": "uuid" + }, + "source_section": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_:-]{0,63}$" + } + }, + "required": [ + "check_id", + "request_id", + "source_section" + ] + }, + "RoutingPublicationOperatorEnum": { + "enum": [ + "equals", + "in", + "contains" + ], + "type": "string", + "description": "* `equals` - equals\n* `in` - in\n* `contains` - contains" + }, + "CaseRoutingPreview": { + "type": "object", + "properties": { + "rule_uuid": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "rule_name": { + "type": "string", + "nullable": true + }, + "rule_version": { + "type": "integer", + "nullable": true + }, + "blueprint_uuid": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "team_uuid": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "assignee_user_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "reason": { + "type": "string" + }, + "candidates": { + "type": "array", + "items": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "format": "uuid" + }, + "eligible": { + "type": "boolean" + }, + "active_cases": { + "type": "integer", + "nullable": true + }, + "reason": { + "type": "string", + "nullable": true + } + } + } + }, + "matched_rules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "priority": { + "type": "integer" + } + } + } + } + } + }, + "FraudDimensionEnum": { + "enum": [ + "identity", + "contact", + "device_network", + "behavioral", + "fraud_network" + ], + "type": "string", + "description": "* `identity` - Identity\n* `contact` - Contact\n* `device_network` - Device Network\n* `behavioral` - Behavioral\n* `fraud_network` - Fraud Network" + }, + "FraudBadRequestErrorMessage": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "default": "Bad Request - Invalid request data" + } + } + }, + "FraudNextActionEnum": { + "enum": [ + "none", + "manual_review", + "step_up", + "resubmit_capture" + ], + "type": "string", + "description": "* `none` - None\n* `manual_review` - Manual Review\n* `step_up` - Step Up\n* `resubmit_capture` - Resubmit Capture" + }, + "RoutingPublicationPolicyDraftPreconditionRequest": { + "type": "object", + "properties": { + "expected_version": { + "type": "integer", + "minimum": 0 + }, + "expected_revision": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "required": [ + "expected_revision", + "expected_version" + ] + }, + "FraudCheckRisk": { + "type": "object", + "properties": { + "dimension": { + "$ref": "#/components/schemas/FraudDimensionEnum" + }, + "score": { + "type": "integer", + "nullable": true + }, + "band": { + "$ref": "#/components/schemas/FraudBandEnum" + }, + "availability": { + "$ref": "#/components/schemas/FraudAvailabilityEnum" + }, + "requested_but_unavailable": { + "type": "boolean", + "description": "True when the request supplied everything this dimension needed and the platform still could not answer it. Such a check is never recommended for approval on the strength of the dimensions that did answer." + }, + "reason_codes": { + "type": "array", + "items": { + "type": "string" + } + }, + "evidence_indexes": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "availability", + "band", + "dimension", + "evidence_indexes", + "reason_codes", + "requested_but_unavailable", + "score" + ] + }, + "CaseDuplicateSourceEnum": { + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ], + "type": "string", + "description": "* `MANUAL` - Manual\n* `AML_SCREENING` - AML screening\n* `TRANSACTION_RULE` - Transaction rule\n* `WORKFLOW_BUILDER` - Workflow builder" + }, + "CaseCollaborationPatchedCaseNoteUpdateRequest": { + "type": "object", + "properties": { + "is_pinned": { + "type": "boolean" + } + } + }, + "CustomerMessagingPurposeEnum": { + "enum": [ + "identity_document", + "proof_of_address", + "business_document", + "confirm_details", + "review_in_progress" + ], + "type": "string", + "description": "* `identity_document` - identity_document\n* `proof_of_address` - proof_of_address\n* `business_document` - business_document\n* `confirm_details` - confirm_details\n* `review_in_progress` - review_in_progress" + }, + "CaseDuplicateTypeEnum": { + "enum": [ + "user", + "business" + ], + "type": "string", + "description": "* `user` - user\n* `business` - business" + }, + "CaseCollaborationCaseNoteCreateRequest": { + "type": "object", + "properties": { + "comment": { + "type": "string", + "minLength": 1, + "maxLength": 10000 + }, + "reply_to": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "client_reference": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email", + "minLength": 1 + } + }, + "is_internal": { + "type": "boolean", + "default": false + }, + "attachments": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "required": [ + "comment" + ] + }, + "CustomerMessagingLanguagesEnum": { + "enum": [ + "en", + "es", + "pt" + ], + "type": "string", + "description": "* `en` - en\n* `es` - es\n* `pt` - pt" + }, + "CustomerMessagingCaseRequestConfirmRequest": { + "type": "object", + "properties": { + "contact_id": { + "type": "string", + "format": "uuid" + }, + "purpose": { + "$ref": "#/components/schemas/CustomerMessagingPurposeEnum" + }, + "language": { + "allOf": [ + { + "$ref": "#/components/schemas/CustomerMessagingLanguageEnum" + } + ], + "default": "en" + }, + "request_id": { + "type": "string", + "format": "uuid" + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "preview_token": { + "type": "string", + "minLength": 1, + "maxLength": 512 + } + }, + "required": [ + "contact_id", + "expires_at", + "preview_token", + "purpose", + "request_id" + ] + }, + "CaseRoutingRule": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "priority": { + "type": "integer", + "minimum": 0 + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseRoutingCondition" + }, + "maxItems": 20 + }, + "required_skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "blueprint": { + "type": "string", + "format": "uuid" + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "version": { + "type": "integer", + "readOnly": true + }, + "blueprint_name": { + "type": "string", + "readOnly": true + }, + "team_name": { + "type": "string", + "nullable": true, + "readOnly": true + } + } + }, + "FraudResultEnum": { + "enum": [ + "match", + "mismatch", + "inconclusive", + "no_reference" + ], + "type": "string", + "description": "* `match` - Match\n* `mismatch` - Mismatch\n* `inconclusive` - Inconclusive\n* `no_reference` - No Reference" + }, + "CaseRoutingRuleCreate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "priority": { + "type": "integer", + "minimum": 0 + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseRoutingCondition" + }, + "maxItems": 20 + }, + "required_skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "blueprint": { + "type": "string", + "format": "uuid" + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "publish": { + "type": "boolean", + "writeOnly": true, + "description": "Set false to save a never-published draft. The record remains inactive at version 0 until the draft is explicitly published. Omitting this field preserves immediate creation for existing integrations." + } + }, + "required": [ + "name", + "conditions", + "blueprint" + ] + }, + "FraudCheckIdentifierRequest": { + "type": "object", + "properties": { + "type": { + "allOf": [ + { + "$ref": "#/components/schemas/FraudTypeEnum" + } + ], + "description": "Identifier type. Determines which namespace fields are required.\n\n* `email` - Email\n* `phone` - Phone\n* `national_id` - National Id\n* `tax_number` - Tax Number\n* `passport_number` - Passport Number\n* `driving_licence_number` - Driving Licence Number\n* `residence_permit_number` - Residence Permit Number\n* `voter_id` - Voter Id\n* `external_id` - External Id" + }, + "value": { + "type": "string", + "minLength": 1, + "description": "The identifier value, in any customary format.", + "maxLength": 255 + }, + "country": { + "type": "string", + "description": "ISO-3166 alpha-2 or alpha-3 issuing country. Required for government identifier types: the same digits mean different people in different countries.", + "maxLength": 3 + }, + "issuer": { + "type": "string", + "description": "Optional issuing authority or sub-national issuer, when the country alone is not unique.", + "maxLength": 128 + } + }, + "required": [ + "type", + "value" + ] + }, + "CaseActionActionEnum": { + "enum": [ + "set_status", + "add_tags", + "add_note", + "add_to_blocklist" + ], + "type": "string", + "description": "* `set_status` - set_status\n* `add_tags` - add_tags\n* `add_note` - add_note\n* `add_to_blocklist` - add_to_blocklist" + }, + "CaseActionCaseActionReceiptPage": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next_offset": { + "type": "integer", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseActionCaseActionReceipt" + } + } + }, + "required": [ + "count", + "next_offset", + "results" + ] + }, + "CaseActionCaseEntityActionResult": { + "type": "object", + "properties": { + "receipt": { + "$ref": "#/components/schemas/CaseActionCaseActionReceipt" + }, + "entity_type": { + "$ref": "#/components/schemas/CaseActionEntityTypeEnum" + }, + "entity_uuid": { + "type": "string", + "format": "uuid" + }, + "action": { + "type": "string" + }, + "previous_status": { + "type": "string" + }, + "new_status": { + "type": "string" + }, + "tags_added": { + "type": "array", + "items": { + "type": "string" + } + }, + "comment_uuid": { + "type": "string", + "format": "uuid" + }, + "blocklisted": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "CustomerMessagingCaseRequest": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "purpose": { + "type": "string", + "readOnly": true + }, + "language": { + "type": "string", + "readOnly": true + }, + "recipient": { + "type": "string", + "format": "email", + "readOnly": true + }, + "expires_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "actor_email": { + "type": "string", + "format": "email", + "readOnly": true, + "nullable": true + }, + "delivery_status": { + "type": "string", + "readOnly": true + }, + "delivered_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "nullable": true + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "nullable": true + }, + "responded_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "nullable": true + }, + "response": { + "readOnly": true, + "nullable": true + }, + "has_attachment": { + "type": "boolean", + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "actor_email", + "cancelled_at", + "created_at", + "delivered_at", + "delivery_status", + "expires_at", + "has_attachment", + "language", + "purpose", + "recipient", + "responded_at", + "response", + "uuid" + ] + }, + "CustomerMessagingCaseRequestPublic": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid" + }, + "purpose": { + "$ref": "#/components/schemas/CustomerMessagingPurposeEnum" + }, + "language": { + "$ref": "#/components/schemas/CustomerMessagingLanguageEnum" + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "responded_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "copy": { + "type": "object", + "additionalProperties": {} + }, + "organization_name": { + "type": "string" + }, + "is_white_label": { + "type": "boolean" + }, + "customization": { + "type": "object", + "additionalProperties": {}, + "nullable": true + } + }, + "required": [ + "copy", + "customization", + "expires_at", + "is_white_label", + "language", + "organization_name", + "purpose", + "request_id", + "responded_at" + ] + }, + "RoutingPublicationFieldEnum": { + "enum": [ + "source", + "priority", + "tags" + ], + "type": "string", + "description": "* `source` - source\n* `priority` - priority\n* `tags` - tags" + }, + "RoutingPublicationCaseRoutingRule": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "priority": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RoutingPublicationRoutingCondition" + } + }, + "required_skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "blueprint": { + "type": "string", + "format": "uuid" + }, + "blueprint_name": { + "type": "string", + "readOnly": true + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "team_name": { + "type": "string", + "readOnly": true + }, + "is_active": { + "type": "boolean" + }, + "version": { + "type": "integer", + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "blueprint", + "blueprint_name", + "conditions", + "created_at", + "name", + "team_name", + "updated_at", + "uuid", + "version" + ] + }, + "CaseActionStatusEnum": { + "enum": [ + "APPLIED", + "FAILED" + ], + "type": "string", + "description": "* `APPLIED` - Applied\n* `FAILED` - Failed" + }, + "FraudCheckModuleSelectionRequest": { + "type": "object", + "properties": { + "include": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudExcludeEnum" + } + }, + "exclude": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudExcludeEnum" + } + } + } + }, + "SignatureField": { + "type": "object", + "description": "A field to place on a session document: which signer fills it, how, and where. Give either a position (`page`, `x`, `y`, `width` and `height`) or an `anchor`. Positions are fractions of the page as it's displayed, from its top-left corner, rounded to 4 decimals, and the field must fit inside the page. A document takes up to 200 fields and one signer up to 500, and every role with fields on a document needs a `signature` or `initials` field there. See [Fields on the document](/core-technology/electronic-signature/overview#fields-on-the-document).", + "required": [ + "key", + "type" + ], + "properties": { + "key": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]{0,63}$", + "description": "Names the field in the results and in `sign.field_values`. Unique within the document." + }, + "type": { + "type": "string", + "enum": [ + "signature", + "initials", + "full_name", + "date_signed", + "text", + "checkbox", + "email", + "data" + ], + "description": "What the field holds. The signer applies their adopted `signature` and `initials`, types `text`, ticks a `checkbox`, and types an `email` when the session has no verified email. Didit fills `full_name` (the verified name), `date_signed` (the date of signing, in the signer's time zone), `data` (from `prefill`) and a verified `email`, and shows them read-only. There is no date picker type: use `text`." + }, + "role": { + "type": "string", + "enum": [ + "verified_person", + "legal_representative", + "key_people" + ], + "description": "Who fills the field, with the `signature_signer` values. A step fills and stamps only the fields of its own signer. Defaults to the step's signer." + }, + "page": { + "type": "integer", + "minimum": 1, + "description": "Page number, from 1 to the document's page count. For a Word document, a page of the converted PDF." + }, + "x": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Left edge, as a fraction of the page width." + }, + "y": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Top edge, as a fraction of the page height." + }, + "width": { + "type": "number", + "minimum": 0.02, + "maximum": 1, + "description": "Width, as a fraction of the page width. A signature is at least `0.12` wide." + }, + "height": { + "type": "number", + "minimum": 0.012, + "maximum": 1, + "description": "Height, as a fraction of the page height. A signature is at least `0.035` high." + }, + "anchor": { + "$ref": "#/components/schemas/SignatureFieldAnchor" + }, + "required": { + "type": "boolean", + "description": "`signature` and `initials` are required unless you set `false`. `full_name`, `date_signed` and `data` are always required. The signer can't finish a document until its required fields have a value, and a required checkbox must be ticked." + }, + "label": { + "type": "string", + "minLength": 1, + "maxLength": 80, + "description": "Shown to the signer and in the results. Defaults from the type." + }, + "placeholder": { + "type": "string", + "nullable": true, + "maxLength": 80, + "description": "`text` and `email` only." + }, + "validation": { + "$ref": "#/components/schemas/SignatureFieldValidation" + }, + "prefill": { + "type": "string", + "nullable": true, + "description": "Where the value comes from: `identity.full_name`, `identity.first_name`, `identity.last_name`, `identity.date_of_birth` (`DD/MM/YYYY`), `identity.nationality`, `identity.document_number`, `identity.document_type`, `identity.issuing_country`, `identity.address`, `contact.email`, `contact.phone`, `company.name`, `company.registration_number`, `company.address`, `company.country` (KYB), `session.vendor_data`, `metadata.` (a key of the session's `metadata`) or `api.` (a key of `sign.field_values`). Required for `data`. `full_name` always uses `identity.full_name` and `email` uses `contact.email`. The values of `data`, `full_name` and verified `email` fields are fixed when the signer starts signing." + }, + "editable_when_prefilled": { + "type": "boolean", + "default": false, + "description": "`text` and `email` only: the signer can change a pre-filled value." + }, + "checked_by_default": { + "type": "boolean", + "default": false, + "description": "`checkbox` only: the box starts ticked." + }, + "date_format": { + "type": "string", + "enum": [ + "DD/MM/YYYY", + "MM/DD/YYYY", + "YYYY-MM-DD", + "D MMMM YYYY" + ], + "default": "DD/MM/YYYY", + "description": "`date_signed` only." + } + } + }, + "SessionSignDocument": { + "type": "object", + "required": [ + "file_name", + "content_base64" + ], + "properties": { + "title": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "Title shown to the signer. Defaults to `file_name`." + }, + "file_name": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "File name, ending in `.pdf` or `.docx`." + }, + "content_base64": { + "type": "string", + "format": "byte", + "description": "The file, base64-encoded. A PDF can have up to 20 MB and 100 pages, without a password, scripts or attached files. A Word file (`.docx`) can have up to 20 MB and is converted to PDF while the request runs, in at most 25 seconds; the PDF then has the same limits. Up to 5 Word files per session. A Word file with macros, tracked changes, links to external content or embedded objects is refused with `signature_document_not_supported`, and one that can't be converted returns `signature_document_conversion_failed`, each with a `reason`." + }, + "fields": { + "type": "array", + "description": "Optional fields to place on this document, up to 200. Pages and positions refer to the PDF, or to the converted PDF for a Word file. Without fields, the signer reads the document and agrees to it.", + "items": { + "$ref": "#/components/schemas/SignatureField" + }, + "maxItems": 200 + } + } + }, + "SignatureResult": { + "type": "object", + "description": "One signer's result for one Electronic Signature step. See the [Electronic Signature report](/core-technology/electronic-signature/report-electronic-signature).", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Pass it as `signature_id` to download this signer's evidence or validate their signature." + }, + "node_id": { + "type": "string", + "description": "The workflow step that produced it." + }, + "status": { + "type": "string", + "enum": [ + "intro", + "review", + "code", + "signing", + "signed", + "declined" + ], + "description": "Where the signer is in the step. Only `signed` means the documents are signed." + }, + "verdict": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "In Review", + "Declined" + ], + "description": "The step's outcome. A warning sets it through its rule; custom status rules apply after." + }, + "level": { + "type": "string", + "enum": [ + "aes", + "qes" + ], + "description": "`aes`. `qes` (Qualified Electronic Signature, QES) is coming soon." + }, + "format": { + "type": "string", + "enum": [ + "pades" + ] + }, + "profile": { + "type": "string", + "nullable": true, + "description": "`PAdES-B-LT` once signed." + }, + "signer": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "description": "Verified name." + }, + "capacity": { + "type": "string", + "enum": [ + "self", + "company" + ] + }, + "company": { + "type": "string", + "nullable": true + }, + "role": { + "type": "string", + "nullable": true, + "description": "The key person's designation, as declared." + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The key person's structured roles, as declared for the company." + }, + "identity_session_id": { + "type": "string", + "format": "uuid", + "description": "The KYC session that verified the signer." + } + } + }, + "identity_used": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid" + }, + "kyc_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "verified": { + "type": "boolean" + }, + "expected_name": { + "type": "string", + "nullable": true, + "description": "The name the signer had to match." + } + } + }, + "certificate": { + "type": "object", + "nullable": true, + "description": "The certificate the documents were signed with. `null` until signed.", + "properties": { + "issuer": { + "type": "string" + }, + "issued_to": { + "type": "string" + }, + "serial": { + "type": "string", + "description": "Serial number, hexadecimal." + }, + "valid_from": { + "type": "string", + "format": "date-time" + }, + "valid_until": { + "type": "string", + "format": "date-time" + } + } + }, + "confirmation": { + "type": "object", + "properties": { + "channel": { + "type": "string", + "enum": [ + "email", + "sms" + ] + }, + "destination": { + "type": "string", + "nullable": true, + "description": "The masked contact the code went to." + }, + "confirmed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "attempts": { + "type": "integer", + "description": "Codes entered, the right one included." + } + } + }, + "timestamp": { + "type": "object", + "nullable": true, + "description": "The trusted timestamp in the signature. `null` until signed.", + "properties": { + "time": { + "type": "string", + "format": "date-time" + }, + "qualified": { + "type": "boolean", + "description": "Always `false` for AES." + }, + "valid": { + "type": "boolean" + } + } + }, + "documents": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The document on this signer's copy. Use it to download the signed file." + }, + "uuid": { + "type": "string", + "format": "uuid", + "description": "Same as `id`." + }, + "title": { + "type": "string" + }, + "file_name": { + "type": "string" + }, + "pages": { + "type": "integer", + "description": "Pages of the document signed. For Word, of the converted PDF." + }, + "size_bytes": { + "type": "integer" + }, + "sha256_original": { + "type": "string", + "description": "SHA-256 of the document as the signer was shown it." + }, + "sha256_signed": { + "type": "string", + "nullable": true, + "description": "SHA-256 of the signed PDF, once signed." + }, + "status": { + "type": "string", + "enum": [ + "signed", + "declined", + "not_signed", + "not_reached" + ], + "description": "`not_signed`: opened but not signed. `not_reached`: never opened." + }, + "opened_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "read_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the signer reached the end of a document without fields, when the step requires it." + }, + "read_to_end": { + "type": "boolean" + }, + "consented_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the signer agreed to a document without fields, or finished the fields of a document with fields." + }, + "consent": { + "type": "boolean" + }, + "signed_file": { + "type": "string", + "nullable": true, + "description": "Path of the signed PDF, once signed. See [Download a signed document](/sessions-api/electronic-signature/download-signed-document)." + }, + "mode": { + "type": "string", + "enum": [ + "fields", + "read_and_agree" + ], + "description": "`fields` for a document with fields, `read_and_agree` for a document without." + }, + "source_format": { + "type": "string", + "enum": [ + "pdf", + "docx" + ], + "description": "`pdf`, or `docx` for a Word document Didit converted to PDF." + }, + "conversion": { + "type": "object", + "nullable": true, + "description": "`null` for a PDF. For a Word document, the original file and the conversion.", + "properties": { + "from": { + "type": "string", + "enum": [ + "docx" + ] + }, + "file_name": { + "type": "string", + "description": "Your original Word file name." + }, + "sha256": { + "type": "string", + "description": "SHA-256 of your original Word file." + }, + "size_bytes": { + "type": "integer", + "description": "Size of your original Word file." + }, + "converter": { + "type": "string", + "description": "Version of the converter that produced the PDF." + } + } + }, + "fields": { + "type": "array", + "description": "Fields on the document, with their values. Empty for a document without fields.", + "items": { + "$ref": "#/components/schemas/SignatureFieldResult" + } + } + } + } + }, + "adopted_signature": { + "type": "object", + "nullable": true, + "description": "The signature and initials the signer adopted for signature and initials fields. `null` until the signer adopts one.", + "properties": { + "method": { + "type": "string", + "enum": [ + "typed", + "drawn" + ], + "description": "`typed`: the verified name in a handwriting style. `drawn`: drawn with a finger or a mouse." + }, + "font": { + "type": "string", + "nullable": true, + "description": "The handwriting style of a typed signature. `null` when drawn." + }, + "adopted_at": { + "type": "string", + "format": "date-time" + } + } + }, + "electronic_signature_consent_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the signer agreed, on the review screen, to sign the documents electronically. Set when any document has fields." + }, + "evidence_file": { + "type": "string", + "nullable": true, + "description": "Path of the evidence PDF, once signed. See [Download the signature evidence](/sessions-api/electronic-signature/download-evidence)." + }, + "warnings": { + "type": "array", + "description": "Warnings raised for this signer. See [Electronic Signature warnings](/core-technology/electronic-signature/warnings-electronic-signature).", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": [ + "SIGNATURE_DECLINED", + "SIGNATURE_OTP_FAILED", + "SIGNATURE_NAME_MISMATCH", + "SIGNATURE_IDENTITY_NOT_VERIFIED", + "SIGNATURE_DOCUMENT_CHANGED", + "SIGNATURE_FIELD_DATA_MISSING" + ] + }, + "node_id": { + "type": "string" + } + } + } + }, + "validation": { + "type": "object", + "nullable": true, + "description": "The last cryptographic check of the signed documents. `null` until the documents are signed. If Didit can't confirm the outcome of signing, it's `{\"valid\": null, \"reason\": \"signature_provider_outcome_unknown\", \"checked_at\": ...}` and `status` stays `signing`: treat it as not signed.", + "properties": { + "valid": { + "type": "boolean", + "nullable": true, + "description": "`true` when every document is valid. `null` when the outcome of signing couldn't be confirmed." + }, + "reason": { + "type": "string", + "description": "Set when `valid` is `null`: `signature_provider_outcome_unknown`." + }, + "checked_at": { + "type": "string", + "format": "date-time" + }, + "documents": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "checked_at": { + "type": "string", + "format": "date-time" + }, + "valid": { + "type": "boolean", + "description": "`true` when the signature and the timestamp are both valid and trusted." + }, + "integrity": { + "type": "boolean", + "description": "`true` when the document is unchanged since signing." + }, + "trusted": { + "type": "boolean", + "description": "`true` when the signing certificate chains to a trusted root." + }, + "revoked": { + "type": "boolean" + }, + "trusted_list": { + "type": "object", + "description": "EU Trusted List check. For AES: `{\"status\": \"not_applicable\", \"reason\": \"aes_not_qualified\"}`.", + "properties": { + "status": { + "type": "string" + }, + "reason": { + "type": "string" + } + } + }, + "certificate": { + "type": "object", + "nullable": true, + "description": "The certificate the documents were signed with. `null` until signed.", + "properties": { + "issuer": { + "type": "string" + }, + "issued_to": { + "type": "string" + }, + "serial": { + "type": "string", + "description": "Serial number, hexadecimal." + }, + "valid_from": { + "type": "string", + "format": "date-time" + }, + "valid_until": { + "type": "string", + "format": "date-time" + } + } + }, + "timestamp": { + "type": "object", + "nullable": true, + "description": "The trusted timestamp in the signature. `null` until signed.", + "properties": { + "time": { + "type": "string", + "format": "date-time" + }, + "qualified": { + "type": "boolean", + "description": "Always `false` for AES." + }, + "valid": { + "type": "boolean" + } + } + } + } + } + } + } + }, + "declined_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "declined_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ] + }, + "signed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the signing link stops working." + }, + "email_sent_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "email_unavailable_reason": { + "type": "string", + "nullable": true + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "The signer's IP address when they asked for their code." + }, + "place": { + "type": "string", + "nullable": true, + "description": "Not collected: always `null`." + }, + "place_unavailable_reason": { + "type": "string", + "nullable": true, + "enum": [ + "not_collected" + ] + } + } + }, + "SignatureFieldValidation": { + "type": "object", + "nullable": true, + "description": "Rules for a `text` field's value. The console preview, the signing flow and the API apply the same rules.", + "properties": { + "kind": { + "type": "string", + "enum": [ + "any", + "letters", + "number", + "email", + "pattern" + ], + "description": "`letters`: letters in any script, spaces, hyphens and apostrophes. `number`: digits with an optional decimal separator. `pattern`: the value must match `pattern`." + }, + "min_length": { + "type": "integer", + "minimum": 0, + "maximum": 500, + "description": "From `0` up to `max_length`." + }, + "max_length": { + "type": "integer", + "minimum": 1, + "maximum": 500, + "default": 120 + }, + "pattern": { + "type": "string", + "nullable": true, + "minLength": 1, + "maxLength": 200, + "description": "Required with `kind: \"pattern\"`. A regular expression the whole value must match. Lookarounds and backreferences aren't supported." + }, + "message": { + "type": "string", + "nullable": true, + "maxLength": 120, + "description": "Shown to the signer instead of the default error." + } + } + }, + "SignatureFieldAnchor": { + "type": "object", + "description": "Places a field by a piece of text in the document instead of a position. Didit doesn't remove the text from the PDF, so write it in the same color as the page background. When the text isn't found, the call returns `400` with `field_anchor_not_found`. See [Place fields by anchor text](/sessions-api/create-session#place-fields-by-anchor-text).", + "required": [ + "text" + ], + "properties": { + "text": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "description": "Text searched in the document. For a Word file, in the converted PDF." + }, + "occurrence": { + "type": "string", + "enum": [ + "first", + "all" + ], + "default": "first", + "description": "`all` places one field per match; the keys after the first get `_2`, `_3` and so on." + }, + "offset_x": { + "type": "number", + "description": "Added to the left edge of the text, as a fraction of the page width." + }, + "offset_y": { + "type": "number", + "description": "Added to the top edge of the text, as a fraction of the page height." + }, + "width": { + "type": "number", + "minimum": 0.02, + "maximum": 1, + "description": "Width of the field, as a fraction of the page width." + }, + "height": { + "type": "number", + "minimum": 0.012, + "maximum": 1, + "description": "Height of the field, as a fraction of the page height." + } + } + }, + "SignatureFieldResult": { + "type": "object", + "description": "A field of a signed document, with its value on this signer's copy. See [Fields](/core-technology/electronic-signature/report-electronic-signature#fields).", + "properties": { + "key": { + "type": "string", + "description": "The field's key, as used in `sign.field_values`." + }, + "type": { + "type": "string", + "enum": [ + "signature", + "initials", + "full_name", + "date_signed", + "text", + "checkbox", + "email", + "data" + ] + }, + "role": { + "type": "string", + "enum": [ + "verified_person", + "legal_representative", + "key_people" + ] + }, + "label": { + "type": "string" + }, + "page": { + "type": "integer", + "description": "Page number, from 1. For a Word document, a page of the converted PDF." + }, + "x": { + "type": "number", + "description": "Left edge, as a fraction of the page width." + }, + "y": { + "type": "number", + "description": "Top edge, as a fraction of the page height." + }, + "width": { + "type": "number", + "description": "Width, as a fraction of the page width." + }, + "height": { + "type": "number", + "description": "Height, as a fraction of the page height." + }, + "required": { + "type": "boolean" + }, + "value": { + "nullable": true, + "description": "A string for `text`, `email`, `full_name`, `date_signed` and `data`, a boolean for a `checkbox`, and `true` for an applied signature or initials. `null` when empty." + }, + "display_value": { + "type": "string", + "nullable": true, + "description": "The value as stamped on the page: dates in the field's `date_format`, `Yes` or `No` for a checkbox, `Signed` for a signature or initials." + }, + "value_source": { + "type": "string", + "nullable": true, + "enum": [ + "signer", + "identity", + "contact", + "company", + "session", + "metadata", + "api", + "signing_time", + null + ], + "description": "Where the value came from: `signer` (typed, ticked or applied by the signer), `identity` (the verified ID), `contact` (the verified email or phone), `company` (the verified company), `session` (`vendor_data`), `metadata`, `api` (`sign.field_values`) or `signing_time` (the date signed)." + }, + "filled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "image_url": { + "type": "string", + "nullable": true, + "description": "Signature and initials only: the path of the applied image. See [Download a signature image](/sessions-api/electronic-signature/download-field-image)." + }, + "value_unavailable_reason": { + "type": "string", + "nullable": true, + "enum": [ + "source_empty", + null + ], + "description": "`source_empty` when a pre-filled field had no value in the session and `signature_field_data_missing_action` let the signer sign anyway." + } + } + }, + "VaspDirectoryWidgetEntry": { + "type": "object", + "description": "What the end user's VASP picker shows: who the VASP is and whether it can be reached.", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The entry's id, for GET /v3/travel-rule/vasps/{vasp_id}/." + }, + "kind": { + "type": "string", + "enum": [ + "directory", + "didit" + ], + "description": "directory for an entry of the shared directory; didit for a member of the Didit network." + }, + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "description": "ISO 3166 country code, as the source gives it." + }, + "domains": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The VASP's web domains. Always empty for Didit network members." + }, + "reachable": { + "type": "boolean", + "description": "Whether your application has an available route to this VASP, or to one of its entities for a brand. Always true for a Didit network member." + } + } + }, + "TravelRuleDataSharingFields": { + "type": "object", + "description": "The fields Didit sends to the counterparty VASP about your own users, per entity type. The `name` and the `wallet_address` are always sent, whatever the list says. An entity type you never chose shares every field. An empty list is a choice too: it shares the name and the wallet only.", + "properties": { + "individual": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "name", + "wallet_address", + "address", + "national_id", + "customer_identification", + "date_of_birth", + "place_of_birth" + ] + }, + "description": "Fields shared about a natural person." + }, + "company": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "name", + "wallet_address", + "address", + "national_id", + "customer_identification" + ] + }, + "description": "Fields shared about a legal person." + } + } + }, + "TravelRuleMatchingRules": { + "type": "object", + "description": "How strictly each field of the counterparty's data is compared with what you hold, per entity type. A field you leave out keeps its default. `NONE` skips the comparison, `STRICT` needs exactly the same words, `CONTAINS` accepts one value whose words all appear in the other, `DEFAULT` is the standard subset match, and `FUZZY` accepts close spellings (and, for dates of birth, a swapped day and month).", + "properties": { + "individual": { + "type": "object", + "description": "Rules for a natural person.", + "properties": { + "name": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default: follows `name_matching_strictness`." + }, + "date_of_birth": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default `NONE`." + }, + "address": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default `NONE`." + }, + "account": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "The wallet the exchange was routed by. Default `STRICT`." + } + } + }, + "company": { + "type": "object", + "description": "Rules for a legal person.", + "properties": { + "name": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default: follows `name_matching_strictness`." + }, + "address": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default `NONE`." + }, + "account": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "The wallet the exchange was routed by. Default `STRICT`." + } + } + } + } + }, + "TravelRuleDataMatch": { + "type": "object", + "nullable": true, + "description": "Field-by-field comparison of the transfer's party data with what the other side knows about the wallet holder. It names fields and results, never values, so it stays readable while the counterparty's data is masked. Null until a comparison ran.", + "properties": { + "matched": { + "type": "boolean", + "description": "True when no compared field differs." + }, + "mismatched_fields": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "name", + "date_of_birth", + "address", + "account" + ] + }, + "description": "The fields that differ." + }, + "fields": { + "type": "object", + "description": "One entry per field compared for the party's entity type (`name`, `date_of_birth`, `address`, `account`; a company has no `date_of_birth`).", + "additionalProperties": { + "type": "object", + "properties": { + "result": { + "type": "string", + "enum": [ + "matched", + "mismatched", + "not_compared" + ], + "description": "`not_compared` when the rule is `NONE` or either side has no value for the field." + }, + "strictness": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ] + } + } + } + } + } + }, + "TravelRuleDataUnmasked": { + "type": "object", + "nullable": true, + "description": "Audit record of the moment the counterparty's data was unmasked. Null while the data is still masked, and for outbound transfers.", + "properties": { + "at": { + "type": "string", + "format": "date-time" + }, + "reason": { + "type": "string", + "enum": [ + "ownership_confirmed", + "wallet_proof", + "address_book_match", + "own_verified_wallet", + "inquiry_approved", + "known_payment_method", + "travel_address" + ], + "description": "What confirmed ownership: ownership_confirmed (your confirmation through the console or the API), wallet_proof (a wallet proof in the widget, or an INTERNAL inbound transfer, set when it is created), address_book_match (a verified address-book wallet), own_verified_wallet (your user's own verified wallet), inquiry_approved (your approval of an inbound TRP inquiry or a GTR or Sygna message), known_payment_method (a wallet your user already paid with) or travel_address (the per-wallet travel address named the wallet)." + }, + "actor": { + "type": "string", + "description": "Who confirmed it: the console user's email or the API key's label. Blank for an automatic decision." + } + } + }, + "TravelRuleMessageAttachment": { + "type": "object", + "nullable": true, + "description": "The document attached to a message, stored like any other transaction document.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string", + "nullable": true + }, + "note": { + "type": "string", + "nullable": true + }, + "uploaded_by": { + "type": "string", + "nullable": true, + "description": "Who attached it. In the pickup portal, a file you sent shows your VASP name, never the person or API key behind it." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "files": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "file_url": { + "type": "string", + "nullable": true, + "description": "Time-limited download URL." + }, + "original_filename": { + "type": "string" + }, + "file_size": { + "type": "integer" + } + } + } + } + } + }, + "TravelRuleMessage": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "kind": { + "type": "string", + "enum": [ + "MESSAGE", + "DOCUMENT_REQUEST" + ] + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "`OUTBOUND` is what the reader's own side sent. In the pickup portal the direction is from the counterparty's side." + }, + "author": { + "type": "string", + "description": "Who wrote it on its own side: a console user's email or an API key label, or the other VASP's name." + }, + "body": { + "type": "string" + }, + "requested_documents": { + "type": "array", + "items": { + "type": "string" + }, + "description": "What a DOCUMENT_REQUEST asks for, in the requester's words." + }, + "reply_to": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "fulfilled_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set on a DOCUMENT_REQUEST once a reply to it carries a document." + }, + "attachment": { + "$ref": "#/components/schemas/TravelRuleMessageAttachment" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleMessageCreateRequest": { + "type": "object", + "description": "Send as JSON, or as multipart/form-data with the attachments in `files`.", + "properties": { + "kind": { + "type": "string", + "enum": [ + "MESSAGE", + "DOCUMENT_REQUEST" + ], + "default": "MESSAGE" + }, + "body": { + "type": "string", + "maxLength": 5000, + "description": "Required for a MESSAGE unless a file is attached." + }, + "requested_documents": { + "type": "array", + "items": { + "type": "string", + "maxLength": 255 + }, + "maxItems": 10, + "description": "Required (at least one) for a DOCUMENT_REQUEST, and only allowed there." + }, + "reply_to": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "A message of the same transfer. A reply that carries a document fulfils the document request it answers." + }, + "files": { + "type": "array", + "items": { + "type": "string", + "format": "binary" + }, + "maxItems": 5, + "description": "Multipart only. Up to 5 files of 10 MB each." + } + } + }, + "SessionDeletionJob": { + "type": "object", + "description": "A bulk session deletion running in the background: progress and counts per outcome, never which sessions it deleted.", + "properties": { + "job_id": { + "type": "string", + "format": "uuid", + "description": "Poll `GET /v3/sessions/delete/{job_id}/` with this id." + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ], + "description": "`PENDING`: accepted, not started yet. `PROCESSING`: deleting; `processed` and `outcomes` are updating. `COMPLETED`: every selected session is accounted for. `FAILED`: the job stopped early; see `error`." + }, + "total": { + "type": "integer", + "description": "Sessions selected when the deletion was requested." + }, + "processed": { + "type": "integer", + "description": "Sessions the job has finished with so far." + }, + "outcomes": { + "type": "object", + "description": "How many sessions ended in each outcome. Every key is always present.", + "properties": { + "deleted": { + "type": "integer", + "description": "Sessions deleted together with their face embedding." + }, + "none": { + "type": "integer", + "description": "Sessions deleted that had no face embedding." + }, + "retained_with_user": { + "type": "integer", + "description": "Sessions deleted while one image-free biometric template was retained for their User, as the request or the application policy asked." + }, + "ineligible_no_vendor_user": { + "type": "integer", + "description": "Sessions deleted, with their face embedding, because retention was requested but they had no linked User." + }, + "failed_retryable": { + "type": "integer", + "description": "Sessions reported as not deleted. A job retries a group it could not delete by itself instead of counting it here." + }, + "already_deleted": { + "type": "integer", + "description": "Selected sessions that were already deleted, for example by another request, when the job reached them." + } + } + }, + "deleted_child_verifications": { + "type": "integer", + "description": "Verifications that the deleted business verifications created, deleted with them. Always `0` for a job started by `POST /v3/sessions/delete/`, which deletes KYC sessions only." + }, + "error": { + "type": "string", + "nullable": true, + "description": "`null` unless `status` is `FAILED`; then why the deletion stopped. The sessions the job had not reached are still in place: repeat the request to delete them." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the deletion was requested." + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the job finished; `null` while it runs." + } + } + }, + "EntityStatusUpdateRequest": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "New lifecycle status. `BLOCKED` also adds the `vendor_data` to the system blocklist." + } + } + }, + "AMLMonitoringToggleResult": { + "oneOf": [ + { + "type": "object", + "required": [ + "users_total", + "already_enabled", + "with_existing_aml", + "needing_screening", + "skipped_no_identity_data", + "skipped_blocked", + "estimated_screening_cost_usd", + "estimated_annual_monitoring_cost_usd", + "balance_sufficient", + "unmatched" + ], + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + }, + "unmatched": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Identifiers of the request that matched no profile in this application, exactly as sent." + } + } + }, + { + "type": "object", + "required": [ + "job_id", + "status", + "is_enabled", + "target_type", + "total", + "processed", + "outcomes", + "results", + "error", + "created_at", + "completed_at", + "estimate", + "unmatched" + ], + "properties": { + "job_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ] + }, + "is_enabled": { + "type": "boolean", + "description": "true when the job enables monitoring, false when it disables it." + }, + "target_type": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Which profile family the job toggles." + }, + "total": { + "type": "integer", + "description": "Profiles the job processes." + }, + "processed": { + "type": "integer", + "description": "Profiles processed so far." + }, + "outcomes": { + "type": "object", + "description": "Per-record outcome counters. Every selected profile resolves to exactly one counter; nothing is silently skipped.", + "properties": { + "enabled": { + "type": "integer", + "description": "Monitoring enabled on an existing AML screening." + }, + "screened_and_enabled": { + "type": "integer", + "description": "A new AML screening was created (billed) and monitoring enabled on it." + }, + "already_enabled": { + "type": "integer", + "description": "Monitoring was already active; nothing changed and nothing was billed." + }, + "disabled": { + "type": "integer", + "description": "Monitoring turned off across every AML screening of the profile." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Skipped: the profile lacks the full name, date of birth and country (users) or a name (businesses) needed to screen." + }, + "skipped_blocked": { + "type": "integer", + "description": "Skipped: the user is blocked." + }, + "skipped_sandbox": { + "type": "integer", + "description": "Skipped: sandbox application, no screening is created." + }, + "failed_provider": { + "type": "integer", + "description": "The screening could not be completed; retry later." + }, + "failed_insufficient_balance": { + "type": "integer", + "description": "Balance ran out while the job was running." + } + } + }, + "results": { + "type": "array", + "description": "One entry per processed profile, in processing order. Grows while the job runs; complete once status is COMPLETED. Recorded for jobs started through the API.", + "items": { + "type": "object", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit internal id of the profile." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the profile." + }, + "outcome": { + "type": "string", + "enum": [ + "enabled", + "screened_and_enabled", + "already_enabled", + "disabled", + "skipped_no_identity_data", + "skipped_blocked", + "skipped_sandbox", + "failed_provider", + "failed_insufficient_balance" + ] + } + } + } + }, + "error": { + "type": "string", + "nullable": true, + "description": "Generic failure message when status is FAILED; the detailed cause is kept on the platform side." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + } + } + }, + "unmatched": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Identifiers of the request that matched no profile in this application, exactly as sent. They are not part of the job." + } + } + } + ] + }, + "AMLMonitoringJob": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ] + }, + "is_enabled": { + "type": "boolean", + "description": "true when the job enables monitoring, false when it disables it." + }, + "target_type": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Which profile family the job toggles." + }, + "total": { + "type": "integer", + "description": "Profiles the job processes." + }, + "processed": { + "type": "integer", + "description": "Profiles processed so far." + }, + "outcomes": { + "type": "object", + "description": "Per-record outcome counters. Every selected profile resolves to exactly one counter; nothing is silently skipped.", + "properties": { + "enabled": { + "type": "integer", + "description": "Monitoring enabled on an existing AML screening." + }, + "screened_and_enabled": { + "type": "integer", + "description": "A new AML screening was created (billed) and monitoring enabled on it." + }, + "already_enabled": { + "type": "integer", + "description": "Monitoring was already active; nothing changed and nothing was billed." + }, + "disabled": { + "type": "integer", + "description": "Monitoring turned off across every AML screening of the profile." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Skipped: the profile lacks the full name, date of birth and country (users) or a name (businesses) needed to screen." + }, + "skipped_blocked": { + "type": "integer", + "description": "Skipped: the user is blocked." + }, + "skipped_sandbox": { + "type": "integer", + "description": "Skipped: sandbox application, no screening is created." + }, + "failed_provider": { + "type": "integer", + "description": "The screening could not be completed; retry later." + }, + "failed_insufficient_balance": { + "type": "integer", + "description": "Balance ran out while the job was running." + } + } + }, + "results": { + "type": "array", + "description": "One entry per processed profile, in processing order. Grows while the job runs; complete once status is COMPLETED. Recorded for jobs started through the API.", + "items": { + "type": "object", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit internal id of the profile." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the profile." + }, + "outcome": { + "type": "string", + "enum": [ + "enabled", + "screened_and_enabled", + "already_enabled", + "disabled", + "skipped_no_identity_data", + "skipped_blocked", + "skipped_sandbox", + "failed_provider", + "failed_insufficient_balance" + ] + } + } + } + }, + "error": { + "type": "string", + "nullable": true, + "description": "Generic failure message when status is FAILED; the detailed cause is kept on the platform side." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + } + } + }, + "unmatched": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Identifiers of the request that matched no profile in this application, exactly as sent. They are not part of the job." + } + } + }, + "OngoingMonitoringToggleRequest": { + "type": "object", + "required": [ + "is_enabled" + ], + "properties": { + "is_enabled": { + "type": "boolean", + "description": "true to enable monitoring, false to disable it." + }, + "estimate_only": { + "type": "boolean", + "default": false, + "description": "When true, returns the cost estimate and performs no writes." + }, + "update_all": { + "type": "boolean", + "default": false, + "description": "Apply to every entity of the application (scoped by the filter_* params when present)." + }, + "filter_search": { + "type": "string" + }, + "filter_status": { + "type": "array", + "items": { + "type": "string" + } + }, + "filter_country": { + "type": "string" + }, + "filter_date_from": { + "type": "string", + "format": "date" + }, + "filter_date_to": { + "type": "string", + "format": "date" + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Entities to toggle. Required unless update_all is true." + } + } + }, + "OngoingMonitoringJob": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ] + }, + "is_enabled": { + "type": "boolean" + }, + "total": { + "type": "integer" + }, + "processed": { + "type": "integer" + }, + "outcomes": { + "type": "object", + "description": "Per-entity outcome counters. Nothing is silently skipped: every requested entity resolves to exactly one counter.", + "properties": { + "enabled": { + "type": "integer", + "description": "Monitoring enabled on an existing AML screening." + }, + "screened_and_enabled": { + "type": "integer", + "description": "A new AML screening was created (billed) and monitoring enabled." + }, + "already_enabled": { + "type": "integer", + "description": "Monitoring was already active (legacy duplicate rows are consolidated)." + }, + "disabled": { + "type": "integer", + "description": "Monitoring disabled on every AML row of the entity." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Skipped: no full name + date of birth + country available to screen." + }, + "skipped_blocked": { + "type": "integer", + "description": "Skipped: the user is blocked." + }, + "skipped_sandbox": { + "type": "integer", + "description": "Skipped: sandbox application." + }, + "failed_provider": { + "type": "integer", + "description": "The screening provider call failed." + }, + "failed_insufficient_balance": { + "type": "integer", + "description": "Balance ran out mid-job." + } + } + }, + "error": { + "type": "string", + "nullable": true + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (no writes are performed).", + "properties": { + "users_total": { + "type": "integer", + "description": "Entities in the selection." + }, + "already_enabled": { + "type": "integer" + }, + "with_existing_aml": { + "type": "integer", + "description": "Entities whose existing AML screening will be switched to monitored." + }, + "needing_screening": { + "type": "integer", + "description": "Entities without any AML screening that will be screened first (billed)." + }, + "skipped_no_identity_data": { + "type": "integer" + }, + "skipped_blocked": { + "type": "integer" + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2 + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07 + }, + "balance_sufficient": { + "type": "boolean" + } + } + } + } + }, + "OngoingMonitoringToggleResult": { + "oneOf": [ + { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (no writes are performed).", + "properties": { + "users_total": { + "type": "integer", + "description": "Entities in the selection." + }, + "already_enabled": { + "type": "integer" + }, + "with_existing_aml": { + "type": "integer", + "description": "Entities whose existing AML screening will be switched to monitored." + }, + "needing_screening": { + "type": "integer", + "description": "Entities without any AML screening that will be screened first (billed)." + }, + "skipped_no_identity_data": { + "type": "integer" + }, + "skipped_blocked": { + "type": "integer" + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2 + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07 + }, + "balance_sufficient": { + "type": "boolean" + } + } + }, + { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + ] + }, + "CaseBlueprintPresetInstallResult": { + "type": "object", + "properties": { + "created": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "CaseBulkUpdateResult": { + "type": "object", + "properties": { + "updated": { + "type": "integer" + }, + "failures": { + "type": "array", + "items": { + "type": "object", + "properties": { + "case_uuid": { + "type": "string", + "format": "uuid" + }, + "detail": { + "type": "string" + } + } + } + } + } + }, + "RegulatoryReportList": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows, capped at 100 for performance." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "EntityNetworkMembershipList": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EntityNetworkMembership" + } + } + } + }, + "SessionDecision": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The unique identifier of the session." + }, + "session_kind": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Discriminator indicating whether this is a User Verification (KYC) or Business Verification (KYB) session. When `user`, the KYC feature arrays (`id_verifications`, `nfc_verifications`, `liveness_checks`, `face_matches`, `poa_verifications`, `document_ai_documents`, `database_validations`, ...) apply and the business arrays are absent. When `business`, the payload instead carries `registry_checks`, `document_verifications`, `key_people_checks` plus the shared arrays (`aml_screenings`, `phone_verifications`, `email_verifications`, `ip_analyses`, `questionnaire_responses`). **Omitted when `include=events` is passed** — the events shape only exists for user sessions, so the discriminator is dropped from that variant." + }, + "shared_from_session": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "For a session cloned with `POST /v3/session/import-shared/`: the `session_id` of the original session in the source application. `null` for every other session. Omitted when `include=events` is passed." + }, + "session_number": { + "type": "integer", + "description": "The number of the session.", + "nullable": true + }, + "session_url": { + "type": "string", + "format": "uri", + "description": "The URL of the session.", + "nullable": true + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Awaiting User", + "In Review", + "Approved", + "Declined", + "Resubmitted", + "Expired", + "Kyc Expired", + "Abandoned" + ], + "description": "Overall lifecycle status of the session — the rolled-up workflow result. Terminal values are `Approved`, `Declined`, `In Review`, `Expired`, `Kyc Expired`, and `Abandoned`; `Not Started`, `In Progress`, `Awaiting User`, and `Resubmitted` indicate the session is still moving through the workflow." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier of the workflow group that produced this session. Workflows are versioned — this value identifies the workflow itself, not the specific version.", + "nullable": true + }, + "features": { + "type": "array", + "items": { + "type": "string", + "description": "The feature of the session." + }, + "description": "List of verification features configured for this session, as display names: `ID_VERIFICATION`, `NFC`, `LIVENESS`, `FACE_MATCH`, `POA`, `PHONE`, `EMAIL_VERIFICATION`, `AML`, `IP_ANALYSIS`, `DATABASE_VALIDATION`, `QUESTIONNAIRE`, `AGE_ESTIMATION`, `FACE_SEARCH` (Business Verification sessions use the KYB feature set: `KYB_REGISTRY`, `KYB_DOCUMENTS`, `KYB_KEY_PEOPLE`, `AML`, `PHONE`, `EMAIL_VERIFICATION`, `IP_ANALYSIS`, `QUESTIONNAIRE`). With `include=events` each entry becomes an object `{feature, node_id}` instead of a plain string, and the email feature is renamed: it appears as `EMAIL` in events mode instead of the `EMAIL_VERIFICATION` used here." + }, + "vendor_data": { + "type": "string", + "description": "The vendor data of the session.", + "nullable": true + }, + "metadata": { + "type": "object", + "description": "The metadata of the session.", + "nullable": true + }, + "expected_details": { + "type": "object", + "description": "Expected details supplied at session creation — used for cross-validation against extracted data (KYC) or to pre-fill the company registry search (KYB). Echoes back exactly the keys that were provided; `null` when none were supplied. KYC sessions carry the person fields; Business (KYB) sessions carry `company_name`, `registry_country` and `registration_number`.", + "properties": { + "first_name": { + "type": "string", + "description": "User's first name. For example, `John`." + }, + "last_name": { + "type": "string", + "description": "User's last name. For example, `Doe`." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "User's date of birth with format: YYYY-MM-DD. For example, `1990-05-15`." + }, + "gender": { + "type": "string", + "nullable": true, + "enum": [ + "M", + "F" + ], + "default": null, + "description": "User's gender. Must be either 'M', 'F', or null." + }, + "nationality": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the applicant's country of origin. For example, `USA`." + }, + "country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the country of the Proof of Address document, or the country of the applicant's ID document, which may differ from nationality. For example, `GBR`." + }, + "address": { + "type": "string", + "description": "The address in a human readable format, including as much information as possible. For example, `123 Main St, San Francisco, CA 94105, USA`." + }, + "identification_number": { + "type": "string", + "description": "The user's document number, personal number, or tax number. For example, `123456789`." + }, + "ip_address": { + "type": "string", + "format": "ipv4", + "description": "Expected IP address for the session. If the actual IP address differs from this value, a warning will be logged. For example, `192.168.1.100`." + }, + "expected_document_types": { + "type": "array", + "description": "Document types the ID verification step was restricted to at session creation. Empty/unset means no restriction. Codes: `P` (passport), `ID` (national ID), `DL` (driver's license), `RP` (residence permit), `HIC` (health insurance card), `TC` (tax card), `SSC` (social security card).", + "items": { + "type": "string", + "enum": [ + "P", + "ID", + "DL", + "RP", + "HIC", + "TC", + "SSC" + ] + } + }, + "id_country": { + "type": "string", + "nullable": true, + "description": "Expected ID-document issuing country (ISO 3166-1 alpha-3), when restricted at creation time.", + "example": "ESP" + }, + "poa_country": { + "type": "string", + "nullable": true, + "description": "Expected Proof of Address document country (ISO 3166-1 alpha-3), when restricted at creation time.", + "example": "ESP" + }, + "company_name": { + "type": "string", + "description": "Expected legal name of the business. Business (KYB) sessions only." + }, + "registry_country": { + "type": "string", + "description": "Expected registry country as `XX` or `XX-YY` (ISO 3166-1 alpha-2, optionally with an ISO 3166-2 subdivision for state-level registries, e.g. `US-CA`). Business (KYB) sessions only." + }, + "registration_number": { + "type": "string", + "description": "Expected company registration number. Business (KYB) sessions only." + } + }, + "nullable": true + }, + "contact_details": { + "type": "object", + "description": "Contact details supplied when the session was created (used for notification emails, prefilled phone/email verification, and communication language). `null` when neither an email nor a phone number was provided.", + "properties": { + "email": { + "type": "string", + "description": "Email address of the user (e.g., \"john.doe@example.com\") that will be used during the Email Verification step. If not provided, the user must provide it during the verification flow." + }, + "email_lang": { + "type": "string", + "description": "Language code (ISO 639-1) for email notifications. Controls the language of all email communications (e.g., \"en\", \"es\", \"fr\")." + }, + "send_notification_emails": { + "type": "boolean", + "default": false, + "description": "If true and an email is provided, Didit sends the initial \"Verify your identity\" email asynchronously when the User Verification (KYC) or Business Verification (KYB) session is created. Didit also sends verification status notifications for sessions requiring manual review to the provided email address." + }, + "phone": { + "type": "string", + "nullable": true, + "description": "Phone number of the user in E.164 format if available (e.g., +14155552671). `null` when only an email was supplied at session creation. Note: This field returns the original phone number provided during session creation. If the number was invalid, it may not be in E.164 format." + } + }, + "nullable": true + }, + "callback": { + "type": "string", + "format": "uri", + "description": "The callback URL of the session.", + "nullable": true + }, + "id_verifications": { + "type": "array", + "nullable": true, + "description": "**Array of ID verification (OCR) reports** produced by ID-document steps in the workflow graph. Always a JSON array — read `id_verifications[0]` and iterate if your workflow can run more than one ID check (for example a primary check plus a step-up). `null` until at least one ID step has produced data. Each item is the full report, including `node_id`, feature-level `status`, extracted document fields, image URLs, quality scores, MRZ, address parsing, cross-session matches, and `warnings[]`.", + "items": { + "type": "object", + "description": "One ID verification report - the full report for a single ID Verification step. `verification_method` says which of the three methods produced it (document capture, non-doc lookup or wallet sign-in) and the method-specific evidence block is populated accordingly.", + "properties": { + "node_id": { + "type": "string", + "description": "Workflow graph node id of the OCR step that produced this report." + }, + "verification_method": { + "type": "string", + "enum": [ + "document", + "id_lookup", + "wallet" + ], + "description": "Which ID Verification method produced this report. `document` - the user photographed a physical ID (the default, and the value on every session created before the other methods existed). `id_lookup` - the user typed their national ID number and it was checked against a government register or another authoritative source for that country (`id_lookup.source` names which). `wallet` - the user signed in with a government or bank digital identity." + }, + "assurance": { + "type": "string", + "enum": [ + "documentary", + "data_match", + "cryptographic" + ], + "description": "Assurance tier derived from `verification_method`: `documentary` for a document that was captured and checked, `data_match` for details that matched a record held by the source queried, `cryptographic` for attributes an identity provider authenticated and signed." + }, + "wallet_provider": { + "type": "string", + "nullable": true, + "description": "Catalog wallet id when `verification_method` is `wallet` (`mitid`, `bankid_se`, `itsme`, ...). `null` otherwise." + }, + "fallback_from": { + "type": "object", + "nullable": true, + "description": "Why a non-document method ended the ID step. Set on the declined report when a lookup or a wallet sign-in failed and the country's fallback switch was `decline`. `null` otherwise.", + "properties": { + "method": { + "type": "string", + "enum": [ + "id_lookup", + "wallet" + ], + "description": "The method that failed." + }, + "reason": { + "type": "string", + "description": "The outcome that ended it, e.g. `no_match`, `provider_error`, `cancelled`, `timeout`." + }, + "action": { + "type": "string", + "description": "The configured action that was taken, e.g. `decline`." + } + } + }, + "id_lookup": { + "type": "object", + "nullable": true, + "description": "Registry evidence. Present only when `verification_method` is `id_lookup`; `null` otherwise.", + "properties": { + "source": { + "type": "string", + "description": "Customer-facing name of the source queried, e.g. `Department of Home Affairs` (a government register) or `UK credit bureau and financial services records` (not a government register). This is the only statement of provenance for the lookup; do not assume a government source." + }, + "checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the register answered." + }, + "attempts": { + "type": "integer", + "description": "1-based attempt number this report came from." + }, + "max_attempts": { + "type": "integer", + "description": "Attempt budget configured for the country (1 to 5, default 1)." + }, + "outcome": { + "type": "string", + "enum": [ + "match", + "partial_match", + "no_match", + "provider_error", + "format_rejected" + ], + "nullable": true, + "description": "What the register answered. Only `match`, `partial_match` and `no_match` count against the attempt budget and bill." + }, + "comparison": { + "type": "array", + "description": "One row per comparable field the source answered on, plus one row per source error. Comparison rows use the normalised field names below rather than the country's request field keys, because several request fields (first and last name, or any of the identifier aliases) collapse into a single comparison. A source that failed appends a row with `field: \"provider\"` and `result: \"error\"`, so read `result` before treating a row as a field verdict.", + "items": { + "type": "object", + "required": [ + "field", + "label", + "provided", + "record", + "result" + ], + "properties": { + "field": { + "type": "string", + "enum": [ + "identification_number", + "full_name", + "date_of_birth", + "address", + "provider" + ], + "description": "Normalised comparison field. `provider` is not a user field: it marks a source error row." + }, + "label": { + "type": "string", + "description": "Display label for the row, e.g. `ID number`, `Full name`, `Provider`." + }, + "provided": { + "type": "string", + "nullable": true, + "description": "What the user typed. `null` on a `provider` error row." + }, + "record": { + "type": "string", + "nullable": true, + "description": "What the source held. On a `provider` error row this carries the error message instead." + }, + "result": { + "type": "string", + "enum": [ + "match", + "partial", + "no_match", + "error" + ], + "description": "Per-field comparison result. `error` appears only on a `provider` row and means that source did not answer; treat it as evidence about the query, never as a field mismatch." + } + } + } + }, + "registry_portrait": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived presigned URL to the portrait the source returned. Only the four government registers that return one (South Africa, Nigeria, Argentina, Panama)." + }, + "face_match_score": { + "type": "number", + "nullable": true, + "description": "Selfie against the registry portrait, 0-100. Only where the register returns a portrait." + } + } + }, + "wallet_verification": { + "type": "object", + "nullable": true, + "description": "Credential evidence. Present only when `verification_method` is `wallet`; `null` otherwise.", + "properties": { + "provider": { + "type": "string", + "description": "Catalog wallet id, e.g. `mitid`." + }, + "provider_name": { + "type": "string", + "nullable": true, + "description": "Wallet name as the end user knows it, e.g. `MitID`." + }, + "issuing_authority": { + "type": "string", + "nullable": true, + "description": "The authority behind the wallet, e.g. `Danish Agency for Digital Government`." + }, + "issuing_country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country the wallet was offered in." + }, + "credential_type": { + "type": "string", + "nullable": true, + "description": "Credential the wallet presented, e.g. `person_identification`." + }, + "level_of_assurance": { + "type": "string", + "enum": [ + "low", + "substantial", + "high" + ], + "nullable": true, + "description": "The level of assurance the wallet asserted. A sign-in that asserts less than the level requested fails instead of downgrading." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "The wallet's authentication instant." + }, + "signature_valid": { + "type": "boolean", + "nullable": true, + "description": "Whether the signed assertion the wallet returned validated." + }, + "attributes": { + "type": "object", + "additionalProperties": true, + "description": "The attributes the wallet shared, keyed as the wallet's catalog entry lists them." + }, + "portrait": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived presigned URL to the portrait the wallet shared, where it shares one." + }, + "face_match_score": { + "type": "number", + "nullable": true, + "description": "Selfie against the wallet portrait, 0-100, where the wallet shares one." + } + } + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Expired" + ], + "description": "Feature-level status of this ID verification step. Independent of the top-level session `status` — a session can reach its overall decision while an individual feature stays `In Review`." + }, + "document_type": { + "type": "string", + "description": "Type of identity document" + }, + "document_subtype": { + "type": "string", + "nullable": true, + "description": "Accepted document subtype code from the workflow document settings, including the document region when applicable. Examples: `ID_CARD_GENERIC`, `QUEENSLAND_DRIVER_LICENSE_GENERIC`." + }, + "document_number": { + "type": "string", + "description": "Document identification number" + }, + "personal_number": { + "type": "string", + "description": "Personal identification number" + }, + "portrait_image": { + "type": "string", + "format": "uri", + "description": "URL of the portrait image" + }, + "front_image": { + "type": "string", + "format": "uri", + "description": "URL of the front document image" + }, + "front_video": { + "type": "string", + "format": "uri", + "description": "URL of the front document video" + }, + "back_image": { + "type": "string", + "format": "uri", + "description": "URL of the back document image" + }, + "back_video": { + "type": "string", + "format": "uri", + "description": "URL of the back document video" + }, + "full_front_image": { + "type": "string", + "format": "uri", + "description": "URL of the full front document image" + }, + "full_back_image": { + "type": "string", + "format": "uri", + "description": "URL of the full back document image" + }, + "front_image_camera_front": { + "type": "string", + "format": "uri", + "description": "URL of the front document image captured from the front-facing camera" + }, + "back_image_camera_front": { + "type": "string", + "format": "uri", + "description": "URL of the back document image captured from the front-facing camera" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth from document" + }, + "age": { + "type": "integer", + "description": "Calculated age from date of birth" + }, + "expiration_date": { + "type": "string", + "format": "date", + "description": "Document expiration date" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "description": "Document issue date" + }, + "issuing_state": { + "type": "string", + "description": "ISO code of issuing state" + }, + "issuing_state_name": { + "type": "string", + "description": "Full name of issuing state" + }, + "first_name": { + "type": "string", + "description": "First name from document" + }, + "last_name": { + "type": "string", + "description": "Last name from document" + }, + "full_name": { + "type": "string", + "description": "Full name from document" + }, + "gender": { + "type": "string", + "description": "Gender from document" + }, + "address": { + "type": "string", + "description": "Raw address from document" + }, + "formatted_address": { + "type": "string", + "description": "Formatted full address" + }, + "place_of_birth": { + "type": "string", + "description": "Place of birth from document" + }, + "marital_status": { + "type": "string", + "description": "Marital status from document" + }, + "nationality": { + "type": "string", + "description": "ISO nationality code" + }, + "extra_fields": { + "type": "object", + "description": "Additional document fields. Includes document-specific attributes (e.g. `dl_categories`, `blood_group`, `sponsor`, `employer`, `dl_class_code_b_from`, `dl_class_code_b_to`). Driver license class validity dates are returned as `dl_class_code__from` and `dl_class_code__to`, and per-class restriction/information codes (e.g. column 12 of UK licences) as `dl_class_code__notes`. When the document carries names, address, or place of birth in multiple scripts, this object also includes alternate-script versions of those fields. The workflow setting `preferred_characters` (`latin` or `non_latin`) controls which script populates the top-level fields (`first_name`, `last_name`, `full_name`, `address`, `place_of_birth`); the opposite script lands here as `first_name_non_latin` / `last_name_non_latin` / `full_name_non_latin` / `middle_name_non_latin` / `address_non_latin` / `place_of_birth_non_latin` (when `preferred_characters` is `latin`) or with a `_latin` suffix (when `preferred_characters` is `non_latin`). Keys appear only when the OCR actually extracts a real alternate-script value — a field whose alternate value is in the same script as the preferred set (for example, a Cyrillic-labelled field whose OCR value came back in Latin homoglyphs) is skipped. When `preferred_characters` is `latin` but the document only carries non-Latin text, Didit transliterates the extracted values and exposes `first_name_latin` / `last_name_latin` / etc. as a fallback.", + "additionalProperties": true, + "properties": { + "dl_categories": { + "type": "array", + "items": { + "type": "string" + } + }, + "blood_group": { + "type": "string", + "nullable": true + }, + "first_name_non_latin": { + "type": "string", + "description": "First name in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document (e.g. `Айна` for a Kyrgyz ID)." + }, + "last_name_non_latin": { + "type": "string", + "description": "Surname in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document." + }, + "middle_name_non_latin": { + "type": "string", + "description": "Middle name / patronymic in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document." + }, + "full_name_non_latin": { + "type": "string", + "description": "Composed full name in the non-Latin script. Reconstructed from the individual components when both first and last names are available." + }, + "address_non_latin": { + "type": "string", + "description": "Address in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document." + }, + "place_of_birth_non_latin": { + "type": "string", + "description": "Place of birth in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document." + }, + "first_name_latin": { + "type": "string", + "description": "First name in Latin script. Populated when `preferred_characters=non_latin` and a Latin version is present on the document, or — as a transliteration fallback — when `preferred_characters=latin` but the document only carries non-Latin text." + }, + "last_name_latin": { + "type": "string", + "description": "Surname in Latin script. Same rules as `first_name_latin`." + }, + "middle_name_latin": { + "type": "string", + "description": "Middle name / patronymic in Latin script. Same rules as `first_name_latin`." + }, + "full_name_latin": { + "type": "string", + "description": "Composed full name in Latin script. Always reconstructed from the individual components when both first and last names are available." + }, + "address_latin": { + "type": "string", + "description": "Address in Latin script. Same rules as `first_name_latin`." + }, + "place_of_birth_latin": { + "type": "string", + "description": "Place of birth in Latin script. Same rules as `first_name_latin`." + } + } + }, + "mrz": { + "type": "object", + "nullable": true, + "description": "Parsed Machine-Readable Zone (MRZ) fields extracted from the document. `null` if no MRZ was detected.", + "properties": { + "surname": { + "type": "string", + "example": "MARTINEZ_families` / `very_high_similarity_plus__families`. Present only when `match_source` is `recovered_high`." + } + } + } + } + } + } + }, + "database_validations": { + "type": "array", + "nullable": true, + "description": "**Array of Database Validation reports** — always a JSON array, never a singular `database_validation` object. `null` until at least one DB validation step has produced data. Each item is the full report, including `node_id`, feature-level `status`, `screened_data`, the per-service `validations[]` (each with its registry `source_data`), and `warnings[]`. The `screened_data` and `source_data` fields vary by country:\n\n**ARG (Argentina)**: screened_data: {document_number, selfie, gender, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, date_of_death, tax_id, tax_id_type, face_match_score, sit_1_since, banks, last_position, highest_position, rejected_checks}. Uses RENAPER biometric face-match validation. Gender is auto-inferred from face analysis or given name when not present in the document (e.g., driver licenses)\n\n**BOL (Bolivia)**: screened_data: {document_number, date_of_birth, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, gender}\n\n**BRA (Brazil)**: screened_data: {tax_number, first_name, last_name, date_of_birth}. source_data always includes {identification_number, first_name, last_name, date_of_birth, lgpd_minor, minor_under_18, minor_under_16} for successful CPF matches. Successful adult lookups set those flags to false. The Receita Federal Consulta CPF service may return HTTP 422 (minor under 18) or 451 (under 16) with LGPD-withheld data; in those cases source_data sets the minor flags to true as appropriate, includes the upstream HTTP status, and field-level validation is no_match. See the Brazil section of the Database Validation Outcome Codes reference for the full code list.\n\n**CHL (Chile)**: screened_data: {personal_number, first_name, last_name, date_of_birth}. source_data: {identification_number, first_name, last_name, date_of_birth, gender}\n\n**COL (Colombia)**: screened_data: {document_number, document_type, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, document_type}\n\n**CRI (Costa Rica)**: screened_data: {personal_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**DOM (Dominican Republic)**: screened_data: {personal_number}. source_data: {identification_number} (returns valid/invalid only)\n\n**ECU (Ecuador)**: screened_data: {personal_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, street, formatted_address, gender, nationality, education, marital_status, spouse, mother_name, father_name, profession}\n\n**ESP (Spain)**: screened_data: {personal_number, document_type, expiration_date, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, document_type, expiration_date}\n\n**GTM (Guatemala)**: screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**HND (Honduras)**: screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**MEX (Mexico)**: screened_data: {personal_number (CURP), first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, gender, nationality, curp_status, state_of_birth, doc_probatorio, registration_year, registration_state, registration_municipality, num_acta, crip, issuing_state_code, foreign_registry_number, folio_carta, folio_certificado}. Uses the RENAPO civil registry. A separate INE validity-check service is also available for MEX as an additional one-by-one validation.\n\n**PAN (Panama)**: screened_data: {personal_number, selfie}. source_data: {identification_number}. Uses SIB biometric validation\n\n**PER (Peru)**: screened_data: {personal_number (DNI), first_name, last_name}. source_data: {identification_number, first_name, last_name, paternal_name, maternal_name, verification_number, verification_letter}\n\n**PRY (Paraguay)**: screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**SLV (El Salvador)**: screened_data: {document_number, date_of_birth, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**URY (Uruguay)**: screened_data: {personal_number, date_of_birth, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**VEN (Venezuela)**: screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, gender, date_of_birth, document_type}", + "items": { + "type": "object", + "properties": { + "node_id": { + "type": "string", + "description": "Unique identifier for this database validation step in the workflow graph." + }, + "issuing_state": { + "type": "string", + "description": "ISO 3166-1 alpha-3 code of the country whose government/registry sources were queried (e.g. `BRA`, `MEX`, `ESP`, `NGA`, `IDN`). Database Validation covers 60+ countries across the Americas, Europe, Africa, the Middle East and Asia-Pacific, plus global multi-country sources — coverage expands regularly, so treat the value as an open set rather than a fixed enum." + }, + "validation_type": { + "type": "string", + "enum": [ + "one_by_one", + "two_by_two", + "not_enabled" + ], + "description": "How the result was corroborated: `one_by_one` (single-service outcome), `two_by_two` (two or more distinct services independently full-matched the identification number), or `not_enabled` (persisted when no service produced a usable match). The value is never `null`. Dedicated biometric 2x2 validation modes are currently available for `ECU` and `PER`." + }, + "screened_data": { + "type": "object", + "description": "The input data used for validation. Fields vary by country — see the Database Validation docs (`/core-technology/database-validation/overview`) for country-specific formats.", + "properties": { + "last_name": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "tax_number": { + "type": "string", + "description": "Brazil: CPF (11 digits)" + }, + "personal_number": { + "type": "string", + "description": "Chile: RUT, DOM: Cédula (11 digits), ECU: Cédula (10 digits), ESP: DNI/NIE, MEX: CURP (18 chars), PER: DNI (8 digits), URY: CI" + }, + "document_number": { + "type": "string", + "description": "ARG: DNI, BOL: CI, COL: Cédula, GTM: DPI, HND: DNI, PAN: Cédula, PRY: CI, SLV: DUI, VEN: Cédula" + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "document_type": { + "type": "string", + "description": "COL: CC/CE/TI/NIT, ESP: ID/PASSPORT" + }, + "expiration_date": { + "type": "string", + "format": "date", + "description": "Required for ESP" + } + } + }, + "validations": { + "type": "array", + "description": "One entry per registry service that returned a validation payload. Each entry identifies the service (`service_id`, `service_name`) and carries the per-field `validation` comparison, the service `outcome_code` / `outcome_detail` when reported, and the normalised registry record in `source_data`.", + "items": { + "type": "object", + "properties": { + "service_id": { + "type": "string", + "description": "Identifier of the registry service that produced this validation result.", + "example": "bra_cpf" + }, + "service_name": { + "type": "string", + "description": "Human-readable name of the registry service.", + "example": "Brazil - CPF status check" + }, + "validation": { + "type": "object", + "description": "The results of the field validations.", + "properties": { + "full_name": { + "type": "string", + "description": "Match result for full name (e.g., 'full_match', 'partial_match', 'no_match')." + }, + "date_of_birth": { + "type": "string", + "description": "Match result for date of birth." + }, + "identification_number": { + "type": "string", + "description": "Match result for identification number." + } + } + }, + "outcome_code": { + "type": "string", + "nullable": true, + "description": "Machine-readable outcome of the registry lookup (e.g. `MATCH`, `NO_MATCH`, `PARTIAL_MATCH`, `INCONCLUSIVE`, source-specific codes). Present when the service reports one." + }, + "outcome_detail": { + "type": "string", + "nullable": true, + "description": "Human-readable explanation of `outcome_code`, when provided by the service." + }, + "source_data": { + "type": "object", + "description": "The data returned from the government database. Common fields: identification_number, first_name, last_name, full_name, date_of_birth, gender. Additional fields by country: BRA always includes lgpd_minor, minor_under_18, and minor_under_16 on successful CPF matches; it also includes the upstream HTTP status when CPF data is withheld under LGPD (HTTP 422/451). ECU adds street, formatted_address, nationality, education, marital_status, spouse, mother_name, father_name, profession. PER adds paternal_name, maternal_name, verification_number, verification_letter. ESP adds document_type, expiration_date. VEN/COL add document_type.", + "additionalProperties": true + } + } + } + }, + "errors": { + "type": "array", + "description": "Errors returned by registry services while validating, each tagged with the `service_id` that produced it. **This key is omitted entirely when there are no errors.**", + "items": { + "type": "object", + "description": "The upstream service's error object spread as-is — its keys vary by service (commonly a message/code and any provider-specific details) — plus `service_id` identifying the service that errored.", + "properties": { + "service_id": { + "type": "string", + "nullable": true, + "description": "Identifier of the registry service that errored." + } + }, + "additionalProperties": true + } + }, + "match_type": { + "type": "string", + "enum": [ + "no_match", + "partial_match", + "full_match" + ], + "nullable": true, + "description": "The aggregate match outcome across the queried services. `null` until the validation has been evaluated." + }, + "status": { + "type": "string", + "description": "Feature-level status of this database validation. Independent of the top-level session `status` — a session can reach its overall decision while an individual feature stays `In Review`.", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ] + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "NFC", + "LIVENESS", + "FACEMATCH", + "PROOF_OF_ADDRESS", + "PHONE", + "EMAIL", + "AML", + "LOCATION", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE" + ], + "description": "The feature that generated this warning. The value identifies the feature block the warning belongs to. Each module's warnings array typically uses the value matching its module (e.g. `PHONE` inside `phone_verifications[].warnings`), but the enum above is authoritative across all feature warnings on the decision payload." + }, + "risk": { + "type": "string" + }, + "additional_data": { + "type": "object", + "nullable": true + }, + "log_type": { + "type": "string" + }, + "short_description": { + "type": "string" + }, + "long_description": { + "type": "string" + }, + "node_id": { + "type": "string", + "description": "The workflow graph node ID that generated this warning" + } + } + }, + "description": "Warnings related to the database validation." + } + } + } + }, + "reviews": { + "type": "array", + "items": { + "type": "object", + "properties": { + "user": { + "type": "string", + "description": "Who performed the review. Usually the console reviewer's identifier (their email), but the value is `\"API Client\"` for status updates made with an API key and `\"Unknown\"` when no actor could be resolved — so do not parse this field as an email address." + }, + "new_status": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp at which the session was created (ISO 8601, UTC)." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp at which this session is scheduled to expire. Set on User Verification (KYC) sessions when an expiration was configured at creation time (or once `Approved` status triggers KYC expiry computation). `null` for sessions without a configured expiration, and for kinds that do not use expiry (e.g. Business Verification (KYB) sessions that never received an `expires_at`)." + }, + "registry_checks": { + "type": "array", + "nullable": true, + "description": "Company registry checks. Present only when `session_kind = business`. Each item wraps the full company-registry payload.", + "items": { + "type": "object", + "description": "One company registry check — the full company-registry payload enriched with ownership structure and warnings. Present only when `session_kind = business`.", + "properties": { + "status": { + "type": "string", + "description": "Feature lifecycle status." + }, + "node_id": { + "type": "string", + "nullable": true + }, + "data_resolved": { + "type": "boolean" + }, + "company": { + "type": "object", + "description": "Full registry payload for the company (registry record merged with any user-provided or console-edited data).", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "node_id": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string" + }, + "registry_status": { + "type": "string", + "nullable": true + }, + "data_resolved": { + "type": "boolean" + }, + "company_name": { + "type": "string" + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_code": { + "type": "string", + "description": "Country of incorporation (ISO 3166-1 alpha-2, e.g. `GB`, `DE`, `US`)." + }, + "region": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision code when applicable (e.g. `CA`, `NY` for US states)." + }, + "company_type": { + "type": "string", + "nullable": true + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "registered_address": { + "type": "string", + "nullable": true + }, + "tax_number": { + "type": "string", + "nullable": true + }, + "risk_level": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ], + "nullable": true + }, + "verification_status": { + "type": "string", + "nullable": true + }, + "is_from_registry": { + "type": "boolean" + }, + "fetch_status": { + "type": "string", + "nullable": true + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "format": "decimal", + "nullable": true, + "description": "Registered/share capital amount as a decimal string (e.g. `\"100.00\"`), parsed from the registry filing. `null` when not available." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 currency code of `registered_capital_amount` (e.g. `\"GBP\"`). `null` when not available." + }, + "website": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "format": "email", + "nullable": true + }, + "phone": { + "type": "string", + "nullable": true + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true + }, + "location_of_registration": { + "type": "string", + "nullable": true + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "designation": { + "type": "string", + "nullable": true + }, + "role": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "Pending" + ], + "nullable": true + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + } + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ] + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership percentage as reported by the registry. Free-form string (e.g. `\"25.00\"`, `\"50\"`); use `effective_ownership_percent` for a parsed numeric value." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership percentage as reported by the registry. Free-form string; use `effective_ownership_percent` for a parsed numeric value." + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "Pending" + ], + "nullable": true + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + }, + "shareholding": { + "$ref": "#/components/schemas/KYBRegistryShareholding" + } + } + } + }, + "addresses": { + "type": "array", + "nullable": true, + "description": "Every address the registry states, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryAddress" + } + }, + "industries": { + "type": "array", + "nullable": true, + "description": "The activities the registry classifies the company under, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryIndustry" + } + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "type": "object", + "description": "Raw record returned by the registry source, kept for reviewers. Its shape differs by country and source and is not a stable contract: read the canonical fields (`addresses`, `industries`, `ownership`, `beneficial_owners[].shareholding`) instead." + }, + "user_provided_data": { + "type": "object", + "description": "Data confirmed or edited by the end user." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "is_editable": { + "type": "boolean" + }, + "ownership": { + "$ref": "#/components/schemas/KYBRegistryOwnership" + }, + "registry_tier": { + "type": "string", + "nullable": true, + "description": "The registry tier the workflow ordered: `basic`, `shareholders` or `ubo`. Also in `ownership.tier_requested`." + }, + "registry_tier_delivered": { + "type": "string", + "nullable": true, + "description": "The tier the registry delivered and that is billed. Also in `ownership.tier_delivered`." + }, + "registry_ownership_status": { + "type": "string", + "nullable": true, + "description": "`not_requested`, `pending`, `delivered`, `not_held` or `unavailable`. Also in `ownership.status`." + } + } + }, + "ownership_structure": { + "type": "object", + "description": "Raw ownership snapshot kept for reviewers; its shape differs by registry source and is not a stable contract. Read `company.beneficial_owners` and `company.ownership` instead." + }, + "warnings": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + }, + "key_people_checks": { + "type": "array", + "nullable": true, + "description": "Aggregate key-people checks. Present only when `session_kind = business`. Each item uses the two-bucket shape (`registry`, `submitted`, `ubo_kyc_summary`).", + "items": { + "type": "object", + "description": "Aggregate check over the company's officers + UBOs. Uses the `registry` / `submitted` shape. Present only when session_kind = business.", + "properties": { + "status": { + "type": "string" + }, + "node_id": { + "type": "string", + "nullable": true + }, + "registry": { + "type": "object", + "description": "Parties discovered via the registry check.", + "properties": { + "officers": { + "type": "array", + "description": "Officers discovered via the registry check, each enriched with the status of any child KYC session and AML screening spawned for them.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "description": "Officer's full name as recorded in the registry." + }, + "designation": { + "type": "string", + "nullable": true, + "description": "Free-text designation from the registry (e.g. `Director`, `Company Secretary`)." + }, + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Normalized officer role." + }, + "nationality": { + "type": "string", + "nullable": true + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "is_active": { + "type": "boolean" + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "aml_status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "nullable": true, + "description": "Status of the AML screening run for this officer. `null` when no AML screening was run." + }, + "kyc_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "nullable": true, + "description": "Raw lifecycle status of the child KYC session spawned for this officer. This is the full session status enum (not a reduced Approved/Declined/Pending set). `null` when no child KYC session exists." + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Shareable URL of the child KYC session. `null` when no child session exists." + }, + "kyc_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Session id of the child KYC session. Pass it to this same decision endpoint to fetch the person's full KYC report. `null` when no child session exists." + }, + "verification_workflow_type": { + "type": "string", + "nullable": true, + "description": "Base workflow type of the child session's workflow (e.g. `kyc`). `null` for graph-based workflows or when no child session exists." + }, + "verification_workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Workflow id used by the child session. `null` when no child session exists." + }, + "verification_workflow_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label of the child session's workflow. `null` when unset." + }, + "verification_workflow_features": { + "type": "array", + "nullable": true, + "description": "Per-feature progress of the child verification session, e.g. `[{\"feature\": \"ID_VERIFICATION\", \"status\": \"Approved\"}]`. `null` when the child session has no resolvable feature list.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature display name (e.g. `ID_VERIFICATION`, `LIVENESS`, `AML`)." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Feature-level status inside the child session." + } + } + } + }, + "didit_internal_id": { + "type": "string", + "nullable": true, + "description": "Stable internal identifier of the verified individual behind the child session — useful for cross-session linking. `null` when no child session exists or the user has not been resolved yet." + }, + "kyc_document_type": { + "type": "string", + "nullable": true, + "description": "Document type presented in the child KYC session (e.g. `P`, `ID`, `DL`). `null` until the child session has an ID verification result." + }, + "kyc_nationality": { + "type": "string", + "nullable": true, + "description": "Nationality extracted from the document in the child KYC session. `null` until available." + }, + "kyc_liveness_passed": { + "type": "boolean", + "nullable": true, + "description": "Whether the child KYC session's liveness check passed. `null` when no liveness check has run." + }, + "kyc_verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the child KYC session reached `Approved`. `null` while the child session has any other status." + }, + "kyc_aml_categories": { + "type": "object", + "nullable": true, + "description": "Per-category summary derived from the person's AML hits. `null` when there are no hits.", + "properties": { + "sanctions": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "pep": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "adverse_media": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "warnings": { + "type": "string", + "enum": [ + "match", + "clear" + ] + } + } + }, + "kyc_notification_sent_at": { + "type": "string", + "nullable": true, + "description": "ISO 8601 timestamp of the last verification-invite notification sent for this person's child session. `null` when none was sent." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + } + }, + "beneficial_owners": { + "type": "array", + "description": "Beneficial owners discovered via the registry check, each enriched with the status of any child KYC session and AML screening spawned for them.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "description": "Beneficial owner's full name (or company name for corporate UBOs)." + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ] + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "roles": { + "type": "array", + "description": "Role names held by this owner, e.g. `[\"ubo\", \"shareholder\"]`.", + "items": { + "type": "string" + } + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership percentage as reported by the registry. Free-form string (e.g. `\"25\"`, `\"25-50%\"`); use `effective_ownership_percent` for a parsed numeric value." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership percentage as reported by the registry. Free-form string; use `effective_ownership_percent` for a parsed numeric value." + }, + "is_active": { + "type": "boolean" + }, + "aml_status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "nullable": true, + "description": "Status of the AML screening run for this beneficial owner. `null` when no AML screening was run." + }, + "kyc_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "nullable": true, + "description": "Raw lifecycle status of the child KYC session spawned for this beneficial owner. This is the full session status enum (not a reduced Approved/Declined/Pending set). `null` when no child KYC session exists." + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Shareable URL of the child KYC session. `null` when no child session exists." + }, + "kyc_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Session id of the child KYC session. Pass it to this same decision endpoint to fetch the person's full KYC report. `null` when no child session exists." + }, + "verification_workflow_type": { + "type": "string", + "nullable": true, + "description": "Base workflow type of the child session's workflow (e.g. `kyc`). `null` for graph-based workflows or when no child session exists." + }, + "verification_workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Workflow id used by the child session. `null` when no child session exists." + }, + "verification_workflow_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label of the child session's workflow. `null` when unset." + }, + "verification_workflow_features": { + "type": "array", + "nullable": true, + "description": "Per-feature progress of the child verification session, e.g. `[{\"feature\": \"ID_VERIFICATION\", \"status\": \"Approved\"}]`. `null` when the child session has no resolvable feature list.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature display name (e.g. `ID_VERIFICATION`, `LIVENESS`, `AML`)." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Feature-level status inside the child session." + } + } + } + }, + "didit_internal_id": { + "type": "string", + "nullable": true, + "description": "Stable internal identifier of the verified individual behind the child session — useful for cross-session linking. `null` when no child session exists or the user has not been resolved yet." + }, + "kyc_document_type": { + "type": "string", + "nullable": true, + "description": "Document type presented in the child KYC session (e.g. `P`, `ID`, `DL`). `null` until the child session has an ID verification result." + }, + "kyc_nationality": { + "type": "string", + "nullable": true, + "description": "Nationality extracted from the document in the child KYC session. `null` until available." + }, + "kyc_liveness_passed": { + "type": "boolean", + "nullable": true, + "description": "Whether the child KYC session's liveness check passed. `null` when no liveness check has run." + }, + "kyc_verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the child KYC session reached `Approved`. `null` while the child session has any other status." + }, + "kyc_aml_categories": { + "type": "object", + "nullable": true, + "description": "Per-category summary derived from the person's AML hits. `null` when there are no hits.", + "properties": { + "sanctions": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "pep": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "adverse_media": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "warnings": { + "type": "string", + "enum": [ + "match", + "clear" + ] + } + } + }, + "kyc_notification_sent_at": { + "type": "string", + "nullable": true, + "description": "ISO 8601 timestamp of the last verification-invite notification sent for this person's child session. `null` when none was sent." + }, + "child_beneficial_owners": { + "type": "array", + "description": "For corporate UBOs: the owning entity's own beneficial owners, as raw objects extracted from the registry. Empty array when not applicable.", + "items": { + "type": "object" + } + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true, + "description": "Parsed numeric ownership percentage — the max bound when available, otherwise the min bound. `null` when neither bound could be parsed." + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + } + } + } + } + } + }, + "submitted": { + "type": "object", + "description": "Parties submitted by the business admin during the Key People flow (BusinessParty rows where source=USER).", + "properties": { + "parties": { + "type": "array", + "items": { + "type": "object", + "description": "Party submitted by the business admin via the Key People flow (source=USER).", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ] + }, + "source": { + "type": "string", + "enum": [ + "USER" + ], + "description": "Always `USER` here — this bucket only contains parties entered or confirmed by the end user in the Key People flow (registry-discovered people appear under `registry`)." + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "format": "email", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true, + "description": "Nationality for persons; country of incorporation for company parties." + }, + "phone_number": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "position": { + "type": "string", + "nullable": true, + "description": "Free-text job title / position (persons only)." + }, + "custom_fields": { + "type": "object", + "description": "Workflow-defined custom fields collected for this party; keys are defined by the workflow's key-people field descriptors." + }, + "is_active": { + "type": "boolean" + }, + "is_skipped": { + "type": "boolean", + "description": "True when the end user skipped verification for this party (only possible when the role allows skipping)." + }, + "requires_verification": { + "type": "boolean", + "description": "True when a child KYC / KYB sub-session is required for this party based on role settings and ownership thresholds." + }, + "roles": { + "type": "array", + "description": "All roles held by this party, each with its own ownership and voting percentages. There is no scalar `role`/`ownership_percent` on the party itself.", + "items": { + "type": "object", + "properties": { + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "ownership_percent": { + "type": "number", + "nullable": true + }, + "voting_percent": { + "type": "number", + "nullable": true + } + } + } + }, + "kyc_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Child KYC session id for person parties requiring verification. `null` otherwise." + }, + "kyc_session_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "nullable": true, + "description": "Lifecycle status of the child KYC session. `null` when no child KYC session exists." + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true + }, + "kyb_sub_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Child KYB sub-session id for corporate parties requiring verification. `null` otherwise." + }, + "kyb_sub_session_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "nullable": true, + "description": "Lifecycle status of the child KYB sub-session. `null` when no sub-session exists." + }, + "kyb_sub_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Shareable URL of the child KYB sub-session. `null` when no sub-session exists." + }, + "verification_workflow_type": { + "type": "string", + "nullable": true, + "description": "Base workflow type of the child session's workflow (KYC session preferred, falling back to the KYB sub-session). `null` for graph-based workflows or when no child session exists." + }, + "verification_workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Workflow id used by the child session. `null` when no child session exists." + }, + "verification_workflow_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label of the child session's workflow. `null` when unset." + }, + "verification_workflow_features": { + "type": "array", + "nullable": true, + "description": "Per-feature progress of the child verification session, e.g. `[{\"feature\": \"ID_VERIFICATION\", \"status\": \"Approved\"}]`. `null` when the child session has no resolvable feature list.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature display name (e.g. `ID_VERIFICATION`, `LIVENESS`, `AML`)." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Feature-level status inside the child session." + } + } + } + }, + "didit_internal_id": { + "type": "string", + "nullable": true, + "description": "Stable internal identifier of the verified individual behind the child session. `null` when not resolved." + } + } + } + } + } + }, + "ubo_kyc_summary": { + "type": "object", + "nullable": true, + "description": "Aggregate UBO KYC progress, or null when no UBOs have linked KYC sessions.", + "properties": { + "total": { + "type": "integer" + }, + "approved": { + "type": "integer" + }, + "flagged": { + "type": "integer", + "description": "UBOs whose KYC is Declined or In Review." + }, + "pending": { + "type": "integer" + } + } + }, + "warnings": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + }, + "document_verifications": { + "type": "array", + "nullable": true, + "description": "Document verification checks grouped by workflow node. Present only when `session_kind = business`.", + "items": { + "type": "object", + "description": "Documents grouped by workflow node. Present only when session_kind = business.", + "properties": { + "status": { + "type": "string" + }, + "node_id": { + "type": "string", + "nullable": true + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "document_type": { + "type": "string", + "enum": [ + "LEGAL_PRESENCE", + "COMPANY_DETAILS", + "OWNERSHIP_STRUCTURE", + "REPRESENTATIVES_AUTHORIZATION", + "OTHER" + ], + "nullable": true, + "description": "High-level KYB document group the file was classified into. `null` while unclassified." + }, + "document_subtype": { + "type": "string", + "nullable": true, + "description": "Specific OCR-detected subtype (e.g. `CERTIFICATE_OF_INCORPORATION`, `SHAREHOLDER_REGISTRY`); `UNKNOWN` when the OCR could not confidently classify the document." + }, + "document_group": { + "type": "string", + "enum": [ + "LEGAL_PRESENCE", + "COMPANY_DETAILS", + "OWNERSHIP_STRUCTURE", + "REPRESENTATIVES_AUTHORIZATION", + "OTHER" + ], + "nullable": true, + "description": "Alias of `document_type` — the group name used by `groups` and `required_groups`." + }, + "file_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived presigned link to the uploaded file. `null` for analyst-created document request placeholders (`is_requested = true`) that have not been fulfilled yet." + }, + "original_filename": { + "type": "string", + "nullable": true, + "description": "Filename as uploaded by the user or analyst." + }, + "file_size": { + "type": "integer", + "nullable": true, + "description": "File size in bytes." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Per-document verification status." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "cross_check_result": { + "type": "object", + "nullable": true, + "description": "Cross-reference of the OCR-extracted document fields against the registry company profile. An object with `critical`, `non_critical` and `people` sections, each mapping a field name (e.g. `company_name`, `registration_number`, `incorporation_date`, `registered_address`) to a comparison record like `{\"document\": \"...\", \"registry\": \"...\", \"match\": true, \"score\": 0.97}`. `null` when the document had no OCR data or there was no registry company to compare against." + }, + "document_metadata": { + "type": "object", + "nullable": true, + "description": "Metadata extracted from the uploaded corporate document. When metadata extraction succeeded, all 16 keys are always present (individual values are `null` when not applicable to the file type); the whole object is `null` when nothing could be extracted. For PDFs, `overlay_manipulation` carries forensic overlay-text analysis.", + "properties": { + "file_size": { + "type": "integer", + "nullable": true, + "description": "Size of the document." + }, + "content_type": { + "type": "string", + "nullable": true, + "description": "Content type of the document." + }, + "creation_date": { + "type": "string", + "nullable": true, + "description": "Date when the document was created." + }, + "modified_date": { + "type": "string", + "nullable": true, + "description": "Date when the document was modified." + }, + "creator": { + "type": "string", + "nullable": true, + "description": "Authoring tool/user recorded in the file metadata (PDF `Creator`)." + }, + "producer": { + "type": "string", + "nullable": true, + "description": "Software that produced the file (PDF `Producer`)." + }, + "software": { + "type": "string", + "nullable": true, + "description": "Editing software recorded in the file metadata." + }, + "encryption": { + "type": "string", + "nullable": true, + "description": "Encryption scheme of the file, when encrypted." + }, + "is_signed": { + "type": "boolean", + "nullable": true, + "description": "Whether the document carries a digital signature." + }, + "is_tampered": { + "type": "boolean", + "nullable": true, + "description": "Whether the digital signature failed validation (post-signing modification)." + }, + "signature_info": { + "type": "object", + "nullable": true, + "description": "Details of the digital signature(s), when present." + }, + "exif_original_date": { + "type": "string", + "nullable": true, + "description": "EXIF original capture date, for image uploads." + }, + "exif_digitized_date": { + "type": "string", + "nullable": true, + "description": "EXIF digitization date, for image uploads." + }, + "processed_by_known_editor": { + "type": "boolean", + "nullable": true, + "description": "Whether metadata indicates the file passed through a known image/PDF editor." + }, + "has_different_creation_mod_date": { + "type": "boolean", + "nullable": true, + "description": "Whether creation and modification dates differ — a possible (but not conclusive) editing signal." + }, + "overlay_manipulation": { + "type": "object", + "nullable": true, + "description": "PDF forensic analysis for suspected overlay-text manipulation. Null when no overlay manipulation evidence was found or the document was not a PDF.", + "properties": { + "detected": { + "type": "boolean", + "description": "Whether overlay-text manipulation evidence was detected." + }, + "analyzed": { + "type": "boolean", + "description": "Whether the PDF could be analyzed." + }, + "signals": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Forensic signals that fired, such as duplicate_font_subset or glyph_fragmentation." + }, + "manipulated_regions": { + "type": "array", + "description": "Suspected manipulated areas in PDF page coordinates.", + "items": { + "type": "object", + "properties": { + "page": { + "type": "integer" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + }, + "width": { + "type": "number" + }, + "height": { + "type": "number" + }, + "page_width": { + "type": "number" + }, + "page_height": { + "type": "number" + } + } + } + } + } + } + } + }, + "description": { + "type": "string", + "nullable": true, + "description": "Analyst-provided label or reason for a document request. `null` for regular uploads." + }, + "is_requested": { + "type": "boolean", + "description": "True for analyst-created document request placeholders awaiting an upload." + }, + "ocr_data": { + "type": "object", + "nullable": true + }, + "is_console_upload": { + "type": "boolean", + "description": "True for documents uploaded from the console rather than the end-user flow. Console uploads never change the feature-level aggregate status." + }, + "is_confirmed": { + "type": "boolean", + "description": "Console uploads start `false` and flip `true` once the admin confirms them; end-user uploads are always `true`." + } + } + } + }, + "groups": { + "type": "object", + "description": "Per-group progress counters keyed by group name (e.g. legal_presence, ownership_structure).", + "additionalProperties": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "approved": { + "type": "integer" + }, + "pending": { + "type": "integer" + }, + "declined": { + "type": "integer" + }, + "in_review": { + "type": "integer" + }, + "other": { + "type": "integer" + }, + "missing": { + "type": "integer", + "description": "Requested placeholders with no file uploaded yet." + } + } + } + }, + "required_groups": { + "type": "array", + "items": { + "type": "string" + } + }, + "warnings": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Whether the session was created by a live application (`live`) or a sandbox application (`sandbox`). Sandbox sessions are free, fully simulated, and never bill credits." + }, + "resubmissions": { + "type": "array", + "description": "Returned only with `include=resubmissions`, for user and business sessions. One entry per resubmission request, oldest first. Each entry carries the results the resubmitted steps returned before the request, so earlier submissions stay readable. Deleting the session deletes every submission.", + "items": { + "type": "object", + "properties": { + "number": { + "type": "integer", + "description": "1 for the first resubmission of the session, 2 for the second, and so on." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the resubmission was requested." + }, + "previous_status": { + "type": "string", + "description": "The session status just before the request." + }, + "nodes": { + "type": "array", + "description": "The workflow steps sent back to the user.", + "items": { + "type": "object", + "properties": { + "node_id": { + "type": "string" + }, + "feature": { + "type": "string" + } + } + } + }, + "reasons": { + "type": "object", + "nullable": true, + "additionalProperties": { + "type": "string" + }, + "description": "The reason given for each step, keyed by `node_id`." + }, + "requested_by": { + "type": "object", + "nullable": true, + "description": "Who requested the resubmission.", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "results": { + "type": "object", + "additionalProperties": true, + "description": "What the resubmitted steps returned before the request, under the same keys and in the same shape as the current results of this response (`id_verifications`, `nfc_verifications`, `liveness_checks`, `face_matches`, `poa_verifications`, `phone_verifications`, `email_verifications`, `aml_screenings`, `ip_analyses`, `database_validations`, `questionnaire_responses`, `document_ai_documents`, `geolocation_checks`; for business sessions also `document_verifications`). Only the keys of the steps that were sent back appear." + } + } + } + }, + "biometric_consent": { + "type": "object", + "nullable": true, + "description": "Whether the session required explicit biometric consent before a face capture, why, and the evidence of the answer. User Verification (KYC) sessions only; the same object is in the `decision` of session webhooks. `null` when the session was never evaluated, for example a workflow without a face capture. Fields with no recorded value are `null`. A native SDK below 4.10.0, or one that does not report its version, cannot show the consent step: its face captures run as before, and a session that required consent then reports `status: \"pending\"` with `reason: \"BIOMETRIC_CONSENT_UNSUPPORTED_SDK\"`. See [Biometric consent](/getting-started/biometric-consent).", + "properties": { + "required": { + "type": "boolean", + "description": "Whether the session must collect explicit biometric consent. Never goes back to `false`." + }, + "trigger": { + "type": "string", + "nullable": true, + "enum": [ + "declared_state", + "ip_state", + "ip_unknown", + "document_issuer", + "forced", + "customer_collected" + ], + "description": "Why consent is required, or how it was satisfied. `declared_state`: you declared a covered state (`user_state`, or a US address in `expected_details`). `ip_state`: the person's IP is in a covered state (Illinois, Texas, Colorado). `ip_unknown`: the evidence places the person in the US but the state cannot be told (the IP is in the US with no state, or cannot be located while your details or the document place the person in the US). `document_issuer`: the identity document was issued by a covered state. `forced`: the application's mode is Always. `customer_collected`: you sent `biometric_consent` when creating the session. `null` while not required." + }, + "status": { + "type": "string", + "enum": [ + "not_required", + "pending", + "given", + "declined" + ], + "description": "`not_required`; `pending` (required and not given: the person has not answered yet, or `reason` says why it was not collected); `given`; `declined` (a decline was recorded and the session ended as `Declined`; no Didit screen offers a decline today)." + }, + "given_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When consent was given. For `customer_collected`, the `given_at` you sent." + }, + "declined_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When a decline was recorded." + }, + "text_version": { + "type": "string", + "nullable": true, + "description": "Version id of the consent text the person answered, or the one you sent." + }, + "text_hash": { + "type": "string", + "nullable": true, + "description": "SHA-256 (hex) of the exact consent text shown, in the language it was shown in." + }, + "locale": { + "type": "string", + "nullable": true, + "description": "Language the consent text was shown in." + }, + "policy_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The biometric data policy the consent step linked to." + }, + "step": { + "type": "string", + "nullable": true, + "description": "Workflow step the session was on when the person answered, for example `LIVENESS`." + }, + "node_id": { + "type": "string", + "nullable": true, + "description": "Workflow graph node the session was on when the person answered." + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "IP the answer came from." + }, + "ip_country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of the answer's IP." + }, + "ip_state": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision of the answer's IP, for example `US-IL`." + }, + "ip_city": { + "type": "string", + "nullable": true, + "description": "City of the answer's IP." + }, + "user_agent": { + "type": "string", + "nullable": true, + "description": "User agent of the answer's request." + }, + "detected_ip_address": { + "type": "string", + "nullable": true, + "description": "The person's IP the requirement was last evaluated from. Can differ from `ip_address` when the person answered on another device." + }, + "detected_country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of the detected IP." + }, + "detected_state": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision of the detected IP." + }, + "detected_city": { + "type": "string", + "nullable": true, + "description": "City of the detected IP." + }, + "declared_state": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision you declared: `user_state`, or the state of a US address in `expected_details`." + }, + "document_state": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision that issued the identity document, read at ID capture." + }, + "jurisdictions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "US states (USPS codes, for example `IL`) whose biometric law covers the person. `US` when the evidence places the person in the US but no state is known. Empty when none applies." + }, + "retention_months": { + "type": "integer", + "nullable": true, + "description": "The shortest biometric retention limit, in months, among `jurisdictions`. `null` when none applies." + }, + "reason": { + "type": "string", + "nullable": true, + "enum": [ + "BIOMETRIC_CONSENT_UNSUPPORTED_SDK", + "BIOMETRIC_CONSENT_DECLINED" + ], + "description": "`BIOMETRIC_CONSENT_UNSUPPORTED_SDK`: consent was required and a native SDK below 4.10.0, or one that did not report its version, captured a face without the consent step; `status` stays `pending`, or `given` if the person later agreed in the hosted flow. `BIOMETRIC_CONSENT_DECLINED`: a decline was recorded (it replaces the SDK reason). `null` otherwise, and always `null` when the session did not require consent." + }, + "decline_reason": { + "type": "string", + "nullable": true, + "enum": [ + "BIOMETRIC_CONSENT_DECLINED" + ], + "description": "`BIOMETRIC_CONSENT_DECLINED` when a recorded decline ended the session, otherwise `null`." + }, + "face_data_deleted_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the decline deleted the session's face images, videos and face templates. `null` unless a decline was recorded." + } + } + }, + "investigator": { + "$ref": "#/components/schemas/InvestigatorBlock" + } + } + }, + "WorkflowDetail": { + "type": "object", + "description": "The fields below are typed; the object also carries every other column of the workflow configuration (feature toggles, thresholds, warning actions, allowed documents, callbacks) as additional properties. Fields that do not apply to the `workflow_type` are `null`.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Per-version identifier (`settings_uuid`). Every version of a workflow has its own." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier shared by every version of the workflow. Sessions created with it use the latest published version." + }, + "workflow_label": { + "type": "string", + "description": "Display name of the workflow." + }, + "workflow_type": { + "type": "string", + "nullable": true, + "enum": [ + "kyc", + "kyb", + "adaptive_age_verification", + "biometric_authentication", + "address_verification", + "questionnaire_verification", + "email_verification", + "phone_verification", + null + ], + "description": "Workflow template. `null` for graph workflows created without a template." + }, + "is_default": { + "type": "boolean", + "description": "Whether this is the application's default workflow." + }, + "is_archived": { + "type": "boolean", + "description": "Archived workflows cannot start new sessions." + }, + "version": { + "type": "integer", + "description": "Version number within the workflow." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Draft versions are editable; published versions are immutable and used for new sessions." + }, + "published_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this version was published. `null` for drafts." + }, + "is_simple_workflow": { + "type": "boolean", + "description": "`true` when the workflow has no custom `workflow_graph`." + }, + "is_editable": { + "type": "boolean", + "description": "`true` only for `draft` versions." + }, + "workflow_url": { + "type": "string", + "format": "uri", + "description": "Public URL that starts a session with this workflow." + }, + "workflow_graph": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Graph structure of the workflow (nodes and edges). `null` for simple workflows." + }, + "features": { + "type": "string", + "description": "Enabled features joined with `\" + \"`, for example `\"OCR + LIVENESS\"`." + }, + "total_price": { + "type": "number", + "nullable": true, + "description": "Price per session; for graph workflows the most expensive path." + }, + "min_price": { + "type": "number", + "nullable": true, + "description": "Price of the cheapest path through the workflow." + }, + "max_price": { + "type": "number", + "nullable": true, + "description": "Price of the most expensive path through the workflow." + }, + "special_add_on_prices": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Organization-specific add-on prices, or `null` when none are set." + }, + "max_retry_attempts": { + "type": "integer", + "description": "Retries allowed after a declined verification." + }, + "retry_window_days": { + "type": "integer", + "nullable": true, + "description": "Days during which the retries are allowed." + }, + "session_expiration_time": { + "type": "integer", + "description": "Seconds before an unfinished session expires." + }, + "questionnaire": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "The questionnaire attached to the workflow, or `null`." + }, + "kyb_role_settings": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Per-role verification settings for KYB key people." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "is_bank_verification_enabled": { + "type": "boolean", + "description": "Whether a simple KYC or KYB workflow runs Bank verification. It runs after the contact steps (and Location verification) and before database validation, AML and IP analysis. Bank verification is not generally available yet: turning it on is refused wherever it is not enabled." + }, + "bank_verification_config": { + "type": "object", + "additionalProperties": true, + "description": "Bank verification settings of a simple workflow, with the same keys a `BANK_VERIFICATION` node takes (for example `bank_countries` and `bank_method_order`). Unknown keys are dropped. Every key is optional and an absent key keeps its default, so `{}` means all defaults. See [Bank verification settings](/management-api/workflows/feature-configs#bank-verification).", + "example": {} + }, + "is_geolocation_enabled": { + "type": "boolean", + "description": "Whether a simple KYC workflow runs Location verification. It runs after the contact steps and before Bank verification and database validation. Business (KYB) workflows cannot run it." + }, + "geolocation_config": { + "type": "object", + "additionalProperties": true, + "description": "Location verification settings of a simple workflow, with the same keys a `GEOLOCATION` node takes (for example `is_geolocation_geofencing_enabled` and `geolocation_max_attempts`). Unknown keys are dropped. Every key is optional and an absent key keeps its default, so `{}` means all defaults.", + "example": {} + } + }, + "additionalProperties": true + }, + "UserSessionListItem": { + "title": "KYC session row (session_kind: user)", + "type": "object", + "description": "Summary row for a User Verification (KYC) session. Identity fields (`full_name`, `country`, `document_type`, `portrait_image`, contact fields) are populated progressively as the user completes verification steps, and are `null` until the relevant step finishes. Fields restricted by the workflow's response-attributes configuration are also returned as `null`.", + "required": [ + "session_id", + "session_kind", + "session_number", + "session_url", + "portrait_image", + "document_type", + "full_name", + "country", + "status", + "vendor_data", + "didit_internal_id", + "created_at", + "features", + "phone_number", + "email_address", + "additional_details" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of the verification session. Use it with `GET /v3/session/{sessionId}/decision/` to fetch the full decision payload.", + "example": "5b3720ed-d429-42ef-b67f-37ea805f48ee" + }, + "session_kind": { + "type": "string", + "enum": [ + "user" + ], + "description": "Row discriminator. Always `\"user\"` for KYC rows; Business Verification (KYB) rows have `\"business\"` and a different shape.", + "example": "user" + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of the session within your application. Also matched by the `search` parameter.", + "example": 720 + }, + "session_url": { + "type": "string", + "nullable": true, + "description": "Hosted verification URL the end user opens to complete the flow. `null` when the session has no hosted URL.", + "example": "https://verify.didit.me/session/CuXoBYCYVO5Y" + }, + "portrait_image": { + "type": "string", + "nullable": true, + "description": "Expiring (presigned) URL of the best available portrait: the NFC chip photo when available, otherwise the ID document portrait, otherwise the liveness reference image. `null` until one of those exists. Download promptly; the link expires.", + "example": "https:///portraits/5b3720ed.jpg?X-Amz-Expires=3600&X-Amz-Signature=..." + }, + "document_type": { + "type": "string", + "nullable": true, + "enum": [ + "Passport", + "Identity Card", + "Driver's License", + "Residence Permit", + "Health Insurance Card", + "Tax Card" + ], + "description": "Human-readable type of the verified ID document. `null` before document verification completes, and also `null` after completion when the verified code has no display mapping (`SSC`, `WP`, `VISA`, `PSC`, `BC`, `OTHER`). Note: the `document_type` query parameter filters by short codes (`P`, `ID`, `DL`, `RP`, ...), not by these display values.", + "example": "Passport" + }, + "full_name": { + "type": "string", + "nullable": true, + "description": "Best available full name for the session, resolved in order: ID document OCR, AML screened data, database-validation screened data, proof-of-address document, then the expected details provided at session creation. `null` when none is available yet.", + "example": "Jane Marie Doe" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-3 country associated with the session, resolved in order: ID document issuing state, database-validation issuing state, proof-of-address issuing state, expected details, then the verified phone's country. May differ from nationality.", + "example": "ESP" + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Overall session status. See the [Verification Statuses](/integration/verification-statuses) page for the lifecycle.", + "example": "Approved" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the end user, echoed back exactly as sent at session creation.", + "example": "your-user-id-123" + }, + "didit_internal_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Didit's internal identifier for the verified user profile behind this session. Stable across multiple sessions of the same user; filterable via the `didit_internal_id` query parameter.", + "example": "d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "UTC timestamp when the session was created.", + "example": "2026-06-09T12:26:54.328364Z" + }, + "features": { + "type": "array", + "description": "Per-feature status for every verification step configured in the session's workflow, in workflow order.", + "items": { + "type": "object", + "required": [ + "feature", + "status" + ], + "properties": { + "feature": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "NFC", + "LIVENESS", + "FACE_MATCH", + "POA", + "QUESTIONNAIRE", + "EMAIL_VERIFICATION", + "PHONE", + "AML", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "DATABASE_VALIDATION" + ], + "description": "Verification feature identifier.", + "example": "ID_VERIFICATION" + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Status of this feature. `Not Finished` until the step completes.", + "example": "Approved" + } + } + } + }, + "phone_number": { + "type": "object", + "nullable": true, + "description": "Phone associated with the session: the verified phone when phone verification ran, otherwise the contact phone provided at session creation. `null` when neither exists.", + "required": [ + "number", + "is_verified" + ], + "properties": { + "number": { + "type": "string", + "description": "Phone number in E.164 format.", + "example": "+14155552671" + }, + "is_verified": { + "type": "boolean", + "description": "`true` only when the phone-verification feature approved this number.", + "example": true + } + } + }, + "email_address": { + "type": "object", + "nullable": true, + "description": "Email associated with the session: the verified email when email verification ran, otherwise the contact email provided at session creation. `null` when neither exists.", + "required": [ + "email", + "is_verified" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "example": "jane@example.com" + }, + "is_verified": { + "type": "boolean", + "description": "`true` only when the email-verification feature approved this address.", + "example": true + } + } + }, + "additional_details": { + "type": "object", + "nullable": true, + "description": "Compact per-feature extras. Only features that produced data appear as keys; `null` when no feature produced extras. Fields excluded by the workflow's response-attributes configuration are omitted.", + "properties": { + "aml": { + "type": "object", + "description": "Present when AML screening ran.", + "properties": { + "total_hits": { + "type": "integer", + "description": "Number of AML screening hits.", + "example": 0 + }, + "entity_type": { + "type": "string", + "description": "Screened entity type.", + "example": "person" + }, + "is_adverse_media_fetched": { + "type": "boolean", + "example": false + }, + "screened_data": { + "type": "object", + "description": "Subset of the data that was screened. Only non-empty keys are included.", + "properties": { + "nationality": { + "type": "string", + "example": "ESP" + }, + "document_number": { + "type": "string", + "example": "XX1234567" + }, + "date_of_birth": { + "type": "string", + "example": "1990-05-21" + } + } + } + } + }, + "database_validation": { + "type": "object", + "description": "Present when database validation ran.", + "properties": { + "validation_type": { + "type": "string", + "example": "DOCUMENT_VALIDATION" + }, + "match_type": { + "type": "string", + "example": "FULL_MATCH" + }, + "identification_number": { + "type": "string", + "description": "Tax, personal, or document number that was validated (first available).", + "example": "12345678Z" + } + } + }, + "poa": { + "type": "object", + "description": "Present when proof of address ran.", + "properties": { + "document_type": { + "type": "string", + "example": "UTILITY_BILL" + }, + "issuer": { + "type": "string", + "example": "Energy Co" + }, + "address": { + "type": "string", + "example": "Calle Mayor 1, 28013 Madrid" + }, + "name_on_document": { + "type": "string", + "example": "Jane Marie Doe" + } + } + }, + "liveness": { + "type": "object", + "description": "Present when a liveness check ran.", + "properties": { + "method": { + "type": "string", + "example": "PASSIVE" + }, + "score": { + "type": "number", + "description": "Liveness confidence score, rounded to 2 decimals.", + "example": 0.98 + } + } + }, + "face_match": { + "type": "object", + "description": "Present when face match ran.", + "properties": { + "score": { + "type": "number", + "description": "Face-match similarity score, rounded to 2 decimals.", + "example": 0.95 + }, + "source_image": { + "type": "string", + "description": "Expiring (presigned) URL of the source face image.", + "example": "https:///faces/source.jpg?X-Amz-Expires=3600&..." + }, + "target_image": { + "type": "string", + "description": "Expiring (presigned) URL of the target face image.", + "example": "https:///faces/target.jpg?X-Amz-Expires=3600&..." + } + } + }, + "phone_verification": { + "type": "object", + "description": "Present when phone verification ran.", + "properties": { + "country_code": { + "type": "string", + "description": "ISO 3166-1 alpha-2 country of the phone number.", + "example": "US" + }, + "is_temporary": { + "type": "boolean", + "example": false + }, + "is_ported": { + "type": "boolean", + "example": false + }, + "is_blocked": { + "type": "boolean", + "example": false + } + } + }, + "email_verification": { + "type": "object", + "description": "Present when email verification ran.", + "properties": { + "is_breached": { + "type": "boolean", + "example": false + }, + "is_disposable": { + "type": "boolean", + "example": false + }, + "is_undeliverable": { + "type": "boolean", + "example": false + }, + "is_blocklisted": { + "type": "boolean", + "example": false + } + } + } + } + } + } + }, + "BusinessSessionListItem": { + "title": "KYB session row (session_kind: business)", + "type": "object", + "description": "Summary row for a Business Verification (KYB) session. Returned only when `session_kind` is `business` or `all`.", + "required": [ + "session_id", + "session_kind", + "session_number", + "status", + "vendor_data", + "created_at", + "workflow_id", + "workflow_type", + "workflow_label", + "company_name", + "registration_number", + "country" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of the business verification session.", + "example": "8a1f63c2-1b9e-4f3a-9c41-2f6a1f0b7d52" + }, + "session_kind": { + "type": "string", + "enum": [ + "business" + ], + "description": "Row discriminator. Always `\"business\"` for KYB rows.", + "example": "business" + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of the business session within your application. Numbered independently from KYC sessions.", + "example": 42 + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Overall session status. See the [Verification Statuses](/integration/verification-statuses) page.", + "example": "Approved" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the business, echoed back exactly as sent at session creation.", + "example": "your-company-id-77" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "UTC timestamp when the session was created.", + "example": "2026-06-08T09:14:02.118450Z" + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Identifier of the workflow this session was created with.", + "example": "9f9b1234-aaaa-bbbb-cccc-1234567890ab" + }, + "workflow_type": { + "type": "string", + "nullable": true, + "description": "Workflow type. `kyb` for business workflows.", + "example": "kyb" + }, + "workflow_label": { + "type": "string", + "nullable": true, + "description": "Display name of the workflow.", + "example": "Standard KYB" + }, + "company_name": { + "type": "string", + "nullable": true, + "description": "Name of the company being verified, from the registry company linked to the session. `null` until a company is selected.", + "example": "Acme Holdings S.L." + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Company registration number from the registry company linked to the session.", + "example": "B12345678" + }, + "country": { + "type": "string", + "nullable": true, + "description": "Country code of the registry company linked to the session.", + "example": "ES" + } + } + }, + "TransactionVerificationSessionAction": { + "title": "Verification session", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "verification_session" + ] + }, + "url": { + "type": "string", + "format": "uri", + "description": "Hosted verification URL to open for the end user." + }, + "session_id": { + "type": "string", + "format": "uuid", + "description": "Verification session id." + }, + "session_token": { + "type": "string", + "description": "Session token, used by the mobile SDKs to launch the flow natively." + }, + "status": { + "type": "string", + "description": "Verification session status." + } + }, + "required": [ + "type", + "url" + ] + }, + "TransactionWalletOwnershipAction": { + "title": "Wallet ownership", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "wallet_ownership" + ] + }, + "url": { + "type": "string", + "format": "uri", + "description": "Hosted wallet-ownership widget URL. Open it in a real browser surface - on mobile use SFSafariViewController or a Chrome Custom Tab, never an embedded webview." + }, + "widget_session_id": { + "type": "string", + "format": "uuid", + "description": "Wallet-ownership widget session id." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the widget session expires." + } + }, + "required": [ + "type", + "url" + ] + }, + "TransactionRuleActionAddScore": { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "add_score" + ] + }, + "value": { + "type": "integer", + "description": "Points added to the transaction's risk score. May be negative." + } + }, + "description": "Add points to the transaction's cumulative risk score; the total decides the status via the application's review/decline thresholds." + }, + "TransactionRuleActionChangeStatus": { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "change_status" + ] + }, + "value": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Status to set immediately, regardless of score. Alias key: status." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "REQUIRED when value is AWAITING_USER: the verification workflow the subject is asked to complete. Alias key: remediation_workflow_id. Didit creates a hosted remediation session for the subject when the rule fires." + } + }, + "description": "Directly set the transaction status. DECLINED always wins across matched rules." + }, + "TransactionRuleActionAddTags": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "add_tags" + ] + }, + "tag_uuid": { + "type": "string", + "format": "uuid", + "description": "Existing application tag to attach." + }, + "tag_name": { + "type": "string", + "description": "Tag name to attach; created on first use (stored uppercased). Provide tag_uuid or tag_name." + }, + "tag_color": { + "type": "string", + "description": "Hex color used when tag_name creates a new tag, e.g. #007AFE." + } + }, + "description": "Attach an application tag to the matched transaction." + }, + "TransactionRuleActionAddNote": { + "type": "object", + "required": [ + "type", + "note" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "add_note" + ] + }, + "note": { + "type": "string", + "description": "Note text attached to the transaction (author: Rule Engine). Alias key: value." + } + }, + "description": "Attach an automated analyst note to the matched transaction." + }, + "TransactionRuleActionAddToList": { + "type": "object", + "required": [ + "type", + "list_id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "add_to_list" + ] + }, + "list_id": { + "type": "string", + "format": "uuid", + "description": "List to add the subject's vendor_data to." + } + }, + "description": "Add the subject's vendor_data to a blocklist, allowlist or custom list." + }, + "TransactionRuleActionOpenCase": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "open_case" + ] + }, + "blueprint": { + "type": "string", + "format": "uuid", + "description": "Case blueprint UUID; defaults to the application's default blueprint." + }, + "grouping": { + "type": "string", + "enum": [ + "by_applicant", + "by_rule_and_applicant" + ], + "description": "Whether repeat matches join one case per applicant or one per rule+applicant." + }, + "attach_matched_transaction": { + "type": "boolean", + "description": "Attach the matched transaction to the case." + } + }, + "description": "Open (or add to) an investigation case. Only the first open_case action on a rule is applied." + }, + "KYBRegistrySearchResults": { + "type": "object", + "properties": { + "companies": { + "type": "array", + "items": { + "type": "object", + "properties": { + "kyb_response_id": { + "type": "string", + "nullable": true, + "description": "Handle for this candidate — pass it to `POST /v3/kyb/select/` to retrieve the full profile. Ephemeral and scoped to this search: do not store it long-term; re-search to get a fresh one.", + "example": "69aeeb95febb0f1704042259" + }, + "name": { + "type": "string", + "nullable": true, + "description": "Registered company name.", + "example": "Tesco PLC" + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Registry registration number.", + "example": "00445790" + }, + "status": { + "type": "string", + "nullable": true, + "description": "Normalized registry status of the company (e.g. `active`, `dissolved`, `inactive`, `struck off`).", + "example": "active" + }, + "type": { + "type": "string", + "nullable": true, + "description": "Legal form as reported by the registry.", + "example": "Public Limited Company" + }, + "registered_address": { + "type": "string", + "nullable": true, + "description": "Registered address of the candidate, when the registry returns one. Use it, with the registration number, to tell same-named companies apart: in the United States a candidate can be listed under the state where the company operates rather than the state you searched.", + "example": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, AL7 1GA" + }, + "risk_level": { + "type": "string", + "nullable": true, + "description": "Registry-reported risk level, when available. Usually `null` at search time." + }, + "fetch_status": { + "type": "string", + "nullable": true, + "description": "Always `pending` for search candidates — the full company profile is only fetched (and billed) when you select the candidate.", + "example": "pending" + } + } + }, + "description": "Deduplicated candidate list. Empty while `search_resolved=false` (webhook mode) or when nothing matched." + }, + "pagination": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Number of candidates returned." + }, + "page": { + "type": "integer" + }, + "per_page": { + "type": "integer", + "example": 25 + } + } + }, + "search_status": { + "type": "string", + "nullable": true, + "enum": [ + "pending", + "resolved" + ], + "description": "Registry search lifecycle status." + }, + "search_resolved": { + "type": "boolean", + "description": "`true` once the candidate list is final." + } + } + }, + "KYBRegistryCandidateRequest": { + "type": "object", + "required": [ + "kyb_response_id" + ], + "properties": { + "kyb_response_id": { + "type": "string", + "maxLength": 255, + "description": "Candidate handle from `POST /v3/kyb/search/` (`kyb_registry.companies[].kyb_response_id`). Ephemeral — use it shortly after the search that produced it.", + "example": "69aeeb95febb0f1704042259" + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for this company; echoed back and stored with the business session." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON stored with the business session and echoed back." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "Must be `true` (the default). KYB registry selects are always persisted so billing and retrieval can be tracked; `false` returns `400`." + } + } + }, + "WebhookEventEnvelope": { + "type": "object", + "description": "Fields Didit adds to every signed webhook delivery, whatever the event type. Read `webhook_type` to know which payload you received.", + "required": [ + "event_id", + "webhook_type", + "timestamp", + "created_at", + "application_id", + "environment" + ], + "properties": { + "event_id": { + "type": "string", + "format": "uuid", + "description": "Identifier of the event. Every destination receives the same value and a redelivery keeps it, so use it to deduplicate." + }, + "webhook_type": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "timestamp": { + "type": "integer", + "description": "Unix time in seconds at which this delivery was signed. Equal to the `X-Timestamp` header; a redelivery carries a new value." + }, + "created_at": { + "type": "integer", + "description": "Unix time in seconds of the change that produced the event." + }, + "application_id": { + "type": "string", + "format": "uuid", + "description": "The application the event belongs to." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "`sandbox` for sandbox sessions and sandbox applications, otherwise `live`." + } + } + }, + "SessionWebhookEventData": { + "type": "object", + "description": "Session fields of `status.updated` and `data.updated`. Optional fields are omitted, never sent as `null`, when they do not apply. Business (KYB) sessions add `business_session_id`, `session_kind` and `vendor_business_id`.", + "required": [ + "session_id", + "status", + "sandbox_scenario" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The verification session." + }, + "business_session_id": { + "type": "string", + "format": "uuid", + "description": "Business (KYB) sessions only: the same value as `session_id`." + }, + "session_kind": { + "type": "string", + "enum": [ + "business" + ], + "description": "Business (KYB) sessions only. User (KYC) session events carry no top-level `session_kind`; read `decision.session_kind` instead." + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Session status after the change." + }, + "trigger": { + "type": "string", + "description": "Present when the event was not produced by the verification flow itself, for example `manual_review`, `manual_step_update`, `ongoing_monitoring`, `webhook_reconciliation`, `manual_webhook_recovery`, `kyb_registry_change` or a `console_*` edit." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "The session's workflow. Omitted when the session has none." + }, + "workflow_version": { + "type": "integer", + "description": "Version of the workflow the session ran. Sent together with `workflow_id`." + }, + "vendor_data": { + "type": "string", + "description": "Your `vendor_data` for the session. Omitted when empty." + }, + "vendor_business_id": { + "type": "string", + "description": "Business (KYB) sessions only: the business the session belongs to. Omitted when none." + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Your session `metadata`. Omitted when empty." + }, + "decision": { + "$ref": "#/components/schemas/SessionDecision" + }, + "resubmit_info": { + "type": "object", + "description": "Only when `status` is `Resubmitted`: the steps the user must redo and why.", + "required": [ + "nodes_to_resubmit", + "reasons" + ], + "properties": { + "nodes_to_resubmit": { + "type": "array", + "items": { + "type": "object", + "required": [ + "node_id", + "feature" + ], + "properties": { + "node_id": { + "type": "string", + "description": "Node identifier as it appears in the session's workflow definition (for example `feature_ocr`, `feature_liveness`).", + "example": "feature_poa" + }, + "feature": { + "type": "string", + "description": "Feature type of the node, for example `OCR`, `LIVENESS` or `PROOF_OF_ADDRESS`.", + "example": "PROOF_OF_ADDRESS" + } + } + }, + "description": "Workflow steps the user must redo. The same `{node_id, feature}` entries as `nodes_to_resubmit` in [Update Status](#patch-/v3/session/-sessionId-/update-status/)." + }, + "reasons": { + "type": "object", + "additionalProperties": true, + "description": "Reason per `node_id` in `nodes_to_resubmit`." + } + } + }, + "sandbox_scenario": { + "type": "string", + "nullable": true, + "description": "The simulated outcome armed on a sandbox session, or `null`. Always `null` for live sessions." + } + } + }, + "SessionStatusUpdatedEvent": { + "description": "A verification session changed status. `decision` is sent only for `Approved`, `Declined`, `In Review` and `Abandoned`; it has the shape of `GET /v3/session/{sessionId}/decision/` for destinations on webhook version 3 (the default), and the older reduced shape for version 1 or 2 destinations. Business sessions always receive the version 3 business decision.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "$ref": "#/components/schemas/SessionWebhookEventData" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "status.updated" + ], + "description": "Always `status.updated`." + } + }, + "required": [ + "webhook_type" + ] + } + ] + }, + "SessionDataUpdatedEvent": { + "description": "Data of a verification session changed without a status change, for example after a manual review edit, a step override or an ongoing-monitoring rescreen. `decision` is sent only for `Approved`, `Declined`, `In Review` and `Abandoned`; it has the shape of `GET /v3/session/{sessionId}/decision/` for destinations on webhook version 3 (the default), and the older reduced shape for version 1 or 2 destinations. Business sessions always receive the version 3 business decision.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "$ref": "#/components/schemas/SessionWebhookEventData" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "data.updated" + ], + "description": "Always `data.updated`." + } + }, + "required": [ + "webhook_type" + ] + } + ] + }, + "UserStatusUpdatedEvent": { + "description": "A user changed status. Not sent when the user is created.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "user.status.updated" + ], + "description": "Always `user.status.updated`." + }, + "vendor_user_id": { + "type": "string", + "format": "uuid", + "description": "The user the event is about." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the user, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "User status after the change." + }, + "previous_status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "Status before the change." + } + }, + "required": [ + "webhook_type", + "vendor_user_id", + "vendor_data", + "status", + "previous_status" + ] + } + ] + }, + "UserDataUpdatedEvent": { + "description": "Fields of a user changed. Not sent when the user is created. One save can produce both a status and a data event.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "user.data.updated" + ], + "description": "Always `user.data.updated`." + }, + "vendor_user_id": { + "type": "string", + "format": "uuid", + "description": "The user the event is about." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the user, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "User status after the change." + }, + "changed_fields": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "vendor_data", + "display_name", + "full_name", + "date_of_birth", + "metadata", + "portrait_image", + "issuing_states", + "approved_emails", + "approved_phones", + "features", + "session_count", + "approved_count", + "declined_count", + "in_review_count", + "first_session_at", + "last_session_at" + ] + }, + "description": "Names of the fields that changed." + }, + "changes": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "previous": { + "nullable": true, + "description": "Value before the change. Dates are ISO 8601 strings." + }, + "current": { + "nullable": true, + "description": "Value after the change. Dates are ISO 8601 strings." + } + } + }, + "description": "Previous and current value of each changed field, keyed by field name. Values can be personal data (names, dates of birth, portrait links, emails, phone numbers); handle them accordingly." + } + }, + "required": [ + "webhook_type", + "vendor_user_id", + "vendor_data", + "status", + "changed_fields", + "changes" + ] + } + ] + }, + "BusinessStatusUpdatedEvent": { + "description": "A business changed status. Not sent when the business is created.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "business.status.updated" + ], + "description": "Always `business.status.updated`." + }, + "vendor_business_id": { + "type": "string", + "format": "uuid", + "description": "The business the event is about." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the business, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "Business status after the change." + }, + "previous_status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "Status before the change." + } + }, + "required": [ + "webhook_type", + "vendor_business_id", + "vendor_data", + "status", + "previous_status" + ] + } + ] + }, + "BusinessDataUpdatedEvent": { + "description": "Fields of a business changed. Not sent when the business is created. One save can produce both a status and a data event.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "business.data.updated" + ], + "description": "Always `business.data.updated`." + }, + "vendor_business_id": { + "type": "string", + "format": "uuid", + "description": "The business the event is about." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the business, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "Business status after the change." + }, + "changed_fields": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "vendor_data", + "display_name", + "metadata", + "legal_name", + "registration_number", + "country_code", + "session_count", + "approved_count", + "declined_count", + "in_review_count", + "features", + "first_session_at", + "last_session_at" + ] + }, + "description": "Names of the fields that changed." + }, + "changes": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "previous": { + "nullable": true, + "description": "Value before the change. Dates are ISO 8601 strings." + }, + "current": { + "nullable": true, + "description": "Value after the change. Dates are ISO 8601 strings." + } + } + }, + "description": "Previous and current value of each changed field, keyed by field name." + } + }, + "required": [ + "webhook_type", + "vendor_business_id", + "vendor_data", + "status", + "changed_fields", + "changes" + ] + } + ] + }, + "TransactionCreatedEvent": { + "description": "A transaction was created and screened. Sent for live applications only.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "transaction.created" + ], + "description": "Always `transaction.created`." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Didit's identifier of the transaction." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "The `transaction_id` you supplied at create time, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Monitoring status after the change." + }, + "score": { + "type": "integer", + "description": "Risk score accumulated by rules." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL", + null + ], + "description": "Risk severity, or `null` until a rule or provider sets one." + }, + "amount": { + "type": "string", + "description": "Transaction amount as a decimal string. Unlike the API it is not normalized, so it can carry trailing zeros (up to 18 decimal places)." + }, + "currency": { + "type": "string", + "description": "Currency code or crypto asset symbol of `amount`." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject." + }, + "action_required": { + "type": "object", + "nullable": true, + "oneOf": [ + { + "$ref": "#/components/schemas/TransactionVerificationSessionAction" + }, + { + "$ref": "#/components/schemas/TransactionWalletOwnershipAction" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "verification_session": "#/components/schemas/TransactionVerificationSessionAction", + "wallet_ownership": "#/components/schemas/TransactionWalletOwnershipAction" + } + }, + "description": "Pending end-user action, or `null`. Same object as `action_required` on the transaction; `session_token` is a credential, so never log it." + } + }, + "required": [ + "webhook_type", + "transaction_id", + "txn_id", + "status", + "score", + "severity", + "amount", + "currency", + "direction", + "action_required" + ] + } + ] + }, + "TransactionStatusUpdatedEvent": { + "description": "A transaction changed status, from a rule, a review or an end-user action. Sent for live applications only.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "transaction.status.updated" + ], + "description": "Always `transaction.status.updated`." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Didit's identifier of the transaction." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "The `transaction_id` you supplied at create time, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Monitoring status after the change." + }, + "score": { + "type": "integer", + "description": "Risk score accumulated by rules." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL", + null + ], + "description": "Risk severity, or `null` until a rule or provider sets one." + }, + "amount": { + "type": "string", + "description": "Transaction amount as a decimal string. Unlike the API it is not normalized, so it can carry trailing zeros (up to 18 decimal places)." + }, + "currency": { + "type": "string", + "description": "Currency code or crypto asset symbol of `amount`." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject." + }, + "action_required": { + "type": "object", + "nullable": true, + "oneOf": [ + { + "$ref": "#/components/schemas/TransactionVerificationSessionAction" + }, + { + "$ref": "#/components/schemas/TransactionWalletOwnershipAction" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "verification_session": "#/components/schemas/TransactionVerificationSessionAction", + "wallet_ownership": "#/components/schemas/TransactionWalletOwnershipAction" + } + }, + "description": "Pending end-user action, or `null`. Same object as `action_required` on the transaction; `session_token` is a credential, so never log it." + } + }, + "required": [ + "webhook_type", + "transaction_id", + "txn_id", + "status", + "score", + "severity", + "amount", + "currency", + "direction", + "action_required" + ] + } + ] + }, + "TravelRuleStatusUpdatedEvent": { + "description": "A Travel Rule exchange changed status. Sent on every transition up to and including `FINISHED`, `CANCELLED` or `EXPIRED`, never after. Sent for live applications and, for Travel Rule exchanges, for sandbox applications too (`environment: sandbox`). There is no top-level `status`.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "travel_rule.status.updated" + ], + "description": "Always `travel_rule.status.updated`." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "The transaction the exchange belongs to." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "The `transaction_id` you supplied at create time, or `null`." + }, + "travel_rule_status": { + "type": "string", + "enum": [ + "AWAITING_COUNTERPARTY", + "ON_HOLD", + "COMPLETED", + "FINISHED", + "CANCELLED", + "EXPIRED", + "COUNTERPARTY_MISMATCHED_DATA", + "COUNTERPARTY_VASP_NOT_FOUND", + "COUNTERPARTY_VASP_NOT_REACHABLE", + "UNCONFIRMED_OWNERSHIP", + "COUNTERPARTY_VASP_GENERAL_DECLINE", + "NOT_ENOUGH_COUNTERPARTY_DATA", + "NOT_APPLICABLE" + ], + "description": "Exchange status after the change." + }, + "rail": { + "type": "string", + "enum": [ + "", + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL", + "TEST_VASP" + ], + "description": "Network the exchange runs on, or an empty string when none could be resolved. `TEST_VASP` is a sandbox transfer the Didit Test VASP answered." + }, + "test_vasp": { + "type": "boolean", + "description": "Present and `true` only on a sandbox transfer the Didit Test VASP answered." + }, + "test_vasp_scenario": { + "type": "string", + "description": "Present only with `test_vasp`: the sandbox scenario that decided the outcome, or an empty string." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction of the transfer." + } + }, + "required": [ + "webhook_type", + "transaction_id", + "txn_id", + "travel_rule_status", + "rail", + "direction" + ] + } + ] + }, + "WorkflowComplianceChangedEvent": { + "description": "The compliance status of a published workflow changed after a re-evaluation. One event per workflow. There is no top-level `status`.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "workflow.compliance_changed" + ], + "description": "Always `workflow.compliance_changed`." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "The workflow that changed." + }, + "workflow_version": { + "type": "integer", + "description": "Version that was evaluated." + }, + "workflow_label": { + "type": "string", + "nullable": true, + "description": "Display name of the workflow." + }, + "transition": { + "type": "object", + "required": [ + "from", + "to" + ], + "properties": { + "from": { + "type": "string", + "enum": [ + "compliant", + "gaps_detected", + "pending_change", + "unknown_coverage", + "not_evaluated" + ], + "description": "Status before the re-evaluation." + }, + "to": { + "type": "string", + "enum": [ + "compliant", + "gaps_detected", + "pending_change", + "unknown_coverage", + "not_evaluated" + ], + "description": "Status after the re-evaluation." + } + } + }, + "severity": { + "type": "string", + "enum": [ + "actionable", + "indicative", + "informative" + ], + "description": "`actionable` when gaps were detected, `indicative` for a pending change, `informative` when gaps were resolved." + }, + "kb_version": { + "type": "string", + "description": "Version of the regulatory knowledge base used." + } + }, + "required": [ + "webhook_type", + "workflow_id", + "workflow_version", + "workflow_label", + "transition", + "severity", + "kb_version" + ] + } + ] + }, + "WebhookEventPayload": { + "description": "Body of a signed webhook delivery, discriminated by `webhook_type`. `activity.created` can be subscribed to but is not sent today. Treat any other `webhook_type` as a new event type and keep the raw body.", + "oneOf": [ + { + "$ref": "#/components/schemas/SessionStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/SessionDataUpdatedEvent" + }, + { + "$ref": "#/components/schemas/UserStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/UserDataUpdatedEvent" + }, + { + "$ref": "#/components/schemas/BusinessStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/BusinessDataUpdatedEvent" + }, + { + "$ref": "#/components/schemas/TransactionCreatedEvent" + }, + { + "$ref": "#/components/schemas/TransactionStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/TravelRuleStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/WorkflowComplianceChangedEvent" + } + ], + "discriminator": { + "propertyName": "webhook_type", + "mapping": { + "status.updated": "#/components/schemas/SessionStatusUpdatedEvent", + "data.updated": "#/components/schemas/SessionDataUpdatedEvent", + "user.status.updated": "#/components/schemas/UserStatusUpdatedEvent", + "user.data.updated": "#/components/schemas/UserDataUpdatedEvent", + "business.status.updated": "#/components/schemas/BusinessStatusUpdatedEvent", + "business.data.updated": "#/components/schemas/BusinessDataUpdatedEvent", + "transaction.created": "#/components/schemas/TransactionCreatedEvent", + "transaction.status.updated": "#/components/schemas/TransactionStatusUpdatedEvent", + "travel_rule.status.updated": "#/components/schemas/TravelRuleStatusUpdatedEvent", + "workflow.compliance_changed": "#/components/schemas/WorkflowComplianceChangedEvent" + } + } + }, + "KYBRegistrySearchResolvedCallback": { + "type": "object", + "description": "Body of the callback sent to the `webhook_url` of `POST /v3/kyb/search/` once the registry search resolves. It is not signed: the request carries `X-Didit-Unsigned-Callback: true` and none of the signature headers, so confirm it by matching `request_id` against a search you started. Uses `event_type`, not `webhook_type`.", + "required": [ + "event_id", + "event_type", + "request_id", + "vendor_data", + "metadata", + "search_status", + "search_resolved", + "kyb_registry", + "timestamp", + "created_at" + ], + "properties": { + "event_id": { + "type": "string", + "format": "uuid", + "description": "Identifier of the callback." + }, + "event_type": { + "type": "string", + "enum": [ + "kyb.registry_search.resolved" + ], + "description": "Always `kyb.registry_search.resolved`." + }, + "request_id": { + "type": "string", + "format": "uuid", + "description": "The `request_id` returned by `POST /v3/kyb/search/`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` you sent." + }, + "search_status": { + "type": "string", + "description": "Always `resolved` today." + }, + "search_resolved": { + "type": "boolean", + "description": "`true` when `search_status` is `resolved`." + }, + "kyb_registry": { + "$ref": "#/components/schemas/KYBRegistrySearchResults" + }, + "timestamp": { + "type": "integer", + "description": "Unix time in seconds at which the callback was queued." + }, + "created_at": { + "type": "integer", + "description": "Unix time in seconds at which the callback was queued." + } + } + }, + "AMLScreening": { + "type": "object", + "properties": { + "node_id": { + "type": "string", + "description": "Unique identifier for this AML step in the workflow graph." + }, + "status": { + "type": "string", + "description": "Feature-level status of this AML screening. Independent of the top-level session `status` — a session can reach its overall decision while an individual feature stays `In Review`.", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ] + }, + "total_hits": { + "type": "integer", + "description": "Total number of matches found" + }, + "entity_type": { + "type": "string", + "description": "Type of entity screened: person or company" + }, + "hits": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier of the match" + }, + "url": { + "type": "string", + "format": "uri", + "description": "Source URL for the match" + }, + "match": { + "type": "boolean", + "description": "Whether this is a confirmed match" + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 1, + "description": "Deprecated weighted match score in the 0-1 range (e.g. `0.73`). Use `match_score` (0-100) instead.", + "deprecated": true + }, + "target": { + "type": "boolean", + "nullable": true, + "description": "Whether the matched entity is the direct target of the listing (as opposed to a related party). `null` when the source does not specify it." + }, + "caption": { + "type": "string", + "description": "Caption or title of the match" + }, + "datasets": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "PEP", + "PEP Level 1", + "PEP Level 2", + "PEP Level 3", + "PEP Level 4", + "Sanctions", + "Adverse Media", + "Warnings and Regulatory Enforcement", + "Fitness and Probity", + "SIP", + "SIE", + "Insolvency" + ] + }, + "description": "List of dataset categories where the match was found. Indicates whether the entity appeared in PEP lists (with level), sanctions databases, adverse media, warnings/regulatory enforcement, or other compliance datasets." + }, + "features": { + "type": "object", + "nullable": true, + "description": "Additional features of the match" + }, + "rca_name": { + "type": "string", + "description": "RCA name if applicable" + }, + "last_seen": { + "type": "string", + "format": "date-time", + "description": "Date when match was last seen" + }, + "risk_view": { + "type": "object", + "properties": { + "crimes": { + "type": "object", + "properties": { + "score": { + "type": "integer" + }, + "weightage": { + "type": "integer" + }, + "risk_level": { + "type": "string" + }, + "risk_scores": { + "type": "object" + } + } + }, + "countries": { + "type": "object", + "properties": { + "score": { + "type": "integer" + }, + "weightage": { + "type": "integer" + }, + "risk_level": { + "type": "string" + }, + "risk_scores": { + "type": "object" + } + } + }, + "categories": { + "type": "object", + "properties": { + "score": { + "type": "integer" + }, + "weightage": { + "type": "integer" + }, + "risk_level": { + "type": "string" + }, + "risk_scores": { + "type": "object" + } + } + }, + "custom_list": { + "type": "object" + } + } + }, + "first_seen": { + "type": "string", + "format": "date-time", + "description": "Date when match was first seen" + }, + "properties": { + "type": "object", + "properties": { + "name": { + "type": "array", + "items": { + "type": "string" + } + }, + "alias": { + "type": "array", + "items": { + "type": "string" + } + }, + "notes": { + "type": "array", + "items": { + "type": "string" + } + }, + "title": { + "type": "string", + "nullable": true + }, + "gender": { + "type": "array", + "items": { + "type": "string" + } + }, + "height": { + "type": "string", + "nullable": true + }, + "topics": { + "type": "string", + "nullable": true + }, + "weight": { + "type": "string", + "nullable": true + }, + "address": { + "type": "string", + "nullable": true + }, + "country": { + "type": "string", + "nullable": true + }, + "website": { + "type": "string", + "nullable": true + }, + "eyeColor": { + "type": "string", + "nullable": true + }, + "keywords": { + "type": "string", + "nullable": true + }, + "lastName": { + "type": "array", + "items": { + "type": "string" + } + }, + "position": { + "type": "string", + "nullable": true + }, + "religion": { + "type": "string", + "nullable": true + }, + "birthDate": { + "type": "array", + "items": { + "type": "string" + } + }, + "education": { + "type": "array", + "items": { + "type": "string" + } + }, + "ethnicity": { + "type": "string", + "nullable": true + }, + "firstName": { + "type": "array", + "items": { + "type": "string" + } + }, + "hairColor": { + "type": "string", + "nullable": true + }, + "weakAlias": { + "type": "string", + "nullable": true + }, + "birthPlace": { + "type": "array", + "items": { + "type": "string" + } + }, + "modifiedAt": { + "type": "string", + "nullable": true + }, + "wikidataId": { + "type": "string", + "nullable": true + }, + "citizenship": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + } + } + }, + "match_score": { + "type": "number", + "description": "Weighted identity confidence score (0-100) calculated from name similarity, date of birth, and country matching. Used to determine if hit is a False Positive or Possible Match.", + "format": "float", + "nullable": true + }, + "risk_score": { + "type": "number", + "description": "Entity risk score (0-100). Used to determine final AML status (Approved/In Review/Declined) based on the highest risk_score among non-False-Positive hits." + }, + "review_status": { + "type": "string", + "description": "Review status for this hit. Can be 'Unreviewed', 'Confirmed Match', 'False Positive', or 'Inconclusive'.", + "enum": [ + "Unreviewed", + "Confirmed Match", + "False Positive", + "Inconclusive" + ] + }, + "score_breakdown": { + "type": "object", + "description": "Detailed breakdown of how the match_score was calculated.", + "properties": { + "name_score": { + "type": "integer", + "description": "Raw name similarity percentage (0-100)." + }, + "name_weight": { + "type": "integer", + "description": "Original weight for name from config (0-100)." + }, + "name_weight_normalized": { + "type": "number", + "description": "Normalized weight for name after re-weighting for missing data (0-100)." + }, + "name_contribution": { + "type": "number", + "description": "Points contributed by name to the total score." + }, + "dob_score": { + "type": "integer", + "description": "DOB match score (-100 to 100)." + }, + "dob_weight": { + "type": "integer", + "description": "Original weight for DOB from config (0-100)." + }, + "dob_weight_normalized": { + "type": "number", + "description": "Normalized weight for DOB after re-weighting for missing data (0-100)." + }, + "dob_contribution": { + "type": "number", + "description": "Points contributed by DOB to the total score." + }, + "country_score": { + "type": "integer", + "description": "Country match score (-100 to 100)." + }, + "country_weight": { + "type": "integer", + "description": "Original weight for country from config (0-100)." + }, + "country_weight_normalized": { + "type": "number", + "description": "Normalized weight for country after re-weighting for missing data (0-100)." + }, + "country_contribution": { + "type": "number", + "description": "Points contributed by country to the total score." + }, + "document_number_match_type": { + "type": "string", + "description": "Result of document number 'Golden Key' evaluation.", + "enum": [ + "MATCH", + "NEUTRAL", + "HARD_MISMATCH" + ] + }, + "document_number_effect": { + "type": "string", + "description": "Human-readable description of the document number effect on the score." + }, + "total_score": { + "type": "integer", + "description": "Final weighted match score (0-100)." + } + } + }, + "pep_matches": { + "type": "array", + "items": { + "type": "object", + "properties": { + "aliases": { + "type": "array", + "items": { + "type": "string" + } + }, + "education": { + "type": "array", + "items": { + "type": "string" + } + }, + "list_name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "source_url": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + }, + "matched_name": { + "type": "string" + }, + "pep_position": { + "type": "string" + }, + "date_of_birth": { + "type": "string" + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + } + }, + "place_of_birth": { + "type": "string" + } + } + } + }, + "linked_entities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "array", + "items": { + "type": "string" + } + }, + "active": { + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "type": "array", + "items": { + "type": "string" + } + }, + "details": { + "type": "array", + "items": { + "type": "string" + } + }, + "relation": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "warning_matches": { + "type": "array", + "description": "List of warning and regulatory enforcement matches. Includes entries from categories: Warning, SIP, Warnings and Regulatory Enforcement, Fitness and Probity, SIE, and Insolvency.", + "items": { + "type": "object", + "properties": { + "list_name": { + "type": "string", + "description": "Name of the warning list publisher" + }, + "matched_name": { + "type": "string", + "description": "Name matched in the warning list" + }, + "description": { + "type": "string", + "description": "Description of the warning entry" + }, + "publisher": { + "type": "string", + "description": "Publisher or source of the warning" + }, + "source_url": { + "type": "string", + "format": "uri", + "description": "URL to the source of the warning" + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Additional source URLs" + }, + "countries": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Countries associated with the warning" + }, + "created_at": { + "type": "string", + "nullable": true, + "description": "Date the warning entry was created" + }, + "updated_at": { + "type": "string", + "nullable": true, + "description": "Date the warning entry was last updated" + }, + "additional_data": { + "type": "object", + "description": "Additional data from the warning source. May contain a `data` object with `case_details` array.", + "properties": { + "data": { + "type": "object", + "properties": { + "case_details": { + "type": "array", + "description": "Details of warning cases associated with this match", + "items": { + "type": "object", + "properties": { + "case_type": { + "type": "string", + "description": "Type of the warning case", + "enum": [ + "Accusation", + "Arrest", + "Bribery", + "Criminal", + "Fraud", + "Illegal Fundraising", + "Non-conviction Terror", + "Proclaimed Offender", + "Regulatory Enforcement", + "Terrorism", + "Terrorism-related", + "Terrorism-related listing" + ] + } + } + } + } + } + } + } + } + } + } + }, + "sanction_matches": { + "type": "array", + "description": "List of sanction list matches found for this entity.", + "items": { + "type": "object", + "properties": { + "list_name": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Name(s) of the sanction list(s)" + }, + "matched_name": { + "type": "string", + "description": "Name matched in the sanction list" + }, + "description": { + "type": "string", + "description": "Description of the sanction entry" + }, + "reason": { + "type": "string", + "nullable": true, + "description": "Free-text reason why the entity was sanctioned. Joined from the sanction list's reason entries. Examples: 'Executive Order 13224 (Terrorism)', 'Support for actions undermining Ukraine's sovereignty', 'Listed due to national security concerns.'" + }, + "source_url": { + "type": "string", + "format": "uri", + "description": "URL to the source of the sanction" + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Additional source URLs" + }, + "details": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Additional details about the sanction" + }, + "legal_basis": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Legal basis for the sanction" + }, + "listed_on": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Date(s) when the entity was listed" + }, + "sanction_list": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Sanction list identifier(s)" + }, + "sanction_program": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Sanction program(s) the entity is listed under" + }, + "sanctioning_authority": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Authority/authorities that imposed the sanction" + }, + "updated_on": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Date(s) when the sanction entry was last updated" + }, + "additional_data": { + "type": "object", + "description": "Additional data from the sanction source not captured in the main fields" + } + } + } + }, + "adverse_media_details": { + "type": "object", + "properties": { + "sentiment": { + "type": "string" + }, + "entity_type": { + "type": "string" + }, + "sentiment_score": { + "type": "integer" + }, + "adverse_keywords": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + } + }, + "adverse_media_matches": { + "type": "array", + "items": { + "type": "object", + "properties": { + "country": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "headline": { + "type": "string" + }, + "sentiment": { + "type": "string" + }, + "thumbnail": { + "type": "string", + "format": "uri" + }, + "source_url": { + "type": "string", + "format": "uri" + }, + "author_name": { + "type": "string", + "nullable": true + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + } + }, + "adverse_keywords": { + "type": "array", + "items": { + "type": "string" + } + }, + "sentiment_score": { + "type": "integer" + }, + "publication_date": { + "type": "string", + "format": "date-time" + } + } + } + }, + "additional_information": { + "type": "object" + } + } + } + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Overall AML screening score — the highest risk score across all hits (float, 0-100). `null` until computed." + }, + "screened_data": { + "type": "object", + "properties": { + "full_name": { + "type": "string" + }, + "nationality": { + "type": "string" + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "document_number": { + "type": "string", + "nullable": true + } + } + }, + "is_ongoing_monitoring_enabled": { + "type": "boolean", + "description": "Whether AML ongoing monitoring is enabled for this screening. When `true`, the entity is re-screened periodically and any new hits update the report." + }, + "next_ongoing_monitoring_bill_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date (YYYY-MM-DD) when this ongoing-monitoring subscription will next be billed. Computed as the date of the last ongoing-monitoring billing plus 365 days when `is_ongoing_monitoring_enabled = true`; otherwise `null`. This is a billing date, not a screening date - use `last_ongoing_screening_at` for that." + }, + "last_ongoing_screening_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When ongoing monitoring last completed a re-screening of this record, whether or not the result changed. `null` when the record has never been re-screened; it is never back-filled from the enablement or billing date." + }, + "last_ongoing_screening_datasets_updated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "The most recent AML dataset update date the provider reported at the time of that screening. `null` when the provider reported no dataset dates; never estimated." + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "NFC", + "LIVENESS", + "FACEMATCH", + "PROOF_OF_ADDRESS", + "PHONE", + "EMAIL", + "AML", + "LOCATION", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE" + ], + "description": "The feature that generated this warning. The value identifies the feature block the warning belongs to. Within an AML screening this is typically `AML`, but the enum above is authoritative across all feature warnings on the decision payload." + }, + "risk": { + "type": "string" + }, + "additional_data": { + "type": "object", + "nullable": true + }, + "log_type": { + "type": "string" + }, + "short_description": { + "type": "string" + }, + "long_description": { + "type": "string" + }, + "node_id": { + "type": "string", + "description": "The workflow graph node ID that generated this warning" + } + } + } + } + } + }, + "MonitoringEvent": { + "type": "object", + "description": "One stored monitoring event as returned by the list and subject-timeline endpoints. Restricted evidence (payload, device context, rule evidence) is only returned by `GET /v3/events/{event_uuid}/`.", + "required": [ + "event_uuid", + "event_id", + "event_category", + "action_type", + "occurred_at", + "received_at", + "status", + "score", + "subject_vendor_data", + "subject_entity_type", + "subject_uuid", + "counterparty_vendor_data", + "session_id", + "device_fingerprint", + "ip_address", + "ip_country", + "amount", + "currency", + "game_id", + "round_id", + "bonus_id", + "affiliate_code", + "payment_reference_id", + "retention_class", + "rules_matched_count", + "correlation_pending", + "is_droppable" + ], + "properties": { + "event_uuid": { + "type": "string", + "format": "uuid", + "description": "Didit identifier of the event. Use it as `{event_uuid}` in the retrieve and delete calls." + }, + "event_id": { + "type": "string", + "maxLength": 128, + "description": "Your own identifier, the idempotency key you sent." + }, + "event_category": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ], + "description": "Event category." + }, + "action_type": { + "type": "string", + "maxLength": 128, + "description": "What happened within the category, as you sent it." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "description": "When the event happened on your side." + }, + "received_at": { + "type": "string", + "format": "date-time", + "description": "When Didit accepted the event." + }, + "status": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ], + "description": "`ACCEPTED`: stored, no matched rule changed its standing (the normal answer for most events, and always the answer in a sandbox application, where no rules run). `APPROVED`: a rule matched and the score stayed under your review threshold. `IN_REVIEW`: a rule pushed the score over the review threshold or set the status. `DECLINED`: a rule declined it or the score crossed your decline threshold." + }, + "score": { + "type": "integer", + "description": "Accumulated rule score. Starts at 0; rule `add_score` actions move it (negative deltas are allowed)." + }, + "subject_vendor_data": { + "type": "string", + "maxLength": 255, + "description": "`subject.vendor_data` of the event: your id for the user or company." + }, + "subject_entity_type": { + "type": "string", + "description": "`individual` or `company`: which kind of subject `subject_vendor_data` names.", + "example": "individual" + }, + "subject_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "UUID of the user (individual) or company the event resolved to, or null when that record no longer exists." + }, + "counterparty_vendor_data": { + "type": "string", + "nullable": true, + "description": "The other player of a P2P event (promoted from `payload.counterparty_vendor_data`).", + "maxLength": 255 + }, + "session_id": { + "type": "string", + "nullable": true, + "description": "`session.session_id` sent with the event.", + "maxLength": 255 + }, + "device_fingerprint": { + "type": "string", + "nullable": true, + "description": "`device_context.fingerprint` sent with the event.", + "maxLength": 255 + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "IP address of the event. On the server lane this is `device_context.network_context.ip_address` as you sent it; on the device lane it is the IP of the request.", + "maxLength": 64 + }, + "ip_country": { + "type": "string", + "nullable": true, + "description": "Country code of `ip_address` from IP enrichment, or null when the address was not enriched (missing, private, or low-value telemetry whose address was not already known).", + "example": "ES" + }, + "amount": { + "type": "string", + "nullable": true, + "description": "Amount as a decimal string with 18 decimal places, from `transaction_context.amount`, `payload.amount` or `payload.stake_amount`, in that order.", + "example": "250.000000000000000000" + }, + "currency": { + "type": "string", + "nullable": true, + "description": "Currency from `transaction_context.currency` or `payload.currency`.", + "maxLength": 10 + }, + "game_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.game_id`.", + "maxLength": 128 + }, + "round_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.round_id`.", + "maxLength": 128 + }, + "bonus_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.bonus_id`.", + "maxLength": 128 + }, + "affiliate_code": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.affiliate_code`.", + "maxLength": 128 + }, + "payment_reference_id": { + "type": "string", + "nullable": true, + "description": "Payment reference from `transaction_context.payment_reference_id` or `payload.payment_reference_id`.", + "maxLength": 255 + }, + "retention_class": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ], + "description": "Retention class. Payment and dispute events are `monetary_or_compliance`; security and responsible-gambling events are `risk_relevant`; `session.page_view`, `session.sdk_heartbeat`, `session.sdk_loaded`, `session.device_seen`, `session.bot_signal_observed`, `bonus.promotion_viewed` and `affiliate.referral_clicked` are `low_value`; everything else is `standard`. An event an active rule matched is promoted to `risk_relevant` (never demoted)." + }, + "rules_matched_count": { + "type": "integer", + "minimum": 0, + "description": "Number of rules that matched the event, including rules in `TEST` mode (which record evidence but apply no actions)." + }, + "correlation_pending": { + "type": "boolean", + "description": "`true` while an asynchronous rule (cross-subject correlation, or a sequence longer than 24 hours) still has to run for this event. It resolves within minutes and `status`, `score` and `rules_matched_count` update in place." + }, + "is_droppable": { + "type": "boolean", + "description": "Whether an SDK under backpressure may drop events of this kind. `false` for every `security`, `payment`, `dispute` and `responsible_gambling` event and for `gameplay.bet_placed`, `gameplay.bet_settled` and `gameplay.p2p_settlement`." + } + } + }, + "MonitoringEventDetail": { + "description": "One monitoring event with its full envelope, enrichment and rule evidence. Every field of `MonitoringEvent` plus the fields below.", + "allOf": [ + { + "$ref": "#/components/schemas/MonitoringEvent" + }, + { + "type": "object", + "required": [ + "payload", + "custom_values", + "device_context", + "field_sources", + "session_sequence", + "table_id", + "hand_id", + "match_id", + "market_id", + "side", + "role", + "net_result_amount", + "payment_fingerprint", + "instrument_reference_hash", + "withdrawal_address_hash", + "dispute_id", + "chargeback_id", + "changed_field", + "identity_match_ref", + "schema_version", + "expires_at", + "rules_evaluated_count", + "rule_runs", + "ip_enrichment", + "subject_profile", + "tags" + ], + "properties": { + "payload": { + "type": "object", + "additionalProperties": true, + "description": "The category fields you sent in `payload`." + }, + "custom_values": { + "type": "object", + "additionalProperties": true, + "description": "The scalar `custom_values` you sent. Didit adds `vpn_or_proxy_detected: true` or `is_data_center: true` when IP enrichment flags the address and you did not set them." + }, + "device_context": { + "type": "object", + "additionalProperties": true, + "description": "The device context as stored: your `device_context` (including `fingerprint`, `persistent_device_id` and `bot_score` when sent) with `network_context` completed by IP enrichment (`ip_country`, `ip_country_code`, `ip_city`, `asn_number`, `asn_organization`, `is_vpn_or_tor`, `is_data_center`, `proxy_type`, `connection_type`) for keys you left empty." + }, + "field_sources": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "customer", + "sdk", + "server" + ] + }, + "description": "Field path to provenance: `customer` (sent by your backend), `sdk` (sent through the device lane) or `server` (derived by Didit, such as `received_at` and enriched network fields). Check it before acting on a match." + }, + "session_sequence": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "`session.sequence` sent with the event." + }, + "table_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.table_id`.", + "maxLength": 128 + }, + "hand_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.hand_id`.", + "maxLength": 128 + }, + "match_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.match_id`.", + "maxLength": 128 + }, + "market_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.market_id`.", + "maxLength": 128 + }, + "side": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.side`.", + "maxLength": 64 + }, + "role": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.role`.", + "maxLength": 64 + }, + "net_result_amount": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.net_result_amount`, as a decimal string with 18 decimal places.", + "example": "-120.000000000000000000" + }, + "payment_fingerprint": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.payment_fingerprint`.", + "maxLength": 255 + }, + "instrument_reference_hash": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.instrument_reference_hash`.", + "maxLength": 255 + }, + "withdrawal_address_hash": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.withdrawal_address_hash` (or `payload.destination_reference_hash`).", + "maxLength": 255 + }, + "dispute_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.dispute_id`.", + "maxLength": 255 + }, + "chargeback_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.chargeback_id`.", + "maxLength": 255 + }, + "changed_field": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.changed_field` (security change events).", + "maxLength": 128 + }, + "identity_match_ref": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.identity_match_ref` (responsible-gambling registry matches).", + "maxLength": 255 + }, + "schema_version": { + "type": "integer", + "description": "Version of the event envelope schema. Currently `1`.", + "example": 1 + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the event expires under its retention class and is deleted: `occurred_at` plus the class period (14, 30 or 90 days by default, adjustable per application). Null for `monetary_or_compliance` events, which follow transaction retention." + }, + "rules_evaluated_count": { + "type": "integer", + "minimum": 0, + "description": "Number of event rules evaluated for this event." + }, + "rule_runs": { + "type": "array", + "description": "Every rule that matched the event, newest first.", + "items": { + "$ref": "#/components/schemas/MonitoringEventRuleRun" + } + }, + "ip_enrichment": { + "type": "object", + "nullable": true, + "description": "Enrichment of the event's IP address, or null when the address was not enriched. See [IP enrichment](https://docs.didit.me/transaction-monitoring/ip-enrichment).", + "properties": { + "ip_country_code": { + "type": "string", + "nullable": true, + "description": "Country code of the address." + }, + "ip_city": { + "type": "string", + "nullable": true, + "description": "City of the address." + }, + "asn_number": { + "type": "integer", + "nullable": true, + "description": "Autonomous system number." + }, + "asn_organization": { + "type": "string", + "nullable": true, + "description": "Organization that operates the autonomous system." + }, + "is_vpn_or_tor": { + "type": "boolean", + "description": "Whether the address is a known VPN or Tor exit." + }, + "is_data_center": { + "type": "boolean", + "description": "Whether the address belongs to a hosting or data-centre network." + }, + "proxy_type": { + "type": "string", + "nullable": true, + "description": "Proxy classification of the address as an opaque string, or null." + }, + "last_enriched_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the address was last enriched." + } + }, + "required": [ + "ip_country_code", + "ip_city", + "asn_number", + "asn_organization", + "is_vpn_or_tor", + "is_data_center", + "proxy_type", + "last_enriched_at" + ] + }, + "subject_profile": { + "type": "object", + "nullable": true, + "description": "Cached email and phone risk signals of the subject, or null when no profile exists for the subject.", + "properties": { + "email_risk_score": { + "type": "integer", + "nullable": true, + "description": "Email risk score." + }, + "email_deliverability": { + "type": "string", + "nullable": true, + "description": "Email deliverability verdict." + }, + "email_is_disposable": { + "type": "boolean", + "nullable": true, + "description": "Whether the email address is disposable." + }, + "email_is_breached": { + "type": "boolean", + "nullable": true, + "description": "Whether the email address appears in known data breaches." + }, + "email_social_count": { + "type": "integer", + "nullable": true, + "description": "Number of online accounts linked to the email address." + }, + "email_social_providers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the online services where an account linked to the email address was found." + }, + "phone_risk_score": { + "type": "integer", + "nullable": true, + "description": "Phone risk score." + }, + "phone_line_type": { + "type": "string", + "nullable": true, + "description": "Phone line type." + }, + "phone_carrier": { + "type": "string", + "nullable": true, + "description": "Phone carrier name." + }, + "phone_social_count": { + "type": "integer", + "nullable": true, + "description": "Number of online accounts linked to the phone number." + }, + "phone_social_providers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the online services where an account linked to the phone number was found." + }, + "source_provider": { + "type": "string", + "nullable": true, + "description": "Opaque identifier of the source of the profile." + }, + "last_enriched_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the profile was last refreshed." + } + }, + "required": [ + "email_risk_score", + "email_deliverability", + "email_is_disposable", + "email_is_breached", + "email_social_count", + "email_social_providers", + "phone_risk_score", + "phone_line_type", + "phone_carrier", + "phone_social_count", + "phone_social_providers", + "source_provider", + "last_enriched_at" + ] + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the tags attached to the event (rule `add_tags` actions)." + } + } + } + ] + }, + "MonitoringEventRuleRun": { + "type": "object", + "properties": { + "rule_uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the transaction monitoring rule." + }, + "rule_title": { + "type": "string", + "description": "Title of the rule." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Library key when the rule was installed from the rules library, otherwise null." + }, + "matched": { + "type": "boolean", + "description": "Always `true`: only matching runs are recorded." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "TEST" + ], + "description": "Mode of the rule when it ran. A `TEST` run records evidence but applied no actions." + }, + "is_async": { + "type": "boolean", + "description": "`true` when the run came from the asynchronous correlation pass rather than the ingest request." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL", + null + ], + "description": "Severity of the rule. Null for `TEST` runs and for rules without a severity." + }, + "score_delta": { + "type": "integer", + "description": "How much this run changed the event's score (0 for `TEST` runs)." + }, + "action_summary": { + "type": "string", + "nullable": true, + "description": "Short summary of the actions applied, for example `score +40; add_tag:VPN_LOGIN`. Null when nothing was applied." + }, + "matched_payload": { + "type": "array", + "description": "The conditions and aggregations that matched. Condition entries carry `field`, `operator`, `expected_value`, `current_value`, `matched`, `group_index`, `group_logic` and `field_source` (`server`, `sdk` or `customer`); aggregation entries carry `metric`, `field`, `operator`, `expected_value`, `computed_value`, `window`, `filters`, `matched` and `sample_references`.", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the rule run was recorded." + } + }, + "required": [ + "rule_uuid", + "rule_title", + "library_key", + "matched", + "mode", + "is_async", + "severity", + "score_delta", + "action_summary", + "matched_payload", + "created_at" + ] + }, + "MonitoringEventPage": { + "type": "object", + "description": "One page of monitoring events, newest `occurred_at` first.", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Number of matching events, exact up to 100 and capped at 100 above that. Do not compute page counts from it; follow `next` until it is null." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or null on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or null on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MonitoringEvent" + } + } + } + }, + "MonitoringEventCreateRequest": { + "type": "object", + "description": "The event envelope, the same for every category. camelCase aliases are accepted: `eventId`, `eventCategory` (or `category`), `actionType`, `occurredAt`, `timeZone` (or `zoneId`), `deviceContext` (or `device`), `transactionContext`, `customValues` (or `props`); inside `subject`: `vendorData`, `entityType`, `fullName`; inside `session`: `sessionId`, `pageUrl`; inside `transaction_context`: `paymentReferenceId`.", + "required": [ + "event_id", + "event_category", + "action_type", + "occurred_at", + "subject" + ], + "properties": { + "event_id": { + "type": "string", + "maxLength": 128, + "description": "Your identifier for the event, unique per application. It is the idempotency key: resending the same `event_category`, `action_type`, `occurred_at`, `subject.vendor_data` and `payload` under it is a replay, anything different is a conflict.", + "example": "login-2026-10-17-000001" + }, + "event_category": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ], + "description": "Event category.", + "example": "auth" + }, + "action_type": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "What happened within the category. Open vocabulary; the managed actions listed in [Event ingestion](https://docs.didit.me/transaction-monitoring/events) (for example `login`, `deposit_completed`, `chargeback_opened`) unlock typed validation and managed rule fields. Leading and trailing whitespace is trimmed.", + "example": "login" + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "description": "When the event happened on your side, ISO 8601 (a value without an offset is read as UTC). Must be no more than 7 days in the past and no more than 5 minutes in the future.", + "example": "2026-10-17T09:15:02.481Z" + }, + "time_zone": { + "type": "string", + "nullable": true, + "maxLength": 64, + "description": "Time zone of the occurrence on your side, for example `Europe/Madrid`. Stored with the event; not returned by the read endpoints." + }, + "subject": { + "type": "object", + "description": "The user or company the event belongs to. It resolves to the user (or company) with this `vendor_data` in your application; one is created when none exists.", + "required": [ + "vendor_data" + ], + "properties": { + "vendor_data": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "Your id for the subject.", + "example": "user-42" + }, + "entity_type": { + "type": "string", + "maxLength": 32, + "default": "individual", + "description": "`company` resolves the subject to a company; any other value (default `individual`) resolves it to an individual. The same `vendor_data` can name one of each.", + "example": "individual" + }, + "full_name": { + "type": "string", + "nullable": true, + "maxLength": 512, + "description": "Display name, used only when this event creates the user or company." + }, + "email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Validated as an email address; not stored on the event." + }, + "phone": { + "type": "string", + "nullable": true, + "maxLength": 32, + "description": "Validated; not stored on the event." + }, + "country": { + "type": "string", + "nullable": true, + "maxLength": 3, + "description": "Validated; not stored on the event." + } + } + }, + "business": { + "type": "object", + "description": "The business the event belongs to, when it differs from the subject. Only `vendor_data` is read.", + "additionalProperties": true, + "properties": { + "vendor_data": { + "type": "string", + "maxLength": 255, + "description": "Your id for the business. Filterable as `business_vendor_data`.", + "example": "merchant-7" + } + } + }, + "session": { + "type": "object", + "description": "Your session context for the event.", + "properties": { + "session_id": { + "type": "string", + "nullable": true, + "maxLength": 255, + "description": "Your session identifier. Filterable and returned as `session_id`." + }, + "sequence": { + "type": "integer", + "nullable": true, + "description": "Position of the event within the session. Returned as `session_sequence`." + }, + "page_url": { + "type": "string", + "nullable": true, + "maxLength": 2048, + "description": "Validated; not stored on the event." + }, + "referrer": { + "type": "string", + "nullable": true, + "maxLength": 2048, + "description": "Validated; not stored on the event." + } + } + }, + "device_context": { + "type": "object", + "additionalProperties": true, + "description": "Device and network context, stored as sent and returned by `GET /v3/events/{event_uuid}/`. On this server lane Didit does not see the end user's connection, so the IP enriched and used by rules is `network_context.ip_address` as you send it.", + "properties": { + "fingerprint": { + "type": "string", + "maxLength": 255, + "description": "Device fingerprint. Filterable as `device_fingerprint`." + }, + "persistent_device_id": { + "type": "string", + "maxLength": 255, + "description": "Your persistent device identifier." + }, + "bot_score": { + "type": "integer", + "description": "Bot score from your own detection, as an integer." + }, + "network_context": { + "type": "object", + "additionalProperties": true, + "properties": { + "ip_address": { + "type": "string", + "maxLength": 64, + "description": "End user's IP address. Public addresses are enriched once per unique address.", + "example": "203.0.113.42" + } + } + } + } + }, + "transaction_context": { + "type": "object", + "description": "Money context. Non-empty values here also satisfy the required payload keys of the action.", + "properties": { + "amount": { + "type": "string", + "nullable": true, + "description": "Amount as a decimal string (a number is also accepted).", + "example": "1000.00" + }, + "currency": { + "type": "string", + "nullable": true, + "maxLength": 10, + "description": "Currency code." + }, + "direction": { + "type": "string", + "nullable": true, + "maxLength": 16, + "description": "Direction of the money movement, for example `inbound` or `outbound`." + }, + "payment_reference_id": { + "type": "string", + "nullable": true, + "maxLength": 255, + "description": "Your payment reference. Filterable as `payment_reference_id`." + } + } + }, + "payload": { + "type": "object", + "additionalProperties": true, + "description": "The category's domain fields, at most 16384 bytes once JSON-encoded. Some actions require keys (in `payload` or `transaction_context`): `gameplay.p2p_settlement` needs `game_id`, `counterparty_vendor_data`, `stake_amount`, `net_result_amount`, `role` and one of `hand_id`, `round_id`, `match_id`; `gameplay.bet_placed` and `gameplay.bet_settled` need `game_id`; `payment.deposit_initiated`, `payment.deposit_completed`, `payment.withdrawal_requested` and `payment.withdrawal_completed` need `payment_reference_id`, `amount`, `currency`, `payment_method`, `status`; `payment.withdrawal_address_changed` needs `withdrawal_address_hash`; `dispute.chargeback_opened`, `dispute.chargeback_won` and `dispute.chargeback_lost` need `payment_reference_id` and one of `dispute_id`, `chargeback_id`; `session.bot_signal_observed` needs `telemetry_schema_version` and one of `bot_score`, `actions_per_minute`, `timing_variance_ms`, `pointer_entropy`, `keystroke_entropy`, `reaction_time_ms_p50`; `responsible_gambling.self_exclusion_match` needs `registry_source`, `exclusion_status`, `identity_match_ref`. Every `security` action ending in `_changed` needs `changed_field`, plus `previous_reference_hash` or `new_reference_hash` when the field is `password`, `mfa_method`, `mfa`, `email`, `phone`, `withdrawal_address`, `payout_method` or `api_key`. Send hashes and references, never a card number, a full withdrawal address, a password hash or an MFA secret." + }, + "custom_values": { + "type": "object", + "description": "Up to 100 keys of your own, addressable from rules as `custom_values.`. Values must be scalars (string, number, boolean or null); nested objects and arrays are rejected.", + "additionalProperties": { + "nullable": true, + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ] + } + } + }, + "example": { + "event_id": "login-2026-10-17-000001", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-17T09:15:02.481Z", + "time_zone": "Europe/Madrid", + "subject": { + "vendor_data": "user-42", + "entity_type": "individual" + }, + "session": { + "session_id": "web-session-abc", + "sequence": 142 + }, + "device_context": { + "fingerprint": "fp-a8c1", + "network_context": { + "ip_address": "203.0.113.42" + } + }, + "payload": { + "method": "password" + }, + "custom_values": { + "vip_tier": "gold" + } + } + }, + "MonitoringEventIngestResult": { + "type": "object", + "description": "Outcome of ingesting one event. The same shape is returned for a new event and for an idempotent replay.", + "required": [ + "event_uuid", + "event_id", + "status", + "score", + "rules_evaluated_count", + "rules_matched_count", + "correlation_pending", + "retention_class", + "expires_at", + "idempotent_replay", + "rule_matches" + ], + "properties": { + "event_uuid": { + "type": "string", + "format": "uuid", + "description": "Didit identifier of the stored event." + }, + "event_id": { + "type": "string", + "description": "Your `event_id`." + }, + "status": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ], + "description": "Status after the real-time rules ran. See `MonitoringEvent.status`." + }, + "score": { + "type": "integer", + "description": "Score after the real-time rules ran." + }, + "rules_evaluated_count": { + "type": "integer", + "minimum": 0, + "description": "Number of event rules evaluated in the request. 0 in a sandbox application, where no rules run." + }, + "rules_matched_count": { + "type": "integer", + "minimum": 0, + "description": "Number of rules that matched, including `TEST` mode rules." + }, + "correlation_pending": { + "type": "boolean", + "description": "`true` when an asynchronous rule still has to run. Read `GET /v3/events/{event_uuid}/` a few minutes later for the final `status` and `score`." + }, + "retention_class": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ], + "description": "Retention class, after any promotion by a matched rule." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the event expires under its retention class and is deleted. Null for `monetary_or_compliance` (payment and dispute) events." + }, + "idempotent_replay": { + "type": "boolean", + "description": "`true` when an event with this `event_id` and the same content was already stored; the values are those of the stored event and no rule ran again." + }, + "rule_matches": { + "type": "array", + "description": "Rules that matched the event.", + "items": { + "type": "object", + "required": [ + "rule_id", + "library_key", + "mode", + "is_async" + ], + "properties": { + "rule_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the rule." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Library key when the rule was installed from the rules library, otherwise null." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "TEST" + ], + "description": "`TEST` rules record the match but apply no actions." + }, + "is_async": { + "type": "boolean", + "description": "`true` when the match came from the asynchronous pass." + } + } + } + } + } + }, + "ComplianceList": { + "type": "object", + "description": "A blocklist, allowlist or custom list of one application.", + "required": [ + "uuid", + "name", + "description", + "list_type", + "entry_type", + "is_system", + "entry_count", + "last_entry_added_at", + "created_at", + "updated_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "List identifier. Use it as `list_uuid` in the list and entry endpoints, and as the value of a workflow or transaction rule condition with `value_type: \"list\"`." + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "List name, unique among the application's lists." + }, + "description": { + "type": "string", + "description": "Free-text description. Empty string when not set." + }, + "list_type": { + "type": "string", + "enum": [ + "blocklist", + "allowlist", + "custom" + ], + "description": "`blocklist`: a match declines the session or transaction (system lists only, one per entry type). `allowlist`: matching items are trusted, for example a face allowlist skips the duplicated-face action; never overrides a blocklist match. `custom`: used in workflow and transaction rule conditions (`value_type: \"list\"`)." + }, + "entry_type": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email", + "ip_address", + "device_fingerprint", + "wallet_address", + "bank_account", + "country", + "user", + "business", + "key" + ], + "description": "What the list holds: `face`, `document` (document number), `phone`, `email`, `ip_address` (IP address or CIDR range), `device_fingerprint`, `wallet_address`, `bank_account`, `country` (ISO 3166-1 alpha-3), `user` or `business` (your `vendor_data`), or `key` (any string). It decides how entry values are validated and matched." + }, + "is_system": { + "type": "boolean", + "description": "`true` for the lists Didit provisions: one blocklist per entry type (`face`, `document`, `phone`, `email`, `ip_address`, `device_fingerprint`, `wallet_address`, `bank_account`, `user`, `business`), the **High Risk Countries** custom list, and system allowlists. System lists cannot be renamed, edited or deleted; you can add and delete their entries." + }, + "entry_count": { + "type": "integer", + "minimum": 0, + "description": "Number of entries currently in the list." + }, + "last_entry_added_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the newest current entry was added. `null` for an empty list." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the list was created." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When the list was last updated." + } + } + }, + "ComplianceListEntry": { + "type": "object", + "description": "One value in a list.", + "required": [ + "uuid", + "value", + "display_label", + "comment", + "added_by", + "reference_session_id", + "reference_session_number", + "reference_session_type", + "reference_object_uuid", + "metadata", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Entry identifier. Use it as `entry_uuid` in Delete a list entry." + }, + "value": { + "type": "string", + "description": "The listed value: a phone number in E.164, an email address, a document number, an IP address or CIDR range, a device fingerprint, a wallet address, a bank account, an ISO 3166-1 alpha-3 country code, a `vendor_data`, any string on a `key` list, or the UUID of the stored face on a face list. Values you send are stored in lower case, except phone numbers and country codes. Matching is case-insensitive." + }, + "display_label": { + "type": "string", + "nullable": true, + "maxLength": 512, + "description": "Human-readable label. Didit fills it for entries taken from a session (for example the holder's name and document number) when you send none." + }, + "comment": { + "type": "string", + "description": "Free-text note. Empty string when not set." + }, + "added_by": { + "type": "string", + "nullable": true, + "description": "Who added the entry: `api` (an API key), a console user's email address or `console`, `system` or `migration` (entries Didit created), or `rule` (a transaction or monitoring rule action)." + }, + "reference_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Verification session the entry was taken from. `null` when the entry was added by value." + }, + "reference_session_number": { + "type": "integer", + "nullable": true, + "description": "Application-scoped number of that session, as shown in the console." + }, + "reference_session_type": { + "type": "string", + "nullable": true, + "enum": [ + "kyc", + "kyb", + null + ], + "description": "`kyc` for a person verification session, `kyb` for a business verification session, `null` when there is no session." + }, + "reference_object_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The record the entry stands for: the face, document, phone or email record read from a session, the face created by Upload a face to a list, or the UUID you sent (for example a transaction UUID or a user's `didit_internal_id`)." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Extra context. Holds the keys Didit sets (listed here) merged with any `metadata` you sent when creating the entry, returned as stored. `null` when there is none.", + "properties": { + "reference_image_url": { + "type": "string", + "description": "Face entries: short-lived signed URL, issued again on every call; `null` for a scoped API key without `media: read`. Absent when no image belongs to the entry." + }, + "front_image_url": { + "type": "string", + "description": "Document entries that carry a document front image: short-lived signed URL, issued again on every call; `null` for a scoped API key without `media: read`." + }, + "full_name": { + "type": "string", + "description": "Document entries taken from a session: holder's full name." + }, + "issuing_state": { + "type": "string", + "description": "Document entries taken from a session: ISO 3166-1 alpha-3 issuing country." + }, + "document_type": { + "type": "string", + "description": "Document entries taken from a session: document type code, for example `P`, `ID` or `DL`." + }, + "region": { + "type": "string", + "description": "Document entries taken from a session: issuing state or province of a regionally issued document, empty when not applicable. Not set by Add session items to the blocklist." + }, + "document_number": { + "type": "string", + "description": "Document entries taken from a session: the document number." + }, + "face_image_source": { + "type": "string", + "enum": [ + "front_image_camera_front", + "back_image_camera_front" + ], + "description": "Face entries taken from a document-capture selfie: which selfie. Absent for liveness faces." + }, + "reference_node_id": { + "type": "string", + "nullable": true, + "description": "Face entries taken from a document-capture selfie: the ID verification node the selfie came from." + }, + "fingerprint_tier": { + "type": "string", + "nullable": true, + "enum": [ + "persistent_id", + "composite_hash", + "legacy_fp", + "unknown", + null + ], + "description": "Device fingerprint entries taken from a session: how the fingerprint was derived. `persistent_id` identifies one device; `composite_hash` and `legacy_fp` can be shared by devices of the same model." + }, + "sandbox": { + "type": "boolean", + "description": "`true` on a face entry uploaded in a sandbox application; such an entry matches nothing." + }, + "reference_type": { + "type": "string", + "enum": [ + "transaction", + "vendor_user", + "vendor_business" + ], + "description": "Set by you: what `reference_object_uuid` points to, so the console links back to it." + }, + "reference_id": { + "type": "string", + "description": "Set by you: identifier of the source record." + } + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the entry was added." + } + } + }, + "BlocklistedItem": { + "type": "object", + "description": "One blocklisted face, identity document, phone number or email address of a verification session. Fields that do not apply to the item's `type` are `null`.", + "required": [ + "type", + "session_id", + "session_number", + "blocklisted_at", + "face_image", + "document_type", + "document_number", + "front_image", + "phone_number", + "country_code", + "email" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email" + ], + "description": "What was blocklisted." + }, + "session_id": { + "type": "string", + "format": "uuid", + "description": "Verification session the item belongs to." + }, + "session_number": { + "type": "integer", + "nullable": true, + "description": "Application-scoped number of that session, as shown in the console." + }, + "blocklisted_at": { + "type": "string", + "format": "date-time", + "description": "When the item was last blocklisted." + }, + "face_image": { + "type": "string", + "nullable": true, + "description": "Face items: short-lived signed URL, issued again on every call; `null` for a scoped API key without `media: read`. Also `null` when no reference image is stored." + }, + "document_type": { + "type": "string", + "nullable": true, + "description": "Document items: document type code, for example `P` (passport), `ID` (national ID), `DL` (driver's license) or `RP` (residence permit)." + }, + "document_number": { + "type": "string", + "nullable": true, + "description": "Document items: the document number, or the personal number when the document has none." + }, + "front_image": { + "type": "string", + "nullable": true, + "description": "Document items: front image of the document, short-lived signed URL, issued again on every call; `null` for a scoped API key without `media: read`." + }, + "phone_number": { + "type": "string", + "nullable": true, + "description": "Phone items: the phone number in E.164." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "Phone items: ISO 3166-1 alpha-2 country of the number." + }, + "email": { + "type": "string", + "nullable": true, + "description": "Email items: the email address." + } + } + }, + "TransactionNote": { + "type": "object", + "description": "One note on a monitored transaction's review timeline.", + "required": [ + "uuid", + "note", + "actor_email", + "actor_name", + "metadata", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Note identifier." + }, + "note": { + "type": "string", + "description": "Plain-text note body." + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email of the Console user who wrote the note or changed the status. `null` for notes created with an API key and for notes Didit adds automatically." + }, + "actor_name": { + "type": "string", + "nullable": true, + "description": "Display name of the Console user (their email when they have no name). `Rule Engine` on notes Didit adds automatically. `null` for notes created with an API key." + }, + "metadata": { + "type": "object", + "description": "Tells the note types apart. Notes written through the API or the Console carry `mentioned_emails`. A status change made in the Console carries `type: status_change` with `previous_status`, `new_status`, `reason_code` and `reason_label`. Notes Didit adds carry `source`: `rule_action` (with `rule_uuid`) for a rule's `add_note` action, or `remediation_session` for a problem creating the user-action verification session.", + "properties": { + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + }, + "description": "Mentioned organization members, lower-cased and de-duplicated. Empty when nobody was mentioned." + }, + "type": { + "type": "string", + "enum": [ + "status_change" + ], + "description": "Present on notes recording a status change made in the Console." + }, + "previous_status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Status before the change." + }, + "new_status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Status after the change." + }, + "reason_code": { + "type": "string", + "nullable": true, + "description": "Reason code given with the change." + }, + "reason_label": { + "type": "string", + "nullable": true, + "description": "Reason label given with the change." + }, + "source": { + "type": "string", + "enum": [ + "rule_action", + "remediation_session" + ], + "description": "Present on notes Didit adds automatically." + }, + "rule_uuid": { + "type": "string", + "format": "uuid", + "description": "Rule whose `add_note` action wrote the note (with `source: rule_action`)." + } + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the note was stored." + } + } + }, + "TransactionSummary": { + "type": "object", + "description": "Summary of a monitored transaction, the shape `GET /v3/transactions/` lists. Call `GET /v3/transactions/{transaction_id}/` with `uuid` for parties, alerts and rule runs.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit transaction UUID. Use it as `{transaction_id}` on the transaction endpoints." + }, + "transaction_number": { + "type": "integer", + "nullable": true, + "description": "Application-scoped sequential number shown in the Console." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "Your own identifier from the create call. Unique per application." + }, + "transaction_type": { + "type": "string", + "description": "Category as stored: `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet`, `gamblingLimitChange`, `gamblingBonusChange` or `auditTrailEvent`." + }, + "action_type": { + "type": "string", + "description": "Sub-type within the category, for example `withdrawal`." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Current monitoring verdict." + }, + "source": { + "type": "string", + "enum": [ + "LIVE", + "IMPORTED" + ], + "description": "`LIVE` for transactions submitted for monitoring, `IMPORTED` for historical imports." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject." + }, + "amount": { + "type": "string", + "description": "Amount as a decimal string with trailing zeros removed (up to 18 decimal places)." + }, + "currency": { + "type": "string", + "description": "Currency code or crypto asset symbol of `amount`." + }, + "currency_type": { + "type": "string", + "nullable": true, + "description": "`fiat` or `crypto` when known." + }, + "preferred_currency_amount": { + "type": "string", + "nullable": true, + "description": "`amount` converted to the application's preferred currency, when a rate was available." + }, + "preferred_currency_code": { + "type": "string", + "nullable": true, + "description": "Currency of `preferred_currency_amount`." + }, + "payment_details": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference." + }, + "txn_date": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When Didit recorded it." + }, + "score": { + "type": "integer", + "description": "Risk score. Higher is riskier." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL", + null + ], + "description": "Risk severity, `null` until something sets it." + }, + "decision_reason_code": { + "type": "string", + "nullable": true, + "description": "Machine-readable reason for the current `status`." + }, + "decision_reason_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label for `decision_reason_code`." + }, + "tags": { + "type": "array", + "description": "Tags on the transaction.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string", + "description": "Hex color, for example `#F59E0B`." + }, + "description": { + "type": "string", + "nullable": true + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ], + "description": "Didit preset or custom tag." + } + } + } + }, + "subject_name": { + "type": "string", + "nullable": true, + "description": "Full name of the `APPLICANT` party." + }, + "subject_vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the applicant." + }, + "subject_type": { + "type": "string", + "nullable": true, + "enum": [ + "user", + "business", + null + ], + "description": "Whether the applicant is linked to a Didit user or business; `null` when it is linked to neither." + }, + "subject_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "UUID of the Didit user or business the applicant is linked to." + }, + "subject_country": { + "type": "string", + "nullable": true, + "description": "Country code of the applicant." + }, + "counterparty_name": { + "type": "string", + "nullable": true, + "description": "Full name of the `COUNTERPARTY` party, if any." + }, + "counterparty_vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the counterparty." + }, + "counterparty_country": { + "type": "string", + "nullable": true, + "description": "Country code of the counterparty." + }, + "assigned_to_name": { + "type": "string", + "nullable": true, + "description": "Name of the Console reviewer assigned to the transaction." + }, + "travel_rule_status": { + "type": "string", + "nullable": true, + "description": "Status of the transaction's Travel Rule transfer, or `null` when it has none." + }, + "application_id": { + "type": "string", + "format": "uuid", + "description": "Application that owns the transaction." + }, + "application_name": { + "type": "string", + "nullable": true, + "description": "Name of that application." + }, + "organization_id": { + "type": "string", + "format": "uuid", + "description": "Organization that owns the application." + }, + "organization_name": { + "type": "string", + "nullable": true, + "description": "Name of that organization." + } + } + }, + "ApplicationAnalyticsDataPoint": { + "type": "object", + "description": "Session counts for one day, by creation date (UTC). Each count reflects the session's current status. Points of the previous period carry at least the required fields.", + "required": [ + "rounded_date", + "total", + "approved", + "declined", + "canceled", + "pending" + ], + "properties": { + "rounded_date": { + "type": "string", + "format": "date", + "description": "The day, `YYYY-MM-DD`." + }, + "total": { + "type": "integer", + "description": "Sessions created that day." + }, + "approved": { + "type": "integer", + "description": "Of those, sessions `Approved` or `Kyc Expired`." + }, + "declined": { + "type": "integer", + "description": "Sessions `Declined`." + }, + "canceled": { + "type": "integer", + "description": "Sessions `Expired` or `Abandoned`." + }, + "pending": { + "type": "integer", + "description": "Sessions `Not Started`, `In Progress` or `In Review`." + }, + "in_review": { + "type": "integer", + "description": "Sessions `In Review`." + }, + "expired": { + "type": "integer", + "description": "Sessions `Expired`." + }, + "abandoned": { + "type": "integer", + "description": "Sessions `Abandoned`." + }, + "in_progress": { + "type": "integer", + "description": "Sessions `In Progress`." + }, + "not_started": { + "type": "integer", + "description": "Sessions `Not Started`." + }, + "manually_approved": { + "type": "integer", + "description": "Sessions a reviewer approved after the workflow gave another verdict." + }, + "manually_declined": { + "type": "integer", + "description": "Sessions a reviewer declined after the workflow gave another verdict." + } + } + }, + "ApplicationAnalyticsRequestBreakdown": { + "type": "object", + "description": "Status totals for the range. The previous-period breakdown carries at least the required fields.", + "required": [ + "total_approved", + "total_in_review", + "total_declined", + "total_canceled", + "total_pending" + ], + "properties": { + "total_approved": { + "type": "integer", + "description": "Sessions `Approved` or `Kyc Expired`." + }, + "total_in_review": { + "type": "integer", + "description": "Sessions `In Review`." + }, + "total_declined": { + "type": "integer", + "description": "Sessions `Declined`." + }, + "total_canceled": { + "type": "integer", + "description": "Sessions `Expired` or `Abandoned`." + }, + "total_pending": { + "type": "integer", + "description": "Sessions `Not Started`, `In Progress` or `In Review` (includes `total_in_review`)." + }, + "total_expired": { + "type": "integer", + "description": "Sessions `Expired`." + }, + "total_abandoned": { + "type": "integer", + "description": "Sessions `Abandoned`." + }, + "total_in_progress": { + "type": "integer", + "description": "Sessions `In Progress`." + }, + "total_not_started": { + "type": "integer", + "description": "Sessions `Not Started`." + }, + "total_manually_approved": { + "type": "integer", + "description": "Approved by a reviewer." + }, + "total_manually_declined": { + "type": "integer", + "description": "Declined by a reviewer." + } + } + }, + "ApplicationAnalyticsCountryCount": { + "type": "object", + "properties": { + "country": { + "type": "string", + "description": "Country name." + }, + "country_code": { + "type": "string", + "description": "Two-letter country code." + }, + "session_count": { + "type": "integer", + "description": "Sessions from that country." + } + } + }, + "ApplicationAnalyticsLocationStats": { + "type": "object", + "properties": { + "ip_countries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsCountryCount" + }, + "description": "Countries of the users' IP addresses, most sessions first." + }, + "countries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsCountryCount" + }, + "description": "Same list as `ip_countries`." + }, + "id_countries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsCountryCount" + }, + "description": "Issuing countries of the verified ID documents, most sessions first." + }, + "conversion_by_country": { + "type": "array", + "description": "Per document issuing country: finished sessions (approved, declined or in review) and how many were approved. Most finished first.", + "items": { + "type": "object", + "properties": { + "country": { + "type": "string" + }, + "country_code": { + "type": "string" + }, + "finished": { + "type": "integer" + }, + "approved": { + "type": "integer" + } + } + } + }, + "cities": { + "type": "array", + "description": "Up to 100 IP cities, most sessions first.", + "items": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "`City, Country`." + }, + "session_count": { + "type": "integer" + }, + "latitude": { + "type": "number", + "nullable": true + }, + "longitude": { + "type": "number", + "nullable": true + } + } + } + } + } + }, + "ApplicationAnalyticsDemographicStats": { + "type": "object", + "properties": { + "age_distribution": { + "type": "object", + "description": "Sessions per age band from the ID document. Empty when there is no data.", + "properties": { + "under_18": { + "type": "integer" + }, + "18_24": { + "type": "integer" + }, + "25_34": { + "type": "integer" + }, + "35_44": { + "type": "integer" + }, + "45_64": { + "type": "integer" + }, + "65_plus": { + "type": "integer" + } + }, + "additionalProperties": { + "type": "integer" + } + }, + "gender_distribution": { + "type": "object", + "description": "Sessions per document gender code: `M`, `F` or `U` (unknown).", + "additionalProperties": { + "type": "integer" + } + } + } + }, + "ApplicationAnalyticsDeviceDetails": { + "type": "object", + "description": "Top five of each, most sessions first.", + "properties": { + "device_models": { + "type": "array", + "description": "Device brands: `Apple`, `Android`, `LG`, everything else grouped as `Other`.", + "items": { + "type": "object", + "properties": { + "device": { + "type": "string" + }, + "count": { + "type": "integer" + } + } + } + }, + "browsers": { + "type": "array", + "description": "Browsers: `Chrome`, `Safari`, `Didit App`, `Firefox`, everything else grouped as `Other`.", + "items": { + "type": "object", + "properties": { + "browser": { + "type": "string" + }, + "count": { + "type": "integer" + } + } + } + }, + "operating_systems": { + "type": "array", + "description": "Operating systems.", + "items": { + "type": "object", + "properties": { + "os": { + "type": "string" + }, + "count": { + "type": "integer" + } + } + } + } + } + }, + "ApplicationAnalyticsWorkflowFunnel": { + "type": "object", + "description": "Keyed by workflow ID. `total` is the sessions started on that workflow; `steps` counts the sessions that reached each step (`OCR`, `LIVENESS`, `FACE_MATCH`, `AML`, `POA`, `PHONE`, `EMAIL`, `NFC`, `DATABASE_VALIDATION`, `QUESTIONNAIRE`, `IP_ANALYSIS`, `AGE_ESTIMATION`, `KYB_REGISTRY`, `KYB_DOCUMENTS`, `KYB_KEY_PEOPLE`). Reaching a step is not passing it.", + "additionalProperties": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "steps": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "reached": { + "type": "integer" + } + } + } + } + } + } + }, + "ApplicationAnalyticsVerificationTimeStats": { + "type": "object", + "description": "Active time users spent verifying, ignoring idle gaps. An empty object when there is no data.", + "properties": { + "overall_avg_seconds": { + "type": "number", + "description": "Average per session, one decimal." + }, + "overall_count": { + "type": "integer", + "description": "Sessions measured." + }, + "per_feature": { + "type": "object", + "description": "Keyed by feature, for example `OCR` or `LIVENESS`.", + "additionalProperties": { + "type": "object", + "properties": { + "avg_seconds": { + "type": "number" + }, + "total_seconds": { + "type": "number" + }, + "count": { + "type": "integer" + } + } + } + }, + "per_sub_step": { + "type": "object", + "description": "Keyed by feature, then by sub-step, for example `front_id_photo`.", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "avg_seconds": { + "type": "number" + }, + "total_seconds": { + "type": "number" + }, + "count": { + "type": "integer" + } + } + } + } + } + } + }, + "ApplicationAnalyticsResubmissionStats": { + "type": "object", + "properties": { + "first_pass": { + "type": "integer", + "description": "Finished sessions with no resubmission." + }, + "second": { + "type": "integer", + "description": "Finished sessions resubmitted once." + }, + "third_plus": { + "type": "integer", + "description": "Finished sessions resubmitted two or more times." + }, + "abandoned": { + "type": "integer", + "description": "Abandoned sessions." + } + } + }, + "ApplicationAnalyticsOriginalStatusStats": { + "type": "object", + "description": "The workflow's automatic verdict, before any reviewer changed it.", + "properties": { + "approved": { + "type": "integer" + }, + "in_review": { + "type": "integer" + }, + "declined": { + "type": "integer" + } + } + }, + "ApplicationAnalyticsWarningStats": { + "type": "object", + "description": "Keyed by feature (`ID_VERIFICATION`, `LIVENESS`, `FACEMATCH`, `AML`, `PROOF_OF_ADDRESS`, `PHONE`, `EMAIL`, `NFC`, `LOCATION`, `DATABASE_VALIDATION`, `QUESTIONNAIRE` and the KYB features). `total` is the sessions that reached the feature; `risks` counts sessions per warning code and level.", + "additionalProperties": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "risks": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "error": { + "type": "integer" + }, + "warning": { + "type": "integer" + }, + "information": { + "type": "integer" + } + } + } + } + } + } + }, + "ApplicationAnalyticsDemographicPoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "age_distribution": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + }, + "gender_distribution": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + } + }, + "ApplicationAnalyticsDevicePoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "devices": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + }, + "browsers": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + }, + "operating_systems": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + } + }, + "ApplicationAnalyticsVerificationTimePoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "overall_avg": { + "type": "number", + "description": "Average active seconds per session." + }, + "per_feature": { + "type": "object", + "description": "Average active seconds per feature.", + "additionalProperties": { + "type": "number" + } + } + } + }, + "ApplicationAnalyticsResubmissionPoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "first_pass": { + "type": "integer" + }, + "second": { + "type": "integer" + }, + "third_plus": { + "type": "integer" + }, + "abandoned": { + "type": "integer" + } + } + }, + "ApplicationAnalyticsOriginalStatusPoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "approved": { + "type": "integer" + }, + "in_review": { + "type": "integer" + }, + "declined": { + "type": "integer" + } + } + }, + "ApplicationAnalyticsSummary": { + "type": "object", + "description": "User verification (KYC) summary, returned when neither `scope` nor `widget_keys` is sent.", + "required": [ + "data_points", + "request_breakdown", + "previous_data_points", + "previous_request_breakdown", + "conversion_rate", + "location_stats", + "demographic_stats", + "device_details", + "workflow_funnel_stats", + "verification_time_stats", + "resubmission_stats", + "warning_stats", + "original_status_stats", + "grouping_mode", + "demographic_time_series", + "device_time_series", + "verification_time_time_series", + "resubmission_time_series", + "original_status_time_series" + ], + "properties": { + "data_points": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDataPoint" + }, + "description": "One point per day, from the first day with sessions in the range to `end_date`. Always daily, whatever `grouping_mode` says." + }, + "request_breakdown": { + "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" + }, + "previous_data_points": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDataPoint" + }, + "description": "Daily points for the period of the same length right before `start_date`." + }, + "previous_request_breakdown": { + "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" + }, + "conversion_rate": { + "type": "number", + "nullable": true, + "description": "`total_approved / (total_approved + total_declined + total_in_review) x 100`, one decimal. `null` when nothing finished." + }, + "location_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsLocationStats" + }, + "demographic_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsDemographicStats" + }, + "device_details": { + "$ref": "#/components/schemas/ApplicationAnalyticsDeviceDetails" + }, + "workflow_funnel_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsWorkflowFunnel" + }, + "verification_time_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimeStats" + }, + "resubmission_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionStats" + }, + "warning_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsWarningStats" + }, + "original_status_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusStats" + }, + "grouping_mode": { + "type": "string", + "enum": [ + "day", + "week", + "month" + ], + "description": "How the `*_time_series` lists are grouped: `day` for up to 14 days, `week` up to 90, `month` beyond. Measured from the first day with sessions in the range." + }, + "demographic_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDemographicPoint" + } + }, + "device_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDevicePoint" + } + }, + "verification_time_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimePoint" + } + }, + "resubmission_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionPoint" + } + }, + "original_status_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusPoint" + } + } + } + }, + "ApplicationAnalyticsWidget": { + "type": "object", + "description": "One dashboard widget. Every widget has `kind` and `title`; the other fields depend on `kind`. The session widgets reuse the summary objects (`data_points`, `request_breakdown`, `workflow_funnel_stats` and so on); list widgets put their entries in `items`; transaction metrics use `value`.", + "required": [ + "kind", + "title" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "sessions_volume", + "workflow_funnel", + "average_verification_time", + "country_distribution", + "warning_insights", + "conversion_rate", + "conversion_by_country", + "resubmission_insights", + "original_status_distribution", + "database_validation_coverage", + "database_validation_latency", + "demographics", + "device_details", + "horizontal_bar", + "kyb_registry_coverage", + "kyb_registry_data_completeness", + "metric", + "time_series", + "breakdown", + "status_time_series", + "leaderboard", + "histogram" + ], + "description": "Widget type." + }, + "title": { + "type": "string", + "description": "Display title, for example `Verification volume`." + }, + "grouping_mode": { + "type": "string", + "enum": [ + "day", + "week", + "month" + ], + "description": "How the `*_time_series` lists are grouped: `day` for up to 14 days, `week` up to 90, `month` beyond. Measured from the first day with sessions in the range." + }, + "data_points": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDataPoint" + } + }, + "request_breakdown": { + "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" + }, + "previous_data_points": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDataPoint" + } + }, + "previous_request_breakdown": { + "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" + }, + "conversion_rate": { + "type": "number", + "nullable": true + }, + "workflow_funnel_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsWorkflowFunnel" + }, + "verification_time_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimeStats" + }, + "verification_time_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimePoint" + } + }, + "warning_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsWarningStats" + }, + "resubmission_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionStats" + }, + "resubmission_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionPoint" + } + }, + "original_status_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusStats" + }, + "original_status_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusPoint" + } + }, + "demographic_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsDemographicStats" + }, + "demographic_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDemographicPoint" + } + }, + "device_details": { + "$ref": "#/components/schemas/ApplicationAnalyticsDeviceDetails" + }, + "device_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDevicePoint" + } + }, + "items": { + "type": "array", + "description": "Entries of a list widget. `country_distribution` (KYC): `country`, `country_code`, `session_count`. `conversion_by_country`: `country`, `country_code`, `finished`, `approved`. `time_series`: `date`, `value`. `status_time_series`: `date`, `approved`, `declined`, `flagged`. `breakdown`: `transaction_type` or `action_type` with `count` and `volume`, or `tag_id`, `tag_name`, `count`. `leaderboard`: `rule_title`, `trigger_count` or `assignee`, `handled_count`, `average_processing_seconds`, each with `approved_percentage`, `declined_percentage`, `flagged_percentage`. `histogram`: `score`, `count`.", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "previous_items": { + "type": "array", + "description": "Transaction time series for the period of the same length right before `start_date`.", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "value": { + "type": "number", + "description": "`metric` widgets: the figure for the range." + }, + "currency": { + "type": "string", + "nullable": true, + "description": "Transaction volume and amount widgets: the application's default transaction currency." + }, + "thresholds": { + "type": "object", + "description": "`histogram` widget: the application's review and decline score thresholds.", + "properties": { + "review": { + "type": "integer" + }, + "decline": { + "type": "integer" + } + } + }, + "search": { + "type": "object", + "description": "`kyb_registry_coverage` widget: registry search totals.", + "properties": { + "searches_total": { + "type": "integer" + }, + "searches_with_no_candidates": { + "type": "integer" + } + } + } + } + }, + "ApplicationAnalyticsWidgetResponse": { + "type": "object", + "description": "Dashboard widgets, returned when `scope` or `widget_keys` is sent.", + "required": [ + "scope", + "available_widget_keys", + "requested_widget_keys", + "widgets" + ], + "properties": { + "scope": { + "type": "string", + "enum": [ + "home", + "kyc", + "kyb", + "transactions" + ], + "description": "Dashboard tab." + }, + "available_widget_keys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Every widget key this scope accepts." + }, + "requested_widget_keys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The widget keys answered, in order: your valid `widget_keys`, or the scope's defaults." + }, + "widgets": { + "type": "object", + "description": "Keyed by widget key, in the order of `requested_widget_keys`.", + "additionalProperties": { + "$ref": "#/components/schemas/ApplicationAnalyticsWidget" + } + } + } + }, + "WebhookConfigDestination": { + "type": "object", + "description": "A webhook destination as listed in the webhook configuration, without delivery metrics. `GET /v3/webhook/destinations/{destination_uuid}/` adds its delivery summary.", + "required": [ + "uuid", + "label", + "url", + "enabled", + "webhook_version", + "secret_shared_key", + "subscribed_events", + "created_at", + "updated_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Destination ID." + }, + "label": { + "type": "string", + "maxLength": 255, + "description": "Human-readable name." + }, + "url": { + "type": "string", + "format": "uri", + "maxLength": 2000, + "description": "HTTPS endpoint Didit posts events to." + }, + "enabled": { + "type": "boolean", + "description": "`false` pauses deliveries to this destination." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload version sent to this destination." + }, + "secret_shared_key": { + "type": "string", + "description": "This destination's signing secret (43-character URL-safe token). Verify each webhook with the secret of the destination it was sent to. API-key callers always receive it in full; Console users without `read:webhooks` receive an empty string." + }, + "subscribed_events": { + "type": "array", + "uniqueItems": true, + "description": "Event types delivered to this destination. Empty when the destination was created without events, in which case it receives nothing.", + "items": { + "$ref": "#/components/schemas/WebhookEvent" + } + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "WebhookConfig": { + "type": "object", + "description": "Application-level webhook configuration, retention policy and Digital ID wallet return hosts, with every webhook destination.", + "required": [ + "webhook_url", + "webhook_version", + "secret_shared_key", + "capture_method", + "data_retention_months", + "face_retention_policy", + "face_retention_days", + "available_events", + "allowed_return_hosts", + "endpoints" + ], + "properties": { + "webhook_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Application-level (legacy) webhook URL. Didit sends to it only while the application has no enabled destination, and then only `status.updated` and `data.updated`. Every create, update or delete of a destination sets it to the URL of the oldest enabled destination, or `null` when none is enabled." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload version of the legacy `webhook_url`. Each destination has its own `webhook_version`." + }, + "secret_shared_key": { + "type": "string", + "description": "Application-level signing secret (43-character URL-safe token). It signs only deliveries to the legacy `webhook_url`; each destination signs with its own `secret_shared_key`. API-key callers always receive it in full; Console users without `read:webhooks` receive an empty string." + }, + "capture_method": { + "type": "string", + "enum": [ + "MOBILE", + "MOBILE_AND_DESKTOP" + ], + "description": "Legacy application setting. The verification flow does not read it: capture options are configured per workflow." + }, + "data_retention_months": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 120, + "description": "Automatic deletion window. User verification sessions created more than this many months ago (a month counts as 30 days) are deleted automatically, and that deletion is irreversible. `null` means sessions are never deleted automatically. See [Data retention](https://docs.didit.me/console/data-retention)." + }, + "face_retention_policy": { + "type": "string", + "enum": [ + "delete_with_session", + "retain_until_user_deleted" + ], + "description": "What happens to a session's face embedding when the session is deleted. `delete_with_session` (default) deletes it with the session. `retain_until_user_deleted` is the opt-in [biometric template retention](https://docs.didit.me/console/data-retention#biometric-template-retention): an image-free biometric template linked to the User is managed separately until its retention period ends, the User is deleted, or you purge it." + }, + "face_retention_days": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 3650, + "description": "Retention period, in days, of biometric templates under `retain_until_user_deleted`. Required when that policy is set." + }, + "biometric_consent_mode": { + "type": "string", + "enum": [ + "auto", + "always", + "never" + ], + "description": "When the hosted flow and the native SDKs ask the person for explicit biometric consent before a face capture. `auto` (default) asks only where a trigger applies: a declared state, the person's IP, or the identity document's issuer is in Illinois, Texas or Colorado, or the person looks to be in the US without a known state. `always` asks every person whose workflow captures a face. `never` never shows the step, and you collect consent yourself before the selfie. Each change is recorded in the audit log. See [Biometric consent](https://docs.didit.me/getting-started/biometric-consent)." + }, + "available_events": { + "type": "array", + "description": "Every event type a destination can subscribe to, with a display label.", + "items": { + "type": "object", + "required": [ + "value", + "label" + ], + "properties": { + "value": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "label": { + "type": "string", + "description": "Display label, for example `Session status updated`." + } + } + } + }, + "allowed_return_hosts": { + "type": "array", + "maxItems": 20, + "description": "Hostnames (exact match, HTTPS only) a standalone Digital ID wallet verification may send the user back to with its `return_url`. Your white-label domain is always allowed as well. See [Register your return destination](https://docs.didit.me/standalone-apis/digital-id-wallets#register-your-return-destination).", + "items": { + "type": "string", + "maxLength": 253 + } + }, + "endpoints": { + "type": "array", + "description": "Every webhook destination of the application, oldest first, each with its signing secret. Read only here: manage destinations with the `/v3/webhook/destinations/` endpoints.", + "items": { + "$ref": "#/components/schemas/WebhookConfigDestination" + } + } + } + }, + "WebhookDestinationDelivery": { + "type": "object", + "description": "One delivery attempt to a webhook destination. The first send of an event, each automatic retry and each resend are separate deliveries.", + "required": [ + "uuid", + "session_id", + "request_url", + "response_status", + "response_time_ms", + "status", + "webhook_type", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Delivery ID, for the detail and resend calls." + }, + "session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "User verification session the event is about. `null` for business session, user, business, transaction, Travel Rule and workflow events." + }, + "request_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL the delivery was sent to: the destination's URL when the event was generated." + }, + "response_status": { + "type": "integer", + "nullable": true, + "description": "HTTP status your endpoint returned. `null` while the delivery is queued and not yet attempted. When no HTTP answer was received Didit records a status of its own: `504` when your endpoint did not answer within 5 seconds, `503` for a connection failure, `400` when the URL was refused as unsafe (for example a private or internal address), and `404` for any other request error. `400` or above counts as a failed delivery." + }, + "response_time_ms": { + "type": "integer", + "nullable": true, + "minimum": 0, + "description": "Time the attempt took, in milliseconds. `null` while the delivery is queued." + }, + "status": { + "type": "string", + "nullable": true, + "description": "Status the event reported when it was generated: the session status for session events (for example `Approved` or `In Review`), the person's or company's status for user and business events, and the transaction status for transaction events. `null` for events that carry no status." + }, + "webhook_type": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the delivery was created." + } + } + }, + "WebhookDestinationDeliveryDetail": { + "type": "object", + "description": "One delivery attempt to a webhook destination, with the exact request headers and body Didit sent.", + "required": [ + "uuid", + "session_id", + "request_url", + "response_status", + "response_time_ms", + "status", + "webhook_type", + "created_at", + "request_header", + "request_body" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Delivery ID, for the detail and resend calls." + }, + "session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "User verification session the event is about. `null` for business session, user, business, transaction, Travel Rule and workflow events." + }, + "request_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL the delivery was sent to: the destination's URL when the event was generated." + }, + "response_status": { + "type": "integer", + "nullable": true, + "description": "HTTP status your endpoint returned. `null` while the delivery is queued and not yet attempted. When no HTTP answer was received Didit records a status of its own: `504` when your endpoint did not answer within 5 seconds, `503` for a connection failure, `400` when the URL was refused as unsafe (for example a private or internal address), and `404` for any other request error. `400` or above counts as a failed delivery." + }, + "response_time_ms": { + "type": "integer", + "nullable": true, + "minimum": 0, + "description": "Time the attempt took, in milliseconds. `null` while the delivery is queued." + }, + "status": { + "type": "string", + "nullable": true, + "description": "Status the event reported when it was generated: the session status for session events (for example `Approved` or `In Review`), the person's or company's status for user and business events, and the transaction status for transaction events. `null` for events that carry no status." + }, + "webhook_type": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the delivery was created." + }, + "request_header": { + "type": "string", + "description": "JSON text (a string) of the request headers Didit sent: `Content-Type`, `X-Signature`, `X-Signature-V2`, `X-Signature-Simple`, `X-Timestamp` and `User-Agent`. Parse it with a JSON parser. It holds signatures, never the signing secret." + }, + "request_body": { + "type": "string", + "description": "JSON text (a string) of the exact payload Didit sent, keys sorted, including `event_id` and `timestamp`. Parse it with a JSON parser; see [Payload shape](https://docs.didit.me/integration/webhooks#payload-shape). For a scoped key without `media: read`, media URLs inside it are `null`; without `sessions: write`, session tokens and hosted verification links are `null`. A body changed that way no longer matches its signatures. Didit does not record the response body your endpoint returned." + } + } + }, + "SessionWebhookDelivery": { + "type": "object", + "description": "One webhook delivery attempt recorded for a verification session, with the exact request headers and body Didit sent.", + "required": [ + "uuid", + "session_id", + "request_url", + "response_status", + "status", + "webhook_type", + "request_header", + "request_body", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Delivery ID. Use it as `{webhook_uuid}` to get or resend this delivery." + }, + "session_id": { + "type": "string", + "format": "uuid", + "description": "The session the delivery belongs to (the session in the path)." + }, + "request_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL the delivery was sent to." + }, + "response_status": { + "type": "integer", + "nullable": true, + "description": "HTTP status your endpoint returned. `null` while the delivery is queued and not yet attempted. When no HTTP answer was received Didit records a status of its own: `504` when your endpoint did not answer within 5 seconds, `503` for a connection failure, `400` when the URL was refused as unsafe (for example a private or internal address), and `404` for any other request error. `400` or above counts as a failed delivery." + }, + "status": { + "type": "string", + "nullable": true, + "description": "Session status when the event was generated, for example `In Progress`, `Approved` or `Declined`." + }, + "webhook_type": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "request_header": { + "type": "string", + "description": "JSON text (a string) of the request headers Didit sent: `Content-Type`, `X-Signature`, `X-Signature-V2`, `X-Signature-Simple`, `X-Timestamp` and `User-Agent`. Parse it with a JSON parser. It holds signatures, never the signing secret." + }, + "request_body": { + "type": "string", + "description": "JSON text (a string) of the exact payload Didit sent, keys sorted, including `event_id` and `timestamp`. Parse it with a JSON parser; see [Payload shape](https://docs.didit.me/integration/webhooks#payload-shape). For a scoped key without `media: read`, media URLs inside it are `null`; without `sessions: write`, session tokens and hosted verification links are `null`. A body changed that way no longer matches its signatures. Didit does not record the response body your endpoint returned." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the delivery was created." + } + } + }, + "SessionEvent": { + "type": "object", + "description": "One entry of a verification session's event timeline.", + "required": [ + "uuid", + "event_type", + "created_at", + "component", + "feature_step", + "step", + "details", + "ip_address", + "user_agent", + "device_info", + "file_url", + "sent_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Event identifier." + }, + "event_type": { + "type": "string", + "enum": [ + "SESSION_CREATED", + "SESSION_COMPLETED", + "SESSION_STATUS_CHANGED", + "LINK_OPENED", + "FORM_SUBMITTED", + "BUTTON_CLICKED", + "FILE_UPLOADED", + "ATTEMPT_FAILED", + "FEATURE_STEP_CHANGED", + "STEP_RENDERED", + "NOTE_ADDED", + "MANUAL_REVIEW_STARTED", + "WORKFLOW_BRANCH_EVALUATED", + "WORKFLOW_STATUS_NODE_REACHED", + "LOG" + ], + "description": "What happened. Reported by the device during the hosted flow or SDK: `LINK_OPENED`, `STEP_RENDERED`, `BUTTON_CLICKED`, `FORM_SUBMITTED`, `FILE_UPLOADED`, `ATTEMPT_FAILED` and `LOG` (debugging). Recorded by the server: `SESSION_CREATED`, `SESSION_STATUS_CHANGED` (`details.old_status`, `details.current_status`), `FEATURE_STEP_CHANGED` (`details.old_feature_step`, `details.current_feature_step`), `WORKFLOW_BRANCH_EVALUATED` and `WORKFLOW_STATUS_NODE_REACHED` (the workflow routing trace). `SESSION_COMPLETED`, `NOTE_ADDED` and `MANUAL_REVIEW_STARTED` can appear on older sessions." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When Didit stored the event. Events are stored asynchronously, a few seconds after `sent_at`." + }, + "component": { + "type": "string", + "nullable": true, + "description": "UI component that reported the event (device events only)." + }, + "feature_step": { + "type": "string", + "nullable": true, + "enum": [ + "OCR", + "OCR_BACK", + "NFC", + "AML", + "FACE", + "LIVENESS", + "FACE_MATCH", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "PROOF_OF_ADDRESS", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "FACE_SEARCH", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "DOCUMENT_AI", + "GEOLOCATION", + null + ], + "description": "Verification feature the session was on when the event happened." + }, + "step": { + "type": "string", + "nullable": true, + "description": "Screen within the feature, for example `DOCUMENT_CAPTURE_FRONT` or `FACE_PASSIVE_LIVENESS`." + }, + "details": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Event-specific data. The keys depend on `event_type`." + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "IP address the device reported the event from. `null` for server-recorded events." + }, + "user_agent": { + "type": "string", + "nullable": true, + "description": "User agent of the device. `null` for server-recorded events." + }, + "device_info": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Device details the SDK or browser reported." + }, + "file_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived presigned link to a file attached to the event (for example a captured image). `null` when there is none, and always `null` for a scoped API key without `media: read`." + }, + "sent_at": { + "type": "integer", + "format": "int64", + "description": "When the event happened, as a Unix timestamp in milliseconds. Results are ordered by it." + } + } + }, + "AMLDatasetSourceFreshness": { + "type": "object", + "description": "Freshness of one family of AML datasets.", + "required": [ + "last_updated_at", + "oldest_updated_at", + "dataset_count", + "dated_dataset_count" + ], + "properties": { + "last_updated_at": { + "type": "string", + "format": "date-time", + "description": "Most recent update of any dataset in the family." + }, + "oldest_updated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Staleness bound: every dataset in the family is at least this current. `null` when at least one dataset in the family reported no update date, because the bound is then unknown." + }, + "dataset_count": { + "type": "integer", + "description": "Number of datasets in the family." + }, + "dated_dataset_count": { + "type": "integer", + "description": "Number of those datasets that reported an update date." + } + } + }, + "AMLDatasetFreshness": { + "type": "object", + "description": "How current the AML datasets were when a screening ran. Dates are never estimated.", + "required": [ + "captured_at", + "provider", + "catalog_version", + "sources" + ], + "properties": { + "captured_at": { + "type": "string", + "format": "date-time", + "description": "When Didit read the dataset catalog this freshness comes from." + }, + "provider": { + "type": "string", + "description": "Opaque identifier of the AML data source. Treat it as an opaque string; do not branch on it.", + "example": "aml_data_source" + }, + "catalog_version": { + "type": "string", + "nullable": true, + "description": "Version identifier of the dataset catalog, when the source reports one." + }, + "sources": { + "type": "object", + "description": "Freshness per dataset family. A family whose datasets reported no update date is absent rather than filled with `null`.", + "properties": { + "sanctions": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" + }, + "pep": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" + }, + "warnings": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" + }, + "adverse_media": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" + } + } + } + } + }, + "AMLScreeningRun": { + "type": "object", + "description": "One completed ongoing-monitoring re-screen of an AML check.", + "required": [ + "uuid", + "completed_at", + "started_at", + "outcome", + "trigger", + "provider", + "status", + "score", + "total_hits", + "dataset_freshness" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Identifier of this screening run." + }, + "completed_at": { + "type": "string", + "format": "date-time", + "description": "When this subject's re-screen finished. Results are ordered by it, newest first." + }, + "started_at": { + "type": "string", + "format": "date-time", + "description": "When the monitoring batch this re-screen belongs to started." + }, + "outcome": { + "type": "string", + "enum": [ + "NO_CHANGE", + "CHANGED", + "FAILED" + ], + "description": "`NO_CHANGE`: completed and the result did not move. `CHANGED`: completed and the score, status, hit count or hits changed. `FAILED`: no usable answer for this subject, so the check was left as it was." + }, + "trigger": { + "type": "string", + "enum": [ + "ONGOING_MONITORING" + ], + "description": "What started the screening. Always `ONGOING_MONITORING` today." + }, + "provider": { + "type": "string", + "description": "Opaque identifier of the AML data source that answered. Treat it as an opaque string; do not branch on it.", + "example": "aml_data_source" + }, + "status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested", + null + ], + "description": "AML check status this run left the check in." + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "AML score this run left the check in." + }, + "total_hits": { + "type": "integer", + "nullable": true, + "description": "Number of hits this run left the check in." + }, + "dataset_freshness": { + "allOf": [ + { + "$ref": "#/components/schemas/AMLDatasetFreshness" + } + ], + "nullable": true, + "description": "Dataset freshness behind this run, or `null` when no dates were available." + } + } + }, + "KYBKeyPeopleParty": { + "type": "object", + "description": "A Key People party of a Business Verification (KYB) session, with its child verification. The same party appears in `key_people_checks[].submitted.parties[]` of the business session decision.", + "required": [ + "uuid", + "entity_type", + "source", + "name", + "is_active", + "is_skipped", + "requires_verification", + "roles", + "kyc_session_deleted", + "kyb_sub_session_deleted" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Party identifier." + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "source": { + "type": "string", + "enum": [ + "REGISTRY", + "USER" + ], + "description": "`REGISTRY`: taken from the company registry. `USER`: entered in the Key People screen." + }, + "name": { + "type": "string", + "description": "Display name of the party." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "First name (persons)." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Last name (persons)." + }, + "company_name": { + "type": "string", + "nullable": true, + "description": "Company name (company parties)." + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Company registration number (company parties)." + }, + "email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email the verification link is sent to." + }, + "nationality": { + "type": "string", + "nullable": true, + "description": "Nationality for persons, country of incorporation for companies." + }, + "phone_number": { + "type": "string", + "nullable": true, + "description": "Phone number collected for the party." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth (persons)." + }, + "position": { + "type": "string", + "nullable": true, + "description": "Free-text job title (persons)." + }, + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined custom fields collected for the party, keyed by field key." + }, + "is_active": { + "type": "boolean", + "description": "Whether the party is active." + }, + "is_skipped": { + "type": "boolean", + "description": "Whether the party is exempt from verification. Regenerating clears it." + }, + "requires_verification": { + "type": "boolean", + "description": "Whether the role and ownership rules require a verification for this party." + }, + "roles": { + "type": "array", + "description": "Roles the party holds in the company.", + "items": { + "type": "object", + "required": [ + "role", + "ownership_percent", + "voting_percent" + ], + "properties": { + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Role code." + }, + "ownership_percent": { + "type": "number", + "nullable": true, + "description": "Ownership percentage for this role, when known." + }, + "voting_percent": { + "type": "number", + "nullable": true, + "description": "Voting-rights percentage for this role, when known." + } + } + } + }, + "kyc_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Child individual KYC session (persons)." + }, + "kyc_session_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User", + null + ], + "description": "Status of the child KYC session. A deleted session keeps the status it had when it was deleted." + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Hosted link of the child KYC session. `null` when there is none or it was deleted, and for a scoped API key without `sessions: write`." + }, + "kyc_session_deleted": { + "type": "boolean", + "description": "The child KYC session was deleted, so it can never settle." + }, + "kyb_sub_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Child KYB session (company parties)." + }, + "kyb_sub_session_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User", + null + ], + "description": "Status of the child KYB session." + }, + "kyb_sub_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Hosted link of the child KYB session, with the same `null` rules as `kyc_session_url`." + }, + "kyb_sub_session_deleted": { + "type": "boolean", + "description": "Company-party counterpart of `kyc_session_deleted`." + }, + "verification_workflow_type": { + "type": "string", + "nullable": true, + "enum": [ + "kyc", + "kyb", + "adaptive_age_verification", + "biometric_authentication", + "address_verification", + "questionnaire_verification", + "email_verification", + "phone_verification", + null + ], + "description": "Type of the workflow the child session runs." + }, + "verification_workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Workflow the child session runs." + }, + "verification_workflow_label": { + "type": "string", + "nullable": true, + "description": "Label of that workflow." + }, + "verification_workflow_features": { + "type": "array", + "nullable": true, + "description": "Features of the child session's workflow with their current status.", + "items": { + "type": "object", + "required": [ + "feature", + "status" + ], + "properties": { + "feature": { + "type": "string", + "description": "Feature code, for example `ID_VERIFICATION` or `LIVENESS`." + }, + "status": { + "type": "string", + "description": "Feature status, `Not Finished` until it runs." + } + } + } + }, + "didit_internal_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Didit's identifier of the verified person, once known." + } + } + }, + "TravelRuleDelivery": { + "type": "object", + "description": "Where the latest message the transfer owes the other side stands. Didit contacts the counterparty after the request that created or changed the transfer has returned, so this object says whether that message got through: queued, being sent, waiting to be retried after an error, delivered, or failed. A failure the network may still recover from (no answer in time, a failed connection, HTTP `408`, `429` or `5xx`) is tried again after 30 seconds, 2 minutes, 10 minutes, 30 minutes and 1 hour. When a first message (`kind: SEND`) fails for good, because the network refused it or the sixth attempt failed too, the transfer moves to `COUNTERPARTY_VASP_NOT_REACHABLE` with `last_error` in its `timeline`; any other message that fails for good is noted in the `timeline`. The transfer's `status` stays the source of truth for the exchange itself.", + "required": [ + "kind", + "status", + "rail", + "attempts", + "last_error", + "next_attempt_at", + "delivered_at" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "SEND", + "ANSWER", + "TXID", + "CANCEL", + "POLL" + ], + "description": "What the message carries. `SEND`: the transfer's first message to the counterparty (outbound; the rail is chosen when it is sent). `ANSWER`: your accept or decline, owed to the network that asked (inbound). `TXID`: the on-chain transaction hash, after you finish the transfer. `CANCEL`: the cancellation, after you cancel it. `POLL`: a status query to a network that offers one, repeated every 5 minutes while the transfer waits on the counterparty." + }, + "status": { + "type": "string", + "enum": [ + "QUEUED", + "SENDING", + "RETRYING", + "DELIVERED", + "FAILED" + ], + "description": "`QUEUED`: waiting to be sent. `SENDING`: an attempt is running. `RETRYING`: an attempt failed and the next one runs at `next_attempt_at`. `DELIVERED`: the other side took the message. `FAILED`: the other side refused it, or the last attempt failed." + }, + "rail": { + "type": "string", + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL", + "" + ], + "description": "The rail that carries the message. Empty until Didit has chosen one for a first message." + }, + "attempts": { + "type": "integer", + "minimum": 0, + "description": "Attempts made so far for this message. A message that is not a status poll is attempted at most 6 times." + }, + "last_error": { + "type": "string", + "description": "The kind of failure the last attempt hit, `\"\"` when none did: for example `email undeliverable`, `trp identity endpoint unreachable` or `trp transfer inquiry delivery failed`. It never carries the counterparty's own answer, which stays on the transfer's `timeline`.\nWhen GTR answers with an error, its own status code and message are included. Response bodies are never included.", + "example": "email undeliverable" + }, + "next_attempt_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the next attempt runs while the message is `QUEUED` or `RETRYING`." + }, + "delivered_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the other side took the message." + } + } + }, + "TravelRuleDiscoveryStep": { + "type": "object", + "description": "One step of the counterparty discovery Didit ran for an outbound transfer, in the order it ran them. The first step that names a VASP with a route ends the chain.", + "properties": { + "step": { + "type": "string", + "enum": [ + "internal_address_book", + "wallet_attribution", + "network_lookup", + "customer_hint", + "end_user" + ], + "description": "`internal_address_book`: another Didit customer that proved control of the destination wallet. `wallet_attribution`: the wallet's blockchain analytics attribution. `network_lookup`: a network that can say which VASP holds the address. `customer_hint`: the VASP name, LEI or jurisdiction you sent in `beneficiary_data`. `end_user`: the counterparty the end user (or you) picked, or the request to pick one." + }, + "outcome": { + "type": "string", + "enum": [ + "match", + "miss", + "skipped" + ], + "description": "`match`: the step named a counterparty. `miss`: it ran and found none. `skipped`: it could not run, and `reason` says why." + }, + "reason": { + "type": "string", + "description": "Machine-readable reason, `\"\"` on a match: `no_proven_internal_wallet`, `attribution_disabled`, `already_attributed`, `no_attributed_entity`, `directory_match_no_route`, `gtr_address_lookup_runs_on_dispatch`, `no_membership`, `network_switched_off`, `no_network_address_lookup`, `no_directory_match`, `awaiting_end_user` or `picked_member_without_proven_wallet`." + }, + "vasp": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The directory VASP the step named, when it named one." + }, + "rail": { + "type": "string", + "nullable": true, + "description": "The rail that reaches that counterparty, when the step found one." + }, + "at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleNextAction": { + "type": "object", + "description": "One thing that moves a `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE` transfer on. See [Why a transfer is stuck](/transaction-monitoring/travel-rule#why-a-transfer-is-stuck).", + "properties": { + "action": { + "type": "string", + "enum": [ + "select_counterparty_vasp", + "declare_self_hosted", + "add_vasp_hint", + "update_compliance_email", + "resend" + ], + "description": "`select_counterparty_vasp`: the end user picks the destination VASP in the wallet-ownership widget, or you PATCH `action=beneficiary_vasp`. `declare_self_hosted`: the end user declares the wallet self-hosted in the widget. `add_vasp_hint`: tell Didit who the counterparty is, with a VASP name, LEI or travel address (for example a `travel_address` on the destination wallet's address-book entry), then resend. `update_compliance_email` and `resend` are `action` values of `PATCH /v3/transactions/{transaction_id}/travel-rule/`: give the counterparty's compliance email again, and re-run routing." + }, + "url": { + "type": "string", + "format": "uri", + "description": "The wallet-ownership widget link, on `select_counterparty_vasp` and `declare_self_hosted` while the transfer has an open widget session." + } + }, + "required": [ + "action" + ] + }, + "TransactionTravelRule": { + "type": "object", + "description": "A transaction's Travel Rule transfer, as `GET /v3/transactions/{transaction_id}/`, the `201` of `POST /v3/transactions/` and each row of `GET /v3/transactions/` return it. `direction`, `rail`, `counterparty_vasp`, `delivery`, `discovery`, `reason` and `next_actions` have the same shape as on `PATCH /v3/transactions/{transaction_id}/travel-rule/`, so one read of a transaction says why its transfer is stuck and what moves it on.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string" + }, + "protocol": { + "type": "string", + "nullable": true + }, + "required": { + "type": "boolean" + }, + "obligations_count": { + "type": "integer" + }, + "originator_data": { + "type": "object", + "description": "Originator party. For an inbound transfer the counterparty's values are masked until ownership is confirmed." + }, + "beneficiary_data": { + "type": "object", + "description": "Beneficiary party. For an inbound transfer the counterparty's values are masked until ownership is confirmed." + }, + "name_match": { + "type": "object", + "nullable": true, + "description": "The name comparison that decided the transfer (score and strictness). Null until one ran. The compared names are masked together with the parties." + }, + "data_match": { + "$ref": "#/components/schemas/TravelRuleDataMatch" + }, + "mismatched_fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Shortcut for `data_match.mismatched_fields`. Empty when nothing differs." + }, + "data_unmasked": { + "$ref": "#/components/schemas/TravelRuleDataUnmasked" + }, + "data_masked": { + "type": "boolean", + "description": "True while the counterparty's personal data is masked in this response." + }, + "metadata": { + "type": "object" + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Which side of the exchange this transfer is." + }, + "rail": { + "type": "string", + "nullable": true, + "description": "The rail the transfer went over: `INTERNAL`, `TRP`, `GTR`, `TRUST`, `VERIFYVASP`, `SYGNA` or `EMAIL` (`TEST_VASP` on a sandbox application, when the Didit Test VASP answered), or `null` until a rail is chosen." + }, + "counterparty_vasp": { + "type": "object", + "nullable": true, + "properties": { + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "dd_score": { + "type": "integer", + "description": "Due-diligence score from 0 to 100, where higher is better. Same scale as in the VASP directory." + }, + "dd_score_breakdown": { + "type": "object", + "description": "Per-component contributions to dd_score, one object per component as in the VASP directory (jurisdiction, verification, protocol, lei, history, essentials, responsiveness), each {points, max, reason, detail}. {} until the counterparty has been assessed.", + "additionalProperties": { + "type": "object", + "properties": { + "points": { + "type": "number", + "description": "Points awarded for this component." + }, + "max": { + "type": "integer", + "description": "Maximum points this component can award." + }, + "reason": { + "type": "string", + "description": "Machine-readable code of the component's verdict." + }, + "detail": { + "type": "string", + "description": "Human-readable reason for the awarded points." + } + } + } + }, + "dd_assessed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the due-diligence assessment last ran. Null until the counterparty has been assessed." + } + }, + "description": "The counterparty's directory entry, or `null` when the transfer links none." + }, + "delivery": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleDelivery" + } + ], + "description": "The newest message the transfer owes another party. `null` when it has owed none, for example below `threshold_amount` or for a declared self-hosted wallet." + }, + "discovery": { + "type": "array", + "description": "How Didit looked for the counterparty, one entry per step that ran. Empty when no discovery ran, for example on an inbound transfer.", + "items": { + "$ref": "#/components/schemas/TravelRuleDiscoveryStep" + } + }, + "reason": { + "type": "string", + "description": "Why a `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE` transfer is stuck; `\"\"` for any other status. A failed delivery decides it first, on `COUNTERPARTY_VASP_NOT_REACHABLE` only: `email_undeliverable` (the mail provider refused the compliance email and no address is left to try: when it was sent, on a reminder, or as a permanent bounce reported later) or `delivery_failed` (the rail that was delivering failed for good: a network rail refused the message or ran out of retries, a `TRP` identity host did not answer, or the counterparty's `TRP` host did not accept the inquiry). Otherwise the `discovery` trace decides it: `awaiting_end_user` (no step named a counterparty Didit can reach), `directory_match_no_route` (a step named a directory VASP that has no route), `picked_member_without_proven_wallet` (the end user picked a Didit customer that has not proven control of the wallet), or `no_directory_match` when the trace gives no other reason. A resend moves the transfer on, so the reason clears. See [Why a transfer is stuck](/transaction-monitoring/travel-rule#why-a-transfer-is-stuck)." + }, + "next_actions": { + "type": "array", + "description": "What moves the transfer on; `[]` for any status but `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE`. `email_undeliverable` offers `update_compliance_email`, then `resend`. `delivery_failed` offers `resend`. A discovery reason offers `select_counterparty_vasp`, `declare_self_hosted`, `add_vasp_hint` and `resend`.", + "items": { + "$ref": "#/components/schemas/TravelRuleNextAction" + } + }, + "chain_details": { + "$ref": "#/components/schemas/TravelRuleChainDetails" + } + } + }, + "NetworkListCounts": { + "type": "object", + "description": "Network counts for the list's views and filter options, each network counted once. Returned only when `summary` is not `false`. `scopes` count every network visible to the application. `views` count the visible networks inside the selected `scope`. `facets` count the networks in the selected `scope` and `status` view (dismissed networks are left out unless `status` includes them). The other list filters do not change these counts.", + "properties": { + "scopes": { + "type": "object", + "description": "Visible networks by product.", + "properties": { + "all": { + "type": "integer" + }, + "identity": { + "type": "integer", + "description": "Every other network." + }, + "transactions": { + "type": "integer", + "description": "Networks whose members are all transaction parties." + } + } + }, + "views": { + "type": "object", + "description": "Visible networks inside the selected `scope`, by status, plus two saved views.", + "properties": { + "all": { + "type": "integer" + }, + "active": { + "type": "integer" + }, + "in_review": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "dismissed": { + "type": "integer" + }, + "mine": { + "type": "integer", + "description": "Networks assigned to the calling console member. Always 0 for an API key." + }, + "high_risk": { + "type": "integer", + "description": "Networks with a risk score of 80 or more that are not dismissed." + } + } + }, + "facets": { + "type": "object", + "description": "Counts for each filter option, inside the selected `scope` and status view.", + "properties": { + "total": { + "type": "integer", + "description": "Networks in the selected `scope` and status view." + }, + "last_activity_days": { + "type": "object", + "description": "Networks with activity in the last 7, 30 and 90 days, keyed by the number of days.", + "properties": { + "7": { + "type": "integer" + }, + "30": { + "type": "integer" + }, + "90": { + "type": "integer" + } + } + }, + "risk_min": { + "type": "object", + "description": "Networks with a risk score of at least 50 and at least 80, keyed by the threshold.", + "properties": { + "50": { + "type": "integer" + }, + "80": { + "type": "integer" + } + } + }, + "signal_types": { + "type": "object", + "description": "Networks with at least one signal of each type.", + "properties": { + "ip_address": { + "type": "integer" + }, + "device": { + "type": "integer" + }, + "face": { + "type": "integer" + }, + "document_number": { + "type": "integer" + }, + "phone": { + "type": "integer" + }, + "email": { + "type": "integer" + }, + "address": { + "type": "integer" + } + } + }, + "priority": { + "type": "object", + "description": "Networks by priority. `none` counts networks without a priority.", + "properties": { + "low": { + "type": "integer" + }, + "medium": { + "type": "integer" + }, + "high": { + "type": "integer" + }, + "none": { + "type": "integer" + } + } + }, + "assignee": { + "type": "object", + "description": "Networks by assignee.", + "properties": { + "me": { + "type": "integer", + "description": "Assigned to the calling console member. Always 0 for an API key." + }, + "unassigned": { + "type": "integer", + "description": "Assigned to nobody." + }, + "users": { + "type": "array", + "description": "One entry per assignee, most networks first.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "full_name": { + "type": "string", + "description": "The assignee's name, or their email when no name is set." + }, + "email": { + "type": "string" + }, + "count": { + "type": "integer" + } + } + } + } + } + }, + "has_case": { + "type": "integer", + "description": "Networks this application opened a case for." + }, + "min_size": { + "type": "object", + "description": "Networks with at least 10 members, keyed by the minimum size.", + "properties": { + "10": { + "type": "integer" + } + } + } + } + } + } + }, + "NetworkTag": { + "type": "object", + "description": "One of your application's tags on the network.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "The tag's UUID, the same value you pass as `tag_uuid`." + }, + "name": { + "type": "string" + }, + "color": { + "type": "string", + "description": "Hex color, for example `#6366F1`." + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ], + "description": "`didit` for a Didit preset tag, `custom` for a tag your team created." + }, + "added_by": { + "type": "string", + "nullable": true, + "description": "Email of the console member who added the tag. Null when an API key added it." + }, + "added_at": { + "type": "string", + "format": "date-time" + } + } + }, + "NetworkOutcomes": { + "type": "object", + "description": "Where the network's people and businesses stand. Each member you may see counts once, by the status of their most recent verification that reached a decision. Transaction parties are not counted.", + "properties": { + "approved": { + "type": "integer", + "description": "Members whose most recent decided verification was approved." + }, + "declined": { + "type": "integer", + "description": "Members whose most recent decided verification was declined." + }, + "in_review": { + "type": "integer", + "description": "Members whose most recent decided verification is in review." + }, + "total": { + "type": "integer", + "description": "Members with at least one verification, decided or not. A member whose verifications are all undecided (not started, in progress, expired or abandoned) counts here and in no other key." + } + } + }, + "NetworkPatternScore": { + "type": "object", + "description": "How alike one kind of the members' data is across the whole network. A pattern is not a signal: no shared value links the members.", + "properties": { + "type": { + "type": "string", + "enum": [ + "background" + ], + "description": "`background`: the mean similarity of every pair of member selfie backgrounds, with the person masked out. It reads the newest selfie of each person, at most the 50 newest, and leaves out age assurance sessions. It covers the whole network, not only the members of your applications." + }, + "score": { + "type": "number", + "nullable": true, + "minimum": 0, + "maximum": 1, + "description": "From 0 to 1, rounded to 4 decimals. Null when `reason` is set." + }, + "members": { + "type": "integer", + "nullable": true, + "description": "How many members' selfies the score compared. A selfie that cannot be read, or shows too little background, is not compared. Null while `computing`; 0 or 1 with `not_enough_selfies`." + }, + "reason": { + "type": "string", + "enum": [ + "computing", + "not_enough_selfies" + ], + "nullable": true, + "description": "Why there is no score. `computing`: the score for the current members is being computed; read the network again later. `not_enough_selfies`: fewer than two members have a usable selfie. Null when there is a score." + }, + "computed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the score was computed. Null while `computing`." + } + } + }, + "NetworkDetailResponse": { + "description": "The network detail, plus the organization's sharing mode, whether cross-organization signals apply to it, your application's tags on the network, member outcomes and pattern scores.", + "allOf": [ + { + "$ref": "#/components/schemas/NetworkDetail" + }, + { + "type": "object", + "properties": { + "sharing_mode": { + "type": "string", + "enum": [ + "off", + "private", + "global" + ], + "description": "The organization's cross-organization sharing mode. Only `global` compares networks across organizations; `private` and `off` keep them to the organization's own history.", + "example": "global" + }, + "cross_org_available": { + "type": "boolean", + "description": "Whether cross-organization signals apply to this organization. They need both the Enterprise plan and the `global` sharing mode.", + "example": true + }, + "cross_org_unavailable_reason": { + "type": "string", + "enum": [ + "plan", + "sharing_mode" + ], + "nullable": true, + "description": "Why cross-organization signals do not apply, checked in this order. `plan`: the organization's plan does not include the cross-organization fraud network (it needs the Enterprise plan), whatever the sharing mode. `sharing_mode`: the plan includes it, but the sharing mode is `private` or `off`. Null when `cross_org_available` is `true`.", + "example": null + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkTag" + }, + "description": "Your application's tags on the network, oldest first. Each application sees only its own tags. `fraud_tags` is separate and unchanged: the tags the network's members carry." + }, + "outcomes": { + "$ref": "#/components/schemas/NetworkOutcomes" + }, + "patterns": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkPatternScore" + }, + "description": "Pattern scores over the whole network. Today one item, `background`." + } + } + } + ] + }, + "NetworkTagRequest": { + "type": "object", + "required": [ + "tag_uuid" + ], + "properties": { + "tag_uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of one of your application's tags." + } + }, + "example": { + "tag_uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7" + } + }, + "NetworkTimeline": { + "title": "Default timeline (no contract)", + "type": "object", + "description": "Without `contract`: the network's own events, oldest first, not paginated.", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkTimelineEvent" + } + } + } + }, + "NetworkTimelinePage": { + "title": "Typed timeline page (contract=2)", + "type": "object", + "description": "With `contract=2`: one page of typed events, newest first.", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkTypedTimelineEvent" + } + }, + "next_cursor": { + "type": "string", + "nullable": true, + "description": "Pass as `cursor` to read the next page. Null on the last page." + } + } + }, + "NetworkTypedTimelineEvent": { + "type": "object", + "description": "One typed timeline event: the four default fields, plus its id, when Didit stored it, the member account and record it belongs to, and the detail block of its event type. The other detail blocks are null.", + "properties": { + "id": { + "type": "string", + "description": "`:`, unique within the timeline.", + "example": "session:3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "event_type": { + "type": "string", + "enum": [ + "network_detected", + "signal_observed", + "network_active", + "network_in_review", + "network_resolved", + "network_dismissed", + "verification_attempt", + "transaction", + "login", + "rule_hit", + "case_linked", + "note_added" + ] + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "description": "When it happened." + }, + "ingested_at": { + "type": "string", + "format": "date-time", + "description": "When Didit stored it." + }, + "label": { + "type": "string", + "description": "A short English label, for example `Verification attempt 2 of 3`." + }, + "metadata": { + "type": "object", + "description": "For the network's own events, the same metadata as the default timeline. Empty otherwise." + }, + "account": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineAccount" + } + ], + "nullable": true, + "description": "The member account. Null for the network's own events, `case_linked` and `note_added`." + }, + "source_ref": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineSourceRef" + } + ], + "nullable": true, + "description": "The source record. Null for the network's own events and `note_added`." + }, + "verification_attempt": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineAttempt" + } + ], + "nullable": true, + "description": "Set on `verification_attempt` events." + }, + "transaction": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineTransaction" + } + ], + "nullable": true, + "description": "Set on `transaction` events." + }, + "login": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineLogin" + } + ], + "nullable": true, + "description": "Set on `login` events." + }, + "rule_hit": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineRuleHit" + } + ], + "nullable": true, + "description": "Set on `rule_hit` events." + }, + "case_linked": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineCaseLinked" + } + ], + "nullable": true, + "description": "Set on `case_linked` events." + }, + "note_added": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineNoteAdded" + } + ], + "nullable": true, + "description": "Set on `note_added` events. Never returned to an API key." + } + } + }, + "NetworkTimelineAccount": { + "type": "object", + "description": "The member account the event belongs to.", + "properties": { + "node_id": { + "type": "string", + "description": "`:`, where kind is `vendor_user`, `vendor_business` or `transaction_party`.", + "example": "vendor_user:3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "node_type": { + "type": "string", + "enum": [ + "account", + "business_account", + "transaction_party" + ] + }, + "uuid": { + "type": "string", + "format": "uuid" + }, + "vendor_data": { + "type": "string", + "nullable": true + }, + "display_name": { + "type": "string", + "nullable": true + }, + "ownership": { + "type": "string", + "enum": [ + "owned", + "organization" + ], + "description": "`owned` when the record belongs to the application you called (for a console member, to an application they were granted). `organization` when it belongs to another application of your organization." + }, + "route": { + "$ref": "#/components/schemas/NetworkTimelineRoute" + } + } + }, + "NetworkTimelineSourceRef": { + "type": "object", + "description": "The record the event was read from.", + "properties": { + "kind": { + "type": "string", + "enum": [ + "session", + "business_session", + "transaction", + "monitoring_event", + "case" + ] + }, + "id": { + "type": "string", + "format": "uuid" + }, + "ownership": { + "type": "string", + "enum": [ + "owned", + "organization" + ], + "description": "`owned` when the record belongs to the application you called (for a console member, to an application they were granted). `organization` when it belongs to another application of your organization." + }, + "route": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineRoute" + } + ], + "description": "Absent for `monitoring_event` and `case`." + } + } + }, + "NetworkTimelineRoute": { + "type": "object", + "description": "Where the record opens: its kind, its UUID and the application it belongs to.", + "properties": { + "kind": { + "type": "string", + "description": "`user`, `business` or `transaction_party` for an account; `session`, `business_session` or `transaction` for a source record." + }, + "id": { + "type": "string", + "format": "uuid" + }, + "application_id": { + "type": "string", + "format": "uuid" + } + } + }, + "NetworkTimelineAttempt": { + "type": "object", + "description": "One verification session of the account.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The session UUID, or the business session UUID for a company." + }, + "session_number": { + "type": "integer", + "nullable": true + }, + "attempt": { + "type": "integer", + "description": "This session's position among the account's sessions, in creation order." + }, + "attempt_count": { + "type": "integer", + "description": "How many sessions the account has." + }, + "status": { + "type": "string", + "description": "The session status, for example `Approved` or `Declined`." + }, + "status_changed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "decline_reasons": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkTimelineDeclineReason" + }, + "description": "For a declined session only: its warnings, oldest first, once per warning. Empty otherwise." + } + } + }, + "NetworkTimelineDeclineReason": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "description": "The warning code." + }, + "feature": { + "type": "string", + "description": "The feature that raised the warning." + }, + "short_description": { + "type": "string" + } + } + }, + "NetworkTimelineTransaction": { + "type": "object", + "description": "A transaction a member account took part in, dated by its transaction date.", + "properties": { + "transaction_id": { + "type": "string", + "format": "uuid" + }, + "transaction_number": { + "type": "integer", + "nullable": true + }, + "transaction_type": { + "type": "string" + }, + "action_type": { + "type": "string" + }, + "amount": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineAmount" + } + ], + "nullable": true, + "description": "Null when the transaction has no amount." + }, + "status": { + "type": "string" + }, + "decision_reason_code": { + "type": "string", + "nullable": true + }, + "decision_reason_label": { + "type": "string", + "nullable": true + }, + "party_role": { + "type": "string", + "description": "The account's role in the transaction: `APPLICANT`, `REMITTER`, `BENEFICIARY` or `COUNTERPARTY`." + } + } + }, + "NetworkTimelineAmount": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Decimal amount as a string." + }, + "currency": { + "type": "string" + }, + "direction": { + "type": "string", + "nullable": true + } + } + }, + "NetworkTimelineLogin": { + "type": "object", + "description": "A sign-in event you posted for a person through the events API.", + "properties": { + "action": { + "type": "string", + "description": "`.`: `auth.login` or `security.login_succeeded` for a sign-in, `auth.login_failed` or `security.login_failed` for a failed one." + }, + "succeeded": { + "type": "boolean" + }, + "status": { + "type": "string", + "description": "The event's status." + }, + "ip": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineIP" + } + ], + "nullable": true, + "description": "Null when the event has no IP address." + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineDevice" + } + ], + "nullable": true, + "description": "Null when the event has no device fingerprint or context." + }, + "is_first_login": { + "type": "boolean", + "description": "The account's first successful sign-in on record." + }, + "is_new_device": { + "type": "boolean", + "description": "A successful sign-in from a device none of the account's earlier successful sign-ins came from. Never true on the first sign-in." + } + } + }, + "NetworkTimelineIP": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "country_code": { + "type": "string", + "nullable": true + }, + "city": { + "type": "string", + "nullable": true + }, + "connection_type": { + "type": "string", + "nullable": true, + "description": "The connection type label for the address, as free text." + }, + "is_vpn_or_tor": { + "type": "boolean" + }, + "is_data_center": { + "type": "boolean" + } + } + }, + "NetworkTimelineDevice": { + "type": "object", + "properties": { + "fingerprint": { + "type": "string", + "nullable": true + }, + "label": { + "type": "string", + "nullable": true, + "description": "A readable device label built from the event's device context." + } + } + }, + "NetworkTimelineRuleHit": { + "type": "object", + "description": "A live rule that matched a member's transaction or event. Test-mode matches are not included.", + "properties": { + "rule_id": { + "type": "string", + "format": "uuid" + }, + "rule_title": { + "type": "string" + }, + "rule_category": { + "type": "string" + }, + "rule_source": { + "type": "string" + }, + "severity": { + "type": "string", + "nullable": true + }, + "score_delta": { + "type": "integer" + }, + "status_target": { + "type": "string", + "nullable": true + }, + "action_summary": { + "type": "string", + "nullable": true + } + } + }, + "NetworkTimelineCaseLinked": { + "type": "object", + "description": "A case opened for the network in your organization. `source_ref.ownership` says whether it belongs to the application you called.", + "properties": { + "case_id": { + "type": "string", + "format": "uuid" + }, + "case_number": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string" + }, + "author_email": { + "type": "string", + "nullable": true + }, + "author_name": { + "type": "string", + "nullable": true + } + } + }, + "NetworkTimelineNoteAdded": { + "type": "object", + "description": "A note left on the network. The note text is not included.", + "properties": { + "note_id": { + "type": "string", + "format": "uuid" + }, + "author_email": { + "type": "string" + } + } + }, + "FeatureStatusOverride": { + "type": "object", + "description": "One override of a check (feature) status made with `PATCH /v3/session/{sessionId}/features/{nodeId}/update-status/`: what it changes, whether it applied or still waits for the person to finish the flow, who asked and why.", + "required": [ + "id", + "node_id", + "feature_type", + "feature_model", + "previous_status", + "new_status", + "state", + "forced", + "reason", + "requested_by", + "requested_at", + "applied_at", + "cancelled_at", + "cancel_reason" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Identifier of the override. Pass it to the cancel endpoint.", + "example": "7c9e6679-7425-40de-944b-e07fc1f90ae7" + }, + "node_id": { + "type": "string", + "description": "The workflow node the request named (`default` for a step without a node).", + "example": "feature_ocr_1" + }, + "feature_type": { + "type": "string", + "description": "The `feature_type` the request passed, or an empty string when it passed none.", + "example": "" + }, + "feature_model": { + "type": "string", + "description": "The kind of check that was overridden, for example `KYC`, `Face`, `AML` or `GeolocationCheck`.", + "example": "KYC" + }, + "previous_status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "The check's status when the override was requested.", + "example": "In Review" + }, + "new_status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "description": "The status the override sets.", + "example": "Approved" + }, + "state": { + "type": "string", + "enum": [ + "applied", + "pending_until_flow_finishes", + "cancelled" + ], + "description": "`applied`: the check has the new status. `pending_until_flow_finishes`: the override waits and applies when the person finishes the flow. `cancelled`: it waited and never applied (see `cancel_reason`).", + "example": "applied" + }, + "forced": { + "type": "boolean", + "description": "`true` when the override applied at once while the person was still in the flow.", + "example": false + }, + "reason": { + "type": "string", + "nullable": true, + "description": "The `comment` sent with the request, or `null`.", + "example": "Document manually verified by compliance." + }, + "requested_by": { + "type": "object", + "nullable": true, + "description": "Who requested the override, or `null` when that is not known.", + "properties": { + "email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email of the console user, or `null` for API keys and the system.", + "example": "reviewer@example.com" + }, + "name": { + "type": "string", + "nullable": true, + "description": "Display name of the requester, or `null`.", + "example": "Alex Reviewer" + }, + "type": { + "type": "string", + "enum": [ + "CONSOLE_USER", + "API_KEY", + "SYSTEM" + ], + "description": "Who made the request: a console user, an API key, or Didit itself.", + "example": "CONSOLE_USER" + } + } + }, + "requested_at": { + "type": "string", + "format": "date-time", + "description": "When the override was requested.", + "example": "2026-10-13T09:15:00Z" + }, + "applied_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the check took the new status, or `null` while it waits or once it is cancelled.", + "example": "2026-10-13T09:15:00Z" + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When a waiting override was cancelled, or `null`.", + "example": null + }, + "cancel_reason": { + "type": "string", + "nullable": true, + "enum": [ + "cancelled_by_reviewer", + "replaced", + "check_changed", + "session_closed", + null + ], + "description": "Why a waiting override never applied, or `null`. `cancelled_by_reviewer`: cancelled with the cancel endpoint. `replaced`: a newer override on the same check replaced it. `check_changed`: the person redid the check before finishing the flow. `session_closed`: the session left the flow before the person finished it (abandoned, expired, or decided mid-flow).", + "example": null + } + } + }, + "SandboxSimulateRequest": { + "type": "object", + "description": "Force a sandbox session's status. Every field is optional, but at least one of `new_status`, `scenario` or `risk` is required.", + "properties": { + "new_status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned" + ], + "description": "The status to set. When you leave it out, it defaults to the default status of the risk case (`risk`, or the first risk the scenario's magic values carry), else to the scenario's default status.", + "example": "Declined" + }, + "comment": { + "type": "string", + "maxLength": 500, + "description": "Note added to the activity entry the status change records.", + "example": "Testing the decline path" + }, + "scenario": { + "type": "string", + "maxLength": 100, + "description": "A scenario slug from the catalog (`GET /v1/sandbox/scenarios/`). It supplies the default status and, without `risk`, the warning its magic values carry. An unknown slug returns `400`.", + "example": "decline_aml_hit" + }, + "risk": { + "type": "string", + "maxLength": 128, + "description": "A risk case from the catalog. Its warning is added to the session, with a log type that follows `new_status`. An unknown value returns `400`.", + "example": "POSSIBLE_MATCH_FOUND" + }, + "node_id": { + "type": "string", + "maxLength": 128, + "description": "Workflow node the risk warning is attached to. Defaults to the session's current node.", + "example": "feature_aml_1" + }, + "send_webhooks": { + "type": "boolean", + "default": true, + "description": "`true` sends exactly the webhooks a real status change sends to the sandbox application's destinations: `status.updated`, plus the `user.status.updated` / `user.data.updated` (or `business.status.updated` / `business.data.updated`) events the change causes, for destinations subscribed to them. `false` changes the status without logging or sending any webhook: the missed-webhook recovery never re-sends that decision, the console still updates live, and the next status change of the session sends webhooks as usual.", + "example": true + } + } + }, + "SandboxSimulateResponse": { + "type": "object", + "required": [ + "session_id", + "previous_status", + "status", + "scenario", + "risk", + "send_webhooks" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "previous_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "The session's status before the simulation.", + "example": "In Progress" + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned" + ], + "description": "The status the simulation set.", + "example": "Declined" + }, + "scenario": { + "type": "string", + "nullable": true, + "description": "The scenario slug you passed, or `null`.", + "example": "decline_aml_hit" + }, + "risk": { + "type": "string", + "nullable": true, + "description": "The risk case applied (`risk`, or the one taken from the scenario), or `null` when none.", + "example": "POSSIBLE_MATCH_FOUND" + }, + "send_webhooks": { + "type": "boolean", + "description": "The `send_webhooks` value the simulation applied.", + "example": false + } + } + }, + "CaseTeamSummary": { + "type": "object", + "nullable": true, + "description": "The review team that owns the case, or null when no team owns it. Routing sets it when a routing rule places the case with one of its teams. Change or clear it with `PATCH .../cases/{case_uuid}/` (`team`).", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + } + } + }, + "CaseChecker": { + "type": "object", + "nullable": true, + "description": "The member named to approve or reject the pending resolution. Set only while a four-eyes resolution waits on a named checker. Null when nothing is pending, while the pending resolution waits on the checker queue, and once the resolution is approved, rejected, or returned for a new review because the evidence changed.", + "properties": { + "member_uuid": { + "type": "string", + "format": "uuid", + "description": "The member's user uuid: the value you pass as `checker` when you resolve the case." + }, + "name": { + "type": "string", + "nullable": true, + "description": "The member's full name, or null when it is not known." + }, + "email": { + "type": "string", + "format": "email" + } + } + }, + "ServiceStatusFeed": { + "type": "object", + "description": "Public service status: the state of each Didit service, the overall state, and the incidents that are ongoing or were resolved in the last 7 days.", + "required": [ + "status", + "updated_at", + "services", + "incidents" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "operational", + "degraded", + "down" + ], + "description": "The worst state of any service right now, decided by the ongoing incidents only.", + "example": "degraded" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When Didit built this response (ISO 8601, UTC). Responses are cached for 30 seconds, so it can be up to 30 seconds old.", + "example": "2026-10-13T09:41:07.512345Z" + }, + "services": { + "type": "array", + "description": "One entry per service, every service always listed.", + "items": { + "$ref": "#/components/schemas/ServiceStatusService" + } + }, + "incidents": { + "type": "array", + "description": "Newest `started_at` first. Every ongoing incident, plus the incidents resolved in the last 7 days (at most 50 resolved incidents). Empty when nothing happened in that time.", + "items": { + "$ref": "#/components/schemas/ServiceStatusIncident" + } + } + } + }, + "ServiceStatusService": { + "type": "object", + "required": [ + "key", + "name", + "status" + ], + "properties": { + "key": { + "type": "string", + "enum": [ + "service_id_verification", + "service_liveness_face_match", + "service_aml_screening", + "service_kyb_registries", + "service_database_validation", + "service_phone_delivery", + "service_email_delivery", + "service_transaction_monitoring" + ], + "description": "Stable service key. Match on this, never on the display name. `service_id_verification`: ID verification, `service_liveness_face_match`: Liveness and face match, `service_aml_screening`: AML screening, `service_kyb_registries`: KYB registries, `service_database_validation`: Database validation, `service_phone_delivery`: Phone verification delivery, `service_email_delivery`: Email verification delivery, `service_transaction_monitoring`: Transaction monitoring.", + "example": "service_database_validation" + }, + "name": { + "type": "string", + "description": "English display name of the service.", + "example": "Database validation" + }, + "status": { + "type": "string", + "enum": [ + "operational", + "degraded", + "down" + ], + "description": "`operational` when no incident is ongoing for the service, otherwise the worst state of its ongoing incidents.", + "example": "degraded" + } + } + }, + "ServiceStatusIncident": { + "type": "object", + "required": [ + "id", + "service", + "service_name", + "state", + "status", + "title", + "body", + "affected", + "country_name", + "started_at", + "resolved_at", + "duration_minutes" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Incident ID.", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "service": { + "type": "string", + "enum": [ + "service_id_verification", + "service_liveness_face_match", + "service_aml_screening", + "service_kyb_registries", + "service_database_validation", + "service_phone_delivery", + "service_email_delivery", + "service_transaction_monitoring" + ], + "description": "Key of the affected service (see `services[].key`).", + "example": "service_database_validation" + }, + "service_name": { + "type": "string", + "description": "English display name of the affected service.", + "example": "Database validation" + }, + "state": { + "type": "string", + "enum": [ + "degraded", + "down" + ], + "description": "How the service is affected. A resolved incident keeps the last state it had.", + "example": "degraded" + }, + "status": { + "type": "string", + "enum": [ + "ongoing", + "resolved" + ], + "description": "`ongoing` until the service recovers, then `resolved`.", + "example": "ongoing" + }, + "title": { + "type": "string", + "description": "English headline. To translate it, build your own text from `service`, `state` and `country_name`.", + "example": "Database validation is degraded in Brazil." + }, + "body": { + "type": "string", + "description": "English detail line: since when checks may be slow or fail, or when it recovered.", + "example": "Checks against Brazil (CPF) may be slow or fail since 08:55 UTC." + }, + "affected": { + "type": "string", + "description": "Countries or registries affected, for example `Brazil (CPF)`. Empty when the incident is not tied to a country or registry.", + "example": "Brazil (CPF)" + }, + "country_name": { + "type": "string", + "description": "Country affected, in English. Empty when the incident is not tied to a country.", + "example": "Brazil" + }, + "started_at": { + "type": "string", + "format": "date-time", + "description": "When the incident started (ISO 8601, UTC).", + "example": "2026-10-13T08:55:00Z" + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the service recovered (ISO 8601, UTC). `null` while the incident is ongoing." + }, + "duration_minutes": { + "type": "integer", + "nullable": true, + "description": "Whole minutes from `started_at` to `resolved_at`. `null` while the incident is ongoing." + } + } + }, + "TravelRuleJoinInvitation": { + "type": "object", + "description": "What the join page shows before anything is typed.", + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "The address the invitation went to." + }, + "vasp_name": { + "type": "string" + }, + "source": { + "type": "string", + "description": "Where the invitation came from, for example `pickup`." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "prefill": { + "type": "object", + "properties": { + "vasp_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "legal_name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "jurisdiction": { + "type": "string" + }, + "domains": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "TravelRuleJoinRegisterRequest": { + "type": "object", + "required": [ + "email", + "password" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "maxLength": 254, + "description": "Must be the address the invitation went to." + }, + "password": { + "type": "string", + "minLength": 8, + "maxLength": 128 + } + } + }, + "TravelRuleJoinVerifyRequest": { + "type": "object", + "required": [ + "email", + "code" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "maxLength": 254 + }, + "code": { + "type": "string", + "minLength": 6, + "maxLength": 6, + "description": "The 6-character code emailed after registering." + } + } + }, + "TravelRuleJoinCredentials": { + "type": "object", + "description": "The new free organization and the credentials its application calls the API with.", + "properties": { + "organization": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + } + } + }, + "application": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + } + } + }, + "client_id": { + "type": "string" + }, + "api_key": { + "type": "string", + "description": "Send as the `x-api-key` header on the workspace endpoints." + } + } + }, + "TravelRuleWorkspaceClaimRequest": { + "type": "object", + "required": [ + "vasp_id" + ], + "properties": { + "vasp_id": { + "type": "string", + "format": "uuid", + "description": "The directory entry this workspace is. Read ids from `GET /v3/travel-rule/vasps/`." + } + } + }, + "TravelRuleWorkspaceDomainConfirmRequest": { + "type": "object", + "required": [ + "code" + ], + "properties": { + "code": { + "type": "string", + "minLength": 6, + "maxLength": 6 + } + } + }, + "TravelRuleWorkspaceBillingRequest": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": true, + "description": "Only `true` is accepted: outbound billing cannot be turned back off through the API." + } + } + }, + "TravelRuleWorkspaceDomainCodeResponse": { + "type": "object", + "properties": { + "domain": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "unverified", + "code_sent", + "verified" + ] + }, + "txt": { + "$ref": "#/components/schemas/TravelRuleWorkspaceTxtRecord" + } + } + }, + "TravelRuleWorkspaceTxtRecord": { + "type": "object", + "description": "The TXT record to publish for the DNS proof. Empty until a proof has been started.", + "properties": { + "host": { + "type": "string", + "example": "didit-tr-verify.example.com" + }, + "value": { + "type": "string" + } + } + }, + "TravelRuleWorkspace": { + "type": "object", + "description": "The free Travel Rule workspace of an application that joined free.", + "properties": { + "joined_free": { + "type": "boolean" + }, + "work_email": { + "type": "string", + "format": "email" + }, + "joined_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "claim": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "unclaimed", + "pending_review", + "claimed", + "rejected" + ] + }, + "proved_by_domain": { + "type": "boolean" + }, + "claimed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reviewed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "review_note": { + "type": "string" + }, + "vasp_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "vasp_name": { + "type": "string" + } + } + }, + "prefill": { + "type": "object", + "description": "Profile values taken from the directory entry: `vasp_id`, `legal_name`, `lei`, `jurisdiction`, `compliance_email`, `domains`. Nothing is written until the claim is made." + }, + "domain": { + "type": "object", + "properties": { + "domain": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "unverified", + "code_sent", + "verified" + ] + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "txt": { + "$ref": "#/components/schemas/TravelRuleWorkspaceTxtRecord" + } + } + }, + "billing_enabled": { + "type": "boolean", + "description": "Receiving Travel Rule data is free. Sending is billed only after billing is enabled." + }, + "billing_enabled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "didit_network": { + "type": "object", + "description": "Whether the Didit network lists this VASP and, when not, each condition it still misses." + }, + "settings": { + "$ref": "#/components/schemas/TravelRuleSettingsDetail" + } + } + }, + "InvestigatorDecision": { + "type": "object", + "description": "How the item left the Investigator queue. `decided_by`, `original_outcome` and `overrides` are returned by the accept and override endpoints and the report; the `investigator` block of a session decision carries the other fields.", + "properties": { + "outcome": { + "type": "string", + "enum": [ + "accepted", + "edited", + "overridden", + "decided_elsewhere", + "automated" + ], + "description": "`accepted` or `edited` took the recommendation (`edited` with a rewritten narrative), `overridden` took another action, `decided_elsewhere` is a decision taken outside the Investigator while the report was open, `automated` is a decision the Investigator took on its own." + }, + "action": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ] + }, + "actor_email": { + "type": "string", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "override_category": { + "type": "string", + "enum": [ + "missing_context", + "needs_information", + "policy_differs", + "evidence_misread", + "other", + "" + ], + "nullable": true + }, + "override_reason": { + "type": "string", + "nullable": true + }, + "final_status": { + "type": "string", + "description": "The item's status after the decision." + }, + "agreed": { + "type": "boolean", + "description": "Whether the decision matched the recommendation.", + "nullable": true + }, + "decided_at": { + "type": "string", + "format": "date-time" + }, + "billed": { + "type": "boolean", + "description": "The review was charged." + }, + "decided_by": { + "type": "string", + "enum": [ + "investigator", + "member" + ] + }, + "original_outcome": { + "type": "object", + "description": "The decision the Investigator took on its own, unchanged by later overrides. Null for a decision the team took.", + "properties": { + "action": { + "type": "string" + }, + "final_status": { + "type": "string" + }, + "decided_at": { + "type": "string", + "format": "date-time" + } + }, + "nullable": true + }, + "overrides": { + "type": "array", + "description": "Every override of a decision the Investigator took on its own, oldest first.", + "items": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "actor_email": { + "type": "string", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "override_category": { + "type": "string" + }, + "override_reason": { + "type": "string" + }, + "previous_status": { + "type": "string" + }, + "final_status": { + "type": "string" + }, + "agreed": { + "type": "boolean", + "nullable": true + }, + "overridden_at": { + "type": "string", + "format": "date-time" + } + } + } + } + } + }, + "InvestigatorBlock": { + "type": "object", + "nullable": true, + "description": "The item's latest Investigator review, or null when it never had one. Carried by the V3 session and business session decision (and so by `status.updated` webhooks).", + "properties": { + "run_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "running", + "completed", + "failed" + ] + }, + "stage": { + "type": "string", + "enum": [ + "shadow", + "live" + ] + }, + "queue": { + "type": "string", + "enum": [ + "kyc", + "kyb", + "aml", + "transactions", + "cases" + ] + }, + "is_open": { + "type": "boolean" + }, + "recommendation": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ], + "nullable": true + }, + "recommendation_detail": { + "type": "string", + "nullable": true + }, + "confidence": { + "type": "string", + "enum": [ + "high", + "moderate", + "low" + ], + "nullable": true + }, + "summary": { + "type": "string", + "nullable": true + }, + "ready_to_accept": { + "type": "boolean" + }, + "prepared_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "automated": { + "type": "boolean", + "description": "The Investigator decided on its own, even if the decision was later overridden." + }, + "decided_by": { + "type": "string", + "enum": [ + "investigator", + "member" + ], + "nullable": true + }, + "decision": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/InvestigatorDecision" + } + ], + "type": "object" + } + } + }, + "InvestigatorQueueRow": { + "type": "object", + "properties": { + "run_id": { + "type": "string", + "format": "uuid" + }, + "kind": { + "type": "string", + "enum": [ + "session", + "business_session", + "transaction", + "case" + ] + }, + "queue": { + "type": "string", + "enum": [ + "kyc", + "kyb", + "aml", + "transactions", + "cases" + ] + }, + "stage": { + "type": "string", + "enum": [ + "shadow", + "live" + ] + }, + "subject": { + "type": "object", + "description": "The item under review: `id`, `number`, `status`, `name` and, depending on the kind, `country`, `workflow`, `vendor_data`, `txn_id`, `amount`, `currency`, `direction`, `title` or `priority`.", + "additionalProperties": true + }, + "recommendation": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ] + }, + "recommendation_detail": { + "type": "string" + }, + "confidence": { + "type": "string", + "enum": [ + "high", + "moderate", + "low" + ] + }, + "summary": { + "type": "string" + }, + "sources_count": { + "type": "integer" + }, + "ready_to_accept": { + "type": "boolean" + }, + "guardrails": { + "description": "The locked guardrails that hold this item, if any." + }, + "waiting_since": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "prepared_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "InvestigatorRunDetail": { + "allOf": [ + { + "$ref": "#/components/schemas/InvestigatorQueueRow" + }, + { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "is_open": { + "type": "boolean" + }, + "lookups": { + "type": "integer", + "description": "How many sections and linked records it read." + }, + "duration_ms": { + "type": "integer", + "nullable": true + }, + "agent_version": { + "type": "string" + }, + "prepared_before_opened": { + "type": "boolean", + "nullable": true + }, + "report": { + "type": "object", + "additionalProperties": true, + "description": "The report: reasoning, evidence points with their verdict (`ok`, `risk`, `look`, `explained`, `context`) and cited sources, screening hit verdicts (`same`, `not_same`, `unclear`) and suggested next steps." + }, + "trail": { + "description": "What I checked: the overview, sections and records it read, in order." + }, + "decision": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/InvestigatorDecision" + } + ], + "type": "object" + }, + "autonomy": { + "type": "object", + "additionalProperties": true, + "description": "What autonomy made of the report (`result`, `reason`, `tier`, `policy_version`, `rule`, `agreement`, `decisions`, `threshold`, `at`). Null when the queue is not automated.", + "nullable": true + } + } + } + ] + }, + "InvestigatorAcceptRequest": { + "type": "object", + "properties": { + "narrative": { + "type": "string", + "maxLength": 4000, + "description": "A rewritten narrative. Sending one records the decision as edited and accepted." + }, + "send_email": { + "type": "boolean", + "default": false, + "description": "Email the person about the decision." + }, + "email_address": { + "type": "string", + "format": "email", + "description": "Required when `send_email` is true." + }, + "email_language": { + "type": "string", + "default": "en", + "maxLength": 16 + } + } + }, + "InvestigatorOverrideRequest": { + "type": "object", + "required": [ + "action", + "category", + "reason" + ], + "properties": { + "action": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ] + }, + "category": { + "type": "string", + "enum": [ + "missing_context", + "needs_information", + "policy_differs", + "evidence_misread", + "other" + ] + }, + "reason": { + "type": "string", + "minLength": 10, + "maxLength": 500 + }, + "send_email": { + "type": "boolean", + "default": false, + "description": "Email the person about the decision." + }, + "email_address": { + "type": "string", + "format": "email", + "description": "Required when `send_email` is true." + }, + "email_language": { + "type": "string", + "default": "en", + "maxLength": 16 + } + } + }, + "InvestigatorBatchAcceptRequest": { + "type": "object", + "required": [ + "run_ids" + ], + "properties": { + "run_ids": { + "type": "array", + "minItems": 1, + "maxItems": 50, + "items": { + "type": "string", + "format": "uuid" + } + } + } + }, + "InvestigatorBatchAcceptResponse": { + "type": "object", + "properties": { + "accepted": { + "type": "integer" + }, + "skipped": { + "type": "integer" + }, + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "run_id": { + "type": "string", + "format": "uuid" + }, + "result": { + "type": "string", + "enum": [ + "accepted", + "skipped" + ] + }, + "reason": { + "type": "string", + "enum": [ + "not_found", + "forbidden", + "already_decided", + "not_ready", + "guardrail", + "invalid" + ] + }, + "detail": {}, + "action": { + "type": "string" + }, + "final_status": { + "type": "string" + } + } + } + } + } + }, + "InvestigatorSettings": { + "type": "object", + "properties": { + "stage": { + "type": "string", + "enum": [ + "none", + "shadow", + "live" + ] + }, + "stage_changed_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "nullable": true + }, + "stage_changed_by": { + "type": "string", + "readOnly": true + }, + "monthly_spend_limit": { + "type": "string", + "format": "decimal", + "description": "Monthly limit in your billing currency, 0.01 to 1,000,000. Null turns it off.", + "nullable": true + }, + "spend_limit": { + "type": "object", + "readOnly": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "limit": { + "type": "string", + "format": "decimal", + "nullable": true + }, + "spent": { + "type": "string", + "format": "decimal" + }, + "reviews": { + "type": "integer" + }, + "reserved": { + "type": "string", + "format": "decimal", + "description": "The price of every open live report, held back because accepting it charges it." + }, + "currency": { + "type": "string" + }, + "paused": { + "type": "boolean", + "description": "At the limit: no new review starts." + } + } + } + } + }, + "TravelRuleUtxoEntry": { + "type": "object", + "description": "One input or output of a UTXO transaction, as the chain recorded it.", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string", + "maxLength": 128 + }, + "amount": { + "type": "string", + "nullable": true, + "description": "Decimal string in the coin's own unit (BTC, not satoshis). Optional.", + "example": "0.0125" + }, + "txid": { + "type": "string", + "maxLength": 128, + "description": "Optional. The transaction an input spends." + }, + "vout": { + "type": "integer", + "nullable": true, + "minimum": 0, + "description": "Optional. The output index an input spends." + } + } + }, + "TravelRuleUtxo": { + "type": "object", + "description": "The inputs and outputs of a UTXO transfer (BTC, LTC, BCH, DOGE). Only accepted on those chains: on any other chain it is a `400`. From them Didit infers the originator wallet (the declared one, else the input address that contributed the most) and the beneficiary output (the declared one, else the only output that does not pay an input address back as change, else the one output paying the transfer amount). The result is on the transfer's `chain_details`.", + "required": [ + "outputs" + ], + "properties": { + "inputs": { + "type": "array", + "maxItems": 1000, + "items": { + "$ref": "#/components/schemas/TravelRuleUtxoEntry" + }, + "description": "Optional. Without inputs no originator is inferred and no change output is recognised." + }, + "outputs": { + "type": "array", + "minItems": 1, + "maxItems": 1000, + "items": { + "$ref": "#/components/schemas/TravelRuleUtxoEntry" + } + } + } + }, + "TravelRuleChainSide": { + "type": "object", + "description": "One side's wallet as Didit resolved it from the chain data.", + "properties": { + "address": { + "type": "string", + "description": "The wallet. On a memo network the classic address (an XRP X-address is split into its classic address and its tag); on Lightning the payee node id or Lightning Address." + }, + "memo": { + "type": "string", + "description": "The destination tag or memo that is part of the wallet; `\"\"` for none." + }, + "source": { + "type": "string", + "enum": [ + "declared", + "x_address", + "largest_input", + "only_non_change_output", + "amount_match", + "invoice_payee", + "updated" + ], + "description": "Where the wallet came from: `declared` (your request), `x_address` (split from an XRP X-address), `largest_input` (the UTXO input address that contributed the most), `only_non_change_output` (the only UTXO output that is not change), `amount_match` (the one UTXO output paying the transfer amount), `invoice_payee` (the payee a Lightning payment request names), `updated` (set later with `action: update_originator_wallet`)." + } + } + }, + "TravelRuleChainDetails": { + "type": "object", + "description": "How the transfer's chain names its two wallets, when that is more than one address paying another. Empty (`{}`) for an account-chain transfer (EVM, Solana, Tron), where the two addresses are the wallets. Matching, routing and the address book read the wallets from here first.", + "properties": { + "chain": { + "type": "string", + "description": "Canonical chain, e.g. `BTC`, `XRP`, or `LIGHTNING` for a Lightning transfer." + }, + "shape": { + "type": "string", + "enum": [ + "utxo", + "memo", + "lightning" + ] + }, + "originator": { + "$ref": "#/components/schemas/TravelRuleChainSide" + }, + "beneficiary": { + "$ref": "#/components/schemas/TravelRuleChainSide" + }, + "utxo": { + "type": "object", + "description": "On a UTXO transfer: the inputs and outputs as stored (amounts as decimal strings) and which outputs Didit read as what.", + "properties": { + "inputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleUtxoEntry" + } + }, + "outputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleUtxoEntry" + } + }, + "change_output_indexes": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "Outputs that pay an input address back." + }, + "beneficiary_output_index": { + "type": "integer", + "nullable": true, + "description": "The output that pays the beneficiary; null when none could be told apart." + } + } + }, + "lightning": { + "type": "object", + "description": "On a Lightning transfer: what the payment request says. Always `kind` (`bolt11`, `bolt12_offer`, `bolt12_invoice`, `bolt12_invoice_request`, `lnurl`, `lightning_address`, `node_id` or `unknown`); a BOLT11 invoice adds `network`, `payee_node_id`, `payment_hash`, `amount_msat`, `created_at` and `expires_at`; a BOLT12 string `blinded_paths` and `payee_node_id`; an LNURL or a Lightning Address its `domain`. Nothing is resolved over the network: the request is decoded only.", + "additionalProperties": true + }, + "warnings": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "declared_originator_not_an_input", + "declared_beneficiary_not_an_output", + "beneficiary_output_ambiguous", + "beneficiary_output_amount_differs" + ] + }, + "description": "UTXO inconsistencies worth a look: the declared originator is not an input, the declared beneficiary is not an output, no single beneficiary output could be told apart, or the beneficiary output pays a different amount than the transaction states." + }, + "not_applicable_reason": { + "type": "string", + "enum": [ + "LIGHTNING_NO_PAYEE_IDENTIFIER", + "LIGHTNING_BOLT12_BLINDED_PAYEE", + "LIGHTNING_LNURL_NAMES_NO_PAYEE" + ], + "description": "On an outbound Lightning transfer whose payment request names no payee: why the transfer is `NOT_APPLICABLE`. `LIGHTNING_NO_PAYEE_IDENTIFIER` (the request names no payee node, or could not be decoded), `LIGHTNING_BOLT12_BLINDED_PAYEE` (the offer hides its payee behind blinded paths), `LIGHTNING_LNURL_NAMES_NO_PAYEE` (an LNURL names a service, not a payee). The same code opens the transfer's last `timeline` entry." + } + } + }, + "WalletAddressImportRow": { + "type": "object", + "description": "One wallet to import: by its `address`, its `address_hash`, or both. The camelCase forms of these fields are accepted too, as are `walletAddress`, `walletAddressHash`, `destinationTag` and `externalUserId`.", + "properties": { + "address": { + "type": "string", + "maxLength": 128, + "description": "The wallet address. Alias: `walletAddress`." + }, + "address_hash": { + "type": "string", + "description": "SHA-256 of the lowercased address, as 64 hex characters or 44 base64 characters. Send it alone to register a wallet without disclosing its address; sent with `address`, it must be that address's hash. Alias: `walletAddressHash`." + }, + "chain": { + "type": "string", + "maxLength": 32 + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "The destination tag or memo. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`." + }, + "holder_name": { + "type": "string", + "maxLength": 255 + }, + "holder_vendor_data": { + "type": "string", + "maxLength": 255, + "description": "Your internal identifier for the holder. Alias: `externalUserId`." + }, + "entity_type": { + "type": "string", + "enum": [ + "individual", + "company" + ], + "default": "individual" + }, + "label": { + "type": "string", + "maxLength": 255 + }, + "owner_type": { + "type": "string", + "enum": [ + "customer", + "internal", + "vasp" + ], + "default": "customer" + }, + "travel_address": { + "type": "string", + "maxLength": 512 + }, + "self_declared": { + "type": "boolean", + "default": false, + "description": "Create the entry already verified through a self-declaration, when your settings allow it." + } + } + }, + "WalletAddressImportReport": { + "type": "object", + "description": "What happened to each row of an import, in the order sent.", + "properties": { + "total": { + "type": "integer" + }, + "created": { + "type": "integer" + }, + "existing": { + "type": "integer" + }, + "duplicate": { + "type": "integer" + }, + "failed": { + "type": "integer" + }, + "rows": { + "type": "array", + "items": { + "type": "object", + "properties": { + "row": { + "type": "integer", + "description": "The row's number, from 1." + }, + "status": { + "type": "string", + "enum": [ + "created", + "existing", + "duplicate", + "failed" + ], + "description": "`created`: added to the book. `existing`: the wallet is already in the book and was left untouched. `duplicate`: repeats an earlier row of this import. `failed`: see `errors`." + }, + "entry_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The address-book entry the row created or found." + }, + "errors": { + "type": "object", + "description": "Field errors of a `failed` row; empty otherwise." + }, + "duplicate_of_row": { + "type": "integer", + "nullable": true, + "description": "On a `duplicate` row, the row it repeats." + } + } + } + } + } + }, + "TravelRuleCounterpartyContact": { + "type": "object", + "description": "A compliance address the EMAIL rail writes to for one directory entry.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "counterparty": { + "type": "string", + "format": "uuid", + "description": "The directory entry's `id`, as GET /v3/travel-rule/vasps/ shows it." + }, + "counterparty_name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "name": { + "type": "string", + "description": "Optional label, for example the team name." + }, + "source": { + "type": "string", + "enum": [ + "staff", + "application", + "learned" + ], + "description": "staff: curated by Didit. learned: an address at one of the VASP's own domains that answered a request through its response link. application: added by your application." + }, + "scope": { + "type": "string", + "enum": [ + "platform", + "application" + ], + "description": "platform: curated by Didit or learned from a verified answer, used for every Didit customer, read-only for you. application: private to your application." + }, + "created_by": { + "type": "string", + "description": "Empty for platform contacts and for contacts an API key added." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this address last answered a request through its response link." + }, + "is_invalid": { + "type": "boolean", + "description": "true once the address bounced or complained; the EMAIL rail skips it." + }, + "invalid_reason": { + "type": "string", + "enum": [ + "", + "hard_bounce", + "soft_bounce", + "spam_complaint", + "suppressed" + ] + }, + "invalid_detail": { + "type": "string", + "description": "The receiving mail server's reason, when it gave one." + }, + "invalid_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleCounterpartyContactCreate": { + "type": "object", + "required": [ + "counterparty", + "email" + ], + "properties": { + "counterparty": { + "type": "string", + "format": "uuid", + "description": "The directory entry's `id`." + }, + "email": { + "type": "string", + "format": "email", + "maxLength": 254 + }, + "name": { + "type": "string", + "description": "Optional label.", + "maxLength": 255 + } + } + }, + "TravelRuleNameMatch": { + "type": "object", + "nullable": true, + "description": "The name comparison that decided the transfer, or null until one ran. While the parties are masked, and on the other customer's copy of an INTERNAL exchange, `transfer_name`, `compared_name`, `matched_names` and `mismatched_names` are hidden. `score` and `matched` can disagree: `matched` follows the strictness, `score` is the similarity of the two full names.", + "properties": { + "score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Similarity of the two full names, 0 to 100. 0 when either name is blank." + }, + "matched": { + "type": "boolean", + "description": "Whether the names satisfy `strictness`. The transfer's outcome follows `data_match`, which compares every field, so a transfer can be COUNTERPARTY_MISMATCHED_DATA while this is true." + }, + "method": { + "type": "string", + "enum": [ + "none", + "exact_token", + "token_subset", + "token_containment", + "sequence_ratio" + ], + "description": "How `matched` was decided: none (`NONE`), exact_token (`STRICT`), token_subset (`DEFAULT`), token_containment (`CONTAINS`) or sequence_ratio (`FUZZY`)." + }, + "strictness": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ] + }, + "compared_with": { + "type": "string", + "enum": [ + "wallet_holder", + "counterparty_vasp" + ], + "description": "wallet_holder: the holder recorded for the wallet. counterparty_vasp: the name the counterparty answered on the email response page, or the Didit Test VASP's answer in sandbox." + }, + "transfer_name": { + "type": "string", + "description": "The name on the transfer." + }, + "compared_name": { + "type": "string", + "description": "The name it was compared with." + }, + "matched_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Words of `transfer_name` found in `compared_name`." + }, + "mismatched_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Words of `transfer_name` missing from `compared_name`, then words of `compared_name` missing from `transfer_name`." + } + } + }, + "TravelRuleTransferRow": { + "type": "object", + "description": "One Travel Rule transfer as the transfers list shows it: VASP names, wallets and memos only, never the originator or beneficiary person data.", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The transfer's id." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "The Didit transaction the transfer belongs to." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "Your own id for the transaction. Transfers Didit books for you, such as a Didit Test VASP inbound transfer, carry a generated one." + }, + "txn_date": { + "type": "string", + "format": "date-time" + }, + "kind": { + "type": "string", + "enum": [ + "withdrawal", + "deposit", + "message" + ], + "description": "withdrawal: outbound. message: an inbound transfer a rail created from a counterparty's message (a TRP inquiry, the INTERNAL inbound leg, or a GTR, VerifyVASP or Sygna Bridge message). deposit: any other inbound transfer." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ] + }, + "status": { + "type": "string", + "enum": [ + "AWAITING_COUNTERPARTY", + "ON_HOLD", + "COMPLETED", + "FINISHED", + "CANCELLED", + "EXPIRED", + "COUNTERPARTY_MISMATCHED_DATA", + "COUNTERPARTY_VASP_NOT_FOUND", + "COUNTERPARTY_VASP_NOT_REACHABLE", + "UNCONFIRMED_OWNERSHIP", + "COUNTERPARTY_VASP_GENERAL_DECLINE", + "NOT_ENOUGH_COUNTERPARTY_DATA", + "NOT_APPLICABLE" + ] + }, + "reason": { + "type": "string", + "description": "Why a `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE` transfer is stuck, the same value as the transfer's own `reason`, so the list tells a bounced compliance email from a counterparty with no route without opening each transfer. `\"\"` for any other status. See [Why a transfer is stuck](/transaction-monitoring/travel-rule#why-a-transfer-is-stuck)." + }, + "rail": { + "type": "string", + "nullable": true, + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL", + "TEST_VASP", + null + ] + }, + "asset": { + "type": "string", + "description": "The transaction's currency." + }, + "amount": { + "type": "string", + "description": "The amount as a plain decimal, for example \"1.25\"." + }, + "originator_vasp": { + "type": "string", + "description": "Outbound: your VASP's legal name. Inbound: the counterparty's name, from its directory entry, the Didit network member, the TRP sender, the Didit Test VASP, or the name the transfer declared. Empty when none is known." + }, + "beneficiary_vasp": { + "type": "string", + "description": "Outbound: the counterparty's name, worked out the same way. Inbound: your VASP's legal name." + }, + "originator_wallet": { + "type": "string" + }, + "beneficiary_wallet": { + "type": "string" + }, + "originator_memo": { + "type": "string", + "description": "Memo or destination tag, empty on chains without one." + }, + "beneficiary_memo": { + "type": "string" + }, + "originator_wallet_type": { + "type": "string", + "enum": [ + "vasp", + "self_hosted", + "own" + ], + "description": "vasp, self_hosted (declared unhosted) or own (a wallet your address book records as yours). Your own side is always vasp." + }, + "beneficiary_wallet_type": { + "type": "string", + "enum": [ + "vasp", + "self_hosted", + "own" + ] + }, + "matched_transaction_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The other side of a deposit you matched to an announced transfer by hand." + }, + "deadline_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the transfer expires if it is still waiting on its counterparty. Set on every transfer you create with `POST /v3/transactions/` that needs an exchange, outbound or inbound. Null below the threshold, on a Lightning payment the Travel Rule does not apply to, and on inbound transfers Didit creates when a counterparty reaches you (an inbound TRP inquiry, a network message or the INTERNAL inbound leg)." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BankAccountCheckHolder": { + "description": "The name the account is checked against. Send exactly one of `full_name` and `business_name`.", + "oneOf": [ + { + "$ref": "#/components/schemas/BankAccountCheckPersonHolder" + }, + { + "$ref": "#/components/schemas/BankAccountCheckBusinessHolder" + } + ] + }, + "BankAccountCheckRequest": { + "type": "object", + "required": [ + "country", + "account", + "holder" + ], + "properties": { + "country": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "description": "The account's country, as an ISO 3166-1 alpha-3 code, for example `GBR`. Lowercase is accepted.", + "example": "GBR" + }, + "account": { + "type": "object", + "description": "The account the way its country writes it: an `iban`, or an `account_number` with the bank or branch code the country needs (`sort_code`, `routing_number`, `transit_institution`, `bsb`, `ifsc`, `bank_code` or `bic`). Mexico takes a `clabe` and Argentina a `cbu_cvu`, which already name the bank. A business account adds a `registration_id` where the country asks for one. Spaces, dashes and dots are ignored. Keys the country's check does not use are ignored; a key it needs and does not get is a `400` that names it.", + "properties": { + "iban": { + "type": "string", + "maxLength": 64, + "description": "The IBAN, in a country that uses IBANs. It must belong to `country`." + }, + "account_number": { + "type": "string", + "maxLength": 64, + "description": "The account number, in a country that writes the account with a separate bank or branch code." + }, + "sort_code": { + "type": "string", + "maxLength": 64, + "description": "United Kingdom: the 6-digit sort code." + }, + "routing_number": { + "type": "string", + "maxLength": 64, + "description": "United States: the 9-digit ABA routing number." + }, + "transit_institution": { + "type": "string", + "maxLength": 64, + "description": "Canada: the 8-digit transit and institution number." + }, + "bsb": { + "type": "string", + "maxLength": 64, + "description": "Australia: the 6-digit BSB." + }, + "ifsc": { + "type": "string", + "maxLength": 64, + "description": "India: the 11-character IFSC." + }, + "bank_code": { + "type": "string", + "maxLength": 64, + "description": "The bank's code, where the country needs one, for example in Nigeria." + }, + "bic": { + "type": "string", + "maxLength": 64, + "description": "The bank's BIC, where the country's check asks for one." + }, + "clabe": { + "type": "string", + "maxLength": 64, + "description": "Mexico: the 18-digit CLABE. It already names the bank." + }, + "cbu_cvu": { + "type": "string", + "maxLength": 64, + "description": "Argentina: the 22-digit CBU or CVU. It already names the bank." + }, + "registration_id": { + "type": "string", + "maxLength": 64, + "description": "A business account's registration number, where the country asks for one." + } + }, + "additionalProperties": { + "type": "string", + "maxLength": 64 + }, + "example": { + "sort_code": "20-45-67", + "account_number": "31926819" + } + }, + "holder": { + "$ref": "#/components/schemas/BankAccountCheckHolder" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this check, returned unchanged.", + "example": "payout_20481" + } + } + }, + "BankAccountCheckResponse": { + "type": "object", + "required": [ + "request_id", + "result", + "holder_name", + "account", + "vendor_data", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "The ID of this check." + }, + "result": { + "type": "string", + "enum": [ + "match", + "close_match", + "no_match", + "cannot_verify" + ], + "description": "`match`: the bank holds the account under the name you sent. `close_match`: under a close name, for example a missing middle name or a typo. `no_match`: under another name, or there is no such account. `cannot_verify`: the bank answered without comparing the name, for example for a blocked or closed account." + }, + "holder_name": { + "type": "string", + "nullable": true, + "description": "Only on a `close_match`, and only when the bank returned it: the name on the account, as the bank holds it. `null` otherwise." + }, + "account": { + "type": "string", + "description": "The account, masked to its last 4 characters." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference, as you sent it." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the check ran, in UTC." + } + } + }, + "BankAccountCheckError": { + "type": "object", + "required": [ + "detail" + ], + "properties": { + "detail": { + "type": "string", + "description": "What went wrong, in English." + }, + "code": { + "type": "string", + "description": "A stable code to branch on. Absent on `401` and `403`." + }, + "field": { + "type": "string", + "description": "`400` only: the body field to correct, as a dotted path, for example `account.sort_code`." + }, + "reason": { + "type": "string", + "enum": [ + "required", + "length", + "format", + "checksum", + "country_mismatch", + "rejected" + ], + "description": "`400` only: why the field was refused. `required`, `length`, `format`, `checksum` and `country_mismatch` (an IBAN of another country) are Didit's own checks; `rejected` means the bank refused the account as sent." + } + } + }, + "BankVerificationResult": { + "type": "object", + "description": "One Bank Verification step's result, for User and Business Verification. `method` says how the account was verified, and `none` that the person continued without a bank before any attempt was evaluated. `continued_without_bank` says the person continued without a bank, also after an evaluated attempt. See [Bank verification methods](/core-technology/bank-verification/verification-methods) and [When the person cannot verify their bank](/core-technology/bank-verification/fallbacks).", + "properties": { + "schema_version": { + "type": "integer", + "description": "Version of this result shape. `1` today." + }, + "bank_verification_id": { + "type": "string", + "format": "uuid", + "description": "The ID of this result." + }, + "node_id": { + "type": "string", + "description": "The workflow node this result belongs to." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Expired" + ], + "description": "The step's verdict, after its actions and status rules." + }, + "original_status": { + "type": "string", + "nullable": true, + "description": "The first verdict the step reached, before any later change such as a manual review." + }, + "method": { + "type": "string", + "nullable": true, + "enum": [ + "open_banking", + "account_lookup", + "microdeposits", + "statement", + "none" + ], + "description": "How the account was verified: `open_banking` (bank sign-in: the person signed in at their bank), `account_lookup` (account check: the person typed the account details and the bank confirmed the name), `microdeposits` or `statement` (a statement upload). `none` when the person continued without a bank before any attempt was evaluated, so no account was verified. After an evaluated attempt, it stays the method of that attempt. `null` until an account is evaluated, and when the workflow narrows the data it returns. Workflow rules read the same value as `bank.method`." + }, + "provider": { + "type": "string", + "nullable": true, + "description": "The key of the route that ran the connection. Read `method`, not this key, to tell how the account was verified." + }, + "credential_source": { + "type": "string", + "nullable": true, + "enum": [ + "native", + "byok" + ], + "description": "`native`: Didit's own route, billed to your Didit balance. `byok`: a marketplace provider you connected with your own credentials." + }, + "route": { + "type": "object", + "nullable": true, + "description": "The route chosen before consent. It never changes afterwards.", + "properties": { + "provider_key": { + "type": "string", + "nullable": true, + "description": "The route's key." + }, + "credential_source": { + "type": "string", + "enum": [ + "native", + "byok" + ] + }, + "is_fallback": { + "type": "boolean", + "description": "Whether Didit's own route stood in for a marketplace provider that could not connect." + }, + "provider_connection_id": { + "type": "string", + "nullable": true, + "description": "The marketplace connection used, for `byok`." + }, + "requested_scopes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The data the connection asked for, for example `ownership` or `balances`." + } + } + }, + "connection": { + "type": "object", + "nullable": true, + "description": "The latest attempt's connection. `null` before the first attempt.", + "properties": { + "status": { + "type": "string", + "enum": [ + "pending", + "connected", + "cancelled", + "timeout", + "failed", + "unsupported", + "revoked", + "expired" + ] + }, + "institution_id": { + "type": "string", + "nullable": true, + "description": "The bank's ID. `null` for an account check, where no bank is picked." + }, + "institution_name": { + "type": "string", + "nullable": true, + "description": "The bank's name. `null` for an account check." + }, + "institution_country": { + "type": "string", + "nullable": true, + "description": "The account's country, ISO 3166-1 alpha-3." + }, + "oauth": { + "type": "boolean", + "nullable": true, + "description": "Whether the bank sign-in ran over OAuth or app-to-app. `null` for an account check." + }, + "connected_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the connection succeeded." + }, + "consent_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the bank consent ends, for bank sign-in." + }, + "last_refreshed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the financial data was last read again." + }, + "attempts": { + "type": "integer", + "description": "The attempts the person spent on this step, every method together." + }, + "reason_code": { + "type": "string", + "nullable": true, + "description": "Why the connection is not simply `connected`, for example `USER_CANCELLED`, `INSTITUTION_DOWN`, `ACCOUNT_NOT_FOUND`, `REFERENCE_IDENTITY_MISSING` or `VERIFICATION_COMPLETED`." + } + } + }, + "account": { + "type": "object", + "nullable": true, + "description": "The evaluated account. `null` until an account is evaluated.", + "properties": { + "account_id_masked": { + "type": "string", + "nullable": true, + "description": "The account number, masked." + }, + "iban_masked": { + "type": "string", + "nullable": true, + "description": "The IBAN, masked." + }, + "sort_code": { + "type": "string", + "nullable": true, + "description": "The sort code, in the United Kingdom." + }, + "routing_number_masked": { + "type": "string", + "nullable": true, + "description": "The routing number, masked." + }, + "type": { + "type": "string", + "nullable": true, + "enum": [ + "checking", + "current", + "savings", + "business", + "other" + ] + }, + "subtype": { + "type": "string", + "nullable": true, + "description": "The bank's own account subtype." + }, + "currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 currency of the account." + }, + "holder_type": { + "type": "string", + "nullable": true, + "enum": [ + "person", + "business", + "unknown" + ] + }, + "holders_count": { + "type": "integer", + "nullable": true + }, + "is_joint": { + "type": "boolean", + "nullable": true + }, + "is_business": { + "type": "boolean", + "nullable": true + } + } + }, + "ownership": { + "type": "object", + "nullable": true, + "description": "How the account holder compared with the verified identity. `null` until an account is evaluated.", + "properties": { + "result": { + "type": "string", + "enum": [ + "match", + "partial_match", + "no_match", + "unknown", + "not_verifiable" + ], + "description": "`partial_match` is a close match. `not_verifiable`: the bank answered with no holder to compare. `unknown`: the evidence could not be read, or there was no identity to compare." + }, + "reference_identity_source": { + "type": "string", + "nullable": true, + "description": "The step whose identity the holder was compared against: `kyc`, `database_validation`, `expected_details`, `questionnaire`, `document_ai` or `kyb`." + }, + "name_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "Didit's holder name score." + }, + "provider_name_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The bank's own holder name score, when it returned one." + }, + "address_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The holder address score, when address matching ran." + }, + "email_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The holder email score, when email matching ran." + }, + "phone_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The holder phone score, when phone matching ran." + }, + "dob_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The holder date of birth score, when the bank shared one." + }, + "close_match": { + "type": "boolean", + "nullable": true, + "description": "Whether the name landed in the close-match band." + }, + "holder_confirmation": { + "type": "string", + "nullable": true, + "enum": [ + "pending", + "confirmed", + "rejected" + ], + "description": "The person's answer when asked to confirm a close-match name. `null` when never asked." + }, + "method": { + "type": "string", + "nullable": true, + "enum": [ + "exact", + "token_set", + "nickname", + "transliteration", + "fuzzy", + "provider_only", + "microdeposits" + ], + "description": "Which name comparison produced the score. This is not the verification method: read the top-level `method` for that." + }, + "matched_holder_index": { + "type": "integer", + "nullable": true + }, + "holders": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Each holder the bank listed, with its scores." + }, + "owner_identity_available": { + "type": "boolean", + "nullable": true + }, + "evidence_freshness": { + "type": "string", + "nullable": true, + "enum": [ + "live", + "cached", + "refreshed" + ] + }, + "evaluated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the verdict was computed." + } + } + }, + "financial_data": { + "type": "object", + "description": "The financial data the step read. Only bank sign-in reads it.", + "properties": { + "balances": { + "type": "object", + "nullable": true, + "description": "With the `balances` scope." + }, + "transactions": { + "type": "object", + "nullable": true, + "description": "With the `transactions` scope." + }, + "income": { + "type": "object", + "nullable": true, + "description": "With the `income` scope." + }, + "categorization": { + "type": "object", + "nullable": true, + "description": "Didit's categorization of the transactions read. `null` without them." + }, + "not_collected": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "balances", + "income", + "transactions" + ] + }, + "description": "The data the step asked for that the method which verified the account does not collect. Balances, transactions and income come from bank sign-in only, so an account verified by account check lists them here. Never a failure. Empty otherwise." + } + } + }, + "billing": { + "type": "object", + "nullable": true, + "properties": { + "route": { + "type": "string", + "enum": [ + "native", + "byok" + ] + }, + "billed": { + "type": "boolean" + }, + "marketplace_fee": { + "type": "string", + "nullable": true, + "description": "The marketplace platform fee charged, for `byok`." + }, + "usage_type": { + "type": "string", + "nullable": true, + "description": "The usage line the charge was booked on: `bank_verification` for a bank sign-in or an account check, `bank_statement_upload` for a statement upload with no charged bank attempt before it, and `bank_byok_fee` for the marketplace fee on your own provider. `null` when nothing was billed." + } + } + }, + "reference_identity": { + "type": "object", + "nullable": true, + "description": "The identity the holder was compared against, with its source." + }, + "signals": { + "type": "object", + "nullable": true, + "description": "Account signals read from the transaction history, when the step read one." + }, + "connection_abandoned": { + "type": "boolean", + "nullable": true, + "description": "Whether the step ended without a match after the person left at their bank." + }, + "not_covered": { + "type": "boolean", + "nullable": true, + "description": "Whether the person's bank or country could not be reached on this step. Also `true` when the person continued without a bank because no method could verify their country or bank." + }, + "continued_without_bank": { + "type": "boolean", + "description": "Whether the person left the step with **Continue without your bank**. `continuation` then says why. `method` is `none` when no attempt was evaluated before they continued. After an evaluated attempt, such as a holder that does not match, `method` stays the method of that attempt and its verdict and reason codes stand. Returned even when the workflow narrows the data it returns." + }, + "continuation": { + "type": "object", + "nullable": true, + "description": "How the person continued without a bank. `null` when they did not. When the workflow narrows the data it returns, `reason` is still returned and every other key is `null`.", + "properties": { + "reason": { + "type": "string", + "enum": [ + "no_route", + "institution_unsupported", + "attempts_exhausted", + "person_choice" + ], + "description": "Why, as Didit worked it out when the person continued: `no_route` (no bank sign-in or account check serves their country), `institution_unsupported` (the bank they picked cannot be used on this step), `attempts_exhausted` (every attempt was already spent and none was in progress), or `person_choice` (a way to verify was there and they chose to continue, also when their bank could not be reached right now). `no_route` and `institution_unsupported` follow `bank_unsupported_action`, `attempts_exhausted` follows `bank_attempts_exhausted_action`, and `person_choice` follows `bank_skipped_action`, unless an evaluated attempt already decided the step." + }, + "country": { + "type": "string", + "nullable": true, + "description": "The country the person was verifying in, ISO 3166-1 alpha-3. `null` when they continued before picking one." + }, + "offered": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "statement", + "microdeposits" + ] + }, + "nullable": true, + "description": "The other ways to verify that country offered the person when they continued, in the order it offered them: `statement` (a statement upload), then `microdeposits`. Empty when it offered none." + }, + "institution_id": { + "type": "string", + "nullable": true, + "description": "For `institution_unsupported`, the ID of the bank the person could not use: the bank they picked, or the bank their last attempt ended on. Didit takes it from its own list of banks, never from the person's device, so a bank that list does not have is `null`. `null` for every other reason." + }, + "institution_name": { + "type": "string", + "nullable": true, + "description": "For `institution_unsupported`, the name of that bank, from the same list as `institution_id`. `null` when that list does not have the bank, and for every other reason." + }, + "continued_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the person continued." + } + } + }, + "evidence_purged_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the bank evidence was deleted under `bank_evidence_retention_days`. `null` otherwise." + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Informational codes, for example `holder_declared`. A warning never changes the status on its own." + }, + "reason_codes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Why the verdict came out the way it did, for example `OWNER_NAME_PARTIAL`. Several may apply. `NOT_COVERED`: the person continued without a bank because no method could verify their country or bank. `SKIPPED`: the person chose to continue without a bank while a way to verify was there. Neither is added when an evaluated attempt already decided the step." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BankAccountCheckPersonHolder": { + "type": "object", + "description": "The person who should hold the account.", + "required": [ + "full_name" + ], + "properties": { + "full_name": { + "type": "string", + "maxLength": 255, + "description": "The full name of the person who should hold the account.", + "example": "Maria Oliveira" + } + } + }, + "BankAccountCheckBusinessHolder": { + "type": "object", + "description": "The company that should hold the account. Checks a business account.", + "required": [ + "business_name" + ], + "properties": { + "business_name": { + "type": "string", + "maxLength": 255, + "description": "The name of the company that should hold the account. Checks a business account." + } + } + }, + "BankVerificationPricing": { + "type": "object", + "description": "Public Bank Verification list prices: the statement upload, the own provider key fee and each country's methods.", + "required": [ + "currency", + "statement_upload", + "own_provider_fee_usd", + "countries" + ], + "properties": { + "currency": { + "type": "string", + "enum": [ + "USD" + ], + "description": "Currency of every price in the response.", + "example": "USD" + }, + "statement_upload": { + "$ref": "#/components/schemas/BankStatementUploadPricing" + }, + "own_provider_fee_usd": { + "type": "string", + "description": "Fee for each billable ownership check on a route that uses your own provider contract instead of Didit's, charged whatever the result. A cancelled, timed out or abandoned connection is not billed. Decimal string with 4 decimals, in USD.", + "example": "0.0200" + }, + "countries": { + "type": "object", + "description": "Every country, keyed by ISO 3166-1 alpha-3 code.", + "additionalProperties": { + "$ref": "#/components/schemas/BankVerificationPricingCountry" + } + } + } + }, + "BankStatementUploadPricing": { + "type": "object", + "description": "How the bank statement upload is priced.", + "required": [ + "price_usd", + "included_after_charged_bank_attempt", + "standalone_available" + ], + "properties": { + "price_usd": { + "type": "string", + "description": "Price of a statement upload after a charged bank attempt. Decimal string with 4 decimals, in USD.", + "example": "0.0000" + }, + "included_after_charged_bank_attempt": { + "type": "boolean", + "description": "`true` when the upload is included at no extra charge after a charged bank attempt in the same session.", + "example": true + }, + "standalone_available": { + "type": "boolean", + "description": "`true` when a statement upload can run on its own, with no bank attempt first. `false` in production; sandbox sessions can test it without billing.", + "example": false + } + } + }, + "BankVerificationPricingCountry": { + "type": "object", + "description": "One country's Bank Verification methods and their list prices.", + "required": [ + "name", + "methods" + ], + "properties": { + "name": { + "type": "string", + "description": "Country name in English.", + "example": "United Kingdom" + }, + "methods": { + "$ref": "#/components/schemas/BankVerificationPricingMethods" + } + } + }, + "BankVerificationPricingMethods": { + "type": "object", + "description": "The two Bank Verification methods in a country.", + "required": [ + "open_banking", + "account_lookup" + ], + "properties": { + "open_banking": { + "$ref": "#/components/schemas/BankSignInPricing" + }, + "account_lookup": { + "$ref": "#/components/schemas/BankAccountCheckPricing" + } + } + }, + "BankSignInPricing": { + "type": "object", + "description": "Bank sign-in: the person signs in to their bank. Priced by market, the same for every bank in it.", + "required": [ + "available", + "market", + "price_usd", + "add_ons" + ], + "properties": { + "available": { + "type": "boolean", + "description": "`true` only where Didit serves bank sign-in in this country in production.", + "example": true + }, + "market": { + "type": "string", + "nullable": true, + "description": "The market whose price applies. `null` when not available.", + "example": "UK and EEA" + }, + "price_usd": { + "type": "string", + "nullable": true, + "description": "List price of one bank sign-in. `null` when not available. Decimal string with 4 decimals, in USD.", + "example": "0.7900" + }, + "add_ons": { + "type": "object", + "description": "Add-ons priced in this country, each a decimal string with 4 decimals in USD, added to the bank sign-in price. Only the add-ons the country offers are listed; empty when bank sign-in is not available.", + "properties": { + "balance": { + "type": "string", + "description": "Account balance add-on.", + "example": "0.3500" + }, + "transactions": { + "type": "string", + "description": "Transactions add-on.", + "example": "1.3000" + } + } + } + } + }, + "BankAccountCheckPricing": { + "type": "object", + "description": "Account check: the person types their account number and Didit checks it with the bank. Priced per country.", + "required": [ + "available", + "price_usd" + ], + "properties": { + "available": { + "type": "boolean", + "description": "`true` only where Didit serves the account check in this country in production.", + "example": true + }, + "price_usd": { + "type": "string", + "nullable": true, + "description": "List price of one account check the bank answers. `null` when not available. Decimal string with 4 decimals, in USD.", + "example": "0.4000" + } + } + }, + "ImportProvider": { + "type": "object", + "properties": { + "key": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ] + }, + "name": { + "type": "string" + }, + "mode": { + "type": "string", + "enum": [ + "api_list", + "api_ids", + "export_file" + ], + "description": "`api_list`: Didit lists records with the key. `api_ids`: upload the record IDs from an export. `export_file`: upload an export in Didit's format." + }, + "record_noun": { + "type": "string" + }, + "credential_fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "label": { + "type": "string" + }, + "secret": { + "type": "boolean" + } + } + } + }, + "regions": { + "type": "array", + "items": { + "type": "string" + } + }, + "required_permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "docs_url": { + "type": "string", + "nullable": true + }, + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + } + } + } + }, + "ImportJobSummary": { + "type": "object", + "description": "One import job of any kind, in the shape the console's Import jobs drawer reads.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "kind": { + "type": "string", + "enum": [ + "sessions", + "businesses", + "users", + "status_rules", + "transactions" + ] + }, + "source": { + "type": "string", + "enum": [ + "file", + "api", + "provider" + ] + }, + "provider": { + "type": "string", + "nullable": true + }, + "provider_name": { + "type": "string", + "nullable": true + }, + "filename": { + "type": "string", + "nullable": true + }, + "state": { + "type": "string", + "enum": [ + "ready", + "queued", + "running", + "paused", + "completed", + "errors", + "failed", + "cancelled" + ], + "description": "The job's state as the console shows it; `errors` is a completed job with rejected rows." + }, + "status": { + "type": "string" + }, + "is_dry_run": { + "type": "boolean" + }, + "credential_hint": { + "type": "string", + "nullable": true + }, + "resumes_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_error": { + "type": "string", + "nullable": true + }, + "started_by": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + } + } + }, + "progress": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int64" + }, + "processed": { + "type": "integer", + "format": "int64" + }, + "imported": { + "type": "integer", + "format": "int64" + }, + "updated": { + "type": "integer", + "format": "int64" + }, + "already_in_didit": { + "type": "integer", + "format": "int64" + }, + "failed": { + "type": "integer", + "format": "int64" + }, + "not_finished": { + "type": "integer", + "format": "int64" + }, + "same_person": { + "type": "integer", + "format": "int64" + } + } + }, + "face_templates": { + "type": "object", + "nullable": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "built": { + "type": "integer", + "format": "int64" + }, + "duplicates": { + "type": "integer", + "format": "int64" + }, + "blocklisted": { + "type": "integer", + "format": "int64" + } + } + }, + "eta_seconds": { + "type": "integer", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "started_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "FiledRegulatoryReport": { + "type": "object", + "description": "A finalized regulatory report that prevents the requested deletion.", + "required": [ + "uuid", + "report_number", + "report_type", + "case_uuid" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Report UUID." + }, + "report_number": { + "type": "string", + "description": "Report number shown in the Console (for example, SAR-0001)." + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ], + "description": "Regulatory report type." + }, + "case_uuid": { + "type": "string", + "format": "uuid", + "description": "Case the report belongs to." + } + } + }, + "FiledReportTransaction": { + "type": "object", + "description": "A transaction that cannot be deleted because a finalized regulatory report lists it.", + "required": [ + "uuid", + "transaction_id", + "reports" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit transaction UUID." + }, + "transaction_id": { + "type": "string", + "description": "The transaction identifier supplied when the transaction was created." + }, + "reports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FiledRegulatoryReport" + }, + "description": "Finalized reports that list this transaction." + } + } + }, + "InFiledReportError": { + "type": "object", + "description": "The deletion is refused because the selected data is in a finalized regulatory report. Nothing is deleted. Delete the report, then repeat the request.", + "required": [ + "detail", + "code" + ], + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "in_filed_report" + ], + "description": "Stable error code for a deletion blocked by a finalized regulatory report." + }, + "reports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FiledRegulatoryReport" + }, + "description": "Reports that block a person, company, case or session deletion." + }, + "transactions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FiledReportTransaction" + }, + "description": "Transactions that block a transaction deletion, each with the reports that list it." + } + } + }, + "TravelRuleNetworkParticipantSummary": { + "type": "object", + "nullable": true, + "description": "This application's own participant on the network, or null when it has none.", + "properties": { + "mode": { + "type": "string", + "enum": [ + "platform", + "own" + ], + "description": "platform: carried by Didit's membership; own: onboarded under the application's own membership." + }, + "status": { + "type": "string", + "enum": [ + "not_requested", + "questionnaire_pending", + "submitted_to_network", + "active", + "suspended", + "rejected" + ], + "description": "not_requested: never requested, or the network refused the first request (see last_error); questionnaire_pending: waiting for the due-diligence questionnaire to be approved; submitted_to_network: handed to the network's onboarding; active: the network gave this application its own participant code; suspended / rejected: Didit suspended or rejected it." + }, + "participant_code": { + "type": "string", + "description": "The code the network gave this application (for GTR, its VASP code). Outbound messages carry this code, never Didit's own." + }, + "requested_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "activated_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_error": { + "type": "string", + "description": "The network's answer when it refused the registration; empty otherwise." + } + } + }, + "TravelRuleNetworkState": { + "type": "object", + "description": "How Travel Rule messages on one network would be sent for this application, and why they cannot be. Configuration status, not a live health or delivery check.", + "properties": { + "rail": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA" + ] + }, + "mode": { + "type": "string", + "enum": [ + "own", + "didit", + "off", + "unavailable" + ], + "description": "own: the application's connected membership is used; didit: Didit's membership carries the application as its own active participant; off: platform routing disabled in disabled_networks with no own membership; unavailable: nothing can send on this network yet (see state and reason)." + }, + "state": { + "type": "string", + "enum": [ + "own_membership", + "live_through_didit", + "off", + "didit_onboarding", + "questionnaire_needed", + "questionnaire_in_review", + "request_access", + "request_sent", + "unavailable", + "error" + ], + "description": "own_membership: your connected membership sends; live_through_didit: your own participant on Didit's membership sends; off: you switched platform routing off; didit_onboarding: Didit's own membership of this network is not live yet, so it cannot carry anyone; questionnaire_needed / questionnaire_in_review: the due-diligence questionnaire must be submitted or is with Didit; request_access: the questionnaire is approved, request the network; request_sent: your participant is being onboarded; unavailable: see reason; error: see reason." + }, + "reason": { + "type": "string", + "enum": [ + "own_membership_connected", + "platform_network_switched_off", + "own_membership_only", + "sandbox_application", + "didit_membership_not_live", + "participant_suspended", + "participant_rejected", + "questionnaire_not_submitted", + "questionnaire_submitted", + "questionnaire_changes_requested", + "questionnaire_rejected", + "participant_not_requested", + "participant_active", + "participant_key_missing", + "participant_key_unresolved", + "provisioning_failed", + "questionnaire_pending", + "submitted_to_network" + ], + "description": "Machine-readable detail of state. own_membership_only: Didit does not carry applications on this network (TRUST), connect your own membership; sandbox_application: sandbox transfers never reach a network; provisioning_failed: the network refused the registration, the participant's last_error says why and requesting the network again retries." + }, + "platform_available": { + "type": "boolean", + "description": "Whether Didit's own membership of this network is configured; not a live health check." + }, + "connected": { + "type": "boolean", + "description": "Whether a connected customer membership exists. Credentials are validated locally; network validation occurs on a live exchange." + }, + "participant": { + "$ref": "#/components/schemas/TravelRuleNetworkParticipantSummary" + }, + "vasps_reachable": { + "type": "integer", + "description": "Directory VASPs reachable on this network." + }, + "routed_30d": { + "type": "integer", + "description": "This application's transfers carried on this network in the last 30 days." + } + } + }, + "TravelRuleNetworkQuestionnaireAnswers": { + "type": "object", + "description": "Didit's network due-diligence questionnaire. Saves are partial: a save keeps every answer it does not name. Submitting requires every field except lei and data_protection_contact_email, and the three attestations true.", + "properties": { + "legal_name": { + "type": "string", + "maxLength": 255 + }, + "registration_number": { + "type": "string", + "maxLength": 255 + }, + "incorporation_country": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code." + }, + "registered_address": { + "type": "string", + "maxLength": 1000 + }, + "website": { + "type": "string", + "format": "uri", + "maxLength": 500 + }, + "lei": { + "type": "string", + "maxLength": 20, + "description": "Optional. A valid ISO 17442 LEI when given." + }, + "licences": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "regulator", + "jurisdiction", + "licence_type", + "licence_number" + ], + "properties": { + "regulator": { + "type": "string", + "maxLength": 255 + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code." + }, + "licence_type": { + "type": "string", + "maxLength": 255 + }, + "licence_number": { + "type": "string", + "maxLength": 255 + } + } + }, + "description": "Every licence or registration the VASP holds." + }, + "operating_jurisdictions": { + "type": "array", + "minItems": 1, + "maxItems": 250, + "items": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code." + } + }, + "compliance_officer_name": { + "type": "string", + "maxLength": 255 + }, + "compliance_officer_email": { + "type": "string", + "format": "email" + }, + "aml_ctf_policy_attested": { + "type": "boolean", + "description": "Must be true to submit." + }, + "sanctions_screening_attested": { + "type": "boolean", + "description": "Must be true to submit." + }, + "data_protection_attested": { + "type": "boolean", + "description": "Must be true to submit." + }, + "data_protection_contact_email": { + "type": "string", + "format": "email", + "description": "Optional." + }, + "expected_monthly_transfers": { + "type": "integer", + "minimum": 0 + }, + "expected_monthly_volume_usd": { + "type": "string", + "format": "decimal", + "description": "Expected monthly volume in USD, up to 2 decimal places. Returned as a string." + }, + "assets": { + "type": "array", + "minItems": 1, + "maxItems": 200, + "items": { + "type": "string", + "maxLength": 32 + }, + "description": "Asset tickers, stored upper-case, sorted and de-duplicated." + } + } + }, + "TravelRuleNetworkQuestionnaire": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "draft", + "submitted", + "changes_requested", + "approved", + "rejected" + ], + "description": "Answers can be saved while draft or changes_requested; submitted, approved and rejected are Didit's to move." + }, + "answers": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaireAnswers" + }, + "submitted_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reviewed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "review_note": { + "type": "string", + "description": "Didit's note on its last decision, for example what to change." + }, + "history": { + "type": "array", + "readOnly": true, + "description": "Every status change, oldest first.", + "items": { + "type": "object", + "properties": { + "at": { + "type": "string", + "format": "date-time" + }, + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "note": { + "type": "string", + "description": "Review note or the network's answer, when there is one." + } + } + } + }, + "updated_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "TravelRuleNetworkParticipant": { + "type": "object", + "description": "This application's own participant on one network.", + "properties": { + "network": { + "type": "string", + "enum": [ + "gtr", + "trust", + "verifyvasp", + "sygna" + ] + }, + "rail": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA" + ] + }, + "mode": { + "type": "string", + "enum": [ + "platform", + "own" + ] + }, + "status": { + "type": "string", + "enum": [ + "not_requested", + "questionnaire_pending", + "submitted_to_network", + "active", + "suspended", + "rejected" + ], + "description": "not_requested: never requested, or the network refused the first request (see last_error); questionnaire_pending: waiting for the due-diligence questionnaire to be approved; submitted_to_network: handed to the network's onboarding; active: the network gave this application its own participant code; suspended / rejected: Didit suspended or rejected it." + }, + "participant_code": { + "type": "string" + }, + "requested_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "submitted_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "activated_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "suspended_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_error": { + "type": "string", + "description": "The network's answer when it refused the registration; empty otherwise." + }, + "history": { + "type": "array", + "readOnly": true, + "description": "Every status change, oldest first.", + "items": { + "type": "object", + "properties": { + "at": { + "type": "string", + "format": "date-time" + }, + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "note": { + "type": "string", + "description": "Review note or the network's answer, when there is one." + } + } + } + } + } + }, + "TravelRuleNetworksOverview": { + "type": "object", + "properties": { + "platform_networks_enabled": { + "type": "boolean", + "description": "Whether network access through Didit's membership is open. While false, questionnaire saves, submissions and network requests answer 403." + }, + "questionnaire": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaire" + }, + "participants": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleNetworkParticipant" + } + }, + "networks": { + "type": "object", + "readOnly": true, + "description": "Per network, how this application would send and why it cannot. No network, including GTR, is included by default: a network is reachable through your own connected membership, or through Didit's membership once Didit's membership of that network is live and your own participant on it is active. Not a live health or delivery check.", + "properties": { + "gtr": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "trust": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "verifyvasp": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "sygna": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + } + } + } + } + }, + "AddressValidationResult": { + "type": "object", + "description": "The result of one Address Validation: whether the address exists, and its normalised form. It confirms the address only; it never links the address to a person.", + "required": [ + "status", + "verification_level", + "address", + "formatted_address", + "latitude", + "longitude" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "verified", + "partially_verified", + "not_verified", + "unavailable" + ], + "description": "`verified`: the address was confirmed down to the building (or the flat or PO box). `partially_verified`: it was confirmed only to the street, or to the most precise level the postal reference data holds for that place. `not_verified`: only the region or the city matched, or nothing did. `unavailable`: the validation could not run (for example the reference service did not answer); it is not charged and never affects a verification." + }, + "verification_level": { + "type": "string", + "nullable": true, + "enum": [ + "administrative_area", + "locality", + "thoroughfare", + "premise", + "delivery_point", + null + ], + "description": "The most precise level the address was matched to. `null` when nothing matched or `status` is `unavailable`." + }, + "address": { + "type": "object", + "nullable": true, + "description": "The normalised address. `null` when `status` is `unavailable`.", + "properties": { + "address_line_1": { + "type": "string", + "nullable": true, + "description": "First delivery line, written the way the country writes it.", + "example": "1600 Amphitheatre Pkwy" + }, + "address_line_2": { + "type": "string", + "nullable": true, + "description": "Second delivery line (a flat, building or neighbourhood), when the address has one." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Mountain View" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State, province or region.", + "example": "CA" + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "94043-1351" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code.", + "example": "US" + } + } + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "The normalised address on one line.", + "example": "1600 Amphitheatre Pkwy, Mountain View CA 94043-1351" + }, + "latitude": { + "type": "number", + "nullable": true, + "example": 37.422022 + }, + "longitude": { + "type": "number", + "nullable": true, + "example": -122.08429 + } + } + }, + "AddressValidationRequest": { + "type": "object", + "required": [ + "country" + ], + "description": "One address: on one line (`address`) or as structured fields with at least `address_line_1`, never both.", + "properties": { + "address": { + "type": "string", + "maxLength": 500, + "description": "The whole address on one line.", + "example": "Paseo de la Castellana 259, 28046 Madrid" + }, + "address_line_1": { + "type": "string", + "maxLength": 255, + "description": "Street and number.", + "example": "1600 Amphitheatre Pkwy" + }, + "address_line_2": { + "type": "string", + "maxLength": 255, + "description": "Apartment, suite, floor or building." + }, + "city": { + "type": "string", + "maxLength": 128, + "description": "City or locality.", + "example": "Mountain View" + }, + "region": { + "type": "string", + "maxLength": 128, + "description": "State, province or region.", + "example": "CA" + }, + "postal_code": { + "type": "string", + "maxLength": 32, + "example": "94043" + }, + "country": { + "type": "string", + "maxLength": 3, + "description": "ISO 3166-1 alpha-2 or alpha-3 code of the address's country.", + "example": "US" + } + } + }, + "AddressValidationResponse": { + "type": "object", + "required": [ + "request_id", + "address_validation", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Identifier of this request; quote it to support. No session is created." + }, + "address_validation": { + "$ref": "#/components/schemas/AddressValidationResult" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleWalletProofFailureReason": { + "type": "string", + "enum": [ + "", + "SIGNATURE_INVALID", + "SIGNATURE_UNSUPPORTED_SCHEME", + "DEPOSIT_NOT_FOUND", + "DEPOSIT_UNCONFIRMED", + "DEPOSIT_LOOKUP_FAILED", + "DEPOSIT_MISMATCH", + "DEPOSIT_STALE", + "TXID_REUSED", + "CHAIN_UNSUPPORTED", + "WINDOW_EXPIRED", + "OWNERSHIP_CHANGED", + "METHOD_SWITCHED", + "REVIEW_REJECTED", + "ABANDONED" + ], + "description": "Why a wallet-ownership proof, or its last attempt, fell short of VERIFIED. Empty when nothing failed. A PENDING proof can carry one: the last attempt failed and the end user can try again. DEPOSIT_MISMATCH, DEPOSIT_STALE, TXID_REUSED and REVIEW_REJECTED close the proof as REJECTED. SIGNATURE_INVALID: the signature does not match the wallet and challenge. SIGNATURE_UNSUPPORTED_SCHEME: the wallet signed in a format Didit does not verify. DEPOSIT_NOT_FOUND, DEPOSIT_UNCONFIRMED, DEPOSIT_LOOKUP_FAILED: the test deposit is not readable or not confirmed yet; Didit keeps checking it until the test window closes. DEPOSIT_MISMATCH: the transaction does not send the expected amount from the wallet to the deposit address. DEPOSIT_STALE: the transaction predates the challenge. TXID_REUSED: the transaction already proved another wallet. CHAIN_UNSUPPORTED: the method is not available on this chain. WINDOW_EXPIRED: the test window closed. OWNERSHIP_CHANGED: the wallet's holder changed, or the wallet was deleted from your address book, after the widget session was minted. METHOD_SWITCHED: the end user picked another method before finishing this one. REVIEW_REJECTED: your reviewer rejected the screenshot. ABANDONED: the widget session lapsed before the end user finished." + }, + "TravelRuleWidgetProofError": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable message in English." + }, + "code": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleWalletProofFailureReason" + } + ], + "description": "Machine-readable reason, so a client can show its own translated message." + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ], + "description": "Present when the proof is closed: REJECTED by the deposit just submitted (400), or REJECTED or EXPIRED when evidence is sent to a proof that is already closed (409)." + }, + "proof_id": { + "type": "string", + "format": "uuid", + "description": "Present with status: the closed proof." + }, + "method": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SATOSHI_TEST", + "SCREENSHOT", + "SELF_DECLARATION" + ], + "description": "Present with status: the closed proof's method." + }, + "max_attempts": { + "type": "integer", + "description": "Present with status: rejected wallet proofs this session allows." + }, + "attempts_used": { + "type": "integer", + "description": "Present with status: rejected proofs so far." + }, + "attempts_remaining": { + "type": "integer", + "description": "Present with status: when 0, starting another proof answers 409 and you mint a new widget session." + } + } + }, + "TravelRuleWidgetProofCreate": { + "type": "object", + "required": [ + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SATOSHI_TEST", + "SCREENSHOT", + "SELF_DECLARATION" + ], + "description": "The proof method the end user picked. It must be one of the context's allowed_methods." + }, + "wallet_address": { + "type": "string", + "maxLength": 128, + "description": "Defaults to the session's wallet_address. Must match the transfer's wallet when the session is linked to a transfer." + }, + "chain": { + "type": "string", + "maxLength": 32, + "description": "Defaults to the session's chain." + } + } + }, + "TravelRuleWidgetProofVerifyResult": { + "type": "object", + "properties": { + "proof_id": { + "type": "string", + "format": "uuid" + }, + "method": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SATOSHI_TEST", + "SCREENSHOT", + "SELF_DECLARATION" + ] + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ] + }, + "is_ownership_verified": { + "type": "boolean", + "description": "Present when the proof is VERIFIED: the address book entry is now ownership-verified." + }, + "transfer_status": { + "type": "string", + "nullable": true, + "description": "Present when the proof is VERIFIED: the linked transfer's status, null without one." + }, + "detail": { + "type": "string", + "description": "Present while the proof stays PENDING: a submitted screenshot awaiting review, or a test deposit that is not confirmed yet." + }, + "code": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleWalletProofFailureReason" + } + ], + "description": "Present on a pending test deposit: why it is not confirmed yet." + } + } + }, + "TravelRuleWalletProofReview": { + "type": "object", + "properties": { + "proof_id": { + "type": "string", + "format": "uuid" + }, + "method": { + "type": "string", + "enum": [ + "SCREENSHOT" + ] + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ] + }, + "wallet_address": { + "type": "string" + }, + "chain": { + "type": "string", + "description": "Canonical chain identifier, e.g. ETH, BTC, SOL, TRX." + }, + "holder_name": { + "type": "string" + }, + "is_ownership_verified": { + "type": "boolean", + "description": "Whether the address book entry is ownership-verified, by this proof or another one." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The transaction whose Travel Rule transfer the widget session was minted for. Null for a session minted without a transaction_id." + }, + "file_name": { + "type": "string", + "nullable": true + }, + "content_type": { + "type": "string", + "nullable": true + }, + "screenshot_url": { + "type": "string", + "nullable": true, + "description": "Time-limited link to the uploaded screenshot. List the queue again for a fresh link." + }, + "attempts": { + "type": "integer", + "description": "How many files the end user uploaded for this proof." + }, + "failure_reason": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleWalletProofFailureReason" + } + ], + "description": "REVIEW_REJECTED after a rejection, OWNERSHIP_CHANGED when the wallet changed holder or was deleted before a decision. Empty otherwise." + }, + "method_chosen_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the end user picked the screenshot method." + }, + "decided_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "decided_by": { + "type": "string", + "description": "Who closed the proof: review: for a console reviewer, review for an API-key decision, widget or sweeper when Didit closed it automatically. Empty while PENDING." + }, + "review_note": { + "type": "string", + "description": "The note sent with the decision. Empty when none." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleWalletProofDecision": { + "type": "object", + "required": [ + "decision" + ], + "properties": { + "decision": { + "type": "string", + "enum": [ + "approve", + "reject" + ] + }, + "note": { + "type": "string", + "maxLength": 512, + "description": "Optional. Kept with the proof and returned as review_note." + } + } + } + }, + "securitySchemes": { + "ApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "x-api-key", + "description": "Your application's API key, from Developers -> API keys in the Business Console. The primary key has full access. A named key can be scoped: none, read or write per resource, limited to some workflows or to approved sessions, to a list of IP addresses, and to an expiry date. 401 means the key is missing, wrong, revoked or expired; 403 means the key has no access to this resource or action, or the request came from an address outside its IP list; 404 on a session route means the session is outside the key's workflows or statuses. A key without media access receives image, video and PDF URLs as null, and a key without sessions write receives session links and tokens as null. See https://docs.didit.me/console/api-keys." + }, + "TransactionTokenAuth": { + "type": "apiKey", + "in": "header", + "name": "X-Transaction-Token", + "description": "Short-lived scoped token minted by your backend via POST /v3/transactions/sdk-token/. Used by the Didit SDKs on the device-facing /v1/transactions/ endpoints." + }, + "SessionTokenAuth": { + "type": "apiKey", + "in": "header", + "name": "Session-Token", + "description": "Short-lived token returned in the `session_token` field of POST /v3/session/. Used by the hosted verification flow (and custom sandbox UIs) to call session-scoped endpoints on behalf of the verifying user, without your server-side API key." + }, + "BearerAuth": { + "type": "http", + "scheme": "bearer", + "description": "The application's client-credentials access token." + }, + "CaseRequestToken": { + "type": "apiKey", + "in": "header", + "name": "X-Case-Request-Token", + "description": "Request-specific credential from the secure link. Grants access only to this request." + } + }, + "responses": { + "Unauthorized": { + "description": "Unauthorized", + "content": { + "application/json": { + "examples": { + "Invalid API Key": { + "summary": "Invalid API Key", + "value": { + "detail": "Invalid API Key." + } + } + } + } + } + }, + "Forbidden": { + "description": "Forbidden", + "content": { + "application/json": { + "examples": { + "No Permission": { + "summary": "No Permission", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not Enough Credits": { + "summary": "Not Enough Credits", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "NotFound": { + "description": "Not Found", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Not Found", + "value": { + "detail": "Resource not found." + } + } + } + } + } + }, + "PhoneRiskValidationError": { + "description": "Request validation failed.", + "content": { + "application/json": { + "examples": { + "Missing phone number": { + "summary": "Missing phone number", + "value": { + "phone_number": [ + "This field is required." + ] + } + }, + "Invalid phone number": { + "summary": "Invalid phone number", + "value": { + "phone_number": [ + "Invalid phone number provided." + ] + } + }, + "Invalid signal IP": { + "summary": "Invalid signal IP", + "value": { + "signals": { + "ip": [ + "Enter a valid IPv4 or IPv6 address." + ] + } + } + } + } + } + } + }, + "EmailRiskValidationError": { + "description": "Request validation failed.", + "content": { + "application/json": { + "examples": { + "Missing email": { + "summary": "Missing email", + "value": { + "email": [ + "This field is required." + ] + } + }, + "Invalid email": { + "summary": "Invalid email", + "value": { + "email": [ + "Enter a valid email address." + ] + } + }, + "Invalid country hint": { + "summary": "Invalid country hint", + "value": { + "country_code": [ + "Ensure this field has no more than 3 characters." + ] + } + } + } + } + } + }, + "IPRiskValidationError": { + "description": "Request validation failed.", + "content": { + "application/json": { + "examples": { + "Missing IP address": { + "summary": "Missing IP address", + "value": { + "ip_address": [ + "This field is required." + ] + } + }, + "Invalid IP address": { + "summary": "Invalid IP address", + "value": { + "ip_address": [ + "Enter a valid IPv4 or IPv6 address." + ] + } + }, + "Country hint too long": { + "summary": "Country hint too long", + "value": { + "claimed_country": [ + "Ensure this field has no more than 3 characters." + ] + } + } + } + } + } + }, + "RiskPermissionDenied": { + "description": "The API key is missing or invalid, or the organization cannot cover the live lookup. This API never returns `401`.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid or revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the lookup price", + "value": { + "error": "You don't have enough credits to perform this request." + } + } + } + } + } + }, + "RateLimitedWrite": { + "description": "The shared write limit of 300 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "detail" + ], + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + }, + "AddressValidationValidationError": { + "description": "The request body is invalid: a missing or unknown `country`, no address, or both `address` and the structured fields.", + "content": { + "application/json": { + "examples": { + "Unknown country": { + "value": { + "country": [ + "Use an ISO 3166-1 alpha-2 or alpha-3 country code, e.g. 'US' or 'USA'." + ] + } + }, + "No address": { + "value": { + "address": [ + "Send the address: 'address' on one line, or the structured fields with 'address_line_1'." + ] + } + }, + "Both shapes": { + "value": { + "address": [ + "Send either 'address' or the structured fields (address_line_1, ...), not both." + ] + } + } + } + } + } + }, + "AddressValidationPermissionDenied": { + "description": "The API key is missing or invalid, a scoped key lacks `sessions` write access, or the organization cannot cover the validation. This API never returns `401`.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "value": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me", + "code": "insufficient_credits" + } + } + } + } + } + } + }, + "parameters": { + "WebhookSignature": { + "name": "X-Signature", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Lowercase hex HMAC-SHA256, keyed with the destination's secret, of the exact raw request body." + }, + "WebhookSignatureV2": { + "name": "X-Signature-V2", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Lowercase hex HMAC-SHA256, keyed with the destination's secret, of the canonical JSON of the body (keys sorted, no whitespace, non-ASCII characters unescaped). Recommended, because it survives middleware that re-encodes the body." + }, + "WebhookSignatureSimple": { + "name": "X-Signature-Simple", + "in": "header", + "required": true, + "deprecated": true, + "schema": { + "type": "string" + }, + "description": "Lowercase hex HMAC-SHA256, keyed with the destination's secret, of `{timestamp}:{session_id}:{status}:{webhook_type}` (missing fields as empty strings). Covers the envelope only, never the payload." + }, + "WebhookTimestamp": { + "name": "X-Timestamp", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Unix time in seconds, equal to the body's `timestamp`. Reject deliveries older than a few minutes." + } + } + }, + "x-webhooks": { + "status.updated": { + "post": { + "summary": "Session status changed", + "description": "A verification session changed status. `decision` is sent only for `Approved`, `Declined`, `In Review` and `Abandoned`; it has the shape of `GET /v3/session/{sessionId}/decision/` for destinations on webhook version 3 (the default), and the older reduced shape for version 1 or 2 destinations. Business sessions always receive the version 3 business decision.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "data.updated": { + "post": { + "summary": "Session data changed", + "description": "Data of a verification session changed without a status change, for example after a manual review edit, a step override or an ongoing-monitoring rescreen. `decision` is sent only for `Approved`, `Declined`, `In Review` and `Abandoned`; it has the shape of `GET /v3/session/{sessionId}/decision/` for destinations on webhook version 3 (the default), and the older reduced shape for version 1 or 2 destinations. Business sessions always receive the version 3 business decision.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionDataUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "user.status.updated": { + "post": { + "summary": "User status changed", + "description": "A user changed status. Not sent when the user is created.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "user.data.updated": { + "post": { + "summary": "User data changed", + "description": "Fields of a user changed. Not sent when the user is created. One save can produce both a status and a data event.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDataUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "business.status.updated": { + "post": { + "summary": "Business status changed", + "description": "A business changed status. Not sent when the business is created.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "business.data.updated": { + "post": { + "summary": "Business data changed", + "description": "Fields of a business changed. Not sent when the business is created. One save can produce both a status and a data event.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDataUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "transaction.created": { + "post": { + "summary": "Transaction created", + "description": "A transaction was created and screened. Sent for live applications only.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionCreatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "transaction.status.updated": { + "post": { + "summary": "Transaction status changed", + "description": "A transaction changed status, from a rule, a review or an end-user action. Sent for live applications only.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "travel_rule.status.updated": { + "post": { + "summary": "Travel Rule status changed", + "description": "A Travel Rule exchange changed status. Sent on every transition up to and including `FINISHED`, `CANCELLED` or `EXPIRED`, never after. Sent for live applications and, for Travel Rule exchanges, for sandbox applications too (`environment: sandbox`). There is no top-level `status`.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "workflow.compliance_changed": { + "post": { + "summary": "Workflow compliance changed", + "description": "The compliance status of a published workflow changed after a re-evaluation. One event per workflow. There is no top-level `status`.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowComplianceChangedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "kyb.registry_search.resolved": { + "post": { + "summary": "Company search resolved (unsigned callback)", + "description": "Body of the callback sent to the `webhook_url` of `POST /v3/kyb/search/` once the registry search resolves. It is not signed: the request carries `X-Didit-Unsigned-Callback: true` and none of the signature headers, so confirm it by matching `request_id` against a search you started. Uses `event_type`, not `webhook_type`.", + "tags": [ + "Webhook" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBRegistrySearchResolvedCallback" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + } + } +} diff --git a/codegen/tests/fixtures/verification-ccc799410f82eee1c25c7ce5abc6caa53846be6b.json b/codegen/tests/fixtures/verification-ccc799410f82eee1c25c7ce5abc6caa53846be6b.json new file mode 100644 index 0000000..940668b --- /dev/null +++ b/codegen/tests/fixtures/verification-ccc799410f82eee1c25c7ce5abc6caa53846be6b.json @@ -0,0 +1,128151 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "3.0.0", + "title": "Didit Verification API", + "description": "Identity verification API. Authenticate with x-api-key header." + }, + "servers": [ + { + "url": "https://verification.didit.me" + } + ], + "tags": [], + "paths": { + "/system/healthcheck": { + "get": { + "summary": "Service healthcheck (public, unauthenticated)", + "description": "Public unauthenticated liveness probe. Returns `HTTP 200` with `{status: \"ok\", timestamp}` when service, DB, cache, and broker are healthy.", + "operationId": "get_system_healthcheck", + "x-didit-sdk": { + "resource": "system", + "method": "healthcheck", + "auth": "public", + "server": false + }, + "tags": [ + "System" + ], + "security": [ + {} + ], + "parameters": [], + "responses": { + "200": { + "description": "Service is healthy and accepting requests. Body is always `{\"status\": \"ok\", \"timestamp\": \"\"}`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "status", + "timestamp" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "ok" + ], + "description": "Liveness marker. Always the literal string `\"ok\"` on a 200 response.", + "example": "ok" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Server time at which the healthcheck was evaluated, in ISO 8601 with millisecond precision and a `Z` (UTC) suffix.", + "example": "2026-04-21T16:42:50.445Z" + } + } + }, + "examples": { + "Healthy": { + "summary": "Service is up", + "value": { + "status": "ok", + "timestamp": "2026-04-21T16:42:50.445Z" + } + } + } + } + } + }, + "503": { + "description": "Service is unhealthy — one of the configured health-check plugins (database, cache, broker) is failing. The response body is intentionally minimal; the HTTP status is the load-bearing signal. Retry with exponential backoff." + }, + "5XX": { + "description": "Verification service is degraded or unreachable. Treat any non-2xx response as \"unhealthy\" and retry with exponential backoff." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -i https://verification.didit.me/system/healthcheck" + }, + { + "lang": "Python", + "label": "Python (requests)", + "source": "import requests\n\nresp = requests.get(\"https://verification.didit.me/system/healthcheck\", timeout=5)\nresp.raise_for_status()\nprint(resp.json()) # {'status': 'ok', 'timestamp': '2026-04-21T16:42:50.445Z'}" + }, + { + "lang": "JavaScript", + "label": "Node.js (fetch)", + "source": "const res = await fetch(\"https://verification.didit.me/system/healthcheck\");\nif (!res.ok) throw new Error(`Didit unhealthy: ${res.status}`);\nconst body = await res.json();\nconsole.log(body); // { status: 'ok', timestamp: '2026-04-21T16:42:50.445Z' }" + } + ] + } + }, + "/v3/email/risk/": { + "post": { + "summary": "Risk-score an email address", + "description": "Return deliverability, disposable-address, breach, cross-session match, and normalized risk-intelligence signals without sending an OTP. The endpoint creates an API session and returns its id as `request_id`.\n\n**Decision.** The top-level `status` mirrors `email.status`. It is normally `Approved`; the risk evaluation can produce `In Review` or `Declined`.\n\n**Compatibility aliases.** Live responses expose the normalized intelligence object as both `email.email_intelligence` and `email.enrichment`. The values are identical. New integrations can read `email.enrichment`. Both keys are absent when no usable intelligence data was returned.\n\n**Billing.** Live calls use Email Risk API pricing. The request is billable once the lookup starts, even when some best-effort fields are unavailable.\n\n**Sandbox.** Sandbox keys validate the request and return deterministic approved data without sending an email, consuming credits, creating a session, or contributing to Networks.\n\n**Authentication.** Send your application's API key in `x-api-key`. Missing or invalid credentials return `403` - this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests per minute per API key across POST/PATCH/DELETE endpoints (transaction creates and event ingestion have a separate budget of 600).\n\n**Social footprint.** Set `enable_social` to `true` to add a `email_social` block: the online platforms this address is registered on, with per-category counts and any profile details available. `profiles_registered: 0` means the platforms were checked and the address was found on none of them - a valid, billable answer. The block is absent when the add-on was not requested or the check could not be completed, and it is not billed in that case. Billing adds one `email_social` unit per request on top of this endpoint's own price.", + "operationId": "post_v3email_risk", + "x-didit-sdk": { + "resource": "email", + "method": "assess_risk", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/email/risk/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"email\": \"alice@example.com\",\n \"country_code\": \"US\",\n \"vendor_data\": \"lead-123\",\n \"metadata\": {\"source\": \"signup_prescreen\"}\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.email.assess_risk(\n email=\"alice@example.com\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.email.assessRisk({\n email: \"alice@example.com\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Email.AssessRisk(ctx, didit.EmailRiskRequest{\n\tEmail: \"alice@example.com\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.EmailRiskRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.email().assessRisk(EmailRiskRequest.builder()\n .email(\"alice@example.com\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "email->assessRisk([\n 'email' => 'alice@example.com',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.email.assess_risk(email: \"alice@example.com\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Email.AssessRiskAsync(new EmailRiskRequest\n{\n Email = \"alice@example.com\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailRiskRequest" + }, + "examples": { + "Minimal": { + "summary": "Score an address with defaults", + "value": { + "email": "alice@example.com" + } + }, + "Full context": { + "summary": "Score with signals, country hint, and correlation ids", + "value": { + "email": "alice@example.com", + "country_code": "US", + "signals": { + "ip": "192.0.2.1", + "device_id": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2", + "device_platform": "web", + "os_version": "14.4", + "app_version": "1.2.34", + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15" + }, + "vendor_data": "lead-123", + "metadata": { + "source": "signup_prescreen" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The risk evaluation completed. Inspect `status` for the decision; HTTP is always `200` for completed lookups.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailRiskResponse" + }, + "examples": { + "Clean address": { + "summary": "Deliverable address with no breach exposure", + "value": { + "request_id": "82397be9-5921-4970-a979-2d5b43e08b33", + "status": "Approved", + "email": { + "status": "Approved", + "email": "alice@example.com", + "is_breached": false, + "breaches": [], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 0, + "verified_at": null, + "warnings": [], + "lifecycle": [], + "matches": [], + "email_intelligence": { + "score": 4.0, + "deliverable": "high", + "breach_risk_level": "Low", + "breach_risk_index": 4, + "is_recent_breach": false, + "is_frequent_breached": false, + "is_disposable": false, + "is_free_domain": false, + "is_breached": false, + "no_of_breaches": 0, + "reason_codes": [] + }, + "enrichment": { + "score": 4.0, + "deliverable": "high", + "breach_risk_level": "Low", + "breach_risk_index": 4, + "is_recent_breach": false, + "is_frequent_breached": false, + "is_disposable": false, + "is_free_domain": false, + "is_breached": false, + "no_of_breaches": 0, + "reason_codes": [] + } + }, + "vendor_data": "lead-123", + "metadata": { + "source": "signup_prescreen" + }, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + }, + "Breached address": { + "summary": "Address found in known breaches", + "value": { + "request_id": "d1e5c2a8-6f30-4c1a-8b2d-3e4f5a6b7c8d", + "status": "In Review", + "email": { + "status": "In Review", + "email": "bob@example.org", + "is_breached": true, + "breaches": [ + { + "name": "ExampleBreach2023", + "domain": "example.org", + "breach_date": "2023-06-01", + "breach_emails_count": 1200000, + "description": "A data breach affecting example.org accounts.", + "logo_path": "https://logos.example.org/example.png", + "data_classes": [ + "email_addresses", + "passwords" + ], + "is_verified": true + } + ], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 0, + "verified_at": null, + "warnings": [], + "lifecycle": [], + "matches": [], + "email_intelligence": { + "score": 58.0, + "deliverable": "high", + "breach_risk_level": "High", + "breach_risk_index": 72, + "is_recent_breach": true, + "is_frequent_breached": true, + "is_disposable": false, + "is_free_domain": true, + "is_breached": true, + "no_of_breaches": 4, + "reason_codes": [ + { + "code": "IR-F784B630", + "direction": "risk", + "message": "Email's digital footprint diversity is Very Low", + "impact_level": "low" + } + ] + }, + "enrichment": { + "score": 58.0, + "deliverable": "high", + "breach_risk_level": "High", + "breach_risk_index": 72, + "is_recent_breach": true, + "is_frequent_breached": true, + "is_disposable": false, + "is_free_domain": true, + "is_breached": true, + "no_of_breaches": 4, + "reason_codes": [ + { + "code": "IR-F784B630", + "direction": "risk", + "message": "Email's digital footprint diversity is Very Low", + "impact_level": "low" + } + ] + } + }, + "vendor_data": "lead-125", + "metadata": null, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/EmailRiskValidationError" + }, + "403": { + "$ref": "#/components/responses/RiskPermissionDenied" + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/ip/risk/": { + "post": { + "summary": "Risk-score an IP address", + "description": "Return geolocation, network ownership, VPN/Tor and data-center flags, threat data, and `country_mismatch` for a supplied IP address. The endpoint creates an API session and returns its id as `request_id`.\n\n**Decision.** `status` is `In Review` when the resolved country conflicts with `claimed_country`, or when the address is associated with VPN, Tor, or data-center infrastructure. Otherwise it is `Approved`.\n\n**Best effort.** IP enrichment is best effort. If no enrichment source returns usable data, the endpoint returns an approved response with `ip.ip_address: null`, `ip.country_mismatch: null`, and an empty `ip.threat` object.\n\n**Billing.** Live calls use IP Risk API pricing. The request is billable once the lookup starts.\n\n**Sandbox.** Sandbox keys validate the request and return deterministic approved data without consuming credits, creating a session, or contributing to Networks.\n\n**Authentication.** Send your application's API key in `x-api-key`. Missing or invalid credentials return `403` - this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests per minute per API key across POST/PATCH/DELETE endpoints (transaction creates and event ingestion have a separate budget of 600).", + "operationId": "post_v3ip_risk", + "x-didit-sdk": { + "resource": "ip", + "method": "assess_risk", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/ip/risk/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"ip_address\": \"8.8.8.8\",\n \"claimed_country\": \"ESP\",\n \"user_agent\": \"Mozilla/5.0\",\n \"vendor_data\": \"lead-123\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.ip.assess_risk(\n ip_address=\"8.8.8.8\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.ip.assessRisk({\n ip_address: \"8.8.8.8\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IP.AssessRisk(ctx, didit.IPRiskRequest{\n\tIPAddress: \"8.8.8.8\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.IPRiskRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.ip().assessRisk(IPRiskRequest.builder()\n .ipAddress(\"8.8.8.8\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "ip->assessRisk([\n 'ip_address' => '8.8.8.8',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.ip.assess_risk(ip_address: \"8.8.8.8\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Ip.AssessRiskAsync(new IPRiskRequest\n{\n IpAddress = \"8.8.8.8\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IPRiskRequest" + }, + "examples": { + "Minimal": { + "summary": "Score an address with defaults", + "value": { + "ip_address": "8.8.8.8" + } + }, + "With claimed country": { + "summary": "Score with a claimed country for mismatch detection", + "value": { + "ip_address": "8.8.8.8", + "claimed_country": "ESP", + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15", + "vendor_data": "lead-123" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The risk evaluation completed. Inspect `status` for the decision; HTTP is always `200` for completed lookups.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IPRiskResponse" + }, + "examples": { + "Clean residential IP": { + "summary": "Residential address, claimed country matches", + "value": { + "request_id": "9a96ed2a-721e-4b86-8cbf-d61eda529279", + "status": "Approved", + "ip": { + "status": "Approved", + "ip_address": "192.0.2.10", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_state": "Madrid", + "ip_city": "Madrid", + "latitude": 40.4168, + "longitude": -3.7038, + "isp": "Example Telecom", + "organization": "Example Telecom SA", + "is_vpn_or_tor": false, + "is_data_center": false, + "time_zone": "Europe/Madrid", + "time_zone_offset": "+0200", + "asn_number": 3352, + "asn_organization": "EXAMPLE-TELECOM", + "connection_type": "broadband", + "carrier": "Example Telecom", + "proxy_type": null, + "claimed_country": "ESP", + "country_mismatch": false, + "threat": {} + }, + "vendor_data": "lead-123", + "metadata": null, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + }, + "Datacenter mismatch": { + "summary": "Data-center address conflicting with the claimed country", + "value": { + "request_id": "5b1d4e9a-3c7b-4f8e-9a2d-1b0c9d8e7f6a", + "status": "In Review", + "ip": { + "status": "In Review", + "ip_address": "8.8.8.8", + "ip_country": "United States", + "ip_country_code": "US", + "ip_state": "California", + "ip_city": "Mountain View", + "latitude": 37.386, + "longitude": -122.0838, + "isp": "Google", + "organization": "Google LLC", + "is_vpn_or_tor": true, + "is_data_center": true, + "time_zone": "America/Los_Angeles", + "time_zone_offset": "-0700", + "asn_number": 15169, + "asn_organization": "GOOGLE, US", + "connection_type": "hosting", + "carrier": null, + "proxy_type": "VPN", + "claimed_country": "ESP", + "country_mismatch": true, + "threat": { + "is_proxy": true, + "is_data_center": true, + "is_tor": false + } + }, + "vendor_data": "lead-126", + "metadata": null, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/IPRiskValidationError" + }, + "403": { + "$ref": "#/components/responses/RiskPermissionDenied" + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/phone/risk/": { + "post": { + "summary": "Risk-score a phone number", + "description": "Resolve a phone number and return carrier, line-type, porting, disposable-number, cross-session match, and normalized risk-intelligence signals without sending an OTP. The endpoint creates an API session and returns its id as `request_id`.\n\n**Decision.** The top-level `status` mirrors `phone.status`. It is normally `Approved`; the risk evaluation can produce `In Review` or `Declined`.\n\n**Compatibility aliases.** Live responses expose the normalized intelligence object as both `phone.phone_intelligence` and `phone.enrichment`. The values are identical. New integrations can read `phone.enrichment`. Both keys are absent when no usable intelligence data was returned.\n\n**Billing.** Live calls use Phone Risk API pricing. The request is billable once the lookup starts, even when some best-effort fields are unavailable.\n\n**Sandbox.** Sandbox keys validate the request and return deterministic approved data without sending an OTP, consuming credits, creating a session, or contributing to Networks.\n\n**Authentication.** Send your application's API key in `x-api-key`. Missing or invalid credentials return `403` - this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests per minute per API key across POST/PATCH/DELETE endpoints (transaction creates and event ingestion have a separate budget of 600).\n\n**Social footprint.** Set `enable_social` to `true` to add a `phone_social` block: the online platforms this number is registered on, with per-category counts and any profile details available. `profiles_registered: 0` means the platforms were checked and the number was found on none of them - a valid, billable answer. The block is absent when the add-on was not requested or the check could not be completed, and it is not billed in that case. Billing adds one `phone_social` unit per request on top of this endpoint's own price.", + "operationId": "post_v3phone_risk", + "x-didit-sdk": { + "resource": "phone", + "method": "assess_risk", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/phone/risk/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"phone_number\": \"+14155552671\",\n \"country_code\": \"US\",\n \"vendor_data\": \"lead-123\",\n \"metadata\": {\"source\": \"signup_prescreen\"}\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.phone.assess_risk(\n phone_number=\"+14155552671\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.phone.assessRisk({\n phone_number: \"+14155552671\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Phone.AssessRisk(ctx, didit.PhoneRiskRequest{\n\tPhoneNumber: \"+14155552671\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.PhoneRiskRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.phone().assessRisk(PhoneRiskRequest.builder()\n .phoneNumber(\"+14155552671\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "phone->assessRisk([\n 'phone_number' => '+14155552671',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.phone.assess_risk(phone_number: \"+14155552671\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Phone.AssessRiskAsync(new PhoneRiskRequest\n{\n PhoneNumber = \"+14155552671\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PhoneRiskRequest" + }, + "examples": { + "Minimal": { + "summary": "Score a number with defaults", + "value": { + "phone_number": "+14155552671" + } + }, + "Full context": { + "summary": "Score with signals, country hint, and correlation ids", + "value": { + "phone_number": "+14155552671", + "country_code": "US", + "signals": { + "ip": "192.0.2.1", + "device_id": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2", + "device_platform": "ios", + "device_model": "iPhone17,2", + "os_version": "18.0.1", + "app_version": "1.2.34", + "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1" + }, + "vendor_data": "lead-123", + "metadata": { + "source": "signup_prescreen" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The risk evaluation completed. Inspect `status` for the decision; HTTP is always `200` for completed lookups.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PhoneRiskResponse" + }, + "examples": { + "Clean mobile number": { + "summary": "Clean mobile number with intelligence coverage", + "value": { + "request_id": "4fd09d2e-9bc9-4c4e-9c46-7f625ac0ab62", + "status": "Approved", + "phone": { + "status": "Approved", + "phone_number_prefix": "+1", + "phone_number": "4155552671", + "full_number": "+14155552671", + "country_code": "US", + "country_name": "United States", + "carrier": { + "name": "Example Mobile", + "type": "mobile" + }, + "is_disposable": false, + "is_virtual": false, + "verification_method": null, + "verification_attempts": 0, + "verified_at": null, + "warnings": [], + "lifecycle": [], + "matches": [], + "phone_intelligence": { + "score": 3.0, + "phone_type_risk": "Low", + "phone_trust_index": 97, + "network_stability_index": 95, + "is_disposable": false, + "is_ported": false, + "is_recently_ported": false, + "carrier_type": "mobile", + "reason_codes": [] + }, + "enrichment": { + "score": 3.0, + "phone_type_risk": "Low", + "phone_trust_index": 97, + "network_stability_index": 95, + "is_disposable": false, + "is_ported": false, + "is_recently_ported": false, + "carrier_type": "mobile", + "reason_codes": [] + } + }, + "vendor_data": "lead-123", + "metadata": { + "source": "signup_prescreen" + }, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + }, + "Number in review": { + "summary": "Disposable or virtual number flagged for review", + "value": { + "request_id": "8c22e1f0-4b3d-4a3a-9d1e-2f6b7c8d9e0f", + "status": "In Review", + "phone": { + "status": "In Review", + "phone_number_prefix": "+44", + "phone_number": "7700900123", + "full_number": "+447700900123", + "country_code": "GB", + "country_name": "United Kingdom", + "carrier": { + "name": "Example VoIP", + "type": "voip" + }, + "is_disposable": true, + "is_virtual": true, + "verification_method": null, + "verification_attempts": 0, + "verified_at": null, + "warnings": [], + "lifecycle": [], + "matches": [], + "phone_intelligence": { + "score": 72.0, + "phone_type_risk": "High", + "phone_trust_index": 28, + "network_stability_index": 40, + "is_disposable": true, + "is_ported": true, + "is_recently_ported": true, + "carrier_type": "voip", + "reason_codes": [ + { + "code": "IR-5AC67A32", + "direction": "risk", + "message": "Phone type identified as non-mobile", + "impact_level": "medium" + } + ] + }, + "enrichment": { + "score": 72.0, + "phone_type_risk": "High", + "phone_trust_index": 28, + "network_stability_index": 40, + "is_disposable": true, + "is_ported": true, + "is_recently_ported": true, + "carrier_type": "voip", + "reason_codes": [ + { + "code": "IR-5AC67A32", + "direction": "risk", + "message": "Phone type identified as non-mobile", + "impact_level": "medium" + } + ] + } + }, + "vendor_data": "lead-124", + "metadata": null, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/PhoneRiskValidationError" + }, + "403": { + "$ref": "#/components/responses/RiskPermissionDenied" + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/": { + "post": { + "summary": "Create a verification session", + "description": "Create a User Verification (KYC) or Business Verification (KYB) session and receive a hosted verification `url` plus a `session_token` to redirect or embed for your end user. The `workflow_id` selects which verification steps run and whether the session is KYC or KYB.\n\nCall this from your backend (never the browser — the API key is a secret) whenever a user or business needs to be verified: at signup, before a sensitive action, or when re-verification is required. Prerequisites: a published workflow (created in the Console [Workflows](https://docs.didit.me/console/workflows) page) and an application API key with available credits. Sandbox applications bypass the credit check.\n\n**Idempotency:** when `vendor_data` is provided and an unfinished session (`Not Started`, `In Progress`, `Resubmitted`, or `Awaiting User`) with the same `vendor_data` already exists on the workflow's **current latest published version** (and that session already has a hosted verification URL), the existing session is returned (still `201`) instead of creating a duplicate — with its `callback` and `metadata` updated to the newly provided values. An unfinished session created on an older version of the workflow (one that has since been republished) is **not** reused; a new session pinned to the latest published version is created instead. Sessions in `Approved`, `Declined`, `In Review`, `Expired`, `Abandoned`, or `Kyc Expired` are never reused.\n\n**Side effects:** the hosted verification URL is generated and stored; if `contact_details.send_notification_emails` is `true` and an email is provided, a verification invite email is sent. The session expires after the workflow's configured session expiration time. For KYB workflows, `contact_details.phone` and `expected_details` are ignored.", + "operationId": "post_v3_session_create", + "x-didit-sdk": { + "resource": "sessions", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "201": { + "description": "Session created (or returned, if an unfinished session with the same `vendor_data` already exists on the workflow's latest published version). The response is the serialized session, including the hosted verification `url` to redirect the user to.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "session_id", + "session_number", + "session_token", + "url", + "vendor_data", + "metadata", + "status", + "workflow_id", + "workflow_version", + "callback" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of the verification session. Use this id when calling `GET /v3/session/{sessionId}/decision/`.", + "example": "11111111-2222-3333-4444-555555555555" + }, + "session_number": { + "type": "integer", + "description": "Sequential, human-friendly number assigned to the session inside your application. Useful for support and dashboards.", + "example": 43762 + }, + "session_token": { + "type": "string", + "description": "12-character URL-safe token that authorizes the end user to access the hosted verification flow at `url`. Valid until the session expires. Treat it as a secret — anyone holding it can open the verification flow for this session.", + "example": "3FaJ9wLqX2Mz" + }, + "url": { + "type": "string", + "format": "uri", + "description": "Hosted verification URL to redirect the end user to. The URL embeds the `session_token` and, if configured, uses your white-label domain. When the request includes `language`, the URL contains a language path segment — `https://verify.didit.me/{language}/session/{session_token}` (e.g. `/es/session/...`); without `language`, the segment is omitted.", + "example": "https://verify.didit.me/session/3FaJ9wLqX2Mz" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Identifier you passed in the request to link the session to a user or business in your own system. Echoed back verbatim. Null when not provided.", + "example": "user-123" + }, + "metadata": { + "description": "Arbitrary JSON payload you stored with the session at creation time. Echoed back verbatim — whatever JSON value you sent (object, string, number, array) is returned as-is, though a JSON object is recommended. Not shown to the end user. Always present in responses; `null` when not provided at creation time.", + "example": { + "user_type": "premium", + "account_id": "ABC123" + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The tags on the session, by name.", + "example": [ + "VIP", + "ONBOARDING Q4" + ] + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Current status of the session. Newly created sessions return `Not Started`. If a non-finished session already existed for the same `vendor_data` on the workflow's latest published version, the returned status reflects that existing session.", + "example": "Not Started" + }, + "callback": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Final redirect URL the user is sent to after completing the flow. Didit appends `?verificationSessionId={session_id}&status={status}` to this URL. Falls back to the workflow's configured callback URL when not provided in the request; `null` when neither is set. When the application has allowed domains and this URL is a web URL outside them (for example a workflow default set before the list changed), the hosted flow does not redirect the user to it.", + "example": "https://example.com/verification/callback" + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier of the workflow this session runs on. Always the workflow's stable group identifier — even if you referenced a specific workflow version UUID in the request (backward-compatible lookup), the response carries the stable `workflow_id`. Use it to correlate the session with the workflow you configured in the Console.", + "example": "11111111-2222-3333-4444-555555555555" + }, + "workflow_version": { + "type": "integer", + "description": "Version number of the published workflow version the session was pinned to at creation. Didit resolves `workflow_id` to the workflow's latest published version when the session is created, and the session keeps running that version even if the workflow is republished later.", + "example": 3 + } + } + }, + "examples": { + "KYC session": { + "summary": "KYC session created", + "value": { + "session_id": "11111111-2222-3333-4444-555555555555", + "session_number": 43762, + "session_token": "3FaJ9wLqX2Mz", + "url": "https://verify.didit.me/en/session/3FaJ9wLqX2Mz", + "vendor_data": "user-123", + "metadata": { + "user_type": "premium", + "account_id": "ABC123" + }, + "status": "Not Started", + "workflow_id": "11111111-2222-3333-4444-555555555555", + "workflow_version": 3, + "callback": "https://example.com/verification/callback" + } + }, + "KYB session": { + "summary": "KYB session created", + "value": { + "session_id": "22222222-3333-4444-5555-666666666666", + "session_number": 43763, + "session_token": "Yk7pQ2vN8aBc", + "url": "https://verify.didit.me/en/session/Yk7pQ2vN8aBc", + "vendor_data": "company-acme-001", + "metadata": { + "tier": "enterprise" + }, + "status": "Not Started", + "workflow_id": "33333333-4444-5555-6666-777777777777", + "workflow_version": 1, + "callback": "https://example.com/kyb/callback" + } + } + } + } + } + }, + "400": { + "description": "Bad request — the payload failed validation (missing or unknown `workflow_id`, invalid `language`, blocklisted `vendor_data`, malformed `portrait_image`, `address` without `country`/`poa_country`, invalid `sandbox_scenario`, `sign` documents, fields or values the workflow's E-signature step refuses, such as `signature_document_not_supported`, `signature_document_conversion_failed`, `field_anchor_not_found` or `field_value_missing`, etc.) or your organization does not have enough credits to start the session.\n\nThe body is an object keyed by the offending field name (or `detail` for non-field errors). Values are an **array of strings** for input-validation failures (e.g. missing required field, invalid language, oversized portrait image) and a **plain string** for failures detected while creating the session (unknown `workflow_id`, blocklisted `vendor_data`, no reference face available for a Face Match workflow). Handle both shapes.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message for non-field errors (e.g. insufficient credits). Field-level failures use the field name as the key instead, with a string or array-of-strings value.", + "example": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + }, + "examples": { + "Missing workflow_id": { + "summary": "Required field omitted (array-of-strings shape)", + "value": { + "workflow_id": [ + "This field is required." + ] + } + }, + "Invalid workflow_id": { + "summary": "Unknown workflow_id (plain-string shape)", + "value": { + "workflow_id": "Invalid workflow_id." + } + }, + "Insufficient credits": { + "summary": "Organization is out of credits", + "value": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + }, + "Blocklisted vendor_data": { + "summary": "vendor_data is blocklisted", + "value": { + "vendor_data": "This user or business has been blocked and cannot create new verification sessions." + } + }, + "Portrait image too large": { + "summary": "portrait_image fails size validation", + "value": { + "portrait_image": [ + "Image size exceeds 2MB." + ] + } + }, + "No stored face for vendor_data": { + "summary": "Face Match workflow without portrait_image and no stored face for the vendor_data user", + "value": { + "portrait_image": "No stored face image was found for this user. Send a portrait_image, or complete an approved verification with face liveness or an ID document for this vendor_data first." + } + }, + "Portrait image required without vendor_data": { + "summary": "Face Match workflow without portrait_image and without vendor_data", + "value": { + "portrait_image": "A portrait image is required to perform face match for this workflow. Send a portrait_image, or provide the vendor_data of a user with a previously stored face image to reuse it." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failed. A wrong, revoked or expired key returns `401`. The endpoint returns `403` when the `x-api-key` header is missing or the API key is valid but the client does not have permission to create sessions in this application (for example the API key belongs to a different organization). These failures share the body `{\"detail\": \"You do not have permission to perform this action.\"}`. Check that the `x-api-key` header is present and belongs to the intended application, re-fetch the application API key via the [Auth API](/auth-api/get-credentials) if needed, and retry.\n\n**Deleted application:** for a few minutes after an application is deleted, a request made with one of its API keys can answer `{\"detail\": \"This application was deleted.\", \"code\": \"application_deleted\"}`. Do not retry: the application and its API keys no longer exist. After those few minutes, the key is refused like any other invalid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You do not have permission to perform this action." + }, + "code": { + "type": "string", + "description": "Machine-readable reason, present only on some failures. `application_deleted`: the API key belongs to an application that was deleted.", + "example": "application_deleted" + } + }, + "required": [ + "detail" + ] + }, + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header or invalid API key", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "No permission": { + "summary": "Client cannot create sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Application deleted": { + "summary": "API key of a deleted application", + "value": { + "detail": "This application was deleted.", + "code": "application_deleted" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded — more than 600 session-create requests in a 60-second window from the same API key / IP. Inspect `Retry-After` and back off before retrying.\n\n**Note:** The source code also defines a `FREE_SESSION_RATE_LIMIT` of 10 calls/minute alongside `PAID_SESSION_RATE_LIMIT=600` (`sessions/serializers/session.py`), but that free-tier ceiling is **not applied to this V3 endpoint** — the rate-limit middleware enforces the 600/min `SESSION_CREATE_RATE_LIMIT` regardless of plan. 600/min is the only limit you can hit here.", + "headers": { + "Retry-After": { + "description": "Seconds the caller must wait before retrying the request.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 42 + } + }, + "X-RateLimit-Limit": { + "description": "Maximum number of requests allowed in the current 60-second window for the limit that was breached (600 for session-create). Absent on the sandbox-quota variant.", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Number of requests still allowed in the current window for the breached limit. Absent on the sandbox-quota variant.", + "schema": { + "type": "integer", + "minimum": 0, + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "UTC epoch seconds when the current rate-limit window resets. Absent on the sandbox-quota variant.", + "schema": { + "type": "integer", + "minimum": 0, + "example": 1747497600 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable explanation of the rate-limit breach. Use this message to distinguish which of the three limits was hit.", + "example": "Session creation rate limit exceeded. You can make up to 600 requests per minute." + } + } + }, + "examples": { + "Session-create limit": { + "summary": "More than 600 session-create requests per minute", + "value": { + "detail": "Session creation rate limit exceeded. You can make up to 600 requests per minute." + } + }, + "Sandbox session quota": { + "summary": "Sandbox application exceeded 500 sessions in 24 hours", + "value": { + "detail": "Sandbox session quota exceeded (500 sessions per 24h). Wait for the window to reset. Expected available in 3600 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/session/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"workflow_id\": \"11111111-2222-3333-4444-555555555555\",\n \"vendor_data\": \"user-123\",\n \"callback\": \"https://example.com/verification/callback\",\n \"callback_method\": \"both\",\n \"metadata\": {\"user_type\": \"premium\", \"account_id\": \"ABC123\"},\n \"language\": \"en\",\n \"contact_details\": {\n \"email\": \"john.doe@example.com\",\n \"send_notification_emails\": true,\n \"email_lang\": \"en\",\n \"phone\": \"+14155552671\"\n },\n \"expected_details\": {\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"date_of_birth\": \"1990-05-15\",\n \"id_country\": \"USA\",\n \"expected_document_types\": [\"P\", \"ID\"]\n }\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.create(\n workflow_id=\"11111111-2222-3333-4444-555555555555\",\n vendor_data=\"user-123\",\n callback=\"https://example.com/verification/callback\",\n callback_method=\"both\",\n metadata={\"user_type\": \"premium\", \"account_id\": \"ABC123\"},\n language=\"en\",\n contact_details={\n \"email\": \"john.doe@example.com\",\n \"send_notification_emails\": True,\n \"email_lang\": \"en\",\n \"phone\": \"+14155552671\",\n },\n expected_details={\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"date_of_birth\": \"1990-05-15\",\n \"nationality\": \"USA\",\n \"id_country\": \"USA\",\n \"expected_document_types\": [\"P\", \"ID\"],\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.create({\n workflow_id: \"11111111-2222-3333-4444-555555555555\",\n vendor_data: \"user-123\",\n callback: \"https://example.com/verification/callback\",\n callback_method: \"both\",\n metadata: { user_type: \"premium\", account_id: \"ABC123\" },\n language: \"en\",\n contact_details: {\n email: \"john.doe@example.com\",\n send_notification_emails: true,\n email_lang: \"en\",\n phone: \"+14155552671\",\n },\n expected_details: {\n first_name: \"John\",\n last_name: \"Doe\",\n date_of_birth: \"1990-05-15\",\n nationality: \"USA\",\n id_country: \"USA\",\n expected_document_types: [\"P\", \"ID\"],\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Create(ctx, didit.SessionsCreateParams{\n\tWorkflowID: \"11111111-2222-3333-4444-555555555555\",\n\tVendorData: didit.String(\"user-123\"),\n\tCallback: didit.String(\"https://example.com/verification/callback\"),\n\tCallbackMethod: didit.Some(didit.SessionsCreateParamsCallbackMethod(\"both\")),\n\tMetadata: didit.Some[any](map[string]any{\"user_type\": \"premium\", \"account_id\": \"ABC123\"}),\n\tLanguage: didit.Some(didit.SessionsCreateParamsLanguage(\"en\")),\n\tContactDetails: didit.Some(didit.SessionsCreateParamsContactDetails{\n\t\tEmail: didit.String(\"john.doe@example.com\"),\n\t\tSendNotificationEmails: didit.Bool(true),\n\t\tEmailLang: didit.Some(didit.SessionsCreateParamsContactDetailsEmailLang(\"en\")),\n\t\tPhone: didit.String(\"+14155552671\"),\n\t}),\n\tExpectedDetails: didit.Some(didit.SessionsCreateParamsExpectedDetails{\n\t\tFirstName: didit.String(\"John\"),\n\t\tLastName: didit.String(\"Doe\"),\n\t\tDateOfBirth: didit.String(\"1990-05-15\"),\n\t\tNationality: didit.String(\"USA\"),\n\t\tIDCountry: didit.String(\"USA\"),\n\t\tExpectedDocumentTypes: didit.Some([]didit.SessionsCreateParamsExpectedDetailsExpectedDocumentTypes{\"P\", \"ID\"}),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SessionsCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().create(SessionsCreateParams.builder()\n .workflowId(\"11111111-2222-3333-4444-555555555555\")\n .vendorData(\"user-123\")\n .callback(\"https://example.com/verification/callback\")\n .callbackMethod(SessionsCreateParams.CallbackMethod.of(\"both\"))\n .metadata(Map.of(\"user_type\", \"premium\", \"account_id\", \"ABC123\"))\n .language(SessionsCreateParams.Language.of(\"en\"))\n .contactDetails(SessionsCreateParams.ContactDetails.builder()\n .email(\"john.doe@example.com\")\n .sendNotificationEmails(true)\n .emailLang(SessionsCreateParams.ContactDetails.EmailLang.of(\"en\"))\n .phone(\"+14155552671\")\n .build())\n .expectedDetails(SessionsCreateParams.ExpectedDetails.builder()\n .firstName(\"John\")\n .lastName(\"Doe\")\n .dateOfBirth(LocalDate.parse(\"1990-05-15\"))\n .nationality(\"USA\")\n .idCountry(\"USA\")\n .expectedDocumentTypes(List.of(SessionsCreateParams.ExpectedDetails.ExpectedDocumentTypes.of(\"P\"), SessionsCreateParams.ExpectedDetails.ExpectedDocumentTypes.of(\"ID\")))\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->create([\n 'workflow_id' => '11111111-2222-3333-4444-555555555555',\n 'vendor_data' => 'user-123',\n 'callback' => 'https://example.com/verification/callback',\n 'callback_method' => 'both',\n 'metadata' => ['user_type' => 'premium', 'account_id' => 'ABC123'],\n 'language' => 'en',\n 'contact_details' => [\n 'email' => 'john.doe@example.com',\n 'send_notification_emails' => true,\n 'email_lang' => 'en',\n 'phone' => '+14155552671',\n ],\n 'expected_details' => [\n 'first_name' => 'John',\n 'last_name' => 'Doe',\n 'date_of_birth' => '1990-05-15',\n 'nationality' => 'USA',\n 'id_country' => 'USA',\n 'expected_document_types' => ['P', 'ID'],\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.create(\n workflow_id: \"11111111-2222-3333-4444-555555555555\",\n vendor_data: \"user-123\",\n callback: \"https://example.com/verification/callback\",\n callback_method: \"both\",\n metadata: { user_type: \"premium\", account_id: \"ABC123\" },\n language: \"en\",\n contact_details: {\n email: \"john.doe@example.com\",\n send_notification_emails: true,\n email_lang: \"en\",\n phone: \"+14155552671\",\n },\n expected_details: {\n first_name: \"John\",\n last_name: \"Doe\",\n date_of_birth: \"1990-05-15\",\n nationality: \"USA\",\n id_country: \"USA\",\n expected_document_types: [\"P\", \"ID\"],\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.CreateAsync(new SessionsCreateParams\n{\n WorkflowId = \"11111111-2222-3333-4444-555555555555\",\n VendorData = \"user-123\",\n Callback = \"https://example.com/verification/callback\",\n CallbackMethod = new SessionsCreateParamsCallbackMethod(\"both\"),\n Metadata = new Dictionary { [\"user_type\"] = \"premium\", [\"account_id\"] = \"ABC123\" },\n Language = new SessionsCreateParamsLanguage(\"en\"),\n ContactDetails = new SessionsCreateParamsContactDetails\n {\n Email = \"john.doe@example.com\",\n SendNotificationEmails = true,\n EmailLang = new SessionsCreateParamsContactDetailsEmailLang(\"en\"),\n Phone = \"+14155552671\",\n },\n ExpectedDetails = new SessionsCreateParamsExpectedDetails\n {\n FirstName = \"John\",\n LastName = \"Doe\",\n DateOfBirth = \"1990-05-15\",\n Nationality = \"USA\",\n IdCountry = \"USA\",\n ExpectedDocumentTypes = new SessionsCreateParamsExpectedDetailsExpectedDocumentTypes[] { \"P\", \"ID\" },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "workflow_id" + ], + "properties": { + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier of the workflow that defines which verification steps the session will run. Workflows are created and managed in the [Workflows](/console/workflows) page of the Console. The `workflow_id` also implicitly selects whether the session is KYC or KYB.", + "example": "11111111-2222-3333-4444-555555555555", + "x-readme-id": "0.0" + }, + "vendor_data": { + "type": "string", + "description": "A unique identifier for the user being verified, such as a UUID, email, or internal user ID. This field is used for: (1) **User grouping** — sessions with the same vendor_data are linked to the same user profile in the Users tab. (2) **Cross-session duplicate detection** — when checking for duplicated faces, documents, phone numbers, emails, IP addresses, or device fingerprints, sessions with the same vendor_data are treated as the same user and excluded from matches. Without vendor_data, every session is treated as a unique user and all potential duplicates are surfaced. We strongly recommend always providing a vendor_data to reduce noise in duplicate detection.", + "example": "user-123", + "x-readme-id": "0.1" + }, + "multi_account_segment": { + "type": "string", + "maxLength": 64, + "description": "Optional duplicate-detection realm inside your organization. Duplicate face checks compare this session only with other sessions that carry the same segment, so separate account bases (personal and business accounts, or one per B2B sub-client) do not flag a person who legitimately holds one account in each. Omit it to use the single default realm, which is what every session uses today. Blocklist and allowlist matching ignore this field.", + "example": "business-accounts" + }, + "callback": { + "type": "string", + "description": "URL to redirect the user to after verification completes. Didit automatically appends `verificationSessionId` and `status` (Approved, Declined, In Review) as query parameters. Custom URL schemes (e.g. `myapp://`) are supported for mobile callbacks. If omitted, the workflow's default `callback_url` is used. When the application has allowed domains (`allowed_domains` on `PATCH /v3/webhook/`), an `http` or `https` callback must be on one of those origins (same scheme, host and port), otherwise the request fails with 400 on `callback`: `callback must be on one of the application's allowed domains: https://app.example.com`. Custom URL schemes are always accepted.", + "example": "https://example.com/verification/callback", + "x-readme-id": "0.2" + }, + "callback_method": { + "type": "string", + "description": "Determines which device should handle the redirect to the provided callback URL. Use `initiator` to redirect only the device that started the flow, `completer` for the device that finishes it, or `both` to allow either device to trigger the callback. If you ever notice the callback not triggering reliably, we recommend setting this value to `both`.", + "enum": [ + "initiator", + "completer", + "both" + ], + "default": "initiator", + "example": "both", + "x-readme-id": "0.3" + }, + "metadata": { + "description": "Arbitrary JSON stored with the session and echoed back in the response and webhooks. Any JSON value is accepted (object, string, number, array), but a JSON object of key/value pairs is recommended. Not shown to the end user. Use it to pass your own correlation ids, A/B variants, or business context.", + "example": { + "user_type": "premium", + "account_id": "ABC123" + }, + "x-readme-id": "0.4" + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "maxLength": 50 + }, + "maxItems": 10, + "description": "Tags added to the session when it is created, by name. Up to 10, each up to 50 characters. Names are case-insensitive and stored in upper case. A name that does not exist yet in the application is created as a new tag. The tags show on the session in the console, where you can filter by them.", + "example": [ + "VIP", + "ONBOARDING Q4" + ] + }, + "language": { + "type": "string", + "description": "Language code (ISO 639-1) for the verification process interface. Controls the language displayed to the end user during verification. If not provided, the browser's language will be automatically detected and used. Check all the supported languages [here](/integration/supported-languages).", + "enum": [ + "en", + "ar", + "bg", + "bn", + "bs", + "ca", + "cnr", + "cs", + "da", + "de", + "el", + "es", + "et", + "fa", + "fi", + "fr", + "he", + "hi", + "hr", + "hu", + "hy", + "id", + "it", + "ja", + "ka", + "kk", + "ko", + "ky", + "lt", + "lv", + "mk", + "mn", + "ms", + "nl", + "no", + "pl", + "pt-BR", + "pt", + "ro", + "ru", + "sk", + "sl", + "so", + "sq", + "sr", + "sv", + "th", + "tr", + "uk", + "uz", + "vi", + "zh-CN", + "zh-TW", + "zh" + ], + "example": "en", + "x-readme-id": "0.5" + }, + "contact_details": { + "type": "object", + "description": "User contact information that can be used for notifications, prefilling verification forms, and phone verification. This includes email address, preferred language for communications, and phone number.", + "example": { + "email": "john.doe@example.com", + "send_notification_emails": true, + "email_lang": "en", + "phone": "+14155552671" + }, + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "Email address of the user (e.g., \"john.doe@example.com\") that will be used during the [Email Verification](/core-technology/email-verification/overview) step. If not provided, the user must provide it during the verification flow.", + "example": "john.doe@example.com", + "x-readme-id": "1.0" + }, + "send_notification_emails": { + "type": "boolean", + "default": false, + "description": "If true and an email is provided, Didit sends the initial \"Verify your identity\" email asynchronously when the User Verification (KYC) or Business Verification (KYB) session is created. Didit also sends verification status notifications for sessions requiring manual review to the provided email address (e.g., from 'In Review' to 'Approved' or 'Declined'). This helps users return to your application once their verification is complete. If you have white-label activated for the session, the email sent will be white-labeled.", + "example": true, + "x-readme-id": "1.1" + }, + "email_lang": { + "type": "string", + "description": "Language code (ISO 639-1) for email notifications. Controls the language of all email communications (e.g., \"en\", \"es\", \"fr\"). There is no stored default — when omitted, the verification invite email simply falls back to English (`en`) at send time. The enum below is a snapshot; the live source of truth for accepted values is the [supported languages](/integration/supported-languages) doc — always consult it before relying on a specific code.", + "enum": [ + "en", + "ar", + "bg", + "bn", + "bs", + "ca", + "cnr", + "cs", + "da", + "de", + "el", + "es", + "et", + "fa", + "fi", + "fr", + "he", + "hi", + "hr", + "hu", + "hy", + "id", + "it", + "ja", + "ka", + "kk", + "ko", + "ky", + "lt", + "lv", + "mk", + "mn", + "ms", + "nl", + "no", + "pl", + "pt-BR", + "pt", + "ro", + "ru", + "sk", + "sl", + "so", + "sq", + "sr", + "sv", + "th", + "tr", + "uk", + "uz", + "vi", + "zh-CN", + "zh-TW", + "zh" + ], + "example": "en", + "x-readme-id": "1.2" + }, + "phone": { + "type": "string", + "description": "Phone number in E.164 format (e.g., \"+14155552671\") that will be used during the [Phone Verification](/core-technology/phone-verification/overview) step. If not provided, the user must provide it during the verification flow. Ignored for Business Verification (KYB) workflows. **Important:** This phone number is only enforced if it is a valid E.164 phone number. If the provided number is invalid or cannot be parsed, it will be ignored and the user will be able to input any valid phone number during the Phone Verification step.", + "example": "+14155552671", + "x-readme-id": "1.3" + } + }, + "x-readme-id": "0.4" + }, + "expected_details": { + "type": "object", + "description": "Expected user details used to cross-validate against the data extracted from the user's ID document, Proof of Address, and other verification steps. Mismatches are surfaced as warnings on the decision; some fields (e.g. `id_country`, `expected_document_types`) also alter the user-facing flow. For Business Verification (KYB) workflows only the business fields (`company_name`, `registry_country`, `registration_number`) are used — they pre-fill the company registry search for the user — and all person-level fields are ignored.", + "example": { + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1990-05-15", + "nationality": "USA", + "id_country": "USA", + "expected_document_types": [ + "P", + "ID" + ] + }, + "properties": { + "first_name": { + "type": "string", + "description": "User's first name. For example, `John`. The matching uses fuzzy comparison, and you can tune the strictness by configuring the name match score threshold in the Console for both ID Document and Proof of Address workflows.", + "example": "John", + "x-readme-id": "1.0" + }, + "last_name": { + "type": "string", + "description": "User's last name. For example, `Doe`. The matching uses fuzzy comparison, and you can tune the strictness by configuring the name match score threshold in the Console for both ID Document and Proof of Address workflows.", + "example": "Doe", + "x-readme-id": "1.1" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "User's date of birth with format: YYYY-MM-DD. For example, `1990-05-15`.", + "example": "1990-05-15", + "x-readme-id": "1.2" + }, + "gender": { + "type": "string", + "nullable": true, + "enum": [ + "M", + "F" + ], + "default": null, + "description": "User's gender. Must be either 'M', 'F', or null.", + "example": "M", + "x-readme-id": "1.3" + }, + "nationality": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the applicant's country of origin. For example, `USA`. See the [full list of supported country codes](https://docs.didit.me/core-technology/id-verification/supported-documents-id-verification#supported-documents-by-country).", + "example": "USA", + "x-readme-id": "1.4" + }, + "country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code used as a fallback for both ID Verification and Proof of Address country-mismatch checks. Required when `address` is provided and neither `id_country` nor `poa_country` is set.", + "example": "USA", + "x-readme-id": "1.4.1" + }, + "id_country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the expected country of the applicant's ID document, which may differ from nationality. For example, `GBR`. Takes priority over `country` for ID verification country mismatch checks. See the [full list of supported country codes](https://docs.didit.me/core-technology/id-verification/supported-documents-id-verification#supported-documents-by-country).", + "example": "GBR", + "x-readme-id": "1.5" + }, + "poa_country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the expected country of the Proof of Address document. For example, `USA`. Takes priority over `country` for POA country mismatch checks. See the [full list of supported country codes](https://docs.didit.me/core-technology/id-verification/supported-documents-id-verification#supported-documents-by-country).", + "example": "USA", + "x-readme-id": "1.5.1" + }, + "address": { + "type": "string", + "description": "The address in a human readable format, including as much information as possible. For example, `123 Main St, San Francisco, CA 94105, USA`. When provided, you must also pass `country` or `poa_country`, otherwise the request fails with `400`.", + "example": "123 Main St, San Francisco, CA 94105, USA", + "x-readme-id": "1.6" + }, + "identification_number": { + "type": "string", + "description": "The user's document number, personal number, or tax number. For example, `123456789`.", + "example": "123456789", + "x-readme-id": "1.7" + }, + "ip_address": { + "type": "string", + "description": "Expected IP address for the session, in IPv4 or IPv6 format. If the actual IP address differs from this value, a warning will be logged. For example, `192.168.1.100` or `2001:db8::1`.", + "example": "192.168.1.100", + "x-readme-id": "1.8" + }, + "expected_document_types": { + "type": "array", + "description": "Restricts the document types the user can present during the ID verification (OCR) step. When set, the document selection screen only shows the requested types and the returned `documents_allowed` is filtered accordingly. Values are case-insensitive and deduplicated; unknown values are rejected with `400`. Allowed codes: `P` (passport), `ID` (national ID), `DL` (driver's license), `RP` (residence permit), `HIC` (health insurance card), `TC` (tax card), `SSC` (social security card).", + "items": { + "type": "string", + "enum": [ + "P", + "ID", + "DL", + "RP", + "HIC", + "TC", + "SSC" + ] + }, + "example": [ + "P", + "ID" + ], + "x-readme-id": "1.9" + }, + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Expected legal name of the business being verified. For example, `Tesco PLC`. Pre-fills the company-name field of the registry search in Business Verification (KYB) workflows; when `registration_number` is also provided, the registry lookup combines both identifiers. Ignored for KYC workflows.", + "example": "Tesco PLC", + "x-readme-id": "1.10" + }, + "registry_country": { + "type": "string", + "maxLength": 10, + "description": "ISO 3166-1 alpha-2 code of the country whose company registry the business is registered in. For example, `GB`. Where company registries operate at state or province level, append the ISO 3166-2 subdivision code as `XX-YY`: `US-CA` targets the California registry. Same format as `country_code` on the [KYB Registry Search API](https://docs.didit.me/standalone-apis/kyb-registry-search). Pre-selects the registry country in Business Verification (KYB) workflows; ignored for KYC workflows.", + "example": "US-CA", + "x-readme-id": "1.11" + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Expected registration number of the business in the company registry. For example, `00445790`. Pre-fills the registration-number field of the registry search in Business Verification (KYB) workflows; when `company_name` is also provided, the registry lookup combines both identifiers. Ignored for KYC workflows.", + "example": "00445790", + "x-readme-id": "1.12" + } + }, + "x-readme-id": "0.5" + }, + "portrait_image": { + "type": "string", + "format": "byte", + "description": "Base64-encoded portrait image of the end user's face (max 2MB; JPEG, PNG, WebP, or TIFF). Used as the reference image to match against the liveness capture when the workflow is a `Biometric Authentication` workflow with Face Match enabled, or any graph workflow where Face Match runs before ID Verification (OCR). Optional when the `vendor_data` user already has a stored face image: Didit reuses it automatically, resolved in priority order: approved liveness face, ePassport chip photo, ID document portrait, manually enrolled profile face (only faces from approved sessions or faces you enrolled yourself are reused). When provided, `portrait_image` always takes precedence over the stored face. If omitted and no stored face exists (or `vendor_data` is not sent), session creation fails with `400`. Ignored for other workflow types.", + "example": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", + "x-readme-id": "0.6" + }, + "sandbox_scenario": { + "type": "string", + "description": "Sandbox-only scenario slug. Only accepted when the API key belongs to a sandbox-mode application; rejected on live applications. When set, the create flow expands the scenario's bundled magic values onto the session's input fields (contact_details, expected_details) so the mocked providers reproduce the scenario's outcome through the real workflow pipeline. See the scenario catalog at GET /v1/sandbox/scenarios/ or [Sandbox & Test Data](/integration/sandbox-testing). You can also arm (or re-arm) a scenario after create via POST /v3/session/{sessionId}/sandbox/arm/, as long as the session is still `Not Started`. For non-doc lookup, use `lookup_partial_match`, `lookup_no_match`, or `lookup_provider_error` to test the configured attempt budget and fallback. For identity wallets, use `wallet_cancelled`, `wallet_timeout`, or `wallet_provider_error`; `approve` simulates a verified wallet. All catalog wallets can be tested in sandbox, including schemes unavailable on live applications. Simulated attempts are not charged.", + "example": "decline_document_expired" + }, + "sign": { + "type": "object", + "description": "E-signature input for this session. `documents` are the documents to sign, for a workflow whose E-signature step takes documents from the session (`signature_documents_source` `session` or `both`): up to 20 documents and 50 MB in total. `field_values` fills the fields whose `prefill` is `api.`, on workflow and session documents alike. Creating a session again for the same `workflow_id` and `vendor_data` returns the pending session; if that call sends `sign`, its documents, fields and values must be identical to the first ones, and anything different returns `400` with `{\"sign\": \"signature_document_changed\"}`. See [E-signature documents](/sessions-api/create-session#e-signature-documents).", + "properties": { + "documents": { + "type": "array", + "minItems": 1, + "maxItems": 20, + "items": { + "$ref": "#/components/schemas/SessionSignDocument" + } + }, + "field_values": { + "type": "object", + "description": "Values keyed by the `` of each `api.` pre-fill source: strings of 1 to 500 characters for `text` and `data` fields, booleans for a `checkbox`. `400` with `field_value_unknown` for a key no field uses, `field_value_missing` for a required field without its value, and `field_value_invalid` for a value that fails its field's validation.", + "additionalProperties": { + "oneOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 500 + }, + { + "type": "boolean" + } + ] + }, + "example": { + "credit_limit": "3,000 EUR" + } + } + } + }, + "user_state": { + "type": "string", + "maxLength": 16, + "description": "Where the person lives, when you know it: an ISO 3166-2 subdivision code (`US-IL`, `ES-M`) or a US state code or name (`IL`, `Illinois`). A state whose law requires explicit biometric consent (Illinois, Texas, Colorado) makes the verification flow ask for it before capturing a face, with trigger `declared_state`. An unrecognized value returns `400`. See [Biometric consent](/getting-started/biometric-consent).", + "example": "US-IL" + }, + "biometric_consent": { + "type": "object", + "description": "Explicit biometric consent you already collected from the person. It satisfies the requirement, so the verification flow does not ask again, and the decision records it as `status: \"given\"` with trigger `customer_collected`. An answer the person already gave in the flow is never replaced. See [Biometric consent](/getting-started/biometric-consent).", + "required": [ + "given_at", + "text_version" + ], + "properties": { + "given_at": { + "type": "string", + "format": "date-time", + "description": "When the person gave explicit biometric consent (ISO 8601). Cannot be in the future." + }, + "text_version": { + "type": "string", + "maxLength": 64, + "description": "Version id of the consent text the person accepted, in your own records." + } + } + } + }, + "example": { + "workflow_id": "11111111-2222-3333-4444-555555555555", + "vendor_data": "user-123", + "callback": "https://example.com/verification/callback", + "callback_method": "both", + "metadata": { + "user_type": "premium", + "account_id": "ABC123" + }, + "language": "en", + "contact_details": { + "email": "john.doe@example.com", + "send_notification_emails": true, + "email_lang": "en", + "phone": "+14155552671" + }, + "expected_details": { + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1990-05-15", + "id_country": "USA", + "expected_document_types": [ + "P", + "ID" + ] + } + } + }, + "examples": { + "Create KYC session": { + "summary": "Full happy-path payload for a User Verification (KYC) session", + "description": "Realistic create call that uses every commonly-set field: workflow selection, vendor linkage, callback routing, metadata, locale, contact prefill, and expected user details. Copy-paste this directly into the cURL sample above.", + "value": { + "workflow_id": "11111111-2222-3333-4444-555555555555", + "vendor_data": "user-123", + "callback": "https://example.com/verification/callback", + "callback_method": "both", + "metadata": { + "user_type": "premium", + "account_id": "ABC123" + }, + "language": "en", + "contact_details": { + "email": "john.doe@example.com", + "send_notification_emails": true, + "email_lang": "en", + "phone": "+14155552671" + }, + "expected_details": { + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1990-05-15", + "nationality": "USA", + "id_country": "USA", + "expected_document_types": [ + "P", + "ID" + ] + } + } + }, + "Create KYB session": { + "summary": "Happy-path payload for a Business Verification (KYB) session", + "description": "Same shape as the KYC call — the `workflow_id` is what makes the session KYB. `vendor_data` typically references the company in your system and `contact_details.email` targets the legal representative who will complete the flow. `expected_details` accepts the business fields (`company_name`, `registry_country`, `registration_number`), which pre-fill the company registry search; person-level fields and `contact_details.phone` are ignored for KYB workflows.", + "value": { + "workflow_id": "33333333-4444-5555-6666-777777777777", + "vendor_data": "company-acme-001", + "callback": "https://example.com/kyb/callback", + "callback_method": "both", + "metadata": { + "tier": "enterprise", + "company_id": "ACME-001" + }, + "expected_details": { + "company_name": "Tesco PLC", + "registry_country": "GB", + "registration_number": "00445790" + }, + "language": "en", + "contact_details": { + "email": "legal-rep@acme.example.com", + "send_notification_emails": true, + "email_lang": "en" + } + } + } + } + } + } + } + } + }, + "/v3/session/imports/": { + "post": { + "summary": "Create a verification import job", + "description": "Create an asynchronous import job for historical User Verification sessions, Business Verification sessions, transaction monitoring records, or workflow custom status rules. Set `source=provider` to migrate from another provider: Didit reads Sumsub, Onfido, Persona and ComplyCube with your read-only key, reads Veriff, iDenfy and MetaMap records by the IDs you upload in `file`, and imports an export file in Didit's format for the other providers. Record IDs are namespaced by provider (`sumsub:`) so a re-run skips everything already in Didit, and a record imported through a file with the provider's bare ID is recognised too, in both directions, so moving between a file import and a provider migration never imports a record twice. An application can run up to 4 import jobs at once; a job interrupted by a deploy or a worker restart continues from its last imported row by itself. Imports are disabled by default and must be enabled per organization by Didit.", + "operationId": "post_v3_session_imports_create", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "import_type": { + "type": "string", + "enum": [ + "user_verification", + "business_verification", + "status_rules", + "transactions" + ], + "default": "user_verification", + "description": "What to import: historical User Verification sessions, historical Business Verification sessions, workflow custom status rules, or historical transaction monitoring records." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Workflow UUID that imported sessions or status rules should be attached to. Required for user_verification, business_verification, and status_rules. Omit for transactions." + }, + "source_format": { + "type": "string", + "enum": [ + "csv", + "ndjson" + ], + "description": "Optional. Inferred from the uploaded file extension when omitted." + }, + "file": { + "type": "string", + "format": "binary", + "description": "Canonical CSV or NDJSON file. Use NDJSON for large imports. Maximum upload size 25 MB (use `source_file_url` for bigger files). Mutually exclusive with `source_file_url`." + }, + "source_file_url": { + "type": "string", + "format": "uri", + "description": "Alternative to file upload. Must be an HTTPS URL resolving to a public address (private/loopback networks are rejected). Mutually exclusive with `file`." + }, + "source": { + "type": "string", + "enum": [ + "file", + "provider" + ], + "description": "`provider` makes this a migration from another identity verification provider. Omit for a file import." + }, + "provider": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ], + "description": "Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`." + }, + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "options": { + "type": "object", + "properties": { + "region": { + "type": "string", + "description": "The provider region, one of its `regions` (Onfido: `eu`, `us`, `ca`)." + } + } + }, + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + }, + "description": "Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported." + }, + "dry_run": { + "type": "boolean", + "default": false, + "description": "Count what the migration would import (by status, already in Didit, same person, not finished) and write nothing. The job ends `READY`; start it with `POST /v3/session/imports/{importId}/start/`." + }, + "face_templates": { + "type": "boolean", + "default": false, + "description": "Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`." + }, + "lawful_basis_confirmed": { + "type": "boolean", + "default": false, + "description": "Confirms you have a lawful basis to share these selfies with Didit as processor." + } + } + } + }, + "application/json": { + "schema": { + "type": "object", + "required": [ + "source_file_url" + ], + "properties": { + "import_type": { + "type": "string", + "enum": [ + "user_verification", + "business_verification", + "status_rules", + "transactions" + ], + "default": "user_verification", + "description": "What to import: historical User Verification sessions, historical Business Verification sessions, workflow custom status rules, or historical transaction monitoring records." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Workflow UUID that imported sessions or status rules should be attached to. Required for user_verification, business_verification, and status_rules. Omit for transactions." + }, + "source_format": { + "type": "string", + "enum": [ + "csv", + "ndjson" + ], + "description": "Optional. Inferred from the URL's file extension when omitted." + }, + "source_file_url": { + "type": "string", + "format": "uri", + "description": "Alternative to file upload. Must be an HTTPS URL resolving to a public address (private/loopback networks are rejected). Mutually exclusive with `file`." + }, + "source": { + "type": "string", + "enum": [ + "file", + "provider" + ], + "description": "`provider` makes this a migration from another identity verification provider. Omit for a file import." + }, + "provider": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ], + "description": "Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`." + }, + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "options": { + "type": "object", + "properties": { + "region": { + "type": "string", + "description": "The provider region, one of its `regions` (Onfido: `eu`, `us`, `ca`)." + } + } + }, + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + }, + "description": "Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported." + }, + "dry_run": { + "type": "boolean", + "default": false, + "description": "Count what the migration would import (by status, already in Didit, same person, not finished) and write nothing. The job ends `READY`; start it with `POST /v3/session/imports/{importId}/start/`." + }, + "face_templates": { + "type": "boolean", + "default": false, + "description": "Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`." + }, + "lawful_basis_confirmed": { + "type": "boolean", + "default": false, + "description": "Confirms you have a lawful basis to share these selfies with Didit as processor." + } + } + }, + "examples": { + "URL-based import": { + "value": { + "import_type": "user_verification", + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "source_file_url": "https://files.example.com/exports/sessions.ndjson" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Import job accepted. Poll the returned `uuid` until `status` is `COMPLETED` or `FAILED`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + }, + "examples": { + "Job accepted": { + "value": { + "uuid": "3f9c1a2b-4d5e-4f60-8a7b-9c0d1e2f3a4b", + "status": "PENDING", + "import_type": "user_verification", + "source_format": "ndjson", + "source_filename": "sessions.ndjson", + "total_rows": 0, + "processed_rows": 0, + "checkpoint_row": 0, + "created_count": 0, + "updated_count": 0, + "skipped_count": 0, + "failed_count": 0, + "total_media_bytes": 0, + "last_error": null, + "created_at": "2026-05-17T08:42:11Z", + "updated_at": "2026-05-17T08:42:11Z", + "started_at": null, + "completed_at": null + } + } + } + } + } + }, + "400": { + "description": "Invalid workflow, file type, file size, source URL, or selector combination. A migration whose key the provider refuses answers `code` (`invalid_credentials`, `missing_permission`, `wrong_region`, `provider_unavailable`, `rate_limited`, `ids_required`, `record_not_found`), `missing_permissions` and `suggested_region`.", + "content": { + "application/json": { + "examples": { + "Invalid workflow": { + "value": { + "workflow_id": [ + "Invalid workflow_id." + ] + } + }, + "Workflow required": { + "value": { + "workflow_id": [ + "This field is required for this import type." + ] + } + }, + "Wrong file type": { + "value": { + "file": [ + "Only CSV and NDJSON files are supported." + ] + } + }, + "File too large": { + "value": { + "file": [ + "Import file exceeds the maximum allowed size." + ] + } + }, + "No source": { + "value": { + "file": [ + "Upload a file or provide source_file_url." + ] + } + }, + "Both sources": { + "value": { + "file": [ + "Use either file or source_file_url, not both." + ] + } + } + } + } + } + }, + "403": { + "description": "Imports not enabled, sandbox application, or missing API key (a wrong, revoked or expired key gets `401`). Verification imports are disabled by default and must be enabled per organization by Didit.", + "content": { + "application/json": { + "examples": { + "Imports not enabled": { + "value": { + "detail": "Verification imports are not enabled for this organization." + } + }, + "Sandbox": { + "value": { + "detail": "Verification imports are not enabled for sandbox applications." + } + }, + "Bad API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "The application already has 4 active import jobs (PENDING/PROCESSING/PAUSED), the most it can run at once. Wait for one to finish before starting another.", + "content": { + "application/json": { + "examples": { + "Active job": { + "value": { + "detail": "This application already has 4 active import jobs, the most it can run at once. Wait for one to finish before starting another." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.create(\n import_type=\"user_verification\",\n workflow_id=\"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n source_file_url=\"https://files.example.com/exports/sessions.ndjson\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.create({\n import_type: \"user_verification\",\n workflow_id: \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n source_file_url: \"https://files.example.com/exports/sessions.ndjson\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Create(ctx, didit.SessionsImportsCreateParams{\n\tImportType: didit.Some(didit.SessionsImportsCreateParamsImportType(\"user_verification\")),\n\tWorkflowID: didit.String(\"9f9b1234-aaaa-bbbb-cccc-1234567890ab\"),\n\tSourceFileURL: didit.String(\"https://files.example.com/exports/sessions.ndjson\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsImportsCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().create(SessionsImportsCreateParams.builder()\n .importType(SessionsImportsCreateParams.ImportType.of(\"user_verification\"))\n .workflowId(\"9f9b1234-aaaa-bbbb-cccc-1234567890ab\")\n .sourceFileUrl(\"https://files.example.com/exports/sessions.ndjson\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->create([\n 'import_type' => 'user_verification',\n 'workflow_id' => '9f9b1234-aaaa-bbbb-cccc-1234567890ab',\n 'source_file_url' => 'https://files.example.com/exports/sessions.ndjson',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.create(\n import_type: \"user_verification\",\n workflow_id: \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n source_file_url: \"https://files.example.com/exports/sessions.ndjson\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.CreateAsync(new SessionsImportsCreateParams\n{\n ImportType = new SessionsImportsCreateParamsImportType(\"user_verification\"),\n WorkflowId = \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n SourceFileUrl = \"https://files.example.com/exports/sessions.ndjson\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/template/": { + "get": { + "summary": "Download verification import template", + "description": "Download the canonical CSV header for verification imports. The column set is determined by `import_type`. The first column of session templates is the idempotency key `external_id`.", + "operationId": "get_v3_session_imports_template", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "retrieve_template", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "import_type", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "user_verification", + "business_verification", + "status_rules", + "transactions" + ], + "default": "user_verification" + } + } + ], + "responses": { + "200": { + "description": "CSV template file.", + "content": { + "text/csv": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot download import templates. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "summary": "Missing, invalid, or revoked API key", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.sessions.imports.retrieve_template()\nwith open(\"download.csv\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.sessions.imports.retrieveTemplate();\nawait writeFile(\"download.csv\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Sessions.Imports.RetrieveTemplate(ctx, didit.SessionsImportsRetrieveTemplateParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.csv\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().imports().retrieveTemplate()\n .writeTo(Path.of(\"download.csv\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->retrieveTemplate();\n$content->saveTo('download.csv');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.sessions.imports.retrieve_template\nFile.binwrite(\"download.csv\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Sessions.Imports.RetrieveTemplateAsync();\nawait content.SaveToAsync(\"download.csv\");" + } + ] + } + }, + "/v3/session/imports/{importId}/": { + "get": { + "summary": "Retrieve a verification import job", + "description": "Retrieve import progress counters and terminal status for one job. Poll until `status` is `COMPLETED` or `FAILED`; `failed_count` > 0 means some rows were rejected — list them via the errors endpoint.", + "operationId": "get_v3_session_imports_retrieve", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Import job status.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "404": { + "description": "Import job not found for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read import jobs for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "summary": "Missing, invalid, or revoked API key", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/{importId}/errors/": { + "get": { + "summary": "List verification import row errors", + "description": "List row-level errors for a verification import job, ordered by `row_number`. Use this endpoint to produce a corrected retry file containing only failed rows.", + "operationId": "get_v3_session_imports_errors", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "list_errors", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size. Defaults to 50 when omitted." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0 + }, + "description": "Zero-based offset of the first record to return." + } + ], + "responses": { + "200": { + "description": "Paginated row errors.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of error rows — exact up to 100, capped at 100 beyond that. Rely on `next` being `null` to detect the last page." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionImportError" + } + } + } + }, + "examples": { + "Row errors": { + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d", + "row_number": 17, + "external_id": "ext-000017", + "error": "status: invalid value 'unknown_status'", + "raw_row": { + "external_id": "ext-000017", + "status": "unknown_status", + "first_name": "Jane" + }, + "created_at": "2026-05-17T08:44:02Z" + } + ] + } + } + } + } + } + }, + "404": { + "description": "Import job not found for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read import jobs for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "summary": "Missing, invalid, or revoked API key", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.sessions.imports.list_errors(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.sessions.imports.listErrors(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Sessions.Imports.ListErrorsAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsImportsListErrorsParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.sessions().imports().listErrors(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->listErrors('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.imports.list_errors(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Sessions.Imports.ListErrorsAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/session/{sessionId}/decision/": { + "get": { + "summary": "Retrieve the full decision for a verification session", + "description": "Retrieve the complete decision report for a verification session — the rolled-up session `status` plus one report per verification feature the workflow executed. This is the canonical read endpoint for both User Verification (KYC) and Business Verification (KYB) sessions: the same path serves both, and the top-level `session_kind` discriminator tells you which shape you received (`\"user\"` or `\"business\"`).\n\n**Every feature report is a plural array.** V3 workflows are graphs that can run the same feature more than once, so each block (`id_verifications`, `nfc_verifications`, `liveness_checks`, `face_matches`, `poa_verifications`, `document_ai_documents`, `phone_verifications`, `email_verifications`, `aml_screenings`, `ip_analyses`, `database_validations`, `questionnaire_responses`) is a JSON array whose items each carry a `node_id` identifying the workflow graph node that produced them. Read NFC as `response.nfc_verifications[0]` — never as a singular `nfc` field. A block is `null` until the workflow has run that feature at least once; features the workflow does not include never appear with data. E-signature results are in `signatures`, which is present only when the workflow has an E-signature step.\n\n**When to call.** The recommended pattern is event-driven: wait for the `status.updated` webhook that fires when the session reaches a decision (`Approved`, `Declined`, `In Review`), then call this endpoint once to fetch the full report. Polling also works — the endpoint can be called at any point in the session lifecycle and returns the data produced so far — but webhook-then-fetch is cheaper and faster. Media URLs in the response (document images, videos, PDFs) are short-lived presigned links: fetch them promptly or re-request the decision to get fresh ones, and do not persist them as long-term references.\n\n**Statuses.** The top-level `status` is the session lifecycle status (`Not Started`, `In Progress`, `Awaiting User`, `In Review`, `Approved`, `Declined`, `Resubmitted`, `Expired`, `Kyc Expired`, `Abandoned`). Each feature item additionally carries its own feature-level `status` (`Not Finished`, `Approved`, `Declined`, `In Review`, ...). A session can be `Approved` overall while an individual feature is `In Review`, depending on the workflow's decision rules.\n\n**Authentication and visibility.** Authenticate with the application API key in the `x-api-key` header (a console user Bearer token with the `read:sessions` permission also works). Only sessions owned by the authenticated application are visible. If your workflow restricts response attributes, non-allowed fields inside feature items are returned as `null` while `status`, `warnings`, and `node_id` are always preserved.\n\n**Rate limits.** GET requests are limited to 600 per minute per API key. Exceeding the limit returns `429` with `Retry-After` and `X-RateLimit-*` headers.", + "operationId": "get_v3_session_decision", + "x-didit-sdk": { + "resource": "sessions", + "method": "retrieve_decision", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "200": { + "description": "Full decision report. `session_kind` discriminates the two shapes: `user` sessions carry the KYC feature arrays shown below; `business` sessions instead carry `registry_checks`, `document_verifications` and `key_people_checks` alongside the shared arrays. Feature arrays are `null` until the workflow has run that feature; media URLs are short-lived presigned links.", + "content": { + "application/json": { + "examples": { + "Example": { + "summary": "Full Example", + "value": { + "session_id": "11111111-2222-3333-4444-555555555555", + "session_kind": "user", + "session_number": 43762, + "session_url": "https://verify.didit.me/session/11111111-2222-3333-4444-555555555555", + "status": "In Review", + "environment": "live", + "workflow_id": "11111111-2222-3333-4444-555555555555", + "features": [ + "ID_VERIFICATION", + "NFC", + "LIVENESS", + "FACE_MATCH", + "POA", + "PHONE", + "DATABASE_VALIDATION", + "AML", + "IP_ANALYSIS" + ], + "vendor_data": "11111111-1111-1111-1111-111111111111", + "metadata": { + "user_type": "premium", + "account_id": "ABC123" + }, + "expected_details": { + "first_name": "Carmen", + "last_name": "Española Española" + }, + "contact_details": { + "email": "carmen@example.com", + "email_lang": "es", + "send_notification_emails": false, + "phone": null + }, + "callback": "https://verify.didit.me/", + "id_verifications": [ + { + "node_id": "feature_ocr_1", + "status": "Approved", + "document_type": "Identity Card", + "document_subtype": "ID_CARD_GENERIC", + "document_number": "CAA000000", + "personal_number": "99999999R", + "portrait_image": "https:///portrait.jpg", + "front_image": "https:///front.jpg", + "front_video": "https:///front.mp4", + "back_image": "https:///back.jpg", + "back_video": "https:///back.mp4", + "full_front_image": "https:///full_front.jpg", + "full_back_image": "https:///full_back.jpg", + "front_image_camera_front": "https:///front_camera_front.jpg", + "back_image_camera_front": "https:///back_camera_front.jpg", + "date_of_birth": "1980-01-01", + "age": 45, + "expiration_date": "2031-06-02", + "date_of_issue": "2021-06-02", + "issuing_state": "ESP", + "issuing_state_name": "Spain", + "first_name": "Carmen", + "last_name": "Española Española", + "full_name": "Carmen Española Española", + "gender": "F", + "address": "Avda de Madrid 34, Madrid, Madrid", + "formatted_address": "Avda de Madrid 34, Madrid, Madrid 28822, Spain", + "place_of_birth": "Madrid", + "marital_status": "Single", + "nationality": "ESP", + "extra_fields": { + "dl_categories": [], + "blood_group": null + }, + "mrz": { + "surname": "ESPANOLA ESPANOLA", + "name": "CARMEN", + "country": "ESP", + "nationality": "ESP", + "birth_date": "800101", + "expiry_date": "310602", + "sex": "F", + "document_type": "ID", + "document_number": "CAA000000", + "optional_data": "99999999R", + "optional_data_2": "", + "birth_date_hash": "4", + "expiry_date_hash": "8", + "document_number_hash": "4", + "final_hash": "1", + "personal_number": "99999999R", + "warnings": [], + "errors": [], + "mrz_type": "TD1", + "mrz_string": "IDESPCAA000000499999999R<<<<<<\n8001014F3106028ESP<<<<<<<<<<<1\nESPANOLA/match_face.jpg", + "status": "Approved", + "is_blocklisted": false, + "is_allowlisted": false, + "api_service": null, + "source": "session" + } + ], + "front_image_quality_score": { + "focus_score": 85.3, + "brightness_score": 92.1, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 72.4, + "overall_score": 88.7 + }, + "back_image_quality_score": { + "focus_score": 79.1, + "brightness_score": 90.5, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 72.4, + "overall_score": 85.2 + }, + "extra_files": [ + "https:///extra_id_verification.jpg" + ], + "matches": [ + { + "session_id": "7d4f1f60-9c2e-4b8a-8f3d-2a1b5c6d7e8f", + "session_number": 1234, + "vendor_data": "user_ref_001", + "verification_date": "2024-06-15T10:30:00Z", + "user_details": { + "name": "John Smith", + "document_type": "ID", + "document_number": "CLM148418" + }, + "status": "Approved", + "is_blocklisted": false, + "api_service": null, + "front_image_url": "https:///document_front.jpg" + }, + { + "session_id": "b2c8e4d1-6a3f-4e7b-95c0-1d8f2a6b4c9e", + "session_number": 1235, + "vendor_data": null, + "verification_date": "2024-05-20T14:45:00Z", + "user_details": { + "name": "John Smith", + "document_type": "ID", + "document_number": "CLM148418" + }, + "status": "Declined", + "is_blocklisted": true, + "api_service": "ID_VERIFICATION", + "front_image_url": "https:///document_front_2.jpg" + } + ], + "warnings": [ + { + "feature": "ID_VERIFICATION", + "risk": "QR_NOT_DETECTED", + "additional_data": null, + "log_type": "information", + "short_description": "QR not detected", + "long_description": "The system couldn't find or read the QR code on the document, which is necessary for document verification. This could be due to poor image quality or an unsupported document type.", + "node_id": "feature_ocr_1" + } + ], + "verification_method": "document", + "assurance": "documentary", + "wallet_provider": null, + "fallback_from": null, + "id_lookup": null, + "wallet_verification": null, + "document_liveness": { + "portrait_replace": { + "score": 96.46, + "front": 96.46, + "back": 98.2, + "decline_threshold": 35, + "review_threshold": 70, + "bucket": "approve" + }, + "printed_copy": { + "score": 91.2, + "front": 91.2, + "back": 93.7, + "decline_threshold": 35, + "review_threshold": 77, + "bucket": "approve" + }, + "screen_replay": { + "score": 99.9, + "front": 99.9, + "back": 99.5, + "decline_threshold": 30, + "review_threshold": 70, + "bucket": "approve" + } + } + }, + { + "node_id": "feature_ocr_2", + "status": "Approved", + "issuing_state": "ZAF", + "issuing_state_name": "South Africa", + "first_name": "Thabo", + "last_name": "Mokoena", + "full_name": "Thabo Mokoena", + "date_of_birth": "1991-06-15", + "personal_number": "9106155043088", + "verification_method": "id_lookup", + "assurance": "data_match", + "wallet_provider": null, + "fallback_from": null, + "id_lookup": { + "source": "Department of Home Affairs", + "checked_at": "2026-09-04T09:12:44Z", + "attempts": 1, + "max_attempts": 2, + "outcome": "match", + "comparison": [ + { + "field": "identification_number", + "label": "ID number", + "provided": "9106155043088", + "record": "9106155043088", + "result": "match" + }, + { + "field": "full_name", + "label": "Full name", + "provided": "Thabo Mokoena", + "record": "Thabo Mokoena", + "result": "match" + }, + { + "field": "date_of_birth", + "label": "Date of birth", + "provided": "1991-06-15", + "record": "1991-06-15", + "result": "match" + } + ], + "registry_portrait": "https:///registry_portrait.jpg", + "face_match_score": 96.4, + "source_errors": [] + }, + "wallet_verification": null, + "warnings": [] + }, + { + "node_id": "feature_ocr_3", + "status": "Approved", + "issuing_state": "DNK", + "issuing_state_name": "Denmark", + "first_name": "Freja", + "last_name": "Nielsen", + "full_name": "Freja Nielsen", + "date_of_birth": "1988-03-02", + "verification_method": "wallet", + "assurance": "cryptographic", + "wallet_provider": "mitid", + "fallback_from": null, + "id_lookup": null, + "wallet_verification": { + "provider": "mitid", + "provider_name": "MitID", + "issuing_authority": "Danish Agency for Digital Government", + "issuing_country": "DNK", + "credential_type": "person_identification", + "level_of_assurance": "substantial", + "verified_at": "2026-09-04T09:12:44Z", + "signature_valid": true, + "attributes": { + "full_name": "Freja Nielsen", + "date_of_birth": "1988-03-02", + "cpr_alias": "b0f1c2d3-e4f5-4678-9abc-def012345678" + }, + "portrait": null, + "face_match_score": null + }, + "warnings": [] + } + ], + "nfc_verifications": [ + { + "node_id": "feature_nfc_1", + "status": "In Review", + "is_nfc_skipped": false, + "skip_reason": null, + "chip_protocol": "icao", + "jpki": null, + "portrait_image": "https:///portrait.jpg", + "signature_image": "https:///signature.jpg", + "chip_data": { + "document_type": "ID", + "issuing_country": "ESP", + "document_number": "123456789", + "expiration_date": "2030-01-01", + "first_name": "John", + "last_name": "Smith", + "birth_date": "1990-05-15", + "gender": "M", + "nationality": "ESP", + "address": "CALLE MAYOR 123 4B, MADRID, MADRID", + "place_of_birth": "MADRID, MADRID" + }, + "authenticity": { + "sod_integrity": true, + "dg_integrity": true + }, + "certificate_summary": { + "issuer": "Common Name: CSCA SPAIN, Serial Number: 3, Organization: DIRECCION GENERAL DE LA POLICIA, Country: ES", + "subject": "Common Name: DS n-eID SPAIN 2, Organizational Unit: PASSPORT, Organization: DIRECCION GENERAL DE LA POLICIA, Country: ES", + "serial_number": "118120836164494130086420187336801405660", + "not_valid_after": "2031-02-18 10:21:11", + "not_valid_before": "2020-11-18 10:21:11" + }, + "warnings": [ + { + "feature": "NFC", + "risk": "DATA_INCONSISTENT", + "additional_data": null, + "log_type": "warning", + "short_description": "OCR and NFC mrz code extracted are not the same", + "long_description": "The Optical Character Recognition (OCR) data and the NFC chip data don't match, indicating potential document tampering or data inconsistency.", + "node_id": "feature_nfc_1" + } + ] + } + ], + "liveness_checks": [ + { + "node_id": "feature_liveness_1", + "status": "Approved", + "method": "ACTIVE_3D", + "score": 89.92, + "reference_image": "https:///reference.jpg", + "video_url": "https:///video.mp4", + "age_estimation": 24.3, + "matches": [ + { + "session_id": "7d4f1f60-9c2e-4b8a-8f3d-2a1b5c6d7e8f", + "session_number": 1234, + "vendor_data": "user_ref_001", + "verification_date": "2024-06-15T10:30:00Z", + "user_details": { + "full_name": "John Smith", + "document_type": "ID", + "document_number": "CLM148418" + }, + "status": "Approved", + "is_blocklisted": false, + "is_allowlisted": false, + "api_service": null, + "source": "session", + "match_image_url": "https:///face_match.jpg", + "similarity_percentage": 98.5 + }, + { + "session_id": "3e9a7c21-5b48-4f0e-9c6a-8d2f4b1e0a35", + "session_number": 1100, + "vendor_data": "customer_xyz", + "verification_date": "2024-04-10T09:15:00Z", + "user_details": { + "full_name": "John Smith", + "document_type": "P", + "document_number": "DZ8375806" + }, + "status": "Approved", + "is_blocklisted": false, + "is_allowlisted": false, + "api_service": "PASSIVE_LIVENESS", + "source": "session", + "match_image_url": "https:///face_match_2.jpg", + "similarity_percentage": 95.2 + } + ], + "warnings": [ + { + "feature": "LIVENESS", + "risk": "LOW_LIVENESS_SCORE", + "additional_data": null, + "log_type": "information", + "short_description": "Low liveness score", + "long_description": "The liveness check resulted in a low score, indicating potential use of non-live facial representations or poor-quality biometric data.", + "node_id": "feature_liveness_1" + } + ], + "face_quality": null, + "face_luminance": null + } + ], + "face_matches": [ + { + "node_id": "feature_face_match_1", + "status": "In Review", + "score": 65.43, + "source_image_session_id": null, + "source_image": "https:///source-image.jpg", + "target_image": "https:///target-image.jpg", + "warnings": [ + { + "feature": "FACEMATCH", + "risk": "LOW_FACE_MATCH_SIMILARITY", + "additional_data": null, + "log_type": "warning", + "short_description": "Low face match similarity", + "long_description": "The facial features of the provided image don't closely match the reference image, suggesting a potential identity mismatch.", + "node_id": "feature_face_match_1" + } + ] + } + ], + "phone_verifications": [ + { + "node_id": "feature_phone_1", + "status": "Approved", + "phone_number_prefix": "+34", + "phone_number": "600600600", + "full_number": "+34600600600", + "country_code": "ES", + "country_name": "Spain", + "carrier": { + "name": "Orange", + "type": "mobile" + }, + "is_disposable": false, + "is_virtual": false, + "verification_method": "sms", + "verification_attempts": 1, + "verified_at": "2024-07-28T06:47:35.654321Z", + "lifecycle": [ + { + "type": "PHONE_VERIFICATION_MESSAGE_SENT", + "timestamp": "2025-08-24T09:12:39.580554+00:00", + "details": { + "status": "Success", + "reason": null, + "channel": "sms", + "actual_channel": "sms" + }, + "fee": 0.1 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2025-08-24T09:12:39.662157+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "PHONE_VERIFICATION_APPROVED", + "timestamp": "2025-08-24T09:12:39.684292+00:00", + "details": null, + "fee": 0 + } + ], + "warnings": [], + "matches": [ + { + "session_id": null, + "session_number": null, + "vendor_data": null, + "verification_date": null, + "phone_number": "+34600000000", + "status": null, + "is_blocklisted": true, + "api_service": null, + "source": "list_entry" + } + ] + } + ], + "email_verifications": [ + { + "node_id": "feature_email_1", + "status": "Approved", + "email": "test@example.com", + "is_breached": true, + "breaches": [ + { + "name": "TAPAirPortugal", + "domain": "flytap.com", + "logo_path": "https://logos.example.com/TAPAirPortugal.png", + "breach_date": "2022-08-25", + "description": "In August 2022, the Portuguese airline TAP Air Portugal was the target of a ransomware attack perpetrated by the Ragnar Locker gang who later leaked the compromised data via a public dark web site. Over 5M unique email addresses were exposed alongside other personal data including names, genders, DoBs, phone numbers and physical addresses.", + "is_verified": true, + "data_classes": [ + "dates_of_birth", + "email_addresses", + "genders", + "names", + "nationalities", + "phone_numbers", + "physical_addresses", + "salutations", + "spoken_languages" + ], + "breach_emails_count": 6083479 + } + ], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 1, + "verified_at": "2025-09-15T17:36:19.963451Z", + "warnings": [ + { + "feature": "EMAIL", + "risk": "BREACHED_EMAIL_DETECTED", + "additional_data": null, + "log_type": "information", + "short_description": "Breached email detected", + "long_description": "This email address was found in one or more known data breaches.", + "node_id": "feature_email_1" + } + ], + "lifecycle": [ + { + "type": "EMAIL_VERIFICATION_MESSAGE_SENT", + "timestamp": "2025-09-15T17:36:19.792684+00:00", + "details": { + "status": "Success", + "reason": null + }, + "fee": 0.02 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2025-09-15T17:36:19.963427+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "EMAIL_VERIFICATION_DECLINED", + "timestamp": "2025-09-15T17:36:20.081471+00:00", + "details": { + "reason": "BREACHED_EMAIL_DETECTED" + }, + "fee": 0 + } + ], + "matches": [ + { + "session_id": null, + "session_number": null, + "vendor_data": null, + "verification_date": null, + "email": "test@example.com", + "status": null, + "is_blocklisted": true, + "api_service": null, + "source": "list_entry" + } + ] + } + ], + "poa_verifications": [ + { + "node_id": "feature_poa_1", + "status": "Declined", + "document_file": "https:///poa_document.pdf", + "issuing_state": "AR", + "document_type": "UTILITY_BILL", + "document_subtype": null, + "document_language": "es", + "document_metadata": { + "file_size": 1000, + "content_type": "application/pdf", + "creation_date": "2021-01-01", + "modified_date": "2021-01-01", + "overlay_manipulation": null, + "creator": null, + "producer": "PDF Generator", + "software": null, + "encryption": null, + "is_signed": false, + "is_tampered": false, + "signature_info": null, + "exif_original_date": null, + "exif_digitized_date": null, + "processed_by_known_editor": false, + "has_different_creation_mod_date": true + }, + "issuer": "Aguas del Norte", + "issue_date": "2021-02-01", + "expiration_date": null, + "poa_address": "AVDA. MONSEÑOR TAVELLA N° 3396, SALTA", + "poa_formatted_address": "Av. Monseñor Tavella 3396, A4400 Salta, Argentina", + "poa_parsed_address": { + "address_type": "Avenida", + "street_1": "Avenida Monseñor Tavella 3396", + "street_2": null, + "city": "Salta", + "region": "Salta", + "country": "AR", + "postal_code": "A4400", + "document_location": { + "latitude": -24.8208664, + "longitude": -65.4131 + } + }, + "name_on_document": "Fernando De Lima", + "name_match_score_expected_details": null, + "name_match_score_id_verification": 38.5, + "expected_details_address": null, + "expected_details_formatted_address": null, + "expected_details_parsed_address": {}, + "extra_fields": { + "bank_account_number": "1234567890", + "bank_iban": null, + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": "+5491112345678", + "additional_names": [] + }, + "extra_files": [ + "https:///extra_poa.pdf" + ], + "detected_codes": [ + { + "type": "QR_CODE", + "page_number": 1, + "position": [ + [ + 820, + 1120 + ], + [ + 1002, + 1120 + ], + [ + 1002, + 1302 + ], + [ + 820, + 1302 + ] + ], + "x": 820, + "y": 1120, + "width": 182, + "height": 182, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "aHR0cHM6Ly9hZ3Vhc2RlbG5vcnRlLmNvbS9mYWN0dXJhLzk5ODgxMQ==", + "parsed_payload": "https://aguasdelnorte.com/factura/998811" + }, + { + "type": "CODE_128", + "page_number": 1, + "position": [ + [ + 112, + 1480 + ], + [ + 560, + 1480 + ], + [ + 560, + 1540 + ], + [ + 112, + 1540 + ] + ], + "x": 112, + "y": 1480, + "width": 448, + "height": 60, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "MDA5OTg4MTEwMDA0Mg==", + "parsed_payload": "0099881100042" + } + ], + "warnings": [] + } + ], + "document_ai_documents": [ + { + "status": "Approved", + "node_id": "feature_document_ai_1", + "items": [ + { + "uuid": "c1a2b3c4-d5e6-7f80-9a1b-2c3d4e5f6071", + "node_id": "feature_document_ai_1", + "document_key": "proof_of_funds", + "original_filename": "bank_statement.pdf", + "file_size": 184320, + "file_url": "https:///document_ai/proof_of_funds.pdf", + "status": "Approved", + "extracted_data": { + "account_holder": "John Doe", + "balance": 15230.55, + "currency": "EUR", + "statement_date": "2026-05-31" + }, + "detected_codes": [ + { + "type": "QR_CODE", + "page_number": 2, + "position": [ + [ + 124, + 86 + ], + [ + 306, + 86 + ], + [ + 306, + 268 + ], + [ + 124, + 268 + ] + ], + "x": 124, + "y": 86, + "width": 182, + "height": 182, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "SU5WLTIwMjYtMDA0Mg==", + "parsed_payload": "INV-2026-0042" + } + ], + "fields": [ + { + "key": "account_holder", + "name": "Account holder", + "instruction": "Extract the full name of the account holder.", + "type": "text", + "required": true + }, + { + "key": "balance", + "name": "Closing balance", + "instruction": "Extract the closing balance amount.", + "type": "number", + "required": true + } + ], + "document_metadata": { + "is_tampered": false + }, + "cross_check_result": null, + "created_at": "2026-06-01T12:34:56Z" + } + ], + "warnings": [] + } + ], + "questionnaire_responses": [ + { + "node_id": "feature_questionnaire_1", + "questionnaire_id": "9f7485e0-b00b-4d56-9d3e-7859b68d1213", + "title": "Source of Funds", + "description": "Reply to this questionnaire to help us understand the source of funds.", + "languages": [ + "en" + ], + "default_language": "en", + "is_active": true, + "is_simple_questionnaire": true, + "questionnaire_group_id": "2b6a9d40-13a8-4b1e-9a6c-3f1d2e4b5a60", + "version": 1, + "published_at": "2025-11-02T09:14:55.000000Z", + "sections": [ + { + "title": null, + "description": null, + "items": [ + { + "uuid": "618636d9-7d5c-41ce-b8d0-1ab2a4be20db", + "value": "question_1", + "element_type": "DROPDOWN", + "is_required": true, + "title": "What is your primary source of funds?", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Salary", + "value": "salary" + }, + { + "label": "Business Income", + "value": "business_income" + }, + { + "label": "Savings", + "value": "savings" + }, + { + "label": "Investments", + "value": "investments" + }, + { + "label": "Sale of Asset", + "value": "sale_of_asset" + }, + { + "label": "Inheritance/Gift", + "value": "inheritance_gift" + }, + { + "label": "Pension", + "value": "pension" + }, + { + "label": "Crypto Proceeds", + "value": "crypto_proceeds" + }, + { + "label": "Other", + "value": "other", + "requires_text_input": true + } + ], + "max_files": 1, + "answer": { + "value": "salary" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "2d16b527-72be-42e0-8e96-b164b818386c", + "value": "question_2", + "element_type": "LONG_TEXT", + "is_required": true, + "title": "Provide details of the primary source.", + "description": null, + "placeholder": null, + "choices": null, + "max_files": 1, + "answer": { + "value": "Test answer" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "a9239b7c-fbfb-4ea7-b7e4-c835cd4016e0", + "value": "question_3", + "element_type": "DROPDOWN", + "is_required": true, + "title": "What is your annual income range (pre-tax)?", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Less than $25,000", + "value": "<25k" + }, + { + "label": "$25,000 - $50,000", + "value": "25k-50k" + }, + { + "label": "$50,001 - $100,000", + "value": "50k-100k" + }, + { + "label": "$100,001 - $250,000", + "value": "100k-250k" + }, + { + "label": "More than $250,000", + "value": ">250k" + } + ], + "max_files": 1, + "answer": { + "value": "<25k" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "93dede62-6d0c-4a7f-934d-42175eb94011", + "value": "question_4", + "element_type": "DROPDOWN", + "is_required": true, + "title": "What is your approximate net worth (range)?", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Less than $50,000", + "value": "<50k" + }, + { + "label": "$50,000 - $250,000", + "value": "50k-250k" + }, + { + "label": "$250,001 - $1,000,000", + "value": "250k-1m" + }, + { + "label": "$1,000,001 - $5,000,000", + "value": "1m-5m" + }, + { + "label": "More than $5,000,000", + "value": ">5m" + } + ], + "max_files": 1, + "answer": { + "value": "<50k" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "2af60d6f-6961-4692-9ee7-24b3033610a6", + "value": "question_5", + "element_type": "LONG_TEXT", + "is_required": true, + "title": "For your initial deposit/funding, indicate the exact source and amount.", + "description": null, + "placeholder": null, + "choices": null, + "max_files": 1, + "answer": { + "value": "Test answer" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "0ad9d1aa-736f-46cf-947b-9e1241b53e40", + "value": "question_6", + "element_type": "LONG_TEXT", + "is_required": true, + "title": "Breakdown of sources by percentage.", + "description": null, + "placeholder": null, + "choices": null, + "max_files": 1, + "answer": { + "value": "Test answer" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "1bf8a2a3-14ee-48a8-bb3d-175481aae4aa", + "value": "question_7", + "element_type": "SINGLE_CHOICE", + "is_required": true, + "title": "Will any funds originate from third parties? If yes, please provide details.", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Yes", + "value": "yes", + "requires_text_input": true + }, + { + "label": "No", + "value": "no" + } + ], + "max_files": 1, + "answer": { + "text": "Additional details", + "value": "yes" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "e18d4dd3-b7bf-4883-ba18-d6046adbe313", + "value": "question_8", + "element_type": "SINGLE_CHOICE", + "is_required": true, + "title": "Do you hold or plan to use virtual assets (e.g., crypto) to fund this account? If yes, please provide details.", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Yes", + "value": "yes", + "requires_text_input": true + }, + { + "label": "No", + "value": "no" + } + ], + "max_files": 1, + "answer": { + "text": "Additional details", + "value": "yes" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "a90a073d-e098-404c-bbce-5130d8b12a13", + "value": "question_9", + "element_type": "FILE_UPLOAD", + "is_required": true, + "title": "Upload a proof of funds document", + "description": "You can upload your three (3) most recent bank statements, three (3) most recent payslips, your most recent tax return, proof of company ownership, trading exchange statement, sale of property, or proof of inheritance.", + "placeholder": null, + "choices": null, + "max_files": 3, + "answer": { + "files": [ + "/media/ocr/", + "/media/ocr/" + ] + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "9503b486-7cb8-4c3b-a97d-4b64926a5a47", + "value": "question_10", + "element_type": "LONG_TEXT", + "is_required": false, + "title": "Would you like to add any details to help us expedite our review of the documents you submitted?", + "description": null, + "placeholder": null, + "choices": null, + "max_files": 1, + "answer": { + "value": "Test answer" + }, + "required_if": null, + "repeatable_config": null + } + ] + } + ], + "status": "Approved" + } + ], + "aml_screenings": [ + { + "node_id": "feature_aml_1", + "status": "In Review", + "total_hits": 1, + "entity_type": "person", + "hits": [ + { + "id": "hit_7c1e9a40b2d35f86e0a4c912", + "url": "https://news.example.org/articles/sample", + "match": false, + "score": 0.73, + "target": null, + "caption": "Javier Ejemplo Modelo", + "datasets": [ + "PEP" + ], + "features": null, + "rca_name": "", + "last_seen": "2025-06-13T00:00:00", + "risk_view": { + "crimes": { + "score": 0, + "weightage": 20, + "risk_level": "Low", + "risk_scores": {} + }, + "countries": { + "score": 0, + "weightage": 30, + "risk_level": "Low", + "risk_scores": {} + }, + "categories": { + "score": 100, + "weightage": 50, + "risk_level": "High", + "risk_scores": { + "PEP": 100 + } + }, + "custom_list": {} + }, + "first_seen": "2025-01-18T00:00:00", + "properties": { + "name": [ + "Javier Ejemplo Modelo" + ], + "alias": [ + "Javier Ejemplo Modelo", + "David Azagra" + ], + "notes": [ + "Spanish orchestra conductor" + ], + "title": null, + "gender": [ + "male" + ], + "height": null, + "topics": null, + "weight": null, + "address": null, + "country": null, + "website": null, + "eyeColor": null, + "keywords": null, + "lastName": [ + "Ejemplo", + "Modelo" + ], + "position": null, + "religion": null, + "birthDate": [ + "1974" + ], + "education": [ + "Tokyo Arts and Space (2012-2012)", + "Saint Petersburg Conservatory (-2010)", + "Comillas Pontifical University" + ], + "ethnicity": null, + "firstName": [ + "David" + ], + "hairColor": null, + "weakAlias": null, + "birthPlace": [ + "Madrid" + ], + "modifiedAt": null, + "wikidataId": null, + "citizenship": null, + "nationality": null + }, + "match_score": 98, + "risk_score": 73, + "review_status": "Unreviewed", + "score_breakdown": { + "name_score": 95, + "name_weight": 60, + "name_weight_normalized": 70.59, + "name_contribution": 67.06, + "dob_score": 100, + "dob_weight": 25, + "dob_weight_normalized": 29.41, + "dob_contribution": 29.41, + "country_score": 0, + "country_weight": 15, + "country_weight_normalized": 0, + "country_contribution": 0, + "document_number_match_type": "NEUTRAL", + "document_number_effect": "No document number provided for screening", + "total_score": 98 + }, + "pep_matches": [ + { + "aliases": [ + "Javier Ejemplo Modelo", + "David Azagra" + ], + "education": [ + "Tokyo Arts and Space (2012-2012)", + "Saint Petersburg Conservatory (-2010)", + "Comillas Pontifical University" + ], + "list_name": "Wikidata", + "publisher": "Wikidata", + "source_url": "https://news.example.org/articles/sample", + "description": "Wikidata is the structured data project of the Wikipedia community, providing fact-based information edited by humans and machines", + "matched_name": "Javier Ejemplo Modelo", + "pep_position": "", + "date_of_birth": "1974", + "other_sources": [], + "place_of_birth": "Madrid" + } + ], + "linked_entities": [ + { + "name": [ + "Antonio Ejemplo" + ], + "active": [], + "status": [], + "details": [], + "relation": [] + } + ], + "warning_matches": [], + "sanction_matches": [], + "adverse_media_details": { + "sentiment": "Moderately Negative", + "entity_type": "person", + "sentiment_score": -2, + "adverse_keywords": { + "fraud": 3, + "juicio": 1, + "delitos": 2, + "imputado": 1, + "impunidad": 1, + "corruption": 2, + "favoritism": 2, + "privilegio": 1, + "corrupción": 1, + "persecution": 2, + "desconfianza": 1, + "desprestigio": 1, + "fraude de ley": 2, + "manipulación": 1, + "investigación": 3, + "prevaricación": 8, + "trato de favor": 1, + "irregularidades": 2, + "crisis de confianza": 1, + "tráfico de influencias": 8 + } + }, + "adverse_media_matches": [ + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in legal violations related to favoritism and influence trafficking, leading to a negative portrayal in the article.", + "headline": "La Diputación de Villaejemplo consolida la plaza a medida para el ex asesor de Moncloa amigo del \"hermanito\" de Ejemplo", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "fraud", + "prevaricación", + "tráfico de influencias", + "irregularidades", + "imputado" + ], + "sentiment_score": -2, + "publication_date": "2025-06-10T20:46:38" + }, + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in legal issues regarding favoritism and corruption, leading to a negative portrayal in the article.", + "headline": "La Diputación de Villaejemplo consolida la plaza a medida para el ex asesor de Moncloa amigo del \"hermanito\" de Ejemplo", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "fraud", + "prevaricación", + "tráfico de influencias", + "irregularidades", + "trato de favor" + ], + "sentiment_score": -2, + "publication_date": "2025-06-10T20:46:38" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is mentioned in connection with Carlos Ficticio's corruption case, indicating his involvement in alleged influence peddling.", + "headline": "España, paraíso del aforado", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias", + "corrupción", + "impunidad", + "privilegio", + "desconfianza", + "manipulación", + "fraude de ley" + ], + "sentiment_score": -2, + "publication_date": "2025-06-10T05:05:34" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is implicated in the same investigation as Ficticio, which raises questions about his conduct and political ethics.", + "headline": "Aforamientos: esa figura incómoda", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias", + "investigación", + "juicio", + "fraude de ley", + "desprestigio", + "crisis de confianza", + "delitos" + ], + "sentiment_score": -2, + "publication_date": "2025-06-08T06:01:00" + }, + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in a legal investigation regarding irregularities in his hiring, which raises serious concerns about his conduct.", + "headline": "La jueza del 'caso Javier Ejemplo' denuncia un «fraude de ley» en el aforamiento de Ficticio (Partido Ejemplo)", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "fraud", + "irregularities", + "prevaricación", + "tráfico de influencias" + ], + "sentiment_score": -2, + "publication_date": "2025-06-09T10:19:10" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is mentioned in connection with legal allegations of corruption, contributing to a negative perception.", + "headline": "'Caso aforamiento': la letrada mayor de la Asamblea certifica a la juez que Ficticio es diputado desde antes de prometer su cargo en el Pleno", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias" + ], + "sentiment_score": -2, + "publication_date": "2025-06-09T08:08:30" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is implicated in the same investigation as Ficticio, raising concerns about political corruption and influence.", + "headline": "Aforamientos: esa figura incómoda", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias", + "investigación" + ], + "sentiment_score": -2, + "publication_date": "2025-06-08T06:01:00" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is implicated in the same investigation as Ficticio, which raises concerns about political misconduct.", + "headline": "Aforamientos: esa figura incómoda", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias", + "investigación", + "delitos" + ], + "sentiment_score": -2, + "publication_date": "2025-06-08T06:00:59" + }, + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in a case of favoritism and corruption, which casts a negative light on his character and actions.", + "headline": "'Caso Javier Ejemplo': las asociaciones judiciales salen en defensa de la juez Luisa Inventada ante las \"presiones externas\" que sufre", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "favoritism", + "corruption", + "persecution" + ], + "sentiment_score": -2, + "publication_date": "2025-06-06T10:08:15" + }, + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in a case of alleged favoritism and corruption, which casts a negative light on his character and actions.", + "headline": "'Caso Javier Ejemplo': las asociaciones judiciales salen en defensa de la juez Luisa Inventada ante las \"presiones externas\" que sufre", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "favoritism", + "corruption", + "persecution" + ], + "sentiment_score": -2, + "publication_date": "2025-06-06T08:08:15" + } + ], + "additional_information": {} + } + ], + "score": 80, + "screened_data": { + "full_name": "Javier Ejemplo Modelo", + "nationality": "ES", + "date_of_birth": "1974-01-01", + "document_number": null + }, + "is_ongoing_monitoring_enabled": true, + "next_ongoing_monitoring_bill_date": "2026-07-24", + "last_ongoing_screening_at": "2026-09-12T04:03:11Z", + "last_ongoing_screening_datasets_updated_at": "2026-09-11T06:00:00Z", + "warnings": [ + { + "feature": "AML", + "risk": "POSSIBLE_MATCH_FOUND", + "additional_data": null, + "log_type": "warning", + "short_description": "Possible match found in AML screening", + "long_description": "The Anti-Money Laundering (AML) screening process identified potential matches with watchlists or high-risk databases, requiring further review.", + "node_id": "feature_aml_1" + } + ] + } + ], + "ip_analyses": [ + { + "node_id": "feature_ip_1", + "status": "Approved", + "device_brand": "Apple", + "device_model": "iPhone", + "browser_family": "Mobile Safari", + "os_family": "iOS", + "platform": "mobile", + "device_fingerprint": "didit-fp-a1b2c3d4e5f6g7h8", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_state": "Barcelona", + "ip_city": "Barcelona", + "latitude": 41.4022, + "longitude": 2.1407, + "ip_address": "83.50.226.71", + "isp": null, + "organization": null, + "is_vpn_or_tor": false, + "is_data_center": false, + "time_zone": "Europe/Madrid", + "time_zone_offset": "+0100", + "ip": { + "location": { + "latitude": 40.2206327, + "longitude": 1.5770097 + }, + "distance_from_id_document": 23.4, + "distance_from_poa_document": 12.3 + }, + "id_document": { + "location": { + "latitude": 41.2706327, + "longitude": 1.9770097 + }, + "distance_from_ip": 23.4, + "distance_from_poa_document": 18.7 + }, + "poa_document": { + "location": { + "latitude": 41.2706327, + "longitude": 1.9770097 + }, + "distance_from_ip": 12.3, + "distance_from_id_document": 18.7 + }, + "warnings": [], + "matches": [ + { + "session_id": "99999999-8888-7777-6666-555555555555", + "session_number": 41205, + "vendor_data": "22222222-2222-2222-2222-222222222222", + "verification_date": "2024-07-20T10:15:30Z", + "match_type": "ip_address", + "match_source": "ip_address", + "matched_value": "83.50.226.71", + "status": "Approved", + "is_blocklisted": false, + "api_service": null, + "source": "session", + "device_info": { + "device_brand": "Apple", + "device_model": "iPhone", + "browser_family": "Mobile Safari", + "os_family": "iOS", + "platform": "mobile", + "device_fingerprint": "didit-fp-f9e8d7c6b5a40312" + }, + "location_info": { + "ip_address": "83.50.226.71", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_state": "Barcelona", + "ip_city": "Barcelona", + "is_vpn_or_tor": false, + "is_data_center": false + }, + "confidence": 0.0, + "match_mode": "co_occurrence" + }, + { + "session_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "session_number": 42830, + "vendor_data": "33333333-3333-3333-3333-333333333333", + "verification_date": "2024-06-30T18:22:05Z", + "match_type": "device_fingerprint", + "match_source": "recovered_high", + "matched_value": "f47ac10b-58cc-4372-a567-0e02b2c3d479", + "status": "Declined", + "is_blocklisted": false, + "api_service": null, + "source": "session", + "device_info": { + "device_brand": "Samsung", + "device_model": "SM-G991B", + "browser_family": "Chrome Mobile", + "os_family": "Android", + "platform": "mobile", + "device_fingerprint": null + }, + "location_info": { + "ip_address": "83.50.231.18", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_state": "Madrid", + "ip_city": "Madrid", + "is_vpn_or_tor": false, + "is_data_center": false + }, + "confidence": 1.0, + "match_mode": "deterministic", + "recovery_similarity": 0.9874, + "tls_ja4_corroborated": true, + "recovery_gate_reason": "hardware_root_match" + } + ] + } + ], + "database_validations": [ + { + "node_id": "feature_db_validation_1", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "last_name": "De Lima", + "first_name": "Fernando", + "tax_number": "01234567890", + "date_of_birth": "1980-01-01", + "document_type": "ID", + "expiration_date": "2030-01-01" + }, + "validations": [ + { + "service_id": "bra_cpf", + "service_name": "Brazil - CPF status check", + "outcome_code": "MATCH", + "validation": { + "full_name": "full_match", + "date_of_birth": "full_match", + "identification_number": "full_match" + }, + "source_data": { + "identification_number": "01234567890", + "first_name": "FERNANDO", + "last_name": "DE LIMA", + "date_of_birth": "1980-01-01", + "gender": "M", + "nationality": "BRA" + } + } + ], + "match_type": "full_match", + "status": "Approved", + "warnings": [] + } + ], + "reviews": [ + { + "user": "compliance@example.com", + "new_status": "Declined", + "comment": "Possible match found in AML screening", + "created_at": "2024-07-18T13:29:00.366811Z" + } + ], + "created_at": "2024-07-24T08:54:25.443172Z", + "expires_at": "2025-07-24T08:54:25.443172Z", + "biometric_consent": { + "required": false, + "trigger": null, + "status": "not_required", + "given_at": null, + "declined_at": null, + "text_version": null, + "text_hash": null, + "locale": null, + "policy_url": null, + "step": null, + "node_id": null, + "ip_address": null, + "ip_country_code": null, + "ip_state": null, + "ip_city": null, + "user_agent": null, + "detected_ip_address": "83.50.226.71", + "detected_country_code": "ES", + "detected_state": "ES-B", + "detected_city": "Barcelona", + "declared_state": null, + "document_state": null, + "jurisdictions": [], + "retention_months": null, + "reason": null, + "decline_reason": null, + "face_data_deleted_at": null + } + } + }, + "BusinessExample": { + "summary": "Business (KYB) session example", + "description": "A `session_kind = \"business\"` decision: KYB sessions replace the KYC feature arrays with `registry_checks`, `document_verifications` and `key_people_checks`, while sharing `aml_screenings`, `phone_verifications`, `email_verifications`, `questionnaire_responses`, `ip_analyses`, `reviews` and the session envelope.", + "value": { + "session_id": "66666666-7777-8888-9999-000000000000", + "session_kind": "business", + "session_number": 31415, + "session_url": "https://verify.didit.me/session/business123", + "status": "In Review", + "environment": "live", + "workflow_id": "abababab-cdcd-efef-0101-232323232323", + "features": [ + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE" + ], + "vendor_data": "merchant-001234", + "metadata": null, + "callback": "https://example.com/verification/callback", + "registry_checks": [ + { + "status": "Approved", + "node_id": "feature_kyb_registry_1", + "data_resolved": true, + "company": { + "uuid": "dddd4444-eeee-5555-ffff-666677778888", + "node_id": "feature_kyb_registry_1", + "status": "Approved", + "registry_status": "active", + "data_resolved": true, + "company_name": "ACME HOLDINGS LTD", + "registration_number": "12345678", + "country_code": "GB", + "region": null, + "company_type": "Private Limited Company", + "incorporation_date": "2015-03-12", + "registered_address": "1 Example Street, London, EC1A 1AA", + "tax_number": null, + "risk_level": null, + "verification_status": "verified", + "is_from_registry": true, + "fetch_status": "resolved", + "alternative_names": null, + "nature_of_business": "62012 - Business and domestic software development", + "registered_capital": "100 GBP", + "registered_capital_amount": "100.00", + "registered_capital_currency": "GBP", + "website": null, + "email": null, + "phone": null, + "legal_entity_identifier": null, + "location_of_registration": "England and Wales", + "financial_summary": null, + "officers": [ + { + "uuid": "aaaa1111-bbbb-2222-cccc-333344445555", + "name": "Jane Smith", + "designation": "Director", + "role": "director", + "nationality": "British", + "is_active": true, + "kyc_status": "Approved", + "kyc_session_url": "https://verify.didit.me/session/abc123def456" + } + ], + "beneficial_owners": [ + { + "uuid": "bbbb2222-cccc-3333-dddd-444455556666", + "name": "John Doe", + "first_name": "John", + "last_name": "Doe", + "entity_type": "person", + "roles": [ + "ubo", + "shareholder" + ], + "ownership_min_shares": "50", + "ownership_max_shares": "75", + "is_active": true, + "kyc_status": "Pending", + "kyc_session_url": "https://verify.didit.me/session/def456ghi789", + "effective_ownership_percent": 75.0 + } + ], + "addresses": [ + { + "type": "registered", + "address": "1 Example Street, London, EC1A 1AA", + "country": "GB" + } + ], + "industries": [ + { + "code": "62012", + "description": "Business and domestic software development" + } + ], + "accounts": null, + "registry_data": {}, + "user_provided_data": { + "company_name": "ACME HOLDINGS", + "registration_number": "12345678" + }, + "confirmed_by_user_at": "2026-06-10T12:01:30.000000Z", + "last_console_edit_at": null, + "is_editable": false + }, + "ownership_structure": { + "shareholders": [ + { + "name": "John Doe", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": null, + "country_of_birth": null, + "company_name": null, + "registration_number": null, + "entity_type": "person", + "nationality": null, + "designation": null, + "roles": [ + "shareholder" + ], + "ownership_min_shares": "75", + "ownership_max_shares": "75", + "voting_min_shares": null, + "voting_max_shares": null, + "appointment_date": null, + "is_active": true, + "shareholding": { + "share_class": "ORDINARY", + "number_of_shares": 750.0, + "currency": "GBP", + "nominal_value": 1.0, + "total_value": null, + "percentage": 75.0 + } + } + ], + "beneficial_owners": [] + }, + "warnings": [] + } + ], + "aml_screenings": null, + "document_verifications": [ + { + "status": "In Review", + "node_id": "feature_kyb_documents_1", + "items": [ + { + "uuid": "eeee5555-ffff-6666-0000-777788889999", + "document_type": "LEGAL_PRESENCE", + "document_subtype": "CERTIFICATE_OF_INCORPORATION", + "document_group": "LEGAL_PRESENCE", + "file_url": "https:///kyb/certificate-of-incorporation.pdf", + "original_filename": "certificate_of_incorporation.pdf", + "file_size": 482133, + "status": "Approved", + "created_at": "2026-06-10T12:03:11.000000Z", + "cross_check_result": { + "critical": { + "company_name": { + "document": "ACME HOLDINGS LTD", + "registry": "ACME HOLDINGS LTD", + "score": 1.0, + "match": true + }, + "registration_number": { + "document": "12345678", + "registry": "12345678", + "match": true + } + }, + "non_critical": {}, + "people": {} + }, + "document_metadata": { + "file_size": 482133, + "content_type": "application/pdf", + "creation_date": "2015-03-12T09:00:00", + "modified_date": "2015-03-12T09:00:00", + "creator": null, + "producer": null, + "software": null, + "encryption": null, + "is_signed": true, + "is_tampered": false, + "signature_info": null, + "exif_original_date": null, + "exif_digitized_date": null, + "processed_by_known_editor": false, + "has_different_creation_mod_date": false, + "overlay_manipulation": null + }, + "description": null, + "is_requested": false, + "ocr_data": { + "company_name": "ACME HOLDINGS LTD", + "registration_number": "12345678", + "incorporation_date": "2015-03-12" + }, + "is_console_upload": false, + "is_confirmed": true + }, + { + "uuid": "ffff6666-0000-7777-1111-888899990000", + "document_type": "OWNERSHIP_STRUCTURE", + "document_subtype": null, + "document_group": "OWNERSHIP_STRUCTURE", + "file_url": null, + "original_filename": null, + "file_size": null, + "status": "Not Finished", + "created_at": "2026-06-10T15:40:02.000000Z", + "cross_check_result": null, + "document_metadata": null, + "description": "Please upload an up-to-date shareholder registry", + "is_requested": true, + "ocr_data": null, + "is_console_upload": false, + "is_confirmed": true + } + ], + "groups": { + "LEGAL_PRESENCE": { + "total": 1, + "approved": 1, + "pending": 0, + "declined": 0, + "in_review": 0, + "other": 0, + "missing": 0 + }, + "OWNERSHIP_STRUCTURE": { + "total": 1, + "approved": 0, + "pending": 0, + "declined": 0, + "in_review": 0, + "other": 0, + "missing": 1 + } + }, + "required_groups": [ + "LEGAL_PRESENCE", + "OWNERSHIP_STRUCTURE" + ], + "warnings": [] + } + ], + "key_people_checks": [ + { + "status": "In Review", + "node_id": "feature_kyb_key_people_1", + "registry": { + "officers": [ + { + "uuid": "aaaa1111-bbbb-2222-cccc-333344445555", + "name": "Jane Smith", + "designation": "Director", + "role": "director", + "nationality": "British", + "appointment_date": "2015-03-12", + "termination_date": null, + "is_active": true, + "address": "1 Example Street, London, EC1A 1AA", + "aml_status": "Approved", + "kyc_status": "Approved", + "kyc_session_url": "https://verify.didit.me/session/abc123def456", + "kyc_session_id": "77777777-8888-9999-aaaa-bbbbbbbbbbbb", + "verification_workflow_type": "kyc", + "verification_workflow_id": "12121212-3434-5656-7878-909090909090", + "verification_workflow_label": "Key People KYC", + "verification_workflow_features": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "didit_internal_id": "99999999-0000-1111-2222-333333333333", + "kyc_document_type": "P", + "kyc_nationality": "GBR", + "kyc_liveness_passed": true, + "kyc_verified_at": "2026-06-10T14:22:31.123456Z", + "kyc_aml_categories": { + "sanctions": "clear", + "pep": "clear", + "adverse_media": "clear", + "warnings": "clear" + }, + "kyc_notification_sent_at": "2026-06-09T09:00:00.000000Z" + } + ], + "beneficial_owners": [ + { + "uuid": "bbbb2222-cccc-3333-dddd-444455556666", + "name": "John Doe", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-05-04", + "country_of_birth": null, + "email": null, + "company_name": null, + "registration_number": null, + "entity_type": "person", + "nationality": "British", + "designation": null, + "roles": [ + "ubo", + "shareholder" + ], + "ownership_min_shares": "50", + "ownership_max_shares": "75", + "is_active": true, + "aml_status": null, + "kyc_status": "In Progress", + "kyc_session_url": "https://verify.didit.me/session/def456ghi789", + "kyc_session_id": "88888888-9999-aaaa-bbbb-cccccccccccc", + "verification_workflow_type": "kyc", + "verification_workflow_id": "12121212-3434-5656-7878-909090909090", + "verification_workflow_label": "Key People KYC", + "verification_workflow_features": [ + { + "feature": "ID_VERIFICATION", + "status": "Not Finished" + }, + { + "feature": "LIVENESS", + "status": "Not Finished" + }, + { + "feature": "AML", + "status": "Not Finished" + } + ], + "didit_internal_id": null, + "kyc_document_type": null, + "kyc_nationality": null, + "kyc_liveness_passed": null, + "kyc_verified_at": null, + "kyc_aml_categories": null, + "kyc_notification_sent_at": null, + "child_beneficial_owners": [], + "effective_ownership_percent": 75.0 + } + ] + }, + "submitted": { + "parties": [ + { + "uuid": "cccc3333-dddd-4444-eeee-555566667777", + "entity_type": "person", + "source": "USER", + "name": "John Doe", + "first_name": "John", + "last_name": "Doe", + "company_name": null, + "registration_number": null, + "email": "john.doe@example.com", + "nationality": "GBR", + "phone_number": "+447700900123", + "date_of_birth": "1980-05-04", + "position": "Chief Executive Officer", + "custom_fields": {}, + "is_active": true, + "is_skipped": false, + "requires_verification": true, + "roles": [ + { + "role": "ubo", + "ownership_percent": 75.0, + "voting_percent": 75.0 + }, + { + "role": "director", + "ownership_percent": null, + "voting_percent": null + } + ], + "kyc_session_id": "88888888-9999-aaaa-bbbb-cccccccccccc", + "kyc_session_status": "In Progress", + "kyc_session_url": "https://verify.didit.me/session/def456ghi789", + "kyb_sub_session_id": null, + "kyb_sub_session_status": null, + "kyb_sub_session_url": null, + "verification_workflow_type": "kyc", + "verification_workflow_id": "12121212-3434-5656-7878-909090909090", + "verification_workflow_label": "Key People KYC", + "verification_workflow_features": [ + { + "feature": "ID_VERIFICATION", + "status": "Not Finished" + }, + { + "feature": "LIVENESS", + "status": "Not Finished" + }, + { + "feature": "AML", + "status": "Not Finished" + } + ], + "didit_internal_id": null + } + ] + }, + "ubo_kyc_summary": { + "total": 1, + "approved": 0, + "flagged": 0, + "pending": 1 + }, + "warnings": [] + } + ], + "phone_verifications": null, + "email_verifications": null, + "questionnaire_responses": null, + "ip_analyses": null, + "reviews": [], + "contact_details": null, + "expected_details": { + "company_name": "Tesco PLC", + "registry_country": "GB", + "registration_number": "00445790" + }, + "created_at": "2026-06-10T11:58:04.000000Z", + "expires_at": "2026-06-24T11:58:04.000000Z" + } + } + }, + "schema": { + "$ref": "#/components/schemas/SessionDecision" + } + } + } + }, + "403": { + "description": "Authentication or authorization failed. A missing key returns `403`; a wrong, revoked or expired key returns `401`. Two `detail` strings distinguish the failure modes:\n\n- `\"Authentication credentials were not provided or are invalid.\"` — no `x-api-key` header (and no `Authorization: Bearer` token), or the supplied credential failed introspection (revoked, expired, or malformed).\n- `\"You do not have permission to perform this action.\"` — the credential is valid but not allowed to read this session: a console-user Bearer token lacks the `read:sessions` permission or cannot access the owning application, or an API key belongs to a different organization than the session.\n\nNote that for a completely unknown `sessionId` the endpoint resolves the owning application before validating credentials, so a missing session returns `404` even when the credential is missing or invalid.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Authentication credentials were not provided or are invalid." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed introspection", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Insufficient permissions": { + "summary": "Valid credential lacking read:sessions or access to the owning application", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Session not found. Returned when no live session matches the supplied `sessionId` as either a User Verification (KYC) or a Business Verification (KYB) session — a session that has been deleted also lands here. Body is the standard `{\"detail\": ...}` envelope.\n\n**Two distinct `detail` strings.** If the `sessionId` is unknown as both a KYC and a KYB session, the pre-auth owner lookup fails and you receive `{\"detail\": \"Not found.\"}` — this happens before credential validation, so it is returned even for unauthenticated calls. If the session exists but cannot be retrieved under the authenticated application (for example a stale or just-deleted record), the view returns `{\"detail\": \"Session not found.\"}`. Both indicate the same outcome — the session is not accessible — so match on the `404` status code rather than the string.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Not found." + } + } + }, + "examples": { + "Unknown session id": { + "summary": "sessionId is unknown to this application", + "value": { + "detail": "Not found." + } + }, + "Session not retrievable": { + "summary": "Session exists but is not retrievable for this application", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. GET requests are limited to **600 per minute per credential** (API key, Bearer token, or source IP when unauthenticated). Inspect `Retry-After` / `X-RateLimit-Reset` and back off before retrying. If you hit this limit while polling for decisions, switch to the `status.updated` webhook and fetch the decision once per status change instead.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying the request.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Maximum number of requests allowed in the current window (600 for GET requests).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests remaining in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix timestamp (seconds) at which the current window resets.", + "schema": { + "type": "integer", + "example": 1750000000 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable explanation of the rate-limit breach.", + "example": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + }, + "examples": { + "Throttled": { + "summary": "More than 600 GET requests in one minute from the same credential", + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session whose decision you want to retrieve. This is the `session_id` returned by `POST /v3/session/`. The same path works for both User Verification (KYC) and Business Verification (KYB) sessions — the server resolves the id against both session types.\n\n**Must be a valid UUID.** The route only matches well-formed UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below. Validate the UUID format client-side so you can distinguish an input error from a genuine missing-session `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "include", + "in": "query", + "required": false, + "description": "Comma-separated list of extra payload sections. Supported values: `events` and `resubmissions`, alone or together (`events,resubmissions`). When `include=events` is passed, the response additionally contains the session event timeline (`events`, including the `WORKFLOW_BRANCH_EVALUATED` / `WORKFLOW_STATUS_NODE_REACHED` routing events described on the Retrieve Session page), the full activity log (`activity_reviews`), blocklist flags (`blocklisted`), a per-session `cost_breakdown`, workflow metadata (`workflow_type`, `workflow_version`, `workflow_version_id`, and `workflow_terminal_node_id` - the last recorded workflow status node that set a session status, or `null` if none has been recorded; historical provenance that is not cleared by manual approvals, declines or resubmissions, and is replaced by a later workflow status node), and session metadata (`api_service` — string or `null`, set only for standalone-API sessions; `session_type` — `API`, `HOSTED`, or `MIGRATED`; `has_device_nfc_support` — boolean). Two fields change relative to the plain shape: the top-level `session_kind` discriminator is **omitted** (the events shape only exists for user sessions), and the `features` array switches from plain strings to `{feature, node_id}` objects — where the email feature is reported as `EMAIL` instead of `EMAIL_VERIFICATION`. This expanded shape is primarily used by the Didit Console; most API integrations should omit the parameter. **KYB limitation:** `include=events` only applies to user (KYC) sessions. Business sessions (`session_kind = \"business\"`) always return the standard business decision shape — `events`, `activity_reviews`, `blocklisted`, `cost_breakdown` and the extra `workflow_*`/session metadata are never returned for business sessions, and their `features` array stays a plain string array. With `include=resubmissions` (user and business sessions alike) the response also contains `resubmissions`: one entry per resubmission request, oldest first, each with `number`, `created_at`, `previous_status`, `nodes`, `reasons`, `requested_by` and `results` (what the resubmitted steps returned before the request, under the same keys and in the same shape as the current results). Each `cost_breakdown` item carries `usage_type`, `price`, `details` and `created_at`: a resubmitted step that runs again is billed as a new item.", + "schema": { + "type": "string", + "enum": [ + "events", + "resubmissions", + "events,resubmissions" + ], + "example": "events" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/decision/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Accept: application/json'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.retrieve_decision(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.retrieveDecision(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.RetrieveDecision(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsRetrieveDecisionParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().retrieveDecision(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->retrieveDecision('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.retrieve_decision(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.RetrieveDecisionAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/delete/": { + "delete": { + "summary": "Delete a verification session (KYC or KYB)", + "description": "Delete a single verification session, User Verification (KYC) or Business Verification (KYB), by its `session_id`. The id is resolved against KYC sessions first, then KYB sessions, so both kinds are deleted through this one URL.\n\n**What is deleted:**\n- The session and every verification attempt it holds, including results that a resubmission replaced: the decision, extracted data, all related feature records (ID verification, face, liveness, face match, AML, registry checks, IP analysis, and so on), its reviews and its activity log.\n- All stored files owned by the session: document front/back photos (full, cropped, and privacy-blurred variants), document-capture videos, portrait crops, NFC chip portrait and signature images, face reference images, liveness videos, face-match source/target images, Proof of Address documents, and any extra uploaded files (KYC); uploaded company documents and extra files (KYB). E-signature documents, signed PDFs, evidence PDFs and signature images (KYC and KYB): their download paths stop working. Previously issued media URLs (`https:///...`) stop resolving.\n- The session's face data. Its face embedding is deleted too, unless you choose to keep an image-free template (see **Face biometric data**).\n- Webhook deliveries still queued for the session, and their retries. Webhooks already delivered are not affected.\n- For a Business Verification (KYB) session: the verifications it created for its key people (KYC) and its corporate owners (KYB), recursively, reported as `deleted_child_verifications`. A verification it only links to, such as an existing approved KYC it reused, is not deleted.\n\nThe session disappears from every read endpoint immediately: `GET /v3/session/{sessionId}/decision/` returns `404` and `GET /v3/sessions/` stops listing it. Ongoing AML monitoring and KYB registry monitoring started by the deleted verifications stop.\n\n**What is not affected:** blocklist entries created from this session (face or document) are **not** removed; manage those with the blocklist endpoints. Share tokens issued for the session are not revoked, but there is nothing left for them to import: an import with one returns `400` with `{\"share_token\": [\"Original session does not exist.\"]}`. Credits already consumed are **not** refunded. The parent User or Business entity is **not** deleted; use `POST /v3/users/delete/` or `POST /v3/businesses/delete/` for those. If the User's profile portrait came from this session, the portrait is removed with it.\n\n**Face biometric data:** by default the session's face embedding is deleted with the session (`face_retention_outcome: deleted`). An application that opted in to biometric-template retention (`face_retention_policy: retain_until_user_deleted` on `PATCH /v3/webhook/`), or a request with `retain_face_embeddings: true`, keeps one separately managed, image-free face biometric template anchored to the session's User (`retained_with_user`) so that duplicate detection, Face Search, and biometric authentication keep working. The template never contains the face image, media, identity fields, the decision, or the deleted session's id; it is biometric data deleted at `biometric_template_expires_at`, or earlier on User deletion, privacy erasure, or an explicit purge through `/v3/biometric-templates/`. A session without a linked User cannot retain a template (`ineligible_no_vendor_user`). Send `deletion_instruction: privacy_erasure` for data-subject requests: it purges every retained template for that User and never retains a new one.\n\n**Irreversible:** deletion is permanent and there is no restore endpoint. Export anything you need (for example a decision PDF) before you call it.\n\n**Side effects:** no webhook is emitted for deletions.\n\n**Response:** `200 OK` once the deletion has completed, with a JSON body reporting the `face_retention_outcome`, the retained template's expiry and, for a KYB session, `deleted_child_verifications`. Earlier versions of this endpoint returned `204 No Content`; update clients that assert on `204`. Keep your `instruction_id` in your own erasure log.\n\n**Idempotency:** not idempotent at the HTTP level: the first call returns `200`; repeating it returns `404` because the session no longer resolves. After a `503` the session is still in place; repeat the same request to finish deleting it.\n\n**Authentication:** send your application's API key in the `x-api-key` header; the session must belong to that application. Console user access tokens (`Authorization: Bearer ...`) may also call this endpoint when they carry the `delete:sessions` permission. Authentication and permission failures both return `403`; this API never returns `401`.\n\n**Rate limit:** shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.\n\nTo bulk-delete KYC sessions by their numeric `session_number`, use `POST /v3/sessions/delete/` instead.\n\n**Filed regulatory reports:** if the selected data is in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "delete_v3_session_by_id", + "x-didit-sdk": { + "resource": "sessions", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID (`session_id`) of the User Verification (KYC) or Business Verification (KYB) session to delete, as returned when the session was created. Must be a canonical hyphenated UUID — a non-UUID value does not match the route and returns `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "responses": { + "200": { + "description": "Session deleted. The body reports what happened to the session's face biometric data and, for a KYB session, how many verifications it created were deleted with it.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The deleted session." + }, + "session_number": { + "type": "integer", + "nullable": true, + "description": "The deleted session's number." + }, + "face_retention_outcome": { + "type": "string", + "enum": [ + "retained_with_user", + "deleted", + "none", + "ineligible_no_vendor_user" + ], + "description": "What happened to the session's face biometric data. `deleted`: the face embedding was deleted with the session (default). `retained_with_user`: the session is deleted and one image-free biometric template stays anchored to the User; see `biometric_template_uuid`. `none`: the session had no face embedding (for example a KYB session, or a KYC session without a liveness selfie). `ineligible_no_vendor_user`: retention was requested but the session has no linked User, so the embedding was deleted with the session. A biometric-store failure is never reported through this field on this endpoint: it returns `503` and the session is not deleted." + }, + "biometric_template_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Id of the retained biometric template when `face_retention_outcome` is `retained_with_user`; `null` otherwise. Manage it through `/v3/biometric-templates/{template_uuid}/`." + }, + "biometric_template_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the retained biometric template is deleted, when `face_retention_outcome` is `retained_with_user`; `null` when no template is retained." + }, + "deleted_child_verifications": { + "type": "integer", + "description": "For a Business Verification (KYB) session: how many verifications it created (its key people's KYC sessions and its corporate owners' KYB sessions, recursively) were deleted with it. Always `0` for a KYC session." + } + } + }, + "examples": { + "Deleted (default)": { + "value": { + "session_id": "4c5c7f3a-3b1d-4b7e-9c39-2b4f7e6a1d02", + "session_number": 1024, + "face_retention_outcome": "deleted", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + } + }, + "Retained with User": { + "value": { + "session_id": "4c5c7f3a-3b1d-4b7e-9c39-2b4f7e6a1d02", + "session_number": 1024, + "face_retention_outcome": "retained_with_user", + "biometric_template_uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "biometric_template_expires_at": "2027-10-09T10:15:00Z", + "deleted_child_verifications": 0 + } + }, + "Ineligible: no User": { + "summary": "Retention requested on a session created without vendor_data", + "value": { + "session_id": "7a1e9c2d-5f4b-4c3a-8e2d-1b0a9f8e7d6c", + "session_number": 1031, + "face_retention_outcome": "ineligible_no_vendor_user", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + } + }, + "KYB session": { + "value": { + "session_id": "0f3c9b7e-2d1a-4e5f-8c6b-7a9d8e1f2c3b", + "session_number": 88, + "face_retention_outcome": "none", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 3 + }, + "summary": "A business verification deleted with the three verifications it created for its key people" + } + } + } + } + }, + "400": { + "description": "Validation error — nothing is deleted.", + "content": { + "application/json": { + "examples": { + "Privacy erasure cannot retain": { + "value": { + "retain_face_embeddings": [ + "Privacy erasure cannot retain biometric templates." + ] + } + }, + "No finite retention duration": { + "summary": "Retention requested without face_retention_days on the request or the application", + "value": { + "detail": "A finite biometric-template retention duration is required." + } + } + } + } + } + }, + "403": { + "description": "Authentication or permission failure. A missing key also returns `403`; a wrong, revoked or expired key returns `401`. Exception: when the `sessionId` does not resolve to a live session, the pre-auth owner lookup returns `404` first, even with missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid API key": { + "summary": "No x-api-key header, or the key is invalid", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Insufficient permission": { + "summary": "User token without delete:sessions, or credentials for an application that does not own the session", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No live KYC or KYB session with this `session_id` exists — the id is unknown or the session was already deleted (repeating a successful delete lands here).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not Found": { + "summary": "Unknown or already-deleted session", + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Shared write rate limit exceeded (300 POST/PATCH/DELETE requests per minute per API key). Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "Write budget exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + }, + "503": { + "description": "The deletion could not finish because a storage service, a biometric store, or a monitoring provider was unavailable. The session is still in place; repeat the same request until it returns `200`. Some of its stored files or its face embedding may already be deleted, so do not rely on them after a `503`. Retries converge on a single template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Session deletion unavailable": { + "summary": "A storage service or a monitoring provider was unavailable", + "value": { + "detail": "The session could not be deleted because a storage service or a monitoring provider is unavailable. The session is still in place; repeat the request to finish deleting it." + } + }, + "Biometric store unavailable": { + "summary": "A biometric store was unavailable while retaining or purging a template", + "value": { + "detail": "The biometric-template operation could not finish because an external store is unavailable. The operation is retryable." + } + } + } + } + } + }, + "409": { + "description": "The session is part of a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl (default policy)", + "source": "curl -X DELETE \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/delete/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "curl", + "label": "curl (retain template)", + "source": "curl -X DELETE \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/delete/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"retain_face_embeddings\": true, \"face_retention_days\": 365}'" + }, + { + "lang": "curl", + "label": "curl (privacy erasure)", + "source": "curl -X DELETE \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/delete/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"deletion_instruction\": \"privacy_erasure\", \"instruction_id\": \"dsar-2026-0142\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.delete(\n \"11111111-2222-3333-4444-555555555555\",\n retain_face_embeddings=True,\n face_retention_days=365,\n instruction_id=\"post-approval-cleanup-2026-08\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.delete(\"11111111-2222-3333-4444-555555555555\", {\n retain_face_embeddings: true,\n face_retention_days: 365,\n instruction_id: \"post-approval-cleanup-2026-08\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Delete(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsDeleteParams{\n\tRetainFaceEmbeddings: didit.Bool(true),\n\tFaceRetentionDays: didit.Int(365),\n\tInstructionID: didit.String(\"post-approval-cleanup-2026-08\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsDeleteParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().delete(\"11111111-2222-3333-4444-555555555555\", SessionsDeleteParams.builder()\n .retainFaceEmbeddings(true)\n .faceRetentionDays(365L)\n .instructionId(\"post-approval-cleanup-2026-08\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->delete('11111111-2222-3333-4444-555555555555', [\n 'retain_face_embeddings' => true,\n 'face_retention_days' => 365,\n 'instruction_id' => 'post-approval-cleanup-2026-08',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.delete(\n \"11111111-2222-3333-4444-555555555555\",\n retain_face_embeddings: true,\n face_retention_days: 365,\n instruction_id: \"post-approval-cleanup-2026-08\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.DeleteAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsDeleteParams\n{\n RetainFaceEmbeddings = true,\n FaceRetentionDays = 365,\n InstructionId = \"post-approval-cleanup-2026-08\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": false, + "description": "Optional. Omit the body entirely to follow the application's retention policy with an operational deletion.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "retain_face_embeddings": { + "type": "boolean", + "nullable": true, + "description": "Override the application's `face_retention_policy` for this deletion. `true` keeps one image-free face biometric template anchored to the session's User after the session is deleted; `false` deletes the face embedding with the session; omit or `null` to follow the application policy. Ignored for KYB sessions and for sessions without a face embedding.", + "example": true + }, + "face_retention_days": { + "type": "integer", + "minimum": 1, + "maximum": 3650, + "description": "Finite retention duration, in days, for a retained template. Required when a template is retained unless the application already sets `face_retention_days`. The template's `expires_at` is the earliest of this duration, the application's general data-retention window, and `face_retention_deadline`.", + "example": 365 + }, + "face_retention_deadline": { + "type": "string", + "format": "date-time", + "description": "Optional hard expiry for a retained template. Must be in the future.", + "example": "2027-08-28T00:00:00Z" + }, + "deletion_instruction": { + "type": "string", + "enum": [ + "operational_session_delete", + "privacy_erasure" + ], + "default": "operational_session_delete", + "description": "The class of instruction you are giving. `operational_session_delete` deletes the session and retains a template only when the policy or `retain_face_embeddings` says so. `privacy_erasure` purges every retained biometric template anchored to the session's User before deleting the session, cannot be overridden by the application policy, and cannot be combined with `retain_face_embeddings: true` (`400`).", + "example": "operational_session_delete" + }, + "instruction_id": { + "type": "string", + "maxLength": 255, + "description": "Your durable reference for this deletion instruction (for example an erasure-ticket id). Recorded on the audit trail and on any retained template. Generated by Didit when omitted.", + "example": "dsar-2026-0142" + } + } + }, + "examples": { + "Retain a biometric template": { + "summary": "Delete the session, keep an image-free template for 365 days", + "value": { + "retain_face_embeddings": true, + "face_retention_days": 365, + "instruction_id": "post-approval-cleanup-2026-08" + } + }, + "Force deletion on an opted-in application": { + "summary": "Delete the face embedding regardless of the application policy", + "value": { + "retain_face_embeddings": false + } + }, + "Privacy erasure": { + "summary": "Data-subject request: delete the session and purge every retained template for that person", + "value": { + "deletion_instruction": "privacy_erasure", + "instruction_id": "dsar-2026-0142" + } + } + } + } + } + } + } + }, + "/v3/session/{sessionId}/generate-pdf/": { + "get": { + "summary": "Download a PDF report for a verification session", + "description": "Render and download a compliance-ready PDF report for a finished verification session. The same path serves both User Verification (KYC) and Business Verification (KYB) sessions — the server resolves the `sessionId` against both session types and picks the matching report template.\n\n**Eligible statuses.** The session must already carry a final or reviewable status:\n\n- KYC sessions: `Approved`, `Declined`, `In Review`, or `Kyc Expired`.\n- KYB sessions: `Approved`, `Declined`, or `In Review`.\n\nAny other status (`Not Started`, `In Progress`, `Expired`, `Abandoned`, …) returns `403` with an explanatory `detail` string — see the `403` response below.\n\n**What the report contains.** The PDF mirrors the console session view at the moment of the request:\n\n- **KYC** — rolled-up session status, the warnings list, and one section per executed feature: ID Verification (document images plus extracted fields), NFC, Liveness, Face Match, Email Verification, Phone Verification, Proof of Address, Questionnaire answers, Database Validation, AML Screening, and IP Analysis — followed by session tags, console review activity (comments and status changes), and the session event timeline.\n- **KYB** — registry checks (company data), AML screenings, business document verifications, key-people checks, questionnaire responses, phone/email verifications, IP analyses, and the session event timeline, followed by tags and review activity.\n\n**Branding.** If your application has white-label customization, the report is rendered with your logo and links to your privacy-policy URL; otherwise it carries Didit branding. Reports are rendered in English — there is no language parameter.\n\n**No caching.** Every call re-renders the PDF from the current session data, so a report generated after a manual review reflects the reviewer’s decision. Two calls for the same session can produce byte-different files — archive the downloaded file if you need an immutable copy.\n\n**Latency.** Rendering downloads every stored image (document sides, selfies, extra files) before composing the PDF, so media-heavy sessions can take several seconds. Use a generous client read timeout (60 s recommended) and stream the body to disk.\n\n**Trailing slash.** The canonical route ends with a trailing slash (`…/generate-pdf/`). A request without it is served directly with the same response — there is no `301` redirect, so no client has to follow redirects and `curl` does not need `-L`. Use the slashed URL as in the samples.\n\nIf you need the underlying data as JSON instead of a rendered report, use `GET /v3/session/{sessionId}/decision/`. For every reportable session of one user in a single document, use `GET /v3/users/{vendor_data}/generate-pdf/`.", + "operationId": "get_v3_session_generate_pdf", + "x-didit-sdk": { + "resource": "sessions", + "method": "generate_pdf", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) or Business Verification (KYB) session to render — the `session_id` returned by `POST /v3/session/`. The same path works for both session kinds; use the `Content-Disposition` filename on the response to tell which report type you received.\n\n**Must be a well-formed, lowercase UUID.** The route only matches valid UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below. The route's UUID converter only matches the canonical lowercase form — uppercase-hex UUIDs are rejected with the HTML 404.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "responses": { + "200": { + "description": "The rendered PDF document, returned directly as binary `application/pdf` — there is no JSON wrapper and no download-URL indirection. The body starts with the `%PDF` magic bytes (PDF 1.7). Save it to a `.pdf` file or stream it through to your caller.", + "headers": { + "Content-Disposition": { + "description": "`attachment; filename=session_{sessionId}.pdf` for KYC sessions, `attachment; filename=business_{sessionId}.pdf` for KYB sessions — the prefix tells you which report template was rendered.", + "schema": { + "type": "string", + "example": "attachment; filename=session_11111111-2222-3333-4444-555555555555.pdf" + } + }, + "Content-Length": { + "description": "Total size of the PDF in bytes; the body is not chunked.", + "schema": { + "type": "integer", + "example": 25191 + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "403": { + "description": "Authentication, authorization, or session-status failure. A missing key returns `403`; a wrong, revoked or expired key returns `401`. Distinguish the failure modes by the `detail` string:\n\n- `\"Authentication credentials were not provided or are invalid.\"` — no `x-api-key` header (and no `Authorization: Bearer` token), or the supplied credential failed introspection (revoked, expired, or malformed).\n- `\"You do not have permission to perform this action.\"` — the credential is valid but cannot read this session: a console-user Bearer token lacks the `read:sessions` permission or cannot access the owning application, or an API key belongs to a different organization than the session.\n- `\"You can only generate a PDF for sessions in review, declined, approved or kyc expired\"` — the KYC session exists but is not in an eligible status yet (for example `Not Started` or `In Progress`). Wait for the `status.updated` webhook before requesting the report.\n- `\"You can only generate a PDF for sessions in review, declined or approved\"` — same condition for KYB sessions.\n\nNote that for a completely unknown `sessionId` the endpoint resolves the owning application before validating credentials, so a missing session returns `404` even when the credential is missing or invalid.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You can only generate a PDF for sessions in review, declined, approved or kyc expired" + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed introspection", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Insufficient permissions": { + "summary": "Valid credential lacking read:sessions or access to the owning application", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "KYC session not finished": { + "summary": "KYC session still Not Started / In Progress", + "value": { + "detail": "You can only generate a PDF for sessions in review, declined, approved or kyc expired" + } + }, + "KYB session not finished": { + "summary": "KYB session still Not Started / In Progress", + "value": { + "detail": "You can only generate a PDF for sessions in review, declined or approved" + } + } + } + } + } + }, + "404": { + "description": "No active session with this `sessionId` exists — it was never created, or it has been deleted. Owner resolution happens before credential validation, so this `404` is returned even for unauthenticated requests. Depending on which lookup misses, the `detail` string is `\"Not found.\"` (the common case) or `\"Session not found.\"`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Not found." + } + } + }, + "examples": { + "Unknown session": { + "summary": "sessionId does not match any KYC or KYB session", + "value": { + "detail": "Not found." + } + }, + "Recently deleted session": { + "summary": "Session was deleted between lookups", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. PDF generation has a dedicated limit of **50 requests per minute per credential** (API key, Bearer token, or source IP when unauthenticated), in addition to the global 600/min GET limit. Wait `Retry-After` seconds (also reflected in the `X-RateLimit-*` headers), then retry. Reports are not cached server-side, so download each report once and store the file instead of re-fetching it.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying the request.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Maximum number of PDF generations allowed in the current window (50).", + "schema": { + "type": "integer", + "example": 50 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests remaining in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix timestamp (seconds) at which the current window resets.", + "schema": { + "type": "integer", + "example": 1750000000 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable explanation of the rate-limit breach.", + "example": "Session PDF generation rate limit exceeded. You can make up to 50 requests per minute." + } + } + }, + "examples": { + "Throttled": { + "summary": "More than 50 PDF generations in one minute from the same credential", + "value": { + "detail": "Session PDF generation rate limit exceeded. You can make up to 50 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --fail \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/generate-pdf/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n --output report.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.sessions.generate_pdf(\n \"11111111-2222-3333-4444-555555555555\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.sessions.generatePdf(\"11111111-2222-3333-4444-555555555555\");\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Sessions.GeneratePDF(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().generatePdf(\"11111111-2222-3333-4444-555555555555\")\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->generatePdf('11111111-2222-3333-4444-555555555555');\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.sessions.generate_pdf(\"11111111-2222-3333-4444-555555555555\")\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Sessions.GeneratePdfAsync(\"11111111-2222-3333-4444-555555555555\");\nawait content.SaveToAsync(\"download.pdf\");" + } + ] + } + }, + "/v3/session/{sessionId}/sandbox/arm/": { + "post": { + "summary": "Arm a sandbox scenario on a session", + "description": "Persist a sandbox scenario on a `Not Started` session and expand its bundled magic values (email, phone, or `expected_details` fields) onto the session, so the mocked providers reproduce the scenario's outcome once the flow runs. This is the endpoint the pre-flow scenario picker in Didit's hosted verification UI calls when a tester chooses an outcome before starting capture. Build your own picker against it, or call it directly from a sandbox test harness.\n\nAuthenticate with the session's own `Session-Token` (returned in `session_token` by [Create Session](/sessions-api/create-session)), not your application `x-api-key`: this endpoint is meant to be called from the client, not your backend. A scenario is only accepted while `status` is `Not Started`. Calling it again while still `Not Started` re-arms the session: it swaps in the new scenario's magic values and clears only the values the *previous* scenario wrote, so any value the user already typed by hand (email, first name, etc.) survives the re-arm. Arming a session that has moved past `Not Started`, or that belongs to a live (non-sandbox) application, is rejected.\n\nSee [Sandbox & Test Data](/integration/sandbox-testing) for the full scenario catalog and the other two ways to drive an outcome: `sandbox_scenario` at session create, and typing magic values directly.", + "operationId": "post_v3_session_sandbox_arm", + "x-didit-sdk": { + "resource": "sessions", + "method": "arm_sandbox", + "auth": "device_token", + "server": false + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "SessionTokenAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the sandbox verification session to arm. Must match the session encoded in the Session-Token used to authenticate.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "scenario" + ], + "properties": { + "scenario": { + "type": "string", + "description": "Slug of the scenario to arm. See the catalog at GET /v1/sandbox/scenarios/ or the scenario table in [Sandbox & Test Data](/integration/sandbox-testing).", + "example": "decline_document_expired" + } + } + }, + "examples": { + "Arm": { + "summary": "Arm the AML PEP-hit scenario", + "value": { + "scenario": "decline_aml_hit" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Scenario armed. Its magic values are now on the session. `status` reflects the session's current status, which this call never changes: it is always `Not Started` on success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid" + }, + "scenario": { + "type": "string", + "description": "The armed scenario slug (echoes the request)." + }, + "status": { + "type": "string", + "example": "Not Started" + } + } + }, + "examples": { + "Armed": { + "summary": "Scenario armed on a Not Started session", + "value": { + "session_id": "11111111-2222-3333-4444-555555555555", + "scenario": "decline_aml_hit", + "status": "Not Started" + } + } + } + } + } + }, + "400": { + "description": "Validation error: an unknown scenario slug, or the session has already progressed past `Not Started`.", + "content": { + "application/json": { + "examples": { + "Unknown Scenario": { + "summary": "scenario is not in the catalog", + "value": { + "scenario": [ + "Unknown sandbox scenario: 'not-a-real-scenario'." + ] + } + }, + "Already Started": { + "summary": "Session moved past Not Started", + "value": [ + "Scenario can only be armed before the verification starts." + ] + } + } + } + } + }, + "404": { + "description": "The Session-Token does not resolve to this `session_id`, or the session belongs to a live (non-sandbox) application.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Live session, or session_id / token mismatch", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/sandbox/arm/ \\\n -H 'Session-Token: YOUR_SESSION_TOKEN' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"scenario\": \"decline_aml_hit\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/sandbox/arm/\",\n headers={\n \"Session-Token\": \"YOUR_SESSION_TOKEN\",\n \"Content-Type\": \"application/json\",\n },\n json={\"scenario\": \"decline_aml_hit\"},\n)\nresponse.raise_for_status()\nprint(response.json()) # {'session_id': '...', 'scenario': 'decline_aml_hit', 'status': 'Not Started'}" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const response = await fetch(\n 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/sandbox/arm/',\n {\n method: 'POST',\n headers: {\n 'Session-Token': sessionToken,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ scenario: 'decline_aml_hit' }),\n },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst { session_id, scenario, status } = await response.json();" + } + ] + } + }, + "/v3/session/{sessionId}/update-status/": { + "patch": { + "summary": "Approve, decline, or request resubmission for a verification session", + "description": "Manually override the final decision of a KYC or KYB verification session. Set `new_status` to `Approved` or `Declined` to record a manual decision, or to `Resubmitted` to clear specific verification steps and let the user redo them through the same verification URL. The endpoint accepts both user (KYC) and business (KYB) session IDs.\n\nCall it once a session has reached a reviewable state: the current status must be `Approved`, `Declined`, `In Review`, `Kyc Expired`, `Abandoned`, or `Resubmitted`. Sessions that are still `Not Started`, `In Progress`, or `Awaiting User`, or that ended as `Expired`, return `400`. Your API key needs the `write:sessions` permission (a scoped API key needs write access to decisions). The eligible-state check runs before body validation, so an ineligible session returns the `Wrong Current Status` error even when the payload is also invalid.\n\nFor `Resubmitted`, pass `nodes_to_resubmit` to choose which workflow steps the user must redo, or omit it to auto-select existing feature attempts in a resubmittable state (`Declined`, `In Review`, `Not Finished`, `Expired`). Never-attempted features are not auto-selected: list them explicitly in `nodes_to_resubmit`, or the request returns `400` when no recorded attempt needs resubmission. The selected steps' previous results move to the resubmission's history (returned by Get Decision with `include=resubmissions`), the steps are reordered to match the workflow graph, the session's expiration window restarts, and the original verification URL becomes usable again. Backend-only steps (`AML`, `DATABASE_VALIDATION`, `IP_ANALYSIS`) at the head of the list run immediately without user interaction; if every resubmitted step is backend-only, the session re-finalizes within the same request. `KYB_REGISTRY` and `KYB_KEY_PEOPLE` cannot be resubmitted directly: resubmit the relevant child KYC sessions instead and the parent business session recomputes automatically.\n\nEvery call that changes the status fires the `status.updated` [webhook](/integration/webhooks) once the change commits and appends an activity entry (with actor attribution and your `comment`) to the session's `reviews`, visible in [Get Decision](#get-/v3/session/-sessionId-/decision/) and in the console activity timeline. The user-profile aggregates linked to the session's `vendor_data` update as well. Declining disables ongoing AML monitoring for the session; re-approving re-enables it (from `Resubmitted`, `Declined`, or `Kyc Expired`) when the workflow has monitoring turned on. Approving or declining a child KYC that belongs to a KYB key-people check recomputes the parent business session's status. Set `send_email: true` (with `email_address`) to notify the user: a status notice for `Approved`/`Declined`, or a resubmission email containing the verification link and per-step reasons for `Resubmitted`. To check the email first, preview it with [Preview Decision Email](/sessions-api/preview-decision-email) (`Approved`, `Declined`) or [Preview Resubmission Email](/sessions-api/preview-resubmission) (`Resubmitted`) and pass its `preview_token` as `email_preview_token`: the update then sends exactly the previewed email, or returns `409` before changing anything when the email or the session changed since the preview or the receipt is older than 15 minutes.\n\nRepeating `Approved` or `Declined` on a session that already has that status changes nothing and returns `200`: no webhook, no activity entry and no email. Repeating `Resubmitted` returns `400` (`The new status is the same as the current status.`). Missing or invalid credentials and missing permissions return `403`; an expired scoped API key returns `401`.", + "operationId": "patch_v3_session_update_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session to update. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "new_status" + ], + "properties": { + "new_status": { + "type": "string", + "description": "Target status. `Approved` and `Declined` record a final manual decision (each can also overturn the other). `Resubmitted` clears the selected steps and sends the session back to the user. Any other value returns `400`.", + "enum": [ + "Approved", + "Declined", + "Resubmitted" + ], + "example": "Approved" + }, + "comment": { + "type": "string", + "description": "Free-text reason for the change, stored on the session's review trail and returned in the `reviews` array of [Get Decision](#get-/v3/session/-sessionId-/decision/). For example `Duplicated user`.", + "example": "All checks passed manual review" + }, + "nodes_to_resubmit": { + "type": "array", + "description": "Workflow steps the user must redo. Only acted on when `new_status` is `Resubmitted`. For `Approved`/`Declined` the entries are still schema-validated (an invalid `feature` value returns `400`) but schema-valid entries are semantically ignored. If omitted, the server auto-selects existing OCR, Liveness, Face Match, POA, Phone, Email, AML, Database Validation, and Questionnaire attempts whose status is `Declined`, `In Review`, `Not Finished`, or `Expired` (NFC, IP analysis, age estimation, face search, and KYB document attempts are never auto-selected; non-face-match face attempts are selected as `LIVENESS`) — features the user never attempted (no recorded attempt) are NOT selected, so a session with zero attempts returns `400` (\"No features found that need resubmission\") even though nothing was approved; pass `nodes_to_resubmit` explicitly in that case. Steps are executed in workflow-graph order regardless of the order you send them. `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and the `KYB` alias are rejected with `400` — those parent checks recompute from their child KYC sessions.", + "items": { + "type": "object", + "required": [ + "node_id", + "feature" + ], + "properties": { + "node_id": { + "type": "string", + "description": "Node identifier as it appears in the session's workflow definition (for example `feature_ocr`, `feature_liveness`).", + "example": "feature_ocr" + }, + "feature": { + "type": "string", + "description": "Feature type of the node. The aliases `ID_VERIFICATION`, `POA`, `PHONE`, and `EMAIL` are normalized server-side to `OCR`, `PROOF_OF_ADDRESS`, `PHONE_VERIFICATION`, and `EMAIL_VERIFICATION`. `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and `KYB` pass schema validation but are always rejected with the business-rule `400` shown in the examples.", + "enum": [ + "OCR", + "OCR_BACK", + "NFC", + "AML", + "FACE", + "LIVENESS", + "FACE_MATCH", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "PROOF_OF_ADDRESS", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "FACE_SEARCH", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_DOCUMENTS", + "ID_VERIFICATION", + "POA", + "PHONE", + "EMAIL", + "KYB_REGISTRY", + "KYB_KEY_PEOPLE", + "KYB", + "GEOLOCATION" + ], + "example": "OCR" + } + } + } + }, + "send_email": { + "type": "boolean", + "description": "Whether to email the user about the change. Requires `email_address`. For `Approved`/`Declined` the user receives a status notice; for `Resubmitted` the email includes the verification link and the per-step resubmission reasons.", + "default": false, + "example": false + }, + "email_address": { + "type": "string", + "format": "email", + "description": "Recipient for the notification email. **Required when `send_email` is `true`** — omitting it returns `400`.", + "example": "user@example.com" + }, + "email_language": { + "type": "string", + "description": "Language for the notification email. Accepts any string at schema level; unsupported codes silently fall back to English (`en`).", + "default": "en", + "example": "en" + }, + "email_preview_token": { + "type": "string", + "writeOnly": true, + "minLength": 1, + "maxLength": 512, + "description": "Optional receipt from [Preview Decision Email](/sessions-api/preview-decision-email) (for `Approved` and `Declined`) or [Preview Resubmission Email](/sessions-api/preview-resubmission) (for `Resubmitted`): its `preview_token`. Valid only with `send_email: true`; without it the request returns `400`. The recipient, language, session status, sender and email (and, for a resubmission, the selected steps) must still match the preview, and the receipt expires after 15 minutes. Otherwise the request returns `409` before the session changes or an email is sent. Existing integrations may omit this field." + } + } + }, + "examples": { + "Approve Session": { + "summary": "Approve a session after manual review", + "value": { + "new_status": "Approved", + "comment": "All checks passed manual review" + } + }, + "Decline Session": { + "summary": "Decline a session", + "value": { + "new_status": "Declined", + "comment": "Suspected fraud" + } + }, + "Resubmit Specific Steps": { + "summary": "Request resubmission of chosen steps, with email", + "value": { + "new_status": "Resubmitted", + "nodes_to_resubmit": [ + { + "node_id": "feature_ocr", + "feature": "OCR" + }, + { + "node_id": "feature_liveness", + "feature": "LIVENESS" + } + ], + "send_email": true, + "email_address": "user@example.com", + "email_language": "en" + } + }, + "Resubmit Auto-select": { + "summary": "Request resubmission (auto-select non-approved steps)", + "value": { + "new_status": "Resubmitted" + } + }, + "Approve With Previewed Email": { + "summary": "Approve and send the email returned by Preview Decision Email", + "value": { + "new_status": "Approved", + "send_email": true, + "email_address": "person@example.com", + "email_language": "en", + "email_preview_token": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Status updated. The response contains only the `session_id`; fetch the updated session via [Get Decision](#get-/v3/session/-sessionId-/decision/). The `status.updated` webhook fires once the change commits. Repeating `Approved` or `Declined` returns the same body and changes nothing.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the updated session." + } + } + }, + "examples": { + "Updated": { + "summary": "Returned for Approved, Declined, and Resubmitted alike", + "value": { + "session_id": "3472fb7c-8f7c-4d1a-9cf4-cf3d74ce1a60" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field errors arrive as `{\"\": [\"\"]}` (some business-rule rejections use a string value instead: `{\"nodes_to_resubmit\": \"\"}`), cross-state errors as `{\"detail\": \"\"}`, and the same-status error as a bare JSON array.", + "content": { + "application/json": { + "examples": { + "Wrong Current Status": { + "summary": "Session is not in an updatable state", + "value": { + "detail": "Only sessions with status Approved, Declined, In Review, Kyc Expired, Abandoned, Resubmitted can be updated. Current status: In Progress" + } + }, + "Invalid Status Value": { + "summary": "new_status is a real session status but not one of Approved/Declined/Resubmitted", + "value": { + "new_status": [ + "You can only update the status to 'Approved', 'Declined', or 'Resubmitted'." + ] + } + }, + "Missing new_status": { + "summary": "Required field omitted", + "value": { + "new_status": [ + "This field is required." + ] + } + }, + "Same Status": { + "summary": "Resubmitted requested on a session that is already Resubmitted", + "value": [ + "The new status is the same as the current status." + ] + }, + "Email Address Required": { + "summary": "send_email true without email_address", + "value": { + "email_address": [ + "Email address is required when send_email is true." + ] + } + }, + "Non-resubmittable Feature": { + "summary": "Resubmit targets a KYB parent check", + "value": { + "nodes_to_resubmit": "The following features cannot be resubmitted directly: KYB_REGISTRY. Resubmit the relevant child KYC sessions instead; the parent will recompute automatically." + } + }, + "Nothing To Resubmit": { + "summary": "Auto-select found no non-approved features", + "value": { + "nodes_to_resubmit": "No features found that need resubmission. All features are already approved." + } + }, + "Invalid Feature Choice": { + "summary": "Unknown feature in nodes_to_resubmit", + "value": { + "nodes_to_resubmit": [ + { + "feature": [ + "\"BOGUS\" is not a valid choice. Valid choices are: OCR, OCR_BACK, NFC, AML, FACE, LIVENESS, FACE_MATCH, IP_ANALYSIS, AGE_ESTIMATION, PROOF_OF_ADDRESS, PHONE_VERIFICATION, EMAIL_VERIFICATION, FACE_SEARCH, DATABASE_VALIDATION, QUESTIONNAIRE, KYB_REGISTRY, KYB_DOCUMENTS, KYB_KEY_PEOPLE, ID_VERIFICATION, POA, PHONE, EMAIL, KYB" + ] + } + ] + } + }, + "Invalid Status Value (not a status at all)": { + "summary": "new_status is not a valid StatusChoices value", + "value": { + "new_status": [ + "\"Bogus\" is not a valid choice." + ] + } + }, + "Preview Receipt Without Email": { + "summary": "email_preview_token sent without send_email: true", + "value": { + "email_preview_token": [ + "A preview receipt is only valid when the email is sent." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "API key lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists (deleted sessions also return `404`).", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "A resubmit targeting only backend-only features (AML, DATABASE_VALIDATION, IP_ANALYSIS) was repeated within the 30-second cooldown window; an immediate repeat cannot change the outcome. Back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Backend-only resubmit throttled": { + "value": { + "detail": "This session was already resubmitted for backend-only features within the last 30 seconds; repeating it cannot change the outcome. Wait before resubmitting again." + } + } + } + } + } + }, + "409": { + "description": "The email preview changed or expired. Generate a new preview before confirming; no session changes or email delivery occur.", + "content": { + "application/json": { + "examples": { + "Preview Changed": { + "summary": "The receipt no longer matches, or is older than 15 minutes", + "value": { + "detail": "This email preview changed or expired. Preview the message again before confirming." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"new_status\": \"Approved\",\n \"comment\": \"All checks passed manual review\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_status(\n \"11111111-2222-3333-4444-555555555555\",\n new_status=\"Approved\",\n comment=\"All checks passed manual review\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateStatus(\"11111111-2222-3333-4444-555555555555\", {\n new_status: \"Approved\",\n comment: \"All checks passed manual review\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateStatus(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsUpdateStatusParams{\n\tNewStatus: \"Approved\",\n\tComment: didit.String(\"All checks passed manual review\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsUpdateStatusParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateStatus(\"11111111-2222-3333-4444-555555555555\", SessionsUpdateStatusParams.builder()\n .newStatus(SessionsUpdateStatusParams.NewStatus.of(\"Approved\"))\n .comment(\"All checks passed manual review\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateStatus('11111111-2222-3333-4444-555555555555', [\n 'new_status' => 'Approved',\n 'comment' => 'All checks passed manual review',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_status(\n \"11111111-2222-3333-4444-555555555555\",\n new_status: \"Approved\",\n comment: \"All checks passed manual review\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateStatusAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsUpdateStatusParams\n{\n NewStatus = \"Approved\",\n Comment = \"All checks passed manual review\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/id-verification/": { + "post": { + "summary": "ID Verification (document OCR + fraud checks)", + "description": "OCR an identity document and run document fraud checks in one call — returns the extracted holder fields, the parsed MRZ, an `Approved`/`Declined`/`In Review` `status`, and a `warnings` list explaining every issue found. Supports 14,000+ document types from 220+ countries and territories.\n\n**How it works.** Send the document's `front_image` (required) and, for two-sided documents, the `back_image`. Upload uncropped photos with all four corners of the document visible — the service detects, aligns, and crops the document itself, so do not pre-crop. The document is classified automatically (you never declare the country or document type), the visual zone is read with OCR, and the MRZ and any barcodes are decoded. Cross-checks then validate dates, number formats, and MRZ check digits, and compare the visual zone against the MRZ. With `perform_document_liveness=true`, the images are additionally screened for screen replays, printed copies, and portrait manipulation.\n\n**Decision logic.** `status` is `Approved` unless at least one warning resolves to a decline. Fraud and hard-failure risks always decline: `DOCUMENT_EXPIRED`, `SCREEN_CAPTURE_DETECTED`, `PRINTED_COPY_DETECTED`, `PORTRAIT_MANIPULATION_DETECTED`, `PUBLIC_DOCUMENT_IMAGE_DETECTED`, plus extraction failures (`NAME_NOT_DETECTED`, `DATE_OF_BIRTH_NOT_DETECTED`, `DOCUMENT_NUMBER_NOT_DETECTED`). (`PORTRAIT_IMAGE_NOT_DETECTED`, `INVALID_DATE`, and `MRZ_NOT_DETECTED` exist in workflow sessions but are never produced by this standalone endpoint. `COULD_NOT_DETECT_DOCUMENT_TYPE` is returned here when the front image does not match a known official document design with high confidence, and declines the request.) Three risk groups are configurable per request via `invalid_mrz_action`, `inconsistent_data_action`, and `expiration_date_not_detected_action` (`DECLINE` or `NO_ACTION`). All other warnings (e.g. `POSSIBLE_DUPLICATED_USER`) are informational and never decline on their own. When `perform_document_liveness=true` (the default) and the forgery check returns no usable score — the provider failed, answered non-200, or answered HTTP 200 with `status=\"ERR\"` - the call returns `200` with `status: \"In Review\"` and a `DOCUMENT_LIVENESS_NOT_COMPUTED` warning (`log_type: \"warning\"`) instead of `Approved`: a check that did not run is not evidence that the document is genuine. This escalation is not configurable and never overrides a `Declined`; with `perform_document_liveness=false` the check is skipped and the status follows the remaining warnings as before. A document that cannot be processed at all returns `400` with `{\"error\": \"COULD_NOT_RECOGNIZE_DOCUMENT\"}`; a readable but problematic document returns `200` with `status: \"Declined\"` — always inspect `id_verification.status` and `id_verification.warnings`, not just the HTTP code.\n\n**Billing.** Each `200` response consumes one ID Verification API credit (paid from your plan credit or top-up balance). When the organization's balance cannot cover the call, the endpoint returns `403` with the not-enough-credits error before any image processing.\n\n**Session persistence (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session: it appears in the Business Console, the returned `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/`, the cropped document/portrait images are stored and returned as short-lived media URLs (`https:///ocr/...`), and a `status.updated` webhook is emitted to your configured webhook endpoints. When `false`, nothing is stored, `request_id` is a one-off correlation UUID, and `portrait_image`/`front_image`/`back_image` are returned inline as base64-encoded JPEG strings.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload (a fictional \"Sandbox User\" USA identity card), no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3id-verification", + "x-didit-sdk": { + "resource": "id_verification", + "method": "verify", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "front_image" + ], + "properties": { + "front_image": { + "type": "string", + "format": "binary", + "description": "Front side of the identity document. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp`, `pdf`. Maximum upload size: **10 MB** (larger files are rejected with `400`). PDFs are rendered to an image before OCR; encrypted PDFs require `front_image_password`. Images are automatically compressed to ~0.5 MB and EXIF orientation is applied. Upload the full, uncropped capture with all four corners of the document visible." + }, + "front_image_password": { + "type": "string", + "description": "Password to decrypt `front_image` when it is an encrypted PDF. Sending an encrypted PDF without (or with a wrong) password returns `400`.", + "writeOnly": true + }, + "back_image": { + "type": "string", + "format": "binary", + "description": "Back side of the document — send it whenever the document has one (many ID cards carry the MRZ or a barcode on the back; omitting it simply leaves the MRZ and back-side fields empty in the response — this endpoint does not raise a missing-MRZ warning). Same format and size limits as `front_image`. When omitted, `back_image` and back-side fields are `null` in the response." + }, + "back_image_password": { + "type": "string", + "description": "Password to decrypt `back_image` when it is an encrypted PDF.", + "writeOnly": true + }, + "perform_document_liveness": { + "type": "boolean", + "default": true, + "description": "Screens both images for presentation fraud: screen replays (`SCREEN_CAPTURE_DETECTED`), printed copies (`PRINTED_COPY_DETECTED`), and portrait manipulation (`PORTRAIT_MANIPULATION_DETECTED`). Any of these auto-declines. Enabled by default; set to `false` to skip fraud screening and reduce latency. When enabled and the provider returns no usable score, the response is `In Review` with a `DOCUMENT_LIVENESS_NOT_COMPUTED` warning rather than `Approved`.", + "example": true + }, + "minimum_age": { + "type": "integer", + "nullable": true, + "default": null, + "minimum": 1, + "maximum": 120, + "description": "Accepted and validated (1–120) but **currently not applied** by this endpoint — it never produces a `MINIMUM_AGE_NOT_MET` warning from this field. Enforce age rules from the returned `date_of_birth`/`age`, or use a verification-session workflow with age restrictions." + }, + "expiration_date_not_detected_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when no expiration date can be read (`EXPIRATION_DATE_NOT_DETECTED`). Default `NO_ACTION` because many identity documents print no expiry date. Note: a *detected and past* expiry date always declines (`DOCUMENT_EXPIRED`), regardless of this option." + }, + "invalid_mrz_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "DECLINE", + "description": "What to do when the extracted MRZ fails check-digit validation (`MRZ_VALIDATION_FAILED`). A missing MRZ raises no warning on this endpoint. Only relevant for documents that carry an MRZ. `REVIEW` is not supported on this endpoint and returns `400`." + }, + "inconsistent_data_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "DECLINE", + "description": "What to do when extracted data is internally inconsistent: `DATA_INCONSISTENT`, `MRZ_AND_DATA_EXTRACTED_FROM_OCR_NOT_SAME` (visual zone disagrees with the MRZ), or `DOCUMENT_NAME_DIFFERENT_FROM_OTHER_APPROVED_DOCUMENTS`." + }, + "critical_field_occlusion_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "DECLINE", + "description": "What to do when a critical printed field was physically covered on the capture (`CRITICAL_FIELD_OCCLUSION_DETECTED`) - an identity number, a name, the date of birth, or the expiry date. Declines by default, like this endpoint's other document-integrity risks: covering the numbers a report is reconciled against is what a redacted document looks like. The value read for a covered field is dropped from the extraction either way, so the matching `*_NOT_DETECTED` warning usually accompanies it. `REVIEW` is not supported on this endpoint and returns `400`." + }, + "document_or_personal_number_format_mismatch_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when a number does not match the format its issuing authority uses for that document type (`DOCUMENT_NUMBER_FORMAT_MISMATCH`, `PERSONAL_NUMBER_FORMAT_MISMATCH`). Default `NO_ACTION`, which reports the warning without changing the status. `REVIEW` is not supported on this endpoint and returns `400`." + }, + "preferred_characters": { + "type": "string", + "enum": [ + "latin", + "non_latin" + ], + "default": "latin", + "description": "Preferred script for name/address fields on documents that print both Latin and non-Latin text (Arabic, Cyrillic, CJK, …). `latin` returns transliterated/Latin values; `non_latin` prefers the native script." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, announced through a `status.updated` webhook, and with document images returned as media URLs. When `false`, nothing is stored, `request_id` is a transient UUID, and images are returned inline as base64 JPEG strings.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"onboarding\"}`) — it is parsed into an object.", + "example": { + "flow": "onboarding" + } + } + } + }, + "example": { + "front_image": "(binary JPEG/PNG/PDF of the document front)", + "back_image": "(binary JPEG/PNG/PDF of the document back)", + "perform_document_liveness": true, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "onboarding" + } + } + } + } + }, + "responses": { + "200": { + "description": "Document processed. `id_verification.status` is `Approved`, `Declined`, or `In Review`; every detected issue is itemized in `id_verification.warnings`. `In Review` is returned when a requested document-liveness check produced no usable result (`DOCUMENT_LIVENESS_NOT_COMPUTED`). A problematic document still returns `200` with `status: \"Declined\"` — inspect the body, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id and image fields are short-lived media URLs; with `save_api_request=false` they are inline base64 JPEG strings.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Two-sided ID card read cleanly (saved — images as media URLs)", + "value": { + "request_id": "11d219ed-d59c-4b1d-8d65-9b933f12d5b8", + "id_verification": { + "status": "Approved", + "document_type": "Identity Card", + "document_subtype": "ID_CARD_GENERIC", + "document_number": "CBX164224", + "personal_number": "20446581H", + "portrait_image": "https:///ocr/11d219ed-d59c-4b1d-8d65-9b933f12d5b8-portrait_image-8c2f.jpg?signature=...", + "front_image": "https:///ocr/11d219ed-d59c-4b1d-8d65-9b933f12d5b8-front_image-4a1e.jpg?signature=...", + "back_image": "https:///ocr/11d219ed-d59c-4b1d-8d65-9b933f12d5b8-back_image-77b0.jpg?signature=...", + "front_image_camera_front": null, + "back_image_camera_front": null, + "front_image_camera_front_face_match_score": null, + "back_image_camera_front_face_match_score": null, + "front_image_quality_score": { + "focus_score": 78.0, + "brightness_score": 83.2, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 40.7, + "overall_score": 70.2 + }, + "back_image_quality_score": { + "focus_score": 100.0, + "brightness_score": 95.6, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 43.0, + "overall_score": 84.4 + }, + "date_of_birth": "1980-01-13", + "age": 46, + "expiration_date": "2032-03-31", + "date_of_issue": "2022-03-31", + "issuing_state": "ESP", + "issuing_state_name": "Spain", + "first_name": "Julio Francisco", + "last_name": "Fores Sena", + "full_name": "Julio Francisco Fores Sena", + "gender": "M", + "address": "Brda. Urb. El Cardonal 0052 52 Po3 B,Taco,San Cristobal De La Laguna,Santa Cruz De Tenerife", + "formatted_address": "Av. el Cardonal, 52, b, 38108 La Laguna, Santa Cruz de Tenerife, Spain", + "place_of_birth": "Valencia, Valencia", + "marital_status": "UNKNOWN", + "nationality": "ESP", + "extra_fields": { + "first_surname": "Fores", + "second_surname": "Sena" + }, + "mrz": { + "surname": "FORES SENA", + "name": "JULIO FRANCISCO", + "country": "ESP", + "nationality": "ESP", + "birth_date": "800113", + "expiry_date": "320331", + "sex": "M", + "document_type": "ID", + "document_number": "CBX164224", + "optional_data": "20446581H", + "optional_data_2": "", + "birth_date_hash": "9", + "expiry_date_hash": "8", + "document_number_hash": "3", + "final_hash": "3", + "personal_number": "20446581H", + "warnings": [], + "errors": [], + "mrz_type": "TD1", + "mrz_string": "IDESPCBX164224320446581H<<<<<<\n8001139M3203318ESP<<<<<<<<<<<3\nFORES/ocr/f61681b8-9422-4378-ac3e-04c6a247fb45-portrait_image-1d9a.jpg?signature=...", + "front_image": "https:///ocr/f61681b8-9422-4378-ac3e-04c6a247fb45-front_image-5c2b.jpg?signature=...", + "back_image": null, + "front_image_camera_front": null, + "back_image_camera_front": null, + "front_image_camera_front_face_match_score": null, + "back_image_camera_front_face_match_score": null, + "front_image_quality_score": { + "focus_score": 100.0, + "brightness_score": 95.7, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 22.2, + "overall_score": 79.3 + }, + "back_image_quality_score": null, + "date_of_birth": "1997-01-30", + "age": 29, + "expiration_date": "2025-11-26", + "date_of_issue": null, + "issuing_state": "ESP", + "issuing_state_name": "Spain", + "first_name": "Lucia", + "last_name": "Marin Castro", + "full_name": "Lucia Marin Castro", + "gender": "F", + "address": null, + "formatted_address": null, + "place_of_birth": null, + "marital_status": "UNKNOWN", + "nationality": "ESP", + "extra_fields": { + "first_surname": "Marin", + "second_surname": "Castro" + }, + "mrz": {}, + "parsed_address": null, + "warnings": [ + { + "risk": "DOCUMENT_EXPIRED", + "feature": "ID_VERIFICATION", + "additional_data": null, + "log_type": "error", + "short_description": "Document expired", + "long_description": "The document's expiration date has passed, rendering it no longer valid for use." + } + ], + "barcodes": [] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:25:48.117204+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "id_verification": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "description": "`Approved` when no warning resolves to a decline and the requested document-liveness check produced a usable result; `In Review` when that requested check produced no usable result (`DOCUMENT_LIVENESS_NOT_COMPUTED`); `Declined` when a warning resolves to a decline — the `warnings` list explains why." + }, + "document_type": { + "type": "string", + "nullable": true, + "description": "Detected document type display name: `Identity Card`, `Passport`, `Driver's License`, `Residence Permit`, `Health Insurance Card`, or `Tax Card`. `null` when the document could not be classified.", + "example": "Identity Card" + }, + "document_subtype": { + "type": "string", + "nullable": true, + "description": "Finer-grained subtype when available (e.g. `ID_CARD_GENERIC`, `EPASSPORT`).", + "example": "ID_CARD_GENERIC" + }, + "document_number": { + "type": "string", + "nullable": true, + "example": "CBX164224" + }, + "personal_number": { + "type": "string", + "nullable": true, + "description": "Secondary personal/national number printed on some documents.", + "example": "20446581H" + }, + "portrait_image": { + "type": "string", + "nullable": true, + "description": "Cropped portrait photo from the document. A short-lived media URL (`https:///ocr/...`) when `save_api_request=true` (default); an inline base64-encoded JPEG string when `save_api_request=false`. `null` when no portrait was detected." + }, + "front_image": { + "type": "string", + "nullable": true, + "description": "Aligned crop of the document front. Media URL when saved, inline base64 JPEG otherwise." + }, + "back_image": { + "type": "string", + "nullable": true, + "description": "Aligned crop of the document back. `null` when no `back_image` was uploaded." + }, + "front_image_camera_front": { + "type": "string", + "nullable": true, + "description": "Always `null` on this endpoint (populated only in camera-capture verification sessions)." + }, + "back_image_camera_front": { + "type": "string", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "front_image_camera_front_face_match_score": { + "type": "number", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "back_image_camera_front_face_match_score": { + "type": "number", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "front_image_quality_score": { + "type": "object", + "nullable": true, + "description": "Capture-quality diagnostics for the uploaded image (`null` when the side was not provided or scoring failed). All scores are 0–100.", + "properties": { + "focus_score": { + "type": "number", + "format": "float", + "description": "Sharpness of the document crop (higher is sharper)." + }, + "brightness_score": { + "type": "number", + "format": "float", + "description": "Exposure quality (higher is better)." + }, + "brightness_issue": { + "type": "string", + "nullable": true, + "description": "`ok` when exposure is fine, otherwise the detected issue (e.g. too dark / too bright)." + }, + "is_document_fully_visible": { + "type": "boolean", + "nullable": true, + "description": "Whether all four corners of the document are inside the frame." + }, + "resolution_score": { + "type": "number", + "format": "float", + "description": "Effective resolution of the document area." + }, + "overall_score": { + "type": "number", + "format": "float", + "description": "Aggregate quality score." + } + } + }, + "back_image_quality_score": { + "type": "object", + "nullable": true, + "description": "Capture-quality diagnostics for the uploaded image (`null` when the side was not provided or scoring failed). All scores are 0–100.", + "properties": { + "focus_score": { + "type": "number", + "format": "float", + "description": "Sharpness of the document crop (higher is sharper)." + }, + "brightness_score": { + "type": "number", + "format": "float", + "description": "Exposure quality (higher is better)." + }, + "brightness_issue": { + "type": "string", + "nullable": true, + "description": "`ok` when exposure is fine, otherwise the detected issue (e.g. too dark / too bright)." + }, + "is_document_fully_visible": { + "type": "boolean", + "nullable": true, + "description": "Whether all four corners of the document are inside the frame." + }, + "resolution_score": { + "type": "number", + "format": "float", + "description": "Effective resolution of the document area." + }, + "overall_score": { + "type": "number", + "format": "float", + "description": "Aggregate quality score." + } + } + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "example": "1980-01-13" + }, + "age": { + "type": "integer", + "nullable": true, + "description": "Holder age in years, computed from `date_of_birth`.", + "example": 46 + }, + "expiration_date": { + "type": "string", + "format": "date", + "nullable": true, + "example": "2032-03-31" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "nullable": true, + "example": "2022-03-31" + }, + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Issuing country (ISO 3166-1 alpha-3).", + "example": "ESP" + }, + "issuing_state_name": { + "type": "string", + "nullable": true, + "example": "Spain" + }, + "first_name": { + "type": "string", + "nullable": true, + "example": "Julio Francisco" + }, + "last_name": { + "type": "string", + "nullable": true, + "example": "Fores Sena" + }, + "full_name": { + "type": "string", + "nullable": true, + "example": "Julio Francisco Fores Sena" + }, + "gender": { + "type": "string", + "nullable": true, + "description": "`M`, `F`, or `null` when not printed/detected.", + "example": "M" + }, + "address": { + "type": "string", + "nullable": true, + "description": "Address exactly as printed on the document." + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "Geocoded, normalized version of `address`." + }, + "place_of_birth": { + "type": "string", + "nullable": true, + "example": "Valencia, Valencia" + }, + "marital_status": { + "type": "string", + "nullable": true, + "description": "`UNKNOWN` unless the document prints marital status.", + "example": "UNKNOWN" + }, + "nationality": { + "type": "string", + "nullable": true, + "description": "Holder nationality (ISO 3166-1 alpha-3).", + "example": "ESP" + }, + "extra_fields": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Document-specific extras that have no dedicated column (e.g. `first_surname`/`second_surname` on Spanish IDs, license classes on driving licenses)." + }, + "mrz": { + "type": "object", + "description": "Parsed machine-readable zone. An **empty object** (`{}`) when the document has no MRZ or it could not be read — also check the `warnings` list in that case. Dates inside the MRZ use the raw `YYMMDD` format.", + "properties": { + "surname": { + "type": "string", + "example": "FORES SENA" + }, + "name": { + "type": "string", + "example": "JULIO FRANCISCO" + }, + "country": { + "type": "string", + "description": "Issuing country (ISO 3166-1 alpha-3).", + "example": "ESP" + }, + "nationality": { + "type": "string", + "description": "Holder nationality (ISO 3166-1 alpha-3).", + "example": "ESP" + }, + "birth_date": { + "type": "string", + "description": "Raw MRZ date of birth (`YYMMDD`).", + "example": "800113" + }, + "expiry_date": { + "type": "string", + "description": "Raw MRZ expiry date (`YYMMDD`).", + "example": "320331" + }, + "sex": { + "type": "string", + "example": "M" + }, + "document_type": { + "type": "string", + "description": "MRZ document-type code (e.g. `ID`, `P`).", + "example": "ID" + }, + "document_number": { + "type": "string", + "example": "CBX164224" + }, + "optional_data": { + "type": "string", + "nullable": true, + "example": "20446581H" + }, + "optional_data_2": { + "type": "string", + "nullable": true, + "example": "" + }, + "birth_date_hash": { + "type": "string", + "description": "MRZ check digit for the birth date.", + "example": "9" + }, + "expiry_date_hash": { + "type": "string", + "description": "MRZ check digit for the expiry date.", + "example": "8" + }, + "document_number_hash": { + "type": "string", + "description": "MRZ check digit for the document number.", + "example": "3" + }, + "final_hash": { + "type": "string", + "description": "Composite MRZ check digit.", + "example": "3" + }, + "personal_number": { + "type": "string", + "nullable": true, + "example": "20446581H" + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Non-fatal MRZ parsing warnings." + }, + "errors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "MRZ check-digit/parsing errors. Non-empty errors surface as an `MRZ_VALIDATION_FAILED` warning." + }, + "mrz_type": { + "type": "string", + "description": "MRZ layout: `TD1` (ID cards, 3 lines), `TD2`, or `TD3` (passports, 2 lines).", + "example": "TD1" + }, + "mrz_string": { + "type": "string", + "description": "Raw MRZ text as read, lines separated by `\\n`." + }, + "mrz_key": { + "type": "string", + "description": "Composite key (document number + check digits) used for cross-checks." + } + } + }, + "parsed_address": { + "type": "object", + "nullable": true, + "description": "Structured, geocoded breakdown of `address`. `null` when the document carries no address or it could not be parsed.", + "properties": { + "street_1": { + "type": "string", + "nullable": true, + "example": "Avenida el Cardonal 52" + }, + "street_2": { + "type": "string", + "nullable": true, + "example": "b" + }, + "city": { + "type": "string", + "nullable": true, + "example": "La Laguna" + }, + "region": { + "type": "string", + "nullable": true, + "example": "Canarias" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code.", + "example": "ES" + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "38108" + }, + "address_type": { + "type": "string", + "nullable": true, + "example": "Avenida" + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "Canonical geocoded address string.", + "example": "Av. el Cardonal, 52, b, 38108 La Laguna, Santa Cruz de Tenerife, Spain" + }, + "raw_results": { + "type": "object", + "additionalProperties": true, + "description": "Raw geocoding payload (`address_components`, `geometry.location`, `place_id`, …) for advanced consumers." + }, + "document_location": { + "type": "object", + "nullable": true, + "properties": { + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + } + }, + "description": "Geocoded coordinates of the document address." + }, + "label": { + "type": "string", + "nullable": true, + "description": "Which document field the address was read from.", + "example": "Spain Identity Card Address" + }, + "is_verified": { + "type": "boolean", + "description": "Whether geocoding confirmed the address exists." + }, + "category": { + "type": "string", + "nullable": true, + "description": "Address category (e.g. `Residential`, `Commercial`).", + "example": "Residential" + } + } + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. Every entry explains one detected risk; entries with `log_type: \"error\"` set `status` to `Declined`, and `DOCUMENT_LIVENESS_NOT_COMPUTED` (`log_type: \"warning\"`) sets it to `In Review`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "description": "Machine-readable risk code. Always-decline risks: `DOCUMENT_EXPIRED`, `MINIMUM_AGE_NOT_MET`, `PORTRAIT_IMAGE_NOT_DETECTED`, `SCREEN_CAPTURE_DETECTED`, `PRINTED_COPY_DETECTED`, `PORTRAIT_MANIPULATION_DETECTED`, `PUBLIC_DOCUMENT_IMAGE_DETECTED`, `INVALID_DATE`, `COULD_NOT_RECOGNIZE_DOCUMENT` (400-only, never a warning), `DOCUMENT_NUMBER_NOT_DETECTED`, `COULD_NOT_DETECT_DOCUMENT_TYPE`, `NAME_NOT_DETECTED`, `DATE_OF_BIRTH_NOT_DETECTED`. Governed by request options: `MRZ_NOT_DETECTED` / `MRZ_VALIDATION_FAILED` (`invalid_mrz_action`), `DATA_INCONSISTENT` / `MRZ_AND_DATA_EXTRACTED_FROM_OCR_NOT_SAME` / `DOCUMENT_NAME_DIFFERENT_FROM_OTHER_APPROVED_DOCUMENTS` (`inconsistent_data_action`), `EXPIRATION_DATE_NOT_DETECTED` (`expiration_date_not_detected_action`). Other risks (e.g. `POSSIBLE_DUPLICATED_USER`) are informational. `DOCUMENT_LIVENESS_NOT_COMPUTED` is raised when `perform_document_liveness=true` but the forgery check returned no usable score; it sets `status` to `In Review`. `COULD_NOT_DETECT_DOCUMENT_TYPE` is also returned when the front of the document does not match a known official document design with high confidence, for example an edited or computer-generated document, or a design that is not supported yet.", + "example": "DOCUMENT_EXPIRED" + }, + "feature": { + "type": "string", + "enum": [ + "ID_VERIFICATION" + ], + "description": "Feature that raised the warning. Always `ID_VERIFICATION` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Extra context for some risks (e.g. `POSSIBLE_DUPLICATED_USER` includes `duplicated_session_id`); `null` for most." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `warning` (only `DOCUMENT_LIVENESS_NOT_COMPUTED`, when the requested forgery check produced no usable score) drives `status` to `In Review`; `information` entries are advisory and never change the status." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "barcodes": { + "type": "array", + "description": "Barcodes decoded from the document (PDF417, QR, …). Empty when the document has none or none could be read.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Barcode type (e.g. `PDF417`, `QR_CODE`, `UNKNOWN`)." + }, + "position": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Location of the barcode in the image, when available." + }, + "data": { + "type": "string", + "description": "Decoded payload." + }, + "data_raw": { + "type": "string", + "description": "Raw (undecoded) payload, when different." + }, + "side": { + "type": "string", + "nullable": true, + "description": "`front` or `back`." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated." + } + } + } + } + } + }, + "400": { + "description": "Validation or processing error. Field-level problems return DRF's standard envelope (one array of messages per offending field). Encrypted-PDF problems return `{\"detail\": ...}`. A document image in which no document can be recognized at all returns `{\"error\": \"COULD_NOT_RECOGNIZE_DOCUMENT\"}`. PDF uploads can additionally fail with \"The submitted PDF file is corrupted or invalid.\" or a no-pages error.", + "content": { + "application/json": { + "examples": { + "Missing front image": { + "summary": "`front_image` not included in the form data", + "value": { + "front_image": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp/pdf", + "value": { + "front_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp, pdf." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 10 MB limit", + "value": { + "front_image": [ + "File size should not exceed 10 MB" + ] + } + }, + "Invalid option value": { + "summary": "Options only accept DECLINE or NO_ACTION", + "value": { + "invalid_mrz_action": [ + "\"REVIEW\" is not a valid choice." + ] + } + }, + "Encrypted PDF": { + "summary": "PDF uploaded without the required password", + "value": { + "detail": "The PDF is encrypted. Please upload a decrypted PDF or a photo instead." + } + }, + "Wrong PDF password": { + "summary": "`front_image_password` / `back_image_password` does not decrypt the PDF", + "value": { + "detail": "The PDF password is incorrect. Please provide the correct password." + } + }, + "Document not recognized": { + "summary": "No identity document could be detected in the image", + "value": { + "error": "COULD_NOT_RECOGNIZE_DOCUMENT" + } + }, + "PDF conversion failure": { + "summary": "Uploaded PDF could not be converted to an image", + "value": { + "error": "Failed to convert front image PDF." + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/id-verification/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'front_image=@./id_front.jpg' \\\n -F 'back_image=@./id_back.jpg' \\\n -F 'perform_document_liveness=true' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_verification.verify(\n front_image=open(\"front_image.jpg\", \"rb\"),\n back_image=open(\"back_image.jpg\", \"rb\"),\n perform_document_liveness=True,\n save_api_request=True,\n vendor_data=\"user-123\",\n metadata={\"flow\": \"onboarding\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idVerification.verify({\n front_image: await fileFromPath(\"front_image.jpg\"),\n back_image: await fileFromPath(\"back_image.jpg\"),\n perform_document_liveness: true,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"onboarding\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDVerification.Verify(ctx, didit.IDVerificationVerifyParams{\n\tFrontImage: didit.FileFromPath(\"front_image.jpg\"),\n\tBackImage: didit.Some(didit.FileFromPath(\"back_image.jpg\")),\n\tPerformDocumentLiveness: didit.Bool(true),\n\tSaveAPIRequest: didit.Bool(true),\n\tVendorData: didit.String(\"user-123\"),\n\tMetadata: didit.Some(map[string]any{\"flow\": \"onboarding\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.IdVerificationVerifyParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idVerification().verify(IdVerificationVerifyParams.builder()\n .frontImage(Path.of(\"front_image.jpg\"))\n .backImage(Path.of(\"back_image.jpg\"))\n .performDocumentLiveness(true)\n .saveApiRequest(true)\n .vendorData(\"user-123\")\n .metadata(Map.of(\"flow\", \"onboarding\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idVerification->verify([\n 'front_image' => File::fromPath('front_image.jpg'),\n 'back_image' => File::fromPath('back_image.jpg'),\n 'perform_document_liveness' => true,\n 'save_api_request' => true,\n 'vendor_data' => 'user-123',\n 'metadata' => ['flow' => 'onboarding'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_verification.verify(\n front_image: Pathname(\"front_image.jpg\"),\n back_image: Pathname(\"back_image.jpg\"),\n perform_document_liveness: true,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"onboarding\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdVerification.VerifyAsync(new IdVerificationVerifyParams\n{\n FrontImage = FileUpload.FromPath(\"front_image.jpg\"),\n BackImage = FileUpload.FromPath(\"back_image.jpg\"),\n PerformDocumentLiveness = true,\n SaveApiRequest = true,\n VendorData = \"user-123\",\n Metadata = new Dictionary { [\"flow\"] = \"onboarding\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/face-match/": { + "post": { + "summary": "Face Match", + "description": "Compare two face images (1:1) and get back a similarity `score` (0–100) plus an `Approved`/`Declined` `status`.\n\n**How it works.** Both images are decoded and analyzed by a biometric model (set `rotate_image=true` if captures may be sideways or upside down), then a similarity score between the two faces is computed. A score **strictly above** `face_match_score_decline_threshold` (default `30`) returns `status: \"Approved\"`; a score at or below it returns `Declined` with a `LOW_FACE_MATCH_SIMILARITY` warning. If no face pair can be scored at all, the call still returns `200` with `status: \"Declined\"` and a `NO_REFERENCE_IMAGE` warning — `score` is `null` on the default save path, or `0` with `save_api_request=false`. Submit one clear, front-facing face per image for best results.\n\n**Billing.** Each `200` response consumes one Face Match API credit (paid from your plan credit or top-up balance). When the organization's balance cannot cover the call, the endpoint returns `403` with the not-enough-credits error before any image processing.\n\n**Session persistence (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session: it appears in the Business Console, the returned `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/`, both images are stored as the session's face-match source/target images, and a `status.updated` webhook is emitted to your configured webhook endpoints. When `false`, nothing is stored and `request_id` is a one-off correlation UUID that cannot be looked up later.\n\n**Face search index.** This endpoint never enrolls faces into your face search index and never runs blocklist or duplicate screening — use `POST /v3/passive-liveness/` or `POST /v3/face-search/` for those.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload, no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3face-match", + "x-didit-sdk": { + "resource": "face_match", + "method": "compare", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "user_image", + "ref_image" + ], + "properties": { + "user_image": { + "type": "string", + "format": "binary", + "description": "Live or candidate face image to verify (e.g. a selfie). Submit a single front-facing photo with the subject's face clearly visible and well lit. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp`. Maximum upload size: **5 MB** (larger files are rejected with `400`). Images are automatically compressed to ~0.5 MB before processing, so very high resolutions do not improve accuracy." + }, + "ref_image": { + "type": "string", + "format": "binary", + "description": "Reference face image to compare against (e.g. an ID document portrait or a previously enrolled photo). Same format and size limits as `user_image`." + }, + "face_match_score_decline_threshold": { + "type": "number", + "format": "float", + "default": 30, + "minimum": 0, + "maximum": 100, + "description": "Similarity threshold (0–100) that drives the response `status`. A computed score **at or below** this value sets `status` to `Declined` and adds a `LOW_FACE_MATCH_SIMILARITY` warning; a score strictly above it returns `Approved`. Default `30` is permissive — raise it (e.g. `50`–`60`) for stricter verification. Values outside 0–100 return `400`.", + "example": 50 + }, + "rotate_image": { + "type": "boolean", + "default": false, + "description": "When `true`, the service tries the input images in 90-degree increments (0, 90, 180, 270) and uses the orientation that yields the best face detection — useful for mobile captures with missing EXIF orientation. Adds latency, so only enable it if you cannot guarantee upright images.", + "example": false + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, and announced through a `status.updated` webhook. When `false`, nothing is stored and `request_id` is a transient UUID for response correlation only.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"login\"}`) — it is parsed into an object.", + "example": { + "flow": "login", + "device_id": "abc123" + } + } + } + }, + "example": { + "user_image": "(binary JPEG/PNG of the live selfie)", + "ref_image": "(binary JPEG/PNG of the reference photo)", + "face_match_score_decline_threshold": 50, + "rotate_image": false, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "login" + } + } + } + } + }, + "responses": { + "200": { + "description": "Face match computed. `face_match.score` is a 0–100 float (two decimals); `status` is `Approved` when the score is strictly above `face_match_score_decline_threshold`, otherwise `Declined` with an explanatory warning. A low similarity or unscorable pair still returns `200` — inspect `face_match.status` and `face_match.warnings`, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Same person — score above the threshold", + "value": { + "request_id": "f9f5a777-8002-44c6-baaf-128bcfd6e226", + "face_match": { + "status": "Approved", + "score": 99.41, + "user_image": { + "entities": [ + { + "bbox": [ + 661, + 728, + 1688, + 2188 + ], + "confidence": 0.732973, + "age": 26.91, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "ref_image": { + "entities": [ + { + "bbox": [ + 653, + 745, + 1691, + 2185 + ], + "confidence": 0.722082, + "age": 27.0, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": { + "flow": "login" + }, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "Declined - low similarity": { + "summary": "Score at or below the decline threshold", + "value": { + "request_id": "7cdda24e-311a-46d3-a3bb-e5d9b564ce73", + "face_match": { + "status": "Declined", + "score": 24.87, + "user_image": { + "entities": [ + { + "bbox": [ + 661, + 728, + 1688, + 2188 + ], + "confidence": 0.732973, + "age": 26.91, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "ref_image": { + "entities": [ + { + "bbox": [ + 156, + 234, + 679, + 898 + ], + "confidence": 0.717775, + "age": 41.2, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "LOW_FACE_MATCH_SIMILARITY", + "feature": "FACEMATCH", + "additional_data": null, + "log_type": "error", + "short_description": "Low face match similarity", + "long_description": "The facial features of the provided image don't closely match the reference image, suggesting a potential identity mismatch." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:08:19.859116+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "face_match": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Approved` when the similarity score is strictly above `face_match_score_decline_threshold` and a score could be computed; `Declined` otherwise (a warning explains why)." + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Similarity score from 0 to 100 — the model's confidence that both images depict the same person. Rounded to two decimals only when `save_api_request` is true; with `save_api_request=false` the raw float is returned. `null` when no face pair could be scored (with the default `save_api_request=true`); with `save_api_request=false` the same condition can surface as `score: 0`. Either way a `NO_REFERENCE_IMAGE` warning is present and `status` is `Declined`.", + "example": 99.41 + }, + "user_image": { + "type": "object", + "description": "Face-detection results for the analyzed images.", + "properties": { + "entities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box of the detected face as `[x_min, y_min, x_max, y_max]` pixel coordinates in the processed image.", + "example": [ + 661, + 728, + 1688, + 2188 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.732973 + }, + "age": { + "type": "number", + "format": "float", + "description": "Model-estimated age of the detected face, in years. Informational only — it does not affect the face-match decision.", + "example": 26.91 + }, + "gender": { + "type": "string", + "description": "Model-predicted gender of the detected face (`male` or `female`). Informational only.", + "example": "male" + }, + "race": { + "type": "string", + "nullable": true, + "description": "Reserved field — always `null` in responses from this endpoint.", + "example": null + } + } + }, + "description": "One entry per detected face." + }, + "best_angle": { + "type": "integer", + "description": "Rotation (degrees: 0, 90, 180, or 270) that produced the best face detection. Only non-zero when `rotate_image=true` corrected the orientation.", + "example": 0 + } + } + }, + "ref_image": { + "type": "object", + "description": "Face-detection results for the reference image, same shape as `user_image`.", + "properties": { + "entities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box of the detected face as `[x_min, y_min, x_max, y_max]` pixel coordinates in the processed image.", + "example": [ + 661, + 728, + 1688, + 2188 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.732973 + }, + "age": { + "type": "number", + "format": "float", + "description": "Model-estimated age of the detected face, in years. Informational only — it does not affect the face-match decision.", + "example": 26.91 + }, + "gender": { + "type": "string", + "description": "Model-predicted gender of the detected face (`male` or `female`). Informational only.", + "example": "male" + }, + "race": { + "type": "string", + "nullable": true, + "description": "Reserved field — always `null` in responses from this endpoint.", + "example": null + } + } + }, + "description": "One entry per detected face." + }, + "best_angle": { + "type": "integer", + "example": 0 + } + } + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. `NO_REFERENCE_IMAGE` (`error`) — no face pair could be scored; `LOW_FACE_MATCH_SIMILARITY` (`error`) — score at or below the decline threshold. Any of these sets `status` to `Declined`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "NO_REFERENCE_IMAGE", + "LOW_FACE_MATCH_SIMILARITY" + ], + "description": "Machine-readable risk code." + }, + "feature": { + "type": "string", + "enum": [ + "FACEMATCH" + ], + "description": "Feature that raised the warning. Always `FACEMATCH` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Always `null` for face-match warnings." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `warning` and `information` entries are advisory and never decline on their own." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated, e.g. `2026-06-12T01:04:42.763237+00:00`." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when a required image is missing, exceeds 5 MB, has an unsupported extension, or when an option is out of range. The body is DRF's standard field-error envelope: one array of messages per offending field.", + "content": { + "application/json": { + "examples": { + "Missing required image": { + "summary": "`ref_image` (or `user_image`) not included in the form data", + "value": { + "ref_image": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp", + "value": { + "user_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 5 MB limit", + "value": { + "user_image": [ + "File size should not exceed 5 MB" + ] + } + }, + "Threshold out of range": { + "summary": "`face_match_score_decline_threshold` outside 0–100", + "value": { + "face_match_score_decline_threshold": [ + "Ensure this value is less than or equal to 100." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/face-match/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'user_image=@./selfie.jpg' \\\n -F 'ref_image=@./id_portrait.jpg' \\\n -F 'face_match_score_decline_threshold=50' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.face_match.compare(\n user_image=open(\"user_image.jpg\", \"rb\"),\n ref_image=open(\"ref_image.jpg\", \"rb\"),\n face_match_score_decline_threshold=50.0,\n rotate_image=False,\n save_api_request=True,\n vendor_data=\"user-123\",\n metadata={\"flow\": \"login\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.faceMatch.compare({\n user_image: await fileFromPath(\"user_image.jpg\"),\n ref_image: await fileFromPath(\"ref_image.jpg\"),\n face_match_score_decline_threshold: 50.0,\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"login\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.FaceMatch.Compare(ctx, didit.FaceMatchCompareParams{\n\tUserImage: didit.FileFromPath(\"user_image.jpg\"),\n\tRefImage: didit.FileFromPath(\"ref_image.jpg\"),\n\tFaceMatchScoreDeclineThreshold: didit.Float(50.0),\n\tRotateImage: didit.Bool(false),\n\tSaveAPIRequest: didit.Bool(true),\n\tVendorData: didit.String(\"user-123\"),\n\tMetadata: didit.Some(map[string]any{\"flow\": \"login\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.FaceMatchCompareParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.faceMatch().compare(FaceMatchCompareParams.builder()\n .userImage(Path.of(\"user_image.jpg\"))\n .refImage(Path.of(\"ref_image.jpg\"))\n .faceMatchScoreDeclineThreshold(50.0)\n .rotateImage(false)\n .saveApiRequest(true)\n .vendorData(\"user-123\")\n .metadata(Map.of(\"flow\", \"login\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "faceMatch->compare([\n 'user_image' => File::fromPath('user_image.jpg'),\n 'ref_image' => File::fromPath('ref_image.jpg'),\n 'face_match_score_decline_threshold' => 50.0,\n 'rotate_image' => false,\n 'save_api_request' => true,\n 'vendor_data' => 'user-123',\n 'metadata' => ['flow' => 'login'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.face_match.compare(\n user_image: Pathname(\"user_image.jpg\"),\n ref_image: Pathname(\"ref_image.jpg\"),\n face_match_score_decline_threshold: 50.0,\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"login\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.FaceMatch.CompareAsync(new FaceMatchCompareParams\n{\n UserImage = FileUpload.FromPath(\"user_image.jpg\"),\n RefImage = FileUpload.FromPath(\"ref_image.jpg\"),\n FaceMatchScoreDeclineThreshold = 50.0,\n RotateImage = false,\n SaveApiRequest = true,\n VendorData = \"user-123\",\n Metadata = new Dictionary { [\"flow\"] = \"login\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/age-estimation/": { + "post": { + "summary": "Age Estimation (face age + passive liveness)", + "description": "Estimate a person's age from a single face photo and run a passive liveness check in the same call — returns the model-predicted `age_estimation` (in years), a liveness `score` (0–100), and an `Approved`/`Declined` `status`. The age is **model-predicted from the face, not verified against a document** — use it for age gates and low-friction checks, not as legal proof of age.\n\n**How it works.** The image is analyzed for faces (set `rotate_image=true` if captures may be sideways or upside down); each detected face gets an estimated age, and the largest face in the frame drives `age_estimation`. In parallel, a passive liveness model scores whether the photo shows a live person (no user challenge required) and detects presentation attacks (photos of photos, screens, masks). Submit one clear, front-facing, well-lit face per image for best results.\n\n**Decision logic.** Any warning declines: `status` is `Approved` only when the `warnings` array is empty. A liveness `score` at or below `face_liveness_score_decline_threshold` (default `30`) adds `LOW_LIVENESS_SCORE`; a detected attack adds `LIVENESS_FACE_ATTACK`; no face found by the liveness model adds `NO_FACE_DETECTED`; no estimable face age adds `AGE_NOT_DETECTED` (`age_estimation` is `null`); an estimated age below `age_estimation_decline_threshold` (default `18`) adds `AGE_BELOW_MINIMUM`. Borderline ages near your threshold deserve a fallback to document-based verification — the model's error grows for faces close to the limit.\n\n**Billing.** Each `200` response consumes one Age Estimation API credit (paid from your plan credit or top-up balance). When the organization's balance cannot cover the call, the endpoint returns `403` with the not-enough-credits error before any image processing.\n\n**Session persistence (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session: it appears in the Business Console, the returned `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/` (the result is exposed there in `liveness_checks[]`, with `features: [\"AGE_ESTIMATION\"]`), the face crop and reference image are stored, the face embedding is added to your application's face index (used by face search and duplicate detection), and a `status.updated` webhook is emitted. When `false`, nothing is stored and `request_id` is a one-off correlation UUID that cannot be looked up later.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload (`age_estimation: 30`, no warnings), no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3age-estimation", + "x-didit-sdk": { + "resource": "age_estimation", + "method": "estimate", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "user_image" + ], + "properties": { + "user_image": { + "type": "string", + "format": "binary", + "description": "Face photo to analyze (e.g. a selfie). Submit a single front-facing photo with the subject's face clearly visible and well lit. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp` (no PDF). Maximum upload size: **5 MB** (larger files are rejected with `400`). Images are automatically compressed to ~0.3 MB before processing, so very high resolutions do not improve accuracy." + }, + "face_liveness_score_decline_threshold": { + "type": "number", + "format": "float", + "default": 30, + "minimum": 0, + "maximum": 100, + "description": "Liveness threshold (0–100). A liveness `score` **at or below** this value adds a `LOW_LIVENESS_SCORE` warning and declines. Raise it for stricter anti-spoofing; values outside 0–100 return `400`.", + "example": 30 + }, + "age_estimation_decline_threshold": { + "type": "integer", + "default": 18, + "minimum": 0, + "maximum": 100, + "description": "Minimum acceptable estimated age in years. An estimated age **below** this value adds an `AGE_BELOW_MINIMUM` warning and declines. Set `0` to disable the age check (the call then only runs liveness + age estimation without an age-based decision).", + "example": 18 + }, + "rotate_image": { + "type": "boolean", + "default": false, + "description": "When `true`, the service tries the input image in 90-degree increments (0, 90, 180, 270) and uses the orientation that yields the best face detection — useful for mobile captures with missing EXIF orientation. Adds latency, so only enable it if you cannot guarantee upright images.", + "example": false + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, announced through a `status.updated` webhook, and with the detected face added to your application's face index. When `false`, nothing is stored and `request_id` is a transient UUID for response correlation only.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"age-gate\"}`) — it is parsed into an object.", + "example": { + "flow": "age-gate" + } + } + } + }, + "example": { + "user_image": "(binary JPEG/PNG of the selfie)", + "face_liveness_score_decline_threshold": 30, + "age_estimation_decline_threshold": 18, + "rotate_image": false, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "age-gate" + } + } + } + } + }, + "responses": { + "200": { + "description": "Age estimation and passive liveness completed. `age_estimation.age_estimation` is the model-predicted age in years of the largest detected face (`null` if none); `age_estimation.score` is the passive-liveness score (0–100); `status` is `Approved` only when `warnings` is empty. An underage or spoofed capture still returns `200` with `status: \"Declined\"` — inspect the body, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Live adult face — both checks passed", + "value": { + "request_id": "0c40ba43-64ab-4e2e-b4b8-7d1f12f81bc1", + "age_estimation": { + "status": "Approved", + "method": "PASSIVE", + "score": 97.5, + "user_image": { + "entities": [ + { + "age": 27.33, + "bbox": [ + 40, + 40, + 100, + 100 + ], + "confidence": 0.7177750468254089, + "gender": "male" + } + ], + "best_angle": 0 + }, + "age_estimation": 27.33, + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": { + "flow": "age-gate" + }, + "created_at": "2026-06-12T02:24:11.512941+00:00" + } + }, + "Declined - age below threshold": { + "summary": "Estimated age below `age_estimation_decline_threshold`", + "value": { + "request_id": "9be41a6f-2f4e-4f57-92f4-b3a4f6f0a1c2", + "age_estimation": { + "status": "Declined", + "method": "PASSIVE", + "score": 95.0, + "user_image": { + "entities": [ + { + "age": 16.5, + "bbox": [ + 40, + 40, + 100, + 100 + ], + "confidence": 0.7177750468254089, + "gender": "female" + } + ], + "best_angle": 0 + }, + "age_estimation": 16.5, + "warnings": [ + { + "risk": "AGE_BELOW_MINIMUM", + "feature": "LIVENESS", + "additional_data": null, + "log_type": "error", + "short_description": "Age below minimum", + "long_description": "The age of the face is below the minimum age threshold for the application." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:26:40.118332+00:00" + } + }, + "Declined - liveness too low": { + "summary": "Liveness score at or below the decline threshold (possible spoof)", + "value": { + "request_id": "5f6f2f1f-7c4f-43b9-8d62-0a8f4c2d9e77", + "age_estimation": { + "status": "Declined", + "method": "PASSIVE", + "score": 5.0, + "user_image": { + "entities": [ + { + "age": 25.0, + "bbox": [ + 40, + 40, + 100, + 100 + ], + "confidence": 0.7177750468254089, + "gender": "male" + } + ], + "best_angle": 0 + }, + "age_estimation": 25.0, + "warnings": [ + { + "risk": "LOW_LIVENESS_SCORE", + "feature": "LIVENESS", + "additional_data": null, + "log_type": "error", + "short_description": "Low liveness score", + "long_description": "The liveness check resulted in a low score, indicating potential use of non-live facial representations or poor-quality biometric data." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:27:02.901547+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "age_estimation": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Approved` when no warnings were raised; `Declined` otherwise (every warning this endpoint produces declines)." + }, + "method": { + "type": "string", + "enum": [ + "PASSIVE" + ], + "description": "Liveness method. Always `PASSIVE` on this endpoint (single-image, no user challenge)." + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Passive-liveness score from 0 to 100 — the model's confidence that the photo shows a live person. Compared against `face_liveness_score_decline_threshold`; at or below it the call declines with `LOW_LIVENESS_SCORE`. `null` when the liveness model returns no confidence — the call still declines with `LOW_LIVENESS_SCORE` because null is treated as 0 for the threshold comparison.", + "example": 97.5 + }, + "user_image": { + "type": "object", + "description": "Face-detection results for the analyzed image.", + "properties": { + "entities": { + "type": "array", + "description": "One entry per detected face. Empty when no face was found (then `age_estimation` is `null` and an `AGE_NOT_DETECTED` warning is added).", + "items": { + "type": "object", + "properties": { + "age": { + "type": "number", + "format": "float", + "description": "Model-estimated age of this face, in years.", + "example": 27.33 + }, + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box of the detected face as `[x_min, y_min, x_max, y_max]` pixel coordinates in the processed image.", + "example": [ + 40, + 40, + 100, + 100 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.7177750468254089 + }, + "gender": { + "type": "string", + "description": "Model-predicted gender of the detected face (`male` or `female`). Informational only.", + "example": "male" + } + } + } + }, + "best_angle": { + "type": "integer", + "nullable": true, + "description": "Rotation (degrees: 0, 90, 180, or 270) that produced the best face detection. Only non-zero when `rotate_image=true` corrected the orientation.", + "example": 0 + } + } + }, + "age_estimation": { + "type": "number", + "format": "float", + "nullable": true, + "description": "Model-predicted age in years of the **largest** detected face. `null` when no face age could be estimated (an `AGE_NOT_DETECTED` warning is added and the call declines).", + "example": 27.33 + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. Any entry sets `status` to `Declined`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "NO_FACE_DETECTED", + "LOW_LIVENESS_SCORE", + "LIVENESS_FACE_ATTACK", + "AGE_NOT_DETECTED", + "AGE_BELOW_MINIMUM" + ], + "description": "Machine-readable risk code. `NO_FACE_DETECTED` — the liveness model found no face; `LOW_LIVENESS_SCORE` — liveness `score` at or below `face_liveness_score_decline_threshold`; `LIVENESS_FACE_ATTACK` — a presentation attack (photo/screen/mask) was detected; `AGE_NOT_DETECTED` — no face age could be estimated; `AGE_BELOW_MINIMUM` — estimated age below `age_estimation_decline_threshold`. `NO_FACE_DETECTED` and `LOW_LIVENESS_SCORE` are mutually exclusive — when no face is found only `NO_FACE_DETECTED` is emitted." + }, + "feature": { + "type": "string", + "enum": [ + "LIVENESS" + ], + "description": "Feature that raised the warning. Always `LIVENESS` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Always `null` for age-estimation warnings." + }, + "log_type": { + "type": "string", + "enum": [ + "error" + ], + "description": "Severity. Every warning this endpoint produces is an `error` and sets `status` to `Declined`." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when `user_image` is missing, exceeds 5 MB, has an unsupported extension, or when a threshold is out of range. The body is DRF's standard field-error envelope: one array of messages per offending field.", + "content": { + "application/json": { + "examples": { + "Missing user image": { + "summary": "`user_image` not included in the form data", + "value": { + "user_image": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp", + "value": { + "user_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 5 MB limit", + "value": { + "user_image": [ + "File size should not exceed 5 MB" + ] + } + }, + "Threshold out of range": { + "summary": "`face_liveness_score_decline_threshold` outside 0–100", + "value": { + "face_liveness_score_decline_threshold": [ + "Ensure this value is less than or equal to 100." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/age-estimation/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'user_image=@./selfie.jpg' \\\n -F 'age_estimation_decline_threshold=18' \\\n -F 'face_liveness_score_decline_threshold=30' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.age_estimation.estimate(\n user_image=open(\"user_image.jpg\", \"rb\"),\n face_liveness_score_decline_threshold=30.0,\n age_estimation_decline_threshold=18,\n rotate_image=False,\n save_api_request=True,\n vendor_data=\"user-123\",\n metadata={\"flow\": \"age-gate\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.ageEstimation.estimate({\n user_image: await fileFromPath(\"user_image.jpg\"),\n face_liveness_score_decline_threshold: 30.0,\n age_estimation_decline_threshold: 18,\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"age-gate\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.AgeEstimation.Estimate(ctx, didit.AgeEstimationEstimateParams{\n\tUserImage: didit.FileFromPath(\"user_image.jpg\"),\n\tFaceLivenessScoreDeclineThreshold: didit.Float(30.0),\n\tAgeEstimationDeclineThreshold: didit.Int(18),\n\tRotateImage: didit.Bool(false),\n\tSaveAPIRequest: didit.Bool(true),\n\tVendorData: didit.String(\"user-123\"),\n\tMetadata: didit.Some(map[string]any{\"flow\": \"age-gate\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.AgeEstimationEstimateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.ageEstimation().estimate(AgeEstimationEstimateParams.builder()\n .userImage(Path.of(\"user_image.jpg\"))\n .faceLivenessScoreDeclineThreshold(30.0)\n .ageEstimationDeclineThreshold(18L)\n .rotateImage(false)\n .saveApiRequest(true)\n .vendorData(\"user-123\")\n .metadata(Map.of(\"flow\", \"age-gate\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "ageEstimation->estimate([\n 'user_image' => File::fromPath('user_image.jpg'),\n 'face_liveness_score_decline_threshold' => 30.0,\n 'age_estimation_decline_threshold' => 18,\n 'rotate_image' => false,\n 'save_api_request' => true,\n 'vendor_data' => 'user-123',\n 'metadata' => ['flow' => 'age-gate'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.age_estimation.estimate(\n user_image: Pathname(\"user_image.jpg\"),\n face_liveness_score_decline_threshold: 30.0,\n age_estimation_decline_threshold: 18,\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"age-gate\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.AgeEstimation.EstimateAsync(new AgeEstimationEstimateParams\n{\n UserImage = FileUpload.FromPath(\"user_image.jpg\"),\n FaceLivenessScoreDeclineThreshold = 30.0,\n AgeEstimationDeclineThreshold = 18,\n RotateImage = false,\n SaveApiRequest = true,\n VendorData = \"user-123\",\n Metadata = new Dictionary { [\"flow\"] = \"age-gate\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/poa/": { + "post": { + "summary": "Proof of Address (document OCR + address checks)", + "description": "Extract and validate a Proof of Address document — utility bills, bank statements, and government-issued documents — in one call. Returns the issuer, issue/expiry dates, the holder name, the raw and parsed/geocoded address, an `Approved`/`Declined` `status`, and a `warnings` list explaining every issue found.\n\n**Latency.** Extraction is LLM-based: typical calls take **5–15 seconds**, multi-page PDFs up to **~30 seconds**. Configure a client timeout of **at least 45 seconds** and do not retry before the call completes.\n\n**How it works.** The document (image or PDF, up to 15 MB; encrypted PDFs require `document_password`) is classified into a document type and subtype (e.g. `UTILITY_BILL` / `WATER_BILL`), and the issuer, dates, holder name, and address are extracted with LLM-based extraction. The address is parsed and geocoded into `poa_parsed_address`. PDF/EXIF forensics screen for tampering — modification after digital signing, known PDF editors, overlay-text manipulation, suspicious re-exports — surfacing `SUSPECTED_DOCUMENT_MANIPULATION` or `DOCUMENT_METADATA_MISMATCH`. A text field that was blanked and re-typed in another font, where the file itself holds no proof of whether its content changed, surfaces `POA_DOCUMENT_UNPROVEN_TEXT_REPLACEMENT`. Document age is validated against `poa_document_age_months`, and the language against `poa_languages_allowed`. If you pass `expected_address`, `expected_country`, or `expected_first_name`/`expected_last_name`, the extracted values are cross-checked and mismatches are flagged.\n\n**Decision logic.** `status` is `Approved` unless at least one warning resolves to a decline. Hard failures always decline: `MISSING_ADDRESS_INFORMATION`, `POA_DOCUMENT_EXPIRED`, `INVALID_DOCUMENT_TYPE`, `UNABLE_TO_VALIDATE_DOCUMENT_AGE`, plus `UNABLE_TO_EXTRACT_ISSUE_DATE` and `POA_NAME_NOT_DETECTED` (not configurable on this endpoint). Five risk groups are configurable per request via the `poa_*_action` options (`DECLINE` or `NO_ACTION`, all defaulting to `DECLINE`). `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION` and `UNPARSABLE_OR_INVALID_ADDRESS` are informational on this endpoint and never decline. A document that cannot be processed at all returns `400`; a readable but problematic document returns `200` with `status: \"Declined\"` — always inspect `poa.status` and `poa.warnings`, not just the HTTP code.\n\n**Billing.** Each `200` response consumes one Proof of Address API credit (paid from your plan credit or top-up balance). When the organization's balance cannot cover the call, the endpoint returns `403` with the not-enough-credits error before any document processing.\n\n**Session persistence (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session: it appears in the Business Console, the uploaded document is stored, the returned `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/` (the decision additionally exposes `document_file` and the `document_metadata` forensics, which this response omits), and a `status.updated` webhook is emitted. When `false`, nothing is stored and `request_id` is a one-off correlation UUID that cannot be looked up later.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload (a fictional \"Sandbox Power Co.\" electricity bill), no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.\n\nNote: `FUTURE_ISSUE_DATE` and `POOR_DOCUMENT_QUALITY` exist in workflow sessions but are never produced by this standalone endpoint — a future-dated document is not auto-declined here.", + "operationId": "post_v3poa", + "x-didit-sdk": { + "resource": "proof_of_address", + "method": "verify", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "document" + ], + "properties": { + "document": { + "type": "string", + "format": "binary", + "description": "The Proof of Address document. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `pdf`, `webp`. Maximum upload size: **15 MB** (larger files are rejected with `400`). Multi-page PDFs are supported (expect higher latency); encrypted PDFs require `document_password`. Images are automatically compressed before processing." + }, + "document_password": { + "type": "string", + "description": "Password to decrypt `document` when it is an encrypted PDF. Sending an encrypted PDF without (or with a wrong) password returns `400`.", + "writeOnly": true + }, + "expected_address": { + "type": "string", + "description": "Address to verify against the document. It is geocoded and compared with the extracted address; a mismatch (or an expected address that cannot be geocoded/verified) adds `ADDRESS_MISMATCH_WITH_PROVIDED`, governed by `poa_address_mismatch_action`. The geocoded form is echoed back in `expected_details_formatted_address` / `expected_details_parsed_address`.", + "example": "Av. Monseñor Tavella 1291, Salta, Argentina" + }, + "expected_country": { + "type": "string", + "description": "Country to verify against the document (ISO 3166-1 code, e.g. `ARG` or `AR`). A mismatch with the document's country adds `POA_COUNTRY_MISMATCH_WITH_PROVIDED`, governed by `poa_address_mismatch_action`.", + "example": "ARG" + }, + "expected_first_name": { + "type": "string", + "description": "First name to verify against `name_on_document` (fuzzy match, transliteration-aware). A score below the match threshold adds `NAME_MISMATCH_WITH_PROVIDED`, governed by `poa_address_mismatch_action`.", + "example": "Sophia" + }, + "expected_last_name": { + "type": "string", + "description": "Last name to verify against `name_on_document`, combined with `expected_first_name` for the match score.", + "example": "Martinez" + }, + "poa_languages_allowed": { + "type": "string", + "description": "Comma-separated list of accepted document languages as ISO 639-1 codes (e.g. `en,es,fr`). When omitted or blank, **all 51 supported languages** are accepted. A document in a language outside the list adds `UNSUPPORTED_DOCUMENT_LANGUAGE` (governed by `poa_unsupported_language_action`); an unknown code in the list returns `400` with the full set of supported codes.", + "example": "en,es" + }, + "poa_document_age_months": { + "type": "string", + "description": "Comma-separated `key:value` pairs setting the maximum document age in months per document type — keys: `utility_bill`, `bank_statement`, `government_issued_document`, `other_poa_document`; values: `1`–`120`, or `-1` for unlimited (no age check). Defaults when omitted: `utility_bill:3,bank_statement:3,government_issued_document:12,other_poa_document:12`. A document older than its limit declines with `POA_DOCUMENT_EXPIRED`. **Caution:** when you provide this field, list *every* type you accept — omitted types are treated as not accepted (informational `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION`) and their age check is skipped.", + "example": "utility_bill:3,bank_statement:6" + }, + "poa_name_mismatch_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "Accepted but **currently not applied** — name-mismatch warnings (`NAME_MISMATCH_WITH_PROVIDED`) follow `poa_address_mismatch_action` instead. Set `poa_address_mismatch_action` to control both name and address mismatch behavior." + }, + "poa_document_issues_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do on file-integrity problems — in practice only `DOCUMENT_METADATA_MISMATCH` is produced by this endpoint." + }, + "poa_document_authenticity_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when document manipulation is suspected (`SUSPECTED_DOCUMENT_MANIPULATION`) — e.g. the PDF was modified after digital signing, processed by a known PDF editor, shows overlay-text manipulation, or has inconsistent EXIF dates. Also governs `POA_DOCUMENT_UNPROVEN_TEXT_REPLACEMENT` (a re-typed text field the file cannot prove changed or unchanged): `DECLINE` declines it too, `NO_ACTION` keeps it as a warning." + }, + "poa_unsupported_language_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when the document language is outside `poa_languages_allowed` (`UNSUPPORTED_DOCUMENT_LANGUAGE`)." + }, + "poa_address_mismatch_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when the extracted details disagree with the `expected_*` fields: `ADDRESS_MISMATCH_WITH_PROVIDED`, `NAME_MISMATCH_WITH_PROVIDED`, and `POA_COUNTRY_MISMATCH_WITH_PROVIDED` all follow this action." + }, + "poa_issuer_not_identified_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when no issuing company/organization can be identified on the document (`ISSUER_NOT_IDENTIFIED`)." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, announced through a `status.updated` webhook, and with the uploaded document stored. When `false`, nothing is stored and `request_id` is a transient UUID for response correlation only.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"onboarding\"}`) — it is parsed into an object.", + "example": { + "flow": "onboarding" + } + } + } + }, + "example": { + "document": "(binary JPEG/PNG/PDF of the utility bill or bank statement)", + "expected_address": "Av. Monseñor Tavella 1291, Salta, Argentina", + "expected_country": "ARG", + "expected_first_name": "Sophia", + "expected_last_name": "Martinez", + "poa_languages_allowed": "en,es", + "save_api_request": true, + "vendor_data": "user-123" + } + } + } + }, + "responses": { + "200": { + "description": "Document processed. `poa.status` is `Approved` or `Declined`; every detected issue is itemized in `poa.warnings`. An expired, mismatched, or suspicious document still returns `200` with `status: \"Declined\"` — inspect the body, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Utility bill read cleanly, no expected-details checks", + "value": { + "request_id": "4b523a8d-ed32-45e4-8aa8-a89eda459d88", + "poa": { + "issuing_state": "ARG", + "document_type": "UTILITY_BILL", + "document_subtype": "WATER_BILL", + "document_language": "es", + "issuer": "Aguas del Norte", + "issue_date": "2026-04-10", + "expiration_date": null, + "poa_address": "AVDA. MONSEÑOR TAVELLA N° 1291, B° VILLA LAVALLE CP: 4400, CAPITAL - SALTA", + "poa_formatted_address": "Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina", + "poa_parsed_address": { + "street_1": "Avenida Monseñor Tavella 1291", + "street_2": "b", + "city": "Salta", + "region": "Salta", + "country": "AR", + "postal_code": "A4400", + "document_location": { + "latitude": -24.8208682, + "longitude": -65.4130954 + } + }, + "expected_details_address": null, + "expected_details_formatted_address": null, + "expected_details_parsed_address": null, + "name_on_document": "OESTE EMBOTELLADORA S.A.", + "extra_fields": { + "bank_account_number": null, + "bank_iban": null, + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": null, + "additional_names": [ + "LA EMBOTELLADORA DEL NORTE SA" + ] + }, + "detected_codes": [ + { + "type": "QR_CODE", + "page_number": 1, + "position": [ + [ + 820, + 1120 + ], + [ + 1002, + 1120 + ], + [ + 1002, + 1302 + ], + [ + 820, + 1302 + ] + ], + "x": 820, + "y": 1120, + "width": 182, + "height": 182, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "aHR0cHM6Ly9hZ3Vhc2RlbG5vcnRlLmNvbS9mYWN0dXJhLzk5ODgxMQ==", + "parsed_payload": "https://aguasdelnorte.com/factura/998811" + }, + { + "type": "CODE_128", + "page_number": 1, + "position": [ + [ + 112, + 1480 + ], + [ + 560, + 1480 + ], + [ + 560, + 1540 + ], + [ + 112, + 1540 + ] + ], + "x": 112, + "y": 1480, + "width": 448, + "height": 60, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "MDA5OTg4MTEwMDA0Mg==", + "parsed_payload": "0099881100042" + } + ], + "status": "Approved", + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:29:02.439655+00:00" + } + }, + "Declined - document too old": { + "summary": "Issue date older than the allowed `poa_document_age_months`", + "value": { + "request_id": "f61681b8-9422-4378-ac3e-04c6a247fb45", + "poa": { + "issuing_state": "ARG", + "document_type": "UTILITY_BILL", + "document_subtype": "WATER_BILL", + "document_language": "es", + "issuer": "Aguas del Norte", + "issue_date": "2021-02-10", + "expiration_date": null, + "poa_address": "AVDA. MONSEÑOR TAVELLA N° 1291, B° VILLA LAVALLE CP: 4400, CAPITAL - SALTA", + "poa_formatted_address": "Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina", + "poa_parsed_address": { + "street_1": "Avenida Monseñor Tavella 1291", + "street_2": "b", + "city": "Salta", + "region": "Salta", + "country": "AR", + "postal_code": "A4400", + "document_location": { + "latitude": -24.8208682, + "longitude": -65.4130954 + } + }, + "expected_details_address": null, + "expected_details_formatted_address": null, + "expected_details_parsed_address": null, + "name_on_document": "OESTE EMBOTELLADORA S.A.", + "extra_fields": { + "bank_account_number": null, + "bank_iban": null, + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": null, + "additional_names": [ + "LA EMBOTELLADORA DEL NORTE SA" + ] + }, + "status": "Declined", + "warnings": [ + { + "risk": "POA_DOCUMENT_EXPIRED", + "feature": "PROOF_OF_ADDRESS", + "additional_data": { + "expiration_reason": "age_window", + "max_age_months": 3, + "document_subtype": "WATER_BILL", + "document_type": "UTILITY_BILL", + "issue_date": "2021-02-10" + }, + "log_type": "error", + "short_description": "Document expired", + "long_description": "The submitted document is older than the maximum accepted age of 3 months from its issue date, which exceeds the acceptable time period for validity." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:23:02.165493+00:00" + } + }, + "Declined - name mismatch": { + "summary": "`expected_first_name`/`expected_last_name` do not match `name_on_document`", + "value": { + "request_id": "0a7f2f9c-58f1-4b8e-b1e3-2f6f9d4f7a21", + "poa": { + "issuing_state": "ARG", + "document_type": "UTILITY_BILL", + "document_subtype": "WATER_BILL", + "document_language": "es", + "issuer": "Aguas del Norte", + "issue_date": "2021-02-10", + "expiration_date": null, + "poa_address": "AVDA. MONSEÑOR TAVELLA N° 1291, B° VILLA LAVALLE CP: 4400, CAPITAL - SALTA", + "poa_formatted_address": "Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina", + "poa_parsed_address": { + "street_1": "Avenida Monseñor Tavella 1291", + "street_2": "b", + "city": "Salta", + "region": "Salta", + "country": "AR", + "postal_code": "A4400", + "document_location": { + "latitude": -24.8208682, + "longitude": -65.4130954 + } + }, + "expected_details_address": null, + "expected_details_formatted_address": null, + "expected_details_parsed_address": null, + "name_on_document": "OESTE EMBOTELLADORA S.A.", + "extra_fields": { + "bank_account_number": null, + "bank_iban": null, + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": null, + "additional_names": [ + "LA EMBOTELLADORA DEL NORTE SA" + ] + }, + "status": "Declined", + "warnings": [ + { + "risk": "NAME_MISMATCH_WITH_PROVIDED", + "feature": "PROOF_OF_ADDRESS", + "additional_data": null, + "log_type": "error", + "short_description": "Name mismatch with provided information", + "long_description": "The full name on the document does not match the name from the user's verified identity documents, or the full name sent by API." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:27:21.330172+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "poa": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Approved` when no warning resolves to a decline; `Declined` otherwise — the `warnings` list explains why." + }, + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Country the document was issued in, as extracted from the document — usually ISO 3166-1 alpha-3 (e.g. `ARG`).", + "example": "ARG" + }, + "document_type": { + "type": "string", + "enum": [ + "UTILITY_BILL", + "BANK_STATEMENT", + "GOVERNMENT_ISSUED_DOCUMENT", + "OTHER_POA_DOCUMENT", + "UNKNOWN" + ], + "description": "Detected document category. `UNKNOWN` means the document could not be classified as an acceptable Proof of Address and auto-declines with `INVALID_DOCUMENT_TYPE`. Never null — classification failures fall back to `UNKNOWN`." + }, + "document_subtype": { + "type": "string", + "description": "Finer-grained classification within `document_type` — e.g. `ELECTRICITY_BILL`, `WATER_BILL`, `GAS_BILL`, `INTERNET_BILL`, `PHONE_BILL` (utility bills); `ACCOUNT_STATEMENT`, `CREDIT_CARD_STATEMENT` (bank statements); `TAX_ASSESSMENT`, `RESIDENCY_CERTIFICATE` (government documents). `UNKNOWN` when no subtype could be determined. Never null — classification failures fall back to `UNKNOWN`.", + "example": "WATER_BILL" + }, + "document_language": { + "type": "string", + "nullable": true, + "description": "Detected document language (ISO 639-1).", + "example": "es" + }, + "issuer": { + "type": "string", + "nullable": true, + "description": "Company or organization that issued the document. `null` adds `ISSUER_NOT_IDENTIFIED`.", + "example": "Aguas del Norte" + }, + "issue_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Document issue date (`YYYY-MM-DD`). `null` when it could not be extracted — that declines with `UNABLE_TO_EXTRACT_ISSUE_DATE` on this endpoint.", + "example": "2021-02-10" + }, + "expiration_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Explicit expiry/validity date printed on the document, when present. Independent of the `poa_document_age_months` age check." + }, + "poa_address": { + "type": "string", + "nullable": true, + "description": "Address exactly as printed on the document. `null` auto-declines with `MISSING_ADDRESS_INFORMATION`." + }, + "poa_formatted_address": { + "type": "string", + "nullable": true, + "description": "Geocoded, normalized version of `poa_address`.", + "example": "Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina" + }, + "poa_parsed_address": { + "type": "object", + "nullable": true, + "description": "Structured, geocoded breakdown of the document address. `null` when the address could not be parsed.", + "properties": { + "street_1": { + "type": "string", + "nullable": true, + "example": "Avenida Monseñor Tavella 1291" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "city": { + "type": "string", + "nullable": true, + "example": "Salta" + }, + "region": { + "type": "string", + "nullable": true, + "example": "Salta" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code.", + "example": "AR" + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "A4400" + }, + "document_location": { + "type": "object", + "nullable": true, + "properties": { + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + } + }, + "description": "Geocoded coordinates of the document address." + } + } + }, + "expected_details_address": { + "type": "string", + "nullable": true, + "description": "Echo of the `expected_address` you sent, or `null`." + }, + "expected_details_formatted_address": { + "type": "string", + "nullable": true, + "description": "Geocoded, normalized version of `expected_address`; `null` when not provided or not geocodable." + }, + "expected_details_parsed_address": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Full geocoding result for `expected_address` (street/city/region/country/postal code plus raw geocoder output and an `is_verified` flag). `null` when `expected_address` was not provided." + }, + "name_on_document": { + "type": "string", + "nullable": true, + "description": "Primary holder name extracted from the document; compared against `expected_first_name`/`expected_last_name` when provided. `null` declines with `POA_NAME_NOT_DETECTED` on this endpoint.", + "example": "OESTE EMBOTELLADORA S.A." + }, + "extra_fields": { + "type": "object", + "description": "Document-specific extras. Bank fields are populated for bank statements when printed on the document; `null` otherwise.", + "properties": { + "bank_account_number": { + "type": "string", + "nullable": true + }, + "bank_iban": { + "type": "string", + "nullable": true + }, + "bank_sort_code": { + "type": "string", + "nullable": true + }, + "bank_routing_number": { + "type": "string", + "nullable": true + }, + "bank_swift_bic": { + "type": "string", + "nullable": true + }, + "bank_branch_name": { + "type": "string", + "nullable": true + }, + "bank_branch_address": { + "type": "string", + "nullable": true + }, + "document_phone_number": { + "type": "string", + "nullable": true, + "description": "Contact phone number printed on the document." + }, + "additional_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Other holder names found on the document (e.g. co-account holders, previous account names)." + } + } + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. Every entry explains one detected risk; entries with `log_type: \"error\"` set `status` to `Declined`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "description": "Machine-readable risk code. Always-decline: `MISSING_ADDRESS_INFORMATION`, `POA_DOCUMENT_EXPIRED`, `INVALID_DOCUMENT_TYPE`, `UNABLE_TO_VALIDATE_DOCUMENT_AGE`, plus `UNABLE_TO_EXTRACT_ISSUE_DATE` and `POA_NAME_NOT_DETECTED` (fixed to decline on this endpoint). Governed by request options: `NAME_MISMATCH_WITH_PROVIDED` / `ADDRESS_MISMATCH_WITH_PROVIDED` / `POA_COUNTRY_MISMATCH_WITH_PROVIDED` (`poa_address_mismatch_action`), `POOR_DOCUMENT_QUALITY` (never produced by this endpoint) / `DOCUMENT_METADATA_MISMATCH` (`poa_document_issues_action`), `SUSPECTED_DOCUMENT_MANIPULATION` and `POA_DOCUMENT_UNPROVEN_TEXT_REPLACEMENT` (`poa_document_authenticity_action`), `UNSUPPORTED_DOCUMENT_LANGUAGE` (`poa_unsupported_language_action`), `ISSUER_NOT_IDENTIFIED` (`poa_issuer_not_identified_action`). Informational on this endpoint: `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION`, `UNPARSABLE_OR_INVALID_ADDRESS`.", + "example": "POA_DOCUMENT_EXPIRED" + }, + "feature": { + "type": "string", + "enum": [ + "PROOF_OF_ADDRESS" + ], + "description": "Feature that raised the warning. Always `PROOF_OF_ADDRESS` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Extra context for some risks — e.g. `POA_DOCUMENT_EXPIRED` includes `expiration_reason` (`printed_expiration` or `age_window`), `document_type`, `document_subtype`, and `issue_date`, plus `expiration_date` on the `printed_expiration` path or `max_age_months` on the `age_window` path; `SUSPECTED_DOCUMENT_MANIPULATION` includes `reason` and `detection_method`; `POA_DOCUMENT_UNPROVEN_TEXT_REPLACEMENT` includes `reason`, `detection_method` (`unproven_text_replacement`), `replaced_text`, `manipulated_regions` and `manipulated_blocks`. `null` for most risks." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `information` entries are advisory and never decline on their own. (`warning` is not produced by this endpoint — its options only accept `DECLINE` or `NO_ACTION`.)" + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "detected_codes": { + "type": "array", + "description": "QR codes and barcodes detected across every processed page of the document. Empty when none are found. A located but undecodable QR code has `parsed_payload: null`.", + "items": { + "$ref": "#/components/schemas/DocumentAIDetectedCode" + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated." + } + } + } + } + } + }, + "400": { + "description": "Validation or processing error. Field-level problems return DRF's standard envelope (one array of messages per offending field). Encrypted-PDF problems return `{\"detail\": ...}`. A document the extraction pipeline cannot process returns `{\"error\": [\"Error extracting POA information\"]}`.", + "content": { + "application/json": { + "examples": { + "Missing document": { + "summary": "`document` not included in the form data", + "value": { + "document": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/pdf/webp", + "value": { + "document": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, pdf, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 15 MB limit", + "value": { + "document": [ + "File size should not exceed 15 MB" + ] + } + }, + "Encrypted PDF": { + "summary": "PDF uploaded without the required password", + "value": { + "detail": "The PDF is encrypted. Please upload a decrypted PDF or a photo instead." + } + }, + "Wrong PDF password": { + "summary": "`document_password` does not decrypt the PDF", + "value": { + "detail": "The PDF password is incorrect. Please provide the correct password." + } + }, + "Invalid language code": { + "summary": "`poa_languages_allowed` contains an unsupported code", + "value": { + "poa_languages_allowed": [ + "Invalid language code: 'xx'. Must be one of the supported languages: ['ar', 'bn', 'hy', 'bg', 'bs', 'ca', 'cnr', 'sq', 'zh', 'hr', 'cs', 'da', 'nl', 'en', 'et', 'fi', 'fr', 'ka', 'kk', 'de', 'el', 'he', 'hi', 'hu', 'id', 'it', 'ja', 'ko', 'ky', 'lv', 'lt', 'mk', 'mn', 'ms', 'no', 'fa', 'pl', 'pt', 'ro', 'ru', 'sr', 'sk', 'sl', 'so', 'es', 'sv', 'th', 'tr', 'uk', 'uz', 'vi']." + ] + } + }, + "Invalid document age value": { + "summary": "`poa_document_age_months` value outside 1–120 / -1", + "value": { + "poa_document_age_months": [ + "Invalid integer value for 'utility_bill': '999'. Must be -1 (unlimited) or a positive integer between 1 and 120." + ] + } + }, + "Extraction failure": { + "summary": "The document could not be processed by the extraction pipeline", + "value": { + "error": [ + "Error extracting POA information" + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any document processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/poa/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'document=@./utility-bill.pdf' \\\n -F 'expected_address=Av. Monseñor Tavella 1291, Salta, Argentina' \\\n -F 'expected_country=ARG' \\\n -F 'expected_first_name=Sophia' \\\n -F 'expected_last_name=Martinez' \\\n -F 'poa_languages_allowed=en,es' \\\n -F 'vendor_data=user-123' \\\n --max-time 45" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.proof_of_address.verify(\n document=open(\"document.pdf\", \"rb\"),\n expected_address=\"Av. Monseñor Tavella 1291, Salta, Argentina\",\n expected_country=\"ARG\",\n expected_first_name=\"Sophia\",\n expected_last_name=\"Martinez\",\n poa_languages_allowed=\"en,es\",\n save_api_request=True,\n vendor_data=\"user-123\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.proofOfAddress.verify({\n document: await fileFromPath(\"document.pdf\"),\n expected_address: \"Av. Monseñor Tavella 1291, Salta, Argentina\",\n expected_country: \"ARG\",\n expected_first_name: \"Sophia\",\n expected_last_name: \"Martinez\",\n poa_languages_allowed: \"en,es\",\n save_api_request: true,\n vendor_data: \"user-123\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.ProofOfAddress.Verify(ctx, didit.ProofOfAddressVerifyParams{\n\tDocument: didit.FileFromPath(\"document.pdf\"),\n\tExpectedAddress: didit.String(\"Av. Monseñor Tavella 1291, Salta, Argentina\"),\n\tExpectedCountry: didit.String(\"ARG\"),\n\tExpectedFirstName: didit.String(\"Sophia\"),\n\tExpectedLastName: didit.String(\"Martinez\"),\n\tPOALanguagesAllowed: didit.String(\"en,es\"),\n\tSaveAPIRequest: didit.Bool(true),\n\tVendorData: didit.String(\"user-123\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.types.ProofOfAddressVerifyParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.proofOfAddress().verify(ProofOfAddressVerifyParams.builder()\n .document(Path.of(\"document.pdf\"))\n .expectedAddress(\"Av. Monseñor Tavella 1291, Salta, Argentina\")\n .expectedCountry(\"ARG\")\n .expectedFirstName(\"Sophia\")\n .expectedLastName(\"Martinez\")\n .poaLanguagesAllowed(\"en,es\")\n .saveApiRequest(true)\n .vendorData(\"user-123\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "proofOfAddress->verify([\n 'document' => File::fromPath('document.pdf'),\n 'expected_address' => 'Av. Monseñor Tavella 1291, Salta, Argentina',\n 'expected_country' => 'ARG',\n 'expected_first_name' => 'Sophia',\n 'expected_last_name' => 'Martinez',\n 'poa_languages_allowed' => 'en,es',\n 'save_api_request' => true,\n 'vendor_data' => 'user-123',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.proof_of_address.verify(\n document: Pathname(\"document.pdf\"),\n expected_address: \"Av. Monseñor Tavella 1291, Salta, Argentina\",\n expected_country: \"ARG\",\n expected_first_name: \"Sophia\",\n expected_last_name: \"Martinez\",\n poa_languages_allowed: \"en,es\",\n save_api_request: true,\n vendor_data: \"user-123\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.ProofOfAddress.VerifyAsync(new ProofOfAddressVerifyParams\n{\n Document = FileUpload.FromPath(\"document.pdf\"),\n ExpectedAddress = \"Av. Monseñor Tavella 1291, Salta, Argentina\",\n ExpectedCountry = \"ARG\",\n ExpectedFirstName = \"Sophia\",\n ExpectedLastName = \"Martinez\",\n PoaLanguagesAllowed = \"en,es\",\n SaveApiRequest = true,\n VendorData = \"user-123\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/document-ai/": { + "post": { + "summary": "Document AI (custom field extraction + tamper checks)", + "description": "Extract the fields **you** define from any supporting document — payslips, proof of funds, source-of-wealth letters, tax statements, contracts, invoices — in one call. Send the file plus a `fields` definition (key, name, extraction instruction, type) and get back typed values (`text`, `number`, `date` as `YYYY-MM-DD`), an optional name-match score against the expected person or company, PDF/EXIF tamper signals, an `Approved`/`Declined`/`In Review` `status`, and a `warnings` list explaining every issue found. This is the server-to-server counterpart of the hosted Document AI workflow step (see `/core-technology/document-ai/overview`); it uses Didit's default extraction model and does not expose provider selection.\n\n**Latency.** Extraction is LLM-based: typical calls take **5–15 seconds**, multi-page PDFs up to **~30 seconds**. Configure a client timeout of **at least 45 seconds** and do not retry before the call completes.\n\n**Request format.** `multipart/form-data` only. `fields` is **one JSON-encoded array string** (1–30 items, at most 16 KB); bracket-style form arrays are rejected with `400`. Each item is `{key, name, instruction, type, required, is_full_name}` — `key` is the JSON key you get back (`^[a-z0-9_]{1,64}$`, unique), `instruction` tells the model what to read, `type` is `text` (default) | `number` | `date`, `required` raises `DOCUMENT_AI_MISSING_REQUIRED_FIELDS` when the value cannot be read, and at most one field may set `is_full_name: true` to mark the value compared against the expected name.\n\n**Name matching.** Pass `expected_first_name` + `expected_last_name` (person) **or** `expected_company_name` (+ optional `expected_company_country`) — not both. The value of the `is_full_name` field (or, when none is marked, the first text field whose key looks like a name) is compared fuzzily; `name_match_score` (0–100) and `name_on_document` are always returned when both sides exist, and a score below `document_ai_name_match_score_threshold` (default 80) adds `DOCUMENT_AI_NAME_MISMATCH`, governed by `document_ai_name_mismatch_action`.\n\n**Decision logic.** Each risk maps to a configurable action: `DECLINE` → `status: \"Declined\"`, `REVIEW` → `status: \"In Review\"`, `NO_ACTION` → informational only. Defaults mirror the workflow step: unreadable document `REVIEW`, missing required fields `REVIEW`, suspected manipulation `DECLINE`, name mismatch `NO_ACTION`, unsupported file `REVIEW`. A document that cannot be processed at all returns `400`; a readable but problematic document returns `200` with `Declined`/`In Review` — always inspect `document_ai.status` and `document_ai.warnings`, not just the HTTP code.\n\n**Billing.** Each `200` response consumes one Document AI API credit — **$0.20 per document** at list, paid from your plan credit or top-up balance. When the organization's balance cannot cover the call, the endpoint returns `403` before any processing. Sandbox API keys return a deterministic mock response and are not charged.\n\n**Persistence.** With `save_api_request=true` (default) the call is stored as an API-type session: the document, the extracted values, the field definitions and the name-match snapshot are kept, the session shows up in the Business Console, and `request_id` works with `GET /v3/session/{sessionId}/decision/`.", + "operationId": "post_v3document-ai", + "x-didit-sdk": { + "resource": "document_ai", + "method": "analyze", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "document", + "fields" + ], + "properties": { + "document": { + "type": "string", + "format": "binary", + "description": "The document to read. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `pdf`, `webp`, `zip`. Maximum upload size: **30 MB** (larger files are rejected with `400`). Multi-page PDFs are supported (expect higher latency); encrypted PDFs are rejected with `400`. Images are automatically compressed before processing." + }, + "fields": { + "type": "string", + "description": "One JSON-encoded array string with **1–30** field definitions (maximum 16 KB). Each item: `key` (required, `^[a-z0-9_]{1,64}$`, unique — the key returned in `extracted_data`), `name` (≤100 chars, shown in messages), `instruction` (≤500 chars, what the model should read), `type` (`text` default | `number` | `date`), `required` (boolean, default `false`), `is_full_name` (boolean, default `false`, at most one per request). Bracket-style form arrays, invalid JSON, duplicate keys, or more than one `is_full_name` return `400` before any processing or billing.", + "example": "[{\"key\": \"employer_name\", \"name\": \"Employer name\", \"instruction\": \"The employer or company issuing the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": false}, {\"key\": \"employee_name\", \"name\": \"Employee name\", \"instruction\": \"The full name of the employee as printed on the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": true}, {\"key\": \"net_pay\", \"name\": \"Net pay\", \"instruction\": \"The net amount paid to the employee for the period.\", \"type\": \"number\", \"required\": true, \"is_full_name\": false}, {\"key\": \"pay_date\", \"name\": \"Pay date\", \"instruction\": \"The payment date shown on the payslip.\", \"type\": \"date\", \"required\": false, \"is_full_name\": false}]" + }, + "expected_first_name": { + "type": "string", + "description": "First name to verify against the document's name field (fuzzy match, transliteration-aware). Use together with `expected_last_name`; cannot be combined with `expected_company_name`.", + "example": "Sophia" + }, + "expected_last_name": { + "type": "string", + "description": "Last name to verify against the document's name field, combined with `expected_first_name` for `name_match_score`.", + "example": "Martinez" + }, + "expected_company_name": { + "type": "string", + "description": "Company name to verify against the document's name field (for business documents). Cannot be combined with `expected_first_name` / `expected_last_name` (no example value on purpose — the generated request sample uses the person fields; swap them for this one when verifying a business document)." + }, + "expected_company_country": { + "type": "string", + "description": "Optional ISO 3166-1 alpha-2 country code (two ASCII letters, e.g. `FR`) that tunes company-name normalization for `expected_company_name`. Only used with `expected_company_name`." + }, + "document_ai_name_match_score_threshold": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 80, + "description": "Minimum `name_match_score` (0–100) to consider the name matched. A lower score adds `DOCUMENT_AI_NAME_MISMATCH`, governed by `document_ai_name_mismatch_action`.", + "example": 80 + }, + "document_ai_unreadable_document_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "REVIEW", + "description": "What to do when no field could be read from the document (`DOCUMENT_AI_UNREADABLE_DOCUMENT`)." + }, + "document_ai_missing_required_fields_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "REVIEW", + "description": "What to do when at least one `required` field could not be read (`DOCUMENT_AI_MISSING_REQUIRED_FIELDS`; `additional_data.missing_fields` lists them)." + }, + "document_ai_document_tampering_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when PDF/EXIF forensics suspect manipulation — modified after digital signing, known PDF editor, overlay-text manipulation, suspicious re-export, image anomalies (`DOCUMENT_AI_SUSPECTED_MANIPULATION`)." + }, + "document_ai_name_mismatch_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "NO_ACTION", + "description": "What to do when `name_match_score` is below the threshold (`DOCUMENT_AI_NAME_MISMATCH`)." + }, + "document_ai_unsupported_file_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "REVIEW", + "description": "What to do when the upload is empty or not a processable file (`DOCUMENT_AI_UNSUPPORTED_FILE`)." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, with the uploaded document, the extracted values, the field definitions and the name-match snapshot stored. When `false`, nothing is stored and `request_id` is a transient UUID for response correlation only; the call is still billed.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"onboarding\"}`) — it is parsed into an object.", + "example": { + "flow": "onboarding" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Document processed. `document_ai.status` is `Approved`, `Declined`, or `In Review`; every detected issue is itemized in `document_ai.warnings`. A problematic document still returns `200` — inspect the body, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Payslip read cleanly, name matched", + "value": { + "request_id": "9d6a3b1e-2f0c-4a8e-9b7d-1c2e3f4a5b6c", + "document_ai": { + "status": "Approved", + "extracted_data": { + "employer_name": "Acme Payments Ltd", + "employee_name": "Sophia Martinez", + "net_pay": 3120.5, + "pay_date": "2026-07-31" + }, + "detected_codes": [ + { + "type": "CODE_128", + "page_number": 1, + "position": [ + [ + 96, + 456 + ], + [ + 96, + 616 + ], + [ + 1012, + 611 + ], + [ + 1015, + 456 + ] + ], + "x": 96, + "y": 456, + "width": 919, + "height": 160, + "page_width": 1200, + "page_height": 1700, + "raw_payload": "SU5WLTIwMjYtMDA0Mg==", + "parsed_payload": "INV-2026-0042" + } + ], + "fields": [ + { + "key": "employer_name", + "name": "Employer name", + "instruction": "The employer or company issuing the payslip.", + "type": "text", + "required": true, + "is_full_name": false + }, + { + "key": "employee_name", + "name": "Employee name", + "instruction": "The full name of the employee as printed on the payslip.", + "type": "text", + "required": true, + "is_full_name": true + }, + { + "key": "net_pay", + "name": "Net pay", + "instruction": "The net amount paid to the employee for the period.", + "type": "number", + "required": true, + "is_full_name": false + }, + { + "key": "pay_date", + "name": "Pay date", + "instruction": "The payment date shown on the payslip.", + "type": "date", + "required": false, + "is_full_name": false + } + ], + "name_match_score": 100.0, + "name_on_document": "Sophia Martinez", + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": { + "flow": "onboarding" + }, + "created_at": "2026-08-19T16:40:12.318Z" + } + }, + "In Review — missing required field": { + "summary": "`net_pay` could not be read; default action REVIEW", + "value": { + "request_id": "3f2a9c7b-5d4e-4b1a-8c6f-0e9d8c7b6a5f", + "document_ai": { + "status": "In Review", + "extracted_data": { + "employer_name": "Acme Payments Ltd", + "employee_name": "Sophia Martinez", + "net_pay": null, + "pay_date": "2026-07-31" + }, + "detected_codes": [], + "fields": [ + { + "key": "employer_name", + "name": "Employer name", + "instruction": "The employer or company issuing the payslip.", + "type": "text", + "required": true, + "is_full_name": false + }, + { + "key": "employee_name", + "name": "Employee name", + "instruction": "The full name of the employee as printed on the payslip.", + "type": "text", + "required": true, + "is_full_name": true + }, + { + "key": "net_pay", + "name": "Net pay", + "instruction": "The net amount paid to the employee for the period.", + "type": "number", + "required": true, + "is_full_name": false + }, + { + "key": "pay_date", + "name": "Pay date", + "instruction": "The payment date shown on the payslip.", + "type": "date", + "required": false, + "is_full_name": false + } + ], + "name_match_score": 100.0, + "name_on_document": "Sophia Martinez", + "warnings": [ + { + "risk": "DOCUMENT_AI_MISSING_REQUIRED_FIELDS", + "feature": "DOCUMENT_AI", + "additional_data": { + "missing_fields": [ + "Net pay" + ] + }, + "log_type": "warning", + "short_description": "Required fields could not be extracted", + "long_description": "One or more fields configured as required could not be extracted from the uploaded document." + } + ] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-08-19T16:41:05.002Z" + } + }, + "Declined — suspected manipulation": { + "summary": "PDF forensics flagged a known editor; default action DECLINE", + "value": { + "request_id": "7c1d2e3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f", + "document_ai": { + "status": "Declined", + "extracted_data": { + "employer_name": "Acme Payments Ltd", + "employee_name": "Sophia Martinez", + "net_pay": 3120.5, + "pay_date": "2026-07-31" + }, + "detected_codes": [], + "fields": [ + { + "key": "employer_name", + "name": "Employer name", + "instruction": "The employer or company issuing the payslip.", + "type": "text", + "required": true, + "is_full_name": false + }, + { + "key": "employee_name", + "name": "Employee name", + "instruction": "The full name of the employee as printed on the payslip.", + "type": "text", + "required": true, + "is_full_name": true + }, + { + "key": "net_pay", + "name": "Net pay", + "instruction": "The net amount paid to the employee for the period.", + "type": "number", + "required": true, + "is_full_name": false + }, + { + "key": "pay_date", + "name": "Pay date", + "instruction": "The payment date shown on the payslip.", + "type": "date", + "required": false, + "is_full_name": false + } + ], + "name_match_score": null, + "name_on_document": "Sophia Martinez", + "warnings": [ + { + "risk": "DOCUMENT_AI_SUSPECTED_MANIPULATION", + "feature": "DOCUMENT_AI", + "additional_data": { + "detection_method": "known_editor", + "processed_by_known_editor": "ilovepdf" + }, + "log_type": "error", + "short_description": "Suspected document manipulation", + "long_description": "The system detected signs of potential manipulation or editing in the uploaded document." + } + ] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-08-19T16:42:40.771Z" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "document_ai": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "description": "`Approved` when no warning resolves to a decline or review; `Declined` when at least one risk maps to `DECLINE`; otherwise `In Review` when at least one maps to `REVIEW`. The `warnings` list explains why." + }, + "extracted_data": { + "type": "object", + "additionalProperties": true, + "description": "One entry per requested field key. Values are typed per the field `type` — strings for `text`, numbers for `number`, `YYYY-MM-DD` strings for `date` — or `null` when the value could not be read.", + "example": { + "employer_name": "Acme Payments Ltd", + "employee_name": "Sophia Martinez", + "net_pay": 3120.5, + "pay_date": "2026-07-31" + } + }, + "detected_codes": { + "type": "array", + "description": "QR codes and barcodes detected across every processed page. Empty when none are found. A located but undecodable QR code has `parsed_payload: null`.", + "items": { + "$ref": "#/components/schemas/DocumentAIDetectedCode" + } + }, + "fields": { + "type": "array", + "description": "The normalized field definitions that were used for this extraction, echoed back (also stored with the session when `save_api_request=true`).", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "instruction": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "text", + "number", + "date" + ] + }, + "required": { + "type": "boolean" + }, + "is_full_name": { + "type": "boolean" + } + } + } + }, + "name_match_score": { + "type": "number", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Fuzzy similarity (0–100) between `name_on_document` and the expected person/company name. Returned whenever both a name value and an expected name exist — on matches **and** mismatches; `null` otherwise.", + "example": 96.0 + }, + "name_on_document": { + "type": "string", + "nullable": true, + "description": "The name value taken from the document (the `is_full_name` field, or the first name-like text field) that was compared against the expected name. `null` when no name value was available.", + "example": "Sophia Martinez" + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. Every entry explains one detected risk; entries with `log_type: \"error\"` set `status` to `Declined`, entries with `log_type: \"warning\"` set it to `In Review`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "DOCUMENT_AI_UNREADABLE_DOCUMENT", + "DOCUMENT_AI_MISSING_REQUIRED_FIELDS", + "DOCUMENT_AI_SUSPECTED_MANIPULATION", + "DOCUMENT_AI_NAME_MISMATCH", + "DOCUMENT_AI_UNSUPPORTED_FILE" + ], + "description": "Machine-readable risk code. Each one is governed by the matching `document_ai_*_action` request option.", + "example": "DOCUMENT_AI_MISSING_REQUIRED_FIELDS" + }, + "feature": { + "type": "string", + "enum": [ + "DOCUMENT_AI" + ], + "description": "Feature that raised the warning. Always `DOCUMENT_AI` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Extra context for the risk: `{\"missing_fields\": [\"Net pay\"]}` for missing required fields, `{\"name_on_document\", \"score\", \"threshold\"}` for a name mismatch, and the forensic finding (`detection_method`, producer/editor details) for suspected manipulation. `null` when there is nothing to add.", + "example": { + "missing_fields": [ + "Net pay" + ] + } + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity derived from the configured action: `error` (DECLINE), `warning` (REVIEW), `information` (NO_ACTION)." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-liner for the risk.", + "example": "Required fields could not be extracted" + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation, interpolated with `additional_data` where relevant." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the request was processed (UTC, ISO 8601)." + } + } + } + } + } + }, + "400": { + "description": "Validation or processing error. Field-level problems return DRF's standard envelope (one array of messages per offending field). Encrypted or unreadable documents and extraction failures return `{\"error\": ...}`.", + "content": { + "application/json": { + "examples": { + "Missing document": { + "summary": "`document` not included in the form data", + "value": { + "document": [ + "No file was submitted." + ] + } + }, + "Invalid fields payload": { + "summary": "`fields` is not one JSON-encoded array string", + "value": { + "fields": [ + "fields must be a valid JSON array string." + ] + } + }, + "Duplicate field keys": { + "summary": "Two items share the same `key`", + "value": { + "fields": [ + "Every fields item must have a unique key." + ] + } + }, + "Conflicting expectations": { + "summary": "Person and company expectations sent together", + "value": { + "non_field_errors": [ + "Provide either person expected-name fields or expected_company_name, not both." + ] + } + }, + "Invalid metadata": { + "summary": "`metadata` is not a JSON object (or contains a NUL character)", + "value": { + "metadata": [ + "Metadata must be a JSON object." + ] + } + }, + "Oversized vendor_data": { + "summary": "`vendor_data` over the 2,000-byte cap", + "value": { + "vendor_data": [ + "vendor_data is too long (maximum 2000 bytes)." + ] + } + }, + "Encrypted PDF": { + "summary": "Password-protected PDF", + "value": { + "error": "The PDF is encrypted. Please upload a decrypted PDF or a photo instead." + } + }, + "Extraction failed": { + "summary": "The extraction pipeline could not process the document", + "value": { + "error": [ + "Error extracting Document AI information" + ] + } + } + }, + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls — and contract-billed organizations whose rate card has no Document AI API entry — return `403` with `{\"error\": ...}` before any document processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + }, + "Contract rate missing": { + "summary": "Contract-billed organization whose rate card has no Document AI API entry", + "value": { + "error": "Document AI API is not included in your contract rate card. Contact support@didit.me to enable it." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "error": { + "type": "string" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.document_ai.analyze(\n document=open(\"document.pdf\", \"rb\"),\n fields=\"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.documentAi.analyze({\n document: await fileFromPath(\"document.pdf\"),\n fields: \"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.DocumentAi.Analyze(ctx, didit.DocumentAiAnalyzeParams{\n\tDocument: didit.FileFromPath(\"document.pdf\"),\n\tFields: \"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.types.DocumentAiAnalyzeParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.documentAi().analyze(DocumentAiAnalyzeParams.builder()\n .document(Path.of(\"document.pdf\"))\n .fields(\"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "documentAi->analyze([\n 'document' => File::fromPath('document.pdf'),\n 'fields' => '[{\"key\": \"employer_name\", \"name\": \"Employer name\", \"instruction\": \"The employer or company issuing the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": false}, {\"key\": \"employee_name\", \"name\": \"Employee name\", \"instruction\": \"The full name of the employee as printed on the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": true}, {\"key\": \"net_pay\", \"name\": \"Net pay\", \"instruction\": \"The net amount paid to the employee for the period.\", \"type\": \"number\", \"required\": true, \"is_full_name\": false}, {\"key\": \"pay_date\", \"name\": \"Pay date\", \"instruction\": \"The payment date shown on the payslip.\", \"type\": \"date\", \"required\": false, \"is_full_name\": false}]',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.document_ai.analyze(\n document: Pathname(\"document.pdf\"),\n fields: \"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.DocumentAi.AnalyzeAsync(new DocumentAiAnalyzeParams\n{\n Document = FileUpload.FromPath(\"document.pdf\"),\n Fields = \"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/aml/": { + "post": { + "summary": "AML screening (standalone)", + "operationId": "post_v3aml", + "x-didit-sdk": { + "resource": "aml", + "method": "screen", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "Idempotency-Key", + "in": "header", + "required": false, + "description": "Optional retry-safety key, up to 255 characters. Reusing the same key with the same request body returns the original response without running or billing a second screening. Reusing the key with a different body returns `409`.", + "schema": { + "type": "string", + "maxLength": 255 + } + } + ], + "description": "Screen a person or company against PEP, sanctions, warning/watchlist, and adverse-media datasets in one call.\n\n**Two-score model.** Each hit carries a `match_score` (0–100, identity confidence) and a `risk_score` (0–100, entity risk). Hits whose `match_score` is below `aml_match_score_threshold` (default **93**) are tagged `review_status: \"False Positive\"` and ignored for risk purposes; the highest `risk_score` among the remaining hits becomes `aml.score`. The final `aml.status` follows your thresholds: `score` > `aml_score_review_threshold` (default **100**) → `Declined`; `score` > `aml_score_approve_threshold` (default **80**) → `In Review`; otherwise `Approved`. Whenever `score` is non-zero and ≥ the approve threshold a `POSSIBLE_MATCH_FOUND` warning is added (a score of exactly 0 never warns).\n\n**Match-score weights.** `aml_name_weight` + `aml_dob_weight` + `aml_country_weight` must sum to exactly 100 (defaults 60/25/15). Components missing from the request are re-normalized away; a matching `document_number` acts as a golden key that overrides `match_score` to 100.\n\n**Latency.** Plain screenings typically complete in a few seconds. With `include_adverse_media=true` the API waits for adverse-media enrichment (an initial ~5 s pause plus up to ~25 s of polling) — configure a client timeout of **at least 60 seconds**.\n\n**Persistence.** `save_api_request` defaults to **true**: the screening is stored as a session (`request_id` works with `GET /v3/session/{sessionId}/decision/`), it appears in the console, a `status.updated` webhook fires, and hit review statuses can be updated later. With `save_api_request=false` nothing is stored and `request_id` is a transient correlation id. `include_ongoing_monitoring=true` keeps re-screening the profile and requires `save_api_request=true` (enforced with a `400`).\n\n**Idempotency.** Send an `Idempotency-Key` header when retrying after a timeout or transient error. The key is scoped to your application and the exact request body. The first completed request stores its response; later identical retries return that same response and do not run or bill another AML screening. If the original request is still running, retry later with the same key. If the same key is reused with a different body, the API returns `409`.\n\n**Billing.** One AML API credit is charged after the first successful screening. Replayed idempotent responses are not charged again; an insufficient balance returns `403` before any new screening happens.\n\n**Sandbox.** Keys from sandbox applications skip screening and billing entirely and return a mocked result — `Approved` with zero hits unless you pass `sandbox_scenario`. `save_api_request` works the same as on live keys, so a saved sandbox screening is retrievable through `GET /v3/session/{sessionId}/decision/` and `GET /v3/session/{sessionId}/generate-pdf/`.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "full_name" + ], + "properties": { + "full_name": { + "type": "string", + "minLength": 2, + "description": "Full name of the person or company to screen. Minimum **2 characters** - the screening provider's own floor. A shorter or blank name returns `400` on this field before any screening runs, so nothing is billed.", + "example": "Antonio Ejemplo Modelo" + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "default": "person", + "description": "Type of entity to screen. Defaults to `person`." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth (persons) or incorporation date (companies), `YYYY-MM-DD`. Improves match precision via the DOB weight.", + "example": "1972-02-29" + }, + "nationality": { + "type": "string", + "description": "ISO 3166-1 **alpha-2** country code (e.g. `ES`). Invalid codes return `400`. Improves match precision via the country weight.", + "example": "ES" + }, + "document_number": { + "type": "string", + "description": "Identity document number. Not weighted — acts as a golden key: an exact match overrides the hit's `match_score` to 100, a hard mismatch penalizes it. Sent to the screening provider as a search filter only when it is **2-50 characters**; a value outside that range is accepted and stored, and the screening still runs and is billed, just not narrowed by document number." + }, + "aml_score_approve_threshold": { + "type": "number", + "minimum": 0, + "maximum": 100, + "default": 80, + "description": "Risk-score threshold at or below which the result is `Approved`. Must be ≤ `aml_score_review_threshold` (else `400`)." + }, + "aml_score_review_threshold": { + "type": "number", + "minimum": 0, + "maximum": 100, + "default": 100, + "description": "Risk-score threshold above which the result is `Declined`. Scores between the two thresholds produce `In Review`." + }, + "aml_name_weight": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 60, + "description": "Weight of name similarity in the per-hit match score. The three weights must sum to exactly 100 (else `400`)." + }, + "aml_dob_weight": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 25, + "description": "Weight of date of birth in the per-hit match score." + }, + "aml_country_weight": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 15, + "description": "Weight of country/nationality in the per-hit match score." + }, + "aml_match_score_threshold": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 93, + "description": "Per-hit cutoff: hits with `match_score` below this are `False Positive` (excluded from risk assessment); at or above are `Unreviewed` possible matches." + }, + "include_adverse_media": { + "type": "boolean", + "default": false, + "description": "Also search news media for negative coverage. Adds up to ~30 s of latency while results are gathered — use a ≥60 s client timeout." + }, + "include_ongoing_monitoring": { + "type": "boolean", + "default": false, + "description": "Keep re-screening this profile and push changes via webhook. Requires `save_api_request=true` (else `400`)." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "Persist the screening as a session (console visibility, decision endpoint, webhooks, hit review). Set to `false` for a stateless check." + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for the screened user; echoed back and stored with the session." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON stored with the request and echoed back." + }, + "sandbox_scenario": { + "type": "string", + "description": "Sandbox-only scenario slug. Only accepted when the API key belongs to a sandbox-mode application; rejected on live applications. A standalone screening has no session to arm, so the slug goes on the screening request itself: `decline_aml_hit` returns one production-shaped hit and settles the screening as `Declined`, `review_aml_possible_match` settles it `In Review`, and a scenario that arms no AML outcome leaves the screening clean. See the scenario catalog at GET /v1/sandbox/scenarios/ or [Sandbox & Test Data](/integration/sandbox-testing).", + "example": "decline_aml_hit" + } + } + }, + "example": { + "full_name": "Antonio Ejemplo Modelo", + "entity_type": "person", + "date_of_birth": "1972-02-29", + "nationality": "ES", + "include_adverse_media": false, + "vendor_data": "user-1234" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/aml/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -H 'Idempotency-Key: aml-user-1234-2026-08-20' \\\n -d '{\n \"full_name\": \"Antonio Ejemplo Modelo\",\n \"entity_type\": \"person\",\n \"date_of_birth\": \"1972-02-29\",\n \"nationality\": \"ES\",\n \"include_adverse_media\": false,\n \"vendor_data\": \"user-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.aml.screen(\n full_name=\"Antonio Ejemplo Modelo\",\n entity_type=\"person\",\n date_of_birth=\"1972-02-29\",\n nationality=\"ES\",\n include_adverse_media=False,\n vendor_data=\"user-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.aml.screen({\n full_name: \"Antonio Ejemplo Modelo\",\n entity_type: \"person\",\n date_of_birth: \"1972-02-29\",\n nationality: \"ES\",\n include_adverse_media: false,\n vendor_data: \"user-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.AML.Screen(ctx, didit.AMLScreenParams{\n\tFullName: \"Antonio Ejemplo Modelo\",\n\tEntityType: didit.Some(didit.AMLScreenParamsEntityType(\"person\")),\n\tDateOfBirth: didit.String(\"1972-02-29\"),\n\tNationality: didit.String(\"ES\"),\n\tIncludeAdverseMedia: didit.Bool(false),\n\tVendorData: didit.String(\"user-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport me.didit.Didit;\nimport me.didit.types.AmlScreenParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.aml().screen(AmlScreenParams.builder()\n .fullName(\"Antonio Ejemplo Modelo\")\n .entityType(AmlScreenParams.EntityType.of(\"person\"))\n .dateOfBirth(LocalDate.parse(\"1972-02-29\"))\n .nationality(\"ES\")\n .includeAdverseMedia(false)\n .vendorData(\"user-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "aml->screen([\n 'full_name' => 'Antonio Ejemplo Modelo',\n 'entity_type' => 'person',\n 'date_of_birth' => '1972-02-29',\n 'nationality' => 'ES',\n 'include_adverse_media' => false,\n 'vendor_data' => 'user-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.aml.screen(\n full_name: \"Antonio Ejemplo Modelo\",\n entity_type: \"person\",\n date_of_birth: \"1972-02-29\",\n nationality: \"ES\",\n include_adverse_media: false,\n vendor_data: \"user-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Aml.ScreenAsync(new AmlScreenParams\n{\n FullName = \"Antonio Ejemplo Modelo\",\n EntityType = new AmlScreenParamsEntityType(\"person\"),\n DateOfBirth = \"1972-02-29\",\n Nationality = \"ES\",\n IncludeAdverseMedia = false,\n VendorData = \"user-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Screening completed. `aml.status` reflects your thresholds; every hit at or above the internal inclusion floor is listed in `aml.hits` with its identity `match_score`, entity `risk_score`, and a `score_breakdown` explaining the match.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "aml": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "`Declined` when `score` > `aml_score_review_threshold`; `In Review` when `score` > `aml_score_approve_threshold`; `Approved` otherwise (including when no score is available)." + }, + "total_hits": { + "type": "integer", + "description": "Number of entries in `hits`." + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Echo of the screened entity type." + }, + "hits": { + "type": "array", + "items": { + "type": "object", + "description": "One screening hit. `match_score` (0–100) measures identity confidence — hits below `aml_match_score_threshold` are tagged `review_status: \"False Positive\"` and excluded from the risk assessment; hits at or above it are `\"Unreviewed\"` possible matches. `risk_score` (0–100) measures how risky the matched entity is and drives the top-level `aml.score`.", + "properties": { + "id": { + "type": "string", + "nullable": true, + "description": "Didit's stable identifier for the hit: `hit_` followed by 24 hexadecimal characters. The same matched entity keeps the same `id` when the subject is screened again. Pass it as `hit_id` to the AML hit review endpoints." + }, + "url": { + "type": "string", + "nullable": true, + "description": "Source URL for the matched entity, when available." + }, + "match": { + "type": "boolean", + "nullable": true, + "description": "Legacy exact-match flag. Prefer `match_score` + `review_status`." + }, + "score": { + "type": "number", + "nullable": true, + "description": "DEPRECATED weighted match score in the 0–1 range. Use `match_score` instead." + }, + "match_score": { + "type": "number", + "nullable": true, + "description": "Identity confidence (0–100). Compared against `aml_match_score_threshold` to split False Positives from Possible Matches." + }, + "risk_score": { + "type": "number", + "nullable": true, + "description": "Entity risk score (0–100). The highest `risk_score` among non-false-positive hits becomes the top-level `aml.score`." + }, + "target": { + "type": "boolean", + "nullable": true + }, + "caption": { + "type": "string", + "nullable": true, + "description": "Display name of the matched entity." + }, + "datasets": { + "type": "array", + "nullable": true, + "items": { + "type": "string", + "enum": [ + "PEP", + "PEP Level 1", + "PEP Level 2", + "PEP Level 3", + "PEP Level 4", + "Sanctions", + "Adverse Media", + "Warnings and Regulatory Enforcement", + "Fitness and Probity", + "SIP", + "SIE", + "Insolvency", + "Other Lists" + ] + }, + "description": "Risk categories the entity is listed under: `PEP` (with its level, `PEP Level 1` to `PEP Level 4`), `Sanctions`, `Adverse Media`, `Warnings and Regulatory Enforcement`, `Fitness and Probity`, `SIP`, `SIE` or `Insolvency`. `Other Lists` when the entity's source states none of these categories." + }, + "features": { + "type": "object", + "nullable": true + }, + "properties": { + "type": "object", + "nullable": true, + "description": "Entity profile: `name`, `alias`, `notes`, `gender`, `birthDate`, `birthPlace`, `nationality`, `position`, and other biographical arrays (each nullable)." + }, + "rca_name": { + "type": "string", + "nullable": true, + "description": "Name of the related close associate through which the entity was matched, when the hit is an RCA." + }, + "pep_matches": { + "type": "array", + "description": "PEP list entries backing this hit.", + "items": { + "type": "object", + "properties": { + "list_name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "source_url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "matched_name": { + "type": "string" + }, + "pep_position": { + "type": "string" + }, + "date_of_birth": { + "type": "string" + }, + "place_of_birth": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "type": "string" + } + }, + "education": { + "type": "array", + "items": { + "type": "string" + } + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "sanction_matches": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Sanctions list entries backing this hit (same backing-list shape as `pep_matches`, plus sanction-specific fields)." + }, + "warning_matches": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Warning/watchlist entries backing this hit." + }, + "adverse_media_matches": { + "type": "array", + "description": "Adverse-media articles. Populated only when `include_adverse_media=true`.", + "items": { + "type": "object", + "properties": { + "headline": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "sentiment": { + "type": "string" + }, + "sentiment_score": { + "type": "number" + }, + "source_url": { + "type": "string" + }, + "thumbnail": { + "type": "string", + "nullable": true + }, + "country": { + "type": "string", + "nullable": true + }, + "author_name": { + "type": "string", + "nullable": true + }, + "publication_date": { + "type": "string", + "nullable": true + }, + "adverse_keywords": { + "type": "array", + "items": { + "type": "string" + } + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "adverse_media_details": { + "type": "object", + "nullable": true, + "description": "Aggregated adverse-media sentiment for the entity (`sentiment`, `sentiment_score`, `adverse_keywords` frequency map). Populated only when `include_adverse_media=true`." + }, + "first_seen": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the entity first appeared in the datasets." + }, + "last_seen": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Most recent dataset refresh that included the entity." + }, + "linked_entities": { + "type": "array", + "description": "Known relationships of the matched entity (family, associates, companies).", + "items": { + "type": "object", + "properties": { + "name": { + "type": "array", + "items": { + "type": "string" + } + }, + "relation": { + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "type": "array", + "items": { + "type": "string" + } + }, + "details": { + "type": "array", + "items": { + "type": "string" + } + }, + "active": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "risk_view": { + "type": "object", + "nullable": true, + "description": "Per-dimension risk breakdown (`crimes`, `countries`, `categories`, `custom_list`), each with `score`, `weightage`, `risk_level`, and a `risk_scores` map." + }, + "additional_information": { + "type": "object", + "nullable": true, + "description": "Free-form extra context, e.g. a `flag_summary` array explaining why the entity is flagged." + }, + "review_status": { + "type": "string", + "nullable": true, + "enum": [ + "False Positive", + "Unreviewed", + "Confirmed Match", + "Inconclusive" + ], + "description": "`False Positive` — `match_score` below `aml_match_score_threshold`, excluded from risk assessment. `Unreviewed` — possible match awaiting review. `Confirmed Match` / `Inconclusive` — set manually by a reviewer (via the Business Console) on saved sessions; that review capability is not part of this spec." + }, + "score_breakdown": { + "type": "object", + "nullable": true, + "description": "How `match_score` was computed. Name, date of birth, and country each contribute `_score` × `_weight_normalized` points; weights are re-normalized when a component is missing from the screened data. The document number is not weighted — it acts as a 'golden key': `MATCH` overrides the score to 100, `NEUTRAL` leaves it unchanged, `HARD_MISMATCH` applies a penalty.", + "properties": { + "name_score": { + "type": "integer", + "nullable": true, + "description": "Raw name similarity (0–100)." + }, + "name_weight": { + "type": "integer", + "nullable": true, + "description": "Configured name weight (`aml_name_weight`)." + }, + "name_weight_normalized": { + "type": "number", + "nullable": true, + "description": "Name weight after re-normalization (0–100)." + }, + "name_contribution": { + "type": "number", + "nullable": true, + "description": "Points the name contributed to `total_score`." + }, + "dob_score": { + "type": "integer", + "nullable": true, + "description": "Date-of-birth score as a percentage of its weight (−100 to 100)." + }, + "dob_weight": { + "type": "integer", + "nullable": true, + "description": "Configured DOB weight (`aml_dob_weight`)." + }, + "dob_weight_normalized": { + "type": "number", + "nullable": true, + "description": "DOB weight after re-normalization (0–100)." + }, + "dob_contribution": { + "type": "number", + "nullable": true, + "description": "Points the DOB contributed to `total_score`." + }, + "country_score": { + "type": "integer", + "nullable": true, + "description": "Country score as a percentage of its weight (−100 to 100)." + }, + "country_weight": { + "type": "integer", + "nullable": true, + "description": "Configured country weight (`aml_country_weight`)." + }, + "country_weight_normalized": { + "type": "number", + "nullable": true, + "description": "Country weight after re-normalization (0–100)." + }, + "country_contribution": { + "type": "number", + "nullable": true, + "description": "Points the country contributed to `total_score`." + }, + "document_number_match_type": { + "type": "string", + "nullable": true, + "enum": [ + "MATCH", + "NEUTRAL", + "HARD_MISMATCH" + ], + "description": "Golden-key result for the document number comparison." + }, + "document_number_effect": { + "type": "string", + "nullable": true, + "description": "Human-readable description of the document-number effect." + }, + "total_score": { + "type": "integer", + "nullable": true, + "description": "Final weighted match score (0–100) — mirrors `match_score`." + } + } + } + } + } + }, + "score": { + "type": "number", + "nullable": true, + "description": "Overall AML risk score (0–100): the highest `risk_score` among hits that are not False Positives. Drives `status` via the two thresholds." + }, + "screened_data": { + "type": "object", + "description": "Echo of what was screened: `full_name`, `date_of_birth`, `nationality`, `document_number` (absent fields are `null`).", + "properties": { + "full_name": { + "type": "string" + }, + "date_of_birth": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "document_number": { + "type": "string", + "nullable": true + } + } + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature that raised the warning." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk identifier." + }, + "additional_data": { + "type": "object", + "nullable": true, + "description": "Extra context for the warning, when available." + }, + "log_type": { + "type": "string", + "description": "`warning`, `information`, or `error` — how the risk affected the outcome." + }, + "short_description": { + "type": "string", + "description": "One-line human-readable summary." + }, + "long_description": { + "type": "string", + "description": "Full human-readable explanation." + } + } + }, + "description": "Contains a `POSSIBLE_MATCH_FOUND` warning whenever `score` is non-zero and ≥ `aml_score_approve_threshold` (a score of exactly 0 never warns); empty otherwise." + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "No hits (Approved)": { + "summary": "Clean screening — zero hits", + "value": { + "request_id": "d27c5509-7a66-45a9-b05e-d69d9f4c336a", + "aml": { + "status": "Approved", + "total_hits": 0, + "entity_type": "person", + "hits": [], + "score": 0, + "screened_data": { + "full_name": "Maximilian Mustermann-Doe", + "date_of_birth": "1992-03-14", + "nationality": "DE", + "document_number": null + }, + "warnings": [] + }, + "vendor_data": "user-1234", + "metadata": { + "internal_ref": "abc-123" + }, + "created_at": "2026-06-11T10:21:07.013938+00:00" + } + }, + "PEP hit (Approved — risk below approve threshold)": { + "summary": "One PEP possible match; risk score 73 stays under the default approve threshold of 80", + "value": { + "request_id": "0b7e7a3e-65a1-4f3d-9c7a-1de2f9a40111", + "aml": { + "status": "Approved", + "total_hits": 1, + "entity_type": "person", + "hits": [ + { + "id": "hit_7c1e9a40b2d35f86e0a4c912", + "url": "https://news.example.org/articles/sample", + "match": false, + "score": 0.93, + "match_score": 93.0, + "risk_score": 73.0, + "target": null, + "caption": "Antonio Ejemplo Modelo", + "datasets": [ + "PEP Level 1", + "PEP" + ], + "features": null, + "rca_name": "", + "first_seen": "2026-01-07T00:00:00", + "last_seen": "2026-01-07T00:00:00", + "review_status": "Unreviewed", + "properties": { + "name": [ + "Antonio Ejemplo Modelo", + "Antonio Ejemplo" + ], + "alias": [ + "Antonio Ejemplo Modelo", + "Ejemplo Modelo, Pedro" + ], + "notes": [ + "Prime Minister of Spain" + ], + "gender": [ + "male" + ], + "birthDate": [ + "1972-02-29" + ], + "birthPlace": [ + "Madrid, Spain" + ], + "nationality": null, + "position": null + }, + "risk_view": { + "crimes": { + "score": 0.0, + "weightage": 20, + "risk_level": "Low", + "risk_scores": {} + }, + "countries": { + "score": 9.64, + "weightage": 30, + "risk_level": "Low", + "risk_scores": { + "Spain": 25.71 + } + }, + "categories": { + "score": 62.5, + "weightage": 50, + "risk_level": "High", + "risk_scores": { + "PEP": 100.0, + "PEP Level 1": 100.0 + } + }, + "custom_list": {} + }, + "score_breakdown": { + "name_score": 89, + "name_weight": 60, + "name_weight_normalized": 60.0, + "name_contribution": 53.4, + "dob_score": 100, + "dob_weight": 25, + "dob_weight_normalized": 25.0, + "dob_contribution": 25.0, + "country_score": 100, + "country_weight": 15, + "country_weight_normalized": 15.0, + "country_contribution": 15.0, + "document_number_match_type": "NEUTRAL", + "document_number_effect": "No document number provided for screening", + "total_score": 93 + }, + "pep_matches": [ + { + "aliases": [], + "education": [], + "list_name": "Presidency of the Government of Spain", + "publisher": "Presidency of the Government of Spain", + "source_url": "https://news.example.org/articles/sample", + "description": "The Presidency of the Government is the central institutional body in Spain that supports the Prime Minister and coordinates the general action of the central government.", + "matched_name": "Antonio Ejemplo Modelo", + "pep_position": "", + "date_of_birth": "1972-02-29", + "other_sources": [], + "place_of_birth": "Madrid, Spain" + } + ], + "sanction_matches": [], + "warning_matches": [], + "adverse_media_matches": [], + "adverse_media_details": null, + "linked_entities": [ + { + "name": [ + "Maria Begona Gomez Fernandez" + ], + "active": [], + "status": [], + "details": [], + "relation": [ + "spouse" + ] + } + ], + "additional_information": { + "flag_summary": [ + "Antonio Ejemplo Modelo has been flagged as a Level 1 Politically Exposed Person (PEP) from Spain, serving as the President of the Government of Spain." + ] + } + } + ], + "score": 73.0, + "screened_data": { + "full_name": "Pedro Sanchez Perez-Castejon", + "date_of_birth": "1972-02-29", + "nationality": "ES", + "document_number": null + }, + "warnings": [] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-11T10:23:44.812000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems return DRF's standard envelope (one array of messages per offending field); cross-field rules (weight sum, threshold ordering, monitoring-without-save) return `{\"error\": [...]}`.", + "content": { + "application/json": { + "examples": { + "Missing full_name": { + "summary": "`full_name` is the only required field", + "value": { + "full_name": [ + "This field is required." + ] + } + }, + "Short full_name": { + "summary": "`full_name` must be at least 2 characters", + "value": { + "full_name": [ + "Ensure this field has at least 2 characters." + ] + } + }, + "Invalid nationality": { + "summary": "`nationality` must be ISO 3166-1 alpha-2", + "value": { + "nationality": [ + "Invalid ISO 3166-1 alpha-2 country code." + ] + } + }, + "Weights do not sum to 100": { + "summary": "`aml_name_weight` + `aml_dob_weight` + `aml_country_weight` ≠ 100", + "value": { + "error": [ + "AML weights must sum to 100. Current sum: 90 (name: 50, dob: 25, country: 15)" + ] + } + }, + "Thresholds out of order": { + "summary": "Approve threshold above review threshold", + "value": { + "error": [ + "Approve threshold (90.0) cannot be greater than review threshold (80.0)" + ] + } + }, + "Monitoring without saving": { + "summary": "`include_ongoing_monitoring=true` with `save_api_request=false`", + "value": { + "error": [ + "Ongoing monitoring can only be enabled when the API request is saved." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/face-search/": { + "post": { + "summary": "Face Search", + "description": "Search a face image (1:N) against your application's face search index — faces enrolled from verification sessions, saved standalone API calls, direct user-profile face uploads, and your block/allow lists — and get back ranked similarity matches plus blocklist and duplicate warnings.\n\n**How it works.** The largest detected face in `user_image` is embedded and searched against the index, scoped to your application. Up to **5 matches** above the similarity floor are returned in `matches`, each with `similarity_percentage`, the originating session (`session_id`, `session_number`, `status`, `vendor_data`), `user_details` extracted during that session's document verification, `is_blocklisted` / `is_allowlisted` flags, and a `source` discriminator (`session`, `imported`, or `list_entry`).\n\n- `search_type=most_similar` (default) ranks every enrolled face by similarity — use it for deduplication and fraud-ring investigation.\n- `search_type=blocklisted_or_approved` restricts candidates to blocklisted faces, allowlisted faces, faces from approved sessions, and imported user-profile faces, ranking blocklisted entries first — use it when the primary goal is blocklist screening.\n\n`status` is `Declined` only when a `FACE_IN_BLOCKLIST` or `POSSIBLE_FACE_IN_BLOCKLIST` warning fires; `DUPLICATED_FACE` / `POSSIBLE_DUPLICATED_FACE` (`information`) and `MULTIPLE_FACES_DETECTED` (`warning`) never decline. Unlike Passive Liveness, this endpoint does **not** exclude prior sessions with the same `vendor_data` from matching. A passive-liveness analysis also runs internally and is stored with the persisted session, but its result is not part of this response.\n\n**Billing.** Each `200` response consumes one Face Search API credit (paid from your plan credit or top-up balance). Insufficient balance returns `403` before any image processing.\n\n**Session persistence and face enrollment (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session (Business Console, `GET /v3/session/{sessionId}/decision/` via the returned `request_id`, `status.updated` webhook) and the searched face is enrolled into your face search index. Faces enrolled by Face Search calls are excluded from future Face Search results, so repeated searches never match each other. When `false`, the call is one-shot: nothing is stored, the face is not enrolled, and `match_image_url` values are internal storage paths rather than downloadable URLs.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload, no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3face-search", + "x-didit-sdk": { + "resource": "face_search", + "method": "search", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "user_image" + ], + "properties": { + "user_image": { + "type": "string", + "format": "binary", + "description": "Front-facing face image to search with. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp`. Maximum upload size: **5 MB** (larger files are rejected with `400`). Images are automatically compressed to ~0.5 MB before processing, so very high resolutions do not improve accuracy. The image must contain at least one detectable face — otherwise the endpoint returns `400`. When several faces are present, the largest one is searched and a `MULTIPLE_FACES_DETECTED` warning is added." + }, + "search_type": { + "type": "string", + "default": "most_similar", + "enum": [ + "most_similar", + "blocklisted_or_approved" + ], + "description": "Search policy. `most_similar` (default) ranks every enrolled face in your application by similarity. `blocklisted_or_approved` restricts candidates to blocklisted faces, allowlisted faces, faces from approved sessions, and imported user-profile faces — with blocklisted entries ranked first.", + "example": "most_similar" + }, + "rotate_image": { + "type": "boolean", + "default": false, + "description": "When `true`, the service tries 90-degree rotations of the input and uses the orientation that yields the best face detection. Useful when EXIF orientation is missing. Adds latency.", + "example": false + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session, emits a `status.updated` webhook, presigns `match_image_url` values, and enrolls the searched face into your face search index (Face Search enrollments are excluded from future searches). When `false`, the search is one-shot — no session is stored and the face is not enrolled.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque identifier (your user id, email, UUID…) stored on the persisted session and echoed back. It does not exclude same-user faces from the search results.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back. In multipart requests, send it as a JSON-encoded string field — it is parsed into an object.", + "example": { + "flow": "dedup_check" + } + } + } + }, + "example": { + "user_image": "(binary JPEG/PNG selfie)", + "search_type": "most_similar", + "rotate_image": false, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "dedup_check" + } + } + } + } + }, + "responses": { + "200": { + "description": "Face search completed. `face_search.matches` holds up to 5 similar faces ordered by similarity; an empty array means no enrolled face exceeded the similarity floor. `status` is `Declined` only on blocklist hits — duplicate matches alone return `Approved`, so inspect `matches` and `warnings`, not just `status`. When `save_api_request=true`, `request_id` is the persisted session id. Each match carries `source` (`session`, `imported`, `list_entry`, or `retained_template`), `vendor_user_id`, and `biometric_template_id`; a `retained_template` match comes from an image-free biometric template kept after the source session was deleted and has `null` session fields, `match_image_url`, and `user_details`.", + "content": { + "application/json": { + "examples": { + "Blocklist hit (Declined)": { + "summary": "The face matches a blocklisted face", + "value": { + "request_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef", + "face_search": { + "status": "Declined", + "total_matches": 1, + "matches": [ + { + "session_id": "882c42d5-8a4d-4d20-8080-a22f57822c86", + "session_number": 323442, + "similarity_percentage": 99.99, + "source": "session", + "vendor_data": "user-1", + "verification_date": "2025-01-01T00:00:00Z", + "user_details": { + "full_name": "Jane Marie Doe", + "document_type": "ID", + "document_number": "X1234567" + }, + "match_image_url": "https:///face/3f6a1c2e/reference.jpg", + "status": "Approved", + "is_blocklisted": true, + "is_allowlisted": false, + "api_service": null, + "vendor_user_id": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f", + "biometric_template_id": null + } + ], + "user_image": { + "entities": [ + { + "bbox": [ + 40, + 40, + 120, + 120 + ], + "confidence": 0.732973 + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "FACE_IN_BLOCKLIST", + "feature": "LIVENESS", + "additional_data": { + "blocklisted_session_id": "882c42d5-8a4d-4d20-8080-a22f57822c86", + "blocklisted_session_number": 323442, + "api_service": null + }, + "log_type": "error", + "short_description": "Face in blocklist", + "long_description": "The system identified a face in the blocklist, which means the face is not allowed to be verified." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "Duplicate found (Approved)": { + "summary": "The face already appeared in another approved session — informational, no decline", + "value": { + "request_id": "5e0c3a1f-7b2d-4c8e-9f10-2a3b4c5d6e7f", + "face_search": { + "status": "Approved", + "total_matches": 1, + "matches": [ + { + "session_id": "1f2e3d4c-5b6a-7980-1122-334455667788", + "session_number": 1024, + "similarity_percentage": 87.42, + "source": "session", + "vendor_data": "user-9", + "verification_date": "2025-11-20T09:15:00Z", + "user_details": null, + "match_image_url": "https:///face/9c8b7a6d/reference.jpg", + "status": "Approved", + "is_blocklisted": false, + "is_allowlisted": false, + "api_service": "PASSIVE_LIVENESS", + "vendor_user_id": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f", + "biometric_template_id": null + } + ], + "user_image": { + "entities": [ + { + "bbox": [ + 40, + 40, + 120, + 120 + ], + "confidence": 0.732973 + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "DUPLICATED_FACE", + "feature": "LIVENESS", + "additional_data": { + "duplicated_session_id": "1f2e3d4c-5b6a-7980-1122-334455667788", + "duplicated_session_number": 1024, + "api_service": "PASSIVE_LIVENESS" + }, + "log_type": "information", + "short_description": "Duplicated face from other approved session", + "long_description": "The system identified a duplicated face from another approved session, requiring further investigation." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "No matches (Approved)": { + "summary": "No enrolled face exceeded the similarity floor", + "value": { + "request_id": "9d8c7b6a-5f4e-3d2c-1b0a-998877665544", + "face_search": { + "status": "Approved", + "total_matches": 0, + "matches": [], + "user_image": { + "entities": [ + { + "bbox": [ + 40, + 40, + 120, + 120 + ], + "confidence": 0.732973 + } + ], + "best_angle": 0 + }, + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "face_search": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Declined` only when a `FACE_IN_BLOCKLIST` or `POSSIBLE_FACE_IN_BLOCKLIST` warning fired. Duplicate matches and multiple-face warnings never decline." + }, + "total_matches": { + "type": "integer", + "description": "Number of entries in `matches` (0–5).", + "example": 1 + }, + "matches": { + "type": "array", + "maxItems": 5, + "description": "Up to 5 faces from your face search index that exceed the similarity floor, ordered by similarity (with blocklisted entries ranked first when `search_type=blocklisted_or_approved`). Empty when no enrolled face is similar enough.", + "items": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Id of the verification session the matched face belongs to. `null` when `source` is `imported`, `list_entry`, or `retained_template`.", + "example": "882c42d5-8a4d-4d20-8080-a22f57822c86" + }, + "session_number": { + "type": "integer", + "nullable": true, + "description": "Human-friendly session number shown in the Business Console. `null` for non-session matches.", + "example": 323442 + }, + "similarity_percentage": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 100, + "description": "Similarity between the submitted face and the matched face (0–100).", + "example": 99.99 + }, + "source": { + "type": "string", + "enum": [ + "session", + "imported", + "list_entry", + "retained_template" + ], + "description": "Where the matched face was enrolled from: `session` — a verification session or a saved standalone API call; `imported` — a face uploaded directly to a User profile; `list_entry` — a face attached to one of your lists; `retained_template` — an image-free biometric template stored on a User, listed under `/v3/biometric-templates/`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of the matched session or linked User for imported and retained-template faces.", + "example": "user-1" + }, + "vendor_user_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Linked User id. `null` for list entries and session matches without a User.", + "example": "2f7c1a9e-4b28-4a6d-97f1-83576f80e1b4" + }, + "biometric_template_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Id of the retained biometric template. Set only when `source` is `retained_template`; `null` otherwise.", + "example": null + }, + "verification_date": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the matched face was originally captured, or when a retained template was created (`YYYY-MM-DDThh:mm:ssZ`). `null` for `list_entry` matches.", + "example": "2025-01-01T00:00:00Z" + }, + "user_details": { + "type": "object", + "nullable": true, + "description": "Identity data extracted during the matched session's document verification. `null` when the matched session has no document data (e.g. liveness-only sessions) and always `null` for `retained_template` matches. For `source: imported` matches it is populated from the User profile instead: `full_name` set, `document_type`/`document_number` null.", + "properties": { + "full_name": { + "type": "string", + "nullable": true, + "example": "Jane Marie Doe" + }, + "document_type": { + "type": "string", + "nullable": true, + "example": "ID" + }, + "document_number": { + "type": "string", + "nullable": true, + "example": "X1234567" + } + } + }, + "match_image_url": { + "type": "string", + "nullable": true, + "description": "Time-limited URL (`https:///...`) of the matched face's reference image when `save_api_request=true`. With `save_api_request=false` this is an internal storage path that is not directly downloadable. Always `null` for `retained_template` matches because retained templates contain no image.", + "example": "https:///face/3f6a1c2e/reference.jpg" + }, + "status": { + "type": "string", + "nullable": true, + "description": "Status of the matched session (`Approved`, `Declined`, `In Review`, …). `null` for `imported`/`list_entry` matches.", + "example": "Approved" + }, + "is_blocklisted": { + "type": "boolean", + "description": "`true` when the matched face is on your face blocklist. Any sufficiently similar blocklisted match declines the search." + }, + "is_allowlisted": { + "type": "boolean", + "description": "`true` when the matched face is on one of your allowlists. Allowlisted matches suppress duplicate warnings." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "Set when the matched face was enrolled by a standalone API call (e.g. `PASSIVE_LIVENESS`); `null` for regular verification sessions and imported faces.", + "example": null + } + } + } + }, + "user_image": { + "type": "object", + "description": "Face-detection results for the submitted image. Entries here carry only `bbox` and `confidence`.", + "properties": { + "entities": { + "type": "array", + "description": "One entry per detected face. The largest face is the one searched.", + "items": { + "type": "object", + "properties": { + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box `[x_min, y_min, x_max, y_max]` in pixels.", + "example": [ + 40, + 40, + 120, + 120 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.732973 + } + } + } + }, + "best_angle": { + "type": "integer", + "description": "Rotation (degrees) that produced the best face detection; non-zero only when `rotate_image=true` corrected the orientation. Never null on this endpoint (defaults to 0 when the model omits it).", + "example": 0 + } + } + }, + "warnings": { + "type": "array", + "description": "Risk signals. `FACE_IN_BLOCKLIST` / `POSSIBLE_FACE_IN_BLOCKLIST` (`error`) decline the search; `MULTIPLE_FACES_DETECTED` (`warning`) and `DUPLICATED_FACE` / `POSSIBLE_DUPLICATED_FACE` (`information`) are advisory.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "MULTIPLE_FACES_DETECTED", + "FACE_IN_BLOCKLIST", + "POSSIBLE_FACE_IN_BLOCKLIST", + "DUPLICATED_FACE", + "POSSIBLE_DUPLICATED_FACE" + ], + "description": "Machine-readable risk code." + }, + "feature": { + "type": "string", + "enum": [ + "LIVENESS" + ], + "description": "Feature that raised the warning. Always `LIVENESS` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "`null` for `MULTIPLE_FACES_DETECTED`. Blocklist hits carry `{blocklisted_session_id, blocklisted_session_number, api_service}`; duplicate hits carry `{duplicated_session_id, duplicated_session_number, api_service}` pointing at the first matching session." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `warning` and `information` entries are advisory and never decline on their own." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated, e.g. `2026-06-12T01:04:42.763237+00:00`." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when `user_image` is missing, exceeds 5 MB, has an unsupported extension, or when **no face is detected** in the submitted image. Field errors use DRF's `{field: [messages]}` envelope; face-detection failures use `{\"error\": ...}`.", + "content": { + "application/json": { + "examples": { + "No face detected": { + "summary": "The image decodes fine but contains no detectable face", + "value": { + "error": "No face detected in the image" + } + }, + "Missing image": { + "summary": "`user_image` not included in the form data", + "value": { + "user_image": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp", + "value": { + "user_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 5 MB limit", + "value": { + "user_image": [ + "File size should not exceed 5 MB" + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/face-search/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'user_image=@./selfie.jpg' \\\n -F 'search_type=most_similar' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.face_search.search(\n user_image=open(\"user_image.jpg\", \"rb\"),\n search_type=\"most_similar\",\n rotate_image=False,\n save_api_request=True,\n vendor_data=\"user-123\",\n metadata={\"flow\": \"dedup_check\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.faceSearch.search({\n user_image: await fileFromPath(\"user_image.jpg\"),\n search_type: \"most_similar\",\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"dedup_check\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.FaceSearch.Search(ctx, didit.FaceSearchSearchParams{\n\tUserImage: didit.FileFromPath(\"user_image.jpg\"),\n\tSearchType: didit.Some(didit.FaceSearchSearchParamsSearchType(\"most_similar\")),\n\tRotateImage: didit.Bool(false),\n\tSaveAPIRequest: didit.Bool(true),\n\tVendorData: didit.String(\"user-123\"),\n\tMetadata: didit.Some(map[string]any{\"flow\": \"dedup_check\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.FaceSearchSearchParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.faceSearch().search(FaceSearchSearchParams.builder()\n .userImage(Path.of(\"user_image.jpg\"))\n .searchType(FaceSearchSearchParams.SearchType.of(\"most_similar\"))\n .rotateImage(false)\n .saveApiRequest(true)\n .vendorData(\"user-123\")\n .metadata(Map.of(\"flow\", \"dedup_check\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "faceSearch->search([\n 'user_image' => File::fromPath('user_image.jpg'),\n 'search_type' => 'most_similar',\n 'rotate_image' => false,\n 'save_api_request' => true,\n 'vendor_data' => 'user-123',\n 'metadata' => ['flow' => 'dedup_check'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.face_search.search(\n user_image: Pathname(\"user_image.jpg\"),\n search_type: \"most_similar\",\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"dedup_check\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.FaceSearch.SearchAsync(new FaceSearchSearchParams\n{\n UserImage = FileUpload.FromPath(\"user_image.jpg\"),\n SearchType = new FaceSearchSearchParamsSearchType(\"most_similar\"),\n RotateImage = false,\n SaveApiRequest = true,\n VendorData = \"user-123\",\n Metadata = new Dictionary { [\"flow\"] = \"dedup_check\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/sessions/": { + "get": { + "summary": "List verification sessions (KYC and/or KYB) with filters and pagination", + "description": "Returns a paginated list of verification sessions for the application identified by your API key, newest first (descending `session_number`).\n\n**Canonical URL:** the route is `/v3/sessions/` (trailing slash). Requests to `/v3/sessions` without the slash receive a `301` redirect to the slashed URL with the query string preserved; most HTTP clients follow it transparently on GET.\n\n**When to use this endpoint vs webhooks.** Use this endpoint for dashboards, back-office search, reconciliation, and batch exports. Do not poll it to track the progress of individual verifications - configure [webhooks](/integration/webhooks) instead, which push `status.updated` events the moment a session changes. For one session's full result, call `GET /v3/session/{sessionId}/decision/`.\n\n**KYC and KYB sessions.** `session_kind` selects which kinds are listed:\n- `user` (default) - User Verification (KYC) sessions only. Backward-compatible behavior.\n- `business` - Business Verification (KYB) sessions only. Rows have a different, smaller shape (see the response schema).\n- `all` - both kinds in one response: the KYC page and the KYB page for the same `limit`/`offset` are concatenated in `results` (KYC rows first), so a page can contain up to `2 x limit` rows. `count` is the sum of both kinds and `next`/`previous` reflect the KYC-side pagination. For strict pagination of mixed data, call `user` and `business` separately.\n\nEvery row carries a `session_kind` discriminator (`\"user\"` or `\"business\"`).\n\n**Pagination.** Standard limit/offset: `limit` (default 50) and `offset` (default 0), with `next`/`previous` ready-made page URLs. For KYC rows `count` is exact. For KYB rows `count` is computed with a bounded scan and is capped at 100 - when there are more than 100 matches it stays at 100, so iterate using `next` (or by checking whether a full page came back) rather than computing total pages from `count`.\n\n**Filter semantics.** All filters combine with AND and apply to KYC rows. When `session_kind` is `business` or `all`, the KYB side honors the shared parameters `vendor_data`, `status`, `workflow_id`, `country` (registry-company country code), `date_from`, `date_to`, `search` (company name, registration number — type registration numbers without separators, e.g. `DPC001` not `DPC-001` — vendor_data, session number, or session UUID), `warning`, and `organization_name`; the remaining KYC-only filters are ignored for KYB rows. Comma-separated parameters (`status`, `country`, `document_type`, `region`, `workflow_id`, `document_name`, `warning`, `organization_name`, `session_type`) match any of the listed values (OR within the parameter). `vendor_data` is an exact match - use `search` for partial matching. Unknown values in list filters simply return no rows (no error); malformed typed values (`date_from`, `date_to`, `didit_internal_id`, `api_service`, `session_kind`) return `400` with a per-field error object.\n\n**Search.** `search` adapts to the input: a full UUID looks up `session_id`; digits (with optional `+ - . space`) match session-number prefix, document/personal numbers, phone-number prefix, or `vendor_data`; values containing `@` match email prefixes; any other text matches names (accent-insensitive) and `vendor_data`.\n\nNo fixed per-endpoint rate limit is enforced, but keep list polling infrequent and rely on webhooks for real-time updates.", + "operationId": "get_v3_sessions", + "x-didit-sdk": { + "resource": "sessions", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "session_kind", + "in": "query", + "required": false, + "description": "Which kind of sessions to list: `user` (KYC only, default), `business` (KYB only), or `all` (both, concatenated - see the description for merged-pagination semantics). Matching is case-insensitive (`BUSINESS` works); any value other than a case variant of `user`/`business`/`all` returns `400`.", + "schema": { + "type": "string", + "enum": [ + "user", + "business", + "all" + ], + "default": "user" + }, + "example": "all" + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of rows per page (per kind when `session_kind=all`). Defaults to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "example": 20 + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of rows to skip before the first returned row. Defaults to 0. Prefer following the `next`/`previous` URLs from the response.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "example": 20 + }, + { + "name": "status", + "in": "query", + "required": false, + "description": "Comma-separated list of session statuses to include, e.g. `Approved,In Review`. Values are case-sensitive; see [Verification Statuses](/integration/verification-statuses). Unknown values match nothing.", + "schema": { + "type": "string" + }, + "example": "Approved,In Review" + }, + { + "name": "vendor_data", + "in": "query", + "required": false, + "description": "Exact-match filter on the `vendor_data` you sent at session creation (your own user/business identifier). For partial matches use `search` instead.", + "schema": { + "type": "string" + }, + "example": "your-user-id-123" + }, + { + "name": "search", + "in": "query", + "required": false, + "description": "A complete displayed reference such as `S-0001` (user) or `B-0001` (business) matches only that session number and kind; leading zeros and letter case do not affect matching. Smart free-text search. A full UUID matches `session_id`; numeric input matches session-number prefix, document/personal numbers, phone prefix, or `vendor_data`; input containing `@` matches email prefixes; other text matches names (accent-insensitive) and `vendor_data`. Free text containing digits additionally matches document/personal-number search fields and session-number prefixes.", + "schema": { + "type": "string" + }, + "example": "jane doe" + }, + { + "name": "workflow_id", + "in": "query", + "required": false, + "description": "Comma-separated workflow UUIDs (find them on the Workflows page in the Console). Malformed UUIDs in the list are ignored; if none are valid the result is empty.", + "schema": { + "type": "string" + }, + "example": "9f9b1234-aaaa-bbbb-cccc-1234567890ab" + }, + { + "name": "date_from", + "in": "query", + "required": false, + "description": "Only sessions created on or after this date (UTC), format `YYYY-MM-DD`. Malformed dates return `400`.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-06-01" + }, + { + "name": "date_to", + "in": "query", + "required": false, + "description": "Only sessions created up to this date (UTC), format `YYYY-MM-DD`. For KYC rows the entire day is included; for KYB rows the comparison is `created_at <= date_to` (midnight), so use the next day's date to include a full day of KYB sessions.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-06-30" + }, + { + "name": "tags", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated tag ids (`tags` of your application). Returns the sessions that carry any of them. Ids that are not valid UUIDs are ignored, and when none is valid the list is empty." + }, + { + "name": "decided_from", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + }, + "description": "ISO 8601 datetime. Returns the sessions whose status last changed at or after it. It filters on the time of the last status change, not on creation time." + }, + { + "name": "decided_to", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + }, + "description": "ISO 8601 datetime. Returns the sessions whose status last changed before it. Combine with `decided_from` for a half-open window." + }, + { + "name": "country", + "in": "query", + "required": false, + "description": "Comma-separated country filter. For KYC rows: ISO 3166-1 alpha-3 codes of the ID document's issuing state (e.g. `ESP,USA`), which may differ from nationality. For KYB rows: the registry company's country code.", + "schema": { + "type": "string" + }, + "example": "ESP,USA" + }, + { + "name": "document_type", + "in": "query", + "required": false, + "description": "Comma-separated ID-document type codes verified in the session (KYC rows only): `P` (Passport), `ID` (Identity Card), `DL` (Driver's License), `RP` (Residence Permit), `SSC` (Social Security Card), `HIC` (Health Insurance Card), `WP` (Work Permit), `TC` (Tax Card), `VISA`, `PSC` (Public Service Card), `BC` (Birth Certificate), `OTHER`. Note the response field `document_type` contains the display name, not the code.", + "schema": { + "type": "string" + }, + "example": "P,ID" + }, + { + "name": "document_name", + "in": "query", + "required": false, + "description": "Exact document names; multiple names can be passed comma-separated, but the value is only split on commas when it contains more than one ` - ` separator (a lone name with an embedded comma is matched whole). Names are as recognized by document verification (front or back side), e.g. `Spain - Id Card (2021)`. KYC rows only.", + "schema": { + "type": "string" + }, + "example": "Spain - Id Card (2021)" + }, + { + "name": "region", + "in": "query", + "required": false, + "description": "Comma-separated document region/state values (for documents issued per region, such as US driver's licenses). KYC rows only.", + "schema": { + "type": "string" + }, + "example": "CA,TX" + }, + { + "name": "didit_internal_id", + "in": "query", + "required": false, + "description": "Filter by Didit's internal user identifier (the `didit_internal_id` response field) to list all sessions of one verified user. Must be a valid UUID; malformed values return `400`. KYC rows only.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147" + }, + { + "name": "session_type", + "in": "query", + "required": false, + "description": "Comma-separated session creation channels: `API`, `HOSTED`, `MIGRATED`. The special value `ALL` (or omitting the parameter) disables the filter. KYC rows only.", + "schema": { + "type": "string" + }, + "example": "HOSTED" + }, + { + "name": "api_service", + "in": "query", + "required": false, + "description": "Filter standalone API sessions by service. Invalid values return `400`. KYC rows only.", + "schema": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "FACE_MATCH", + "AGE_ESTIMATION", + "FACE_SEARCH", + "POA", + "AML", + "KYB_REGISTRY", + "PASSIVE_LIVENESS", + "DATABASE_VALIDATION", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION" + ] + }, + "example": "AML" + }, + { + "name": "screened_full_name", + "in": "query", + "required": false, + "description": "Accent-insensitive name search inside AML and database-validation screened data. When combined with `api_service=AML` or `api_service=DATABASE_VALIDATION`, the search is restricted to that feature's screened data. KYC rows only.", + "schema": { + "type": "string" + }, + "example": "Jane Doe" + }, + { + "name": "warning", + "in": "query", + "required": false, + "description": "Comma-separated warning risk codes; returns sessions that raised any of them (e.g. `POSSIBLE_FRAUD`). Risk codes are listed per feature in the [warnings documentation](/integration/webhooks). Applies to KYC rows and, with business warning codes, to KYB rows.", + "schema": { + "type": "string" + }, + "example": "POSSIBLE_FRAUD" + }, + { + "name": "status_initialized_as_in_review", + "in": "query", + "required": false, + "description": "When `true`, only sessions whose decision initially landed in `In Review` (useful to audit manual-review volume). KYC rows only.", + "schema": { + "type": "boolean" + }, + "example": true + }, + { + "name": "is_kyc_to_be_reviewed", + "in": "query", + "required": false, + "description": "`true` returns sessions whose document verification is flagged for manual review; `false` returns sessions with document verification explicitly not flagged. Omit to disable. KYC rows only.", + "schema": { + "type": "boolean" + }, + "example": true + }, + { + "name": "euuid", + "in": "query", + "required": false, + "description": "Filter by the end user's euuid (UUID assigned to the verifying user, used by Console deep links). Must be a valid UUID; malformed values return a 400 field error. KYC rows only.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "0f8fad5b-d9cb-469f-a165-70867728950e" + }, + { + "name": "organization_name", + "in": "query", + "required": false, + "description": "Comma-separated organization names. Console-oriented; with an API key (already scoped to one application) it only matches your own organization's name.", + "schema": { + "type": "string" + }, + "example": "Acme Inc" + } + ], + "responses": { + "200": { + "description": "Paginated session list. `results` items are KYC rows, KYB rows, or both depending on `session_kind`; discriminate with each row's `session_kind` field.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Number of sessions matching the filters: exact up to 100 and capped at 100 beyond that, so do not use it as a total. Use `next` to know whether more pages exist. For `session_kind=all` it is the sum of both kinds, each capped at 100.", + "example": 2 + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page.", + "example": "https://verification.didit.me/v3/sessions/?limit=20&offset=20" + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page.", + "example": null + }, + "results": { + "type": "array", + "description": "Session rows, newest first. With `session_kind=all`, KYC rows come first, then KYB rows.", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/UserSessionListItem" + }, + { + "$ref": "#/components/schemas/BusinessSessionListItem" + } + ], + "discriminator": { + "propertyName": "session_kind", + "mapping": { + "user": "#/components/schemas/UserSessionListItem", + "business": "#/components/schemas/BusinessSessionListItem" + } + } + } + } + } + }, + "examples": { + "KYC sessions (default)": { + "summary": "session_kind=user - one completed and one not-started session", + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "session_id": "5b3720ed-d429-42ef-b67f-37ea805f48ee", + "session_kind": "user", + "session_number": 720, + "session_url": "https://verify.didit.me/session/CuXoBYCYVO5Y", + "portrait_image": "https:///portraits/5b3720ed.jpg?X-Amz-Expires=3600&X-Amz-Signature=...", + "document_type": "Passport", + "full_name": "Jane Marie Doe", + "country": "ESP", + "status": "Approved", + "vendor_data": "your-user-id-123", + "didit_internal_id": "d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147", + "created_at": "2026-06-09T12:26:54.328364Z", + "features": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + }, + { + "feature": "IP_ANALYSIS", + "status": "Approved" + } + ], + "phone_number": { + "number": "+14155552671", + "is_verified": true + }, + "email_address": { + "email": "jane@example.com", + "is_verified": true + }, + "additional_details": { + "aml": { + "total_hits": 0, + "entity_type": "person", + "is_adverse_media_fetched": false + }, + "liveness": { + "method": "PASSIVE", + "score": 0.98 + }, + "face_match": { + "score": 0.95 + } + } + }, + { + "session_id": "038ad62f-5ebd-46c2-afe9-4b2956bf687f", + "session_kind": "user", + "session_number": 719, + "session_url": "https://verify.didit.me/session/7RGKiO6ioGH6", + "portrait_image": null, + "document_type": null, + "full_name": null, + "country": null, + "status": "Not Started", + "vendor_data": "your-user-id-124", + "didit_internal_id": "eb4e679d-ca4b-461f-9685-687bac29ebdb", + "created_at": "2026-06-09T08:02:23.343053Z", + "features": [ + { + "feature": "ID_VERIFICATION", + "status": "Not Finished" + }, + { + "feature": "LIVENESS", + "status": "Not Finished" + }, + { + "feature": "FACE_MATCH", + "status": "Not Finished" + }, + { + "feature": "AML", + "status": "Not Finished" + }, + { + "feature": "IP_ANALYSIS", + "status": "Not Finished" + } + ], + "phone_number": null, + "email_address": null, + "additional_details": null + } + ] + } + }, + "Mixed KYC + KYB (session_kind=all)": { + "summary": "session_kind=all - KYC row followed by a KYB row", + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "session_id": "5b3720ed-d429-42ef-b67f-37ea805f48ee", + "session_kind": "user", + "session_number": 720, + "session_url": "https://verify.didit.me/session/CuXoBYCYVO5Y", + "portrait_image": null, + "document_type": "Identity Card", + "full_name": "Jane Marie Doe", + "country": "ESP", + "status": "In Review", + "vendor_data": "your-user-id-123", + "didit_internal_id": "d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147", + "created_at": "2026-06-09T12:26:54.328364Z", + "features": [ + { + "feature": "ID_VERIFICATION", + "status": "In Review" + }, + { + "feature": "LIVENESS", + "status": "Approved" + } + ], + "phone_number": null, + "email_address": null, + "additional_details": { + "liveness": { + "method": "PASSIVE", + "score": 0.98 + } + } + }, + { + "session_id": "8a1f63c2-1b9e-4f3a-9c41-2f6a1f0b7d52", + "session_kind": "business", + "session_number": 42, + "status": "Approved", + "vendor_data": "your-company-id-77", + "created_at": "2026-06-08T09:14:02.118450Z", + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "workflow_type": "kyb", + "workflow_label": "Standard KYB", + "company_name": "Acme Holdings S.L.", + "registration_number": "B12345678", + "country": "ES" + } + ] + } + } + } + } + } + }, + "301": { + "description": "Redirect to the canonical trailing-slash URL. Returned when the request path is `/v3/sessions` (no trailing slash); the `Location` header points to `/v3/sessions/` with the query string preserved. GET clients typically follow it automatically." + }, + "400": { + "description": "Invalid query parameter. The body is an object keyed by the offending parameter.", + "content": { + "application/json": { + "examples": { + "Invalid session_kind": { + "summary": "session_kind not one of user/business/all", + "value": { + "session_kind": "Must be \"user\", \"business\", or \"all\"." + } + }, + "Invalid date": { + "summary": "Malformed date_from/date_to", + "value": { + "date_from": [ + "Enter a valid date." + ] + } + }, + "Invalid didit_internal_id": { + "summary": "didit_internal_id is not a UUID", + "value": { + "didit_internal_id": [ + "Enter a valid UUID." + ] + } + }, + "Invalid api_service": { + "summary": "api_service not in the allowed choices", + "value": { + "api_service": [ + "Select a valid choice. FOO is not one of the available choices." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, malformed, or revoked API key. A missing key returns `403`; a wrong, revoked or expired key returns `401`.\n\n**Deleted application:** for a few minutes after an application is deleted, a request made with one of its API keys can answer `{\"detail\": \"This application was deleted.\", \"code\": \"application_deleted\"}`. Do not retry: the application and its API keys no longer exist. After those few minutes, the key is refused like any other invalid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You do not have permission to perform this action." + }, + "code": { + "type": "string", + "description": "Machine-readable reason, present only on some failures. `application_deleted`: the API key belongs to an application that was deleted.", + "example": "application_deleted" + } + }, + "required": [ + "detail" + ] + }, + "examples": { + "Forbidden": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "ApplicationDeleted": { + "summary": "API key of a deleted application", + "value": { + "detail": "This application was deleted.", + "code": "application_deleted" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -s 'https://verification.didit.me/v3/sessions/?status=Approved,In%20Review&date_from=2026-06-01&limit=20' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.sessions.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.sessions.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Sessions.ListAutoPaging(ctx, didit.SessionsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.sessions().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->list() as $item) {\n print_r($item);\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Sessions.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/session/{sessionId}/share/": { + "post": { + "summary": "Mint a share token for a finished verification session", + "description": "Mint a short-lived JWT that lets a specific Didit application import this finished session. Pair with `POST /v3/session/import-shared/`.", + "operationId": "post_v3_session_share", + "x-didit-sdk": { + "resource": "sessions", + "method": "share", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "200": { + "description": "Share token minted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "share_token": { + "type": "string", + "description": "HS256-signed JWT. Pass this verbatim as `share_token` to [`POST /v3/session/import-shared/`](/sessions-api/import-shared-session) on the target application.", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + }, + "for_application_id": { + "type": "string", + "format": "uuid", + "description": "Echoes the target application UUID that the token is bound to.", + "example": "a5f3bca2-46e2-411e-90ef-a580900a57ee" + }, + "session_kind": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Whether the source session is a User Verification (KYC) or Business Verification (KYB) session." + } + } + }, + "examples": { + "User session": { + "summary": "Share a KYC session", + "value": { + "share_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uX2lkIjoiMjBiYjNjM2ItNjE0Ni00MzlmLTg0YTQtYmQzMGQwMGFjNmEyIiwiZnJvbV9hcHBsaWNhdGlvbl9pZCI6ImRiZDIwZTM0LTQyZTktNGYyYy1iYTkxLWNmMDc2MjAxNmY2NCIsImZvcl9hcHBsaWNhdGlvbl9pZCI6ImE1ZjNiY2EyLTQ2ZTItNDExZS05MGVmLWE1ODA5MDBhNTdlZSIsImlhdCI6MTc1MzYzMDY2NiwiZXhwIjoxNzUzNjM0MjY2fQ.JJ9pNE_hqZsOtbR0XYZIWw4JzidjdEl279iUrsIkhGE", + "for_application_id": "a5f3bca2-46e2-411e-90ef-a580900a57ee", + "session_kind": "user" + } + }, + "Business session": { + "summary": "Share a KYB session", + "value": { + "share_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "for_application_id": "a5f3bca2-46e2-411e-90ef-a580900a57ee", + "session_kind": "business" + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials. Unlike most v3 endpoints, this endpoint authenticates through the permission decorator and returns `401` when no valid token/key is presented.", + "content": { + "application/json": { + "examples": { + "Unauthorized": { + "summary": "Unauthorized", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "404": { + "description": "No session with the given `session_id` exists in your application.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Session not found", + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field errors are keyed by field name; the not-finished error arrives under a `detail` key (as an array).", + "content": { + "application/json": { + "examples": { + "Wrong status": { + "summary": "Source session is not finished", + "value": { + "detail": [ + "Only finished sessions (\"Approved\", \"Declined\", \"In Review\") can be shared." + ] + } + }, + "Target missing": { + "summary": "Target application does not exist", + "value": { + "for_application_id": [ + "Target application does not exist." + ] + } + }, + "Self-share": { + "summary": "Target is the calling application", + "value": { + "for_application_id": [ + "Cannot share a session with the same application." + ] + } + }, + "TTL bounds": { + "summary": "ttl_in_seconds outside bounds", + "value": { + "ttl_in_seconds": [ + "Ensure this value is greater than or equal to 60." + ] + } + } + } + } + } + }, + "403": { + "description": "The credentials are valid but lack the `write:sessions` permission for this application.", + "content": { + "application/json": { + "examples": { + "No Permission": { + "value": { + "detail": "You do not have permission to perform this action." + }, + "summary": "No Permission" + } + } + } + } + } + }, + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the source verification session to mint a token for.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "for_application_id" + ], + "properties": { + "for_application_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the Didit application that will redeem the token. Must exist, must not be deleted, and must differ from the calling application. Find it in the Business Console under **Settings → Application**.", + "example": "a5f3bca2-46e2-411e-90ef-a580900a57ee" + }, + "ttl_in_seconds": { + "type": "integer", + "description": "Token lifetime, in seconds. Minimum `60`, maximum `86400` (24 h). Defaults to `3600` (1 h).", + "minimum": 60, + "maximum": 86400, + "default": 3600, + "example": 3600 + } + } + }, + "example": { + "for_application_id": "a5f3bca2-46e2-411e-90ef-a580900a57ee", + "ttl_in_seconds": 7200 + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/share/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"for_application_id\": \"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n \"ttl_in_seconds\": 7200\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.share(\n \"11111111-2222-3333-4444-555555555555\",\n for_application_id=\"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n ttl_in_seconds=7200,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.share(\"11111111-2222-3333-4444-555555555555\", {\n for_application_id: \"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n ttl_in_seconds: 7200,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Share(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsShareParams{\n\tForApplicationID: \"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n\tTTLInSeconds: didit.Int(7200),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsShareParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().share(\"11111111-2222-3333-4444-555555555555\", SessionsShareParams.builder()\n .forApplicationId(\"a5f3bca2-46e2-411e-90ef-a580900a57ee\")\n .ttlInSeconds(7200L)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->share('11111111-2222-3333-4444-555555555555', [\n 'for_application_id' => 'a5f3bca2-46e2-411e-90ef-a580900a57ee',\n 'ttl_in_seconds' => 7200,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.share(\n \"11111111-2222-3333-4444-555555555555\",\n for_application_id: \"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n ttl_in_seconds: 7200\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ShareAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsShareParams\n{\n ForApplicationId = \"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n TtlInSeconds = 7200,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/import-shared/": { + "post": { + "summary": "Redeem a share token to clone a verification session into your application", + "description": "Redeem a share token to clone a KYC or KYB session into the calling application with a fresh `session_id`. Not idempotent — a token redeems once per receiver.", + "operationId": "post_v3_session_import_shared", + "x-didit-sdk": { + "resource": "sessions", + "method": "import_shared", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "201": { + "description": "Session cloned. The body is the V3 decision payload for both kinds, discriminated by `session_kind`: `\"user\"` (KYC) with list-based feature blocks such as `id_verifications`, `liveness_checks`, `aml_screenings`; `\"business\"` (KYB) with blocks like `registry_checks` and `key_people_checks`. Both include `shared_from_session` — the `session_id` of the original session in the source application. With `trust_review: false` the cloned session's `status` is forced to `In Review`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionDecision" + }, + "examples": { + "User session imported": { + "summary": "Cloned KYC session (truncated — full V3 decision payload in reality)", + "value": { + "session_id": "11111111-2222-3333-4444-555555555555", + "session_kind": "user", + "shared_from_session": "99999999-8888-7777-6666-555555555555", + "session_number": 43762, + "session_url": null, + "status": "In Review", + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "vendor_data": "user-1", + "created_at": "2026-05-17T08:42:11Z", + "expires_at": "2026-05-24T08:42:11Z" + } + }, + "Business session imported": { + "summary": "Cloned KYB session (truncated — full V3 KYB decision payload in reality)", + "value": { + "session_id": "22222222-3333-4444-5555-666666666666", + "session_kind": "business", + "shared_from_session": "88888888-7777-6666-5555-444444444444", + "session_number": 982, + "session_url": null, + "status": "Approved", + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "vendor_data": "company-1" + } + } + } + } + } + }, + "400": { + "description": "Validation error (token invalid, expired, wrong target, or references a session that no longer exists).", + "content": { + "application/json": { + "examples": { + "Invalid Share Token": { + "summary": "Invalid JWT", + "value": { + "share_token": [ + "Invalid share token." + ] + } + }, + "Expired Share Token": { + "summary": "Token past `exp`", + "value": { + "share_token": [ + "Share token has expired." + ] + } + }, + "Wrong audience": { + "summary": "Token not bound to this application", + "value": { + "share_token": [ + "This token is not valid for this application." + ] + } + }, + "Original missing": { + "summary": "Source session was deleted", + "value": { + "share_token": [ + "Original session does not exist." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing API key (a wrong, revoked or expired key gets `401`), or this source session has already been imported into the calling application (one redeem per receiver).", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "summary": "Forbidden", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Already imported": { + "summary": "Source already imported once", + "value": { + "detail": "This session has already been shared with your application." + } + } + } + } + } + }, + "404": { + "description": "`workflow_id` does not belong to the calling application.", + "content": { + "application/json": { + "examples": { + "Workflow not found": { + "summary": "Workflow unknown", + "value": { + "detail": "Workflow does not exist for this application." + } + } + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "share_token", + "trust_review", + "workflow_id" + ], + "properties": { + "share_token": { + "type": "string", + "description": "JWT share token issued by [`POST /v3/session/{sessionId}/share/`](/sessions-api/share-session).", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + }, + "trust_review": { + "type": "boolean", + "description": "If `true`, the cloned session keeps the source's final `status`. If `false`, it is forced into `In Review`.", + "example": false + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "UUID of a workflow in the calling application. Cross-application IDs are rejected with `404`.", + "example": "9f9b1234-aaaa-bbbb-cccc-1234567890ab" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Optional override for the cloned session's `vendor_data`.", + "example": "user-1" + } + } + }, + "example": { + "share_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "trust_review": false, + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "vendor_data": "user-1" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/import-shared/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"share_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n \"trust_review\": false,\n \"workflow_id\": \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n \"vendor_data\": \"user-1\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.import_shared(\n share_token=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n trust_review=False,\n workflow_id=\"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n vendor_data=\"user-1\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.importShared({\n share_token: \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n trust_review: false,\n workflow_id: \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n vendor_data: \"user-1\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.ImportShared(ctx, didit.SessionsImportSharedParams{\n\tShareToken: \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n\tTrustReview: false,\n\tWorkflowID: \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n\tVendorData: didit.String(\"user-1\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsImportSharedParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().importShared(SessionsImportSharedParams.builder()\n .shareToken(\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\")\n .trustReview(false)\n .workflowId(\"9f9b1234-aaaa-bbbb-cccc-1234567890ab\")\n .vendorData(\"user-1\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->importShared([\n 'share_token' => 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',\n 'trust_review' => false,\n 'workflow_id' => '9f9b1234-aaaa-bbbb-cccc-1234567890ab',\n 'vendor_data' => 'user-1',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.import_shared(\n share_token: \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n trust_review: false,\n workflow_id: \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n vendor_data: \"user-1\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ImportSharedAsync(new SessionsImportSharedParams\n{\n ShareToken = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n TrustReview = false,\n WorkflowId = \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n VendorData = \"user-1\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/kyb/search/": { + "post": { + "summary": "KYB registry company search (standalone)", + "operationId": "post_v3_kyb_search", + "x-didit-sdk": { + "resource": "kyb", + "method": "search", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Search official company registries for candidate companies by name and/or registration number. This is step 1 of the two-step registry flow: **search** returns lightweight candidates, then `POST /v3/kyb/select/` retrieves the full profile of one candidate.\n\n**Search billing.** Successful searches using a paid company-data source cost USD 0.50 per request, regardless of candidate count. Empty results, failures and results served only by other sources are not charged. The applicable price is reserved before searching; insufficient balance returns 403 with code `insufficient_credits`. Contract rates apply where agreed, and an unconfigured contract search rate returns 403. Sandbox searches are free. Repeating a successful search can incur another charge; callback delivery does not add a charge. Profile retrieval is billed separately. This search fee also applies to v2. Candidates always come back with `fetch_status: \"pending\"`: the full company data is only fetched (and billed) on select.\n\n**`kyb_response_id` is ephemeral.** It is a per-search handle, not a stable company id. Select promptly after searching and re-search to get a fresh handle instead of storing it.\n\n**`tier` picks the provider chain.** `tier` names the registry tier the search runs on: `basic` (the default, the Lite chain), `shareholders` or `ubo`. The search uses the provider chain that serves that tier, and it is not always the same chain the Lite search reads, so an empty Lite search is not proof that the registry does not hold the company — search again at the tier you need. A country that does not offer the tier you name is searched at the highest tier it offers up to that one. Retrieve the candidate from the endpoint that sells that tier: `POST /v3/kyb/select/` (Lite), `POST /v3/kyb/shareholders/` or `POST /v3/kyb/ubo/`.\n\n**Authentication.** `POST /v3/kyb/search/` (and `/v2/`) takes your application's API key in the `x-api-key` header. The hosted `POST /v1/kyb/search/` is the session-authenticated route a hosted verification flow calls: it answers `403` to an API key and takes no `tier` — the workflow's KYB registry configuration picks the chain.\n\n**Two modes.**\n- *Synchronous (default):* the API polls the registry until the search resolves and returns the final candidate list (`search_resolved: true`).\n- *Asynchronous:* pass `webhook_url` and the API returns immediately — usually with `search_status: \"pending\"` and an empty candidate list — then POSTs a `kyb.registry_search.resolved` callback to your URL when the registry finishes. The callback body is `{\"event_id\", \"event_type\": \"kyb.registry_search.resolved\", \"request_id\", \"vendor_data\", \"metadata\", \"search_status\", \"search_resolved\", \"kyb_registry\", \"timestamp\", \"created_at\"}` — note `timestamp` and `created_at` in the callback are epoch integers, unlike this endpoint's ISO `created_at` — and is sent **unsigned** (header `X-Didit-Unsigned-Callback: true`, no `X-Signature`).\n\n**Country codes.** ISO 3166-1 alpha-2 (`GB`, `DE`), plus `XX-YY` subdivision codes where registries are regional — e.g. `US-CA` for California.\n\n**Sandbox.** Keys from sandbox applications return one static candidate without contacting any registry.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "country_code" + ], + "properties": { + "country_code": { + "type": "string", + "maxLength": 10, + "description": "ISO 3166-1 alpha-2 code of the country whose company registry to search. For example, `GB`. Where company registries operate at state or province level, append the ISO 3166-2 subdivision code as `XX-YY`: `US-CA` searches the California registry. United States searches always require the subdivision — a bare `US` is rejected. Case-insensitive; normalized to upper case.", + "example": "GB" + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "Company name to search for. Either `name` or `registration_number` is required.", + "example": "Tesco" + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Registry registration number to search for. Either `name` or `registration_number` is required." + }, + "tier": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "default": "basic", + "description": "Registry tier whose provider chain this search runs on. `basic` (default) searches the country's Lite chain; `shareholders` and `ubo` search that tier's ownership chain, which can be a different source, so an empty Lite search does not prove the registry lacks the company. A country that does not offer the named tier is searched at the highest tier it offers up to that one. Retrieve the candidate from the endpoint that sells the tier you searched: `POST /v3/kyb/select/` (Lite), `POST /v3/kyb/shareholders/` or `POST /v3/kyb/ubo/`.", + "example": "basic" + }, + "search_type": { + "type": "string", + "enum": [ + "contains" + ], + "default": "contains", + "description": "Name-matching strategy applied by the registry search. `contains` is the only accepted value: no registry source behind the search offers a prefix or fuzzy mode, so `start_with` and `fuzzy` are rejected with `400` (`not a valid choice`)." + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for this lookup; echoed back and included in the webhook callback." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON; echoed back and included in the webhook callback." + }, + "webhook_url": { + "type": "string", + "format": "uri", + "maxLength": 500, + "description": "Switches the search to asynchronous mode: the API returns immediately and POSTs an unsigned `kyb.registry_search.resolved` callback to this URL when the candidate list is ready." + } + } + }, + "example": { + "country_code": "GB", + "name": "Tesco", + "search_type": "contains", + "vendor_data": "company-1234" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/kyb/search/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"country_code\": \"GB\",\n \"name\": \"Tesco\",\n \"search_type\": \"contains\",\n \"vendor_data\": \"company-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.kyb.search(\n country_code=\"GB\",\n name=\"Tesco\",\n search_type=\"contains\",\n vendor_data=\"company-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.kyb.search({\n country_code: \"GB\",\n name: \"Tesco\",\n search_type: \"contains\",\n vendor_data: \"company-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.KYB.Search(ctx, didit.KYBSearchParams{\n\tCountryCode: \"GB\",\n\tName: didit.String(\"Tesco\"),\n\tSearchType: didit.Some(didit.KYBSearchParamsSearchType(\"contains\")),\n\tVendorData: didit.String(\"company-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.KybSearchParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.kyb().search(KybSearchParams.builder()\n .countryCode(\"GB\")\n .name(\"Tesco\")\n .searchType(KybSearchParams.SearchType.of(\"contains\"))\n .vendorData(\"company-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "kyb->search([\n 'country_code' => 'GB',\n 'name' => 'Tesco',\n 'search_type' => 'contains',\n 'vendor_data' => 'company-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.kyb.search(\n country_code: \"GB\",\n name: \"Tesco\",\n search_type: \"contains\",\n vendor_data: \"company-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Kyb.SearchAsync(new KybSearchParams\n{\n CountryCode = \"GB\",\n Name = \"Tesco\",\n SearchType = new KybSearchParamsSearchType(\"contains\"),\n VendorData = \"company-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Search accepted. Synchronous mode returns the resolved candidate list; webhook mode returns immediately (often `search_status: \"pending\"`) and delivers the final list via the `kyb.registry_search.resolved` callback.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "With `webhook_url`: the persisted search-request id, echoed as `request_id` in the `kyb.registry_search.resolved` callback. Without `webhook_url`: a transient correlation UUID (the search is not stored)." + }, + "kyb_registry": { + "$ref": "#/components/schemas/KYBRegistrySearchResults" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Resolved search": { + "summary": "Synchronous mode — final candidate list", + "value": { + "request_id": "0c7e9a40-3a13-4f7e-8a44-9d2e2f1c5b6a", + "kyb_registry": { + "companies": [ + { + "kyb_response_id": "69aeeb95febb0f1704042259", + "name": "Tesco PLC", + "registration_number": "00445790", + "status": "active", + "type": "Public Limited Company", + "risk_level": null, + "fetch_status": "pending" + }, + { + "kyb_response_id": "69aeeb95febb0f170404225a", + "name": "E-TESCO LTD", + "registration_number": "15168476", + "status": "active", + "type": "Private Limited Company", + "risk_level": null, + "fetch_status": "pending" + } + ], + "pagination": { + "total": 2, + "page": 1, + "per_page": 25 + }, + "search_status": "resolved", + "search_resolved": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:40:00.000000+00:00" + } + }, + "Pending search (webhook mode)": { + "summary": "`webhook_url` provided — candidates arrive via the kyb.registry_search.resolved callback", + "value": { + "request_id": "5f2d8c1b-7e4a-4b9c-9f0d-3a6e8c2b1d4f", + "kyb_registry": { + "companies": [], + "pagination": { + "total": 0, + "page": 1, + "per_page": 25 + }, + "search_status": "pending", + "search_resolved": false + }, + "vendor_data": "company-1234", + "metadata": { + "source": "onboarding" + }, + "created_at": "2026-06-11T10:41:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems use DRF's standard envelope; the name/registration-number rule reports under `non_field_errors`. A country the registries do not cover answers `400` with `code: kyb_registry_country_unsupported`.", + "content": { + "application/json": { + "examples": { + "Missing search criteria": { + "summary": "Neither `name` nor `registration_number` provided", + "value": { + "non_field_errors": [ + "Either 'name' or 'registration_number' is required." + ] + } + }, + "Invalid country_code": { + "summary": "Must be `XX` (alpha-2) or `XX-YY` subdivision", + "value": { + "country_code": [ + "country_code must be a 2-letter ISO code." + ] + } + }, + "Invalid search_type": { + "summary": "Only `contains` is accepted", + "value": { + "search_type": [ + "\"start_with\" is not a valid choice." + ] + } + }, + "Invalid webhook_url": { + "summary": "`webhook_url` must be a valid URL", + "value": { + "webhook_url": [ + "Enter a valid URL." + ] + } + }, + "Country not covered": { + "summary": "No company registry covers the country", + "value": { + "detail": "Company registry search is not available for this country. Enter the company details manually to continue.", + "code": "kyb_registry_country_unsupported" + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Also returned before searching when the applicable search fee cannot be covered (`code: \"insufficient_credits\"`), or when a contract search rate is missing.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded, or the search allowance is spent. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600), with `X-RateLimit-*` and `Retry-After` headers. Separately, paid registry searches are limited to 600 per application per hour: past that the response is `429` with `code: kyb_registry_search_limit_reached`, and searches served only by free sources still work. Wait and search again later.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + }, + "Search allowance spent": { + "summary": "More than 600 paid searches in the last hour", + "value": { + "detail": "The maximum number of company registry searches for this session has been reached. Enter the company details manually to continue.", + "code": "kyb_registry_search_limit_reached" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + } + } + } + }, + "502": { + "description": "The KYB registry provider is temporarily unavailable - a 5xx response, timeout, or connection failure from the upstream registry. The search is not billed. Retry later, or, in Didit's hosted verification flow, the user is automatically offered manual company entry instead of a retry-only error.", + "content": { + "application/json": { + "examples": { + "Registry provider outage": { + "summary": "The registry provider did not answer the search", + "value": { + "detail": "The KYB registry provider is temporarily unavailable. Please try again shortly.", + "code": "kyb_registry_provider_unavailable" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/kyb/select/": { + "post": { + "summary": "KYB registry company select — Lite tier (standalone)", + "operationId": "post_v3_kyb_select", + "x-didit-sdk": { + "resource": "kyb", + "method": "select", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Retrieve the **Lite** registry profile of one candidate returned by `POST /v3/kyb/search/` — company details, registered address, identifiers, status, activity and the officers, plus the raw registry payload. Shareholders and ultimate beneficial owners are sold by `POST /v3/kyb/shareholders/` and `POST /v3/kyb/ubo/`.\n\n**Pairing with search.** Pass the candidate's `kyb_response_id` exactly as returned by a search of the same application. The handle is ephemeral and scoped to that search — select promptly, and re-search for a fresh handle rather than storing it.\n\n**Billing.** Each select is billed at the country's Lite price (any search fee is separate); see [KYB registry pricing](/getting-started/kyb-registry-pricing). The balance is checked before any registry call (`403` when short) and the charge is recorded against the created business session.\n\n**Persistence is mandatory.** Every select creates a business session (its id is the `request_id`) so billing and retrieval can be tracked; it appears in the console. `save_api_request` is accepted for symmetry but must be `true` (the default) — sending `false` returns `400`.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBRegistryCandidateRequest" + }, + "example": { + "kyb_response_id": "69aeeb95febb0f1704042259", + "vendor_data": "company-1234" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/kyb/select/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"kyb_response_id\": \"69aeeb95febb0f1704042259\",\n \"vendor_data\": \"company-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.kyb.select(\n kyb_response_id=\"69aeeb95febb0f1704042259\",\n vendor_data=\"company-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.kyb.select({\n kyb_response_id: \"69aeeb95febb0f1704042259\",\n vendor_data: \"company-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.KYB.Select(ctx, didit.KYBRegistryCandidateRequest{\n\tKYBResponseID: \"69aeeb95febb0f1704042259\",\n\tVendorData: didit.String(\"company-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.KYBRegistryCandidateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.kyb().select(KYBRegistryCandidateRequest.builder()\n .kybResponseId(\"69aeeb95febb0f1704042259\")\n .vendorData(\"company-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "kyb->select([\n 'kyb_response_id' => '69aeeb95febb0f1704042259',\n 'vendor_data' => 'company-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.kyb.select(\n kyb_response_id: \"69aeeb95febb0f1704042259\",\n vendor_data: \"company-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Kyb.SelectAsync(new KYBRegistryCandidateRequest\n{\n KybResponseId = \"69aeeb95febb0f1704042259\",\n VendorData = \"company-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Company retrieved and billed. `kyb_registry` carries the full profile; check `data_resolved` — when `false` the registry was still processing and the stored record completes automatically once it resolves.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the business session created for this retrieval. The stored record (visible in the console) keeps updating if the registry resolves after the response." + }, + "registry_tier_requested": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "The tier this endpoint sells: `basic` (Lite) for select, `shareholders`, `ubo`." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "The tier actually delivered and billed. Lower than requested when the country's registries hold no ownership data, or the registry has no ownership record for this company." + }, + "kyb_registry": { + "type": "object", + "description": "Full registry company profile.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit's id for this company record." + }, + "node_id": { + "type": "string", + "example": "feature_kyb_registry" + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review" + ], + "description": "Didit's assessment of the registry check (e.g. `In Review` when the company is not active in the registry or ownership data could not be derived)." + }, + "registry_status": { + "type": "string", + "nullable": true, + "enum": [ + "active", + "dissolved", + "deregistered", + "see full details", + "authorised", + "appointed representative", + "unauthorised", + "inactive", + "no longer authorised", + "closed", + "struck off" + ], + "description": "Normalized company status as reported by the registry." + }, + "data_resolved": { + "type": "boolean", + "description": "`true` once the registry returned the full profile. `false` when the registry was still processing after polling — the stored record completes automatically once the registry resolves." + }, + "company_name": { + "type": "string", + "example": "Tesco PLC" + }, + "registration_number": { + "type": "string", + "nullable": true, + "example": "00445790" + }, + "country_code": { + "type": "string", + "description": "ISO 3166-1 alpha-2.", + "example": "GB" + }, + "region": { + "type": "string", + "nullable": true, + "description": "Subdivision code when the registry is regional (e.g. `CA` for `US-CA`); empty otherwise." + }, + "company_type": { + "type": "string", + "nullable": true, + "example": "Public Limited Company" + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true, + "example": "1947-11-27" + }, + "registered_address": { + "type": "string", + "nullable": true, + "example": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA" + }, + "tax_number": { + "type": "string", + "nullable": true, + "example": "GB412 8925 93" + }, + "risk_level": { + "type": "string", + "nullable": true, + "description": "Registry-reported risk level, when available." + }, + "verification_status": { + "type": "string", + "enum": [ + "verified", + "failed", + "unknown" + ], + "nullable": true, + "description": "Cross-jurisdiction verification status: `verified` when the registry status establishes standing (`active`), `failed` when it says the company is no longer standing (for example `dissolved`), otherwise `unknown`. `null` for a company that did not come from a registry and while a selection is still `pending`." + }, + "is_from_registry": { + "type": "boolean", + "description": "Always `true` for this endpoint — the profile was retrieved from an official registry." + }, + "fetch_status": { + "type": "string", + "enum": [ + "pending", + "resolved" + ], + "description": "Registry fetch lifecycle. Mirrored by `data_resolved`." + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true, + "description": "Activity description or classification code, when reported." + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "nullable": true, + "description": "Decimal amount as a string, when reported." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 code, when reported." + }, + "website": { + "type": "string", + "nullable": true, + "example": "https://www.tesco.com/" + }, + "email": { + "type": "string", + "nullable": true, + "example": "customer.service@tesco.co.uk" + }, + "phone": { + "type": "string", + "nullable": true, + "example": "03301231688" + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true, + "description": "LEI, when reported." + }, + "location_of_registration": { + "type": "string", + "nullable": true, + "description": "Registry/jurisdiction the company is registered with, when reported." + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards. At select time this is typically not yet set — VAT and tax ID validation runs when the end user submits the registry step in the hosted flow." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true, + "description": "Registry-reported share/capital details (currency, total shares, share classes), when available." + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "example": "MELISSA BETHELL" + }, + "designation": { + "type": "string", + "nullable": true, + "description": "Raw registry designation (e.g. `Director`, `Secretary`)." + }, + "role": { + "type": "string", + "description": "Normalized role derived from the designation. Full 17-value model set; registry parsing on this endpoint typically emits director, non_executive_director, secretary, chairman, or company_officer.", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "company_officer", + "settlor", + "protector", + "investor", + "other" + ] + }, + "nationality": { + "type": "string", + "nullable": true + }, + "is_active": { + "type": "boolean", + "description": "`false` when the registry marks the appointment as resigned." + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending" + ], + "description": "Status of an associated KYC session, when one was started for this officer. `null` otherwise." + }, + "kyc_session_url": { + "type": "string", + "nullable": true, + "description": "Verification URL of the associated KYC session, when one exists." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + }, + "description": "Directors, secretaries, and other officers extracted from the registry." + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the owner is a natural person or a corporate entity." + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Normalized roles (e.g. `ubo`, `shareholder`)." + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership band reported by the registry (e.g. `25%`)." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership band reported by the registry." + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending" + ] + }, + "kyc_session_url": { + "type": "string", + "nullable": true + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true, + "description": "Parsed ownership percentage (max bound, falling back to min bound)." + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + }, + "shareholding": { + "$ref": "#/components/schemas/KYBRegistryShareholding" + } + } + }, + "description": "Beneficial owners / persons with significant control extracted from the registry. Can be empty when the registry does not expose ownership." + }, + "addresses": { + "type": "array", + "nullable": true, + "description": "Every address the registry states, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryAddress" + } + }, + "industries": { + "type": "array", + "nullable": true, + "description": "The activities the registry classifies the company under, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryIndustry" + } + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "$ref": "#/components/schemas/KYBRegistryData" + }, + "user_provided_data": { + "type": "object", + "nullable": true, + "description": "Always `null` on this endpoint (used by the hosted KYB flow when end users edit registry data)." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set when a console reviewer edits the record later." + }, + "is_editable": { + "type": "boolean", + "description": "Whether the registry-sourced data can still be edited in the hosted flow/console." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "Same as the top-level `registry_tier_delivered`." + }, + "registry_ownership_status": { + "type": "string", + "nullable": true, + "enum": [ + "not_requested", + "delivered", + "not_held", + "unavailable" + ], + "description": "`delivered` when the ownership data came back, `not_held` when the ownership registry answered and does not list this company (see `registry_data.ownership_error`), `unavailable` when an ownership tier was requested but the registry had none for this company or could not be asked (see `registry_data.ownership_error`), `not_requested` on the Lite tier." + }, + "ownership": { + "$ref": "#/components/schemas/KYBRegistryOwnership" + }, + "registry_tier": { + "type": "string", + "nullable": true, + "description": "The registry tier the workflow ordered: `basic`, `shareholders` or `ubo`. Also in `ownership.tier_requested`." + }, + "registry_monitoring": { + "type": "object", + "nullable": true, + "description": "The latest registry monitor for the company, or `null` when none exists.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string" + }, + "unavailable_reason": { + "type": "string", + "nullable": true + }, + "enrolled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "next_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "last_event_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "events_count": { + "type": "integer" + }, + "watched_fields": { + "description": "The company fields the monitor watches." + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Resolved company": { + "summary": "Full registry profile (officer list truncated for brevity)", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "f1e2d3c4-b5a6-4789-9c0d-1e2f3a4b5c6d", + "node_id": "feature_kyb_registry", + "status": "In Review", + "registry_status": "active", + "data_resolved": true, + "company_name": "Tesco PLC", + "registration_number": "00445790", + "country_code": "GB", + "region": "", + "company_type": "Public Limited Company", + "incorporation_date": "1947-11-27", + "registered_address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "tax_number": "GB412 8925 93", + "risk_level": "low risk", + "verification_status": "verified", + "is_from_registry": true, + "fetch_status": "resolved", + "alternative_names": "TESCO STORES (HOLDINGS) PUBLIC LIMITED COMPANY", + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": "https://www.tesco.com/", + "email": "customer.service@tesco.co.uk", + "phone": "03301231688", + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": { + "currency": "Pound (£)", + "total_number_of_shares": "8174378721", + "meta_detail": { + "Class_Of_Share": "Ordinary" + } + }, + "officers": [ + { + "uuid": "a1b2c3d4-e5f6-4789-9a0b-1c2d3e4f5a6b", + "name": "CHRISTOPHER JON TAYLOR", + "designation": "Secretary", + "role": "secretary", + "nationality": null, + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + }, + { + "uuid": "b2c3d4e5-f6a7-4890-8b1c-2d3e4f5a6b7c", + "name": "MELISSA BETHELL", + "designation": "Director", + "role": "director", + "nationality": "British", + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + } + ], + "beneficial_owners": [], + "addresses": [ + { + "address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "type": "legal_entity_registry_address", + "description": "registered office address" + } + ], + "industries": null, + "accounts": { + "last_account": "26 February 2025", + "next_account": "26 February 2026", + "due_by_date": "26 August 2026" + }, + "registry_data": {}, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00" + } + }, + "Pending (registry still resolving)": { + "summary": "Select stayed pending after polling — sparse fields, auto-completes server-side", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "7c0a4f7e-2b1d-4f6a-9c3e-8d5b2a1f0e9d", + "node_id": "feature_kyb_registry", + "status": "Not Finished", + "registry_status": null, + "data_resolved": false, + "company_name": "Tesco PLC", + "registration_number": "00445790", + "country_code": "GB", + "region": null, + "company_type": null, + "incorporation_date": null, + "registered_address": null, + "tax_number": null, + "risk_level": null, + "verification_status": null, + "is_from_registry": true, + "fetch_status": "pending", + "alternative_names": null, + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": null, + "email": null, + "phone": null, + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": {}, + "officers": [], + "beneficial_owners": [], + "addresses": null, + "industries": null, + "accounts": null, + "registry_data": {}, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems use DRF's standard envelope. A country the registries do not cover answers `400` with `code: kyb_registry_country_unsupported`.", + "content": { + "application/json": { + "examples": { + "Missing kyb_response_id": { + "summary": "`kyb_response_id` is required", + "value": { + "kyb_response_id": [ + "This field is required." + ] + } + }, + "save_api_request=false rejected": { + "summary": "Selects are always persisted", + "value": { + "save_api_request": [ + "KYB registry select must be saved so billing and retrieval can be tracked." + ] + } + }, + "Country not covered": { + "summary": "No company registry covers the country", + "value": { + "detail": "Company registry search is not available for this country. Enter the company details manually to continue.", + "code": "kyb_registry_country_unsupported" + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/passive-liveness/": { + "post": { + "summary": "Passive Liveness", + "description": "Detect presentation attacks — printed photos, screen replays, masks, deepfakes — from a single face image. No video, motion, or user interaction required.\n\n**How it works.** The image is analyzed by a biometric model; the largest detected face is evaluated and a liveness `score` (0–100) is computed. `status` is `Declined` when any `error`-severity warning fires:\n- `NO_FACE_DETECTED` — the liveness model found no face,\n- `LOW_LIVENESS_SCORE` — score at or below `face_liveness_score_decline_threshold` (default `30`),\n- `LIVENESS_FACE_ATTACK` — the model detected a presentation attack,\n- `FACE_IN_BLOCKLIST` / `POSSIBLE_FACE_IN_BLOCKLIST` — the face matches an entry on your face blocklist.\n\n`MULTIPLE_FACES_DETECTED` is a `warning`-severity signal (does not decline), and `DUPLICATED_FACE` / `POSSIBLE_DUPLICATED_FACE` are `information`-severity signals that the same face already appeared in another approved session (they never decline). `face_quality` and `face_luminance` (0–100, nullable) describe capture quality.\n\n**Blocklist and duplicate screening.** Runs on **every** call — with or without `save_api_request` — by matching the detected face against your application's face search index. Prior faces with the same `vendor_data` are excluded from the entire index search — so re-running liveness for the same user does not trigger `DUPLICATED_FACE`, and a blocklisted face originating from a session with the caller's own `vendor_data` is also NOT flagged (`FACE_IN_BLOCKLIST` screening is bypassed for same-`vendor_data` faces).\n\n**Billing.** Each `200` response consumes one Passive Liveness API credit (paid from your plan credit or top-up balance). Insufficient balance returns `403` before any image processing.\n\n**Session persistence and face enrollment (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session (Business Console, `GET /v3/session/{sessionId}/decision/` via the returned `request_id`, `status.updated` webhook), the reference image and face crop are stored, and the detected face is **enrolled into your face search index** so later Face Search calls and duplicate checks can match it. When `false`, the call is one-shot: nothing is stored and the face is not enrolled (blocklist/duplicate screening still runs).\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload, no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3passive-liveness", + "x-didit-sdk": { + "resource": "passive_liveness", + "method": "check", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "user_image" + ], + "properties": { + "user_image": { + "type": "string", + "format": "binary", + "description": "Front-facing face image (e.g. a selfie). Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp`. Maximum upload size: **5 MB** (larger files are rejected with `400`). Images are automatically compressed to ~0.5 MB before processing, so very high resolutions do not improve accuracy. The image should contain a single subject — when several faces are present, the largest one is evaluated and a `MULTIPLE_FACES_DETECTED` warning is added." + }, + "face_liveness_score_decline_threshold": { + "type": "number", + "format": "float", + "default": 30, + "minimum": 0, + "maximum": 100, + "description": "Liveness score threshold (0–100). A computed score **at or below** this value emits a `LOW_LIVENESS_SCORE` warning and sets `status` to `Declined`. Default `30`; raise it for a stricter anti-spoofing posture. Values outside 0–100 return `400`.", + "example": 30 + }, + "rotate_image": { + "type": "boolean", + "default": false, + "description": "When `true`, the service tries 90-degree rotations of the input and uses the orientation that yields the best face detection. Useful when EXIF orientation is missing. Adds latency.", + "example": false + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session, stores the face images, emits a `status.updated` webhook, and enrolls the detected face into your face search index. When `false`, the call is one-shot — nothing is stored and the face is not enrolled. Blocklist and duplicate screening run either way.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque identifier (your user id, email, UUID…) stored on the persisted session and echoed back. Also used to exclude prior sessions with the same `vendor_data` from the duplicate-face check, so re-verifying the same user does not raise `DUPLICATED_FACE`.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back. In multipart requests, send it as a JSON-encoded string field — it is parsed into an object.", + "example": { + "flow": "withdrawal" + } + } + } + }, + "example": { + "user_image": "(binary JPEG/PNG selfie)", + "face_liveness_score_decline_threshold": 30, + "rotate_image": false, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "withdrawal" + } + } + } + } + }, + "responses": { + "200": { + "description": "Passive liveness completed. `liveness.score` is the model's 0–100 confidence that the image shows a live person; `status` is `Approved` unless an `error`-severity warning fired. A spoofed or blocklisted face still returns `200` — inspect `liveness.status` and `liveness.warnings`, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Live face — no warnings", + "value": { + "request_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef", + "liveness": { + "status": "Approved", + "method": "PASSIVE", + "score": 95.0, + "user_image": { + "entities": [ + { + "bbox": [ + 661, + 728, + 1688, + 2188 + ], + "confidence": 0.732973, + "age": 26.91, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [], + "face_quality": 84.21, + "face_luminance": 50.33 + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "Declined - presentation attack": { + "summary": "The model flagged a spoof (print, replay, mask…)", + "value": { + "request_id": "f1c8c9b2-d3e4-4f5a-9b8c-7d6e5f4a3b2c", + "liveness": { + "status": "Declined", + "method": "PASSIVE", + "score": 12.5, + "user_image": { + "entities": [ + { + "bbox": [ + 100, + 120, + 420, + 540 + ], + "confidence": 0.81, + "age": 30.1, + "gender": "female", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "LOW_LIVENESS_SCORE", + "feature": "LIVENESS", + "additional_data": null, + "log_type": "error", + "short_description": "Low liveness score", + "long_description": "The liveness check resulted in a low score, indicating potential use of non-live facial representations or poor-quality biometric data." + }, + { + "risk": "LIVENESS_FACE_ATTACK", + "feature": "LIVENESS", + "additional_data": null, + "log_type": "error", + "short_description": "Liveness Face Attack", + "long_description": "The system detected a potential attempt to bypass the liveness check." + } + ], + "face_quality": 72.4, + "face_luminance": 48.2 + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "Declined - face on blocklist": { + "summary": "Live face, but it matches a blocklist entry", + "value": { + "request_id": "0b54a1de-22aa-4e0f-9c84-5a2b1f3c4d5e", + "liveness": { + "status": "Declined", + "method": "PASSIVE", + "score": 93.75, + "user_image": { + "entities": [ + { + "bbox": [ + 40, + 40, + 100, + 100 + ], + "confidence": 0.722082, + "age": 27.0, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "FACE_IN_BLOCKLIST", + "feature": "LIVENESS", + "additional_data": { + "blocklisted_session_id": "882c42d5-8a4d-4d20-8080-a22f57822c86", + "blocklisted_session_number": 3242, + "api_service": null + }, + "log_type": "error", + "short_description": "Face in blocklist", + "long_description": "The system identified a face in the blocklist, which means the face is not allowed to be verified." + } + ], + "face_quality": null, + "face_luminance": null + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "liveness": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Declined` when at least one `error`-severity warning fired (no face, low score, presentation attack, or a blocklist hit); `Approved` otherwise. `warning`/`information` entries (multiple faces, duplicates) never decline on their own." + }, + "method": { + "type": "string", + "enum": [ + "PASSIVE" + ], + "description": "Always `PASSIVE` for this endpoint." + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Liveness confidence (0–100, two decimals) that the image depicts a live person rather than a spoof. `null` when the model returned no confidence.", + "example": 95.0 + }, + "user_image": { + "type": "object", + "description": "Face-detection results for the submitted image.", + "properties": { + "entities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box of the detected face as `[x_min, y_min, x_max, y_max]` pixel coordinates in the processed image.", + "example": [ + 661, + 728, + 1688, + 2188 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.732973 + }, + "age": { + "type": "number", + "format": "float", + "description": "Model-estimated age of the detected face, in years. Informational only — it does not affect the liveness decision.", + "example": 26.91 + }, + "gender": { + "type": "string", + "description": "Model-predicted gender of the detected face (`male` or `female`). Informational only.", + "example": "male" + }, + "race": { + "type": "string", + "nullable": true, + "description": "Reserved field — always `null` in responses from this endpoint.", + "example": null + } + } + }, + "description": "One entry per detected face. The largest face is the one evaluated." + }, + "best_angle": { + "type": "integer", + "nullable": true, + "description": "Rotation (degrees) that produced the best face detection; non-zero only when `rotate_image=true` corrected the orientation.", + "example": 0 + } + } + }, + "warnings": { + "type": "array", + "description": "Risk signals. `error` severity (declines): `NO_FACE_DETECTED`, `LOW_LIVENESS_SCORE` (score at or below the threshold), `LIVENESS_FACE_ATTACK`, `FACE_IN_BLOCKLIST`, `POSSIBLE_FACE_IN_BLOCKLIST`. `warning` severity (review, no decline): `MULTIPLE_FACES_DETECTED`. `information` severity (no decline): `DUPLICATED_FACE`, `POSSIBLE_DUPLICATED_FACE` — the same face already appeared in another approved session.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "NO_FACE_DETECTED", + "LOW_LIVENESS_SCORE", + "LIVENESS_FACE_ATTACK", + "MULTIPLE_FACES_DETECTED", + "FACE_IN_BLOCKLIST", + "POSSIBLE_FACE_IN_BLOCKLIST", + "DUPLICATED_FACE", + "POSSIBLE_DUPLICATED_FACE" + ], + "description": "Machine-readable risk code." + }, + "feature": { + "type": "string", + "enum": [ + "LIVENESS" + ], + "description": "Feature that raised the warning. Always `LIVENESS` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "`null` for most warnings. Blocklist hits carry `{blocklisted_session_id, blocklisted_session_number, api_service}`; duplicate hits carry `{duplicated_session_id, duplicated_session_number, api_service}` pointing at the matching session." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `warning` and `information` entries are advisory and never decline on their own." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "face_quality": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Capture-quality score of the detected face, normalized to 0–100. `null` when the model could not produce a reliable value.", + "example": 84.21 + }, + "face_luminance": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Brightness of the detected face, normalized to 0–100. `null` when unavailable.", + "example": 50.33 + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated, e.g. `2026-06-12T01:04:42.763237+00:00`." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when `user_image` is missing, exceeds 5 MB, has an unsupported extension, fails server-side image decoding, or when an option is out of range. Field errors use DRF's `{field: [messages]}` envelope; image-decoding failures use `{\"error\": ...}`.", + "content": { + "application/json": { + "examples": { + "Missing image": { + "summary": "`user_image` not included in the form data", + "value": { + "user_image": [ + "No file was submitted." + ] + } + }, + "Invalid image format": { + "summary": "Allowed extension but undecodable image content", + "value": { + "error": "Invalid user image format.", + "code": "invalid_image" + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp", + "value": { + "user_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 5 MB limit", + "value": { + "user_image": [ + "File size should not exceed 5 MB" + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/passive-liveness/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'user_image=@./selfie.jpg' \\\n -F 'face_liveness_score_decline_threshold=30' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.passive_liveness.check(\n user_image=open(\"user_image.jpg\", \"rb\"),\n face_liveness_score_decline_threshold=30.0,\n rotate_image=False,\n save_api_request=True,\n vendor_data=\"user-123\",\n metadata={\"flow\": \"withdrawal\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.passiveLiveness.check({\n user_image: await fileFromPath(\"user_image.jpg\"),\n face_liveness_score_decline_threshold: 30.0,\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"withdrawal\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.PassiveLiveness.Check(ctx, didit.PassiveLivenessCheckParams{\n\tUserImage: didit.FileFromPath(\"user_image.jpg\"),\n\tFaceLivenessScoreDeclineThreshold: didit.Float(30.0),\n\tRotateImage: didit.Bool(false),\n\tSaveAPIRequest: didit.Bool(true),\n\tVendorData: didit.String(\"user-123\"),\n\tMetadata: didit.Some(map[string]any{\"flow\": \"withdrawal\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.PassiveLivenessCheckParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.passiveLiveness().check(PassiveLivenessCheckParams.builder()\n .userImage(Path.of(\"user_image.jpg\"))\n .faceLivenessScoreDeclineThreshold(30.0)\n .rotateImage(false)\n .saveApiRequest(true)\n .vendorData(\"user-123\")\n .metadata(Map.of(\"flow\", \"withdrawal\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "passiveLiveness->check([\n 'user_image' => File::fromPath('user_image.jpg'),\n 'face_liveness_score_decline_threshold' => 30.0,\n 'rotate_image' => false,\n 'save_api_request' => true,\n 'vendor_data' => 'user-123',\n 'metadata' => ['flow' => 'withdrawal'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.passive_liveness.check(\n user_image: Pathname(\"user_image.jpg\"),\n face_liveness_score_decline_threshold: 30.0,\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"withdrawal\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.PassiveLiveness.CheckAsync(new PassiveLivenessCheckParams\n{\n UserImage = FileUpload.FromPath(\"user_image.jpg\"),\n FaceLivenessScoreDeclineThreshold = 30.0,\n RotateImage = false,\n SaveApiRequest = true,\n VendorData = \"user-123\",\n Metadata = new Dictionary { [\"flow\"] = \"withdrawal\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/database-validation/": { + "post": { + "summary": "Database Validation (standalone)", + "operationId": "post_v3database-validation", + "x-didit-sdk": { + "resource": "database_validation", + "method": "validate", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Validate a person's identity data against official government and registry sources — CPF in Brazil, RENAPER in Argentina, DNI registries in Spain and Peru, INE in Mexico, and 100+ more services across 60+ countries.\n\n**Service selection.** Each country exposes one or more catalog services identified by a `service_id` (e.g. `bra_cpf`, `arg_renaper`, `pan_cedula_sib`). Pin the services you want with the `services` array — if you omit it, exactly **one** default service runs (the longest-established live service for the country). Sending `services` also switches the response to the extended shape with `services_used` and `match_score`. Discover services, their required fields, and prices via `GET /v1/organization/database-validation-countries/` (a catalog endpoint not documented in this spec) or the Business Console workflow editor. Some services are flagged `requires_consent=true` in the catalog and demand `consent=true`; others require onboarding for your organization before they can be called.\n\n**Input fields.** `identification_number` is a universal field that maps to the right country-specific field automatically (see its description). Country format rules are enforced before any provider is called (e.g. BRA CPF must be exactly 11 digits) and format failures are never billed. Biometric services need a `selfie` upload (multipart only); Argentina RENAPER additionally requires `gender`.\n\n**Results.** Each service returns a field-by-field `validation` map plus a vendor-neutral `outcome_code` (`MATCH`, `PARTIAL_MATCH`, `NO_MATCH`, `DOCUMENT_NOT_FOUND`, `INVALID_DOCUMENT_FORMAT`, `INVALID_INPUT`, `MINOR_BLOCKED`, `DECEASED`, `BIOMETRIC_NO_MATCH`, `BIOMETRIC_IMAGE_UNUSABLE`, `INCONCLUSIVE`, `DOCUMENT_SUPERSEDED`, `REGISTRY_UNAVAILABLE`, `REGISTRY_ERROR`). Note the distinctions: `NO_MATCH` is a definitive mismatch, `INCONCLUSIVE` means the registry could not confirm either way (review, not decline), and `BIOMETRIC_IMAGE_UNUSABLE` is a technical selfie problem (retake), unlike the definitive `BIOMETRIC_NO_MATCH`. The overall `match_type` aggregates across services (any full → `full_match`, else any partial → `partial_match`, else `no_match`) and `no_match_action`/`partial_match_action` translate it into the final `status`. `validation_type` is derived: `two_by_two` when two or more distinct services produced a full match (on the identification number, on full name + date of birth together, or on the address without contradictions), else `one_by_one`.\n\n**Persistence.** `save_api_request` defaults to **true**: the result is stored as a session (`request_id` works with `GET /v3/session/{sessionId}/decision/`), appears in the console, and fires a `status.updated` webhook. It also controls the `validations` shape — per-service objects when saved, a merged `{field: match}` map when not.\n\n**Billing.** Per-service: each service that returns a billable result is charged its own catalog price. Failed or skipped services are not billed, and requests rejected with `400` cost nothing. The pre-flight balance check covers the sum of the selected services' prices (`403` when short).\n\n**Failures.** When no selected service returns a usable result, the API returns `400` with `validation_errors` if every unanswered attempt explicitly refused the input (`provider_rejected_input`, `retryable: false`). Correct the input before trying again. Otherwise it returns `502` with `validation_errors` (`empty_provider_response`, `retryable: true`), including mixed input rejection and unavailable fallback attempts; nothing is billed (a session is still recorded with status `Not Finished` when `save_api_request=true`). When only some services fail, the `200` response carries their failures in `database_validation.errors`.\n\n**Sandbox.** Keys from sandbox applications skip the registry calls and billing and return a static `full_match` response — the sandbox payload always includes `services_used` and `match_score: 100` regardless of how `services` was sent, so don't validate the live extended-shape gating or the 0.0–1.0 `match_score` fraction against sandbox responses.\n\nSend the request as `application/json`, or as `multipart/form-data` when uploading a `selfie`.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "issuing_state" + ], + "properties": { + "issuing_state": { + "type": "string", + "description": "ISO 3166-1 **alpha-3** country code of the registry to validate against (e.g. `BRA`, `ESP`, `COL`). Determines which services are available. Unsupported codes return `400` with the full list of valid options.", + "example": "BRA" + }, + "services": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Catalog `service_id`s to run for this country (e.g. `[\"bra_cpf\"]`). Also accepts a single string or a comma-separated/JSON-encoded string. **If omitted or empty, exactly one default service runs** — the longest-established live service for the country; newer, biometric, and pay-as-you-go services must be named explicitly. Sending `services` switches the response to the extended shape (adds `services_used` and `match_score`). Every id must exist and be live for the `issuing_state`; services that require onboarding return `400` until activated for your organization. Discover available services per country via `GET /v1/organization/database-validation-countries/` (a catalog endpoint not documented in this spec) or the Business Console workflow editor.", + "example": [ + "bra_cpf" + ] + }, + "validation_type": { + "type": "string", + "deprecated": true, + "description": "DEPRECATED. Accepted for backward compatibility but ignored — the response `validation_type` is now derived from how many services full-matched. Use `services` to pin specific services." + }, + "consent": { + "type": "boolean", + "default": false, + "description": "Set to `true` when the end user has explicitly consented to the selected validation services. Required for services flagged `requires_consent=true` in the catalog — without it those services return `400` with `services_requiring_consent`." + }, + "identification_number": { + "type": "string", + "description": "Universal identification number — automatically mapped to the correct country-specific field, so you can use it instead of `personal_number`/`tax_number`/`document_number`: ARG→document_number (DNI), BOL→document_number (CI), BRA→tax_number (CPF, 11 digits), CHL→personal_number (RUT), COL→personal_number (Cédula), CRI→personal_number (Cédula), DOM→personal_number (Cédula, 11 digits), ECU→personal_number (Cédula, 10 digits), ESP→personal_number (DNI/NIE), GTM→document_number (DPI), HND→document_number (DNI), MEX→personal_number (CURP, 18 chars), PAN→personal_number (Cédula), PER→personal_number (DNI, 8 digits), PRY→document_number (CI), SLV→document_number (DUI), URY→personal_number (CI), VEN→document_number (Cédula). It never overrides an explicitly provided country-specific field." + }, + "first_name": { + "type": "string", + "description": "The individual's first name. Required by some services/countries.", + "example": "John" + }, + "last_name": { + "type": "string", + "description": "The individual's last name. Required by some services/countries.", + "example": "Doe" + }, + "middle_name": { + "type": "string", + "description": "Middle name, used by some country services (AUS, NZL, …)." + }, + "full_name": { + "type": "string", + "description": "Full name — some services accept this in lieu of first/last name (e.g. CHN services, which match the native-script name)." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth, `YYYY-MM-DD`. Required by many services.", + "example": "1980-01-01" + }, + "personal_number": { + "type": "string", + "description": "Government-issued unique personal identifier. Used by: CHL (RUT), COL (Cédula), CRI, DOM, ECU, ESP (DNI/NIE), MEX (CURP), PAN (Cédula), PER (DNI), URY. Consider `identification_number` instead." + }, + "tax_number": { + "type": "string", + "description": "Tax identification number. Used by: BRA (CPF, 11 digits). Consider `identification_number` instead." + }, + "document_number": { + "type": "string", + "description": "Document number. Used by: ARG (DNI), BOL (CI), GTM (DPI), HND (DNI), PRY (CI), SLV (DUI), VEN (Cédula). Consider `identification_number` instead." + }, + "document_type": { + "type": "string", + "enum": [ + "P", + "DL", + "ID", + "RP", + "SSC", + "HIC", + "WP", + "TC", + "VISA", + "PSC", + "BC", + "OTHER" + ], + "description": "Type of document being validated: `P` passport, `DL` driver license, `ID` national ID, `RP` residence permit (plus `SSC`, `HIC`, `WP`, `TC`, `VISA`, `PSC`, `BC`, `OTHER`). Required by some services (e.g. ESP)." + }, + "expiration_date": { + "type": "string", + "format": "date", + "description": "Document expiration date, `YYYY-MM-DD`. Required for ESP (Spain) validation.", + "example": "2030-01-15" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "description": "Document issue date, `YYYY-MM-DD` (fecha de expedición). Required for COL (Colombia) cédula validation.", + "example": "2015-06-20" + }, + "nationality": { + "type": "string", + "description": "Nationality as ISO 3166-1 alpha-3. Required by some services." + }, + "gender": { + "type": "string", + "enum": [ + "M", + "F", + "X" + ], + "description": "Gender: `M`, `F`, or `X` (other/unknown). Required for Argentina's RENAPER validation (`arg_renaper`)." + }, + "address": { + "description": "Residential address. Prefer the structured object `{\"street_1\":\"123 Main St\",\"street_2\":\"Apt 4B\",\"city\":\"Springfield\",\"region\":\"IL\",\"postal_code\":\"62701\",\"country\":\"US\"}`; a complete single-line string is still accepted. Required by address-verification services; when a service defines address requirements, missing parts return `400` with `address_fields_required_by`.", + "oneOf": [ + { + "type": "object", + "properties": { + "street_1": { + "type": "string" + }, + "street_2": { + "type": "string" + }, + "city": { + "type": "string" + }, + "region": { + "type": "string" + }, + "postal_code": { + "type": "string" + }, + "country": { + "type": "string" + } + } + }, + { + "type": "string", + "description": "Complete single-line address (legacy)." + } + ] + }, + "address_element_1": { + "type": "string", + "description": "Street address including street number and type. If omitted, derived from `address`." + }, + "address_element_2": { + "type": "string", + "description": "Apartment, unit, building, floor, or extra address line. Only send when you have it explicitly." + }, + "address_element_3": { + "type": "string", + "description": "City, suburb, district, locality, or neighborhood. If omitted, derived from `address`." + }, + "address_element_4": { + "type": "string", + "description": "State, province, region, or town. If omitted, derived from `address`." + }, + "address_element_5": { + "type": "string", + "description": "Postcode or postal code. `postal_code` is accepted as an alias." + }, + "postal_code": { + "type": "string", + "description": "Postal code for address-based services (alias of `address_element_5`)." + }, + "driver_license_number": { + "type": "string", + "description": "Driver licence number for government document-verification services (AUS, NZL, IND)." + }, + "driver_license_card_number": { + "type": "string", + "description": "Physical driver licence card number, where required separately from the licence number (AUS)." + }, + "driver_license_state": { + "type": "string", + "description": "Driver licence state or territory of issue (AUS)." + }, + "driver_license_version": { + "type": "string", + "description": "Driver licence version code (NZL)." + }, + "passport_number": { + "type": "string", + "description": "Passport number for government passport-verification services (AUS, NZL, IND, CHN)." + }, + "passport_expiration_date": { + "type": "string", + "format": "date", + "description": "Passport expiry date, `YYYY-MM-DD`, for passport-verification services." + }, + "passport_file_number": { + "type": "string", + "description": "Passport file number (IND)." + }, + "passport_issue_country": { + "type": "string", + "description": "Issuing country of the passport (ISO 3166-1 alpha-3) for passport-verification services." + }, + "medicare_card_number": { + "type": "string", + "description": "Medicare card number (AUS)." + }, + "immi_card_number": { + "type": "string", + "description": "Australian ImmiCard number." + }, + "immi_card_expiry_date": { + "type": "string", + "format": "date", + "description": "Australian ImmiCard expiry date." + }, + "citizenship_certificate_number": { + "type": "string", + "description": "Citizenship certificate number (AUS)." + }, + "birth_registration_number": { + "type": "string", + "description": "Birth-certificate registration number (AUS)." + }, + "birth_registration_date": { + "type": "string", + "format": "date", + "description": "Birth-certificate registration date (AUS)." + }, + "birth_registration_state": { + "type": "string", + "description": "Birth-certificate registration state (AUS)." + }, + "marriage_certificate_number": { + "type": "string", + "description": "Marriage certificate number (AUS)." + }, + "change_of_name_certificate_number": { + "type": "string", + "description": "Change-of-name certificate number (AUS)." + }, + "first_partner_name": { + "type": "string", + "description": "First name of the partner on a marriage certificate (AUS)." + }, + "last_partner_name": { + "type": "string", + "description": "Last name of the partner on a marriage certificate (AUS)." + }, + "voter_id": { + "type": "string", + "description": "Voter registration number (IND EPIC, IRL)." + }, + "epic_card": { + "type": "string", + "description": "India EPIC voter card number." + }, + "pan": { + "type": "string", + "description": "India PAN (Permanent Account Number)." + }, + "national_id": { + "type": "string", + "description": "National ID number (CHN, MYS, KEN, KHM, NGA, ZAF, …)." + }, + "cic": { + "type": "string", + "description": "Mexican INE/IFE Código de Identificación de Credencial (CIC), 9 digits. Used by the MEX INE credential-validity service (`mex_ine_vigencia`)." + }, + "identificador_ciudadano": { + "type": "string", + "description": "Mexican INE Identificador del Ciudadano (9 digits). Used with `cic` for modern INE models (E/F/G/H) in `mex_ine_vigencia`." + }, + "ocr": { + "type": "string", + "description": "Mexican INE OCR number (13 digits, back of card). Used with `cic` for Model D in `mex_ine_vigencia`." + }, + "voter_number": { + "type": "string", + "description": "Mexican INE Clave de Elector (18 chars). Used with `emission_number` for legacy IFE models (A/B/C) in `mex_ine_vigencia`." + }, + "emission_number": { + "type": "string", + "description": "Mexican INE Número de Emisión. Used with `voter_number` for legacy IFE models (A/B/C) in `mex_ine_vigencia`." + }, + "bvn": { + "type": "string", + "description": "Nigerian Bank Verification Number." + }, + "bank_card_number": { + "type": "string", + "description": "Bank card number (CHN bank-card verification)." + }, + "company_registration_number": { + "type": "string", + "description": "Company registration number as issued by the companies register: the CNPJ in Brazil (e.g. `11222333000181`), the registration number in South Africa (e.g. `1936/007721/06`). Required by the company registry and company directors services (`bra_company_registry`, `bra_company_directors`, `zaf_company_registry`, `zaf_company_directors`)." + }, + "company_name": { + "type": "string", + "description": "Registered company name, cross-checked against the register entry. Optional for `zaf_company_registry`." + }, + "initials": { + "type": "string", + "description": "The person's initials as printed on the document. Required by `zaf_drivers_license`; accepted by `zaf_bank_account_holder`." + }, + "licence_number": { + "type": "string", + "description": "Driving licence number as printed on the card, for `zaf_drivers_license`. When omitted, `document_number` is used." + }, + "refugee_file_number": { + "type": "string", + "description": "Refugee file number issued by South Africa's Department of Home Affairs. Required by `zaf_refugee`." + }, + "bank_name": { + "type": "string", + "description": "Name of the bank that holds the account. Required by `zaf_bank_account_holder`." + }, + "bank_account_number": { + "type": "string", + "description": "Bank account number. Required by `zaf_bank_account_holder`; accepted by `zaf_fraud_prevention`." + }, + "account_type": { + "type": "string", + "description": "Bank account type for `zaf_bank_account_holder`: `current`, `savings`, `transmission`, `subscriptionShare` or `notKnown`, case-insensitive. Any other value returns `400`. Omit it when you do not know the type: the account-type cross-check is then reported as not confirmed." + }, + "phone_number": { + "type": "string", + "description": "Contact mobile number, for the services that cross-check contactability (`zaf_bank_account_holder`, `zaf_fraud_prevention`, `zaf_refugee`)." + }, + "right_thumb": { + "type": "string", + "description": "Base64-encoded right thumb fingerprint for `zaf_dha_fingerprint_match`, with `left_thumb` or on its own." + }, + "left_thumb": { + "type": "string", + "description": "Base64-encoded left thumb fingerprint for `zaf_dha_fingerprint_match`, with `right_thumb` or on its own." + }, + "score_level": { + "type": "integer", + "description": "Minimum biometric score level for `zaf_criminal_face_screening`. Defaults to `3`." + }, + "max_matches": { + "type": "integer", + "description": "Maximum number of candidate matches `zaf_criminal_face_screening` returns. Defaults to `1`." + }, + "vehicle_register_number": { + "type": "string", + "description": "Vehicle register number. Required, with `vehicle_licence_plate` and `vin`, by `zaf_vehicle_ownership`." + }, + "vehicle_licence_plate": { + "type": "string", + "description": "Vehicle licence plate number. Required, with `vehicle_register_number` and `vin`, by `zaf_vehicle_ownership`." + }, + "vin": { + "type": "string", + "description": "Vehicle Identification Number (VIN or chassis number). Required, with `vehicle_register_number` and `vehicle_licence_plate`, by `zaf_vehicle_ownership`." + }, + "ssn": { + "type": "string", + "description": "Social Security Number (USA)." + }, + "phone": { + "type": "string", + "description": "Phone number for phone-verification services." + }, + "landline": { + "type": "string", + "description": "Landline number for phone-verification services." + }, + "email": { + "type": "string", + "format": "email", + "description": "Email address for identity-verification services." + }, + "country_of_residence": { + "type": "string", + "description": "ISO 3166-1 alpha-2 country of residence. Used by global identity-enrichment services (e.g. `glb_identity_enrichment`) to focus the lookup.", + "example": "US" + }, + "partial_match_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "NO_ACTION", + "description": "What a `partial_match` does to `database_validation.status`. Defaults to `NO_ACTION` (status stays `Approved`, with a warning)." + }, + "no_match_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What a `no_match` does to `database_validation.status`. Defaults to `DECLINE`." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "Persist the validation as a session (console visibility, decision endpoint, `status.updated` webhook). Also changes the `validations` response shape: per-service objects when `true` (default), a merged field map when `false`." + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for the validated user; echoed back and stored with the session." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON stored with the request and echoed back." + } + } + }, + "example": { + "issuing_state": "BRA", + "services": [ + "bra_cpf" + ], + "identification_number": "12345678900", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-01-01", + "vendor_data": "user-1234" + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "issuing_state" + ], + "properties": { + "issuing_state": { + "type": "string", + "description": "ISO 3166-1 **alpha-3** country code of the registry to validate against (e.g. `BRA`, `ESP`, `COL`). Determines which services are available. Unsupported codes return `400` with the full list of valid options.", + "example": "BRA" + }, + "services": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Catalog `service_id`s to run for this country (e.g. `[\"bra_cpf\"]`). Also accepts a single string or a comma-separated/JSON-encoded string. **If omitted or empty, exactly one default service runs** — the longest-established live service for the country; newer, biometric, and pay-as-you-go services must be named explicitly. Sending `services` switches the response to the extended shape (adds `services_used` and `match_score`). Every id must exist and be live for the `issuing_state`; services that require onboarding return `400` until activated for your organization. Discover available services per country via `GET /v1/organization/database-validation-countries/` (a catalog endpoint not documented in this spec) or the Business Console workflow editor.", + "example": [ + "bra_cpf" + ] + }, + "validation_type": { + "type": "string", + "deprecated": true, + "description": "DEPRECATED. Accepted for backward compatibility but ignored — the response `validation_type` is now derived from how many services full-matched. Use `services` to pin specific services." + }, + "consent": { + "type": "boolean", + "default": false, + "description": "Set to `true` when the end user has explicitly consented to the selected validation services. Required for services flagged `requires_consent=true` in the catalog — without it those services return `400` with `services_requiring_consent`." + }, + "identification_number": { + "type": "string", + "description": "Universal identification number — automatically mapped to the correct country-specific field, so you can use it instead of `personal_number`/`tax_number`/`document_number`: ARG→document_number (DNI), BOL→document_number (CI), BRA→tax_number (CPF, 11 digits), CHL→personal_number (RUT), COL→personal_number (Cédula), CRI→personal_number (Cédula), DOM→personal_number (Cédula, 11 digits), ECU→personal_number (Cédula, 10 digits), ESP→personal_number (DNI/NIE), GTM→document_number (DPI), HND→document_number (DNI), MEX→personal_number (CURP, 18 chars), PAN→personal_number (Cédula), PER→personal_number (DNI, 8 digits), PRY→document_number (CI), SLV→document_number (DUI), URY→personal_number (CI), VEN→document_number (Cédula). It never overrides an explicitly provided country-specific field." + }, + "first_name": { + "type": "string", + "description": "The individual's first name. Required by some services/countries.", + "example": "John" + }, + "last_name": { + "type": "string", + "description": "The individual's last name. Required by some services/countries.", + "example": "Doe" + }, + "middle_name": { + "type": "string", + "description": "Middle name, used by some country services (AUS, NZL, …)." + }, + "full_name": { + "type": "string", + "description": "Full name — some services accept this in lieu of first/last name (e.g. CHN services, which match the native-script name)." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth, `YYYY-MM-DD`. Required by many services.", + "example": "1980-01-01" + }, + "personal_number": { + "type": "string", + "description": "Government-issued unique personal identifier. Used by: CHL (RUT), COL (Cédula), CRI, DOM, ECU, ESP (DNI/NIE), MEX (CURP), PAN (Cédula), PER (DNI), URY. Consider `identification_number` instead." + }, + "tax_number": { + "type": "string", + "description": "Tax identification number. Used by: BRA (CPF, 11 digits). Consider `identification_number` instead." + }, + "document_number": { + "type": "string", + "description": "Document number. Used by: ARG (DNI), BOL (CI), GTM (DPI), HND (DNI), PRY (CI), SLV (DUI), VEN (Cédula). Consider `identification_number` instead." + }, + "document_type": { + "type": "string", + "enum": [ + "P", + "DL", + "ID", + "RP", + "SSC", + "HIC", + "WP", + "TC", + "VISA", + "PSC", + "BC", + "OTHER" + ], + "description": "Type of document being validated: `P` passport, `DL` driver license, `ID` national ID, `RP` residence permit (plus `SSC`, `HIC`, `WP`, `TC`, `VISA`, `PSC`, `BC`, `OTHER`). Required by some services (e.g. ESP)." + }, + "expiration_date": { + "type": "string", + "format": "date", + "description": "Document expiration date, `YYYY-MM-DD`. Required for ESP (Spain) validation.", + "example": "2030-01-15" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "description": "Document issue date, `YYYY-MM-DD` (fecha de expedición). Required for COL (Colombia) cédula validation.", + "example": "2015-06-20" + }, + "nationality": { + "type": "string", + "description": "Nationality as ISO 3166-1 alpha-3. Required by some services." + }, + "gender": { + "type": "string", + "enum": [ + "M", + "F", + "X" + ], + "description": "Gender: `M`, `F`, or `X` (other/unknown). Required for Argentina's RENAPER validation (`arg_renaper`)." + }, + "address": { + "description": "Residential address. Prefer the structured object `{\"street_1\":\"123 Main St\",\"street_2\":\"Apt 4B\",\"city\":\"Springfield\",\"region\":\"IL\",\"postal_code\":\"62701\",\"country\":\"US\"}`; a complete single-line string is still accepted. Required by address-verification services; when a service defines address requirements, missing parts return `400` with `address_fields_required_by`.", + "oneOf": [ + { + "type": "object", + "properties": { + "street_1": { + "type": "string" + }, + "street_2": { + "type": "string" + }, + "city": { + "type": "string" + }, + "region": { + "type": "string" + }, + "postal_code": { + "type": "string" + }, + "country": { + "type": "string" + } + } + }, + { + "type": "string", + "description": "Complete single-line address (legacy)." + } + ] + }, + "address_element_1": { + "type": "string", + "description": "Street address including street number and type. If omitted, derived from `address`." + }, + "address_element_2": { + "type": "string", + "description": "Apartment, unit, building, floor, or extra address line. Only send when you have it explicitly." + }, + "address_element_3": { + "type": "string", + "description": "City, suburb, district, locality, or neighborhood. If omitted, derived from `address`." + }, + "address_element_4": { + "type": "string", + "description": "State, province, region, or town. If omitted, derived from `address`." + }, + "address_element_5": { + "type": "string", + "description": "Postcode or postal code. `postal_code` is accepted as an alias." + }, + "postal_code": { + "type": "string", + "description": "Postal code for address-based services (alias of `address_element_5`)." + }, + "driver_license_number": { + "type": "string", + "description": "Driver licence number for government document-verification services (AUS, NZL, IND)." + }, + "driver_license_card_number": { + "type": "string", + "description": "Physical driver licence card number, where required separately from the licence number (AUS)." + }, + "driver_license_state": { + "type": "string", + "description": "Driver licence state or territory of issue (AUS)." + }, + "driver_license_version": { + "type": "string", + "description": "Driver licence version code (NZL)." + }, + "passport_number": { + "type": "string", + "description": "Passport number for government passport-verification services (AUS, NZL, IND, CHN)." + }, + "passport_expiration_date": { + "type": "string", + "format": "date", + "description": "Passport expiry date, `YYYY-MM-DD`, for passport-verification services." + }, + "passport_file_number": { + "type": "string", + "description": "Passport file number (IND)." + }, + "passport_issue_country": { + "type": "string", + "description": "Issuing country of the passport (ISO 3166-1 alpha-3) for passport-verification services." + }, + "medicare_card_number": { + "type": "string", + "description": "Medicare card number (AUS)." + }, + "immi_card_number": { + "type": "string", + "description": "Australian ImmiCard number." + }, + "immi_card_expiry_date": { + "type": "string", + "format": "date", + "description": "Australian ImmiCard expiry date." + }, + "citizenship_certificate_number": { + "type": "string", + "description": "Citizenship certificate number (AUS)." + }, + "birth_registration_number": { + "type": "string", + "description": "Birth-certificate registration number (AUS)." + }, + "birth_registration_date": { + "type": "string", + "format": "date", + "description": "Birth-certificate registration date (AUS)." + }, + "birth_registration_state": { + "type": "string", + "description": "Birth-certificate registration state (AUS)." + }, + "marriage_certificate_number": { + "type": "string", + "description": "Marriage certificate number (AUS)." + }, + "change_of_name_certificate_number": { + "type": "string", + "description": "Change-of-name certificate number (AUS)." + }, + "first_partner_name": { + "type": "string", + "description": "First name of the partner on a marriage certificate (AUS)." + }, + "last_partner_name": { + "type": "string", + "description": "Last name of the partner on a marriage certificate (AUS)." + }, + "voter_id": { + "type": "string", + "description": "Voter registration number (IND EPIC, IRL)." + }, + "epic_card": { + "type": "string", + "description": "India EPIC voter card number." + }, + "pan": { + "type": "string", + "description": "India PAN (Permanent Account Number)." + }, + "national_id": { + "type": "string", + "description": "National ID number (CHN, MYS, KEN, KHM, NGA, ZAF, …)." + }, + "cic": { + "type": "string", + "description": "Mexican INE/IFE Código de Identificación de Credencial (CIC), 9 digits. Used by the MEX INE credential-validity service (`mex_ine_vigencia`)." + }, + "identificador_ciudadano": { + "type": "string", + "description": "Mexican INE Identificador del Ciudadano (9 digits). Used with `cic` for modern INE models (E/F/G/H) in `mex_ine_vigencia`." + }, + "ocr": { + "type": "string", + "description": "Mexican INE OCR number (13 digits, back of card). Used with `cic` for Model D in `mex_ine_vigencia`." + }, + "voter_number": { + "type": "string", + "description": "Mexican INE Clave de Elector (18 chars). Used with `emission_number` for legacy IFE models (A/B/C) in `mex_ine_vigencia`." + }, + "emission_number": { + "type": "string", + "description": "Mexican INE Número de Emisión. Used with `voter_number` for legacy IFE models (A/B/C) in `mex_ine_vigencia`." + }, + "bvn": { + "type": "string", + "description": "Nigerian Bank Verification Number." + }, + "bank_card_number": { + "type": "string", + "description": "Bank card number (CHN bank-card verification)." + }, + "company_registration_number": { + "type": "string", + "description": "Company registration number as issued by the companies register: the CNPJ in Brazil (e.g. `11222333000181`), the registration number in South Africa (e.g. `1936/007721/06`). Required by the company registry and company directors services (`bra_company_registry`, `bra_company_directors`, `zaf_company_registry`, `zaf_company_directors`)." + }, + "company_name": { + "type": "string", + "description": "Registered company name, cross-checked against the register entry. Optional for `zaf_company_registry`." + }, + "initials": { + "type": "string", + "description": "The person's initials as printed on the document. Required by `zaf_drivers_license`; accepted by `zaf_bank_account_holder`." + }, + "licence_number": { + "type": "string", + "description": "Driving licence number as printed on the card, for `zaf_drivers_license`. When omitted, `document_number` is used." + }, + "refugee_file_number": { + "type": "string", + "description": "Refugee file number issued by South Africa's Department of Home Affairs. Required by `zaf_refugee`." + }, + "bank_name": { + "type": "string", + "description": "Name of the bank that holds the account. Required by `zaf_bank_account_holder`." + }, + "bank_account_number": { + "type": "string", + "description": "Bank account number. Required by `zaf_bank_account_holder`; accepted by `zaf_fraud_prevention`." + }, + "account_type": { + "type": "string", + "description": "Bank account type for `zaf_bank_account_holder`: `current`, `savings`, `transmission`, `subscriptionShare` or `notKnown`, case-insensitive. Any other value returns `400`. Omit it when you do not know the type: the account-type cross-check is then reported as not confirmed." + }, + "phone_number": { + "type": "string", + "description": "Contact mobile number, for the services that cross-check contactability (`zaf_bank_account_holder`, `zaf_fraud_prevention`, `zaf_refugee`)." + }, + "right_thumb": { + "type": "string", + "description": "Base64-encoded right thumb fingerprint for `zaf_dha_fingerprint_match`, with `left_thumb` or on its own." + }, + "left_thumb": { + "type": "string", + "description": "Base64-encoded left thumb fingerprint for `zaf_dha_fingerprint_match`, with `right_thumb` or on its own." + }, + "score_level": { + "type": "integer", + "description": "Minimum biometric score level for `zaf_criminal_face_screening`. Defaults to `3`." + }, + "max_matches": { + "type": "integer", + "description": "Maximum number of candidate matches `zaf_criminal_face_screening` returns. Defaults to `1`." + }, + "vehicle_register_number": { + "type": "string", + "description": "Vehicle register number. Required, with `vehicle_licence_plate` and `vin`, by `zaf_vehicle_ownership`." + }, + "vehicle_licence_plate": { + "type": "string", + "description": "Vehicle licence plate number. Required, with `vehicle_register_number` and `vin`, by `zaf_vehicle_ownership`." + }, + "vin": { + "type": "string", + "description": "Vehicle Identification Number (VIN or chassis number). Required, with `vehicle_register_number` and `vehicle_licence_plate`, by `zaf_vehicle_ownership`." + }, + "ssn": { + "type": "string", + "description": "Social Security Number (USA)." + }, + "phone": { + "type": "string", + "description": "Phone number for phone-verification services." + }, + "landline": { + "type": "string", + "description": "Landline number for phone-verification services." + }, + "email": { + "type": "string", + "format": "email", + "description": "Email address for identity-verification services." + }, + "country_of_residence": { + "type": "string", + "description": "ISO 3166-1 alpha-2 country of residence. Used by global identity-enrichment services (e.g. `glb_identity_enrichment`) to focus the lookup.", + "example": "US" + }, + "partial_match_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "NO_ACTION", + "description": "What a `partial_match` does to `database_validation.status`. Defaults to `NO_ACTION` (status stays `Approved`, with a warning)." + }, + "no_match_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What a `no_match` does to `database_validation.status`. Defaults to `DECLINE`." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "Persist the validation as a session (console visibility, decision endpoint, `status.updated` webhook). Also changes the `validations` response shape: per-service objects when `true` (default), a merged field map when `false`." + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for the validated user; echoed back and stored with the session." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON stored with the request and echoed back." + }, + "selfie": { + "type": "string", + "format": "binary", + "description": "Selfie image (`jpg`, `jpeg`, `png`, `webp`; max 2 MB — images are compressed to ~1.5 MB before upload to the registry) for biometric validation services, e.g. Argentina RENAPER (`arg_renaper`) and Panama SIB (`pan_cedula_sib`, `pan_cedula_sib_plus`). Required whenever a selected service lists it as a required field. Only available via `multipart/form-data`." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/database-validation/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"issuing_state\": \"BRA\",\n \"services\": [\"bra_cpf\"],\n \"identification_number\": \"12345678900\",\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"date_of_birth\": \"1980-01-01\",\n \"vendor_data\": \"user-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.database_validation.validate(\n issuing_state=\"BRA\",\n services=[\"bra_cpf\"],\n identification_number=\"12345678900\",\n first_name=\"John\",\n last_name=\"Doe\",\n date_of_birth=\"1980-01-01\",\n vendor_data=\"user-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.databaseValidation.validate({\n issuing_state: \"BRA\",\n services: [\"bra_cpf\"],\n identification_number: \"12345678900\",\n first_name: \"John\",\n last_name: \"Doe\",\n date_of_birth: \"1980-01-01\",\n vendor_data: \"user-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.DatabaseValidation.Validate(ctx, didit.DatabaseValidationValidateParams{\n\tIssuingState: \"BRA\",\n\tServices: didit.Some([]string{\"bra_cpf\"}),\n\tIdentificationNumber: didit.String(\"12345678900\"),\n\tFirstName: didit.String(\"John\"),\n\tLastName: didit.String(\"Doe\"),\n\tDateOfBirth: didit.String(\"1980-01-01\"),\n\tVendorData: didit.String(\"user-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.DatabaseValidationValidateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.databaseValidation().validate(DatabaseValidationValidateParams.builder()\n .issuingState(\"BRA\")\n .services(List.of(\"bra_cpf\"))\n .identificationNumber(\"12345678900\")\n .firstName(\"John\")\n .lastName(\"Doe\")\n .dateOfBirth(LocalDate.parse(\"1980-01-01\"))\n .vendorData(\"user-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "databaseValidation->validate([\n 'issuing_state' => 'BRA',\n 'services' => ['bra_cpf'],\n 'identification_number' => '12345678900',\n 'first_name' => 'John',\n 'last_name' => 'Doe',\n 'date_of_birth' => '1980-01-01',\n 'vendor_data' => 'user-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.database_validation.validate(\n issuing_state: \"BRA\",\n services: [\"bra_cpf\"],\n identification_number: \"12345678900\",\n first_name: \"John\",\n last_name: \"Doe\",\n date_of_birth: \"1980-01-01\",\n vendor_data: \"user-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.DatabaseValidation.ValidateAsync(new DatabaseValidationValidateParams\n{\n IssuingState = \"BRA\",\n Services = new string[] { \"bra_cpf\" },\n IdentificationNumber = \"12345678900\",\n FirstName = \"John\",\n LastName = \"Doe\",\n DateOfBirth = \"1980-01-01\",\n VendorData = \"user-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Validation completed — at least one selected service returned a usable result. Inspect `database_validation.match_type` for the aggregate outcome, `validations` for the per-service field comparisons and `outcome_code`s, and `errors` (when present) for services that failed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "database_validation": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "description": "`Declined` when the overall `match_type` triggers a `DECLINE` action (`no_match_action` defaults to `DECLINE`, `partial_match_action` to `NO_ACTION`); `In Review` when no service produced a usable comparison, for example when the only selected service came back `REGISTRY_UNAVAILABLE` or `INCONCLUSIVE`; `Approved` otherwise. A request that selected several services can be `Approved` on one service's match while another was unavailable, so check `errors` and the per-service `outcome_code`s too." + }, + "issuing_state": { + "type": "string", + "description": "Echo of the ISO 3166-1 alpha-3 country validated against.", + "example": "BRA" + }, + "validation_type": { + "type": "string", + "enum": [ + "one_by_one", + "two_by_two" + ], + "description": "Derived from the results: `two_by_two` when ≥2 distinct services produced a full match (on the identification number, on full name + date of birth together, or on the address without contradictions); `one_by_one` otherwise. (The request field of the same name is deprecated and ignored.)" + }, + "screened_data": { + "type": "object", + "description": "Echo of the input fields that were sent to the selected services (dates normalized to `YYYY-MM-DD`; a `selfie`, when sent, is returned as a pre-signed URL; `consent_obtained: true` is added when consent was given)." + }, + "match_type": { + "type": "string", + "nullable": true, + "enum": [ + "full_match", + "partial_match", + "no_match" + ], + "description": "Overall result aggregated across services with field-level comparisons: any `full_match` → `full_match`; else any `partial_match` → `partial_match`; else `no_match`. Outcome-only services, such as biometric criminal screening, can return `null`; inspect each item in `validations` for its `outcome_code`." + }, + "validations": { + "description": "Shape depends on `save_api_request`. **`true` (default):** an array of per-service objects (`service_id`, `service_name`, `validation`, `outcome_code`, `outcome_detail`, `source_data`). **`false`:** a single flat object merging the field-level results of all services (`{\"full_name\": \"full_match\", ...}`).", + "oneOf": [ + { + "type": "array", + "items": { + "type": "object", + "description": "Per-service validation result (returned when `save_api_request=true`, the default).", + "properties": { + "service_id": { + "type": "string", + "description": "Catalog id of the service that produced this result (e.g. `bra_cpf`).", + "example": "bra_cpf" + }, + "service_name": { + "type": "string", + "description": "Human-readable catalog name of the service.", + "example": "Brazil - CPF status check" + }, + "validation": { + "type": "object", + "description": "Field-by-field comparison between your input and the registry record. Keys are canonical field names (`full_name`, `date_of_birth`, `identification_number`, `address`, …); values are `full_match`, `partial_match`, or `no_match`.", + "additionalProperties": { + "type": "string", + "enum": [ + "full_match", + "partial_match", + "no_match" + ] + } + }, + "outcome_code": { + "type": "string", + "enum": [ + "MATCH", + "PARTIAL_MATCH", + "NO_MATCH", + "DOCUMENT_NOT_FOUND", + "INVALID_DOCUMENT_FORMAT", + "INVALID_INPUT", + "MINOR_BLOCKED", + "DECEASED", + "BIOMETRIC_NO_MATCH", + "BIOMETRIC_IMAGE_UNUSABLE", + "INCONCLUSIVE", + "DOCUMENT_SUPERSEDED", + "REGISTRY_UNAVAILABLE", + "REGISTRY_ERROR" + ], + "description": "Vendor-neutral outcome of the registry lookup. `MATCH`/`PARTIAL_MATCH`/`NO_MATCH` describe the comparison; `DOCUMENT_NOT_FOUND` means the identifier is not in the registry; `INCONCLUSIVE` means the registry could not confirm either way (routed to review — NOT a no-match); `BIOMETRIC_NO_MATCH` is a definitive face mismatch while `BIOMETRIC_IMAGE_UNUSABLE` is a technical image problem (retake the selfie); `DECEASED`/`MINOR_BLOCKED` are registry-policy outcomes; `DOCUMENT_SUPERSEDED` means the identity matched but the edition of the document presented is not the one on file (a newer edition has been issued); `INVALID_DOCUMENT_FORMAT`/`INVALID_INPUT` mean the registry itself rejected the data it was sent; `REGISTRY_UNAVAILABLE`/`REGISTRY_ERROR` indicate upstream failures." + }, + "outcome_detail": { + "type": "string", + "nullable": true, + "description": "Raw upstream status detail backing the outcome code (e.g. an HTTP or registry status code)." + }, + "source_data": { + "type": "object", + "nullable": true, + "description": "Cleaned, normalized view of the registry record using canonical field names (`identification_number`, `first_name`, `last_name`, `date_of_birth`, plus registry-specific flags). Image fields, when present, are returned as pre-signed URLs." + } + } + } + }, + { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "full_match", + "partial_match", + "no_match" + ] + }, + "description": "Merged field-level results (stateless `save_api_request=false` shape)." + } + ] + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature that raised the warning." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk identifier." + }, + "additional_data": { + "type": "object", + "nullable": true, + "description": "Extra context for the warning, when available." + }, + "log_type": { + "type": "string", + "description": "`warning`, `information`, or `error` — how the risk affected the outcome." + }, + "short_description": { + "type": "string", + "description": "One-line human-readable summary." + }, + "long_description": { + "type": "string", + "description": "Full human-readable explanation." + } + } + }, + "description": "`DATABASE_VALIDATION_NO_MATCH` or `DATABASE_VALIDATION_PARTIAL_MATCH` when applicable; empty on a full match." + }, + "errors": { + "type": "array", + "description": "Present only when some (but not all) selected services failed. Each entry carries `service_id`, `code`, `message`, and `retryable`. A service reported here is absent from `validations` and `services_used`, and is not billed. This array is the only place it appears, so inspect it even when `status` is `Approved`.", + "items": { + "type": "object", + "properties": { + "service_id": { + "type": "string" + }, + "code": { + "type": "string", + "example": "empty_provider_response" + }, + "message": { + "type": "string" + }, + "retryable": { + "type": "boolean", + "description": "`true` when the service produced no answer and retrying it later can succeed; `false` when the source refused the data it was sent and the input must be corrected first.", + "example": true + } + } + } + }, + "services_used": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Catalog ids of the services that returned a billable result. **Only present when the request explicitly sent `services`.**" + }, + "match_score": { + "type": "number", + "description": "Fraction (0.0–1.0) of attempted services that produced a full match (on the identification number, on full name + date of birth together, or on the address without contradictions). **Only present when the request explicitly sent `services`.**" + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Full match (Approved)": { + "summary": "Default save_api_request=true — per-service `validations` objects", + "value": { + "request_id": "7f9a1c0e-1f2b-4f6e-9d3a-2b1c0e7f9a1c", + "database_validation": { + "status": "Approved", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "tax_number": "12345678900", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-01-01" + }, + "match_type": "full_match", + "validations": [ + { + "validation": { + "full_name": "full_match", + "date_of_birth": "full_match", + "identification_number": "full_match" + }, + "outcome_code": "MATCH", + "outcome_detail": "200", + "service_id": "bra_cpf", + "service_name": "Brazil - CPF status check", + "source_data": { + "identification_number": "12345678900", + "first_name": "JOHN", + "last_name": "DOE", + "date_of_birth": "1980-01-01", + "lgpd_minor": false, + "minor_under_16": false, + "minor_under_18": false + } + } + ], + "warnings": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-11T10:30:00.000000+00:00" + } + }, + "Explicit services (extended shape)": { + "summary": "Request sent `services` — response adds `services_used` and `match_score`", + "value": { + "request_id": "b3e7c1a2-9d4f-4f0b-8a6c-5e2d1f0a9b3e", + "database_validation": { + "status": "Approved", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "tax_number": "12345678900", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-01-01" + }, + "match_type": "full_match", + "validations": [ + { + "validation": { + "full_name": "full_match", + "date_of_birth": "full_match", + "identification_number": "full_match" + }, + "outcome_code": "MATCH", + "outcome_detail": "200", + "service_id": "bra_cpf", + "service_name": "Brazil - CPF status check", + "source_data": { + "identification_number": "12345678900", + "first_name": "JOHN", + "last_name": "DOE", + "date_of_birth": "1980-01-01", + "lgpd_minor": false, + "minor_under_16": false, + "minor_under_18": false + } + } + ], + "warnings": [], + "services_used": [ + "bra_cpf" + ], + "match_score": 1.0 + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-11T10:30:00.000000+00:00" + } + }, + "No match (Declined)": { + "summary": "Registry record does not match — default `no_match_action=DECLINE`", + "value": { + "request_id": "0a1b2c3d-4e5f-6789-abcd-ef0123456789", + "database_validation": { + "status": "Declined", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "tax_number": "12345678900", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-01-01" + }, + "match_type": "no_match", + "validations": [ + { + "validation": { + "full_name": "no_match", + "date_of_birth": "no_match", + "identification_number": "no_match" + }, + "outcome_code": "NO_MATCH", + "outcome_detail": "200", + "service_id": "bra_cpf", + "service_name": "Brazil - CPF status check", + "source_data": { + "identification_number": "00987654321", + "first_name": "TOTALLY", + "last_name": "DIFFERENT NAME", + "date_of_birth": "1999-01-01", + "lgpd_minor": false, + "minor_under_16": false, + "minor_under_18": false + } + } + ], + "warnings": [ + { + "feature": "DATABASE_VALIDATION", + "risk": "DATABASE_VALIDATION_NO_MATCH", + "additional_data": null, + "log_type": "error", + "short_description": "Database validation no match", + "long_description": "The system identified a no match in the database validation, requiring further investigation." + } + ] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-11T10:31:00.000000+00:00" + } + }, + "Stateless (save_api_request=false)": { + "summary": "`validations` collapses to a merged field map; nothing is stored", + "value": { + "request_id": "c4d5e6f7-8901-2345-6789-abcdef012345", + "database_validation": { + "status": "Approved", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "tax_number": "12345678900", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-01-01" + }, + "match_type": "full_match", + "validations": { + "full_name": "full_match", + "date_of_birth": "full_match", + "identification_number": "full_match" + }, + "warnings": [] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-11T10:32:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error - nothing is billed. Preflight field errors occur before any external call. If every unanswered attempt explicitly refuses the input, the response instead includes `validation_errors` with `code: provider_rejected_input` and `retryable: false`; correct the input before retrying. Field-level problems use DRF's standard envelope; catalog problems return a `services` array (plus `requires_onboarding` or `services_requiring_consent` when relevant); missing-field errors include `fields_required_by` mapping each missing field to the services that demand it; services that need at least one extra identifier (e.g. `usa_states_credit_bureau`, which cannot verify a name alone) return `any_field_required_by` mapping each service to the fields of which at least one must be present; format problems checked per service return `invalid_fields_by_service`.", + "content": { + "application/json": { + "examples": { + "Invalid issuing_state": { + "summary": "Country not supported — message lists every valid alpha-3 code", + "value": { + "error": [ + "Invalid issuing state. Valid options are: ARE, ARG, AUS, AUT, BEL, BGD, BOL, BRA, CAN, CHE, CHL, CHN, CIV, COL, CRI, CZE, DEU, DNK, DOM, ECU, ESP, FIN, FRA, GBR, GHA, GLB, GRC, GTM, HKG, HND, IDN, IND, IRL, ITA, KEN, KHM, MAR, MEX, MYS, NGA, NLD, NOR, NZL, OMN, PAN, PER, PHL, POL, PRT, PRY, QAT, SGP, SLV, SVK, SWE, THA, UGA, URY, USA, VEN, ZAF, ZMB, ZWE" + ] + } + }, + "Missing required fields": { + "summary": "Union of required fields across selected services, with the universal-field hint", + "value": { + "error": [ + "Missing required fields: tax_number (or use 'identification_number' which maps to 'tax_number' for BRA)" + ], + "fields_required_by": { + "tax_number": [ + "bra_cpf" + ] + } + } + }, + "Unknown service id": { + "summary": "`services` contains an id that is not in the catalog", + "value": { + "services": [ + "Unknown service_id(s): ['bogus_service']" + ] + } + }, + "Service not live for country": { + "summary": "`services` names a service from another country (or a stub entry)", + "value": { + "services": [ + "Services not currently live for PER: ['bra_cpf']. Available services: ['per_dni', 'per_residential', 'per_tax_registration']" + ] + } + }, + "Service requires onboarding": { + "summary": "Service exists but is not activated for your organization yet", + "value": { + "services": [ + "Services require onboarding before they can be enabled for AUS: ['aus_australia_driver_licence']. Contact Didit support to activate them." + ], + "requires_onboarding": [ + "aus_australia_driver_licence" + ] + } + }, + "Consent missing": { + "summary": "A selected service is flagged requires_consent=true and `consent` was not `true`", + "value": { + "consent": [ + "Explicit end-user consent is required for these database validation services: ['chn_national_id']. Send `consent=true`." + ], + "services_requiring_consent": [ + "chn_national_id" + ] + } + }, + "Country format rule violation": { + "summary": "Identifier fails the country's format rule (length/digits/regex)", + "value": { + "tax_number": [ + "Invalid tax number for BRA: Brazilian CPF (exactly 11 digits). Must be exactly 11 characters long (got 10)." + ] + } + }, + "Service-specific format violation": { + "summary": "A field fails the selected service's own format requirements", + "value": { + "error": [ + "One or more fields do not match the format required by the selected service." + ], + "invalid_fields_by_service": { + "chn_national_id": [ + "national_id_format" + ] + } + } + }, + "Input refused by every attempt": { + "summary": "Correct the input before retrying", + "value": { + "error": "The source refused to run this query with the supplied input.", + "validation_errors": [ + { + "service_id": "bra_cpf", + "code": "provider_rejected_input", + "message": "The source refused to run this query with the supplied input.", + "retryable": false + } + ] + } + } + }, + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "error": { + "description": "Request-level explanation or preflight field errors.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "validation_errors": { + "type": "array", + "description": "Present when every unanswered attempt refused the input after preflight.", + "items": { + "type": "object", + "properties": { + "service_id": { + "type": "string" + }, + "code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retryable": { + "type": "boolean", + "description": "Whether retrying later can resolve this service failure. False means correct the supplied input first." + } + } + } + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "502": { + "description": "No selected service returned a usable result. Inspect each validation_errors entry before retrying; HTTP 502 alone does not establish a transient failure. An empty_provider_response with retryable: true indicates unavailable or unexplained results, including a refused primary followed by an unavailable fallback. Legacy provider_invalid_input and provider_validation_error entries instead require corrected input and may omit retryable. Nothing is billed. When save_api_request=true, the failed attempt is recorded as a session with status Not Finished. Partial failures return 200 with database_validation.errors.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "validation_errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "service_id": { + "type": "string" + }, + "code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retryable": { + "type": "boolean", + "description": "Whether retrying later can resolve this service failure. False means correct the supplied input first." + } + } + } + } + } + }, + "examples": { + "Provider returned no result": { + "summary": "Every selected service failed", + "value": { + "error": "Database validation could not be performed. The external validation service did not return a result. Please try again later.", + "validation_errors": [ + { + "service_id": "bra_cpf", + "code": "empty_provider_response", + "message": "The provider did not return a database validation result.", + "retryable": true + } + ] + } + } + } + } + } + } + } + } + }, + "/v3/phone/send/": { + "post": { + "summary": "Send Phone Code", + "description": "Send a one-time passcode (OTP) to a phone number over WhatsApp, SMS, or another supported channel, then verify it with [`POST /v3/phone/check/`](/standalone-apis/phone-check).\n\n**How send and check pair up.** Verification state is keyed by your application plus the E.164 `phone_number` — the check call does not take `request_id`. Each new send creates a pending verification that lives for **5 minutes**: call the check with the same number and the code the user received before the window closes. Calling send again for the same number while a verification is pending re-sends a code for that same verification (`status: \"Retry\"`, same `request_id`). At most one retry is attached this way (two sends total); a further send starts a fresh verification with a new `request_id`. The 5-minute window is measured from the first send and is not extended by retries.\n\n**Delivery channels.** `options.preferred_channel` selects the channel (`whatsapp` by default; `sms`, `telegram`, `voice`, `rcs`, `viber`, and `zalo` are also supported). When the preferred channel is unavailable for the destination country or number, delivery automatically falls back to SMS — the send still reports `Success`, and the channel actually used is reported by the check response (`phone.verification_method` and the `lifecycle` events).\n\n**Branding.** SMS codes carry your application's SMS sender name, set in the Business Console, at no extra cost. Pass `options.sms_sender_name` to sign one code with another name, or `options.use_white_label_customization: true` to fall back to your white-label public name when no sender name is set. WhatsApp codes use a fixed message; connect your own WhatsApp Business number in the Business Console to send them from your business name and number, billed at the Didit fee only.\n\n**Send statuses.** `Success` — OTP sent for a new verification; `Retry` — OTP re-sent for the pending verification; `Blocked` — the anti-fraud layer refused to send (see `reason`, e.g. `repeated_attempts`, `suspicious`, `spam`). A `Blocked` send immediately finalizes the verification as `Declined` with a high-risk warning, so a follow-up check returns `Expired or Not Found`.\n\n**Billing.** The price depends on the destination country and the channel that actually delivers the message — see [Phone Verification Pricing](/getting-started/phone-verification-pricing). Only `Blocked` sends are guaranteed free: any other unbilled send attempt is charged at verification finalization or expiration, even if delivery was never confirmed or the delivery provider reported the message undeliverable. Checks are always free. As an anti-abuse measure, phone verification is disabled until your organization's first top-up (`403`).\n\n**Session persistence.** Every new verification is persisted as an API-type session: `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/`, the verification appears in the Business Console, and `status.updated` webhooks fire as it progresses.\n\n**Sandbox.** Sandbox API keys skip delivery and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Success` payload with a random `request_id`; no message is sent and nothing is persisted. Use code `123456` on the sandbox check.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limits.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints, plus an anti-abuse cap of 4 send attempts per phone number per hour; exceeding either returns `429`.", + "operationId": "post_v3phone_send", + "x-didit-sdk": { + "resource": "phone", + "method": "send_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/phone/send/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"phone_number\": \"+14155552671\",\n \"options\": {\n \"code_size\": 6,\n \"preferred_channel\": \"whatsapp\",\n \"locale\": \"en-US\"\n },\n \"vendor_data\": \"user-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.phone.send_code(\n phone_number=\"+14155552671\",\n options={\n \"locale\": \"en-US\",\n \"preferred_channel\": \"whatsapp\",\n },\n vendor_data=\"user-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.phone.sendCode({\n phone_number: \"+14155552671\",\n options: {\n locale: \"en-US\",\n preferred_channel: \"whatsapp\",\n },\n vendor_data: \"user-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Phone.SendCode(ctx, didit.PhoneSendCodeParams{\n\tPhoneNumber: \"+14155552671\",\n\tOptions: didit.Some(didit.PhoneSendCodeParamsOptions{\n\t\tLocale: didit.String(\"en-US\"),\n\t\tPreferredChannel: didit.Some(didit.PhoneSendCodeParamsOptionsPreferredChannel(\"whatsapp\")),\n\t}),\n\tVendorData: didit.String(\"user-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.PhoneSendCodeParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.phone().sendCode(PhoneSendCodeParams.builder()\n .phoneNumber(\"+14155552671\")\n .options(PhoneSendCodeParams.Options.builder()\n .locale(\"en-US\")\n .preferredChannel(PhoneSendCodeParams.Options.PreferredChannel.of(\"whatsapp\"))\n .build())\n .vendorData(\"user-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "phone->sendCode([\n 'phone_number' => '+14155552671',\n 'options' => [\n 'locale' => 'en-US',\n 'preferred_channel' => 'whatsapp',\n ],\n 'vendor_data' => 'user-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.phone.send_code(\n phone_number: \"+14155552671\",\n options: {\n locale: \"en-US\",\n preferred_channel: \"whatsapp\",\n },\n vendor_data: \"user-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Phone.SendCodeAsync(new PhoneSendCodeParams\n{\n PhoneNumber = \"+14155552671\",\n Options = new PhoneSendCodeParamsOptions\n {\n Locale = \"en-US\",\n PreferredChannel = new PhoneSendCodeParamsOptionsPreferredChannel(\"whatsapp\"),\n },\n VendorData = \"user-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "phone_number": { + "type": "string", + "maxLength": 20, + "description": "Recipient phone number in E.164 format — leading `+` and country code (e.g., `+14155552671`). Numbers are validated and normalized to E.164; unparseable or invalid numbers return `400` with a `phone_number` field error.", + "example": "+14155552671" + }, + "options": { + "type": "object", + "description": "OTP delivery options. All fields are optional.", + "properties": { + "code_size": { + "type": "integer", + "minimum": 4, + "maximum": 8, + "default": 6, + "description": "Number of digits in the OTP." + }, + "locale": { + "type": "string", + "maxLength": 5, + "pattern": "^[a-z]{2,3}(-[A-Z]{2,3})?$", + "description": "BCP-47 locale used to localize the verification message (e.g., `en-US`, `fr`, `es`). Invalid formats return `400`.", + "example": "en-US" + }, + "preferred_channel": { + "type": "string", + "enum": [ + "whatsapp", + "sms", + "telegram", + "voice", + "rcs", + "viber", + "zalo" + ], + "default": "whatsapp", + "description": "Preferred delivery channel. If the channel is unavailable for the destination country or number, the message automatically falls back to SMS — the send still reports `Success`." + }, + "use_white_label_customization": { + "type": "boolean", + "default": false, + "description": "When `true`, the send is white-labelled: if your application has no SMS sender name, the SMS names your white-label public name instead (\"123456 is your verification code for Acme Bank\"). Your SMS sender name, when set, is used with or without this flag. Applies to SMS messages, including an SMS fallback from another channel; WhatsApp codes are sent with a fixed message and are not branded. Defaults to `false`." + }, + "sms_sender_name": { + "type": "string", + "nullable": true, + "maxLength": 30, + "example": "Acme Bank", + "description": "The name this code's SMS carries (\"123456 is your verification code for Acme Bank\"), instead of your application's SMS sender name. Free. 1 to 30 characters: letters, digits, spaces and ' & - ! only, not only digits, with single spaces between words; any other value returns `400` and no code is sent. Omitted, `null` or blank, your application's SMS sender name is used. Each application can start using up to two new names per day (UTC); past that, the code is sent with your application's name. Applies to SMS messages, including an SMS fallback from another channel; WhatsApp codes are not branded.", + "pattern": "^$|^(?![\\d ]+$)[\\p{L}\\p{N}'&!-]+( [\\p{L}\\p{N}'&!-]+)*$" + } + } + }, + "signals": { + "type": "object", + "description": "Optional device and network signals about the end user, forwarded to the anti-fraud layer to improve detection of abusive or automated traffic. All fields are optional.", + "properties": { + "ip": { + "type": "string", + "description": "IP address of the end user's device. IPv4 or IPv6.", + "example": "192.0.2.1" + }, + "device_id": { + "type": "string", + "maxLength": 255, + "description": "Unique identifier of the end user's device. Android: `ANDROID_ID`; iOS: `identifierForVendor`.", + "example": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2" + }, + "device_platform": { + "type": "string", + "enum": [ + "android", + "ios", + "ipados", + "tvos", + "web" + ], + "description": "Platform of the end user's device." + }, + "device_model": { + "type": "string", + "maxLength": 255, + "description": "Model of the end user's device.", + "example": "iPhone17,2" + }, + "os_version": { + "type": "string", + "maxLength": 64, + "description": "Operating-system version of the end user's device.", + "example": "18.0.1" + }, + "app_version": { + "type": "string", + "maxLength": 64, + "description": "Version of your application.", + "example": "1.2.34" + }, + "user_agent": { + "type": "string", + "maxLength": 512, + "description": "User agent of the end user's browser or app.", + "example": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1" + } + } + }, + "vendor_data": { + "type": "string", + "description": "Optional caller-controlled identifier (your internal user id, an email, a UUID, etc.) persisted on the session and echoed back in the send response, the matching check response, webhooks, and the Business Console. Use it to correlate Didit's `request_id` with your user record." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Optional free-form JSON object persisted on the session and echoed back in the send response, the matching check response, webhooks, and the Business Console." + } + }, + "required": [ + "phone_number" + ] + }, + "examples": { + "WhatsApp (default)": { + "summary": "6-digit OTP via WhatsApp, falling back to SMS automatically", + "value": { + "phone_number": "+14155552671", + "options": { + "preferred_channel": "whatsapp", + "locale": "en-US" + }, + "vendor_data": "user-1234" + } + }, + "SMS with custom code length": { + "summary": "Force SMS delivery with a 4-digit code", + "value": { + "phone_number": "+34699999999", + "options": { + "preferred_channel": "sms", + "code_size": 4, + "locale": "es" + } + } + }, + "With fraud signals": { + "summary": "Forward device signals to strengthen anti-fraud screening", + "value": { + "phone_number": "+14155552671", + "signals": { + "ip": "192.0.2.1", + "device_id": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2", + "device_platform": "ios", + "device_model": "iPhone17,2", + "os_version": "18.0.1", + "app_version": "1.2.34" + }, + "vendor_data": "user-1234" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Send acknowledged. Inspect `status`: `Success` and `Retry` mean a code is on its way; `Blocked` means the anti-fraud layer refused and the verification is already finalized as `Declined`. `request_id` is the persisted session id (same id on a `Retry`).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PhoneVerificationSendResponse" + }, + "examples": { + "Success": { + "summary": "OTP sent for a new verification", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Success", + "reason": null, + "vendor_data": "user-1234", + "metadata": null + } + }, + "Retry": { + "summary": "Second send for the same number inside the 5-minute window — same request_id", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Retry", + "reason": null, + "vendor_data": "user-1234", + "metadata": null + } + }, + "Blocked": { + "summary": "Anti-fraud refusal — verification finalized as Declined, nothing billed", + "value": { + "request_id": "0d8af1f4-0c43-46d7-b75c-6e4e22c0a7b3", + "status": "Blocked", + "reason": "repeated_attempts", + "vendor_data": "user-1234", + "metadata": null + } + } + } + } + } + }, + "400": { + "description": "Validation error. Malformed input returns DRF's field-error envelope (one array of messages per offending field, nested under `options` for option errors). A number that parses but is rejected at send time (unreachable, or an ineligible line type such as a short code) returns a `{\"detail\": ...}` envelope instead.", + "content": { + "application/json": { + "examples": { + "Invalid phone number (field error)": { + "summary": "Number is not valid E.164", + "value": { + "phone_number": [ + "Invalid phone number provided." + ] + } + }, + "Missing phone number": { + "summary": "`phone_number` not included in the body", + "value": { + "phone_number": [ + "This field is required." + ] + } + }, + "Invalid options": { + "summary": "Out-of-range `code_size`, malformed `locale`, unknown `preferred_channel`", + "value": { + "options": { + "code_size": [ + "Ensure this value is less than or equal to 8." + ], + "locale": [ + "Ensure this field has no more than 5 characters." + ], + "preferred_channel": [ + "\"carrier_pigeon\" is not a valid choice." + ] + } + } + }, + "Number rejected at send time": { + "summary": "Valid E.164 shape but the number cannot receive an OTP", + "value": { + "detail": "Invalid phone number provided." + } + }, + "Ineligible line type": { + "summary": "Line type cannot receive an OTP (e.g., short code)", + "value": { + "detail": "Invalid phone line type provided." + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing (`{\"detail\": ...}`); a wrong, revoked or expired key returns `401`. Also returned before any send when the organization has never topped up (anti-abuse gate) or when its balance cannot cover the destination's send price (`{\"error\": ...}`).", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Top-up required": { + "summary": "Phone verification stays disabled until the organization's first top-up", + "value": { + "detail": "To protect against abuse, phone verification is disabled until your first top-up." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the send price", + "value": { + "error": "You don't have enough credits to perform this request." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). Additionally, an anti-abuse cap of 4 verification attempts per phone number per hour applies upstream. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers. Note: the `X-RateLimit-*`/`Retry-After` headers accompany only the 300/min write-limit 429; the per-number cap 429 carries no rate-limit headers.", + "content": { + "application/json": { + "examples": { + "Per-number cap": { + "summary": "More than 4 verification attempts for the same number in the last hour", + "value": { + "detail": "Maximum verification attempts reached for this phone number. Only 4 authentication attempts are allowed per hour. Try again later or use a different number." + } + }, + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "502": { + "description": "The phone verification provider is temporarily unavailable - a 5xx response, timeout, or connection failure that survived the built-in retries. Safe to retry; nothing is billed and no attempt is consumed.", + "content": { + "application/json": { + "examples": { + "Provider outage": { + "summary": "The provider did not answer the send request", + "value": { + "detail": "Phone verification is temporarily unavailable. Please try again shortly.", + "code": "phone_provider_unavailable" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "Unexpected failure while creating the verification that is not a known provider outage. Safe to retry.", + "content": { + "application/json": { + "examples": { + "Provider error": { + "summary": "OTP delivery could not be initiated", + "value": { + "detail": "Error creating phone verification" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/phone/check/": { + "post": { + "summary": "Check Phone Code", + "description": "Verify the OTP delivered by [`POST /v3/phone/send/`](/standalone-apis/phone-send) and get the final verification result plus phone intelligence: carrier name and line type, virtual (VoIP) and disposable flags, and duplicate usage of the number across your sessions.\n\n**How the check finds the verification.** Matching is by your application plus the E.164 `phone_number` — `request_id` is not an input. The most recent pending verification created within the last **5 minutes** is checked. If there is none (never sent, already finalized, or older than 5 minutes) the endpoint returns `200` with `status: \"Expired or Not Found\"`.\n\n**Attempt budget.** Each verification allows **3 code attempts**. The first two wrong codes return `status: \"Failed\"` with the attempts remaining and `phone: null`; the third wrong code finalizes the verification as `Declined` with a `VERIFICATION_CODE_ATTEMPTS_EXCEEDED` warning and returns the full phone report.\n\n**Outcomes.** `Approved` — correct code and no declining risk. `Declined` — terminal: the code was correct but a declining risk matched (a `DECLINE` action below, a blocklisted or high-risk number), or the attempt budget was exhausted. `Failed` — wrong code, attempts remaining. `Expired or Not Found` — nothing to check. On `Approved`/`Declined` the `request_id` equals the send's `request_id` (the session id) and `phone` carries the full report (`carrier`, `is_virtual`, `is_disposable`, `warnings`, `lifecycle`, `matches`); on `Failed` and `Expired or Not Found` the `request_id` is a one-off random UUID.\n\n**Risk actions.** `duplicated_phone_number_action`, `disposable_number_action`, and `voip_number_action` decide what happens when the corresponding risk is detected on a correct code: `DECLINE` flips the final status to `Declined`; `NO_ACTION` (default) records the risk in `phone.warnings` without affecting the status.\n\n**Billing.** Checks are free — delivery is billed on the send side (see [Phone Verification Pricing](/getting-started/phone-verification-pricing)).\n\n**Session persistence.** A finalized check updates the session created by the send (visible in the Business Console, queryable via `GET /v3/session/{sessionId}/decision/`) and fires a `status.updated` webhook.\n\n**Sandbox.** Sandbox API keys skip all processing: any well-formed code (4–8 digits, `123456` included) returns a static `Approved` payload with a simplified flat `phone` object (`status`, `phone_number`, `country_code`, `carrier_name`, `line_type`, `flags`); malformed input still returns `400`. Nothing is persisted.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limits.** Shared write budget of 300 requests/min per API key, plus the upstream anti-abuse cap of 4 attempts per number per hour; exceeding either returns `429`.\n\n**Social footprint.** Set `enable_social` to `true` to add a `phone_social` block: the online platforms this number is registered on, with per-category counts and any profile details available. `profiles_registered: 0` means the platforms were checked and the number was found on none of them - a valid, billable answer. The block is absent when the add-on was not requested or the check could not be completed, and it is not billed in that case. Billing adds one `phone_social` unit per request on top of this endpoint's own price.", + "operationId": "post_v3phone_check", + "x-didit-sdk": { + "resource": "phone", + "method": "check_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/phone/check/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"phone_number\": \"+14155552671\",\n \"code\": \"123456\",\n \"voip_number_action\": \"DECLINE\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.phone.check_code(\n phone_number=\"+14155552671\",\n code=\"123456\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.phone.checkCode({\n phone_number: \"+14155552671\",\n code: \"123456\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Phone.CheckCode(ctx, didit.PhoneCheckCodeParams{\n\tPhoneNumber: \"+14155552671\",\n\tCode: \"123456\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.PhoneCheckCodeParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.phone().checkCode(PhoneCheckCodeParams.builder()\n .phoneNumber(\"+14155552671\")\n .code(\"123456\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "phone->checkCode([\n 'phone_number' => '+14155552671',\n 'code' => '123456',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.phone.check_code(\n phone_number: \"+14155552671\",\n code: \"123456\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Phone.CheckCodeAsync(new PhoneCheckCodeParams\n{\n PhoneNumber = \"+14155552671\",\n Code = \"123456\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "phone_number": { + "type": "string", + "maxLength": 20, + "description": "The same phone number used in the matching [`POST /v3/phone/send/`](/standalone-apis/phone-send) call, in E.164 format. This is what links the check to the send.", + "example": "+14155552671" + }, + "code": { + "type": "string", + "minLength": 4, + "maxLength": 8, + "pattern": "^[0-9]{4,8}$", + "description": "The OTP the end user received. Must be 4–8 numeric digits — anything else returns `400` without consuming an attempt. A well-formed but wrong code returns `200` with `status: \"Failed\"` and consumes one of the 3 attempts.", + "example": "123456" + }, + "duplicated_phone_number_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the same number was already used by a different user (different `vendor_data`) of your application. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `phone.warnings` and fills `phone.matches`." + }, + "disposable_number_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the carrier lookup flags the number as a temporary/burner line (`phone.is_disposable`). `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `phone.warnings`." + }, + "voip_number_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the carrier lookup reports a virtual line type — `voip`, `isp`, or `vpn` (`phone.is_virtual`). `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `phone.warnings`." + }, + "enable_enrichment": { + "type": "boolean", + "default": false, + "description": "When `true`, also run phone intelligence on the number: a risk score, the line type, whether it was recently ported and a trust index. Billed as one `phone_intelligence_api` unit per request ($0.05 at list price) in addition to this endpoint's own price. The request returns `403` when your balance cannot cover it.", + "example": false + }, + "phone_intelligence_score_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the phone intelligence risk score is above the threshold. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "phone_type_risk_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the number is a high-risk line type. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "recent_port_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the number was ported to another carrier recently. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "low_phone_trust_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the phone intelligence trust index is at or below the threshold. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "enable_social": { + "type": "boolean", + "default": false, + "description": "When true, also look up the social footprint of the number - the messaging, social, e-commerce and professional platforms it is registered on. Billed as one `phone_social` unit per request in addition to this endpoint's own price.", + "example": true + }, + "phone_no_social_presence_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the social footprint check finds the number registered on none of the platforms it covers. Only NO_ACTION and DECLINE are accepted on this endpoint; REVIEW is available on the workflow configuration key of the same name." + } + }, + "required": [ + "phone_number", + "code" + ] + }, + "examples": { + "Default": { + "summary": "Record any risks but never auto-decline", + "value": { + "phone_number": "+14155552671", + "code": "123456" + } + }, + "Strict risk policy": { + "summary": "Auto-decline VoIP and disposable lines", + "value": { + "phone_number": "+14155552671", + "code": "123456", + "voip_number_action": "DECLINE", + "disposable_number_action": "DECLINE" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Check completed — wrong codes and missing verifications also return `200`; inspect `status`, not the HTTP code. `phone` is populated only on finalized outcomes (`Approved`/`Declined`), `null` on `Failed`, and absent on `Expired or Not Found`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PhoneVerificationCheckResponse" + }, + "examples": { + "Approved": { + "summary": "Correct code — full phone report with carrier, lifecycle, and matches", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Approved", + "message": "The verification code is correct.", + "phone": { + "status": "Approved", + "phone_number_prefix": "+1", + "phone_number": "4155552671", + "full_number": "+14155552671", + "country_code": "US", + "country_name": "United States", + "carrier": { + "name": "AT&T", + "type": "mobile" + }, + "is_disposable": false, + "is_virtual": false, + "verification_method": "whatsapp", + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [], + "lifecycle": [ + { + "type": "PHONE_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null, + "channel": "whatsapp", + "actual_channel": "whatsapp" + }, + "fee": 0.0509 + }, + { + "type": "PHONE_DELIVERY_DELIVERED", + "timestamp": "2026-06-12T01:23:43.118220+00:00", + "details": { + "channel": "whatsapp", + "status": "delivered" + }, + "fee": 0 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "PHONE_VERIFICATION_APPROVED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": null, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Failed (attempts remaining)": { + "summary": "Wrong code — the user can retry; request_id here is a one-off UUID", + "value": { + "request_id": "11111111-2222-3333-4444-555555555555", + "status": "Failed", + "message": "The verification code is incorrect. Attempts remaining: 2", + "phone": null, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:21.512340+00:00" + } + }, + "Declined (max attempts)": { + "summary": "Third wrong code — verification finalized as Declined", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Declined", + "message": "The verification code is incorrect. Maximum attempts reached.", + "phone": { + "status": "Declined", + "phone_number_prefix": "+1", + "phone_number": "4155552671", + "full_number": "+14155552671", + "country_code": "US", + "country_name": "United States", + "carrier": { + "name": "AT&T", + "type": "mobile" + }, + "is_disposable": false, + "is_virtual": false, + "verification_method": "whatsapp", + "verification_attempts": 1, + "verified_at": null, + "warnings": [ + { + "feature": "PHONE", + "risk": "VERIFICATION_CODE_ATTEMPTS_EXCEEDED", + "additional_data": null, + "log_type": "error", + "short_description": "Verification code attempts exceeded", + "long_description": "The system detected that the phone number has exceeded the maximum number of verification code attempts." + } + ], + "lifecycle": [ + { + "type": "PHONE_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null, + "channel": "whatsapp", + "actual_channel": "whatsapp" + }, + "fee": 0.0509 + }, + { + "type": "INVALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:01.118220+00:00", + "details": { + "code_tried": "111111", + "status": "Failed" + }, + "fee": 0 + }, + { + "type": "INVALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:21.512340+00:00", + "details": { + "code_tried": "222222", + "status": "Failed" + }, + "fee": 0 + }, + { + "type": "INVALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "333333", + "status": "Failed" + }, + "fee": 0 + }, + { + "type": "PHONE_VERIFICATION_DECLINED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": { + "reason": "VERIFICATION_CODE_ATTEMPTS_EXCEEDED" + }, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Declined (risk policy)": { + "summary": "Correct code, but the line is VoIP and voip_number_action=DECLINE", + "value": { + "request_id": "7f1c9d2e-44a1-4b6e-9a3e-5b8e6f1c2d3a", + "status": "Declined", + "message": "The verification code is correct.", + "phone": { + "status": "Declined", + "phone_number_prefix": "+1", + "phone_number": "5005550006", + "full_number": "+15005550006", + "country_code": "US", + "country_name": "United States", + "carrier": { + "name": "CLEC LLC", + "type": "voip" + }, + "is_disposable": false, + "is_virtual": true, + "verification_method": "sms", + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [ + { + "feature": "PHONE", + "risk": "VOIP_NUMBER_DETECTED", + "additional_data": null, + "log_type": "error", + "short_description": "VoIP number detected", + "long_description": "The system detected that the phone number is a VoIP number, which is not allowed." + } + ], + "lifecycle": [ + { + "type": "PHONE_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null, + "channel": "whatsapp", + "actual_channel": "sms" + }, + "fee": 0.0509 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "PHONE_VERIFICATION_DECLINED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": { + "reason": "VOIP_NUMBER_DETECTED" + }, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Expired or Not Found": { + "summary": "No pending verification for this number in the last 5 minutes", + "value": { + "request_id": "69cc35c6-280a-47a6-ab8c-4f32abc02ed0", + "status": "Expired or Not Found", + "message": "No pending phone verification found in the last 5 minutes.", + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-12T01:24:47.311323+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error — DRF's field-error envelope: one array of messages per offending field.", + "content": { + "application/json": { + "examples": { + "Invalid code format": { + "summary": "`code` is not 4–8 numeric digits", + "value": { + "code": [ + "Invalid code format. Enter 4 to 8 numeric digits." + ] + } + }, + "Invalid phone number": { + "summary": "`phone_number` is not valid E.164", + "value": { + "phone_number": [ + "Invalid phone number provided." + ] + } + }, + "Invalid action": { + "summary": "Risk actions accept only NO_ACTION or DECLINE", + "value": { + "voip_number_action": [ + "\"REVIEW\" is not a valid choice." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing; a wrong, revoked or expired key returns `401`. Checks are free, so there is no credit check here.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). Additionally, an anti-abuse cap of 4 verification attempts per phone number per hour applies upstream. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers. Note: the `X-RateLimit-*`/`Retry-After` headers accompany only the 300/min write-limit 429; the per-number cap 429 carries no rate-limit headers.", + "content": { + "application/json": { + "examples": { + "Per-number cap": { + "summary": "More than 4 verification attempts for the same number in the last hour", + "value": { + "detail": "Maximum verification attempts reached for this phone number. Only 4 authentication attempts are allowed per hour. Try again later or use a different number." + } + }, + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "502": { + "description": "The phone verification provider is temporarily unavailable - a 5xx response, timeout, or connection failure that survived the built-in retries. Safe to retry; the attempt budget is not consumed.", + "content": { + "application/json": { + "examples": { + "Provider outage": { + "summary": "The provider did not answer the check request", + "value": { + "detail": "Phone verification is temporarily unavailable. Please try again shortly.", + "code": "phone_provider_unavailable" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "Unexpected failure while verifying the code upstream that is not a known provider outage. Safe to retry; the attempt budget is only consumed by completed checks.", + "content": { + "application/json": { + "examples": { + "Provider error": { + "summary": "Code verification could not be completed", + "value": { + "detail": "Error checking phone verification" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/bank/account-check/": { + "post": { + "summary": "Check a bank account", + "description": "**Not available in production yet.** Bank Verification is switched off in production, so until it is switched on this endpoint answers `404` with `bank_verification_not_available`. Do not build on it yet. The rest of this description is what the endpoint does once it is available.\n\nCheck that a bank account belongs to the person or company you name, before you send money to it. No session and no sign-in: send the name and the account, read the result. It is the [account check](/core-technology/bank-verification/account-check) of the Bank Verification step, run from your backend.\n\n**What you send.** The account's `country` (ISO 3166-1 alpha-3), the `account` the way that country writes it, and the `holder` name: `full_name` for a person or `business_name` for a company. A business name checks a business account.\n\n**What you get back.** `result` is `match`, `close_match`, `no_match` or `cannot_verify`. On a `close_match`, `holder_name` is the name the bank holds the account under, when the bank returned it. `account` comes back masked to its last 4 characters.\n\n**Billing.** Each check the bank answers is billed once at the Account check price of the account's country, whatever the result: a `no_match`, an account that does not exist and a `cannot_verify` are billed too. A request answered with an error is never billed.\n\n**Every request is a new check.** There is no idempotency key and no endpoint to read a check back, so a retried request runs, and is billed, again. Store the response.\n\n**Sandbox.** Where Bank Verification is available, a sandbox key calls no bank and is charged nothing. The last 4 characters of the IBAN, else of the account number, choose the answer: `0001` a `close_match` with a `holder_name`, `0002` a `close_match` without one, `0003` and `0006` a `no_match`, `0004` `cannot_verify`, `0005` a `503` and `0009` a `400`. Any other ending is a `match`.\n\n**API keys.** A scoped API key needs write access to sessions.", + "operationId": "post_v3_bank_account_check", + "tags": [ + "Standalone APIs" + ], + "x-didit-sdk": { + "resource": "bank", + "method": "account_check", + "auth": "api_key", + "server": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckRequest" + }, + "examples": { + "Person, United Kingdom": { + "summary": "A sort code and account number, checked against a person's name", + "value": { + "country": "GBR", + "account": { + "sort_code": "20-45-67", + "account_number": "31926819" + }, + "holder": { + "full_name": "Maria Oliveira" + }, + "vendor_data": "payout_20481" + } + }, + "Company, IBAN": { + "summary": "An IBAN, checked against a company's name", + "value": { + "country": "DEU", + "account": { + "iban": "DE89 3704 0044 0532 0130 00" + }, + "holder": { + "business_name": "Northwind Trading GmbH" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The bank answered. The check is billed, whatever the result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckResponse" + }, + "examples": { + "Close match": { + "summary": "The bank holds the account under a close name", + "value": { + "request_id": "c2f4a9e0-7b1d-4c3a-9e55-1d0a6b8f2a47", + "result": "close_match", + "holder_name": "MARIA R OLIVEIRA", + "account": "****6819", + "vendor_data": "payout_20481", + "created_at": "2026-10-21T14:32:08Z" + } + }, + "Match": { + "summary": "The bank holds the account under the name sent", + "value": { + "request_id": "5b0e2d7c-41a8-4f6e-9c3b-8e7d1a2f6c90", + "result": "match", + "holder_name": null, + "account": "****3000", + "vendor_data": null, + "created_at": "2026-10-21T14:35:41Z" + } + } + } + } + } + }, + "400": { + "description": "Refused as sent, and not billed. `field` names the body field to correct and `reason` says why, with the same reasons the hosted account form shows. Only the first refused field is reported.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Missing field": { + "summary": "The country's check needs a sort code", + "value": { + "detail": "The sort code is not valid (required).", + "code": "invalid_account_input", + "field": "account.sort_code", + "reason": "required" + } + }, + "Invalid IBAN": { + "summary": "The IBAN has the wrong length for its country", + "value": { + "detail": "The iban is not valid (length).", + "code": "invalid_account_input", + "field": "account.iban", + "reason": "length" + } + }, + "Refused by the bank": { + "summary": "The bank refused the account as sent", + "value": { + "detail": "The account was refused as typed (rejected).", + "code": "invalid_account_input", + "field": "account.account_number", + "reason": "rejected" + } + }, + "No name": { + "summary": "`holder` has neither `full_name` nor `business_name`", + "value": { + "detail": "Send full_name for a person or business_name for a company.", + "code": "invalid_account_input", + "field": "holder", + "reason": "required" + } + }, + "Consent required": { + "summary": "The country's account check needs the account holder's consent", + "value": { + "detail": "Account checks in this country need the account holder's explicit consent. Ask for it, then send `consent=true`.", + "code": "invalid_account_input", + "field": "consent", + "reason": "required" + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "402": { + "description": "The check cannot be paid for, so it did not run. Top up your balance, then send it again.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Out of credit": { + "summary": "Your balance cannot cover one check", + "value": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me", + "code": "insufficient_credits" + } + }, + "Not in your contract": { + "summary": "Your contract has no price for the Account check API", + "value": { + "detail": "Your contract does not price the Account check API. Contact your account manager.", + "code": "contract_rate_missing" + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header or the bearer token is missing or invalid, the request came from an address outside the key's IP list, or a scoped key has no write access to sessions.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No valid API key or token", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "No sessions write": { + "summary": "A scoped key without write access to sessions", + "value": { + "detail": "This API key has no write access to sessions." + } + } + } + } + } + }, + "404": { + "description": "No account check serves this account yet, so nothing was checked or billed. `country_not_covered`: none serves its country, or its kind of account (person or business) there. `bank_not_covered`: none serves its bank. `bank_verification_not_available`: Bank Verification is not enabled in production yet, which is the answer for every request there until it is switched on.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Country not covered": { + "summary": "No account check serves the country", + "value": { + "detail": "No account check serves this country yet.", + "code": "country_not_covered" + } + }, + "Bank not covered": { + "summary": "No account check serves the account's bank", + "value": { + "detail": "No account check serves this account's bank yet.", + "code": "bank_not_covered" + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + }, + "503": { + "description": "The bank could not answer. Nothing was checked or billed; send the check again later.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Unavailable": { + "summary": "The bank did not answer", + "value": { + "detail": "The account check is temporarily unavailable and this request was not charged. Please try again.", + "code": "account_check_unavailable" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/bank/account-check/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"country\": \"GBR\",\n \"account\": {\"sort_code\": \"20-45-67\", \"account_number\": \"31926819\"},\n \"holder\": {\"full_name\": \"Maria Oliveira\"},\n \"vendor_data\": \"payout_20481\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.bank.account_check(\n country=\"GBR\",\n account={\n \"account_number\": \"31926819\",\n \"sort_code\": \"20-45-67\",\n },\n holder={\n \"full_name\": \"Maria Oliveira\",\n },\n vendor_data=\"payout_20481\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.bank.accountCheck({\n country: \"GBR\",\n account: {\n account_number: \"31926819\",\n sort_code: \"20-45-67\",\n },\n holder: {\n full_name: \"Maria Oliveira\",\n },\n vendor_data: \"payout_20481\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Bank.AccountCheck(ctx, didit.BankAccountCheckRequest{\n\tCountry: \"GBR\",\n\tAccount: didit.BankAccountCheckRequestAccount{\n\t\tAccountNumber: didit.String(\"31926819\"),\n\t\tSortCode: didit.String(\"20-45-67\"),\n\t},\n\tHolder: didit.BankAccountCheckHolder{\n\t\tFullName: didit.String(\"Maria Oliveira\"),\n\t},\n\tVendorData: didit.String(\"payout_20481\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BankAccountCheckHolder;\nimport me.didit.types.BankAccountCheckPersonHolder;\nimport me.didit.types.BankAccountCheckRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.bank().accountCheck(BankAccountCheckRequest.builder()\n .country(\"GBR\")\n .account(BankAccountCheckRequest.Account.builder()\n .accountNumber(\"31926819\")\n .sortCode(\"20-45-67\")\n .build())\n .holder(BankAccountCheckHolder.ofBankAccountCheckPersonHolder(BankAccountCheckPersonHolder.builder()\n .fullName(\"Maria Oliveira\")\n .build()))\n .vendorData(\"payout_20481\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "bank->accountCheck([\n 'country' => 'GBR',\n 'account' => [\n 'account_number' => '31926819',\n 'sort_code' => '20-45-67',\n ],\n 'holder' => [\n 'full_name' => 'Maria Oliveira',\n ],\n 'vendor_data' => 'payout_20481',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.bank.account_check(\n country: \"GBR\",\n account: {\n account_number: \"31926819\",\n sort_code: \"20-45-67\",\n },\n holder: {\n full_name: \"Maria Oliveira\",\n },\n vendor_data: \"payout_20481\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Bank.AccountCheckAsync(new BankAccountCheckRequest\n{\n Country = \"GBR\",\n Account = new()\n {\n AccountNumber = \"31926819\",\n SortCode = \"20-45-67\",\n },\n Holder = new()\n {\n FullName = \"Maria Oliveira\",\n },\n VendorData = \"payout_20481\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/email/send/": { + "post": { + "summary": "Send Email Code", + "description": "Send a one-time passcode (OTP) to an email address, then verify it with [`POST /v3/email/check/`](/standalone-apis/email-check).\n\n**How send and check pair up.** Verification state is keyed by your application plus the `email` address — the check call does not take `request_id`. Each new send creates a pending verification that lives for **5 minutes**: call the check with the same address and the code the user received before the window closes. Calling send again for the same address while a verification is pending generates a fresh code for that same verification (`status: \"Retry\"`, same `request_id`); the previous code stops working. At most one retry is attached this way (two sends total); a further send starts a fresh verification with a new `request_id`. The 5-minute window is measured from the first send and is not extended by retries.\n\n**Deliverability pre-check.** Before anything is sent, the address goes through syntax and DNS/MX validation. Addresses that cannot receive mail return `200` with `status: \"Undeliverable\"` and `reason: \"email_can_not_be_delivered\"` — no email is sent, nothing is billed, and the verification is immediately finalized as `Declined` (a follow-up check returns `Expired or Not Found`). A downstream send failure reports the same way.\n\n**Code format and branding.** Codes are 4–8 characters (`options.code_size`, default 6), numeric by default; set `options.alphanumeric_code: true` for uppercase letters and digits (the check comparison is case-insensitive). The email template is localized via `options.locale` (54 supported languages) and can use your application's white-label branding via `options.use_white_label_customization`.\n\n**Billing.** One Email Verification API credit per successful send (`status: \"Success\"`), charged at send time. `Retry` and `Undeliverable` sends are free, and checks are free.\n\n**Session persistence.** Every new verification is persisted as an API-type session: `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/`, the verification appears in the Business Console, and `status.updated` webhooks fire as it progresses.\n\n**Sandbox.** Sandbox API keys skip delivery and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Success` payload with a random `request_id`; no email is sent and nothing is persisted. Use code `123456` on the sandbox check.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3email_send", + "x-didit-sdk": { + "resource": "email", + "method": "send_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/email/send/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"email\": \"alice@example.com\",\n \"options\": { \"code_size\": 6, \"locale\": \"en\" },\n \"vendor_data\": \"user-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.email.send_code(\n email=\"alice@example.com\",\n options={\n \"locale\": \"en\",\n },\n vendor_data=\"user-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.email.sendCode({\n email: \"alice@example.com\",\n options: {\n locale: \"en\",\n },\n vendor_data: \"user-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Email.SendCode(ctx, didit.EmailSendCodeParams{\n\tEmail: \"alice@example.com\",\n\tOptions: didit.Some(didit.EmailSendCodeParamsOptions{\n\t\tLocale: didit.Some(didit.EmailSendCodeParamsOptionsLocale(\"en\")),\n\t}),\n\tVendorData: didit.String(\"user-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.EmailSendCodeParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.email().sendCode(EmailSendCodeParams.builder()\n .email(\"alice@example.com\")\n .options(EmailSendCodeParams.Options.builder()\n .locale(EmailSendCodeParams.Options.Locale.of(\"en\"))\n .build())\n .vendorData(\"user-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "email->sendCode([\n 'email' => 'alice@example.com',\n 'options' => [\n 'locale' => 'en',\n ],\n 'vendor_data' => 'user-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.email.send_code(\n email: \"alice@example.com\",\n options: {\n locale: \"en\",\n },\n vendor_data: \"user-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Email.SendCodeAsync(new EmailSendCodeParams\n{\n Email = \"alice@example.com\",\n Options = new EmailSendCodeParamsOptions\n {\n Locale = new EmailSendCodeParamsOptionsLocale(\"en\"),\n },\n VendorData = \"user-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "Recipient email address. Malformed addresses return `400`; syntactically valid addresses that cannot receive mail (failed DNS/MX validation) return `200` with `status: \"Undeliverable\"`.", + "example": "alice@example.com" + }, + "options": { + "type": "object", + "description": "OTP format, localization, and branding options. All fields are optional.", + "properties": { + "code_size": { + "type": "integer", + "minimum": 4, + "maximum": 8, + "default": 6, + "description": "Number of characters in the OTP (digits by default; uppercase letters and digits when `alphanumeric_code` is `true`)." + }, + "alphanumeric_code": { + "type": "boolean", + "default": false, + "description": "When `true`, the OTP is composed of uppercase letters `A–Z` and digits `0–9` instead of digits only. The `/v3/email/check/` comparison is case-insensitive." + }, + "locale": { + "type": "string", + "maxLength": 5, + "enum": [ + "en", + "ar", + "bn", + "bg", + "bs", + "ca", + "cs", + "da", + "de", + "el", + "es", + "et", + "fa", + "fi", + "fr", + "he", + "hi", + "hr", + "hu", + "hy", + "id", + "it", + "ja", + "ka", + "kk", + "ko", + "ky", + "lt", + "lv", + "cnr", + "mk", + "mn", + "ms", + "nl", + "no", + "pl", + "pt-BR", + "pt", + "ro", + "ru", + "sk", + "sl", + "so", + "sq", + "sr", + "sv", + "th", + "tr", + "uk", + "uz", + "vi", + "zh-CN", + "zh-TW", + "zh" + ], + "description": "Language of the OTP email template. Defaults to `en`. Unsupported values return `400` listing all supported locales.", + "example": "en" + }, + "use_white_label_customization": { + "type": "boolean", + "default": false, + "description": "When `true`, the OTP email uses your application's white-label customization (logo, colors, sender branding) instead of the default Didit template." + } + } + }, + "signals": { + "type": "object", + "description": "Optional device and network signals about the end user, forwarded to the anti-fraud layer to improve detection of abusive or automated traffic. All fields are optional.", + "properties": { + "ip": { + "type": "string", + "description": "IP address of the end user's device. IPv4 or IPv6.", + "example": "192.0.2.1" + }, + "device_id": { + "type": "string", + "maxLength": 255, + "description": "Unique identifier of the end user's device. Android: `ANDROID_ID`; iOS: `identifierForVendor`.", + "example": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2" + }, + "device_platform": { + "type": "string", + "enum": [ + "android", + "ios", + "ipados", + "tvos", + "web" + ], + "description": "Platform of the end user's device." + }, + "device_model": { + "type": "string", + "maxLength": 255, + "description": "Model of the end user's device.", + "example": "iPhone17,2" + }, + "os_version": { + "type": "string", + "maxLength": 64, + "description": "Operating-system version of the end user's device.", + "example": "18.0.1" + }, + "app_version": { + "type": "string", + "maxLength": 64, + "description": "Version of your application.", + "example": "1.2.34" + }, + "user_agent": { + "type": "string", + "maxLength": 512, + "description": "User agent of the end user's browser or app.", + "example": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1" + } + } + }, + "vendor_data": { + "type": "string", + "description": "Optional caller-controlled identifier (your internal user id, an email, a UUID, etc.) persisted on the session and echoed back in the send response, the matching check response, webhooks, and the Business Console. Use it to correlate Didit's `request_id` with your user record." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Optional free-form JSON object persisted on the session and echoed back in the send response, the matching check response, webhooks, and the Business Console." + } + }, + "required": [ + "email" + ] + }, + "examples": { + "Numeric 6-digit code": { + "summary": "Default — 6-digit numeric code in English", + "value": { + "email": "alice@example.com", + "options": { + "locale": "en" + }, + "vendor_data": "user-1234" + } + }, + "Alphanumeric 8-character code": { + "summary": "Uppercase letters + digits, Spanish template", + "value": { + "email": "bob@example.com", + "options": { + "code_size": 8, + "alphanumeric_code": true, + "locale": "es" + } + } + }, + "White-label branding": { + "summary": "Send the OTP email with your application's white-label customization", + "value": { + "email": "carol@example.com", + "options": { + "locale": "en", + "use_white_label_customization": true + }, + "vendor_data": "user-5678" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Send acknowledged. Inspect `status`: `Success` and `Retry` mean a code is on its way; `Undeliverable` means the address cannot receive mail and the verification is already finalized as `Declined`. `request_id` is the persisted session id (same id on a `Retry`).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailVerificationSendResponse" + }, + "examples": { + "Success": { + "summary": "OTP emailed for a new verification (billed)", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Success", + "reason": null, + "vendor_data": "user-1234", + "metadata": null + } + }, + "Retry": { + "summary": "Second send inside the 5-minute window — fresh code, same request_id, free", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Retry", + "reason": null, + "vendor_data": "user-1234", + "metadata": null + } + }, + "Undeliverable": { + "summary": "Address failed DNS/MX validation — verification finalized as Declined, nothing billed", + "value": { + "request_id": "cb1a5011-344d-444d-bdce-218904745d7f", + "status": "Undeliverable", + "reason": "email_can_not_be_delivered", + "vendor_data": "user-1234", + "metadata": null + } + } + } + } + } + }, + "400": { + "description": "Validation error — DRF's field-error envelope: one array of messages per offending field, nested under `options` for option errors.", + "content": { + "application/json": { + "examples": { + "Invalid email": { + "summary": "Malformed email address", + "value": { + "email": [ + "Enter a valid email address." + ] + } + }, + "Unsupported locale": { + "summary": "`options.locale` outside the supported set", + "value": { + "options": { + "locale": [ + "Invalid locale. Supported locales are en, ar, bn, bg, bs, ca, cs, da, de, el, es, et, fa, fi, fr, he, hi, hr, hu, hy, id, it, ja, ka, kk, ko, ky, lt, lv, cnr, mk, mn, ms, nl, no, pl, pt-BR, pt, ro, ru, sk, sl, so, sq, sr, sv, th, tr, uk, uz, vi, zh-CN, zh-TW, zh." + ] + } + } + }, + "Code size out of range": { + "summary": "`options.code_size` outside 4–8", + "value": { + "options": { + "code_size": [ + "Ensure this value is less than or equal to 8." + ] + } + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing (`{\"detail\": ...}`); a wrong, revoked or expired key returns `401`. Also returned before any send when the organization's balance cannot cover one Email Verification API credit (`{\"error\": ...}`).", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the send price", + "value": { + "error": "You don't have enough credits to perform this request." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "503": { + "description": "The email provider could not be reached (an outage or a timeout), so no code was sent and nothing was billed or counted as a send attempt. Repeat the same request.", + "content": { + "application/json": { + "examples": { + "Email delivery unavailable": { + "summary": "The email provider failed after one automatic retry", + "value": { + "detail": "Email delivery is temporarily unavailable. Please try again." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/email/check/": { + "post": { + "summary": "Check Email Code", + "description": "Verify the OTP delivered by [`POST /v3/email/send/`](/standalone-apis/email-send) and get the final verification result plus email intelligence: deliverability, breach exposure (`is_breached` and the `breaches` list from a breach-intelligence database), disposable-provider detection, and duplicate usage of the address across your sessions.\n\n**How the check finds the verification.** Matching is by your application plus the `email` address — `request_id` is not an input. The most recent pending verification created within the last **5 minutes** is checked. If there is none (never sent, already finalized, undeliverable at send time, or older than 5 minutes) the endpoint returns `200` with `status: \"Expired or Not Found\"`.\n\n**Attempt budget.** Each verification allows **3 code attempts**. The first two wrong codes return `status: \"Failed\"` with the attempts remaining and `email: null`; the third wrong code finalizes the verification as `Declined` with an `EMAIL_CODE_ATTEMPTS_EXCEEDED` warning and returns the full email report. Codes are compared case-insensitively (relevant for `alphanumeric_code` sends); only the most recently sent code is valid.\n\n**Outcomes.** `Approved` — correct code and no declining risk. `Declined` — terminal: the code was correct but a declining risk matched (a `DECLINE` action below, a blocklisted address, or the address found undeliverable at finalization), or the attempt budget was exhausted. `Failed` — wrong code, attempts remaining. `Expired or Not Found` — nothing to check. On `Approved`/`Declined` the `request_id` equals the send's `request_id` (the session id) and `email` carries the full report (`is_breached`, `breaches`, `is_disposable`, `is_undeliverable`, `warnings`, `lifecycle`, `matches`); on `Failed` and `Expired or Not Found` the `request_id` is a one-off random UUID.\n\n**Risk actions.** `duplicated_email_action`, `breached_email_action`, and `disposable_email_action` decide what happens when the corresponding risk is detected on a correct code: `DECLINE` flips the final status to `Declined`; `NO_ACTION` (default) records the risk in `email.warnings` without affecting the status.\n\n**Billing.** Checks are free — the credit is consumed by the successful send.\n\n**Session persistence.** A finalized check updates the session created by the send (visible in the Business Console, queryable via `GET /v3/session/{sessionId}/decision/`) and fires a `status.updated` webhook.\n\n**Sandbox.** Sandbox API keys skip all processing: any well-formed code (`123456` included) returns a static `Approved` payload with a simplified `email` object (`status`, `email`, `is_breached`, `is_disposable`, `is_undeliverable`); malformed input still returns `400`. Nothing is persisted.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.\n\n**Social footprint.** Set `enable_social` to `true` to add a `email_social` block: the online platforms this address is registered on, with per-category counts and any profile details available. `profiles_registered: 0` means the platforms were checked and the address was found on none of them - a valid, billable answer. The block is absent when the add-on was not requested or the check could not be completed, and it is not billed in that case. Billing adds one `email_social` unit per request on top of this endpoint's own price.", + "operationId": "post_v3email_check", + "x-didit-sdk": { + "resource": "email", + "method": "check_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/email/check/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"email\": \"alice@example.com\",\n \"code\": \"123456\",\n \"disposable_email_action\": \"DECLINE\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.email.check_code(\n email=\"alice@example.com\",\n code=\"123456\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.email.checkCode({\n email: \"alice@example.com\",\n code: \"123456\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Email.CheckCode(ctx, didit.EmailCheckCodeParams{\n\tEmail: \"alice@example.com\",\n\tCode: \"123456\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.EmailCheckCodeParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.email().checkCode(EmailCheckCodeParams.builder()\n .email(\"alice@example.com\")\n .code(\"123456\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "email->checkCode([\n 'email' => 'alice@example.com',\n 'code' => '123456',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.email.check_code(\n email: \"alice@example.com\",\n code: \"123456\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Email.CheckCodeAsync(new EmailCheckCodeParams\n{\n Email = \"alice@example.com\",\n Code = \"123456\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "The same email address used in the matching [`POST /v3/email/send/`](/standalone-apis/email-send) call. This is what links the check to the send.", + "example": "alice@example.com" + }, + "code": { + "type": "string", + "maxLength": 10, + "description": "The OTP the end user received: 4–8 digits, or 4–8 letters/digits when `alphanumeric_code: true` was used at send time. Comparison is case-insensitive.", + "example": "123456" + }, + "duplicated_email_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the same address was already used and approved by a different user (only previously Approved verifications count) (different `vendor_data`) of your application. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `email.warnings` and fills `email.matches`." + }, + "breached_email_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the address appears in known data breaches (`email.is_breached`). `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `email.warnings`." + }, + "disposable_email_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the domain belongs to a disposable/temporary-mail provider (`email.is_disposable`). `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `email.warnings`." + }, + "enable_enrichment": { + "type": "boolean", + "default": false, + "description": "When `true`, also run email intelligence on the address: a risk score and its breach history. Billed as one `email_intelligence_api` unit per request ($0.05 at list price) in addition to this endpoint's own price. The request returns `403` when your balance cannot cover it.", + "example": false + }, + "email_intelligence_score_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the email intelligence risk score is above the threshold. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "recent_email_breach_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the address appears in a recent known data breach. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "frequent_email_breach_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the address appears in many known data breaches. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "enable_social": { + "type": "boolean", + "default": false, + "description": "When true, also look up the social footprint of the address - the email, social, professional, e-commerce and entertainment platforms it is registered on. Billed as one `email_social` unit per request in addition to this endpoint's own price.", + "example": true + }, + "email_no_social_presence_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the social footprint check finds the address registered on none of the platforms it covers. Only NO_ACTION and DECLINE are accepted on this endpoint; REVIEW is available on the workflow configuration key of the same name." + } + }, + "required": [ + "email", + "code" + ] + }, + "examples": { + "Default": { + "summary": "Record any risks but never auto-decline", + "value": { + "email": "alice@example.com", + "code": "123456" + } + }, + "Strict risk policy": { + "summary": "Auto-decline disposable and breached inboxes", + "value": { + "email": "alice@example.com", + "code": "123456", + "disposable_email_action": "DECLINE", + "breached_email_action": "DECLINE" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Check completed — wrong codes and missing verifications also return `200`; inspect `status`, not the HTTP code. `email` is populated only on finalized outcomes (`Approved`/`Declined`), `null` on `Failed`, and absent on `Expired or Not Found`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailVerificationCheckResponse" + }, + "examples": { + "Approved": { + "summary": "Correct code — clean address, full email report", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Approved", + "message": "The verification code is correct.", + "email": { + "status": "Approved", + "email": "alice@example.com", + "is_breached": false, + "breaches": [], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [], + "lifecycle": [ + { + "type": "EMAIL_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null + }, + "fee": 0.02 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "EMAIL_VERIFICATION_APPROVED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": null, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Approved with breach exposure": { + "summary": "Correct code; breach exposure recorded as a warning (breached_email_action=NO_ACTION)", + "value": { + "request_id": "3a1f2b6c-9d4e-4f7a-8b2c-1e5d6f7a8b9c", + "status": "Approved", + "message": "The verification code is correct.", + "email": { + "status": "Approved", + "email": "bob@example.com", + "is_breached": true, + "breaches": [ + { + "name": "ExampleApp", + "domain": "exampleapp.com", + "breach_date": "2019-03-21", + "breach_emails_count": 763117, + "description": "In March 2019, the social platform ExampleApp suffered a data breach that exposed email addresses and passwords.", + "logo_path": "https:///logos/ExampleApp.png", + "data_classes": [ + "email_addresses", + "passwords" + ], + "is_verified": true + } + ], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [ + { + "feature": "EMAIL", + "risk": "BREACHED_EMAIL_DETECTED", + "additional_data": null, + "log_type": "information", + "short_description": "Breached email detected", + "long_description": "This email address was found in one or more known data breaches." + } + ], + "lifecycle": [ + { + "type": "EMAIL_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null + }, + "fee": 0.02 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "835126", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "EMAIL_VERIFICATION_APPROVED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": null, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-5678", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Failed (attempts remaining)": { + "summary": "Wrong code — the user can retry; request_id here is a one-off UUID", + "value": { + "request_id": "11111111-2222-3333-4444-555555555555", + "status": "Failed", + "message": "The verification code is incorrect. Attempts remaining: 2", + "email": null, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:21.512340+00:00" + } + }, + "Declined (risk policy)": { + "summary": "Correct code, but the address is disposable and disposable_email_action=DECLINE", + "value": { + "request_id": "8c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "status": "Declined", + "message": "The verification code is correct.", + "email": { + "status": "Declined", + "email": "tempuser42@mailinator.com", + "is_breached": false, + "breaches": [], + "is_disposable": true, + "is_undeliverable": false, + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [ + { + "feature": "EMAIL", + "risk": "DISPOSABLE_EMAIL_DETECTED", + "additional_data": null, + "log_type": "error", + "short_description": "Disposable email detected", + "long_description": "The system detected that the email is disposable, which is not allowed." + } + ], + "lifecycle": [ + { + "type": "EMAIL_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null + }, + "fee": 0.02 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "EMAIL_VERIFICATION_DECLINED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": { + "reason": "DISPOSABLE_EMAIL_DETECTED" + }, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Expired or Not Found": { + "summary": "No pending verification for this address in the last 5 minutes", + "value": { + "request_id": "88808a77-06e9-4cb0-85f5-9c052ddfd987", + "status": "Expired or Not Found", + "message": "No pending email verification found in the last 5 minutes.", + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-12T01:24:59.887904+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error — DRF's field-error envelope: one array of messages per offending field.", + "content": { + "application/json": { + "examples": { + "Invalid email": { + "summary": "Malformed email address", + "value": { + "email": [ + "Enter a valid email address." + ] + } + }, + "Missing code": { + "summary": "`code` not included in the body", + "value": { + "code": [ + "This field is required." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing; a wrong, revoked or expired key returns `401`. Checks are free, so there is no credit check here.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/workflows/": { + "get": { + "summary": "List workflows", + "description": "List your workflows, newest first, in a limit/offset pagination envelope (`{count, next, previous, results}`, default page size 50). Each row is one version; `workflow_id` is the stable group ID and new sessions use the latest `published` version.", + "operationId": "list_workflows", + "x-didit-sdk": { + "resource": "workflows", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50 + }, + "description": "Page size (number of workflows per page)." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0 + }, + "description": "Number of rows to skip before the first returned workflow." + } + ], + "responses": { + "200": { + "description": "One page of workflows ordered by `created_at` descending, wrapped in the pagination envelope. Each item in `results` is a `WorkflowListItem`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of workflow versions for the application." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkflowListItem" + } + } + } + }, + "examples": { + "Two workflows": { + "summary": "A default KYC workflow and a draft KYC + AML workflow", + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "uuid": "a1b2c3d4-5678-90ab-cdef-111111111111", + "workflow_id": "a1b2c3d4-5678-90ab-cdef-111111111111", + "workflow_label": "Standard KYC", + "workflow_type": "kyc", + "is_default": true, + "is_archived": false, + "is_white_label_enabled": false, + "total_price": 0.15, + "min_price": 0.15, + "max_price": 0.15, + "features": "OCR + LIVENESS + FACE_MATCH", + "is_simple_workflow": true, + "is_editable": false, + "workflow_url": "https://verify.didit.me/u/_yljPOx8RWG3UAz5ZRQGVQ", + "max_retry_attempts": 3, + "retry_window_days": 7, + "session_expiration_time": 604800, + "version": 2, + "status": "published", + "has_draft": false, + "created_by": { + "name": "Alex Mateo", + "email": "alex@example.com", + "type": "user" + }, + "updated_at": "2026-04-12T10:30:00Z" + }, + { + "uuid": "b2c3d4e5-6789-01bc-defa-222222222222", + "workflow_id": "b2c3d4e5-6789-01bc-defa-222222222222", + "workflow_label": "Full Verification + AML", + "workflow_type": "kyc", + "is_default": false, + "is_archived": false, + "is_white_label_enabled": false, + "total_price": 0.25, + "min_price": 0.25, + "max_price": 0.25, + "features": "OCR + LIVENESS + FACE_MATCH + AML", + "is_simple_workflow": true, + "is_editable": true, + "workflow_url": "https://verify.didit.me/u/Aq3kPOx8RWG3UAz5ZRQGVQ", + "max_retry_attempts": 3, + "retry_window_days": 7, + "session_expiration_time": 604800, + "version": 1, + "status": "draft", + "has_draft": true, + "created_by": null, + "updated_at": "2026-04-15T18:05:21Z" + } + ] + } + } + } + } + } + }, + "403": { + "description": "The API key is missing, malformed, expired, or belongs to a different application. Body: `{\"detail\": \"You do not have permission to perform this action.\"}`. Refresh the token via the [Auth API](/auth-api/get-credentials) and retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff and honour any `Retry-After` header." + }, + "500": { + "description": "Unexpected server error while building the list. The request can be safely retried." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/workflows/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Accept: application/json'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.workflows.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.workflows.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Workflows.ListAutoPaging(ctx, didit.WorkflowsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.workflows().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "workflows->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.workflows.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Workflows.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "summary": "Create workflow", + "description": "Create a simple workflow. Send `features` in execution order (ordering rules apply); defaults to `published` v1. The body accepts ONLY the fields listed in `SimpleWorkflowRequest` — any other key (including `workflow_type`) is rejected with a 400. Not idempotent — each call creates a new workflow. Maximum 50 workflows per application.", + "operationId": "create_workflow", + "x-didit-sdk": { + "resource": "workflows", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleWorkflowRequest" + }, + "examples": { + "Simple KYC": { + "summary": "Linear ID, liveness, and face match workflow", + "value": { + "workflow_label": "Standard KYC", + "features": [ + { + "feature": "OCR", + "config": { + "duplicated_user_action": "review" + } + }, + { + "feature": "LIVENESS", + "config": { + "face_liveness_method": "passive" + } + }, + { + "feature": "FACE_MATCH", + "config": { + "face_match_score_decline_threshold": 40, + "face_match_score_review_threshold": 60 + } + } + ] + } + }, + "KYC + AML": { + "summary": "Run AML after ID verification", + "value": { + "workflow_label": "KYC with AML", + "features": [ + { + "feature": "OCR" + }, + { + "feature": "AML", + "config": { + "aml_score_review_threshold": 70, + "aml_score_approve_threshold": 30 + } + } + ] + } + }, + "Questionnaire workflow": { + "summary": "Run a questionnaire created with POST /v3/questionnaires/", + "value": { + "workflow_label": "Source of Funds", + "features": [ + { + "feature": "QUESTIONNAIRE", + "config": { + "questionnaire_uuid": "11111111-2222-3333-4444-555555555555", + "review_questionnaire_manually": true + } + } + ] + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Workflow created. The response body is the FULL workflow configuration — the same raw `VerificationSettings` serialization returned by `GET /v3/workflows/{settings_uuid}/` (~190 fields: every feature toggle, threshold, action and the generated `workflow_graph`), not the compact list item. Store the `uuid` (also exposed as `workflow_id`) so you can reference it from later session-creation calls. Note: `features` is a `\" + \"`-joined string and prices are JSON numbers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowDetail" + }, + "examples": { + "Created (abbreviated)": { + "summary": "Key fields of the full configuration object", + "value": { + "uuid": "c3d4e5f6-7890-12cd-ef34-333333333333", + "workflow_id": "c3d4e5f6-7890-12cd-ef34-333333333333", + "workflow_label": "Standard KYC", + "workflow_type": null, + "is_default": false, + "is_archived": false, + "total_price": 0.15, + "min_price": 0.15, + "max_price": 0.15, + "features": "OCR + LIVENESS + FACE_MATCH", + "is_simple_workflow": false, + "is_editable": false, + "workflow_url": "https://verify.didit.me/u/w9TkPOx8RWG3UAz5ZRQGVQ", + "workflow_graph": { + "start_node": "ocr", + "nodes": "... one node per feature plus a final status node ..." + }, + "max_retry_attempts": 3, + "retry_window_days": 7, + "session_expiration_time": 604800, + "version": 1, + "status": "published", + "published_at": "2026-05-17T10:30:00Z", + "created_at": "2026-05-17T10:30:00Z", + "updated_at": "2026-05-17T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Common causes: a top-level key outside the accepted whitelist (e.g. `workflow_type`), an unknown `feature` value, a feature placed before its dependency (e.g., `FACE_MATCH` before `OCR`), a passive feature (`AML`, `IP_ANALYSIS`, `DATABASE_VALIDATION`, `KYB_KEY_PEOPLE`) used as the first step, a `QUESTIONNAIRE` feature whose `config.questionnaire_uuid` does not exist for this application, a `SIGNATURE` feature that would go live with no document to sign, a `SIGNATURE` feature at `signature_level: \"aes\"` while Advanced (AES) is not available (`aes_not_available`, see `signature_availability` on the workflow), or an out-of-range threshold. Body is a DRF validation error object — keys are field names, values are arrays of human-readable messages.", + "content": { + "application/json": { + "examples": { + "Advanced (AES) not available": { + "value": { + "features": { + "signature_level": [ + "Node 'signature': Advanced (AES) is not available yet (aes_not_available)." + ] + } + } + }, + "Signature step with no document": { + "value": { + "features": { + "non_field_errors": [ + "Node 'signature': Add a document to sign, or let each session bring its own." + ] + } + } + }, + "Missing feature": { + "value": { + "features": [ + "At least one feature is required." + ] + } + }, + "Unknown top-level field": { + "value": { + "workflow_type": [ + "The v3 workflow API only accepts `workflow_label`, `is_default`, `status`, `features`, `is_white_label_enabled`, `is_desktop_allowed`, `max_retry_attempts`, `retry_window_days`, `session_expiration_time`, `face_liveness_max_attempts`, `face_match_max_attempts`." + ] + } + }, + "Unknown feature": { + "value": { + "features": [ + "Invalid feature 'FOO'. Allowed values: AGE_ESTIMATION, AML, DATABASE_VALIDATION, EMAIL_VERIFICATION, FACE_MATCH, IP_ANALYSIS, KYB_DOCUMENTS, KYB_KEY_PEOPLE, KYB_REGISTRY, LIVENESS, NFC, OCR, PHONE_VERIFICATION, PROOF_OF_ADDRESS, QUESTIONNAIRE." + ] + } + }, + "Ordering": { + "value": { + "features": [ + "Feature 'FACE_MATCH' must appear after one of: OCR." + ] + } + }, + "Questionnaire not found": { + "value": { + "features": "Questionnaire '11111111-2222-3333-4444-555555555555' was not found for this application. Create the questionnaire first, then use its questionnaire_id." + } + } + } + } + } + }, + "403": { + "description": "The API key is missing, malformed, expired, or scoped to a different application. Refresh via the [Auth API](/auth-api/get-credentials) and retry.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "415": { + "description": "Unsupported media type. Set `Content-Type: application/json` and send a JSON body." + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error while creating the workflow. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/workflows/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"workflow_label\": \"Standard KYC\",\n \"features\": [\n {\"feature\": \"OCR\"},\n {\"feature\": \"LIVENESS\", \"config\": {\"face_liveness_method\": \"passive\"}},\n {\"feature\": \"FACE_MATCH\", \"config\": {\"face_match_score_decline_threshold\": 40, \"face_match_score_review_threshold\": 60}}\n ]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.workflows.create(\n workflow_label=\"Standard KYC\",\n features=[{\n \"feature\": \"OCR\",\n \"config\": {\"duplicated_user_action\": \"review\"},\n }, {\n \"feature\": \"LIVENESS\",\n \"config\": {\"face_liveness_method\": \"passive\"},\n }, {\n \"feature\": \"FACE_MATCH\",\n \"config\": {\"face_match_score_decline_threshold\": 40, \"face_match_score_review_threshold\": 60},\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.workflows.create({\n workflow_label: \"Standard KYC\",\n features: [{\n feature: \"OCR\",\n config: { duplicated_user_action: \"review\" },\n }, {\n feature: \"LIVENESS\",\n config: { face_liveness_method: \"passive\" },\n }, {\n feature: \"FACE_MATCH\",\n config: { face_match_score_decline_threshold: 40, face_match_score_review_threshold: 60 },\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Workflows.Create(ctx, didit.SimpleWorkflowRequest{\n\tWorkflowLabel: didit.String(\"Standard KYC\"),\n\tFeatures: []didit.SimpleWorkflowFeature{{\n\t\tFeature: \"OCR\",\n\t\tConfig: didit.Some(map[string]any{\"duplicated_user_action\": \"review\"}),\n\t}, {\n\t\tFeature: \"LIVENESS\",\n\t\tConfig: didit.Some(map[string]any{\"face_liveness_method\": \"passive\"}),\n\t}, {\n\t\tFeature: \"FACE_MATCH\",\n\t\tConfig: didit.Some(map[string]any{\"face_match_score_decline_threshold\": 40, \"face_match_score_review_threshold\": 60}),\n\t}},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SimpleWorkflowFeature;\nimport me.didit.types.SimpleWorkflowRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.workflows().create(SimpleWorkflowRequest.builder()\n .workflowLabel(\"Standard KYC\")\n .features(List.of(\n SimpleWorkflowFeature.builder()\n .feature(SimpleWorkflowFeature.Feature.of(\"OCR\"))\n .config(Map.of(\"duplicated_user_action\", \"review\"))\n .build(),\n SimpleWorkflowFeature.builder()\n .feature(SimpleWorkflowFeature.Feature.of(\"LIVENESS\"))\n .config(Map.of(\"face_liveness_method\", \"passive\"))\n .build(),\n SimpleWorkflowFeature.builder()\n .feature(SimpleWorkflowFeature.Feature.of(\"FACE_MATCH\"))\n .config(Map.of(\"face_match_score_decline_threshold\", 40, \"face_match_score_review_threshold\", 60))\n .build()))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "workflows->create([\n 'workflow_label' => 'Standard KYC',\n 'features' => [[\n 'feature' => 'OCR',\n 'config' => ['duplicated_user_action' => 'review'],\n ], [\n 'feature' => 'LIVENESS',\n 'config' => ['face_liveness_method' => 'passive'],\n ], [\n 'feature' => 'FACE_MATCH',\n 'config' => ['face_match_score_decline_threshold' => 40, 'face_match_score_review_threshold' => 60],\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.workflows.create(\n workflow_label: \"Standard KYC\",\n features: [{\n feature: \"OCR\",\n config: { duplicated_user_action: \"review\" },\n }, {\n feature: \"LIVENESS\",\n config: { face_liveness_method: \"passive\" },\n }, {\n feature: \"FACE_MATCH\",\n config: { face_match_score_decline_threshold: 40, face_match_score_review_threshold: 60 },\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Workflows.CreateAsync(new SimpleWorkflowRequest\n{\n WorkflowLabel = \"Standard KYC\",\n Features = new SimpleWorkflowFeature[]\n {\n new()\n {\n Feature = \"OCR\",\n Config = new Dictionary { [\"duplicated_user_action\"] = \"review\" },\n },\n new()\n {\n Feature = \"LIVENESS\",\n Config = new Dictionary { [\"face_liveness_method\"] = \"passive\" },\n },\n new()\n {\n Feature = \"FACE_MATCH\",\n Config = new Dictionary { [\"face_match_score_decline_threshold\"] = 40, [\"face_match_score_review_threshold\"] = 60 },\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/workflows/{settings_uuid}/": { + "get": { + "summary": "Get workflow", + "description": "Fetch full configuration for one workflow version by `settings_uuid`: feature toggles, thresholds, allowed documents, graph, retry policy, callbacks, and pricing.", + "operationId": "get_workflow", + "x-didit-sdk": { + "resource": "workflows", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "parameters": [ + { + "name": "settings_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the workflow (the `uuid` value returned by the list endpoint). Each version of a workflow has its own `settings_uuid`." + } + ], + "responses": { + "200": { + "description": "Full workflow configuration. The shape is the raw `VerificationSettings` serialization (~190 fields: every feature toggle, threshold, warning action, allowed-documents config, the `workflow_graph`, retry policy, callbacks and pricing). The example below is abbreviated. Notes: `features` is a `\" + \"`-joined string (e.g. `\"OCR + LIVENESS\"`), prices are JSON numbers, and `is_editable` is `true` only for `draft` versions.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowDetail" + }, + "examples": { + "KYC workflow with face match (abbreviated)": { + "value": { + "uuid": "a1b2c3d4-5678-90ab-cdef-111111111111", + "workflow_id": "a1b2c3d4-5678-90ab-cdef-111111111111", + "workflow_label": "Standard KYC", + "workflow_type": "kyc", + "is_default": true, + "is_archived": false, + "is_liveness_enabled": true, + "face_liveness_method": "passive", + "face_liveness_score_decline_threshold": 50, + "face_liveness_score_review_threshold": 70, + "is_face_match_enabled": true, + "face_match_score_decline_threshold": 40, + "face_match_score_review_threshold": 60, + "is_aml_enabled": false, + "is_phone_verification_enabled": false, + "is_email_verification_enabled": false, + "is_database_validation_enabled": false, + "is_ip_analysis_enabled": false, + "is_nfc_enabled": false, + "duplicated_user_action": "no_action", + "documents_allowed": { + "ESP": { + "P": { + "sides": 1, + "enabled": 1, + "subtypes": [ + "EPASSPORT", + "PASSPORT_GENERIC" + ], + "preferred_characters": "latin", + "expiration_check_mode": "strict" + } + } + }, + "max_retry_attempts": 3, + "retry_window_days": 7, + "session_expiration_time": 604800, + "version": 2, + "status": "published", + "published_at": "2026-04-12T10:30:00Z", + "is_simple_workflow": false, + "is_editable": false, + "workflow_url": "https://verify.didit.me/u/_yljPOx8RWG3UAz5ZRQGVQ", + "total_price": 0.15, + "min_price": 0.15, + "max_price": 0.15, + "features": "OCR + LIVENESS + FACE_MATCH", + "created_at": "2025-01-15T10:30:00Z", + "updated_at": "2026-04-12T10:30:00Z" + } + } + } + } + } + }, + "403": { + "description": "API key is missing, malformed, expired, or scoped to a different application.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No workflow with this `settings_uuid` exists for the authenticated application. Either the UUID is wrong or it belongs to another application — Didit deliberately does not distinguish the two cases to avoid leaking the existence of cross-application records.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/workflows/a1b2c3d4-5678-90ab-cdef-111111111111/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.workflows.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.workflows.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Workflows.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.workflows().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "workflows->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.workflows.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Workflows.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "patch": { + "summary": "Update workflow", + "description": "Update a workflow. Versioning depends on the `status` field you send and the current state of the version: (1) no `status` on a **published** version → the published version is updated in place (same `uuid`, same `version`); (2) no `status` on a **draft** → the draft is updated and immediately published; (3) `status: \"published\"` on a **published** version → a NEW published version is created under the same `workflow_id` (new `uuid`, `version` + 1) and returned, and any existing draft in the group is deleted; (4) `status: \"draft\"` keeps a draft as a draft (autosave) but is rejected with 400 on a published version (`\"Cannot revert a published version to draft.\"`). The body accepts the same strict field whitelist as create — unknown keys are rejected with 400.", + "operationId": "update_workflow", + "x-didit-sdk": { + "resource": "workflows", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "parameters": [ + { + "name": "settings_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the workflow to update." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleWorkflowUpdateRequest" + }, + "examples": { + "Replace features": { + "summary": "Send the full desired feature order", + "value": { + "features": [ + { + "feature": "OCR" + }, + { + "feature": "AML" + } + ] + } + }, + "Rename": { + "summary": "Rename a workflow", + "value": { + "workflow_label": "Premium KYC" + } + }, + "Set as default": { + "summary": "Make this workflow the application's default", + "value": { + "is_default": true + } + }, + "Update config": { + "summary": "Change feature configuration", + "value": { + "features": [ + { + "feature": "OCR" + }, + { + "feature": "LIVENESS", + "config": { + "face_liveness_method": "passive" + } + } + ] + } + }, + "Publish a draft": { + "summary": "Promote a draft to published", + "value": { + "status": "published" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Workflow updated. The body is the FULL configuration of the resulting version (same raw `VerificationSettings` shape as `GET /v3/workflows/{settings_uuid}/`). When the update created a new version (`status: \"published\"` sent on a published row) the returned `uuid` and `version` differ from the ones you patched — use the returned `uuid` for subsequent calls.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowDetail" + } + } + } + }, + "400": { + "description": "Validation error — a top-level key outside the accepted whitelist, an unknown feature, invalid ordering, out-of-range threshold, referencing a non-existent questionnaire, a `SIGNATURE` feature that would go live with no document to sign, a new `SIGNATURE` feature at `signature_level: \"aes\"` while Advanced (AES) is not available (`aes_not_available`, see `signature_availability` on the workflow; a step that already stores `aes` keeps it), or `status: \"draft\"` sent for a published version (`\"Cannot revert a published version to draft.\"`). The response body is a DRF validation error keyed by field name." + }, + "403": { + "description": "API key missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No workflow with this `settings_uuid` exists for the authenticated application." + }, + "415": { + "description": "Unsupported media type. Set `Content-Type: application/json`." + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X PATCH 'https://verification.didit.me/v3/workflows/a1b2c3d4-5678-90ab-cdef-111111111111/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"workflow_label\": \"Premium KYC\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.workflows.update(\n \"11111111-2222-3333-4444-555555555555\",\n features=[{\n \"feature\": \"OCR\",\n }, {\n \"feature\": \"AML\",\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.workflows.update(\"11111111-2222-3333-4444-555555555555\", {\n features: [{\n feature: \"OCR\",\n }, {\n feature: \"AML\",\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Workflows.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SimpleWorkflowUpdateRequest{\n\tFeatures: didit.Some([]didit.SimpleWorkflowFeature{{\n\t\tFeature: \"OCR\",\n\t}, {\n\t\tFeature: \"AML\",\n\t}}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.SimpleWorkflowFeature;\nimport me.didit.types.SimpleWorkflowUpdateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.workflows().update(\"11111111-2222-3333-4444-555555555555\", SimpleWorkflowUpdateRequest.builder()\n .features(List.of(\n SimpleWorkflowFeature.builder()\n .feature(SimpleWorkflowFeature.Feature.of(\"OCR\"))\n .build(),\n SimpleWorkflowFeature.builder()\n .feature(SimpleWorkflowFeature.Feature.of(\"AML\"))\n .build()))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "workflows->update('11111111-2222-3333-4444-555555555555', [\n 'features' => [[\n 'feature' => 'OCR',\n ], [\n 'feature' => 'AML',\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.workflows.update(\n \"11111111-2222-3333-4444-555555555555\",\n features: [{\n feature: \"OCR\",\n }, {\n feature: \"AML\",\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Workflows.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new SimpleWorkflowUpdateRequest\n{\n Features = new SimpleWorkflowFeature[]\n {\n new()\n {\n Feature = \"OCR\",\n },\n new()\n {\n Feature = \"AML\",\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "summary": "Delete workflow", + "description": "Permanently delete one workflow version (no undo). Existing sessions keep their verification results but their workflow reference is cleared; other versions in the same `workflow_id` group are not deleted.", + "operationId": "delete_workflow", + "x-didit-sdk": { + "resource": "workflows", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "parameters": [ + { + "name": "settings_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the workflow to delete." + } + ], + "responses": { + "204": { + "description": "Workflow deleted. The response body is empty." + }, + "403": { + "description": "API key missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No workflow with this `settings_uuid` exists for the authenticated application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X DELETE 'https://verification.didit.me/v3/workflows/a1b2c3d4-5678-90ab-cdef-111111111111/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.workflows.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.workflows.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Workflows.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.workflows().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "workflows->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.workflows.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Workflows.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ] + } + }, + "/v3/questionnaires/": { + "get": { + "summary": "List questionnaires", + "description": "List your questionnaires, newest first, in a limit/offset pagination envelope (`{count, next, previous, results}`, default page size 50). Each row is one version; `questionnaire_group_id` is the stable group ID resolved to the latest `published` version.", + "operationId": "list_questionnaires", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50 + }, + "description": "Page size (number of questionnaires per page)." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0 + }, + "description": "Number of rows to skip before the first returned questionnaire." + } + ], + "responses": { + "200": { + "description": "One page of questionnaires ordered by `created_at` descending, wrapped in the pagination envelope. Each item in `results` is a `QuestionnaireListItem`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of questionnaire versions for the application." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionnaireListItem" + } + } + } + }, + "examples": { + "One questionnaire": { + "summary": "A simple bilingual questionnaire", + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "11111111-2222-3333-4444-555555555555", + "title": "KYC Questionnaire", + "description": "Additional verification questions", + "languages": [ + "en", + "es" + ], + "default_language": "en", + "is_active": true, + "is_simple_questionnaire": true, + "created_at": "2026-04-23T09:55:00Z", + "updated_at": "2026-04-23T10:00:00Z", + "question_types": [ + "SHORT_TEXT", + "MULTIPLE_CHOICE", + "FILE_UPLOAD" + ], + "workflow_names": [], + "questionnaire_group_id": "11111111-2222-3333-4444-555555555555", + "version": 1, + "status": "published", + "published_at": "2026-04-23T10:00:00Z", + "is_editable": false + } + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error while building the list. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/questionnaires/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.questionnaires.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.questionnaires.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Questionnaires.ListAutoPaging(ctx, didit.QuestionnairesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.questionnaires().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "questionnaires->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.questionnaires.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Questionnaires.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "summary": "Create questionnaire", + "description": "Create a simple linear questionnaire. Send `form_elements` in display order; branching and repeatable groups require the Console. Defaults to `published` v1.", + "operationId": "create_questionnaire", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleQuestionnaireRequest" + }, + "examples": { + "Simple": { + "summary": "Basic questionnaire", + "value": { + "title": "Customer Onboarding", + "description": "Additional information needed for verification", + "default_language": "en", + "languages": [ + "en" + ], + "is_active": true, + "form_elements": [ + { + "id": "occupation", + "element_type": "short_text", + "label": { + "en": "What is your occupation?" + }, + "is_required": true, + "placeholder": { + "en": "Software engineer" + } + }, + { + "id": "source_of_funds", + "element_type": "multiple_choice", + "label": { + "en": "Source of funds" + }, + "is_required": true, + "options": [ + { + "value": "employment", + "label": { + "en": "Employment" + } + }, + { + "value": "business", + "label": { + "en": "Business" + } + }, + { + "value": "investments", + "label": { + "en": "Investments" + } + }, + { + "value": "other", + "label": { + "en": "Other" + } + } + ] + } + ] + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Questionnaire created. The response includes the `questionnaire_id` — store it to reference this questionnaire from workflows or future update/delete calls.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QuestionnaireDetail" + }, + "examples": { + "Created": { + "value": { + "questionnaire_id": "11111111-2222-3333-4444-555555555555", + "title": "Customer Onboarding", + "description": "Additional information needed for verification", + "languages": [ + "en" + ], + "default_language": "en", + "is_active": true, + "is_simple_questionnaire": true, + "graph": { + "start_node": "occupation", + "nodes": { + "occupation": { + "element_type": "SHORT_TEXT", + "is_required": true, + "title": { + "en": "What is your occupation?" + }, + "next": "source_of_funds" + }, + "source_of_funds": { + "element_type": "MULTIPLE_CHOICE", + "is_required": true, + "title": { + "en": "Source of funds" + }, + "choices": [ + { + "value": "employment", + "label": { + "en": "Employment" + } + }, + { + "value": "business", + "label": { + "en": "Business" + } + } + ], + "next": null + } + } + }, + "sections": [], + "questionnaire_group_id": "11111111-2222-3333-4444-555555555555", + "version": 1, + "status": "published", + "published_at": "2026-04-23T10:00:00Z", + "is_editable": false + } + } + } + } + } + }, + "400": { + "description": "Validation error. Common causes: an unknown `element_type`, a translatable field missing a translation for one of `languages`, `languages` missing `en`, `default_language` not in `languages`, an option without a `label`, a question that has `branches`/`required_if`/`repeatable_config` (not supported by this endpoint), or sending a raw `graph` instead of `form_elements`. The body is a DRF validation error keyed by field name.", + "content": { + "application/json": { + "examples": { + "Graph not accepted": { + "value": { + "graph": "The v3 questionnaire API only accepts simple `form_elements`, not `graph`." + } + }, + "Missing label": { + "value": { + "form_elements": [ + { + "label": [ + "This field is required." + ] + } + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "415": { + "description": "Unsupported media type. Set `Content-Type: application/json` and send a JSON body." + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error while creating the questionnaire. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/questionnaires/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"title\": \"Customer Onboarding\",\n \"default_language\": \"en\",\n \"languages\": [\"en\"],\n \"form_elements\": [\n {\n \"id\": \"occupation\",\n \"element_type\": \"short_text\",\n \"label\": {\"en\": \"What is your occupation?\"},\n \"is_required\": true\n }\n ]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.questionnaires.create(\n title=\"Customer Onboarding\",\n description=\"Additional information needed for verification\",\n languages=[\"en\"],\n default_language=\"en\",\n is_active=True,\n form_elements=[{\n \"id\": \"occupation\",\n \"element_type\": \"short_text\",\n \"label\": {\"en\": \"What is your occupation?\"},\n \"placeholder\": {\"en\": \"Software engineer\"},\n \"is_required\": True,\n }, {\n \"id\": \"source_of_funds\",\n \"element_type\": \"multiple_choice\",\n \"label\": {\"en\": \"Source of funds\"},\n \"is_required\": True,\n \"options\": [{\n \"value\": \"employment\",\n \"label\": {\"en\": \"Employment\"},\n }, {\n \"value\": \"business\",\n \"label\": {\"en\": \"Business\"},\n }, {\n \"value\": \"investments\",\n \"label\": {\"en\": \"Investments\"},\n }, {\n \"value\": \"other\",\n \"label\": {\"en\": \"Other\"},\n }],\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.questionnaires.create({\n title: \"Customer Onboarding\",\n description: \"Additional information needed for verification\",\n languages: [\"en\"],\n default_language: \"en\",\n is_active: true,\n form_elements: [{\n id: \"occupation\",\n element_type: \"short_text\",\n label: { en: \"What is your occupation?\" },\n placeholder: { en: \"Software engineer\" },\n is_required: true,\n }, {\n id: \"source_of_funds\",\n element_type: \"multiple_choice\",\n label: { en: \"Source of funds\" },\n is_required: true,\n options: [{\n value: \"employment\",\n label: { en: \"Employment\" },\n }, {\n value: \"business\",\n label: { en: \"Business\" },\n }, {\n value: \"investments\",\n label: { en: \"Investments\" },\n }, {\n value: \"other\",\n label: { en: \"Other\" },\n }],\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Questionnaires.Create(ctx, didit.SimpleQuestionnaireRequest{\n\tTitle: \"Customer Onboarding\",\n\tDescription: didit.String(\"Additional information needed for verification\"),\n\tLanguages: []string{\"en\"},\n\tDefaultLanguage: didit.String(\"en\"),\n\tIsActive: didit.Bool(true),\n\tFormElements: []didit.SimpleQuestionnaireElement{{\n\t\tID: didit.String(\"occupation\"),\n\t\tElementType: \"short_text\",\n\t\tLabel: map[string]string{\"en\": \"What is your occupation?\"},\n\t\tPlaceholder: didit.Some(map[string]string{\"en\": \"Software engineer\"}),\n\t\tIsRequired: didit.Bool(true),\n\t}, {\n\t\tID: didit.String(\"source_of_funds\"),\n\t\tElementType: \"multiple_choice\",\n\t\tLabel: map[string]string{\"en\": \"Source of funds\"},\n\t\tIsRequired: didit.Bool(true),\n\t\tOptions: didit.Some([]didit.SimpleQuestionnaireElementOptions{{\n\t\t\tValue: didit.String(\"employment\"),\n\t\t\tLabel: map[string]string{\"en\": \"Employment\"},\n\t\t}, {\n\t\t\tValue: didit.String(\"business\"),\n\t\t\tLabel: map[string]string{\"en\": \"Business\"},\n\t\t}, {\n\t\t\tValue: didit.String(\"investments\"),\n\t\t\tLabel: map[string]string{\"en\": \"Investments\"},\n\t\t}, {\n\t\t\tValue: didit.String(\"other\"),\n\t\t\tLabel: map[string]string{\"en\": \"Other\"},\n\t\t}}),\n\t}},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SimpleQuestionnaireElement;\nimport me.didit.types.SimpleQuestionnaireRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.questionnaires().create(SimpleQuestionnaireRequest.builder()\n .title(\"Customer Onboarding\")\n .description(\"Additional information needed for verification\")\n .languages(List.of(\"en\"))\n .defaultLanguage(\"en\")\n .isActive(true)\n .formElements(List.of(\n SimpleQuestionnaireElement.builder()\n .id(\"occupation\")\n .elementType(SimpleQuestionnaireElement.ElementType.of(\"short_text\"))\n .label(Map.of(\"en\", \"What is your occupation?\"))\n .placeholder(Map.of(\"en\", \"Software engineer\"))\n .isRequired(true)\n .build(),\n SimpleQuestionnaireElement.builder()\n .id(\"source_of_funds\")\n .elementType(SimpleQuestionnaireElement.ElementType.of(\"multiple_choice\"))\n .label(Map.of(\"en\", \"Source of funds\"))\n .isRequired(true)\n .options(List.of(\n SimpleQuestionnaireElement.Options.builder()\n .value(\"employment\")\n .label(Map.of(\"en\", \"Employment\"))\n .build(),\n SimpleQuestionnaireElement.Options.builder()\n .value(\"business\")\n .label(Map.of(\"en\", \"Business\"))\n .build(),\n SimpleQuestionnaireElement.Options.builder()\n .value(\"investments\")\n .label(Map.of(\"en\", \"Investments\"))\n .build(),\n SimpleQuestionnaireElement.Options.builder()\n .value(\"other\")\n .label(Map.of(\"en\", \"Other\"))\n .build()))\n .build()))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "questionnaires->create([\n 'title' => 'Customer Onboarding',\n 'description' => 'Additional information needed for verification',\n 'languages' => ['en'],\n 'default_language' => 'en',\n 'is_active' => true,\n 'form_elements' => [[\n 'id' => 'occupation',\n 'element_type' => 'short_text',\n 'label' => ['en' => 'What is your occupation?'],\n 'placeholder' => ['en' => 'Software engineer'],\n 'is_required' => true,\n ], [\n 'id' => 'source_of_funds',\n 'element_type' => 'multiple_choice',\n 'label' => ['en' => 'Source of funds'],\n 'is_required' => true,\n 'options' => [[\n 'value' => 'employment',\n 'label' => ['en' => 'Employment'],\n ], [\n 'value' => 'business',\n 'label' => ['en' => 'Business'],\n ], [\n 'value' => 'investments',\n 'label' => ['en' => 'Investments'],\n ], [\n 'value' => 'other',\n 'label' => ['en' => 'Other'],\n ]],\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.questionnaires.create(\n title: \"Customer Onboarding\",\n description: \"Additional information needed for verification\",\n languages: [\"en\"],\n default_language: \"en\",\n is_active: true,\n form_elements: [{\n id: \"occupation\",\n element_type: \"short_text\",\n label: { en: \"What is your occupation?\" },\n placeholder: { en: \"Software engineer\" },\n is_required: true,\n }, {\n id: \"source_of_funds\",\n element_type: \"multiple_choice\",\n label: { en: \"Source of funds\" },\n is_required: true,\n options: [{\n value: \"employment\",\n label: { en: \"Employment\" },\n }, {\n value: \"business\",\n label: { en: \"Business\" },\n }, {\n value: \"investments\",\n label: { en: \"Investments\" },\n }, {\n value: \"other\",\n label: { en: \"Other\" },\n }],\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Questionnaires.CreateAsync(new SimpleQuestionnaireRequest\n{\n Title = \"Customer Onboarding\",\n Description = \"Additional information needed for verification\",\n Languages = new string[] { \"en\" },\n DefaultLanguage = \"en\",\n IsActive = true,\n FormElements = new SimpleQuestionnaireElement[]\n {\n new()\n {\n Id = \"occupation\",\n ElementType = \"short_text\",\n Label = new Dictionary { [\"en\"] = \"What is your occupation?\" },\n Placeholder = new Dictionary { [\"en\"] = \"Software engineer\" },\n IsRequired = true,\n },\n new()\n {\n Id = \"source_of_funds\",\n ElementType = \"multiple_choice\",\n Label = new Dictionary { [\"en\"] = \"Source of funds\" },\n IsRequired = true,\n Options = new SimpleQuestionnaireElementOptions[]\n {\n new()\n {\n Value = \"employment\",\n Label = new Dictionary { [\"en\"] = \"Employment\" },\n },\n new()\n {\n Value = \"business\",\n Label = new Dictionary { [\"en\"] = \"Business\" },\n },\n new()\n {\n Value = \"investments\",\n Label = new Dictionary { [\"en\"] = \"Investments\" },\n },\n new()\n {\n Value = \"other\",\n Label = new Dictionary { [\"en\"] = \"Other\" },\n },\n },\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/questionnaires/{questionnaire_uuid}/": { + "get": { + "summary": "Get questionnaire", + "description": "Fetch full questionnaire detail for one version by `questionnaire_uuid`: title, `languages`, canonical `graph`, derived `sections`, and version metadata.", + "operationId": "get_questionnaire", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "parameters": [ + { + "name": "questionnaire_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the questionnaire (the `uuid` value returned by the list endpoint, also returned as `questionnaire_id` on detail endpoints)." + } + ], + "responses": { + "200": { + "description": "Full questionnaire detail including the canonical `graph`, the derived `sections` view, version metadata, and `is_editable`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QuestionnaireDetail" + }, + "examples": { + "Published questionnaire": { + "value": { + "questionnaire_id": "11111111-2222-3333-4444-555555555555", + "title": "Customer Onboarding", + "description": "Additional information needed for verification", + "languages": [ + "en" + ], + "default_language": "en", + "is_active": true, + "is_simple_questionnaire": true, + "graph": { + "start_node": "occupation", + "nodes": { + "occupation": { + "element_type": "SHORT_TEXT", + "is_required": true, + "title": { + "en": "What is your occupation?" + }, + "next": "source_of_funds" + }, + "source_of_funds": { + "element_type": "MULTIPLE_CHOICE", + "is_required": true, + "title": { + "en": "Source of funds" + }, + "choices": [ + { + "value": "employment", + "label": { + "en": "Employment" + } + }, + { + "value": "business", + "label": { + "en": "Business" + } + } + ], + "next": null + } + } + }, + "sections": [ + { + "title": {}, + "description": {}, + "items": [ + { + "uuid": "11111111-2222-3333-4444-666666666666", + "value": "occupation", + "element_type": "SHORT_TEXT", + "is_required": true, + "title": { + "en": "What is your occupation?" + } + } + ] + } + ], + "questionnaire_group_id": "11111111-2222-3333-4444-555555555555", + "version": 1, + "status": "published", + "published_at": "2026-04-23T10:00:00Z", + "is_editable": false + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No questionnaire with this `questionnaire_uuid` exists for the authenticated application. Either the UUID is wrong or it belongs to another application — the API deliberately does not distinguish the two cases.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/questionnaires/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.questionnaires.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.questionnaires.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Questionnaires.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.questionnaires().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "questionnaires->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.questionnaires.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Questionnaires.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "patch": { + "summary": "Update questionnaire", + "description": "Update a questionnaire. Versioning depends on the `status` field you send and the current state of the version: (1) no `status` on a **published** version → the published version is updated in place (same `questionnaire_id`, same `version`); (2) no `status` on a **draft** → the draft is updated and immediately published; (3) `status: \"published\"` on a **published** version → a NEW published version is created under the same `questionnaire_group_id` (new `questionnaire_id`, `version` + 1) and returned, and any existing draft in the group is deleted; (4) `status: \"draft\"` keeps a draft as a draft (autosave) but is rejected with 400 on a published version (`\"Cannot revert a published version to draft.\"`).", + "operationId": "update_questionnaire", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "parameters": [ + { + "name": "questionnaire_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the questionnaire to update." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleQuestionnaireUpdateRequest" + }, + "examples": { + "Rename": { + "summary": "Rename a questionnaire", + "value": { + "title": "Updated Customer Onboarding" + } + }, + "Replace questions": { + "summary": "Replace the full linear question list", + "value": { + "form_elements": [ + { + "id": "occupation", + "element_type": "short_text", + "label": { + "en": "What is your occupation?" + }, + "is_required": true + }, + { + "id": "source_of_funds", + "element_type": "multiple_choice", + "label": { + "en": "Source of funds" + }, + "options": [ + { + "value": "employment", + "label": { + "en": "Employment" + } + }, + { + "value": "business", + "label": { + "en": "Business" + } + } + ] + } + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Questionnaire updated. The body is the latest state of the resulting version — the in-place updated version, or the newly created published version when you sent `status: \"published\"` on a published row (in that case `questionnaire_id` and `version` differ from the ones you patched). `questionnaire_id` in the response is the per-version UUID; use `questionnaire_group_id` if you need the stable group identifier.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QuestionnaireDetail" + } + } + } + }, + "400": { + "description": "Validation error — unknown `element_type`, missing translation, unsupported branching/conditional fields, `languages` missing `en`, `default_language` not in `languages`, option without a label, sending a raw `graph`, or `status: \"draft\"` on a published version (`\"Cannot revert a published version to draft.\"`). Response body is a DRF validation error keyed by field name." + }, + "403": { + "description": "API key missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No questionnaire with this `questionnaire_uuid` exists for the authenticated application." + }, + "415": { + "description": "Unsupported media type. Set `Content-Type: application/json`." + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X PATCH 'https://verification.didit.me/v3/questionnaires/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"title\": \"Updated Customer Onboarding\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.questionnaires.update(\n \"11111111-2222-3333-4444-555555555555\",\n title=\"Updated Customer Onboarding\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.questionnaires.update(\"11111111-2222-3333-4444-555555555555\", {\n title: \"Updated Customer Onboarding\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Questionnaires.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SimpleQuestionnaireUpdateRequest{\n\tTitle: didit.String(\"Updated Customer Onboarding\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SimpleQuestionnaireUpdateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.questionnaires().update(\"11111111-2222-3333-4444-555555555555\", SimpleQuestionnaireUpdateRequest.builder()\n .title(\"Updated Customer Onboarding\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "questionnaires->update('11111111-2222-3333-4444-555555555555', [\n 'title' => 'Updated Customer Onboarding',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.questionnaires.update(\n \"11111111-2222-3333-4444-555555555555\",\n title: \"Updated Customer Onboarding\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Questionnaires.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new SimpleQuestionnaireUpdateRequest\n{\n Title = \"Updated Customer Onboarding\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "summary": "Delete questionnaire", + "description": "Permanently delete one questionnaire version (no undo). Stored questionnaire responses linked to this version are deleted with it, and sessions referencing it lose the link; other versions in the same group are not deleted. Workflows that referenced it keep running but the questionnaire step can no longer load it.", + "operationId": "delete_questionnaire", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "parameters": [ + { + "name": "questionnaire_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the questionnaire to delete." + } + ], + "responses": { + "204": { + "description": "Questionnaire deleted. The response body is empty." + }, + "403": { + "description": "API key missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No questionnaire with this `questionnaire_uuid` exists for the authenticated application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X DELETE 'https://verification.didit.me/v3/questionnaires/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.questionnaires.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.questionnaires.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Questionnaires.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.questionnaires().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "questionnaires->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.questionnaires.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Questionnaires.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ] + } + }, + "/v3/users/": { + "get": { + "summary": "List users", + "description": "List [users](/entities/users) Didit assembles from KYC sessions sharing the same `vendor_data` (free-form string, NOT a UUID), plus users created via the API. Paginated, ordered by `last_session_at` desc. This endpoint does not support filtering or search parameters — fetch pages and filter client-side.", + "operationId": "list_users", + "x-didit-sdk": { + "resource": "users", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "description": "Page size. Defaults to 50 when omitted. There is no enforced maximum, but keep pages small for latency." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "description": "Zero-based offset of the first record to return. Prefer following the `next` URL from the previous page over computing this by hand." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/users/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.users.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.users.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Users.ListAutoPaging(ctx, didit.UsersListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.users().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.users.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Users.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated list of users with verification status, session counts, and feature breakdown.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows — exact up to 100, capped at 100 beyond that for performance. Do not use it to detect the last page; rely on `next` being `null` instead." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserListItem" + } + } + } + }, + "examples": { + "Example": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": null, + "full_name": "John Michael Doe", + "date_of_birth": "1990-05-15", + "effective_name": "John Michael Doe", + "status": "ACTIVE", + "portrait_image_url": "https:///...", + "session_count": 3, + "approved_count": 2, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [ + "USA" + ], + "approved_emails": [ + "john@example.com" + ], + "approved_phones": [ + "+14155551234" + ], + "features": { + "ID_VERIFICATION": "Approved", + "LIVENESS": "Approved", + "FACE_MATCH": "Approved", + "AML": "Approved" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-01T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2025-06-01T08:00:00Z" + }, + { + "didit_internal_id": "6ed99d53-e8f5-4cf8-9ac1-4a506cb40f6b", + "vendor_data": null, + "display_name": "Jane S.", + "full_name": "Jane Elizabeth Smith", + "date_of_birth": "1985-11-22", + "effective_name": "Jane S.", + "status": "FLAGGED", + "portrait_image_url": null, + "session_count": 1, + "approved_count": 0, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [], + "approved_emails": [], + "approved_phones": [], + "features": { + "ID_VERIFICATION": "In Review" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "In Review" + } + ], + "last_session_at": "2025-06-20T14:00:00Z", + "last_activity_at": "2025-06-20T14:05:00Z", + "first_session_at": "2025-06-20T14:00:00Z", + "tags": [], + "created_at": "2025-06-20T14:00:00Z" + } + ] + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "The key cannot list users for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/users/{vendor_data}/": { + "get": { + "summary": "Get user", + "description": "Fetch one [user](/entities/users) by `vendor_data` (exact match). Adds `metadata`, `comments`, and `updated_at` to the list view, and switches `tags` to the detailed tag-link shape.", + "operationId": "get_user", + "x-didit-sdk": { + "resource": "users", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the user — a free-form string (NOT a UUID). This is the same value you passed as `vendor_data` when creating the session that first introduced this user, matched exactly as sent.", + "example": "user-abc-123" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/users/user-abc-123/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.users.retrieve(\n \"user-abc-123\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.users.retrieve(\"user-abc-123\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Users.Retrieve(ctx, \"user-abc-123\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.users().retrieve(\"user-abc-123\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->retrieve('user-abc-123');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.users.retrieve(\"user-abc-123\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Users.RetrieveAsync(\"user-abc-123\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Full user detail including metadata, comments/activity log, and aggregated session data.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDetailItem" + }, + "examples": { + "Active user": { + "value": { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": null, + "full_name": "John Michael Doe", + "date_of_birth": "1990-05-15", + "effective_name": "John Michael Doe", + "status": "ACTIVE", + "metadata": { + "tier": "premium", + "source": "website" + }, + "portrait_image_url": "https:///...", + "session_count": 3, + "approved_count": 2, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [ + "USA" + ], + "approved_emails": [ + "john@example.com" + ], + "approved_phones": [ + "+14155551234" + ], + "features": { + "ID_VERIFICATION": "Approved", + "LIVENESS": "Approved", + "FACE_MATCH": "Approved", + "AML": "Approved" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-01T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "tag": { + "uuid": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b", + "name": "VIP", + "color": "#2567FF", + "description": null, + "source": "custom", + "created_at": "2025-05-01T09:00:00Z", + "updated_at": "2025-05-01T09:00:00Z" + }, + "added_by_email": "analyst@acme.com", + "added_by_name": "Jane Analyst", + "created_at": "2025-06-02T11:00:00Z" + } + ], + "comments": [ + { + "uuid": "c1111111-2222-4333-8444-555555555555", + "comment_type": "STATUS_CHANGED", + "comment": null, + "actor_email": "analyst@acme.com", + "actor_name": "Jane Analyst", + "previous_status": "FLAGGED", + "new_status": "ACTIVE", + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "mentioned_emails": [], + "created_at": "2025-06-01T10:30:00Z" + } + ], + "created_at": "2025-06-01T08:00:00Z", + "updated_at": "2025-06-15T10:30:00Z" + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "Invalid or revoked API key, or the key cannot read this application's users. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No user with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update user", + "description": "Partial update of a user: `display_name`, `full_name`, `date_of_birth`, `status`, `metadata`, `approved_emails`/`approved_phones`/`issuing_states`. `metadata` fully replaces. Changing `status` here works like [Update User Status](#patch-/v3/users/-vendor_data-/update-status/): it records a `STATUS_CHANGED` activity with the optional `reason`, and `BLOCKED` adds the `vendor_data` to the system blocklist (moving away from `BLOCKED` removes that entry). Changes to `display_name`, `full_name`, `date_of_birth` or `metadata` are recorded as a `PROFILE_UPDATED` activity.", + "operationId": "update_user", + "x-didit-sdk": { + "resource": "users", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the user (free-form string, NOT a UUID).", + "example": "user-abc-123" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/users/user-abc-123/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"display_name\": \"Jane S.\", \"status\": \"FLAGGED\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.users.update(\n \"user-abc-123\",\n display_name=\"Jane S.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.users.update(\"user-abc-123\", {\n display_name: \"Jane S.\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Users.Update(ctx, \"user-abc-123\", didit.UsersUpdateParams{\n\tDisplayName: didit.String(\"Jane S.\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.UsersUpdateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.users().update(\"user-abc-123\", UsersUpdateParams.builder()\n .displayName(\"Jane S.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->update('user-abc-123', [\n 'display_name' => 'Jane S.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.users.update(\n \"user-abc-123\",\n display_name: \"Jane S.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Users.UpdateAsync(\"user-abc-123\", new UsersUpdateParams\n{\n DisplayName = \"Jane S.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "User updated. The full updated user record (same shape as Get User) is returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDetailItem" + }, + "examples": { + "Updated": { + "value": { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": "Jane S.", + "full_name": "Jane Elizabeth Smith", + "date_of_birth": "1985-11-22", + "effective_name": "Jane S.", + "status": "FLAGGED", + "metadata": { + "tier": "premium" + }, + "portrait_image_url": "https:///...", + "session_count": 3, + "approved_count": 2, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [ + "USA" + ], + "approved_emails": [ + "john@example.com" + ], + "approved_phones": [ + "+14155551234" + ], + "features": { + "ID_VERIFICATION": "Approved", + "LIVENESS": "Approved", + "FACE_MATCH": "Approved", + "AML": "Approved" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-01T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "tag": { + "uuid": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b", + "name": "VIP", + "color": "#2567FF", + "description": null, + "source": "custom", + "created_at": "2025-05-01T09:00:00Z", + "updated_at": "2025-05-01T09:00:00Z" + }, + "added_by_email": "analyst@acme.com", + "added_by_name": "Jane Analyst", + "created_at": "2025-06-02T11:00:00Z" + } + ], + "comments": [], + "created_at": "2025-06-01T08:00:00Z", + "updated_at": "2025-06-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Invalid body — typically an unknown `status` or a `date_of_birth` not in `YYYY-MM-DD`.", + "content": { + "application/json": { + "examples": { + "Bad status": { + "value": { + "status": [ + "\"INVALID\" is not a valid choice." + ] + } + }, + "Bad date_of_birth": { + "value": { + "date_of_birth": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + }, + "Reason too long": { + "value": { + "reason": [ + "Ensure this field has no more than 1000 characters." + ] + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "The key cannot update users for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No user with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "full_name": { + "type": "string", + "nullable": true, + "maxLength": 512, + "description": "Full name of the user" + }, + "display_name": { + "type": "string", + "nullable": true, + "description": "Custom display name for this user" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth in YYYY-MM-DD format" + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "User lifecycle status. A change is recorded as a `STATUS_CHANGED` activity, and `BLOCKED` adds the `vendor_data` to the system blocklist (moving away from `BLOCKED` removes that entry)." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Arbitrary JSON object you attach to the user. **Fully replaces** the existing metadata on update — merge client-side if you only want to add keys." + }, + "approved_emails": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Verified email addresses for this user, e.g. `[\"john@example.com\"]`. Fully replaces the stored list." + }, + "approved_phones": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Verified phone numbers for this user. Fully replaces the stored list." + }, + "issuing_states": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Issuing countries (ISO 3166-1 alpha-3), e.g. `[\"USA\"]`. Fully replaces the stored list." + }, + "reason": { + "type": "string", + "nullable": true, + "maxLength": 1000, + "description": "Why the status changed. Recorded on the status-change entry of the user's activity (`comments[].comment`, and **Activity** in the Business Console), only when the status actually changes. Webhooks do not carry it." + } + } + }, + "examples": { + "Rename": { + "summary": "Set display name", + "value": { + "display_name": "Jane S." + } + }, + "Override status": { + "summary": "Flag the user for review", + "value": { + "status": "FLAGGED" + } + }, + "Add metadata": { + "summary": "Replace metadata JSON", + "value": { + "metadata": { + "tier": "premium", + "risk_level": "low" + } + } + }, + "Fix extracted DOB": { + "summary": "Correct extracted profile fields", + "value": { + "full_name": "Jane Elizabeth Smith", + "date_of_birth": "1985-11-22" + } + }, + "Flag with reason": { + "summary": "Flag the user and record why", + "value": { + "status": "FLAGGED", + "reason": "Address does not match the billing country" + } + } + } + } + } + } + } + }, + "/v3/users/{vendor_data}/generate-pdf/": { + "get": { + "summary": "Download the full user history PDF report", + "description": "Render and download one compliance-ready PDF containing the full verification history of the user identified by `vendor_data`. It is the same document the Console produces from **User detail → Actions → Download PDF**, so periodic compliance exports no longer need a person clicking in the Console.\n\n**What the report contains.** A cover page with the profile summary (name, date of birth, issuing states, approved emails and phones), the latest status of every feature, the session counters and an index of the sessions included — followed by the standard report of every included session, in chronological order (oldest first). Each session report is the same document `GET /v3/session/{sessionId}/generate-pdf/` returns for that session.\n\n**Eligible sessions.** Only User Verification (KYC) sessions in `Approved`, `Declined`, `In Review` or `Kyc Expired` are included; sessions in any other status are skipped. If the user has no session in one of those statuses the endpoint returns `403` — see below.\n\n**Cap.** The report is capped at the **20 most recent** reportable sessions. When a user has more, the newest 20 are included and the cover page states how many older sessions were left out. Use `GET /v3/sessions/?vendor_data=…` for the complete list as JSON.\n\n**Branding.** If your application has white-label customization, the cover and every session report are rendered with your logo and privacy-policy URL; otherwise they carry Didit branding. Reports are rendered in English — there is no language parameter.\n\n**No caching.** Every call re-renders the PDF from the current data, so a report generated after a manual review reflects the reviewer's decision. Two calls for the same user can produce byte-different files — archive the downloaded file if you need an immutable copy.\n\n**Latency.** Rendering is synchronous and downloads every stored image of every included session, so a user with many media-heavy sessions can take tens of seconds. Use a generous client read timeout (120 s recommended) and stream the body to disk. Generation stops at a server-side time budget instead of running until your client gives up: if that budget is reached, the response is still a valid PDF containing the most recent sessions, and the cover states how many were omitted.\n\n**Scope.** The user is resolved inside the application that owns the API key. A key can never export a user of another application — such a user does not exist for it and the endpoint returns `404`.\n\n**Trailing slash.** The canonical route ends with a trailing slash (`…/generate-pdf/`). A request without it is served directly with the same response — there is no `301` redirect, so no client has to follow redirects and `curl` does not need `-L`. Use the slashed URL as in the samples.", + "operationId": "get_user_generate_pdf", + "x-didit-sdk": { + "resource": "users", + "method": "generate_pdf", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the user — a free-form string (NOT a UUID). This is the same value you passed as `vendor_data` when creating the user's sessions, matched exactly as sent. URL-encode it; a value containing `/` cannot be addressed by this route.", + "example": "user-abc-123" + } + ], + "responses": { + "200": { + "description": "The rendered PDF document, returned directly as binary `application/pdf` — there is no JSON wrapper and no download-URL indirection. The body starts with the `%PDF` magic bytes. Save it to a `.pdf` file or stream it through to your caller.", + "headers": { + "Content-Disposition": { + "description": "`attachment; filename=user_{didit_internal_id}.pdf` — the file is named after the user's stable Didit identifier (`didit_internal_id` on `GET /v3/users/{vendor_data}/`), not after `vendor_data`.", + "schema": { + "type": "string", + "example": "attachment; filename=user_f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418.pdf" + } + }, + "Content-Length": { + "description": "Total size of the PDF in bytes; the body is not chunked.", + "schema": { + "type": "integer", + "example": 148213 + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "Authorization or eligibility failure. Distinguish the two by the `detail` string:\n\n- `\"You do not have permission to perform this action.\"` — the API key is invalid or revoked, or the request carries a Console user token instead of an application API key.\n- `\"This user has no sessions in review, declined, approved or kyc expired to report.\"` — the user exists in your application but none of its sessions is in a reportable status yet (for example every session is still `In Progress`, or the user was created through `POST /v3/users/create/` and has never verified). Wait for a `status.updated` webhook before requesting the report.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "This user has no sessions in review, declined, approved or kyc expired to report." + } + } + }, + "examples": { + "Invalid credentials": { + "summary": "Revoked or invalid API key, or a Console user token", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "No reportable sessions": { + "summary": "User exists but has no Approved / Declined / In Review / Kyc Expired session", + "value": { + "detail": "This user has no sessions in review, declined, approved or kyc expired to report." + } + } + } + } + } + }, + "404": { + "description": "No user with the supplied `vendor_data` exists in the application behind the API key. Users of other applications are invisible to this key and return the same `404`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "User not found." + } + } + }, + "examples": { + "Unknown user": { + "summary": "vendor_data does not match any user of this application", + "value": { + "detail": "User not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. PDF generation has a dedicated limit of **50 requests per minute per credential** (API key, Bearer token, or source IP when unauthenticated), shared between this endpoint and `GET /v3/session/{sessionId}/generate-pdf/`, in addition to the global 600/min GET limit. Wait `Retry-After` seconds (also reflected in the `X-RateLimit-*` headers), then retry. Reports are not cached server-side, so download each report once and store the file instead of re-fetching it.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying the request.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Maximum number of PDF generations allowed in the current window (50).", + "schema": { + "type": "integer", + "example": 50 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests remaining in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix timestamp (seconds) at which the current window resets.", + "schema": { + "type": "integer", + "example": 1750000000 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable explanation of the rate-limit breach.", + "example": "Session PDF generation rate limit exceeded. You can make up to 50 requests per minute." + } + } + }, + "examples": { + "Throttled": { + "summary": "More than 50 PDF generations in one minute from the same credential", + "value": { + "detail": "Session PDF generation rate limit exceeded. You can make up to 50 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --fail \\\n 'https://verification.didit.me/v3/users/user-abc-123/generate-pdf/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n --output user-history.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.users.generate_pdf(\n \"user-abc-123\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.users.generatePdf(\"user-abc-123\");\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Users.GeneratePDF(ctx, \"user-abc-123\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.users().generatePdf(\"user-abc-123\")\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->generatePdf('user-abc-123');\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.users.generate_pdf(\"user-abc-123\")\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Users.GeneratePdfAsync(\"user-abc-123\");\nawait content.SaveToAsync(\"download.pdf\");" + } + ] + } + }, + "/v3/users/delete/": { + "post": { + "summary": "Batch delete users", + "description": "**Permanently delete** users: the user records are removed permanently. For everyday blocking prefer [Update User Status](#patch-/v3/users/-vendor_data-/update-status/) with `BLOCKED`. Select users with `vendor_data_list`, with `didit_internal_id_list`, or with both at once; or pass `delete_all: true`. This endpoint never narrows `delete_all` with filters. The Console's People filters are refused with a `400` (see the `400` response), so a narrowed intent does not turn into a full wipe. Two exceptions: `filter_has_approved_sessions` and `filter_has_declined_sessions` are not refused and not applied, so with `delete_all: true` they still delete every user; do not send them.\n\n**Biometric templates:** unless you set `retain_face_embeddings` to `true`, every image-free biometric template retained for these users after session deletion (see `/v3/biometric-templates/`) is purged before the user is deleted, so the person stops matching in duplicate detection and Face Search. With `retain_face_embeddings: true` and `face_retention_days`, you choose to keep one image-free biometric template per person for that many days, and nothing else about the person is kept. If a template cannot be purged because a biometric store is unavailable, the call returns `503`, no user in the request is deleted, and repeating the request finishes the job.\n\n**Filed regulatory reports:** if the selected data is in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "batch_delete_users", + "x-didit-sdk": { + "resource": "users", + "method": "batch_delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Provide `vendor_data_list`, `didit_internal_id_list`, or `delete_all: true`. The two lists can be combined and are OR'd. When `delete_all` is `true` it takes precedence and both lists are ignored.", + "properties": { + "vendor_data_list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Your own identifiers to delete, matched exactly as sent." + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit internal user IDs to delete - the `didit_internal_id` returned by the create and get endpoints." + }, + "delete_all": { + "type": "boolean", + "default": false, + "description": "If `true`, deletes every user in the application." + }, + "retain_face_embeddings": { + "type": "boolean", + "default": false, + "description": "Your explicit choice to keep one image-free biometric template of each deleted person for duplicate detection, for `face_retention_days`. Everything else about the person is deleted. Omit it, or send `false`, to delete the templates with the users. When `true`, `face_retention_days` is required." + }, + "face_retention_days": { + "type": "integer", + "minimum": 1, + "maximum": 3650, + "description": "Number of days the image-free biometric template is kept when `retain_face_embeddings` is `true`. Required with it, and rejected without it with a `400` on `face_retention_days`.", + "example": 365 + } + } + }, + "examples": { + "Specific users": { + "summary": "Delete users by vendor_data", + "value": { + "vendor_data_list": [ + "user-123", + "user-456" + ] + } + }, + "By Didit internal ID": { + "summary": "Delete users by didit_internal_id", + "value": { + "didit_internal_id_list": [ + "3f1c0d3e-8f0a-4c2e-9a3d-9b1f5f4a2c11" + ] + } + }, + "All users": { + "summary": "Wipe every user in this application", + "value": { + "delete_all": true + } + }, + "Keep face templates": { + "summary": "Delete users and keep anonymous face templates for 365 days", + "value": { + "vendor_data_list": [ + "user-123" + ], + "retain_face_embeddings": true, + "face_retention_days": 365 + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/users/delete/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vendor_data_list\": [\"user-123\", \"user-456\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.users.batch_delete(\n vendor_data_list=[\"user-123\", \"user-456\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.users.batchDelete({\n vendor_data_list: [\"user-123\", \"user-456\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Users.BatchDelete(ctx, didit.UsersBatchDeleteParams{\n\tVendorDataList: didit.Some([]string{\"user-123\", \"user-456\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.UsersBatchDeleteParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.users().batchDelete(UsersBatchDeleteParams.builder()\n .vendorDataList(List.of(\"user-123\", \"user-456\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->batchDelete([\n 'vendor_data_list' => ['user-123', 'user-456'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.users.batch_delete(vendor_data_list: [\"user-123\", \"user-456\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Users.BatchDeleteAsync(new UsersBatchDeleteParams\n{\n VendorDataList = new string[] { \"user-123\", \"user-456\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Users deleted. Returns the number of users actually deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deleted": { + "type": "integer", + "description": "Number of users deleted (excludes vendor_data values that didn't match anything)." + } + } + }, + "examples": { + "Deleted some": { + "value": { + "deleted": 2 + } + }, + "Nothing matched": { + "value": { + "deleted": 0 + } + } + } + } + } + }, + "400": { + "description": "Validation error; nothing is deleted. Errors are keyed by field name. With no selector (neither list and `delete_all` not `true`) the body is `{\"vendor_data_list\": [\"This field is required unless delete_all is true.\"]}`; an empty list is refused under its own key with `\"This list may not be empty.\"`. A Console-only filter (`filter_search`, `filter_status`, `filter_country`, `filter_date_from`, `filter_date_to`, `filter_tag`, `filter_latest_verification_status`, `filter_risk`, `filter_has_sessions`, `filter_has_open_alerts`, `filter_aml_monitoring`, `filter_can_sign_in`, `filter_auth_status`, `filter_auth_method` or `filter_auth_mfa_enabled`) is refused whenever it carries a value, even `false`, with an object keyed by each such field. Any other unknown `filter_*` key is refused with `\"Unknown filter.\"`.", + "content": { + "application/json": { + "examples": { + "Missing selector": { + "summary": "Neither list nor delete_all provided", + "value": { + "vendor_data_list": [ + "This field is required unless delete_all is true." + ] + } + }, + "Console-only filter": { + "value": { + "filter_has_sessions": "This filter is Console-only and is not supported by this endpoint." + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "Invalid or revoked API key, or the key cannot delete users for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + }, + "503": { + "description": "A biometric store was unavailable while retaining or purging a biometric template. Nothing is deleted; repeat the same request. Retries are idempotent and converge on a single template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Retryable": { + "value": { + "detail": "The biometric-template operation could not finish because an external store is unavailable. The operation is retryable." + } + } + } + } + } + }, + "409": { + "description": "Nothing is deleted. `code` says why: `in_filed_report` when the selection includes a person in a finalized regulatory report, `subject_deletion_in_progress` when another request is still deleting one of the selected people. Repeat the request once that deletion has finished.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/InFiledReportError" + }, + { + "$ref": "#/components/schemas/SubjectDeletionInProgressError" + } + ] + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/sessions/delete/": { + "post": { + "summary": "Bulk delete KYC sessions by session_number", + "description": "Bulk delete User Verification (KYC) sessions by their numeric `session_number`, or every KYC session in your application with `delete_all: true`.\n\n`session_number` is the human-friendly incrementing counter shown in the Console session list and returned as `session_number` by the create-session and decision endpoints; it is **not** the `session_id` UUID. To delete a session by UUID, or to delete a Business Verification (KYB) session, which this endpoint never touches, use `DELETE /v3/session/{sessionId}/delete/` instead.\n\n**Up to 20 sessions are deleted in the request, more run as a job.** The number of matching sessions decides, whether they are selected by `session_numbers` or with `delete_all`:\n- Up to 20 matching sessions: they are deleted before the response, which is `200` with one entry per session in `results[]`.\n- More than 20: the response is `202` with a deletion job, and the sessions are deleted in the background. Poll `GET /v3/sessions/delete/{job_id}/` until `status` is `COMPLETED` or `FAILED`. A job reports counts per outcome, never which sessions it deleted. A `delete_all` job deletes only the sessions created before the request; sessions started while it runs are not deleted.\n\nEach deleted session gets exactly the same treatment as the single-session delete endpoint: the session, every verification attempt it holds, its decision and extracted data, its related feature records (ID verification, face, liveness, face match, and so on) and all of its stored files are deleted, it disappears from every read endpoint, and previously issued media URLs stop resolving. Webhook deliveries still queued for a deleted session, and their retries, are dropped; webhooks already delivered are not affected. Blocklist entries are **not** removed, credits already consumed are **not** refunded, and the parent User entities are **not** deleted (use `POST /v3/users/delete/` for those); no webhook is emitted for the deletion. Deletion is permanent and irreversible.\n\n**Selection semantics:**\n- With `delete_all: true` you can narrow the selection with the filters of `GET /v3/sessions/`, each prefixed with `filter_` (for example `filter_status`, `filter_workflow_id`, `filter_date_from`). Filters that take several values in List Sessions (`status`, `workflow_id`, `country`, `region`, `document_type`, `document_name`, `session_type`, `warning`, `organization_name`) accept a comma-separated string or an array; the others take one value. An empty value is ignored. Filters apply only together with `delete_all`, and a deletion job applies the same filters.\n- An unknown `filter_*` key, or a filter value that cannot be read, rejects the whole request with `400` before anything is deleted, so a mistyped filter never widens the deletion to every session.\n- Entries in `session_numbers` that do not match a live KYC session in your application (unknown numbers, already-deleted sessions, or sessions owned by another application) are **silently skipped**: they do not appear in `results` and are not counted in a job's `total`.\n- Validation failures (a non-numeric entry, an empty list, neither `session_numbers` nor `delete_all` provided, or a template retention the application cannot honour) reject the **whole request** with `400` and delete nothing.\n- Sessions are deleted in groups. If a group cannot be deleted (for example because a storage service is unavailable), every session in that group is reported as `face_retention_outcome: failed_retryable` and stays in place; the other groups are unaffected. Repeat the request for those sessions. A job retries such a group by itself.\n- There is no documented cap on the list length; the shared write rate limit (below) is the practical bound.\n\n**Face biometric data:** the optional `retain_face_embeddings`, `face_retention_days`, `face_retention_deadline`, `deletion_instruction`, and `instruction_id` fields have the same meaning as on `DELETE /v3/session/{sessionId}/delete/` and apply to every matched session. Each result reports its own `face_retention_outcome`.\n\n**Response:** `200 OK` with `results[]` for up to 20 matching sessions, or `202 Accepted` with a deletion job for more. Every result confirms deletion except `face_retention_outcome: failed_retryable`, which reports a session that was NOT deleted and should be retried. Earlier versions of this endpoint returned `204 No Content`; update clients that assert on `204`.\n\n**Idempotency:** repeating the same request deletes nothing more: numbers that no longer match a session are skipped.\n\n**`delete_all: true` (destructive):** deletes **every** remaining KYC session in the application; `session_numbers` is ignored when set. There is no confirmation step and no undo.\n\n**Authentication:** API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`); this API never returns `401`.\n\n**Rate limit:** shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.\n\nThis endpoint accepts API keys only. Business Console user Bearer tokens are rejected with `403` (unlike the single-session delete, which accepts both).\n\n**Filed regulatory reports:** if the selected data is in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "batch_delete_sessions", + "x-didit-sdk": { + "resource": "sessions", + "method": "batch_delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_numbers": { + "type": "array", + "description": "Numeric `session_number` values of the KYC sessions to delete, as digit-only strings (JSON numbers are also accepted and coerced). Required unless `delete_all` is `true`; an empty list returns `400`. Any non-digit entry rejects the whole request with `400` (unless `delete_all` is `true`, in which case validation is skipped and the entries are ignored). Numbers that do not match a live session in your application are silently skipped.", + "items": { + "type": "string", + "pattern": "^[0-9]+$", + "example": "1001" + }, + "minItems": 1, + "example": [ + "1001", + "1002", + "1003" + ] + }, + "delete_all": { + "type": "boolean", + "description": "When `true`, every remaining KYC session in the calling application is deleted and `session_numbers` is ignored, unless you narrow the selection with `filter_*` keys. More than 20 matching sessions run as a deletion job (`202`) that covers only the sessions created before the request. Defaults to `false`. Irreversible; there is no confirmation step.", + "default": false, + "example": false + }, + "retain_face_embeddings": { + "type": "boolean", + "nullable": true, + "description": "Override the application's `face_retention_policy` for this deletion. `true` keeps one image-free face biometric template anchored to the session's User after the session is deleted; `false` deletes the face embedding with the session; omit or `null` to follow the application policy. Ignored for KYB sessions and for sessions without a face embedding.", + "example": true + }, + "face_retention_days": { + "type": "integer", + "minimum": 1, + "maximum": 3650, + "description": "Finite retention duration, in days, for a retained template. Required when a template is retained unless the application already sets `face_retention_days`. The template's `expires_at` is the earliest of this duration, the application's general data-retention window, and `face_retention_deadline`.", + "example": 365 + }, + "face_retention_deadline": { + "type": "string", + "format": "date-time", + "description": "Optional hard expiry for a retained template. Must be in the future.", + "example": "2027-08-28T00:00:00Z" + }, + "deletion_instruction": { + "type": "string", + "enum": [ + "operational_session_delete", + "privacy_erasure" + ], + "default": "operational_session_delete", + "description": "The class of instruction you are giving. `operational_session_delete` deletes the session and retains a template only when the policy or `retain_face_embeddings` says so. `privacy_erasure` purges every retained biometric template anchored to the session's User before deleting the session, cannot be overridden by the application policy, and cannot be combined with `retain_face_embeddings: true` (`400`).", + "example": "operational_session_delete" + }, + "instruction_id": { + "type": "string", + "maxLength": 255, + "description": "Your durable reference for this deletion instruction (for example an erasure-ticket id). Recorded on the audit trail and on any retained template. Generated by Didit when omitted.", + "example": "dsar-2026-0142" + }, + "filter_status": { + "description": "Narrows a `delete_all` to the sessions that match the `status` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_vendor_data": { + "description": "Narrows a `delete_all` to the sessions that match the `vendor_data` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated text, so several values are not matched as alternatives. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string" + }, + "filter_search": { + "description": "Narrows a `delete_all` to the sessions that match the `search` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated text, so several values are not matched as alternatives. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string" + }, + "filter_workflow_id": { + "description": "Narrows a `delete_all` to the sessions that match the `workflow_id` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_date_from": { + "description": "Narrows a `delete_all` to the sessions that match the `date_from` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string", + "format": "date" + }, + "filter_date_to": { + "description": "Narrows a `delete_all` to the sessions that match the `date_to` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string", + "format": "date" + }, + "filter_country": { + "description": "Narrows a `delete_all` to the sessions that match the `country` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_document_type": { + "description": "Narrows a `delete_all` to the sessions that match the `document_type` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_document_name": { + "description": "Narrows a `delete_all` to the sessions that match the `document_name` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_region": { + "description": "Narrows a `delete_all` to the sessions that match the `region` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_didit_internal_id": { + "description": "Narrows a `delete_all` to the sessions that match the `didit_internal_id` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string", + "format": "uuid" + }, + "filter_session_type": { + "description": "Narrows a `delete_all` to the sessions that match the `session_type` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_api_service": { + "description": "Narrows a `delete_all` to the sessions that match the `api_service` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string" + }, + "filter_screened_full_name": { + "description": "Narrows a `delete_all` to the sessions that match the `screened_full_name` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated text, so several values are not matched as alternatives. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string" + }, + "filter_warning": { + "description": "Narrows a `delete_all` to the sessions that match the `warning` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_status_initialized_as_in_review": { + "description": "Narrows a `delete_all` to the sessions that match the `status_initialized_as_in_review` filter of [List Sessions](/sessions-api/list-sessions), with the same values. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "boolean" + }, + "filter_is_kyc_to_be_reviewed": { + "description": "Narrows a `delete_all` to the sessions that match the `is_kyc_to_be_reviewed` filter of [List Sessions](/sessions-api/list-sessions), with the same values. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "boolean" + }, + "filter_euuid": { + "description": "Narrows a `delete_all` to the sessions that match the `euuid` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string", + "format": "uuid" + }, + "filter_organization_name": { + "description": "Narrows a `delete_all` to the sessions that match the `organization_name` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. With an API key it can only match your own organization's name, so it selects all of your sessions or none. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + } + }, + "examples": { + "Specific sessions": { + "summary": "Delete specific sessions by number", + "value": { + "session_numbers": [ + "1001", + "1002", + "1003" + ] + } + }, + "All sessions": { + "summary": "Delete every KYC session in the application", + "value": { + "delete_all": true + } + }, + "Delete and retain templates": { + "summary": "Delete sessions but keep image-free biometric templates for 365 days", + "value": { + "session_numbers": [ + "1001", + "1002" + ], + "retain_face_embeddings": true, + "face_retention_days": 365, + "instruction_id": "post-approval-cleanup-2026-08" + } + }, + "Privacy erasure": { + "summary": "Delete sessions and purge every retained template for their Users", + "value": { + "session_numbers": [ + "1001" + ], + "deletion_instruction": "privacy_erasure", + "instruction_id": "dsar-2026-0142" + } + }, + "Filtered delete_all": { + "summary": "Delete every Declined session of one workflow created in September 2026", + "value": { + "delete_all": true, + "filter_status": [ + "Declined" + ], + "filter_workflow_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "filter_date_from": "2026-09-01", + "filter_date_to": "2026-09-30" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Up to 20 sessions matched and were processed in the request. One result per matched session; numbers that matched nothing (unknown or already deleted) are skipped and do not appear. A result confirms deletion except when `face_retention_outcome` is `failed_retryable`: that session was NOT deleted and should be retried.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The deleted session." + }, + "session_number": { + "type": "integer", + "nullable": true, + "description": "The deleted session's number." + }, + "face_retention_outcome": { + "type": "string", + "enum": [ + "retained_with_user", + "deleted", + "none", + "ineligible_no_vendor_user", + "failed_retryable" + ], + "description": "What happened to the session's face biometric data. `deleted`: the face embedding was deleted with the session (default). `retained_with_user`: the session is deleted and one image-free biometric template stays anchored to the User; see `biometric_template_uuid`. `none`: the session had no face embedding (for example a document-only KYC session without a liveness selfie; KYB sessions are never selected by this endpoint). `ineligible_no_vendor_user`: retention was requested but the session has no linked User, so the embedding was deleted with the session. `failed_retryable` (batch endpoint only): the group this session was deleted with could not be deleted (a storage service, a biometric store, or a monitoring provider was unavailable); this session is still in place, repeat the request for it." + }, + "biometric_template_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Id of the retained biometric template when `face_retention_outcome` is `retained_with_user`; `null` otherwise. Manage it through `/v3/biometric-templates/{template_uuid}/`." + }, + "biometric_template_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the retained biometric template is deleted, when `face_retention_outcome` is `retained_with_user`; `null` when no template is retained." + }, + "deleted_child_verifications": { + "type": "integer", + "description": "Always `0` on this endpoint, which deletes KYC sessions only. Deleting a Business Verification (KYB) session with `DELETE /v3/session/{sessionId}/delete/` reports the verifications it created that were deleted with it." + } + } + } + } + } + }, + "examples": { + "Mixed outcomes": { + "value": { + "results": [ + { + "session_id": "4c5c7f3a-3b1d-4b7e-9c39-2b4f7e6a1d02", + "session_number": 1001, + "face_retention_outcome": "deleted", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + }, + { + "session_id": "b8e1d2c3-4f5a-4b6c-8d7e-9f0a1b2c3d4e", + "session_number": 1002, + "face_retention_outcome": "retained_with_user", + "biometric_template_uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "biometric_template_expires_at": "2027-10-09T10:15:00Z", + "deleted_child_verifications": 0 + }, + { + "session_id": "7a1e9c2d-5f4b-4c3a-8e2d-1b0a9f8e7d6c", + "session_number": 1003, + "face_retention_outcome": "ineligible_no_vendor_user", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + }, + { + "session_id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f", + "session_number": 1004, + "face_retention_outcome": "failed_retryable", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + } + ] + } + } + } + } + } + }, + "202": { + "description": "More than 20 sessions matched. They are deleted in the background; poll `GET /v3/sessions/delete/{job_id}/` until `status` is `COMPLETED` or `FAILED`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionDeletionJob" + }, + "examples": { + "Job accepted": { + "value": { + "job_id": "5b8f2c1e-7d3a-4f6b-9e2c-1a0d8f7e6c5b", + "status": "PENDING", + "total": 250, + "processed": 0, + "outcomes": { + "deleted": 0, + "none": 0, + "retained_with_user": 0, + "ineligible_no_vendor_user": 0, + "failed_retryable": 0, + "already_deleted": 0 + }, + "deleted_child_verifications": 0, + "error": null, + "created_at": "2026-10-09T10:15:00Z", + "completed_at": null + } + } + } + } + } + }, + "400": { + "description": "Validation error — nothing was deleted. Error values are arrays of message strings keyed by field name.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_numbers": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": { + "description": "Other keys: a `filter_*` key that is not a List Sessions filter (`\"Unknown filter.\"`), or a `filter_*` key whose value cannot be read (keyed by that `filter_*` key, with an array of messages)." + } + }, + "examples": { + "Missing selector": { + "summary": "Neither session_numbers nor delete_all provided", + "value": { + "session_numbers": [ + "This field is required unless delete_all is true." + ] + } + }, + "Empty list": { + "summary": "session_numbers is an empty array", + "value": { + "session_numbers": [ + "This list may not be empty." + ] + } + }, + "Non-numeric entry": { + "summary": "session_numbers contains a non-digit string", + "value": { + "session_numbers": [ + "All session_numbers must be numeric." + ] + } + }, + "Privacy erasure cannot retain": { + "summary": "deletion_instruction privacy_erasure combined with retain_face_embeddings true", + "value": { + "retain_face_embeddings": [ + "Privacy erasure cannot retain biometric templates." + ] + } + }, + "No finite retention duration": { + "summary": "Retention requested without face_retention_days on the request or the application", + "value": { + "detail": "A finite biometric-template retention duration is required." + } + }, + "Unknown filter": { + "summary": "A filter_* key that List Sessions does not have", + "value": { + "filter_reviewer": "Unknown filter." + } + }, + "Unreadable filter value": { + "summary": "A filter value that cannot be read", + "value": { + "filter_date_from": [ + "Enter a valid date." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing API key. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid API key": { + "summary": "No x-api-key header, or the key is invalid", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Shared write rate limit exceeded (300 POST/PATCH/DELETE requests per minute per API key). Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "Write budget exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + }, + "409": { + "description": "A selected session is part of a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/sessions/delete/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"session_numbers\": [\"1001\", \"1002\", \"1003\"]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.batch_delete(\n session_numbers=[\"1001\", \"1002\", \"1003\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.batchDelete({\n session_numbers: [\"1001\", \"1002\", \"1003\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.BatchDelete(ctx, didit.SessionsBatchDeleteParams{\n\tSessionNumbers: didit.Some([]string{\"1001\", \"1002\", \"1003\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.SessionsBatchDeleteParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().batchDelete(SessionsBatchDeleteParams.builder()\n .sessionNumbers(List.of(\"1001\", \"1002\", \"1003\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->batchDelete([\n 'session_numbers' => ['1001', '1002', '1003'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.batch_delete(session_numbers: [\"1001\", \"1002\", \"1003\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.BatchDeleteAsync(new SessionsBatchDeleteParams\n{\n SessionNumbers = new string[] { \"1001\", \"1002\", \"1003\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/sessions/delete/{job_id}/": { + "get": { + "summary": "Get a bulk session deletion job", + "description": "Progress of a bulk session deletion that `POST /v3/sessions/delete/` answered with `202` (more than 20 matching sessions, selected by `session_numbers` or with `delete_all`). Poll every few seconds while `status` is `PENDING` or `PROCESSING`; stop when it is `COMPLETED` or `FAILED`.\n\nThe job reports how many sessions ended in each outcome and how many it has processed, never which sessions it deleted. A `FAILED` job carries the reason in `error`; the sessions it had not reached are still in place, so repeat the deletion request to delete them.\n\n**Authentication:** API key in the `x-api-key` header. A job is only visible to the application that created it; any other `job_id` returns `404`. Missing or invalid credentials return `403`; this API never returns `401`.", + "operationId": "get_session_deletion_job", + "x-didit-sdk": { + "resource": "sessions", + "method": "retrieve_deletion_job", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The `job_id` returned with the `202` from `POST /v3/sessions/delete/`." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl 'https://verification.didit.me/v3/sessions/delete/JOB_ID/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.retrieve_deletion_job(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.retrieveDeletionJob(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.RetrieveDeletionJob(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().retrieveDeletionJob(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->retrieveDeletionJob('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.retrieve_deletion_job(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.RetrieveDeletionJobAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionDeletionJob" + }, + "examples": { + "Processing": { + "value": { + "job_id": "5b8f2c1e-7d3a-4f6b-9e2c-1a0d8f7e6c5b", + "status": "PROCESSING", + "total": 250, + "processed": 100, + "outcomes": { + "deleted": 82, + "none": 18, + "retained_with_user": 0, + "ineligible_no_vendor_user": 0, + "failed_retryable": 0, + "already_deleted": 0 + }, + "deleted_child_verifications": 0, + "error": null, + "created_at": "2026-10-09T10:15:00Z", + "completed_at": null + } + }, + "Completed": { + "value": { + "job_id": "5b8f2c1e-7d3a-4f6b-9e2c-1a0d8f7e6c5b", + "status": "COMPLETED", + "total": 250, + "processed": 250, + "outcomes": { + "deleted": 204, + "none": 44, + "retained_with_user": 0, + "ineligible_no_vendor_user": 0, + "failed_retryable": 0, + "already_deleted": 2 + }, + "deleted_child_verifications": 0, + "error": null, + "created_at": "2026-10-09T10:15:00Z", + "completed_at": "2026-10-09T10:16:40Z" + } + }, + "Failed": { + "value": { + "job_id": "5b8f2c1e-7d3a-4f6b-9e2c-1a0d8f7e6c5b", + "status": "FAILED", + "total": 250, + "processed": 100, + "outcomes": { + "deleted": 82, + "none": 18, + "retained_with_user": 0, + "ineligible_no_vendor_user": 0, + "failed_retryable": 0, + "already_deleted": 0 + }, + "deleted_child_verifications": 0, + "error": "The deletion stopped before it finished. The sessions it had not reached are still in place; repeat the request to delete them.", + "created_at": "2026-10-09T10:15:00Z", + "completed_at": "2026-10-09T10:15:52Z" + } + } + } + } + } + }, + "403": { + "description": "Missing API key. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid API key": { + "summary": "No x-api-key header, or the key is invalid", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No deletion job with this `job_id` exists for the calling application. Jobs are only visible to the application that created them." + } + } + } + }, + "/v3/sessions/{session_id}/reviews/": { + "get": { + "summary": "List session reviews and activity log", + "description": "Return the audit trail / activity feed for a User Verification (KYC) session, newest first, in a paginated envelope (`count` / `next` / `previous` / `results`, 50 entries per page by default).\n\nFeed entries are written by every actor that touches the session: manual decisions made through `PATCH /v3/session/{session_id}/update-status/` (which records `previous_status` → `new_status`), Console reviewer activity (comments with `@email` mentions, KYC/POA data edits, file uploads/removals, tag changes, detail-page views), AML ongoing-monitoring matches, and notes appended via `POST` on this same path. Reading this feed is the API-side equivalent of the **Activity** panel in the Didit Console session view.\n\nOnly User Verification (KYC) sessions are addressable here; Business Verification (KYB) session IDs return `404`.", + "operationId": "list_session_reviews", + "x-didit-sdk": { + "resource": "sessions.reviews", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session. Must belong to the same application as the API key — cross-application UUIDs, deleted sessions, and Business Verification (KYB) session IDs all return `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Page size. Defaults to `50`.", + "schema": { + "type": "integer", + "default": 50, + "example": 50 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of entries to skip from the start of the (ordered) feed. Defaults to `0`. The `next` / `previous` URLs in the response carry the right values for walking the feed.", + "schema": { + "type": "integer", + "default": 0, + "example": 0 + } + }, + { + "name": "ordering", + "in": "query", + "required": false, + "description": "Sort order. The feed defaults to newest first (`-created_at`); pass `created_at` to read it oldest first. Unknown values are silently ignored (default order is used); other serializer field names are also accepted.", + "schema": { + "type": "string", + "default": "-created_at", + "example": "created_at" + } + } + ], + "responses": { + "200": { + "description": "Paginated activity feed, newest first by default. `count` is the total number of entries; `next` / `previous` are ready-made page URLs (`null` at the ends). A `count` of `0` with empty `results` means no activity has been recorded yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of feed entries for this session (across all pages).", + "example": 3 + }, + "next": { + "type": "string", + "nullable": true, + "description": "URL of the next page, or `null` on the last page.", + "example": null + }, + "previous": { + "type": "string", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page.", + "example": null + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ReviewItem" + }, + "description": "Feed entries for this page." + } + } + }, + "examples": { + "DecisionTrail": { + "summary": "Manual approval plus API-added notes", + "value": { + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "uuid": "de0561bc-5583-4409-9171-1a31a4c8a5f4", + "activity_type": "STATUS_UPDATED", + "actor_display": "API Client", + "actor_email": null, + "actor_type": "API_KEY", + "new_status": "Approved", + "previous_status": "In Review", + "comment": "Document re-checked manually; address matches.", + "mentioned_emails": [], + "previous_value": { + "status": "In Review" + }, + "new_value": { + "status": "Approved" + }, + "changed_fields": [ + "status" + ], + "metadata": null, + "created_at": "2026-06-12T00:19:31.918735Z" + }, + { + "uuid": "35370f58-5098-44ee-8a06-8c9af80b2455", + "activity_type": "STATUS_UPDATED", + "actor_display": "Unknown", + "actor_email": null, + "actor_type": "CONSOLE_USER", + "new_status": null, + "previous_status": null, + "comment": "Note without status.", + "mentioned_emails": [], + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "created_at": "2026-06-12T00:18:44.504157Z" + }, + { + "uuid": "00338f82-f1c5-4f57-8614-c49bb258af1a", + "activity_type": "STATUS_UPDATED", + "actor_display": "Unknown", + "actor_email": null, + "actor_type": "CONSOLE_USER", + "new_status": "In Review", + "previous_status": null, + "comment": "Flagging for manual document re-check.", + "mentioned_emails": [], + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "created_at": "2026-06-12T00:18:44.466537Z" + } + ] + } + }, + "PaginatedFirstPage": { + "summary": "First page with limit=2 — next holds the follow-up URL", + "value": { + "count": 4, + "next": "https://verification.didit.me/v3/sessions/8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d/reviews/?limit=2&offset=2", + "previous": null, + "results": [ + { + "uuid": "de0561bc-5583-4409-9171-1a31a4c8a5f4", + "activity_type": "STATUS_UPDATED", + "actor_display": "API Client", + "actor_email": null, + "actor_type": "API_KEY", + "new_status": "Approved", + "previous_status": "In Review", + "comment": "Document re-checked manually; address matches.", + "mentioned_emails": [], + "previous_value": { + "status": "In Review" + }, + "new_value": { + "status": "Approved" + }, + "changed_fields": [ + "status" + ], + "metadata": null, + "created_at": "2026-06-12T00:19:31.918735Z" + }, + { + "uuid": "bd4e6227-bdd2-42b2-a726-1d12f785a457", + "activity_type": "STATUS_UPDATED", + "actor_display": "Unknown", + "actor_email": null, + "actor_type": "CONSOLE_USER", + "new_status": null, + "previous_status": null, + "comment": null, + "mentioned_emails": [], + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "created_at": "2026-06-12T00:18:44.540238Z" + } + ] + } + }, + "Empty": { + "summary": "Session with no recorded activity yet", + "value": { + "count": 0, + "next": null, + "previous": null, + "results": [] + } + } + } + } + } + }, + "403": { + "description": "Missing, malformed, expired, or revoked `x-api-key`. Authentication is checked before the session lookup, so a missing key returns `403` even when the session UUID does not exist.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You do not have permission to perform this action." + } + } + }, + "examples": { + "InvalidKey": { + "summary": "Missing or invalid x-api-key header", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No User Verification (KYC) session with this UUID is visible to your application: the UUID does not exist, the session was deleted, or it belongs to a different application. Business Verification (KYB) session IDs also return `404` here — the reviews feed is only addressable for KYC sessions.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Session not found." + } + } + }, + "examples": { + "NotFound": { + "summary": "Unknown, deleted, or cross-application session UUID", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Inspect `Retry-After` and `X-RateLimit-*` response headers. See [Rate limiting](/integration/rate-limiting)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -s 'https://verification.didit.me/v3/sessions/8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d/reviews/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.sessions.reviews.list(\n \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.sessions.reviews.list(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Sessions.Reviews.ListAutoPaging(ctx, \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\", didit.SessionsReviewsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.sessions().reviews().list(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->reviews->list('8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.reviews.list(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Sessions.Reviews.ListAsync(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "summary": "Add a review note to a session's audit trail", + "description": "Append a note to a User Verification (KYC) session's review / activity feed. **This endpoint only records an entry — it never changes the session.** The session's `status` is left untouched, no status-transition validation runs, and no webhook fires. To actually approve, decline, or re-open a session (which also writes a fully-attributed `STATUS_UPDATED` entry to this feed), use `PATCH /v3/session/{session_id}/update-status/` instead.\n\nBoth body fields are optional and an empty JSON object is accepted. `new_status` is stored purely as a label on the entry and accepts any of the ten session status values (case-sensitive). `@email` mentions inside `comment` are stored as plain text only — they trigger no notifications on this endpoint, and the entry's `mentioned_emails` stays empty.\n\nNote on attribution: entries created here carry no actor information. In the feed they show `actor_display: \"Unknown\"`, `actor_type: \"CONSOLE_USER\"`, and `activity_type: \"STATUS_UPDATED\"` (storage defaults), and the `201` body's `user` field is `\"Unknown\"`.", + "operationId": "create_session_review", + "x-didit-sdk": { + "resource": "sessions.reviews", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "Content-Type", + "in": "header", + "required": false, + "description": "Set to `application/json` when sending a JSON body.", + "schema": { + "type": "string", + "example": "application/json" + } + }, + { + "name": "session_id", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session. Must belong to the same application as the API key — cross-application UUIDs, deleted sessions, and Business Verification (KYB) session IDs all return `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "new_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Optional status label to record on the entry. Accepts any session status value, case-sensitive (`\"approved\"` is rejected with `400`). Recording a status here does **not** change the session's actual status — use the update-status endpoint for that.", + "example": "In Review" + }, + "comment": { + "type": "string", + "nullable": true, + "description": "Optional free-text note (no enforced length limit). Stored verbatim; `@email` mentions are not processed on this endpoint.", + "example": "Flagging for manual document re-check." + } + } + }, + "examples": { + "NoteWithStatusLabel": { + "summary": "Note carrying a status label (session status unchanged)", + "value": { + "new_status": "In Review", + "comment": "Flagging for manual document re-check." + } + }, + "CommentOnly": { + "summary": "Plain note without a status label", + "value": { + "comment": "Customer emailed an updated utility bill; awaiting upload." + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Entry recorded on the session's audit trail. The session itself is unchanged — its `status` keeps its previous value and no webhook is sent. The new entry also appears at the top of `GET …/reviews/` (with `actor_display: \"Unknown\"`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "user": { + "type": "string", + "description": "Display name of the entry's author. Always `\"Unknown\"` for entries created through this endpoint, because it records no actor attribution.", + "example": "Unknown" + }, + "new_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "The status label recorded on the entry, or `null` if none was sent. The session's actual status is not affected.", + "example": "In Review" + }, + "comment": { + "type": "string", + "nullable": true, + "description": "The note recorded on the entry, or `null` if none was sent.", + "example": "Flagging for manual document re-check." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the entry was written, ISO 8601 UTC.", + "example": "2026-06-12T00:18:44.466537Z" + } + } + }, + "examples": { + "NoteWithStatusLabel": { + "summary": "Note with a status label", + "value": { + "user": "Unknown", + "new_status": "In Review", + "comment": "Flagging for manual document re-check.", + "created_at": "2026-06-12T00:18:44.466537Z" + } + }, + "CommentOnly": { + "summary": "Plain note", + "value": { + "user": "Unknown", + "new_status": null, + "comment": "Note without status.", + "created_at": "2026-06-12T00:18:44.504157Z" + } + } + } + } + } + }, + "400": { + "description": "Validation error, returned as a per-field map of messages. The usual cause is a `new_status` value that is not one of the ten allowed statuses — the check is case-sensitive.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "new_status": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "\"approved\" is not a valid choice." + ] + } + } + }, + "examples": { + "InvalidStatusChoice": { + "summary": "Lowercase / unknown status value", + "value": { + "new_status": [ + "\"approved\" is not a valid choice." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, malformed, expired, or revoked `x-api-key`. Authentication is checked before the session lookup, so a missing key returns `403` even when the session UUID does not exist.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You do not have permission to perform this action." + } + } + }, + "examples": { + "InvalidKey": { + "summary": "Missing or invalid x-api-key header", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No User Verification (KYC) session with this UUID is visible to your application: the UUID does not exist, the session was deleted, or it belongs to a different application. Business Verification (KYB) session IDs also return `404` here — the reviews feed is only addressable for KYC sessions.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Session not found." + } + } + }, + "examples": { + "NotFound": { + "summary": "Unknown, deleted, or cross-application session UUID", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Inspect `Retry-After` and `X-RateLimit-*` response headers. See [Rate limiting](/integration/rate-limiting)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "# Records an audit-trail note only -- the session's status does NOT change.\ncurl -s -X POST https://verification.didit.me/v3/sessions/8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d/reviews/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"new_status\": \"In Review\",\n \"comment\": \"Flagging for manual document re-check.\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.reviews.create(\n \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\",\n new_status=\"In Review\",\n comment=\"Flagging for manual document re-check.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.reviews.create(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\", {\n new_status: \"In Review\",\n comment: \"Flagging for manual document re-check.\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Reviews.Create(ctx, \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\", didit.SessionsReviewsCreateParams{\n\tNewStatus: didit.Some(didit.SessionsReviewsCreateParamsNewStatus(\"In Review\")),\n\tComment: didit.String(\"Flagging for manual document re-check.\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsReviewsCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().reviews().create(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\", SessionsReviewsCreateParams.builder()\n .newStatus(SessionsReviewsCreateParams.NewStatus.of(\"In Review\"))\n .comment(\"Flagging for manual document re-check.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->reviews->create('8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d', [\n 'new_status' => 'In Review',\n 'comment' => 'Flagging for manual document re-check.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.reviews.create(\n \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\",\n new_status: \"In Review\",\n comment: \"Flagging for manual document re-check.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Reviews.CreateAsync(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\", new SessionsReviewsCreateParams\n{\n NewStatus = new SessionsReviewsCreateParamsNewStatus(\"In Review\"),\n Comment = \"Flagging for manual document re-check.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/billing/balance/": { + "get": { + "summary": "Get credit balance", + "description": "Get current pre-paid credit balance in USD (organization-wide) plus auto-refill config. Amounts are decimal strings — parse with a decimal library.", + "operationId": "get_billing_balance", + "x-didit-sdk": { + "resource": "billing", + "method": "retrieve_balance", + "auth": "api_key", + "server": true + }, + "tags": [ + "Billing" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/billing/balance/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.billing.retrieve_balance()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.billing.retrieveBalance();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Billing.RetrieveBalance(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.billing().retrieveBalance();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "billing->retrieveBalance();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.billing.retrieve_balance\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Billing.RetrieveBalanceAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Current credit balance plus the organization's auto-refill configuration.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "balance", + "auto_refill_enabled" + ], + "properties": { + "balance": { + "type": "string", + "description": "Current pre-paid credit balance in USD, as a fixed-point decimal string. Decreases as verifications are billed and increases when a top-up settles." + }, + "auto_refill_enabled": { + "type": "boolean", + "description": "When `true`, Didit will automatically charge the saved payment method to add credits once the balance crosses `auto_refill_threshold`. Configured from Console → Billing." + }, + "auto_refill_amount": { + "type": "string", + "nullable": true, + "description": "USD amount that will be charged and added to the balance on each auto-refill, at face value. Set from \\$50 to \\$20,000 in Console → Billing. `null` when auto-refill is disabled or unset." + }, + "auto_refill_threshold": { + "type": "string", + "nullable": true, + "description": "Trigger threshold in USD. Auto-refill fires when `balance` falls below this value. `null` when auto-refill is disabled or unset." + } + } + }, + "examples": { + "Healthy balance with auto-refill": { + "value": { + "balance": "1234.5600", + "auto_refill_enabled": true, + "auto_refill_amount": "500.00", + "auto_refill_threshold": "100.00" + } + }, + "Low balance, manual top-up only": { + "value": { + "balance": "12.0000", + "auto_refill_enabled": false, + "auto_refill_amount": null, + "auto_refill_threshold": null + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read billing data for this organization. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/billing/top-up/": { + "post": { + "summary": "Top up credits", + "description": "Add credits via a hosted Stripe Checkout session (**\\$50 to \\$20,000** per top-up). Top-ups are available on the Free, Growth and Scale plans, added at face value with no discount or bonus, and last 12 months; Enterprise annual contracts cannot top up. Redirect the customer to the returned `checkout_session_url`.", + "operationId": "billing_top_up", + "x-didit-sdk": { + "resource": "billing", + "method": "top_up", + "auth": "api_key", + "server": true + }, + "tags": [ + "Billing" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "amount_in_dollars" + ], + "properties": { + "amount_in_dollars": { + "type": "number", + "minimum": 50, + "maximum": 20000, + "description": "Amount to charge, in USD. **Must be from 50 to 20,000.** The balance receives the same amount, with no discount or bonus. Tax is added automatically at checkout based on the customer's billing address." + }, + "success_url": { + "type": "string", + "format": "uri", + "description": "URL the hosted checkout redirects the customer to after a successful payment. Currently defaults to `https://console.didit.me`. Pass a working URL explicitly; the default host is unavailable. Append the literal placeholder `{CHECKOUT_SESSION_ID}` if you want the hosted checkout to substitute the session ID." + }, + "cancel_url": { + "type": "string", + "format": "uri", + "description": "URL the hosted checkout redirects the customer to when they abort or close the Checkout window. Currently defaults to `https://console.didit.me`. Pass a working URL explicitly; the default host is unavailable." + } + } + }, + "examples": { + "Minimum top-up": { + "value": { + "amount_in_dollars": 50 + } + }, + "Custom amount with redirects": { + "value": { + "amount_in_dollars": 500, + "success_url": "https://yourapp.com/billing/top-up/success", + "cancel_url": "https://yourapp.com/billing/top-up/cancel" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/billing/top-up/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"amount_in_dollars\": 500,\n \"success_url\": \"https://yourapp.com/billing/success\",\n \"cancel_url\": \"https://yourapp.com/billing/cancel\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.billing.top_up(\n amount_in_dollars=50.0,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.billing.topUp({\n amount_in_dollars: 50.0,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Billing.TopUp(ctx, didit.BillingTopUpParams{\n\tAmountInDollars: 50.0,\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BillingTopUpParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.billing().topUp(BillingTopUpParams.builder()\n .amountInDollars(50.0)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "billing->topUp([\n 'amount_in_dollars' => 50.0,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.billing.top_up(amount_in_dollars: 50.0)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Billing.TopUpAsync(new BillingTopUpParams\n{\n AmountInDollars = 50.0,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Hosted checkout session created. Redirect the user to `checkout_session_url` to complete payment.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "checkout_session_id", + "checkout_session_url" + ], + "properties": { + "checkout_session_id": { + "type": "string", + "description": "Hosted checkout session identifier (e.g. `cs_test_a1B2c3...`). Useful for server-side reconciliation." + }, + "checkout_session_url": { + "type": "string", + "format": "uri", + "description": "Single-use hosted payment page URL. Redirect the customer here to complete payment." + } + } + }, + "examples": { + "Created": { + "value": { + "checkout_session_id": "cs_test_a1B2c3D4e5F6g7H8i9J0", + "checkout_session_url": "https://checkout.example.com/c/pay/cs_test_a1B2c3D4e5F6g7H8i9J0" + } + } + } + } + } + }, + "400": { + "description": "Invalid payload: the amount is missing, not a number, below the \\$50 minimum or above the \\$20,000 maximum, or the organization is on an Enterprise annual contract. The error body is a JSON array.", + "content": { + "application/json": { + "examples": { + "Missing amount": { + "value": [ + "amount_in_dollars is required." + ] + }, + "Not a number": { + "value": [ + "amount_in_dollars must be a number." + ] + }, + "Below minimum": { + "value": [ + "Amount must be at least $50." + ] + }, + "Above maximum": { + "value": [ + "Amount must be at most $20,000." + ] + }, + "Annual plan": { + "value": [ + "You have an annual plan. Contact your account manager to top up." + ] + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot create top-up sessions for this organization. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/": { + "get": { + "summary": "List webhook destinations", + "description": "List all webhook destinations (unpaginated array, oldest first). Each item carries the destination's configuration, its delivery metrics for the last 30 days (`metrics_window_days`) with one `daily_points` entry per day, and its health (`health_status`, `failing_streak`, `failing_since`, `last_success_at`). The list has no `summary` object and never includes the signing secret: each item shows only `secret_last4`. Fetch a single destination (`GET /v3/webhook/destinations/{destination_uuid}/`) to read `secret_shared_key`.", + "operationId": "list_webhook_destinations", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.list()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.list();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.List(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().list();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->list();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.webhooks.destinations.list.each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.ListAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "List of webhook destinations (unpaginated, oldest first).", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Stable destination identifier; use as `{destination_uuid}` for update/delete." + }, + "label": { + "type": "string", + "description": "Human-readable name shown in the Console." + }, + "url": { + "type": "string", + "format": "uri", + "description": "Destination URL that receives `POST` requests with the webhook payload." + }, + "enabled": { + "type": "boolean", + "description": "When `false`, Didit silently skips delivery to this destination." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload schema version sent to this destination. `v3` is the current/recommended format." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + }, + "subscribed_events_count": { + "type": "integer", + "description": "Cached length of `subscribed_events`, useful for UI rendering." + }, + "deliveries_count": { + "type": "integer", + "description": "Delivery attempts (retries included) made to this destination in the last 30 days." + }, + "failed_deliveries_count": { + "type": "integer", + "description": "Delivery attempts in the last 30 days that the destination answered with HTTP 400 or above, or did not answer (timeout, refused connection or blocked address)." + }, + "average_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Average response time in milliseconds over the last 30 days, rounded to an integer. `null` when there were no deliveries in that window." + }, + "error_rate_percentage": { + "type": "integer", + "description": "`round(failed_deliveries_count / deliveries_count * 100)` over the last 30 days; `0` when there are no deliveries." + }, + "last_delivery_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent delivery attempt (success or failure). `null` if never delivered." + }, + "metrics_window_days": { + "type": "integer", + "description": "Length in days of the window that `deliveries_count`, `failed_deliveries_count`, `average_response_time_ms`, `error_rate_percentage` and `daily_points` cover. Always `30`." + }, + "health_status": { + "type": "string", + "enum": [ + "healthy", + "degraded", + "failing" + ], + "description": "Delivery health. `failing`: 3 or more consecutive events failed their first delivery attempt since the destination last answered successfully (see `failing_streak`). `degraded`: not failing, but a delivery attempt failed in the last 24 hours and no later attempt has delivered that event; a retry or resend that delivers it clears this. `healthy`: neither. Independent of `enabled`. A failed attempt is one answered with HTTP 400 or above, or not answered at all." + }, + "failing_streak": { + "type": "integer", + "description": "How many consecutive events failed their first delivery attempt since the destination last answered successfully, within the last 30 days. Retries are not counted, and any successful attempt, including a retry or a resend, ends the streak. `0` when no event has failed since the last success." + }, + "failing_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the first failed attempt of the current `failing_streak` was made. `null` when `failing_streak` is `0`." + }, + "last_success_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the destination last answered a delivery with an HTTP status under 400, within the last 30 days. `null` if it has not in that window." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the destination." + }, + "daily_points": { + "type": "array", + "description": "One entry per UTC day of the last 30 days, oldest first, including days with no deliveries. Counts every attempt, retries included.", + "items": { + "type": "object", + "properties": { + "date": { + "type": "string", + "format": "date", + "description": "The UTC day." + }, + "total_deliveries": { + "type": "integer", + "description": "Delivery attempts made that day." + }, + "failed_deliveries": { + "type": "integer", + "description": "Attempts made that day that were answered with HTTP 400 or above, or not answered at all." + } + } + } + }, + "secret_last4": { + "type": "string", + "description": "Last four characters of the destination's current signing secret, so you can tell secrets apart. Not enough to verify a signature." + }, + "created_by_email": { + "type": "string", + "nullable": true, + "description": "Email of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_by_name": { + "type": "string", + "nullable": true, + "description": "Name of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + }, + "examples": { + "Two destinations": { + "value": [ + { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "subscribed_events_count": 2, + "deliveries_count": 12453, + "failed_deliveries_count": 14, + "average_response_time_ms": 187, + "error_rate_percentage": 0, + "last_delivery_at": "2026-05-17T09:21:18Z", + "metrics_window_days": 30, + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": "2026-05-17T09:21:18Z", + "environment": "live", + "daily_points": [ + { + "date": "2026-04-18", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-19", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-20", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-21", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-22", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-23", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-24", + "total_deliveries": 415, + "failed_deliveries": 9 + }, + { + "date": "2026-04-25", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-26", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-27", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-28", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-29", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-30", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-01", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-02", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-03", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-04", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-05", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-06", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-07", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-08", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-09", + "total_deliveries": 415, + "failed_deliveries": 5 + }, + { + "date": "2026-05-10", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-11", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-12", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-13", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-14", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-15", + "total_deliveries": 416, + "failed_deliveries": 0 + }, + { + "date": "2026-05-16", + "total_deliveries": 416, + "failed_deliveries": 0 + }, + { + "date": "2026-05-17", + "total_deliveries": 416, + "failed_deliveries": 0 + } + ], + "secret_last4": "0xnQ", + "created_by_email": null, + "created_by_name": null, + "created_at": "2026-01-04T08:00:00Z", + "updated_at": "2026-04-22T14:09:11Z" + }, + { + "uuid": "22222222-3333-4444-5555-666666666666", + "label": "Staging mirror", + "url": "https://staging.yourapp.com/webhooks/didit", + "enabled": false, + "webhook_version": "v3", + "subscribed_events": [ + "status.updated" + ], + "subscribed_events_count": 1, + "deliveries_count": 0, + "failed_deliveries_count": 0, + "average_response_time_ms": null, + "error_rate_percentage": 0, + "last_delivery_at": null, + "metrics_window_days": 30, + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": null, + "environment": "live", + "daily_points": [ + { + "date": "2026-04-18", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-19", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-20", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-21", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-22", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-23", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-24", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-25", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-26", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-27", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-28", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-29", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-30", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-01", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-02", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-03", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-04", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-05", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-06", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-07", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-08", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-09", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-10", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-11", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-12", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-13", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-14", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-15", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-16", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-17", + "total_deliveries": 0, + "failed_deliveries": 0 + } + ], + "secret_last4": "k9Qe", + "created_by_email": "reviewer@example.com", + "created_by_name": "Example Reviewer", + "created_at": "2026-03-12T10:15:02Z", + "updated_at": "2026-03-12T10:15:02Z" + } + ] + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create webhook destination", + "description": "Create a webhook destination. The response includes the auto-generated `secret_shared_key` HMAC signing secret (also readable later via `GET /v3/webhook/destinations/{destination_uuid}/`). Always send `subscribed_events` with at least one event (no wildcards): an explicit empty list is rejected with 400, and omitting the field creates a destination subscribed to nothing, which never receives a webhook. The `(application, url)` pair must be unique among the application's destinations.", + "operationId": "create_webhook_destination", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/webhook/destinations/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"label\": \"Production session webhooks\",\n \"url\": \"https://yourapp.com/webhooks/didit\",\n \"webhook_version\": \"v3\",\n \"subscribed_events\": [\"status.updated\", \"data.updated\"]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.create(\n label=\"Production session webhooks\",\n url=\"https://yourapp.com/webhooks/didit\",\n webhook_version=\"v3\",\n subscribed_events=[\"status.updated\", \"data.updated\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.create({\n label: \"Production session webhooks\",\n url: \"https://yourapp.com/webhooks/didit\",\n webhook_version: \"v3\",\n subscribed_events: [\"status.updated\", \"data.updated\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.Create(ctx, didit.WebhooksDestinationsCreateParams{\n\tLabel: \"Production session webhooks\",\n\tURL: \"https://yourapp.com/webhooks/didit\",\n\tWebhookVersion: didit.Some(didit.WebhooksDestinationsCreateParamsWebhookVersion(\"v3\")),\n\tSubscribedEvents: didit.Some([]didit.WebhookEvent{didit.WebhookEvent(\"status.updated\"), didit.WebhookEvent(\"data.updated\")}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.WebhookEvent;\nimport me.didit.types.WebhooksDestinationsCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().create(WebhooksDestinationsCreateParams.builder()\n .label(\"Production session webhooks\")\n .url(\"https://yourapp.com/webhooks/didit\")\n .webhookVersion(WebhooksDestinationsCreateParams.WebhookVersion.of(\"v3\"))\n .subscribedEvents(List.of(WebhookEvent.of(\"status.updated\"), WebhookEvent.of(\"data.updated\")))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->create([\n 'label' => 'Production session webhooks',\n 'url' => 'https://yourapp.com/webhooks/didit',\n 'webhook_version' => 'v3',\n 'subscribed_events' => ['status.updated', 'data.updated'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.destinations.create(\n label: \"Production session webhooks\",\n url: \"https://yourapp.com/webhooks/didit\",\n webhook_version: \"v3\",\n subscribed_events: [\"status.updated\", \"data.updated\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.CreateAsync(new WebhooksDestinationsCreateParams\n{\n Label = \"Production session webhooks\",\n Url = \"https://yourapp.com/webhooks/didit\",\n WebhookVersion = new WebhooksDestinationsCreateParamsWebhookVersion(\"v3\"),\n SubscribedEvents = new WebhookEvent[] { \"status.updated\", \"data.updated\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "label", + "url" + ], + "properties": { + "label": { + "type": "string", + "maxLength": 255, + "description": "Human-readable name displayed in Console → Webhooks. Helps distinguish destinations when you have several (e.g. \"Prod\", \"Staging mirror\")." + }, + "url": { + "type": "string", + "format": "uri", + "description": "HTTPS URL that will receive `POST` requests. Must be unique among the application's destinations. Use a stable, hardened endpoint: Didit retries failed deliveries on a backoff schedule.", + "maxLength": 2000 + }, + "enabled": { + "type": "boolean", + "default": true, + "description": "When `false`, the destination is saved but Didit will not push any webhooks to it. Useful for staging configs you want to keep parked." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload schema version Didit will send. `v3` is the current/recommended format; older versions are kept for backward compatibility with legacy integrations." + }, + "subscribed_events": { + "allOf": [ + { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + } + ], + "description": "Events to deliver. Effectively required: an explicit `[]` is rejected with 400, and omitting the field creates a destination with no subscriptions that receives nothing." + } + } + }, + "examples": { + "session_updates": { + "summary": "Session status and data updates", + "value": { + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "webhook_version": "v3", + "subscribed_events": [ + "status.updated", + "data.updated" + ] + } + }, + "transaction_updates": { + "summary": "Transaction monitoring updates", + "value": { + "label": "Transaction monitoring webhooks", + "url": "https://yourapp.com/webhooks/didit-transactions", + "webhook_version": "v3", + "subscribed_events": [ + "transaction.created", + "transaction.status.updated" + ] + } + }, + "entity_updates": { + "summary": "User and Business entity updates", + "value": { + "label": "Entity lifecycle webhooks", + "url": "https://yourapp.com/webhooks/didit-entities", + "webhook_version": "v3", + "subscribed_events": [ + "user.status.updated", + "user.data.updated", + "business.status.updated", + "business.data.updated", + "activity.created" + ] + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Destination created. `secret_shared_key` is the HMAC signing secret — store it now.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Stable destination identifier. Use as `{destination_uuid}` in subsequent calls." + }, + "label": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "enabled": { + "type": "boolean" + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ] + }, + "secret_shared_key": { + "type": "string", + "description": "Auto-generated HMAC signing secret bound to this destination (43-character URL-safe token). Use it to verify the signature headers on incoming webhooks. Always returned in full to API-key callers; Console users need the `read:webhooks` permission, otherwise they see an empty string." + }, + "secret_last4": { + "type": "string", + "description": "Last four characters of the destination's current signing secret, so you can tell secrets apart. Not enough to verify a signature." + }, + "previous_secret_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set for 24 hours after the destination's signing secret is rotated in the Business Console. Until this time, every delivery to the destination also carries `X-Signature-Previous`, `X-Signature-V2-Previous` and `X-Signature-Simple-Previous`, made with the replaced secret (see [Signature verification](/integration/webhooks#signature-verification)). `null` when no rotation happened in the last 24 hours." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + }, + "created_by_email": { + "type": "string", + "nullable": true, + "description": "Email of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_by_name": { + "type": "string", + "nullable": true, + "description": "Name of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "health_status": { + "type": "string", + "enum": [ + "healthy", + "degraded", + "failing" + ], + "description": "Delivery health. `failing`: 3 or more consecutive events failed their first delivery attempt since the destination last answered successfully (see `failing_streak`). `degraded`: not failing, but a delivery attempt failed in the last 24 hours and no later attempt has delivered that event; a retry or resend that delivers it clears this. `healthy`: neither. Independent of `enabled`. A failed attempt is one answered with HTTP 400 or above, or not answered at all." + }, + "failing_streak": { + "type": "integer", + "description": "How many consecutive events failed their first delivery attempt since the destination last answered successfully, within the last 30 days. Retries are not counted, and any successful attempt, including a retry or a resend, ends the streak. `0` when no event has failed since the last success." + }, + "failing_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the first failed attempt of the current `failing_streak` was made. `null` when `failing_streak` is `0`." + }, + "last_success_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the destination last answered a delivery with an HTTP status under 400, within the last 30 days. `null` if it has not in that window." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the destination." + }, + "summary": { + "type": "object", + "description": "Delivery metrics for the last `metrics_window_days` (30) days. `last_delivery_at` is the most recent attempt, whenever it was made.", + "properties": { + "total_deliveries": { + "type": "integer" + }, + "failed_deliveries": { + "type": "integer" + }, + "error_rate_percentage": { + "type": "integer" + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true + }, + "last_delivery_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "metrics_window_days": { + "type": "integer", + "description": "Length in days of the window the other summary metrics cover. Always `30`." + } + } + } + } + }, + "examples": { + "Created": { + "value": { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "secret_shared_key": "tno2NTeRC1ZK3sBOYlBJDyEzBVENl3pQ1AcZyAW0xnQ", + "secret_last4": "0xnQ", + "previous_secret_expires_at": null, + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_by_email": null, + "created_by_name": null, + "created_at": "2026-05-17T10:00:00Z", + "updated_at": "2026-05-17T10:00:00Z", + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": null, + "environment": "live", + "summary": { + "total_deliveries": 0, + "failed_deliveries": 0, + "error_rate_percentage": 0, + "min_response_time_ms": null, + "avg_response_time_ms": null, + "max_response_time_ms": null, + "last_delivery_at": null, + "metrics_window_days": 30 + } + } + } + } + } + } + }, + "400": { + "description": "Validation failed — usually a duplicate URL, missing/invalid `subscribed_events`, or unknown event name.", + "content": { + "application/json": { + "examples": { + "Duplicate URL": { + "value": { + "url": [ + "This webhook destination already exists for the application." + ] + } + }, + "Empty events list": { + "value": { + "subscribed_events": [ + "At least one subscribed event is required." + ] + } + }, + "Unknown event": { + "value": { + "subscribed_events": [ + "Unsupported webhook events: foo.bar" + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/": { + "get": { + "summary": "Get webhook destination", + "description": "Retrieve one webhook destination with its `secret_shared_key` signing secret, its health (`health_status`, `failing_streak`, `failing_since`, `last_success_at`) and a delivery `summary` for the last 30 days. API-key callers always receive the secret in full; Console users without `read:webhooks` see an empty string.", + "operationId": "get_webhook_destination", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination (the `uuid` returned from create / list).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.destinations.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Webhook destination details.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "enabled": { + "type": "boolean" + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ] + }, + "secret_shared_key": { + "type": "string", + "description": "HMAC signing secret (43-character URL-safe token). Always returned in full to API-key callers; Console users need `read:webhooks`, otherwise an empty string." + }, + "secret_last4": { + "type": "string", + "description": "Last four characters of the destination's current signing secret, so you can tell secrets apart. Not enough to verify a signature." + }, + "previous_secret_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set for 24 hours after the destination's signing secret is rotated in the Business Console. Until this time, every delivery to the destination also carries `X-Signature-Previous`, `X-Signature-V2-Previous` and `X-Signature-Simple-Previous`, made with the replaced secret (see [Signature verification](/integration/webhooks#signature-verification)). `null` when no rotation happened in the last 24 hours." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + }, + "created_by_email": { + "type": "string", + "nullable": true, + "description": "Email of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_by_name": { + "type": "string", + "nullable": true, + "description": "Name of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "health_status": { + "type": "string", + "enum": [ + "healthy", + "degraded", + "failing" + ], + "description": "Delivery health. `failing`: 3 or more consecutive events failed their first delivery attempt since the destination last answered successfully (see `failing_streak`). `degraded`: not failing, but a delivery attempt failed in the last 24 hours and no later attempt has delivered that event; a retry or resend that delivers it clears this. `healthy`: neither. Independent of `enabled`. A failed attempt is one answered with HTTP 400 or above, or not answered at all." + }, + "failing_streak": { + "type": "integer", + "description": "How many consecutive events failed their first delivery attempt since the destination last answered successfully, within the last 30 days. Retries are not counted, and any successful attempt, including a retry or a resend, ends the streak. `0` when no event has failed since the last success." + }, + "failing_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the first failed attempt of the current `failing_streak` was made. `null` when `failing_streak` is `0`." + }, + "last_success_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the destination last answered a delivery with an HTTP status under 400, within the last 30 days. `null` if it has not in that window." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the destination." + }, + "summary": { + "type": "object", + "description": "Delivery metrics for the last `metrics_window_days` (30) days. `last_delivery_at` is the most recent attempt, whenever it was made.", + "properties": { + "total_deliveries": { + "type": "integer" + }, + "failed_deliveries": { + "type": "integer" + }, + "error_rate_percentage": { + "type": "integer" + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true + }, + "last_delivery_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "metrics_window_days": { + "type": "integer", + "description": "Length in days of the window the other summary metrics cover. Always `30`." + } + } + } + } + }, + "examples": { + "Healthy destination": { + "value": { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "secret_shared_key": "tno2NTeRC1ZK3sBOYlBJDyEzBVENl3pQ1AcZyAW0xnQ", + "secret_last4": "0xnQ", + "previous_secret_expires_at": null, + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_by_email": null, + "created_by_name": null, + "created_at": "2026-01-04T08:00:00Z", + "updated_at": "2026-04-22T14:09:11Z", + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": "2026-05-17T09:21:18Z", + "environment": "live", + "summary": { + "total_deliveries": 12453, + "failed_deliveries": 14, + "error_rate_percentage": 0, + "min_response_time_ms": 41, + "avg_response_time_ms": 187, + "max_response_time_ms": 2103, + "last_delivery_at": "2026-05-17T09:21:18Z", + "metrics_window_days": 30 + } + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No destination with this UUID exists, or it belongs to a different application, or it has been deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update webhook destination", + "description": "Update a webhook destination. **`subscribed_events` is REPLACED wholesale, not merged**: fetch first, append, resend. Secret is not rotated. A `uuid` in the body is ignored: the path names the destination.", + "operationId": "update_webhook_destination", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination to update.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "label": { + "type": "string", + "description": "New human-readable name." + }, + "url": { + "type": "string", + "format": "uri", + "description": "New target URL. Must be unique within the application." + }, + "enabled": { + "type": "boolean", + "description": "Set to `false` to pause deliveries to this destination without deleting it." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "New payload schema version for future deliveries." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + } + } + }, + "examples": { + "Pause destination": { + "value": { + "enabled": false + } + }, + "Add transaction events": { + "value": { + "subscribed_events": [ + "status.updated", + "data.updated", + "transaction.created", + "transaction.status.updated" + ] + } + }, + "Move target URL": { + "value": { + "label": "Production session webhooks v2", + "url": "https://api.yourapp.com/webhooks/didit/v2" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"subscribed_events\": [\n \"status.updated\",\n \"data.updated\",\n \"transaction.created\"\n ]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.update(\n \"11111111-2222-3333-4444-555555555555\",\n enabled=False,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.update(\"11111111-2222-3333-4444-555555555555\", {\n enabled: false,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.WebhooksDestinationsUpdateParams{\n\tEnabled: didit.Bool(false),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.WebhooksDestinationsUpdateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().update(\"11111111-2222-3333-4444-555555555555\", WebhooksDestinationsUpdateParams.builder()\n .enabled(false)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->update('11111111-2222-3333-4444-555555555555', [\n 'enabled' => false,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.destinations.update(\n \"11111111-2222-3333-4444-555555555555\",\n enabled: false\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new WebhooksDestinationsUpdateParams\n{\n Enabled = false,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated webhook destination. Body has the same shape as `GET /v3/webhook/destinations/{destination_uuid}/`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "enabled": { + "type": "boolean" + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ] + }, + "secret_shared_key": { + "type": "string" + }, + "secret_last4": { + "type": "string", + "description": "Last four characters of the destination's current signing secret, so you can tell secrets apart. Not enough to verify a signature." + }, + "previous_secret_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set for 24 hours after the destination's signing secret is rotated in the Business Console. Until this time, every delivery to the destination also carries `X-Signature-Previous`, `X-Signature-V2-Previous` and `X-Signature-Simple-Previous`, made with the replaced secret (see [Signature verification](/integration/webhooks#signature-verification)). `null` when no rotation happened in the last 24 hours." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + }, + "created_by_email": { + "type": "string", + "nullable": true, + "description": "Email of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_by_name": { + "type": "string", + "nullable": true, + "description": "Name of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "health_status": { + "type": "string", + "enum": [ + "healthy", + "degraded", + "failing" + ], + "description": "Delivery health. `failing`: 3 or more consecutive events failed their first delivery attempt since the destination last answered successfully (see `failing_streak`). `degraded`: not failing, but a delivery attempt failed in the last 24 hours and no later attempt has delivered that event; a retry or resend that delivers it clears this. `healthy`: neither. Independent of `enabled`. A failed attempt is one answered with HTTP 400 or above, or not answered at all." + }, + "failing_streak": { + "type": "integer", + "description": "How many consecutive events failed their first delivery attempt since the destination last answered successfully, within the last 30 days. Retries are not counted, and any successful attempt, including a retry or a resend, ends the streak. `0` when no event has failed since the last success." + }, + "failing_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the first failed attempt of the current `failing_streak` was made. `null` when `failing_streak` is `0`." + }, + "last_success_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the destination last answered a delivery with an HTTP status under 400, within the last 30 days. `null` if it has not in that window." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the destination." + }, + "summary": { + "type": "object", + "properties": { + "total_deliveries": { + "type": "integer" + }, + "failed_deliveries": { + "type": "integer" + }, + "error_rate_percentage": { + "type": "integer" + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true + }, + "last_delivery_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "metrics_window_days": { + "type": "integer", + "description": "Length in days of the window the other summary metrics cover. Always `30`." + } + }, + "description": "Delivery metrics for the last `metrics_window_days` (30) days. `last_delivery_at` is the most recent attempt, whenever it was made." + } + } + }, + "examples": { + "Paused": { + "value": { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": false, + "webhook_version": "v3", + "secret_shared_key": "tno2NTeRC1ZK3sBOYlBJDyEzBVENl3pQ1AcZyAW0xnQ", + "secret_last4": "0xnQ", + "previous_secret_expires_at": null, + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_by_email": null, + "created_by_name": null, + "created_at": "2026-01-04T08:00:00Z", + "updated_at": "2026-05-17T11:02:55Z", + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": "2026-05-17T09:21:18Z", + "environment": "live", + "summary": { + "total_deliveries": 12453, + "failed_deliveries": 14, + "error_rate_percentage": 0, + "min_response_time_ms": 41, + "avg_response_time_ms": 187, + "max_response_time_ms": 2103, + "last_delivery_at": "2026-05-17T09:21:18Z", + "metrics_window_days": 30 + } + } + } + } + } + } + }, + "400": { + "description": "Validation failed (duplicate URL, empty/unknown events list, malformed body).", + "content": { + "application/json": { + "examples": { + "Duplicate URL": { + "value": { + "url": [ + "This webhook destination already exists for the application." + ] + } + }, + "Empty events": { + "value": { + "subscribed_events": [ + "At least one subscribed event is required." + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Destination with this UUID does not exist (or is deleted, or belongs to another application).", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete webhook destination", + "description": "Permanently delete a webhook destination, its signing secret and its entire delivery log. This cannot be undone. No new webhook is sent to the destination's URL; other destinations subscribed to the same events keep receiving them. Afterwards the destination no longer appears in `GET /v3/webhook/destinations/`, and every route under `/v3/webhook/destinations/{destination_uuid}/`, including its deliveries, a delivery's detail and its resend, answers 404. The application's legacy `webhook_url` is set to the URL of the oldest remaining enabled destination, or cleared when no enabled destination is left. Calling DELETE again returns 404.", + "operationId": "delete_webhook_destination", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination to delete.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.webhooks.destinations.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.webhooks.destinations.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Webhooks.Destinations.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.webhooks().destinations().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.webhooks.destinations.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Webhooks.Destinations.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ], + "responses": { + "204": { + "description": "Destination, signing secret and delivery log deleted permanently. No response body." + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No destination with this UUID exists for the application (already deleted or never existed).", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/wallet-screening/": { + "post": { + "summary": "Screen a wallet address", + "description": "Run on-demand AML screening for a single crypto wallet/address without creating a transaction. Resolves your application's configured blockchain analytics provider, screens the address synchronously, and returns the normalised risk result. LIGHTNING is available only with an Elliptic or Chainalysis BYOK connection added through Marketplace. Nothing is written to the transactions table. One AML monitoring usage is billed per successful screening (sandbox applications are not billed).", + "operationId": "screenWallet", + "x-didit-sdk": { + "resource": "wallet_screening", + "method": "screen", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "wallet_address" + ], + "properties": { + "wallet_address": { + "type": "string", + "description": "The crypto address or network identifier to screen. Must match the selected network (e.g. a `0x`-prefixed 40-hex-char address for EVM chains). Alias: `address`.", + "example": "0x94f1b9b64e2932f6a2db338f616844400cd58e8a" + }, + "blockchain": { + "type": "string", + "description": "Optional network identifier. When omitted, the network is read from the address format: `bc1`, `1` and `3` addresses are Bitcoin (BTC), `T` addresses are Tron (TRX) and base58 public keys are Solana (SOL). A `0x` address is valid on every EVM network, so omitting `blockchain` for it answers 422 with the candidate networks in `candidate_chains`. Managed screening supports BTC, ETH, LTC, XRP, BCH, DOGE, TRX, SOL, MATIC, BNB, BASE (Base), OP (Optimism), ARB (Arbitrum One), AVAX (Avalanche C-Chain), ADA (Cardano), XLM (Stellar) and HBAR (Hedera). USDT and USDC are legacy Ethereum aliases, not multi-network searches. Connected-provider coverage varies. LIGHTNING requires a supported connected provider. CELO (Celo), MODE (Mode) and PLUME (Plume) are screened through a connected TRM Labs account; managed screening answers 400 for them. Alias: `currency`.", + "enum": [ + "BTC", + "LIGHTNING", + "ETH", + "LTC", + "XRP", + "BCH", + "DOGE", + "TRX", + "SOL", + "MATIC", + "BNB", + "USDT", + "USDC", + "BASE", + "OP", + "ARB", + "AVAX", + "ADA", + "XLM", + "HBAR", + "CELO", + "MODE", + "PLUME" + ], + "example": "ETH" + }, + "direction": { + "type": "string", + "description": "Optional. Whether the address is being screened as an inbound (deposit) or outbound (withdrawal) counterparty. Accepts `inbound`, `outbound`, `deposit`, or `withdrawal`. Defaults to a neutral pre-transfer screen when omitted. This is passed to the provider as context only; it does not change the returned `risk_score`, `severity`, or the `source_of_funds`/`destination_of_funds` breakdowns, which are always both returned regardless of `direction`. Changing or omitting `direction` therefore does not require re-screening an address.", + "enum": [ + "inbound", + "outbound", + "deposit", + "withdrawal" + ] + } + } + }, + "example": { + "wallet_address": "0x94f1b9b64e2932f6a2db338f616844400cd58e8a", + "blockchain": "ETH" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/wallet-screening/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"wallet_address\": \"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\",\n \"blockchain\": \"ETH\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.wallet_screening.screen(\n wallet_address=\"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\",\n blockchain=\"ETH\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.walletScreening.screen({\n wallet_address: \"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\",\n blockchain: \"ETH\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.WalletScreening.Screen(ctx, didit.WalletScreeningScreenParams{\n\tWalletAddress: \"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\",\n\tBlockchain: didit.Some(didit.WalletScreeningScreenParamsBlockchain(\"ETH\")),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.WalletScreeningScreenParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.walletScreening().screen(WalletScreeningScreenParams.builder()\n .walletAddress(\"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\")\n .blockchain(WalletScreeningScreenParams.Blockchain.of(\"ETH\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "walletScreening->screen([\n 'wallet_address' => '0x94f1b9b64e2932f6a2db338f616844400cd58e8a',\n 'blockchain' => 'ETH',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.wallet_screening.screen(\n wallet_address: \"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\",\n blockchain: \"ETH\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.WalletScreening.ScreenAsync(new WalletScreeningScreenParams\n{\n WalletAddress = \"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\",\n Blockchain = new WalletScreeningScreenParamsBlockchain(\"ETH\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The normalised wallet screening result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletScreeningResult" + }, + "example": { + "provider": "merklescience", + "screening_type": "WALLET_SCREENING", + "risk_score": 95, + "severity": "CRITICAL", + "status": "SCREENED", + "summary": "Merkle Science risk: Critical (5/5). SANCTIONS HIT. Dominant risk: sanctioned. Driven by: the address is attributed to Sanctions / OFAC (Example Sanctions)", + "wallet_address": "0x94f1b9b64e2932f6a2db338f616844400cd58e8a", + "transaction_hash": null, + "blockchain": "ETH", + "block_timestamp": null, + "total_value_usd": null, + "fee_usd": null, + "sender_rows": [], + "receiver_rows": [], + "transaction_flows": [], + "transaction_flows_status": null, + "transaction_flows_error": null, + "source_of_funds": [ + { + "category": "sanctioned", + "percentage": 100.0, + "amount_usd": 9813695.0, + "entity_name": "Example Sanctions", + "entity_type": "Sanctions", + "entity_subtype": "OFAC", + "hops": null, + "description": "Sanctions / OFAC", + "is_direct": true, + "exposure_direction": "incoming", + "exposure_type": "direct", + "provider_entity_id": "118885", + "provider_entity_type_id": "30", + "provider_entity_subtype_id": "125", + "country": "", + "raw_amount": null, + "raw_currency": null + } + ], + "destination_of_funds": [ + { + "category": "defi", + "percentage": 84.824453, + "amount_usd": 7783414.38, + "entity_name": "Example DeFi 7", + "entity_type": "DeFi", + "entity_subtype": "Bridges", + "hops": null, + "description": "DeFi / Bridges", + "is_direct": false, + "exposure_direction": "outgoing", + "exposure_type": "indirect", + "provider_entity_id": "118459", + "provider_entity_type_id": "33", + "provider_entity_subtype_id": "143", + "country": "", + "raw_amount": null, + "raw_currency": null + }, + { + "category": "defi", + "percentage": 15.175547, + "amount_usd": 1392494.37, + "entity_name": "Example DeFi 2", + "entity_type": "DeFi", + "entity_subtype": "Yield Aggregator", + "hops": null, + "description": "DeFi / Yield Aggregator", + "is_direct": false, + "exposure_direction": "outgoing", + "exposure_type": "indirect", + "provider_entity_id": "90192", + "provider_entity_type_id": "33", + "provider_entity_subtype_id": "142", + "country": "", + "raw_amount": null, + "raw_currency": null + } + ], + "counterparty_connections": [ + { + "entity_name": "Example Sanctions", + "entity_type": "Sanctions", + "entity_subtype": "OFAC", + "risk_level": "critical", + "categories": [ + "sanctioned" + ], + "received_usd": null, + "sent_usd": null, + "received_hops": null, + "sent_hops": null, + "percentage": 0.0, + "is_direct": true, + "country": null, + "provider_entity_id": null + }, + { + "entity_name": "Example Sanctions", + "entity_type": "Sanctions", + "entity_subtype": "OFAC", + "risk_level": "critical", + "categories": [ + "sanctioned" + ], + "received_usd": null, + "sent_usd": null, + "received_hops": null, + "sent_hops": null, + "percentage": 0.0, + "is_direct": true, + "country": null, + "provider_entity_id": null + } + ], + "network_nodes": [], + "network_edges": [], + "sanctions_hit": true, + "pep_counterparty": null, + "dominant_risk_category": "sanctioned", + "risk_factors": [ + { + "source": "attribution", + "category": "sanctioned", + "label": "Sanctions / OFAC", + "entity_name": "Example Sanctions", + "direction": "attribution", + "exposure_type": "attribution", + "amount_usd": null, + "percentage": 0.0, + "is_high_risk": true, + "description": "the address is attributed to Sanctions / OFAC (Example Sanctions)" + }, + { + "source": "exposure", + "category": "sanctioned", + "label": "Sanctions / OFAC", + "entity_name": "Example Sanctions", + "direction": "incoming", + "exposure_type": "direct", + "amount_usd": 9813695.0, + "percentage": 51.679304, + "is_high_risk": true, + "description": "51.6793% direct incoming exposure to Sanctions / OFAC (Example Sanctions)" + }, + { + "source": "exposure", + "category": "defi", + "label": "DeFi / Bridges", + "entity_name": "Example DeFi 7", + "direction": "outgoing", + "exposure_type": "indirect", + "amount_usd": 7783414.38, + "percentage": 40.987766, + "is_high_risk": false, + "description": "40.9878% indirect outgoing exposure to DeFi / Bridges (Example DeFi 7)" + }, + { + "source": "exposure", + "category": "defi", + "label": "DeFi / Yield Aggregator", + "entity_name": "Example DeFi 2", + "direction": "outgoing", + "exposure_type": "indirect", + "amount_usd": 1392494.37, + "percentage": 7.33293, + "is_high_risk": false, + "description": "7.33293% indirect outgoing exposure to DeFi / Yield Aggregator (Example DeFi 2)" + } + ], + "raw_response": { + "...": "provider-specific details" + }, + "report_signature": "" + } + } + } + }, + "400": { + "description": "Validation error — malformed wallet address, missing `wallet_address`, unsupported `blockchain`, `blockchain` omitted for an address whose format names no network, or LIGHTNING without an Elliptic or Chainalysis BYOK Marketplace connection." + }, + "401": { + "description": "Missing or invalid `x-api-key`." + }, + "409": { + "description": "No AML provider is configured for the application, or the configured provider does not support wallet screening yet. Configure transaction monitoring first." + }, + "422": { + "description": "`blockchain` omitted for a `0x` address, which every EVM network shares. Nothing is screened or billed; resend with `blockchain` set to one of `candidate_chains`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "blockchain": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "A 0x address is valid on every EVM chain. Send blockchain as one of: ETH, BNB, MATIC, BASE, ARB, OP, CELO, MODE, PLUME." + ] + }, + "candidate_chains": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "ETH", + "BNB", + "MATIC", + "BASE", + "ARB", + "OP", + "CELO", + "MODE", + "PLUME" + ] + } + } + } + } + } + }, + "502": { + "description": "The AML provider could not complete the screening. Retry later." + } + } + } + }, + "/v3/wallet-screening/pdf/": { + "post": { + "summary": "Render a wallet-screening result as a PDF", + "description": "Stateless companion to `POST /v3/wallet-screening/`: repost the exact JSON body that endpoint returned — including the `report_signature` it added — and get back a Didit-branded PDF of the same result. No provider is called and nothing is billed or persisted; this only re-renders a result Didit already produced. The signature ties the PDF to an unmodified result: editing `risk_score`, `severity`, `sanctions_hit`, or any other signed field before reposting fails with `400`.", + "operationId": "screenWalletPdf", + "x-didit-sdk": { + "resource": "wallet_screening", + "method": "screen_pdf", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "description": "The unmodified JSON object returned by `POST /v3/wallet-screening/`, including `report_signature`.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/WalletScreeningResult" + } + ], + "required": [ + "report_signature" + ], + "description": "Same shape as the `POST /v3/wallet-screening/` response — pass that response body back verbatim, including `report_signature`." + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "# $RESULT is the saved JSON body from POST /v3/wallet-screening/\ncurl -X POST 'https://verification.didit.me/v3/wallet-screening/pdf/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d \"$RESULT\" \\\n -o wallet_screening_report.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.wallet_screening.screen_pdf(\n provider=\"your-provider\",\n screening_type=\"WALLET_SCREENING\",\n risk_score=1,\n severity=\"UNKNOWN\",\n status=\"SCREENED\",\n summary=\"your-summary\",\n wallet_address=\"your-wallet-address\",\n blockchain=\"your-blockchain\",\n source_of_funds=[{}],\n destination_of_funds=[{}],\n counterparty_connections=[{}],\n sanctions_hit=True,\n dominant_risk_category=\"your-dominant-risk-category\",\n report_signature=\"your-report-signature\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.walletScreening.screenPdf({\n provider: \"your-provider\",\n screening_type: \"WALLET_SCREENING\",\n risk_score: 1,\n severity: \"UNKNOWN\",\n status: \"SCREENED\",\n summary: \"your-summary\",\n wallet_address: \"your-wallet-address\",\n blockchain: \"your-blockchain\",\n source_of_funds: [{}],\n destination_of_funds: [{}],\n counterparty_connections: [{}],\n sanctions_hit: true,\n dominant_risk_category: \"your-dominant-risk-category\",\n report_signature: \"your-report-signature\",\n});\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.WalletScreening.ScreenPDF(ctx, didit.WalletScreeningResultParam{\n\tProvider: \"your-provider\",\n\tScreeningType: \"WALLET_SCREENING\",\n\tRiskScore: 1,\n\tSeverity: \"UNKNOWN\",\n\tStatus: \"SCREENED\",\n\tSummary: \"your-summary\",\n\tWalletAddress: \"your-wallet-address\",\n\tBlockchain: \"your-blockchain\",\n\tSourceOfFunds: []didit.WalletScreeningResultParamSourceOfFunds{{}},\n\tDestinationOfFunds: []didit.WalletScreeningResultParamDestinationOfFunds{{}},\n\tCounterpartyConnections: []didit.WalletScreeningResultParamCounterpartyConnections{{}},\n\tSanctionsHit: true,\n\tDominantRiskCategory: didit.String(\"your-dominant-risk-category\"),\n\tReportSignature: \"your-report-signature\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.WalletScreeningResult;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.walletScreening().screenPdf(WalletScreeningResult.builder()\n .provider(\"your-provider\")\n .screeningType(WalletScreeningResult.ScreeningType.of(\"WALLET_SCREENING\"))\n .riskScore(1L)\n .severity(WalletScreeningResult.Severity.of(\"UNKNOWN\"))\n .status(WalletScreeningResult.Status.of(\"SCREENED\"))\n .summary(\"your-summary\")\n .walletAddress(\"your-wallet-address\")\n .blockchain(\"your-blockchain\")\n .sourceOfFunds(List.of(WalletScreeningResult.SourceOfFunds.builder()\n .build()))\n .destinationOfFunds(List.of(WalletScreeningResult.DestinationOfFunds.builder()\n .build()))\n .counterpartyConnections(List.of(WalletScreeningResult.CounterpartyConnections.builder()\n .build()))\n .sanctionsHit(true)\n .dominantRiskCategory(\"your-dominant-risk-category\")\n .reportSignature(\"your-report-signature\")\n .build())\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "walletScreening->screenPdf([\n 'provider' => 'your-provider',\n 'screening_type' => 'WALLET_SCREENING',\n 'risk_score' => 1,\n 'severity' => 'UNKNOWN',\n 'status' => 'SCREENED',\n 'summary' => 'your-summary',\n 'wallet_address' => 'your-wallet-address',\n 'blockchain' => 'your-blockchain',\n 'source_of_funds' => [[]],\n 'destination_of_funds' => [[]],\n 'counterparty_connections' => [[]],\n 'sanctions_hit' => true,\n 'dominant_risk_category' => 'your-dominant-risk-category',\n 'report_signature' => 'your-report-signature',\n]);\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.wallet_screening.screen_pdf(\n provider: \"your-provider\",\n screening_type: \"WALLET_SCREENING\",\n risk_score: 1,\n severity: \"UNKNOWN\",\n status: \"SCREENED\",\n summary: \"your-summary\",\n wallet_address: \"your-wallet-address\",\n blockchain: \"your-blockchain\",\n source_of_funds: [{}],\n destination_of_funds: [{}],\n counterparty_connections: [{}],\n sanctions_hit: true,\n dominant_risk_category: \"your-dominant-risk-category\",\n report_signature: \"your-report-signature\"\n)\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.WalletScreening.ScreenPdfAsync(new WalletScreeningResult\n{\n Provider = \"your-provider\",\n ScreeningType = \"WALLET_SCREENING\",\n RiskScore = 1,\n Severity = \"UNKNOWN\",\n Status = \"SCREENED\",\n Summary = \"your-summary\",\n WalletAddress = \"your-wallet-address\",\n Blockchain = \"your-blockchain\",\n SourceOfFunds = new WalletScreeningResultSourceOfFunds[] { new() },\n DestinationOfFunds = new WalletScreeningResultDestinationOfFunds[] { new() },\n CounterpartyConnections = new WalletScreeningResultCounterpartyConnections[] { new() },\n SanctionsHit = true,\n DominantRiskCategory = \"your-dominant-risk-category\",\n ReportSignature = \"your-report-signature\",\n});\nawait content.SaveToAsync(\"download.pdf\");" + } + ], + "responses": { + "200": { + "description": "The rendered PDF document, returned directly as binary `application/pdf` — there is no JSON wrapper.", + "headers": { + "Content-Disposition": { + "description": "`attachment; filename=wallet_screening_{address}.pdf`, derived from the `wallet_address` (or `transaction_hash`) in the reposted result.", + "schema": { + "type": "string", + "example": "attachment; filename=wallet_screening_0x28c6c06298d514db0899.pdf" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "400": { + "description": "The body is not a JSON object matching the wallet-screening result shape, or `report_signature` is missing or does not match the reposted fields (edited or truncated before repost).", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "examples": { + "Not an object": { + "value": { + "non_field_errors": [ + "Expected a JSON object matching a wallet-screening result." + ] + } + }, + "Missing or invalid signature": { + "value": { + "report_signature": [ + "This wallet-screening result is missing or fails its report signature - repost the exact JSON returned by the wallet-screening endpoint, unmodified." + ] + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid `x-api-key`." + } + } + } + }, + "/v3/transactions/": { + "get": { + "summary": "List transactions", + "description": "Paginated list of monitored transactions, newest first (`txn_date` desc). `count` is capped at 100; use `total_transactions` for the true row count. This endpoint does not support filtering or search parameters.", + "operationId": "listTransactions", + "x-didit-sdk": { + "resource": "transactions", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "description": "Page size. Defaults to 50 when omitted. There is no enforced maximum, but keep pages small for latency." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "description": "Zero-based offset of the first record to return. Prefer following the `next` URL from the previous page." + }, + { + "name": "travel_rule_status", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Return only transactions whose Travel Rule transfer is in this exact status (for example `ON_HOLD` or `AWAITING_COUNTERPARTY`). `count` follows this filter; `total_transactions` still counts every transaction in the application." + }, + { + "name": "assignee", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated; a transaction matches when it meets any value. `unassigned` is what nobody holds yet, `rules` is what the rules decided on their own without a reviewer, and a member email matches the reviewer assigned to it. `me` is the signed-in Console reviewer, so an API key sending `me` gets `400`." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.transactions.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.transactions.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Transactions.ListAutoPaging(ctx, didit.TransactionsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.transactions().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Transactions.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated list of transaction summaries, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Capped count of matching transactions (exact up to 100, capped thereafter). Use `total_transactions` for the true count when paginating." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "total_transactions": { + "type": "integer", + "description": "Exact total count of (non-deleted) transactions for the application. Present on paginated responses." + }, + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Stable Didit transaction identifier. Use as `{transaction_id}` in detail/notes/related calls." + }, + "transaction_number": { + "type": "integer", + "description": "Application-scoped sequential transaction number shown in Console (e.g. `4123`)." + }, + "txn_id": { + "type": "string", + "description": "Customer-provided identifier from the create call (max 128 chars). Unique per application." + }, + "transaction_type": { + "type": "string", + "description": "Top-level category as stored: `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet`, `gamblingLimitChange`, `gamblingBonusChange`, `auditTrailEvent`. Note multi-word values are echoed back in camelCase even when submitted in snake_case." + }, + "action_type": { + "type": "string", + "description": "Sub-type within the category (e.g. `deposit`, `withdrawal`, `transfer`)." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Current monitoring verdict." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject. Stored uppercase regardless of the casing submitted." + }, + "amount": { + "type": "string", + "description": "Transaction amount as a decimal string with trailing zeros stripped (e.g. `\"1500\"`, `\"0.5\"`, `\"0.123456789012345678\"`). Up to 18 decimal places." + }, + "currency": { + "type": "string", + "description": "Currency code of `amount`. Any ISO 4217 alphabetic code is accepted (e.g. `EUR`, `USD`, `NGN`), as is a crypto asset symbol (e.g. `BTC`). Prefer a currently-circulating code. Not every accepted code has an exchange rate: when none is available for the code (for example the ISO accounting unit `XSU`, or a crypto symbol Didit does not price), automatic conversion fails and `preferred_currency_amount` is returned null unless the request supplied a pre-converted `amount_in_default_currency`." + }, + "preferred_currency_amount": { + "type": "string", + "nullable": true, + "description": "`amount` converted to the application's preferred currency, when available." + }, + "preferred_currency_code": { + "type": "string", + "nullable": true, + "description": "Preferred currency code used for `preferred_currency_amount` - the application's base currency, any currently-circulating ISO 4217 code. A code with no available exchange rate leaves `preferred_currency_amount` null unless the request supplied a pre-converted `amount_in_default_currency`." + }, + "payment_details": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference or memo from the submission." + }, + "score": { + "type": "integer", + "description": "Risk score (typically 0–100). Higher = riskier." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ], + "description": "Categorical risk severity set by rules/providers (`UNKNOWN`, `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`). `null` until something sets it." + }, + "subject_name": { + "type": "string", + "nullable": true, + "description": "Full name of the `APPLICANT` party." + }, + "subject_vendor_data": { + "type": "string", + "nullable": true, + "description": "Your internal user/business identifier for the applicant." + }, + "subject_country": { + "type": "string", + "nullable": true, + "description": "Country code of the applicant, from the submitted `address.country` (typically ISO 3166-1 alpha-3)." + }, + "counterparty_name": { + "type": "string", + "nullable": true, + "description": "Full name of the `COUNTERPARTY` party, if any." + }, + "counterparty_vendor_data": { + "type": "string", + "nullable": true, + "description": "Your internal identifier for the counterparty." + }, + "counterparty_country": { + "type": "string", + "nullable": true, + "description": "Country code of the counterparty, from the submitted `address.country`." + }, + "tags": { + "type": "array", + "description": "Tags attached to the transaction (manually or by rules).", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ], + "description": "Whether the tag is a Didit preset or a custom tag." + } + } + } + }, + "txn_date": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred (from `transaction_at` at create time)." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When Didit recorded the submission." + }, + "decision_reason_code": { + "type": "string", + "nullable": true, + "description": "Machine-readable reason for the current `status` (e.g. `WALLET_HIGH_RISK`)." + }, + "decision_reason_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label for `decision_reason_code`." + }, + "assigned_to_name": { + "type": "string", + "nullable": true, + "description": "Display name of the Console reviewer assigned to this transaction." + }, + "travel_rule_status": { + "type": "string", + "nullable": true, + "description": "Status of the transaction's Travel Rule transfer (for example `COMPLETED`), or `null` when it has none." + }, + "travel_rule": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TransactionTravelRule" + } + ], + "description": "The transaction's Travel Rule transfer, the same block as `travel_rule` on `GET /v3/transactions/{transaction_id}/`, or `null` when the transaction has none." + } + } + } + } + } + }, + "examples": { + "Single transaction": { + "value": { + "count": 1, + "next": null, + "previous": null, + "total_transactions": 1, + "results": [ + { + "uuid": "abcdef12-3456-4890-abcd-ef1234567890", + "transaction_number": 4123, + "txn_id": "your-txn-2026-05-17-001", + "transaction_type": "finance", + "action_type": "withdrawal", + "status": "IN_REVIEW", + "direction": "OUTBOUND", + "amount": "1500", + "currency": "EUR", + "preferred_currency_amount": "1620.45", + "preferred_currency_code": "USD", + "payment_details": "Withdrawal to crypto wallet", + "txn_date": "2026-05-17T08:42:00Z", + "created_at": "2026-05-17T08:42:11Z", + "score": 78, + "severity": "HIGH", + "decision_reason_code": "WALLET_HIGH_RISK", + "decision_reason_label": "Destination wallet flagged as high risk", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "Manual review", + "color": "#F59E0B", + "description": null, + "source": "custom" + } + ], + "subject_name": "Maria Garcia", + "subject_vendor_data": "user-12345", + "counterparty_name": null, + "counterparty_vendor_data": null, + "counterparty_country": null, + "subject_country": null, + "assigned_to_name": "Alex Reviewer", + "travel_rule_status": null, + "travel_rule": null + } + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot list transactions for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create transaction", + "description": "Submit a transaction for synchronous AML/risk monitoring. The transaction is created `APPROVED`, then your rules (and crypto screening, when applicable) run inline and may flip the returned `status` to `IN_REVIEW` or `DECLINED`. **`transaction_id` is your idempotency key** (max 128 chars, unique per app); reusing it returns 400. A `transaction.created` webhook is dispatched on success for live applications. Sandbox applications are not billed and do not emit `transaction.created` or `transaction.status.updated` webhooks. Sandbox Travel Rule exchanges do emit `travel_rule.status.updated` webhooks with `environment: sandbox`. A Travel Rule transfer is stored and returned before any counterparty is contacted: `travel_rule.status` comes back `AWAITING_COUNTERPARTY` (or `NOT_APPLICABLE` below your threshold, `UNCONFIRMED_OWNERSHIP` for a declared self-hosted wallet, `COMPLETED` when the other wallet is your own verified wallet) and `travel_rule.protocol` is null. Didit then chooses the rail and sends the exchange, and every later status change of the transfer, including `COUNTERPARTY_VASP_NOT_FOUND` when no rail reaches the counterparty, arrives as a `travel_rule.status.updated` webhook. Your rules run again when the rail or the status changes.", + "operationId": "createTransaction", + "tags": [ + "Transactions" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"transaction_id\": \"your-txn-2026-05-17-001\",\n \"transaction_category\": \"finance\",\n \"transaction_details\": {\n \"direction\": \"outbound\",\n \"amount\": 1500,\n \"currency\": \"EUR\",\n \"action_type\": \"withdrawal\"\n },\n \"subject\": {\n \"entity_type\": \"individual\",\n \"vendor_data\": \"user-12345\",\n \"full_name\": \"Maria Garcia\"\n }\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.create(\n transaction_id=\"your-txn-2026-05-17-001\",\n transaction_category=\"finance\",\n transaction_details={\n \"direction\": \"outbound\",\n \"amount\": 1500.0,\n \"currency\": \"EUR\",\n \"action_type\": \"withdrawal\",\n },\n subject={\n \"entity_type\": \"individual\",\n \"vendor_data\": \"user-12345\",\n \"full_name\": \"Maria Garcia\",\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.create({\n transaction_id: \"your-txn-2026-05-17-001\",\n transaction_category: \"finance\",\n transaction_details: {\n direction: \"outbound\",\n amount: 1500.0,\n currency: \"EUR\",\n action_type: \"withdrawal\",\n },\n subject: {\n entity_type: \"individual\",\n vendor_data: \"user-12345\",\n full_name: \"Maria Garcia\",\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Create(ctx, didit.TransactionsCreateParams{\n\tTransactionID: \"your-txn-2026-05-17-001\",\n\tTransactionCategory: \"finance\",\n\tTransactionDetails: didit.TransactionsCreateParamsTransactionDetails{\n\t\tDirection: \"outbound\",\n\t\tAmount: 1500.0,\n\t\tCurrency: \"EUR\",\n\t\tActionType: didit.String(\"withdrawal\"),\n\t},\n\tSubject: didit.TransactionsCreateParamsSubject{\n\t\tEntityType: didit.String(\"individual\"),\n\t\tVendorData: \"user-12345\",\n\t\tFullName: \"Maria Garcia\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TransactionsCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().create(TransactionsCreateParams.builder()\n .transactionId(\"your-txn-2026-05-17-001\")\n .transactionCategory(TransactionsCreateParams.TransactionCategory.of(\"finance\"))\n .transactionDetails(TransactionsCreateParams.TransactionDetails.builder()\n .direction(TransactionsCreateParams.TransactionDetails.Direction.of(\"outbound\"))\n .amount(1500.0)\n .currency(\"EUR\")\n .actionType(\"withdrawal\")\n .build())\n .subject(TransactionsCreateParams.Subject.builder()\n .entityType(\"individual\")\n .vendorData(\"user-12345\")\n .fullName(\"Maria Garcia\")\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->create([\n 'transaction_id' => 'your-txn-2026-05-17-001',\n 'transaction_category' => 'finance',\n 'transaction_details' => [\n 'direction' => 'outbound',\n 'amount' => 1500.0,\n 'currency' => 'EUR',\n 'action_type' => 'withdrawal',\n ],\n 'subject' => [\n 'entity_type' => 'individual',\n 'vendor_data' => 'user-12345',\n 'full_name' => 'Maria Garcia',\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.create(\n transaction_id: \"your-txn-2026-05-17-001\",\n transaction_category: \"finance\",\n transaction_details: {\n direction: \"outbound\",\n amount: 1500.0,\n currency: \"EUR\",\n action_type: \"withdrawal\",\n },\n subject: {\n entity_type: \"individual\",\n vendor_data: \"user-12345\",\n full_name: \"Maria Garcia\",\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.CreateAsync(new TransactionsCreateParams\n{\n TransactionId = \"your-txn-2026-05-17-001\",\n TransactionCategory = \"finance\",\n TransactionDetails = new()\n {\n Direction = \"outbound\",\n Amount = 1500.0,\n Currency = \"EUR\",\n ActionType = \"withdrawal\",\n },\n Subject = new()\n {\n EntityType = \"individual\",\n VendorData = \"user-12345\",\n FullName = \"Maria Garcia\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "transaction_id": { + "type": "string", + "description": "Your unique identifier for this transaction (max 128 characters).", + "example": "your-txn-2026-05-17-001" + }, + "transaction_at": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred. Defaults to now if omitted." + }, + "time_zone": { + "type": "string", + "description": "IANA time zone identifier, e.g. `Europe/Madrid`." + }, + "transaction_category": { + "type": "string", + "description": "Category of the transaction. CamelCase aliases (`travelRule`, `userPlatformEvent`, `gamblingBet`, …) are also accepted; the stored/echoed value is camelCase for multi-word categories.", + "example": "finance", + "enum": [ + "finance", + "kyc", + "travel_rule", + "user_event", + "audit_trail_event", + "gambling_bet", + "gambling_limit_change", + "gambling_bonus_change", + "travelRule" + ] + }, + "transaction_details": { + "type": "object", + "description": "Core financial details of the transaction.", + "required": [ + "direction", + "amount", + "currency" + ], + "properties": { + "direction": { + "type": "string", + "description": "Direction of the transaction relative to the subject. Case-insensitive; `in`/`out` are accepted as aliases. Echoed back uppercase (`INBOUND`/`OUTBOUND`).", + "example": "outbound", + "enum": [ + "inbound", + "outbound", + "in", + "out" + ] + }, + "amount": { + "type": "number", + "description": "Transaction amount (up to 18 decimal places). Send it as a decimal string (e.g. `\"0.123456789012345678\"`) when exact precision matters: a JSON number is parsed as a binary float first, so digits beyond about 15 significant digits are rounded before the amount is stored. Must be zero or greater: the direction carries the sign, and a negative amount returns `400`.", + "example": 1500 + }, + "currency": { + "type": "string", + "description": "Currency code. Any ISO 4217 alphabetic code is accepted (e.g. `EUR`, `USD`, `NGN`), as is a crypto asset symbol (e.g. `BTC`). Prefer a currently-circulating code. Not every accepted code has an exchange rate: when none is available for the code (for example the ISO accounting unit `XSU`, or a crypto symbol Didit does not price), automatic conversion fails and `preferred_currency_amount` is returned null unless the request supplied a pre-converted `amount_in_default_currency`.", + "example": "EUR" + }, + "currency_kind": { + "type": "string", + "description": "Whether the currency is `fiat` or `crypto`." + }, + "amount_in_default_currency": { + "type": "number", + "nullable": true, + "description": "Pre-converted amount in the default currency. If omitted, automatic FX conversion is attempted. Must be zero or greater." + }, + "default_currency": { + "type": "string", + "description": "The default currency code for the converted amount. Any currently-circulating ISO 4217 code." + }, + "payment_details": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference or memo." + }, + "payment_reference_id": { + "type": "string", + "nullable": true, + "description": "External payment system reference. For crypto AML transaction-hash screening, send the blockchain transaction hash and also provide `subject.payment_method.account_id` with the related address." + }, + "action_type": { + "type": "string", + "nullable": true, + "description": "Sub-type of the transaction, e.g. `deposit`, `withdrawal`, `transfer`." + }, + "crypto_params": { + "type": "object", + "description": "Crypto network selection, separate from currency (the asset symbol). Recording a custom token symbol does not guarantee transaction-hash coverage for its contract.", + "properties": { + "crypto_chain": { + "type": "string", + "description": "Actual network used for screening. Managed screening supports BTC, ETH, LTC, XRP, BCH, DOGE, TRX, SOL, MATIC, BNB, BASE (Base), OP (Optimism), ARB (Arbitrum One), AVAX (Avalanche C-Chain), ADA (Cardano), XLM (Stellar) and HBAR (Hedera). Common network names are accepted too, for example POLYGON or POL for MATIC, BSC for BNB and TRON for TRX. Always specify this for multi-network tokens. When it is missing for USDT, USDC or ETH sent with a 0x transaction hash, Didit looks the hash up on Ethereum, BNB Chain, Polygon, Base, Arbitrum and Optimism and screens it on the network where it is found; if no network has it, the transaction is not screened and custom_values.aml_screening_status is FAILED with the reason in custom_values.aml_screening_error. When it is missing for USDT or USDC screened by wallet (no transaction hash), a Tron address is screened on Tron, a Solana address on Solana, and a 0x address on the first of those six networks where it has sent a transaction or holds the token, or on Ethereum when it is active on none of them. Connected-provider coverage varies; LIGHTNING requires a supported connected provider.", + "example": "BASE" + } + } + } + } + }, + "subject": { + "type": "object", + "description": "The subject (applicant) of the transaction.", + "required": [ + "vendor_data", + "full_name" + ], + "properties": { + "entity_type": { + "type": "string", + "description": "Entity type: `individual` or `company`." + }, + "vendor_data": { + "type": "string", + "description": "Your internal user/business identifier.", + "example": "user-12345" + }, + "full_name": { + "type": "string", + "description": "Full name of the subject.", + "example": "Maria Garcia" + }, + "first_name": { + "type": "string", + "description": "First name of the subject." + }, + "last_name": { + "type": "string", + "description": "Last name of the subject." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth (YYYY-MM-DD)." + }, + "address": { + "type": "object", + "description": "Address object.", + "properties": { + "country": { + "type": "string", + "description": "Country code (ISO 3166-1 alpha-3 recommended). Copied to the party's `country_code`." + }, + "town": { + "type": "string" + }, + "state": { + "type": "string" + }, + "street": { + "type": "string" + }, + "post_code": { + "type": "string" + } + } + }, + "institution_details": { + "type": "object", + "description": "Institution details (for institutional subjects).", + "properties": { + "name": { + "type": "string" + }, + "code": { + "type": "string" + }, + "address": { + "type": "object" + } + } + }, + "device_context": { + "type": "object", + "description": "Device context captured at transaction time. browser_family and platform must be sent as explicit top-level keys — they are not derived from user_agent. The camelCase alias device is also accepted on the request.", + "properties": { + "fingerprint": { + "type": "string", + "description": "Unique device fingerprint." + }, + "persistent_device_id": { + "type": "string", + "description": "Stable cross-session device identifier (v2 fingerprint schema)." + }, + "user_agent": { + "type": "string", + "description": "Browser or app user agent string. CamelCase alias: userAgent." + }, + "browser_family": { + "type": "string", + "description": "Browser family (e.g. Chrome, Firefox, Safari). Must be sent explicitly — it is not derived from user_agent. CamelCase alias: browserFamily." + }, + "browser_version": { + "type": "string", + "description": "Browser version string. Must be sent explicitly. CamelCase alias: browserVersion." + }, + "platform": { + "type": "string", + "description": "Platform identifier (web, ios, android, macos, windows, linux). Must be sent explicitly — it is not derived from user_agent." + }, + "os_family": { + "type": "string", + "description": "Operating system family (e.g. iOS, Android, Windows). CamelCase alias: osFamily." + }, + "os_version": { + "type": "string", + "description": "Operating system version string. CamelCase alias: osVersion." + }, + "accept_language": { + "type": "string", + "description": "Accept-Language header value. CamelCase alias: acceptLang." + }, + "session_id": { + "type": "string", + "description": "Session identifier of the user current session. Stored in metadata. CamelCase alias: sessionId." + }, + "session_age_ms": { + "type": "number", + "description": "Age of the user session in milliseconds at transaction time. CamelCase alias: sessionAgeMs." + }, + "network_context": { + "type": "object", + "description": "Network context with IP address and geo-location. A top-level ipInfo block is also folded into this object.", + "properties": { + "ip_address": { + "type": "string", + "description": "Client IP address. CamelCase alias: ip." + }, + "ip_country_code": { + "type": "string", + "description": "Geo-resolved country of the IP (ISO 3166-1 alpha-3). CamelCase alias: countryCode3." + }, + "latitude": { + "type": "number", + "description": "Geographic latitude of the IP. CamelCase alias: lat." + }, + "longitude": { + "type": "number", + "description": "Geographic longitude of the IP. CamelCase alias: lon." + } + } + } + } + }, + "payment_method": { + "type": "object", + "description": "Payment method used by the subject.", + "properties": { + "method_type": { + "type": "string", + "description": "Type of payment method: `bank_card`, `bank_account`, `crypto_wallet`, `ewallet`, `unhosted_wallet`, `other`." + }, + "account_id": { + "type": "string", + "description": "Account identifier (e.g. IBAN, wallet address). For crypto screening, this should contain the wallet address for this participant. Inbound transaction-hash screening requires the service/customer deposit address on the subject. Outbound screening uses the destination wallet on the counterparty. On a crypto transfer on bitcoin or Lightning it may be a Lightning payment request (BOLT11 invoice, BOLT12 offer, LNURL) or a Lightning Address: Didit stores the payee it names as the account (\"\" when it names none) and what the request says on the Travel Rule transfer's `chain_details.lightning`. At most 255 characters once stored. On a crypto transfer on bitcoin or Lightning it may be a Lightning payment request (BOLT11 invoice, BOLT12 offer, LNURL) or a Lightning Address: Didit stores the payee it names as the account (\"\" when it names none) and what the request says on the Travel Rule transfer's `chain_details.lightning`. At most 255 characters once stored." + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "The destination tag or memo that is part of the wallet on a memo network (XRP, XLM, EOS, ATOM, HBAR). On one shared exchange address each memo is its own payment method. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`. Alias: `destinationTag` or `destination_tag`." + }, + "issuing_country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code of the issuer." + } + } + } + } + }, + "counterparty": { + "type": "object", + "description": "The counterparty of the transaction (optional).", + "properties": { + "entity_type": { + "type": "string", + "description": "Entity type: `individual` or `company`." + }, + "vendor_data": { + "type": "string", + "description": "Your internal identifier for the counterparty." + }, + "full_name": { + "type": "string", + "description": "Full name of the counterparty." + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "address": { + "type": "object", + "description": "Address object with `country` (ISO 3166-1 alpha-3)." + }, + "institution_details": { + "type": "object" + }, + "device_context": { + "type": "object", + "description": "Device context for the counterparty. browser_family and platform must be sent as explicit top-level keys — they are not derived from user_agent.", + "properties": { + "fingerprint": { + "type": "string", + "description": "Unique device fingerprint." + }, + "persistent_device_id": { + "type": "string", + "description": "Stable cross-session device identifier (v2 fingerprint schema)." + }, + "user_agent": { + "type": "string", + "description": "Browser or app user agent string. CamelCase alias: userAgent." + }, + "browser_family": { + "type": "string", + "description": "Browser family (e.g. Chrome, Firefox, Safari). Must be sent explicitly — it is not derived from user_agent. CamelCase alias: browserFamily." + }, + "browser_version": { + "type": "string", + "description": "Browser version string. Must be sent explicitly. CamelCase alias: browserVersion." + }, + "platform": { + "type": "string", + "description": "Platform identifier (web, ios, android, macos, windows, linux). Must be sent explicitly — it is not derived from user_agent." + }, + "os_family": { + "type": "string", + "description": "Operating system family (e.g. iOS, Android, Windows). CamelCase alias: osFamily." + }, + "os_version": { + "type": "string", + "description": "Operating system version string. CamelCase alias: osVersion." + }, + "accept_language": { + "type": "string", + "description": "Accept-Language header value. CamelCase alias: acceptLang." + }, + "session_id": { + "type": "string", + "description": "Session identifier for the counterparty session. Stored in metadata. CamelCase alias: sessionId." + }, + "session_age_ms": { + "type": "number", + "description": "Age of the user session in milliseconds at transaction time. CamelCase alias: sessionAgeMs." + }, + "network_context": { + "type": "object", + "description": "Network context with IP address and geo-location. A top-level ipInfo block is also folded into this object.", + "properties": { + "ip_address": { + "type": "string", + "description": "Client IP address. CamelCase alias: ip." + }, + "ip_country_code": { + "type": "string", + "description": "Geo-resolved country of the IP (ISO 3166-1 alpha-3). CamelCase alias: countryCode3." + }, + "latitude": { + "type": "number", + "description": "Geographic latitude of the IP. CamelCase alias: lat." + }, + "longitude": { + "type": "number", + "description": "Geographic longitude of the IP. CamelCase alias: lon." + } + } + } + } + }, + "payment_method": { + "type": "object", + "description": "Payment method used by the counterparty. On travelRule transactions, `method_type: \"unhosted_wallet\"` declares the counterparty wallet as self-hosted: the transfer skips counterparty-VASP routing, is created directly in `UNCONFIRMED_OWNERSHIP`, and (when `auto_wallet_verification` is enabled) a wallet-ownership widget session is auto-minted and returned in the transaction's `action_required` block.", + "properties": { + "method_type": { + "type": "string", + "description": "Type of payment method: `bank_card`, `bank_account`, `crypto_wallet`, `ewallet`, `unhosted_wallet`, `other`. Use `unhosted_wallet` to declare a self-hosted wallet for the Travel Rule." + }, + "account_id": { + "type": "string", + "description": "Account identifier (e.g. IBAN or wallet address). On a crypto transfer on bitcoin or Lightning it may be a Lightning payment request (BOLT11 invoice, BOLT12 offer, LNURL) or a Lightning Address: Didit stores the payee it names as the account (\"\" when it names none) and what the request says on the Travel Rule transfer's `chain_details.lightning`. At most 255 characters once stored. On a crypto transfer on bitcoin or Lightning it may be a Lightning payment request (BOLT11 invoice, BOLT12 offer, LNURL) or a Lightning Address: Didit stores the payee it names as the account (\"\" when it names none) and what the request says on the Travel Rule transfer's `chain_details.lightning`. At most 255 characters once stored." + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "The destination tag or memo that is part of the wallet on a memo network (XRP, XLM, EOS, ATOM, HBAR). On one shared exchange address each memo is its own payment method. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`. Alias: `destinationTag` or `destination_tag`." + }, + "issuing_country": { + "type": "string" + } + } + } + } + }, + "custom_properties": { + "type": "object", + "description": "Arbitrary key-value pairs attached to the transaction. Each key can be referenced in rule conditions with the field path `custom_values.`.", + "additionalProperties": true, + "example": { + "merchant_id": "test", + "bank_name": "test_2" + } + }, + "travel_rule_details": { + "type": "object", + "description": "Travel Rule data. When Travel Rule is enabled and transaction_category is travel_rule, Didit runs the exchange itself: it ignores status, protocol, required and obligations_count, and uses originator_data, beneficiary_data and metadata. Otherwise Didit stores these values as a passive Travel Rule record. While Travel Rule is on, an outbound transfer at or above your threshold must carry what its regime requires (EU TFR or FATF Recommendation 16), checked on the sides you send: a missing field answers 400 under `travel_rule_details.originator_data` or `travel_rule_details.beneficiary_data`, for example \"EU TFR requires the originator name.\"", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "description": "Required whenever you send travel_rule_details. One of the Travel Rule transfer statuses (for example AWAITING_COUNTERPARTY, COMPLETED, NOT_APPLICABLE); lowercase and camelCase spellings and the legacy values UNKNOWN, COMPLIANT, PENDING_ACTION, PENDING_COUNTERPARTY, FAILED and EXEMPT are also accepted and mapped. Any other value returns 400." + }, + "protocol": { + "type": "string" + }, + "required": { + "type": "boolean" + }, + "obligations_count": { + "type": "integer" + }, + "originator_data": { + "type": "object", + "description": "Originator party, as flat fields or one IVMS 101 person. For your user with an approved KYC verification, Didit replaces the fields the verification holds with the verified values (see `metadata.originator_provenance`). It must be an object: anything else is a `400`. On a memo network a `memo` key names the originator wallet's memo or tag." + }, + "beneficiary_data": { + "type": "object", + "description": "Beneficiary party, as flat fields or one IVMS 101 person. It must be an object: anything else is a `400`. To route the transfer, Didit reads `wallet_address` (an empty one is filled from `counterparty.payment_method.account_id`), `chain` (filled from `transaction_details.crypto_params.crypto_chain` or the `currency` when that names a supported chain), `name` (falling back to `counterparty.full_name`), `travel_address`, `counterparty_vasp_name`, `counterparty_vasp_lei` and `counterparty_vasp_email`. On a memo network `memo` names the beneficiary wallet's memo or tag; `wallet_address` and `memo` win over the counterparty's payment method." + }, + "metadata": { + "type": "object" + }, + "utxo": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleUtxo" + } + ], + "description": "Optional, UTXO chains only (BTC, LTC, BCH, DOGE). The transaction's inputs and outputs, from which Didit infers the originator wallet and the beneficiary output when you do not declare them." + }, + "originator": { + "type": "object", + "description": "An IVMS 101 originator block: `originatorPersons` (one `naturalPerson` or `legalPerson`; a legal person makes the party a company) and `accountNumber` (wallet addresses). Used for `originator_data` when that object is empty. A malformed block answers 400 keyed by its path, for example `originator.originatorPersons`." + }, + "beneficiary": { + "type": "object", + "description": "An IVMS 101 beneficiary block: `beneficiaryPersons` (one `naturalPerson` or `legalPerson`; a legal person makes the party a company) and `accountNumber` (wallet addresses). Used for `beneficiary_data` when that object is empty. A malformed block answers 400 keyed by its path, for example `beneficiary.beneficiaryPersons`." + }, + "ivms101": { + "type": "object", + "description": "The `originator` and `beneficiary` blocks wrapped in one IVMS 101 object (`IVMS101` is accepted too). Top-level blocks win over the wrapper's." + } + } + }, + "network_snapshot": { + "type": "object", + "description": "Pre-computed network graph snapshot.", + "properties": { + "nodes": { + "type": "array", + "items": { + "type": "object" + } + }, + "edges": { + "type": "array", + "items": { + "type": "object" + } + }, + "metrics": { + "type": "object" + } + } + }, + "include_crypto_screening": { + "type": "boolean", + "nullable": true, + "description": "Per-transaction override for crypto blockchain analytics screening. When `true`, crypto screening is performed regardless of the application default. When `false`, crypto screening is skipped even if enabled in the application settings. When omitted or `null`, the application-level default configured in the Console is used. Crypto screening requires `transaction_details.direction`, `currency_kind: \"crypto\"`, a chain/currency, and the wallet address selected by the direction: inbound pre-transfer screening uses `counterparty.payment_method.account_id`, inbound transaction-hash screening uses `subject.payment_method.account_id`, and outbound screening uses `counterparty.payment_method.account_id`. When `transaction_details.payment_reference_id` contains a blockchain transaction hash, Didit performs transaction screening and returns transaction summary enrichment when available." + } + }, + "required": [ + "transaction_id", + "transaction_category", + "transaction_details", + "subject" + ] + }, + "examples": { + "Minimal fiat withdrawal": { + "summary": "Smallest acceptable payload for a fiat outbound transfer", + "value": { + "transaction_id": "your-txn-2026-05-17-001", + "transaction_category": "finance", + "transaction_details": { + "direction": "outbound", + "amount": 1500, + "currency": "EUR", + "action_type": "withdrawal" + }, + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia" + } + } + }, + "Crypto outbound with screening": { + "summary": "Outbound crypto withdrawal to an unhosted wallet, opt-in screening", + "value": { + "transaction_id": "your-txn-2026-05-17-002", + "transaction_category": "finance", + "include_crypto_screening": true, + "transaction_details": { + "direction": "outbound", + "amount": 0.5, + "currency": "BTC", + "currency_kind": "crypto", + "amount_in_default_currency": 32500, + "default_currency": "USD", + "action_type": "withdrawal" + }, + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "payment_method": { + "method_type": "crypto_wallet", + "account_id": "bc1qexampleexchangewalletaddressxyz", + "issuing_country": "ESP" + } + }, + "counterparty": { + "entity_type": "individual", + "payment_method": { + "method_type": "unhosted_wallet", + "account_id": "bc1qexampleexternalwalletaddressabc" + } + } + } + }, + "Travel Rule transfer": { + "summary": "Outbound crypto transfer with Travel Rule originator/beneficiary data", + "value": { + "transaction_id": "your-txn-2026-05-17-003", + "transaction_category": "travel_rule", + "transaction_details": { + "direction": "outbound", + "amount": 25000, + "currency": "USDT", + "currency_kind": "crypto", + "action_type": "transfer" + }, + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "date_of_birth": "1992-03-14", + "address": { + "country": "ESP", + "town": "Madrid", + "street": "Calle Mayor 1", + "post_code": "28013" + } + }, + "counterparty": { + "entity_type": "individual", + "full_name": "John Beneficiary", + "institution_details": { + "name": "Beneficiary VASP, Inc.", + "code": "BVASP-US" + } + }, + "travel_rule_details": { + "status": "AWAITING_COUNTERPARTY", + "protocol": "IVMS101", + "required": true, + "originator_data": { + "name": "Maria Garcia" + }, + "beneficiary_data": { + "name": "John Beneficiary" + } + } + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Transaction submitted and monitored. The body is the full transaction detail (same shape as `GET /v3/transactions/{transaction_id}/`), reflecting any rule outcomes already applied.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionDetail" + }, + "examples": { + "Approved with no rule hits": { + "value": { + "uuid": "abcdef12-3456-4890-abcd-ef1234567890", + "environment": "live", + "transaction_number": 4123, + "txn_id": "your-txn-2026-05-17-001", + "txn_date": "2026-05-17T08:42:00Z", + "zone_id": "Europe/Madrid", + "transaction_type": "finance", + "action_type": "withdrawal", + "direction": "OUTBOUND", + "status": "APPROVED", + "source": "LIVE", + "amount": "1500", + "currency": "EUR", + "currency_type": "fiat", + "amount_in_default_currency": "1620.45", + "default_currency_code": "USD", + "preferred_currency_amount": "1620.45", + "preferred_currency_code": "USD", + "payment_details": "Withdrawal to crypto wallet", + "payment_txn_id": null, + "score": 0, + "severity": null, + "decision_reason_code": null, + "decision_reason_label": null, + "vendor_data": "user-12345", + "metadata": { + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "address": {}, + "institution_details": {}, + "device_context": {} + }, + "counterparty": {} + }, + "props": {}, + "tags": [], + "parties": [ + { + "uuid": "b1111111-2222-4333-8444-555555555555", + "role": "APPLICANT", + "entity_type": "individual", + "kind": "USER", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "first_name": null, + "last_name": null, + "country_code": null, + "dob": null, + "address": {}, + "institution_info": {}, + "device": {}, + "external_party_snapshot": null + } + ], + "payment_methods": [], + "documents": [], + "activities": [ + { + "uuid": "d4e5f6a7-8901-4bcd-9ef0-123456789abc", + "activity_type": "TRANSACTION_CREATED", + "source": "TRANSACTION", + "status": "APPROVED", + "title": "withdrawal", + "description": "Transaction recorded.", + "metadata": {}, + "occurred_at": "2026-05-17T08:42:11Z" + } + ], + "alerts": [], + "rule_runs": [], + "provider_results": [], + "travel_rule": null, + "network_snapshot": null, + "remediation": null, + "action_required": null, + "cost_breakdown": null + } + }, + "Flagged for review": { + "value": { + "uuid": "abcdef12-3456-4890-abcd-ef1234567891", + "environment": "live", + "transaction_number": 4124, + "txn_id": "your-txn-2026-05-17-002", + "txn_date": "2026-05-17T08:42:00Z", + "zone_id": "Europe/Madrid", + "transaction_type": "finance", + "action_type": "withdrawal", + "direction": "OUTBOUND", + "status": "IN_REVIEW", + "source": "LIVE", + "amount": "0.5", + "currency": "BTC", + "currency_type": "crypto", + "amount_in_default_currency": "32500", + "default_currency_code": "USD", + "preferred_currency_amount": "32500", + "preferred_currency_code": "USD", + "payment_details": "Withdrawal to crypto wallet", + "payment_txn_id": null, + "score": 78, + "severity": "HIGH", + "decision_reason_code": "WALLET_HIGH_RISK", + "decision_reason_label": "Destination wallet flagged as high risk", + "vendor_data": "user-12345", + "metadata": { + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "address": {}, + "institution_details": {}, + "device_context": {} + }, + "counterparty": {} + }, + "props": { + "order_id": "ord-9988" + }, + "tags": [], + "parties": [ + { + "uuid": "b1111111-2222-4333-8444-555555555555", + "role": "APPLICANT", + "entity_type": "individual", + "kind": "USER", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "first_name": null, + "last_name": null, + "country_code": null, + "dob": null, + "address": {}, + "institution_info": {}, + "device": {}, + "external_party_snapshot": null + } + ], + "payment_methods": [], + "documents": [], + "activities": [], + "alerts": [ + { + "uuid": "a7b8c9d0-1234-4abc-9def-567890abcdef", + "title": "Destination wallet flagged as high risk", + "description": "Provider risk score exceeded the configured threshold.", + "severity": "HIGH", + "status": "OPEN", + "source": "RULE", + "metadata": {}, + "created_at": "2026-05-17T08:42:12Z", + "due_at": null + } + ], + "rule_runs": [ + { + "uuid": "e5f6a7b8-9012-4cde-af01-23456789abcd", + "rule": "f6a7b8c9-0123-4def-b012-3456789abcde", + "rule_title": "High-risk wallet screening", + "rule_description": null, + "rule_category": "crypto", + "matched": true, + "is_test": false, + "mode": "ACTIVE", + "severity": "HIGH", + "score_delta": 78, + "status_target": "IN_REVIEW", + "action_summary": "score +78; change_status:IN_REVIEW", + "metadata": {}, + "created_at": "2026-05-17T08:42:12Z" + } + ], + "provider_results": [], + "travel_rule": null, + "network_snapshot": null, + "remediation": null, + "action_required": null, + "cost_breakdown": null + } + } + } + } + } + }, + "400": { + "description": "Validation failed (missing required fields, duplicate `transaction_id`, unknown enum value, invalid currency, etc.).", + "content": { + "application/json": { + "examples": { + "Missing required field": { + "value": { + "transaction_details": { + "amount": [ + "This field is required." + ] + } + } + }, + "Duplicate transaction_id": { + "value": { + "transaction_id": [ + "A transaction with this transaction_id already exists." + ] + } + }, + "Invalid category": { + "value": { + "non_field_errors": [ + "Unsupported transaction category." + ] + } + }, + "Missing subject identity": { + "value": { + "subject": [ + "subject.vendor_data and subject.full_name are required." + ] + } + }, + "Blocklisted participant": { + "value": { + "vendor_data": [ + "The individual 'user-12345' is in the blocklist and cannot participate in transactions." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot submit transactions for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "409": { + "description": "Another request with the same `transaction_id` created the transaction while this one was being processed, so only one of two simultaneous submissions succeeds. Read the existing transaction instead of resubmitting.", + "content": { + "application/json": { + "examples": { + "Concurrent duplicate": { + "value": { + "detail": "A transaction with this transaction_id already exists." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: transaction creates and event ingestion (`POST /v3/transactions/`, `POST /v3/events/`, `POST /v3/events/batch/`) share a budget of 600 requests per minute per API key, separate from the 300 shared by every other write request. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying, rounded up so the retry lands in the next window.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Ingestion rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-didit-sdk": { + "resource": "transactions", + "method": "create", + "auth": "api_key", + "server": true + } + } + }, + "/v3/transactions/{transaction_id}/": { + "get": { + "summary": "Get transaction", + "description": "Retrieve the full monitoring record for one transaction: parties, score/severity, rule runs, alerts, Travel Rule outcome, remediation session.", + "operationId": "getTransaction", + "x-didit-sdk": { + "resource": "transactions", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID (the `uuid` returned from create / list, **not** your `txn_id`).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/abcdef12-3456-7890-abcd-ef1234567890/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.retrieve(\n \"abcdef12-3456-7890-abcd-ef1234567890\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.retrieve(\"abcdef12-3456-7890-abcd-ef1234567890\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Retrieve(ctx, \"abcdef12-3456-7890-abcd-ef1234567890\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().retrieve(\"abcdef12-3456-7890-abcd-ef1234567890\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->retrieve('abcdef12-3456-7890-abcd-ef1234567890');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.retrieve(\"abcdef12-3456-7890-abcd-ef1234567890\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.RetrieveAsync(\"abcdef12-3456-7890-abcd-ef1234567890\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Full transaction detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionDetail" + }, + "examples": { + "In review with an alert": { + "value": { + "uuid": "abcdef12-3456-4890-abcd-ef1234567890", + "environment": "live", + "transaction_number": 4123, + "txn_id": "your-txn-2026-05-17-001", + "txn_date": "2026-05-17T08:42:00Z", + "zone_id": "Europe/Madrid", + "transaction_type": "finance", + "action_type": "withdrawal", + "direction": "OUTBOUND", + "status": "IN_REVIEW", + "source": "LIVE", + "amount": "1500", + "currency": "EUR", + "currency_type": "fiat", + "amount_in_default_currency": "1620.45", + "default_currency_code": "USD", + "preferred_currency_amount": "1620.45", + "preferred_currency_code": "USD", + "payment_details": "Withdrawal to crypto wallet", + "payment_txn_id": null, + "score": 78, + "severity": "HIGH", + "decision_reason_code": "WALLET_HIGH_RISK", + "decision_reason_label": "Destination wallet flagged as high risk", + "vendor_data": "user-12345", + "metadata": { + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "address": {}, + "institution_details": {}, + "device_context": {} + }, + "counterparty": {} + }, + "props": { + "order_id": "ord-9988" + }, + "tags": [], + "parties": [ + { + "uuid": "b1111111-2222-4333-8444-555555555555", + "role": "APPLICANT", + "entity_type": "individual", + "kind": "USER", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "first_name": null, + "last_name": null, + "country_code": null, + "dob": null, + "address": {}, + "institution_info": {}, + "device": {}, + "external_party_snapshot": null + } + ], + "payment_methods": [], + "documents": [], + "activities": [], + "alerts": [ + { + "uuid": "a7b8c9d0-1234-4abc-9def-567890abcdef", + "title": "Destination wallet flagged as high risk", + "description": "Provider risk score exceeded the configured threshold.", + "severity": "HIGH", + "status": "OPEN", + "source": "RULE", + "metadata": {}, + "created_at": "2026-05-17T08:42:12Z", + "due_at": null + } + ], + "rule_runs": [], + "provider_results": [], + "travel_rule": null, + "network_snapshot": null, + "remediation": null, + "action_required": null, + "cost_breakdown": null + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read this application's transactions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists for the application (deleted, never created, belongs to another application, or the path segment is not a valid UUID).", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/transactions/sdk-token/": { + "post": { + "summary": "Mint an SDK transaction token", + "description": "Mint a short-lived scoped token your app passes to the Didit SDKs so they can submit transactions directly from the end-user's device via `POST /v1/transactions/`. The token is bound to one `vendor_data` (your user id): every transaction submitted with it has its subject identity enforced server-side from that binding, so a tampered client cannot submit on behalf of another user. Mint tokens from your backend - never ship the API key itself in an app. camelCase aliases (`vendorData`, `ttlSeconds`, `maxUses`) are also accepted.", + "operationId": "createTransactionSdkToken", + "x-didit-sdk": { + "resource": "transactions", + "method": "create_sdk_token", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "vendor_data": { + "type": "string", + "description": "Your internal identifier for the end user this token is scoped to. Enforced as the subject identity of every transaction submitted with the token.", + "example": "user-042" + }, + "ttl_seconds": { + "type": "integer", + "default": 900, + "maximum": 86400, + "description": "Token lifetime in seconds. Defaults to 900 (15 minutes); maximum 86400 (24 hours)." + }, + "max_uses": { + "type": "integer", + "nullable": true, + "description": "Maximum number of successful submissions allowed with this token. Omit or null for unlimited uses within the TTL." + } + }, + "required": [ + "vendor_data" + ] + }, + "example": { + "vendor_data": "user-042", + "ttl_seconds": 900, + "max_uses": 3 + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/transactions/sdk-token/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"vendor_data\": \"user-042\", \"ttl_seconds\": 900, \"max_uses\": 3}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.create_sdk_token(\n vendor_data=\"user-042\",\n ttl_seconds=900,\n max_uses=3,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.createSdkToken({\n vendor_data: \"user-042\",\n ttl_seconds: 900,\n max_uses: 3,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.CreateSDKToken(ctx, didit.TransactionsCreateSDKTokenParams{\n\tVendorData: \"user-042\",\n\tTTLSeconds: didit.Int(900),\n\tMaxUses: didit.Int(3),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TransactionsCreateSdkTokenParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().createSdkToken(TransactionsCreateSdkTokenParams.builder()\n .vendorData(\"user-042\")\n .ttlSeconds(900L)\n .maxUses(3L)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->createSdkToken([\n 'vendor_data' => 'user-042',\n 'ttl_seconds' => 900,\n 'max_uses' => 3,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.create_sdk_token(\n vendor_data: \"user-042\",\n ttl_seconds: 900,\n max_uses: 3\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.CreateSdkTokenAsync(new TransactionsCreateSdkTokenParams\n{\n VendorData = \"user-042\",\n TtlSeconds = 900,\n MaxUses = 3,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The minted token. Hand `sdk_token` to your app; it authenticates the device-facing `/v1/transactions/` endpoints via the `X-Transaction-Token` header until `expires_at`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sdk_token": { + "type": "string", + "description": "The scoped transaction token to pass to the SDK." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the token stops being accepted." + } + } + }, + "example": { + "sdk_token": "q7Zt...urlsafe", + "expires_at": "2026-07-07T12:15:00Z" + } + } + } + }, + "400": { + "description": "Validation failed: missing `vendor_data`, or `ttl_seconds` outside the allowed range.", + "content": { + "application/json": { + "examples": { + "Missing vendor_data": { + "value": { + "vendor_data": [ + "This field is required." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key.", + "content": { + "application/json": { + "examples": { + "Invalid API Key": { + "value": { + "detail": "Invalid API Key." + } + } + } + } + } + } + } + } + }, + "/v1/transactions/": { + "post": { + "summary": "Submit a transaction from an SDK", + "description": "Device-facing endpoint the Didit SDKs call to submit a transaction directly from the end-user's device. Authentication is a scoped token from `POST /v3/transactions/sdk-token/` sent in the `X-Transaction-Token` header instead of an API key, and the endpoint is CORS-enabled so browsers can call it cross-origin. The payload uses the camelCase wire aliases shown below (the SDK `submitTransaction` methods build it for you). Three server-side guarantees: the application is always taken from the token; the subject identity (`externalUserId`) is always enforced from the token's `vendor_data`, so spoofed payload values are ignored; and device intelligence (the SDK device fingerprint plus server-derived IP and user agent) is attached to the subject automatically.", + "operationId": "submitTransactionFromSdk", + "x-didit-sdk": { + "resource": "transactions", + "method": "submit_from_device", + "auth": "device_token", + "server": false + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "TransactionTokenAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "txnId": { + "type": "string", + "description": "Your unique identifier for this transaction (maps to `transaction_id`, max 128 characters).", + "example": "wd-2026-07-07-0042" + }, + "txnDate": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred (maps to `transaction_at`). Defaults to now if omitted." + }, + "zoneId": { + "type": "string", + "description": "IANA time zone identifier (maps to `time_zone`), e.g. `Europe/Madrid`." + }, + "type": { + "type": "string", + "description": "Transaction category (maps to `transaction_category`), e.g. `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet`.", + "example": "travelRule" + }, + "info": { + "type": "object", + "description": "Core financial details of the transaction (maps to `transaction_details`).", + "required": [ + "direction", + "amount", + "currency" + ], + "properties": { + "direction": { + "type": "string", + "description": "Direction relative to the subject. Case-insensitive; `in`/`out` are accepted as aliases. Echoed back uppercase (`INBOUND`/`OUTBOUND`).", + "example": "out" + }, + "amount": { + "type": "number", + "description": "Transaction amount (up to 18 decimal places). A decimal string is also accepted and is the only way to preserve more than about 15 significant digits: a JSON number is parsed as a binary float first, so `0.123456789012345678` is stored as `0.12345678901234568`.", + "example": 0.25 + }, + "currency": { + "type": "string", + "description": "Currency code. Any ISO 4217 alphabetic code is accepted (e.g. `EUR`, `USD`, `NGN`), as is a crypto asset symbol (e.g. `BTC`). Prefer a currently-circulating code. Not every accepted code has an exchange rate: when none is available for the code (for example the ISO accounting unit `XSU`, or a crypto symbol Didit does not price), automatic conversion fails and `preferred_currency_amount` is returned null unless the request supplied a pre-converted `amountInDefaultCurrency`.", + "example": "ETH" + }, + "currencyType": { + "type": "string", + "description": "Whether the currency is `fiat` or `crypto` (maps to `currency_kind`)." + }, + "amountInDefaultCurrency": { + "type": "number", + "nullable": true, + "description": "Pre-converted amount in the default currency (maps to `amount_in_default_currency`). If omitted, automatic FX conversion is attempted." + }, + "defaultCurrencyCode": { + "type": "string", + "description": "The default currency code for the converted amount (maps to `default_currency`). Any currently-circulating ISO 4217 code." + }, + "paymentDetails": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference or memo (maps to `payment_details`)." + }, + "paymentTxnId": { + "type": "string", + "nullable": true, + "description": "External payment reference (maps to `payment_reference_id`); the on-chain transaction hash for crypto transactions." + }, + "type": { + "type": "string", + "nullable": true, + "description": "Sub-type of the transaction (maps to `action_type`), e.g. `deposit`, `withdrawal`, `transfer`." + }, + "cryptoParams": { + "type": "object", + "description": "Crypto-specific parameters, e.g. `{\"crypto_chain\": \"ETH\"}`." + } + } + }, + "subject": { + "type": "object", + "description": "The subject (applicant) of the transaction. `externalUserId` is ignored here: the subject identity is always enforced from the token's `vendor_data`.", + "required": [ + "fullName" + ], + "properties": { + "type": { + "type": "string", + "description": "Entity type (maps to `entity_type`): `individual` (default) or `company`." + }, + "externalUserId": { + "type": "string", + "description": "Ignored on the subject - enforced server-side from the token's `vendor_data`." + }, + "fullName": { + "type": "string", + "example": "Jane Doe" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "dob": { + "type": "string", + "format": "date", + "description": "Date of birth (YYYY-MM-DD; maps to `date_of_birth`)." + }, + "address": { + "type": "object", + "description": "Address object with `country` (ISO 3166-1 alpha-3), `town`, `state`, `street`, `post_code`." + }, + "institutionInfo": { + "type": "object", + "description": "Institution details for institutional participants (maps to `institution_details`)." + }, + "device": { + "type": "object", + "description": "Optional explicit device context. Merged with the automatically captured device intelligence; server-derived fields win on conflict." + }, + "paymentMethod": { + "type": "object", + "description": "Payment method used by the subject (maps to `payment_method`).", + "properties": { + "type": { + "type": "string", + "description": "Payment method kind (maps to `method_type`), e.g. `bank_card`, `bank_account`, `crypto_wallet`, `ewallet`, `unhosted_wallet`, `other`." + }, + "accountId": { + "type": "string", + "description": "Account identifier (maps to `account_id`): IBAN, card fingerprint, or wallet address." + }, + "issuingCountry": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code of the issuer (maps to `issuing_country`)." + } + } + } + } + }, + "counterparty": { + "type": "object", + "description": "The counterparty of the transaction (optional). Same shape as `subject`; `externalUserId` is meaningful here.", + "properties": { + "type": { + "type": "string", + "description": "Entity type (maps to `entity_type`): `individual` or `company`." + }, + "externalUserId": { + "type": "string", + "description": "Your internal identifier for the counterparty (maps to `vendor_data`)." + }, + "fullName": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "dob": { + "type": "string", + "format": "date" + }, + "address": { + "type": "object" + }, + "institutionInfo": { + "type": "object" + }, + "device": { + "type": "object" + }, + "paymentMethod": { + "type": "object", + "description": "Payment method used by the counterparty (maps to `payment_method`). On travelRule transactions, `type: \"unhosted_wallet\"` declares the counterparty wallet as self-hosted: the transfer skips counterparty-VASP routing, is created directly in `UNCONFIRMED_OWNERSHIP`, and (when `auto_wallet_verification` is enabled) a wallet-ownership widget session is auto-minted and returned in `action_required` so the SDK can launch it.", + "properties": { + "type": { + "type": "string", + "description": "Payment method kind (maps to `method_type`), e.g. `bank_card`, `bank_account`, `crypto_wallet`, `ewallet`, `unhosted_wallet`, `other`. Use `unhosted_wallet` to declare a self-hosted wallet for the Travel Rule." + }, + "accountId": { + "type": "string", + "description": "Account identifier (maps to `account_id`): IBAN, card fingerprint, or wallet address." + }, + "issuingCountry": { + "type": "string" + } + } + } + } + }, + "props": { + "type": "object", + "additionalProperties": true, + "description": "Custom key-value pairs (maps to `custom_properties`). Each key can be referenced in rule conditions as `custom_values.`." + }, + "travelRule": { + "type": "object", + "description": "Travel Rule details (maps to `travel_rule_details`). When Travel Rule is enabled for the application, Didit ignores any `status` you send and drives the exchange itself.", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string" + }, + "protocol": { + "type": "string" + }, + "required": { + "type": "boolean" + }, + "obligationsCount": { + "type": "integer" + }, + "originatorData": { + "type": "object" + }, + "beneficiaryData": { + "type": "object" + }, + "metadata": { + "type": "object" + } + } + }, + "includeCryptoScreening": { + "type": "boolean", + "nullable": true, + "description": "Per-transaction override for crypto blockchain analytics screening (maps to `include_crypto_screening`)." + }, + "fingerprint_v2": { + "type": "object", + "description": "The `didit-fp-v2` device fingerprint payload. Injected automatically by the SDKs; only relevant when calling the wire API directly." + } + }, + "required": [ + "txnId", + "type", + "info", + "subject" + ] + }, + "example": { + "txnId": "wd-2026-07-07-0042", + "type": "travelRule", + "info": { + "direction": "out", + "amount": "0.25", + "currency": "ETH", + "currencyType": "crypto", + "cryptoParams": { + "crypto_chain": "ETH" + } + }, + "subject": { + "fullName": "Jane Doe" + }, + "counterparty": { + "fullName": "Ana Diaz", + "paymentMethod": { + "type": "unhosted_wallet", + "accountId": "0xBeneficiaryWallet01" + } + }, + "travelRule": { + "status": "AWAITING_COUNTERPARTY", + "beneficiaryData": { + "wallet_address": "0xBeneficiaryWallet01", + "name": "Ana Diaz" + } + }, + "includeCryptoScreening": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v1/transactions/ \\\n -H 'X-Transaction-Token: SDK_TOKEN' -H 'Content-Type: application/json' \\\n -d '{\"txnId\": \"wd-2026-07-07-0042\", \"type\": \"travelRule\", \"info\": {\"direction\": \"out\", \"amount\": \"0.25\", \"currency\": \"ETH\", \"currencyType\": \"crypto\"}, \"subject\": {\"fullName\": \"Jane Doe\"}, \"counterparty\": {\"fullName\": \"Ana Diaz\", \"paymentMethod\": {\"type\": \"unhosted_wallet\", \"accountId\": \"0xBeneficiaryWallet01\"}}, \"travelRule\": {\"status\": \"AWAITING_COUNTERPARTY\", \"beneficiaryData\": {\"wallet_address\": \"0xBeneficiaryWallet01\", \"name\": \"Ana Diaz\"}}, \"includeCryptoScreening\": true}'" + }, + { + "lang": "javascript", + "label": "Web SDK", + "source": "import { DiditSdk } from '@didit-protocol/sdk-web';\n\nconst result = await DiditSdk.shared.submitTransaction({\n transactionToken: sdkToken,\n transaction: {\n txnId: 'wd-2026-07-07-0042',\n category: 'travelRule',\n details: { direction: 'out', amount: '0.25', currency: 'ETH', currencyType: 'crypto' },\n subject: { fullName: 'Jane Doe' },\n counterparty: { fullName: 'Ana Diaz', paymentMethod: { type: 'unhosted_wallet', accountId: '0xBeneficiaryWallet01' } },\n travelRule: { status: 'AWAITING_COUNTERPARTY', beneficiaryData: { wallet_address: '0xBeneficiaryWallet01', name: 'Ana Diaz' } },\n includeCryptoScreening: true\n },\n onActionCompleted: (refreshed) => console.log(refreshed.status)\n});" + } + ], + "responses": { + "201": { + "description": "Transaction submitted and monitored. Same detail shape as `GET /v3/transactions/{transaction_id}/`, including the `action_required` block when the end user must complete a follow-up action (the SDKs auto-launch it by default).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionDetail" + }, + "examples": { + "Wallet ownership required": { + "value": { + "uuid": "22222222-3333-4444-5555-666666666666", + "txn_id": "wd-2026-07-07-0042", + "transaction_type": "travelRule", + "direction": "OUTBOUND", + "status": "APPROVED", + "amount": "0.25", + "currency": "ETH", + "travel_rule": { + "uuid": "33333333-4444-5555-6666-777777777777", + "status": "UNCONFIRMED_OWNERSHIP", + "required": true + }, + "action_required": { + "type": "wallet_ownership", + "url": "https://verify.didit.me/wallet-ownership/3s9x...urlsafe", + "widget_session_id": "77777777-8888-9999-aaaa-bbbbbbbbbbbb", + "expires_at": "2026-07-07T13:00:00Z" + } + } + } + } + } + } + }, + "400": { + "description": "Validation failed (missing required fields, duplicate `txnId`, unknown category).", + "content": { + "application/json": { + "examples": { + "Missing fields": { + "value": { + "transaction_details": { + "currency": [ + "This field is required." + ] + } + } + }, + "Duplicate txnId": { + "value": { + "transaction_id": [ + "A transaction with this transaction_id already exists." + ] + } + } + } + } + } + }, + "401": { + "description": "The transaction token was rejected: missing, unknown, revoked, over its `max_uses`, or expired. Expired-token responses always contain the word `expired` in the detail, which is how the SDKs distinguish `expired_token` from `invalid_token` errors.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "Invalid transaction token." + } + }, + "Expired token": { + "value": { + "detail": "Transaction token expired." + } + } + } + } + } + }, + "409": { + "description": "Another request with the same `txnId` created the transaction while this one was being processed, so only one of two simultaneous submissions succeeds. Read the existing transaction instead of resubmitting.", + "content": { + "application/json": { + "examples": { + "Concurrent duplicate": { + "value": { + "detail": "A transaction with this transaction_id already exists." + } + } + } + } + } + } + } + } + }, + "/v1/transactions/{transaction_id}/": { + "get": { + "summary": "Get a transaction from an SDK", + "description": "Device-facing read endpoint, authenticated with the same `X-Transaction-Token` used to submit. The SDKs poll it after an auto-launched action flow completes or its window closes, so the final state never depends on a single notification. Returns the same transaction detail as the submit response, including the current `action_required` block (`null` once the action is resolved).", + "operationId": "getTransactionFromSdk", + "x-didit-sdk": { + "resource": "transactions", + "method": "retrieve_from_device", + "auth": "device_token", + "server": false + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID (the `uuid` from the submit response, **not** your `txnId`).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + } + ], + "security": [ + { + "TransactionTokenAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v1/transactions/22222222-3333-4444-5555-666666666666/' \\\n -H 'X-Transaction-Token: SDK_TOKEN'" + } + ], + "responses": { + "200": { + "description": "The full transaction detail, including `action_required` (`null` once the pending action is resolved).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionDetail" + } + } + } + }, + "401": { + "description": "The transaction token was rejected: missing, unknown, revoked, over its `max_uses`, or expired.", + "content": { + "application/json": { + "examples": { + "Expired token": { + "value": { + "detail": "Transaction token expired." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists for the token's application, or the path segment is not a valid UUID.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + } + } + } + }, + "/v3/businesses/": { + "get": { + "summary": "List businesses", + "description": "List [businesses](/entities/businesses) Didit assembles from KYB sessions sharing the same `vendor_data` (free-form string, NOT a UUID), plus businesses created via the API. Paginated, ordered by `last_session_at` desc. This endpoint does not support filtering or search parameters — fetch pages and filter client-side.", + "operationId": "list_businesses", + "x-didit-sdk": { + "resource": "businesses", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "description": "Page size. Defaults to 50 when omitted. There is no enforced maximum, but keep pages small for latency." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "description": "Zero-based offset of the first record to return. Prefer following the `next` URL from the previous page over computing this by hand." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/businesses/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.businesses.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.businesses.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Businesses.ListAutoPaging(ctx, didit.BusinessesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.businesses().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.businesses.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Businesses.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated list of businesses with status, session counts, and feature breakdown.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows — exact up to 100, capped at 100 beyond that for performance. Do not use it to detect the last page; rely on `next` being `null` instead." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BusinessListItem" + } + } + } + }, + "examples": { + "Example": { + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-123", + "display_name": null, + "legal_name": "Acme Trading Ltd", + "registration_number": "12345678", + "country_code": "GB", + "effective_name": "Acme Trading Ltd", + "status": "ACTIVE", + "session_count": 2, + "approved_count": 1, + "declined_count": 0, + "in_review_count": 1, + "features": { + "KYB": "Approved" + }, + "features_list": [ + { + "feature": "KYB", + "status": "Approved" + } + ], + "last_session_at": "2026-03-15T10:30:00Z", + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": "2026-01-10T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2026-01-10T08:00:00Z" + } + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot list businesses for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/customization/": { + "get": { + "summary": "Get branding customization", + "description": "Returns the white-label branding for the application resolved from the API key: colors, fonts, border radii, logo URLs and UI/UX options. Custom domain and white-label email settings are managed only in the Business Console and are not part of this response.", + "operationId": "get_customization", + "x-didit-sdk": { + "resource": "customization", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Customization" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/customization/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.customization.retrieve()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.customization.retrieve();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Customization.Retrieve(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.customization().retrieve();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "customization->retrieve();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.customization.retrieve\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Customization.RetrieveAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Current branding customization for the application.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrandingCustomization" + }, + "examples": { + "Default branding": { + "value": { + "color_primary": "#000000", + "color_secondary": "#9DA1A1", + "color_background": "#FFFFFF", + "color_panel": "#2567FF", + "color_panel_10": "#1A1A1A", + "color_on_panel_1": "#9DA1A1", + "color_on_panel_2": "#F4F4F6", + "color_on_background": "#000000", + "color_button_1": "#2567FF", + "color_button_2": "#F8F8F8", + "color_button_text_1": "#FFFFFF", + "color_button_text_2": "#000000", + "color_pill_text": "#111827", + "border_radius_panel": 10, + "border_radius_buttons": 12, + "font_family": "Inter", + "font_weight": "regular", + "font_url": "https://fonts.googleapis.com/css2?family=Inter", + "logo_square": null, + "logo_rectangular": null, + "logo_rectangular_dark": null, + "favicon": null, + "app_public_name": "Acme Verify", + "privacy_policy_url": "https://acme.com/privacy", + "skip_welcome_screen": false, + "disable_login_with_didit": false, + "hide_progress_bar": false, + "callback_seconds": 3 + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update branding customization", + "description": "Partial update of the application's white-label branding. Send `application/json` to change colors, fonts, radii and UI options, or `multipart/form-data` to upload logo images. Only the fields you include are changed. Custom domain and white-label email cannot be set through this endpoint — use the Business Console for those.", + "operationId": "update_customization", + "x-didit-sdk": { + "resource": "customization", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Customization" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrandingCustomizationUpdate" + }, + "examples": { + "Update colors and font": { + "summary": "Change branding colors and font", + "value": { + "color_primary": "#123456", + "color_button_1": "#2567FF", + "font_family": "AR One Sans", + "border_radius_buttons": 16 + } + }, + "Update UI options": { + "summary": "Change public name and flow options", + "value": { + "app_public_name": "Acme Verify", + "privacy_policy_url": "https://acme.com/privacy", + "skip_welcome_screen": true, + "hide_progress_bar": true, + "callback_seconds": 5 + } + }, + "Remove a logo": { + "summary": "Delete the current rectangular logo", + "value": { + "delete_logo_rectangular": true + } + }, + "Remove the dark-mode logo": { + "summary": "Delete the current dark-mode logo", + "value": { + "delete_logo_rectangular_dark": true + } + } + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "description": "Use multipart/form-data only to upload logo images. Branding fields above may also be sent as form fields in the same request.", + "properties": { + "image_square": { + "type": "string", + "format": "binary", + "description": "Square logo (1:1 aspect ratio). PNG, JPG or WebP, at least 512 px on the long side, at most **1 MB**." + }, + "image_rectangular": { + "type": "string", + "format": "binary", + "description": "Rectangular logo (2:1 aspect ratio). PNG, JPG or WebP, at least 512 px on the long side, at most **1 MB**." + }, + "image_rectangular_dark": { + "type": "string", + "format": "binary", + "description": "Rectangular logo for dark mode (2:1 aspect ratio), shown on a dark background. PNG, JPG or WebP, at least 512 px on the long side, at most **1 MB**. Returned as `logo_rectangular_dark`." + }, + "image_favicon": { + "type": "string", + "format": "binary", + "description": "Favicon image. Max **500 KB**." + } + } + }, + "example": { + "image_rectangular": "(binary PNG logo, 2:1)", + "image_rectangular_dark": "(binary PNG logo for dark mode, 2:1)", + "image_favicon": "(binary favicon)" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/customization/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"color_primary\": \"#123456\", \"font_family\": \"AR One Sans\"}'" + }, + { + "lang": "curl", + "label": "curl (logo upload)", + "source": "curl -X PATCH 'https://verification.didit.me/v3/customization/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'image_rectangular=@logo.png' \\\n -F 'image_rectangular_dark=@logo-dark.png' \\\n -F 'image_favicon=@favicon.ico'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.customization.update(\n image_rectangular=open(\"image_rectangular.jpg\", \"rb\"),\n image_rectangular_dark=open(\"image_rectangular_dark.jpg\", \"rb\"),\n image_favicon=open(\"image_favicon.jpg\", \"rb\"),\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.customization.update({\n image_rectangular: await fileFromPath(\"image_rectangular.jpg\"),\n image_rectangular_dark: await fileFromPath(\"image_rectangular_dark.jpg\"),\n image_favicon: await fileFromPath(\"image_favicon.jpg\"),\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Customization.Update(ctx, didit.CustomizationUpdateParams{\n\tImageRectangular: didit.Some(didit.FileFromPath(\"image_rectangular.jpg\")),\n\tImageRectangularDark: didit.Some(didit.FileFromPath(\"image_rectangular_dark.jpg\")),\n\tImageFavicon: didit.Some(didit.FileFromPath(\"image_favicon.jpg\")),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.types.CustomizationUpdateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.customization().update(CustomizationUpdateParams.builder()\n .imageRectangular(Path.of(\"image_rectangular.jpg\"))\n .imageRectangularDark(Path.of(\"image_rectangular_dark.jpg\"))\n .imageFavicon(Path.of(\"image_favicon.jpg\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "customization->update([\n 'image_rectangular' => File::fromPath('image_rectangular.jpg'),\n 'image_rectangular_dark' => File::fromPath('image_rectangular_dark.jpg'),\n 'image_favicon' => File::fromPath('image_favicon.jpg'),\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.customization.update(\n image_rectangular: Pathname(\"image_rectangular.jpg\"),\n image_rectangular_dark: Pathname(\"image_rectangular_dark.jpg\"),\n image_favicon: Pathname(\"image_favicon.jpg\")\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Customization.UpdateAsync(new CustomizationUpdateParams\n{\n ImageRectangular = FileUpload.FromPath(\"image_rectangular.jpg\"),\n ImageRectangularDark = FileUpload.FromPath(\"image_rectangular_dark.jpg\"),\n ImageFavicon = FileUpload.FromPath(\"image_favicon.jpg\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Branding updated. The FULL branding object is returned (all fields, not just the ones you changed).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrandingCustomization" + }, + "examples": { + "Updated": { + "value": { + "color_primary": "#123456", + "color_secondary": "#9DA1A1", + "color_background": "#FFFFFF", + "color_panel": "#2567FF", + "color_panel_10": "#1A1A1A", + "color_on_panel_1": "#9DA1A1", + "color_on_panel_2": "#F4F4F6", + "color_on_background": "#000000", + "color_button_1": "#2567FF", + "color_button_2": "#F8F8F8", + "color_button_text_1": "#FFFFFF", + "color_button_text_2": "#000000", + "color_pill_text": "#111827", + "border_radius_panel": 10, + "border_radius_buttons": 16, + "font_family": "AR One Sans", + "font_weight": "400", + "font_url": "https://fonts.googleapis.com/css2?family=AR+One+Sans", + "logo_square": null, + "logo_rectangular": "https://cdn.didit.me/white_label/logo_rectangular/...", + "logo_rectangular_dark": null, + "favicon": null, + "app_public_name": "Acme Verify", + "privacy_policy_url": "https://acme.com/privacy", + "skip_welcome_screen": true, + "disable_login_with_didit": false, + "hide_progress_bar": false, + "callback_seconds": 3 + } + } + } + } + } + }, + "400": { + "description": "Invalid body. For example: a malformed hex color, an unknown font family or weight, a Business-Console-only field (`domain`, `from_email`, `verify_domain`, `force_remove_domain`, `verify_email_domain`, `force_remove_email_domain`, and their read-only companions), or a logo upload that is not a PNG, JPG or WebP image, is under 512 px on its long side, is over 1 MB, or has the wrong aspect ratio. Also returned when setting `disable_login_with_didit` to `false` while a custom domain is configured.", + "content": { + "application/json": { + "examples": { + "Bad color": { + "value": { + "color_primary": [ + "Invalid color format. Use '#RRGGBB' or '#RRGGBBAA' format." + ] + } + }, + "Bad logo": { + "value": { + "image_rectangular": [ + "Image aspect ratio must be 2:1. Got 1024x1024 image dimensions" + ] + } + }, + "Unsupported logo format": { + "summary": "Not a PNG, JPG or WebP image (an SVG, for example)", + "value": { + "image_rectangular_dark": [ + "Upload a PNG, JPG or WebP image." + ] + } + }, + "Logo too small": { + "summary": "Under 512 px on the long side", + "value": { + "image_rectangular": [ + "Image must be at least 512 px on its long side. Got 400x200 image dimensions" + ] + } + }, + "Logo too large": { + "summary": "Over 1 MB", + "value": { + "image_square": [ + "File size should not exceed 1 MB" + ] + } + }, + "Console-only field": { + "value": { + "domain": [ + "Manage this field in the Business Console." + ] + } + }, + "Bad font weight": { + "value": { + "font_weight": [ + "Font weight 950 is not available for Inter. Available weights are: 100, 200, 300, 400, 500, 600, 700, 800, 900" + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/businesses/{vendor_data}/": { + "get": { + "summary": "Get business", + "description": "Fetch one [business](/entities/businesses) by `vendor_data` (exact match). Adds `metadata` and `updated_at` to the list view. Deleted businesses return 404.", + "operationId": "get_business", + "x-didit-sdk": { + "resource": "businesses", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the business — a free-form string (NOT a UUID), matched exactly as sent.", + "example": "company-123" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/businesses/company-123/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.businesses.retrieve(\n \"company-123\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.businesses.retrieve(\"company-123\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.Retrieve(ctx, \"company-123\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.businesses().retrieve(\"company-123\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->retrieve('company-123');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.businesses.retrieve(\"company-123\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.RetrieveAsync(\"company-123\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Full business detail including metadata and aggregated session data.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDetailItem" + }, + "examples": { + "Active business": { + "value": { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-123", + "display_name": null, + "legal_name": "Acme Trading Ltd", + "registration_number": "12345678", + "country_code": "GB", + "effective_name": "Acme Trading Ltd", + "status": "ACTIVE", + "session_count": 2, + "approved_count": 1, + "declined_count": 0, + "in_review_count": 1, + "features": { + "KYB": "Approved" + }, + "features_list": [ + { + "feature": "KYB", + "status": "Approved" + } + ], + "last_session_at": "2026-03-15T10:30:00Z", + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": "2026-01-10T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2026-01-10T08:00:00Z", + "metadata": { + "industry": "fintech" + }, + "updated_at": "2026-03-15T10:30:00Z" + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read this application's businesses. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No (non-deleted) business with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update business", + "description": "Partial update of a business: `display_name`, `legal_name`, `registration_number`, `country_code`, `status`, `metadata`. `metadata` fully replaces. Setting `status` to `BLOCKED` adds the `vendor_data` to the system blocklist; moving away from `BLOCKED` removes that entry. Every edit is recorded in the company's activity: a status change with the optional `reason`, and a change to `display_name`, `legal_name`, `registration_number`, `country_code` or `metadata` as a profile update.", + "operationId": "update_business", + "x-didit-sdk": { + "resource": "businesses", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the business (free-form string, NOT a UUID).", + "example": "company-123" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Partial-update body. Provide only the fields you want to change.", + "properties": { + "display_name": { + "type": "string", + "nullable": true, + "description": "Friendly display name shown in the console (takes precedence over `legal_name` for UI display)." + }, + "legal_name": { + "type": "string", + "nullable": true, + "description": "Official legal name from registry or manual entry." + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Company registration or incorporation number." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "Country of incorporation (ISO 3166-1 alpha-2, e.g. `GB`, `US`)." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "Business lifecycle status. Setting `BLOCKED` automatically adds the `vendor_data` to the system blocklist; flipping away from `BLOCKED` removes that blocklist entry." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Arbitrary JSON object you attach to the business. **Fully replaces** the existing metadata on update — merge client-side if you only want to add keys." + }, + "reason": { + "type": "string", + "nullable": true, + "maxLength": 1000, + "description": "Why the status changed. Recorded on the status-change entry of the company's **Activity** in the Business Console, only when the status actually changes. Webhooks do not carry it." + } + } + }, + "examples": { + "Update name": { + "summary": "Update display name", + "value": { + "display_name": "Acme Corp" + } + }, + "Flag for review": { + "summary": "Flip status", + "value": { + "status": "FLAGGED" + } + }, + "Add metadata": { + "summary": "Replace metadata JSON", + "value": { + "metadata": { + "industry": "fintech", + "risk_level": "low" + } + } + }, + "Fix registry data": { + "summary": "Correct extracted fields", + "value": { + "legal_name": "Acme Trading International Ltd", + "registration_number": "12345678", + "country_code": "GB" + } + }, + "Flag with reason": { + "summary": "Flag the business and record why", + "value": { + "status": "FLAGGED", + "reason": "Ownership structure under review" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/businesses/company-123/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"display_name\": \"Acme Corp\", \"status\": \"FLAGGED\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.businesses.update(\n \"company-123\",\n display_name=\"Acme Corp\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.businesses.update(\"company-123\", {\n display_name: \"Acme Corp\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.Update(ctx, \"company-123\", didit.BusinessesUpdateParams{\n\tDisplayName: didit.String(\"Acme Corp\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BusinessesUpdateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.businesses().update(\"company-123\", BusinessesUpdateParams.builder()\n .displayName(\"Acme Corp\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->update('company-123', [\n 'display_name' => 'Acme Corp',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.businesses.update(\n \"company-123\",\n display_name: \"Acme Corp\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.UpdateAsync(\"company-123\", new BusinessesUpdateParams\n{\n DisplayName = \"Acme Corp\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Business updated. The full updated business record is returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDetailItem" + }, + "examples": { + "Updated": { + "value": { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-123", + "display_name": "Acme Corp", + "legal_name": "Acme Trading Ltd", + "registration_number": "12345678", + "country_code": "GB", + "effective_name": "Acme Corp", + "status": "FLAGGED", + "session_count": 2, + "approved_count": 1, + "declined_count": 0, + "in_review_count": 1, + "features": { + "KYB": "Approved" + }, + "features_list": [ + { + "feature": "KYB", + "status": "Approved" + } + ], + "last_session_at": "2026-03-15T10:30:00Z", + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": "2026-01-10T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2026-01-10T08:00:00Z", + "metadata": { + "industry": "fintech", + "risk_level": "low" + }, + "updated_at": "2026-03-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Invalid body: typically an unknown `status`, malformed `metadata` or a `reason` over 1000 characters.", + "content": { + "application/json": { + "examples": { + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Bad metadata": { + "value": { + "metadata": [ + "Metadata must be a JSON object." + ] + } + }, + "Reason too long": { + "value": { + "reason": [ + "Ensure this field has no more than 1000 characters." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot update businesses for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No (non-deleted) business with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/businesses/{vendor_data}/update-status/": { + "patch": { + "summary": "Update business status", + "description": "Flip only the lifecycle `status` of a business: `ACTIVE`, `FLAGGED` or `BLOCKED` (not session statuses). `BLOCKED` adds the `vendor_data` to the system blocklist; moving away from `BLOCKED` removes that entry. Each change is recorded in the company's activity, with the optional `reason`. Sending the current status changes nothing and records no `reason`. The `business.status.updated` webhook does not carry `reason`.", + "operationId": "update_business_status", + "x-didit-sdk": { + "resource": "businesses", + "method": "update_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the business (free-form string, NOT a UUID).", + "example": "company-123" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/EntityStatusUpdateRequest" + }, + { + "type": "object", + "properties": { + "reason": { + "type": "string", + "nullable": true, + "maxLength": 1000, + "description": "Why the status changed. Recorded on the status-change entry of the company's **Activity** in the Business Console, only when the status actually changes. Webhooks do not carry it." + } + } + } + ] + }, + "examples": { + "Flag": { + "summary": "Flag the business for review", + "value": { + "status": "FLAGGED" + } + }, + "Block": { + "summary": "Block the business (adds to system blocklist)", + "value": { + "status": "BLOCKED" + } + }, + "Reactivate": { + "summary": "Reactivate the business", + "value": { + "status": "ACTIVE" + } + }, + "Block with reason": { + "summary": "Block the business and record why", + "value": { + "status": "BLOCKED", + "reason": "Registry shows the company dissolved" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/businesses/company-123/update-status/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"status\": \"BLOCKED\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.businesses.update_status(\n \"company-123\",\n status=\"FLAGGED\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.businesses.updateStatus(\"company-123\", {\n status: \"FLAGGED\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.UpdateStatus(ctx, \"company-123\", didit.BusinessesUpdateStatusParams{\n\tStatus: \"FLAGGED\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BusinessesUpdateStatusParams;\nimport me.didit.types.EntityStatusUpdateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.businesses().updateStatus(\"company-123\", BusinessesUpdateStatusParams.builder()\n .status(EntityStatusUpdateRequest.Status.of(\"FLAGGED\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->updateStatus('company-123', [\n 'status' => 'FLAGGED',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.businesses.update_status(\n \"company-123\",\n status: \"FLAGGED\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.UpdateStatusAsync(\"company-123\", new BusinessesUpdateStatusParams\n{\n Status = \"FLAGGED\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Business status updated. Full business record returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDetailItem" + }, + "examples": { + "Blocked": { + "value": { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-123", + "display_name": null, + "legal_name": "Acme Trading Ltd", + "registration_number": "12345678", + "country_code": "GB", + "effective_name": "Acme Trading Ltd", + "status": "BLOCKED", + "session_count": 2, + "approved_count": 1, + "declined_count": 0, + "in_review_count": 1, + "features": { + "KYB": "Approved" + }, + "features_list": [ + { + "feature": "KYB", + "status": "Approved" + } + ], + "last_session_at": "2026-03-15T10:30:00Z", + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": "2026-01-10T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2026-01-10T08:00:00Z", + "metadata": { + "industry": "fintech" + }, + "updated_at": "2026-03-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Missing or invalid `status`, or a `reason` over 1000 characters.", + "content": { + "application/json": { + "examples": { + "Missing status": { + "value": { + "status": [ + "This field is required." + ] + } + }, + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Reason too long": { + "value": { + "reason": [ + "Ensure this field has no more than 1000 characters." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot update businesses for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No (non-deleted) business with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/businesses/delete/": { + "post": { + "summary": "Batch delete businesses", + "description": "**Permanently delete** businesses — this cannot be undone. For blocking prefer [Update Business Status](#patch-/v3/businesses/-vendor_data-/update-status/) with `BLOCKED`.\n\n**Filed regulatory reports:** if the selected data is in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "batch_delete_businesses", + "x-didit-sdk": { + "resource": "businesses", + "method": "batch_delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Provide `vendor_data_list`, `didit_internal_id_list`, or `delete_all: true`. Precedence when several are sent: `delete_all` > `vendor_data_list` > `didit_internal_id_list` (only the highest-precedence selector is used).", + "properties": { + "vendor_data_list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Your own identifiers to delete, matched exactly as sent." + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit's internal UUIDs (`didit_internal_id`) to delete." + }, + "delete_all": { + "type": "boolean", + "default": false, + "description": "If `true`, deletes every business in the application. Cannot be combined with the list selectors." + } + } + }, + "examples": { + "Specific businesses": { + "summary": "Delete by vendor_data", + "value": { + "vendor_data_list": [ + "company-123", + "company-456" + ] + } + }, + "Specific internal IDs": { + "summary": "Delete by didit_internal_id", + "value": { + "didit_internal_id_list": [ + "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "b2c3d4e5-f6a7-8901-bcde-f12345678901" + ] + } + }, + "All businesses": { + "summary": "Wipe every business in this application", + "value": { + "delete_all": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/businesses/delete/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vendor_data_list\": [\"company-123\", \"company-456\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.businesses.batch_delete(\n vendor_data_list=[\"company-123\", \"company-456\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.businesses.batchDelete({\n vendor_data_list: [\"company-123\", \"company-456\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.BatchDelete(ctx, didit.BusinessesBatchDeleteParams{\n\tVendorDataList: didit.Some([]string{\"company-123\", \"company-456\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.BusinessesBatchDeleteParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.businesses().batchDelete(BusinessesBatchDeleteParams.builder()\n .vendorDataList(List.of(\"company-123\", \"company-456\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->batchDelete([\n 'vendor_data_list' => ['company-123', 'company-456'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.businesses.batch_delete(vendor_data_list: [\"company-123\", \"company-456\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.BatchDeleteAsync(new BusinessesBatchDeleteParams\n{\n VendorDataList = new string[] { \"company-123\", \"company-456\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Businesses deleted. Returns the number of rows actually removed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deleted": { + "type": "integer", + "description": "Number of businesses deleted (excludes vendor_data values that didn't match anything)." + } + } + }, + "examples": { + "Deleted some": { + "value": { + "deleted": 2 + } + }, + "Nothing matched": { + "value": { + "deleted": 0 + } + } + } + } + } + }, + "400": { + "description": "No selector supplied (all of `vendor_data_list`, `didit_internal_id_list`, and `delete_all` missing or empty). The error body is a JSON array.", + "content": { + "application/json": { + "examples": { + "Missing selector": { + "value": [ + "Provide vendor_data_list, didit_internal_id_list, or set delete_all=true." + ] + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot delete businesses for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + }, + "409": { + "description": "Nothing is deleted. `code` says why: `in_filed_report` when the selection includes a company in a finalized regulatory report, `subject_deletion_in_progress` when another request is still deleting one of the selected companies. Repeat the request once that deletion has finished.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/InFiledReportError" + }, + { + "$ref": "#/components/schemas/SubjectDeletionInProgressError" + } + ] + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/users/aml-monitoring/": { + "post": { + "summary": "Enable or disable AML ongoing monitoring for users", + "description": "Turns AML ongoing monitoring on or off for the selected users, with the same semantics as the Business Console. Exactly one monitored AML screening is kept per user, so a profile that verified several times is monitored and billed once; disabling turns monitoring off across all of the profile's screenings. Users without any AML screening are screened first (billed as a regular AML screening) when they have the full name, date of birth and country needed; otherwise they are reported as `skipped_no_identity_data`.\n\nSet `estimate_only: true` to preview counts and costs without writing anything. Selections of 20 profiles or fewer resolve inline and return `200` with the completed job; larger selections return `202` and run in the background - poll [Get AML monitoring job](#get-/v3/aml-monitoring/jobs/-job_id-/) with the returned `job_id`. Enabling is refused with `403` when the organization balance does not cover the estimate. Identifiers that match nothing are listed in `unmatched` and never fail the request: a selection that matches nothing completes as an empty job (`total` 0), the same convention as the batch delete endpoints.", + "operationId": "toggle_users_aml_monitoring", + "x-didit-sdk": { + "resource": "users", + "method": "set_aml_monitoring", + "auth": "api_key", + "server": true + }, + "tags": [ + "AML Ongoing Monitoring" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_enabled" + ], + "description": "Select users with `vendor_data_list`, `didit_internal_id_list`, or both (the two lists are OR'd). At most 1000 identifiers per request across both lists.", + "properties": { + "is_enabled": { + "type": "boolean", + "description": "true to enable monitoring, false to disable it." + }, + "estimate_only": { + "type": "boolean", + "default": false, + "description": "When true, returns the cost estimate and performs no writes - no screening, no billing, no job." + }, + "vendor_data_list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Your own identifiers of the users to toggle, matched exactly as sent." + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit internal ids of the users to toggle - the `didit_internal_id` returned by the create, get and list endpoints." + } + } + }, + "examples": { + "Estimate first": { + "summary": "Preview the cost without writing", + "value": { + "is_enabled": true, + "estimate_only": true, + "vendor_data_list": [ + "user-123", + "user-456" + ] + } + }, + "Enable": { + "summary": "Enable monitoring for two users", + "value": { + "is_enabled": true, + "vendor_data_list": [ + "user-123", + "user-456" + ] + } + }, + "Disable": { + "summary": "Disable monitoring by Didit internal id", + "value": { + "is_enabled": false, + "didit_internal_id_list": [ + "3f1c0d3e-8f0a-4c2e-9a3d-9b1f5f4a2c11" + ] + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/users/aml-monitoring/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"is_enabled\": true, \"vendor_data_list\": [\"user-123\", \"user-456\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.users.set_aml_monitoring(\n is_enabled=True,\n estimate_only=True,\n vendor_data_list=[\"user-123\", \"user-456\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.users.setAmlMonitoring({\n is_enabled: true,\n estimate_only: true,\n vendor_data_list: [\"user-123\", \"user-456\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Users.SetAMLMonitoring(ctx, didit.UsersSetAMLMonitoringParams{\n\tIsEnabled: true,\n\tEstimateOnly: didit.Bool(true),\n\tVendorDataList: didit.Some([]string{\"user-123\", \"user-456\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.UsersSetAmlMonitoringParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.users().setAmlMonitoring(UsersSetAmlMonitoringParams.builder()\n .isEnabled(true)\n .estimateOnly(true)\n .vendorDataList(List.of(\"user-123\", \"user-456\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->setAmlMonitoring([\n 'is_enabled' => true,\n 'estimate_only' => true,\n 'vendor_data_list' => ['user-123', 'user-456'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.users.set_aml_monitoring(\n is_enabled: true,\n estimate_only: true,\n vendor_data_list: [\"user-123\", \"user-456\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Users.SetAmlMonitoringAsync(new UsersSetAmlMonitoringParams\n{\n IsEnabled = true,\n EstimateOnly = true,\n VendorDataList = new string[] { \"user-123\", \"user-456\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The estimate (when `estimate_only` is true) or the completed job for a selection processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AMLMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger selections run in the background. Poll the job endpoint with `job_id`; `results` fills in as profiles are processed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AMLMonitoringJob" + } + } + } + }, + "400": { + "description": "Validation error: `is_enabled` missing, no selector, or more than 1000 identifiers across both lists." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Insufficient balance to enable monitoring for this selection. The response carries the `estimate` so you can top up the right amount.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + } + } + } + } + } + } + } + } + } + } + }, + "/v3/businesses/aml-monitoring/": { + "post": { + "summary": "Enable or disable AML ongoing monitoring for businesses", + "description": "Turns AML ongoing monitoring on or off for the selected businesses, with the same semantics as the Business Console. Exactly one monitored AML screening is kept per business, so a profile that verified several times is monitored and billed once; disabling turns monitoring off across all of the profile's screenings. Businesses without any AML screening are screened first (billed as a regular AML screening) when they have the legal or display name needed; otherwise they are reported as `skipped_no_identity_data`.\n\nSet `estimate_only: true` to preview counts and costs without writing anything. Selections of 20 profiles or fewer resolve inline and return `200` with the completed job; larger selections return `202` and run in the background - poll [Get AML monitoring job](#get-/v3/aml-monitoring/jobs/-job_id-/) with the returned `job_id`. Enabling is refused with `403` when the organization balance does not cover the estimate. Identifiers that match nothing are listed in `unmatched` and never fail the request: a selection that matches nothing completes as an empty job (`total` 0), the same convention as the batch delete endpoints.", + "operationId": "toggle_businesses_aml_monitoring", + "x-didit-sdk": { + "resource": "businesses", + "method": "set_aml_monitoring", + "auth": "api_key", + "server": true + }, + "tags": [ + "AML Ongoing Monitoring" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_enabled" + ], + "description": "Select businesses with `vendor_data_list`, `didit_internal_id_list`, or both (the two lists are OR'd). At most 1000 identifiers per request across both lists.", + "properties": { + "is_enabled": { + "type": "boolean", + "description": "true to enable monitoring, false to disable it." + }, + "estimate_only": { + "type": "boolean", + "default": false, + "description": "When true, returns the cost estimate and performs no writes - no screening, no billing, no job." + }, + "vendor_data_list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Your own identifiers of the businesses to toggle, matched exactly as sent." + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit internal ids of the businesses to toggle - the `didit_internal_id` returned by the create, get and list endpoints." + } + } + }, + "examples": { + "Estimate first": { + "summary": "Preview the cost without writing", + "value": { + "is_enabled": true, + "estimate_only": true, + "vendor_data_list": [ + "company-123", + "company-456" + ] + } + }, + "Enable": { + "summary": "Enable monitoring for two businesses", + "value": { + "is_enabled": true, + "vendor_data_list": [ + "company-123", + "company-456" + ] + } + }, + "Disable": { + "summary": "Disable monitoring by Didit internal id", + "value": { + "is_enabled": false, + "didit_internal_id_list": [ + "3f1c0d3e-8f0a-4c2e-9a3d-9b1f5f4a2c11" + ] + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/businesses/aml-monitoring/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"is_enabled\": true, \"vendor_data_list\": [\"company-123\", \"company-456\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.businesses.set_aml_monitoring(\n is_enabled=True,\n estimate_only=True,\n vendor_data_list=[\"company-123\", \"company-456\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.businesses.setAmlMonitoring({\n is_enabled: true,\n estimate_only: true,\n vendor_data_list: [\"company-123\", \"company-456\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.SetAMLMonitoring(ctx, didit.BusinessesSetAMLMonitoringParams{\n\tIsEnabled: true,\n\tEstimateOnly: didit.Bool(true),\n\tVendorDataList: didit.Some([]string{\"company-123\", \"company-456\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.BusinessesSetAmlMonitoringParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.businesses().setAmlMonitoring(BusinessesSetAmlMonitoringParams.builder()\n .isEnabled(true)\n .estimateOnly(true)\n .vendorDataList(List.of(\"company-123\", \"company-456\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->setAmlMonitoring([\n 'is_enabled' => true,\n 'estimate_only' => true,\n 'vendor_data_list' => ['company-123', 'company-456'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.businesses.set_aml_monitoring(\n is_enabled: true,\n estimate_only: true,\n vendor_data_list: [\"company-123\", \"company-456\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.SetAmlMonitoringAsync(new BusinessesSetAmlMonitoringParams\n{\n IsEnabled = true,\n EstimateOnly = true,\n VendorDataList = new string[] { \"company-123\", \"company-456\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The estimate (when `estimate_only` is true) or the completed job for a selection processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AMLMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger selections run in the background. Poll the job endpoint with `job_id`; `results` fills in as profiles are processed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AMLMonitoringJob" + } + } + } + }, + "400": { + "description": "Validation error: `is_enabled` missing, no selector, or more than 1000 identifiers across both lists." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Insufficient balance to enable monitoring for this selection. The response carries the `estimate` so you can top up the right amount.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + } + } + } + } + } + } + } + } + } + } + }, + "/v3/aml-monitoring/jobs/{job_id}/": { + "get": { + "summary": "Get AML ongoing monitoring job", + "description": "Status, outcome counters and per-record results of a monitoring toggle job started from `POST /v3/users/aml-monitoring/` or `POST /v3/businesses/aml-monitoring/`. Poll every couple of seconds while `status` is `PENDING` or `PROCESSING`; `results` is complete once it is `COMPLETED`.", + "operationId": "get_aml_monitoring_job", + "x-didit-sdk": { + "resource": "aml_monitoring", + "method": "retrieve_job", + "auth": "api_key", + "server": true + }, + "tags": [ + "AML Ongoing Monitoring" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The `job_id` returned by the toggle endpoint." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl 'https://verification.didit.me/v3/aml-monitoring/jobs/JOB_ID/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.aml_monitoring.retrieve_job(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.amlMonitoring.retrieveJob(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.AMLMonitoring.RetrieveJob(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.amlMonitoring().retrieveJob(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "amlMonitoring->retrieveJob('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.aml_monitoring.retrieve_job(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.AmlMonitoring.RetrieveJobAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ] + }, + "is_enabled": { + "type": "boolean", + "description": "true when the job enables monitoring, false when it disables it." + }, + "target_type": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Which profile family the job toggles." + }, + "total": { + "type": "integer", + "description": "Profiles the job processes." + }, + "processed": { + "type": "integer", + "description": "Profiles processed so far." + }, + "outcomes": { + "type": "object", + "description": "Per-record outcome counters. Every selected profile resolves to exactly one counter; nothing is silently skipped.", + "properties": { + "enabled": { + "type": "integer", + "description": "Monitoring enabled on an existing AML screening." + }, + "screened_and_enabled": { + "type": "integer", + "description": "A new AML screening was created (billed) and monitoring enabled on it." + }, + "already_enabled": { + "type": "integer", + "description": "Monitoring was already active; nothing changed and nothing was billed." + }, + "disabled": { + "type": "integer", + "description": "Monitoring turned off across every AML screening of the profile." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Skipped: the profile lacks the full name, date of birth and country (users) or a name (businesses) needed to screen." + }, + "skipped_blocked": { + "type": "integer", + "description": "Skipped: the user is blocked." + }, + "skipped_sandbox": { + "type": "integer", + "description": "Skipped: sandbox application, no screening is created." + }, + "failed_provider": { + "type": "integer", + "description": "The screening could not be completed; retry later." + }, + "failed_insufficient_balance": { + "type": "integer", + "description": "Balance ran out while the job was running." + } + } + }, + "results": { + "type": "array", + "description": "One entry per processed profile, in processing order. Grows while the job runs; complete once status is COMPLETED. Recorded for jobs started through the API; a job started from the Business Console reports counters only.", + "items": { + "type": "object", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit internal id of the profile." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the profile." + }, + "outcome": { + "type": "string", + "enum": [ + "enabled", + "screened_and_enabled", + "already_enabled", + "disabled", + "skipped_no_identity_data", + "skipped_blocked", + "skipped_sandbox", + "failed_provider", + "failed_insufficient_balance" + ] + } + } + } + }, + "error": { + "type": "string", + "nullable": true, + "description": "Generic failure message when status is FAILED; the detailed cause is kept on the platform side." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "404": { + "description": "No such job for this application." + } + } + } + }, + "/v3/users/{vendor_data}/update-status/": { + "patch": { + "summary": "Update user status", + "description": "Flip only the lifecycle `status` of a user: `ACTIVE`, `FLAGGED` or `BLOCKED` (not session statuses). `BLOCKED` adds the `vendor_data` to the system blocklist; moving away from `BLOCKED` removes that entry. Each change is recorded as a `STATUS_CHANGED` activity on the user, with the optional `reason` as its `comment`. Sending the current status changes nothing and records no `reason`. The `user.status.updated` webhook does not carry `reason`.", + "operationId": "update_user_status", + "x-didit-sdk": { + "resource": "users", + "method": "update_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the user." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/EntityStatusUpdateRequest" + }, + { + "type": "object", + "properties": { + "reason": { + "type": "string", + "nullable": true, + "maxLength": 1000, + "description": "Why the status changed. Recorded on the status-change entry of the user's activity (`comments[].comment`, and **Activity** in the Business Console), only when the status actually changes. Webhooks do not carry it." + } + } + } + ] + }, + "examples": { + "Activate": { + "summary": "Reactivate the user", + "value": { + "status": "ACTIVE" + } + }, + "Flag": { + "summary": "Flag the user for review", + "value": { + "status": "FLAGGED" + } + }, + "Block": { + "summary": "Block the user (adds to system blocklist)", + "value": { + "status": "BLOCKED" + } + }, + "Block with reason": { + "summary": "Block the user and record why", + "value": { + "status": "BLOCKED", + "reason": "Chargeback fraud pattern confirmed" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/users/user-abc-123/update-status/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"status\": \"BLOCKED\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.users.update_status(\n \"user-123\",\n status=\"ACTIVE\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.users.updateStatus(\"user-123\", {\n status: \"ACTIVE\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Users.UpdateStatus(ctx, \"user-123\", didit.UsersUpdateStatusParams{\n\tStatus: \"ACTIVE\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.EntityStatusUpdateRequest;\nimport me.didit.types.UsersUpdateStatusParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.users().updateStatus(\"user-123\", UsersUpdateStatusParams.builder()\n .status(EntityStatusUpdateRequest.Status.of(\"ACTIVE\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->updateStatus('user-123', [\n 'status' => 'ACTIVE',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.users.update_status(\n \"user-123\",\n status: \"ACTIVE\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Users.UpdateStatusAsync(\"user-123\", new UsersUpdateStatusParams\n{\n Status = \"ACTIVE\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "User status updated. Full user record returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDetailItem" + }, + "examples": { + "Blocked": { + "value": { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": null, + "full_name": "John Michael Doe", + "date_of_birth": "1990-05-15", + "effective_name": "John Michael Doe", + "status": "BLOCKED", + "metadata": { + "tier": "premium", + "source": "website" + }, + "portrait_image_url": "https:///...", + "session_count": 3, + "approved_count": 2, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [ + "USA" + ], + "approved_emails": [ + "john@example.com" + ], + "approved_phones": [ + "+14155551234" + ], + "features": { + "ID_VERIFICATION": "Approved", + "LIVENESS": "Approved", + "FACE_MATCH": "Approved", + "AML": "Approved" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-01T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "tag": { + "uuid": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b", + "name": "VIP", + "color": "#2567FF", + "description": null, + "source": "custom", + "created_at": "2025-05-01T09:00:00Z", + "updated_at": "2025-05-01T09:00:00Z" + }, + "added_by_email": "analyst@acme.com", + "added_by_name": "Jane Analyst", + "created_at": "2025-06-02T11:00:00Z" + } + ], + "comments": [ + { + "uuid": "c1111111-2222-4333-8444-555555555555", + "comment_type": "STATUS_CHANGED", + "comment": "Chargeback fraud pattern confirmed", + "actor_email": null, + "actor_name": null, + "previous_status": "ACTIVE", + "new_status": "BLOCKED", + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "mentioned_emails": [], + "created_at": "2025-06-15T10:30:00Z" + } + ], + "created_at": "2025-06-01T08:00:00Z", + "updated_at": "2025-06-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Missing or invalid `status`, or a `reason` over 1000 characters.", + "content": { + "application/json": { + "examples": { + "Missing status": { + "value": { + "status": [ + "This field is required." + ] + } + }, + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Reason too long": { + "value": { + "reason": [ + "Ensure this field has no more than 1000 characters." + ] + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "The key cannot update users for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No (non-deleted) user with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/businesses/create/": { + "post": { + "summary": "Create business", + "description": "Pre-create a [business](/entities/businesses) *without* a KYB session. All fields are optional, but you should always send `vendor_data` — without it the record cannot be linked to sessions or transactions. When provided, `vendor_data` must be unique among non-deleted businesses (exact match; conflicts return 400). Not idempotent.", + "operationId": "create_business", + "x-didit-sdk": { + "resource": "businesses", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your unique identifier for this business (free-form string, NOT a UUID). Optional but strongly recommended. When provided, must be unique among non-deleted businesses for the application; matched exactly as sent." + }, + "display_name": { + "type": "string", + "nullable": true, + "maxLength": 255, + "description": "Friendly display name shown in the console (takes precedence over `legal_name` for UI display)." + }, + "legal_name": { + "type": "string", + "nullable": true, + "maxLength": 255, + "description": "Official legal name of the company." + }, + "registration_number": { + "type": "string", + "nullable": true, + "maxLength": 100, + "description": "Company registration or incorporation number." + }, + "country_code": { + "type": "string", + "nullable": true, + "maxLength": 10, + "description": "Country of incorporation (ISO 3166-1 alpha-2, e.g. `GB`, `US`)." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "default": "ACTIVE", + "description": "Initial lifecycle status. Defaults to `ACTIVE`." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Arbitrary JSON object you attach to the business. Defaults to `{}`." + } + } + }, + "examples": { + "Basic": { + "summary": "Recommended minimum", + "value": { + "vendor_data": "company-456", + "legal_name": "New Corp Ltd", + "country_code": "US" + } + }, + "Full": { + "summary": "All fields", + "value": { + "vendor_data": "company-789", + "display_name": "New Corp", + "legal_name": "New Corp International Ltd", + "registration_number": "98765432", + "country_code": "DE", + "status": "ACTIVE", + "metadata": { + "industry": "fintech", + "tier": "enterprise" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/businesses/create/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vendor_data\": \"company-456\", \"legal_name\": \"New Corp Ltd\", \"country_code\": \"US\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.businesses.create(\n vendor_data=\"company-456\",\n legal_name=\"New Corp Ltd\",\n country_code=\"US\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.businesses.create({\n vendor_data: \"company-456\",\n legal_name: \"New Corp Ltd\",\n country_code: \"US\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.Create(ctx, didit.BusinessesCreateParams{\n\tVendorData: didit.String(\"company-456\"),\n\tLegalName: didit.String(\"New Corp Ltd\"),\n\tCountryCode: didit.String(\"US\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BusinessesCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.businesses().create(BusinessesCreateParams.builder()\n .vendorData(\"company-456\")\n .legalName(\"New Corp Ltd\")\n .countryCode(\"US\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->create([\n 'vendor_data' => 'company-456',\n 'legal_name' => 'New Corp Ltd',\n 'country_code' => 'US',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.businesses.create(\n vendor_data: \"company-456\",\n legal_name: \"New Corp Ltd\",\n country_code: \"US\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.CreateAsync(new BusinessesCreateParams\n{\n VendorData = \"company-456\",\n LegalName = \"New Corp Ltd\",\n CountryCode = \"US\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Business created. Full business record returned (same shape as Get Business).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDetailItem" + }, + "examples": { + "Created": { + "value": { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-456", + "display_name": null, + "legal_name": "New Corp Ltd", + "registration_number": null, + "country_code": "US", + "effective_name": "New Corp Ltd", + "status": "ACTIVE", + "session_count": 0, + "approved_count": 0, + "declined_count": 0, + "in_review_count": 0, + "features": {}, + "features_list": [], + "last_session_at": null, + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": null, + "tags": [], + "created_at": "2026-03-15T10:30:00Z", + "metadata": {}, + "updated_at": "2026-03-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Invalid body — typically a duplicate `vendor_data`, an unknown `status`, or malformed `metadata`.", + "content": { + "application/json": { + "examples": { + "Duplicate": { + "value": { + "vendor_data": [ + "A business with this vendor_data already exists." + ] + } + }, + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Bad metadata": { + "value": { + "metadata": [ + "Metadata must be a JSON object." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot create businesses for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/users/create/": { + "post": { + "summary": "Create user", + "description": "Create a [user](/entities/users) by `vendor_data`, before or without a verification session. `vendor_data` must be unique among the application's users (a duplicate returns `400`). Not idempotent.\n\n**Contact details:** `email` and `phone_number` are stored in `metadata.contact`, and `country` in `metadata.address`. When you send `first_name` or `last_name` without `full_name`, Didit joins them into `full_name`.\n\n**Verification link:** with `send_verification_link: true`, Didit also creates a verification session for the person on `workflow_id` and emails them the link at `email`. The session runs the same checks as [Create Session](/sessions-api/create-session), such as the blocklist and your credit balance. The person and the session are created together or not at all: if any check fails, nothing is written and no email is sent. The response's `verification_link` describes the session, and is `null` when you don't ask for a link.\n\n**API key access:** a scoped API key needs `users` write. With `send_verification_link: true` it also needs `sessions` write, or the call answers `403` before anything is written. Full-access keys are unaffected. See [API keys](/console/api-keys).", + "operationId": "create_user", + "x-didit-sdk": { + "resource": "users", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "vendor_data" + ], + "properties": { + "vendor_data": { + "type": "string", + "description": "Your unique identifier for this user (free-form string, NOT a UUID). Must be unique among the application's users; matched exactly as sent." + }, + "full_name": { + "type": "string", + "nullable": true, + "maxLength": 512, + "description": "Full legal name of the user." + }, + "display_name": { + "type": "string", + "nullable": true, + "description": "Friendly display name shown in the console (takes precedence over `full_name` for UI display).", + "maxLength": 255 + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth in `YYYY-MM-DD` format." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "default": "ACTIVE", + "description": "Initial lifecycle status. Defaults to `ACTIVE`." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Arbitrary JSON object you attach to the user. Defaults to `{}`." + }, + "approved_emails": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Pre-trusted email addresses for this user, e.g. `[\"john@example.com\"]`." + }, + "approved_phones": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Pre-trusted phone numbers for this user, e.g. `[\"+14155551234\"]`." + }, + "issuing_states": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Pre-recorded issuing countries (ISO 3166-1 alpha-3), e.g. `[\"USA\"]`." + }, + "first_name": { + "type": "string", + "maxLength": 255, + "description": "Given name. When `full_name` is not sent, Didit joins `first_name` and `last_name` with a space into `full_name`. Not returned as a separate field." + }, + "last_name": { + "type": "string", + "maxLength": 255, + "description": "Family name. Joined with `first_name` into `full_name` when `full_name` is not sent. Not returned as a separate field." + }, + "email": { + "type": "string", + "format": "email", + "description": "Contact email, stored in `metadata.contact.email` (it replaces an `email` you send inside `metadata.contact`). Required when `send_verification_link` is `true`: the link is sent to this address. If `metadata.contact` is not a JSON object, the call answers `400` on this field." + }, + "phone_number": { + "type": "string", + "maxLength": 32, + "description": "Contact phone number, stored in `metadata.contact.phone_number`. If `metadata.contact` is not a JSON object, the call answers `400` on this field." + }, + "country": { + "type": "string", + "maxLength": 3, + "description": "Country as an ISO 3166 code, alpha-2 or alpha-3 (`ES` or `ESP`). Stored uppercased in `metadata.address.country`. If `metadata.address` is not a JSON object, the call answers `400` on this field." + }, + "send_verification_link": { + "type": "boolean", + "default": false, + "description": "When `true`, Didit creates a verification session for the person on `workflow_id` and emails them the link at `email`. Requires `email` and `workflow_id`. A scoped API key also needs `sessions` write." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "The workflow of the verification session. Required when `send_verification_link` is `true`. It must verify a person: a Business Verification (KYB) workflow is refused. Ignored otherwise." + }, + "language": { + "type": "string", + "maxLength": 10, + "description": "Language of the verification email, as a [supported language](/integration/supported-languages) code. English when omitted. Ignored when `send_verification_link` is not `true`." + } + } + }, + "examples": { + "Basic": { + "summary": "Create with basic info", + "value": { + "vendor_data": "user-abc-123", + "full_name": "John Doe" + } + }, + "Full": { + "summary": "All fields", + "value": { + "vendor_data": "user-abc-456", + "full_name": "Jane Smith", + "display_name": "Jane S.", + "date_of_birth": "1990-05-15", + "status": "ACTIVE", + "metadata": { + "tier": "premium", + "source": "api" + }, + "approved_emails": [ + "jane@example.com" + ] + } + }, + "Contact details": { + "summary": "Name, email, phone and country", + "value": { + "vendor_data": "user-abc-789", + "first_name": "Julia", + "last_name": "Martin", + "email": "julia.martin@example.com", + "phone_number": "+34600000000", + "country": "ES" + } + }, + "With verification link": { + "summary": "Create the person and email them a verification link", + "value": { + "vendor_data": "user-abc-789", + "first_name": "Julia", + "last_name": "Martin", + "email": "julia.martin@example.com", + "send_verification_link": true, + "workflow_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "language": "es" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/users/create/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vendor_data\": \"user-abc-123\", \"full_name\": \"John Doe\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.users.create(\n vendor_data=\"user-abc-123\",\n full_name=\"John Doe\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.users.create({\n vendor_data: \"user-abc-123\",\n full_name: \"John Doe\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Users.Create(ctx, didit.UsersCreateParams{\n\tVendorData: \"user-abc-123\",\n\tFullName: didit.String(\"John Doe\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.UsersCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.users().create(UsersCreateParams.builder()\n .vendorData(\"user-abc-123\")\n .fullName(\"John Doe\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->create([\n 'vendor_data' => 'user-abc-123',\n 'full_name' => 'John Doe',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.users.create(\n vendor_data: \"user-abc-123\",\n full_name: \"John Doe\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Users.CreateAsync(new UsersCreateParams\n{\n VendorData = \"user-abc-123\",\n FullName = \"John Doe\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "User created. The full user record (same shape as Get User), plus `verification_link`: the verification session Didit emailed when `send_verification_link` was `true`, otherwise `null`.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UserDetailItem" + }, + { + "type": "object", + "required": [ + "verification_link" + ], + "properties": { + "verification_link": { + "type": "object", + "nullable": true, + "description": "The verification session Didit created and emailed to the person. Always present: `null` when `send_verification_link` was not `true`.", + "required": [ + "session_id", + "session_number", + "status", + "url", + "workflow_id", + "email" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "ID of the verification session." + }, + "session_number": { + "type": "integer", + "description": "Sequential number of the session inside your application." + }, + "status": { + "type": "string", + "description": "Status of the session, `Not Started` for a session that was just created." + }, + "url": { + "type": "string", + "format": "uri", + "description": "The verification link Didit emailed to the person." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The workflow the session runs." + }, + "email": { + "type": "string", + "format": "email", + "description": "The address the link was sent to." + } + } + } + } + } + ] + }, + "examples": { + "Created": { + "value": { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": null, + "full_name": "John Doe", + "date_of_birth": null, + "effective_name": "John Doe", + "status": "ACTIVE", + "metadata": {}, + "portrait_image_url": null, + "session_count": 0, + "approved_count": 0, + "declined_count": 0, + "in_review_count": 0, + "issuing_states": [], + "approved_emails": [], + "approved_phones": [], + "features": {}, + "features_list": [], + "last_session_at": null, + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": null, + "tags": [], + "comments": [], + "created_at": "2025-06-15T10:30:00Z", + "updated_at": "2025-06-15T10:30:00Z", + "verification_link": null + } + }, + "Created with verification link": { + "value": { + "didit_internal_id": "9b2f6c1e-3d4a-4e5f-8a7b-1c2d3e4f5a6b", + "vendor_data": "user-abc-789", + "display_name": null, + "full_name": "Julia Martin", + "date_of_birth": null, + "effective_name": "Julia Martin", + "status": "ACTIVE", + "metadata": { + "contact": { + "email": "julia.martin@example.com" + } + }, + "portrait_image_url": null, + "session_count": 1, + "approved_count": 0, + "declined_count": 0, + "in_review_count": 0, + "issuing_states": [], + "approved_emails": [], + "approved_phones": [], + "features": {}, + "features_list": [], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-15T10:30:00Z", + "tags": [], + "comments": [], + "created_at": "2025-06-15T10:30:00Z", + "updated_at": "2025-06-15T10:30:00Z", + "verification_link": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa7", + "session_number": 43762, + "status": "Not Started", + "url": "https://verify.didit.me/en/session/3FaJ9wLqX2Mz", + "workflow_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "email": "julia.martin@example.com" + } + } + } + } + } + } + }, + "400": { + "description": "Invalid body, keyed by field: typically a duplicate `vendor_data`, a missing required field or a malformed value. With `send_verification_link: true`, also a missing `email` or `workflow_id`, an unknown or Business Verification (KYB) workflow, an unsupported `language` (keyed `contact_details.email_lang`), a blocklisted `vendor_data`, or too few credits to start the session. Nothing is written.", + "content": { + "application/json": { + "examples": { + "Duplicate": { + "value": { + "vendor_data": [ + "A user with this vendor_data already exists." + ] + } + }, + "Missing vendor_data": { + "value": { + "vendor_data": [ + "This field is required." + ] + } + }, + "Bad metadata": { + "value": { + "metadata": [ + "Metadata must be a JSON object." + ] + } + }, + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Bad country": { + "value": { + "country": [ + "Use an ISO 3166 country code, like ES or ESP." + ] + } + }, + "Contact not an object": { + "value": { + "email": [ + "metadata.contact must be a JSON object to store this value." + ] + } + }, + "Address not an object": { + "value": { + "country": [ + "metadata.address must be a JSON object to store this value." + ] + } + }, + "Link without email or workflow": { + "value": { + "email": [ + "An email address is required to send a verification link." + ], + "workflow_id": [ + "Pick the workflow to send a verification link for." + ] + } + }, + "Unknown workflow": { + "value": { + "workflow_id": [ + "Invalid workflow_id." + ] + } + }, + "Business workflow": { + "value": { + "workflow_id": [ + "This workflow verifies a business. Pick a workflow that verifies a person to send them a verification link." + ] + } + }, + "Blocklisted vendor_data": { + "value": { + "vendor_data": "This user or business has been blocked and cannot create new verification sessions." + } + }, + "Insufficient credits": { + "value": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "Invalid or revoked API key, or the key cannot create users for this application. A scoped API key needs `users` write, and also `sessions` write when `send_verification_link` is `true`; nothing is written. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "No sessions write": { + "summary": "Scoped key asks for a verification link without sessions write", + "value": { + "detail": "This API key has no write access to sessions." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-users/by-id/{didit_internal_id}/faces/upload/": { + "post": { + "summary": "Upload user face", + "description": "Attach a trusted imported face image to an existing User profile. Use this after creating or retrieving a User by `vendor_data` and reading its `didit_internal_id`. The uploaded face is stored on the User profile and indexed for duplicate detection and face search. It is not added to a face blocklist.", + "operationId": "upload_user_face", + "x-didit-sdk": { + "resource": "users", + "method": "upload_face", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID that owns the application." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID that owns the User profile." + }, + { + "name": "didit_internal_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Didit's internal User UUID, returned as `didit_internal_id` by `GET /v3/users/{vendor_data}/` and `POST /v3/users/create/`." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "image" + ], + "properties": { + "image": { + "type": "string", + "format": "byte", + "description": "Raw base64-encoded JPG or PNG face image. Do not include a `data:image/...;base64,` prefix. Max decoded size: 2 MB." + }, + "comment": { + "type": "string", + "description": "Optional operator-visible note for the uploaded face.", + "default": "" + } + } + }, + "examples": { + "Imported face": { + "summary": "Attach a face imported from a previous provider", + "value": { + "image": "/9j/4AAQSkZJRgABAQEA...", + "comment": "Imported from previous KYC provider" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/vendor-users/by-id/f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418/faces/upload/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"image\": \"/9j/4AAQSkZJRgABAQEA...\", \"comment\": \"Imported from previous KYC provider\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.users.upload_face(\n \"11111111-2222-3333-4444-555555555555\",\n image=\"/9j/4AAQSkZJRgABAQEA...\",\n comment=\"Imported from previous KYC provider\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.users.uploadFace(\"11111111-2222-3333-4444-555555555555\", {\n image: \"/9j/4AAQSkZJRgABAQEA...\",\n comment: \"Imported from previous KYC provider\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Users.UploadFace(ctx, \"11111111-2222-3333-4444-555555555555\", didit.UsersUploadFaceParams{\n\tImage: \"/9j/4AAQSkZJRgABAQEA...\",\n\tComment: didit.String(\"Imported from previous KYC provider\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.UsersUploadFaceParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.users().uploadFace(\"11111111-2222-3333-4444-555555555555\", UsersUploadFaceParams.builder()\n .image(\"/9j/4AAQSkZJRgABAQEA...\")\n .comment(\"Imported from previous KYC provider\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->uploadFace('11111111-2222-3333-4444-555555555555', [\n 'image' => '/9j/4AAQSkZJRgABAQEA...',\n 'comment' => 'Imported from previous KYC provider',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.users.upload_face(\n \"11111111-2222-3333-4444-555555555555\",\n image: \"/9j/4AAQSkZJRgABAQEA...\",\n comment: \"Imported from previous KYC provider\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Users.UploadFaceAsync(\"11111111-2222-3333-4444-555555555555\", new UsersUploadFaceParams\n{\n Image = \"/9j/4AAQSkZJRgABAQEA...\",\n Comment = \"Imported from previous KYC provider\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Face uploaded and attached to the User profile.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "uuid", + "image_url", + "comment", + "uploaded_by", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the uploaded User face record." + }, + "image_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Signed URL for the uploaded face image." + }, + "comment": { + "type": "string", + "nullable": true, + "description": "Comment supplied when the face was uploaded." + }, + "uploaded_by": { + "type": "string", + "nullable": true, + "description": "Email or actor identifier of the uploader when available." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Upload timestamp." + } + } + }, + "examples": { + "Uploaded": { + "value": { + "uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "image_url": "https://signed-url.example/face.jpg", + "comment": "Imported from previous KYC provider", + "uploaded_by": "api", + "created_at": "2026-05-18T12:00:00Z" + } + } + } + } + } + }, + "400": { + "description": "Invalid request body or image. Common causes: invalid base64, invalid image, no detectable face, image over 2 MB, missing embedding, or the User already has 5 uploaded faces.", + "content": { + "application/json": { + "examples": { + "No face": { + "value": { + "image": "No face detected in the image. Please upload a clear photo with a visible face." + } + }, + "Too many faces": { + "value": { + "detail": "Maximum of 5 faces per user reached." + } + }, + "Too large": { + "value": { + "image": "Image exceeds maximum size of 2 MB." + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "examples": { + "Missing key": { + "value": { + "detail": "Authentication credentials were not provided." + } + } + } + } + } + }, + "403": { + "description": "The credential is valid but cannot write User profiles for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No User exists for that `didit_internal_id` in the application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/update-data/": { + "patch": { + "summary": "Update KYC data extracted from the ID document on a verification session", + "description": "Correct the identity fields that were extracted (OCR / NFC) from the user's ID document — misread names, dates, document numbers, and so on — after the verification has finished. Typical callers are a compliance analyst working from your own back office, or an automated reconciliation job. Every field in the body is optional: only the keys you send are written, everything else is left untouched (`PATCH` semantics).\n\nThe session must belong to your application and be in `Approved`, `Declined`, `In Review`, or `Kyc Expired` status. Sessions in any other state (e.g. still `In Progress`) return `404` with `\"No Session matches the given query.\"` — distinct from the `\"Not found.\"` returned for an unknown or deleted `sessionId`. The session must also carry an ID-verification (KYC) record: sessions whose workflow never ran an ID-verification step return `404` (`\"The session does not have a KYC.\"`). For graph workflows with multiple ID-verification steps, target a specific step with `node_id` (query parameter or body field); when omitted, the most recent KYC record is patched.\n\nTwo derived fields are recomputed on every call: `full_name` (from `first_name` + `last_name`) and `issuing_state_name` (from `issuing_state`). If the free-text `address` changes, the server may also re-geocode it and refresh `parsed_address` (skipped for sandbox sessions). `extra_fields` and `parsed_address` are merged key-by-key rather than replaced — see the per-field descriptions for the `null`-to-delete semantics.\n\nSubmitting at least one valid field fires the `data.updated` [webhook](/integration/webhooks) (trigger `manual_review`) — even when the submitted values equal the stored ones. When values actually change, a `KYC_DATA_UPDATED` activity entry recording the changed fields with previous and new values is appended to the session's `reviews`, visible in [Get Decision](#get-/v3/session/-sessionId-/decision/) and the console activity timeline. The session's status and decision are **not** re-evaluated — use [Update Status](#patch-/v3/session/-sessionId-/update-status/) to change the outcome. An empty JSON body (`{}`) is accepted and simply returns the current KYC data without firing the webhook.\n\nAccepts an API key or a console user token; either way the credential needs the `write:sessions` permission. Authentication and permission failures both return `403` — this API never responds `401`. Note that the session lookup runs before credential checks, so an unknown `sessionId` returns `404` even with missing credentials. This endpoint has a dedicated rate limit of 10 requests per minute per API key (invalid requests count too); the shared write budget of 300 requests per minute also applies.", + "operationId": "patch_v3_session_update_data", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_data", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session whose extracted document data you are correcting. Business (KYB) session IDs are not valid here — for company data use [Update KYB Company Data](#patch-/v3/session/-sessionId-/kyb/-companyUuid-/update-data/).", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node whose KYC record to patch, for graph workflows with multiple ID-verification steps (also accepted as a `node_id` body field). When omitted, the most recent KYC record on the session is patched. If no KYC exists for the given node, the call returns `404` (`\"The session does not have a KYC for node ''.\"`).", + "schema": { + "type": "string", + "example": "feature_ocr" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "All fields optional — send only what you want to change.", + "properties": { + "document_type": { + "type": "string", + "description": "Document type code. Accepts the short codes listed in the enum, plus six long-form aliases normalized server-side before validation: `Passport` → `P`, `Identity Card` → `ID`, `Driver's License` → `DL`, `Residence Permit` → `RP`, `Health Insurance Card` → `HIC`, `Tax Card` → `TC`. Any other value (including `PASSPORT` in caps) returns `400`.", + "enum": [ + "P", + "DL", + "ID", + "RP", + "SSC", + "HIC", + "WP", + "TC", + "VISA", + "PSC", + "BC", + "OTHER" + ], + "nullable": true, + "example": "P" + }, + "document_subtype": { + "type": "string", + "maxLength": 128, + "nullable": true, + "description": "Document subtype from the document registry, including the region prefix when the registry distinguishes regional variants." + }, + "document_number": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Document number as printed on the ID.", + "example": "PAB123456" + }, + "personal_number": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Personal / national identification number where the document carries one (e.g. Spanish DNI on a passport).", + "example": "99999999R" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth in `YYYY-MM-DD`. Any other format returns `400`.", + "example": "1980-01-01" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Issue date of the document in `YYYY-MM-DD`." + }, + "expiration_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Expiration date of the document in `YYYY-MM-DD`." + }, + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Issuing country as an ISO 3166-1 alpha-3 code. Case-insensitive (`esp` is stored as `ESP`); the human-readable `issuing_state_name` shown in [Get Decision](#get-/v3/session/-sessionId-/decision/) is recomputed from this value. Invalid codes return `400`.", + "example": "ESP" + }, + "first_name": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Given name(s). The stored `full_name` is recomputed as `first_name + ' ' + last_name` on every call to this endpoint.", + "example": "Carmen" + }, + "last_name": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Family name(s). Also feeds the recomputed `full_name`.", + "example": "Espanola Espanola" + }, + "gender": { + "type": "string", + "enum": [ + "M", + "F", + "U" + ], + "nullable": true, + "description": "Sex as printed on the document: `M` (male), `F` (female), or `U` (unknown). Any other value — including `X` — returns `400`." + }, + "address": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it synchronously before responding — the 200 body already reflects the refreshed `parsed_address`. The re-geocode also requires `issuing_state` to be present on the KYC record." + }, + "place_of_birth": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Place of birth as printed on the document." + }, + "nationality": { + "type": "string", + "maxLength": 32, + "nullable": true, + "description": "Nationality as printed on the document. Free text up to 32 characters — typically an ISO 3166-1 alpha-3 code (e.g. `ESP`), but not validated as one." + }, + "marital_status": { + "type": "string", + "enum": [ + "SINGLE", + "MARRIED", + "DIVORCED", + "WIDOWED", + "UNKNOWN" + ], + "description": "Marital status where the document carries it. Any other value returns `400`." + }, + "extra_fields": { + "type": "object", + "additionalProperties": true, + "nullable": true, + "description": "Per-key overrides for document-specific extra fields (e.g. `blood_group`, `dl_categories`, `mrz_string`). Keys are merged into the existing override map: send a key with a value to set it, or with `null` / `\"\"` to remove that key from the merged view. Send the whole field as `null` to clear all previously applied overrides. Must be a JSON object, otherwise `400`. The response always returns the merged view (OCR-extracted values with your overrides applied)." + }, + "parsed_address": { + "type": "object", + "nullable": true, + "description": "Structured address. Only these nested keys are accepted: `address_type`, `city`, `region`, `street_1`, `street_2`, `postal_code`, `country` — any other key returns `400`. Per-key semantics: a value sets the key, `null` removes it. `formatted_address` is recomputed server-side by joining `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country` and is returned in the response — you cannot set it directly.", + "properties": { + "address_type": { + "type": "string", + "nullable": true, + "description": "Free-form address classification (e.g. `home`)." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Madrid" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State / province / autonomous community." + }, + "street_1": { + "type": "string", + "nullable": true, + "example": "Avda de Madrid 34" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "28013" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`.", + "example": "ES" + } + } + } + } + }, + "examples": { + "Correct name and DOB": { + "summary": "Fix OCR-misread name, date of birth, and structured address", + "value": { + "first_name": "Carmen", + "last_name": "Espanola Espanola", + "date_of_birth": "1980-01-01", + "issuing_state": "ESP", + "marital_status": "MARRIED", + "extra_fields": { + "blood_group": "O+" + }, + "parsed_address": { + "street_1": "Avda de Madrid 34", + "city": "Madrid", + "postal_code": "28013", + "country": "ES" + } + } + }, + "Remove an extra field": { + "summary": "Delete one extra-field key, keep the rest", + "value": { + "extra_fields": { + "blood_group": null + } + } + }, + "Long-form document type": { + "summary": "Long-form alias is normalized to the short code (here P)", + "value": { + "document_type": "Passport" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "KYC data updated. Returns the **full current KYC record** (every updatable field with its stored value, plus the merged `extra_fields` and the current `parsed_address`) — not just the fields you submitted. The `data.updated` webhook fires when at least one valid field was in the payload.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "document_type": { + "type": "string", + "description": "Document type code. Accepts the short codes listed in the enum, plus six long-form aliases normalized server-side before validation: `Passport` → `P`, `Identity Card` → `ID`, `Driver's License` → `DL`, `Residence Permit` → `RP`, `Health Insurance Card` → `HIC`, `Tax Card` → `TC`. Any other value (including `PASSPORT` in caps) returns `400`.", + "enum": [ + "P", + "DL", + "ID", + "RP", + "SSC", + "HIC", + "WP", + "TC", + "VISA", + "PSC", + "BC", + "OTHER" + ], + "nullable": true, + "example": "P" + }, + "document_subtype": { + "type": "string", + "maxLength": 128, + "nullable": true, + "description": "Document subtype from the document registry, including the region prefix when the registry distinguishes regional variants." + }, + "document_number": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Document number as printed on the ID.", + "example": "PAB123456" + }, + "personal_number": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Personal / national identification number where the document carries one (e.g. Spanish DNI on a passport).", + "example": "99999999R" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth in `YYYY-MM-DD`. Any other format returns `400`.", + "example": "1980-01-01" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Issue date of the document in `YYYY-MM-DD`." + }, + "expiration_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Expiration date of the document in `YYYY-MM-DD`." + }, + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Issuing country as an ISO 3166-1 alpha-3 code. Case-insensitive (`esp` is stored as `ESP`); the human-readable `issuing_state_name` shown in [Get Decision](#get-/v3/session/-sessionId-/decision/) is recomputed from this value. Invalid codes return `400`.", + "example": "ESP" + }, + "first_name": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Given name(s). The stored `full_name` is recomputed as `first_name + ' ' + last_name` on every call to this endpoint.", + "example": "Carmen" + }, + "last_name": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Family name(s). Also feeds the recomputed `full_name`.", + "example": "Espanola Espanola" + }, + "gender": { + "type": "string", + "enum": [ + "M", + "F", + "U" + ], + "nullable": true, + "description": "Sex as printed on the document: `M` (male), `F` (female), or `U` (unknown). Any other value — including `X` — returns `400`." + }, + "address": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it in the background of the same request and may overwrite `parsed_address` with the geocoder result." + }, + "place_of_birth": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Place of birth as printed on the document." + }, + "nationality": { + "type": "string", + "maxLength": 32, + "nullable": true, + "description": "Nationality as printed on the document. Free text up to 32 characters — typically an ISO 3166-1 alpha-3 code (e.g. `ESP`), but not validated as one." + }, + "marital_status": { + "type": "string", + "enum": [ + "SINGLE", + "MARRIED", + "DIVORCED", + "WIDOWED", + "UNKNOWN" + ], + "description": "Marital status where the document carries it. Any other value returns `400`." + }, + "extra_fields": { + "type": "object", + "additionalProperties": true, + "nullable": true, + "description": "Merged view of the document's extra fields: the OCR-extracted values with all operator overrides applied. Keys removed via a `null` override are absent from this map." + }, + "parsed_address": { + "type": "object", + "nullable": true, + "description": "Current structured address (with internal bookkeeping keys stripped), or `null` when none exists. Includes the recomputed `formatted_address` alongside the seven editable keys.", + "properties": { + "address_type": { + "type": "string", + "nullable": true, + "description": "Free-form address classification (e.g. `home`)." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Madrid" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State / province / autonomous community." + }, + "street_1": { + "type": "string", + "nullable": true, + "example": "Avda de Madrid 34" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "28013" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`.", + "example": "ES" + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "Joined, human-readable form of the structured address. Recomputed server-side after every `parsed_address` edit.", + "example": "Avda de Madrid 34, Madrid, 28013, ES" + } + } + } + } + }, + "examples": { + "Updated KYC": { + "summary": "Full KYC record after the correction", + "value": { + "document_type": "P", + "document_subtype": null, + "document_number": "PAB123456", + "personal_number": "99999999R", + "date_of_birth": "1980-01-01", + "date_of_issue": "2021-02-09", + "expiration_date": "2031-02-08", + "issuing_state": "ESP", + "first_name": "Carmen", + "last_name": "Espanola Espanola", + "gender": "F", + "address": "AVDA DE MADRID 34, MADRID, MADRID 28013", + "place_of_birth": "MADRID", + "nationality": "ESP", + "marital_status": "MARRIED", + "extra_fields": { + "mrz_string": "P\": [\"\"]}`; errors on nested `parsed_address` / `extra_fields` keys use an object value: `{\"parsed_address\": {\"\": \"\"}}`.", + "content": { + "application/json": { + "examples": { + "Invalid gender": { + "summary": "gender must be M, F, or U", + "value": { + "gender": [ + "\"X\" is not a valid choice." + ] + } + }, + "Invalid document type": { + "summary": "Not a short code or long-form alias", + "value": { + "document_type": [ + "\"PASSPORT\" is not a valid choice." + ] + } + }, + "Invalid issuing state": { + "summary": "issuing_state is not ISO 3166-1 alpha-3", + "value": { + "issuing_state": [ + "Invalid ISO 3166-1 alpha-3 country code for issuing_state." + ] + } + }, + "Invalid marital status": { + "summary": "marital_status not in the enum", + "value": { + "marital_status": [ + "\"ENGAGED\" is not a valid choice." + ] + } + }, + "Bad date format": { + "summary": "Dates must be YYYY-MM-DD", + "value": { + "date_of_birth": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + }, + "Unsupported parsed_address key": { + "summary": "Only the seven documented keys are allowed", + "value": { + "parsed_address": { + "zipcode": "Unsupported key. Allowed keys: address_type, city, region, street_1, street_2, postal_code, country" + } + } + }, + "Invalid parsed_address country": { + "summary": "Nested country must be alpha-2, not alpha-3", + "value": { + "parsed_address": { + "country": "Invalid ISO 3166-1 alpha-2 country code." + } + } + }, + "extra_fields not an object": { + "summary": "extra_fields must be a JSON object", + "value": { + "extra_fields": [ + "This field must be a JSON object." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Credential lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown session, session in a non-editable state, or no KYC record to patch. Each case has a distinct `detail` message (see examples). The unknown-session lookup runs before credential checks.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "summary": "No session with this ID in your application (or it was deleted)", + "value": { + "detail": "Not found." + } + }, + "Wrong session state": { + "summary": "Session exists but is not Approved / Declined / In Review / Kyc Expired", + "value": { + "detail": "No Session matches the given query." + } + }, + "No KYC record": { + "summary": "The workflow never ran an ID-verification step", + "value": { + "detail": "The session does not have a KYC." + } + }, + "No KYC for node": { + "summary": "node_id given but no KYC exists for it", + "value": { + "detail": "The session does not have a KYC for node 'feature_missing'." + } + } + } + } + } + }, + "429": { + "description": "Dedicated rate limit exceeded — 10 KYC data updates per minute per API key (rejected requests count toward the budget). The shared 300/min write budget can also trigger with its own message. Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "KYC update budget exhausted", + "value": { + "detail": "Session KYC data update rate limit exceeded. You can make up to 10 requests per minute." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-data/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"first_name\": \"Carmen\",\n \"last_name\": \"Espanola Espanola\",\n \"date_of_birth\": \"1980-01-01\",\n \"issuing_state\": \"ESP\",\n \"parsed_address\": {\n \"street_1\": \"Avda de Madrid 34\",\n \"city\": \"Madrid\",\n \"postal_code\": \"28013\",\n \"country\": \"ES\"\n }\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_data(\n \"11111111-2222-3333-4444-555555555555\",\n date_of_birth=\"1980-01-01\",\n issuing_state=\"ESP\",\n first_name=\"Carmen\",\n last_name=\"Espanola Espanola\",\n marital_status=\"MARRIED\",\n extra_fields={\"blood_group\": \"O+\"},\n parsed_address={\n \"city\": \"Madrid\",\n \"street_1\": \"Avda de Madrid 34\",\n \"postal_code\": \"28013\",\n \"country\": \"ES\",\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateData(\"11111111-2222-3333-4444-555555555555\", {\n date_of_birth: \"1980-01-01\",\n issuing_state: \"ESP\",\n first_name: \"Carmen\",\n last_name: \"Espanola Espanola\",\n marital_status: \"MARRIED\",\n extra_fields: { blood_group: \"O+\" },\n parsed_address: {\n city: \"Madrid\",\n street_1: \"Avda de Madrid 34\",\n postal_code: \"28013\",\n country: \"ES\",\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateData(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsUpdateDataParams{\n\tDateOfBirth: didit.String(\"1980-01-01\"),\n\tIssuingState: didit.String(\"ESP\"),\n\tFirstName: didit.String(\"Carmen\"),\n\tLastName: didit.String(\"Espanola Espanola\"),\n\tMaritalStatus: didit.Some(didit.SessionsUpdateDataParamsMaritalStatus(\"MARRIED\")),\n\tExtraFields: didit.Some(map[string]any{\"blood_group\": \"O+\"}),\n\tParsedAddress: didit.Some(didit.SessionsUpdateDataParamsParsedAddress{\n\t\tCity: didit.String(\"Madrid\"),\n\t\tStreet1: didit.String(\"Avda de Madrid 34\"),\n\t\tPostalCode: didit.String(\"28013\"),\n\t\tCountry: didit.String(\"ES\"),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SessionsUpdateDataParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateData(\"11111111-2222-3333-4444-555555555555\", SessionsUpdateDataParams.builder()\n .dateOfBirth(LocalDate.parse(\"1980-01-01\"))\n .issuingState(\"ESP\")\n .firstName(\"Carmen\")\n .lastName(\"Espanola Espanola\")\n .maritalStatus(SessionsUpdateDataParams.MaritalStatus.of(\"MARRIED\"))\n .extraFields(Map.of(\"blood_group\", \"O+\"))\n .parsedAddress(SessionsUpdateDataParams.ParsedAddress.builder()\n .city(\"Madrid\")\n .street1(\"Avda de Madrid 34\")\n .postalCode(\"28013\")\n .country(\"ES\")\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateData('11111111-2222-3333-4444-555555555555', [\n 'date_of_birth' => '1980-01-01',\n 'issuing_state' => 'ESP',\n 'first_name' => 'Carmen',\n 'last_name' => 'Espanola Espanola',\n 'marital_status' => 'MARRIED',\n 'extra_fields' => ['blood_group' => 'O+'],\n 'parsed_address' => [\n 'city' => 'Madrid',\n 'street_1' => 'Avda de Madrid 34',\n 'postal_code' => '28013',\n 'country' => 'ES',\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_data(\n \"11111111-2222-3333-4444-555555555555\",\n date_of_birth: \"1980-01-01\",\n issuing_state: \"ESP\",\n first_name: \"Carmen\",\n last_name: \"Espanola Espanola\",\n marital_status: \"MARRIED\",\n extra_fields: { blood_group: \"O+\" },\n parsed_address: {\n city: \"Madrid\",\n street_1: \"Avda de Madrid 34\",\n postal_code: \"28013\",\n country: \"ES\",\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateDataAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsUpdateDataParams\n{\n DateOfBirth = \"1980-01-01\",\n IssuingState = \"ESP\",\n FirstName = \"Carmen\",\n LastName = \"Espanola Espanola\",\n MaritalStatus = new SessionsUpdateDataParamsMaritalStatus(\"MARRIED\"),\n ExtraFields = new Dictionary { [\"blood_group\"] = \"O+\" },\n ParsedAddress = new SessionsUpdateDataParamsParsedAddress\n {\n City = \"Madrid\",\n Street1 = \"Avda de Madrid 34\",\n PostalCode = \"28013\",\n Country = \"ES\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/features/{nodeId}/update-status/": { + "patch": { + "summary": "Update an individual feature (step) status", + "description": "Set the status of a single verification feature (step) within a session. Target the feature by its workflow graph `nodeId`. On non-graph workflows, whose steps have no node, use `default` and also pass `feature_type` to say which step you mean.\n\nWorks for both User Verification (KYC) and Business Verification (KYB) sessions and every step type: ID/OCR, Liveness, Face Match, NFC, AML, Proof of Address, Phone, Email, Database Validation, Questionnaire, and the KYB registry / documents / key-people checks. This endpoint is **not** available for standalone API sessions (`session_type = API`).\n\n`new_status` must be one of `Approved`, `Declined`, or `In Review`, and must differ from the feature's current status.\n\n**When the override applies.** Without `defer_until_flow_finishes` (or with `false`) the override applies at once, as it always did; when the person is still in the flow it is recorded as `forced`. With `defer_until_flow_finishes: true` while the person is still in the flow (the session is `Not Started`, `In Progress` or `Resubmitted` and has not finished), the override is stored as `pending_until_flow_finishes` and applies when the flow finishes, after the flow reaches its own decision. On a session whose flow already ended it applies at once either way. A waiting override is cancelled instead when the person redoes its step before finishing, when a newer override on the same check replaces it, when the session leaves the flow unfinished (abandoned, expired, or decided mid-flow), or when you cancel it with `POST /v3/session/{sessionId}/features/overrides/{overrideId}/cancel/`. List a session's overrides with `GET /v3/session/{sessionId}/features/overrides/`.\n\n**Effect on the session.** A session that already has a decision keeps it: only the check changes. When the session has no decision yet and its workflow is waiting on exactly this step, applying the override completes the step: the workflow moves on, later steps run, and a status node can set the session decision and fire `status.updated`.\n\n**Webhooks and activity.** A `data.updated` webhook fires when the override applies (at once, or when the flow finishes), never while it waits or when it is cancelled. Every override is recorded in the session's activity as `FEATURE_OVERRIDE_PENDING`, `FEATURE_OVERRIDE_APPLIED` or `FEATURE_OVERRIDE_CANCELLED`, with who made it; the request's entry carries your `comment`.\n\nAuthenticate with your API key (`x-api-key`) or a user authorization header carrying the `write:sessions` privilege; a scoped API key needs write access to decisions.", + "operationId": "patch_v3_session_update_feature_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_feature_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "in": "path", + "name": "nodeId", + "required": true, + "description": "Workflow graph node ID of the feature to update (for example `feature_ocr_1`). Use the literal value `default` for non-graph (single-step) workflows where the feature has no node ID.", + "schema": { + "type": "string", + "example": "feature_ocr_1" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "new_status" + ], + "properties": { + "new_status": { + "type": "string", + "description": "Target status for this feature. Must differ from the current value.", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "example": "Approved" + }, + "comment": { + "type": "string", + "description": "Optional free-text note recorded on the session's review trail for this feature change.", + "example": "Document manually verified by compliance." + }, + "feature_type": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "OCR", + "OCR_BACK", + "NFC", + "LIVENESS", + "FACE", + "AGE_ESTIMATION", + "AGE_VERIFICATION", + "FACE_SEARCH", + "FACE_MATCH", + "IP_ANALYSIS", + "AML", + "PHONE", + "PHONE_VERIFICATION", + "EMAIL", + "EMAIL_VERIFICATION", + "POA", + "PROOF_OF_ADDRESS", + "DATABASE_VALIDATION", + "DATA_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "BANK_VERIFICATION", + "GEOLOCATION" + ], + "description": "Which kind of step to target. Needed on non-graph workflows (`nodeId` = `default`), where every step shares no node; optional otherwise. An unknown value returns `400`.", + "example": "LIVENESS" + }, + "defer_until_flow_finishes": { + "type": "boolean", + "default": false, + "description": "While the person is still in the flow, store the override as `pending_until_flow_finishes` and apply it when the flow finishes, instead of at once. Omitted or `false`, the override applies at once. On a session whose flow already ended it applies at once either way.", + "example": false + } + } + }, + "examples": { + "Apply Now": { + "summary": "Approve the ID step at once", + "value": { + "new_status": "Approved", + "comment": "Document manually verified by compliance." + } + }, + "Wait For The Flow": { + "summary": "Approve the liveness step once the person finishes the flow", + "value": { + "new_status": "Approved", + "comment": "Liveness retried with a reviewer present.", + "defer_until_flow_finishes": true + } + }, + "Non-graph Workflow": { + "summary": "Target the liveness step of a workflow without nodes (nodeId = default)", + "value": { + "new_status": "In Review", + "feature_type": "LIVENESS" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The override was recorded. `new_status` is the check's status as stored: the requested one once the override applied, the unchanged one while it waits for the flow to finish. `override` says which happened.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid" + }, + "node_id": { + "type": "string", + "nullable": true, + "description": "The updated step's workflow node, or `null` for a step without a node (the `default` path in a non-graph workflow).", + "example": "feature_ocr_1" + }, + "feature_type": { + "type": "string", + "description": "Model name of the updated feature (e.g. `KYC`, `Face`, `FaceMatch`, `AML`, `POA`).", + "example": "KYC" + }, + "new_status": { + "type": "string", + "description": "The check's stored status: the requested one once the override applied, the unchanged one while the override waits for the flow to finish.", + "example": "Approved" + }, + "override": { + "$ref": "#/components/schemas/FeatureStatusOverride" + } + }, + "required": [ + "session_id", + "node_id", + "feature_type", + "new_status", + "override" + ] + }, + "examples": { + "Applied": { + "summary": "The override applied at once", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "node_id": "feature_ocr_1", + "feature_type": "KYC", + "new_status": "Approved", + "override": { + "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "node_id": "feature_ocr_1", + "feature_type": "", + "feature_model": "KYC", + "previous_status": "In Review", + "new_status": "Approved", + "state": "applied", + "forced": false, + "reason": "Document manually verified by compliance.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": "2026-10-13T09:15:00Z", + "cancelled_at": null, + "cancel_reason": null + } + } + }, + "Waiting For The Flow": { + "summary": "defer_until_flow_finishes while the person is still in the flow", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "node_id": "feature_liveness_1", + "feature_type": "Face", + "new_status": "Declined", + "override": { + "id": "9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3", + "node_id": "feature_liveness_1", + "feature_type": "", + "feature_model": "Face", + "previous_status": "Declined", + "new_status": "Approved", + "state": "pending_until_flow_finishes", + "forced": false, + "reason": "Liveness retried with a reviewer present.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": null, + "cancelled_at": null, + "cancel_reason": null + } + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when `new_status` is invalid or unchanged, when `feature_type` is unknown, or when the session is a standalone API session.", + "content": { + "application/json": { + "examples": { + "Same Status": { + "summary": "new_status equals the current status", + "value": { + "new_status": [ + "The new status is the same as the current status." + ] + } + }, + "API Session": { + "summary": "Feature status cannot be changed on standalone API sessions", + "value": [ + "Cannot change feature status for API sessions." + ] + }, + "Unknown feature_type": { + "summary": "feature_type is not a known step type", + "value": { + "feature_type": [ + "Unknown feature_type 'SELFIE'." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "API key lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without write access to decisions", + "value": { + "detail": "This API key has no write access to decisions." + } + } + } + } + } + }, + "404": { + "description": "The session was not found, or it has no feature with the given `nodeId`.", + "content": { + "application/json": { + "examples": { + "Session Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Session not found." + } + }, + "Feature Not Found": { + "summary": "No feature for this node_id", + "value": { + "detail": "No feature found with node_id 'feature_ocr_1' in this session." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/features/feature_ocr_1/update-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"new_status\": \"Approved\",\n \"comment\": \"Document manually verified by compliance.\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_feature_status(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_ocr_1\",\n new_status=\"Approved\",\n comment=\"Document manually verified by compliance.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateFeatureStatus(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_ocr_1\",\n {\n new_status: \"Approved\",\n comment: \"Document manually verified by compliance.\",\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateFeatureStatus(ctx, \"11111111-2222-3333-4444-555555555555\", \"feature_ocr_1\", didit.SessionsUpdateFeatureStatusParams{\n\tNewStatus: \"Approved\",\n\tComment: didit.String(\"Document manually verified by compliance.\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsUpdateFeatureStatusParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateFeatureStatus(\"11111111-2222-3333-4444-555555555555\", \"feature_ocr_1\", SessionsUpdateFeatureStatusParams.builder()\n .newStatus(SessionsUpdateFeatureStatusParams.NewStatus.of(\"Approved\"))\n .comment(\"Document manually verified by compliance.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateFeatureStatus('11111111-2222-3333-4444-555555555555', 'feature_ocr_1', [\n 'new_status' => 'Approved',\n 'comment' => 'Document manually verified by compliance.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_feature_status(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_ocr_1\",\n new_status: \"Approved\",\n comment: \"Document manually verified by compliance.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateFeatureStatusAsync(\"11111111-2222-3333-4444-555555555555\", \"feature_ocr_1\", new SessionsUpdateFeatureStatusParams\n{\n NewStatus = \"Approved\",\n Comment = \"Document manually verified by compliance.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/update-aml-hit-status/": { + "patch": { + "summary": "Update an AML hit review status", + "description": "Set the review status of a **single AML hit** (a potential sanction / PEP / watchlist / adverse-media match) returned by AML screening, without changing the overall session decision. Identify the hit by its `hit_id` (the `id` of an entry in the AML check's `hits` array — see the session decision). A hit id returned in the earlier format is still accepted; the response always names the hit by its current `id`.\n\n`review_status` is one of `Unreviewed`, `Confirmed Match`, `False Positive`, or `Inconclusive`. Reviewer decisions are preserved across ongoing-monitoring updates, so a hit you mark `False Positive` stays that way when the provider re-screens.\n\nA successful call fires a `data.updated` webhook and appends an activity entry (with actor attribution) to the session's review trail. For graph workflows with multiple AML checks, pass `node_id` to target a specific check.\n\nAuthenticate with your API key (`x-api-key`) or a user authorization header — either must carry the `write:sessions` privilege.", + "operationId": "patch_v3_session_update_aml_hit_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_aml_hit_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "in": "query", + "name": "node_id", + "required": false, + "description": "Workflow graph node ID. Required only when the session has multiple AML checks (one per node); omit it for single-AML sessions.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "hit_id", + "review_status" + ], + "properties": { + "hit_id": { + "type": "string", + "description": "The `id` of the hit in the AML check's `hits` array (`hit_...`). A hit id returned in the earlier format is still accepted.", + "example": "hit_7c1e9a40b2d35f86e0a4c912" + }, + "review_status": { + "type": "string", + "description": "The new review status for the hit.", + "enum": [ + "Unreviewed", + "Confirmed Match", + "False Positive", + "Inconclusive" + ], + "example": "False Positive" + }, + "node_id": { + "type": "string", + "description": "Optional graph node ID. Use it (here or as a query parameter) when the session has multiple AML checks.", + "example": "feature_aml_1" + } + } + }, + "example": { + "hit_id": "hit_7c1e9a40b2d35f86e0a4c912", + "review_status": "False Positive" + } + } + } + }, + "responses": { + "200": { + "description": "Hit review status updated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message", + "hit_id", + "review_status" + ], + "properties": { + "message": { + "type": "string", + "description": "Always `Hit status updated successfully`." + }, + "hit_id": { + "type": "string", + "description": "The `id` of the updated hit, as the session decision returns it, also when the request named the hit by an id in the earlier format." + }, + "review_status": { + "type": "string", + "enum": [ + "Unreviewed", + "Confirmed Match", + "False Positive", + "Inconclusive" + ], + "description": "The review status now stored on the hit." + } + } + }, + "example": { + "message": "Hit status updated successfully", + "hit_id": "hit_7c1e9a40b2d35f86e0a4c912", + "review_status": "False Positive" + } + } + } + }, + "400": { + "description": "Validation error — `hit_id` empty or `review_status` not one of the allowed values.", + "content": { + "application/json": { + "example": { + "review_status": [ + "\"Maybe\" is not a valid choice." + ] + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "API key lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "The session was not found, the session has no AML check (optionally for the given `node_id`), or no hit matches `hit_id`.", + "content": { + "application/json": { + "examples": { + "No AML Check": { + "summary": "Session has no AML check", + "value": { + "detail": "The session does not have an AML check." + } + }, + "Hit Not Found": { + "summary": "Unknown hit_id", + "value": { + "detail": "Hit with ID 'hit_9d2e7f41c0a8b3e65d1f0c47' not found in the AML check." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-aml-hit-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"hit_id\": \"abc123\",\n \"review_status\": \"False Positive\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_aml_hit_status(\n \"11111111-2222-3333-4444-555555555555\",\n hit_id=\"hit_7c1e9a40b2d35f86e0a4c912\",\n review_status=\"False Positive\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateAmlHitStatus(\"11111111-2222-3333-4444-555555555555\", {\n hit_id: \"hit_7c1e9a40b2d35f86e0a4c912\",\n review_status: \"False Positive\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateAMLHitStatus(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsUpdateAMLHitStatusParams{\n\tHitID: \"hit_7c1e9a40b2d35f86e0a4c912\",\n\tReviewStatus: \"False Positive\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsUpdateAmlHitStatusParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateAmlHitStatus(\"11111111-2222-3333-4444-555555555555\", SessionsUpdateAmlHitStatusParams.builder()\n .hitId(\"hit_7c1e9a40b2d35f86e0a4c912\")\n .reviewStatus(SessionsUpdateAmlHitStatusParams.ReviewStatus.of(\"False Positive\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateAmlHitStatus('11111111-2222-3333-4444-555555555555', [\n 'hit_id' => 'hit_7c1e9a40b2d35f86e0a4c912',\n 'review_status' => 'False Positive',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_aml_hit_status(\n \"11111111-2222-3333-4444-555555555555\",\n hit_id: \"hit_7c1e9a40b2d35f86e0a4c912\",\n review_status: \"False Positive\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateAmlHitStatusAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsUpdateAmlHitStatusParams\n{\n HitId = \"hit_7c1e9a40b2d35f86e0a4c912\",\n ReviewStatus = \"False Positive\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/bulk-update-aml-hit-status/": { + "patch": { + "summary": "Bulk update AML hit review statuses", + "description": "Update the review status of **multiple AML hits** in one call. Provide `hit_updates`, a list of `{ hit_id, review_status }` pairs. `review_status` is one of `Unreviewed`, `Confirmed Match`, `False Positive`, or `Inconclusive`.\n\nThe update is all-or-nothing: if any `hit_id` is not found in the AML check, the request returns `404` and no hit is changed. A successful call fires a single `data.updated` webhook and records one activity entry covering all changes. For graph workflows with multiple AML checks, pass `node_id` to target a specific check.\n\nAuthenticate with your API key (`x-api-key`) or a user authorization header — either must carry the `write:sessions` privilege.", + "operationId": "patch_v3_session_bulk_update_aml_hit_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "bulk_update_aml_hit_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "in": "query", + "name": "node_id", + "required": false, + "description": "Workflow graph node ID. Required only when the session has multiple AML checks (one per node); omit it for single-AML sessions.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "hit_updates" + ], + "properties": { + "hit_updates": { + "type": "array", + "description": "Non-empty list of hit updates.", + "items": { + "type": "object", + "required": [ + "hit_id", + "review_status" + ], + "properties": { + "hit_id": { + "type": "string", + "example": "hit_7c1e9a40b2d35f86e0a4c912", + "description": "The `id` of the hit in the AML check's `hits` array (`hit_...`). A hit id returned in the earlier format is still accepted." + }, + "review_status": { + "type": "string", + "enum": [ + "Unreviewed", + "Confirmed Match", + "False Positive", + "Inconclusive" + ], + "example": "Confirmed Match" + } + } + } + }, + "node_id": { + "type": "string", + "description": "Optional graph node ID. Use it (here or as a query parameter) when the session has multiple AML checks.", + "example": "feature_aml_1" + } + } + }, + "example": { + "hit_updates": [ + { + "hit_id": "hit_7c1e9a40b2d35f86e0a4c912", + "review_status": "Confirmed Match" + }, + { + "hit_id": "hit_3b8f20d6a91c4e75f0b2d6a8", + "review_status": "False Positive" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "All hits updated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message", + "updated_hits" + ], + "properties": { + "message": { + "type": "string", + "description": "`Updated {n} hit(s) successfully`, where `n` is the number of entries in `hit_updates`." + }, + "updated_hits": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The `id` of the hit each entry in `hit_updates` names, as the session decision returns it, in request order, including hits whose status did not change." + } + } + }, + "example": { + "message": "Updated 2 hit(s) successfully", + "updated_hits": [ + "hit_7c1e9a40b2d35f86e0a4c912", + "hit_3b8f20d6a91c4e75f0b2d6a8" + ] + } + } + } + }, + "400": { + "description": "Validation error — `hit_updates` empty, or an entry is missing `hit_id` / `review_status` or carries an invalid value.", + "content": { + "application/json": { + "example": { + "hit_updates": [ + "hit_updates cannot be empty" + ] + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "API key lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "The session was not found, the session has no AML check, or one or more `hit_id`s do not exist (no hit is changed in that case).", + "content": { + "application/json": { + "example": { + "detail": "Hits not found: hit_9d2e7f41c0a8b3e65d1f0c47" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/bulk-update-aml-hit-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"hit_updates\": [\n { \"hit_id\": \"abc123\", \"review_status\": \"Confirmed Match\" },\n { \"hit_id\": \"def456\", \"review_status\": \"False Positive\" }\n ]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.bulk_update_aml_hit_status(\n \"11111111-2222-3333-4444-555555555555\",\n hit_updates=[{\n \"hit_id\": \"hit_7c1e9a40b2d35f86e0a4c912\",\n \"review_status\": \"Confirmed Match\",\n }, {\n \"hit_id\": \"hit_3b8f20d6a91c4e75f0b2d6a8\",\n \"review_status\": \"False Positive\",\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.bulkUpdateAmlHitStatus(\"11111111-2222-3333-4444-555555555555\", {\n hit_updates: [{\n hit_id: \"hit_7c1e9a40b2d35f86e0a4c912\",\n review_status: \"Confirmed Match\",\n }, {\n hit_id: \"hit_3b8f20d6a91c4e75f0b2d6a8\",\n review_status: \"False Positive\",\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.BulkUpdateAMLHitStatus(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsBulkUpdateAMLHitStatusParams{\n\tHitUpdates: []didit.SessionsBulkUpdateAMLHitStatusParamsHitUpdates{{\n\t\tHitID: \"hit_7c1e9a40b2d35f86e0a4c912\",\n\t\tReviewStatus: \"Confirmed Match\",\n\t}, {\n\t\tHitID: \"hit_3b8f20d6a91c4e75f0b2d6a8\",\n\t\tReviewStatus: \"False Positive\",\n\t}},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.SessionsBulkUpdateAmlHitStatusParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().bulkUpdateAmlHitStatus(\"11111111-2222-3333-4444-555555555555\", SessionsBulkUpdateAmlHitStatusParams.builder()\n .hitUpdates(List.of(\n SessionsBulkUpdateAmlHitStatusParams.HitUpdates.builder()\n .hitId(\"hit_7c1e9a40b2d35f86e0a4c912\")\n .reviewStatus(SessionsBulkUpdateAmlHitStatusParams.HitUpdates.ReviewStatus.of(\"Confirmed Match\"))\n .build(),\n SessionsBulkUpdateAmlHitStatusParams.HitUpdates.builder()\n .hitId(\"hit_3b8f20d6a91c4e75f0b2d6a8\")\n .reviewStatus(SessionsBulkUpdateAmlHitStatusParams.HitUpdates.ReviewStatus.of(\"False Positive\"))\n .build()))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->bulkUpdateAmlHitStatus('11111111-2222-3333-4444-555555555555', [\n 'hit_updates' => [[\n 'hit_id' => 'hit_7c1e9a40b2d35f86e0a4c912',\n 'review_status' => 'Confirmed Match',\n ], [\n 'hit_id' => 'hit_3b8f20d6a91c4e75f0b2d6a8',\n 'review_status' => 'False Positive',\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.bulk_update_aml_hit_status(\n \"11111111-2222-3333-4444-555555555555\",\n hit_updates: [{\n hit_id: \"hit_7c1e9a40b2d35f86e0a4c912\",\n review_status: \"Confirmed Match\",\n }, {\n hit_id: \"hit_3b8f20d6a91c4e75f0b2d6a8\",\n review_status: \"False Positive\",\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.BulkUpdateAmlHitStatusAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsBulkUpdateAmlHitStatusParams\n{\n HitUpdates = new SessionsBulkUpdateAmlHitStatusParamsHitUpdates[]\n {\n new()\n {\n HitId = \"hit_7c1e9a40b2d35f86e0a4c912\",\n ReviewStatus = \"Confirmed Match\",\n },\n new()\n {\n HitId = \"hit_3b8f20d6a91c4e75f0b2d6a8\",\n ReviewStatus = \"False Positive\",\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/update-poa-data/": { + "patch": { + "summary": "Update Proof of Address data extracted from the POA document on a verification session", + "description": "Correct the fields extracted from the user's Proof of Address document — utility bill, bank statement, or other address evidence — after the verification has finished: issuer, dates, the printed name, the address itself, and banking details. Every field in the body is optional: only the keys you send are written, everything else is left untouched (`PATCH` semantics).\n\nThe session must belong to your application and be in `Approved`, `Declined`, `In Review`, or `Kyc Expired` status. Sessions in any other state (e.g. still `In Progress`) return `404` with `\"No Session matches the given query.\"` — distinct from the `\"Not found.\"` returned for an unknown or deleted `sessionId`. The session must also carry a POA record: sessions whose workflow never ran a Proof of Address step return `404` (`\"The session does not have a POA.\"`). For graph workflows with multiple POA steps, target a specific step with `node_id` (query parameter or body field); when omitted, the most recent POA record is patched.\n\n`extra_fields` is merged key-by-key: nine known keys (`bank_iban`, `additional_names`, …) write through to dedicated POA columns, any other key is kept as a custom extra, and `null` / `\"\"` deletes a key. `poa_parsed_address` accepts only the seven documented keys and recomputes `formatted_address` / `poa_formatted_address` after every edit. If the free-text `poa_address` changes, the server may also re-geocode it and refresh the structured address (skipped for sandbox sessions).\n\nUnlike the KYC variant of this endpoint, the `data.updated` [webhook](/integration/webhooks) (trigger `manual_review`) fires only when at least one stored value **actually changed** — re-sending identical values is a silent no-op. When values change, a `POA_DATA_UPDATED` activity entry recording the changed fields with previous and new values is appended to the session's `reviews`, visible in [Get Decision](#get-/v3/session/-sessionId-/decision/) and the console activity timeline. The session's status and decision are **not** re-evaluated — use [Update Status](#patch-/v3/session/-sessionId-/update-status/) to change the outcome.\n\nAccepts an API key or a console user token; either way the credential needs the `write:sessions` permission. Authentication and permission failures both return `403` — this API never responds `401`. Note that the session lookup runs before credential checks, so an unknown `sessionId` returns `404` even with missing credentials. This endpoint has a dedicated rate limit of 10 requests per minute per API key (invalid requests count too); the shared write budget of 300 requests per minute also applies.\n\nAn empty JSON body `{}` is a no-op: it returns `200` echoing the current POA record without firing the webhook (`has_changes` stays false).", + "operationId": "patch_v3_session_update_poa_data", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_poa_data", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session whose attached POA record you are correcting.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node whose POA record to patch, for graph workflows with multiple Proof of Address steps (also accepted as a `node_id` body field). When omitted, the most recent POA record on the session is patched. If no POA exists for the given node, the call returns `404` (`\"The session does not have a POA for node ''.\"`).", + "schema": { + "type": "string", + "example": "feature_poa" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "All fields optional — send only what you want to change.", + "properties": { + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Country that issued the POA document, ISO 3166-1 alpha-3. Case-insensitive (normalized to uppercase); invalid codes return `400`.", + "example": "ESP" + }, + "document_type": { + "type": "string", + "enum": [ + "UTILITY_BILL", + "BANK_STATEMENT", + "GOVERNMENT_ISSUED_DOCUMENT", + "OTHER_POA_DOCUMENT", + "UNKNOWN" + ], + "description": "POA document category. Values are uppercase — lowercase variants like `utility_bill` return `400`.", + "example": "UTILITY_BILL" + }, + "document_language": { + "type": "string", + "maxLength": 50, + "nullable": true, + "description": "Language of the document, typically an ISO 639-1 code (e.g. `es`). Free text — not validated as a language code.", + "example": "es" + }, + "issuer": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Organization that issued the document (utility company, bank, government agency).", + "example": "Energy Provider S.A." + }, + "issue_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Issue date of the POA document in `YYYY-MM-DD`. Any other format returns `400`.", + "example": "2026-04-12" + }, + "poa_address": { + "type": "string", + "nullable": true, + "description": "Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it in the background of the same request and may overwrite `poa_parsed_address` / `poa_formatted_address` with the geocoder result. The re-geocode also requires `issuing_state` to be present on the POA record.", + "example": "Avda de Madrid 34, 28013 Madrid" + }, + "name_on_document": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Recipient name as printed on the document.", + "example": "Carmen Espanola" + }, + "extra_fields": { + "type": "object", + "additionalProperties": true, + "nullable": true, + "description": "Document-specific extras, merged key-by-key. Nine keys map to dedicated POA columns: `bank_account_number`, `bank_iban`, `bank_sort_code`, `bank_routing_number`, `bank_swift_bic`, `bank_branch_name`, `bank_branch_address`, `document_phone_number`, and `additional_names` (must be a list of strings or `null`, otherwise `400`). Any other key is stored as a custom extra field. Per-key semantics: a value sets the key; `null` or `\"\"` deletes it. Must be a JSON object, otherwise `400`." + }, + "poa_parsed_address": { + "type": "object", + "nullable": true, + "description": "Structured address. Only these nested keys are accepted: `address_type`, `city`, `region`, `street_1`, `street_2`, `postal_code`, `country` — any other key returns `400`. Per-key semantics: a value sets the key, `null` removes it. `formatted_address` (and the top-level `poa_formatted_address`) is recomputed server-side by joining `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country` — you cannot set it directly.", + "properties": { + "address_type": { + "type": "string", + "nullable": true, + "description": "Free-form address classification (e.g. `home`)." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Madrid" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State / province / autonomous community." + }, + "street_1": { + "type": "string", + "nullable": true, + "example": "Avda de Madrid 34" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "28013" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`.", + "example": "ES" + } + } + } + } + }, + "examples": { + "Correct issuer and address": { + "summary": "Fix document classification, issuer, and the structured address", + "value": { + "issuing_state": "ESP", + "document_type": "BANK_STATEMENT", + "issuer": "Banco Example S.A.", + "issue_date": "2026-05-02", + "name_on_document": "Carmen Espanola", + "poa_address": "Avda de Madrid 34, 28013 Madrid", + "extra_fields": { + "bank_iban": "ES9121000418450200051332", + "additional_names": [ + "Carmen E. Espanola" + ] + }, + "poa_parsed_address": { + "street_1": "Avda de Madrid 34", + "city": "Madrid", + "postal_code": "28013", + "country": "ES" + } + } + }, + "Remove a banking detail": { + "summary": "Delete one extra-field key, keep the rest", + "value": { + "extra_fields": { + "bank_iban": null + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "POA data updated. Returns the **full current POA record** (every updatable field with its stored value, plus the complete `extra_fields` map, `poa_parsed_address`, and `poa_formatted_address`) — not just the fields you submitted. The `data.updated` webhook fires only when at least one value actually changed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Country that issued the POA document, ISO 3166-1 alpha-3. Case-insensitive (normalized to uppercase); invalid codes return `400`.", + "example": "ESP" + }, + "document_type": { + "type": "string", + "enum": [ + "UTILITY_BILL", + "BANK_STATEMENT", + "GOVERNMENT_ISSUED_DOCUMENT", + "OTHER_POA_DOCUMENT", + "UNKNOWN" + ], + "description": "POA document category. Values are uppercase — lowercase variants like `utility_bill` return `400`.", + "example": "UTILITY_BILL" + }, + "document_language": { + "type": "string", + "maxLength": 50, + "nullable": true, + "description": "Language of the document, typically an ISO 639-1 code (e.g. `es`). Free text — not validated as a language code.", + "example": "es" + }, + "issuer": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Organization that issued the document (utility company, bank, government agency).", + "example": "Energy Provider S.A." + }, + "issue_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Issue date of the POA document in `YYYY-MM-DD`. Any other format returns `400`.", + "example": "2026-04-12" + }, + "poa_address": { + "type": "string", + "nullable": true, + "description": "Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it in the background of the same request and may overwrite `poa_parsed_address` / `poa_formatted_address` with the geocoder result.", + "example": "Avda de Madrid 34, 28013 Madrid" + }, + "name_on_document": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Recipient name as printed on the document.", + "example": "Carmen Espanola" + }, + "extra_fields": { + "type": "object", + "additionalProperties": true, + "nullable": true, + "description": "Always present in the response: the nine known keys (with `null` for unset ones, `additional_names` defaulting to `[]`) plus any custom keys previously stored." + }, + "poa_parsed_address": { + "type": "object", + "nullable": true, + "description": "Current structured address, or `null` when none exists. Includes the recomputed `formatted_address` alongside the seven editable keys. Returned as stored — after a server-side geocode it can contain bookkeeping keys (e.g. `raw_results`) alongside the editable keys.", + "properties": { + "address_type": { + "type": "string", + "nullable": true, + "description": "Free-form address classification (e.g. `home`)." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Madrid" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State / province / autonomous community." + }, + "street_1": { + "type": "string", + "nullable": true, + "example": "Avda de Madrid 34" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "28013" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`.", + "example": "ES" + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "Joined, human-readable form of the structured address. Recomputed server-side after every `poa_parsed_address` edit.", + "example": "Avda de Madrid 34, Madrid, 28013, ES" + } + } + }, + "poa_formatted_address": { + "type": "string", + "nullable": true, + "description": "Top-level copy of the recomputed `formatted_address`, kept in sync when the structured address changes.", + "example": "Avda de Madrid 34, Madrid, 28013, ES" + } + } + }, + "examples": { + "Updated POA": { + "summary": "Full POA record after the correction", + "value": { + "issuing_state": "ESP", + "document_type": "BANK_STATEMENT", + "document_language": "es", + "issuer": "Banco Example S.A.", + "issue_date": "2026-05-02", + "poa_address": "Avda de Madrid 34, 28013 Madrid", + "name_on_document": "Carmen Espanola", + "extra_fields": { + "bank_account_number": null, + "bank_iban": "ES9121000418450200051332", + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": null, + "additional_names": [ + "Carmen E. Espanola" + ] + }, + "poa_parsed_address": { + "city": "Madrid", + "street_1": "Avda de Madrid 34", + "postal_code": "28013", + "country": "ES", + "formatted_address": "Avda de Madrid 34, Madrid, 28013, ES" + }, + "poa_formatted_address": "Avda de Madrid 34, Madrid, 28013, ES" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field errors arrive as `{\"\": [\"\"]}`; errors on nested `poa_parsed_address` / `extra_fields` keys use an object value: `{\"extra_fields\": {\"\": \"\"}}`.", + "content": { + "application/json": { + "examples": { + "Invalid document type": { + "summary": "Enum is uppercase — lowercase variants are rejected", + "value": { + "document_type": [ + "\"utility_bill\" is not a valid choice." + ] + } + }, + "Invalid issuing state": { + "summary": "issuing_state is not ISO 3166-1 alpha-3", + "value": { + "issuing_state": [ + "Invalid ISO 3166-1 alpha-3 country code for issuing_state." + ] + } + }, + "additional_names not a list": { + "summary": "additional_names accepts a list of strings or null", + "value": { + "extra_fields": { + "additional_names": "Must be a list or null." + } + } + }, + "Unsupported poa_parsed_address key": { + "summary": "Only the seven documented keys are allowed", + "value": { + "poa_parsed_address": { + "zipcode": "Unsupported key. Allowed keys: address_type, city, region, street_1, street_2, postal_code, country" + } + } + }, + "Bad date format": { + "summary": "Dates must be YYYY-MM-DD", + "value": { + "issue_date": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Credential lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown session, session in a non-editable state, or no POA record to patch. Each case has a distinct `detail` message (see examples). The unknown-session lookup runs before credential checks.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "summary": "No session with this ID in your application (or it was deleted)", + "value": { + "detail": "Not found." + } + }, + "Wrong session state": { + "summary": "Session exists but is not Approved / Declined / In Review / Kyc Expired", + "value": { + "detail": "No Session matches the given query." + } + }, + "No POA record": { + "summary": "The workflow never ran a Proof of Address step", + "value": { + "detail": "The session does not have a POA." + } + }, + "No POA for node": { + "summary": "node_id given but no POA exists for it", + "value": { + "detail": "The session does not have a POA for node 'feature_missing'." + } + } + } + } + } + }, + "429": { + "description": "Dedicated rate limit exceeded — 10 POA data updates per minute per API key (rejected requests count toward the budget). The shared 300/min write budget can also trigger with its own message. Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "POA update budget exhausted", + "value": { + "detail": "Session POA data update rate limit exceeded. You can make up to 10 requests per minute." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-poa-data/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"issuing_state\": \"ESP\",\n \"document_type\": \"BANK_STATEMENT\",\n \"issuer\": \"Banco Example S.A.\",\n \"issue_date\": \"2026-05-02\",\n \"name_on_document\": \"Carmen Espanola\",\n \"poa_address\": \"Avda de Madrid 34, 28013 Madrid\",\n \"extra_fields\": {\n \"bank_iban\": \"ES9121000418450200051332\"\n },\n \"poa_parsed_address\": {\n \"city\": \"Madrid\",\n \"postal_code\": \"28013\",\n \"country\": \"ES\"\n }\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_poa_data(\n \"11111111-2222-3333-4444-555555555555\",\n issuing_state=\"ESP\",\n document_type=\"BANK_STATEMENT\",\n issuer=\"Banco Example S.A.\",\n issue_date=\"2026-05-02\",\n poa_address=\"Avda de Madrid 34, 28013 Madrid\",\n name_on_document=\"Carmen Espanola\",\n extra_fields={\"bank_iban\": \"ES9121000418450200051332\", \"additional_names\": [\"Carmen E. Espanola\"]},\n poa_parsed_address={\n \"city\": \"Madrid\",\n \"street_1\": \"Avda de Madrid 34\",\n \"postal_code\": \"28013\",\n \"country\": \"ES\",\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updatePoaData(\"11111111-2222-3333-4444-555555555555\", {\n issuing_state: \"ESP\",\n document_type: \"BANK_STATEMENT\",\n issuer: \"Banco Example S.A.\",\n issue_date: \"2026-05-02\",\n poa_address: \"Avda de Madrid 34, 28013 Madrid\",\n name_on_document: \"Carmen Espanola\",\n extra_fields: { bank_iban: \"ES9121000418450200051332\", additional_names: [\"Carmen E. Espanola\"] },\n poa_parsed_address: {\n city: \"Madrid\",\n street_1: \"Avda de Madrid 34\",\n postal_code: \"28013\",\n country: \"ES\",\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdatePOAData(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsUpdatePOADataParams{\n\tIssuingState: didit.String(\"ESP\"),\n\tDocumentType: didit.Some(didit.SessionsUpdatePOADataParamsDocumentType(\"BANK_STATEMENT\")),\n\tIssuer: didit.String(\"Banco Example S.A.\"),\n\tIssueDate: didit.String(\"2026-05-02\"),\n\tPOAAddress: didit.String(\"Avda de Madrid 34, 28013 Madrid\"),\n\tNameOnDocument: didit.String(\"Carmen Espanola\"),\n\tExtraFields: didit.Some(map[string]any{\"bank_iban\": \"ES9121000418450200051332\", \"additional_names\": []any{\"Carmen E. Espanola\"}}),\n\tPOAParsedAddress: didit.Some(didit.SessionsUpdatePOADataParamsPOAParsedAddress{\n\t\tCity: didit.String(\"Madrid\"),\n\t\tStreet1: didit.String(\"Avda de Madrid 34\"),\n\t\tPostalCode: didit.String(\"28013\"),\n\t\tCountry: didit.String(\"ES\"),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SessionsUpdatePoaDataParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updatePoaData(\"11111111-2222-3333-4444-555555555555\", SessionsUpdatePoaDataParams.builder()\n .issuingState(\"ESP\")\n .documentType(SessionsUpdatePoaDataParams.DocumentType.of(\"BANK_STATEMENT\"))\n .issuer(\"Banco Example S.A.\")\n .issueDate(LocalDate.parse(\"2026-05-02\"))\n .poaAddress(\"Avda de Madrid 34, 28013 Madrid\")\n .nameOnDocument(\"Carmen Espanola\")\n .extraFields(Map.of(\"bank_iban\", \"ES9121000418450200051332\", \"additional_names\", List.of(\"Carmen E. Espanola\")))\n .poaParsedAddress(SessionsUpdatePoaDataParams.PoaParsedAddress.builder()\n .city(\"Madrid\")\n .street1(\"Avda de Madrid 34\")\n .postalCode(\"28013\")\n .country(\"ES\")\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updatePoaData('11111111-2222-3333-4444-555555555555', [\n 'issuing_state' => 'ESP',\n 'document_type' => 'BANK_STATEMENT',\n 'issuer' => 'Banco Example S.A.',\n 'issue_date' => '2026-05-02',\n 'poa_address' => 'Avda de Madrid 34, 28013 Madrid',\n 'name_on_document' => 'Carmen Espanola',\n 'extra_fields' => ['bank_iban' => 'ES9121000418450200051332', 'additional_names' => ['Carmen E. Espanola']],\n 'poa_parsed_address' => [\n 'city' => 'Madrid',\n 'street_1' => 'Avda de Madrid 34',\n 'postal_code' => '28013',\n 'country' => 'ES',\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_poa_data(\n \"11111111-2222-3333-4444-555555555555\",\n issuing_state: \"ESP\",\n document_type: \"BANK_STATEMENT\",\n issuer: \"Banco Example S.A.\",\n issue_date: \"2026-05-02\",\n poa_address: \"Avda de Madrid 34, 28013 Madrid\",\n name_on_document: \"Carmen Espanola\",\n extra_fields: { bank_iban: \"ES9121000418450200051332\", additional_names: [\"Carmen E. Espanola\"] },\n poa_parsed_address: {\n city: \"Madrid\",\n street_1: \"Avda de Madrid 34\",\n postal_code: \"28013\",\n country: \"ES\",\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdatePoaDataAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsUpdatePoaDataParams\n{\n IssuingState = \"ESP\",\n DocumentType = new SessionsUpdatePoaDataParamsDocumentType(\"BANK_STATEMENT\"),\n Issuer = \"Banco Example S.A.\",\n IssueDate = \"2026-05-02\",\n PoaAddress = \"Avda de Madrid 34, 28013 Madrid\",\n NameOnDocument = \"Carmen Espanola\",\n ExtraFields = new Dictionary { [\"bank_iban\"] = \"ES9121000418450200051332\", [\"additional_names\"] = new object[] { \"Carmen E. Espanola\" } },\n PoaParsedAddress = new SessionsUpdatePoaDataParamsPoaParsedAddress\n {\n City = \"Madrid\",\n Street1 = \"Avda de Madrid 34\",\n PostalCode = \"28013\",\n Country = \"ES\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/{companyUuid}/update-data/": { + "patch": { + "summary": "Update registry-extracted company data on a Business Verification (KYB) session", + "description": "Correct the registry-extracted company data on a Business Verification (KYB) session — company name, registration number, addresses, status, contact details — identified by `companyUuid`. Typical callers are a compliance analyst fixing a registry misread, or your back office syncing corrected company data. Every field in the body is optional (`PATCH` semantics); an empty JSON body (`{}`) is a no-op that simply echoes the current company record.\n\nEdits are written in two places at once: fields with a canonical company column (name, registration number, country, incorporation date, tax number, type, statuses, capital fields (`registered_capital`, `registered_capital_amount`, `registered_capital_currency`), alternative names, nature of business, website, email, phone, LEI, location, and `legal_address` → `registered_address`) update that column, and **every** submitted key is also merged into the `registry_data` snapshot (dates as ISO strings) so the \"extracted from registry\" view reflects the correction. Fields without a column — `region`, `registration_date`, `dissolution_date`, `control_scheme` — land in `registry_data` only. `user_provided_data` (what the end user confirmed in the hosted flow) is never touched, and `last_console_edit_at` is stamped on every non-empty PATCH.\n\nUnlike the KYC/POA update endpoints, there is **no session-status gate**: the company can be edited at any point in the business session's lifecycle. The company must belong to the session in the path (`404` with `\"KYB company does not belong to the given session.\"` otherwise) and to your application. Every non-empty PATCH fires the `data.updated` [webhook](/integration/webhooks) on the business session (trigger `console_registry_edit`) — even when the submitted values equal the stored ones. The session's status and decision are **not** re-evaluated, and no review/audit entry is recorded.\n\nAccepts an API key or a console user token; either way the credential needs the `write:businesses` permission. Authentication and permission failures both return `403` — this API never responds `401`. Note that the session lookup runs before credential checks, so an unknown `sessionId` returns `404` even with missing credentials. The shared write budget of 300 requests per minute per API key applies.\n\nFor User Verification sessions, the KYC counterpart is `PATCH /v3/session/{sessionId}/update-data/`; to change the session decision itself use `PATCH /v3/session/{sessionId}/update-status/`.", + "operationId": "patch_v3_session_kyb_company_update_data", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_kyb_company_data", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the parent Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "companyUuid", + "in": "path", + "required": true, + "description": "UUID of the company record to update (`registry_checks[].company.uuid` on the business session decision). Must belong to the session referenced by `sessionId`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "88888888-9999-aaaa-bbbb-cccccccccccc" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "All fields optional — send only what you want to change. Provided fields update the canonical company columns (where one exists) and are merged into the `registry_data` snapshot.", + "properties": { + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Legal name of the company. Written to the canonical column and the registry snapshot.", + "example": "Docs Probe Corp" + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Official registration number with the company registry.", + "example": "DPC-001" + }, + "country_code": { + "type": "string", + "maxLength": 2, + "description": "Country of incorporation, ISO 3166-1 alpha-2. Case-insensitive (`us` is stored as `US`). Alpha-3 codes fail the 2-character limit with `400`.", + "example": "US" + }, + "region": { + "type": "string", + "maxLength": 10, + "nullable": true, + "description": "Subdivision / region tag where the registry has one (e.g. US state). Stored in the registry snapshot only — it has no canonical column." + }, + "incorporation_date": { + "type": "string", + "format": "date", + "description": "Incorporation date in `YYYY-MM-DD`. Optional like every other field — the `PATCH` is partial. Any other date format returns `400`.", + "example": "2015-04-12" + }, + "registration_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Registration date where the registry distinguishes it from incorporation. Stored in the registry snapshot only." + }, + "dissolution_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Dissolution date for companies that are no longer active. Stored in the registry snapshot only." + }, + "tax_number": { + "type": "string", + "maxLength": 100, + "description": "Tax identification number." + }, + "legal_address": { + "type": "string", + "description": "Legal / registered address. Aliased server-side to the canonical `registered_address` column — the response returns it under `registered_address`, while the registry snapshot keeps the `legal_address` key you sent.", + "example": "1 Example Plaza, Wilmington, DE 19801" + }, + "company_type": { + "type": "string", + "maxLength": 100, + "description": "Legal entity type (e.g. `Limited Liability Company`, `PLC`, `GmbH`).", + "example": "Limited Liability Company" + }, + "registry_status": { + "type": "string", + "enum": [ + "active", + "dissolved", + "deregistered", + "see full details", + "authorised", + "appointed representative", + "unauthorised", + "inactive", + "no longer authorised", + "closed", + "struck off" + ], + "nullable": true, + "description": "Registry-reported company status. Lowercase, some values contain spaces (e.g. `struck off`, `no longer authorised`). Any other value returns `400`.", + "example": "active" + }, + "verification_status": { + "type": "string", + "enum": [ + "verified", + "failed", + "unknown" + ], + "nullable": true, + "description": "Outcome of the registry verification. Any other value returns `400`.", + "example": "verified" + }, + "alternative_names": { + "type": "string", + "description": "Trade names, former legal names, DBA names (free text)." + }, + "nature_of_business": { + "type": "string", + "description": "Short description of the company's activity.", + "example": "Software development" + }, + "registered_capital": { + "type": "string", + "maxLength": 100, + "description": "Human-readable registered capital (e.g. `USD 50000`). Use `registered_capital_amount` + `registered_capital_currency` for structured values, or this field for free-form text.", + "example": "USD 50000" + }, + "registered_capital_amount": { + "type": "number", + "format": "double", + "nullable": true, + "description": "Numeric registered-capital amount (up to 20 digits, 2 decimal places). Sets the `registered_capital_amount` column and is mirrored into `registry_data` as a decimal string; the response echoes it as a decimal string (e.g. `\"1500000.50\"`)." + }, + "registered_capital_currency": { + "type": "string", + "maxLength": 3, + "nullable": true, + "description": "ISO 4217 currency code for `registered_capital_amount`. Case-insensitive (`usd` is stored as `USD`); values that are not exactly 3 letters return `400` with the ISO 4217 message. Sets the canonical `registered_capital_currency` column and is mirrored into `registry_data`.", + "example": "USD" + }, + "website": { + "type": "string", + "maxLength": 500, + "description": "Company website. Bare domains are accepted and normalized with an `https://` prefix (`docsprobe.example.com` is stored as `https://docsprobe.example.com`); values that still fail URL validation return `400`.", + "example": "https://docsprobe.example.com" + }, + "email": { + "type": "string", + "format": "email", + "description": "Contact email on file with the registry.", + "example": "legal@docsprobe.example.com" + }, + "phone": { + "type": "string", + "maxLength": 50, + "description": "Contact phone. Must match `+`, digits, spaces, dashes, dots, or parentheses, with 7-15 digits total — otherwise `400`.", + "example": "+1 302 555 0143" + }, + "legal_entity_identifier": { + "type": "string", + "maxLength": 50, + "description": "LEI, where the company has one." + }, + "location_of_registration": { + "type": "string", + "maxLength": 255, + "description": "City / subdivision of the registry of record." + }, + "vat_number": { + "type": "string", + "maxLength": 64, + "description": "EU VAT number. Merged into the `registry_data` snapshot only — editing it here does not re-run VAT and tax ID validation, so `vat_validation_status` and the other `vat_*` result fields are unchanged." + }, + "control_scheme": { + "type": "string", + "description": "Control / governance scheme when the registry exposes it. Stored in the registry snapshot only." + } + } + }, + "examples": { + "Correct registry data": { + "summary": "Fix the company's registry-extracted fields", + "value": { + "company_name": "Docs Probe Corp", + "registration_number": "DPC-001", + "country_code": "US", + "incorporation_date": "2015-04-12", + "company_type": "Limited Liability Company", + "legal_address": "1 Example Plaza, Wilmington, DE 19801", + "registry_status": "active", + "verification_status": "verified", + "website": "docsprobe.example.com", + "email": "legal@docsprobe.example.com", + "phone": "+1 302 555 0143", + "registered_capital": "USD 50000", + "nature_of_business": "Software development" + } + }, + "Single field": { + "summary": "Only the keys you send are written", + "value": { + "registry_status": "dissolved" + } + }, + "No-op": { + "summary": "Empty body echoes the current record without firing the webhook", + "value": {} + } + } + } + } + }, + "responses": { + "200": { + "description": "Company data updated (or echoed unchanged for an empty body). Returns the full KYB company record — the same shape as `registry_checks[].company` on the business session decision. Note how `legal_address` comes back as `registered_address`. `registered_capital_amount`/`registered_capital_currency` populate their canonical columns and are mirrored into `registry_data`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Full KYB company record — the same shape as the `company` object inside `registry_checks[]` on the business session decision.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Company record identifier (the `companyUuid` path parameter)." + }, + "node_id": { + "type": "string", + "nullable": true, + "description": "Workflow graph node this company record belongs to, when the workflow is graph-based." + }, + "status": { + "type": "string", + "description": "Feature lifecycle status of the registry check (e.g. `Not Finished`, `Approved`, `Declined`, `In Review`)." + }, + "registry_status": { + "type": "string", + "nullable": true, + "description": "Registry-reported company status (e.g. `active`, `dissolved`, `struck off`)." + }, + "data_resolved": { + "type": "boolean", + "description": "`true` when the company data is complete — always `true` for manual-entry companies; for registry-sourced companies, `true` once the registry fetch resolved." + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of incorporation." + }, + "region": { + "type": "string", + "nullable": true + }, + "company_type": { + "type": "string", + "nullable": true + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "registered_address": { + "type": "string", + "nullable": true, + "description": "Canonical registered address — updated when you send `legal_address`." + }, + "tax_number": { + "type": "string", + "nullable": true + }, + "risk_level": { + "type": "string", + "nullable": true + }, + "verification_status": { + "type": "string", + "nullable": true, + "description": "`verified`, `failed`, or `unknown`." + }, + "is_from_registry": { + "type": "boolean", + "description": "`true` when the company was pre-filled from a registry search rather than entered manually." + }, + "fetch_status": { + "type": "string", + "nullable": true, + "description": "Registry fetch lifecycle for registry-sourced companies (`pending` / `resolved`); `null` for manual entries." + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "nullable": true, + "description": "Canonical column. Returned as a decimal string (e.g. `\"2750000.00\"`)." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "Canonical column. Set by the registry flow or by console/API edits via this endpoint." + }, + "website": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "phone": { + "type": "string", + "nullable": true + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true + }, + "location_of_registration": { + "type": "string", + "nullable": true + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true, + "additionalProperties": true + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Directors / officers attached to the company, including per-person KYC progress." + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Beneficial owners attached to the company, including per-person KYC progress." + }, + "addresses": { + "type": "array", + "nullable": true, + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Registry address detail, when the registry returned any." + }, + "industries": { + "type": "array", + "nullable": true, + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Registry industry classification, when available." + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "$ref": "#/components/schemas/KYBRegistryData" + }, + "user_provided_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "What the end user typed during the hosted registry-confirmation step. Never modified by this endpoint." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the end user confirmed the company data in the hosted flow." + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Stamped on every successful non-empty PATCH, so UIs can surface \"edited by operator\" context." + }, + "is_editable": { + "type": "boolean", + "description": "`true` while the end user can still edit the registry-sourced data in the hosted flow (registry-sourced and not yet confirmed)." + } + } + }, + "examples": { + "Updated company": { + "summary": "Full company record after the correction", + "value": { + "uuid": "6b9479f5-6769-46c8-baf1-df63bbbc5f8c", + "node_id": null, + "status": "Not Finished", + "registry_status": "active", + "data_resolved": true, + "company_name": "Docs Probe Corp", + "registration_number": "DPC-001", + "country_code": "US", + "region": null, + "company_type": "Limited Liability Company", + "incorporation_date": "2015-04-12", + "registered_address": "1 Example Plaza, Wilmington, DE 19801", + "tax_number": null, + "risk_level": null, + "verification_status": "verified", + "is_from_registry": false, + "fetch_status": null, + "alternative_names": null, + "nature_of_business": "Software development", + "registered_capital": "USD 50000", + "registered_capital_amount": null, + "registered_capital_currency": "USD", + "website": "https://docsprobe.example.com", + "email": "legal@docsprobe.example.com", + "phone": "+1 302 555 0143", + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": null, + "officers": [], + "beneficial_owners": [], + "addresses": null, + "industries": null, + "accounts": null, + "registry_data": { + "company_name": "Docs Probe Corp", + "registration_number": "DPC-001", + "country_code": "US", + "incorporation_date": "2015-04-12", + "legal_address": "1 Example Plaza, Wilmington, DE 19801", + "company_type": "Limited Liability Company", + "registry_status": "active", + "verification_status": "verified", + "nature_of_business": "Software development", + "registered_capital": "USD 50000", + "registered_capital_currency": "USD", + "website": "https://docsprobe.example.com", + "email": "legal@docsprobe.example.com", + "phone": "+1 302 555 0143" + }, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": "2026-06-12T00:02:34.276061Z", + "is_editable": false + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field errors arrive as `{\"\": [\"\"]}`. Max-length checks run before format checks, so `country_code: \"USA\"` reports the 2-character limit rather than the ISO message.", + "content": { + "application/json": { + "examples": { + "Alpha-3 country code": { + "summary": "country_code must be alpha-2 — length check fires first", + "value": { + "country_code": [ + "Ensure this field has no more than 2 characters." + ] + } + }, + "Non-alphabetic country code": { + "summary": "2 characters but not letters", + "value": { + "country_code": [ + "country_code must be a 2-letter ISO 3166-1 alpha-2 code." + ] + } + }, + "Invalid currency": { + "summary": "3 characters but not an ISO 4217 code", + "value": { + "registered_capital_currency": [ + "Currency must be a 3-letter ISO 4217 code (e.g. USD, EUR, GBP)." + ] + } + }, + "Invalid phone": { + "summary": "Phone fails the format / digit-count check", + "value": { + "phone": [ + "Invalid phone number format." + ] + } + }, + "Invalid registry status": { + "summary": "registry_status not in the enum", + "value": { + "registry_status": [ + "\"liquidated\" is not a valid choice." + ] + } + }, + "Invalid email": { + "summary": "Malformed email address", + "value": { + "email": [ + "Enter a valid email address." + ] + } + }, + "Invalid website": { + "summary": "Value fails URL validation even after https:// normalization", + "value": { + "website": [ + "Enter a valid URL." + ] + } + }, + "Bad date format": { + "summary": "Dates must be YYYY-MM-DD", + "value": { + "incorporation_date": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Credential lacks write:businesses", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown session, unknown company, or a company that belongs to a different session. Each case has a distinct `detail` message (see examples). The unknown-session lookup runs before credential checks.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "summary": "No session with this ID in your application (or it was deleted)", + "value": { + "detail": "Not found." + } + }, + "Unknown company": { + "summary": "No company with this UUID in your application", + "value": { + "detail": "No KYBCompany matches the given query." + } + }, + "Company / session mismatch": { + "summary": "companyUuid exists but belongs to another session", + "value": { + "detail": "KYB company does not belong to the given session." + } + } + } + } + } + }, + "429": { + "description": "Shared write rate limit exceeded (300 POST/PATCH/DELETE requests per minute per API key). Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "Write budget exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/33333333-4444-5555-6666-777777777777/kyb/88888888-9999-aaaa-bbbb-cccccccccccc/update-data/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"company_name\": \"Docs Probe Corp\",\n \"registration_number\": \"DPC-001\",\n \"country_code\": \"US\",\n \"incorporation_date\": \"2015-04-12\",\n \"company_type\": \"Limited Liability Company\",\n \"legal_address\": \"1 Example Plaza, Wilmington, DE 19801\",\n \"registry_status\": \"active\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_kyb_company_data(\n \"33333333-4444-5555-6666-777777777777\",\n \"88888888-9999-aaaa-bbbb-cccccccccccc\",\n company_name=\"Docs Probe Corp\",\n registration_number=\"DPC-001\",\n country_code=\"US\",\n incorporation_date=\"2015-04-12\",\n legal_address=\"1 Example Plaza, Wilmington, DE 19801\",\n company_type=\"Limited Liability Company\",\n registry_status=\"active\",\n verification_status=\"verified\",\n nature_of_business=\"Software development\",\n registered_capital=\"USD 50000\",\n website=\"docsprobe.example.com\",\n email=\"legal@docsprobe.example.com\",\n phone=\"+1 302 555 0143\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateKybCompanyData(\n \"33333333-4444-5555-6666-777777777777\",\n \"88888888-9999-aaaa-bbbb-cccccccccccc\",\n {\n company_name: \"Docs Probe Corp\",\n registration_number: \"DPC-001\",\n country_code: \"US\",\n incorporation_date: \"2015-04-12\",\n legal_address: \"1 Example Plaza, Wilmington, DE 19801\",\n company_type: \"Limited Liability Company\",\n registry_status: \"active\",\n verification_status: \"verified\",\n nature_of_business: \"Software development\",\n registered_capital: \"USD 50000\",\n website: \"docsprobe.example.com\",\n email: \"legal@docsprobe.example.com\",\n phone: \"+1 302 555 0143\",\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateKYBCompanyData(ctx, \"33333333-4444-5555-6666-777777777777\", \"88888888-9999-aaaa-bbbb-cccccccccccc\", didit.SessionsUpdateKYBCompanyDataParams{\n\tCompanyName: didit.String(\"Docs Probe Corp\"),\n\tRegistrationNumber: didit.String(\"DPC-001\"),\n\tCountryCode: didit.String(\"US\"),\n\tIncorporationDate: didit.String(\"2015-04-12\"),\n\tLegalAddress: didit.String(\"1 Example Plaza, Wilmington, DE 19801\"),\n\tCompanyType: didit.String(\"Limited Liability Company\"),\n\tRegistryStatus: didit.Some(didit.SessionsUpdateKYBCompanyDataParamsRegistryStatus(\"active\")),\n\tVerificationStatus: didit.Some(didit.SessionsUpdateKYBCompanyDataParamsVerificationStatus(\"verified\")),\n\tNatureOfBusiness: didit.String(\"Software development\"),\n\tRegisteredCapital: didit.String(\"USD 50000\"),\n\tWebsite: didit.String(\"docsprobe.example.com\"),\n\tEmail: didit.String(\"legal@docsprobe.example.com\"),\n\tPhone: didit.String(\"+1 302 555 0143\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport me.didit.Didit;\nimport me.didit.types.SessionsUpdateKybCompanyDataParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateKybCompanyData(\"33333333-4444-5555-6666-777777777777\", \"88888888-9999-aaaa-bbbb-cccccccccccc\", SessionsUpdateKybCompanyDataParams.builder()\n .companyName(\"Docs Probe Corp\")\n .registrationNumber(\"DPC-001\")\n .countryCode(\"US\")\n .incorporationDate(LocalDate.parse(\"2015-04-12\"))\n .legalAddress(\"1 Example Plaza, Wilmington, DE 19801\")\n .companyType(\"Limited Liability Company\")\n .registryStatus(SessionsUpdateKybCompanyDataParams.RegistryStatus.of(\"active\"))\n .verificationStatus(SessionsUpdateKybCompanyDataParams.VerificationStatus.of(\"verified\"))\n .natureOfBusiness(\"Software development\")\n .registeredCapital(\"USD 50000\")\n .website(\"docsprobe.example.com\")\n .email(\"legal@docsprobe.example.com\")\n .phone(\"+1 302 555 0143\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateKybCompanyData('33333333-4444-5555-6666-777777777777', '88888888-9999-aaaa-bbbb-cccccccccccc', [\n 'company_name' => 'Docs Probe Corp',\n 'registration_number' => 'DPC-001',\n 'country_code' => 'US',\n 'incorporation_date' => '2015-04-12',\n 'legal_address' => '1 Example Plaza, Wilmington, DE 19801',\n 'company_type' => 'Limited Liability Company',\n 'registry_status' => 'active',\n 'verification_status' => 'verified',\n 'nature_of_business' => 'Software development',\n 'registered_capital' => 'USD 50000',\n 'website' => 'docsprobe.example.com',\n 'email' => 'legal@docsprobe.example.com',\n 'phone' => '+1 302 555 0143',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_kyb_company_data(\n \"33333333-4444-5555-6666-777777777777\",\n \"88888888-9999-aaaa-bbbb-cccccccccccc\",\n company_name: \"Docs Probe Corp\",\n registration_number: \"DPC-001\",\n country_code: \"US\",\n incorporation_date: \"2015-04-12\",\n legal_address: \"1 Example Plaza, Wilmington, DE 19801\",\n company_type: \"Limited Liability Company\",\n registry_status: \"active\",\n verification_status: \"verified\",\n nature_of_business: \"Software development\",\n registered_capital: \"USD 50000\",\n website: \"docsprobe.example.com\",\n email: \"legal@docsprobe.example.com\",\n phone: \"+1 302 555 0143\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateKybCompanyDataAsync(\"33333333-4444-5555-6666-777777777777\", \"88888888-9999-aaaa-bbbb-cccccccccccc\", new SessionsUpdateKybCompanyDataParams\n{\n CompanyName = \"Docs Probe Corp\",\n RegistrationNumber = \"DPC-001\",\n CountryCode = \"US\",\n IncorporationDate = \"2015-04-12\",\n LegalAddress = \"1 Example Plaza, Wilmington, DE 19801\",\n CompanyType = \"Limited Liability Company\",\n RegistryStatus = new SessionsUpdateKybCompanyDataParamsRegistryStatus(\"active\"),\n VerificationStatus = new SessionsUpdateKybCompanyDataParamsVerificationStatus(\"verified\"),\n NatureOfBusiness = \"Software development\",\n RegisteredCapital = \"USD 50000\",\n Website = \"docsprobe.example.com\",\n Email = \"legal@docsprobe.example.com\",\n Phone = \"+1 302 555 0143\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/parties/{partyUuid}/": { + "patch": { + "summary": "Skip (or re-require) a Key People party on a Business Verification (KYB) session", + "description": "Exempt a Key People party from verification, or put it back in scope. This is the operator escape hatch for a party that can never finish its own check - most often because the individual KYC session handed to a UBO or director was deleted, which freezes that child's status and leaves the KYB parked in `Awaiting User` with nothing left that could move it.\n\nSetting `is_skipped: true` drops the party out of the Key People aggregation and immediately recomputes the `KYBKeyPeopleCheck` and the parent session status, so the business session can settle and then be approved or declined. Sending `is_skipped: false` puts the party back in scope and the check goes back to waiting on it.\n\nUnlike the applicant-facing skip in the hosted Key People screen, this endpoint is **not** gated by the role's `allow_skip` setting - that flag limits what the applicant may decide, not what your analyst may override. `requires_verification` is deliberately left untouched, so an overridden party reads as `requires_verification: true` **and** `is_skipped: true` and the record still shows what the rules asked for.\n\nThe party must belong to the session in the path (`404` with `\"Party does not belong to the given session.\"` otherwise) and to your application. A PATCH that actually changes the flag fires the `data.updated` [webhook](/integration/webhooks) on the business session with trigger `console_key_people_skip`; a PATCH that sets the flag to its current value is a no-op that echoes the party back.\n\nAccepts an API key or a console user token; either way the credential needs the `write:businesses` permission.", + "operationId": "patch_v3_session_kyb_party_update", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_kyb_party", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the parent Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "partyUuid", + "in": "path", + "required": true, + "description": "UUID of the Key People party to update (`key_people_checks[].submitted.parties[].uuid` on the business session decision). Must belong to the session referenced by `sessionId`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_skipped" + ], + "properties": { + "is_skipped": { + "type": "boolean", + "description": "`true` exempts the party from verification so the Key People check can settle without it; `false` puts it back in scope.", + "example": true + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Party updated (or echoed unchanged when the flag already had this value). Returns the party - the same shape as an entry of `key_people_checks[].submitted.parties[]` on the business session decision.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Key People party record - the same shape as an entry of `key_people_checks[].submitted.parties[]` on the business session decision.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Party identifier." + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "name": { + "type": "string", + "description": "Display name of the party." + }, + "is_skipped": { + "type": "boolean", + "description": "Whether the party is exempt from verification." + }, + "requires_verification": { + "type": "boolean", + "description": "What the role and threshold rules asked for. Left untouched by this endpoint." + }, + "kyc_session_status": { + "type": "string", + "nullable": true, + "description": "Status of the linked individual KYC session, frozen at deletion time when the session was deleted." + }, + "kyc_session_url": { + "type": "string", + "nullable": true, + "description": "Link to the linked individual KYC session. `null` when there is none or when it was deleted." + }, + "kyc_session_deleted": { + "type": "boolean", + "description": "The linked individual KYC session was deleted, so it can never settle." + }, + "kyb_sub_session_deleted": { + "type": "boolean", + "description": "Corporate-party counterpart of `kyc_session_deleted`." + } + } + } + } + } + }, + "400": { + "description": "Validation error on the request body. `is_skipped` is the only field this endpoint reads, and it is required. Field errors arrive as `{\"\": [\"\"]}`. Body validation runs *after* the party lookup, so a malformed `is_skipped` sent to a party that does not exist reports `404`, not `400`.", + "content": { + "application/json": { + "examples": { + "Missing is_skipped": { + "summary": "Empty body, or a body that omits `is_skipped`", + "value": { + "is_skipped": [ + "This field is required." + ] + } + }, + "Non-boolean is_skipped": { + "summary": "Value is not a boolean — `\"maybe\"`, `2`, `[]`. Booleans plus `\"true\"`/`\"false\"`, `1`/`0` and `\"yes\"`/`\"no\"` are all accepted", + "value": { + "is_skipped": [ + "Must be a valid boolean." + ] + } + }, + "Null is_skipped": { + "summary": "`is_skipped` is present but `null`", + "value": { + "is_skipped": [ + "This field may not be null." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Credential lacks write:businesses", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown party, or a party that belongs to a different session. Each case has a distinct `detail` message (see examples). Credential and permission checks run before the party lookup, so an unauthorised call reports `403` rather than `404`. There is no separate session lookup: an unknown `sessionId` paired with a party of yours reports the mismatch message.", + "content": { + "application/json": { + "examples": { + "Unknown party": { + "summary": "No party with this UUID in your application", + "value": { + "detail": "Not found." + } + }, + "Party / session mismatch": { + "summary": "partyUuid exists but hangs off another session", + "value": { + "detail": "Party does not belong to the given session." + } + } + } + } + } + }, + "429": { + "description": "Shared write rate limit exceeded (300 POST/PATCH/DELETE requests per minute per API key). Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "Write budget exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_kyb_party(\n \"33333333-4444-5555-6666-777777777777\",\n \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\",\n is_skipped=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateKybParty(\n \"33333333-4444-5555-6666-777777777777\",\n \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\",\n {\n is_skipped: true,\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateKYBParty(ctx, \"33333333-4444-5555-6666-777777777777\", \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\", didit.SessionsUpdateKYBPartyParams{\n\tIsSkipped: true,\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsUpdateKybPartyParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateKybParty(\"33333333-4444-5555-6666-777777777777\", \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\", SessionsUpdateKybPartyParams.builder()\n .isSkipped(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateKybParty('33333333-4444-5555-6666-777777777777', '0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41', [\n 'is_skipped' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_kyb_party(\n \"33333333-4444-5555-6666-777777777777\",\n \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\",\n is_skipped: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateKybPartyAsync(\"33333333-4444-5555-6666-777777777777\", \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\", new SessionsUpdateKybPartyParams\n{\n IsSkipped = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/travel-rule/settings/": { + "get": { + "summary": "Get Travel Rule settings", + "description": "Read the application's Travel Rule settings and VASP profile. Auto-creates a default (disabled) settings row and an empty profile on first read.", + "operationId": "getTravelRuleSettings", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_settings", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/settings/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.retrieve_settings()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.retrieveSettings();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.RetrieveSettings(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().retrieveSettings();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->retrieveSettings();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.retrieve_settings\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.RetrieveSettingsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Current Travel Rule settings and VASP profile.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleSettingsDetail" + }, + "examples": { + "Disabled (default)": { + "value": { + "is_enabled": false, + "jurisdiction": "EU", + "name_matching_strictness": "DEFAULT", + "confirmation_timeout_hours": 48, + "timeout_outcome": "HOLD", + "threshold_amount": "0.00", + "inbound_auto_accept": true, + "allow_self_declaration": true, + "allow_screenshot_proof": true, + "auto_wallet_verification": true, + "vasp_attribution_enabled": true, + "legal_name": "", + "lei": "", + "compliance_email": "", + "is_discoverable": true, + "counterparty_screening_enabled": false, + "sunrise_policy": "", + "sunrise_jurisdictions": [ + "CO", + "KH", + "VN" + ], + "non_responsive_policy": "", + "travel_address": "", + "disabled_networks": [], + "networks": { + "gtr": { + "rail": "GTR", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "trust": { + "rail": "TRUST", + "mode": "unavailable", + "state": "unavailable", + "reason": "own_membership_only", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "verifyvasp": { + "rail": "VERIFYVASP", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "sygna": { + "rail": "SYGNA", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + } + }, + "didit_network": { + "listed": false, + "missing": [ + "travel_rule_off", + "legal_name_missing", + "compliance_email_missing" + ], + "compliance_email_confirmed": false + } + } + } + } + } + } + } + } + }, + "put": { + "summary": "Update Travel Rule settings", + "description": "Upsert the application's Travel Rule settings and VASP profile. Partial update — only send the fields you want to change. Setting `is_enabled: true` requires a non-blank `legal_name` (already stored or included in the same request), otherwise returns 400.", + "operationId": "updateTravelRuleSettings", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "update_settings", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleSettingsUpdate" + }, + "example": { + "is_enabled": true, + "legal_name": "Origin CASP SL", + "jurisdiction": "EU", + "name_matching_strictness": "DEFAULT", + "confirmation_timeout_hours": 48, + "timeout_outcome": "HOLD", + "is_discoverable": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PUT https://verification.didit.me/v3/travel-rule/settings/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"is_enabled\": true, \"legal_name\": \"Origin CASP SL\", \"jurisdiction\": \"EU\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.update_settings(\n is_enabled=True,\n legal_name=\"Origin CASP SL\",\n jurisdiction=\"EU\",\n name_matching_strictness=\"DEFAULT\",\n confirmation_timeout_hours=48,\n timeout_outcome=\"HOLD\",\n is_discoverable=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.updateSettings({\n is_enabled: true,\n legal_name: \"Origin CASP SL\",\n jurisdiction: \"EU\",\n name_matching_strictness: \"DEFAULT\",\n confirmation_timeout_hours: 48,\n timeout_outcome: \"HOLD\",\n is_discoverable: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.UpdateSettings(ctx, didit.TravelRuleSettingsUpdate{\n\tIsEnabled: didit.Bool(true),\n\tLegalName: didit.String(\"Origin CASP SL\"),\n\tJurisdiction: didit.String(\"EU\"),\n\tNameMatchingStrictness: didit.Some(didit.TravelRuleSettingsUpdateNameMatchingStrictness(\"DEFAULT\")),\n\tConfirmationTimeoutHours: didit.Int(48),\n\tTimeoutOutcome: didit.Some(didit.TravelRuleSettingsUpdateTimeoutOutcome(\"HOLD\")),\n\tIsDiscoverable: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleSettingsUpdate;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().updateSettings(TravelRuleSettingsUpdate.builder()\n .isEnabled(true)\n .legalName(\"Origin CASP SL\")\n .jurisdiction(\"EU\")\n .nameMatchingStrictness(TravelRuleSettingsUpdate.NameMatchingStrictness.of(\"DEFAULT\"))\n .confirmationTimeoutHours(48L)\n .timeoutOutcome(TravelRuleSettingsUpdate.TimeoutOutcome.of(\"HOLD\"))\n .isDiscoverable(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->updateSettings([\n 'is_enabled' => true,\n 'legal_name' => 'Origin CASP SL',\n 'jurisdiction' => 'EU',\n 'name_matching_strictness' => 'DEFAULT',\n 'confirmation_timeout_hours' => 48,\n 'timeout_outcome' => 'HOLD',\n 'is_discoverable' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.update_settings(\n is_enabled: true,\n legal_name: \"Origin CASP SL\",\n jurisdiction: \"EU\",\n name_matching_strictness: \"DEFAULT\",\n confirmation_timeout_hours: 48,\n timeout_outcome: \"HOLD\",\n is_discoverable: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.UpdateSettingsAsync(new TravelRuleSettingsUpdate\n{\n IsEnabled = true,\n LegalName = \"Origin CASP SL\",\n Jurisdiction = \"EU\",\n NameMatchingStrictness = new TravelRuleSettingsUpdateNameMatchingStrictness(\"DEFAULT\"),\n ConfirmationTimeoutHours = 48,\n TimeoutOutcome = new TravelRuleSettingsUpdateTimeoutOutcome(\"HOLD\"),\n IsDiscoverable = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated settings and profile, in the same shape as GET.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleSettingsDetail" + } + } + } + }, + "400": { + "description": "Validation error, e.g. enabling without a legal_name, a sunrise_policy or non_responsive_policy other than HOLD, REJECT, PROCEED or empty, an unknown field in `matching_rules`, or a `data_sharing_fields` choice that cannot meet the originator obligations of your jurisdiction's regime (the errors are keyed by entity type under `data_sharing_fields`).", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "legal_name": [ + "legal_name is required to enable travel rule." + ] + } + } + } + } + } + } + }, + "/v3/travel-rule/settings/compliance-email/confirmation/": { + "post": { + "summary": "Send the compliance email confirmation", + "description": "Emails a single-use confirmation link to your VASP profile's compliance_email. Opening the link and pressing its confirm button confirms the address, which the Didit network needs before it lists your profile. A new request replaces the previous link; the link stops working when the compliance email changes. One email per minute per address.", + "operationId": "sendTravelRuleComplianceEmailConfirmation", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "send_compliance_email_confirmation", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/settings/compliance-email/confirmation/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.send_compliance_email_confirmation()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.sendComplianceEmailConfirmation();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.SendComplianceEmailConfirmation(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().sendComplianceEmailConfirmation();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->sendComplianceEmailConfirmation();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.send_compliance_email_confirmation\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.SendComplianceEmailConfirmationAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "202": { + "description": "The confirmation email was sent.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sent_to": { + "type": "string", + "format": "email" + } + } + }, + "example": { + "sent_to": "compliance@your-vasp.example" + } + } + } + }, + "400": { + "description": "No compliance email is set, or it is already confirmed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "No compliance email": { + "value": { + "compliance_email": [ + "Set a compliance email before confirming it." + ] + } + }, + "Already confirmed": { + "value": { + "compliance_email": [ + "This compliance email is already confirmed." + ] + } + } + } + } + } + }, + "429": { + "description": "A confirmation went to this address less than a minute ago." + }, + "503": { + "description": "The email could not be sent. Nothing changed; try again in a moment." + } + } + } + }, + "/v3/travel-rule/wallet-addresses/": { + "get": { + "summary": "List wallet address book entries", + "description": "Paginated list of your application's Travel Rule wallet address book, newest first.", + "operationId": "listTravelRuleWalletAddresses", + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/wallet-addresses/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.travel_rule.wallet_addresses.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.travelRule.walletAddresses.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.TravelRule.WalletAddresses.ListAutoPaging(ctx, didit.TravelRuleWalletAddressesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.travelRule().walletAddresses().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletAddresses->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.wallet_addresses.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.TravelRule.WalletAddresses.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated wallet address book entries.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WalletAddressEntry" + } + } + } + } + } + } + } + } + }, + "post": { + "summary": "Register a wallet address", + "description": "Add an entry to your Travel Rule wallet address book so inbound INTERNAL-rail transfers can resolve against it.", + "operationId": "createTravelRuleWalletAddress", + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressEntryCreate" + }, + "example": { + "address": "0xBeneficiaryWallet01", + "chain": "ETH", + "holder_name": "Ana Diaz", + "holder_vendor_data": "user-042", + "self_declared": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/wallet-addresses/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"address\": \"0xBeneficiaryWallet01\", \"chain\": \"ETH\", \"holder_name\": \"Ana Diaz\", \"self_declared\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.wallet_addresses.create(\n address=\"0xBeneficiaryWallet01\",\n chain=\"ETH\",\n holder_vendor_data=\"user-042\",\n holder_name=\"Ana Diaz\",\n self_declared=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.walletAddresses.create({\n address: \"0xBeneficiaryWallet01\",\n chain: \"ETH\",\n holder_vendor_data: \"user-042\",\n holder_name: \"Ana Diaz\",\n self_declared: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.WalletAddresses.Create(ctx, didit.WalletAddressEntryCreate{\n\tAddress: \"0xBeneficiaryWallet01\",\n\tChain: didit.String(\"ETH\"),\n\tHolderVendorData: didit.String(\"user-042\"),\n\tHolderName: didit.String(\"Ana Diaz\"),\n\tSelfDeclared: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.WalletAddressEntryCreate;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().walletAddresses().create(WalletAddressEntryCreate.builder()\n .address(\"0xBeneficiaryWallet01\")\n .chain(\"ETH\")\n .holderVendorData(\"user-042\")\n .holderName(\"Ana Diaz\")\n .selfDeclared(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletAddresses->create([\n 'address' => '0xBeneficiaryWallet01',\n 'chain' => 'ETH',\n 'holder_vendor_data' => 'user-042',\n 'holder_name' => 'Ana Diaz',\n 'self_declared' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.wallet_addresses.create(\n address: \"0xBeneficiaryWallet01\",\n chain: \"ETH\",\n holder_vendor_data: \"user-042\",\n holder_name: \"Ana Diaz\",\n self_declared: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.WalletAddresses.CreateAsync(new WalletAddressEntryCreate\n{\n Address = \"0xBeneficiaryWallet01\",\n Chain = \"ETH\",\n HolderVendorData = \"user-042\",\n HolderName = \"Ana Diaz\",\n SelfDeclared = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The created entry.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressEntry" + } + } + } + }, + "400": { + "description": "Duplicate wallet (address, chain and memo) for this application, a memo or X-address the chain cannot carry, a missing `address`, an invalid `travel_address`, `self_declared: true` while `allow_self_declaration` is off, or `self_declared: true` while `ownership_validation_mode` is `FULL`.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "examples": { + "Duplicate entry": { + "value": { + "address": [ + "A wallet address entry with this address and chain already exists." + ] + } + }, + "Invalid travel address": { + "value": { + "travel_address": [ + "Invalid travel address." + ] + } + }, + "Self-declaration disabled": { + "value": { + "self_declared": [ + "Self-declared ownership is disabled for this application." + ] + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/wallet-addresses/{entry_uuid}/": { + "patch": { + "summary": "Update wallet address holder metadata", + "description": "Partial update of holder_name, holder_vendor_data, entity_type, and travel_address. Setting travel_address attaches a TRP routing address to the entry; invalid values are rejected with 400. The response echoes only these four fields.", + "operationId": "updateTravelRuleWalletAddress", + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "entry_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressEntryUpdate" + }, + "example": { + "holder_name": "Ana Updated", + "travel_address": "ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH https://verification.didit.me/v3/travel-rule/wallet-addresses/{entry_uuid}/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"holder_name\": \"Ana Updated\", \"travel_address\": \"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.wallet_addresses.update(\n \"11111111-2222-3333-4444-555555555555\",\n holder_name=\"Ana Updated\",\n travel_address=\"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.walletAddresses.update(\"11111111-2222-3333-4444-555555555555\", {\n holder_name: \"Ana Updated\",\n travel_address: \"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.WalletAddresses.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.WalletAddressEntryUpdateParam{\n\tHolderName: didit.String(\"Ana Updated\"),\n\tTravelAddress: didit.String(\"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.WalletAddressEntryUpdate;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().walletAddresses().update(\"11111111-2222-3333-4444-555555555555\", WalletAddressEntryUpdate.builder()\n .holderName(\"Ana Updated\")\n .travelAddress(\"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletAddresses->update('11111111-2222-3333-4444-555555555555', [\n 'holder_name' => 'Ana Updated',\n 'travel_address' => 'ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.wallet_addresses.update(\n \"11111111-2222-3333-4444-555555555555\",\n holder_name: \"Ana Updated\",\n travel_address: \"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.WalletAddresses.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new WalletAddressEntryUpdate\n{\n HolderName = \"Ana Updated\",\n TravelAddress = \"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The updated fields.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressEntryUpdate" + } + } + } + }, + "400": { + "description": "`travel_address` is not a valid travel address.", + "content": { + "application/json": { + "examples": { + "Invalid travel address": { + "value": { + "travel_address": [ + "Invalid travel address." + ] + } + } + } + } + } + }, + "404": { + "description": "Unknown or deleted entry." + } + } + }, + "delete": { + "summary": "Delete a wallet address", + "description": "Deletes the entry together with its ownership proofs and the screenshots they stored. It stops matching new transfers immediately and the address is freed up for re-registration. Transfers that already used the wallet keep their own record of it and read the same. Deletion is irreversible.", + "operationId": "deleteTravelRuleWalletAddress", + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "entry_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE https://verification.didit.me/v3/travel-rule/wallet-addresses/{entry_uuid}/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.travel_rule.wallet_addresses.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.travelRule.walletAddresses.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.TravelRule.WalletAddresses.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.travelRule().walletAddresses().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletAddresses->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.wallet_addresses.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.TravelRule.WalletAddresses.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ], + "responses": { + "204": { + "description": "Deleted." + }, + "404": { + "description": "Unknown or already deleted entry." + }, + "503": { + "description": "`wallet_address_deletion_incomplete`: a stored file could not be deleted, so the deletion stopped before it finished. The address-book entry is still there; retry the same request to finish it.", + "content": { + "application/json": { + "example": { + "detail": "A stored file could not be deleted, so the deletion stopped before it finished. Try again.", + "code": "wallet_address_deletion_incomplete" + } + } + } + } + } + } + }, + "/v3/travel-rule/vasps/": { + "get": { + "summary": "Search the VASP directory", + "description": "Paginated, name-sorted search of the VASP directory: the shared directory entries, which Didit builds from the ESMA interim MiCA register, the public TRISA directory, the global LEI register and its own curation, plus the members of the Didit network. Each row carries the routes your application has to it today. Your own application's profile is never listed. Rows also carry the shared due-diligence assessment and your application's own decision, entity screening and questionnaires.", + "operationId": "listTravelRuleVasps", + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "search", + "in": "query", + "schema": { + "type": "string", + "maxLength": 255 + }, + "description": "A name or alias fragment (case-insensitive), an exact LEI, or a domain (example.com or a URL on it). Didit network members match on their legal name, LEI or the domain of their compliance email." + }, + { + "name": "network", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "TRISA" + ] + }, + "description": "Only directory entries a network's member directory currently lists." + }, + { + "name": "jurisdiction", + "in": "query", + "schema": { + "type": "string" + }, + "description": "ISO 3166 alpha-2 or alpha-3 country code. 400 for anything else." + }, + { + "name": "reachable", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "true: only VASPs your application has an available route to (Didit network members included). false: only directory entries it cannot reach today." + }, + { + "name": "kind", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "directory", + "didit" + ] + }, + "description": "Only directory entries, or only Didit network members." + }, + { + "name": "risk", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "description": "Only directory entries whose risk_level is this band. Didit network members, which are not scored, never match." + }, + { + "name": "responsiveness", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "RESPONSIVE", + "UNRESPONSIVE", + "UNKNOWN" + ] + }, + "description": "Only directory entries whose responsiveness.status is this value. Didit network members never match." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/travel-rule/vasps/?search=counterparty&reachable=true' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.travel_rule.list_vasps():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.travelRule.listVasps()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.TravelRule.ListVASPsAutoPaging(ctx, didit.TravelRuleListVASPsParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.travelRule().listVasps()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->listVasps() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.list_vasps.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.TravelRule.ListVaspsAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated, name-sorted VASP directory results.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VaspDirectoryEntry" + } + } + } + } + } + } + }, + "400": { + "description": "A filter is not valid: the body names each field, for example an unknown network, country code, risk band or responsiveness status.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "jurisdiction": [ + "Use an ISO 3166 alpha-2 or alpha-3 country code." + ], + "network": [ + "\"SWIFT\" is not a valid choice." + ] + } + } + } + } + }, + "x-didit-sdk": { + "resource": "travel_rule", + "method": "list_vasps", + "auth": "api_key", + "server": true + } + } + }, + "/v3/travel-rule/vasps/{vasp_id}/": { + "get": { + "summary": "Get a VASP directory entry", + "description": "One directory entry or Didit network member by the id the search returned, with the same fields and the routes your application has to it.", + "operationId": "getTravelRuleVasp", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_vasp", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.retrieve_vasp(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.retrieveVasp(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.RetrieveVASP(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().retrieveVasp(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->retrieveVasp('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.retrieve_vasp(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.RetrieveVaspAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The entry.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VaspDirectoryEntry" + } + } + } + }, + "404": { + "description": "No entry or listed Didit network member has this id.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry has this id." + } + } + } + } + } + } + }, + "/v3/travel-rule/vasps/{vasp_id}/decision/": { + "get": { + "summary": "Get your decision on a VASP", + "description": "Your application's decision on a counterparty (a directory entry or a Didit network member): approved, restricted or blocked. An entity of a brand with no decision of its own follows the brand's decision, named in inherited_from. Private to your application.", + "operationId": "getTravelRuleVaspDecision", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_vasp_decision", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/decision/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.retrieve_vasp_decision(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.retrieveVaspDecision(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.RetrieveVASPDecision(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().retrieveVaspDecision(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->retrieveVaspDecision('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.retrieve_vasp_decision(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.RetrieveVaspDecisionAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The decision that applies, or null.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecisionResponse" + }, + "examples": { + "Own decision": { + "value": { + "decision": { + "decision": "APPROVED", + "reason": "Licensed in DE, questionnaire reviewed", + "decided_by": "", + "updated_at": "2026-10-14T10:05:00Z", + "inherited_from": null + } + } + }, + "Inherited from the brand": { + "value": { + "decision": { + "decision": "BLOCKED", + "reason": "Group refused after review", + "decided_by": "", + "updated_at": "2026-10-14T10:05:00Z", + "inherited_from": { + "id": "5e4d3c2b-1a0f-4e9d-8c7b-6a5f4e3d2c1b", + "name": "Example Group" + } + } + } + }, + "No decision": { + "value": { + "decision": null + } + } + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + } + } + }, + "put": { + "summary": "Set your decision on a VASP", + "description": "Make or change your application's decision on a counterparty: APPROVED, RESTRICTED or BLOCKED, with an optional reason. It never changes the shared directory entry and acts only through your transaction rules (counterparty_dd_status). A decision on a brand also covers each of its entities that has no decision of its own.", + "operationId": "updateTravelRuleVaspDecision", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "update_vasp_decision", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecisionRequest" + }, + "example": { + "decision": "BLOCKED", + "reason": "Unlicensed in its home jurisdiction" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PUT https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/decision/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"decision\": \"BLOCKED\", \"reason\": \"Unlicensed in its home jurisdiction\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.update_vasp_decision(\n \"11111111-2222-3333-4444-555555555555\",\n decision=\"BLOCKED\",\n reason=\"Unlicensed in its home jurisdiction\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.updateVaspDecision(\"11111111-2222-3333-4444-555555555555\", {\n decision: \"BLOCKED\",\n reason: \"Unlicensed in its home jurisdiction\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.UpdateVASPDecision(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleCounterpartyDecisionRequest{\n\tDecision: \"BLOCKED\",\n\tReason: didit.String(\"Unlicensed in its home jurisdiction\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleCounterpartyDecisionRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().updateVaspDecision(\"11111111-2222-3333-4444-555555555555\", TravelRuleCounterpartyDecisionRequest.builder()\n .decision(TravelRuleCounterpartyDecisionRequest.Decision.of(\"BLOCKED\"))\n .reason(\"Unlicensed in its home jurisdiction\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->updateVaspDecision('11111111-2222-3333-4444-555555555555', [\n 'decision' => 'BLOCKED',\n 'reason' => 'Unlicensed in its home jurisdiction',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.update_vasp_decision(\n \"11111111-2222-3333-4444-555555555555\",\n decision: \"BLOCKED\",\n reason: \"Unlicensed in its home jurisdiction\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.UpdateVaspDecisionAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleCounterpartyDecisionRequest\n{\n Decision = \"BLOCKED\",\n Reason = \"Unlicensed in its home jurisdiction\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The decision as it now applies.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecisionResponse" + }, + "example": { + "decision": { + "decision": "BLOCKED", + "reason": "Unlicensed in its home jurisdiction", + "decided_by": "", + "updated_at": "2026-10-14T10:05:00Z", + "inherited_from": null + } + } + } + } + }, + "400": { + "description": "The decision is missing or not one of APPROVED, RESTRICTED or BLOCKED, or the reason is longer than 255 characters.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "decision": [ + "\"MAYBE\" is not a valid choice." + ] + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + } + } + }, + "delete": { + "summary": "Clear your decision on a VASP", + "description": "Clear your application's own decision on a counterparty. The response shows the decision that applies afterwards: null, or for an entity of a brand the brand's decision, which the entity follows again.", + "operationId": "clearTravelRuleVaspDecision", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "clear_vasp_decision", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/decision/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.clear_vasp_decision(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.clearVaspDecision(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.ClearVASPDecision(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().clearVaspDecision(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->clearVaspDecision('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.clear_vasp_decision(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.ClearVaspDecisionAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The decision cleared; the body shows the decision that applies now.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecisionResponse" + }, + "example": { + "decision": null + } + } + } + }, + "404": { + "description": "No counterparty has this id, or your application has no decision of its own on it (a decision it only inherits from its brand cannot be cleared here: clear the brand's).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Unknown id": { + "value": { + "detail": "No directory entry or Didit network member has this id." + } + }, + "No decision of its own": { + "value": { + "detail": "This application has no decision of its own on this counterparty." + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/vasps/{vasp_id}/screening/": { + "get": { + "summary": "Get a VASP's entity screening", + "description": "Whether counterparty screening is on for your application, what one screening costs your organization, how long a result stays fresh, and your latest sanctions and adverse-media screening of the counterparty. Reading it is free.", + "operationId": "getTravelRuleVaspScreening", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_vasp_screening", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/screening/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.retrieve_vasp_screening(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.retrieveVaspScreening(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.RetrieveVASPScreening(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().retrieveVaspScreening(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->retrieveVaspScreening('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.retrieve_vasp_screening(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.RetrieveVaspScreeningAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Your screening setting, its price and your latest screening.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreeningStatus" + }, + "example": { + "enabled": true, + "price": "0.20", + "fresh_days": 30, + "screening": { + "id": "7a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d", + "status": "Approved", + "total_hits": 0, + "score": null, + "screened_at": "2026-10-14T09:58:00Z", + "fresh": true, + "flagged": false + } + } + } + } + }, + "403": { + "description": "Your organization's contract has no rate configured for counterparty screening.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No contract rate is configured for this usage type." + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + } + } + }, + "post": { + "summary": "Screen a VASP", + "description": "Screen the counterparty entity against sanctions and adverse media for your application. Opt-in: counterparty_screening_enabled must be on in your Travel Rule settings. Each new screening is billed at your organization's Company AML Screening price; a screening younger than 30 days is returned instead (200, not billed) unless you send refresh: true. One screening of a counterparty runs at a time per application. The result is private to your application. Sandbox applications are never screened.", + "operationId": "screenTravelRuleVasp", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "screen_vasp", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreeningRequest" + }, + "example": { + "refresh": false + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/screening/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"refresh\": false}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.screen_vasp(\n \"11111111-2222-3333-4444-555555555555\",\n refresh=False,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.screenVasp(\"11111111-2222-3333-4444-555555555555\", {\n refresh: false,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.ScreenVASP(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleCounterpartyScreeningRequest{\n\tRefresh: didit.Bool(false),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleCounterpartyScreeningRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().screenVasp(\"11111111-2222-3333-4444-555555555555\", TravelRuleCounterpartyScreeningRequest.builder()\n .refresh(false)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->screenVasp('11111111-2222-3333-4444-555555555555', [\n 'refresh' => false,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.screen_vasp(\n \"11111111-2222-3333-4444-555555555555\",\n refresh: false\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.ScreenVaspAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleCounterpartyScreeningRequest\n{\n Refresh = false,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "A fresh screening already existed and was returned. Nothing was billed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreeningResult" + }, + "example": { + "outcome": "reused", + "screening": { + "id": "7a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d", + "status": "Approved", + "total_hits": 0, + "score": null, + "screened_at": "2026-10-14T09:58:00Z", + "fresh": true, + "flagged": false + } + } + } + } + }, + "201": { + "description": "A new screening ran and was billed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreeningResult" + }, + "example": { + "outcome": "screened", + "screening": { + "id": "7a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d", + "status": "In Review", + "total_hits": 2, + "score": 85.0, + "screened_at": "2026-10-14T09:58:00Z", + "fresh": true, + "flagged": true + } + } + } + } + }, + "400": { + "description": "The body is not valid, or the counterparty has no name to screen.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "detail": [ + "This counterparty has no name to screen." + ] + } + } + } + }, + "403": { + "description": "Your organization's balance cannot cover the screening (code insufficient_credits; nothing ran and nothing was billed), or your contract has no rate configured for it.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me", + "code": "insufficient_credits" + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + }, + "409": { + "description": "Nothing ran and nothing was billed: counterparty screening is off for your application, the application is a sandbox one, or a screening of this counterparty is already running.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Screening off": { + "value": { + "detail": "Counterparty screening is turned off. Enable it in the Travel Rule settings first." + } + }, + "Sandbox application": { + "value": { + "detail": "Counterparty screening does not run for sandbox applications." + } + }, + "Already running": { + "value": { + "detail": "A screening of this counterparty is already running. Try again in a moment." + } + } + } + } + } + }, + "502": { + "description": "The screening could not be completed. Nothing was billed; try again in a moment.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "The screening provider did not answer. Try again in a moment." + } + } + } + } + } + } + }, + "/v3/travel-rule/vasps/{vasp_id}/questionnaires/": { + "get": { + "summary": "List due-diligence questionnaires with a VASP", + "description": "Every due-diligence questionnaire your application sent the counterparty, newest first, with its status and, once answered, the answers. Private to your application.", + "operationId": "listTravelRuleVaspQuestionnaires", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "list_vasp_questionnaires", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/questionnaires/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.list_vasp_questionnaires(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.listVaspQuestionnaires(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.ListVASPQuestionnaires(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().listVaspQuestionnaires(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->listVaspQuestionnaires('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.list_vasp_questionnaires(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.ListVaspQuestionnairesAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Your questionnaires with this counterparty.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaireList" + }, + "example": { + "questionnaires": [ + { + "id": "3f9e8d7c-6b5a-4c3d-9e2f-1a0b9c8d7e6f", + "status": "SENT", + "form_version": "1", + "sent_to": "travelrule@counterparty.example", + "sent_at": "2026-10-14T10:00:00Z", + "expires_at": "2026-11-13T10:00:00Z", + "answered_at": null, + "valid_until": null, + "current": false, + "answers": {} + }, + { + "id": "8b7a6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d", + "status": "ANSWERED", + "form_version": "1", + "sent_to": "travelrule@counterparty.example", + "sent_at": "2026-09-02T08:00:00Z", + "expires_at": "2026-10-02T08:00:00Z", + "answered_at": "2026-09-04T15:20:00Z", + "valid_until": "2027-09-04T15:20:00Z", + "current": true, + "answers": { + "legal_name": "Example Counterparty VASP, incorporated in the example jurisdiction.", + "licensing": "Crypto-asset service provider authorised under MiCA by its national regulator, licence active.", + "jurisdictions": "EU and EEA. We do not serve the United States.", + "aml_program": "Written AML/CFT policy, annual independent review, last completed in March 2026.", + "compliance_officer": "Head of Compliance, reachable at compliance@counterparty.example.", + "sanctions_screening": "Customers at onboarding and daily; counterparties on every transfer.", + "transaction_monitoring": "Rule-based monitoring with analyst review; suspicious activity reports filed with the FIU.", + "travel_rule": "TRP and email; we answer requests within one business day.", + "custody": "Segregated customer wallets held with a qualified custodian, insured.", + "ownership": "No shareholder holds 25 percent or more; parent company Example Holding Group.", + "record_keeping": "Five-year record-keeping for Travel Rule and transaction data, as our regulator requires.", + "data_protection": "Encrypted at rest and in transit, stored in the EU." + } + } + ] + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + } + } + }, + "post": { + "summary": "Send a due-diligence questionnaire to a VASP", + "description": "Email Didit's standard due-diligence questionnaire to a compliance address on record for the counterparty, with a single-use answer link. The counterparty has 30 days to answer every question; the answers stay current for 365 days. A new send replaces your earlier unanswered questionnaire to the same counterparty, whose link stops working. One email per minute per address.", + "operationId": "sendTravelRuleVaspQuestionnaire", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "send_vasp_questionnaire", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaireRequest" + }, + "example": { + "sent_to": "travelrule@counterparty.example" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/questionnaires/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"sent_to\": \"travelrule@counterparty.example\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.send_vasp_questionnaire(\n \"11111111-2222-3333-4444-555555555555\",\n sent_to=\"travelrule@counterparty.example\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.sendVaspQuestionnaire(\"11111111-2222-3333-4444-555555555555\", {\n sent_to: \"travelrule@counterparty.example\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.SendVASPQuestionnaire(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleCounterpartyQuestionnaireRequest{\n\tSentTo: didit.String(\"travelrule@counterparty.example\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleCounterpartyQuestionnaireRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().sendVaspQuestionnaire(\"11111111-2222-3333-4444-555555555555\", TravelRuleCounterpartyQuestionnaireRequest.builder()\n .sentTo(\"travelrule@counterparty.example\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->sendVaspQuestionnaire('11111111-2222-3333-4444-555555555555', [\n 'sent_to' => 'travelrule@counterparty.example',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.send_vasp_questionnaire(\n \"11111111-2222-3333-4444-555555555555\",\n sent_to: \"travelrule@counterparty.example\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.SendVaspQuestionnaireAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleCounterpartyQuestionnaireRequest\n{\n SentTo = \"travelrule@counterparty.example\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "202": { + "description": "The questionnaire was emailed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaire" + }, + "example": { + "id": "3f9e8d7c-6b5a-4c3d-9e2f-1a0b9c8d7e6f", + "status": "SENT", + "form_version": "1", + "sent_to": "travelrule@counterparty.example", + "sent_at": "2026-10-14T10:00:00Z", + "expires_at": "2026-11-13T10:00:00Z", + "answered_at": null, + "valid_until": null, + "current": false, + "answers": {} + } + } + } + }, + "400": { + "description": "sent_to is not a valid email address or not a compliance address on record for the counterparty, or the counterparty has no compliance address on record.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "Not on record": { + "value": { + "sent_to": [ + "Send the questionnaire to a compliance address on record for this counterparty." + ] + } + }, + "No address": { + "value": { + "sent_to": [ + "No compliance address is on record for this counterparty. Add a contact for it first." + ] + } + } + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + }, + "429": { + "description": "A questionnaire went to this address less than a minute ago." + }, + "503": { + "description": "The email could not be sent and no questionnaire was created. Try again in a moment." + } + } + } + }, + "/v3/transactions/{transaction_id}/travel-rule/": { + "patch": { + "summary": "Finish, cancel, resend, or update the originator wallet of a Travel Rule transfer", + "description": "Provide `payment_txn_id` to report the on-chain hash and move an outbound COMPLETED transfer to FINISHED (its INTERNAL-rail counterpart also moves to FINISHED), `{\"action\": \"cancel\"}` to cancel a non-terminal transfer, or `{\"action\": \"resend\"}` to route an outbound transfer stuck in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE, or NOT_ENOUGH_COUNTERPARTY_DATA again (improve the counterparty data first, e.g. add a travel_address to the destination wallet entry). resend applies to outbound transfers only. `{\"action\": \"update_compliance_email\", \"compliance_email\": \"desk@example.com\"}` replaces the compliance email of an outbound transfer in one of those three statuses, the fix for `reason: email_undeliverable`: the status does not change, and the next `resend` sends the request to that address. A resend is queued, not run inside the request: the response shows the transfer back in AWAITING_COUNTERPARTY with a `resend queued` timeline entry and a queued `delivery`, and where it went arrives as a `travel_rule.status.updated` webhook. When the transfer went over GTR, VerifyVASP or Sygna Bridge, the network learns the on-chain hash or the cancellation after the response, with retries; `delivery` shows how that message stands. `{\"action\": \"update_originator_wallet\", \"wallet_address\": \"...\"}` records the wallet the funds actually left from on an outbound transfer, for example the address a UTXO withdrawal was broadcast from rather than the main wallet you declared. It changes the record only: nothing is sent to the counterparty again and the status stays. The originator becomes `chain_details.originator` with `source: updated`, the change is added to the `timeline` with the previous and the new wallet, and the INTERNAL-rail counterpart of the transfer is updated the same way. `{\"action\": \"beneficiary_vasp\", \"beneficiary_vasp\": \"\"}` names the counterparty yourself when discovery could not, on an outbound transfer in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE or NOT_ENOUGH_COUNTERPARTY_DATA: the transfer is routed to that VASP and sent again.", + "operationId": "patchTravelRuleTransfer", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "update_transfer", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID that owns the transfer.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTransferPatchRequest" + }, + "examples": { + "Finish": { + "value": { + "payment_txn_id": "0xchainhash" + } + }, + "Cancel": { + "value": { + "action": "cancel" + } + }, + "Resend": { + "value": { + "action": "resend" + } + }, + "Update compliance email": { + "value": { + "action": "update_compliance_email", + "compliance_email": "desk@example.com" + } + }, + "Update originator wallet": { + "value": { + "action": "update_originator_wallet", + "wallet_address": "bc1qOriginatorInputAddress01" + } + }, + "Name the counterparty": { + "value": { + "action": "beneficiary_vasp", + "beneficiary_vasp": "9f1c2b34-5d6e-4f70-8a91-b2c3d4e5f607" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH https://verification.didit.me/v3/transactions/{transaction_id}/travel-rule/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"payment_txn_id\": \"0xchainhash\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.travel_rule.update_transfer(\n \"11111111-2222-3333-4444-555555555555\",\n payment_txn_id=\"0xchainhash\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.travelRule.updateTransfer(\"11111111-2222-3333-4444-555555555555\", {\n payment_txn_id: \"0xchainhash\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.TravelRule.UpdateTransfer(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleTransferPatchRequest{\n\tPaymentTxnID: didit.String(\"0xchainhash\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleTransferPatchRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().travelRule().updateTransfer(\"11111111-2222-3333-4444-555555555555\", TravelRuleTransferPatchRequest.builder()\n .paymentTxnId(\"0xchainhash\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->travelRule->updateTransfer('11111111-2222-3333-4444-555555555555', [\n 'payment_txn_id' => '0xchainhash',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.travel_rule.update_transfer(\n \"11111111-2222-3333-4444-555555555555\",\n payment_txn_id: \"0xchainhash\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.TravelRule.UpdateTransferAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleTransferPatchRequest\n{\n PaymentTxnId = \"0xchainhash\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The updated transfer. After `update_compliance_email` its status is unchanged and it keeps `reason: email_undeliverable` until the next `resend`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTransferDetail" + } + } + } + }, + "400": { + "description": "Neither payment_txn_id nor a supported action was provided, `compliance_email` is missing or not an email address on `update_compliance_email`, or, for `update_originator_wallet`, no wallet_address, a wallet or memo the transfer's chain cannot carry, or a wallet that is not one of the transfer's UTXO inputs, or `beneficiary_vasp` is missing or not a valid directory VASP id.", + "content": { + "application/json": { + "examples": { + "No action": { + "value": { + "detail": [ + "Provide payment_txn_id, action=cancel, action=resend, action=beneficiary_vasp, action=update_originator_wallet or action=update_compliance_email." + ] + } + }, + "Invalid compliance email": { + "value": { + "compliance_email": [ + "Enter a valid email address." + ] + } + } + } + } + } + }, + "404": { + "description": "Unknown transaction, the transaction has no Travel Rule transfer, or (action `beneficiary_vasp`) no directory entry has that id." + }, + "409": { + "description": "The transfer is not in a state that allows this operation (e.g. not COMPLETED/outbound for finish, already terminal for cancel, or not an outbound transfer in a resendable status for resend). `update_compliance_email` answers `409` for an inbound transfer, and for an outbound one outside COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE and NOT_ENOUGH_COUNTERPARTY_DATA. `update_originator_wallet` answers `409` for an inbound, cancelled or expired transfer. `beneficiary_vasp` answers `409` unless the transfer is outbound and in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE or NOT_ENOUGH_COUNTERPARTY_DATA.", + "content": { + "application/json": { + "examples": { + "Cancel from a terminal status": { + "value": { + "detail": "Transfer is already in a terminal state." + } + }, + "Resend not allowed": { + "value": { + "detail": "Transfer cannot be resent from its current state." + } + }, + "Finish not allowed": { + "value": { + "detail": "Transfer must be an outbound transfer in COMPLETED status to finish." + } + }, + "Compliance email cannot change": { + "value": { + "detail": "The compliance email of this transfer cannot change from its current state." + } + } + } + } + } + } + } + } + }, + "/v3/transactions/{transaction_id}/travel-rule/ownership/": { + "post": { + "summary": "Confirm or deny wallet ownership", + "description": "Resolve a transfer that waits on wallet ownership: call it on your own side of the transfer when it is UNCONFIRMED_OWNERSHIP, or when it is an inbound transfer in ON_HOLD. Confirming marks the matching wallet address book entry as ownership-verified (for an inbound ON_HOLD transfer with no entry, Didit creates one from beneficiary_data.wallet_address) and runs the beneficiary name match on both sides of the transfer; denying declines both sides. An inbound transfer whose deposit already settled moves on to FINISHED when the match succeeds. When the counterparty wallet was declared self-hosted (counterparty.payment_method.method_type: unhosted_wallet), confirming records that the customer verified who controls that wallet and completes the transfer (COMPLETED) in both directions, or FINISHED for an inbound deposit whose on-chain hash is already known. The wallet verified is the counterparty's: the destination of an outbound withdrawal, or the source (originator's wallet) of an inbound deposit. If it is not in your wallet address book yet, Didit adds it as ownership-verified under the counterparty's name; if the book already holds it under a different holder name, the transfer moves to COUNTERPARTY_MISMATCHED_DATA. For an inbound TRP transfer in UNCONFIRMED_OWNERSHIP, ON_HOLD or COUNTERPARTY_MISMATCHED_DATA, Didit sends your decision to the originating VASP instead: approving returns the payment address (wallet_address, or the matched entry's address) and moves the transfer to COMPLETED, and denying declines it. The same call also decides an inbound transfer that a GTR or Sygna Bridge counterparty sent and that waits for your review (`UNCONFIRMED_OWNERSHIP`, `ON_HOLD` or `COUNTERPARTY_MISMATCHED_DATA`): `confirmed: true` approves it (pass `wallet_address` when the transfer names none) and `confirmed: false` declines it, and the network is answered after the response, with retries. VerifyVASP takes its answer when it asks, so a VerifyVASP transfer that would need your review is declined at once and cannot be decided later.", + "operationId": "confirmTravelRuleOwnership", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "confirm_ownership", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable UUID of the transaction your application holds for this transfer: the beneficiary side's transaction on an INTERNAL-rail transfer, or the transaction you created (outbound or inbound) when the counterparty wallet was declared self-hosted.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleOwnershipConfirmRequest" + }, + "example": { + "confirmed": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/transactions/{transaction_id}/travel-rule/ownership/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"confirmed\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.travel_rule.confirm_ownership(\n \"11111111-2222-3333-4444-555555555555\",\n confirmed=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.travelRule.confirmOwnership(\"11111111-2222-3333-4444-555555555555\", {\n confirmed: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.TravelRule.ConfirmOwnership(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleOwnershipConfirmRequest{\n\tConfirmed: true,\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleOwnershipConfirmRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().travelRule().confirmOwnership(\"11111111-2222-3333-4444-555555555555\", TravelRuleOwnershipConfirmRequest.builder()\n .confirmed(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->travelRule->confirmOwnership('11111111-2222-3333-4444-555555555555', [\n 'confirmed' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.travel_rule.confirm_ownership(\n \"11111111-2222-3333-4444-555555555555\",\n confirmed: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.TravelRule.ConfirmOwnershipAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleOwnershipConfirmRequest\n{\n Confirmed = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The updated transfer. For an inbound transfer the counterparty's originator_data/beneficiary_data stay masked (`data_masked: true`) until ownership is confirmed, then `data_unmasked` records when, why and by whom.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTransferDetail" + } + } + } + }, + "409": { + "description": "The transfer is not waiting on ownership confirmation, no wallet address book entry matches its counterparty wallet, an INTERNAL transfer from another Didit customer needs a control proof (a signed message or a test deposit) before it can be confirmed, or a network transfer was already answered over its network (a VerifyVASP transfer, or a GTR or Sygna Bridge transfer you or Didit already decided).", + "content": { + "application/json": { + "examples": { + "Not awaiting confirmation": { + "value": { + "detail": "Transfer is not awaiting ownership confirmation." + } + }, + "No matching address book entry": { + "value": { + "detail": "wallet address entry no longer available" + } + } + } + } + } + }, + "400": { + "description": "Validation error: confirmed is missing, an inbound TRP approval has no wallet_address and no address to fall back on, or `confirmed: true` on a declared self-hosted wallet transfer that names no counterparty wallet and no `wallet_address` was sent.", + "content": { + "application/json": { + "examples": { + "No payment address": { + "value": { + "wallet_address": [ + "wallet_address is required to approve." + ] + } + } + } + } + } + }, + "404": { + "description": "Unknown transaction, or the transaction has no Travel Rule transfer." + }, + "502": { + "description": "Inbound TRP transfer only: Didit could not deliver your decision to the originating VASP, so the transfer status does not change.", + "content": { + "application/json": { + "examples": { + "Callback not delivered": { + "value": { + "detail": "Counterparty inquiry-resolution callback could not be delivered." + } + } + } + } + } + } + } + } + }, + "/v3/transactions/{transaction_id}/travel-rule/messages/": { + "get": { + "summary": "List Travel Rule messages", + "description": "Messages and document requests exchanged with the counterparty VASP about this transfer.", + "operationId": "listTravelRuleMessages", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "list_messages", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID that owns the transfer.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/transactions/{transaction_id}/travel-rule/messages/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.travel_rule.list_messages(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.travelRule.listMessages(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.TravelRule.ListMessages(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().travelRule().listMessages(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->travelRule->listMessages('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.travel_rule.list_messages(\"11111111-2222-3333-4444-555555555555\").each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.TravelRule.ListMessagesAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The messages, oldest first.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleMessage" + } + } + } + } + }, + "404": { + "description": "Unknown transaction or token, or no Travel Rule transfer on the transaction." + } + } + }, + "post": { + "summary": "Send a Travel Rule message or document request", + "description": "Sends a message or a document request to the counterparty VASP. Works on INTERNAL transfers (the other Didit customer sees it on its side of the pair) and EMAIL transfers (the counterparty is emailed and answers in the pickup portal). A transfer over TRP, GTR, TRUST, VerifyVASP or Sygna carries no messages, because those protocols have none. Limits: 100 messages per transfer, 5000 characters per body, 10 requested documents, 5 files of 10 MB each.", + "operationId": "createTravelRuleMessage", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "create_message", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID that owns the transfer.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessageCreateRequest" + }, + "example": { + "kind": "DOCUMENT_REQUEST", + "body": "Please confirm the date of birth of the beneficiary.", + "requested_documents": [ + "Proof of address" + ] + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessageCreateRequest" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/transactions/{transaction_id}/travel-rule/messages/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"kind\": \"DOCUMENT_REQUEST\", \"body\": \"Please confirm the date of birth of the beneficiary.\", \"requested_documents\": [\"Proof of address\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.travel_rule.create_message(\n \"11111111-2222-3333-4444-555555555555\",\n kind=\"DOCUMENT_REQUEST\",\n body=\"Please confirm the date of birth of the beneficiary.\",\n requested_documents=[\"Proof of address\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.travelRule.createMessage(\"11111111-2222-3333-4444-555555555555\", {\n kind: \"DOCUMENT_REQUEST\",\n body: \"Please confirm the date of birth of the beneficiary.\",\n requested_documents: [\"Proof of address\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.TravelRule.CreateMessage(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleMessageCreateRequest{\n\tKind: didit.Some(didit.TravelRuleMessageCreateRequestKind(\"DOCUMENT_REQUEST\")),\n\tBody: didit.String(\"Please confirm the date of birth of the beneficiary.\"),\n\tRequestedDocuments: didit.Some([]string{\"Proof of address\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.TravelRuleMessageCreateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().travelRule().createMessage(\"11111111-2222-3333-4444-555555555555\", TravelRuleMessageCreateRequest.builder()\n .kind(TravelRuleMessageCreateRequest.Kind.of(\"DOCUMENT_REQUEST\"))\n .body(\"Please confirm the date of birth of the beneficiary.\")\n .requestedDocuments(List.of(\"Proof of address\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->travelRule->createMessage('11111111-2222-3333-4444-555555555555', [\n 'kind' => 'DOCUMENT_REQUEST',\n 'body' => 'Please confirm the date of birth of the beneficiary.',\n 'requested_documents' => ['Proof of address'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.travel_rule.create_message(\n \"11111111-2222-3333-4444-555555555555\",\n kind: \"DOCUMENT_REQUEST\",\n body: \"Please confirm the date of birth of the beneficiary.\",\n requested_documents: [\"Proof of address\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.TravelRule.CreateMessageAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleMessageCreateRequest\n{\n Kind = new TravelRuleMessageCreateRequestKind(\"DOCUMENT_REQUEST\"),\n Body = \"Please confirm the date of birth of the beneficiary.\",\n RequestedDocuments = new string[] { \"Proof of address\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The stored message.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessage" + } + } + } + }, + "400": { + "description": "Invalid body, a file type or size that is not accepted, more than 100 messages on the transfer, or a `reply_to` that is not a message of this transfer.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "requested_documents": [ + "Name at least one document the counterparty should send." + ] + } + } + } + }, + "404": { + "description": "Unknown transaction or token, or no Travel Rule transfer on the transaction." + }, + "409": { + "description": "No message can reach the counterparty. `detail` gives the reason: the transfer runs over a network rail (TRP, GTR, TRUST, VerifyVASP or Sygna), it has no counterparty yet, an INTERNAL transfer's data did not match the wallet holder so nothing reached the recipient, or an EMAIL transfer's pickup link has expired or the transfer is closed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/pickup/{token}/": { + "get": { + "summary": "Get email-rail pickup info", + "description": "Public, unauthenticated. Lets a non-Didit counterparty VASP preview the pending exchange from the emailed pickup link. The token itself is the authentication.", + "operationId": "getTravelRulePickup", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_pickup", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "contact", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "The link's `?contact=` value, the contact the link was sent to. Echoed back as `contact` unchanged; it is not validated." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/pickup/{token}/" + } + ], + "responses": { + "200": { + "description": "Pickup preview.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRulePickupInfo" + }, + "example": { + "reference": "33333333-4444-5555-6666-777777777777", + "requested_at": "2026-07-05T10:00:00Z", + "originator_vasp_name": "Origin CASP SL", + "originator_vasp_lei": "529900EXAMPLEORIG001", + "recipient_vasp_name": "Example Counterparty VASP", + "language": "es", + "amount": "0.250000000000000000", + "currency": "ETH", + "fiat_amount": "", + "fiat_currency": "", + "chain": "ETH", + "beneficiary_wallet": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb", + "status": "AWAITING_COUNTERPARTY", + "ivms_version": "ivms101.2023", + "deadline_at": "2026-07-07T10:00:00Z", + "reminders_sent": 0, + "contact": "", + "decline_reasons": [ + "data_mismatch", + "not_our_customer", + "wallet_not_hosted", + "sanctions", + "risk_appetite", + "jurisdiction", + "insufficient_data", + "other" + ], + "join_invitation": { + "eligible": false, + "url": "" + }, + "originator_data": { + "name": "Jane Doe", + "wallet_address": "0x77ccff0cc624a53cd8b8d8703ced826710211ec7" + }, + "beneficiary_data": { + "name": "Ana Diaz", + "wallet_address": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb" + }, + "ivms_request": { + "originator": { + "originatorPersons": [ + { + "naturalPerson": { + "name": { + "nameIdentifier": [ + { + "primaryIdentifier": "Doe", + "secondaryIdentifier": "Jane", + "nameIdentifierType": "LEGL" + } + ] + } + } + } + ], + "accountNumber": [ + "0x77ccff0cc624a53cd8b8d8703ced826710211ec7" + ] + }, + "beneficiary": { + "beneficiaryPersons": [ + { + "naturalPerson": { + "name": { + "nameIdentifier": [ + { + "primaryIdentifier": "Diaz", + "secondaryIdentifier": "Ana", + "nameIdentifierType": "LEGL" + } + ] + } + } + } + ], + "accountNumber": [ + "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb" + ] + } + }, + "data_masked": false + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The window has closed: the counterparty already answered, the transfer left AWAITING_COUNTERPARTY, or the deadline passed." + } + } + } + }, + "/v3/travel-rule/pickup/{token}/respond/": { + "post": { + "summary": "Respond to an email-rail pickup", + "description": "Public, unauthenticated. Accepts or declines the exchange from the emailed pickup link. On accept, beneficiary_name is checked against the originator's name_matching_strictness policy.", + "operationId": "respondTravelRulePickup", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "respond_pickup", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "contact", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "The link's `?contact=` value. Used when the body sends no `contact`." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRulePickupRespondRequest" + }, + "example": { + "decision": "accept", + "beneficiary_name": "Carlos Ruiz" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/pickup/{token}/respond/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"decision\": \"accept\", \"beneficiary_name\": \"Carlos Ruiz\"}'" + } + ], + "responses": { + "200": { + "description": "The resulting exchange status: COMPLETED or COUNTERPARTY_MISMATCHED_DATA after an accept, COUNTERPARTY_VASP_GENERAL_DECLINE after a decline, which also echoes `decline_reason`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "decline_reason": { + "type": "string", + "description": "Present after a decline." + } + } + }, + "examples": { + "accepted": { + "value": { + "status": "COMPLETED" + } + }, + "declined": { + "value": { + "status": "COUNTERPARTY_VASP_GENERAL_DECLINE", + "decline_reason": "not_our_customer" + } + } + } + } + } + }, + "400": { + "description": "A field is invalid: for example `decision` missing or not `accept` or `decline`, `decline_detail` missing on a decline with `decline_reason: other`, an `ivms_response` that is not an IVMS 101 object or is longer than 16384 characters, or a `contact` in the body that is not a UUID (`{\"contact\": [\"Must be a valid UUID.\"]}`).", + "content": { + "application/json": { + "example": { + "decline_detail": [ + "Say what the reason is when you choose another reason." + ] + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The window has closed: the counterparty already answered, the transfer left AWAITING_COUNTERPARTY, or the deadline passed." + } + } + } + }, + "/v3/travel-rule/pickup/{token}/messages/": { + "get": { + "summary": "List pickup messages", + "description": "Public, unauthenticated. The messages of an EMAIL-rail transfer as the counterparty VASP reads them: what the customer sent is `INBOUND` and is signed with the customer's VASP name. The token is the authentication.", + "operationId": "listTravelRulePickupMessages", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "list_pickup_messages", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/pickup/{token}/messages/" + } + ], + "responses": { + "200": { + "description": "The messages, oldest first, from the counterparty's side.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleMessage" + } + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The pickup link has expired or the transfer is closed." + } + } + }, + "post": { + "summary": "Reply in the pickup portal", + "description": "Public, unauthenticated. The counterparty VASP writes back on an EMAIL-rail transfer. Same body as the transaction's messages; attachments must be PDF, PNG, JPEG or WebP. The token is the authentication.", + "operationId": "createTravelRulePickupMessage", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "create_pickup_message", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessageCreateRequest" + }, + "example": { + "kind": "MESSAGE", + "body": "Attached is the proof of address of the wallet holder." + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessageCreateRequest" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/pickup/{token}/messages/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"kind\": \"MESSAGE\", \"body\": \"Attached is the proof of address of the wallet holder.\"}'" + } + ], + "responses": { + "201": { + "description": "The stored message.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessage" + } + } + } + }, + "400": { + "description": "Invalid body, a file type or size that is not accepted, more than 100 messages on the transfer, or a `reply_to` that is not a message of this transfer.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "requested_documents": [ + "Name at least one document the counterparty should send." + ] + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The pickup link has expired or the transfer is closed." + } + } + } + }, + "/v3/travel-rule/inbound/": { + "post": { + "summary": "Register an after-deposit (sunrise) inbound transfer", + "description": "Register Travel Rule data for a crypto deposit that already settled on-chain before any exchange took place. Creates an inbound travelRule transaction (or reuses an unclaimed deposit) and resolves the transfer against your wallet address book. For a newly created transaction, Didit sends transaction.created first and the travel_rule.status.updated webhooks after it.", + "operationId": "registerTravelRuleInbound", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "register_inbound", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleInboundRequest" + }, + "example": { + "chain": "ETH", + "txid": "0xdeposit-hash", + "wallet_address": "0xYourDepositWallet", + "amount": "500.00", + "currency": "USDC", + "originator_data": { + "name": "Origin CASP SL" + }, + "beneficiary_data": { + "name": "Ana Diaz" + }, + "originating_vasp": { + "name": "Origin CASP SL", + "lei": "", + "travel_address": "ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/inbound/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"chain\":\"ETH\",\"txid\":\"0xdeposit-hash\",\"wallet_address\":\"0xYourDepositWallet\",\"amount\":\"500.00\",\"currency\":\"USDC\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.register_inbound(\n chain=\"ETH\",\n txid=\"0xdeposit-hash\",\n wallet_address=\"0xYourDepositWallet\",\n amount=\"500.00\",\n currency=\"USDC\",\n originator_data={\"name\": \"Origin CASP SL\"},\n beneficiary_data={\"name\": \"Ana Diaz\"},\n originating_vasp={\n \"name\": \"Origin CASP SL\",\n \"lei\": \"\",\n \"travel_address\": \"ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC\",\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.registerInbound({\n chain: \"ETH\",\n txid: \"0xdeposit-hash\",\n wallet_address: \"0xYourDepositWallet\",\n amount: \"500.00\",\n currency: \"USDC\",\n originator_data: { name: \"Origin CASP SL\" },\n beneficiary_data: { name: \"Ana Diaz\" },\n originating_vasp: {\n name: \"Origin CASP SL\",\n lei: \"\",\n travel_address: \"ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC\",\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.RegisterInbound(ctx, didit.TravelRuleInboundRequest{\n\tChain: \"ETH\",\n\tTxid: \"0xdeposit-hash\",\n\tWalletAddress: \"0xYourDepositWallet\",\n\tAmount: \"500.00\",\n\tCurrency: \"USDC\",\n\tOriginatorData: didit.Some(map[string]any{\"name\": \"Origin CASP SL\"}),\n\tBeneficiaryData: didit.Some(map[string]any{\"name\": \"Ana Diaz\"}),\n\tOriginatingVASP: didit.Some(didit.TravelRuleInboundRequestOriginatingVASP{\n\t\tName: didit.String(\"Origin CASP SL\"),\n\t\tLei: didit.String(\"\"),\n\t\tTravelAddress: didit.String(\"ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC\"),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.TravelRuleInboundRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().registerInbound(TravelRuleInboundRequest.builder()\n .chain(\"ETH\")\n .txid(\"0xdeposit-hash\")\n .walletAddress(\"0xYourDepositWallet\")\n .amount(\"500.00\")\n .currency(\"USDC\")\n .originatorData(Map.of(\"name\", \"Origin CASP SL\"))\n .beneficiaryData(Map.of(\"name\", \"Ana Diaz\"))\n .originatingVasp(TravelRuleInboundRequest.OriginatingVasp.builder()\n .name(\"Origin CASP SL\")\n .lei(\"\")\n .travelAddress(\"ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC\")\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->registerInbound([\n 'chain' => 'ETH',\n 'txid' => '0xdeposit-hash',\n 'wallet_address' => '0xYourDepositWallet',\n 'amount' => '500.00',\n 'currency' => 'USDC',\n 'originator_data' => ['name' => 'Origin CASP SL'],\n 'beneficiary_data' => ['name' => 'Ana Diaz'],\n 'originating_vasp' => [\n 'name' => 'Origin CASP SL',\n 'lei' => '',\n 'travel_address' => 'ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC',\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.register_inbound(\n chain: \"ETH\",\n txid: \"0xdeposit-hash\",\n wallet_address: \"0xYourDepositWallet\",\n amount: \"500.00\",\n currency: \"USDC\",\n originator_data: { name: \"Origin CASP SL\" },\n beneficiary_data: { name: \"Ana Diaz\" },\n originating_vasp: {\n name: \"Origin CASP SL\",\n lei: \"\",\n travel_address: \"ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC\",\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.RegisterInboundAsync(new TravelRuleInboundRequest\n{\n Chain = \"ETH\",\n Txid = \"0xdeposit-hash\",\n WalletAddress = \"0xYourDepositWallet\",\n Amount = \"500.00\",\n Currency = \"USDC\",\n OriginatorData = new Dictionary { [\"name\"] = \"Origin CASP SL\" },\n BeneficiaryData = new Dictionary { [\"name\"] = \"Ana Diaz\" },\n OriginatingVasp = new TravelRuleInboundRequestOriginatingVasp\n {\n Name = \"Origin CASP SL\",\n Lei = \"\",\n TravelAddress = \"ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "A transfer for this txid and wallet_address was already registered; the existing one is returned unchanged (created is false).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleInboundResponse" + } + } + } + }, + "201": { + "description": "A new Travel Rule transfer was registered, on a new inbound transaction or on an existing unclaimed deposit (created is true).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleInboundResponse" + } + } + } + }, + "400": { + "description": "A required field (`chain`, `txid`, `wallet_address`, `amount`, `currency`) is missing, `originating_vasp.lei` is not a valid LEI, `originating_vasp.travel_address` is not a valid travel address, a memo, X-address or `utxo` the chain cannot carry, or `originator_data` / `beneficiary_data` that is not an object.", + "content": { + "application/json": { + "examples": { + "Missing field": { + "value": { + "txid": [ + "This field is required." + ] + } + }, + "Invalid LEI": { + "value": { + "originating_vasp": { + "lei": [ + "Invalid LEI: expected 20 alphanumeric characters (ISO 17442)." + ] + } + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/test-vasp/inbound/": { + "post": { + "summary": "Trigger an inbound transfer from the Didit Test VASP (sandbox)", + "description": "Sandbox applications only. The Didit Test VASP sends your application an inbound Travel Rule transfer, so you can exercise the receiving side of an exchange end to end before going live. The transfer is booked and decided by the same logic a real inbound transfer from a network runs against your wallet address book, so the inbox, the decision and the wallet ownership flow behave as they do live. Nothing leaves Didit. A live application receives 400. Send the same `reference` again to get the transfer the first call booked instead of a second one.", + "operationId": "triggerTravelRuleTestVaspInbound", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "trigger_test_vasp_inbound", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTestVaspInboundRequest" + }, + "example": { + "beneficiary_wallet_address": "0xYourSandboxWallet", + "scenario": "accept", + "amount": "1", + "reference": "demo-001" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/test-vasp/inbound/ \\\n -H 'x-api-key: YOUR_SANDBOX_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"beneficiary_wallet_address\":\"0xYourSandboxWallet\",\"scenario\":\"accept\",\"reference\":\"demo-001\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.travel_rule.trigger_test_vasp_inbound(\n beneficiary_wallet_address=\"0xYourSandboxWallet\",\n scenario=\"accept\",\n amount=\"1\",\n reference=\"demo-001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.travelRule.triggerTestVaspInbound({\n beneficiary_wallet_address: \"0xYourSandboxWallet\",\n scenario: \"accept\",\n amount: \"1\",\n reference: \"demo-001\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.TravelRule.TriggerTestVASPInbound(ctx, didit.TravelRuleTestVASPInboundRequest{\n\tBeneficiaryWalletAddress: \"0xYourSandboxWallet\",\n\tScenario: didit.Some(didit.TravelRuleTestVASPInboundRequestScenario(\"accept\")),\n\tAmount: didit.String(\"1\"),\n\tReference: didit.String(\"demo-001\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleTestVaspInboundRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().travelRule().triggerTestVaspInbound(TravelRuleTestVaspInboundRequest.builder()\n .beneficiaryWalletAddress(\"0xYourSandboxWallet\")\n .scenario(TravelRuleTestVaspInboundRequest.Scenario.of(\"accept\"))\n .amount(\"1\")\n .reference(\"demo-001\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->travelRule->triggerTestVaspInbound([\n 'beneficiary_wallet_address' => '0xYourSandboxWallet',\n 'scenario' => 'accept',\n 'amount' => '1',\n 'reference' => 'demo-001',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.travel_rule.trigger_test_vasp_inbound(\n beneficiary_wallet_address: \"0xYourSandboxWallet\",\n scenario: \"accept\",\n amount: \"1\",\n reference: \"demo-001\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.TravelRule.TriggerTestVaspInboundAsync(new TravelRuleTestVaspInboundRequest\n{\n BeneficiaryWalletAddress = \"0xYourSandboxWallet\",\n Scenario = new TravelRuleTestVaspInboundRequestScenario(\"accept\"),\n Amount = \"1\",\n Reference = \"demo-001\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The inbound transfer was booked and decided. A repeat with the same `reference` returns the same body.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTestVaspInboundResponse" + } + } + } + }, + "400": { + "description": "Invalid body, an unknown or unsupported `scenario`, or the application is a live application.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "409": { + "description": "The transfer could not be booked, for example because Travel Rule is not enabled for the application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/widget-session/": { + "post": { + "summary": "Mint a wallet-ownership widget session", + "description": "Mint a hosted wallet-ownership widget URL/token. Send the customer to the returned url to prove control of a wallet (message signing, Satoshi test, screenshot, or self-declaration). The widget is web-only in this release - open the url in the system browser or a custom tab, never inside a mobile SDK webview.", + "operationId": "createTravelRuleWidgetSession", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "create_widget_session", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetSessionCreate" + }, + "example": { + "wallet_address": "0xd3b62acb76fb27a7f8961ec40fa11746b113aca9", + "chain": "ETH", + "holder_name": "Jane Doe", + "vendor_data": "user-042", + "transaction_id": "99999999-aaaa-bbbb-cccc-dddddddddddd", + "expires_in_minutes": 60 + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/widget-session/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"wallet_address\":\"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\",\"chain\":\"ETH\",\"holder_name\":\"Jane Doe\",\"transaction_id\":\"99999999-aaaa-bbbb-cccc-dddddddddddd\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.create_widget_session(\n wallet_address=\"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\",\n chain=\"ETH\",\n holder_name=\"Jane Doe\",\n vendor_data=\"user-042\",\n transaction_id=\"99999999-aaaa-bbbb-cccc-dddddddddddd\",\n expires_in_minutes=60,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.createWidgetSession({\n wallet_address: \"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\",\n chain: \"ETH\",\n holder_name: \"Jane Doe\",\n vendor_data: \"user-042\",\n transaction_id: \"99999999-aaaa-bbbb-cccc-dddddddddddd\",\n expires_in_minutes: 60,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.CreateWidgetSession(ctx, didit.TravelRuleWidgetSessionCreate{\n\tWalletAddress: didit.String(\"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\"),\n\tChain: didit.String(\"ETH\"),\n\tHolderName: didit.String(\"Jane Doe\"),\n\tVendorData: didit.String(\"user-042\"),\n\tTransactionID: didit.String(\"99999999-aaaa-bbbb-cccc-dddddddddddd\"),\n\tExpiresInMinutes: didit.Int(60),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleWidgetSessionCreate;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().createWidgetSession(TravelRuleWidgetSessionCreate.builder()\n .walletAddress(\"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\")\n .chain(\"ETH\")\n .holderName(\"Jane Doe\")\n .vendorData(\"user-042\")\n .transactionId(\"99999999-aaaa-bbbb-cccc-dddddddddddd\")\n .expiresInMinutes(60L)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->createWidgetSession([\n 'wallet_address' => '0xd3b62acb76fb27a7f8961ec40fa11746b113aca9',\n 'chain' => 'ETH',\n 'holder_name' => 'Jane Doe',\n 'vendor_data' => 'user-042',\n 'transaction_id' => '99999999-aaaa-bbbb-cccc-dddddddddddd',\n 'expires_in_minutes' => 60,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.create_widget_session(\n wallet_address: \"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\",\n chain: \"ETH\",\n holder_name: \"Jane Doe\",\n vendor_data: \"user-042\",\n transaction_id: \"99999999-aaaa-bbbb-cccc-dddddddddddd\",\n expires_in_minutes: 60\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.CreateWidgetSessionAsync(new TravelRuleWidgetSessionCreate\n{\n WalletAddress = \"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\",\n Chain = \"ETH\",\n HolderName = \"Jane Doe\",\n VendorData = \"user-042\",\n TransactionId = \"99999999-aaaa-bbbb-cccc-dddddddddddd\",\n ExpiresInMinutes = 60,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The minted widget session.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetSessionResponse" + } + } + } + }, + "400": { + "description": "Invalid body: `callback_url` is not an absolute http(s) URL, `expires_in_minutes` is outside 5 to 10080, `transaction_id` is not a UUID, or a field is longer than its limit.", + "content": { + "application/json": { + "examples": { + "Invalid callback_url": { + "value": { + "callback_url": [ + "callback_url must be an absolute http(s) URL." + ] + } + } + } + } + } + }, + "404": { + "description": "`transaction_id` does not name a Travel Rule transfer in this application." + } + } + } + }, + "/v3/travel-rule/widget/{token}/": { + "get": { + "summary": "Get wallet-ownership widget context", + "description": "Public read endpoint keyed by the widget session token - no API key required. Returns the widget context plus the status of every proof, so you can poll it to detect completion; completed_at becomes non-null once a proof is verified. A SATOSHI_TEST proof can sit at status PENDING while its deposit is seen on-chain but unconfirmed - a background job automatically rechecks it and completes it once the deposit confirms, so no client action is needed while it waits. Each proof carries the fields its method needs: deposit_address, expected_amount, and its own expires_at for SATOSHI_TEST; challenge for MESSAGE_SIGNING. Prefer the travel_rule.status.updated webhook over polling when the session is linked to a transfer.", + "operationId": "getTravelRuleWidgetContext", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_widget_context", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/widget/{token}/" + } + ], + "responses": { + "200": { + "description": "The widget context and proof statuses.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetContext" + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The session expired without completing." + } + } + } + }, + "/v3/travel-rule/widget/{token}/vasps/": { + "get": { + "summary": "Search VASPs from the wallet widget", + "description": "Public read endpoint keyed by the widget session token, no API key required: the VASP picker of the end user's widget. Same search and filters as GET /v3/travel-rule/vasps/ for the widget's application, answering only who each VASP is and whether it can be reached.", + "operationId": "listTravelRuleWidgetVasps", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "list_widget_vasps", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + }, + { + "name": "search", + "in": "query", + "schema": { + "type": "string", + "maxLength": 255 + }, + "description": "A name or alias fragment (case-insensitive), an exact LEI, or a domain (example.com or a URL on it). Didit network members match on their legal name, LEI or the domain of their compliance email." + }, + { + "name": "network", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "TRISA" + ] + }, + "description": "Only directory entries a network's member directory currently lists." + }, + { + "name": "jurisdiction", + "in": "query", + "schema": { + "type": "string" + }, + "description": "ISO 3166 alpha-2 or alpha-3 country code. 400 for anything else." + }, + { + "name": "reachable", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "true: only VASPs your application has an available route to (Didit network members included). false: only directory entries it cannot reach today." + }, + { + "name": "kind", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "directory", + "didit" + ] + }, + "description": "Only directory entries, or only Didit network members." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl 'https://verification.didit.me/v3/travel-rule/widget/{token}/vasps/?search=counterparty'" + } + ], + "responses": { + "200": { + "description": "Paginated, name-sorted results.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VaspDirectoryWidgetEntry" + } + } + } + } + } + } + }, + "400": { + "description": "A filter is not valid: the body names each field, for example an unknown network or country code.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "jurisdiction": [ + "Use an ISO 3166 alpha-2 or alpha-3 country code." + ], + "network": [ + "\"SWIFT\" is not a valid choice." + ] + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The session expired." + } + } + }, + "post": { + "summary": "Pick the counterparty VASP in the wallet widget", + "description": "Public write endpoint keyed by the widget session token, no API key required: the end user chose the destination VASP that automatic discovery could not name. Only an outbound transfer in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE or NOT_ENOUGH_COUNTERPARTY_DATA accepts it. The transfer is routed to that VASP and sent again; automatic discovery never overrides the pick.", + "operationId": "selectTravelRuleWidgetVasp", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "select_widget_vasp", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "vasp" + ], + "properties": { + "vasp": { + "type": "string", + "format": "uuid", + "description": "The `id` of a VASP from `GET /v3/travel-rule/widget/{token}/vasps/`." + } + } + }, + "example": { + "vasp": "9f1c2b34-5d6e-4f70-8a91-b2c3d4e5f607" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/widget/{token}/vasps/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"vasp\": \"9f1c2b34-5d6e-4f70-8a91-b2c3d4e5f607\"}'" + } + ], + "responses": { + "200": { + "description": "The pick was recorded and the transfer is being sent again.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "transfer_status": { + "type": "string", + "description": "The transfer status after the pick." + }, + "counterparty_vasp": { + "type": "string", + "description": "The name of the VASP the end user picked." + } + } + }, + "example": { + "detail": "Counterparty VASP selected; the transfer is being resent.", + "transfer_status": "AWAITING_COUNTERPARTY", + "counterparty_vasp": "Northwind Exchange" + } + } + } + }, + "400": { + "description": "`vasp` is missing, or no live directory entry has that id.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "vasp": [ + "No directory entry has this id." + ] + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "409": { + "description": "The widget session is not linked to a Travel Rule transfer, or the transfer is not an outbound one in a status that accepts a counterparty." + }, + "410": { + "description": "The session expired." + } + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-users/toggle-ongoing-monitoring/": { + "patch": { + "summary": "Toggle AML ongoing monitoring for users", + "description": "Enables or disables AML ongoing monitoring at the user level. Exactly one monitored AML screening is kept per user; users without any AML screening (for example imported users) get one created and screened first, billed as a regular AML screening. Set estimate_only to preview counts and costs without writing. Batches of 20 or fewer resolve inline (200, COMPLETED); larger batches return 202 and run asynchronously.", + "operationId": "toggle_vendor_users_ongoing_monitoring", + "x-didit-sdk": { + "resource": "users", + "method": "set_ongoing_monitoring", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleRequest" + }, + "example": { + "is_enabled": true, + "didit_internal_id_list": [ + "5b9a0000-0000-4000-8000-00000000f00d" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Estimate (when estimate_only) or the completed job for small batches processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger batches are processed asynchronously; poll the job endpoint with the returned job_id.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "403": { + "description": "Missing write:sessions privilege, or insufficient balance to enable (the estimate is included in the error body)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.users.set_ongoing_monitoring(\n is_enabled=True,\n didit_internal_id_list=[\"5b9a0000-0000-4000-8000-00000000f00d\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.users.setOngoingMonitoring({\n is_enabled: true,\n didit_internal_id_list: [\"5b9a0000-0000-4000-8000-00000000f00d\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Users.SetOngoingMonitoring(ctx, didit.OngoingMonitoringToggleRequest{\n\tIsEnabled: true,\n\tDiditInternalIDList: didit.Some([]string{\"5b9a0000-0000-4000-8000-00000000f00d\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.OngoingMonitoringToggleRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.users().setOngoingMonitoring(OngoingMonitoringToggleRequest.builder()\n .isEnabled(true)\n .diditInternalIdList(List.of(\"5b9a0000-0000-4000-8000-00000000f00d\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->setOngoingMonitoring([\n 'is_enabled' => true,\n 'didit_internal_id_list' => ['5b9a0000-0000-4000-8000-00000000f00d'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.users.set_ongoing_monitoring(\n is_enabled: true,\n didit_internal_id_list: [\"5b9a0000-0000-4000-8000-00000000f00d\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Users.SetOngoingMonitoringAsync(new OngoingMonitoringToggleRequest\n{\n IsEnabled = true,\n DiditInternalIdList = new string[] { \"5b9a0000-0000-4000-8000-00000000f00d\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-users/toggle-ongoing-monitoring/{job_id}/": { + "get": { + "summary": "Get AML ongoing monitoring job", + "description": "Progress and per-outcome counters of a monitoring toggle job. Poll every couple of seconds while status is PENDING or PROCESSING.", + "operationId": "get_vendor_users_ongoing_monitoring_job", + "x-didit-sdk": { + "resource": "users", + "method": "retrieve_ongoing_monitoring_job", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Monitoring job UUID returned by the toggle endpoint." + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "404": { + "description": "Unknown job for this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.users.retrieve_ongoing_monitoring_job(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.users.retrieveOngoingMonitoringJob(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Users.RetrieveOngoingMonitoringJob(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.users().retrieveOngoingMonitoringJob(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->retrieveOngoingMonitoringJob('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.users.retrieve_ongoing_monitoring_job(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Users.RetrieveOngoingMonitoringJobAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-businesses/toggle-ongoing-monitoring/": { + "patch": { + "summary": "Toggle AML ongoing monitoring for businesses", + "description": "Business-level equivalent of the user toggle: one monitored AML screening per business, with businesses lacking any AML screening screened first (company entity). Same estimate_only, inline fast-path and async job contract.", + "operationId": "toggle_vendor_businesses_ongoing_monitoring", + "x-didit-sdk": { + "resource": "businesses", + "method": "set_ongoing_monitoring", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleRequest" + }, + "example": { + "is_enabled": true, + "didit_internal_id_list": [ + "b0000000-0000-4000-8000-0000000000b1" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Estimate (when estimate_only) or the completed job for small batches processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger batches are processed asynchronously; poll the job endpoint with the returned job_id.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "403": { + "description": "Missing write:sessions privilege, or insufficient balance to enable (the estimate is included in the error body)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.businesses.set_ongoing_monitoring(\n is_enabled=True,\n didit_internal_id_list=[\"b0000000-0000-4000-8000-0000000000b1\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.businesses.setOngoingMonitoring({\n is_enabled: true,\n didit_internal_id_list: [\"b0000000-0000-4000-8000-0000000000b1\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.SetOngoingMonitoring(ctx, didit.OngoingMonitoringToggleRequest{\n\tIsEnabled: true,\n\tDiditInternalIDList: didit.Some([]string{\"b0000000-0000-4000-8000-0000000000b1\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.OngoingMonitoringToggleRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.businesses().setOngoingMonitoring(OngoingMonitoringToggleRequest.builder()\n .isEnabled(true)\n .diditInternalIdList(List.of(\"b0000000-0000-4000-8000-0000000000b1\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->setOngoingMonitoring([\n 'is_enabled' => true,\n 'didit_internal_id_list' => ['b0000000-0000-4000-8000-0000000000b1'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.businesses.set_ongoing_monitoring(\n is_enabled: true,\n didit_internal_id_list: [\"b0000000-0000-4000-8000-0000000000b1\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.SetOngoingMonitoringAsync(new OngoingMonitoringToggleRequest\n{\n IsEnabled = true,\n DiditInternalIdList = new string[] { \"b0000000-0000-4000-8000-0000000000b1\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-businesses/toggle-ongoing-monitoring/{job_id}/": { + "get": { + "summary": "Get AML ongoing monitoring job", + "description": "Progress and per-outcome counters of a monitoring toggle job. Poll every couple of seconds while status is PENDING or PROCESSING.", + "operationId": "get_vendor_businesses_ongoing_monitoring_job", + "x-didit-sdk": { + "resource": "businesses", + "method": "retrieve_ongoing_monitoring_job", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Monitoring job UUID returned by the toggle endpoint." + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "404": { + "description": "Unknown job for this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.businesses.retrieve_ongoing_monitoring_job(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.businesses.retrieveOngoingMonitoringJob(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.RetrieveOngoingMonitoringJob(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.businesses().retrieveOngoingMonitoringJob(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->retrieveOngoingMonitoringJob('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.businesses.retrieve_ongoing_monitoring_job(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.RetrieveOngoingMonitoringJobAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/businesses/toggle-ongoing-monitoring/": { + "patch": { + "summary": "Toggle AML ongoing monitoring for business sessions", + "description": "Session-level toggle for KYB verifications, addressed by session number. Sessions without an AML screening get one created and attached to the session itself. Same estimate_only, inline fast-path and async job contract as the user-level endpoints. Replaces the legacy synchronous behavior that skipped sessions without an AML screening.", + "operationId": "toggle_business_sessions_ongoing_monitoring", + "x-didit-sdk": { + "resource": "businesses", + "method": "set_session_ongoing_monitoring", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_enabled" + ], + "properties": { + "is_enabled": { + "type": "boolean", + "description": "true to enable monitoring, false to disable it." + }, + "estimate_only": { + "type": "boolean", + "default": false, + "description": "When true, returns the cost estimate and performs no writes." + }, + "update_all": { + "type": "boolean", + "default": false, + "description": "Apply to every entity of the application (scoped by the filter_* params when present)." + }, + "filter_search": { + "type": "string" + }, + "filter_status": { + "type": "array", + "items": { + "type": "string" + } + }, + "filter_country": { + "type": "string" + }, + "filter_date_from": { + "type": "string", + "format": "date" + }, + "filter_date_to": { + "type": "string", + "format": "date" + }, + "session_numbers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Business session numbers to toggle. Required unless update_all (or the legacy toggle_all alias) is true." + }, + "toggle_all": { + "type": "boolean", + "default": false, + "description": "Legacy alias of update_all." + }, + "filter_workflow_id": { + "type": "string" + } + } + }, + "example": { + "is_enabled": true, + "session_numbers": [ + "1001", + "1002" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Estimate (when estimate_only) or the completed job for small batches processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger batches are processed asynchronously; poll the job endpoint with the returned job_id.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "403": { + "description": "Missing write:sessions privilege, or insufficient balance to enable (the estimate is included in the error body)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.businesses.set_session_ongoing_monitoring(\n is_enabled=True,\n session_numbers=[\"1001\", \"1002\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.businesses.setSessionOngoingMonitoring({\n is_enabled: true,\n session_numbers: [\"1001\", \"1002\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.SetSessionOngoingMonitoring(ctx, didit.BusinessesSetSessionOngoingMonitoringParams{\n\tIsEnabled: true,\n\tSessionNumbers: didit.Some([]string{\"1001\", \"1002\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.BusinessesSetSessionOngoingMonitoringParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.businesses().setSessionOngoingMonitoring(BusinessesSetSessionOngoingMonitoringParams.builder()\n .isEnabled(true)\n .sessionNumbers(List.of(\"1001\", \"1002\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->setSessionOngoingMonitoring([\n 'is_enabled' => true,\n 'session_numbers' => ['1001', '1002'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.businesses.set_session_ongoing_monitoring(\n is_enabled: true,\n session_numbers: [\"1001\", \"1002\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.SetSessionOngoingMonitoringAsync(new BusinessesSetSessionOngoingMonitoringParams\n{\n IsEnabled = true,\n SessionNumbers = new string[] { \"1001\", \"1002\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/businesses/toggle-ongoing-monitoring/{job_id}/": { + "get": { + "summary": "Get AML ongoing monitoring job", + "description": "Progress and per-outcome counters of a monitoring toggle job. Poll every couple of seconds while status is PENDING or PROCESSING.", + "operationId": "get_business_sessions_ongoing_monitoring_job", + "x-didit-sdk": { + "resource": "businesses", + "method": "retrieve_session_ongoing_monitoring_job", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Monitoring job UUID returned by the toggle endpoint." + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "404": { + "description": "Unknown job for this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.businesses.retrieve_session_ongoing_monitoring_job(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.businesses.retrieveSessionOngoingMonitoringJob(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.RetrieveSessionOngoingMonitoringJob(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.businesses().retrieveSessionOngoingMonitoringJob(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->retrieveSessionOngoingMonitoringJob('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.businesses.retrieve_session_ongoing_monitoring_job(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.RetrieveSessionOngoingMonitoringJobAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/": { + "get": { + "summary": "List cases", + "description": "List cases for the application, newest first. Supports filtering, search, and sorting; see query parameters. Paginated with `limit`/`offset`.", + "operationId": "list_cases", + "x-didit-sdk": { + "resource": "cases", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size. Defaults to 50." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset of the first record." + }, + { + "name": "status__in", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated statuses to include, e.g. OPEN,AWAITING_USER." + }, + { + "name": "priority", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated priorities to include." + }, + { + "name": "source__in", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated sources to include." + }, + { + "name": "blueprint", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter to cases on this blueprint." + }, + { + "name": "assigned_to", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter to cases assigned to this user." + }, + { + "name": "unassigned", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "true for unassigned cases, false for assigned cases." + }, + { + "name": "tag", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Filter to cases carrying this exact tag." + }, + { + "name": "search", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Matches title, case_number, tag, subject name/UUID/external ID, linked session UUID, or linked transaction UUID." + }, + { + "name": "date_from", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Created-at lower bound (inclusive)." + }, + { + "name": "date_to", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Created-at upper bound (inclusive)." + }, + { + "name": "due_before", + "in": "query", + "schema": { + "type": "string", + "format": "date-time" + }, + "description": "due_at upper bound." + }, + { + "name": "overdue", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "true to return only overdue open/awaiting-user cases." + }, + { + "name": "ordering", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "created_at", + "-created_at", + "due_at", + "-due_at" + ], + "default": "-created_at" + }, + "description": "Sort order. due_at sorts nulls last." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/?status__in=OPEN,AWAITING_USER&ordering=-due_at' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.ListAutoPaging(ctx, didit.CasesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated case list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows. Exact up to 100, capped at 100 beyond that for performance. Rely on `next` being null to detect the last page." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or null." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or null." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseListItem" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create case", + "description": "Create a case anchored to exactly one subject (a user or a business), directly or by deriving the subject from an attached session/transaction link. Source is always `MANUAL` for API-created cases.", + "operationId": "create_case", + "x-didit-sdk": { + "resource": "cases", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string", + "maxLength": 255 + }, + "blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "CaseBlueprint UUID. Omit for no blueprint." + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ], + "default": "MEDIUM" + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "subject": { + "type": "object", + "description": "Required unless links is provided; the case's exactly-one subject.", + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "business" + ] + }, + "uuid": { + "type": "string", + "format": "uuid" + } + } + }, + "links": { + "type": "array", + "description": "Sessions/business sessions/transactions to attach on creation, e.g. [{\"entity_type\": \"transaction\", \"entity_uuid\": \"...\"}]. When subject is omitted, the subject is derived from the first link's owner.", + "items": { + "type": "object" + } + }, + "assigned_to": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "User UUID to assign immediately." + }, + "review_duplicates": { + "type": "boolean", + "default": false, + "description": "Recheck existing investigations before creating the case. When true, a subject with existing cases requires a nonblank separate_incident_reason. The console always enables this protection. Omitting it preserves existing API behavior." + }, + "separate_incident_reason": { + "type": "string", + "maxLength": 1000 + }, + "source_context": { + "$ref": "#/components/schemas/CaseDuplicateCaseFraudSourceContextRequest" + } + } + }, + "example": { + "title": "Manual - Jane Doe", + "blueprint": "b1de0000-0000-4000-8000-000000000002", + "priority": "HIGH", + "subject": { + "type": "user", + "uuid": "5b9a0000-0000-4000-8000-00000000f00d" + }, + "tags": [ + "review" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"title\": \"Manual - Jane Doe\", \"blueprint\": \"b1de0000-0000-4000-8000-000000000002\", \"priority\": \"HIGH\", \"subject\": {\"type\": \"user\", \"uuid\": \"5b9a0000-0000-4000-8000-00000000f00d\"}, \"tags\": [\"review\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.create(\n title=\"Manual - Jane Doe\",\n blueprint=\"b1de0000-0000-4000-8000-000000000002\",\n priority=\"HIGH\",\n tags=[\"review\"],\n subject={\n \"type\": \"user\",\n \"uuid\": \"5b9a0000-0000-4000-8000-00000000f00d\",\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.create({\n title: \"Manual - Jane Doe\",\n blueprint: \"b1de0000-0000-4000-8000-000000000002\",\n priority: \"HIGH\",\n tags: [\"review\"],\n subject: {\n type: \"user\",\n uuid: \"5b9a0000-0000-4000-8000-00000000f00d\",\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Create(ctx, didit.CasesCreateParams{\n\tTitle: \"Manual - Jane Doe\",\n\tBlueprint: didit.String(\"b1de0000-0000-4000-8000-000000000002\"),\n\tPriority: didit.Some(didit.CasesCreateParamsPriority(\"HIGH\")),\n\tTags: didit.Some([]string{\"review\"}),\n\tSubject: didit.Some(didit.CasesCreateParamsSubject{\n\t\tType: didit.Some(didit.CasesCreateParamsSubjectType(\"user\")),\n\t\tUUID: didit.String(\"5b9a0000-0000-4000-8000-00000000f00d\"),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CasesCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().create(CasesCreateParams.builder()\n .title(\"Manual - Jane Doe\")\n .blueprint(\"b1de0000-0000-4000-8000-000000000002\")\n .priority(CasesCreateParams.Priority.of(\"HIGH\"))\n .tags(List.of(\"review\"))\n .subject(CasesCreateParams.Subject.builder()\n .type(CasesCreateParams.Subject.Type.of(\"user\"))\n .uuid(\"5b9a0000-0000-4000-8000-00000000f00d\")\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->create([\n 'title' => 'Manual - Jane Doe',\n 'blueprint' => 'b1de0000-0000-4000-8000-000000000002',\n 'priority' => 'HIGH',\n 'tags' => ['review'],\n 'subject' => [\n 'type' => 'user',\n 'uuid' => '5b9a0000-0000-4000-8000-00000000f00d',\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.create(\n title: \"Manual - Jane Doe\",\n blueprint: \"b1de0000-0000-4000-8000-000000000002\",\n priority: \"HIGH\",\n tags: [\"review\"],\n subject: {\n type: \"user\",\n uuid: \"5b9a0000-0000-4000-8000-00000000f00d\",\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.CreateAsync(new CasesCreateParams\n{\n Title = \"Manual - Jane Doe\",\n Blueprint = \"b1de0000-0000-4000-8000-000000000002\",\n Priority = new CasesCreateParamsPriority(\"HIGH\"),\n Tags = new string[] { \"review\" },\n Subject = new CasesCreateParamsSubject\n {\n Type = new CasesCreateParamsSubjectType(\"user\"),\n Uuid = \"5b9a0000-0000-4000-8000-00000000f00d\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Case created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "400": { + "description": "Neither subject nor links provided, or the subject/blueprint/assignee was not found.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "409": { + "description": "Duplicate review is enabled and an existing case was found without a separate-incident reason. No case was created." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/": { + "get": { + "summary": "Get case", + "description": "Retrieve a case with its subject, blueprint, notes, events, links, and checklist.", + "operationId": "get_case", + "x-didit-sdk": { + "resource": "cases", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.retrieve(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.retrieve(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Retrieve(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().retrieve(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->retrieve('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.retrieve(\"c1a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RetrieveAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Case detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "404": { + "description": "Case (or nested resource) not found in this application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update case", + "description": "Update the case's title, priority, due date, tags, or owning review team. Send only the fields you want to change; fields you omit keep their value. `tags` replaces the whole tag list. `team` takes the uuid of an active review team of this application (list them with `GET .../cases/teams/`), or null to clear the team. Requires a full-access API key, or `write:cases` for a console member: a key with restricted access gets 403. Logs PRIORITY_CHANGED, DUE_DATE_CHANGED, TAG_ADDED, TAG_REMOVED and TEAM_CHANGED events as applicable; a title change is saved without a timeline event. Returns the updated case.", + "operationId": "update_case", + "x-didit-sdk": { + "resource": "cases", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string", + "maxLength": 255, + "description": "New case title." + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "New deadline. Send null to clear it." + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The complete tag list. It replaces the current tags." + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "UUID of the review team that owns the case. It must be an active team of this application. Send null to clear the team." + } + } + }, + "examples": { + "Change priority and deadline": { + "value": { + "priority": "HIGH", + "due_at": "2026-11-01T00:00:00Z" + } + }, + "Move the case to a team": { + "value": { + "team": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + }, + "Clear the team": { + "value": { + "team": null + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"priority\": \"HIGH\", \"due_at\": \"2026-08-01T00:00:00Z\"}'" + }, + { + "lang": "curl", + "label": "curl (change team)", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"team\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.update(\n \"c1a5e000-0000-4000-8000-000000000001\",\n priority=\"HIGH\",\n due_at=\"2026-11-01T00:00:00Z\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.update(\"c1a5e000-0000-4000-8000-000000000001\", {\n priority: \"HIGH\",\n due_at: \"2026-11-01T00:00:00Z\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Update(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesUpdateParams{\n\tPriority: didit.Some(didit.CasesUpdateParamsPriority(\"HIGH\")),\n\tDueAt: didit.Time(time.Date(2026, 11, 1, 0, 0, 0, 0, time.UTC)),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.OffsetDateTime;\nimport me.didit.Didit;\nimport me.didit.types.CasesUpdateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().update(\"c1a5e000-0000-4000-8000-000000000001\", CasesUpdateParams.builder()\n .priority(CasesUpdateParams.Priority.of(\"HIGH\"))\n .dueAt(OffsetDateTime.parse(\"2026-11-01T00:00:00Z\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->update('c1a5e000-0000-4000-8000-000000000001', [\n 'priority' => 'HIGH',\n 'due_at' => '2026-11-01T00:00:00Z',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.update(\n \"c1a5e000-0000-4000-8000-000000000001\",\n priority: \"HIGH\",\n due_at: \"2026-11-01T00:00:00Z\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.UpdateAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesUpdateParams\n{\n Priority = new CasesUpdateParamsPriority(\"HIGH\"),\n DueAt = DateTimeOffset.Parse(\"2026-11-01T00:00:00Z\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated case.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "400": { + "description": "A field failed validation. The body is keyed by field name. `team` is rejected when it is not an active review team of this application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "Team not active in this application": { + "value": { + "team": [ + "Choose an active team of this application." + ] + } + }, + "Invalid priority": { + "value": { + "priority": [ + "\"URGENT\" is not a valid choice." + ] + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Cases endpoints need a full-access key. A console member token needs `write:cases`.", + "content": { + "application/json": { + "examples": { + "Restricted API Key": { + "summary": "API key with restricted access", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "Missing Privilege": { + "summary": "Console member without write:cases, or a key of another application", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Case not found in this application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No Case matches the given query." + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete case", + "description": "Delete a case permanently. Its draft regulatory reports are deleted with it. Linked verifications, people, companies and transactions are not deleted. There is no undelete endpoint.\n\nIf the case has a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "delete_case", + "x-didit-sdk": { + "resource": "cases", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.cases.delete(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.cases.delete(\"c1a5e000-0000-4000-8000-000000000001\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.Cases.Delete(ctx, \"c1a5e000-0000-4000-8000-000000000001\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().delete(\"c1a5e000-0000-4000-8000-000000000001\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->delete('c1a5e000-0000-4000-8000-000000000001');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.delete(\"c1a5e000-0000-4000-8000-000000000001\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Cases.DeleteAsync(\"c1a5e000-0000-4000-8000-000000000001\");" + } + ], + "responses": { + "204": { + "description": "Case deleted." + }, + "403": { + "description": "The key cannot delete cases. It requires a full-access key with `write:cases`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "The case does not exist in this application or was already deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "409": { + "description": "The case has a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/assign/": { + "post": { + "summary": "Assign or unassign case", + "description": "Assign the case to a user, or unassign it by sending assignee_user_id: null. assignee_email/assignee_name auto-provision a User row when the assignee does not exist yet.", + "operationId": "assign_case", + "x-didit-sdk": { + "resource": "cases", + "method": "assign", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "assignee_user_id" + ], + "properties": { + "assignee_user_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "null to unassign." + }, + "assignee_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "assignee_name": { + "type": "string", + "nullable": true + } + } + }, + "example": { + "assignee_user_id": "9a000000-0000-4000-8000-0000000000aa" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/assign/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"assignee_user_id\": \"9a000000-0000-4000-8000-0000000000aa\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.assign(\n \"c1a5e000-0000-4000-8000-000000000001\",\n assignee_user_id=\"9a000000-0000-4000-8000-0000000000aa\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.assign(\"c1a5e000-0000-4000-8000-000000000001\", {\n assignee_user_id: \"9a000000-0000-4000-8000-0000000000aa\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Assign(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesAssignParams{\n\tAssigneeUserID: didit.String(\"9a000000-0000-4000-8000-0000000000aa\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesAssignParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().assign(\"c1a5e000-0000-4000-8000-000000000001\", CasesAssignParams.builder()\n .assigneeUserId(\"9a000000-0000-4000-8000-0000000000aa\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->assign('c1a5e000-0000-4000-8000-000000000001', [\n 'assignee_user_id' => '9a000000-0000-4000-8000-0000000000aa',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.assign(\n \"c1a5e000-0000-4000-8000-000000000001\",\n assignee_user_id: \"9a000000-0000-4000-8000-0000000000aa\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.AssignAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesAssignParams\n{\n AssigneeUserId = \"9a000000-0000-4000-8000-0000000000aa\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Case assigned/unassigned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/resolve/": { + "post": { + "summary": "Resolve case", + "description": "Resolve the case with one of five resolutions: FALSE_POSITIVE (no concern found), VALID_THREAT (concern confirmed), INCONCLUSIVE, REQUIREMENTS_NOT_MET or REQUIREMENTS_SATISFIED. Blocked with 400 if the blueprint's checklist is not optional and items remain incomplete, or if the blueprint requires a resolution note and none is given. When the case's blueprint has a four-eyes checker step (four_eyes_checker_blueprint), the resolution is staged (case.pending_resolution) and the case moves to the checker blueprint instead of resolving immediately; a different officer must call approve or reject-approval. Submit a four-eyes resolution as a console member: a resolution an API key submits has no recorded maker, so approve and reject-approval return 403 for every caller. On a four-eyes blueprint you can name the member who must approve with `checker`: then only that member can approve or reject the resolution, and the response's `checker` names them. Without `checker`, the resolution waits on the checker blueprint's queue. Sending `checker` for a blueprint without a checker step returns 400.", + "operationId": "resolve_case", + "x-didit-sdk": { + "resource": "cases", + "method": "resolve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "resolution" + ], + "properties": { + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ] + }, + "note": { + "type": "string", + "nullable": true, + "description": "Required when the blueprint sets require_resolution_note." + }, + "checker": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Four-eyes blueprints only. The user uuid of the member who must approve this resolution (the same id as `assigned_to.uuid` on a case, or `members[].user_id` on a review team). The member must have case access to this application and cannot be the officer who submits the resolution. Omit it or send null to leave the resolution to the checker queue." + } + } + }, + "examples": { + "Resolve": { + "value": { + "resolution": "FALSE_POSITIVE", + "note": "Confirmed legitimate activity after reviewing documents." + } + }, + "Four-eyes blueprint with a named checker": { + "value": { + "resolution": "VALID_THREAT", + "note": "Document tampering confirmed.", + "checker": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/resolve/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"resolution\": \"FALSE_POSITIVE\", \"note\": \"Confirmed legitimate activity after reviewing documents.\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.resolve(\n \"c1a5e000-0000-4000-8000-000000000001\",\n resolution=\"FALSE_POSITIVE\",\n note=\"Confirmed legitimate activity after reviewing documents.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.resolve(\"c1a5e000-0000-4000-8000-000000000001\", {\n resolution: \"FALSE_POSITIVE\",\n note: \"Confirmed legitimate activity after reviewing documents.\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Resolve(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesResolveParams{\n\tResolution: \"FALSE_POSITIVE\",\n\tNote: didit.String(\"Confirmed legitimate activity after reviewing documents.\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesResolveParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().resolve(\"c1a5e000-0000-4000-8000-000000000001\", CasesResolveParams.builder()\n .resolution(CasesResolveParams.Resolution.of(\"FALSE_POSITIVE\"))\n .note(\"Confirmed legitimate activity after reviewing documents.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->resolve('c1a5e000-0000-4000-8000-000000000001', [\n 'resolution' => 'FALSE_POSITIVE',\n 'note' => 'Confirmed legitimate activity after reviewing documents.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.resolve(\n \"c1a5e000-0000-4000-8000-000000000001\",\n resolution: \"FALSE_POSITIVE\",\n note: \"Confirmed legitimate activity after reviewing documents.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ResolveAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesResolveParams\n{\n Resolution = \"FALSE_POSITIVE\",\n Note = \"Confirmed legitimate activity after reviewing documents.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Resolved, or staged for 4-eyes approval.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "400": { + "description": "The resolution was refused. Non-field errors come as a list of strings: an incomplete checklist, a missing required note, or a `checker` that could not be checked or is not an active reviewer with case access to this application. `checker` errors come keyed by the field: the named member is the officer submitting the resolution, or the case's blueprint has no checker step.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + } + ] + }, + "examples": { + "Incomplete checklist": { + "value": [ + "Case has incomplete checklist items: Verify source of funds" + ] + }, + "Resolution note required": { + "value": [ + "A resolution note is required to resolve this case." + ] + }, + "Reviewer access could not be verified": { + "value": [ + "Reviewer access could not be verified. Try again." + ] + }, + "Checker is not an active reviewer": { + "value": [ + "Choose an active reviewer with case access to this application." + ] + }, + "Checker is the submitting officer": { + "value": { + "checker": [ + "The officer who submitted the resolution cannot be its checker." + ] + } + }, + "Policy has no checker step": { + "value": { + "checker": [ + "This review policy has no checker step, so the case resolves directly." + ] + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/reopen/": { + "post": { + "summary": "Reopen case", + "description": "Move an awaiting-user or resolved case back to open, clearing resolution, resolution_notes, resolved_at, and resolved_by_email.", + "operationId": "reopen_case", + "x-didit-sdk": { + "resource": "cases", + "method": "reopen", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/reopen/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.reopen(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.reopen(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Reopen(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().reopen(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->reopen('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.reopen(\"c1a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ReopenAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Reopen case.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/request-info/": { + "post": { + "summary": "Request info (awaiting user)", + "description": "Move an open case to awaiting_user while more information is collected from the verified user. Fails with 400 if a resolution is currently pending 4-eyes approval.", + "operationId": "request_case_info", + "x-didit-sdk": { + "resource": "cases", + "method": "request_info", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/request-info/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.request_info(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.requestInfo(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RequestInfo(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().requestInfo(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requestInfo('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.request_info(\"c1a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RequestInfoAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Request info (awaiting user).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/escalate/": { + "post": { + "summary": "Escalate case", + "description": "Move the case to its blueprint's configured escalation_blueprint. Requires the current blueprint to have escalation_enabled and an escalation_blueprint set, otherwise 400.", + "operationId": "escalate_case", + "x-didit-sdk": { + "resource": "cases", + "method": "escalate", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/escalate/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.escalate(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.escalate(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Escalate(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().escalate(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->escalate('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.escalate(\"c1a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.EscalateAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Escalate case.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/transfer/": { + "post": { + "summary": "Transfer case", + "description": "Move the case to a different, active blueprint of the same application. Requires the current blueprint to have transfer_enabled, otherwise 400.", + "operationId": "transfer_case", + "x-didit-sdk": { + "resource": "cases", + "method": "transfer", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "target_blueprint" + ], + "properties": { + "target_blueprint": { + "type": "string", + "format": "uuid" + } + } + }, + "example": { + "target_blueprint": "b1de0000-0000-4000-8000-000000000002" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/transfer/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"target_blueprint\": \"b1de0000-0000-4000-8000-000000000002\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.transfer(\n \"c1a5e000-0000-4000-8000-000000000001\",\n target_blueprint=\"b1de0000-0000-4000-8000-000000000002\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.transfer(\"c1a5e000-0000-4000-8000-000000000001\", {\n target_blueprint: \"b1de0000-0000-4000-8000-000000000002\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Transfer(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesTransferParams{\n\tTargetBlueprint: \"b1de0000-0000-4000-8000-000000000002\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesTransferParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().transfer(\"c1a5e000-0000-4000-8000-000000000001\", CasesTransferParams.builder()\n .targetBlueprint(\"b1de0000-0000-4000-8000-000000000002\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->transfer('c1a5e000-0000-4000-8000-000000000001', [\n 'target_blueprint' => 'b1de0000-0000-4000-8000-000000000002',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.transfer(\n \"c1a5e000-0000-4000-8000-000000000001\",\n target_blueprint: \"b1de0000-0000-4000-8000-000000000002\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.TransferAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesTransferParams\n{\n TargetBlueprint = \"b1de0000-0000-4000-8000-000000000002\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Case transferred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/approve/": { + "post": { + "summary": "Approve staged resolution (4-eyes)", + "description": "Apply a resolution staged by resolve on a four-eyes blueprint. The caller must be a different officer than the one who submitted it, otherwise 403. When the resolution names a checker (the case's `checker`), only that member can approve it: any other caller, including an API key, gets 403.", + "operationId": "approve_case_resolution", + "x-didit-sdk": { + "resource": "cases", + "method": "approve_resolution", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/approve/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.approve_resolution(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.approveResolution(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.ApproveResolution(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().approveResolution(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->approveResolution('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.approve_resolution(\"c1a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ApproveResolutionAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Resolution applied.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "409": { + "description": "Case has no resolution pending approval.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "403": { + "description": "The caller cannot approve this resolution: they submitted it, the case has no recorded maker, or the resolution names a different checker. An API key always gets this 403 when a checker is named.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Caller submitted the resolution": { + "value": { + "detail": "The officer who submitted the resolution cannot approve it." + } + }, + "No recorded maker": { + "value": { + "detail": "Case has no recorded maker for this pending resolution; approve is not permitted." + } + }, + "Caller is not the named checker": { + "value": { + "detail": "Only the named checker can approve this resolution." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/reject-approval/": { + "post": { + "summary": "Reject staged resolution (4-eyes)", + "description": "Reject a resolution staged by resolve and return the case to its maker blueprint. The caller must be a different officer than the one who submitted it, otherwise 403. When the resolution names a checker (the case's `checker`), only that member can reject it: any other caller, including an API key, gets 403.", + "operationId": "reject_case_resolution", + "x-didit-sdk": { + "resource": "cases", + "method": "reject_resolution", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "notes": { + "type": "string", + "nullable": true + } + } + }, + "example": { + "notes": "Needs a clearer rationale before this can be approved." + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/reject-approval/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"notes\": \"Needs a clearer rationale before this can be approved.\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.reject_resolution(\n \"c1a5e000-0000-4000-8000-000000000001\",\n notes=\"Needs a clearer rationale before this can be approved.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.rejectResolution(\"c1a5e000-0000-4000-8000-000000000001\", {\n notes: \"Needs a clearer rationale before this can be approved.\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RejectResolution(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesRejectResolutionParams{\n\tNotes: didit.String(\"Needs a clearer rationale before this can be approved.\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesRejectResolutionParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().rejectResolution(\"c1a5e000-0000-4000-8000-000000000001\", CasesRejectResolutionParams.builder()\n .notes(\"Needs a clearer rationale before this can be approved.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->rejectResolution('c1a5e000-0000-4000-8000-000000000001', [\n 'notes' => 'Needs a clearer rationale before this can be approved.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.reject_resolution(\n \"c1a5e000-0000-4000-8000-000000000001\",\n notes: \"Needs a clearer rationale before this can be approved.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RejectResolutionAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesRejectResolutionParams\n{\n Notes = \"Needs a clearer rationale before this can be approved.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Resolution rejected, case returned to the maker blueprint.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "409": { + "description": "Case has no resolution pending approval.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "403": { + "description": "The caller cannot reject this resolution: they submitted it, the case has no recorded maker, or the resolution names a different checker. An API key always gets this 403 when a checker is named.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Caller submitted the resolution": { + "value": { + "detail": "The officer who submitted the resolution cannot reject it." + } + }, + "No recorded maker": { + "value": { + "detail": "Case has no recorded maker for this pending resolution; reject is not permitted." + } + }, + "Caller is not the named checker": { + "value": { + "detail": "Only the named checker can reject this resolution." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/entity-actions/": { + "post": { + "operationId": "apply_case_entity_action", + "x-didit-sdk": { + "resource": "cases", + "method": "apply_entity_action", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseEntityActionRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseEntityActionRequest" + }, + "example": { + "client_reference": "e1a00000-0000-4000-8000-000000000001", + "action": "add_tags", + "tags": [ + "source-of-funds-reviewed" + ] + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseEntityActionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseEntityActionResult" + } + } + }, + "description": "" + }, + "409": { + "description": "The reference belongs to a different action payload." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Apply one explicit action to the case subject. Supply a unique client_reference to obtain an action receipt and make retries idempotent. Reusing that reference with a different payload returns 409. With a reference, inspect receipt.status even when HTTP status is 200: FAILED means no action was applied. Repeating a failed request does not execute it again unless retry is true. Omitting client_reference preserves the legacy response.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.apply_entity_action(\n \"11111111-2222-3333-4444-555555555555\",\n client_reference=\"e1a00000-0000-4000-8000-000000000001\",\n action=\"add_tags\",\n tags=[\"source-of-funds-reviewed\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.applyEntityAction(\"11111111-2222-3333-4444-555555555555\", {\n client_reference: \"e1a00000-0000-4000-8000-000000000001\",\n action: \"add_tags\",\n tags: [\"source-of-funds-reviewed\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.ApplyEntityAction(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CaseActionCaseEntityActionRequest{\n\tClientReference: didit.String(\"e1a00000-0000-4000-8000-000000000001\"),\n\tAction: didit.CaseActionActionEnum(\"add_tags\"),\n\tTags: didit.Some([]string{\"source-of-funds-reviewed\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CaseActionActionEnum;\nimport me.didit.types.CaseActionCaseEntityActionRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().applyEntityAction(\"11111111-2222-3333-4444-555555555555\", CaseActionCaseEntityActionRequest.builder()\n .clientReference(\"e1a00000-0000-4000-8000-000000000001\")\n .action(CaseActionActionEnum.of(\"add_tags\"))\n .tags(List.of(\"source-of-funds-reviewed\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->applyEntityAction('11111111-2222-3333-4444-555555555555', [\n 'client_reference' => 'e1a00000-0000-4000-8000-000000000001',\n 'action' => 'add_tags',\n 'tags' => ['source-of-funds-reviewed'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.apply_entity_action(\n \"11111111-2222-3333-4444-555555555555\",\n client_reference: \"e1a00000-0000-4000-8000-000000000001\",\n action: \"add_tags\",\n tags: [\"source-of-funds-reviewed\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ApplyEntityActionAsync(\"11111111-2222-3333-4444-555555555555\", new CaseActionCaseEntityActionRequest\n{\n ClientReference = \"e1a00000-0000-4000-8000-000000000001\",\n Action = \"add_tags\",\n Tags = new string[] { \"source-of-funds-reviewed\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/print/": { + "get": { + "summary": "Print case (PDF)", + "description": "Render the full case (summary, subject, checklist state, notes, attached sessions/transactions, events, FIU report list) as a PDF and return a presigned download URL.", + "operationId": "print_case", + "x-didit-sdk": { + "resource": "cases", + "method": "print", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/print/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.print(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.print(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Print(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().print(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->print('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.print(\"c1a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.PrintAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Presigned PDF URL.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/notes/": { + "get": { + "operationId": "list_case_notes", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "name": "ordering", + "required": false, + "in": "query", + "description": "Which field to use when ordering the results.", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "offset", + "required": false, + "in": "query", + "description": "The initial index from which to return the results.", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationPaginatedCaseNoteList" + } + } + }, + "description": "" + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The case or note is unavailable for this caller." + } + }, + "tags": [ + "Case Management" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.notes.list(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.notes.list(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.Notes.ListAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasesNotesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().notes().list(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->notes->list('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.notes.list(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.Notes.ListAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "operationId": "create_case_note", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNoteCreateRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNoteCreateRequest" + }, + "example": { + "comment": "Proof of address matches the ID document." + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNoteCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNote" + } + } + }, + "description": "" + }, + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNote" + } + } + }, + "description": "" + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The case or note is unavailable for this caller." + }, + "409": { + "description": "This client reference was already used for another note or for a note that has been deleted." + } + }, + "tags": [ + "Case Management" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Add a case-scoped note. An optional client_reference makes an unchanged retry return the same note without duplicate activity or notifications. reply_to must identify a live note in this case. The response includes a bounded parent excerpt without copying that excerpt into the saved reply.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.notes.create(\n \"11111111-2222-3333-4444-555555555555\",\n comment=\"Proof of address matches the ID document.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.notes.create(\"11111111-2222-3333-4444-555555555555\", {\n comment: \"Proof of address matches the ID document.\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Notes.Create(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CaseCollaborationCaseNoteCreateRequest{\n\tComment: \"Proof of address matches the ID document.\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CaseCollaborationCaseNoteCreateRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().notes().create(\"11111111-2222-3333-4444-555555555555\", CaseCollaborationCaseNoteCreateRequest.builder()\n .comment(\"Proof of address matches the ID document.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->notes->create('11111111-2222-3333-4444-555555555555', [\n 'comment' => 'Proof of address matches the ID document.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.notes.create(\n \"11111111-2222-3333-4444-555555555555\",\n comment: \"Proof of address matches the ID document.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Notes.CreateAsync(\"11111111-2222-3333-4444-555555555555\", new CaseCollaborationCaseNoteCreateRequest\n{\n Comment = \"Proof of address matches the ID document.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/notes/{note_uuid}/": { + "patch": { + "operationId": "root_partial_update", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "note_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationPatchedCaseNoteUpdateRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationPatchedCaseNoteUpdateRequest" + }, + "example": { + "is_pinned": true + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationPatchedCaseNoteUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNote" + } + } + }, + "description": "" + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The case or note is unavailable for this caller." + } + }, + "tags": [ + "Case Management" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Pin or unpin an existing note. Pinned notes appear first. Pin changes are recorded in case activity and do not change evidence or source decisions.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.notes.update(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n is_pinned=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.notes.update(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n {\n is_pinned: true,\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Notes.Update(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\", didit.CaseCollaborationPatchedCaseNoteUpdateRequest{\n\tIsPinned: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CaseCollaborationPatchedCaseNoteUpdateRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().notes().update(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\", CaseCollaborationPatchedCaseNoteUpdateRequest.builder()\n .isPinned(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->notes->update('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555', [\n 'is_pinned' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.notes.update(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n is_pinned: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Notes.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\", new CaseCollaborationPatchedCaseNoteUpdateRequest\n{\n IsPinned = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "operationId": "delete_case_note", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "note_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "The note is permanently deleted. Replies remain, without the deleted parent excerpt. Attachments are deleted unless another note still uses them." + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The case or note is unavailable for this caller." + } + }, + "tags": [ + "Case Management" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.cases.notes.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.cases.notes.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.Cases.Notes.Delete(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().notes().delete(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->notes->delete('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.notes.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\"\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Cases.Notes.DeleteAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/notes/presign/": { + "post": { + "summary": "Upload note attachment", + "description": "Upload a file (JPEG, PNG, WebP, PDF, plain text, or CSV; max 5 MB) to attach to a note. Returns the s3_key to pass in the note's attachments array.", + "operationId": "upload_case_note_attachment", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "create_attachment_upload", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "file" + ], + "properties": { + "file": { + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/notes/presign/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'file=@evidence.pdf'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.notes.create_attachment_upload(\n \"c1a5e000-0000-4000-8000-000000000001\",\n file=open(\"document.pdf\", \"rb\"),\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.notes.createAttachmentUpload(\"c1a5e000-0000-4000-8000-000000000001\", {\n file: await fileFromPath(\"document.pdf\"),\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Notes.CreateAttachmentUpload(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesNotesCreateAttachmentUploadParams{\n\tFile: didit.FileFromPath(\"document.pdf\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.types.CasesNotesCreateAttachmentUploadParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().notes().createAttachmentUpload(\"c1a5e000-0000-4000-8000-000000000001\", CasesNotesCreateAttachmentUploadParams.builder()\n .file(Path.of(\"document.pdf\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->notes->createAttachmentUpload('c1a5e000-0000-4000-8000-000000000001', [\n 'file' => File::fromPath('document.pdf'),\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.notes.create_attachment_upload(\n \"c1a5e000-0000-4000-8000-000000000001\",\n file: Pathname(\"document.pdf\")\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Notes.CreateAttachmentUploadAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesNotesCreateAttachmentUploadParams\n{\n File = FileUpload.FromPath(\"document.pdf\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "File uploaded.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "s3_key": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "filename": { + "type": "string" + }, + "file_size": { + "type": "integer" + } + } + } + } + } + }, + "400": { + "description": "No file, unsupported content type, or file exceeds 5 MB.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "get": { + "summary": "Get note attachment URL", + "description": "Mint a fresh presigned download URL for an attachment already stored on one of the case's notes.", + "operationId": "get_case_note_attachment_url", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "retrieve_attachment_url", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "s3_key", + "in": "query", + "schema": { + "type": "string" + }, + "description": "The attachment's s3_key, as returned by upload or listed on the note.", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/notes/presign/?s3_key=case-notes/c1a5e000-0000-4000-8000-000000000001/evidence.pdf' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.notes.retrieve_attachment_url(\n \"c1a5e000-0000-4000-8000-000000000001\",\n s3_key=\"your-s3-key\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.notes.retrieveAttachmentUrl(\"c1a5e000-0000-4000-8000-000000000001\", {\n s3_key: \"your-s3-key\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Notes.RetrieveAttachmentURL(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesNotesRetrieveAttachmentURLParams{\n\tS3Key: \"your-s3-key\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesNotesRetrieveAttachmentUrlParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().notes().retrieveAttachmentUrl(\"c1a5e000-0000-4000-8000-000000000001\", CasesNotesRetrieveAttachmentUrlParams.builder()\n .s3Key(\"your-s3-key\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->notes->retrieveAttachmentUrl('c1a5e000-0000-4000-8000-000000000001', [\n 's3_key' => 'your-s3-key',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.notes.retrieve_attachment_url(\n \"c1a5e000-0000-4000-8000-000000000001\",\n s3_key: \"your-s3-key\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Notes.RetrieveAttachmentUrlAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesNotesRetrieveAttachmentUrlParams\n{\n S3Key = \"your-s3-key\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Presigned URL.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "s3_key": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "filename": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "s3_key missing or does not belong to this case.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/links/": { + "get": { + "summary": "List case links", + "description": "List sessions, business sessions, and transactions attached to the case.", + "operationId": "list_case_links", + "x-didit-sdk": { + "resource": "cases.links", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/links/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.links.list(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.links.list(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Links.List(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().links().list(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->links->list('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.links.list(\"c1a5e000-0000-4000-8000-000000000001\").each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Links.ListAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Case links.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseLink" + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Add case link", + "description": "Attach a session, business session, or transaction to the case. Rejected with 400 if the entity belongs to a different subject than the case.", + "operationId": "create_case_link", + "x-didit-sdk": { + "resource": "cases.links", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "entity_type", + "entity_uuid" + ], + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "session", + "business_session", + "transaction" + ] + }, + "entity_uuid": { + "type": "string", + "format": "uuid" + } + } + }, + "example": { + "entity_type": "transaction", + "entity_uuid": "77000000-0000-4000-8000-000000000077" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/links/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"entity_type\": \"transaction\", \"entity_uuid\": \"77000000-0000-4000-8000-000000000077\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.links.create(\n \"c1a5e000-0000-4000-8000-000000000001\",\n entity_type=\"transaction\",\n entity_uuid=\"77000000-0000-4000-8000-000000000077\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.links.create(\"c1a5e000-0000-4000-8000-000000000001\", {\n entity_type: \"transaction\",\n entity_uuid: \"77000000-0000-4000-8000-000000000077\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Links.Create(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesLinksCreateParams{\n\tEntityType: \"transaction\",\n\tEntityUUID: \"77000000-0000-4000-8000-000000000077\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesLinksCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().links().create(\"c1a5e000-0000-4000-8000-000000000001\", CasesLinksCreateParams.builder()\n .entityType(CasesLinksCreateParams.EntityType.of(\"transaction\"))\n .entityUuid(\"77000000-0000-4000-8000-000000000077\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->links->create('c1a5e000-0000-4000-8000-000000000001', [\n 'entity_type' => 'transaction',\n 'entity_uuid' => '77000000-0000-4000-8000-000000000077',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.links.create(\n \"c1a5e000-0000-4000-8000-000000000001\",\n entity_type: \"transaction\",\n entity_uuid: \"77000000-0000-4000-8000-000000000077\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Links.CreateAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesLinksCreateParams\n{\n EntityType = \"transaction\",\n EntityUuid = \"77000000-0000-4000-8000-000000000077\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Link created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseLink" + } + } + } + }, + "400": { + "description": "Entity belongs to a different profile than the case subject.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/links/{link_uuid}/": { + "delete": { + "summary": "Remove case link", + "description": "Detach a linked session, business session, or transaction from the case.", + "operationId": "delete_case_link", + "x-didit-sdk": { + "resource": "cases.links", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "link_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "11000000-0000-4000-8000-000000000004" + }, + "description": "Link UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/links/11000000-0000-4000-8000-000000000004/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.cases.links.delete(\n \"c1a5e000-0000-4000-8000-000000000001\",\n \"11000000-0000-4000-8000-000000000004\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.cases.links.delete(\n \"c1a5e000-0000-4000-8000-000000000001\",\n \"11000000-0000-4000-8000-000000000004\",\n);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.Cases.Links.Delete(ctx, \"c1a5e000-0000-4000-8000-000000000001\", \"11000000-0000-4000-8000-000000000004\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().links().delete(\"c1a5e000-0000-4000-8000-000000000001\", \"11000000-0000-4000-8000-000000000004\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->links->delete('c1a5e000-0000-4000-8000-000000000001', '11000000-0000-4000-8000-000000000004');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.links.delete(\n \"c1a5e000-0000-4000-8000-000000000001\",\n \"11000000-0000-4000-8000-000000000004\"\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Cases.Links.DeleteAsync(\"c1a5e000-0000-4000-8000-000000000001\", \"11000000-0000-4000-8000-000000000004\");" + } + ], + "responses": { + "204": { + "description": "Link removed." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/events/": { + "get": { + "summary": "List case events", + "description": "Read-only audit timeline of the case (created, status changes, assignment, resolution, escalation, transfers, tag/priority/due-date changes, entity actions, and more).", + "operationId": "list_case_events", + "x-didit-sdk": { + "resource": "cases", + "method": "list_events", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/events/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.list_events(\n \"c1a5e000-0000-4000-8000-000000000001\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.listEvents(\"c1a5e000-0000-4000-8000-000000000001\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.ListEventsAutoPaging(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesListEventsParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().listEvents(\"c1a5e000-0000-4000-8000-000000000001\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->listEvents('c1a5e000-0000-4000-8000-000000000001') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.list_events(\"c1a5e000-0000-4000-8000-000000000001\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.ListEventsAsync(\"c1a5e000-0000-4000-8000-000000000001\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated events, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows. Exact up to 100, capped at 100 beyond that for performance. Rely on `next` being null to detect the last page." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or null." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or null." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseEvent" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/checklist/": { + "get": { + "summary": "List case checklist", + "description": "List the case's checklist items, copied from the blueprint's content_config.checklist.items at creation, ordered.", + "operationId": "list_case_checklist", + "x-didit-sdk": { + "resource": "cases.checklist", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/checklist/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.checklist.list(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.checklist.list(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Checklist.List(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().checklist().list(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->checklist->list('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.checklist.list(\"c1a5e000-0000-4000-8000-000000000001\").each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Checklist.ListAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Checklist items.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseChecklistItem" + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/checklist/{item_uuid}/": { + "patch": { + "summary": "Toggle checklist item", + "description": "Mark a checklist item complete or incomplete. Resolution is blocked while any item is incomplete, unless the blueprint marks the checklist optional.", + "operationId": "toggle_case_checklist_item", + "x-didit-sdk": { + "resource": "cases.checklist", + "method": "toggle_item", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "item_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c4ec0000-0000-4000-8000-000000000005" + }, + "description": "Checklist item UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_completed" + ], + "properties": { + "is_completed": { + "type": "boolean" + } + } + }, + "example": { + "is_completed": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/checklist/c4ec0000-0000-4000-8000-000000000005/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"is_completed\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.checklist.toggle_item(\n \"c1a5e000-0000-4000-8000-000000000001\",\n \"c4ec0000-0000-4000-8000-000000000005\",\n is_completed=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.checklist.toggleItem(\n \"c1a5e000-0000-4000-8000-000000000001\",\n \"c4ec0000-0000-4000-8000-000000000005\",\n {\n is_completed: true,\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Checklist.ToggleItem(ctx, \"c1a5e000-0000-4000-8000-000000000001\", \"c4ec0000-0000-4000-8000-000000000005\", didit.CasesChecklistToggleItemParams{\n\tIsCompleted: true,\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesChecklistToggleItemParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().checklist().toggleItem(\"c1a5e000-0000-4000-8000-000000000001\", \"c4ec0000-0000-4000-8000-000000000005\", CasesChecklistToggleItemParams.builder()\n .isCompleted(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->checklist->toggleItem('c1a5e000-0000-4000-8000-000000000001', 'c4ec0000-0000-4000-8000-000000000005', [\n 'is_completed' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.checklist.toggle_item(\n \"c1a5e000-0000-4000-8000-000000000001\",\n \"c4ec0000-0000-4000-8000-000000000005\",\n is_completed: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Checklist.ToggleItemAsync(\"c1a5e000-0000-4000-8000-000000000001\", \"c4ec0000-0000-4000-8000-000000000005\", new CasesChecklistToggleItemParams\n{\n IsCompleted = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated checklist item.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseChecklistItem" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/aml/": { + "get": { + "summary": "List case AML hits", + "description": "List AML screening hits for the case's subject. Same shape as the hits returned by POST /v3/aml/.", + "operationId": "list_case_aml_hits", + "x-didit-sdk": { + "resource": "cases", + "method": "list_aml_hits", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/aml/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.list_aml_hits(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.listAmlHits(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.ListAMLHits(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().listAmlHits(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->listAmlHits('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.list_aml_hits(\"c1a5e000-0000-4000-8000-000000000001\").each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ListAmlHitsAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "AML hits.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AMLScreening" + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/statistics/": { + "get": { + "summary": "Case analytics", + "description": "Aggregate case analytics for the date range (defaults to the last 30 days; max 731 days). Charts, tables, totals, assignment events, and current queue counts use the same application-scoped filters. Filters use current case values. Dates apply to each metric event, so older cases assigned or resolved during the period remain included. Queue depth is independent of the date range. Invalid filters return 400; an unknown blueprint or assignee returns an empty result.", + "operationId": "get_case_analytics", + "x-didit-sdk": { + "resource": "cases", + "method": "retrieve_statistics", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "date_from", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Range start (inclusive). Defaults to 30 days ago." + }, + { + "name": "date_to", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Range end (inclusive). Defaults to today." + }, + { + "name": "blueprint", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter by the current blueprint UUID." + }, + { + "name": "assigned_to", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter by the current assigned reviewer user UUID." + }, + { + "name": "priority", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "description": "Filter by current case priority." + }, + { + "name": "source", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ] + }, + "description": "Filter by case creation source." + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ] + }, + "description": "Filter by current case status." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/statistics/?date_from=2026-06-01&date_to=2026-06-30' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.retrieve_statistics()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.retrieveStatistics();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RetrieveStatistics(ctx, didit.CasesRetrieveStatisticsParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().retrieveStatistics();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->retrieveStatistics();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.retrieve_statistics\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RetrieveStatisticsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Analytics payload.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "series": { + "type": "array", + "items": { + "type": "object", + "properties": { + "date": { + "type": "string", + "format": "date" + }, + "created": { + "type": "integer" + }, + "reviewed": { + "type": "integer" + } + } + } + }, + "tables": { + "type": "object", + "properties": { + "officer": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Dimension value: officer email, \"blueprint_name:uuid\", source, or status. \"unassigned\"/\"none\" for missing dimensions." + }, + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + } + }, + "blueprint": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Dimension value: officer email, \"blueprint_name:uuid\", source, or status. \"unassigned\"/\"none\" for missing dimensions." + }, + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + } + }, + "source": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Dimension value: officer email, \"blueprint_name:uuid\", source, or status. \"unassigned\"/\"none\" for missing dimensions." + }, + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + } + }, + "status": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Dimension value: officer email, \"blueprint_name:uuid\", source, or status. \"unassigned\"/\"none\" for missing dimensions." + }, + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + } + } + } + }, + "totals": { + "type": "object", + "properties": { + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + }, + "queue": { + "type": "object", + "description": "Live snapshot, not scoped to the date range.", + "properties": { + "open": { + "type": "integer" + }, + "overdue": { + "type": "integer" + } + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/bulk/status/": { + "post": { + "summary": "Bulk update case status", + "description": "Apply the same status transition to several cases in one call: OPEN reopens, AWAITING_USER requests info, RESOLVED resolves (resolution required). Each case is evaluated independently; gate failures (incomplete checklist, missing required note) are reported per case without aborting the batch.", + "operationId": "bulk_update_case_status", + "x-didit-sdk": { + "resource": "cases", + "method": "bulk_update_status", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "case_uuids", + "status" + ], + "properties": { + "case_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1 + }, + "status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ] + }, + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ], + "description": "Required when status is RESOLVED." + }, + "note": { + "type": "string", + "nullable": true + } + } + }, + "example": { + "case_uuids": [ + "c1a5e000-0000-4000-8000-000000000001" + ], + "status": "RESOLVED", + "resolution": "FALSE_POSITIVE" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/bulk/status/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"case_uuids\": [\"c1a5e000-0000-4000-8000-000000000001\"], \"status\": \"RESOLVED\", \"resolution\": \"FALSE_POSITIVE\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.bulk_update_status(\n case_uuids=[\"c1a5e000-0000-4000-8000-000000000001\"],\n status=\"RESOLVED\",\n resolution=\"FALSE_POSITIVE\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.bulkUpdateStatus({\n case_uuids: [\"c1a5e000-0000-4000-8000-000000000001\"],\n status: \"RESOLVED\",\n resolution: \"FALSE_POSITIVE\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.BulkUpdateStatus(ctx, didit.CasesBulkUpdateStatusParams{\n\tCaseUUIDs: []string{\"c1a5e000-0000-4000-8000-000000000001\"},\n\tStatus: \"RESOLVED\",\n\tResolution: didit.Some(didit.CasesBulkUpdateStatusParamsResolution(\"FALSE_POSITIVE\")),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CasesBulkUpdateStatusParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().bulkUpdateStatus(CasesBulkUpdateStatusParams.builder()\n .caseUuids(List.of(\"c1a5e000-0000-4000-8000-000000000001\"))\n .status(CasesBulkUpdateStatusParams.Status.of(\"RESOLVED\"))\n .resolution(CasesBulkUpdateStatusParams.Resolution.of(\"FALSE_POSITIVE\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->bulkUpdateStatus([\n 'case_uuids' => ['c1a5e000-0000-4000-8000-000000000001'],\n 'status' => 'RESOLVED',\n 'resolution' => 'FALSE_POSITIVE',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.bulk_update_status(\n case_uuids: [\"c1a5e000-0000-4000-8000-000000000001\"],\n status: \"RESOLVED\",\n resolution: \"FALSE_POSITIVE\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.BulkUpdateStatusAsync(new CasesBulkUpdateStatusParams\n{\n CaseUuids = new string[] { \"c1a5e000-0000-4000-8000-000000000001\" },\n Status = \"RESOLVED\",\n Resolution = new CasesBulkUpdateStatusParamsResolution(\"FALSE_POSITIVE\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Per-case results.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBulkUpdateResult" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/bulk/assign/": { + "post": { + "summary": "Bulk assign cases", + "description": "Assign (or unassign, with assignee_user_id: null) several cases at once. All cases must share the same blueprint; mismatches are reported per case without aborting the batch.", + "operationId": "bulk_assign_cases", + "x-didit-sdk": { + "resource": "cases", + "method": "bulk_assign", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "case_uuids", + "assignee_user_id" + ], + "properties": { + "case_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1 + }, + "assignee_user_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "assignee_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "assignee_name": { + "type": "string", + "nullable": true + } + } + }, + "example": { + "case_uuids": [ + "c1a5e000-0000-4000-8000-000000000001" + ], + "assignee_user_id": "9a000000-0000-4000-8000-0000000000aa" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/bulk/assign/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"case_uuids\": [\"c1a5e000-0000-4000-8000-000000000001\"], \"assignee_user_id\": \"9a000000-0000-4000-8000-0000000000aa\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.bulk_assign(\n case_uuids=[\"c1a5e000-0000-4000-8000-000000000001\"],\n assignee_user_id=\"9a000000-0000-4000-8000-0000000000aa\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.bulkAssign({\n case_uuids: [\"c1a5e000-0000-4000-8000-000000000001\"],\n assignee_user_id: \"9a000000-0000-4000-8000-0000000000aa\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.BulkAssign(ctx, didit.CasesBulkAssignParams{\n\tCaseUUIDs: []string{\"c1a5e000-0000-4000-8000-000000000001\"},\n\tAssigneeUserID: didit.String(\"9a000000-0000-4000-8000-0000000000aa\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CasesBulkAssignParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().bulkAssign(CasesBulkAssignParams.builder()\n .caseUuids(List.of(\"c1a5e000-0000-4000-8000-000000000001\"))\n .assigneeUserId(\"9a000000-0000-4000-8000-0000000000aa\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->bulkAssign([\n 'case_uuids' => ['c1a5e000-0000-4000-8000-000000000001'],\n 'assignee_user_id' => '9a000000-0000-4000-8000-0000000000aa',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.bulk_assign(\n case_uuids: [\"c1a5e000-0000-4000-8000-000000000001\"],\n assignee_user_id: \"9a000000-0000-4000-8000-0000000000aa\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.BulkAssignAsync(new CasesBulkAssignParams\n{\n CaseUuids = new string[] { \"c1a5e000-0000-4000-8000-000000000001\" },\n AssigneeUserId = \"9a000000-0000-4000-8000-0000000000aa\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Per-case results.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBulkUpdateResult" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/": { + "get": { + "summary": "List blueprints", + "description": "List case blueprints for the application.", + "operationId": "list_case_blueprints", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.blueprints.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.blueprints.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.Blueprints.ListAutoPaging(ctx, didit.CasesBlueprintsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().blueprints().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.blueprints.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.Blueprints.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated blueprint list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows, capped at 100 for performance." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseBlueprintListItem" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create blueprint", + "description": "Create a case blueprint: general settings (assignment, escalation, transfer, 4-eyes pairing) plus content_config, the six sections that drive the case page. Set publish=false to create an unpublished draft; Publish creates its first live version. Discarding a never-published draft deletes it.", + "operationId": "create_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "assignee_pool" + ], + "properties": { + "name": { + "type": "string", + "maxLength": 24, + "description": "Max 24 characters, e.g. \"AML investigation\"." + }, + "description": { + "type": "string", + "nullable": true + }, + "assignee_pool": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1, + "description": "List of User UUIDs. Empty arrays are rejected with \"Pick at least one assignee\"." + }, + "assignment_method": { + "type": "string", + "enum": [ + "MANUAL", + "AUTOMATIC" + ], + "default": "MANUAL" + }, + "max_active_cases": { + "type": "integer", + "nullable": true, + "minimum": 1, + "description": "Automatic-mode threshold: \"fewer than N cases of this blueprint assigned\"." + }, + "escalation_enabled": { + "type": "boolean", + "default": false + }, + "escalation_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Target blueprint UUID. Not enforced at save time, but the escalate action fails with 400 until both escalation_enabled and escalation_blueprint are set." + }, + "transfer_enabled": { + "type": "boolean", + "default": false + }, + "four_eyes_checker_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Setting this enables 4-eyes review: resolutions on this blueprint stage on the checker blueprint's queue." + }, + "require_resolution_note": { + "type": "boolean", + "default": false + }, + "content_config": { + "$ref": "#/components/schemas/CaseContentConfig" + }, + "is_active": { + "type": "boolean", + "default": true + }, + "default_deadline_hours": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 8760, + "description": "Default deadline for newly opened cases. Existing case deadlines are unchanged." + }, + "publish": { + "type": "boolean", + "writeOnly": true, + "default": true, + "description": "Set false to save a never-published draft. The record remains inactive at version 0 until the draft is explicitly published. Omitting this field preserves immediate creation for existing integrations." + } + } + }, + "example": { + "name": "AML investigation", + "assignee_pool": [ + "9a000000-0000-4000-8000-0000000000aa" + ], + "assignment_method": "AUTOMATIC", + "max_active_cases": 15, + "require_resolution_note": true, + "content_config": { + "aml_control": { + "enabled": true, + "financial": true, + "identity": false + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"name\": \"AML investigation\", \"assignee_pool\": [\"9a000000-0000-4000-8000-0000000000aa\"], \"assignment_method\": \"AUTOMATIC\", \"max_active_cases\": 15, \"require_resolution_note\": true, \"content_config\": {\"aml_control\": {\"enabled\": true, \"financial\": true, \"identity\": false}}}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.create(\n name=\"AML investigation\",\n assignee_pool=[\"9a000000-0000-4000-8000-0000000000aa\"],\n assignment_method=\"AUTOMATIC\",\n max_active_cases=15,\n require_resolution_note=True,\n content_config={\n \"aml_control\": {\n \"enabled\": True,\n \"financial\": True,\n \"identity\": False,\n },\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.create({\n name: \"AML investigation\",\n assignee_pool: [\"9a000000-0000-4000-8000-0000000000aa\"],\n assignment_method: \"AUTOMATIC\",\n max_active_cases: 15,\n require_resolution_note: true,\n content_config: {\n aml_control: {\n enabled: true,\n financial: true,\n identity: false,\n },\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.Create(ctx, didit.CasesBlueprintsCreateParams{\n\tName: \"AML investigation\",\n\tAssigneePool: []string{\"9a000000-0000-4000-8000-0000000000aa\"},\n\tAssignmentMethod: didit.Some(didit.CasesBlueprintsCreateParamsAssignmentMethod(\"AUTOMATIC\")),\n\tMaxActiveCases: didit.Int(15),\n\tRequireResolutionNote: didit.Bool(true),\n\tContentConfig: didit.Some(didit.CaseContentConfigParam{\n\t\tAMLControl: didit.Some(didit.CaseContentConfigParamAMLControl{\n\t\t\tEnabled: didit.Bool(true),\n\t\t\tFinancial: didit.Bool(true),\n\t\t\tIdentity: didit.Bool(false),\n\t\t}),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CaseContentConfig;\nimport me.didit.types.CasesBlueprintsCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().create(CasesBlueprintsCreateParams.builder()\n .name(\"AML investigation\")\n .assigneePool(List.of(\"9a000000-0000-4000-8000-0000000000aa\"))\n .assignmentMethod(CasesBlueprintsCreateParams.AssignmentMethod.of(\"AUTOMATIC\"))\n .maxActiveCases(15L)\n .requireResolutionNote(true)\n .contentConfig(CaseContentConfig.builder()\n .amlControl(CaseContentConfig.AmlControl.builder()\n .enabled(true)\n .financial(true)\n .identity(false)\n .build())\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->create([\n 'name' => 'AML investigation',\n 'assignee_pool' => ['9a000000-0000-4000-8000-0000000000aa'],\n 'assignment_method' => 'AUTOMATIC',\n 'max_active_cases' => 15,\n 'require_resolution_note' => true,\n 'content_config' => [\n 'aml_control' => [\n 'enabled' => true,\n 'financial' => true,\n 'identity' => false,\n ],\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.create(\n name: \"AML investigation\",\n assignee_pool: [\"9a000000-0000-4000-8000-0000000000aa\"],\n assignment_method: \"AUTOMATIC\",\n max_active_cases: 15,\n require_resolution_note: true,\n content_config: {\n aml_control: {\n enabled: true,\n financial: true,\n identity: false,\n },\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.CreateAsync(new CasesBlueprintsCreateParams\n{\n Name = \"AML investigation\",\n AssigneePool = new string[] { \"9a000000-0000-4000-8000-0000000000aa\" },\n AssignmentMethod = new CasesBlueprintsCreateParamsAssignmentMethod(\"AUTOMATIC\"),\n MaxActiveCases = 15,\n RequireResolutionNote = true,\n ContentConfig = new CaseContentConfig\n {\n AmlControl = new CaseContentConfigAmlControl\n {\n Enabled = true,\n Financial = true,\n Identity = false,\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Blueprint created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintDetail" + } + } + } + }, + "400": { + "description": "Name over 24 chars, empty assignee_pool, or a cross-referenced blueprint (escalation/checker) not found.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/{blueprint_uuid}/": { + "get": { + "summary": "Get blueprint", + "description": "Retrieve a blueprint, including its connected creation sources (AML config, transaction rules, workflow nodes) and which blueprints escalate to it.", + "operationId": "get_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "blueprint_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "b1de0000-0000-4000-8000-000000000002" + }, + "description": "Blueprint UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/b1de0000-0000-4000-8000-000000000002/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.retrieve(\n \"b1de0000-0000-4000-8000-000000000002\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.retrieve(\"b1de0000-0000-4000-8000-000000000002\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.Retrieve(ctx, \"b1de0000-0000-4000-8000-000000000002\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().retrieve(\"b1de0000-0000-4000-8000-000000000002\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->retrieve('b1de0000-0000-4000-8000-000000000002');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.retrieve(\"b1de0000-0000-4000-8000-000000000002\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.RetrieveAsync(\"b1de0000-0000-4000-8000-000000000002\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Blueprint detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update blueprint", + "description": "Partially update a blueprint's general settings or content_config.", + "operationId": "update_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "blueprint_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "b1de0000-0000-4000-8000-000000000002" + }, + "description": "Blueprint UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 24, + "description": "Max 24 characters, e.g. \"AML investigation\"." + }, + "description": { + "type": "string", + "nullable": true + }, + "assignee_pool": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1, + "description": "List of User UUIDs. Empty arrays are rejected with \"Pick at least one assignee\"." + }, + "assignment_method": { + "type": "string", + "enum": [ + "MANUAL", + "AUTOMATIC" + ] + }, + "max_active_cases": { + "type": "integer", + "nullable": true, + "minimum": 1, + "description": "Automatic-mode threshold: \"fewer than N cases of this blueprint assigned\"." + }, + "escalation_enabled": { + "type": "boolean" + }, + "escalation_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Target blueprint UUID. Not enforced at save time, but the escalate action fails with 400 until both escalation_enabled and escalation_blueprint are set." + }, + "transfer_enabled": { + "type": "boolean" + }, + "four_eyes_checker_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Setting this enables 4-eyes review: resolutions on this blueprint stage on the checker blueprint's queue." + }, + "require_resolution_note": { + "type": "boolean" + }, + "content_config": { + "$ref": "#/components/schemas/CaseContentConfig" + }, + "is_active": { + "type": "boolean" + }, + "default_deadline_hours": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 8760, + "description": "Default deadline for newly opened cases. Existing case deadlines are unchanged." + }, + "expected_version": { + "type": "integer", + "minimum": 1, + "description": "The version opened by the editor. A stale version returns 409 without changing the review policy." + } + } + }, + "example": { + "transfer_enabled": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/b1de0000-0000-4000-8000-000000000002/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"transfer_enabled\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.update(\n \"b1de0000-0000-4000-8000-000000000002\",\n transfer_enabled=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.update(\"b1de0000-0000-4000-8000-000000000002\", {\n transfer_enabled: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.Update(ctx, \"b1de0000-0000-4000-8000-000000000002\", didit.CasesBlueprintsUpdateParams{\n\tTransferEnabled: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesBlueprintsUpdateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().update(\"b1de0000-0000-4000-8000-000000000002\", CasesBlueprintsUpdateParams.builder()\n .transferEnabled(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->update('b1de0000-0000-4000-8000-000000000002', [\n 'transfer_enabled' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.update(\n \"b1de0000-0000-4000-8000-000000000002\",\n transfer_enabled: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.UpdateAsync(\"b1de0000-0000-4000-8000-000000000002\", new CasesBlueprintsUpdateParams\n{\n TransferEnabled = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated blueprint.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintDetail" + } + } + } + }, + "409": { + "description": "The review policy changed since expected_version. Review the latest version before retrying." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete blueprint", + "description": "Delete a blueprint. Deletion always succeeds and does not touch the blueprint's cases: existing cases keep their blueprint reference and its settings continue to apply to them. A deleted blueprint no longer appears in blueprint lists and can no longer be chosen as a transfer target; if a maker blueprint is deleted while a resolution is pending 4-eyes approval, a rejected resolution leaves the case on the checker blueprint.", + "operationId": "delete_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "blueprint_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "b1de0000-0000-4000-8000-000000000002" + }, + "description": "Blueprint UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/b1de0000-0000-4000-8000-000000000002/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.cases.blueprints.delete(\n \"b1de0000-0000-4000-8000-000000000002\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.cases.blueprints.delete(\"b1de0000-0000-4000-8000-000000000002\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.Cases.Blueprints.Delete(ctx, \"b1de0000-0000-4000-8000-000000000002\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().blueprints().delete(\"b1de0000-0000-4000-8000-000000000002\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->delete('b1de0000-0000-4000-8000-000000000002');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.blueprints.delete(\"b1de0000-0000-4000-8000-000000000002\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Cases.Blueprints.DeleteAsync(\"b1de0000-0000-4000-8000-000000000002\");" + } + ], + "responses": { + "204": { + "description": "Blueprint deleted." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/presets/": { + "get": { + "summary": "List blueprint presets", + "description": "List the four built-in presets (Screening hit, AML investigation, Suspicious activity, KYC review) with their content_config and whether each is already installed on this application.", + "operationId": "list_case_blueprint_presets", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "list_presets", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/presets/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.list_presets()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.listPresets();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.ListPresets(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().listPresets();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->listPresets();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.list_presets\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.ListPresetsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Preset catalogue.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "preset_key": { + "type": "string", + "enum": [ + "screening_hit", + "aml_investigation", + "suspicious_activity", + "kyc" + ] + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "content_config": { + "$ref": "#/components/schemas/CaseContentConfig" + }, + "installed": { + "type": "boolean" + } + } + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/presets/install/": { + "post": { + "summary": "Install blueprint presets", + "description": "Copy one or more presets into the application as editable blueprints. Idempotent per preset_key; omit preset_keys to install every preset not yet installed.", + "operationId": "install_case_blueprint_presets", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "install_presets", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "preset_keys": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Subset of screening_hit, aml_investigation, suspicious_activity, kyc. Omit to install all." + } + } + }, + "example": { + "preset_keys": [ + "aml_investigation", + "kyc" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/presets/install/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"preset_keys\": [\"aml_investigation\", \"kyc\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.install_presets(\n preset_keys=[\"aml_investigation\", \"kyc\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.installPresets({\n preset_keys: [\"aml_investigation\", \"kyc\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.InstallPresets(ctx, didit.CasesBlueprintsInstallPresetsParams{\n\tPresetKeys: didit.Some([]string{\"aml_investigation\", \"kyc\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CasesBlueprintsInstallPresetsParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().installPresets(CasesBlueprintsInstallPresetsParams.builder()\n .presetKeys(List.of(\"aml_investigation\", \"kyc\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->installPresets([\n 'preset_keys' => ['aml_investigation', 'kyc'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.install_presets(preset_keys: [\"aml_investigation\", \"kyc\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.InstallPresetsAsync(new CasesBlueprintsInstallPresetsParams\n{\n PresetKeys = new string[] { \"aml_investigation\", \"kyc\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "One or more presets installed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintPresetInstallResult" + } + } + } + }, + "200": { + "description": "No new presets to install (all requested presets already exist).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintPresetInstallResult" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/report-templates/": { + "get": { + "summary": "List report templates", + "description": "List FIU report templates for the application.", + "operationId": "list_report_templates", + "x-didit-sdk": { + "resource": "report_templates", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.report_templates.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.reportTemplates.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.ReportTemplates.ListAutoPaging(ctx, didit.ReportTemplatesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.reportTemplates().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "reportTemplates->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.report_templates.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.ReportTemplates.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated templates.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows, capped at 100 for performance." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ReportTemplateDetail" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create report template", + "description": "Create a reusable per-jurisdiction, per-report-type goAML header template. Rejected with 400 if any of the template-level obligatory fields (rentity_id, submission_code, currency_code_local) is missing from field_config; an invalid template cannot be saved or used to create a report. Per-filing header fields (action, submission_date, transactions) are enforced at report validate/finalize time instead.", + "operationId": "create_report_template", + "x-didit-sdk": { + "resource": "report_templates", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "jurisdiction", + "report_type" + ], + "properties": { + "name": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code, upper-cased on save." + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "field_config": { + "type": "object", + "description": "Reusable goAML header defaults. Template-level obligatory fields: rentity_id, submission_code, and currency_code_local (3-letter). Optional: enabled_fields (which optional report blocks to include) plus reporting_person, location, reason, and action defaults. Per-filing header fields (action, submission_date, transactions, jurisdiction) are enforced when the report is validated or finalized, not on the template." + } + } + }, + "example": { + "name": "US SAR default", + "jurisdiction": "US", + "report_type": "SAR", + "field_config": { + "rentity_id": "FIU-US-00123", + "submission_code": "E", + "currency_code_local": "USD" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"name\": \"US SAR default\", \"jurisdiction\": \"US\", \"report_type\": \"SAR\", \"field_config\": {\"rentity_id\": \"FIU-US-00123\", \"submission_code\": \"E\", \"currency_code_local\": \"USD\"}}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.report_templates.create(\n name=\"US SAR default\",\n jurisdiction=\"US\",\n report_type=\"SAR\",\n field_config={\"rentity_id\": \"FIU-US-00123\", \"submission_code\": \"E\", \"currency_code_local\": \"USD\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.reportTemplates.create({\n name: \"US SAR default\",\n jurisdiction: \"US\",\n report_type: \"SAR\",\n field_config: { rentity_id: \"FIU-US-00123\", submission_code: \"E\", currency_code_local: \"USD\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.ReportTemplates.Create(ctx, didit.ReportTemplatesCreateParams{\n\tName: \"US SAR default\",\n\tJurisdiction: \"US\",\n\tReportType: \"SAR\",\n\tFieldConfig: didit.Some(map[string]any{\"rentity_id\": \"FIU-US-00123\", \"submission_code\": \"E\", \"currency_code_local\": \"USD\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.ReportTemplatesCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.reportTemplates().create(ReportTemplatesCreateParams.builder()\n .name(\"US SAR default\")\n .jurisdiction(\"US\")\n .reportType(ReportTemplatesCreateParams.ReportType.of(\"SAR\"))\n .fieldConfig(Map.of(\"rentity_id\", \"FIU-US-00123\", \"submission_code\", \"E\", \"currency_code_local\", \"USD\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "reportTemplates->create([\n 'name' => 'US SAR default',\n 'jurisdiction' => 'US',\n 'report_type' => 'SAR',\n 'field_config' => ['rentity_id' => 'FIU-US-00123', 'submission_code' => 'E', 'currency_code_local' => 'USD'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.report_templates.create(\n name: \"US SAR default\",\n jurisdiction: \"US\",\n report_type: \"SAR\",\n field_config: { rentity_id: \"FIU-US-00123\", submission_code: \"E\", currency_code_local: \"USD\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.ReportTemplates.CreateAsync(new ReportTemplatesCreateParams\n{\n Name = \"US SAR default\",\n Jurisdiction = \"US\",\n ReportType = \"SAR\",\n FieldConfig = new Dictionary { [\"rentity_id\"] = \"FIU-US-00123\", [\"submission_code\"] = \"E\", [\"currency_code_local\"] = \"USD\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Template created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReportTemplateDetail" + } + } + } + }, + "400": { + "description": "Unrecognized jurisdiction, or field_config fails obligatory-field validation.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/report-templates/{template_uuid}/": { + "get": { + "summary": "Get report template", + "description": "Retrieve a report template.", + "operationId": "get_report_template", + "x-didit-sdk": { + "resource": "report_templates", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "template_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "7ec00000-0000-4000-8000-000000000006" + }, + "description": "Report template UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/7ec00000-0000-4000-8000-000000000006/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.report_templates.retrieve(\n \"7ec00000-0000-4000-8000-000000000006\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.reportTemplates.retrieve(\"7ec00000-0000-4000-8000-000000000006\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.ReportTemplates.Retrieve(ctx, \"7ec00000-0000-4000-8000-000000000006\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.reportTemplates().retrieve(\"7ec00000-0000-4000-8000-000000000006\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "reportTemplates->retrieve('7ec00000-0000-4000-8000-000000000006');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.report_templates.retrieve(\"7ec00000-0000-4000-8000-000000000006\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.ReportTemplates.RetrieveAsync(\"7ec00000-0000-4000-8000-000000000006\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Template detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReportTemplateDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update report template", + "description": "Partially update a report template. Re-validates obligatory goAML fields on save.", + "operationId": "update_report_template", + "x-didit-sdk": { + "resource": "report_templates", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "template_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "7ec00000-0000-4000-8000-000000000006" + }, + "description": "Report template UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code, upper-cased on save." + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "field_config": { + "type": "object", + "description": "Reusable goAML header defaults. Template-level obligatory fields: rentity_id, submission_code, and currency_code_local (3-letter). Optional: enabled_fields (which optional report blocks to include) plus reporting_person, location, reason, and action defaults. Per-filing header fields (action, submission_date, transactions, jurisdiction) are enforced when the report is validated or finalized, not on the template." + } + } + }, + "example": { + "field_config": { + "rentity_id": "FIU-US-00124" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/7ec00000-0000-4000-8000-000000000006/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"field_config\": {\"rentity_id\": \"FIU-US-00124\"}}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.report_templates.update(\n \"7ec00000-0000-4000-8000-000000000006\",\n field_config={\"rentity_id\": \"FIU-US-00124\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.reportTemplates.update(\"7ec00000-0000-4000-8000-000000000006\", {\n field_config: { rentity_id: \"FIU-US-00124\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.ReportTemplates.Update(ctx, \"7ec00000-0000-4000-8000-000000000006\", didit.ReportTemplatesUpdateParams{\n\tFieldConfig: didit.Some(map[string]any{\"rentity_id\": \"FIU-US-00124\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.ReportTemplatesUpdateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.reportTemplates().update(\"7ec00000-0000-4000-8000-000000000006\", ReportTemplatesUpdateParams.builder()\n .fieldConfig(Map.of(\"rentity_id\", \"FIU-US-00124\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "reportTemplates->update('7ec00000-0000-4000-8000-000000000006', [\n 'field_config' => ['rentity_id' => 'FIU-US-00124'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.report_templates.update(\n \"7ec00000-0000-4000-8000-000000000006\",\n field_config: { rentity_id: \"FIU-US-00124\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.ReportTemplates.UpdateAsync(\"7ec00000-0000-4000-8000-000000000006\", new ReportTemplatesUpdateParams\n{\n FieldConfig = new Dictionary { [\"rentity_id\"] = \"FIU-US-00124\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated template.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReportTemplateDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete report template", + "description": "Delete a report template. Reports already created from it are unaffected.", + "operationId": "delete_report_template", + "x-didit-sdk": { + "resource": "report_templates", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "template_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "7ec00000-0000-4000-8000-000000000006" + }, + "description": "Report template UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/7ec00000-0000-4000-8000-000000000006/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.report_templates.delete(\n \"7ec00000-0000-4000-8000-000000000006\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.reportTemplates.delete(\"7ec00000-0000-4000-8000-000000000006\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.ReportTemplates.Delete(ctx, \"7ec00000-0000-4000-8000-000000000006\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.reportTemplates().delete(\"7ec00000-0000-4000-8000-000000000006\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "reportTemplates->delete('7ec00000-0000-4000-8000-000000000006');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.report_templates.delete(\"7ec00000-0000-4000-8000-000000000006\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.ReportTemplates.DeleteAsync(\"7ec00000-0000-4000-8000-000000000006\");" + } + ], + "responses": { + "204": { + "description": "Template deleted." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/regulatory-reports/": { + "get": { + "summary": "List reports for case", + "description": "List FIU reports generated for the case.", + "operationId": "list_case_regulatory_reports", + "x-didit-sdk": { + "resource": "cases.regulatory_reports", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/regulatory-reports/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.regulatory_reports.list(\n \"c1a5e000-0000-4000-8000-000000000001\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.regulatoryReports.list(\"c1a5e000-0000-4000-8000-000000000001\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.RegulatoryReports.ListAutoPaging(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesRegulatoryReportsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().regulatoryReports().list(\"c1a5e000-0000-4000-8000-000000000001\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->regulatoryReports->list('c1a5e000-0000-4000-8000-000000000001') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.regulatory_reports.list(\"c1a5e000-0000-4000-8000-000000000001\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.RegulatoryReports.ListAsync(\"c1a5e000-0000-4000-8000-000000000001\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated reports for the case.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportList" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create report from case", + "description": "Create a draft FIU report for the case: DRAFT status, auto-populated from the optional template plus the case's subject, linked transactions, and narrative. Validate then finalize to produce the goAML XML and PDF.", + "operationId": "create_case_regulatory_report", + "x-didit-sdk": { + "resource": "cases.regulatory_reports", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ], + "description": "Required unless a template is given; the template's report_type is used otherwise." + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "Required unless a template is given." + }, + "template": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Report template UUID; its field_config seeds the draft before case-derived data is merged in." + }, + "transactions": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "nullable": true, + "description": "Transactions to attach; defaults to the case's linked transactions." + }, + "rentity_id": { + "type": "string", + "nullable": true + }, + "rentity_branch": { + "type": "string", + "nullable": true + }, + "submission_code": { + "type": "string", + "enum": [ + "E", + "M" + ], + "default": "E" + }, + "entity_reference": { + "type": "string", + "nullable": true + }, + "fiu_ref_number": { + "type": "string", + "nullable": true + }, + "currency_code_local": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "nullable": true + }, + "submission_date": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reporting_person": { + "type": "object" + }, + "location": { + "type": "object" + }, + "reason": { + "type": "string", + "nullable": true + }, + "action": { + "type": "string", + "nullable": true + }, + "activity": { + "type": "string", + "nullable": true + }, + "narrative": { + "type": "object", + "properties": { + "who": { + "type": "string" + }, + "what": { + "type": "string" + }, + "when": { + "type": "string" + }, + "where": { + "type": "string" + }, + "why": { + "type": "string" + }, + "how": { + "type": "string" + } + } + }, + "report_indicators": { + "type": "array", + "items": { + "type": "string" + } + }, + "included_fields": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "example": { + "template": "7ec00000-0000-4000-8000-000000000006" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/regulatory-reports/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"template\": \"7ec00000-0000-4000-8000-000000000006\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.regulatory_reports.create(\n \"c1a5e000-0000-4000-8000-000000000001\",\n template=\"7ec00000-0000-4000-8000-000000000006\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.regulatoryReports.create(\"c1a5e000-0000-4000-8000-000000000001\", {\n template: \"7ec00000-0000-4000-8000-000000000006\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RegulatoryReports.Create(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesRegulatoryReportsCreateParams{\n\tTemplate: didit.String(\"7ec00000-0000-4000-8000-000000000006\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesRegulatoryReportsCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().regulatoryReports().create(\"c1a5e000-0000-4000-8000-000000000001\", CasesRegulatoryReportsCreateParams.builder()\n .template(\"7ec00000-0000-4000-8000-000000000006\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->regulatoryReports->create('c1a5e000-0000-4000-8000-000000000001', [\n 'template' => '7ec00000-0000-4000-8000-000000000006',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.regulatory_reports.create(\n \"c1a5e000-0000-4000-8000-000000000001\",\n template: \"7ec00000-0000-4000-8000-000000000006\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RegulatoryReports.CreateAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesRegulatoryReportsCreateParams\n{\n Template = \"7ec00000-0000-4000-8000-000000000006\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Draft report created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "400": { + "description": "The referenced template was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/": { + "get": { + "summary": "List reports", + "description": "Application-wide FIU report list, optionally filtered by status, report_type, or case.", + "operationId": "list_regulatory_reports", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + }, + { + "name": "status", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "DRAFT", + "VALIDATED", + "FINALIZED" + ] + }, + "description": "Filter by status." + }, + { + "name": "report_type", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "description": "Filter by report type." + }, + { + "name": "case", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter to reports on this case." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.regulatory_reports.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.regulatoryReports.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.RegulatoryReports.ListAutoPaging(ctx, didit.RegulatoryReportsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.regulatoryReports().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.regulatory_reports.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.RegulatoryReports.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated reports across the application.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportList" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create standalone report", + "description": "Create a draft FIU report from the standalone Reports page: both case and template are required (unlike the case-scoped create, which can omit template).", + "operationId": "create_standalone_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "case", + "template" + ], + "properties": { + "case": { + "type": "string", + "format": "uuid" + }, + "template": { + "type": "string", + "format": "uuid" + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ], + "description": "Required unless a template is given; the template's report_type is used otherwise." + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "Required unless a template is given." + }, + "transactions": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "nullable": true, + "description": "Transactions to attach; defaults to the case's linked transactions." + }, + "rentity_id": { + "type": "string", + "nullable": true + }, + "rentity_branch": { + "type": "string", + "nullable": true + }, + "submission_code": { + "type": "string", + "enum": [ + "E", + "M" + ], + "default": "E" + }, + "entity_reference": { + "type": "string", + "nullable": true + }, + "fiu_ref_number": { + "type": "string", + "nullable": true + }, + "currency_code_local": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "nullable": true + }, + "submission_date": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reporting_person": { + "type": "object" + }, + "location": { + "type": "object" + }, + "reason": { + "type": "string", + "nullable": true + }, + "action": { + "type": "string", + "nullable": true + }, + "activity": { + "type": "string", + "nullable": true + }, + "narrative": { + "type": "object", + "properties": { + "who": { + "type": "string" + }, + "what": { + "type": "string" + }, + "when": { + "type": "string" + }, + "where": { + "type": "string" + }, + "why": { + "type": "string" + }, + "how": { + "type": "string" + } + } + }, + "report_indicators": { + "type": "array", + "items": { + "type": "string" + } + }, + "included_fields": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "example": { + "case": "c1a5e000-0000-4000-8000-000000000001", + "template": "7ec00000-0000-4000-8000-000000000006" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"case\": \"c1a5e000-0000-4000-8000-000000000001\", \"template\": \"7ec00000-0000-4000-8000-000000000006\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.regulatory_reports.create(\n case=\"c1a5e000-0000-4000-8000-000000000001\",\n template=\"7ec00000-0000-4000-8000-000000000006\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.regulatoryReports.create({\n case: \"c1a5e000-0000-4000-8000-000000000001\",\n template: \"7ec00000-0000-4000-8000-000000000006\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.RegulatoryReports.Create(ctx, didit.RegulatoryReportsCreateParams{\n\tCase: \"c1a5e000-0000-4000-8000-000000000001\",\n\tTemplate: \"7ec00000-0000-4000-8000-000000000006\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.RegulatoryReportsCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.regulatoryReports().create(RegulatoryReportsCreateParams.builder()\n .case_(\"c1a5e000-0000-4000-8000-000000000001\")\n .template(\"7ec00000-0000-4000-8000-000000000006\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->create([\n 'case' => 'c1a5e000-0000-4000-8000-000000000001',\n 'template' => '7ec00000-0000-4000-8000-000000000006',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.regulatory_reports.create(\n case: \"c1a5e000-0000-4000-8000-000000000001\",\n template: \"7ec00000-0000-4000-8000-000000000006\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.RegulatoryReports.CreateAsync(new RegulatoryReportsCreateParams\n{\n Case = \"c1a5e000-0000-4000-8000-000000000001\",\n Template = \"7ec00000-0000-4000-8000-000000000006\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Draft report created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "400": { + "description": "The referenced template was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/{report_uuid}/": { + "get": { + "summary": "Get report", + "description": "Retrieve a regulatory report.", + "operationId": "get_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.regulatory_reports.retrieve(\n \"5ec00000-0000-4000-8000-000000000007\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.regulatoryReports.retrieve(\"5ec00000-0000-4000-8000-000000000007\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.RegulatoryReports.Retrieve(ctx, \"5ec00000-0000-4000-8000-000000000007\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.regulatoryReports().retrieve(\"5ec00000-0000-4000-8000-000000000007\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->retrieve('5ec00000-0000-4000-8000-000000000007');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.regulatory_reports.retrieve(\"5ec00000-0000-4000-8000-000000000007\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.RegulatoryReports.RetrieveAsync(\"5ec00000-0000-4000-8000-000000000007\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Report detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update report", + "description": "Edit a draft or validated report's fields. Finalized reports cannot be edited (409).", + "operationId": "update_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2 + }, + "rentity_id": { + "type": "string", + "nullable": true + }, + "rentity_branch": { + "type": "string", + "nullable": true + }, + "submission_code": { + "type": "string", + "enum": [ + "E", + "M" + ], + "default": "E" + }, + "entity_reference": { + "type": "string", + "nullable": true + }, + "fiu_ref_number": { + "type": "string", + "nullable": true + }, + "currency_code_local": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "nullable": true + }, + "submission_date": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reporting_person": { + "type": "object" + }, + "location": { + "type": "object" + }, + "reason": { + "type": "string", + "nullable": true + }, + "action": { + "type": "string", + "nullable": true + }, + "activity": { + "type": "string", + "nullable": true + }, + "narrative": { + "type": "object", + "properties": { + "who": { + "type": "string" + }, + "what": { + "type": "string" + }, + "when": { + "type": "string" + }, + "where": { + "type": "string" + }, + "why": { + "type": "string" + }, + "how": { + "type": "string" + } + } + }, + "report_indicators": { + "type": "array", + "items": { + "type": "string" + } + }, + "included_fields": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "example": { + "rentity_id": "FIU-US-00123", + "currency_code_local": "USD" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"rentity_id\": \"FIU-US-00123\", \"currency_code_local\": \"USD\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.regulatory_reports.update(\n \"5ec00000-0000-4000-8000-000000000007\",\n rentity_id=\"FIU-US-00123\",\n currency_code_local=\"USD\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.regulatoryReports.update(\"5ec00000-0000-4000-8000-000000000007\", {\n rentity_id: \"FIU-US-00123\",\n currency_code_local: \"USD\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.RegulatoryReports.Update(ctx, \"5ec00000-0000-4000-8000-000000000007\", didit.RegulatoryReportsUpdateParams{\n\tRentityID: didit.String(\"FIU-US-00123\"),\n\tCurrencyCodeLocal: didit.String(\"USD\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.RegulatoryReportsUpdateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.regulatoryReports().update(\"5ec00000-0000-4000-8000-000000000007\", RegulatoryReportsUpdateParams.builder()\n .rentityId(\"FIU-US-00123\")\n .currencyCodeLocal(\"USD\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->update('5ec00000-0000-4000-8000-000000000007', [\n 'rentity_id' => 'FIU-US-00123',\n 'currency_code_local' => 'USD',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.regulatory_reports.update(\n \"5ec00000-0000-4000-8000-000000000007\",\n rentity_id: \"FIU-US-00123\",\n currency_code_local: \"USD\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.RegulatoryReports.UpdateAsync(\"5ec00000-0000-4000-8000-000000000007\", new RegulatoryReportsUpdateParams\n{\n RentityId = \"FIU-US-00123\",\n CurrencyCodeLocal = \"USD\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated report.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "409": { + "description": "Report is already finalized.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete report", + "description": "Delete a regulatory report permanently. The report, its copied narrative and reasons, its transaction links, and its generated goAML XML and PDF are erased, including every stored version of those files. The report disappears from lists and download URLs stop working. Other records, including the case, transactions, people and companies, are not deleted. There is no undo.\n\nA draft or validated report can be deleted without a request body. Deleting a finalized report requires `confirm_delete: true` in the request body. If the report is missing, the request returns `404`; if a finalized report is sent without confirmation, it returns `400` and nothing is deleted.", + "operationId": "delete_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl (draft report)", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "curl", + "label": "curl (finalized report)", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"confirm_delete\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.regulatory_reports.delete(\n \"5ec00000-0000-4000-8000-000000000007\",\n confirm_delete=True,\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.regulatoryReports.delete(\"5ec00000-0000-4000-8000-000000000007\", {\n confirm_delete: true,\n});" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.RegulatoryReportsDeleteParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.regulatoryReports().delete(\"5ec00000-0000-4000-8000-000000000007\", RegulatoryReportsDeleteParams.builder()\n .confirmDelete(true)\n .build());" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->delete('5ec00000-0000-4000-8000-000000000007', [\n 'confirm_delete' => true,\n]);" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.regulatory_reports.delete(\n \"5ec00000-0000-4000-8000-000000000007\",\n confirm_delete: true\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.RegulatoryReports.DeleteAsync(\"5ec00000-0000-4000-8000-000000000007\", new RegulatoryReportsDeleteParams\n{\n ConfirmDelete = true,\n});" + } + ], + "responses": { + "204": { + "description": "Report deleted, including its generated files." + }, + "400": { + "description": "A finalized report was sent without `confirm_delete: true`. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "confirm_delete": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "example": { + "confirm_delete": [ + "This field is required to delete a finalized report." + ] + } + } + } + }, + "403": { + "description": "The API key cannot delete reports. The endpoint requires `write:cases`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Report not found in this application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "409": { + "description": "The report is already being deleted. Repeat the request to finish erasing its files.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "This report is being deleted. Retry deleting it to finish erasing its files.", + "code": "report_deletion_pending" + } + } + } + }, + "503": { + "description": "The report files could not be erased right now. The report is still in place. Repeat the request.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "The report files could not be erased. Please retry deleting the report.", + "code": "report_storage_unavailable" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": false, + "description": "Required only for a finalized report.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "confirm_delete": { + "type": "boolean", + "description": "Set to true to confirm that a finalized report and its generated files are deleted permanently." + } + }, + "example": { + "confirm_delete": true + } + }, + "example": { + "confirm_delete": true + } + } + } + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/{report_uuid}/validate/": { + "post": { + "summary": "Validate report", + "description": "Check the report against the obligatory goAML header fields (reporting entity ID, submission code, local currency, action taken, submission date), plus at least one attached transaction and a 2-letter jurisdiction, without generating any files. Also stores the result on the report: validation_errors and validated_at are updated, and a non-finalized report's status moves to VALIDATED (no errors) or back to DRAFT (errors).", + "operationId": "validate_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "validate", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/validate/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.regulatory_reports.validate(\n \"5ec00000-0000-4000-8000-000000000007\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.regulatoryReports.validate(\"5ec00000-0000-4000-8000-000000000007\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.RegulatoryReports.Validate(ctx, \"5ec00000-0000-4000-8000-000000000007\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.regulatoryReports().validate(\"5ec00000-0000-4000-8000-000000000007\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->validate('5ec00000-0000-4000-8000-000000000007');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.regulatory_reports.validate(\"5ec00000-0000-4000-8000-000000000007\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.RegulatoryReports.ValidateAsync(\"5ec00000-0000-4000-8000-000000000007\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Validation result.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "DRAFT", + "VALIDATED", + "FINALIZED" + ] + }, + "is_valid": { + "type": "boolean" + }, + "errors": { + "type": "array", + "description": "Empty when valid.", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/{report_uuid}/finalize/": { + "post": { + "summary": "Finalize report", + "description": "Re-validate, then render the goAML XML and PDF and upload both to S3. On SAR/STR/TFR reports, this also moves the case's transaction alerts from Pending SAR to SAR filed.", + "operationId": "finalize_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "finalize", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/finalize/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.regulatory_reports.finalize(\n \"5ec00000-0000-4000-8000-000000000007\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.regulatoryReports.finalize(\"5ec00000-0000-4000-8000-000000000007\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.RegulatoryReports.Finalize(ctx, \"5ec00000-0000-4000-8000-000000000007\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.regulatoryReports().finalize_(\"5ec00000-0000-4000-8000-000000000007\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->finalize('5ec00000-0000-4000-8000-000000000007');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.regulatory_reports.finalize(\"5ec00000-0000-4000-8000-000000000007\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.RegulatoryReports.FinalizeAsync(\"5ec00000-0000-4000-8000-000000000007\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Report finalized; xml_s3_key and pdf_s3_key are set.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "400": { + "description": "Report failed validation.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "409": { + "description": "Report is not in a finalizable state.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/{report_uuid}/download-url/": { + "get": { + "summary": "Get report download URL", + "description": "Mint a presigned URL for a finalized report's goAML XML or PDF.", + "operationId": "get_regulatory_report_download_url", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "retrieve_download_url", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + }, + { + "name": "file", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "pdf", + "xml" + ], + "default": "pdf" + }, + "description": "Which file to download." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/download-url/?file=xml' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.regulatory_reports.retrieve_download_url(\n \"5ec00000-0000-4000-8000-000000000007\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.regulatoryReports.retrieveDownloadUrl(\"5ec00000-0000-4000-8000-000000000007\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.RegulatoryReports.RetrieveDownloadURL(ctx, \"5ec00000-0000-4000-8000-000000000007\", didit.RegulatoryReportsRetrieveDownloadURLParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.regulatoryReports().retrieveDownloadUrl(\"5ec00000-0000-4000-8000-000000000007\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->retrieveDownloadUrl('5ec00000-0000-4000-8000-000000000007');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.regulatory_reports.retrieve_download_url(\"5ec00000-0000-4000-8000-000000000007\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.RegulatoryReports.RetrieveDownloadUrlAsync(\"5ec00000-0000-4000-8000-000000000007\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Presigned download URL.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "download_url": { + "type": "string", + "format": "uri" + }, + "file": { + "type": "string", + "enum": [ + "pdf", + "xml" + ] + } + } + } + } + } + }, + "409": { + "description": "Report is not finalized yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "status": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/transactions/rules/": { + "get": { + "summary": "List rules", + "description": "List the application's transaction-monitoring rules: your custom rules plus any library presets you have installed. Filter by source, category, mode, bundle or free-text search; order with `ordering` (run_count, approved_pct, reviewed_pct, declined_pct, latest_triggered_at, title, created_at, source, each with an optional `-` prefix).", + "operationId": "listTransactionRules", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "source", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "PRESET", + "CUSTOM" + ] + }, + "description": "PRESET or CUSTOM" + }, + { + "name": "category", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Filter by rule category" + }, + { + "name": "mode", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ] + }, + "description": "ACTIVE, DISABLED or TEST" + }, + { + "name": "bundle", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Filter presets by bundle" + }, + { + "name": "search", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Case-insensitive match against title and description" + }, + { + "name": "ordering", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated ordering fields, default source,-run_count" + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "description": "Page size" + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "description": "Page offset" + } + ], + "responses": { + "200": { + "description": "Paginated rules.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleDetail" + } + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/rules/?source=CUSTOM&ordering=-run_count' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.transactions.rules.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.transactions.rules.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Transactions.Rules.ListAutoPaging(ctx, didit.TransactionsRulesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.transactions().rules().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.rules.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Transactions.Rules.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "summary": "Create rule", + "description": "Create a CUSTOM transaction-monitoring rule. Titles are unique per application (duplicate -> 400). A `change_status` action targeting `AWAITING_USER` must carry `workflow_id`. Rules created in ACTIVE mode apply to new transactions immediately - the recommended workflow is create in TEST mode, backtest, then switch mode to ACTIVE. To change a library rule, create it as a copy: send the library rule's definition with `metadata.based_on` (its library key) and `metadata.based_on_rule_uuid`.", + "operationId": "createTransactionRule", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleCreateRequest" + }, + "example": { + "title": "Structuring - inbound (custom)", + "description": "20+ inbound EUR transfers, all under 10,000 EUR, 30 days", + "category": "finance", + "mode": "TEST", + "evaluation_mode": "ALL", + "scope": { + "transaction_types": [ + "finance" + ], + "directions": [ + "INBOUND" + ] + }, + "conditions": [ + { + "field": "currency", + "operator": "eq", + "value": "EUR" + }, + { + "field": "amount", + "operator": "lt", + "value": 10000 + } + ], + "aggregation": [ + { + "metric": "count", + "operator": "gte", + "value": 20, + "window": "30d", + "filters": { + "direction": "INBOUND", + "transaction_type": "finance", + "subject_vendor_data": "__current__", + "currency": "EUR" + } + }, + { + "metric": "max", + "field": "amount", + "operator": "lt", + "value": 10000, + "window": "30d", + "filters": { + "direction": "INBOUND", + "transaction_type": "finance", + "subject_vendor_data": "__current__", + "currency": "EUR" + } + } + ], + "actions": [ + { + "type": "add_score", + "value": 35 + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "The created rule.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleDetail" + } + } + } + }, + "400": { + "description": "Validation error - duplicate title, unknown operator/metric, malformed window, invalid scope direction, or AWAITING_USER without workflow_id.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "title": [ + "A rule with this name already exists." + ] + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/rules/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"title\": \"Structuring - inbound (custom)\", \"description\": \"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\", \"category\": \"finance\", \"mode\": \"TEST\", \"evaluation_mode\": \"ALL\", \"scope\": {\"transaction_types\": [\"finance\"], \"directions\": [\"INBOUND\"]}, \"conditions\": [{\"field\": \"currency\", \"operator\": \"eq\", \"value\": \"EUR\"}, {\"field\": \"amount\", \"operator\": \"lt\", \"value\": 10000}], \"aggregation\": [{\"metric\": \"count\", \"operator\": \"gte\", \"value\": 20, \"window\": \"30d\", \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}}, {\"metric\": \"max\", \"field\": \"amount\", \"operator\": \"lt\", \"value\": 10000, \"window\": \"30d\", \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}}], \"actions\": [{\"type\": \"add_score\", \"value\": 35}]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.rules.create(\n title=\"Structuring - inbound (custom)\",\n description=\"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\",\n category=\"finance\",\n mode=\"TEST\",\n evaluation_mode=\"ALL\",\n scope={\n \"transaction_types\": [\"finance\"],\n \"directions\": [\"INBOUND\"],\n },\n conditions=[{\n \"field\": \"currency\",\n \"operator\": \"eq\",\n \"value\": \"EUR\",\n }, {\n \"field\": \"amount\",\n \"operator\": \"lt\",\n \"value\": 10000,\n }],\n aggregation=[{\n \"metric\": \"count\",\n \"operator\": \"gte\",\n \"value\": 20.0,\n \"window\": \"30d\",\n \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"},\n }, {\n \"metric\": \"max\",\n \"field\": \"amount\",\n \"operator\": \"lt\",\n \"value\": 10000.0,\n \"window\": \"30d\",\n \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"},\n }],\n actions=[{\n \"type\": \"add_score\",\n \"value\": 35,\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.rules.create({\n title: \"Structuring - inbound (custom)\",\n description: \"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\",\n category: \"finance\",\n mode: \"TEST\",\n evaluation_mode: \"ALL\",\n scope: {\n transaction_types: [\"finance\"],\n directions: [\"INBOUND\"],\n },\n conditions: [{\n field: \"currency\",\n operator: \"eq\",\n value: \"EUR\",\n }, {\n field: \"amount\",\n operator: \"lt\",\n value: 10000,\n }],\n aggregation: [{\n metric: \"count\",\n operator: \"gte\",\n value: 20.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }, {\n metric: \"max\",\n field: \"amount\",\n operator: \"lt\",\n value: 10000.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }],\n actions: [{\n type: \"add_score\",\n value: 35,\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Rules.Create(ctx, didit.TransactionRuleCreateRequest{\n\tTitle: \"Structuring - inbound (custom)\",\n\tDescription: didit.String(\"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\"),\n\tCategory: \"finance\",\n\tMode: \"TEST\",\n\tEvaluationMode: didit.Some(didit.TransactionRuleCreateRequestEvaluationMode(\"ALL\")),\n\tScope: didit.Some(didit.TransactionRuleScopeParam{\n\t\tTransactionTypes: didit.Some([]string{\"finance\"}),\n\t\tDirections: didit.Some([]didit.TransactionRuleScopeDirections{\"INBOUND\"}),\n\t}),\n\tConditions: didit.Some([]didit.TransactionRuleConditionParam{{\n\t\tField: \"currency\",\n\t\tOperator: \"eq\",\n\t\tValue: didit.Some[any](\"EUR\"),\n\t}, {\n\t\tField: \"amount\",\n\t\tOperator: \"lt\",\n\t\tValue: didit.Some[any](10000),\n\t}}),\n\tAggregation: didit.Some([]didit.TransactionRuleAggregationParam{{\n\t\tMetric: didit.Some(didit.TransactionRuleAggregationMetric(\"count\")),\n\t\tOperator: didit.Some(didit.TransactionRuleAggregationOperator(\"gte\")),\n\t\tValue: 20.0,\n\t\tWindow: didit.String(\"30d\"),\n\t\tFilters: didit.Some(map[string]any{\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}),\n\t}, {\n\t\tMetric: didit.Some(didit.TransactionRuleAggregationMetric(\"max\")),\n\t\tField: didit.String(\"amount\"),\n\t\tOperator: didit.Some(didit.TransactionRuleAggregationOperator(\"lt\")),\n\t\tValue: 10000.0,\n\t\tWindow: didit.String(\"30d\"),\n\t\tFilters: didit.Some(map[string]any{\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}),\n\t}}),\n\tActions: didit.Some([]didit.TransactionRuleActionParam{{\n\t\tType: \"add_score\",\n\t\tValue: didit.Some[any](35),\n\t}}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.TransactionRuleAction;\nimport me.didit.types.TransactionRuleActionAddScore;\nimport me.didit.types.TransactionRuleAggregation;\nimport me.didit.types.TransactionRuleCondition;\nimport me.didit.types.TransactionRuleCreateRequest;\nimport me.didit.types.TransactionRuleScope;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().rules().create(TransactionRuleCreateRequest.builder()\n .title(\"Structuring - inbound (custom)\")\n .description(\"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\")\n .category(\"finance\")\n .mode(TransactionRuleCreateRequest.Mode.of(\"TEST\"))\n .evaluationMode(TransactionRuleCreateRequest.EvaluationMode.of(\"ALL\"))\n .scope(TransactionRuleScope.builder()\n .transactionTypes(List.of(\"finance\"))\n .directions(List.of(TransactionRuleScope.Directions.of(\"INBOUND\")))\n .build())\n .conditions(List.of(\n TransactionRuleCondition.builder()\n .field(\"currency\")\n .operator(TransactionRuleCondition.Operator.of(\"eq\"))\n .value(\"EUR\")\n .build(),\n TransactionRuleCondition.builder()\n .field(\"amount\")\n .operator(TransactionRuleCondition.Operator.of(\"lt\"))\n .value(10000)\n .build()))\n .aggregation(List.of(\n TransactionRuleAggregation.builder()\n .metric(TransactionRuleAggregation.Metric.of(\"count\"))\n .operator(TransactionRuleAggregation.Operator.of(\"gte\"))\n .value(20.0)\n .window(\"30d\")\n .filters(Map.of(\"direction\", \"INBOUND\", \"transaction_type\", \"finance\", \"subject_vendor_data\", \"__current__\", \"currency\", \"EUR\"))\n .build(),\n TransactionRuleAggregation.builder()\n .metric(TransactionRuleAggregation.Metric.of(\"max\"))\n .field(\"amount\")\n .operator(TransactionRuleAggregation.Operator.of(\"lt\"))\n .value(10000.0)\n .window(\"30d\")\n .filters(Map.of(\"direction\", \"INBOUND\", \"transaction_type\", \"finance\", \"subject_vendor_data\", \"__current__\", \"currency\", \"EUR\"))\n .build()))\n .actions(List.of(TransactionRuleAction.ofAddScore(TransactionRuleActionAddScore.builder()\n .type(TransactionRuleActionAddScore.Type.of(\"add_score\"))\n .value(35L)\n .build())))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->create([\n 'title' => 'Structuring - inbound (custom)',\n 'description' => '20+ inbound EUR transfers, all under 10,000 EUR, 30 days',\n 'category' => 'finance',\n 'mode' => 'TEST',\n 'evaluation_mode' => 'ALL',\n 'scope' => [\n 'transaction_types' => ['finance'],\n 'directions' => ['INBOUND'],\n ],\n 'conditions' => [[\n 'field' => 'currency',\n 'operator' => 'eq',\n 'value' => 'EUR',\n ], [\n 'field' => 'amount',\n 'operator' => 'lt',\n 'value' => 10000,\n ]],\n 'aggregation' => [[\n 'metric' => 'count',\n 'operator' => 'gte',\n 'value' => 20.0,\n 'window' => '30d',\n 'filters' => ['direction' => 'INBOUND', 'transaction_type' => 'finance', 'subject_vendor_data' => '__current__', 'currency' => 'EUR'],\n ], [\n 'metric' => 'max',\n 'field' => 'amount',\n 'operator' => 'lt',\n 'value' => 10000.0,\n 'window' => '30d',\n 'filters' => ['direction' => 'INBOUND', 'transaction_type' => 'finance', 'subject_vendor_data' => '__current__', 'currency' => 'EUR'],\n ]],\n 'actions' => [[\n 'type' => 'add_score',\n 'value' => 35,\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.rules.create(\n title: \"Structuring - inbound (custom)\",\n description: \"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\",\n category: \"finance\",\n mode: \"TEST\",\n evaluation_mode: \"ALL\",\n scope: {\n transaction_types: [\"finance\"],\n directions: [\"INBOUND\"],\n },\n conditions: [{\n field: \"currency\",\n operator: \"eq\",\n value: \"EUR\",\n }, {\n field: \"amount\",\n operator: \"lt\",\n value: 10000,\n }],\n aggregation: [{\n metric: \"count\",\n operator: \"gte\",\n value: 20.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }, {\n metric: \"max\",\n field: \"amount\",\n operator: \"lt\",\n value: 10000.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }],\n actions: [{\n type: \"add_score\",\n value: 35,\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Rules.CreateAsync(new TransactionRuleCreateRequest\n{\n Title = \"Structuring - inbound (custom)\",\n Description = \"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\",\n Category = \"finance\",\n Mode = \"TEST\",\n EvaluationMode = new TransactionRuleCreateRequestEvaluationMode(\"ALL\"),\n Scope = new TransactionRuleScope\n {\n TransactionTypes = new string[] { \"finance\" },\n Directions = new TransactionRuleScopeDirections[] { \"INBOUND\" },\n },\n Conditions = new TransactionRuleCondition[]\n {\n new()\n {\n Field = \"currency\",\n Operator = \"eq\",\n Value = \"EUR\",\n },\n new()\n {\n Field = \"amount\",\n Operator = \"lt\",\n Value = 10000,\n },\n },\n Aggregation = new TransactionRuleAggregation[]\n {\n new()\n {\n Metric = new TransactionRuleAggregationMetric(\"count\"),\n Operator = new TransactionRuleAggregationOperator(\"gte\"),\n Value = 20.0,\n Window = \"30d\",\n Filters = new Dictionary { [\"direction\"] = \"INBOUND\", [\"transaction_type\"] = \"finance\", [\"subject_vendor_data\"] = \"__current__\", [\"currency\"] = \"EUR\" },\n },\n new()\n {\n Metric = new TransactionRuleAggregationMetric(\"max\"),\n Field = \"amount\",\n Operator = new TransactionRuleAggregationOperator(\"lt\"),\n Value = 10000.0,\n Window = \"30d\",\n Filters = new Dictionary { [\"direction\"] = \"INBOUND\", [\"transaction_type\"] = \"finance\", [\"subject_vendor_data\"] = \"__current__\", [\"currency\"] = \"EUR\" },\n },\n },\n Actions = new TransactionRuleAction[]\n {\n new()\n {\n Type = new TransactionRuleActionType(\"add_score\"),\n Value = 35,\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/transactions/rules/{rule_uuid}/": { + "parameters": [ + { + "name": "rule_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The rule's uuid from list or create." + } + ], + "get": { + "summary": "Get rule", + "description": "Retrieve one rule with its full configuration and execution counters.", + "operationId": "getTransactionRule", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "The rule.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleDetail" + } + } + } + }, + "404": { + "description": "No such rule on this application.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/rules/RULE_UUID/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.rules.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.rules.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Rules.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().rules().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.rules.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Rules.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "patch": { + "summary": "Update rule", + "description": "Partially update a rule. Custom rules accept every configuration field. Library (PRESET) rules accept only `mode` - use it to activate, disable, or switch a preset to TEST; any other field returns 400, because Didit maintains library rules and applies their updates automatically. To change a library rule's conditions, create a custom copy with `POST /v3/transactions/rules/` using the library rule's definition and `metadata.based_on` (the library key) and `metadata.based_on_rule_uuid`, then switch the library rule off with `mode: DISABLED` so a transaction does not alert twice. An installed copy of a withheld library rule is switched off: setting its `mode` to anything but `DISABLED` answers 400 (\"This library rule cannot run yet: its conditions read data the rules engine cannot evaluate.\").", + "operationId": "updateTransactionRule", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleUpdateRequest" + }, + "example": { + "mode": "ACTIVE" + } + } + } + }, + "responses": { + "200": { + "description": "The updated rule.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleDetail" + } + } + } + }, + "400": { + "description": "Validation error, a non-mode field on a library rule, or a mode other than DISABLED on a withheld library rule.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/transactions/rules/RULE_UUID/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"mode\": \"ACTIVE\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.rules.update(\n \"11111111-2222-3333-4444-555555555555\",\n mode=\"ACTIVE\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.rules.update(\"11111111-2222-3333-4444-555555555555\", {\n mode: \"ACTIVE\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Rules.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TransactionRuleUpdateRequest{\n\tMode: didit.Some(didit.TransactionRuleUpdateRequestMode(\"ACTIVE\")),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TransactionRuleUpdateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().rules().update(\"11111111-2222-3333-4444-555555555555\", TransactionRuleUpdateRequest.builder()\n .mode(TransactionRuleUpdateRequest.Mode.of(\"ACTIVE\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->update('11111111-2222-3333-4444-555555555555', [\n 'mode' => 'ACTIVE',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.rules.update(\n \"11111111-2222-3333-4444-555555555555\",\n mode: \"ACTIVE\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Rules.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new TransactionRuleUpdateRequest\n{\n Mode = new TransactionRuleUpdateRequestMode(\"ACTIVE\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "summary": "Delete rule", + "description": "Delete a CUSTOM rule. Deletion is immediate and permanent: the rule stops evaluating for new transactions and disappears from listings, while past rule runs on already-evaluated transactions keep their recorded results. Library rules cannot be deleted - uninstall them via the install endpoint instead (they can always be reinstalled from the library).", + "operationId": "deleteTransactionRule", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "204": { + "description": "Deleted." + }, + "400": { + "description": "The rule is a library preset - uninstall instead.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "detail": "Library rules cannot be deleted. Uninstall them via the rules install endpoint instead." + } + } + } + }, + "404": { + "description": "No such rule on this application.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/transactions/rules/RULE_UUID/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.transactions.rules.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.transactions.rules.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Transactions.Rules.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.transactions().rules().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.rules.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Transactions.Rules.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ] + } + }, + "/v3/transactions/rules/backtest/": { + "post": { + "summary": "Backtest rule configuration", + "description": "Evaluate a hypothetical rule configuration against your recent transactions (most recent first, up to the backtest cap) without creating a rule or touching any transaction. Use it before activating a rule to see how often it would have matched and how many subjects it would have affected.", + "operationId": "backtestTransactionRules", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "backtest", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleBacktestRequest" + }, + "example": { + "conditions": [ + { + "field": "currency", + "operator": "eq", + "value": "EUR" + }, + { + "field": "amount", + "operator": "lt", + "value": 10000 + } + ], + "aggregation": [ + { + "metric": "count", + "operator": "gte", + "value": 20, + "window": "30d", + "filters": { + "direction": "INBOUND", + "transaction_type": "finance", + "subject_vendor_data": "__current__", + "currency": "EUR" + } + }, + { + "metric": "max", + "field": "amount", + "operator": "lt", + "value": 10000, + "window": "30d", + "filters": { + "direction": "INBOUND", + "transaction_type": "finance", + "subject_vendor_data": "__current__", + "currency": "EUR" + } + } + ], + "evaluation_mode": "ALL", + "scope": { + "transaction_types": [ + "finance" + ], + "directions": [ + "INBOUND" + ] + }, + "period_days": 90 + } + } + } + }, + "responses": { + "200": { + "description": "Backtest statistics.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleBacktestResult" + }, + "example": { + "evaluated": 4820, + "matched": 12, + "affected_entities": 4, + "period_days": 90 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/rules/backtest/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"conditions\": [{\"field\": \"currency\", \"operator\": \"eq\", \"value\": \"EUR\"}, {\"field\": \"amount\", \"operator\": \"lt\", \"value\": 10000}], \"aggregation\": [{\"metric\": \"count\", \"operator\": \"gte\", \"value\": 20, \"window\": \"30d\", \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}}, {\"metric\": \"max\", \"field\": \"amount\", \"operator\": \"lt\", \"value\": 10000, \"window\": \"30d\", \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}}], \"evaluation_mode\": \"ALL\", \"scope\": {\"transaction_types\": [\"finance\"], \"directions\": [\"INBOUND\"]}, \"period_days\": 90}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.rules.backtest(\n conditions=[{\n \"field\": \"currency\",\n \"operator\": \"eq\",\n \"value\": \"EUR\",\n }, {\n \"field\": \"amount\",\n \"operator\": \"lt\",\n \"value\": 10000,\n }],\n aggregation=[{\n \"metric\": \"count\",\n \"operator\": \"gte\",\n \"value\": 20.0,\n \"window\": \"30d\",\n \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"},\n }, {\n \"metric\": \"max\",\n \"field\": \"amount\",\n \"operator\": \"lt\",\n \"value\": 10000.0,\n \"window\": \"30d\",\n \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"},\n }],\n evaluation_mode=\"ALL\",\n scope={\n \"transaction_types\": [\"finance\"],\n \"directions\": [\"INBOUND\"],\n },\n period_days=90,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.rules.backtest({\n conditions: [{\n field: \"currency\",\n operator: \"eq\",\n value: \"EUR\",\n }, {\n field: \"amount\",\n operator: \"lt\",\n value: 10000,\n }],\n aggregation: [{\n metric: \"count\",\n operator: \"gte\",\n value: 20.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }, {\n metric: \"max\",\n field: \"amount\",\n operator: \"lt\",\n value: 10000.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }],\n evaluation_mode: \"ALL\",\n scope: {\n transaction_types: [\"finance\"],\n directions: [\"INBOUND\"],\n },\n period_days: 90,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Rules.Backtest(ctx, didit.TransactionRuleBacktestRequest{\n\tConditions: didit.Some([]didit.TransactionRuleConditionParam{{\n\t\tField: \"currency\",\n\t\tOperator: \"eq\",\n\t\tValue: didit.Some[any](\"EUR\"),\n\t}, {\n\t\tField: \"amount\",\n\t\tOperator: \"lt\",\n\t\tValue: didit.Some[any](10000),\n\t}}),\n\tAggregation: didit.Some([]didit.TransactionRuleAggregationParam{{\n\t\tMetric: didit.Some(didit.TransactionRuleAggregationMetric(\"count\")),\n\t\tOperator: didit.Some(didit.TransactionRuleAggregationOperator(\"gte\")),\n\t\tValue: 20.0,\n\t\tWindow: didit.String(\"30d\"),\n\t\tFilters: didit.Some(map[string]any{\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}),\n\t}, {\n\t\tMetric: didit.Some(didit.TransactionRuleAggregationMetric(\"max\")),\n\t\tField: didit.String(\"amount\"),\n\t\tOperator: didit.Some(didit.TransactionRuleAggregationOperator(\"lt\")),\n\t\tValue: 10000.0,\n\t\tWindow: didit.String(\"30d\"),\n\t\tFilters: didit.Some(map[string]any{\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}),\n\t}}),\n\tEvaluationMode: didit.Some(didit.TransactionRuleBacktestRequestEvaluationMode(\"ALL\")),\n\tScope: didit.Some(didit.TransactionRuleScopeParam{\n\t\tTransactionTypes: didit.Some([]string{\"finance\"}),\n\t\tDirections: didit.Some([]didit.TransactionRuleScopeDirections{\"INBOUND\"}),\n\t}),\n\tPeriodDays: didit.Int(90),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.TransactionRuleAggregation;\nimport me.didit.types.TransactionRuleBacktestRequest;\nimport me.didit.types.TransactionRuleCondition;\nimport me.didit.types.TransactionRuleScope;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().rules().backtest(TransactionRuleBacktestRequest.builder()\n .conditions(List.of(\n TransactionRuleCondition.builder()\n .field(\"currency\")\n .operator(TransactionRuleCondition.Operator.of(\"eq\"))\n .value(\"EUR\")\n .build(),\n TransactionRuleCondition.builder()\n .field(\"amount\")\n .operator(TransactionRuleCondition.Operator.of(\"lt\"))\n .value(10000)\n .build()))\n .aggregation(List.of(\n TransactionRuleAggregation.builder()\n .metric(TransactionRuleAggregation.Metric.of(\"count\"))\n .operator(TransactionRuleAggregation.Operator.of(\"gte\"))\n .value(20.0)\n .window(\"30d\")\n .filters(Map.of(\"direction\", \"INBOUND\", \"transaction_type\", \"finance\", \"subject_vendor_data\", \"__current__\", \"currency\", \"EUR\"))\n .build(),\n TransactionRuleAggregation.builder()\n .metric(TransactionRuleAggregation.Metric.of(\"max\"))\n .field(\"amount\")\n .operator(TransactionRuleAggregation.Operator.of(\"lt\"))\n .value(10000.0)\n .window(\"30d\")\n .filters(Map.of(\"direction\", \"INBOUND\", \"transaction_type\", \"finance\", \"subject_vendor_data\", \"__current__\", \"currency\", \"EUR\"))\n .build()))\n .evaluationMode(TransactionRuleBacktestRequest.EvaluationMode.of(\"ALL\"))\n .scope(TransactionRuleScope.builder()\n .transactionTypes(List.of(\"finance\"))\n .directions(List.of(TransactionRuleScope.Directions.of(\"INBOUND\")))\n .build())\n .periodDays(90L)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->backtest([\n 'conditions' => [[\n 'field' => 'currency',\n 'operator' => 'eq',\n 'value' => 'EUR',\n ], [\n 'field' => 'amount',\n 'operator' => 'lt',\n 'value' => 10000,\n ]],\n 'aggregation' => [[\n 'metric' => 'count',\n 'operator' => 'gte',\n 'value' => 20.0,\n 'window' => '30d',\n 'filters' => ['direction' => 'INBOUND', 'transaction_type' => 'finance', 'subject_vendor_data' => '__current__', 'currency' => 'EUR'],\n ], [\n 'metric' => 'max',\n 'field' => 'amount',\n 'operator' => 'lt',\n 'value' => 10000.0,\n 'window' => '30d',\n 'filters' => ['direction' => 'INBOUND', 'transaction_type' => 'finance', 'subject_vendor_data' => '__current__', 'currency' => 'EUR'],\n ]],\n 'evaluation_mode' => 'ALL',\n 'scope' => [\n 'transaction_types' => ['finance'],\n 'directions' => ['INBOUND'],\n ],\n 'period_days' => 90,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.rules.backtest(\n conditions: [{\n field: \"currency\",\n operator: \"eq\",\n value: \"EUR\",\n }, {\n field: \"amount\",\n operator: \"lt\",\n value: 10000,\n }],\n aggregation: [{\n metric: \"count\",\n operator: \"gte\",\n value: 20.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }, {\n metric: \"max\",\n field: \"amount\",\n operator: \"lt\",\n value: 10000.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }],\n evaluation_mode: \"ALL\",\n scope: {\n transaction_types: [\"finance\"],\n directions: [\"INBOUND\"],\n },\n period_days: 90\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Rules.BacktestAsync(new TransactionRuleBacktestRequest\n{\n Conditions = new TransactionRuleCondition[]\n {\n new()\n {\n Field = \"currency\",\n Operator = \"eq\",\n Value = \"EUR\",\n },\n new()\n {\n Field = \"amount\",\n Operator = \"lt\",\n Value = 10000,\n },\n },\n Aggregation = new TransactionRuleAggregation[]\n {\n new()\n {\n Metric = new TransactionRuleAggregationMetric(\"count\"),\n Operator = new TransactionRuleAggregationOperator(\"gte\"),\n Value = 20.0,\n Window = \"30d\",\n Filters = new Dictionary { [\"direction\"] = \"INBOUND\", [\"transaction_type\"] = \"finance\", [\"subject_vendor_data\"] = \"__current__\", [\"currency\"] = \"EUR\" },\n },\n new()\n {\n Metric = new TransactionRuleAggregationMetric(\"max\"),\n Field = \"amount\",\n Operator = new TransactionRuleAggregationOperator(\"lt\"),\n Value = 10000.0,\n Window = \"30d\",\n Filters = new Dictionary { [\"direction\"] = \"INBOUND\", [\"transaction_type\"] = \"finance\", [\"subject_vendor_data\"] = \"__current__\", [\"currency\"] = \"EUR\" },\n },\n },\n EvaluationMode = new TransactionRuleBacktestRequestEvaluationMode(\"ALL\"),\n Scope = new TransactionRuleScope\n {\n TransactionTypes = new string[] { \"finance\" },\n Directions = new TransactionRuleScopeDirections[] { \"INBOUND\" },\n },\n PeriodDays = 90,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/transactions/rules/library/": { + "get": { + "summary": "List rule library", + "description": "Browse the full preset rule library (150+ rules) with each entry's complete configuration and an `is_installed` flag for this application. Filter by bundle, category, or free-text search. A library rule whose conditions read data the rules engine cannot evaluate yet is withheld until it can run. Withheld rules are not listed.", + "operationId": "listTransactionRuleLibrary", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "list_library", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "bundle", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Filter by bundle, e.g. finance, aml_ctf, crypto_monitoring, travel_rule" + }, + { + "name": "category", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Filter by category" + }, + { + "name": "search", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Case-insensitive match against title and description" + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "description": "Page size" + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "description": "Page offset" + } + ], + "responses": { + "200": { + "description": "Paginated library entries.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleLibraryEntry" + } + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/rules/library/?bundle=finance' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.transactions.rules.list_library():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.transactions.rules.listLibrary()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Transactions.Rules.ListLibraryAutoPaging(ctx, didit.TransactionsRulesListLibraryParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.transactions().rules().listLibrary()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->listLibrary() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.rules.list_library.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Transactions.Rules.ListLibraryAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/transactions/rules/install/": { + "post": { + "summary": "Install library rules", + "description": "Install one or more presets onto the application, by `library_keys` or a whole `bundle`. An installed preset is active at once while transaction monitoring is on, and disabled otherwise; already-installed keys are skipped. A library rule whose conditions read data the rules engine cannot evaluate yet is withheld until it can run. A request that names one in `library_keys` answers 400 and installs nothing (\"These library rules cannot run yet: their conditions read data the rules engine cannot evaluate.\" followed by the keys), and a `bundle` install skips it. A withheld rule comes back on its own once its conditions can be evaluated. A `library_keys` entry that is not in the library is skipped, not refused.", + "operationId": "installTransactionRuleLibrary", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "install_library", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleInstallRequest" + }, + "example": { + "library_keys": [ + "structuring-inbound", + "structuring-outbound" + ] + } + } + } + }, + "responses": { + "200": { + "description": "What was installed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleInstallResult" + }, + "example": { + "installed_library_keys": [ + "structuring-inbound", + "structuring-outbound" + ], + "installed_count": 2 + } + } + } + }, + "400": { + "description": "Neither library_keys nor bundle provided, `library_keys` is not a list, or a `library_keys` entry is withheld. An unknown entry is skipped and answers 200 without it.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/rules/install/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"library_keys\": [\"structuring-inbound\", \"structuring-outbound\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.rules.install_library(\n library_keys=[\"structuring-inbound\", \"structuring-outbound\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.rules.installLibrary({\n library_keys: [\"structuring-inbound\", \"structuring-outbound\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Rules.InstallLibrary(ctx, didit.TransactionRuleInstallRequest{\n\tLibraryKeys: didit.Some([]string{\"structuring-inbound\", \"structuring-outbound\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.TransactionRuleInstallRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().rules().installLibrary(TransactionRuleInstallRequest.builder()\n .libraryKeys(List.of(\"structuring-inbound\", \"structuring-outbound\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->installLibrary([\n 'library_keys' => ['structuring-inbound', 'structuring-outbound'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.rules.install_library(library_keys: [\"structuring-inbound\", \"structuring-outbound\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Rules.InstallLibraryAsync(new TransactionRuleInstallRequest\n{\n LibraryKeys = new string[] { \"structuring-inbound\", \"structuring-outbound\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "summary": "Uninstall library rules", + "description": "Remove installed presets from the application, by `library_keys` or a whole `bundle`. Uninstalled presets stop evaluating immediately and can be reinstalled from the library at any time.", + "operationId": "uninstallTransactionRuleLibrary", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "uninstall_library", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleInstallRequest" + }, + "example": { + "bundle": "responsible_gaming" + } + } + } + }, + "responses": { + "200": { + "description": "How many presets were removed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleInstallResult" + }, + "example": { + "uninstalled_count": 5 + } + } + } + }, + "400": { + "description": "Neither library_keys nor bundle provided.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/transactions/rules/install/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"bundle\": \"responsible_gaming\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.rules.uninstall_library(\n bundle=\"responsible_gaming\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.rules.uninstallLibrary({\n bundle: \"responsible_gaming\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Rules.UninstallLibrary(ctx, didit.TransactionRuleInstallRequest{\n\tBundle: didit.String(\"responsible_gaming\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TransactionRuleInstallRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().rules().uninstallLibrary(TransactionRuleInstallRequest.builder()\n .bundle(\"responsible_gaming\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->uninstallLibrary([\n 'bundle' => 'responsible_gaming',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.rules.uninstall_library(bundle: \"responsible_gaming\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Rules.UninstallLibraryAsync(new TransactionRuleInstallRequest\n{\n Bundle = \"responsible_gaming\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/": { + "get": { + "summary": "List networks", + "description": "List fraud networks detected within this application's organization, highest risk first by default. By default dismissed networks are excluded; pass status to include them. Sandbox applications never have networks: clustering excludes sandbox data entirely. Every response carries the organization's `sharing_mode` and whether cross-organization signals apply to it (`cross_org_available`, `cross_org_unavailable_reason`). `kpis` aggregate the networks that match your filters and `counts` aggregate every network the application can see; pass `summary=false` to skip both when you only need the page. Requires a full-access API key, or `list:networks` for a console member.", + "operationId": "list_networks", + "x-didit-sdk": { + "resource": "networks", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "status", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated statuses to include: active,in_review,resolved,dismissed. Omit to exclude dismissed." + }, + { + "name": "scope", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "identity", + "transactions" + ] + }, + "description": "`transactions` keeps networks whose members are all transaction parties; `identity` keeps every other network. Omit for both. It also narrows `counts.views` and `counts.facets`. Any other value returns 400." + }, + { + "name": "signal_type", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated signal types: ip_address,device,face,document_number,phone,email,address." + }, + { + "name": "pattern_type", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated pattern types, e.g. exact_same_device,same_email." + }, + { + "name": "risk_band", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated bands: low,medium,high." + }, + { + "name": "min_size", + "in": "query", + "schema": { + "type": "integer" + }, + "description": "Minimum member count." + }, + { + "name": "max_size", + "in": "query", + "schema": { + "type": "integer" + }, + "description": "Maximum member count." + }, + { + "name": "assignee", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated assignees: `unassigned`, member emails (matched case-insensitively), or `me` for the calling console member. A network matches any value. `me` needs a console member token: an API key gets 400 `{\"assignee\": [\"\\\"me\\\" needs a user token; an API key has no assignee identity.\"]}`." + }, + { + "name": "priority", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated priorities: `low`, `medium`, `high`, or `none` for networks without a priority. Any other value returns 400 `{\"priority\": \"Unknown priority: .\"}`." + }, + { + "name": "risk_min", + "in": "query", + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "description": "Minimum risk score, a whole number from 0 to 100. A value that is not a whole number returns 400 `{\"risk_min\": \"Use a whole number from 0 to 100.\"}`." + }, + { + "name": "has_case", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "`true` keeps the networks your application opened a case for, `false` the others. Any other value is ignored." + }, + { + "name": "date_from", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Lower bound on last_activity_at (ISO 8601 date or datetime)." + }, + { + "name": "date_to", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Upper bound on first_activity_at (ISO 8601 date or datetime)." + }, + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Free-text search across network name, member names, the network_id (N-0001), and exact pattern_type tokens." + }, + { + "name": "ordering", + "in": "query", + "schema": { + "type": "string" + }, + "description": "One of risk_score, -risk_score, last_activity_at, -last_activity_at, first_activity_at, -first_activity_at, network_number, -network_number. Defaults to -risk_score,-last_activity_at." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 100, + "minimum": 1, + "maximum": 500 + }, + "description": "Page size, 1-500. Defaults to 100." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset of the first record." + }, + { + "name": "summary", + "in": "query", + "schema": { + "type": "boolean", + "default": true + }, + "description": "`false` returns the page and its `count` only, without `kpis` and `counts`. Defaults to `true`. Any value other than `true` or `false` returns 400." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/?status=active,in_review&ordering=-risk_score' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.networks.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.networks.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Networks.ListAutoPaging(ctx, didit.NetworksListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.networks().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.networks.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Networks.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated network list, with the organization's sharing mode and cross-organization availability, plus `kpis` and `counts` unless `summary=false`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "integer", + "nullable": true, + "description": "Offset of the next page, or null on the last page." + }, + "previous": { + "type": "integer", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkListItem" + } + }, + "kpis": { + "type": "object", + "description": "Each key is {value, delta} over the results matching the current filters. Omitted when `summary=false`.", + "properties": { + "active": { + "type": "object" + }, + "in_review": { + "type": "object" + }, + "applicants_involved": { + "type": "object" + }, + "detected_this_period": { + "type": "object" + }, + "false_positive_rate": { + "type": "object" + } + } + }, + "counts": { + "$ref": "#/components/schemas/NetworkListCounts" + }, + "sharing_mode": { + "type": "string", + "enum": [ + "off", + "private", + "global" + ], + "description": "The organization's cross-organization sharing mode. Only `global` compares networks across organizations; `private` and `off` keep them to the organization's own history.", + "example": "global" + }, + "cross_org_available": { + "type": "boolean", + "description": "Whether cross-organization signals apply to this organization. They need both the Enterprise plan and the `global` sharing mode.", + "example": true + }, + "cross_org_unavailable_reason": { + "type": "string", + "enum": [ + "plan", + "sharing_mode" + ], + "nullable": true, + "description": "Why cross-organization signals do not apply, checked in this order. `plan`: the organization's plan does not include the cross-organization fraud network (it needs the Enterprise plan), whatever the sharing mode. `sharing_mode`: the plan includes it, but the sharing mode is `private` or `off`. Null when `cross_org_available` is `true`.", + "example": null + } + }, + "required": [ + "count", + "next", + "previous", + "results", + "sharing_mode", + "cross_org_available", + "cross_org_unavailable_reason" + ] + } + } + } + }, + "400": { + "description": "An invalid filter, ordering, pagination or `summary` value.", + "content": { + "application/json": { + "examples": { + "Invalid summary": { + "summary": "Invalid summary", + "value": { + "summary": [ + "Must be \"true\" or \"false\"." + ] + } + }, + "Invalid scope": { + "summary": "Invalid scope", + "value": { + "scope": [ + "Use identity or transactions." + ] + } + }, + "Unsupported ordering": { + "summary": "Unsupported ordering", + "value": [ + "Unsupported ordering." + ] + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `list:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/": { + "get": { + "summary": "Get network", + "description": "Retrieve a single network's full detail: stats, fraud tags and risk signals, plus the organization's `sharing_mode` and cross-organization availability, your application's `tags` on the network, member `outcomes` and pattern scores (`patterns`). Requires a full-access API key, or `read:networks` for a console member.", + "operationId": "get_network", + "x-didit-sdk": { + "resource": "networks", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.retrieve(\n \"n3a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.retrieve(\"n3a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.Retrieve(ctx, \"n3a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().retrieve(\"n3a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->retrieve('n3a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.retrieve(\"n3a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.RetrieveAsync(\"n3a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Network detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkDetailResponse" + }, + "example": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "network_id": "N-0042", + "name": null, + "status": "in_review", + "risk": { + "score": 82, + "band": "high", + "bar": 0.82 + }, + "pattern_types": [ + "exact_same_device", + "same_email" + ], + "signal_types": [ + "device", + "email" + ], + "member_counts": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signal_coverage": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + }, + "first_activity_at": "2026-10-01T09:12:44Z", + "last_activity_at": "2026-10-12T16:03:10Z", + "stats": { + "members": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signals": { + "total": 2, + "by_type": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + } + } + }, + "fraud_tags": [], + "risk_signals": { + "residential_proxy_reuse": { + "detected": false, + "signal_count": 0, + "max_subject_count": 0 + }, + "email_intelligence": { + "enriched_signal_count": 1, + "high_risk_count": 0, + "social_profile_count": 0 + }, + "phone_intelligence": { + "enriched_signal_count": 0, + "high_risk_count": 0 + } + }, + "reviewed_at": "2026-10-12T16:30:00Z", + "reviewed_by": "reviewer@example.com", + "review_reason": "Shared device across three new accounts.", + "notes_count": 0, + "sharing_mode": "global", + "cross_org_available": true, + "cross_org_unavailable_reason": null, + "tags": [ + { + "uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "name": "SUSPICIOUS", + "color": "#6366F1", + "source": "custom", + "added_by": null, + "added_at": "2026-10-13T08:15:00Z" + } + ], + "outcomes": { + "approved": 1, + "declined": 1, + "in_review": 0, + "total": 3 + }, + "patterns": [ + { + "type": "background", + "score": 0.8125, + "members": 3, + "reason": null, + "computed_at": "2026-10-13T08:00:00Z" + } + ] + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `read:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/graph/": { + "get": { + "summary": "Get network graph", + "description": "Retrieve the network as a graph of subject nodes (vendor users, vendor businesses, transaction parties) and signal nodes (the shared device/IP/face/document/phone/email/address values linking them), with edges representing memberships.", + "operationId": "get_network_graph", + "x-didit-sdk": { + "resource": "networks", + "method": "retrieve_graph", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + }, + { + "name": "depth", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "1", + "2", + "3", + "all" + ] + }, + "description": "Hops from the focus subject to include. Defaults to all (unbounded)." + }, + { + "name": "focus_kind", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "vendor_user", + "vendor_business", + "transaction_party" + ] + }, + "description": "Subject type to center the graph on. Must be supplied together with focus_id; defaults to an arbitrary member when depth is bounded and neither is supplied." + }, + { + "name": "focus_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Subject UUID to center the graph on." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/graph/?depth=2' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.retrieve_graph(\n \"n3a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.retrieveGraph(\"n3a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.RetrieveGraph(ctx, \"n3a5e000-0000-4000-8000-000000000001\", didit.NetworksRetrieveGraphParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().retrieveGraph(\"n3a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->retrieveGraph('n3a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.retrieve_graph(\"n3a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.RetrieveGraphAsync(\"n3a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Network graph.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkGraph" + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/members/": { + "get": { + "summary": "List network members", + "description": "List the subjects (vendor users, vendor businesses, or transaction parties) that belong to this network.", + "operationId": "list_network_members", + "x-didit-sdk": { + "resource": "networks", + "method": "list_members", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/members/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.list_members(\n \"n3a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.listMembers(\"n3a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.ListMembers(ctx, \"n3a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().listMembers(\"n3a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->listMembers('n3a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.list_members(\"n3a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.ListMembersAsync(\"n3a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Network members. Not paginated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkMember" + } + } + } + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/signals/": { + "get": { + "summary": "List network signals", + "description": "List the signals (shared device, IP, face, document number, phone, email, or address values) that link this network's members.", + "operationId": "list_network_signals", + "x-didit-sdk": { + "resource": "networks", + "method": "list_signals", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/signals/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.list_signals(\n \"n3a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.listSignals(\"n3a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.ListSignals(ctx, \"n3a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().listSignals(\"n3a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->listSignals('n3a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.list_signals(\"n3a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.ListSignalsAsync(\"n3a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Network signals. Not paginated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkSignal" + } + } + } + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/timeline/": { + "get": { + "summary": "Get network timeline", + "description": "Without `contract`: the network's own events (detection, each signal's latest observation and its review), oldest first and not paginated. `limit`, `cursor` and `event_type` are ignored. With `contract=2`: the typed timeline, newest first and paginated by cursor. It adds what the member accounts did (verification attempts, transactions, sign-ins you post through the events API and the live rules they hit) and the cases opened for the network. `note_added` events are returned to console members only, never to an API key. Requires a full-access API key, or `read:networks` for a console member.", + "operationId": "get_network_timeline", + "x-didit-sdk": { + "resource": "networks", + "method": "retrieve_timeline", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + }, + { + "name": "contract", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "2" + ] + }, + "description": "`2` returns the typed timeline. Omit it for the default timeline. Any other value returns 400." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 500, + "default": 100 + }, + "description": "Typed timeline only. Events per page, 1 to 500. Defaults to 100." + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Typed timeline only. The previous page's `next_cursor`. An invalid or empty cursor returns 400." + }, + { + "name": "event_type", + "in": "query", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "array", + "maxItems": 20, + "items": { + "type": "string", + "enum": [ + "network_detected", + "signal_observed", + "network_active", + "network_in_review", + "network_resolved", + "network_dismissed", + "verification_attempt", + "transaction", + "login", + "rule_hit", + "case_linked", + "note_added" + ] + } + }, + "description": "Typed timeline only. Event types to keep, repeated or comma-separated, at most 20 values. Defaults to every type." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/timeline/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "curl", + "label": "curl (typed timeline)", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/3fa85f64-5717-4562-b3fc-2c963f66afa6/timeline/?contract=2&limit=50&event_type=verification_attempt,login' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.retrieve_timeline(\n \"n3a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.retrieveTimeline(\"n3a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.RetrieveTimeline(ctx, \"n3a5e000-0000-4000-8000-000000000001\", didit.NetworksRetrieveTimelineParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().retrieveTimeline(\"n3a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->retrieveTimeline('n3a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.retrieve_timeline(\"n3a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.RetrieveTimelineAsync(\"n3a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Without `contract`: `{results}`, oldest first, not paginated. With `contract=2`: `{results, next_cursor}`, newest first.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/NetworkTimeline" + }, + { + "$ref": "#/components/schemas/NetworkTimelinePage" + } + ] + }, + "examples": { + "Default timeline": { + "summary": "Without contract", + "value": { + "results": [ + { + "event_type": "network_detected", + "occurred_at": "2026-10-01T09:12:44Z", + "label": "Network detected", + "metadata": { + "network_id": "N-0042" + } + }, + { + "event_type": "signal_observed", + "occurred_at": "2026-10-12T16:03:10Z", + "label": "device observed", + "metadata": { + "signal_type": "device", + "subject_count": 3, + "is_hub": false + } + }, + { + "event_type": "network_in_review", + "occurred_at": "2026-10-12T16:30:00Z", + "label": "Network in_review", + "metadata": { + "reason": "Shared device across three new accounts." + } + } + ] + } + }, + "Typed timeline": { + "summary": "With contract=2", + "value": { + "results": [ + { + "id": "monitoring_event:9b2f1c3e-4d5a-4b6c-8d7e-0f1a2b3c4d5e", + "event_type": "login", + "occurred_at": "2026-10-12T18:41:02Z", + "ingested_at": "2026-10-12T18:41:03Z", + "label": "Login", + "metadata": {}, + "account": { + "node_id": "vendor_user:5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "node_type": "account", + "uuid": "5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "vendor_data": "user-1042", + "display_name": "Alex Example", + "ownership": "owned", + "route": { + "kind": "user", + "id": "5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "application_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + } + }, + "source_ref": { + "kind": "monitoring_event", + "id": "9b2f1c3e-4d5a-4b6c-8d7e-0f1a2b3c4d5e", + "ownership": "owned" + }, + "verification_attempt": null, + "transaction": null, + "login": { + "action": "auth.login", + "succeeded": true, + "status": "APPROVED", + "ip": { + "address": "203.0.113.24", + "country_code": "ES", + "city": "Madrid", + "connection_type": null, + "is_vpn_or_tor": false, + "is_data_center": false + }, + "device": { + "fingerprint": "fp_0a1b2c3d", + "label": "Chrome · Android" + }, + "is_first_login": false, + "is_new_device": true + }, + "rule_hit": null, + "case_linked": null, + "note_added": null + }, + { + "id": "session:1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "event_type": "verification_attempt", + "occurred_at": "2026-10-12T16:03:10Z", + "ingested_at": "2026-10-12T16:03:10Z", + "label": "Verification attempt 2 of 2", + "metadata": {}, + "account": { + "node_id": "vendor_user:5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "node_type": "account", + "uuid": "5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "vendor_data": "user-1042", + "display_name": "Alex Example", + "ownership": "owned", + "route": { + "kind": "user", + "id": "5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "application_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + } + }, + "source_ref": { + "kind": "session", + "id": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "ownership": "owned", + "route": { + "kind": "session", + "id": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "application_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + } + }, + "verification_attempt": { + "session_id": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "session_number": 1287, + "attempt": 2, + "attempt_count": 2, + "status": "Approved", + "status_changed_at": "2026-10-12T16:05:41Z", + "decline_reasons": [] + }, + "transaction": null, + "login": null, + "rule_hit": null, + "case_linked": null, + "note_added": null + }, + { + "id": "signal_observed:8d7c6b5a-4e3f-4a2b-9c1d-0e9f8a7b6c5d", + "event_type": "signal_observed", + "occurred_at": "2026-10-12T16:03:10Z", + "ingested_at": "2026-10-01T09:12:44Z", + "label": "device observed", + "metadata": { + "signal_type": "device", + "subject_count": 3, + "is_hub": false + }, + "account": null, + "source_ref": null, + "verification_attempt": null, + "transaction": null, + "login": null, + "rule_hit": null, + "case_linked": null, + "note_added": null + } + ], + "next_cursor": "eyJ0IjoiMjAyNi0xMC0xMlQxNjowMzoxMCswMDowMCIsImMiOiIyMDI2LTEwLTAxVDA5OjEyOjQ0KzAwOjAwIiwiciI6MSwidSI6IjhkN2M2YjVhLTRlM2YtNGEyYi05YzFkLTBlOWY4YTdiNmM1ZCJ9" + } + } + } + } + } + }, + "400": { + "description": "With `contract` present: an invalid `contract`, `limit`, `cursor` or `event_type` value.", + "content": { + "application/json": { + "examples": { + "Unsupported contract": { + "summary": "Unsupported contract", + "value": { + "contract": [ + "\"3\" is not a valid choice." + ] + } + }, + "Invalid cursor": { + "summary": "Invalid cursor", + "value": { + "cursor": [ + "This cursor is not valid. Reload the feed." + ] + } + }, + "Limit too large": { + "summary": "Limit too large", + "value": { + "limit": [ + "Ensure this value is less than or equal to 500." + ] + } + }, + "Unknown event type": { + "summary": "Unknown event type", + "value": { + "event_type": { + "0": [ + "\"logout\" is not a valid choice." + ] + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `read:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/map/": { + "get": { + "summary": "Get network map", + "description": "Geographic points where this network's members' addresses/locations converge, sourced from the sessions, business sessions, and transactions backing its memberships.", + "operationId": "get_network_map", + "x-didit-sdk": { + "resource": "networks", + "method": "retrieve_map", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/map/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.retrieve_map(\n \"n3a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.retrieveMap(\"n3a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.RetrieveMap(ctx, \"n3a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().retrieveMap(\"n3a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->retrieveMap('n3a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.retrieve_map(\"n3a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.RetrieveMapAsync(\"n3a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Network map.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkMap" + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/sessions/{session_id}/networks/": { + "get": { + "summary": "List networks for a session", + "description": "List the networks the session's vendor user belongs to. Resolves through the session's vendor_data-linked user.", + "operationId": "list_session_network_memberships", + "x-didit-sdk": { + "resource": "sessions", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Session UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/sessions/aaaaaaaa-0000-4000-8000-000000000001/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.sessions.list_networks(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.sessions.listNetworks(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.ListNetworks(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.sessions().listNetworks(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->listNetworks('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.sessions.list_networks(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ListNetworksAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/business-sessions/{business_session_id}/networks/": { + "get": { + "summary": "List networks for a business session", + "description": "List the networks the business session's vendor business belongs to.", + "operationId": "list_business_session_network_memberships", + "x-didit-sdk": { + "resource": "business_sessions", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "business_session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Business session UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/business-sessions/aaaaaaaa-0000-4000-8000-000000000002/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.business_sessions.list_networks(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.businessSessions.listNetworks(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.BusinessSessions.ListNetworks(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.businessSessions().listNetworks(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businessSessions->listNetworks('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.business_sessions.list_networks(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.BusinessSessions.ListNetworksAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/users/{vendor_user_uuid}/networks/": { + "get": { + "summary": "List networks for a user", + "description": "List the networks a vendor user belongs to.", + "operationId": "list_user_network_memberships", + "x-didit-sdk": { + "resource": "users", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "vendor_user_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Vendor user UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/users/aaaaaaaa-0000-4000-8000-000000000003/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.users.list_networks(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.users.listNetworks(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Users.ListNetworks(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.users().listNetworks(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->listNetworks('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.users.list_networks(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Users.ListNetworksAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/businesses/{vendor_business_uuid}/networks/": { + "get": { + "summary": "List networks for a business", + "description": "List the networks a vendor business belongs to.", + "operationId": "list_business_network_memberships", + "x-didit-sdk": { + "resource": "businesses", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "vendor_business_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Vendor business UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/businesses/aaaaaaaa-0000-4000-8000-000000000004/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.businesses.list_networks(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.businesses.listNetworks(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.ListNetworks(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.businesses().listNetworks(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->listNetworks('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.businesses.list_networks(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.ListNetworksAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/transactions/{transaction_uuid}/networks/": { + "get": { + "summary": "List networks for a transaction", + "description": "List the networks the transaction's backing KYC/KYB subject belongs to (resolved through the transaction's session or business session).", + "operationId": "list_transaction_network_memberships", + "x-didit-sdk": { + "resource": "transactions", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "transaction_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Transaction UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/transactions/aaaaaaaa-0000-4000-8000-000000000005/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.transactions.list_networks(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.transactions.listNetworks(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.ListNetworks(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.transactions().listNetworks(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->listNetworks('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.transactions.list_networks(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.ListNetworksAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/transactions/{transaction_uuid}/parties/{transaction_party_uuid}/networks/": { + "get": { + "summary": "List networks for a transaction party", + "description": "List the networks a transaction party (remitter, beneficiary, or counterparty) belongs to, resolved through the party itself rather than through the transaction's backing session.", + "operationId": "list_transaction_party_network_memberships", + "x-didit-sdk": { + "resource": "transactions", + "method": "list_party_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "transaction_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Transaction UUID." + }, + { + "name": "transaction_party_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Transaction party UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/transactions/aaaaaaaa-0000-4000-8000-000000000005/parties/bbbbbbbb-0000-4000-8000-000000000006/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.transactions.list_party_networks(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.transactions.listPartyNetworks(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.ListPartyNetworks(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.transactions().listPartyNetworks(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->listPartyNetworks('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.transactions.list_party_networks(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.ListPartyNetworksAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/biometric-templates/": { + "get": { + "summary": "List retained biometric templates", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nReturns a paginated list of the templates the application currently holds. Purged templates are not listed.", + "operationId": "list_biometric_templates", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "query", + "name": "vendor_data", + "required": false, + "description": "Case-insensitive substring match on the owning User's current `vendor_data`.", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "vendor_user_uuid", + "required": false, + "description": "Exact match on the owning User.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "query", + "name": "status", + "required": false, + "description": "Lifecycle status. Purged templates are never listed.", + "schema": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed" + ] + } + }, + { + "in": "query", + "name": "source_type", + "required": false, + "description": "How the template was created.", + "schema": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ] + } + }, + { + "in": "query", + "name": "retained_from", + "required": false, + "description": "Templates retained at or after this time.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "retained_to", + "required": false, + "description": "Templates retained at or before this time. Must not be earlier than `retained_from` (`400`).", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "ordering", + "required": false, + "description": "Sort order.", + "schema": { + "type": "string", + "enum": [ + "retained_at", + "-retained_at", + "expires_at", + "-expires_at" + ], + "default": "-retained_at" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "description": "Page size.", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "required": false, + "description": "Pagination offset.", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Paginated templates.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Template id. Randomly generated; not derived from the deleted session or its face." + }, + "vendor_user_uuid": { + "type": "string", + "format": "uuid", + "description": "The owning User." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "The User's current `vendor_data`, resolved when you read the template (never copied)." + }, + "source_type": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ], + "description": "How the template was created: a session deletion (API or Console), the application's automatic data-retention window, or a Didit-operated backfill you instructed." + }, + "provenance_reference": { + "type": "string", + "format": "uuid", + "description": "Opaque reference tying the template to its audit trail. It is not the deleted session's id and cannot be resolved to it." + }, + "retention_policy": { + "type": "string", + "enum": [ + "delete_with_session", + "retain_until_user_deleted" + ], + "description": "The application policy at retention time." + }, + "retention_override": { + "type": "boolean", + "nullable": true, + "description": "The `retain_face_embeddings` value sent on the deletion, or `null` when the policy applied." + }, + "retained_at": { + "type": "string", + "format": "date-time" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the template expires and is purged automatically. Always set." + }, + "retained_by": { + "type": "string", + "nullable": true, + "description": "Principal that gave the instruction: the application id for API keys, the Console member id for Console actions, or a `service:` identifier for automatic retention. Never an email address." + }, + "retained_by_type": { + "type": "string", + "enum": [ + "API_KEY", + "CONSOLE_USER", + "SYSTEM" + ] + }, + "instruction_class": { + "type": "string", + "enum": [ + "operational_session_delete", + "privacy_erasure" + ] + }, + "instruction_source": { + "type": "string", + "enum": [ + "api", + "console", + "automatic_retention", + "backfill", + "privacy_erasure", + "explicit_template_purge", + "vendor_user_delete", + "expiry", + "reconciliation", + "migration", + "system" + ] + }, + "instruction_id": { + "type": "string", + "description": "The `instruction_id` sent on the deletion, or a Didit-generated id." + }, + "held_out_of_use": { + "type": "boolean", + "description": "`true` only while a legal hold applies. A held template is excluded from all matching." + }, + "status": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed", + "purged" + ], + "description": "Only `active` templates that are not held, not past `expires_at`, and whose User still exists take part in matching." + }, + "purged_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + } + } + } + }, + "examples": { + "One active template": { + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "vendor_user_uuid": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f", + "vendor_data": "user-42", + "source_type": "session_delete", + "provenance_reference": "6b1e8d2f-4a3c-4f9e-8c7d-1a2b3c4d5e6f", + "retention_policy": "retain_until_user_deleted", + "retention_override": null, + "retained_at": "2026-08-28T10:15:42.318402Z", + "expires_at": "2027-08-28T10:15:42.318402Z", + "retained_by": "3c9e2b1f-7d4a-4e8b-9c1d-5f6a7b8c9d0e", + "retained_by_type": "API_KEY", + "instruction_class": "operational_session_delete", + "instruction_source": "api", + "instruction_id": "post-approval-cleanup-2026-08", + "held_out_of_use": false, + "status": "active", + "purged_at": null + } + ] + } + } + } + } + } + }, + "400": { + "description": "Invalid filter value, or `retained_to` earlier than `retained_from`. Field-keyed validation errors.", + "content": { + "application/json": { + "examples": { + "Bad range": { + "value": { + "retained_to": [ + "Must be on or after retained_from." + ] + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/biometric-templates/?vendor_data=user-42&status=active' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.biometric_templates.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.biometricTemplates.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.BiometricTemplates.ListAutoPaging(ctx, didit.BiometricTemplatesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.biometricTemplates().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "biometricTemplates->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.biometric_templates.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.BiometricTemplates.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/biometric-templates/count/": { + "get": { + "summary": "Count retained biometric templates", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nReturns the number of templates matching the same filters as the list endpoint. Use it to check what a filtered bulk purge with `delete_all: true` would touch.", + "operationId": "count_biometric_templates", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "count", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "query", + "name": "vendor_data", + "required": false, + "description": "Case-insensitive substring match on the owning User's current `vendor_data`.", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "vendor_user_uuid", + "required": false, + "description": "Exact match on the owning User.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "query", + "name": "status", + "required": false, + "description": "Lifecycle status. Purged templates are never listed.", + "schema": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed" + ] + } + }, + { + "in": "query", + "name": "source_type", + "required": false, + "description": "How the template was created.", + "schema": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ] + } + }, + { + "in": "query", + "name": "retained_from", + "required": false, + "description": "Templates retained at or after this time.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "retained_to", + "required": false, + "description": "Templates retained at or before this time. Must not be earlier than `retained_from` (`400`).", + "schema": { + "type": "string", + "format": "date-time" + } + } + ], + "responses": { + "200": { + "description": "Count.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "minimum": 0 + } + } + }, + "examples": { + "Count": { + "value": { + "count": 128 + } + } + } + } + } + }, + "400": { + "description": "Invalid filter value, or `retained_to` earlier than `retained_from`. Field-keyed validation errors.", + "content": { + "application/json": { + "examples": { + "Bad range": { + "value": { + "retained_to": [ + "Must be on or after retained_from." + ] + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/biometric-templates/count/?status=active' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.biometric_templates.count()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.biometricTemplates.count();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.BiometricTemplates.Count(ctx, didit.BiometricTemplatesCountParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.biometricTemplates().count();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "biometricTemplates->count();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.biometric_templates.count\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.BiometricTemplates.CountAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/biometric-templates/{template_uuid}/": { + "get": { + "summary": "Get a retained biometric template", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nReturns one template. The response never includes an image, a vector, the deleted session's id, or identity data.", + "operationId": "get_biometric_template", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "template_uuid", + "required": true, + "description": "Template id, as returned in `biometric_template_uuid` by the session deletion endpoints, `biometric_template_id` on a Face Search match, or the list endpoint.", + "schema": { + "type": "string", + "format": "uuid", + "example": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b" + } + } + ], + "responses": { + "200": { + "description": "The template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Template id. Randomly generated; not derived from the deleted session or its face." + }, + "vendor_user_uuid": { + "type": "string", + "format": "uuid", + "description": "The owning User." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "The User's current `vendor_data`, resolved when you read the template (never copied)." + }, + "source_type": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ], + "description": "How the template was created: a session deletion (API or Console), the application's automatic data-retention window, or a Didit-operated backfill you instructed." + }, + "provenance_reference": { + "type": "string", + "format": "uuid", + "description": "Opaque reference tying the template to its audit trail. It is not the deleted session's id and cannot be resolved to it." + }, + "retention_policy": { + "type": "string", + "enum": [ + "delete_with_session", + "retain_until_user_deleted" + ], + "description": "The application policy at retention time." + }, + "retention_override": { + "type": "boolean", + "nullable": true, + "description": "The `retain_face_embeddings` value sent on the deletion, or `null` when the policy applied." + }, + "retained_at": { + "type": "string", + "format": "date-time" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the template expires and is purged automatically. Always set." + }, + "retained_by": { + "type": "string", + "nullable": true, + "description": "Principal that gave the instruction: the application id for API keys, the Console member id for Console actions, or a `service:` identifier for automatic retention. Never an email address." + }, + "retained_by_type": { + "type": "string", + "enum": [ + "API_KEY", + "CONSOLE_USER", + "SYSTEM" + ] + }, + "instruction_class": { + "type": "string", + "enum": [ + "operational_session_delete", + "privacy_erasure" + ] + }, + "instruction_source": { + "type": "string", + "enum": [ + "api", + "console", + "automatic_retention", + "backfill", + "privacy_erasure", + "explicit_template_purge", + "vendor_user_delete", + "expiry", + "reconciliation", + "migration", + "system" + ] + }, + "instruction_id": { + "type": "string", + "description": "The `instruction_id` sent on the deletion, or a Didit-generated id." + }, + "held_out_of_use": { + "type": "boolean", + "description": "`true` only while a legal hold applies. A held template is excluded from all matching." + }, + "status": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed", + "purged" + ], + "description": "Only `active` templates that are not held, not past `expires_at`, and whose User still exists take part in matching." + }, + "purged_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "examples": { + "Active template": { + "value": { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "vendor_user_uuid": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f", + "vendor_data": "user-42", + "source_type": "session_delete", + "provenance_reference": "6b1e8d2f-4a3c-4f9e-8c7d-1a2b3c4d5e6f", + "retention_policy": "retain_until_user_deleted", + "retention_override": null, + "retained_at": "2026-08-28T10:15:42.318402Z", + "expires_at": "2027-08-28T10:15:42.318402Z", + "retained_by": "3c9e2b1f-7d4a-4e8b-9c1d-5f6a7b8c9d0e", + "retained_by_type": "API_KEY", + "instruction_class": "operational_session_delete", + "instruction_source": "api", + "instruction_id": "post-approval-cleanup-2026-08", + "held_out_of_use": false, + "status": "active", + "purged_at": null + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown template, a template of another application, or (on `GET`) a purged template.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Biometric template not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/biometric-templates/9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.biometric_templates.retrieve(\n \"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.biometricTemplates.retrieve(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.BiometricTemplates.Retrieve(ctx, \"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.biometricTemplates().retrieve(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "biometricTemplates->retrieve('9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.biometric_templates.retrieve(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.BiometricTemplates.RetrieveAsync(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "summary": "Purge a retained biometric template", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nPurges one template: it is removed from every kind of matching and the stored template is deleted. Irreversible and recorded on the audit trail with the acting principal.\n\n**Retry semantics:** Didit deletes the stored template first and only then marks the template purged. If a biometric store is unavailable the call returns `503`, the template moves to `status: purge_failed` and is already excluded from matching; repeat the same call to finish. Repeating a purge of an already purged template returns `already_purged`.\n\nTemplates are also purged by `POST /v3/users/delete/` for the owning User, by any deletion of that User's sessions with `deletion_instruction: privacy_erasure`, and automatically at `expires_at`.", + "operationId": "purge_biometric_template", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "template_uuid", + "required": true, + "description": "Template id, as returned in `biometric_template_uuid` by the session deletion endpoints, `biometric_template_id` on a Face Search match, or the list endpoint.", + "schema": { + "type": "string", + "format": "uuid", + "example": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b" + } + } + ], + "responses": { + "200": { + "description": "Purge outcome.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "outcome": { + "type": "string", + "enum": [ + "purged", + "already_purged" + ] + } + } + }, + "examples": { + "Purged": { + "value": { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "outcome": "purged" + } + }, + "Already purged": { + "value": { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "outcome": "already_purged" + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown template, a template of another application, or (on `GET`) a purged template.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Biometric template not found." + } + } + } + } + } + }, + "503": { + "description": "A biometric store was unavailable while retaining or purging a biometric template. Nothing is deleted; repeat the same request. Retries are idempotent and converge on a single template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Retryable": { + "value": { + "detail": "The biometric-template operation could not finish because an external store is unavailable. The operation is retryable." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE https://verification.didit.me/v3/biometric-templates/9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.biometric_templates.delete(\n \"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.biometricTemplates.delete(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.BiometricTemplates.Delete(ctx, \"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.biometricTemplates().delete(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "biometricTemplates->delete('9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.biometric_templates.delete(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.BiometricTemplates.DeleteAsync(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/biometric-templates/delete/": { + "post": { + "summary": "Bulk purge retained biometric templates", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nPurges many templates in one call, by explicit id list or with `delete_all: true` optionally narrowed by the same filters as the list endpoint. Templates are purged one by one and each result reports `purged` or `already_purged`. If a biometric store is unavailable the call returns `503` at the first failing template: templates purged earlier in the request stay purged, the failing one moves to `purge_failed` (already excluded from matching), and repeating the request finishes the job.\n\n**`delete_all: true` without filters purges every biometric template in the application and cannot be undone.** Run the count endpoint with the same filters first.", + "operationId": "bulk_purge_biometric_templates", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "batch_delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "template_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1, + "description": "Templates to purge. Required unless `delete_all` is `true`. Already purged ids are reported as `already_purged`." + }, + "delete_all": { + "type": "boolean", + "default": false, + "description": "When `true`, purges every template matching the filters below (all of the application's templates when no filter is given). `template_uuids` is ignored. Irreversible." + }, + "vendor_data": { + "type": "string", + "description": "Only with `delete_all`: case-insensitive substring match on the owning User's `vendor_data`." + }, + "vendor_user_uuid": { + "type": "string", + "format": "uuid", + "description": "Only with `delete_all`: exact match on the owning User." + }, + "status": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed" + ], + "description": "Only with `delete_all`." + }, + "source_type": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ], + "description": "Only with `delete_all`." + }, + "retained_from": { + "type": "string", + "format": "date-time", + "description": "Only with `delete_all`." + }, + "retained_to": { + "type": "string", + "format": "date-time", + "description": "Only with `delete_all`. Must not be earlier than `retained_from`." + } + } + }, + "examples": { + "By id": { + "value": { + "template_uuids": [ + "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" + ] + } + }, + "Every template of one User": { + "value": { + "delete_all": true, + "vendor_user_uuid": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f" + } + }, + "Every template in the application": { + "value": { + "delete_all": true + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Per-template purge outcomes.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "outcome": { + "type": "string", + "enum": [ + "purged", + "already_purged" + ] + } + } + } + } + } + }, + "examples": { + "Mixed": { + "value": { + "results": [ + { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "outcome": "purged" + }, + { + "uuid": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", + "outcome": "already_purged" + } + ] + } + } + } + } + } + }, + "400": { + "description": "Validation error — nothing is purged.", + "content": { + "application/json": { + "examples": { + "Missing selector": { + "value": { + "template_uuids": [ + "This field is required unless delete_all is true." + ] + } + }, + "Bad range": { + "value": { + "retained_to": [ + "Must be on or after retained_from." + ] + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "503": { + "description": "A biometric store was unavailable while retaining or purging a biometric template. Nothing is deleted; repeat the same request. Retries are idempotent and converge on a single template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Retryable": { + "value": { + "detail": "The biometric-template operation could not finish because an external store is unavailable. The operation is retryable." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/biometric-templates/delete/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"delete_all\": true, \"vendor_user_uuid\": \"2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.biometric_templates.batch_delete(\n template_uuids=[\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\", \"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.biometricTemplates.batchDelete({\n template_uuids: [\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\", \"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.BiometricTemplates.BatchDelete(ctx, didit.BiometricTemplatesBatchDeleteParams{\n\tTemplateUUIDs: didit.Some([]string{\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\", \"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.BiometricTemplatesBatchDeleteParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.biometricTemplates().batchDelete(BiometricTemplatesBatchDeleteParams.builder()\n .templateUuids(List.of(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\", \"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "biometricTemplates->batchDelete([\n 'template_uuids' => ['9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b', '1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.biometric_templates.batch_delete(template_uuids: [\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\", \"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.BiometricTemplates.BatchDeleteAsync(new BiometricTemplatesBatchDeleteParams\n{\n TemplateUuids = new string[] { \"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\", \"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/kyb/shareholders/": { + "post": { + "summary": "KYB registry company with shareholders (standalone)", + "operationId": "post_v3_kyb_shareholders", + "x-didit-sdk": { + "resource": "kyb", + "method": "list_shareholders", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Retrieve the Lite profile **plus the direct shareholders** (natural persons and corporate entities, with their ownership bands) of one candidate returned by `POST /v3/kyb/search/`.\n\n**Pairing with search.** Pass the candidate's `kyb_response_id` exactly as returned by a search of the same application. Search at the tier you intend to buy (`tier` on `POST /v3/kyb/search/`), because the search runs on the registry chain that serves that tier and an empty Lite search is not proof that the registry does not hold the company. When the country's ownership data lives at another registry, Didit re-finds the company there by registration number, at no extra cost.\n\n**Billing.** Billed at the country's Shareholders price - see [KYB registry pricing](/getting-started/kyb-registry-pricing). When the registry holds no shareholder record for the company (or the country offers no ownership data at all) the Lite profile is delivered and billed instead: read `registry_tier_delivered` and `registry_ownership_status`. The balance is checked against the Shareholders price before any registry call (`403` when short).\n\n**Availability.** v3 only. Every call creates a business session (its id is the `request_id`).\n\n**Pending reports.** When a report is still being prepared, or the ownership section has not been retrieved from the registry yet, the endpoint returns 202 Accepted, kyb_registry.fetch_status=pending, kyb_registry.registry_ownership_status=pending, and registry_tier_delivered=null. Keep request_id and retrieve GET /v3/session/{request_id}/decision/ until registry_checks[0].company.fetch_status is resolved. Didit completes the report automatically and charges the delivered tier once it is ready. Do not repeat the purchase while it is pending; every new POST creates a separate request.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBRegistryCandidateRequest" + }, + "example": { + "kyb_response_id": "69aeeb95febb0f1704042259", + "vendor_data": "company-1234" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/kyb/shareholders/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"kyb_response_id\": \"69aeeb95febb0f1704042259\",\n \"vendor_data\": \"company-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.kyb.list_shareholders(\n kyb_response_id=\"69aeeb95febb0f1704042259\",\n vendor_data=\"company-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.kyb.listShareholders({\n kyb_response_id: \"69aeeb95febb0f1704042259\",\n vendor_data: \"company-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.KYB.ListShareholders(ctx, didit.KYBRegistryCandidateRequest{\n\tKYBResponseID: \"69aeeb95febb0f1704042259\",\n\tVendorData: didit.String(\"company-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.KYBRegistryCandidateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.kyb().listShareholders(KYBRegistryCandidateRequest.builder()\n .kybResponseId(\"69aeeb95febb0f1704042259\")\n .vendorData(\"company-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "kyb->listShareholders([\n 'kyb_response_id' => '69aeeb95febb0f1704042259',\n 'vendor_data' => 'company-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.kyb.list_shareholders(\n kyb_response_id: \"69aeeb95febb0f1704042259\",\n vendor_data: \"company-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Kyb.ListShareholdersAsync(new KYBRegistryCandidateRequest\n{\n KybResponseId = \"69aeeb95febb0f1704042259\",\n VendorData = \"company-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Company report returned and billed at the delivered tier. Ownership reports still being prepared can return 202; keep request_id to retrieve their saved decision.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the business session created for this retrieval. The stored record (visible in the console) keeps updating if the registry resolves after the response." + }, + "registry_tier_requested": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "The tier this endpoint sells: `basic` (Lite) for select, `shareholders`, `ubo`." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "The tier actually delivered and billed. Lower than requested when the country's registries hold no ownership data, or the registry has no ownership record for this company.", + "nullable": true + }, + "kyb_registry": { + "type": "object", + "description": "Full registry company profile.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit's id for this company record." + }, + "node_id": { + "type": "string", + "example": "feature_kyb_registry" + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review" + ], + "description": "Didit's assessment of the registry check (e.g. `In Review` when the company is not active in the registry or ownership data could not be derived)." + }, + "registry_status": { + "type": "string", + "nullable": true, + "enum": [ + "active", + "dissolved", + "deregistered", + "see full details", + "authorised", + "appointed representative", + "unauthorised", + "inactive", + "no longer authorised", + "closed", + "struck off", + null + ], + "description": "Normalized company status as reported by the registry." + }, + "data_resolved": { + "type": "boolean", + "description": "`true` once the registry returned the full profile. `false` when the registry was still processing after polling — the stored record completes automatically once the registry resolves." + }, + "company_name": { + "type": "string", + "example": "Tesco PLC" + }, + "registration_number": { + "type": "string", + "nullable": true, + "example": "00445790" + }, + "country_code": { + "type": "string", + "description": "ISO 3166-1 alpha-2.", + "example": "GB" + }, + "region": { + "type": "string", + "nullable": true, + "description": "Subdivision code when the registry is regional (e.g. `CA` for `US-CA`); empty otherwise." + }, + "company_type": { + "type": "string", + "nullable": true, + "example": "Public Limited Company" + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true, + "example": "1947-11-27" + }, + "registered_address": { + "type": "string", + "nullable": true, + "example": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA" + }, + "tax_number": { + "type": "string", + "nullable": true, + "example": "GB412 8925 93" + }, + "risk_level": { + "type": "string", + "nullable": true, + "description": "Registry-reported risk level, when available." + }, + "verification_status": { + "type": "string", + "enum": [ + "verified", + "failed", + "unknown" + ], + "nullable": true, + "description": "Cross-jurisdiction verification status: `verified` when the registry status establishes standing (`active`), `failed` when it says the company is no longer standing (for example `dissolved`), otherwise `unknown`. `null` for a company that did not come from a registry and while a selection is still `pending`." + }, + "is_from_registry": { + "type": "boolean", + "description": "Always `true` for this endpoint — the profile was retrieved from an official registry." + }, + "fetch_status": { + "type": "string", + "enum": [ + "pending", + "resolved" + ], + "description": "Registry fetch lifecycle. Mirrored by `data_resolved`." + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true, + "description": "Activity description or classification code, when reported." + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "nullable": true, + "description": "Decimal amount as a string, when reported." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 code, when reported." + }, + "website": { + "type": "string", + "nullable": true, + "example": "https://www.tesco.com/" + }, + "email": { + "type": "string", + "nullable": true, + "example": "customer.service@tesco.co.uk" + }, + "phone": { + "type": "string", + "nullable": true, + "example": "03301231688" + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true, + "description": "LEI, when reported." + }, + "location_of_registration": { + "type": "string", + "nullable": true, + "description": "Registry/jurisdiction the company is registered with, when reported." + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards. At select time this is typically not yet set — VAT and tax ID validation runs when the end user submits the registry step in the hosted flow." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true, + "description": "Registry-reported share/capital details (currency, total shares, share classes), when available." + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "example": "MELISSA BETHELL" + }, + "designation": { + "type": "string", + "nullable": true, + "description": "Raw registry designation (e.g. `Director`, `Secretary`)." + }, + "role": { + "type": "string", + "description": "Normalized role derived from the designation. Full 17-value model set; registry parsing on this endpoint typically emits director, non_executive_director, secretary, chairman, or company_officer.", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "company_officer", + "settlor", + "protector", + "investor", + "other" + ] + }, + "nationality": { + "type": "string", + "nullable": true + }, + "is_active": { + "type": "boolean", + "description": "`false` when the registry marks the appointment as resigned." + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending", + null + ], + "description": "Status of an associated KYC session, when one was started for this officer. `null` otherwise." + }, + "kyc_session_url": { + "type": "string", + "nullable": true, + "description": "Verification URL of the associated KYC session, when one exists." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + }, + "description": "Directors, secretaries, and other officers extracted from the registry." + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the owner is a natural person or a corporate entity." + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Normalized roles (e.g. `ubo`, `shareholder`)." + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership band reported by the registry (e.g. `25%`)." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership band reported by the registry." + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending", + null + ] + }, + "kyc_session_url": { + "type": "string", + "nullable": true + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true, + "description": "Parsed ownership percentage (max bound, falling back to min bound)." + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + }, + "shareholding": { + "$ref": "#/components/schemas/KYBRegistryShareholding" + } + } + }, + "description": "Beneficial owners / persons with significant control extracted from the registry. Can be empty when the registry does not expose ownership." + }, + "addresses": { + "type": "array", + "nullable": true, + "description": "Every address the registry states, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryAddress" + } + }, + "industries": { + "type": "array", + "nullable": true, + "description": "The activities the registry classifies the company under, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryIndustry" + } + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "$ref": "#/components/schemas/KYBRegistryData" + }, + "user_provided_data": { + "type": "object", + "nullable": true, + "description": "Always `null` on this endpoint (used by the hosted KYB flow when end users edit registry data)." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set when a console reviewer edits the record later." + }, + "is_editable": { + "type": "boolean", + "description": "Whether the registry-sourced data can still be edited in the hosted flow/console." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "Same as the top-level `registry_tier_delivered`.", + "nullable": true + }, + "registry_ownership_status": { + "type": "string", + "nullable": true, + "enum": [ + "not_requested", + "delivered", + "not_held", + "unavailable", + "pending", + null + ], + "description": "`delivered` when the ownership data came back, `not_held` when the ownership registry answered and does not list this company (see `registry_data.ownership_error`), `unavailable` when an ownership tier was requested but the registry had none for this company or could not be asked (see `registry_data.ownership_error`), `pending` when the ownership section has not been retrieved from the registry yet and Didit is still completing it, `not_requested` on the Lite tier." + }, + "ownership": { + "$ref": "#/components/schemas/KYBRegistryOwnership" + }, + "registry_tier": { + "type": "string", + "nullable": true, + "description": "The registry tier the workflow ordered: `basic`, `shareholders` or `ubo`. Also in `ownership.tier_requested`." + }, + "registry_monitoring": { + "type": "object", + "nullable": true, + "description": "The latest registry monitor for the company, or `null` when none exists.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string" + }, + "unavailable_reason": { + "type": "string", + "nullable": true + }, + "enrolled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "next_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "last_event_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "events_count": { + "type": "integer" + }, + "watched_fields": { + "description": "The company fields the monitor watches." + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Resolved company": { + "summary": "Full registry profile (officer list truncated for brevity)", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "f1e2d3c4-b5a6-4789-9c0d-1e2f3a4b5c6d", + "node_id": "feature_kyb_registry", + "status": "In Review", + "registry_status": "active", + "data_resolved": true, + "company_name": "Tesco PLC", + "registration_number": "00445790", + "country_code": "GB", + "region": "", + "company_type": "Public Limited Company", + "incorporation_date": "1947-11-27", + "registered_address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "tax_number": "GB412 8925 93", + "risk_level": "low risk", + "verification_status": "verified", + "is_from_registry": true, + "fetch_status": "resolved", + "alternative_names": "TESCO STORES (HOLDINGS) PUBLIC LIMITED COMPANY", + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": "https://www.tesco.com/", + "email": "customer.service@tesco.co.uk", + "phone": "03301231688", + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": { + "currency": "Pound (£)", + "total_number_of_shares": "8174378721", + "meta_detail": { + "Class_Of_Share": "Ordinary" + } + }, + "officers": [ + { + "uuid": "a1b2c3d4-e5f6-4789-9a0b-1c2d3e4f5a6b", + "name": "CHRISTOPHER JON TAYLOR", + "designation": "Secretary", + "role": "secretary", + "nationality": null, + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + }, + { + "uuid": "b2c3d4e5-f6a7-4890-8b1c-2d3e4f5a6b7c", + "name": "MELISSA BETHELL", + "designation": "Director", + "role": "director", + "nationality": "British", + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + } + ], + "beneficial_owners": [], + "addresses": [ + { + "address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "type": "legal_entity_registry_address", + "description": "registered office address" + } + ], + "industries": null, + "accounts": { + "last_account": "26 February 2025", + "next_account": "26 February 2026", + "due_by_date": "26 August 2026" + }, + "registry_data": {}, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems use DRF's standard envelope.", + "content": { + "application/json": { + "examples": { + "Missing kyb_response_id": { + "summary": "`kyb_response_id` is required", + "value": { + "kyb_response_id": [ + "This field is required." + ] + } + }, + "save_api_request=false rejected": { + "summary": "Selects are always persisted", + "value": { + "save_api_request": [ + "KYB registry select must be saved so billing and retrieval can be tracked." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "202": { + "description": "Report accepted and still processing. When a report is still being prepared, the endpoint returns 202 Accepted, kyb_registry.fetch_status=pending, and registry_tier_delivered=null. Keep request_id and retrieve GET /v3/session/{request_id}/decision/ until registry_checks[0].company.fetch_status is resolved. Didit completes the report automatically and charges the delivered tier once it is ready. Do not repeat the purchase while it is pending; every new POST creates a separate request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/paths/~1v3~1kyb~1shareholders~1/post/responses/200/content/application~1json/schema" + }, + "examples": { + "Pending (registry still resolving)": { + "summary": "Ownership report is still processing; retrieve the saved decision", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "7c0a4f7e-2b1d-4f6a-9c3e-8d5b2a1f0e9d", + "node_id": "feature_kyb_registry", + "status": "Not Finished", + "registry_status": null, + "data_resolved": false, + "company_name": "Example Holdings Limited", + "registration_number": "12345678", + "country_code": "HK", + "region": null, + "company_type": null, + "incorporation_date": null, + "registered_address": null, + "tax_number": null, + "risk_level": null, + "verification_status": null, + "is_from_registry": true, + "fetch_status": "pending", + "alternative_names": null, + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": null, + "email": null, + "phone": null, + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": {}, + "officers": [], + "beneficial_owners": [], + "addresses": null, + "industries": null, + "accounts": null, + "registry_data": {}, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true, + "registry_tier_delivered": null, + "registry_ownership_status": "pending" + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00", + "registry_tier_delivered": null, + "registry_tier_requested": "shareholders" + } + } + } + } + } + } + } + } + }, + "/v3/kyb/ubo/": { + "post": { + "summary": "KYB registry company with ultimate beneficial owners (standalone)", + "operationId": "post_v3_kyb_ubo", + "x-didit-sdk": { + "resource": "kyb", + "method": "list_ubos", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Retrieve the Lite profile, the direct shareholders **and the ultimate beneficial owners** resolved through the ownership chain (`registry_data.ubo_resolution` carries the corporate parent chain) of one candidate returned by `POST /v3/kyb/search/`.\n\n**Pairing with search.** Pass the candidate's `kyb_response_id` exactly as returned by a search of the same application. Search at the tier you intend to buy (`tier` on `POST /v3/kyb/search/`), because the search runs on the registry chain that serves that tier and an empty Lite search is not proof that the registry does not hold the company. When the country's ownership data lives at another registry, Didit re-finds the company there by registration number, at no extra cost.\n\n**Billing.** Billed at the country's UBO price - see [KYB registry pricing](/getting-started/kyb-registry-pricing). The tier delivered is the highest the country and the registry can serve: Shareholders when the resolved report names no natural person as a beneficial owner, Lite when there is no ownership record at all; `registry_tier_delivered` names what was billed. The balance is checked against the UBO price before any registry call (`403` when short).\n\n**Availability.** v3 only. Every call creates a business session (its id is the `request_id`).\n\n**Pending reports.** When a report is still being prepared, or the ownership section has not been retrieved from the registry yet, the endpoint returns 202 Accepted, kyb_registry.fetch_status=pending, kyb_registry.registry_ownership_status=pending, and registry_tier_delivered=null. Keep request_id and retrieve GET /v3/session/{request_id}/decision/ until registry_checks[0].company.fetch_status is resolved. Didit completes the report automatically and charges the delivered tier once it is ready. Do not repeat the purchase while it is pending; every new POST creates a separate request.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBRegistryCandidateRequest" + }, + "example": { + "kyb_response_id": "69aeeb95febb0f1704042259", + "vendor_data": "company-1234" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/kyb/ubo/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"kyb_response_id\": \"69aeeb95febb0f1704042259\",\n \"vendor_data\": \"company-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.kyb.list_ubos(\n kyb_response_id=\"69aeeb95febb0f1704042259\",\n vendor_data=\"company-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.kyb.listUbos({\n kyb_response_id: \"69aeeb95febb0f1704042259\",\n vendor_data: \"company-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.KYB.ListUBOs(ctx, didit.KYBRegistryCandidateRequest{\n\tKYBResponseID: \"69aeeb95febb0f1704042259\",\n\tVendorData: didit.String(\"company-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.KYBRegistryCandidateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.kyb().listUbos(KYBRegistryCandidateRequest.builder()\n .kybResponseId(\"69aeeb95febb0f1704042259\")\n .vendorData(\"company-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "kyb->listUbos([\n 'kyb_response_id' => '69aeeb95febb0f1704042259',\n 'vendor_data' => 'company-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.kyb.list_ubos(\n kyb_response_id: \"69aeeb95febb0f1704042259\",\n vendor_data: \"company-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Kyb.ListUbosAsync(new KYBRegistryCandidateRequest\n{\n KybResponseId = \"69aeeb95febb0f1704042259\",\n VendorData = \"company-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Company report returned and billed at the delivered tier. Ownership reports still being prepared can return 202; keep request_id to retrieve their saved decision.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the business session created for this retrieval. The stored record (visible in the console) keeps updating if the registry resolves after the response." + }, + "registry_tier_requested": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "The tier this endpoint sells: `basic` (Lite) for select, `shareholders`, `ubo`." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "The tier actually delivered and billed. Lower than requested when the country's registries hold no ownership data, when the registry has no ownership record for this company, or when the resolved report of a `ubo` request names no natural person as a beneficial owner.", + "nullable": true + }, + "kyb_registry": { + "type": "object", + "description": "Full registry company profile.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit's id for this company record." + }, + "node_id": { + "type": "string", + "example": "feature_kyb_registry" + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review" + ], + "description": "Didit's assessment of the registry check (e.g. `In Review` when the company is not active in the registry or ownership data could not be derived)." + }, + "registry_status": { + "type": "string", + "nullable": true, + "enum": [ + "active", + "dissolved", + "deregistered", + "see full details", + "authorised", + "appointed representative", + "unauthorised", + "inactive", + "no longer authorised", + "closed", + "struck off", + null + ], + "description": "Normalized company status as reported by the registry." + }, + "data_resolved": { + "type": "boolean", + "description": "`true` once the registry returned the full profile. `false` when the registry was still processing after polling — the stored record completes automatically once the registry resolves." + }, + "company_name": { + "type": "string", + "example": "Tesco PLC" + }, + "registration_number": { + "type": "string", + "nullable": true, + "example": "00445790" + }, + "country_code": { + "type": "string", + "description": "ISO 3166-1 alpha-2.", + "example": "GB" + }, + "region": { + "type": "string", + "nullable": true, + "description": "Subdivision code when the registry is regional (e.g. `CA` for `US-CA`); empty otherwise." + }, + "company_type": { + "type": "string", + "nullable": true, + "example": "Public Limited Company" + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true, + "example": "1947-11-27" + }, + "registered_address": { + "type": "string", + "nullable": true, + "example": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA" + }, + "tax_number": { + "type": "string", + "nullable": true, + "example": "GB412 8925 93" + }, + "risk_level": { + "type": "string", + "nullable": true, + "description": "Registry-reported risk level, when available." + }, + "verification_status": { + "type": "string", + "enum": [ + "verified", + "failed", + "unknown" + ], + "nullable": true, + "description": "Cross-jurisdiction verification status: `verified` when the registry status establishes standing (`active`), `failed` when it says the company is no longer standing (for example `dissolved`), otherwise `unknown`. `null` for a company that did not come from a registry and while a selection is still `pending`." + }, + "is_from_registry": { + "type": "boolean", + "description": "Always `true` for this endpoint — the profile was retrieved from an official registry." + }, + "fetch_status": { + "type": "string", + "enum": [ + "pending", + "resolved" + ], + "description": "Registry fetch lifecycle. Mirrored by `data_resolved`." + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true, + "description": "Activity description or classification code, when reported." + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "nullable": true, + "description": "Decimal amount as a string, when reported." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 code, when reported." + }, + "website": { + "type": "string", + "nullable": true, + "example": "https://www.tesco.com/" + }, + "email": { + "type": "string", + "nullable": true, + "example": "customer.service@tesco.co.uk" + }, + "phone": { + "type": "string", + "nullable": true, + "example": "03301231688" + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true, + "description": "LEI, when reported." + }, + "location_of_registration": { + "type": "string", + "nullable": true, + "description": "Registry/jurisdiction the company is registered with, when reported." + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards. At select time this is typically not yet set — VAT and tax ID validation runs when the end user submits the registry step in the hosted flow." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true, + "description": "Registry-reported share/capital details (currency, total shares, share classes), when available." + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "example": "MELISSA BETHELL" + }, + "designation": { + "type": "string", + "nullable": true, + "description": "Raw registry designation (e.g. `Director`, `Secretary`)." + }, + "role": { + "type": "string", + "description": "Normalized role derived from the designation. Full 17-value model set; registry parsing on this endpoint typically emits director, non_executive_director, secretary, chairman, or company_officer.", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "company_officer", + "settlor", + "protector", + "investor", + "other" + ] + }, + "nationality": { + "type": "string", + "nullable": true + }, + "is_active": { + "type": "boolean", + "description": "`false` when the registry marks the appointment as resigned." + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending", + null + ], + "description": "Status of an associated KYC session, when one was started for this officer. `null` otherwise." + }, + "kyc_session_url": { + "type": "string", + "nullable": true, + "description": "Verification URL of the associated KYC session, when one exists." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + }, + "description": "Directors, secretaries, and other officers extracted from the registry." + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the owner is a natural person or a corporate entity." + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Normalized roles (e.g. `ubo`, `shareholder`)." + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership band reported by the registry (e.g. `25%`)." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership band reported by the registry." + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending", + null + ] + }, + "kyc_session_url": { + "type": "string", + "nullable": true + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true, + "description": "Parsed ownership percentage (max bound, falling back to min bound)." + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + }, + "shareholding": { + "$ref": "#/components/schemas/KYBRegistryShareholding" + } + } + }, + "description": "Beneficial owners / persons with significant control extracted from the registry. Can be empty when the registry does not expose ownership." + }, + "addresses": { + "type": "array", + "nullable": true, + "description": "Every address the registry states, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryAddress" + } + }, + "industries": { + "type": "array", + "nullable": true, + "description": "The activities the registry classifies the company under, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryIndustry" + } + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "$ref": "#/components/schemas/KYBRegistryData" + }, + "user_provided_data": { + "type": "object", + "nullable": true, + "description": "Always `null` on this endpoint (used by the hosted KYB flow when end users edit registry data)." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set when a console reviewer edits the record later." + }, + "is_editable": { + "type": "boolean", + "description": "Whether the registry-sourced data can still be edited in the hosted flow/console." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "Same as the top-level `registry_tier_delivered`.", + "nullable": true + }, + "registry_ownership_status": { + "type": "string", + "nullable": true, + "enum": [ + "not_requested", + "delivered", + "not_held", + "unavailable", + "pending", + null + ], + "description": "`delivered` when the ownership data came back, `not_held` when the ownership registry answered and does not list this company (see `registry_data.ownership_error`), `unavailable` when an ownership tier was requested but the registry had none for this company or could not be asked (see `registry_data.ownership_error`), `pending` when the ownership section has not been retrieved from the registry yet and Didit is still completing it, `not_requested` on the Lite tier." + }, + "ownership": { + "$ref": "#/components/schemas/KYBRegistryOwnership" + }, + "registry_tier": { + "type": "string", + "nullable": true, + "description": "The registry tier the workflow ordered: `basic`, `shareholders` or `ubo`. Also in `ownership.tier_requested`." + }, + "registry_monitoring": { + "type": "object", + "nullable": true, + "description": "The latest registry monitor for the company, or `null` when none exists.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string" + }, + "unavailable_reason": { + "type": "string", + "nullable": true + }, + "enrolled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "next_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "last_event_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "events_count": { + "type": "integer" + }, + "watched_fields": { + "description": "The company fields the monitor watches." + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Resolved company": { + "summary": "Full registry profile (officer list truncated for brevity)", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "f1e2d3c4-b5a6-4789-9c0d-1e2f3a4b5c6d", + "node_id": "feature_kyb_registry", + "status": "In Review", + "registry_status": "active", + "data_resolved": true, + "company_name": "Tesco PLC", + "registration_number": "00445790", + "country_code": "GB", + "region": "", + "company_type": "Public Limited Company", + "incorporation_date": "1947-11-27", + "registered_address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "tax_number": "GB412 8925 93", + "risk_level": "low risk", + "verification_status": "verified", + "is_from_registry": true, + "fetch_status": "resolved", + "alternative_names": "TESCO STORES (HOLDINGS) PUBLIC LIMITED COMPANY", + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": "https://www.tesco.com/", + "email": "customer.service@tesco.co.uk", + "phone": "03301231688", + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": { + "currency": "Pound (£)", + "total_number_of_shares": "8174378721", + "meta_detail": { + "Class_Of_Share": "Ordinary" + } + }, + "officers": [ + { + "uuid": "a1b2c3d4-e5f6-4789-9a0b-1c2d3e4f5a6b", + "name": "CHRISTOPHER JON TAYLOR", + "designation": "Secretary", + "role": "secretary", + "nationality": null, + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + }, + { + "uuid": "b2c3d4e5-f6a7-4890-8b1c-2d3e4f5a6b7c", + "name": "MELISSA BETHELL", + "designation": "Director", + "role": "director", + "nationality": "British", + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + } + ], + "beneficial_owners": [], + "addresses": [ + { + "address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "type": "legal_entity_registry_address", + "description": "registered office address" + } + ], + "industries": null, + "accounts": { + "last_account": "26 February 2025", + "next_account": "26 February 2026", + "due_by_date": "26 August 2026" + }, + "registry_data": { + "ubo_resolution": { + "parent_chain": [], + "subsidiaries": [], + "unresolved": [] + } + }, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems use DRF's standard envelope.", + "content": { + "application/json": { + "examples": { + "Missing kyb_response_id": { + "summary": "`kyb_response_id` is required", + "value": { + "kyb_response_id": [ + "This field is required." + ] + } + }, + "save_api_request=false rejected": { + "summary": "Selects are always persisted", + "value": { + "save_api_request": [ + "KYB registry select must be saved so billing and retrieval can be tracked." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "202": { + "description": "Report accepted and still processing. When a report is still being prepared, the endpoint returns 202 Accepted, kyb_registry.fetch_status=pending, and registry_tier_delivered=null. Keep request_id and retrieve GET /v3/session/{request_id}/decision/ until registry_checks[0].company.fetch_status is resolved. Didit completes the report automatically and charges the delivered tier once it is ready. Do not repeat the purchase while it is pending; every new POST creates a separate request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/paths/~1v3~1kyb~1ubo~1/post/responses/200/content/application~1json/schema" + }, + "examples": { + "Pending (registry still resolving)": { + "summary": "Ownership report is still processing; retrieve the saved decision", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "7c0a4f7e-2b1d-4f6a-9c3e-8d5b2a1f0e9d", + "node_id": "feature_kyb_registry", + "status": "Not Finished", + "registry_status": null, + "data_resolved": false, + "company_name": "Example Holdings Limited", + "registration_number": "12345678", + "country_code": "HK", + "region": null, + "company_type": null, + "incorporation_date": null, + "registered_address": null, + "tax_number": null, + "risk_level": null, + "verification_status": null, + "is_from_registry": true, + "fetch_status": "pending", + "alternative_names": null, + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": null, + "email": null, + "phone": null, + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": {}, + "officers": [], + "beneficial_owners": [], + "addresses": null, + "industries": null, + "accounts": null, + "registry_data": {}, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true, + "registry_tier_delivered": null, + "registry_ownership_status": "pending" + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00", + "registry_tier_delivered": null, + "registry_tier_requested": "ubo" + } + } + } + } + } + } + } + } + }, + "/v3/id-verification/wallets/": { + "get": { + "operationId": "list_wallets", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "list", + "auth": "api_key", + "server": true + }, + "description": "Every Digital ID wallet with its start endpoint, countries, required fields and whether this application can start it in this environment now. Listed does not mean live.", + "summary": "List Digital ID wallets", + "tags": [ + "Digital ID wallets" + ], + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletCatalogResponse" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "Missing credentials (x-api-key or Authorization: Bearer)." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request GET \\\n --url https://verification.didit.me/v3/id-verification/wallets/ \\\n --header 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.list()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.list();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.List(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().list();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->list();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.list\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.ListAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/id-verification/wallets/verifications/{request_id}/": { + "get": { + "operationId": "get_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "retrieve_verification", + "auth": "api_key", + "server": true + }, + "description": "Status and result of a wallet verification: the normalized identity and the wallet's own attributes once verified, the error code once declined, the next action while pending. A verification past expires_at is decided timeout.", + "summary": "Get a wallet verification", + "parameters": [ + { + "in": "path", + "name": "request_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The request_id the start endpoint returned.", + "required": true + } + ], + "tags": [ + "Digital ID wallets" + ], + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "ftn", + "name": "Finnish Trust Network", + "issuing_authority": "Finnish banks and mobile operators (FTN)" + }, + "country": "FIN", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "Missing credentials (x-api-key or Authorization: Bearer)." + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error not_found: no wallet verification of this application has this request_id." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request GET \\\n --url https://verification.didit.me/v3/id-verification/wallets/verifications/REQUEST_ID/ \\\n --header 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.retrieve_verification(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.retrieveVerification(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.RetrieveVerification(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().retrieveVerification(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->retrieveVerification('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.retrieve_verification(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.RetrieveVerificationAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/id-verification/wallets/verifications/{request_id}/poll/": { + "post": { + "operationId": "poll_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "poll_verification", + "auth": "api_key", + "server": true + }, + "description": "Smart-ID and Mobile-ID: ask the provider whether the user confirmed, refused or let the prompt lapse, and verify the signed identity when they confirmed. Redirect wallets complete through the user's browser, so for them this only reads the verification back. Answers the verification as it is afterwards.", + "summary": "Poll a wallet verification", + "parameters": [ + { + "in": "path", + "name": "request_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The request_id the start endpoint returned.", + "required": true + } + ], + "tags": [ + "Digital ID wallets" + ], + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "smart_id", + "name": "Smart-ID", + "issuing_authority": "SK ID Solutions" + }, + "country": "EST", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "in_app", + "simulated": true, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "confirm_in_app", + "verification_code": "1234", + "poll_interval_seconds": 2 + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "Missing credentials (x-api-key or Authorization: Bearer)." + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error not_found: no wallet verification of this application has this request_id." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/verifications/REQUEST_ID/poll/ \\\n --header 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.poll_verification(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.pollVerification(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.PollVerification(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().pollVerification(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->pollVerification('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.poll_verification(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.PollVerificationAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/id-verification/wallets/verifications/{request_id}/cancel/": { + "post": { + "operationId": "cancel_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "cancel_verification", + "auth": "api_key", + "server": true + }, + "description": "Withdraw a pending verification: it is declined as cancelled and never billed. A decided one is returned unchanged. Smart-ID and Mobile-ID: a cancel that arrives while a poll is checking the sign-in waits a few seconds for that poll, then declines it, or returns it verified if the user had already confirmed. A verification the user is completing right now cannot be cancelled (409 verification_in_progress with Retry-After): retry after that many seconds to read its outcome.", + "summary": "Cancel a wallet verification", + "parameters": [ + { + "in": "path", + "name": "request_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The request_id the start endpoint returned.", + "required": true + } + ], + "tags": [ + "Digital ID wallets" + ], + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "ftn", + "name": "Finnish Trust Network", + "issuing_authority": "Finnish banks and mobile operators (FTN)" + }, + "country": "FIN", + "status": "Declined", + "outcome": "cancelled", + "error": "cancelled_by_customer", + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": null, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "Missing credentials (x-api-key or Authorization: Bearer)." + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error not_found: no wallet verification of this application has this request_id." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error verification_in_progress: the user is completing this sign-in right now. The Retry-After header says when to retry; the retry returns the decided verification." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/verifications/REQUEST_ID/cancel/ \\\n --header 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.cancel_verification(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.cancelVerification(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.CancelVerification(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().cancelVerification(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->cancelVerification('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.cancel_verification(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.CancelVerificationAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/id-verification/wallets/mitid/": { + "post": { + "operationId": "start_mitid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_mitid", + "auth": "api_key", + "server": true + }, + "description": "Start a MitID sign-in (Danish Agency for Digital Government; DNK) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start MitID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMitidWalletVerificationStartRequest" + }, + "example": { + "country": "DNK", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMitidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMitidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "mitid", + "name": "MitID", + "issuing_authority": "Danish Agency for Digital Government" + }, + "country": "DNK", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/mitid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"DNK\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_mitid(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"DNK\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startMitid({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"DNK\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartMitid(ctx, didit.IDWalletsStartMitidParams{\n\tBody: didit.DigitalIDWalletAPIMitidWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIMitidWalletVerificationStartCountryEnum(\"DNK\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiMitidWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiMitidWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startMitid(DigitalIdWalletApiMitidWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiMitidWalletVerificationStartCountryEnum.of(\"DNK\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startMitid([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'DNK',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_mitid(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"DNK\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartMitidAsync(new DigitalIdWalletApiMitidWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiMitidWalletVerificationStartCountryEnum(\"DNK\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "mitid" + } + }, + "/v3/id-verification/wallets/bankid-se/": { + "post": { + "operationId": "start_bankid_se_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_bankid_se", + "auth": "api_key", + "server": true + }, + "description": "Start a BankID (SWE) sign-in (Finansiell ID-Teknik / bank consortium; SWE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start BankID (SWE) verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidSeWalletVerificationStartRequest" + }, + "example": { + "country": "SWE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidSeWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidSeWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "bankid_se", + "name": "BankID", + "issuing_authority": "Finansiell ID-Teknik / bank consortium" + }, + "country": "SWE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/bankid-se/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"SWE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_bankid_se(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"SWE\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startBankidSe({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"SWE\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartBankidSe(ctx, didit.IDWalletsStartBankidSeParams{\n\tBody: didit.DigitalIDWalletAPIBankidSeWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryEe9enum(\"SWE\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiBankidSeWalletVerificationStartRequest;\nimport me.didit.types.DigitalIdWalletApiCountryEe9Enum;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startBankidSe(DigitalIdWalletApiBankidSeWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryEe9Enum.of(\"SWE\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startBankidSe([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'SWE',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_bankid_se(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"SWE\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartBankidSeAsync(new DigitalIdWalletApiBankidSeWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryEe9Enum(\"SWE\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "bankid_se" + } + }, + "/v3/id-verification/wallets/bankid-no/": { + "post": { + "operationId": "start_bankid_no_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_bankid_no", + "auth": "api_key", + "server": true + }, + "description": "Start a BankID (NOR) sign-in (BankID BankAxept AS; NOR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start BankID (NOR) verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidNoWalletVerificationStartRequest" + }, + "example": { + "country": "NOR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidNoWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidNoWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "bankid_no", + "name": "BankID", + "issuing_authority": "BankID BankAxept AS" + }, + "country": "NOR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/bankid-no/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"NOR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_bankid_no(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"NOR\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startBankidNo({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NOR\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartBankidNo(ctx, didit.IDWalletsStartBankidNoParams{\n\tBody: didit.DigitalIDWalletAPIBankidNoWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryAfcEnum(\"NOR\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiBankidNoWalletVerificationStartRequest;\nimport me.didit.types.DigitalIdWalletApiCountryAfcEnum;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startBankidNo(DigitalIdWalletApiBankidNoWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryAfcEnum.of(\"NOR\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startBankidNo([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'NOR',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_bankid_no(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NOR\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartBankidNoAsync(new DigitalIdWalletApiBankidNoWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryAfcEnum(\"NOR\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "bankid_no" + } + }, + "/v3/id-verification/wallets/vipps/": { + "post": { + "operationId": "start_vipps_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_vipps", + "auth": "api_key", + "server": true + }, + "description": "Start a Vipps sign-in (Vipps MobilePay / BankID NO; NOR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Vipps verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiVippsWalletVerificationStartRequest" + }, + "example": { + "country": "NOR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiVippsWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiVippsWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "vipps", + "name": "Vipps", + "issuing_authority": "Vipps MobilePay / BankID NO" + }, + "country": "NOR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/vipps/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"NOR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_vipps(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"NOR\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startVipps({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NOR\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartVipps(ctx, didit.IDWalletsStartVippsParams{\n\tBody: didit.DigitalIDWalletAPIVippsWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryAfcEnum(\"NOR\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiCountryAfcEnum;\nimport me.didit.types.DigitalIdWalletApiVippsWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startVipps(DigitalIdWalletApiVippsWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryAfcEnum.of(\"NOR\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startVipps([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'NOR',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_vipps(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NOR\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartVippsAsync(new DigitalIdWalletApiVippsWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryAfcEnum(\"NOR\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "vipps" + } + }, + "/v3/id-verification/wallets/buypass/": { + "post": { + "operationId": "start_buypass_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_buypass", + "auth": "api_key", + "server": true + }, + "description": "Start a Buypass ID sign-in (Buypass AS; NOR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Buypass ID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBuypassWalletVerificationStartRequest" + }, + "example": { + "country": "NOR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBuypassWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBuypassWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "buypass", + "name": "Buypass ID", + "issuing_authority": "Buypass AS" + }, + "country": "NOR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/buypass/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"NOR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_buypass(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"NOR\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startBuypass({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NOR\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartBuypass(ctx, didit.IDWalletsStartBuypassParams{\n\tBody: didit.DigitalIDWalletAPIBuypassWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryAfcEnum(\"NOR\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiBuypassWalletVerificationStartRequest;\nimport me.didit.types.DigitalIdWalletApiCountryAfcEnum;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startBuypass(DigitalIdWalletApiBuypassWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryAfcEnum.of(\"NOR\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startBuypass([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'NOR',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_buypass(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NOR\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartBuypassAsync(new DigitalIdWalletApiBuypassWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryAfcEnum(\"NOR\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "buypass" + } + }, + "/v3/id-verification/wallets/itsme/": { + "post": { + "operationId": "start_itsme_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_itsme", + "auth": "api_key", + "server": true + }, + "description": "Start a itsme sign-in (Belgian Mobile ID; BEL, LUX, NLD) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start itsme verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiItsmeWalletVerificationStartRequest" + }, + "example": { + "country": "BEL", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiItsmeWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiItsmeWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "itsme", + "name": "itsme", + "issuing_authority": "Belgian Mobile ID" + }, + "country": "BEL", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/itsme/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"BEL\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_itsme(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"BEL\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startItsme({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"BEL\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartItsme(ctx, didit.IDWalletsStartItsmeParams{\n\tBody: didit.DigitalIDWalletAPIItsmeWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.DigitalIDWalletAPIItsmeWalletVerificationStartCountryEnum(\"BEL\"),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiItsmeWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiItsmeWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startItsme(DigitalIdWalletApiItsmeWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiItsmeWalletVerificationStartCountryEnum.of(\"BEL\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startItsme([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'BEL',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_itsme(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"BEL\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartItsmeAsync(new DigitalIdWalletApiItsmeWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = \"BEL\",\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "itsme" + } + }, + "/v3/id-verification/wallets/idin/": { + "post": { + "operationId": "start_idin_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_idin", + "auth": "api_key", + "server": true + }, + "description": "Start a iDIN sign-in (Dutch banks (Currence iDIN); NLD) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start iDIN verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdinWalletVerificationStartRequest" + }, + "example": { + "country": "NLD", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdinWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdinWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "idin", + "name": "iDIN", + "issuing_authority": "Dutch banks (Currence iDIN)" + }, + "country": "NLD", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/idin/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"NLD\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_idin(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"NLD\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startIdin({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NLD\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartIdin(ctx, didit.IDWalletsStartIdinParams{\n\tBody: didit.DigitalIDWalletAPIIdinWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIIdinWalletVerificationStartCountryEnum(\"NLD\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiIdinWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiIdinWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startIdin(DigitalIdWalletApiIdinWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiIdinWalletVerificationStartCountryEnum.of(\"NLD\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startIdin([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'NLD',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_idin(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NLD\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartIdinAsync(new DigitalIdWalletApiIdinWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiIdinWalletVerificationStartCountryEnum(\"NLD\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "idin" + } + }, + "/v3/id-verification/wallets/ftn/": { + "post": { + "operationId": "start_ftn_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_ftn", + "auth": "api_key", + "server": true + }, + "description": "Start a Finnish Trust Network sign-in (Finnish banks and mobile operators (FTN); FIN) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Finnish Trust Network verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFtnWalletVerificationStartRequest" + }, + "example": { + "country": "FIN", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFtnWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFtnWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "ftn", + "name": "Finnish Trust Network", + "issuing_authority": "Finnish banks and mobile operators (FTN)" + }, + "country": "FIN", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/ftn/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"FIN\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_ftn(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"FIN\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startFtn({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"FIN\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartFtn(ctx, didit.IDWalletsStartFtnParams{\n\tBody: didit.DigitalIDWalletAPIFtnWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIFtnWalletVerificationStartCountryEnum(\"FIN\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiFtnWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiFtnWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startFtn(DigitalIdWalletApiFtnWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiFtnWalletVerificationStartCountryEnum.of(\"FIN\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startFtn([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'FIN',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_ftn(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"FIN\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartFtnAsync(new DigitalIdWalletApiFtnWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiFtnWalletVerificationStartCountryEnum(\"FIN\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "ftn" + } + }, + "/v3/id-verification/wallets/personalausweis/": { + "post": { + "operationId": "start_personalausweis_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_personalausweis", + "auth": "api_key", + "server": true + }, + "description": "Start a Personalausweis sign-in (Bundesministerium des Innern (eID); DEU) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Personalausweis verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest" + }, + "example": { + "country": "DEU", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "personalausweis", + "name": "Personalausweis", + "issuing_authority": "Bundesministerium des Innern (eID)" + }, + "country": "DEU", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/personalausweis/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"DEU\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_personalausweis(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"DEU\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startPersonalausweis({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"DEU\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartPersonalausweis(ctx, didit.IDWalletsStartPersonalausweisParams{\n\tBody: didit.DigitalIDWalletAPIPersonalausweisWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIPersonalausweisWalletVerificationStartCountryEnum(\"DEU\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiPersonalausweisWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startPersonalausweis(DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiPersonalausweisWalletVerificationStartCountryEnum.of(\"DEU\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startPersonalausweis([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'DEU',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_personalausweis(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"DEU\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartPersonalausweisAsync(new DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiPersonalausweisWalletVerificationStartCountryEnum(\"DEU\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "personalausweis" + } + }, + "/v3/id-verification/wallets/frejaid/": { + "post": { + "operationId": "start_frejaid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_frejaid", + "auth": "api_key", + "server": true + }, + "description": "Start a Freja eID sign-in (Freja eID Group; SWE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Freja eID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFrejaidWalletVerificationStartRequest" + }, + "example": { + "country": "SWE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFrejaidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFrejaidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "frejaid", + "name": "Freja eID", + "issuing_authority": "Freja eID Group" + }, + "country": "SWE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/frejaid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"SWE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_frejaid(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"SWE\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startFrejaid({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"SWE\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartFrejaid(ctx, didit.IDWalletsStartFrejaidParams{\n\tBody: didit.DigitalIDWalletAPIFrejaidWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryEe9enum(\"SWE\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiCountryEe9Enum;\nimport me.didit.types.DigitalIdWalletApiFrejaidWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startFrejaid(DigitalIdWalletApiFrejaidWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryEe9Enum.of(\"SWE\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startFrejaid([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'SWE',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_frejaid(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"SWE\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartFrejaidAsync(new DigitalIdWalletApiFrejaidWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryEe9Enum(\"SWE\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "frejaid" + } + }, + "/v3/id-verification/wallets/uae-pass/": { + "post": { + "operationId": "start_uae_pass_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_uae_pass", + "auth": "api_key", + "server": true + }, + "description": "Start a UAE PASS sign-in (UAE Digital Government Authority; ARE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start UAE PASS verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiUaePassWalletVerificationStartRequest" + }, + "example": { + "country": "ARE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiUaePassWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiUaePassWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "uae_pass", + "name": "UAE PASS", + "issuing_authority": "UAE Digital Government Authority" + }, + "country": "ARE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/uae-pass/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"ARE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_uae_pass(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"ARE\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startUaePass({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"ARE\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartUaePass(ctx, didit.IDWalletsStartUaePassParams{\n\tBody: didit.DigitalIDWalletAPIUaePassWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIUaePassWalletVerificationStartCountryEnum(\"ARE\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiUaePassWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiUaePassWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startUaePass(DigitalIdWalletApiUaePassWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiUaePassWalletVerificationStartCountryEnum.of(\"ARE\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startUaePass([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'ARE',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_uae_pass(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"ARE\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartUaePassAsync(new DigitalIdWalletApiUaePassWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiUaePassWalletVerificationStartCountryEnum(\"ARE\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "uae_pass" + } + }, + "/v3/id-verification/wallets/govbr/": { + "post": { + "operationId": "start_govbr_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_govbr", + "auth": "api_key", + "server": true + }, + "description": "Start a gov.br sign-in (Governo Federal do Brasil; BRA) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start gov.br verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovbrWalletVerificationStartRequest" + }, + "example": { + "country": "BRA", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovbrWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovbrWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "govbr", + "name": "gov.br", + "issuing_authority": "Governo Federal do Brasil" + }, + "country": "BRA", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/govbr/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"BRA\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_govbr(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"BRA\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startGovbr({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"BRA\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartGovbr(ctx, didit.IDWalletsStartGovbrParams{\n\tBody: didit.DigitalIDWalletAPIGovbrWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIGovbrWalletVerificationStartCountryEnum(\"BRA\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiGovbrWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiGovbrWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startGovbr(DigitalIdWalletApiGovbrWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiGovbrWalletVerificationStartCountryEnum.of(\"BRA\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startGovbr([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'BRA',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_govbr(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"BRA\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartGovbrAsync(new DigitalIdWalletApiGovbrWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiGovbrWalletVerificationStartCountryEnum(\"BRA\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "govbr" + } + }, + "/v3/id-verification/wallets/oneid/": { + "post": { + "operationId": "start_oneid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_oneid", + "auth": "api_key", + "server": true + }, + "description": "Start a OneID sign-in (OneID (UK bank-verified identity); GBR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start OneID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiOneidWalletVerificationStartRequest" + }, + "example": { + "country": "GBR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiOneidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiOneidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "oneid", + "name": "OneID", + "issuing_authority": "OneID (UK bank-verified identity)" + }, + "country": "GBR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/oneid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"GBR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_oneid(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"GBR\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startOneid({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"GBR\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartOneid(ctx, didit.IDWalletsStartOneidParams{\n\tBody: didit.DigitalIDWalletAPIOneidWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountry520enum(\"GBR\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiCountry520Enum;\nimport me.didit.types.DigitalIdWalletApiOneidWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startOneid(DigitalIdWalletApiOneidWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountry520Enum.of(\"GBR\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startOneid([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'GBR',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_oneid(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"GBR\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartOneidAsync(new DigitalIdWalletApiOneidWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountry520Enum(\"GBR\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "oneid" + } + }, + "/v3/id-verification/wallets/govuk-wallet/": { + "post": { + "operationId": "start_govuk_wallet_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_govuk_wallet", + "auth": "api_key", + "server": true + }, + "description": "Start a GOV.UK Wallet sign-in (UK Government Digital Service; GBR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start GOV.UK Wallet verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovukWalletWalletVerificationStartRequest" + }, + "example": { + "country": "GBR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovukWalletWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovukWalletWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "govuk_wallet", + "name": "GOV.UK Wallet", + "issuing_authority": "UK Government Digital Service" + }, + "country": "GBR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/govuk-wallet/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"GBR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_govuk_wallet(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"GBR\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startGovukWallet({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"GBR\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartGovukWallet(ctx, didit.IDWalletsStartGovukWalletParams{\n\tBody: didit.DigitalIDWalletAPIGovukWalletWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountry520enum(\"GBR\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiCountry520Enum;\nimport me.didit.types.DigitalIdWalletApiGovukWalletWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startGovukWallet(DigitalIdWalletApiGovukWalletWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountry520Enum.of(\"GBR\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startGovukWallet([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'GBR',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_govuk_wallet(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"GBR\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartGovukWalletAsync(new DigitalIdWalletApiGovukWalletWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountry520Enum(\"GBR\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "govuk_wallet" + } + }, + "/v3/id-verification/wallets/smart-id/": { + "post": { + "operationId": "start_smart_id_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_smart_id", + "auth": "api_key", + "server": true + }, + "description": "Start a Smart-ID sign-in (SK ID Solutions; EST, LVA, LTU, BEL) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=confirm_in_app with the comparison code to show the user; the user confirms it and enters their PIN in the app (never collect the PIN). Poll POST /v3/id-verification/wallets/verifications/{request_id}/poll/ every poll_interval_seconds, or rely on the webhook: Didit polls in the background too. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Smart-ID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiSmartIdWalletVerificationStartRequest" + }, + "example": { + "country": "EST", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "personal_code": "00000000000" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiSmartIdWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiSmartIdWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "smart_id", + "name": "Smart-ID", + "issuing_authority": "SK ID Solutions" + }, + "country": "EST", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "in_app", + "simulated": true, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "confirm_in_app", + "verification_code": "1234", + "poll_interval_seconds": 2 + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/smart-id/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"EST\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"personal_code\": \"00000000000\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_smart_id(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"EST\",\n personal_code=\"00000000000\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startSmartId({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"EST\",\n personal_code: \"00000000000\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartSmartID(ctx, didit.IDWalletsStartSmartIDParams{\n\tBody: didit.DigitalIDWalletAPISmartIDWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.DigitalIDWalletAPISmartIDWalletVerificationStartCountryEnum(\"EST\"),\n\t\tPersonalCode: \"00000000000\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiSmartIdWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiSmartIdWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startSmartId(DigitalIdWalletApiSmartIdWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiSmartIdWalletVerificationStartCountryEnum.of(\"EST\"))\n .personalCode(\"00000000000\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startSmartId([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'EST',\n 'personal_code' => '00000000000',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_smart_id(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"EST\",\n personal_code: \"00000000000\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartSmartIdAsync(new DigitalIdWalletApiSmartIdWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = \"EST\",\n PersonalCode = \"00000000000\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "smart_id" + } + }, + "/v3/id-verification/wallets/mobile-id/": { + "post": { + "operationId": "start_mobile_id_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_mobile_id", + "auth": "api_key", + "server": true + }, + "description": "Start a Mobile-ID sign-in (SK ID Solutions with the national mobile operators; EST, LTU) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=confirm_in_app with the comparison code to show the user; the user confirms it and enters their PIN in the app (never collect the PIN). Poll POST /v3/id-verification/wallets/verifications/{request_id}/poll/ every poll_interval_seconds, or rely on the webhook: Didit polls in the background too. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Mobile-ID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMobileIdWalletVerificationStartRequest" + }, + "example": { + "country": "EST", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "personal_code": "00000000000", + "phone_number": "+37200000000" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMobileIdWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMobileIdWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "mobile_id", + "name": "Mobile-ID", + "issuing_authority": "SK ID Solutions with the national mobile operators" + }, + "country": "EST", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "in_app", + "simulated": true, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "confirm_in_app", + "verification_code": "1234", + "poll_interval_seconds": 2 + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/mobile-id/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"EST\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"personal_code\": \"00000000000\",\n \"phone_number\": \"+37200000000\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_mobile_id(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"EST\",\n personal_code=\"00000000000\",\n phone_number=\"+37200000000\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startMobileId({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"EST\",\n personal_code: \"00000000000\",\n phone_number: \"+37200000000\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartMobileID(ctx, didit.IDWalletsStartMobileIDParams{\n\tBody: didit.DigitalIDWalletAPIMobileIDWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.DigitalIDWalletAPIMobileIDWalletVerificationStartCountryEnum(\"EST\"),\n\t\tPersonalCode: \"00000000000\",\n\t\tPhoneNumber: \"+37200000000\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiMobileIdWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiMobileIdWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startMobileId(DigitalIdWalletApiMobileIdWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiMobileIdWalletVerificationStartCountryEnum.of(\"EST\"))\n .personalCode(\"00000000000\")\n .phoneNumber(\"+37200000000\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startMobileId([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'EST',\n 'personal_code' => '00000000000',\n 'phone_number' => '+37200000000',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_mobile_id(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"EST\",\n personal_code: \"00000000000\",\n phone_number: \"+37200000000\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartMobileIdAsync(new DigitalIdWalletApiMobileIdWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = \"EST\",\n PersonalCode = \"00000000000\",\n PhoneNumber = \"+37200000000\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "mobile_id" + } + }, + "/v3/id-verification/wallets/idcard-ee/": { + "post": { + "operationId": "start_idcard_ee_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_idcard_ee", + "auth": "api_key", + "server": true + }, + "description": "Start a Estonian ID-card sign-in (Police and Border Guard Board (Estonia); EST) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Estonian ID-card verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdcardEeWalletVerificationStartRequest" + }, + "example": { + "country": "EST", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdcardEeWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdcardEeWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "idcard_ee", + "name": "Estonian ID-card", + "issuing_authority": "Police and Border Guard Board (Estonia)" + }, + "country": "EST", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/idcard-ee/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"EST\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_idcard_ee(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"EST\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startIdcardEe({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"EST\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartIdcardEe(ctx, didit.IDWalletsStartIdcardEeParams{\n\tBody: didit.DigitalIDWalletAPIIdcardEeWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIIdcardEeWalletVerificationStartCountryEnum(\"EST\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiIdcardEeWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiIdcardEeWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startIdcardEe(DigitalIdWalletApiIdcardEeWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiIdcardEeWalletVerificationStartCountryEnum.of(\"EST\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startIdcardEe([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'EST',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_idcard_ee(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"EST\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartIdcardEeAsync(new DigitalIdWalletApiIdcardEeWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiIdcardEeWalletVerificationStartCountryEnum(\"EST\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "idcard_ee" + } + }, + "/v3/id-verification/wallets/eparaksts-mobile/": { + "post": { + "operationId": "start_eparaksts_mobile_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_eparaksts_mobile", + "auth": "api_key", + "server": true + }, + "description": "Start a eParaksts mobile sign-in (LVRTC (Latvian State Radio and Television Centre); LVA) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start eParaksts mobile verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest" + }, + "example": { + "country": "LVA", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "eparaksts_mobile", + "name": "eParaksts mobile", + "issuing_authority": "LVRTC (Latvian State Radio and Television Centre)" + }, + "country": "LVA", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/eparaksts-mobile/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"LVA\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_eparaksts_mobile(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"LVA\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startEparakstsMobile({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"LVA\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartEparakstsMobile(ctx, didit.IDWalletsStartEparakstsMobileParams{\n\tBody: didit.DigitalIDWalletAPIEparakstsMobileWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIEparakstsMobileWalletVerificationStartCountryEnum(\"LVA\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiEparakstsMobileWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startEparakstsMobile(DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiEparakstsMobileWalletVerificationStartCountryEnum.of(\"LVA\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startEparakstsMobile([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'LVA',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_eparaksts_mobile(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"LVA\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartEparakstsMobileAsync(new DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiEparakstsMobileWalletVerificationStartCountryEnum(\"LVA\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "eparaksts_mobile" + } + }, + "/v3/id-verification/wallets/bankid-cz/": { + "post": { + "operationId": "start_bankid_cz_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_bankid_cz", + "auth": "api_key", + "server": true + }, + "description": "Start a Bank iD sign-in (Bankovní identita, a.s.; CZE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Bank iD verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidCzWalletVerificationStartRequest" + }, + "example": { + "country": "CZE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidCzWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidCzWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "bankid_cz", + "name": "Bank iD", + "issuing_authority": "Bankovní identita, a.s." + }, + "country": "CZE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/bankid-cz/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"CZE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_bankid_cz(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"CZE\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startBankidCz({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"CZE\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartBankidCz(ctx, didit.IDWalletsStartBankidCzParams{\n\tBody: didit.DigitalIDWalletAPIBankidCzWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryE2dEnum(\"CZE\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiBankidCzWalletVerificationStartRequest;\nimport me.didit.types.DigitalIdWalletApiCountryE2dEnum;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startBankidCz(DigitalIdWalletApiBankidCzWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryE2dEnum.of(\"CZE\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startBankidCz([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'CZE',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_bankid_cz(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"CZE\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartBankidCzAsync(new DigitalIdWalletApiBankidCzWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryE2dEnum(\"CZE\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "bankid_cz" + } + }, + "/v3/id-verification/wallets/mojeid/": { + "post": { + "operationId": "start_mojeid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_mojeid", + "auth": "api_key", + "server": true + }, + "description": "Start a MojeID sign-in (CZ.NIC; CZE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start MojeID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMojeidWalletVerificationStartRequest" + }, + "example": { + "country": "CZE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMojeidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMojeidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "mojeid", + "name": "MojeID", + "issuing_authority": "CZ.NIC" + }, + "country": "CZE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/mojeid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"CZE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_mojeid(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"CZE\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startMojeid({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"CZE\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartMojeid(ctx, didit.IDWalletsStartMojeidParams{\n\tBody: didit.DigitalIDWalletAPIMojeidWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryE2dEnum(\"CZE\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiCountryE2dEnum;\nimport me.didit.types.DigitalIdWalletApiMojeidWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startMojeid(DigitalIdWalletApiMojeidWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryE2dEnum.of(\"CZE\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startMojeid([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'CZE',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_mojeid(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"CZE\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartMojeidAsync(new DigitalIdWalletApiMojeidWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryE2dEnum(\"CZE\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "mojeid" + } + }, + "/v3/id-verification/wallets/diia/": { + "post": { + "operationId": "start_diia_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_diia", + "auth": "api_key", + "server": true + }, + "description": "Start a Diia sign-in (Ministry of Digital Transformation of Ukraine; UKR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Diia verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiDiiaWalletVerificationStartRequest" + }, + "example": { + "country": "UKR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiDiiaWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiDiiaWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "diia", + "name": "Diia", + "issuing_authority": "Ministry of Digital Transformation of Ukraine" + }, + "country": "UKR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/diia/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"UKR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_diia(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"UKR\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startDiia({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"UKR\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartDiia(ctx, didit.IDWalletsStartDiiaParams{\n\tBody: didit.DigitalIDWalletAPIDiiaWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIDiiaWalletVerificationStartCountryEnum(\"UKR\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiDiiaWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiDiiaWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startDiia(DigitalIdWalletApiDiiaWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiDiiaWalletVerificationStartCountryEnum.of(\"UKR\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startDiia([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'UKR',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_diia(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"UKR\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartDiiaAsync(new DigitalIdWalletApiDiiaWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiDiiaWalletVerificationStartCountryEnum(\"UKR\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "diia" + } + }, + "/v3/id-verification/wallets/franceconnect/": { + "post": { + "operationId": "start_franceconnect_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_franceconnect", + "auth": "api_key", + "server": true + }, + "description": "Start a FranceConnect sign-in (DINUM (French state); FRA) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start FranceConnect verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFranceconnectWalletVerificationStartRequest" + }, + "example": { + "country": "FRA", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFranceconnectWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFranceconnectWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "franceconnect", + "name": "FranceConnect", + "issuing_authority": "DINUM (French state)" + }, + "country": "FRA", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/franceconnect/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"FRA\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_franceconnect(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"FRA\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startFranceconnect({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"FRA\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartFranceconnect(ctx, didit.IDWalletsStartFranceconnectParams{\n\tBody: didit.DigitalIDWalletAPIFranceconnectWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIFranceconnectWalletVerificationStartCountryEnum(\"FRA\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiFranceconnectWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiFranceconnectWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startFranceconnect(DigitalIdWalletApiFranceconnectWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiFranceconnectWalletVerificationStartCountryEnum.of(\"FRA\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startFranceconnect([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'FRA',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_franceconnect(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"FRA\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartFranceconnectAsync(new DigitalIdWalletApiFranceconnectWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiFranceconnectWalletVerificationStartCountryEnum(\"FRA\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "franceconnect" + } + }, + "/v3/id-verification/wallets/audkenni/": { + "post": { + "operationId": "start_audkenni_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_audkenni", + "auth": "api_key", + "server": true + }, + "description": "Start a Auðkenni sign-in (Auðkenni (Icelandic electronic ID); ISL) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Auðkenni verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiAudkenniWalletVerificationStartRequest" + }, + "example": { + "country": "ISL", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiAudkenniWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiAudkenniWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "audkenni", + "name": "Auðkenni", + "issuing_authority": "Auðkenni (Icelandic electronic ID)" + }, + "country": "ISL", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/audkenni/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"ISL\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_audkenni(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"ISL\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startAudkenni({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"ISL\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartAudkenni(ctx, didit.IDWalletsStartAudkenniParams{\n\tBody: didit.DigitalIDWalletAPIAudkenniWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIAudkenniWalletVerificationStartCountryEnum(\"ISL\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiAudkenniWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiAudkenniWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startAudkenni(DigitalIdWalletApiAudkenniWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiAudkenniWalletVerificationStartCountryEnum.of(\"ISL\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startAudkenni([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'ISL',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_audkenni(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"ISL\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartAudkenniAsync(new DigitalIdWalletApiAudkenniWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiAudkenniWalletVerificationStartCountryEnum(\"ISL\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "audkenni" + } + }, + "/v3/id-verification/wallets/connectid/": { + "post": { + "operationId": "start_connectid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_connectid", + "auth": "api_key", + "server": true + }, + "description": "Start a ConnectID sign-in (Australian Payments Plus; AUS) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start ConnectID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiConnectidWalletVerificationStartRequest" + }, + "example": { + "country": "AUS", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiConnectidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiConnectidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "connectid", + "name": "ConnectID", + "issuing_authority": "Australian Payments Plus" + }, + "country": "AUS", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/connectid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"AUS\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_connectid(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"AUS\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startConnectid({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"AUS\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartConnectid(ctx, didit.IDWalletsStartConnectidParams{\n\tBody: didit.DigitalIDWalletAPIConnectidWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIConnectidWalletVerificationStartCountryEnum(\"AUS\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiConnectidWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiConnectidWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startConnectid(DigitalIdWalletApiConnectidWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiConnectidWalletVerificationStartCountryEnum.of(\"AUS\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startConnectid([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'AUS',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_connectid(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"AUS\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartConnectidAsync(new DigitalIdWalletApiConnectidWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiConnectidWalletVerificationStartCountryEnum(\"AUS\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "connectid" + } + }, + "/v3/id-verification/wallets/eudi/": { + "post": { + "operationId": "start_eudi_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_eudi", + "auth": "api_key", + "server": true + }, + "description": "Start a EUDI Wallet sign-in (The user's own member state; the issuer differs per country; AUT, BEL, BGR, HRV, CYP, CZE, DNK, EST, FIN, FRA, DEU, GRC, HUN, IRL, ITA, LVA, LTU, LUX, MLT, NLD, POL, PRT, ROU, SVK, SVN, ESP, SWE, ISL, LIE, NOR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start EUDI Wallet verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEudiWalletVerificationStartRequest" + }, + "example": { + "country": "AUT", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEudiWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEudiWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "eudi", + "name": "EUDI Wallet", + "issuing_authority": "The user's own member state; the issuer differs per country" + }, + "country": "AUT", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/eudi/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"AUT\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_eudi(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"AUT\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startEudi({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"AUT\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartEudi(ctx, didit.IDWalletsStartEudiParams{\n\tBody: didit.DigitalIDWalletAPIEudiWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.DigitalIDWalletAPIEudiWalletVerificationStartCountryEnum(\"AUT\"),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiEudiWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiEudiWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startEudi(DigitalIdWalletApiEudiWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiEudiWalletVerificationStartCountryEnum.of(\"AUT\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startEudi([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'AUT',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_eudi(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"AUT\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartEudiAsync(new DigitalIdWalletApiEudiWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = \"AUT\",\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "eudi" + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/teams/": { + "get": { + "summary": "List review teams", + "description": "Requires `list:cases` for the requested application.", + "operationId": "list_case_teams", + "x-didit-sdk": { + "resource": "cases.teams", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000000, + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseReviewTeam" + } + }, + "count": { + "type": "integer", + "description": "Matching count, capped at 100. Follow next to continue beyond the cap." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.teams.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.teams.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.Teams.ListAutoPaging(ctx, didit.CasesTeamsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().teams().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->teams->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.teams.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.Teams.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "summary": "Create review team", + "description": "Requires `write:cases` for the requested application.", + "operationId": "create_case_teams", + "x-didit-sdk": { + "resource": "cases.teams", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "responses": { + "201": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeam" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeamCreate" + }, + "example": { + "name": "AML analysts", + "members": [ + { + "user_id": "9a000000-0000-4000-8000-0000000000aa", + "capacity": 10 + } + ] + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.teams.create(\n name=\"AML analysts\",\n members=[{\n \"user_id\": \"9a000000-0000-4000-8000-0000000000aa\",\n \"capacity\": 10,\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.teams.create({\n name: \"AML analysts\",\n members: [{\n user_id: \"9a000000-0000-4000-8000-0000000000aa\",\n capacity: 10,\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Teams.Create(ctx, didit.CaseReviewTeamCreate{\n\tName: \"AML analysts\",\n\tMembers: []didit.CaseReviewTeamMemberParam{{\n\t\tUserID: \"9a000000-0000-4000-8000-0000000000aa\",\n\t\tCapacity: didit.Int(10),\n\t}},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CaseReviewTeamCreate;\nimport me.didit.types.CaseReviewTeamMember;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().teams().create(CaseReviewTeamCreate.builder()\n .name(\"AML analysts\")\n .members(List.of(CaseReviewTeamMember.builder()\n .userId(\"9a000000-0000-4000-8000-0000000000aa\")\n .capacity(10L)\n .build()))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->teams->create([\n 'name' => 'AML analysts',\n 'members' => [[\n 'user_id' => '9a000000-0000-4000-8000-0000000000aa',\n 'capacity' => 10,\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.teams.create(\n name: \"AML analysts\",\n members: [{\n user_id: \"9a000000-0000-4000-8000-0000000000aa\",\n capacity: 10,\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Teams.CreateAsync(new CaseReviewTeamCreate\n{\n Name = \"AML analysts\",\n Members = new CaseReviewTeamMember[]\n {\n new()\n {\n UserId = \"9a000000-0000-4000-8000-0000000000aa\",\n Capacity = 10,\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/fraud/checks/{check_id}/": { + "get": { + "operationId": "root_retrieve", + "x-didit-sdk": { + "resource": "fraud_checks", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "description": "Fetch a previously created check by its `check_id`.", + "summary": "Retrieve a Fraud Check", + "parameters": [ + { + "in": "path", + "name": "check_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckResponse" + } + } + }, + "description": "" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudClientCredentialsErrorMessage" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "tags": [ + "Fraud Check" + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.fraud_checks.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.fraudChecks.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.FraudChecks.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.fraudChecks().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "fraudChecks->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.fraud_checks.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.FraudChecks.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/": { + "get": { + "operationId": "organization_application_cases_requests_list", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "name": "ordering", + "required": false, + "in": "query", + "description": "Which field to use when ordering the results.", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "offset", + "required": false, + "in": "query", + "description": "The initial index from which to return the results.", + "schema": { + "type": "integer" + } + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingPaginatedCaseRequestList" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.requests.list(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.requests.list(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.Requests.ListAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasesRequestsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().requests().list(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->list('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.requests.list(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.Requests.ListAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "operationId": "organization_application_cases_requests_create", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestConfirmRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestConfirmRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestConfirmRequest" + } + } + }, + "required": true + }, + "responses": { + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "400": { + "description": "Invalid inputs, unsupported purpose, unavailable verified contact or an existing immutable response." + }, + "409": { + "description": "The preview changed or expired. Preview again before confirming." + }, + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequest" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Confirm the exact signed preview within 15 minutes. Only fixed templates and an OTP-verified contact belonging to the case subject are accepted. The request ID makes retries idempotent. Delivery is queued; SENT, SIMULATED and UNKNOWN are distinct outcomes. An actionable request sets the case to Awaiting user. A review update leaves its status unchanged.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.requests.create(\n \"11111111-2222-3333-4444-555555555555\",\n contact_id=\"11111111-2222-3333-4444-555555555555\",\n purpose=\"identity_document\",\n request_id=\"11111111-2222-3333-4444-555555555555\",\n expires_at=\"2026-01-15T10:00:00Z\",\n preview_token=\"your-preview-token\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.requests.create(\"11111111-2222-3333-4444-555555555555\", {\n contact_id: \"11111111-2222-3333-4444-555555555555\",\n purpose: \"identity_document\",\n request_id: \"11111111-2222-3333-4444-555555555555\",\n expires_at: \"2026-01-15T10:00:00Z\",\n preview_token: \"your-preview-token\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Requests.Create(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CustomerMessagingCaseRequestConfirmRequest{\n\tContactID: \"11111111-2222-3333-4444-555555555555\",\n\tPurpose: didit.CustomerMessagingPurposeEnum(\"identity_document\"),\n\tRequestID: \"11111111-2222-3333-4444-555555555555\",\n\tExpiresAt: time.Date(2026, 1, 15, 10, 0, 0, 0, time.UTC),\n\tPreviewToken: \"your-preview-token\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.OffsetDateTime;\nimport me.didit.Didit;\nimport me.didit.types.CustomerMessagingCaseRequestConfirmRequest;\nimport me.didit.types.CustomerMessagingPurposeEnum;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().requests().create(\"11111111-2222-3333-4444-555555555555\", CustomerMessagingCaseRequestConfirmRequest.builder()\n .contactId(\"11111111-2222-3333-4444-555555555555\")\n .purpose(CustomerMessagingPurposeEnum.of(\"identity_document\"))\n .requestId(\"11111111-2222-3333-4444-555555555555\")\n .expiresAt(OffsetDateTime.parse(\"2026-01-15T10:00:00Z\"))\n .previewToken(\"your-preview-token\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->create('11111111-2222-3333-4444-555555555555', [\n 'contact_id' => '11111111-2222-3333-4444-555555555555',\n 'purpose' => 'identity_document',\n 'request_id' => '11111111-2222-3333-4444-555555555555',\n 'expires_at' => '2026-01-15T10:00:00Z',\n 'preview_token' => 'your-preview-token',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.requests.create(\n \"11111111-2222-3333-4444-555555555555\",\n contact_id: \"11111111-2222-3333-4444-555555555555\",\n purpose: \"identity_document\",\n request_id: \"11111111-2222-3333-4444-555555555555\",\n expires_at: \"2026-01-15T10:00:00Z\",\n preview_token: \"your-preview-token\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Requests.CreateAsync(\"11111111-2222-3333-4444-555555555555\", new CustomerMessagingCaseRequestConfirmRequest\n{\n ContactId = \"11111111-2222-3333-4444-555555555555\",\n Purpose = \"identity_document\",\n RequestId = \"11111111-2222-3333-4444-555555555555\",\n ExpiresAt = DateTimeOffset.Parse(\"2026-01-15T10:00:00Z\"),\n PreviewToken = \"your-preview-token\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/fraud/checks/": { + "post": { + "operationId": "create_fraud_check", + "x-didit-sdk": { + "resource": "fraud_checks", + "method": "create", + "auth": "api_key", + "server": true + }, + "description": "Submit one or more typed identifiers and receive normalization, fresh enrichment, authoritative validation where the country and identifier type are covered, independent risk dimensions and a recommendation in one response.\n\nThese profiles support identifier checks. Selfie-based identity binding (`biometric_identity`) requires profile version `2026-09-14.2` and a Brazilian CPF; it runs only with the user's consent and when your organization is entitled to it. Earlier versions report it as `not_available_in_profile_version`. Modules that cannot run are reported as skipped with a deterministic reason; a source that could not answer is never reported as an identity mismatch.", + "summary": "Run a Fraud Check", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + }, + "example": { + "client_reference": "review-attempt-001", + "subject": { + "identifiers": [ + { + "type": "email", + "value": "applicant@example.com" + } + ], + "attributes": { + "first_name": "Alex", + "last_name": "Example" + } + }, + "checks": { + "include": [ + "networks_same_org", + "networks_cross_org", + "email_risk" + ] + } + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckResponse" + } + } + }, + "description": "" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudClientCredentialsErrorMessage" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "" + }, + "409": { + "description": "Conflict, returned without a charge: the `client_reference` was already used with a different request body; a check with the same `client_reference` is still running (retry after `Retry-After` seconds with the same reference); or the verification this check records into was deleted before the check completed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + }, + "examples": { + "different_body": { + "summary": "Reference reused with a different body", + "value": { + "error": "client_reference has already been used with a different request body." + } + }, + "still_running": { + "summary": "A check with this reference is still running", + "value": { + "error": "A check with this client_reference is already running. Retry with the same reference later." + } + }, + "verification_deleted": { + "summary": "The verification was deleted before the check completed", + "value": { + "error": "This verification was deleted before the check completed." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying, sent when a check with the same `client_reference` is still running.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "tags": [ + "Fraud Check" + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.fraud_checks.create(\n client_reference=\"review-attempt-001\",\n subject={\n \"identifiers\": [{\n \"type\": \"email\",\n \"value\": \"applicant@example.com\",\n }],\n \"attributes\": {\n \"first_name\": \"Alex\",\n \"last_name\": \"Example\",\n },\n },\n checks={\n \"include\": [\"networks_same_org\", \"networks_cross_org\", \"email_risk\"],\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.fraudChecks.create({\n client_reference: \"review-attempt-001\",\n subject: {\n identifiers: [{\n type: \"email\",\n value: \"applicant@example.com\",\n }],\n attributes: {\n first_name: \"Alex\",\n last_name: \"Example\",\n },\n },\n checks: {\n include: [\"networks_same_org\", \"networks_cross_org\", \"email_risk\"],\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.FraudChecks.Create(ctx, didit.FraudCheckRequestRequest{\n\tClientReference: didit.String(\"review-attempt-001\"),\n\tSubject: didit.FraudCheckSubjectRequest{\n\t\tIdentifiers: []didit.FraudCheckIdentifierRequest{{\n\t\t\tType: didit.FraudTypeEnum(\"email\"),\n\t\t\tValue: \"applicant@example.com\",\n\t\t}},\n\t\tAttributes: didit.Some(didit.FraudCheckAttributesRequest{\n\t\t\tFirstName: didit.String(\"Alex\"),\n\t\t\tLastName: didit.String(\"Example\"),\n\t\t}),\n\t},\n\tChecks: didit.Some(didit.FraudCheckModuleSelectionRequest{\n\t\tInclude: didit.Some([]didit.FraudExcludeEnum{didit.FraudExcludeEnum(\"networks_same_org\"), didit.FraudExcludeEnum(\"networks_cross_org\"), didit.FraudExcludeEnum(\"email_risk\")}),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.FraudCheckAttributesRequest;\nimport me.didit.types.FraudCheckIdentifierRequest;\nimport me.didit.types.FraudCheckModuleSelectionRequest;\nimport me.didit.types.FraudCheckRequestRequest;\nimport me.didit.types.FraudCheckSubjectRequest;\nimport me.didit.types.FraudExcludeEnum;\nimport me.didit.types.FraudTypeEnum;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.fraudChecks().create(FraudCheckRequestRequest.builder()\n .clientReference(\"review-attempt-001\")\n .subject(FraudCheckSubjectRequest.builder()\n .identifiers(List.of(FraudCheckIdentifierRequest.builder()\n .type(FraudTypeEnum.of(\"email\"))\n .value(\"applicant@example.com\")\n .build()))\n .attributes(FraudCheckAttributesRequest.builder()\n .firstName(\"Alex\")\n .lastName(\"Example\")\n .build())\n .build())\n .checks(FraudCheckModuleSelectionRequest.builder()\n .include(List.of(FraudExcludeEnum.of(\"networks_same_org\"), FraudExcludeEnum.of(\"networks_cross_org\"), FraudExcludeEnum.of(\"email_risk\")))\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "fraudChecks->create([\n 'client_reference' => 'review-attempt-001',\n 'subject' => [\n 'identifiers' => [[\n 'type' => 'email',\n 'value' => 'applicant@example.com',\n ]],\n 'attributes' => [\n 'first_name' => 'Alex',\n 'last_name' => 'Example',\n ],\n ],\n 'checks' => [\n 'include' => ['networks_same_org', 'networks_cross_org', 'email_risk'],\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.fraud_checks.create(\n client_reference: \"review-attempt-001\",\n subject: {\n identifiers: [{\n type: \"email\",\n value: \"applicant@example.com\",\n }],\n attributes: {\n first_name: \"Alex\",\n last_name: \"Example\",\n },\n },\n checks: {\n include: [\"networks_same_org\", \"networks_cross_org\", \"email_risk\"],\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.FraudChecks.CreateAsync(new FraudCheckRequestRequest\n{\n ClientReference = \"review-attempt-001\",\n Subject = new()\n {\n Identifiers = new FraudCheckIdentifierRequest[]\n {\n new()\n {\n Type = \"email\",\n Value = \"applicant@example.com\",\n },\n },\n Attributes = new FraudCheckAttributesRequest\n {\n FirstName = \"Alex\",\n LastName = \"Example\",\n },\n },\n Checks = new FraudCheckModuleSelectionRequest\n {\n Include = new FraudExcludeEnum[] { \"networks_same_org\", \"networks_cross_org\", \"email_risk\" },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/{request_uuid}/cancel/": { + "post": { + "operationId": "organization_application_cases_requests_cancel_create", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "cancel", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequest" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "400": { + "description": "Invalid inputs, unsupported purpose, unavailable verified contact or an existing immutable response." + }, + "409": { + "description": "The preview changed or expired. Preview again before confirming." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.requests.cancel(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.requests.cancel(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Requests.Cancel(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().requests().cancel(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->cancel('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.requests.cancel(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Requests.CancelAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/options/": { + "get": { + "operationId": "organization_application_cases_requests_options_retrieve", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "retrieve_options", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestOptions" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.requests.retrieve_options(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.requests.retrieveOptions(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Requests.RetrieveOptions(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().requests().retrieveOptions(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->retrieveOptions('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.requests.retrieve_options(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Requests.RetrieveOptionsAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/{request_uuid}/attachment/": { + "get": { + "operationId": "organization_application_cases_requests_attachment_retrieve", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "retrieve_attachment", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "image/jpeg": { + "schema": { + "type": "string", + "format": "binary" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\ncontent = client.cases.requests.retrieve_attachment(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nwith open(\"download.png\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst content = await didit.cases.requests.retrieveAttachment(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nawait writeFile(\"download.png\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresp, err := client.Cases.Requests.RetrieveAttachment(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.png\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().requests().retrieveAttachment(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\")\n .writeTo(Path.of(\"download.png\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->retrieveAttachment('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555');\n$content->saveTo('download.png');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ncontent = didit.cases.requests.retrieve_attachment(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nFile.binwrite(\"download.png\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar content = await client.Cases.Requests.RetrieveAttachmentAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nawait content.SaveToAsync(\"download.png\");" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/{record_uuid}/history/": { + "get": { + "operationId": "list_case_routing_rule_history", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "list_history", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "name": "ordering", + "required": false, + "in": "query", + "description": "Which field to use when ordering the results.", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "offset", + "required": false, + "in": "query", + "description": "The initial index from which to return the results.", + "schema": { + "type": "integer" + } + } + ], + "tags": [ + "Case Assignment" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPaginatedPolicyRevisionList" + } + } + }, + "description": "" + }, + "403": { + "description": "Case access is required." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.routing_rules.list_history(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.routingRules.listHistory(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.RoutingRules.ListHistoryAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasesRoutingRulesListHistoryParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().routingRules().listHistory(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->listHistory('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.routing_rules.list_history(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.RoutingRules.ListHistoryAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/session/{sessionId}/resubmission-preview/": { + "post": { + "summary": "Preview a resubmission email", + "description": "Render the email that would accompany a KYC or KYB resubmission without changing the session, resetting checks, extending its expiry, running checks or sending mail. Requires the `write:sessions` privilege (a scoped API key needs write access to decisions) and a session in `Approved`, `Declined`, `In Review`, `Kyc Expired` or `Abandoned` status. The server validates the selected workflow steps and renders its fixed translated template with the applicable verified white-label sender and branding. The request cannot set a subject, message body or action URL.\n\n`email_address` is optional. Without it, the email goes to the person's own address: the one they verified in an email verification step of the flow, else the one given in `contact_details` when the session was created. An address you pass wins (`recipient_source: request`). When no address is known and you pass none, `recipient` and `preview_token` are empty strings and no email can be sent.\n\nThe response includes the recipient and where it comes from, the sender, subject, HTML, language, resolved steps, whether confirming sends the email (`will_be_sent`, `not_sent_reason`) and a signed `preview_token`. `requires_user_action` is `false` when every selected step runs without the person: the resubmission then finishes on its own and no email goes out (`not_sent_reason: no_user_action`).\n\nThis preview does not reserve session state or authorize delivery. After reviewing it, call [Update Session Status](/sessions-api/update-status) with `new_status: Resubmitted`, the same `nodes_to_resubmit` and `email_language` you previewed, `send_email: true`, `email_address` set to `recipient` and `email_preview_token` set to `preview_token`. The receipt expires after 15 minutes. When a receipt is supplied, changed inputs, message content, sender or session state return `409` before any mutation or send. Existing integrations may omit the receipt. Regenerate the preview after a conflict. Treat the response as sensitive verification content; responses use `Cache-Control: no-store`. Render HTML in a sandboxed frame, never directly in your application DOM.", + "operationId": "post_v3_session_resubmission_preview", + "x-didit-sdk": { + "resource": "sessions", + "method": "preview_resubmission", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session to update. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "nodes_to_resubmit": { + "type": "array", + "description": "Steps to preview, using the same validation, selection and workflow ordering as a resubmission. Omit to auto-select recorded attempts needing resubmission.", + "items": { + "type": "object", + "required": [ + "node_id", + "feature" + ], + "properties": { + "node_id": { + "type": "string", + "description": "Node identifier as it appears in the session's workflow definition (for example `feature_ocr`, `feature_liveness`).", + "example": "feature_ocr" + }, + "feature": { + "type": "string", + "description": "Feature type of the node. The aliases `ID_VERIFICATION`, `POA`, `PHONE`, and `EMAIL` are normalized server-side to `OCR`, `PROOF_OF_ADDRESS`, `PHONE_VERIFICATION`, and `EMAIL_VERIFICATION`. `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and `KYB` pass schema validation but are always rejected with the business-rule `400` shown in the examples.", + "enum": [ + "OCR", + "OCR_BACK", + "NFC", + "AML", + "FACE", + "LIVENESS", + "FACE_MATCH", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "PROOF_OF_ADDRESS", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "FACE_SEARCH", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_DOCUMENTS", + "ID_VERIFICATION", + "POA", + "PHONE", + "EMAIL", + "KYB_REGISTRY", + "KYB_KEY_PEOPLE", + "KYB", + "GEOLOCATION" + ], + "example": "OCR" + } + } + } + }, + "email_address": { + "type": "string", + "format": "email", + "description": "Optional. Send the email to this address instead of the person's own. Without it, the recipient is the address the person verified in the flow, else the one given in `contact_details`. No email is sent by this call.", + "example": "user@example.com" + }, + "email_language": { + "type": "string", + "description": "Language for the notification email. Accepts any string at schema level; unsupported codes silently fall back to English (`en`).", + "default": "en", + "example": "en" + } + } + }, + "examples": { + "Person's Own Address": { + "summary": "Preview a proof-of-address resubmission for the person's own address", + "value": { + "nodes_to_resubmit": [ + { + "node_id": "feature_poa_1", + "feature": "POA" + } + ], + "email_language": "en" + } + }, + "Named Recipient": { + "summary": "Preview the email for an address you name", + "value": { + "nodes_to_resubmit": [ + { + "node_id": "feature_ocr", + "feature": "OCR" + } + ], + "email_address": "person@example.com", + "email_language": "en" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Preview only. The session is unchanged and no email is sent.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "recipient", + "recipient_source", + "sender", + "sender_name", + "sender_email", + "sender_kind", + "subject", + "html", + "text", + "language", + "will_be_sent", + "not_sent_reason", + "preview_token", + "nodes_to_resubmit", + "requires_user_action" + ], + "properties": { + "recipient": { + "type": "string", + "description": "The address the email goes to. An empty string when no address is known for the person: then no email can be sent and `preview_token` is empty too. Pass it as `email_address` when you confirm.", + "example": "person@example.com" + }, + "recipient_source": { + "type": "string", + "enum": [ + "email_verification", + "contact_details", + "request", + "" + ], + "description": "Where `recipient` comes from. `request`: the `email_address` you passed. `email_verification`: the address the person verified in an email verification step of the flow. `contact_details`: the address given in `contact_details` when the session was created. An empty string when there is no address.", + "example": "email_verification" + }, + "sender": { + "type": "string", + "description": "The `From` header the email goes out with.", + "example": "Example App " + }, + "sender_name": { + "type": "string", + "description": "The display name part of `sender`.", + "example": "Example App" + }, + "sender_email": { + "type": "string", + "format": "email", + "description": "The address part of `sender`.", + "example": "no-reply@mail.example.com" + }, + "sender_kind": { + "type": "string", + "enum": [ + "customer", + "didit" + ], + "description": "`customer`: the email goes out from your application's verified sender domain (white label). `didit`: it goes out from Didit's own sender.", + "example": "customer" + }, + "subject": { + "type": "string", + "description": "The subject line exactly as it is sent.", + "example": "Action Required: Complete your verification in Example App" + }, + "html": { + "type": "string", + "description": "The email's HTML exactly as it is sent, with its logo embedded as a data URL. Render it in a sandboxed frame, never directly in your application DOM.", + "example": "..." + }, + "text": { + "type": "string", + "description": "The email's words as plain text, as the person's Messages thread keeps them, for a compact preview (the HTML stays the email the person receives).", + "example": "Almost there\n\nSome steps need to be completed again.\nHere’s what to fix:\n\nhttps://verify.didit.me/session/abc123" + }, + "language": { + "type": "string", + "description": "The language the email is written in. English (`en`) when the `email_language` you asked for is not offered.", + "example": "en" + }, + "will_be_sent": { + "type": "boolean", + "description": "Whether confirming the change with `send_email: true` sends this email. `false` when `not_sent_reason` is set.", + "example": true + }, + "not_sent_reason": { + "type": "string", + "nullable": true, + "enum": [ + "no_user_action", + "no_recipient", + "sandbox", + null + ], + "description": "Why confirming would send no email, or `null` when it will be sent. `no_user_action`: every selected step runs without the person, so the resubmission finishes on its own and no email goes out. `no_recipient`: no address is known for the person and you passed none. `sandbox`: a sandbox session whose recipient has not opted into sandbox email delivery, so the send is only simulated.", + "example": null + }, + "nodes_to_resubmit": { + "type": "array", + "items": { + "type": "object", + "required": [ + "node_id", + "feature" + ], + "properties": { + "node_id": { + "type": "string", + "description": "Node identifier as it appears in the session's workflow definition (for example `feature_ocr`, `feature_liveness`).", + "example": "feature_ocr" + }, + "feature": { + "type": "string", + "description": "Feature type of the node. The aliases `ID_VERIFICATION`, `POA`, `PHONE`, and `EMAIL` are normalized server-side to `OCR`, `PROOF_OF_ADDRESS`, `PHONE_VERIFICATION`, and `EMAIL_VERIFICATION`. `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and `KYB` pass schema validation but are always rejected with the business-rule `400` shown in the examples.", + "enum": [ + "OCR", + "OCR_BACK", + "NFC", + "AML", + "FACE", + "LIVENESS", + "FACE_MATCH", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "PROOF_OF_ADDRESS", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "FACE_SEARCH", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_DOCUMENTS", + "ID_VERIFICATION", + "POA", + "PHONE", + "EMAIL", + "KYB_REGISTRY", + "KYB_KEY_PEOPLE", + "KYB", + "GEOLOCATION" + ], + "example": "OCR" + } + } + } + }, + "requires_user_action": { + "type": "boolean", + "description": "Whether at least one selected check requires user interaction. This does not predict the final workflow outcome." + }, + "preview_token": { + "type": "string", + "description": "Signed receipt for this exact email, valid for 15 minutes. Pass it as `email_preview_token` to [Update Session Status](/sessions-api/update-status). An empty string when there is no recipient.", + "example": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + } + }, + "examples": { + "Will Be Sent": { + "summary": "Default recipient: the address verified in the flow", + "value": { + "recipient": "person@example.com", + "recipient_source": "email_verification", + "sender": "Example App ", + "sender_name": "Example App", + "sender_email": "no-reply@mail.example.com", + "sender_kind": "customer", + "subject": "Action Required: Complete your verification in Example App", + "html": "...", + "language": "en", + "will_be_sent": true, + "not_sent_reason": null, + "nodes_to_resubmit": [ + { + "node_id": "feature_poa_1", + "feature": "PROOF_OF_ADDRESS" + } + ], + "requires_user_action": true, + "preview_token": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + }, + "No User Action": { + "summary": "Only backend steps are selected, so no email goes out", + "value": { + "recipient": "person@example.com", + "recipient_source": "contact_details", + "sender": "Example App ", + "sender_name": "Example App", + "sender_email": "no-reply@mail.example.com", + "sender_kind": "customer", + "subject": "Action Required: Complete your verification in Example App", + "html": "...", + "language": "en", + "will_be_sent": false, + "not_sent_reason": "no_user_action", + "nodes_to_resubmit": [ + { + "node_id": "feature_aml", + "feature": "AML" + } + ], + "requires_user_action": false, + "preview_token": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + } + } + } + }, + "headers": { + "Cache-Control": { + "description": "Always `no-store`: the preview carries verification content and must not be cached.", + "schema": { + "type": "string", + "example": "no-store" + } + } + } + }, + "400": { + "description": "The session is not in a status this endpoint accepts, it is already `Resubmitted`, the selected steps cannot be resubmitted, or a field is invalid.", + "content": { + "application/json": { + "examples": { + "Wrong Current Status": { + "summary": "Session is not in a status this endpoint accepts", + "value": { + "detail": "Only sessions with status Approved, Declined, In Review, Kyc Expired, Abandoned, Resubmitted can be updated. Current status: In Progress" + } + }, + "Already Resubmitted": { + "summary": "The session is already Resubmitted", + "value": [ + "The new status is the same as the current status." + ] + }, + "Nothing To Resubmit": { + "summary": "Auto-select found no step that needs resubmission", + "value": { + "nodes_to_resubmit": "No features found that need resubmission. All features are already approved." + } + }, + "Invalid Email": { + "summary": "email_address is not an email address", + "value": { + "email_address": [ + "Enter a valid email address." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials, a caller without the `write:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no write access to decisions." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "The steps to resubmit are all backend-only (AML, DATABASE_VALIDATION, IP_ANALYSIS), and the session was already resubmitted for backend-only steps within the last 30 seconds, so Update Session Status would refuse the resubmission. Retry after the interval in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Backend-only resubmit throttled": { + "value": { + "detail": "This session was already resubmitted for backend-only features within the last 30 seconds; repeating it cannot change the outcome. Wait before resubmitting again." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.preview_resubmission(\n \"11111111-2222-3333-4444-555555555555\",\n nodes_to_resubmit=[{\n \"node_id\": \"feature_poa_1\",\n \"feature\": \"POA\",\n }],\n email_language=\"en\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.previewResubmission(\"11111111-2222-3333-4444-555555555555\", {\n nodes_to_resubmit: [{\n node_id: \"feature_poa_1\",\n feature: \"POA\",\n }],\n email_language: \"en\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.PreviewResubmission(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsPreviewResubmissionParams{\n\tNodesToResubmit: didit.Some([]didit.SessionsPreviewResubmissionParamsNodesToResubmit{{\n\t\tNodeID: \"feature_poa_1\",\n\t\tFeature: \"POA\",\n\t}}),\n\tEmailLanguage: didit.String(\"en\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.SessionsPreviewResubmissionParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().previewResubmission(\"11111111-2222-3333-4444-555555555555\", SessionsPreviewResubmissionParams.builder()\n .nodesToResubmit(List.of(SessionsPreviewResubmissionParams.NodesToResubmit.builder()\n .nodeId(\"feature_poa_1\")\n .feature(SessionsPreviewResubmissionParams.NodesToResubmit.Feature.of(\"POA\"))\n .build()))\n .emailLanguage(\"en\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->previewResubmission('11111111-2222-3333-4444-555555555555', [\n 'nodes_to_resubmit' => [[\n 'node_id' => 'feature_poa_1',\n 'feature' => 'POA',\n ]],\n 'email_language' => 'en',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.preview_resubmission(\n \"11111111-2222-3333-4444-555555555555\",\n nodes_to_resubmit: [{\n node_id: \"feature_poa_1\",\n feature: \"POA\",\n }],\n email_language: \"en\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.PreviewResubmissionAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsPreviewResubmissionParams\n{\n NodesToResubmit = new SessionsPreviewResubmissionParamsNodesToResubmit[]\n {\n new()\n {\n NodeId = \"feature_poa_1\",\n Feature = \"POA\",\n },\n },\n EmailLanguage = \"en\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/{record_uuid}/": { + "get": { + "summary": "Retrieve routing rule", + "description": "Requires `read:cases` for the requested application.", + "operationId": "retrieve_case_routing_rules", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "record_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRule" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "404": { + "description": "Record not found in this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "patch": { + "summary": "Update routing rule", + "description": "Requires `write:cases` for the requested application.", + "operationId": "update_case_routing_rules", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "record_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRule" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "404": { + "description": "Record not found in this application." + }, + "409": { + "description": "The routing rule changed since expected_version. Review the latest rule before retrying." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRuleUpdate" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.update(\n \"11111111-2222-3333-4444-555555555555\",\n name=\"your-name\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.update(\"11111111-2222-3333-4444-555555555555\", {\n name: \"your-name\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CaseRoutingRuleUpdate{\n\tName: didit.String(\"your-name\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CaseRoutingRuleUpdate;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().update(\"11111111-2222-3333-4444-555555555555\", CaseRoutingRuleUpdate.builder()\n .name(\"your-name\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->update('11111111-2222-3333-4444-555555555555', [\n 'name' => 'your-name',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.update(\n \"11111111-2222-3333-4444-555555555555\",\n name: \"your-name\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new CaseRoutingRuleUpdate\n{\n Name = \"your-name\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/case-requests/{request_uuid}/": { + "get": { + "operationId": "case_requests_retrieve", + "x-didit-sdk": { + "resource": "case_requests", + "method": "retrieve", + "auth": "device_token", + "server": false + }, + "parameters": [ + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPublic" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + } + }, + "security": [ + { + "CaseRequestToken": [] + } + ], + "description": "Access one customer request using its request-specific token. No private case findings, original identity data or recipient address are returned. Actionable requests expire after seven days. Cancellation or case resolution invalidates the link. Responses are immutable and remain evidence for reviewer assessment; they never approve or update a source verification." + }, + "post": { + "operationId": "case_requests_create", + "x-didit-sdk": { + "resource": "case_requests", + "method": "respond", + "auth": "device_token", + "server": false + }, + "parameters": [ + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseCustomerResponseRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseCustomerResponseRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseCustomerResponseResult" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "400": { + "description": "Invalid inputs, unsupported purpose, unavailable verified contact or an existing immutable response." + }, + "409": { + "description": "The preview changed or expired. Preview again before confirming." + } + }, + "security": [ + { + "CaseRequestToken": [] + } + ], + "description": "Access one customer request using its request-specific token. No private case findings, original identity data or recipient address are returned. Actionable requests expire after seven days. Cancellation or case resolution invalidates the link. Responses are immutable and remain evidence for reviewer assessment; they never approve or update a source verification." + } + }, + "/v3/fraud/checks/plan/": { + "post": { + "operationId": "plan_create", + "x-didit-sdk": { + "resource": "fraud_checks", + "method": "plan", + "auth": "api_key", + "server": true + }, + "description": "Validate selected checks and estimate their price without executing, creating a session or billing.", + "summary": "Plan a Fraud Check", + "tags": [ + "Fraud Check" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + }, + "example": { + "client_reference": "review-attempt-001", + "subject": { + "identifiers": [ + { + "type": "email", + "value": "applicant@example.com" + } + ], + "attributes": { + "first_name": "Alex", + "last_name": "Example" + } + }, + "checks": { + "include": [ + "networks_same_org", + "networks_cross_org", + "email_risk" + ] + } + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckPlan" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudBadRequestErrorMessage" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.fraud_checks.plan(\n client_reference=\"review-attempt-001\",\n subject={\n \"identifiers\": [{\n \"type\": \"email\",\n \"value\": \"applicant@example.com\",\n }],\n \"attributes\": {\n \"first_name\": \"Alex\",\n \"last_name\": \"Example\",\n },\n },\n checks={\n \"include\": [\"networks_same_org\", \"networks_cross_org\", \"email_risk\"],\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.fraudChecks.plan({\n client_reference: \"review-attempt-001\",\n subject: {\n identifiers: [{\n type: \"email\",\n value: \"applicant@example.com\",\n }],\n attributes: {\n first_name: \"Alex\",\n last_name: \"Example\",\n },\n },\n checks: {\n include: [\"networks_same_org\", \"networks_cross_org\", \"email_risk\"],\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.FraudChecks.Plan(ctx, didit.FraudCheckRequestRequest{\n\tClientReference: didit.String(\"review-attempt-001\"),\n\tSubject: didit.FraudCheckSubjectRequest{\n\t\tIdentifiers: []didit.FraudCheckIdentifierRequest{{\n\t\t\tType: didit.FraudTypeEnum(\"email\"),\n\t\t\tValue: \"applicant@example.com\",\n\t\t}},\n\t\tAttributes: didit.Some(didit.FraudCheckAttributesRequest{\n\t\t\tFirstName: didit.String(\"Alex\"),\n\t\t\tLastName: didit.String(\"Example\"),\n\t\t}),\n\t},\n\tChecks: didit.Some(didit.FraudCheckModuleSelectionRequest{\n\t\tInclude: didit.Some([]didit.FraudExcludeEnum{didit.FraudExcludeEnum(\"networks_same_org\"), didit.FraudExcludeEnum(\"networks_cross_org\"), didit.FraudExcludeEnum(\"email_risk\")}),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.FraudCheckAttributesRequest;\nimport me.didit.types.FraudCheckIdentifierRequest;\nimport me.didit.types.FraudCheckModuleSelectionRequest;\nimport me.didit.types.FraudCheckRequestRequest;\nimport me.didit.types.FraudCheckSubjectRequest;\nimport me.didit.types.FraudExcludeEnum;\nimport me.didit.types.FraudTypeEnum;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.fraudChecks().plan(FraudCheckRequestRequest.builder()\n .clientReference(\"review-attempt-001\")\n .subject(FraudCheckSubjectRequest.builder()\n .identifiers(List.of(FraudCheckIdentifierRequest.builder()\n .type(FraudTypeEnum.of(\"email\"))\n .value(\"applicant@example.com\")\n .build()))\n .attributes(FraudCheckAttributesRequest.builder()\n .firstName(\"Alex\")\n .lastName(\"Example\")\n .build())\n .build())\n .checks(FraudCheckModuleSelectionRequest.builder()\n .include(List.of(FraudExcludeEnum.of(\"networks_same_org\"), FraudExcludeEnum.of(\"networks_cross_org\"), FraudExcludeEnum.of(\"email_risk\")))\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "fraudChecks->plan([\n 'client_reference' => 'review-attempt-001',\n 'subject' => [\n 'identifiers' => [[\n 'type' => 'email',\n 'value' => 'applicant@example.com',\n ]],\n 'attributes' => [\n 'first_name' => 'Alex',\n 'last_name' => 'Example',\n ],\n ],\n 'checks' => [\n 'include' => ['networks_same_org', 'networks_cross_org', 'email_risk'],\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.fraud_checks.plan(\n client_reference: \"review-attempt-001\",\n subject: {\n identifiers: [{\n type: \"email\",\n value: \"applicant@example.com\",\n }],\n attributes: {\n first_name: \"Alex\",\n last_name: \"Example\",\n },\n },\n checks: {\n include: [\"networks_same_org\", \"networks_cross_org\", \"email_risk\"],\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.FraudChecks.PlanAsync(new FraudCheckRequestRequest\n{\n ClientReference = \"review-attempt-001\",\n Subject = new()\n {\n Identifiers = new FraudCheckIdentifierRequest[]\n {\n new()\n {\n Type = \"email\",\n Value = \"applicant@example.com\",\n },\n },\n Attributes = new FraudCheckAttributesRequest\n {\n FirstName = \"Alex\",\n LastName = \"Example\",\n },\n },\n Checks = new FraudCheckModuleSelectionRequest\n {\n Include = new FraudExcludeEnum[] { \"networks_same_org\", \"networks_cross_org\", \"email_risk\" },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/": { + "get": { + "summary": "List routing rules", + "description": "Requires `list:cases` for the requested application.", + "operationId": "list_case_routing_rules", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000000, + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseRoutingRule" + } + }, + "count": { + "type": "integer", + "description": "Matching count, capped at 100. Follow next to continue beyond the cap." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.routing_rules.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.routingRules.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.RoutingRules.ListAutoPaging(ctx, didit.CasesRoutingRulesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().routingRules().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.routing_rules.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.RoutingRules.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "summary": "Create routing rule", + "description": "Requires `write:cases` for the requested application. Set publish=false to create an unpublished draft; Publish creates its first live version. Discarding a never-published draft deletes it.", + "operationId": "create_case_routing_rules", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "responses": { + "201": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRule" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRuleCreate" + }, + "example": { + "name": "AML screening alerts", + "conditions": [ + { + "field": "source", + "operator": "equals", + "value": "AML_SCREENING" + } + ], + "blueprint": "b1de0000-0000-4000-8000-000000000002" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.create(\n name=\"AML screening alerts\",\n conditions=[{\n \"field\": \"source\",\n \"operator\": \"equals\",\n \"value\": \"AML_SCREENING\",\n }],\n blueprint=\"b1de0000-0000-4000-8000-000000000002\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.create({\n name: \"AML screening alerts\",\n conditions: [{\n field: \"source\",\n operator: \"equals\",\n value: \"AML_SCREENING\",\n }],\n blueprint: \"b1de0000-0000-4000-8000-000000000002\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.Create(ctx, didit.CaseRoutingRuleCreate{\n\tName: \"AML screening alerts\",\n\tConditions: []didit.CaseRoutingConditionParam{{\n\t\tField: \"source\",\n\t\tOperator: \"equals\",\n\t\tValue: \"AML_SCREENING\",\n\t}},\n\tBlueprint: \"b1de0000-0000-4000-8000-000000000002\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CaseRoutingCondition;\nimport me.didit.types.CaseRoutingRuleCreate;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().create(CaseRoutingRuleCreate.builder()\n .name(\"AML screening alerts\")\n .conditions(List.of(CaseRoutingCondition.builder()\n .field(CaseRoutingCondition.Field.of(\"source\"))\n .operator(CaseRoutingCondition.Operator.of(\"equals\"))\n .value(CaseRoutingCondition.Value.ofString(\"AML_SCREENING\"))\n .build()))\n .blueprint(\"b1de0000-0000-4000-8000-000000000002\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->create([\n 'name' => 'AML screening alerts',\n 'conditions' => [[\n 'field' => 'source',\n 'operator' => 'equals',\n 'value' => 'AML_SCREENING',\n ]],\n 'blueprint' => 'b1de0000-0000-4000-8000-000000000002',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.create(\n name: \"AML screening alerts\",\n conditions: [{\n field: \"source\",\n operator: \"equals\",\n value: \"AML_SCREENING\",\n }],\n blueprint: \"b1de0000-0000-4000-8000-000000000002\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.CreateAsync(new CaseRoutingRuleCreate\n{\n Name = \"AML screening alerts\",\n Conditions = new CaseRoutingCondition[]\n {\n new()\n {\n Field = \"source\",\n Operator = \"equals\",\n Value = \"AML_SCREENING\",\n },\n },\n Blueprint = \"b1de0000-0000-4000-8000-000000000002\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/{record_uuid}/publish/": { + "post": { + "operationId": "publish_case_routing_rule", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "publish", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Assignment" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftPreconditionRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftPreconditionRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftPreconditionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationCaseRoutingRule" + } + } + }, + "description": "" + }, + "403": { + "description": "Case access is required." + }, + "409": { + "description": "The published version or draft revision has changed. Reload before retrying." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.publish(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version=0,\n expected_revision=\"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.publish(\"11111111-2222-3333-4444-555555555555\", {\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.Publish(ctx, \"11111111-2222-3333-4444-555555555555\", didit.RoutingPublicationPolicyDraftPreconditionRequest{\n\tExpectedVersion: 0,\n\tExpectedRevision: didit.String(\"11111111-2222-3333-4444-555555555555\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.RoutingPublicationPolicyDraftPreconditionRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().publish(\"11111111-2222-3333-4444-555555555555\", RoutingPublicationPolicyDraftPreconditionRequest.builder()\n .expectedVersion(0L)\n .expectedRevision(\"11111111-2222-3333-4444-555555555555\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->publish('11111111-2222-3333-4444-555555555555', [\n 'expected_version' => 0,\n 'expected_revision' => '11111111-2222-3333-4444-555555555555',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.publish(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.PublishAsync(\"11111111-2222-3333-4444-555555555555\", new RoutingPublicationPolicyDraftPreconditionRequest\n{\n ExpectedVersion = 0,\n ExpectedRevision = \"11111111-2222-3333-4444-555555555555\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/{blueprint_uuid}/publish/": { + "post": { + "operationId": "publish_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "publish", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftPreconditionRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftPreconditionRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftPreconditionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationCaseBlueprint" + } + } + }, + "description": "" + }, + "409": { + "description": "Published version or saved draft revision changed. Reload before retrying." + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.publish(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version=0,\n expected_revision=\"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.publish(\"11111111-2222-3333-4444-555555555555\", {\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.Publish(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasePublicationPolicyDraftPreconditionRequest{\n\tExpectedVersion: 0,\n\tExpectedRevision: didit.String(\"11111111-2222-3333-4444-555555555555\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasePublicationPolicyDraftPreconditionRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().publish(\"11111111-2222-3333-4444-555555555555\", CasePublicationPolicyDraftPreconditionRequest.builder()\n .expectedVersion(0L)\n .expectedRevision(\"11111111-2222-3333-4444-555555555555\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->publish('11111111-2222-3333-4444-555555555555', [\n 'expected_version' => 0,\n 'expected_revision' => '11111111-2222-3333-4444-555555555555',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.publish(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.PublishAsync(\"11111111-2222-3333-4444-555555555555\", new CasePublicationPolicyDraftPreconditionRequest\n{\n ExpectedVersion = 0,\n ExpectedRevision = \"11111111-2222-3333-4444-555555555555\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/preview/": { + "post": { + "operationId": "organization_application_cases_requests_preview_create", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "preview", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPreviewRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPreviewRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPreviewRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPreviewResponse" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "400": { + "description": "Invalid inputs, unsupported purpose, unavailable verified contact or an existing immutable response." + }, + "409": { + "description": "The preview changed or expired. Preview again before confirming." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Render the production email without sending or changing the case. Returns the exact recipient, sender, subject, body and a signed preview_token. Send the same contact_id, purpose, language, request_id, expires_at and preview_token to confirm within 15 minutes.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.requests.preview(\n \"11111111-2222-3333-4444-555555555555\",\n contact_id=\"11111111-2222-3333-4444-555555555555\",\n purpose=\"identity_document\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.requests.preview(\"11111111-2222-3333-4444-555555555555\", {\n contact_id: \"11111111-2222-3333-4444-555555555555\",\n purpose: \"identity_document\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Requests.Preview(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CustomerMessagingCaseRequestPreviewRequest{\n\tContactID: \"11111111-2222-3333-4444-555555555555\",\n\tPurpose: didit.CustomerMessagingPurposeEnum(\"identity_document\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CustomerMessagingCaseRequestPreviewRequest;\nimport me.didit.types.CustomerMessagingPurposeEnum;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().requests().preview(\"11111111-2222-3333-4444-555555555555\", CustomerMessagingCaseRequestPreviewRequest.builder()\n .contactId(\"11111111-2222-3333-4444-555555555555\")\n .purpose(CustomerMessagingPurposeEnum.of(\"identity_document\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->preview('11111111-2222-3333-4444-555555555555', [\n 'contact_id' => '11111111-2222-3333-4444-555555555555',\n 'purpose' => 'identity_document',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.requests.preview(\n \"11111111-2222-3333-4444-555555555555\",\n contact_id: \"11111111-2222-3333-4444-555555555555\",\n purpose: \"identity_document\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Requests.PreviewAsync(\"11111111-2222-3333-4444-555555555555\", new CustomerMessagingCaseRequestPreviewRequest\n{\n ContactId = \"11111111-2222-3333-4444-555555555555\",\n Purpose = \"identity_document\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/action-receipts/": { + "get": { + "operationId": "action_receipts_retrieve", + "x-didit-sdk": { + "resource": "cases", + "method": "list_action_receipts", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "query", + "name": "offset", + "schema": { + "type": "integer", + "maximum": 1000000, + "minimum": 0, + "default": 0 + } + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseActionReceiptPage" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "List subject-action receipts, newest first, with at most 20 results per page. Use next_offset for the next page. Input note text is not exposed in the history.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.list_action_receipts(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.listActionReceipts(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.ListActionReceipts(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasesListActionReceiptsParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().listActionReceipts(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->listActionReceipts('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.list_action_receipts(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ListActionReceiptsAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/notes/{note_uuid}/": { + "delete": { + "operationId": "delete_network_note", + "x-didit-sdk": { + "resource": "networks.notes", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "summary": "Delete your network note", + "description": "Requires write:networks and the authenticated author of the note. Deletion is irreversible. Connected account and verification data are unaffected.", + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Network UUID." + }, + { + "name": "note_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Console user access token in the form Bearer ." + } + ], + "responses": { + "204": { + "description": "Note deleted." + }, + "401": { + "description": "Authentication is required." + }, + "403": { + "description": "Missing permission or Networks access." + }, + "404": { + "description": "Network or note is not visible to this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\nclient = Didit()\n\nclient.networks.notes.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n extra_headers={\"Authorization\": \"Bearer YOUR_CONSOLE_USER_TOKEN\"},\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nconst didit = new Didit({\n fetch: (url, init) => {\n const headers = new Headers(init?.headers);\n headers.set(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\");\n return fetch(url, { ...init, headers });\n },\n});\n\nawait didit.networks.notes.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nclient := didit.NewClient()\n\nif err := client.Networks.Notes.Delete(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\", didit.NetworksNotesDeleteParams{\n\tAuthorization: \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n}); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.core.RequestOptions;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nDidit client = new Didit();\n\nclient.networks().notes().delete(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\", RequestOptions.builder()\n .header(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\")\n .build());" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->notes->delete('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555', [\n 'authorization' => 'Bearer YOUR_CONSOLE_USER_TOKEN',\n]);" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\ndidit = Didit::Client.new\n\ndidit.networks.notes.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n request_options: { extra_headers: { \"Authorization\" => \"Bearer YOUR_CONSOLE_USER_TOKEN\" } }\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nusing var client = new DiditClient();\n\nawait client.Networks.Notes.DeleteAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\", requestOptions: new RequestOptions\n{\n Headers = new Dictionary\n {\n [\"Authorization\"] = \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n },\n});" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/action-receipts/{receipt_uuid}/retry/": { + "post": { + "operationId": "action_receipts_retry_create", + "x-didit-sdk": { + "resource": "cases", + "method": "retry_action_receipt", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "receipt_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseActionReceipt" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Retry only this failed subject action using its saved inputs. Current write permission is checked. Applied actions return their receipt without executing again. Case closure is independent.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.retry_action_receipt(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.retryActionReceipt(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RetryActionReceipt(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().retryActionReceipt(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->retryActionReceipt('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.retry_action_receipt(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RetryActionReceiptAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/{blueprint_uuid}/draft/": { + "get": { + "operationId": "get_case_blueprint_draft", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "retrieve_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftResponse" + } + } + }, + "description": "" + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.retrieve_draft(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.retrieveDraft(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.RetrieveDraft(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().retrieveDraft(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->retrieveDraft('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.retrieve_draft(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.RetrieveDraftAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "put": { + "operationId": "save_case_blueprint_draft", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "save_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftWriteRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftWriteRequest" + }, + "example": { + "expected_version": 1, + "expected_revision": "4e5f0000-0000-4000-8000-000000000003", + "changes": { + "max_active_cases": 20 + } + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftWriteRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraft" + } + } + }, + "description": "" + }, + "409": { + "description": "Published version or saved draft revision changed. Reload before retrying." + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.save_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version=1,\n expected_revision=\"4e5f0000-0000-4000-8000-000000000003\",\n changes={\"max_active_cases\": 20},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.saveDraft(\"11111111-2222-3333-4444-555555555555\", {\n expected_version: 1,\n expected_revision: \"4e5f0000-0000-4000-8000-000000000003\",\n changes: { max_active_cases: 20 },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.SaveDraft(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasePublicationPolicyDraftWriteRequest{\n\tExpectedVersion: 1,\n\tExpectedRevision: didit.String(\"4e5f0000-0000-4000-8000-000000000003\"),\n\tChanges: map[string]any{\"max_active_cases\": 20},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.CasePublicationPolicyDraftWriteRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().saveDraft(\"11111111-2222-3333-4444-555555555555\", CasePublicationPolicyDraftWriteRequest.builder()\n .expectedVersion(1L)\n .expectedRevision(\"4e5f0000-0000-4000-8000-000000000003\")\n .changes(Map.of(\"max_active_cases\", 20))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->saveDraft('11111111-2222-3333-4444-555555555555', [\n 'expected_version' => 1,\n 'expected_revision' => '4e5f0000-0000-4000-8000-000000000003',\n 'changes' => ['max_active_cases' => 20],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.save_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version: 1,\n expected_revision: \"4e5f0000-0000-4000-8000-000000000003\",\n changes: { max_active_cases: 20 }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.SaveDraftAsync(\"11111111-2222-3333-4444-555555555555\", new CasePublicationPolicyDraftWriteRequest\n{\n ExpectedVersion = 1,\n ExpectedRevision = \"4e5f0000-0000-4000-8000-000000000003\",\n Changes = new Dictionary { [\"max_active_cases\"] = 20 },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "operationId": "discard_case_blueprint_draft", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "discard_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "204": { + "description": "No response body" + }, + "409": { + "description": "Published version or saved draft revision changed. Reload before retrying." + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftPreconditionRequest" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.cases.blueprints.discard_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version=0,\n expected_revision=\"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.cases.blueprints.discardDraft(\"11111111-2222-3333-4444-555555555555\", {\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\",\n});" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.Cases.Blueprints.DiscardDraft(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasePublicationPolicyDraftPreconditionRequest{\n\tExpectedVersion: 0,\n\tExpectedRevision: didit.String(\"11111111-2222-3333-4444-555555555555\"),\n}); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasePublicationPolicyDraftPreconditionRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().blueprints().discardDraft(\"11111111-2222-3333-4444-555555555555\", CasePublicationPolicyDraftPreconditionRequest.builder()\n .expectedVersion(0L)\n .expectedRevision(\"11111111-2222-3333-4444-555555555555\")\n .build());" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->discardDraft('11111111-2222-3333-4444-555555555555', [\n 'expected_version' => 0,\n 'expected_revision' => '11111111-2222-3333-4444-555555555555',\n]);" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.blueprints.discard_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\"\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Cases.Blueprints.DiscardDraftAsync(\"11111111-2222-3333-4444-555555555555\", new CasePublicationPolicyDraftPreconditionRequest\n{\n ExpectedVersion = 0,\n ExpectedRevision = \"11111111-2222-3333-4444-555555555555\",\n});" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/teams/{record_uuid}/": { + "get": { + "summary": "Retrieve review team", + "description": "Requires `read:cases` for the requested application.", + "operationId": "retrieve_case_teams", + "x-didit-sdk": { + "resource": "cases.teams", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "record_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeam" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "404": { + "description": "Record not found in this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.teams.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.teams.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Teams.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().teams().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->teams->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.teams.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Teams.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "patch": { + "summary": "Update review team", + "description": "Requires `write:cases` for the requested application.", + "operationId": "update_case_teams", + "x-didit-sdk": { + "resource": "cases.teams", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "record_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeam" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "404": { + "description": "Record not found in this application." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeamUpdate" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.teams.update(\n \"11111111-2222-3333-4444-555555555555\",\n name=\"your-name\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.teams.update(\"11111111-2222-3333-4444-555555555555\", {\n name: \"your-name\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Teams.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CaseReviewTeamUpdate{\n\tName: didit.String(\"your-name\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CaseReviewTeamUpdate;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().teams().update(\"11111111-2222-3333-4444-555555555555\", CaseReviewTeamUpdate.builder()\n .name(\"your-name\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->teams->update('11111111-2222-3333-4444-555555555555', [\n 'name' => 'your-name',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.teams.update(\n \"11111111-2222-3333-4444-555555555555\",\n name: \"your-name\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Teams.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new CaseReviewTeamUpdate\n{\n Name = \"your-name\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/report/": { + "get": { + "operationId": "download_network_report", + "x-didit-sdk": { + "resource": "networks", + "method": "download_report", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "summary": "Download a network evidence report", + "description": "Download a PDF of the current organization network, its observed account connections, signal references and review status. Requires read:networks. Global intelligence and private analyst notes are not included. Each account and signal list is capped at 500 entries and the report states when it is incomplete. The request does not change network status. Errors are returned as JSON.", + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Network UUID." + }, + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Console user access token in the form Bearer ." + } + ], + "responses": { + "200": { + "description": "Network evidence report. Cache-Control: no-store.", + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "description": "Authentication is required." + }, + "403": { + "description": "Missing permission or Networks access." + }, + "404": { + "description": "Network or note is not visible to this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\nclient = Didit()\n\ncontent = client.networks.download_report(\n \"11111111-2222-3333-4444-555555555555\",\n extra_headers={\"Authorization\": \"Bearer YOUR_CONSOLE_USER_TOKEN\"},\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nconst didit = new Didit({\n fetch: (url, init) => {\n const headers = new Headers(init?.headers);\n headers.set(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\");\n return fetch(url, { ...init, headers });\n },\n});\n\nconst content = await didit.networks.downloadReport(\"11111111-2222-3333-4444-555555555555\");\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nclient := didit.NewClient()\n\nresp, err := client.Networks.DownloadReport(ctx, \"11111111-2222-3333-4444-555555555555\", didit.NetworksDownloadReportParams{\n\tAuthorization: \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.core.RequestOptions;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nDidit client = new Didit();\n\nclient.networks().downloadReport(\"11111111-2222-3333-4444-555555555555\", RequestOptions.builder()\n .header(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\")\n .build())\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->downloadReport('11111111-2222-3333-4444-555555555555', [\n 'authorization' => 'Bearer YOUR_CONSOLE_USER_TOKEN',\n]);\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\ndidit = Didit::Client.new\n\ncontent = didit.networks.download_report(\n \"11111111-2222-3333-4444-555555555555\",\n request_options: { extra_headers: { \"Authorization\" => \"Bearer YOUR_CONSOLE_USER_TOKEN\" } }\n)\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nusing var client = new DiditClient();\n\nvar content = await client.Networks.DownloadReportAsync(\"11111111-2222-3333-4444-555555555555\", requestOptions: new RequestOptions\n{\n Headers = new Dictionary\n {\n [\"Authorization\"] = \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n },\n});\nawait content.SaveToAsync(\"download.pdf\");" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/notes/": { + "get": { + "operationId": "list_network_notes", + "x-didit-sdk": { + "resource": "networks.notes", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "summary": "List private network notes", + "description": "Console users with read:networks can read notes on a network visible to the application. Notes stay within the organization and are not contributed to shared intelligence. Newest notes appear first.", + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Network UUID." + }, + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Console user access token in the form Bearer ." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 30 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000000, + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "A page of notes. Count is capped at 100; use next to continue.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkNote" + } + } + } + } + } + } + }, + "401": { + "description": "Authentication is required." + }, + "403": { + "description": "Missing permission or Networks access." + }, + "404": { + "description": "Network or note is not visible to this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\nclient = Didit()\n\nfor item in client.networks.notes.list(\n \"11111111-2222-3333-4444-555555555555\",\n extra_headers={\"Authorization\": \"Bearer YOUR_CONSOLE_USER_TOKEN\"},\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nconst didit = new Didit({\n fetch: (url, init) => {\n const headers = new Headers(init?.headers);\n headers.set(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\");\n return fetch(url, { ...init, headers });\n },\n});\n\nfor await (const item of didit.networks.notes.list(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nclient := didit.NewClient()\n\niter := client.Networks.Notes.ListAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.NetworksNotesListParams{\n\tAuthorization: \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.core.RequestOptions;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nDidit client = new Didit();\n\nfor (var item : client.networks().notes().list(\"11111111-2222-3333-4444-555555555555\", RequestOptions.builder()\n .header(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\")\n .build())) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->notes->list('11111111-2222-3333-4444-555555555555', [\n 'authorization' => 'Bearer YOUR_CONSOLE_USER_TOKEN',\n]) as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\ndidit = Didit::Client.new\n\ndidit.networks.notes.list(\n \"11111111-2222-3333-4444-555555555555\",\n request_options: { extra_headers: { \"Authorization\" => \"Bearer YOUR_CONSOLE_USER_TOKEN\" } }\n).auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Networks.Notes.ListAsync(\"11111111-2222-3333-4444-555555555555\", requestOptions: new RequestOptions\n{\n Headers = new Dictionary\n {\n [\"Authorization\"] = \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n },\n}))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "operationId": "create_network_note", + "x-didit-sdk": { + "resource": "networks.notes", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "summary": "Add a private network note", + "description": "Requires a console user with write:networks. The author is assigned from the authenticated user. Supply a new random UUID client_reference for each note; retry the same body and reference after an uncertain response to avoid duplicates. Reusing a reference for another body, author or network returns 409. Notes follow the surviving investigation when networks merge.", + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Network UUID." + }, + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Console user access token in the form Bearer ." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "comment", + "client_reference" + ], + "properties": { + "comment": { + "type": "string", + "minLength": 1, + "maxLength": 10000 + }, + "client_reference": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Saved private note.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkNote" + } + } + } + }, + "200": { + "description": "Saved private note.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkNote" + } + } + } + }, + "400": { + "description": "Invalid or blank note." + }, + "409": { + "description": "The reference was already used for another note." + }, + "401": { + "description": "Authentication is required." + }, + "403": { + "description": "Missing permission or Networks access." + }, + "404": { + "description": "Network or note is not visible to this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\nclient = Didit()\n\nresult = client.networks.notes.create(\n \"11111111-2222-3333-4444-555555555555\",\n comment=\"your-comment\",\n client_reference=\"11111111-2222-3333-4444-555555555555\",\n extra_headers={\"Authorization\": \"Bearer YOUR_CONSOLE_USER_TOKEN\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nconst didit = new Didit({\n fetch: (url, init) => {\n const headers = new Headers(init?.headers);\n headers.set(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\");\n return fetch(url, { ...init, headers });\n },\n});\n\nconst result = await didit.networks.notes.create(\"11111111-2222-3333-4444-555555555555\", {\n comment: \"your-comment\",\n client_reference: \"11111111-2222-3333-4444-555555555555\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nclient := didit.NewClient()\n\nresult, err := client.Networks.Notes.Create(ctx, \"11111111-2222-3333-4444-555555555555\", didit.NetworksNotesCreateParams{\n\tComment: \"your-comment\",\n\tClientReference: \"11111111-2222-3333-4444-555555555555\",\n\tAuthorization: \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.core.RequestOptions;\nimport me.didit.types.NetworksNotesCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nDidit client = new Didit();\n\nvar result = client.networks().notes().create(\"11111111-2222-3333-4444-555555555555\", NetworksNotesCreateParams.builder()\n .comment(\"your-comment\")\n .clientReference(\"11111111-2222-3333-4444-555555555555\")\n .build(), RequestOptions.builder()\n .header(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->notes->create('11111111-2222-3333-4444-555555555555', [\n 'comment' => 'your-comment',\n 'client_reference' => '11111111-2222-3333-4444-555555555555',\n 'authorization' => 'Bearer YOUR_CONSOLE_USER_TOKEN',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\ndidit = Didit::Client.new\n\nresult = didit.networks.notes.create(\n \"11111111-2222-3333-4444-555555555555\",\n comment: \"your-comment\",\n client_reference: \"11111111-2222-3333-4444-555555555555\",\n request_options: { extra_headers: { \"Authorization\" => \"Bearer YOUR_CONSOLE_USER_TOKEN\" } }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.Notes.CreateAsync(\"11111111-2222-3333-4444-555555555555\", new NetworksNotesCreateParams\n{\n Comment = \"your-comment\",\n ClientReference = \"11111111-2222-3333-4444-555555555555\",\n}, requestOptions: new RequestOptions\n{\n Headers = new Dictionary\n {\n [\"Authorization\"] = \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/{record_uuid}/draft/": { + "get": { + "operationId": "get_case_routing_rule_draft", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "retrieve_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Assignment" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftResponse" + } + } + }, + "description": "" + }, + "403": { + "description": "Case access is required." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.retrieve_draft(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.retrieveDraft(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.RetrieveDraft(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().retrieveDraft(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->retrieveDraft('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.retrieve_draft(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.RetrieveDraftAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "put": { + "operationId": "save_case_routing_rule_draft", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "save_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Assignment" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftWriteRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftWriteRequest" + }, + "example": { + "expected_version": 1, + "expected_revision": "4e5f0000-0000-4000-8000-000000000003", + "changes": { + "priority": 10 + } + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftWriteRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraft" + } + } + }, + "description": "" + }, + "403": { + "description": "Case access is required." + }, + "409": { + "description": "The published version or draft revision has changed. Reload before retrying." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.save_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version=1,\n expected_revision=\"4e5f0000-0000-4000-8000-000000000003\",\n changes={\"priority\": 10},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.saveDraft(\"11111111-2222-3333-4444-555555555555\", {\n expected_version: 1,\n expected_revision: \"4e5f0000-0000-4000-8000-000000000003\",\n changes: { priority: 10 },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.SaveDraft(ctx, \"11111111-2222-3333-4444-555555555555\", didit.RoutingPublicationPolicyDraftWriteRequest{\n\tExpectedVersion: 1,\n\tExpectedRevision: didit.String(\"4e5f0000-0000-4000-8000-000000000003\"),\n\tChanges: map[string]any{\"priority\": 10},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.RoutingPublicationPolicyDraftWriteRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().saveDraft(\"11111111-2222-3333-4444-555555555555\", RoutingPublicationPolicyDraftWriteRequest.builder()\n .expectedVersion(1L)\n .expectedRevision(\"4e5f0000-0000-4000-8000-000000000003\")\n .changes(Map.of(\"priority\", 10))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->saveDraft('11111111-2222-3333-4444-555555555555', [\n 'expected_version' => 1,\n 'expected_revision' => '4e5f0000-0000-4000-8000-000000000003',\n 'changes' => ['priority' => 10],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.save_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version: 1,\n expected_revision: \"4e5f0000-0000-4000-8000-000000000003\",\n changes: { priority: 10 }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.SaveDraftAsync(\"11111111-2222-3333-4444-555555555555\", new RoutingPublicationPolicyDraftWriteRequest\n{\n ExpectedVersion = 1,\n ExpectedRevision = \"4e5f0000-0000-4000-8000-000000000003\",\n Changes = new Dictionary { [\"priority\"] = 10 },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "operationId": "discard_case_routing_rule_draft", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "discard_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Assignment" + ], + "responses": { + "204": { + "description": "No response body" + }, + "403": { + "description": "Case access is required." + }, + "409": { + "description": "The published version or draft revision has changed. Reload before retrying." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftPreconditionRequest" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.cases.routing_rules.discard_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version=0,\n expected_revision=\"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.cases.routingRules.discardDraft(\"11111111-2222-3333-4444-555555555555\", {\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\",\n});" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.Cases.RoutingRules.DiscardDraft(ctx, \"11111111-2222-3333-4444-555555555555\", didit.RoutingPublicationPolicyDraftPreconditionRequest{\n\tExpectedVersion: 0,\n\tExpectedRevision: didit.String(\"11111111-2222-3333-4444-555555555555\"),\n}); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.RoutingPublicationPolicyDraftPreconditionRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().routingRules().discardDraft(\"11111111-2222-3333-4444-555555555555\", RoutingPublicationPolicyDraftPreconditionRequest.builder()\n .expectedVersion(0L)\n .expectedRevision(\"11111111-2222-3333-4444-555555555555\")\n .build());" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->discardDraft('11111111-2222-3333-4444-555555555555', [\n 'expected_version' => 0,\n 'expected_revision' => '11111111-2222-3333-4444-555555555555',\n]);" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.routing_rules.discard_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\"\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Cases.RoutingRules.DiscardDraftAsync(\"11111111-2222-3333-4444-555555555555\", new RoutingPublicationPolicyDraftPreconditionRequest\n{\n ExpectedVersion = 0,\n ExpectedRevision = \"11111111-2222-3333-4444-555555555555\",\n});" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/duplicates/": { + "post": { + "operationId": "duplicates_create", + "x-didit-sdk": { + "resource": "cases", + "method": "find_duplicates", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicateQueryRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicateQueryRequest" + }, + "example": { + "subject": { + "type": "user", + "uuid": "5b9a0000-0000-4000-8000-00000000f00d" + } + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicateQueryRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicatePage" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Review existing cases for a subject or owned source record before manual case creation. Both open and resolved investigations are returned. Each page contains at most 20 cases; use next_offset to request another page.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.find_duplicates(\n subject={\n \"type\": \"user\",\n \"uuid\": \"5b9a0000-0000-4000-8000-00000000f00d\",\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.findDuplicates({\n subject: {\n type: \"user\",\n uuid: \"5b9a0000-0000-4000-8000-00000000f00d\",\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.FindDuplicates(ctx, didit.CaseDuplicateCaseDuplicateQueryRequest{\n\tSubject: didit.Some(didit.CaseDuplicateCaseSubjectInputRequest{\n\t\tType: didit.CaseDuplicateTypeEnum(\"user\"),\n\t\tUUID: \"5b9a0000-0000-4000-8000-00000000f00d\",\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CaseDuplicateCaseDuplicateQueryRequest;\nimport me.didit.types.CaseDuplicateCaseSubjectInputRequest;\nimport me.didit.types.CaseDuplicateTypeEnum;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().findDuplicates(CaseDuplicateCaseDuplicateQueryRequest.builder()\n .subject(CaseDuplicateCaseSubjectInputRequest.builder()\n .type(CaseDuplicateTypeEnum.of(\"user\"))\n .uuid(\"5b9a0000-0000-4000-8000-00000000f00d\")\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->findDuplicates([\n 'subject' => [\n 'type' => 'user',\n 'uuid' => '5b9a0000-0000-4000-8000-00000000f00d',\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.find_duplicates(\n subject: {\n type: \"user\",\n uuid: \"5b9a0000-0000-4000-8000-00000000f00d\",\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.FindDuplicatesAsync(new CaseDuplicateCaseDuplicateQueryRequest\n{\n Subject = new CaseDuplicateCaseSubjectInputRequest\n {\n Type = \"user\",\n Uuid = \"5b9a0000-0000-4000-8000-00000000f00d\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/preview/": { + "post": { + "summary": "Test case routing", + "description": "Requires `read:cases` for the requested application. Evaluates current rules or a supplied draft against current reviewer access, availability, skills and capacity. It does not save the draft or reserve reviewer capacity.", + "operationId": "preview_case_routing", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "preview", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingPreview" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingPreviewRequest" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.preview(\n source=\"MANUAL\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.preview({\n source: \"MANUAL\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.Preview(ctx, didit.CaseRoutingPreviewRequest{\n\tSource: \"MANUAL\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CaseRoutingPreviewRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().preview(CaseRoutingPreviewRequest.builder()\n .source(CaseRoutingPreviewRequest.Source.of(\"MANUAL\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->preview([\n 'source' => 'MANUAL',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.preview(source: \"MANUAL\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.PreviewAsync(new CaseRoutingPreviewRequest\n{\n Source = \"MANUAL\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/reviewers/": { + "get": { + "summary": "List eligible case reviewers", + "description": "Requires `list:cases` for the requested application.", + "operationId": "list_case_reviewers", + "x-didit-sdk": { + "resource": "cases", + "method": "list_reviewers", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "format": "uuid" + }, + "email": { + "type": "string", + "format": "email" + }, + "name": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "503": { + "description": "Reviewer access could not be verified. Retry later." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.list_reviewers()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.listReviewers();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.ListReviewers(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().listReviewers();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->listReviewers();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.list_reviewers\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ListReviewersAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/{blueprint_uuid}/history/": { + "get": { + "operationId": "list_case_blueprint_history", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "list_history", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "name": "ordering", + "required": false, + "in": "query", + "description": "Which field to use when ordering the results.", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "offset", + "required": false, + "in": "query", + "description": "The initial index from which to return the results.", + "schema": { + "type": "integer" + } + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPaginatedPolicyRevisionList" + } + } + }, + "description": "" + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.blueprints.list_history(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.blueprints.listHistory(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.Blueprints.ListHistoryAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasesBlueprintsListHistoryParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().blueprints().listHistory(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->listHistory('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.blueprints.list_history(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.Blueprints.ListHistoryAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/session/{sessionId}/signatures/{nodeId}/documents/{documentId}/file/": { + "get": { + "summary": "Download a signed document", + "description": "Download the signed PDF of one document of an E-signature step. Use the `signed_file` path of `signatures[].documents[]` in the decision. The PDF holds the document the signer was shown with their field values stamped and the evidence page (unless `signature_evidence_page` is off). An Advanced (AES) PDF also carries the PAdES signature with its timestamp; a Simple PDF has no digital signature, seal or timestamp. Didit checks the file against `sha256_signed` before returning it.\n\nWorks for User Verification (KYC) and Business Verification (KYB) sessions. Needs `read:sessions`; your API key works. See the [E-signature report](/core-technology/electronic-signature/report-electronic-signature#download-the-signed-documents).", + "operationId": "get_v3_session_signature_document_file", + "x-didit-sdk": { + "resource": "sessions.signatures", + "method": "download_document", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "The User Verification (KYC) or Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "nodeId", + "in": "path", + "required": true, + "description": "The E-signature step: `node_id` of the item in `signatures[]`.", + "schema": { + "type": "string", + "example": "feature_signature" + } + }, + { + "name": "documentId", + "in": "path", + "required": true, + "description": "`id` of the document in `signatures[].documents[]`.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "The signed PDF.", + "headers": { + "Content-Disposition": { + "description": "Attachment with the file name.", + "schema": { + "type": "string", + "example": "attachment; filename=\"agreement-signed.pdf\"" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "403": { + "description": "Missing credentials, or a credential that can't read this session. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "You do not have permission to perform this action." + } + } + } + }, + "404": { + "description": "No signed document matches: the session, step or document doesn't exist, was deleted, or isn't signed yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not signed yet": { + "summary": "The signer hasn't signed", + "value": { + "code": "signature_not_signed", + "detail": "signature_not_signed" + } + }, + "Unknown document": { + "summary": "No such document in this step", + "value": { + "code": "signature_not_found", + "detail": "signature_not_found" + } + } + } + } + } + }, + "409": { + "description": "The stored file no longer matches its recorded SHA-256. Nothing is returned.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Changed file": { + "summary": "Stored file doesn't match its hash", + "value": { + "code": "signature_document_changed", + "detail": "signature_document_changed" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.sessions.signatures.download_document(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.sessions.signatures.downloadDocument(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Sessions.Signatures.DownloadDocument(ctx, \"11111111-2222-3333-4444-555555555555\", \"feature_signature\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().signatures().downloadDocument(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\", \"11111111-2222-3333-4444-555555555555\")\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->signatures->downloadDocument('11111111-2222-3333-4444-555555555555', 'feature_signature', '11111111-2222-3333-4444-555555555555');\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.sessions.signatures.download_document(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Sessions.Signatures.DownloadDocumentAsync(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\", \"11111111-2222-3333-4444-555555555555\");\nawait content.SaveToAsync(\"download.pdf\");" + } + ] + } + }, + "/v3/session/{sessionId}/signatures/{nodeId}/evidence/": { + "get": { + "summary": "Download the signature evidence", + "description": "Download a signer's evidence PDF: the level and format, the signer, the IP address, each document with its original SHA-256 and field values and the SHA-256 of every signed file, plus, for an Advanced (AES) signature, the verified identity session, the code confirmation, the certificate and the timestamp. Use the `evidence_file` path of the signer's item in `signatures[]`.\n\nAlways pass `signature_id` when a Business Verification step has several signers: without it, the route picks one of the step's signers. Needs `read:sessions`; your API key works.", + "operationId": "get_v3_session_signature_evidence", + "x-didit-sdk": { + "resource": "sessions.signatures", + "method": "download_evidence", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "The User Verification (KYC) or Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "nodeId", + "in": "path", + "required": true, + "description": "The E-signature step: `node_id` of the item in `signatures[]`.", + "schema": { + "type": "string", + "example": "feature_signature" + } + }, + { + "name": "signature_id", + "in": "query", + "required": false, + "description": "`id` of the signer's item in `signatures[]`.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "The evidence PDF.", + "headers": { + "Content-Disposition": { + "description": "Attachment with the file name.", + "schema": { + "type": "string", + "example": "attachment; filename=\"signature-evidence.pdf\"" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "403": { + "description": "Missing credentials, or a credential that can't read this session. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "You do not have permission to perform this action." + } + } + } + }, + "404": { + "description": "No evidence matches: the session, step or signer doesn't exist, was deleted, or hasn't signed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not signed yet": { + "summary": "The signer hasn't signed", + "value": { + "code": "signature_not_signed", + "detail": "signature_not_signed" + } + }, + "Unknown signer": { + "summary": "No such signature in this step", + "value": { + "code": "signature_not_found", + "detail": "signature_not_found" + } + } + } + } + } + }, + "409": { + "description": "The stored file no longer matches its recorded SHA-256. Nothing is returned.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Changed file": { + "summary": "Stored file doesn't match its hash", + "value": { + "code": "signature_document_changed", + "detail": "signature_document_changed" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.sessions.signatures.download_evidence(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.sessions.signatures.downloadEvidence(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n);\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Sessions.Signatures.DownloadEvidence(ctx, \"11111111-2222-3333-4444-555555555555\", \"feature_signature\", didit.SessionsSignaturesDownloadEvidenceParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().signatures().downloadEvidence(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\")\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->signatures->downloadEvidence('11111111-2222-3333-4444-555555555555', 'feature_signature');\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.sessions.signatures.download_evidence(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\"\n)\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Sessions.Signatures.DownloadEvidenceAsync(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\");\nawait content.SaveToAsync(\"download.pdf\");" + } + ] + } + }, + "/v3/session/{sessionId}/signatures/{nodeId}/fields/{fieldId}/image/": { + "get": { + "summary": "Download a signature image", + "description": "Download the signature or initials a signer applied to a field, as PNG. Use the `image_url` of the field in `signatures[].documents[].fields[]`. Needs `read:sessions`; your API key works.", + "operationId": "get_v3_session_signature_field_image", + "x-didit-sdk": { + "resource": "sessions.signatures", + "method": "download_field_image", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "The User Verification (KYC) or Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "nodeId", + "in": "path", + "required": true, + "description": "The E-signature step: `node_id` of the item in `signatures[]`.", + "schema": { + "type": "string", + "example": "feature_signature" + } + }, + { + "name": "fieldId", + "in": "path", + "required": true, + "description": "The field on this signer's copy, as in the field's `image_url`.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "The image.", + "content": { + "image/png": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "400": { + "description": "The stored image no longer matches its recorded SHA-256.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "example": { + "code": "signature_document_changed", + "detail": "signature_document_changed" + } + } + } + }, + "403": { + "description": "Missing credentials, or a credential that can't read this session. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "You do not have permission to perform this action." + } + } + } + }, + "404": { + "description": "No image matches: the session, step or field doesn't exist, was deleted, or has no applied signature or initials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Unknown field": { + "summary": "No such field", + "value": { + "code": "signature_field_not_found", + "detail": "signature_field_not_found" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.sessions.signatures.download_field_image(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nwith open(\"download.png\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.sessions.signatures.downloadFieldImage(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nawait writeFile(\"download.png\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Sessions.Signatures.DownloadFieldImage(ctx, \"11111111-2222-3333-4444-555555555555\", \"feature_signature\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.png\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().signatures().downloadFieldImage(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\", \"11111111-2222-3333-4444-555555555555\")\n .writeTo(Path.of(\"download.png\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->signatures->downloadFieldImage('11111111-2222-3333-4444-555555555555', 'feature_signature', '11111111-2222-3333-4444-555555555555');\n$content->saveTo('download.png');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.sessions.signatures.download_field_image(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nFile.binwrite(\"download.png\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Sessions.Signatures.DownloadFieldImageAsync(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\", \"11111111-2222-3333-4444-555555555555\");\nawait content.SaveToAsync(\"download.png\");" + } + ] + } + }, + "/v3/session/{sessionId}/signatures/{nodeId}/validate/": { + "post": { + "summary": "Validate signatures again", + "description": "Re-check the signed documents of an E-signature step. Didit re-reads every signed PDF, checks its SHA-256 and, for an Advanced (AES) signature, validates the PAdES signature, the certificate, the revocation data and the timestamp (a Simple signature has no digital signature, so only the file integrity is checked), then saves the result in each signer's `validation` and returns the updated items. Nothing is signed again. Send `signature_id` to check one signer; without it, every signer of the step is checked, and the call fails if any of them hasn't signed.\n\nNeeds `write:sessions`; your API key works. In the console it's the **Validate again** button.", + "operationId": "post_v3_session_signature_validate", + "x-didit-sdk": { + "resource": "sessions.signatures", + "method": "validate", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "The User Verification (KYC) or Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "nodeId", + "in": "path", + "required": true, + "description": "The E-signature step: `node_id` of the item in `signatures[]`.", + "schema": { + "type": "string", + "example": "feature_signature" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "signature_id": { + "type": "string", + "format": "uuid", + "description": "`id` of one signer's item in `signatures[]`." + } + } + }, + "example": { + "signature_id": "0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10" + } + } + } + }, + "responses": { + "200": { + "description": "The checked signers, with their updated `validation`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "signatures": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SignatureResult" + } + } + } + } + } + } + }, + "400": { + "description": "A stored signed PDF no longer matches the SHA-256 recorded when it was signed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Document changed": { + "summary": "A signed file no longer matches its hash", + "value": { + "code": "signature_document_changed", + "detail": "signature_document_changed" + } + } + } + } + } + }, + "403": { + "description": "Missing credentials, or a credential that can't read this session. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "You do not have permission to perform this action." + } + } + } + }, + "404": { + "description": "No signature matches: the session, step or signer doesn't exist or was deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Unknown signer": { + "summary": "No such signature in this step", + "value": { + "code": "signature_not_found", + "detail": "signature_not_found" + } + } + } + } + } + }, + "409": { + "description": "A signer of the step hasn't signed, so there's nothing to validate.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not signed": { + "summary": "A signer hasn't signed yet", + "value": { + "code": "signature_not_signed", + "detail": "signature_not_signed" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.signatures.validate(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n signature_id=\"0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.signatures.validate(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n {\n signature_id: \"0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10\",\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Signatures.Validate(ctx, \"11111111-2222-3333-4444-555555555555\", \"feature_signature\", didit.SessionsSignaturesValidateParams{\n\tSignatureID: didit.String(\"0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsSignaturesValidateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().signatures().validate(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\", SessionsSignaturesValidateParams.builder()\n .signatureId(\"0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->signatures->validate('11111111-2222-3333-4444-555555555555', 'feature_signature', [\n 'signature_id' => '0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.signatures.validate(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n signature_id: \"0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Signatures.ValidateAsync(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\", new SessionsSignaturesValidateParams\n{\n SignatureId = \"0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/document-ai/{sessionId}/documents/{documentUuid}/": { + "patch": { + "summary": "Update a Document AI document", + "description": "Correct the fields Document AI extracted from one uploaded document, set its review status, or both, on a KYC or KYB session. `extracted_data` must be a JSON object and is merged key by key: a key you send replaces the stored value (a key sent as `null` is stored as `null`) and keys you leave out keep their values. The document status feeds the Document AI result of its workflow node in the session decision; the session status itself is not recomputed. Every successful call, including one that changes nothing, sends a `data.updated` [webhook](https://docs.didit.me/integration/webhooks) with trigger `manual_review`. A scoped API key needs `decisions: write`; see [Document AI](https://docs.didit.me/core-technology/document-ai/overview).", + "operationId": "patch_v3_document_ai_document", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_document_ai_data", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "documentUuid", + "in": "path", + "required": true, + "description": "UUID of the Document AI document (`document_ai_documents[].items[].uuid` in `GET /v3/session/{sessionId}/decision/`). It must belong to the session in the path.", + "schema": { + "type": "string", + "format": "uuid", + "example": "44444444-5555-6666-7777-888888888888" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "extracted_data": { + "type": "object", + "additionalProperties": true, + "description": "Field keys (the keys you configured for the document) and their corrected values. Must be a JSON object; merged key by key into the stored data. Leave it out rather than sending `null`, which returns `400`.", + "example": { + "invoice_number": "INV-2026-0042" + } + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Not Finished" + ], + "description": "New review status of the document.", + "example": "Approved" + } + } + }, + "example": { + "extracted_data": { + "invoice_number": "INV-2026-0042" + }, + "status": "Approved" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/document-ai/11111111-2222-3333-4444-555555555555/documents/44444444-5555-6666-7777-888888888888/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"extracted_data\": {\"invoice_number\": \"INV-2026-0042\"}, \"status\": \"Approved\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_document_ai_data(\n \"11111111-2222-3333-4444-555555555555\",\n \"44444444-5555-6666-7777-888888888888\",\n extracted_data={\"invoice_number\": \"INV-2026-0042\"},\n status=\"Approved\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateDocumentAiData(\n \"11111111-2222-3333-4444-555555555555\",\n \"44444444-5555-6666-7777-888888888888\",\n {\n extracted_data: { invoice_number: \"INV-2026-0042\" },\n status: \"Approved\",\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateDocumentAiData(ctx, \"11111111-2222-3333-4444-555555555555\", \"44444444-5555-6666-7777-888888888888\", didit.SessionsUpdateDocumentAiDataParams{\n\tExtractedData: didit.Some(map[string]any{\"invoice_number\": \"INV-2026-0042\"}),\n\tStatus: didit.Some(didit.SessionsUpdateDocumentAiDataParamsStatus(\"Approved\")),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SessionsUpdateDocumentAiDataParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateDocumentAiData(\"11111111-2222-3333-4444-555555555555\", \"44444444-5555-6666-7777-888888888888\", SessionsUpdateDocumentAiDataParams.builder()\n .extractedData(Map.of(\"invoice_number\", \"INV-2026-0042\"))\n .status(SessionsUpdateDocumentAiDataParams.Status.of(\"Approved\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateDocumentAiData('11111111-2222-3333-4444-555555555555', '44444444-5555-6666-7777-888888888888', [\n 'extracted_data' => ['invoice_number' => 'INV-2026-0042'],\n 'status' => 'Approved',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_document_ai_data(\n \"11111111-2222-3333-4444-555555555555\",\n \"44444444-5555-6666-7777-888888888888\",\n extracted_data: { invoice_number: \"INV-2026-0042\" },\n status: \"Approved\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateDocumentAiDataAsync(\"11111111-2222-3333-4444-555555555555\", \"44444444-5555-6666-7777-888888888888\", new SessionsUpdateDocumentAiDataParams\n{\n ExtractedData = new Dictionary { [\"invoice_number\"] = \"INV-2026-0042\" },\n Status = new SessionsUpdateDocumentAiDataParamsStatus(\"Approved\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Document updated. Returns the document after the merge.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "uuid", + "document_key", + "original_filename", + "file_url", + "status", + "extracted_data" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Document identifier." + }, + "document_key": { + "type": "string", + "description": "The configured document this upload corresponds to." + }, + "original_filename": { + "type": "string", + "nullable": true, + "description": "File name the document was uploaded with." + }, + "file_url": { + "type": "string", + "nullable": true, + "description": "Storage path of the uploaded file. It is not a download link: read the presigned `file_url` from the session decision to download the file." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Review status of the document." + }, + "extracted_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "All extracted fields after the merge, keyed by field key." + } + } + }, + "example": { + "uuid": "44444444-5555-6666-7777-888888888888", + "document_key": "invoice", + "original_filename": "invoice-2026-09.pdf", + "file_url": "document_ai/11111111-2222-3333-4444-555555555555-66666666-7777-8888-9999-000000000000.pdf", + "status": "Approved", + "extracted_data": { + "invoice_number": "INV-2026-0042", + "total_amount": 1250.0, + "issue_date": "2026-09-01" + } + } + } + } + }, + "400": { + "description": "Validation error, checked before the document is looked up: `status` not one of the allowed values, or `extracted_data` sent as `null`.", + "content": { + "application/json": { + "examples": { + "Invalid status": { + "summary": "status not in the enum", + "value": { + "status": [ + "\"Rejected\" is not a valid choice." + ] + } + }, + "Null extracted_data": { + "summary": "extracted_data is null", + "value": { + "extracted_data": [ + "This field may not be null." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits, or no current document with this UUID belongs to it. The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's limits (also returned for a session deleted moments ago)", + "value": { + "detail": "Session not found." + } + }, + "Unknown document": { + "summary": "documentUuid does not belong to the session", + "value": { + "detail": "Document AI document not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/aml/dataset-freshness/": { + "get": { + "summary": "Get AML dataset freshness", + "description": "Report how current the datasets behind AML screening are, per source family (`sanctions`, `pep`, `warnings`, `adverse_media`), as audit evidence for [ongoing AML monitoring](https://docs.didit.me/core-technology/aml-screening/continuous-monitoring-aml-screening#proving-that-monitoring-ran). The answer is platform-wide: no session in the path, no customer data in the body, the same answer for every application.\n\nAlways `200` for an accepted key. Check `availability` first: when it is not `available`, `dataset_freshness` is `null`. Dates are never estimated: a family whose datasets reported no date is absent from `sources`, and a family where only some datasets reported one has `oldest_updated_at: null`. Readings are taken while ongoing monitoring runs, not on each call; `captured_at` says when.\n\nA scoped API key needs `sessions: read`; a key limited to some workflows or to approved sessions cannot call this endpoint. Nothing is billed.", + "operationId": "get_aml_dataset_freshness", + "x-didit-sdk": { + "resource": "aml", + "method": "retrieve_dataset_freshness", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/aml/dataset-freshness/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.aml.retrieve_dataset_freshness()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.aml.retrieveDatasetFreshness();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.AML.RetrieveDatasetFreshness(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.aml().retrieveDatasetFreshness();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "aml->retrieveDatasetFreshness();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.aml.retrieve_dataset_freshness\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Aml.RetrieveDatasetFreshnessAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The latest dataset freshness reading, or `null` with the reason.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "dataset_freshness", + "availability" + ], + "properties": { + "dataset_freshness": { + "allOf": [ + { + "$ref": "#/components/schemas/AMLDatasetFreshness" + } + ], + "nullable": true, + "description": "The latest reading, or `null` when `availability` is not `available`." + }, + "availability": { + "type": "string", + "enum": [ + "available", + "not_configured", + "not_yet_collected" + ], + "description": "`available` when a reading exists; `not_configured` when dataset dates are not collected in this environment; `not_yet_collected` when collection is set up but no reading exists yet." + } + } + }, + "examples": { + "Available": { + "value": { + "dataset_freshness": { + "captured_at": "2026-10-17T06:00:12.345678+00:00", + "provider": "aml_data_source", + "catalog_version": "20261017054500-abc", + "sources": { + "sanctions": { + "last_updated_at": "2026-10-17T04:10:00+00:00", + "oldest_updated_at": "2026-10-15T22:30:00+00:00", + "dataset_count": 48, + "dated_dataset_count": 48 + }, + "pep": { + "last_updated_at": "2026-10-16T23:05:00+00:00", + "oldest_updated_at": null, + "dataset_count": 12, + "dated_dataset_count": 11 + } + } + }, + "availability": "available" + } + }, + "Not collected yet": { + "value": { + "dataset_freshness": null, + "availability": "not_yet_collected" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing key gets the generic message; a wrong, revoked or expired key gets `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions read": { + "value": { + "detail": "This API key has no read access to sessions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/": { + "get": { + "summary": "List lists", + "description": "Returns the application's lists, newest first: the system lists Didit provisions (one blocklist per entry type, the **High Risk Countries** custom list, and any system allowlist) and the allowlists and custom lists you created. Filter with `list_type` and `entry_type`; deleted lists never appear. Results are paginated with `limit` and `offset`. A scoped API key needs `workflows: read`. See [Lists](https://docs.didit.me/management-api/lists/overview) for what each list type does.", + "operationId": "list_lists", + "x-didit-sdk": { + "resource": "lists", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_type", + "in": "query", + "required": false, + "description": "Only lists of this type.", + "schema": { + "type": "string", + "enum": [ + "blocklist", + "allowlist", + "custom" + ] + } + }, + { + "name": "entry_type", + "in": "query", + "required": false, + "description": "Only lists of this entry type.", + "schema": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email", + "ip_address", + "device_fingerprint", + "wallet_address", + "bank_account", + "country", + "user", + "business", + "key" + ] + } + }, + { + "name": "ordering", + "in": "query", + "required": false, + "description": "Sort order: `-created_at` (newest first, the default) or `created_at` (oldest first).", + "schema": { + "type": "string", + "enum": [ + "-created_at", + "created_at" + ], + "default": "-created_at" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Page size. Defaults to 50. A value above 1000000 returns `400`; zero, a negative or a non-numeric value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Zero-based index of the first item to return. Prefer following `next`. A value above 1000000 returns `400`; a negative or non-numeric value falls back to 0.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/lists/?list_type=blocklist' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.lists.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.lists.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Lists.ListAutoPaging(ctx, didit.ListsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.lists().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.lists.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Lists.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "A page of lists.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "description": "Total number of lists matching the filters." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ComplianceList" + } + } + } + }, + "examples": { + "Lists": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "uuid": "22222222-3333-4444-5555-666666666666", + "name": "Trusted office IPs", + "description": "Office egress ranges", + "list_type": "allowlist", + "entry_type": "ip_address", + "is_system": false, + "entry_count": 3, + "last_entry_added_at": "2026-10-02T08:15:27.501334Z", + "created_at": "2026-09-30T13:20:45.889120Z", + "updated_at": "2026-09-30T13:20:45.889120Z" + }, + { + "uuid": "11111111-2222-3333-4444-555555555555", + "name": "Face Blocklist", + "description": "Blocked biometric faces. When a face matching an entry in this list is detected during liveness verification, the session will be automatically declined.", + "list_type": "blocklist", + "entry_type": "face", + "is_system": true, + "entry_count": 12, + "last_entry_added_at": "2026-10-09T16:42:03.118204Z", + "created_at": "2026-01-14T10:02:11.402917Z", + "updated_at": "2026-01-14T10:02:11.402917Z" + } + ] + } + } + } + } + } + }, + "400": { + "description": "A query parameter is invalid.", + "content": { + "application/json": { + "examples": { + "Unknown list type": { + "value": { + "list_type": [ + "\"denylist\" is not a valid choice." + ] + } + }, + "Unsupported ordering": { + "value": { + "ordering": [ + "\"name\" is not a valid choice." + ] + } + }, + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: read` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no read access to workflows." + } + } + } + } + } + }, + "429": { + "description": "The shared read limit of 600 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create a list", + "description": "Creates an allowlist or a custom list in the application the API key belongs to. Blocklists cannot be created: Didit provisions one system blocklist per entry type, and you add to it with [Create a list entry](https://docs.didit.me/management-api/lists/create-entry). `entry_type` cannot change after creation and decides how entry values are validated and matched. Allowlist entries mark matching items as trusted (for example, a face allowlist skips the duplicated-face action) and never override a blocklist match; reference any list from a workflow or transaction rule condition with `value_type: \"list\"`. A scoped API key needs `workflows: write`.", + "operationId": "create_list", + "x-didit-sdk": { + "resource": "lists", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "list_type", + "entry_type" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "List name. Must differ from the name of every other list of the application, system lists included." + }, + "description": { + "type": "string", + "default": "", + "description": "Optional free-text description." + }, + "list_type": { + "type": "string", + "enum": [ + "allowlist", + "custom" + ], + "description": "`allowlist` or `custom`. `blocklist` is refused with `400`." + }, + "entry_type": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email", + "ip_address", + "device_fingerprint", + "wallet_address", + "bank_account", + "country", + "user", + "business", + "key" + ], + "description": "What the list holds: `face`, `document` (document number), `phone`, `email`, `ip_address` (IP address or CIDR range), `device_fingerprint`, `wallet_address`, `bank_account`, `country` (ISO 3166-1 alpha-3), `user` or `business` (your `vendor_data`), or `key` (any string). It decides how entry values are validated and matched. Cannot be changed later." + } + } + }, + "examples": { + "IP allowlist": { + "value": { + "name": "Trusted office IPs", + "description": "Office egress ranges", + "list_type": "allowlist", + "entry_type": "ip_address" + } + }, + "Custom country list": { + "value": { + "name": "Enhanced due diligence countries", + "list_type": "custom", + "entry_type": "country" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/lists/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"name\": \"Trusted office IPs\",\n \"description\": \"Office egress ranges\",\n \"list_type\": \"allowlist\",\n \"entry_type\": \"ip_address\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.lists.create(\n name=\"Trusted office IPs\",\n description=\"Office egress ranges\",\n list_type=\"allowlist\",\n entry_type=\"ip_address\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.lists.create({\n name: \"Trusted office IPs\",\n description: \"Office egress ranges\",\n list_type: \"allowlist\",\n entry_type: \"ip_address\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Lists.Create(ctx, didit.ListsCreateParams{\n\tName: \"Trusted office IPs\",\n\tDescription: didit.String(\"Office egress ranges\"),\n\tListType: \"allowlist\",\n\tEntryType: \"ip_address\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.ListsCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.lists().create(ListsCreateParams.builder()\n .name(\"Trusted office IPs\")\n .description(\"Office egress ranges\")\n .listType(ListsCreateParams.ListType.of(\"allowlist\"))\n .entryType(ListsCreateParams.EntryType.of(\"ip_address\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->create([\n 'name' => 'Trusted office IPs',\n 'description' => 'Office egress ranges',\n 'list_type' => 'allowlist',\n 'entry_type' => 'ip_address',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.lists.create(\n name: \"Trusted office IPs\",\n description: \"Office egress ranges\",\n list_type: \"allowlist\",\n entry_type: \"ip_address\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Lists.CreateAsync(new ListsCreateParams\n{\n Name = \"Trusted office IPs\",\n Description = \"Office egress ranges\",\n ListType = \"allowlist\",\n EntryType = \"ip_address\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The list was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ComplianceList" + }, + "examples": { + "New allowlist": { + "value": { + "uuid": "22222222-3333-4444-5555-666666666666", + "name": "Trusted office IPs", + "description": "Office egress ranges", + "list_type": "allowlist", + "entry_type": "ip_address", + "is_system": false, + "entry_count": 0, + "last_entry_added_at": null, + "created_at": "2026-10-12T09:15:44.031876Z", + "updated_at": "2026-10-12T09:15:44.031876Z" + } + } + } + } + } + }, + "400": { + "description": "The body is invalid.", + "content": { + "application/json": { + "examples": { + "Blocklist refused": { + "value": { + "list_type": [ + "Cannot create additional blocklist lists. System blocklists are auto-created for each application." + ] + } + }, + "Duplicate name": { + "value": { + "name": [ + "A list with this name already exists for this application." + ] + } + }, + "Missing fields": { + "value": { + "name": [ + "This field is required." + ], + "list_type": [ + "This field is required." + ], + "entry_type": [ + "This field is required." + ] + } + }, + "Unknown entry type": { + "value": { + "entry_type": [ + "\"iban\" is not a valid choice." + ] + } + }, + "Name too long": { + "value": { + "name": [ + "Ensure this field has no more than 255 characters." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/{list_uuid}/": { + "get": { + "summary": "Get a list", + "description": "Returns one list of the application with its entry count and when its newest entry was added. A list of another application, or one that was deleted, returns `404`. This returns the list only: read its values with [List list entries](https://docs.didit.me/management-api/lists/list-entries). A scoped API key needs `workflows: read`.", + "operationId": "get_list", + "x-didit-sdk": { + "resource": "lists", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list, as returned by List lists or Create a list.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.lists.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.lists.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Lists.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.lists().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.lists.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Lists.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The list.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ComplianceList" + }, + "examples": { + "System face blocklist": { + "value": { + "uuid": "11111111-2222-3333-4444-555555555555", + "name": "Face Blocklist", + "description": "Blocked biometric faces. When a face matching an entry in this list is detected during liveness verification, the session will be automatically declined.", + "list_type": "blocklist", + "entry_type": "face", + "is_system": true, + "entry_count": 12, + "last_entry_added_at": "2026-10-09T16:42:03.118204Z", + "created_at": "2026-01-14T10:02:11.402917Z", + "updated_at": "2026-01-14T10:02:11.402917Z" + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: read` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no read access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No List matches the given query." + } + } + } + } + } + }, + "429": { + "description": "The shared read limit of 600 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update a list", + "description": "Renames a list or changes its description. Only `name` and `description` are read; any other field is ignored, so `list_type` and `entry_type` keep the values they were created with. System lists cannot be modified and return `400`. The response contains only `name` and `description`: call [Get a list](https://docs.didit.me/management-api/lists/get) for the full list. A scoped API key needs `workflows: write`.", + "operationId": "update_list", + "x-didit-sdk": { + "resource": "lists", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list, as returned by List lists or Create a list.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "New name. Must differ from the name of every other list of the application." + }, + "description": { + "type": "string", + "description": "New description. Send an empty string to clear it." + } + } + }, + "examples": { + "Rename": { + "value": { + "name": "Trusted office and VPN IPs" + } + }, + "Rename and describe": { + "value": { + "name": "Trusted office and VPN IPs", + "description": "Office egress ranges and the corporate VPN" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"name\": \"Trusted office and VPN IPs\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.lists.update(\n \"11111111-2222-3333-4444-555555555555\",\n name=\"Trusted office and VPN IPs\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.lists.update(\"11111111-2222-3333-4444-555555555555\", {\n name: \"Trusted office and VPN IPs\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Lists.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.ListsUpdateParams{\n\tName: didit.String(\"Trusted office and VPN IPs\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.ListsUpdateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.lists().update(\"11111111-2222-3333-4444-555555555555\", ListsUpdateParams.builder()\n .name(\"Trusted office and VPN IPs\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->update('11111111-2222-3333-4444-555555555555', [\n 'name' => 'Trusted office and VPN IPs',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.lists.update(\n \"11111111-2222-3333-4444-555555555555\",\n name: \"Trusted office and VPN IPs\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Lists.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new ListsUpdateParams\n{\n Name = \"Trusted office and VPN IPs\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The list was updated. Only the editable fields are returned.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "description" + ], + "properties": { + "name": { + "type": "string", + "description": "The list's name after the update." + }, + "description": { + "type": "string", + "description": "The list's description after the update." + } + } + }, + "examples": { + "Renamed": { + "value": { + "name": "Trusted office and VPN IPs", + "description": "Office egress ranges" + } + } + } + } + } + }, + "400": { + "description": "The list is a system list, or the body is invalid.", + "content": { + "application/json": { + "examples": { + "System list": { + "value": { + "non_field_errors": [ + "System lists cannot be modified." + ] + } + }, + "Duplicate name": { + "value": { + "name": [ + "A list with this name already exists for this application." + ] + } + }, + "Blank name": { + "value": { + "name": [ + "This field may not be blank." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No List matches the given query." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete a list", + "description": "Deletes an allowlist or custom list together with its entries. Deletion is irreversible: the list and its entries no longer appear in any read, and workflow and transaction rule conditions that reference the list evaluate it as an empty list from then on. Sessions and transactions already evaluated keep their results, and the name can be reused for a new list. System lists cannot be deleted and return `400`; to empty one, [delete its entries](https://docs.didit.me/management-api/lists/delete-entry). A scoped API key needs `workflows: write`.", + "operationId": "delete_list", + "x-didit-sdk": { + "resource": "lists", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list, as returned by List lists or Create a list.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.lists.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.lists.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Lists.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.lists().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.lists.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Lists.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ], + "responses": { + "204": { + "description": "The list and its entries were deleted. No body." + }, + "400": { + "description": "The list is a system list.", + "content": { + "application/json": { + "examples": { + "System list": { + "value": { + "detail": "System lists cannot be deleted." + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was already deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No List matches the given query." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/{list_uuid}/entries/": { + "get": { + "summary": "List list entries", + "description": "Returns the current entries of one list, newest first. `search` matches part of an entry's `value` or `display_label` without regard to case, and `reference_session_id` narrows the results to the entries taken from one person verification session. Image URLs in `metadata` are short-lived signed URLs issued again on every call; a scoped API key without `media: read` receives `null` in their place. A scoped API key needs `workflows: read`.", + "operationId": "list_list_entries", + "x-didit-sdk": { + "resource": "lists.entries", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list, as returned by List lists or Create a list.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "search", + "in": "query", + "required": false, + "description": "Case-insensitive text that the entry's `value` or `display_label` must contain.", + "schema": { + "type": "string" + } + }, + { + "name": "reference_session_id", + "in": "query", + "required": false, + "description": "Only the entries taken from this person verification session, for example to see which of its faces are already listed.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Page size. Defaults to 50. A value above 1000000 returns `400`; zero, a negative or a non-numeric value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Zero-based index of the first item to return. Prefer following `next`. A value above 1000000 returns `400`; a negative or non-numeric value falls back to 0.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/entries/?search=example.com' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.lists.entries.list(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.lists.entries.list(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Lists.Entries.ListAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.ListsEntriesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.lists().entries().list(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->entries->list('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.lists.entries.list(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Lists.Entries.ListAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "A page of entries, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "description": "Total number of entries matching the filters." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ComplianceListEntry" + } + } + } + }, + "examples": { + "Entries": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "uuid": "bbbbbbbb-cccc-dddd-eeee-ffffffffffff", + "value": "X1234567", + "display_label": "Jane Doe - X1234567", + "comment": "", + "added_by": "api", + "reference_session_id": "33333333-4444-5555-6666-777777777777", + "reference_session_number": 1042, + "reference_session_type": "kyc", + "reference_object_uuid": "66666666-7777-8888-9999-000000000000", + "metadata": { + "full_name": "Jane Doe", + "issuing_state": "ESP", + "document_type": "ID", + "region": "", + "document_number": "X1234567" + }, + "created_at": "2026-10-12T10:06:12.774915Z" + }, + { + "uuid": "eeeeeeee-ffff-0000-1111-222222222222", + "value": "y7654321", + "display_label": "", + "comment": "Reported by a partner", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": null, + "metadata": null, + "created_at": "2026-10-03T07:48:30.552810Z" + } + ] + } + } + } + } + } + }, + "400": { + "description": "A query parameter is invalid.", + "content": { + "application/json": { + "examples": { + "Invalid session id": { + "value": { + "reference_session_id": [ + "Must be a valid UUID." + ] + } + }, + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: read` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no read access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "List not found." + } + } + } + } + } + }, + "429": { + "description": "The shared read limit of 600 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create a list entry", + "description": "Adds one entry to a list, in one of three ways: by `value`; from a verification session with `reference_session_id`, where Didit reads the value from the session's most recent record of the list's entry type and links the entry to it; or by `value` with a `reference_object_uuid` and `metadata.reference_type` pointing back to a transaction, user or business. On the system face, document, phone and email blocklists, the record read from the session is also flagged as blocklisted, so new matching sessions are declined; on the system user and business blocklists, matching users or businesses are set to `BLOCKED`. Sessions and transactions that already finished are not changed. A scoped API key needs `workflows: write`. See [Create a list entry](https://docs.didit.me/management-api/lists/create-entry) for each mode.", + "operationId": "create_list_entry", + "x-didit-sdk": { + "resource": "lists.entries", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list to add to. For a face you only have as an image, use Upload a face to a list instead.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Send `value`, `reference_session_id`, or both. Which fields apply depends on the list's `entry_type`.", + "properties": { + "value": { + "type": "string", + "description": "The value to list, validated against the list's `entry_type`: `email` a valid email address; `phone` a valid international number, stored in E.164; `ip_address` an IP address or CIDR range (for example `10.0.0.0/8`); `device_fingerprint` at least 8 characters among letters, digits, `-`, `_`, `:` and `.` (a blocklist refuses a fingerprint that several distinct devices have reported); `country` an ISO 3166-1 alpha-2 or alpha-3 code, stored as alpha-3; `user` and `business` the `vendor_data`; any other type a non-empty string. Stored in lower case except phone numbers and country codes, and a list holds each value once (case-insensitive). Required unless `reference_session_id` is sent; must be left empty with a document-capture `face_image_source`." + }, + "reference_session_id": { + "type": "string", + "format": "uuid", + "description": "A person or business verification session of your application to take the value from. Didit reads the session's most recent record of the list's entry type (`face`, `document`, `phone`, `email`, `ip_address` or `device_fingerprint`), links the entry to the session and to that record, and fills `display_label` and `metadata` for documents. For a `device_fingerprint` blocklist, a session that only has a device-class fingerprint, or was completed on more than one device, is refused: send the exact fingerprint as `value`. For other entry types, or when the session has no such record, send `value` too; your `value` is then stored and the entry is still linked to the session." + }, + "reference_object_uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the record the entry stands for when it does not come from a session: a transaction UUID, or the `didit_internal_id` of a user or business. Pair it with `metadata.reference_type`. Replaced by the session's record when `reference_session_id` provides one." + }, + "face_image_source": { + "type": "string", + "enum": [ + "liveness", + "front_image_camera_front", + "back_image_camera_front" + ], + "default": "liveness", + "description": "Face lists with `reference_session_id`: which of the session's images the face is taken from. `liveness` (default) is the liveness selfie. `front_image_camera_front` and `back_image_camera_front` are the selfies the front camera took while the user photographed the front or back of their document: accepted only on the system face blocklist, with `reference_session_id` and an empty `value`. Each selfie can be added once and must show one clear face." + }, + "reference_node_id": { + "type": "string", + "nullable": true, + "maxLength": 100, + "description": "With a document-capture `face_image_source`: the ID verification node whose selfie to use, for workflows with more than one. Defaults to the most recent ID verification that has that image. Refused with `liveness`." + }, + "display_label": { + "type": "string", + "maxLength": 512, + "description": "Human-readable label. When empty, Didit derives one from the session where it can (for example the holder's name and document number)." + }, + "comment": { + "type": "string", + "description": "Free-text note, such as the reason for listing." + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Free-form JSON stored with the entry and returned as stored. Use `reference_type` and `reference_id` so the console links back to the source. `reference_image_url` and `front_image_url` are set by Didit only and are dropped if you send them. For an entry read from a session, Didit's keys are merged with yours.", + "properties": { + "reference_type": { + "type": "string", + "enum": [ + "transaction", + "vendor_user", + "vendor_business" + ], + "description": "What `reference_object_uuid` points to." + }, + "reference_id": { + "type": "string", + "description": "Identifier of the source record." + } + } + } + } + }, + "examples": { + "By value": { + "value": { + "value": "+14155552671", + "comment": "Reported fraud" + } + }, + "From a session": { + "value": { + "reference_session_id": "33333333-4444-5555-6666-777777777777" + } + }, + "Face from a document-capture selfie": { + "value": { + "reference_session_id": "33333333-4444-5555-6666-777777777777", + "face_image_source": "front_image_camera_front" + } + }, + "From a transaction": { + "value": { + "value": "0x52908400098527886E0F7030069857D2E4169EE7", + "reference_object_uuid": "55555555-6666-7777-8888-999999999999", + "comment": "Linked to a chargeback", + "metadata": { + "reference_type": "transaction", + "reference_id": "55555555-6666-7777-8888-999999999999" + } + } + }, + "Country": { + "value": { + "value": "PRK", + "comment": "Sanctioned jurisdiction" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/entries/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"value\": \"+14155552671\", \"comment\": \"Reported fraud\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.lists.entries.create(\n \"11111111-2222-3333-4444-555555555555\",\n value=\"+14155552671\",\n comment=\"Reported fraud\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.lists.entries.create(\"11111111-2222-3333-4444-555555555555\", {\n value: \"+14155552671\",\n comment: \"Reported fraud\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Lists.Entries.Create(ctx, \"11111111-2222-3333-4444-555555555555\", didit.ListsEntriesCreateParams{\n\tValue: didit.String(\"+14155552671\"),\n\tComment: didit.String(\"Reported fraud\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.ListsEntriesCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.lists().entries().create(\"11111111-2222-3333-4444-555555555555\", ListsEntriesCreateParams.builder()\n .value(\"+14155552671\")\n .comment(\"Reported fraud\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->entries->create('11111111-2222-3333-4444-555555555555', [\n 'value' => '+14155552671',\n 'comment' => 'Reported fraud',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.lists.entries.create(\n \"11111111-2222-3333-4444-555555555555\",\n value: \"+14155552671\",\n comment: \"Reported fraud\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Lists.Entries.CreateAsync(\"11111111-2222-3333-4444-555555555555\", new ListsEntriesCreateParams\n{\n Value = \"+14155552671\",\n Comment = \"Reported fraud\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The entry was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ComplianceListEntry" + }, + "examples": { + "By value": { + "value": { + "uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "value": "+14155552671", + "display_label": "", + "comment": "Reported fraud", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": null, + "metadata": null, + "created_at": "2026-10-12T10:04:51.220391Z" + } + }, + "Document from a session": { + "value": { + "uuid": "bbbbbbbb-cccc-dddd-eeee-ffffffffffff", + "value": "X1234567", + "display_label": "Jane Doe - X1234567", + "comment": "", + "added_by": "api", + "reference_session_id": "33333333-4444-5555-6666-777777777777", + "reference_session_number": 1042, + "reference_session_type": "kyc", + "reference_object_uuid": "66666666-7777-8888-9999-000000000000", + "metadata": { + "full_name": "Jane Doe", + "issuing_state": "ESP", + "document_type": "ID", + "region": "", + "document_number": "X1234567" + }, + "created_at": "2026-10-12T10:06:12.774915Z" + } + }, + "From a transaction": { + "value": { + "uuid": "dddddddd-eeee-ffff-0000-111111111111", + "value": "0x52908400098527886e0f7030069857d2e4169ee7", + "display_label": "", + "comment": "Linked to a chargeback", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": "55555555-6666-7777-8888-999999999999", + "metadata": { + "reference_type": "transaction", + "reference_id": "55555555-6666-7777-8888-999999999999" + }, + "created_at": "2026-10-12T10:20:40.117062Z" + } + } + } + } + } + }, + "400": { + "description": "The body is invalid, the value is already in the list, or the session cannot provide the value.", + "content": { + "application/json": { + "examples": { + "Nothing to list": { + "value": { + "value": [ + "Either 'value' or 'reference_session_id' must be provided." + ] + } + }, + "Duplicate value": { + "value": { + "value": [ + "An entry with this value already exists in this list." + ] + } + }, + "Invalid phone": { + "value": { + "value": [ + "Invalid phone number. Provide a valid international number (e.g. +1234567890)." + ] + } + }, + "Invalid IP": { + "value": { + "value": [ + "Invalid IP address or CIDR range (e.g. 192.168.1.1 or 10.0.0.0/24)." + ] + } + }, + "Invalid country": { + "value": { + "value": [ + "Invalid country code: XX. Provide a valid ISO 3166-1 alpha-2 or alpha-3 country code." + ] + } + }, + "Device-class fingerprint": { + "value": { + "value": [ + "This value is a device-class fingerprint: several distinct devices have reported it, so blocklisting it would decline unrelated people. Block the user by document, face, email or phone instead." + ] + } + }, + "Unknown session": { + "value": { + "reference_session_id": [ + "Session not found or does not belong to this application." + ] + } + }, + "No data in session": { + "value": { + "value": [ + "Either provide a 'value' or ensure the session contains data for this entry type." + ] + } + }, + "Selfie on another list": { + "value": { + "face_image_source": [ + "A document-capture selfie can only be added to the face blocklist." + ] + } + }, + "No clear face in selfie": { + "value": { + "face_image_source": [ + "No clear face was found in this selfie, so it cannot be blocklisted." + ] + } + }, + "Selfie already listed": { + "value": { + "face_image_source": [ + "This selfie is already in the blocklist." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "List not found." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + }, + "503": { + "description": "Face analysis of a document-capture selfie is temporarily unavailable. Nothing was added; retry later.", + "content": { + "application/json": { + "examples": { + "Unavailable": { + "value": { + "detail": "Service temporarily unavailable. Please try again later." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/{list_uuid}/entries/{entry_uuid}/": { + "delete": { + "summary": "Delete a list entry", + "description": "Deletes one entry from a list, system lists included. The entry stops matching at once, and deletion is irreversible: to list the value again, create a new entry. On a system face, document, phone or email blocklist, the record the entry stands for is no longer flagged as blocklisted; on the system user or business blocklist, users or businesses of the application whose `vendor_data` equals the entry's value (or whose `didit_internal_id` equals its `reference_object_uuid`) and that are `BLOCKED` return to `ACTIVE`. Sessions and transactions that already finished keep their status. A scoped API key needs `workflows: write`.", + "operationId": "delete_list_entry", + "x-didit-sdk": { + "resource": "lists.entries", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list that holds the entry.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "entry_uuid", + "in": "path", + "required": true, + "description": "UUID of the entry, as returned by List list entries or Create a list entry.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/entries/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.lists.entries.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.lists.entries.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\n);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Lists.Entries.Delete(ctx, \"11111111-2222-3333-4444-555555555555\", \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.lists().entries().delete(\"11111111-2222-3333-4444-555555555555\", \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->entries->delete('11111111-2222-3333-4444-555555555555', 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.lists.entries.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\"\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Lists.Entries.DeleteAsync(\"11111111-2222-3333-4444-555555555555\", \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\");" + } + ], + "responses": { + "204": { + "description": "The entry was deleted. No body." + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No entry with this UUID exists in this list, the list is not in the API key's application or was deleted, or the entry was already deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No ListEntry matches the given query." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/{list_uuid}/entries/face-upload/": { + "post": { + "summary": "Upload a face to a list", + "description": "Adds a face to a face blocklist or face allowlist from a base64-encoded JPEG or PNG image, for when you have no verification session to reference. Didit detects the faces in the image, uses the largest one, and creates an entry whose `value` and `reference_object_uuid` are the UUID of the new face. A blocklisted face declines new sessions whose face matches; an allowlisted face skips the duplicated-face action and never overrides a blocklist match. In a sandbox application the image is not analyzed and the entry matches nothing. A scoped API key needs `workflows: write`. To list a face from an existing session, use [Create a list entry](https://docs.didit.me/management-api/lists/create-entry) with `reference_session_id` instead.", + "operationId": "upload_list_entry_face", + "x-didit-sdk": { + "resource": "lists.entries", + "method": "upload_face", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of a list with `entry_type: face` and `list_type: blocklist` or `allowlist`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "image" + ], + "properties": { + "image": { + "type": "string", + "format": "byte", + "description": "Base64-encoded JPEG or PNG, at most 2 MB once decoded, showing a clearly visible face. Send the bare base64 string, without a `data:image/...;base64,` prefix." + }, + "comment": { + "type": "string", + "default": "", + "description": "Optional free-text note, such as the reason for listing." + } + } + }, + "examples": { + "Face image": { + "value": { + "image": "/9j/4AAQSkZJRgABAQAAAQABAAD...", + "comment": "Known fraud attempt" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/entries/face-upload/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d \"{\\\"image\\\": \\\"$(base64 < face.jpg | tr -d '\\n')\\\", \\\"comment\\\": \\\"Known fraud attempt\\\"}\"" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.lists.entries.upload_face(\n \"11111111-2222-3333-4444-555555555555\",\n image=\"/9j/4AAQSkZJRgABAQAAAQABAAD...\",\n comment=\"Known fraud attempt\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.lists.entries.uploadFace(\"11111111-2222-3333-4444-555555555555\", {\n image: \"/9j/4AAQSkZJRgABAQAAAQABAAD...\",\n comment: \"Known fraud attempt\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Lists.Entries.UploadFace(ctx, \"11111111-2222-3333-4444-555555555555\", didit.ListsEntriesUploadFaceParams{\n\tImage: \"/9j/4AAQSkZJRgABAQAAAQABAAD...\",\n\tComment: didit.String(\"Known fraud attempt\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.ListsEntriesUploadFaceParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.lists().entries().uploadFace(\"11111111-2222-3333-4444-555555555555\", ListsEntriesUploadFaceParams.builder()\n .image(\"/9j/4AAQSkZJRgABAQAAAQABAAD...\")\n .comment(\"Known fraud attempt\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->entries->uploadFace('11111111-2222-3333-4444-555555555555', [\n 'image' => '/9j/4AAQSkZJRgABAQAAAQABAAD...',\n 'comment' => 'Known fraud attempt',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.lists.entries.upload_face(\n \"11111111-2222-3333-4444-555555555555\",\n image: \"/9j/4AAQSkZJRgABAQAAAQABAAD...\",\n comment: \"Known fraud attempt\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Lists.Entries.UploadFaceAsync(\"11111111-2222-3333-4444-555555555555\", new ListsEntriesUploadFaceParams\n{\n Image = \"/9j/4AAQSkZJRgABAQAAAQABAAD...\",\n Comment = \"Known fraud attempt\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The face entry was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ComplianceListEntry" + }, + "examples": { + "Uploaded face": { + "value": { + "uuid": "cccccccc-dddd-eeee-ffff-000000000000", + "value": "44444444-5555-6666-7777-888888888888", + "display_label": "Manually uploaded face", + "comment": "Known fraud attempt", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": "44444444-5555-6666-7777-888888888888", + "metadata": { + "reference_image_url": "https://media.example.com/faces/reference.jpg?expires=1760263938&signature=3f9a1c" + }, + "created_at": "2026-10-12T10:12:18.904467Z" + } + }, + "Sandbox application": { + "value": { + "uuid": "cccccccc-dddd-eeee-ffff-000000000000", + "value": "44444444-5555-6666-7777-888888888888", + "display_label": "Sandbox face entry", + "comment": "Known fraud attempt", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": "44444444-5555-6666-7777-888888888888", + "metadata": { + "sandbox": true + }, + "created_at": "2026-10-12T10:12:18.904467Z" + } + } + } + } + } + }, + "400": { + "description": "The list does not take face uploads, or the image is missing, invalid, too large or shows no usable face. No entry was created.", + "content": { + "application/json": { + "examples": { + "Not a face list": { + "value": { + "detail": "Face upload is only available for face-type lists." + } + }, + "Custom face list": { + "value": { + "detail": "Face upload is only available for face blocklists and allowlists." + } + }, + "Missing image": { + "value": { + "image": [ + "This field is required." + ] + } + }, + "Invalid base64": { + "value": { + "image": [ + "Invalid base64 encoding." + ] + } + }, + "Too large": { + "value": { + "image": [ + "Image exceeds maximum size of 2 MB." + ] + } + }, + "Unreadable image": { + "value": { + "image": "Invalid image. Provide a valid base64-encoded JPG or PNG." + } + }, + "No face": { + "value": { + "image": "No face detected in the image. Please upload a clear photo with a visible face." + } + }, + "No usable face": { + "value": { + "image": "Could not extract face embedding. Try a different image." + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "List not found." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + }, + "503": { + "description": "Face analysis is temporarily unavailable. No entry was created; retry later.", + "content": { + "application/json": { + "examples": { + "Unavailable": { + "value": { + "detail": "Service temporarily unavailable. Please try again later." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/blocklist/": { + "get": { + "summary": "List blocklisted items", + "description": "Returns one paginated feed of the faces, identity documents, phone numbers and email addresses of the application's verification sessions that are currently blocklisted, newest `blocklisted_at` first; filter with `item_type`. Items of deleted sessions are not included. The feed covers records blocklisted from a session (with [Add session items to the blocklist](https://docs.didit.me/management-api/blocklist/add), from the console, or by a list entry created from a session); values added to a list by hand, uploaded faces, faces from a document-capture selfie and the other entry types are read with [List list entries](https://docs.didit.me/management-api/lists/list-entries). `face_image` and `front_image` are short-lived signed URLs, and a scoped API key without `media: read` receives `null`. A scoped API key needs `workflows: read`.", + "operationId": "list_blocklist_items", + "x-didit-sdk": { + "resource": "blocklist", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Blocklist" + ], + "parameters": [ + { + "name": "item_type", + "in": "query", + "required": false, + "description": "Only items of this type. When omitted, every type is returned.", + "schema": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email" + ] + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Page size. Defaults to 50. A value above 1000000 returns `400`; zero, a negative or a non-numeric value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Zero-based index of the first item to return. Prefer following `next`. A value above 1000000 returns `400`; a negative or non-numeric value falls back to 0.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/blocklist/?item_type=document' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.blocklist.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.blocklist.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Blocklist.ListAutoPaging(ctx, didit.BlocklistListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.blocklist().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "blocklist->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.blocklist.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Blocklist.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "A page of blocklisted items, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "description": "Total number of blocklisted items matching the filters." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BlocklistedItem" + } + } + } + }, + "examples": { + "Mixed feed": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "type": "document", + "session_id": "33333333-4444-5555-6666-777777777777", + "session_number": 1042, + "blocklisted_at": "2026-10-12T10:06:12.701553Z", + "face_image": null, + "document_type": "ID", + "document_number": "X1234567", + "front_image": "https://media.example.com/documents/front.jpg?expires=1760263938&signature=8b2e47", + "phone_number": null, + "country_code": null, + "email": null + }, + { + "type": "phone", + "session_id": "77777777-8888-9999-0000-111111111111", + "session_number": 1017, + "blocklisted_at": "2026-10-08T15:31:09.400127Z", + "face_image": null, + "document_type": null, + "document_number": null, + "front_image": null, + "phone_number": "+14155552671", + "country_code": "US", + "email": null + } + ] + } + } + } + } + } + }, + "400": { + "description": "A query parameter is invalid.", + "content": { + "application/json": { + "examples": { + "Unknown item type": { + "value": { + "item_type": [ + "\"wallet\" is not a valid choice." + ] + } + }, + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: read` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no read access to workflows." + } + } + } + } + } + }, + "429": { + "description": "The shared read limit of 600 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/blocklist/add/": { + "post": { + "summary": "Add session items to the blocklist", + "description": "Blocklists the face, identity document, phone number or email address of one person verification session of the application. For each selected type, Didit takes the session's most recent record, flags it as blocklisted and adds an entry linked to the session to the matching system blocklist (`added_by: \"api\"`), so new sessions with a matching face, document number, phone number or email address are declined. Every selected item is checked first, so if one is missing nothing is blocklisted; repeating the call for an item already listed keeps its single entry and refreshes `blocklisted_at`. The session itself and sessions that already finished are not changed. A scoped API key needs `workflows: write`.", + "operationId": "add_to_blocklist", + "x-didit-sdk": { + "resource": "blocklist", + "method": "add", + "auth": "api_key", + "server": true + }, + "tags": [ + "Blocklist" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "session_id" + ], + "description": "At least one `blocklist_*` flag must be `true`.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "A person verification session of your application. Deleted sessions are refused." + }, + "blocklist_face": { + "type": "boolean", + "default": false, + "description": "Blocklist the session's face." + }, + "blocklist_document": { + "type": "boolean", + "default": false, + "description": "Blocklist the session's identity document. The document needs its issuing state, document type, document number and full name." + }, + "blocklist_phone": { + "type": "boolean", + "default": false, + "description": "Blocklist the session's phone number." + }, + "blocklist_email": { + "type": "boolean", + "default": false, + "description": "Blocklist the session's email address." + } + } + }, + "examples": { + "Face and document": { + "value": { + "session_id": "33333333-4444-5555-6666-777777777777", + "blocklist_face": true, + "blocklist_document": true + } + }, + "Phone only": { + "value": { + "session_id": "33333333-4444-5555-6666-777777777777", + "blocklist_phone": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/blocklist/add/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"session_id\": \"33333333-4444-5555-6666-777777777777\",\n \"blocklist_face\": true,\n \"blocklist_document\": true\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.blocklist.add(\n session_id=\"33333333-4444-5555-6666-777777777777\",\n blocklist_face=True,\n blocklist_document=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.blocklist.add({\n session_id: \"33333333-4444-5555-6666-777777777777\",\n blocklist_face: true,\n blocklist_document: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Blocklist.Add(ctx, didit.BlocklistAddParams{\n\tSessionID: \"33333333-4444-5555-6666-777777777777\",\n\tBlocklistFace: didit.Bool(true),\n\tBlocklistDocument: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BlocklistAddParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.blocklist().add(BlocklistAddParams.builder()\n .sessionId(\"33333333-4444-5555-6666-777777777777\")\n .blocklistFace(true)\n .blocklistDocument(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "blocklist->add([\n 'session_id' => '33333333-4444-5555-6666-777777777777',\n 'blocklist_face' => true,\n 'blocklist_document' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.blocklist.add(\n session_id: \"33333333-4444-5555-6666-777777777777\",\n blocklist_face: true,\n blocklist_document: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Blocklist.AddAsync(new BlocklistAddParams\n{\n SessionId = \"33333333-4444-5555-6666-777777777777\",\n BlocklistFace = true,\n BlocklistDocument = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The selected items were blocklisted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string", + "description": "Always `Successfully updated blocklist.`" + } + } + }, + "examples": { + "Blocklisted": { + "value": { + "message": "Successfully updated blocklist." + } + } + } + } + } + }, + "400": { + "description": "The body is invalid, the session is not a verification session of this application, or it has no record of a selected type. Nothing was blocklisted.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "value": { + "non_field_errors": [ + "Session not found" + ] + } + }, + "No item selected": { + "value": { + "non_field_errors": [ + "Must specify at least one item to blocklist" + ] + } + }, + "No face record": { + "value": { + "non_field_errors": [ + "Session does not have a face record" + ] + } + }, + "Incomplete document": { + "value": { + "non_field_errors": [ + "To blocklist a document, the document must have an issuing state, document type, document number and full name" + ] + } + }, + "Invalid session id": { + "value": { + "session_id": [ + "Must be a valid UUID." + ] + } + }, + "Missing session id": { + "value": { + "session_id": [ + "This field is required." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/blocklist/remove/": { + "post": { + "summary": "Remove session items from the blocklist", + "description": "Lifts the block on the face, identity document, phone number or email address of one person verification session of the application. For each selected type, Didit takes the session's most recent record, clears its blocklisted flag and deletes the system blocklist entries created from that record, so it no longer declines new sessions. Entries added to a list by value are not touched: find them with [List list entries](https://docs.didit.me/management-api/lists/list-entries) and delete them with [Delete a list entry](https://docs.didit.me/management-api/lists/delete-entry). Unblocking a record that is not blocklisted succeeds and changes nothing, and sessions that already finished are not changed. A scoped API key needs `workflows: write`.", + "operationId": "remove_from_blocklist", + "x-didit-sdk": { + "resource": "blocklist", + "method": "remove", + "auth": "api_key", + "server": true + }, + "tags": [ + "Blocklist" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "session_id" + ], + "description": "At least one `unblock_*` flag must be `true`.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "A person verification session of your application. Deleted sessions are refused." + }, + "unblock_face": { + "type": "boolean", + "default": false, + "description": "Lift the block on the session's face." + }, + "unblock_document": { + "type": "boolean", + "default": false, + "description": "Lift the block on the session's identity document." + }, + "unblock_phone": { + "type": "boolean", + "default": false, + "description": "Lift the block on the session's phone number." + }, + "unblock_email": { + "type": "boolean", + "default": false, + "description": "Lift the block on the session's email address." + } + } + }, + "examples": { + "Face and document": { + "value": { + "session_id": "33333333-4444-5555-6666-777777777777", + "unblock_face": true, + "unblock_document": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/blocklist/remove/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"session_id\": \"33333333-4444-5555-6666-777777777777\",\n \"unblock_face\": true,\n \"unblock_document\": true\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.blocklist.remove(\n session_id=\"33333333-4444-5555-6666-777777777777\",\n unblock_face=True,\n unblock_document=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.blocklist.remove({\n session_id: \"33333333-4444-5555-6666-777777777777\",\n unblock_face: true,\n unblock_document: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Blocklist.Remove(ctx, didit.BlocklistRemoveParams{\n\tSessionID: \"33333333-4444-5555-6666-777777777777\",\n\tUnblockFace: didit.Bool(true),\n\tUnblockDocument: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BlocklistRemoveParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.blocklist().remove(BlocklistRemoveParams.builder()\n .sessionId(\"33333333-4444-5555-6666-777777777777\")\n .unblockFace(true)\n .unblockDocument(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "blocklist->remove([\n 'session_id' => '33333333-4444-5555-6666-777777777777',\n 'unblock_face' => true,\n 'unblock_document' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.blocklist.remove(\n session_id: \"33333333-4444-5555-6666-777777777777\",\n unblock_face: true,\n unblock_document: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Blocklist.RemoveAsync(new BlocklistRemoveParams\n{\n SessionId = \"33333333-4444-5555-6666-777777777777\",\n UnblockFace = true,\n UnblockDocument = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The block on the selected items was lifted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string", + "description": "Always `Successfully removed from blocklist.`" + } + } + }, + "examples": { + "Unblocked": { + "value": { + "message": "Successfully removed from blocklist." + } + } + } + } + } + }, + "400": { + "description": "The body is invalid, the session is not a verification session of this application, or it has no record of a selected type. Nothing was changed.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "value": { + "non_field_errors": [ + "Session not found" + ] + } + }, + "No item selected": { + "value": { + "non_field_errors": [ + "Must specify at least one item to unblock" + ] + } + }, + "No email record": { + "value": { + "non_field_errors": [ + "Session does not have an email record" + ] + } + }, + "Invalid session id": { + "value": { + "session_id": [ + "Must be a valid UUID." + ] + } + }, + "Missing session id": { + "value": { + "session_id": [ + "This field is required." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/": { + "get": { + "summary": "Get webhook configuration", + "description": "Return the application's webhook settings in one call: every [webhook destination](https://docs.didit.me/management-api/webhook/list-destinations) with its signing secret, the event types a destination can subscribe to, the application-level (legacy) webhook URL, version and secret, the data and biometric-template retention policy, and the Digital ID wallet return hosts. Didit delivers webhooks to destinations, each signed with its own `secret_shared_key`; the top-level `webhook_url`, `webhook_version` and `secret_shared_key` are used only while no destination is enabled, and then only for `status.updated` and `data.updated`. The response contains signing secrets, so keep it out of logs and client-side code. A scoped API key needs `webhooks: read`.", + "operationId": "get_webhook_config", + "x-didit-sdk": { + "resource": "webhooks", + "method": "retrieve_config", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.retrieve_config()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.retrieveConfig();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.RetrieveConfig(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().retrieveConfig();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->retrieveConfig();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.retrieve_config\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.RetrieveConfigAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The webhook configuration.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookConfig" + }, + "examples": { + "One destination": { + "value": { + "webhook_url": "https://yourapp.com/webhooks/didit", + "webhook_version": "v3", + "secret_shared_key": "dGhpcy1pcy10aGUtYXBwLWxldmVsLXNlY3JldC0wMDA", + "capture_method": "MOBILE", + "data_retention_months": 24, + "face_retention_policy": "delete_with_session", + "face_retention_days": null, + "biometric_consent_mode": "auto", + "available_events": [ + { + "value": "status.updated", + "label": "Session status updated" + }, + { + "value": "data.updated", + "label": "Session data updated" + }, + { + "value": "user.status.updated", + "label": "User status updated" + }, + { + "value": "user.data.updated", + "label": "User data updated" + }, + { + "value": "business.status.updated", + "label": "Business status updated" + }, + { + "value": "business.data.updated", + "label": "Business data updated" + }, + { + "value": "activity.created", + "label": "Activity created" + }, + { + "value": "transaction.created", + "label": "Transaction created" + }, + { + "value": "transaction.status.updated", + "label": "Transaction status updated" + }, + { + "value": "travel_rule.status.updated", + "label": "Travel Rule Status Updated" + }, + { + "value": "workflow.compliance_changed", + "label": "Workflow Compliance Changed" + } + ], + "allowed_return_hosts": [ + "shop.example.com" + ], + "endpoints": [ + { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "secret_shared_key": "dGhpcy1pcy1hbi1leGFtcGxlLXNlY3JldC0wMDAwMDA", + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_at": "2026-01-04T08:00:00.000000Z", + "updated_at": "2026-04-22T14:09:11.523671Z" + } + ] + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks read": { + "value": { + "detail": "This API key has no read access to webhooks." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update webhook configuration", + "description": "Partially update application-level settings: the legacy webhook URL, version and secret, the data and biometric-template retention policy, and the Digital ID wallet return hosts. Send only the fields to change; the response is the full configuration, the same shape as `GET /v3/webhook/`. Destinations are not managed here: create, update and delete them with the `/v3/webhook/destinations/` endpoints, and do not send an `endpoints` field to this endpoint. A `data_retention_months` change also applies to existing sessions, and sessions it deletes cannot be restored; read [Data retention](https://docs.didit.me/console/data-retention) first. A scoped API key needs `webhooks: write`.", + "operationId": "update_webhook_config", + "x-didit-sdk": { + "resource": "webhooks", + "method": "update_config", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Send only the fields to change. Destinations are not managed here.", + "properties": { + "webhook_url": { + "type": "string", + "format": "uri", + "nullable": true, + "maxLength": 2000, + "description": "Legacy application-level webhook URL. Didit uses it only while the application has no enabled destination, and then only for `status.updated` and `data.updated`. It does not create a destination, and the next create, update or delete of a destination replaces it. To receive webhooks, create a destination with `POST /v3/webhook/destinations/` instead." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload version of the legacy `webhook_url`." + }, + "rotate_secret_key": { + "type": "boolean", + "default": false, + "description": "`true` replaces the application-level `secret_shared_key` with a new random value at once; the new value is in the response. Destination secrets are not changed. Every call with `true` generates a new secret, so it is not idempotent." + }, + "capture_method": { + "type": "string", + "enum": [ + "MOBILE", + "MOBILE_AND_DESKTOP" + ], + "description": "Legacy application setting. The verification flow does not read it." + }, + "data_retention_months": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 120, + "description": "Automatic deletion window in months (a month counts as 30 days), or `null` for no automatic deletion. A change applies to existing sessions too: every user verification session older than the new window is deleted automatically, and that deletion is irreversible." + }, + "face_retention_policy": { + "type": "string", + "enum": [ + "delete_with_session", + "retain_until_user_deleted" + ], + "description": "What happens to a session's face embedding when the session is deleted. `retain_until_user_deleted` needs `face_retention_days` in the same request or already set. A change applies to session deletions from then on; it creates or purges no existing biometric template." + }, + "face_retention_days": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 3650, + "description": "Retention period, in days, of biometric templates under `retain_until_user_deleted`." + }, + "biometric_consent_mode": { + "type": "string", + "enum": [ + "auto", + "always", + "never" + ], + "description": "When the hosted flow and the native SDKs ask the person for explicit biometric consent before a face capture. `auto` (default) asks only where a trigger applies: a declared state, the person's IP, or the identity document's issuer is in Illinois, Texas or Colorado, or the person looks to be in the US without a known state. `always` asks every person whose workflow captures a face. `never` never shows the step, and you collect consent yourself before the selfie. Each change is recorded in the audit log. See [Biometric consent](https://docs.didit.me/getting-started/biometric-consent)." + }, + "allowed_return_hosts": { + "type": "array", + "maxItems": 20, + "description": "Replaces the whole list of Digital ID wallet return hosts. Bare hostnames only, such as `shop.example.com`: no scheme, port, path or wildcard. Values are lowercased and duplicates are dropped. Send `[]` to clear the list.", + "items": { + "type": "string", + "maxLength": 253 + } + }, + "allowed_domains": { + "type": "array", + "maxItems": 50, + "items": { + "type": "string", + "maxLength": 300 + }, + "description": "Replaces the whole list of allowed origins. Origins (for example `https://app.example.com`; `http` only for `localhost`) the hosted verification flow may be embedded in and may send the user back to with a session callback. Server-to-server calls are not affected. An empty list means no restriction. Didit stores each origin in lower case without a default port. An invalid entry fails with `400` on `allowed_domains`. Send `[]` to remove the restriction.", + "example": [ + "https://app.example.com" + ] + } + } + }, + "examples": { + "Enable biometric template retention": { + "value": { + "face_retention_policy": "retain_until_user_deleted", + "face_retention_days": 365, + "biometric_consent_mode": "auto" + } + }, + "Rotate the legacy secret": { + "value": { + "rotate_secret_key": true + } + }, + "Set data retention": { + "value": { + "data_retention_months": 24 + } + }, + "Register return hosts": { + "value": { + "allowed_return_hosts": [ + "shop.example.com", + "app.example.com" + ] + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/webhook/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"face_retention_policy\": \"retain_until_user_deleted\", \"face_retention_days\": 365}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.update_config(\n face_retention_policy=\"retain_until_user_deleted\",\n face_retention_days=365,\n biometric_consent_mode=\"auto\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.updateConfig({\n face_retention_policy: \"retain_until_user_deleted\",\n face_retention_days: 365,\n biometric_consent_mode: \"auto\",\n});\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.WebhooksUpdateConfigParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().updateConfig(WebhooksUpdateConfigParams.builder()\n .faceRetentionPolicy(WebhooksUpdateConfigParams.FaceRetentionPolicy.of(\"retain_until_user_deleted\"))\n .faceRetentionDays(365L)\n .biometricConsentMode(WebhooksUpdateConfigParams.BiometricConsentMode.of(\"auto\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->updateConfig([\n 'face_retention_policy' => 'retain_until_user_deleted',\n 'face_retention_days' => 365,\n 'biometric_consent_mode' => 'auto',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.update_config(\n face_retention_policy: \"retain_until_user_deleted\",\n face_retention_days: 365,\n biometric_consent_mode: \"auto\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.UpdateConfigAsync(new WebhooksUpdateConfigParams\n{\n FaceRetentionPolicy = new WebhooksUpdateConfigParamsFaceRetentionPolicy(\"retain_until_user_deleted\"),\n FaceRetentionDays = 365,\n BiometricConsentMode = new WebhooksUpdateConfigParamsBiometricConsentMode(\"auto\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The full configuration after the update, the same shape as `GET /v3/webhook/`. When you rotated the secret, store the new `secret_shared_key` before you discard the response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookConfig" + }, + "examples": { + "Retention enabled": { + "value": { + "webhook_url": "https://yourapp.com/webhooks/didit", + "webhook_version": "v3", + "secret_shared_key": "dGhpcy1pcy10aGUtYXBwLWxldmVsLXNlY3JldC0wMDA", + "capture_method": "MOBILE", + "data_retention_months": 24, + "face_retention_policy": "retain_until_user_deleted", + "face_retention_days": 365, + "biometric_consent_mode": "auto", + "available_events": [ + { + "value": "status.updated", + "label": "Session status updated" + }, + { + "value": "data.updated", + "label": "Session data updated" + }, + { + "value": "user.status.updated", + "label": "User status updated" + }, + { + "value": "user.data.updated", + "label": "User data updated" + }, + { + "value": "business.status.updated", + "label": "Business status updated" + }, + { + "value": "business.data.updated", + "label": "Business data updated" + }, + { + "value": "activity.created", + "label": "Activity created" + }, + { + "value": "transaction.created", + "label": "Transaction created" + }, + { + "value": "transaction.status.updated", + "label": "Transaction status updated" + }, + { + "value": "travel_rule.status.updated", + "label": "Travel Rule Status Updated" + }, + { + "value": "workflow.compliance_changed", + "label": "Workflow Compliance Changed" + } + ], + "allowed_return_hosts": [ + "shop.example.com" + ], + "endpoints": [ + { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "secret_shared_key": "dGhpcy1pcy1hbi1leGFtcGxlLXNlY3JldC0wMDAwMDA", + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_at": "2026-01-04T08:00:00.000000Z", + "updated_at": "2026-04-22T14:09:11.523671Z" + } + ] + } + } + } + } + } + }, + "400": { + "description": "Validation failed: each invalid field is mapped to a list of messages, and nothing is changed. A body that is not valid JSON returns `{\"detail\": \"JSON parse error - ...\"}`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Each invalid field mapped to a list of messages.", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "Retention days missing": { + "value": { + "face_retention_days": [ + "A finite retention duration is required when face retention is enabled." + ], + "biometric_consent_mode": "auto" + } + }, + "Retention window out of range": { + "value": { + "data_retention_months": [ + "Ensure this value is less than or equal to 120." + ] + } + }, + "Invalid return host": { + "value": { + "allowed_return_hosts": [ + "'https://shop.example.com' is not a hostname. Register the bare host (e.g. shop.example.com): no scheme, port, path or wildcard." + ] + } + }, + "Invalid choice": { + "value": { + "face_retention_policy": [ + "\"forever\" is not a valid choice." + ] + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks write": { + "value": { + "detail": "This API key has no write access to webhooks." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 300 write requests (POST, PATCH, PUT, DELETE) in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (300).", + "schema": { + "type": "integer", + "example": 300 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-write" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/analytics/": { + "get": { + "summary": "Get webhook destination analytics", + "description": "Delivery totals, failure rate and response times for one webhook destination, over the whole range (`summary`) and per UTC day (`points`). Every attempt counts as a delivery: the first send of an event, each automatic [retry](https://docs.didit.me/integration/webhooks#retry-policy) and each resend. A delivery is failed when its recorded HTTP status is 400 or above, so timeouts (recorded as `504`) and connection failures (`503`) count as failed; a delivery still queued counts in `total_deliveries` only. Delivery records are available for about 30 days. A scoped API key needs `webhooks: read`.", + "operationId": "get_webhook_destination_analytics", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "retrieve_analytics", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination (the `uuid` returned by `GET /v3/webhook/destinations/`).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "date_from", + "in": "query", + "required": false, + "description": "First UTC day to include, as `YYYY-MM-DD` (inclusive). Omit to start from the oldest delivery available. `dateFrom` is accepted as an alias.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-10-01" + }, + { + "name": "date_to", + "in": "query", + "required": false, + "description": "Last UTC day to include, as `YYYY-MM-DD` (inclusive). Must not be earlier than `date_from`. `dateTo` is accepted as an alias.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-10-16" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/analytics/?date_from=2026-10-01&date_to=2026-10-16' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.retrieve_analytics(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.retrieveAnalytics(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.RetrieveAnalytics(ctx, \"11111111-2222-3333-4444-555555555555\", didit.WebhooksDestinationsRetrieveAnalyticsParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().retrieveAnalytics(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->retrieveAnalytics('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.destinations.retrieve_analytics(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.RetrieveAnalyticsAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Delivery statistics for the destination.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "summary", + "points" + ], + "properties": { + "summary": { + "type": "object", + "description": "Totals over the requested range.", + "required": [ + "total_deliveries", + "failed_deliveries", + "min_response_time_ms", + "avg_response_time_ms", + "max_response_time_ms", + "error_rate_percentage" + ], + "properties": { + "total_deliveries": { + "type": "integer", + "minimum": 0, + "description": "Delivery attempts, including queued ones." + }, + "failed_deliveries": { + "type": "integer", + "minimum": 0, + "description": "Attempts whose recorded HTTP status is 400 or above." + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Fastest attempt in milliseconds, or `null` when no attempt has a recorded time." + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Mean attempt time in milliseconds, rounded, or `null` when no attempt has a recorded time." + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Slowest attempt in milliseconds, or `null` when no attempt has a recorded time." + }, + "error_rate_percentage": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "`failed_deliveries` / `total_deliveries` x 100, rounded to a whole number. `0` when there are no deliveries." + } + } + }, + "points": { + "type": "array", + "description": "One entry per UTC day that had deliveries, oldest first. Days without deliveries are omitted, so fill the gaps with zeros when you chart them.", + "items": { + "type": "object", + "required": [ + "date", + "total_deliveries", + "failed_deliveries", + "min_response_time_ms", + "avg_response_time_ms", + "max_response_time_ms" + ], + "properties": { + "date": { + "type": "string", + "format": "date", + "description": "UTC day, `YYYY-MM-DD`." + }, + "total_deliveries": { + "type": "integer", + "minimum": 0, + "description": "Delivery attempts, including queued ones." + }, + "failed_deliveries": { + "type": "integer", + "minimum": 0, + "description": "Attempts whose recorded HTTP status is 400 or above." + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Fastest attempt in milliseconds, or `null` when no attempt has a recorded time." + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Mean attempt time in milliseconds, rounded, or `null` when no attempt has a recorded time." + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Slowest attempt in milliseconds, or `null` when no attempt has a recorded time." + } + } + } + } + } + }, + "examples": { + "Three days of deliveries": { + "value": { + "summary": { + "total_deliveries": 1250, + "failed_deliveries": 12, + "min_response_time_ms": 38, + "avg_response_time_ms": 164, + "max_response_time_ms": 5004, + "error_rate_percentage": 1 + }, + "points": [ + { + "date": "2026-10-14", + "total_deliveries": 410, + "failed_deliveries": 0, + "min_response_time_ms": 38, + "avg_response_time_ms": 152, + "max_response_time_ms": 911 + }, + { + "date": "2026-10-15", + "total_deliveries": 395, + "failed_deliveries": 12, + "min_response_time_ms": 41, + "avg_response_time_ms": 190, + "max_response_time_ms": 5004 + }, + { + "date": "2026-10-16", + "total_deliveries": 445, + "failed_deliveries": 0, + "min_response_time_ms": 40, + "avg_response_time_ms": 151, + "max_response_time_ms": 1220 + } + ] + } + }, + "No deliveries in range": { + "value": { + "summary": { + "total_deliveries": 0, + "failed_deliveries": 0, + "min_response_time_ms": null, + "avg_response_time_ms": null, + "max_response_time_ms": null, + "error_rate_percentage": 0 + }, + "points": [] + } + } + } + } + } + }, + "400": { + "description": "`date_from` or `date_to` is not a `YYYY-MM-DD` date, or `date_to` is earlier than `date_from`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Each invalid field mapped to a list of messages.", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "Malformed date": { + "value": { + "date_from": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + }, + "Reversed range": { + "value": { + "date_to": [ + "date_to must be greater than or equal to date_from." + ] + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks read": { + "value": { + "detail": "This API key has no read access to webhooks." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No destination with this UUID exists for the application, or it has been deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Destination not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/deliveries/": { + "get": { + "summary": "List webhook destination deliveries", + "description": "Page through every delivery attempt to one webhook destination, newest first. The first send of each event, each automatic [retry](https://docs.didit.me/integration/webhooks#retry-policy) and each resend are separate entries. Entries carry the HTTP status and response time but not the payload: fetch one delivery to read its headers and body. `count` is exact up to 100 and reads 100 above that, so follow `next` until it is `null`. Delivery records are available for about 30 days. A scoped API key needs `webhooks: read` and `decisions: read`; a key limited to some workflows or to approved sessions cannot use this endpoint.", + "operationId": "list_webhook_destination_deliveries", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "list_deliveries", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination (the `uuid` returned by `GET /v3/webhook/destinations/`).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of deliveries per page. Defaults to 50. A value that is not a positive integer falls back to 50; a value above 1000000 returns `400`.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "example": 50 + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of deliveries to skip. Defaults to 0. Prefer following the `next` and `previous` URLs from the response.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "example": 0 + }, + { + "name": "date_from", + "in": "query", + "required": false, + "description": "First UTC day to include, as `YYYY-MM-DD` (inclusive). Omit to start from the oldest delivery available. `dateFrom` is accepted as an alias.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-10-01" + }, + { + "name": "date_to", + "in": "query", + "required": false, + "description": "Last UTC day to include, as `YYYY-MM-DD` (inclusive). Must not be earlier than `date_from`. `dateTo` is accepted as an alias.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-10-16" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/deliveries/?limit=50&date_from=2026-10-01' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.webhooks.destinations.list_deliveries(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.webhooks.destinations.listDeliveries(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Webhooks.Destinations.ListDeliveriesAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.WebhooksDestinationsListDeliveriesParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.webhooks().destinations().listDeliveries(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->listDeliveries('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.webhooks.destinations.list_deliveries(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Webhooks.Destinations.ListDeliveriesAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "One page of deliveries, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "description": "Matching deliveries, exact up to 100 and capped at 100 above that. Use `next` to know whether more pages exist." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebhookDestinationDelivery" + } + } + } + }, + "examples": { + "Failed delivery, its retry and a queued event": { + "value": { + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "uuid": "66666666-7777-8888-9999-000000000003", + "session_id": null, + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": null, + "response_time_ms": null, + "status": "APPROVED", + "webhook_type": "transaction.created", + "created_at": "2026-10-15T09:23:02.871190Z" + }, + { + "uuid": "66666666-7777-8888-9999-000000000002", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 200, + "response_time_ms": 143, + "status": "In Progress", + "webhook_type": "status.updated", + "created_at": "2026-10-15T09:22:19.104233Z" + }, + { + "uuid": "66666666-7777-8888-9999-000000000001", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 503, + "response_time_ms": 412, + "status": "In Progress", + "webhook_type": "status.updated", + "created_at": "2026-10-15T09:21:18.412345Z" + } + ] + } + } + } + } + } + }, + "400": { + "description": "`date_from` or `date_to` is not a `YYYY-MM-DD` date, `date_to` is earlier than `date_from`, or `limit` or `offset` is above 1000000 (that error is a JSON array of messages).", + "content": { + "application/json": { + "examples": { + "Malformed date": { + "value": { + "date_from": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + }, + "Reversed range": { + "value": { + "date_to": [ + "date_to must be greater than or equal to date_from." + ] + } + }, + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` A key limited to some workflows or to approved sessions gets `This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks read": { + "value": { + "detail": "This API key has no read access to webhooks." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No destination with this UUID exists for the application, or it has been deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Destination not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/deliveries/{delivery_uuid}/": { + "get": { + "summary": "Get webhook destination delivery", + "description": "Retrieve one delivery attempt to a webhook destination with the exact request headers and JSON body Didit sent, the HTTP status your endpoint returned and the response time. `request_header` and `request_body` are JSON text: parse them to read the signature headers and the payload, for example to check your [signature verification](https://docs.didit.me/integration/webhooks#signature-verification) against the destination's `secret_shared_key`. A delivery never contains the signing secret, and Didit does not record your endpoint's response body. A scoped API key needs `webhooks: read` and `decisions: read`; a key limited to some workflows or to approved sessions cannot use this endpoint.", + "operationId": "get_webhook_destination_delivery", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "retrieve_delivery", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination the delivery was sent to.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "delivery_uuid", + "in": "path", + "required": true, + "description": "UUID of the delivery: the `uuid` of an entry returned by `GET /v3/webhook/destinations/{destination_uuid}/deliveries/`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "66666666-7777-8888-9999-000000000001" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/deliveries/66666666-7777-8888-9999-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.retrieve_delivery(\n \"11111111-2222-3333-4444-555555555555\",\n \"66666666-7777-8888-9999-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.retrieveDelivery(\n \"11111111-2222-3333-4444-555555555555\",\n \"66666666-7777-8888-9999-000000000001\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.RetrieveDelivery(ctx, \"11111111-2222-3333-4444-555555555555\", \"66666666-7777-8888-9999-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().retrieveDelivery(\"11111111-2222-3333-4444-555555555555\", \"66666666-7777-8888-9999-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->retrieveDelivery('11111111-2222-3333-4444-555555555555', '66666666-7777-8888-9999-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.destinations.retrieve_delivery(\n \"11111111-2222-3333-4444-555555555555\",\n \"66666666-7777-8888-9999-000000000001\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.RetrieveDeliveryAsync(\"11111111-2222-3333-4444-555555555555\", \"66666666-7777-8888-9999-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The delivery, with its request headers and body.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookDestinationDeliveryDetail" + }, + "examples": { + "Connection failure": { + "value": { + "uuid": "66666666-7777-8888-9999-000000000001", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 503, + "response_time_ms": 412, + "status": "In Progress", + "webhook_type": "status.updated", + "created_at": "2026-10-15T09:21:18.412345Z", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"a5e02a03404e1337a0198e6be15d34abbd053c658a0e51e5d0e78554213ef29b\", \"X-Signature-V2\": \"927e7cb770c94ce745939bff73f04aa200015f3bc64795ba46c2aaf78a6cedc5\", \"X-Signature-Simple\": \"e37b0d4c716c72bb5ae7127b9cc455d28ab4686e9e22312b4ec2327409a73302\", \"X-Timestamp\": \"1792056078\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792056078,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` A key limited to some workflows or to approved sessions gets `This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks read": { + "value": { + "detail": "This API key has no read access to webhooks." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The destination does not exist for the application or has been deleted, or the delivery does not exist, belongs to another destination, or is no longer available.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Destination not found": { + "value": { + "detail": "Webhook destination not found." + } + }, + "Delivery not found": { + "value": { + "detail": "Webhook delivery not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/deliveries/{delivery_uuid}/resend/": { + "post": { + "summary": "Resend webhook destination delivery", + "description": "Send a past delivery again, for example after your endpoint was down. Didit takes the stored payload, sets a fresh `timestamp`, signs it again with the destination's current `secret_shared_key` (`X-Signature`, `X-Signature-V2`, `X-Signature-Simple` and `X-Timestamp`) and posts it to the delivery's `request_url`, even if the destination's URL has changed since or the destination is disabled. `event_id` and the rest of the payload are unchanged, so deduplicate on `event_id`. The call waits up to 5 seconds for your endpoint and returns the new delivery with `200` whatever your endpoint answered: read `response_status`. A resend is not retried automatically, every call sends again and creates a new delivery, and the request has no body. A scoped API key needs `webhooks: write` and `decisions: read`; a key limited to some workflows or to approved sessions cannot use this endpoint.", + "operationId": "resend_webhook_destination_delivery", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "resend_delivery", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination the delivery was sent to.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "delivery_uuid", + "in": "path", + "required": true, + "description": "UUID of the delivery to resend: the `uuid` of an entry returned by `GET /v3/webhook/destinations/{destination_uuid}/deliveries/`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "66666666-7777-8888-9999-000000000001" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/deliveries/66666666-7777-8888-9999-000000000001/resend/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.resend_delivery(\n \"11111111-2222-3333-4444-555555555555\",\n \"66666666-7777-8888-9999-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.resendDelivery(\n \"11111111-2222-3333-4444-555555555555\",\n \"66666666-7777-8888-9999-000000000001\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.ResendDelivery(ctx, \"11111111-2222-3333-4444-555555555555\", \"66666666-7777-8888-9999-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().resendDelivery(\"11111111-2222-3333-4444-555555555555\", \"66666666-7777-8888-9999-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->resendDelivery('11111111-2222-3333-4444-555555555555', '66666666-7777-8888-9999-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.destinations.resend_delivery(\n \"11111111-2222-3333-4444-555555555555\",\n \"66666666-7777-8888-9999-000000000001\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.ResendDeliveryAsync(\"11111111-2222-3333-4444-555555555555\", \"66666666-7777-8888-9999-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The new delivery, already attempted. `response_status` is what your endpoint answered this time (`504` timeout, `503` connection failure). The original delivery is unchanged.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookDestinationDeliveryDetail" + }, + "examples": { + "Accepted on resend": { + "value": { + "uuid": "66666666-7777-8888-9999-000000000004", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 200, + "response_time_ms": 151, + "status": "In Progress", + "webhook_type": "status.updated", + "created_at": "2026-10-16T14:05:42.330981Z", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"b8eb1e376517ee0cad8ba98ff7f8fd35fc72eca231000963a96a335f82328835\", \"X-Signature-V2\": \"9efd2ce9ed1303e1d05273ef9608a59363ed9789a05253db4748b3e8e50cb109\", \"X-Signature-Simple\": \"bf3a9dc04b2c20ab539203b0cd5cca2a10f630bf62d8feb6d68d82af80f64774\", \"X-Timestamp\": \"1792159542\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792159542,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}" + } + } + } + } + } + }, + "400": { + "description": "The stored delivery could not be signed again. Nothing is sent.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string", + "description": "Stable machine-readable error code. Branch on this, not on `detail`." + } + } + }, + "examples": { + "Unsignable delivery": { + "value": { + "detail": "Invalid webhook data format", + "code": "invalid_webhook_payload" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` A key limited to some workflows or to approved sessions gets `This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks write": { + "value": { + "detail": "This API key has no write access to webhooks." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The destination does not exist for the application or has been deleted, or the delivery does not exist, belongs to another destination, or is no longer available.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Destination not found": { + "value": { + "detail": "Webhook destination not found." + } + }, + "Delivery not found": { + "value": { + "detail": "Webhook delivery not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 300 write requests (POST, PATCH, PUT, DELETE) in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (300).", + "schema": { + "type": "integer", + "example": 300 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-write" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/": { + "get": { + "summary": "List monitoring events", + "description": "List the monitoring events stored for your application, newest `occurred_at` first, with optional filters that combine with AND. Multi-value filters (`event_category`, `action_type`, `status`, `retention_class`) repeat the parameter and match any of the values; the other filters are exact matches, and unknown values simply return no events. Each result is a summary: call `GET /v3/events/{event_uuid}/` for the payload, device context, IP enrichment and rule evidence, and prefer `GET /v3/events/subjects/{vendor_data}/` for one subject's history. Reads work whether or not event ingestion is switched on. A scoped API key needs `transactions: read`. See [Event ingestion](https://docs.didit.me/transaction-monitoring/events).", + "operationId": "list_monitoring_events", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of events per page. Defaults to 50. Values above 1000000 return `400`; a non-numeric, zero or negative value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "example": 50 + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of events to skip. Defaults to 0. Prefer following the `next` URL from the response.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "example": 0 + }, + { + "name": "event_category", + "in": "query", + "required": false, + "description": "Only events in these categories. Repeat the parameter for several values (`?event_category=payment&event_category=dispute`); an event matching any of them is returned.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ] + } + }, + "example": [ + "payment", + "dispute" + ] + }, + { + "name": "action_type", + "in": "query", + "required": false, + "description": "Only events with these action types (exact, case-sensitive). Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "example": [ + "deposit_completed", + "chargeback_opened" + ] + }, + { + "name": "status", + "in": "query", + "required": false, + "description": "Only events with these statuses. Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ] + } + }, + "example": [ + "IN_REVIEW", + "DECLINED" + ] + }, + { + "name": "retention_class", + "in": "query", + "required": false, + "description": "Only events in these retention classes. Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ] + } + }, + "example": [ + "risk_relevant" + ] + }, + { + "name": "subject_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the subject's `vendor_data`. The same id can name an individual and a company; add `subject_entity_type` to pick one, or use `GET /v3/events/subjects/{vendor_data}/`.", + "schema": { + "type": "string" + }, + "example": "user-42" + }, + { + "name": "subject_entity_type", + "in": "query", + "required": false, + "description": "`company` returns only events of company subjects; any other non-empty value (for example `individual`) returns only events of individuals. Omit it to return both.", + "schema": { + "type": "string", + "enum": [ + "individual", + "company" + ] + }, + "example": "individual" + }, + { + "name": "business_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the `business.vendor_data` sent with the event.", + "schema": { + "type": "string" + }, + "example": "merchant-7" + }, + { + "name": "session_id", + "in": "query", + "required": false, + "description": "Exact match on `session.session_id`.", + "schema": { + "type": "string" + }, + "example": "web-session-abc" + }, + { + "name": "device_fingerprint", + "in": "query", + "required": false, + "description": "Exact match on `device_context.fingerprint`.", + "schema": { + "type": "string" + }, + "example": "fp-a8c1" + }, + { + "name": "ip_address", + "in": "query", + "required": false, + "description": "Exact match on the event's IP address.", + "schema": { + "type": "string" + }, + "example": "203.0.113.42" + }, + { + "name": "game_id", + "in": "query", + "required": false, + "description": "Exact match on the promoted `game_id`.", + "schema": { + "type": "string" + }, + "example": "poker-holdem-01" + }, + { + "name": "round_id", + "in": "query", + "required": false, + "description": "Exact match on the promoted `round_id`.", + "schema": { + "type": "string" + }, + "example": "round-88231" + }, + { + "name": "affiliate_code", + "in": "query", + "required": false, + "description": "Exact match on the promoted `affiliate_code`.", + "schema": { + "type": "string" + }, + "example": "aff-summer" + }, + { + "name": "payment_reference_id", + "in": "query", + "required": false, + "description": "Exact match on the payment reference (from `transaction_context.payment_reference_id` or `payload.payment_reference_id`).", + "schema": { + "type": "string" + }, + "example": "dep_735" + }, + { + "name": "counterparty_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the promoted `counterparty_vendor_data`.", + "schema": { + "type": "string" + }, + "example": "user-77" + }, + { + "name": "matched_only", + "in": "query", + "required": false, + "description": "`true` (also `1` or `yes`, case-insensitive) returns only events at least one rule matched (`rules_matched_count` above 0). Any other value is ignored.", + "schema": { + "type": "boolean", + "default": false + }, + "example": true + }, + { + "name": "occurred_from", + "in": "query", + "required": false, + "description": "Only events with `occurred_at` at or after this instant (inclusive). Send an ISO 8601 value: `2026-10-01T00:00:00Z`, an explicit offset with the `+` percent-encoded (`2026-10-01T02:00:00%2B02:00`; an unencoded `+` is decoded as a space and the value no longer parses), or a date alone (`2026-10-01`), which means 00:00:00 UTC of that day. A value without an offset is read as UTC. Values in any other format are not accepted.", + "schema": { + "type": "string", + "format": "date-time" + }, + "example": "2026-10-01T00:00:00Z" + }, + { + "name": "occurred_to", + "in": "query", + "required": false, + "description": "Only events with `occurred_at` at or before this instant (inclusive). A date alone means the start of that day, so `occurred_to=2026-10-15` excludes the rest of 15 October; send `2026-10-15T23:59:59Z` to include it. Send an ISO 8601 value: `2026-10-01T00:00:00Z`, an explicit offset with the `+` percent-encoded (`2026-10-01T02:00:00%2B02:00`; an unencoded `+` is decoded as a space and the value no longer parses), or a date alone (`2026-10-01`), which means 00:00:00 UTC of that day. A value without an offset is read as UTC. Values in any other format are not accepted.", + "schema": { + "type": "string", + "format": "date-time" + }, + "example": "2026-10-17T23:59:59Z" + }, + { + "name": "search", + "in": "query", + "required": false, + "description": "Case-insensitive substring match on `event_id`, `action_type`, `subject_vendor_data` or `counterparty_vendor_data`.", + "schema": { + "type": "string" + }, + "example": "chargeback" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/events/?event_category=payment&event_category=dispute&matched_only=true&occurred_from=2026-10-01T00:00:00Z&limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.monitoring_events.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.monitoringEvents.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.MonitoringEvents.ListAutoPaging(ctx, didit.MonitoringEventsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.monitoringEvents().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.monitoring_events.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.MonitoringEvents.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "One page of event summaries.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventPage" + }, + "examples": { + "Payment and dispute events": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "event_uuid": "11111111-2222-3333-4444-555555555555", + "event_id": "chargeback-2026-10-16-000318", + "event_category": "dispute", + "action_type": "chargeback_opened", + "occurred_at": "2026-10-16T14:02:11Z", + "received_at": "2026-10-16T14:02:12.418230Z", + "status": "IN_REVIEW", + "score": 60, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": null, + "device_fingerprint": null, + "ip_address": null, + "ip_country": null, + "amount": "250.000000000000000000", + "currency": "EUR", + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": "dep_735", + "retention_class": "monetary_or_compliance", + "rules_matched_count": 1, + "correlation_pending": false, + "is_droppable": false + }, + { + "event_uuid": "22222222-3333-4444-5555-666666666666", + "event_id": "login-2026-10-15-000912", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-15T08:30:00.120000Z", + "received_at": "2026-10-15T08:30:00.734512Z", + "status": "ACCEPTED", + "score": 0, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": "web-session-abc", + "device_fingerprint": "fp-a8c1", + "ip_address": "203.0.113.42", + "ip_country": "ES", + "amount": null, + "currency": null, + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": null, + "retention_class": "standard", + "rules_matched_count": 0, + "correlation_pending": false, + "is_droppable": true + } + ] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is larger than 1000000. The body is a JSON array with one message. A non-numeric or non-positive `limit` falls back to the default of 50 instead of failing.", + "content": { + "application/json": { + "examples": { + "limit too large": { + "summary": "limit above 1000000", + "value": [ + "limit must not exceed 1000000." + ] + }, + "offset too large": { + "summary": "offset above 1000000", + "value": [ + "offset must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: read`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: `GET` requests share a budget of 600 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create monitoring event", + "description": "Send one non-monetary event (login, session, gameplay, bonus, affiliate, payment, dispute, security, responsible gambling or custom) from your backend. The event is validated against its category contract, stored, IP-enriched once per unique public address, and evaluated by your real-time event rules before the response returns; cross-subject and long-window rules run asynchronously (`correlation_pending: true`), and in a sandbox application no rules run. `event_id` is the idempotency key: the same `event_id` with the same `event_category`, `action_type`, `occurred_at`, `subject.vendor_data` and `payload` returns `200` with `idempotent_replay: true` and the stored outcome (other envelope fields are not compared), while different values return `409`. Event ingestion is opt-in per application: until it is switched on this endpoint returns `403` with `code: event_monitoring_disabled` and stores nothing. A scoped API key needs `transactions: write`. See [Event ingestion](https://docs.didit.me/transaction-monitoring/events) and [Rules on events](https://docs.didit.me/transaction-monitoring/event-rules); to send events from a browser or app, use [SDK event submission](https://docs.didit.me/transaction-monitoring/sdk-event-submission).", + "operationId": "create_monitoring_event", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventCreateRequest" + }, + "examples": { + "Login": { + "value": { + "event_id": "login-2026-10-17-000001", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-17T09:15:02.481Z", + "time_zone": "Europe/Madrid", + "subject": { + "vendor_data": "user-42", + "entity_type": "individual" + }, + "session": { + "session_id": "web-session-abc", + "sequence": 142 + }, + "device_context": { + "fingerprint": "fp-a8c1", + "network_context": { + "ip_address": "203.0.113.42" + } + }, + "payload": { + "method": "password" + }, + "custom_values": { + "vip_tier": "gold" + } + } + }, + "Deposit completed": { + "value": { + "event_id": "deposit-2026-10-17-000002", + "event_category": "payment", + "action_type": "deposit_completed", + "occurred_at": "2026-10-17T09:16:40Z", + "subject": { + "vendor_data": "user-42" + }, + "transaction_context": { + "amount": "100.00", + "currency": "EUR", + "direction": "inbound", + "payment_reference_id": "dep_736" + }, + "payload": { + "payment_method": "card", + "status": "completed" + } + } + }, + "Chargeback opened": { + "value": { + "event_id": "chargeback-2026-10-16-000318", + "event_category": "dispute", + "action_type": "chargeback_opened", + "occurred_at": "2026-10-16T14:02:11Z", + "subject": { + "vendor_data": "user-42" + }, + "payload": { + "payment_reference_id": "dep_735", + "chargeback_id": "cb_5521", + "amount": "250.00", + "currency": "EUR", + "reason_code": "fraud" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/events/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"event_id\": \"login-2026-10-17-000001\",\n \"event_category\": \"auth\",\n \"action_type\": \"login\",\n \"occurred_at\": \"2026-10-17T09:15:02.481Z\",\n \"subject\": {\"vendor_data\": \"user-42\", \"entity_type\": \"individual\"},\n \"session\": {\"session_id\": \"web-session-abc\", \"sequence\": 142},\n \"device_context\": {\n \"fingerprint\": \"fp-a8c1\",\n \"network_context\": {\"ip_address\": \"203.0.113.42\"}\n },\n \"payload\": {\"method\": \"password\"}\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.monitoring_events.create(\n event_id=\"login-2026-10-17-000001\",\n event_category=\"auth\",\n action_type=\"login\",\n occurred_at=\"2026-10-17T09:15:02.481Z\",\n time_zone=\"Europe/Madrid\",\n subject={\n \"vendor_data\": \"user-42\",\n \"entity_type\": \"individual\",\n },\n session={\n \"session_id\": \"web-session-abc\",\n \"sequence\": 142,\n },\n device_context={\n \"fingerprint\": \"fp-a8c1\",\n \"network_context\": {\n \"ip_address\": \"203.0.113.42\",\n },\n },\n payload={\"method\": \"password\"},\n custom_values={\"vip_tier\": \"gold\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.monitoringEvents.create({\n event_id: \"login-2026-10-17-000001\",\n event_category: \"auth\",\n action_type: \"login\",\n occurred_at: \"2026-10-17T09:15:02.481Z\",\n time_zone: \"Europe/Madrid\",\n subject: {\n vendor_data: \"user-42\",\n entity_type: \"individual\",\n },\n session: {\n session_id: \"web-session-abc\",\n sequence: 142,\n },\n device_context: {\n fingerprint: \"fp-a8c1\",\n network_context: {\n ip_address: \"203.0.113.42\",\n },\n },\n payload: { method: \"password\" },\n custom_values: { vip_tier: \"gold\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.MonitoringEvents.Create(ctx, didit.MonitoringEventCreateRequest{\n\tEventID: \"login-2026-10-17-000001\",\n\tEventCategory: \"auth\",\n\tActionType: \"login\",\n\tOccurredAt: time.Date(2026, 10, 17, 9, 15, 2, 0, time.UTC),\n\tTimeZone: didit.String(\"Europe/Madrid\"),\n\tSubject: didit.MonitoringEventCreateRequestSubject{\n\t\tVendorData: \"user-42\",\n\t\tEntityType: didit.String(\"individual\"),\n\t},\n\tSession: didit.Some(didit.MonitoringEventCreateRequestSession{\n\t\tSessionID: didit.String(\"web-session-abc\"),\n\t\tSequence: didit.Int(142),\n\t}),\n\tDeviceContext: didit.Some(didit.MonitoringEventCreateRequestDeviceContext{\n\t\tFingerprint: didit.String(\"fp-a8c1\"),\n\t\tNetworkContext: didit.Some(didit.MonitoringEventCreateRequestDeviceContextNetworkContext{\n\t\t\tIPAddress: didit.String(\"203.0.113.42\"),\n\t\t}),\n\t}),\n\tPayload: didit.Some(map[string]any{\"method\": \"password\"}),\n\tCustomValues: didit.Some(map[string]any{\"vip_tier\": \"gold\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.OffsetDateTime;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.MonitoringEventCreateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.monitoringEvents().create(MonitoringEventCreateRequest.builder()\n .eventId(\"login-2026-10-17-000001\")\n .eventCategory(MonitoringEventCreateRequest.EventCategory.of(\"auth\"))\n .actionType(\"login\")\n .occurredAt(OffsetDateTime.parse(\"2026-10-17T09:15:02.481Z\"))\n .timeZone(\"Europe/Madrid\")\n .subject(MonitoringEventCreateRequest.Subject.builder()\n .vendorData(\"user-42\")\n .entityType(\"individual\")\n .build())\n .session(MonitoringEventCreateRequest.Session.builder()\n .sessionId(\"web-session-abc\")\n .sequence(142L)\n .build())\n .deviceContext(MonitoringEventCreateRequest.DeviceContext.builder()\n .fingerprint(\"fp-a8c1\")\n .networkContext(MonitoringEventCreateRequest.DeviceContext.NetworkContext.builder()\n .ipAddress(\"203.0.113.42\")\n .build())\n .build())\n .payload(Map.of(\"method\", \"password\"))\n .customValues(Map.of(\"vip_tier\", MonitoringEventCreateRequest.CustomValuesValue.ofString(\"gold\")))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->create([\n 'event_id' => 'login-2026-10-17-000001',\n 'event_category' => 'auth',\n 'action_type' => 'login',\n 'occurred_at' => '2026-10-17T09:15:02.481Z',\n 'time_zone' => 'Europe/Madrid',\n 'subject' => [\n 'vendor_data' => 'user-42',\n 'entity_type' => 'individual',\n ],\n 'session' => [\n 'session_id' => 'web-session-abc',\n 'sequence' => 142,\n ],\n 'device_context' => [\n 'fingerprint' => 'fp-a8c1',\n 'network_context' => [\n 'ip_address' => '203.0.113.42',\n ],\n ],\n 'payload' => ['method' => 'password'],\n 'custom_values' => ['vip_tier' => 'gold'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.monitoring_events.create(\n event_id: \"login-2026-10-17-000001\",\n event_category: \"auth\",\n action_type: \"login\",\n occurred_at: \"2026-10-17T09:15:02.481Z\",\n time_zone: \"Europe/Madrid\",\n subject: {\n vendor_data: \"user-42\",\n entity_type: \"individual\",\n },\n session: {\n session_id: \"web-session-abc\",\n sequence: 142,\n },\n device_context: {\n fingerprint: \"fp-a8c1\",\n network_context: {\n ip_address: \"203.0.113.42\",\n },\n },\n payload: { method: \"password\" },\n custom_values: { vip_tier: \"gold\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.MonitoringEvents.CreateAsync(new MonitoringEventCreateRequest\n{\n EventId = \"login-2026-10-17-000001\",\n EventCategory = \"auth\",\n ActionType = \"login\",\n OccurredAt = DateTimeOffset.Parse(\"2026-10-17T09:15:02.481Z\"),\n TimeZone = \"Europe/Madrid\",\n Subject = new()\n {\n VendorData = \"user-42\",\n EntityType = \"individual\",\n },\n Session = new MonitoringEventCreateRequestSession\n {\n SessionId = \"web-session-abc\",\n Sequence = 142,\n },\n DeviceContext = new MonitoringEventCreateRequestDeviceContext\n {\n Fingerprint = \"fp-a8c1\",\n NetworkContext = new MonitoringEventCreateRequestDeviceContextNetworkContext\n {\n IpAddress = \"203.0.113.42\",\n },\n },\n Payload = new Dictionary { [\"method\"] = \"password\" },\n CustomValues = new Dictionary { [\"vip_tier\"] = \"gold\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The event was stored and evaluated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventIngestResult" + }, + "examples": { + "Stored, no rule matched": { + "value": { + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "standard", + "expires_at": "2026-11-16T09:15:02.481000Z", + "idempotent_replay": false, + "rule_matches": [] + } + }, + "Rule matched, async rule pending": { + "value": { + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "IN_REVIEW", + "score": 40, + "rules_evaluated_count": 6, + "rules_matched_count": 1, + "correlation_pending": true, + "retention_class": "risk_relevant", + "expires_at": "2027-01-15T09:15:02.481000Z", + "idempotent_replay": false, + "rule_matches": [ + { + "rule_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "library_key": null, + "mode": "ACTIVE", + "is_async": false + } + ] + } + } + } + } + } + }, + "200": { + "description": "Idempotent replay: an event with this `event_id` and the same content is already stored. Nothing was stored or evaluated again; the body describes the stored event.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventIngestResult" + }, + "examples": { + "Replay": { + "value": { + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "standard", + "expires_at": "2026-11-16T09:15:02.481000Z", + "idempotent_replay": true, + "rule_matches": [] + } + } + } + } + } + }, + "400": { + "description": "Validation failed. The body is keyed by field; nested fields are nested objects. Nothing is stored.", + "content": { + "application/json": { + "examples": { + "Missing fields": { + "value": { + "event_id": [ + "This field is required." + ], + "subject": [ + "This field is required." + ] + } + }, + "Unknown category": { + "value": { + "event_category": [ + "\"login\" is not a valid choice." + ] + } + }, + "occurred_at too old": { + "value": { + "occurred_at": [ + "occurred_at is more than 7 days in the past. Use the batch import lane for backfills." + ] + } + }, + "occurred_at in the future": { + "value": { + "occurred_at": [ + "occurred_at is more than 5 minutes in the future." + ] + } + }, + "Missing subject vendor_data": { + "value": { + "subject": { + "vendor_data": [ + "This field is required." + ] + } + } + }, + "Missing required payload keys": { + "value": { + "payload": [ + "payment.deposit_completed requires: payment_method, status." + ] + } + }, + "Missing one-of payload key": { + "value": { + "payload": [ + "dispute.chargeback_opened requires one of: dispute_id, chargeback_id." + ] + } + }, + "Security change without changed_field": { + "value": { + "payload": [ + "security.*_changed requires 'changed_field'." + ] + } + }, + "Payload too large": { + "value": { + "payload": [ + "payload exceeds 16384 bytes. Summarise telemetry client-side." + ] + } + }, + "Too many custom_values": { + "value": { + "custom_values": [ + "custom_values accepts at most 100 keys." + ] + } + }, + "Nested custom value": { + "value": { + "custom_values": [ + "custom_values['limits'] must be a scalar - rule conditions cannot address nested values." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: write`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist. Also returned with `code: event_monitoring_disabled` while event ingestion is switched off for the application; nothing is stored in that case, and the check runs before the body is validated.", + "content": { + "application/json": { + "examples": { + "Event monitoring disabled": { + "value": { + "detail": "Event monitoring is not enabled for this application. Turn on event ingestion in the transaction monitoring settings before submitting events.", + "code": "event_monitoring_disabled" + } + }, + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "409": { + "description": "`event_id` was already used for an event with different content (category, action type, `occurred_at`, subject `vendor_data` or payload). Send the event under a new `event_id`.", + "content": { + "application/json": { + "examples": { + "event_id reused": { + "value": { + "event_id": "event_id 'login-2026-10-17-000001' already exists with a different payload." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: transaction creates and event ingestion (`POST /v3/transactions/`, `POST /v3/events/`, `POST /v3/events/batch/`) share a budget of 600 requests per minute per API key, separate from the 300 shared by every other write request. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying, rounded up so the retry lands in the next window.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Ingestion rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/batch/": { + "post": { + "summary": "Create monitoring events in batch", + "description": "Submit 1 to 500 events in one request, each in the same envelope as `POST /v3/events/`. Every item is validated, stored and evaluated on its own and gets its own outcome (`accepted`, `replayed` or `rejected`), so one malformed event never rolls back the others: the response is `201` when no item was rejected and `207` when at least one was, and both mean the request was processed. Send `{\"events\": [...]}`; a bare JSON array of events is also accepted. Event ingestion must be switched on for the application, otherwise the whole request returns `403` with `code: event_monitoring_disabled` and nothing is stored. A scoped API key needs `transactions: write`. See [Batches](https://docs.didit.me/transaction-monitoring/events#batches).", + "operationId": "create_monitoring_events_batch", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "create_batch", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "events" + ], + "properties": { + "events": { + "type": "array", + "minItems": 1, + "maxItems": 500, + "description": "1 to 500 events. Each item follows `MonitoringEventCreateRequest`, including the 7-day past and 5-minute future window on `occurred_at`.", + "items": { + "$ref": "#/components/schemas/MonitoringEventCreateRequest" + } + } + } + }, + "example": { + "events": [ + { + "event_id": "login-2026-10-17-000001", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-17T09:15:02.481Z", + "subject": { + "vendor_data": "user-42" + } + }, + { + "event_id": "deposit-2026-10-17-000002", + "event_category": "payment", + "action_type": "deposit_completed", + "occurred_at": "2026-10-17T09:16:40Z", + "subject": { + "vendor_data": "user-42" + }, + "transaction_context": { + "amount": "100.00", + "currency": "EUR", + "payment_reference_id": "dep_736" + }, + "payload": { + "payment_method": "card", + "status": "completed" + } + } + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/events/batch/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"events\": [\n {\"event_id\": \"login-2026-10-17-000001\", \"event_category\": \"auth\", \"action_type\": \"login\",\n \"occurred_at\": \"2026-10-17T09:15:02.481Z\", \"subject\": {\"vendor_data\": \"user-42\"}},\n {\"event_id\": \"deposit-2026-10-17-000002\", \"event_category\": \"payment\", \"action_type\": \"deposit_completed\",\n \"occurred_at\": \"2026-10-17T09:16:40.000Z\", \"subject\": {\"vendor_data\": \"user-42\"},\n \"transaction_context\": {\"amount\": \"100.00\", \"currency\": \"EUR\", \"payment_reference_id\": \"dep_736\"},\n \"payload\": {\"payment_method\": \"card\", \"status\": \"completed\"}}\n ]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.monitoring_events.create_batch(\n events=[{\n \"event_id\": \"login-2026-10-17-000001\",\n \"event_category\": \"auth\",\n \"action_type\": \"login\",\n \"occurred_at\": \"2026-10-17T09:15:02.481Z\",\n \"subject\": {\n \"vendor_data\": \"user-42\",\n },\n }, {\n \"event_id\": \"deposit-2026-10-17-000002\",\n \"event_category\": \"payment\",\n \"action_type\": \"deposit_completed\",\n \"occurred_at\": \"2026-10-17T09:16:40Z\",\n \"subject\": {\n \"vendor_data\": \"user-42\",\n },\n \"transaction_context\": {\n \"amount\": \"100.00\",\n \"currency\": \"EUR\",\n \"payment_reference_id\": \"dep_736\",\n },\n \"payload\": {\"payment_method\": \"card\", \"status\": \"completed\"},\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.monitoringEvents.createBatch({\n events: [{\n event_id: \"login-2026-10-17-000001\",\n event_category: \"auth\",\n action_type: \"login\",\n occurred_at: \"2026-10-17T09:15:02.481Z\",\n subject: {\n vendor_data: \"user-42\",\n },\n }, {\n event_id: \"deposit-2026-10-17-000002\",\n event_category: \"payment\",\n action_type: \"deposit_completed\",\n occurred_at: \"2026-10-17T09:16:40Z\",\n subject: {\n vendor_data: \"user-42\",\n },\n transaction_context: {\n amount: \"100.00\",\n currency: \"EUR\",\n payment_reference_id: \"dep_736\",\n },\n payload: { payment_method: \"card\", status: \"completed\" },\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.MonitoringEvents.CreateBatch(ctx, didit.MonitoringEventsCreateBatchParams{\n\tEvents: []didit.MonitoringEventCreateRequest{{\n\t\tEventID: \"login-2026-10-17-000001\",\n\t\tEventCategory: \"auth\",\n\t\tActionType: \"login\",\n\t\tOccurredAt: time.Date(2026, 10, 17, 9, 15, 2, 0, time.UTC),\n\t\tSubject: didit.MonitoringEventCreateRequestSubject{\n\t\t\tVendorData: \"user-42\",\n\t\t},\n\t}, {\n\t\tEventID: \"deposit-2026-10-17-000002\",\n\t\tEventCategory: \"payment\",\n\t\tActionType: \"deposit_completed\",\n\t\tOccurredAt: time.Date(2026, 10, 17, 9, 16, 40, 0, time.UTC),\n\t\tSubject: didit.MonitoringEventCreateRequestSubject{\n\t\t\tVendorData: \"user-42\",\n\t\t},\n\t\tTransactionContext: didit.Some(didit.MonitoringEventCreateRequestTransactionContext{\n\t\t\tAmount: didit.String(\"100.00\"),\n\t\t\tCurrency: didit.String(\"EUR\"),\n\t\t\tPaymentReferenceID: didit.String(\"dep_736\"),\n\t\t}),\n\t\tPayload: didit.Some(map[string]any{\"payment_method\": \"card\", \"status\": \"completed\"}),\n\t}},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.OffsetDateTime;\nimport java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.MonitoringEventCreateRequest;\nimport me.didit.types.MonitoringEventsCreateBatchParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.monitoringEvents().createBatch(MonitoringEventsCreateBatchParams.builder()\n .events(List.of(\n MonitoringEventCreateRequest.builder()\n .eventId(\"login-2026-10-17-000001\")\n .eventCategory(MonitoringEventCreateRequest.EventCategory.of(\"auth\"))\n .actionType(\"login\")\n .occurredAt(OffsetDateTime.parse(\"2026-10-17T09:15:02.481Z\"))\n .subject(MonitoringEventCreateRequest.Subject.builder()\n .vendorData(\"user-42\")\n .build())\n .build(),\n MonitoringEventCreateRequest.builder()\n .eventId(\"deposit-2026-10-17-000002\")\n .eventCategory(MonitoringEventCreateRequest.EventCategory.of(\"payment\"))\n .actionType(\"deposit_completed\")\n .occurredAt(OffsetDateTime.parse(\"2026-10-17T09:16:40Z\"))\n .subject(MonitoringEventCreateRequest.Subject.builder()\n .vendorData(\"user-42\")\n .build())\n .transactionContext(MonitoringEventCreateRequest.TransactionContext.builder()\n .amount(\"100.00\")\n .currency(\"EUR\")\n .paymentReferenceId(\"dep_736\")\n .build())\n .payload(Map.of(\"payment_method\", \"card\", \"status\", \"completed\"))\n .build()))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->createBatch([\n 'events' => [[\n 'event_id' => 'login-2026-10-17-000001',\n 'event_category' => 'auth',\n 'action_type' => 'login',\n 'occurred_at' => '2026-10-17T09:15:02.481Z',\n 'subject' => [\n 'vendor_data' => 'user-42',\n ],\n ], [\n 'event_id' => 'deposit-2026-10-17-000002',\n 'event_category' => 'payment',\n 'action_type' => 'deposit_completed',\n 'occurred_at' => '2026-10-17T09:16:40Z',\n 'subject' => [\n 'vendor_data' => 'user-42',\n ],\n 'transaction_context' => [\n 'amount' => '100.00',\n 'currency' => 'EUR',\n 'payment_reference_id' => 'dep_736',\n ],\n 'payload' => ['payment_method' => 'card', 'status' => 'completed'],\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.monitoring_events.create_batch(\n events: [{\n event_id: \"login-2026-10-17-000001\",\n event_category: \"auth\",\n action_type: \"login\",\n occurred_at: \"2026-10-17T09:15:02.481Z\",\n subject: {\n vendor_data: \"user-42\",\n },\n }, {\n event_id: \"deposit-2026-10-17-000002\",\n event_category: \"payment\",\n action_type: \"deposit_completed\",\n occurred_at: \"2026-10-17T09:16:40Z\",\n subject: {\n vendor_data: \"user-42\",\n },\n transaction_context: {\n amount: \"100.00\",\n currency: \"EUR\",\n payment_reference_id: \"dep_736\",\n },\n payload: { payment_method: \"card\", status: \"completed\" },\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.MonitoringEvents.CreateBatchAsync(new MonitoringEventsCreateBatchParams\n{\n Events = new MonitoringEventCreateRequest[]\n {\n new()\n {\n EventId = \"login-2026-10-17-000001\",\n EventCategory = \"auth\",\n ActionType = \"login\",\n OccurredAt = DateTimeOffset.Parse(\"2026-10-17T09:15:02.481Z\"),\n Subject = new()\n {\n VendorData = \"user-42\",\n },\n },\n new()\n {\n EventId = \"deposit-2026-10-17-000002\",\n EventCategory = \"payment\",\n ActionType = \"deposit_completed\",\n OccurredAt = DateTimeOffset.Parse(\"2026-10-17T09:16:40Z\"),\n Subject = new()\n {\n VendorData = \"user-42\",\n },\n TransactionContext = new MonitoringEventCreateRequestTransactionContext\n {\n Amount = \"100.00\",\n Currency = \"EUR\",\n PaymentReferenceId = \"dep_736\",\n },\n Payload = new Dictionary { [\"payment_method\"] = \"card\", [\"status\"] = \"completed\" },\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "No item was rejected: every item is `accepted` or `replayed`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "accepted_count", + "replayed_count", + "rejected_count", + "results" + ], + "properties": { + "accepted_count": { + "type": "integer", + "minimum": 0, + "description": "Items stored and evaluated." + }, + "replayed_count": { + "type": "integer", + "minimum": 0, + "description": "Items already stored with the same content." + }, + "rejected_count": { + "type": "integer", + "minimum": 0, + "description": "Items not stored." + }, + "results": { + "type": "array", + "description": "One entry per item, in request order.", + "items": { + "type": "object", + "description": "Outcome of one item. Accepted and replayed items also carry every field of `MonitoringEventIngestResult`; rejected items carry `event_id` and `errors` instead.", + "required": [ + "index", + "outcome" + ], + "properties": { + "index": { + "type": "integer", + "minimum": 0, + "description": "Position of the item in your `events` array." + }, + "outcome": { + "type": "string", + "enum": [ + "accepted", + "replayed", + "rejected" + ], + "description": "`accepted`: stored and evaluated. `replayed`: already stored with the same content. `rejected`: not stored; see `errors`." + }, + "event_id": { + "type": "string", + "nullable": true, + "description": "Your `event_id`. On items rejected by validation it is read from the item's snake_case `event_id` key, so it is null when the item omitted it or sent the `eventId` alias." + }, + "errors": { + "type": "object", + "additionalProperties": true, + "description": "Rejected items only. Validation errors keyed by field, in the same shape as the `400` of `POST /v3/events/`; a reused `event_id` with different content is reported as a string under `errors.event_id`." + }, + "event_uuid": { + "type": "string", + "format": "uuid", + "description": "Accepted and replayed items only." + }, + "status": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ], + "description": "Accepted and replayed items only." + }, + "score": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "rules_evaluated_count": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "rules_matched_count": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "correlation_pending": { + "type": "boolean", + "description": "Accepted and replayed items only." + }, + "retention_class": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ], + "description": "Accepted and replayed items only." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Accepted and replayed items only." + }, + "idempotent_replay": { + "type": "boolean", + "description": "Accepted and replayed items only." + }, + "rule_matches": { + "type": "array", + "description": "Accepted and replayed items only.", + "items": { + "type": "object", + "required": [ + "rule_id", + "library_key", + "mode", + "is_async" + ], + "properties": { + "rule_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the rule." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Library key when the rule was installed from the rules library, otherwise null." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "TEST" + ], + "description": "`TEST` rules record the match but apply no actions." + }, + "is_async": { + "type": "boolean", + "description": "`true` when the match came from the asynchronous pass." + } + } + } + } + } + } + } + } + }, + "examples": { + "All landed": { + "value": { + "accepted_count": 1, + "replayed_count": 1, + "rejected_count": 0, + "results": [ + { + "index": 0, + "outcome": "accepted", + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "standard", + "expires_at": "2026-11-16T09:15:02.481000Z", + "idempotent_replay": false, + "rule_matches": [] + }, + { + "index": 1, + "outcome": "replayed", + "event_uuid": "11111111-2222-3333-4444-555555555555", + "event_id": "deposit-2026-10-17-000002", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "monetary_or_compliance", + "expires_at": null, + "idempotent_replay": true, + "rule_matches": [] + } + ] + } + } + } + } + } + }, + "207": { + "description": "At least one item was rejected. The other items were stored; fix and resend only the rejected ones (resending an accepted item is safe, it comes back `replayed`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "accepted_count", + "replayed_count", + "rejected_count", + "results" + ], + "properties": { + "accepted_count": { + "type": "integer", + "minimum": 0, + "description": "Items stored and evaluated." + }, + "replayed_count": { + "type": "integer", + "minimum": 0, + "description": "Items already stored with the same content." + }, + "rejected_count": { + "type": "integer", + "minimum": 0, + "description": "Items not stored." + }, + "results": { + "type": "array", + "description": "One entry per item, in request order.", + "items": { + "type": "object", + "description": "Outcome of one item. Accepted and replayed items also carry every field of `MonitoringEventIngestResult`; rejected items carry `event_id` and `errors` instead.", + "required": [ + "index", + "outcome" + ], + "properties": { + "index": { + "type": "integer", + "minimum": 0, + "description": "Position of the item in your `events` array." + }, + "outcome": { + "type": "string", + "enum": [ + "accepted", + "replayed", + "rejected" + ], + "description": "`accepted`: stored and evaluated. `replayed`: already stored with the same content. `rejected`: not stored; see `errors`." + }, + "event_id": { + "type": "string", + "nullable": true, + "description": "Your `event_id`. On items rejected by validation it is read from the item's snake_case `event_id` key, so it is null when the item omitted it or sent the `eventId` alias." + }, + "errors": { + "type": "object", + "additionalProperties": true, + "description": "Rejected items only. Validation errors keyed by field, in the same shape as the `400` of `POST /v3/events/`; a reused `event_id` with different content is reported as a string under `errors.event_id`." + }, + "event_uuid": { + "type": "string", + "format": "uuid", + "description": "Accepted and replayed items only." + }, + "status": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ], + "description": "Accepted and replayed items only." + }, + "score": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "rules_evaluated_count": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "rules_matched_count": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "correlation_pending": { + "type": "boolean", + "description": "Accepted and replayed items only." + }, + "retention_class": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ], + "description": "Accepted and replayed items only." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Accepted and replayed items only." + }, + "idempotent_replay": { + "type": "boolean", + "description": "Accepted and replayed items only." + }, + "rule_matches": { + "type": "array", + "description": "Accepted and replayed items only.", + "items": { + "type": "object", + "required": [ + "rule_id", + "library_key", + "mode", + "is_async" + ], + "properties": { + "rule_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the rule." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Library key when the rule was installed from the rules library, otherwise null." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "TEST" + ], + "description": "`TEST` rules record the match but apply no actions." + }, + "is_async": { + "type": "boolean", + "description": "`true` when the match came from the asynchronous pass." + } + } + } + } + } + } + } + } + }, + "examples": { + "Partial success": { + "value": { + "accepted_count": 1, + "replayed_count": 0, + "rejected_count": 2, + "results": [ + { + "index": 0, + "outcome": "accepted", + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "standard", + "expires_at": "2026-11-16T09:15:02.481000Z", + "idempotent_replay": false, + "rule_matches": [] + }, + { + "index": 1, + "event_id": "deposit-2026-10-17-000002", + "outcome": "rejected", + "errors": { + "payload": [ + "payment.deposit_completed requires: payment_method, status." + ] + } + }, + { + "index": 2, + "event_id": "login-2026-10-16-000777", + "outcome": "rejected", + "errors": { + "event_id": "event_id 'login-2026-10-16-000777' already exists with a different payload." + } + } + ] + } + } + } + } + } + }, + "400": { + "description": "The batch itself is malformed: `events` is missing or not an array, is empty, or has more than 500 items. Nothing is stored; split large batches.", + "content": { + "application/json": { + "examples": { + "Not a list": { + "value": { + "events": "Expected a list of events." + } + }, + "Empty": { + "value": { + "events": "At least one event is required." + } + }, + "Too many": { + "value": { + "events": "A batch accepts at most 500 events; received 612." + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: write`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist. Also returned with `code: event_monitoring_disabled` while event ingestion is switched off for the application; nothing is stored in that case, and the check runs before the body is validated.", + "content": { + "application/json": { + "examples": { + "Event monitoring disabled": { + "value": { + "detail": "Event monitoring is not enabled for this application. Turn on event ingestion in the transaction monitoring settings before submitting events.", + "code": "event_monitoring_disabled" + } + }, + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: transaction creates and event ingestion (`POST /v3/transactions/`, `POST /v3/events/`, `POST /v3/events/batch/`) share a budget of 600 requests per minute per API key, separate from the 300 shared by every other write request. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying, rounded up so the retry lands in the next window.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Ingestion rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/sdk-token/": { + "post": { + "summary": "Mint monitoring SDK token", + "description": "Mint a short-lived device credential bound to one subject, so your web or mobile app can send that user's events straight to Didit without an API key. The app sends the returned `monitoring_token` in the `X-Monitoring-Token` header to the device lane (`POST /v1/events/` and `POST /v1/events/batch/`), where the subject is always overwritten with this `vendor_data`, events outside `allowed_event_categories` are rejected, each submitted event spends one use, and a request whose `Origin` does not equal `bind_to_origin` is refused. Call it from your backend only; minting creates the credential and nothing else (no user is created). Event ingestion must be switched on for the application, otherwise this endpoint returns `403` with `code: event_monitoring_disabled` instead of minting a credential that could never write. A scoped API key needs `transactions: write`. See [SDK event submission](https://docs.didit.me/transaction-monitoring/sdk-event-submission).", + "operationId": "create_monitoring_event_sdk_token", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "create_sdk_token", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "camelCase aliases are accepted: `vendorData`, `entityType`, `allowedEventCategories`, `ttlSeconds`, `maxUses`, `bindToOrigin`.", + "required": [ + "vendor_data" + ], + "properties": { + "vendor_data": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "Your id for the subject every event submitted with this credential belongs to." + }, + "entity_type": { + "type": "string", + "maxLength": 32, + "default": "individual", + "description": "`company` binds a company subject; any other value (default `individual`) binds an individual." + }, + "allowed_event_categories": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ] + }, + "description": "Categories the credential may submit. Omit it or send `[]` to allow every category." + }, + "ttl_seconds": { + "type": "integer", + "minimum": 60, + "maximum": 86400, + "default": 900, + "description": "Lifetime of the credential in seconds." + }, + "max_uses": { + "type": "integer", + "minimum": 1, + "nullable": true, + "default": 1000, + "description": "Number of events the credential may submit (a batch spends one use per event). Send `null` for no cap within the lifetime." + }, + "bind_to_origin": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "When set, the browser `Origin` header of every device request must equal this value exactly; a request without `Origin` is refused." + } + } + }, + "example": { + "vendor_data": "user-42", + "allowed_event_categories": [ + "auth", + "session", + "gameplay" + ], + "ttl_seconds": 900, + "max_uses": 1000, + "bind_to_origin": "https://app.example.com" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/events/sdk-token/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"vendor_data\": \"user-42\",\n \"allowed_event_categories\": [\"auth\", \"session\", \"gameplay\"],\n \"ttl_seconds\": 900,\n \"max_uses\": 1000,\n \"bind_to_origin\": \"https://app.example.com\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.monitoring_events.create_sdk_token(\n vendor_data=\"user-42\",\n allowed_event_categories=[\"auth\", \"session\", \"gameplay\"],\n ttl_seconds=900,\n max_uses=1000,\n bind_to_origin=\"https://app.example.com\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.monitoringEvents.createSdkToken({\n vendor_data: \"user-42\",\n allowed_event_categories: [\"auth\", \"session\", \"gameplay\"],\n ttl_seconds: 900,\n max_uses: 1000,\n bind_to_origin: \"https://app.example.com\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.MonitoringEvents.CreateSDKToken(ctx, didit.MonitoringEventsCreateSDKTokenParams{\n\tVendorData: \"user-42\",\n\tAllowedEventCategories: didit.Some([]didit.MonitoringEventsCreateSDKTokenParamsAllowedEventCategories{\"auth\", \"session\", \"gameplay\"}),\n\tTTLSeconds: didit.Int(900),\n\tMaxUses: didit.Int(1000),\n\tBindToOrigin: didit.String(\"https://app.example.com\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.MonitoringEventsCreateSdkTokenParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.monitoringEvents().createSdkToken(MonitoringEventsCreateSdkTokenParams.builder()\n .vendorData(\"user-42\")\n .allowedEventCategories(List.of(MonitoringEventsCreateSdkTokenParams.AllowedEventCategories.of(\"auth\"), MonitoringEventsCreateSdkTokenParams.AllowedEventCategories.of(\"session\"), MonitoringEventsCreateSdkTokenParams.AllowedEventCategories.of(\"gameplay\")))\n .ttlSeconds(900L)\n .maxUses(1000L)\n .bindToOrigin(\"https://app.example.com\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->createSdkToken([\n 'vendor_data' => 'user-42',\n 'allowed_event_categories' => ['auth', 'session', 'gameplay'],\n 'ttl_seconds' => 900,\n 'max_uses' => 1000,\n 'bind_to_origin' => 'https://app.example.com',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.monitoring_events.create_sdk_token(\n vendor_data: \"user-42\",\n allowed_event_categories: [\"auth\", \"session\", \"gameplay\"],\n ttl_seconds: 900,\n max_uses: 1000,\n bind_to_origin: \"https://app.example.com\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.MonitoringEvents.CreateSdkTokenAsync(new MonitoringEventsCreateSdkTokenParams\n{\n VendorData = \"user-42\",\n AllowedEventCategories = new MonitoringEventsCreateSdkTokenParamsAllowedEventCategories[] { \"auth\", \"session\", \"gameplay\" },\n TtlSeconds = 900,\n MaxUses = 1000,\n BindToOrigin = \"https://app.example.com\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The credential was minted. Hand `monitoring_token` to the app and treat it as a secret until `expires_at`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "monitoring_token", + "expires_at", + "allowed_event_categories", + "max_uses", + "bind_to_origin" + ], + "properties": { + "monitoring_token": { + "type": "string", + "description": "The device credential (a 43-character URL-safe token). Send it in the `X-Monitoring-Token` header." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the credential stops being accepted." + }, + "allowed_event_categories": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ] + }, + "description": "Categories the credential may submit; `[]` means every category." + }, + "max_uses": { + "type": "integer", + "nullable": true, + "description": "Event budget of the credential; null means no cap." + }, + "bind_to_origin": { + "type": "string", + "nullable": true, + "description": "Origin the credential is bound to, or null." + } + } + }, + "example": { + "monitoring_token": "q7ZtR2xKp9VbN4mWc8LdH1sYfJ3gT6uEaQ0oPiXzBkC", + "expires_at": "2026-10-17T09:30:02.481000Z", + "allowed_event_categories": [ + "auth", + "session", + "gameplay" + ], + "max_uses": 1000, + "bind_to_origin": "https://app.example.com" + } + } + } + }, + "400": { + "description": "Validation failed: missing `vendor_data`, `ttl_seconds` outside 60 to 86400, `max_uses` below 1, or an unknown category (keyed by its position in the array).", + "content": { + "application/json": { + "examples": { + "Missing vendor_data": { + "value": { + "vendor_data": [ + "This field is required." + ] + } + }, + "ttl_seconds too short": { + "value": { + "ttl_seconds": [ + "Ensure this value is greater than or equal to 60." + ] + } + }, + "Unknown category": { + "value": { + "allowed_event_categories": { + "1": [ + "\"login\" is not a valid choice." + ] + } + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: write`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist. Also returned with `code: event_monitoring_disabled` while event ingestion is switched off for the application; nothing is stored in that case, and the check runs before the body is validated.", + "content": { + "application/json": { + "examples": { + "Event monitoring disabled": { + "value": { + "detail": "Event monitoring is not enabled for this application. Turn on event ingestion in the transaction monitoring settings before submitting events.", + "code": "event_monitoring_disabled" + } + }, + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: write requests (`POST`, `PATCH`, `DELETE`) share a budget of 300 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/{event_uuid}/": { + "get": { + "summary": "Get monitoring event", + "description": "Retrieve one monitoring event with everything the list returns plus the payload, custom values, device context, field provenance (`field_sources`), the promoted domain fields, `expires_at`, every matching rule run with its evidence, the IP enrichment and the tags rules attached. While `correlation_pending` is `true` an asynchronous rule still has to run; read the event again a few minutes later for the final `status`, `score` and `rule_runs`. Reads work whether or not event ingestion is switched on. A scoped API key needs `transactions: read`. See [Evidence](https://docs.didit.me/transaction-monitoring/event-rules#evidence) and [IP enrichment](https://docs.didit.me/transaction-monitoring/ip-enrichment).", + "operationId": "get_monitoring_event", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "event_uuid", + "in": "path", + "required": true, + "description": "Didit identifier of the event (`event_uuid` from the create, batch, list or timeline response), not your own `event_id`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/events/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.monitoring_events.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.monitoringEvents.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.MonitoringEvents.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.monitoringEvents().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.monitoring_events.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.MonitoringEvents.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The event with its evidence.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventDetail" + }, + "examples": { + "Login from a VPN, in review": { + "value": { + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-17T09:15:02.481000Z", + "received_at": "2026-10-17T09:15:02.903114Z", + "status": "IN_REVIEW", + "score": 40, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": "web-session-abc", + "device_fingerprint": "fp-a8c1", + "ip_address": "203.0.113.42", + "ip_country": "ES", + "amount": null, + "currency": null, + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": null, + "retention_class": "risk_relevant", + "rules_matched_count": 1, + "correlation_pending": false, + "is_droppable": true, + "payload": { + "method": "password" + }, + "custom_values": { + "vip_tier": "gold", + "vpn_or_proxy_detected": true + }, + "device_context": { + "fingerprint": "fp-a8c1", + "network_context": { + "ip_address": "203.0.113.42", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_city": "Madrid", + "asn_number": 64500, + "asn_organization": "Example Networks", + "is_vpn_or_tor": true, + "is_data_center": false + } + }, + "field_sources": { + "subject.vendor_data": "customer", + "event_category": "customer", + "action_type": "customer", + "occurred_at": "customer", + "received_at": "server", + "payload.method": "customer", + "session.session_id": "customer", + "session.sequence": "customer", + "device_context.fingerprint": "customer", + "device_context.network_context.ip_address": "customer", + "device_context.network_context.ip_country": "server", + "device_context.network_context.ip_country_code": "server", + "device_context.network_context.ip_city": "server", + "device_context.network_context.asn_number": "server", + "device_context.network_context.asn_organization": "server", + "device_context.network_context.is_vpn_or_tor": "server", + "device_context.network_context.is_data_center": "server" + }, + "session_sequence": 142, + "table_id": null, + "hand_id": null, + "match_id": null, + "market_id": null, + "side": null, + "role": null, + "net_result_amount": null, + "payment_fingerprint": null, + "instrument_reference_hash": null, + "withdrawal_address_hash": null, + "dispute_id": null, + "chargeback_id": null, + "changed_field": null, + "identity_match_ref": null, + "schema_version": 1, + "expires_at": "2027-01-15T09:15:02.481000Z", + "rules_evaluated_count": 6, + "rule_runs": [ + { + "rule_uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "rule_title": "Login from VPN or Tor", + "library_key": null, + "matched": true, + "mode": "ACTIVE", + "is_async": false, + "severity": "MEDIUM", + "score_delta": 40, + "action_summary": "score +40; add_tag:VPN_LOGIN", + "matched_payload": [ + { + "field": "event.ip.is_vpn_or_tor", + "operator": "eq", + "expected_value": true, + "current_value": true, + "matched": true, + "group_index": null, + "group_logic": "AND", + "field_source": "server" + } + ], + "created_at": "2026-10-17T09:15:02.951207Z" + } + ], + "ip_enrichment": { + "ip_country_code": "ES", + "ip_city": "Madrid", + "asn_number": 64500, + "asn_organization": "Example Networks", + "is_vpn_or_tor": true, + "is_data_center": false, + "proxy_type": null, + "last_enriched_at": "2026-10-17T09:15:02.910442Z" + }, + "subject_profile": null, + "tags": [ + "VPN_LOGIN" + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: read`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No event with this `event_uuid` exists in the application: it was never stored, it was deleted, or it belongs to another application. A path segment that is not a UUID does not match the route and also returns `404`.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No MonitoringEvent matches the given query." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: `GET` requests share a budget of 600 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/subjects/{vendor_data}/": { + "get": { + "summary": "Get subject event timeline", + "description": "List every monitoring event of one subject, newest `occurred_at` first. A `vendor_data` is unique only within an entity type, so the same id can name an individual and a company: `entity_type` picks which timeline you get, and it defaults to `individual`, so a company's events need `entity_type=company`. The filters of `GET /v3/events/` apply on top, and a `vendor_data` with no events returns an empty page rather than `404`. A scoped API key needs `transactions: read`. See [Reading events back](https://docs.didit.me/transaction-monitoring/events#reading-events-back).", + "operationId": "get_monitoring_event_subject_timeline", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "retrieve_subject_timeline", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "description": "Your id for the subject (`subject.vendor_data` of its events). URL-encode it; a value containing `/` cannot be addressed by this endpoint.", + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "example": "user-42" + }, + { + "name": "entity_type", + "in": "query", + "required": false, + "description": "`company` returns the company with this `vendor_data`; any other value, or none, returns the individual. `subject_entity_type` is accepted as an alias when `entity_type` is absent.", + "schema": { + "type": "string", + "enum": [ + "individual", + "company" + ], + "default": "individual" + }, + "example": "individual" + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of events per page. Defaults to 50. Values above 1000000 return `400`; a non-numeric, zero or negative value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "example": 50 + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of events to skip. Defaults to 0. Prefer following the `next` URL from the response.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "example": 0 + }, + { + "name": "event_category", + "in": "query", + "required": false, + "description": "Only events in these categories. Repeat the parameter for several values (`?event_category=payment&event_category=dispute`); an event matching any of them is returned.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ] + } + }, + "example": [ + "payment", + "dispute" + ] + }, + { + "name": "action_type", + "in": "query", + "required": false, + "description": "Only events with these action types (exact, case-sensitive). Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "example": [ + "deposit_completed", + "chargeback_opened" + ] + }, + { + "name": "status", + "in": "query", + "required": false, + "description": "Only events with these statuses. Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ] + } + }, + "example": [ + "IN_REVIEW", + "DECLINED" + ] + }, + { + "name": "retention_class", + "in": "query", + "required": false, + "description": "Only events in these retention classes. Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ] + } + }, + "example": [ + "risk_relevant" + ] + }, + { + "name": "business_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the `business.vendor_data` sent with the event.", + "schema": { + "type": "string" + }, + "example": "merchant-7" + }, + { + "name": "session_id", + "in": "query", + "required": false, + "description": "Exact match on `session.session_id`.", + "schema": { + "type": "string" + }, + "example": "web-session-abc" + }, + { + "name": "device_fingerprint", + "in": "query", + "required": false, + "description": "Exact match on `device_context.fingerprint`.", + "schema": { + "type": "string" + }, + "example": "fp-a8c1" + }, + { + "name": "ip_address", + "in": "query", + "required": false, + "description": "Exact match on the event's IP address.", + "schema": { + "type": "string" + }, + "example": "203.0.113.42" + }, + { + "name": "game_id", + "in": "query", + "required": false, + "description": "Exact match on the promoted `game_id`.", + "schema": { + "type": "string" + }, + "example": "poker-holdem-01" + }, + { + "name": "round_id", + "in": "query", + "required": false, + "description": "Exact match on the promoted `round_id`.", + "schema": { + "type": "string" + }, + "example": "round-88231" + }, + { + "name": "affiliate_code", + "in": "query", + "required": false, + "description": "Exact match on the promoted `affiliate_code`.", + "schema": { + "type": "string" + }, + "example": "aff-summer" + }, + { + "name": "payment_reference_id", + "in": "query", + "required": false, + "description": "Exact match on the payment reference (from `transaction_context.payment_reference_id` or `payload.payment_reference_id`).", + "schema": { + "type": "string" + }, + "example": "dep_735" + }, + { + "name": "counterparty_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the promoted `counterparty_vendor_data`.", + "schema": { + "type": "string" + }, + "example": "user-77" + }, + { + "name": "matched_only", + "in": "query", + "required": false, + "description": "`true` (also `1` or `yes`, case-insensitive) returns only events at least one rule matched (`rules_matched_count` above 0). Any other value is ignored.", + "schema": { + "type": "boolean", + "default": false + }, + "example": true + }, + { + "name": "occurred_from", + "in": "query", + "required": false, + "description": "Only events with `occurred_at` at or after this instant (inclusive). Send an ISO 8601 value: `2026-10-01T00:00:00Z`, an explicit offset with the `+` percent-encoded (`2026-10-01T02:00:00%2B02:00`; an unencoded `+` is decoded as a space and the value no longer parses), or a date alone (`2026-10-01`), which means 00:00:00 UTC of that day. A value without an offset is read as UTC. Values in any other format are not accepted.", + "schema": { + "type": "string", + "format": "date-time" + }, + "example": "2026-10-01T00:00:00Z" + }, + { + "name": "occurred_to", + "in": "query", + "required": false, + "description": "Only events with `occurred_at` at or before this instant (inclusive). A date alone means the start of that day, so `occurred_to=2026-10-15` excludes the rest of 15 October; send `2026-10-15T23:59:59Z` to include it. Send an ISO 8601 value: `2026-10-01T00:00:00Z`, an explicit offset with the `+` percent-encoded (`2026-10-01T02:00:00%2B02:00`; an unencoded `+` is decoded as a space and the value no longer parses), or a date alone (`2026-10-01`), which means 00:00:00 UTC of that day. A value without an offset is read as UTC. Values in any other format are not accepted.", + "schema": { + "type": "string", + "format": "date-time" + }, + "example": "2026-10-17T23:59:59Z" + }, + { + "name": "search", + "in": "query", + "required": false, + "description": "Case-insensitive substring match on `event_id`, `action_type`, `subject_vendor_data` or `counterparty_vendor_data`.", + "schema": { + "type": "string" + }, + "example": "chargeback" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/events/subjects/user-42/?entity_type=individual&limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.monitoring_events.retrieve_subject_timeline(\n \"user-42\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.monitoringEvents.retrieveSubjectTimeline(\"user-42\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.MonitoringEvents.RetrieveSubjectTimelineAutoPaging(ctx, \"user-42\", didit.MonitoringEventsRetrieveSubjectTimelineParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.monitoringEvents().retrieveSubjectTimeline(\"user-42\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->retrieveSubjectTimeline('user-42') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.monitoring_events.retrieve_subject_timeline(\"user-42\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.MonitoringEvents.RetrieveSubjectTimelineAsync(\"user-42\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "One page of the subject's events.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventPage" + }, + "examples": { + "Individual timeline": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "event_uuid": "11111111-2222-3333-4444-555555555555", + "event_id": "chargeback-2026-10-16-000318", + "event_category": "dispute", + "action_type": "chargeback_opened", + "occurred_at": "2026-10-16T14:02:11Z", + "received_at": "2026-10-16T14:02:12.418230Z", + "status": "IN_REVIEW", + "score": 60, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": null, + "device_fingerprint": null, + "ip_address": null, + "ip_country": null, + "amount": "250.000000000000000000", + "currency": "EUR", + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": "dep_735", + "retention_class": "monetary_or_compliance", + "rules_matched_count": 1, + "correlation_pending": false, + "is_droppable": false + }, + { + "event_uuid": "22222222-3333-4444-5555-666666666666", + "event_id": "login-2026-10-15-000912", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-15T08:30:00.120000Z", + "received_at": "2026-10-15T08:30:00.734512Z", + "status": "ACCEPTED", + "score": 0, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": "web-session-abc", + "device_fingerprint": "fp-a8c1", + "ip_address": "203.0.113.42", + "ip_country": "ES", + "amount": null, + "currency": null, + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": null, + "retention_class": "standard", + "rules_matched_count": 0, + "correlation_pending": false, + "is_droppable": true + } + ] + } + }, + "No events": { + "value": { + "count": 0, + "next": null, + "previous": null, + "results": [] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is larger than 1000000. The body is a JSON array with one message. A non-numeric or non-positive `limit` falls back to the default of 50 instead of failing.", + "content": { + "application/json": { + "examples": { + "limit too large": { + "summary": "limit above 1000000", + "value": [ + "limit must not exceed 1000000." + ] + }, + "offset too large": { + "summary": "offset above 1000000", + "value": [ + "offset must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: read`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: `GET` requests share a budget of 600 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/enrichment-metrics/": { + "get": { + "summary": "Get event enrichment metrics", + "description": "Return the event ingestion and IP-enrichment counters of your application for a period, summed over every lane (server, batch and device), so you can watch event volume and confirm that enrichment is resolved once per unique address rather than once per event. The period covers today plus the previous `days` days, counted by UTC calendar date. The three ratios are null when their denominator is zero. A scoped API key needs `transactions: read`. See [IP enrichment](https://docs.didit.me/transaction-monitoring/events#ip-enrichment).", + "operationId": "get_monitoring_event_enrichment_metrics", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "retrieve_enrichment_metrics", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "days", + "in": "query", + "required": false, + "description": "Length of the period in days. Defaults to 30; values are clamped to 1 to 365, and a non-integer value falls back to 30. The value applied is returned as `period_days`.", + "schema": { + "type": "integer", + "default": 30, + "minimum": 1, + "maximum": 365 + }, + "example": 30 + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/events/enrichment-metrics/?days=30' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.monitoring_events.retrieve_enrichment_metrics()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.monitoringEvents.retrieveEnrichmentMetrics();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.MonitoringEvents.RetrieveEnrichmentMetrics(ctx, didit.MonitoringEventsRetrieveEnrichmentMetricsParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.monitoringEvents().retrieveEnrichmentMetrics();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->retrieveEnrichmentMetrics();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.monitoring_events.retrieve_enrichment_metrics\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.MonitoringEvents.RetrieveEnrichmentMetricsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Counters and ratios for the period.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "events_ingested", + "events_rejected", + "enrichment_lookups", + "enrichment_cache_hits", + "enrichment_provider_calls", + "enrichment_skipped_low_value", + "unique_ips_seen", + "period_days", + "cache_hit_rate", + "unique_ip_ratio", + "provider_calls_per_1k_events" + ], + "properties": { + "events_ingested": { + "type": "integer", + "description": "New events stored. Idempotent replays are not counted." + }, + "events_rejected": { + "type": "integer", + "description": "Items rejected inside batch requests (server and device lanes). A rejected single-event request is not counted." + }, + "enrichment_lookups": { + "type": "integer", + "description": "Events whose public IP address was resolved, from the cache or freshly." + }, + "enrichment_cache_hits": { + "type": "integer", + "description": "Lookups answered from the cache." + }, + "enrichment_provider_calls": { + "type": "integer", + "description": "Lookups that needed a fresh resolution of the address." + }, + "enrichment_skipped_low_value": { + "type": "integer", + "description": "Low-value telemetry events whose address was not already known. They never trigger a fresh resolution of their own." + }, + "unique_ips_seen": { + "type": "integer", + "description": "Addresses that needed a fresh resolution." + }, + "period_days": { + "type": "integer", + "description": "The `days` value applied after clamping." + }, + "cache_hit_rate": { + "type": "number", + "nullable": true, + "description": "`enrichment_cache_hits / enrichment_lookups`, rounded to 4 decimals. Null when there were no lookups." + }, + "unique_ip_ratio": { + "type": "number", + "nullable": true, + "description": "`unique_ips_seen / events_ingested`, rounded to 4 decimals. Null when no events were ingested." + }, + "provider_calls_per_1k_events": { + "type": "number", + "nullable": true, + "description": "Fresh resolutions per 1000 ingested events, rounded to 2 decimals. Null when no events were ingested." + } + } + }, + "examples": { + "Last 30 days": { + "value": { + "events_ingested": 128450, + "events_rejected": 214, + "enrichment_lookups": 41230, + "enrichment_cache_hits": 40118, + "enrichment_provider_calls": 1112, + "enrichment_skipped_low_value": 74320, + "unique_ips_seen": 1112, + "period_days": 30, + "cache_hit_rate": 0.973, + "unique_ip_ratio": 0.0087, + "provider_calls_per_1k_events": 8.66 + } + }, + "No events yet": { + "value": { + "events_ingested": 0, + "events_rejected": 0, + "enrichment_lookups": 0, + "enrichment_cache_hits": 0, + "enrichment_provider_calls": 0, + "enrichment_skipped_low_value": 0, + "unique_ips_seen": 0, + "period_days": 30, + "cache_hit_rate": null, + "unique_ip_ratio": null, + "provider_calls_per_1k_events": null + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: read`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: `GET` requests share a budget of 600 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/{event_uuid}/delete/": { + "delete": { + "summary": "Delete monitoring event", + "description": "Permanently delete one monitoring event together with its rule runs, notes and tag links, for example an event sent by mistake. After the `204` the event is gone from every read (retrieve returns `404`, and it no longer appears in the list or the subject timeline), the deletion is irreversible, and its `event_id` is free to be used for a new event. Not affected: the user or company the event resolved to, cases a rule opened, list entries a rule added, and the scores and statuses already applied to other events. To erase everything about a person or a company, delete the user or the company instead, which also deletes their events. A scoped API key needs `transactions: write`.", + "operationId": "delete_monitoring_event", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "event_uuid", + "in": "path", + "required": true, + "description": "Didit identifier of the event (`event_uuid` from the create, batch, list or timeline response), not your own `event_id`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/events/11111111-2222-3333-4444-555555555555/delete/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.monitoring_events.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.monitoringEvents.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.MonitoringEvents.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.monitoringEvents().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.monitoring_events.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.MonitoringEvents.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ], + "responses": { + "204": { + "description": "The event was deleted. No body." + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: write`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No event with this `event_uuid` exists in the application: it was never stored, it was deleted, or it belongs to another application. A path segment that is not a UUID does not match the route and also returns `404`.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No MonitoringEvent matches the given query." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: write requests (`POST`, `PATCH`, `DELETE`) share a budget of 300 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/kyb/parties/{partyUuid}/regenerate/": { + "post": { + "summary": "Regenerate a Key People party's KYC session", + "description": "Give a Key People party of a Business Verification (KYB) session a new individual KYC session after its previous one was deleted or expired before the person started it, so the Key People check can finish again; send no request body. It works when the party's KYC session was deleted (`kyc_session_deleted: true`) or expired before the person started it (`Expired`, never started). A session still in progress or one that ended in any other status (`Approved`, `Declined`, `In Review`, `Abandoned`, ...) returns `409` unless it was deleted. A party that never had one or a company party always returns `409`. Didit creates a new hosted KYC session with the workflow the party was verified with (its latest published version) or, when that is no longer known, the workflow your Key People settings assign to the party's role, so its link lasts that KYC workflow's session expiration time; it emails the link to the party when an email is on file, clears `is_skipped`, and recomputes the Key People check and the business session status. It then sends a `data.updated` [webhook](https://docs.didit.me/integration/webhooks) for the business session with trigger `console_key_people_regenerate`; to exempt the party instead, use `PATCH /v3/session/{sessionId}/kyb/parties/{partyUuid}/`. A scoped API key needs `decisions: write`, and without `sessions: write` it receives `kyc_session_url` as `null`. Deleted sessions qualify even if the person had already started or finished verification. Imported sessions do not qualify through expiry; they must have been deleted.", + "operationId": "post_v3_session_kyb_party_regenerate", + "x-didit-sdk": { + "resource": "sessions", + "method": "regenerate_kyb_party", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the parent Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "partyUuid", + "in": "path", + "required": true, + "description": "UUID of the Key People party (`key_people_checks[].submitted.parties[].uuid` on the business session decision). Must belong to the session referenced by `sessionId`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/session/33333333-4444-5555-6666-777777777777/kyb/parties/0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41/regenerate/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.regenerate_kyb_party(\n \"33333333-4444-5555-6666-777777777777\",\n \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.regenerateKybParty(\n \"33333333-4444-5555-6666-777777777777\",\n \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.RegenerateKYBParty(ctx, \"33333333-4444-5555-6666-777777777777\", \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().regenerateKybParty(\"33333333-4444-5555-6666-777777777777\", \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->regenerateKybParty('33333333-4444-5555-6666-777777777777', '0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.regenerate_kyb_party(\n \"33333333-4444-5555-6666-777777777777\",\n \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.RegenerateKybPartyAsync(\"33333333-4444-5555-6666-777777777777\", \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "New KYC session created. Returns the updated party with its new child session, which expires after its KYC workflow's session expiration time.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBKeyPeopleParty" + }, + "example": { + "uuid": "0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41", + "entity_type": "person", + "source": "USER", + "name": "Jane Smith", + "first_name": "Jane", + "last_name": "Smith", + "company_name": null, + "registration_number": null, + "email": "user@example.com", + "nationality": "GB", + "phone_number": null, + "date_of_birth": "1980-05-04", + "position": "Director", + "custom_fields": {}, + "is_active": true, + "is_skipped": false, + "requires_verification": true, + "roles": [ + { + "role": "director", + "ownership_percent": null, + "voting_percent": null + } + ], + "kyc_session_id": "55555555-6666-7777-8888-999999999999", + "kyc_session_status": "Not Started", + "kyc_session_url": "https://verify.didit.me/session/abc123def456", + "kyc_session_deleted": false, + "kyb_sub_session_id": null, + "kyb_sub_session_status": null, + "kyb_sub_session_url": null, + "kyb_sub_session_deleted": false, + "verification_workflow_type": "kyc", + "verification_workflow_id": "12121212-3434-5656-7878-909090909090", + "verification_workflow_label": "Key People KYC", + "verification_workflow_features": [ + { + "feature": "ID_VERIFICATION", + "status": "Not Finished" + }, + { + "feature": "LIVENESS", + "status": "Not Finished" + }, + { + "feature": "AML", + "status": "Not Finished" + } + ], + "didit_internal_id": null + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits; no party with this UUID exists in your application; or the party belongs to another session. Each case has its own `detail`. The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's workflow or approved-only limits", + "value": { + "detail": "Session not found." + } + }, + "Unknown party": { + "summary": "No party with this UUID in your application", + "value": { + "detail": "No BusinessParty matches the given query." + } + }, + "Party of another session": { + "summary": "partyUuid exists but belongs to a different session", + "value": { + "detail": "Party does not belong to the given session." + } + } + } + } + } + }, + "409": { + "description": "The party has no eligible KYC session to replace. Unless deleted, a session still in progress or one that ended in another status (`Approved`, `Declined`, `In Review`, `Abandoned`, ...) cannot be regenerated. A party that never had one or a company party also returns `409`. An eligible session was deleted or expired before the person started it. Deleted sessions qualify even if the person had already started or finished verification. Imported sessions do not qualify through expiry; they must have been deleted. Branch on `code`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "detail", + "code" + ], + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string", + "enum": [ + "kyb_party_kyc_session_not_deleted" + ], + "description": "Stable machine-readable error code." + } + } + }, + "example": { + "detail": "This party's KYC session is still live (it has neither been deleted nor expired without starting), so it cannot be regenerated.", + "code": "kyb_party_kyc_session_not_deleted" + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/transactions/{transaction_id}/notes/": { + "get": { + "summary": "List transaction notes", + "description": "List the notes on one monitored transaction, newest first (`created_at` descending): notes you add through the API, notes and status changes reviewers make in the Console, and notes Didit adds automatically (a rule's `add_note` action, or a problem creating the user-action verification session), which carry `actor_name` `Rule Engine`. `metadata` tells the note types apart.\n\nPaginated with `limit` and `offset`. `count` is exact up to 100 and capped at 100 above that, so follow `next` until it is `null` instead of computing pages from `count`. This is a read with no side effects.\n\nA scoped API key needs `transactions: read`; a key limited to some workflows or to approved sessions cannot call this endpoint. Reviewers see the same list under **Notes** on the [transaction detail](https://docs.didit.me/transaction-monitoring/console#transaction-detail).", + "operationId": "list_transaction_notes", + "x-didit-sdk": { + "resource": "transactions.notes", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit transaction UUID: the `uuid` returned by `POST /v3/transactions/` and `GET /v3/transactions/`. Your own `txn_id` is not accepted.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "description": "Number of notes per page. Defaults to 50. A value that is not a positive integer falls back to 50; a value above 1000000 returns `400`." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "description": "Zero-based position of the first note to return. Prefer following `next`. A value that is not a whole number falls back to 0; a value above 1000000 returns `400`." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/abcdef12-3456-7890-abcd-ef1234567890/notes/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.transactions.notes.list(\n \"abcdef12-3456-7890-abcd-ef1234567890\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.transactions.notes.list(\"abcdef12-3456-7890-abcd-ef1234567890\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Transactions.Notes.ListAutoPaging(ctx, \"abcdef12-3456-7890-abcd-ef1234567890\", didit.TransactionsNotesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.transactions().notes().list(\"abcdef12-3456-7890-abcd-ef1234567890\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->notes->list('abcdef12-3456-7890-abcd-ef1234567890') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.notes.list(\"abcdef12-3456-7890-abcd-ef1234567890\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Transactions.Notes.ListAsync(\"abcdef12-3456-7890-abcd-ef1234567890\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "A page of notes, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Number of notes on the transaction: exact up to 100, capped at 100 above that. Follow `next` instead of computing pages from it." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionNote" + } + } + } + }, + "examples": { + "API, Console and rule notes": { + "value": { + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "uuid": "c1111111-2222-4333-8444-555555555555", + "note": "Customer confirmed the withdrawal by phone.", + "actor_email": null, + "actor_name": null, + "metadata": { + "mentioned_emails": [ + "analyst@example.com" + ] + }, + "created_at": "2026-10-16T10:05:12.481203Z" + }, + { + "uuid": "c2222222-3333-4444-8555-666666666666", + "note": "Status changed from IN_REVIEW to APPROVED. Reason: Source of funds verified", + "actor_email": "reviewer@example.com", + "actor_name": "Alex Reviewer", + "metadata": { + "type": "status_change", + "previous_status": "IN_REVIEW", + "new_status": "APPROVED", + "reason_code": "SOF_VERIFIED", + "reason_label": "Source of funds verified" + }, + "created_at": "2026-10-15T17:22:40.103911Z" + }, + { + "uuid": "c3333333-4444-4555-8666-777777777777", + "note": "Large withdrawal to a new wallet: check the source of funds.", + "actor_email": null, + "actor_name": "Rule Engine", + "metadata": { + "rule_uuid": "d1111111-2222-4333-8444-555555555555", + "source": "rule_action" + }, + "created_at": "2026-10-15T08:42:12.004518Z" + } + ] + } + }, + "No notes yet": { + "value": { + "count": 0, + "next": null, + "previous": null, + "results": [] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is above 1000000. The body is a JSON array of messages, not an object.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "examples": { + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + }, + "Offset too large": { + "value": [ + "offset must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing key gets the generic message; a wrong, revoked or expired key gets `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without transactions read": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists in the application (never created, deleted, or owned by another application). A `transaction_id` that is not a UUID does not reach the API: it returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "No Transaction matches the given query." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create transaction note", + "description": "Add a plain-text note to a monitored transaction's review timeline, where Console reviewers see it. A note is context, not a decision: the transaction's `status`, `score` and alerts do not change, no webhook is sent, and nothing is billed. The transaction's `activities` gain a `TRANSACTION_NOTE_ADDED` entry.\n\nNotes created with an API key have no author: `actor_email` and `actor_name` are `null`, so put the author in the text if you need it. Every address in `mentioned_emails` must belong to a member of your organization, otherwise the whole request fails with `400`; mentions are stored lower-cased and de-duplicated, and this endpoint does not notify the mentioned members.\n\nEach call creates a new note, so retrying a timed-out request can add it twice: list the notes before retrying. There is no API endpoint to edit or remove a note. A scoped API key needs `transactions: write`; a key limited to some workflows or to approved sessions cannot call this endpoint.", + "operationId": "create_transaction_note", + "x-didit-sdk": { + "resource": "transactions.notes", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit transaction UUID: the `uuid` returned by `POST /v3/transactions/` and `GET /v3/transactions/`. Your own `txn_id` is not accepted.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "note" + ], + "properties": { + "note": { + "type": "string", + "minLength": 1, + "description": "Plain-text note. Leading and trailing whitespace is trimmed and a blank note is rejected. No length limit." + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + }, + "default": [], + "description": "Emails of members of your organization to mention. Each must be a valid email of an organization member." + } + } + }, + "examples": { + "Note with a mention": { + "value": { + "note": "Customer confirmed the withdrawal by phone.", + "mentioned_emails": [ + "analyst@example.com" + ] + } + }, + "Plain note": { + "value": { + "note": "Source of funds document requested by email." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/abcdef12-3456-7890-abcd-ef1234567890/notes/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"note\": \"Customer confirmed the withdrawal by phone.\", \"mentioned_emails\": [\"analyst@example.com\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.notes.create(\n \"abcdef12-3456-7890-abcd-ef1234567890\",\n note=\"Customer confirmed the withdrawal by phone.\",\n mentioned_emails=[\"analyst@example.com\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.notes.create(\"abcdef12-3456-7890-abcd-ef1234567890\", {\n note: \"Customer confirmed the withdrawal by phone.\",\n mentioned_emails: [\"analyst@example.com\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Notes.Create(ctx, \"abcdef12-3456-7890-abcd-ef1234567890\", didit.TransactionsNotesCreateParams{\n\tNote: \"Customer confirmed the withdrawal by phone.\",\n\tMentionedEmails: didit.Some([]string{\"analyst@example.com\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.TransactionsNotesCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().notes().create(\"abcdef12-3456-7890-abcd-ef1234567890\", TransactionsNotesCreateParams.builder()\n .note(\"Customer confirmed the withdrawal by phone.\")\n .mentionedEmails(List.of(\"analyst@example.com\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->notes->create('abcdef12-3456-7890-abcd-ef1234567890', [\n 'note' => 'Customer confirmed the withdrawal by phone.',\n 'mentioned_emails' => ['analyst@example.com'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.notes.create(\n \"abcdef12-3456-7890-abcd-ef1234567890\",\n note: \"Customer confirmed the withdrawal by phone.\",\n mentioned_emails: [\"analyst@example.com\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Notes.CreateAsync(\"abcdef12-3456-7890-abcd-ef1234567890\", new TransactionsNotesCreateParams\n{\n Note = \"Customer confirmed the withdrawal by phone.\",\n MentionedEmails = new string[] { \"analyst@example.com\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The note was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionNote" + }, + "examples": { + "Created": { + "value": { + "uuid": "c1111111-2222-4333-8444-555555555555", + "note": "Customer confirmed the withdrawal by phone.", + "actor_email": null, + "actor_name": null, + "metadata": { + "mentioned_emails": [ + "analyst@example.com" + ] + }, + "created_at": "2026-10-16T10:05:12.481203Z" + } + } + } + } + } + }, + "400": { + "description": "The body is invalid: `note` missing, null or blank, an invalid address in `mentioned_emails` (errors keyed by list position), or a mentioned address that is not a member of your organization. Nothing is created.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Field errors keyed by field name.", + "additionalProperties": {} + }, + "examples": { + "Missing note": { + "value": { + "note": [ + "This field is required." + ] + } + }, + "Blank note": { + "value": { + "note": [ + "This field may not be blank." + ] + } + }, + "Invalid email": { + "value": { + "mentioned_emails": { + "0": [ + "Enter a valid email address." + ] + } + } + }, + "Not an organization member": { + "value": { + "mentioned_emails": [ + "Mentioned users must belong to this organization and have access to this application." + ] + } + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing key gets the generic message; a wrong, revoked or expired key gets `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without transactions write": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists in the application (never created, deleted, or owned by another application). A `transaction_id` that is not a UUID does not reach the API: it returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "No Transaction matches the given query." + } + } + } + } + } + }, + "429": { + "description": "More than 300 write requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/transactions/{transaction_id}/related/": { + "get": { + "summary": "List related transactions", + "description": "List the other transactions in your application that involve the same people or companies as this one. A transaction is related when any of its parties, in any role, has the same `vendor_data` as one of this transaction's parties. The transaction itself is never returned, and parties without `vendor_data` are ignored: when none of this transaction's parties has one, the result is always empty.\n\nResults use the summary shape of `GET /v3/transactions/`, newest first (`txn_date`, then `created_at`). Paginated with `limit` and `offset`; `count` is exact up to 100 and capped at 100 above that, so follow `next` until it is `null`.\n\nA scoped API key needs `transactions: read`; a key limited to some workflows or to approved sessions cannot call this endpoint. Send the same `vendor_data` for the same person or company on every [transaction](https://docs.didit.me/transaction-monitoring/transactions#subject-and-counterparty) to make this list complete.", + "operationId": "list_related_transactions", + "x-didit-sdk": { + "resource": "transactions", + "method": "list_related", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit transaction UUID: the `uuid` returned by `POST /v3/transactions/` and `GET /v3/transactions/`. Your own `txn_id` is not accepted.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "description": "Number of transactions per page. Defaults to 50. A value that is not a positive integer falls back to 50; a value above 1000000 returns `400`." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "description": "Zero-based position of the first transaction to return. Prefer following `next`. A value that is not a whole number falls back to 0; a value above 1000000 returns `400`." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/abcdef12-3456-7890-abcd-ef1234567890/related/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.transactions.list_related(\n \"abcdef12-3456-7890-abcd-ef1234567890\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.transactions.listRelated(\"abcdef12-3456-7890-abcd-ef1234567890\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Transactions.ListRelatedAutoPaging(ctx, \"abcdef12-3456-7890-abcd-ef1234567890\", didit.TransactionsListRelatedParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.transactions().listRelated(\"abcdef12-3456-7890-abcd-ef1234567890\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->listRelated('abcdef12-3456-7890-abcd-ef1234567890') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.list_related(\"abcdef12-3456-7890-abcd-ef1234567890\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Transactions.ListRelatedAsync(\"abcdef12-3456-7890-abcd-ef1234567890\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "A page of related transactions, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Number of related transactions: exact up to 100, capped at 100 above that. Follow `next` instead of computing pages from it." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionSummary" + } + } + } + }, + "examples": { + "One related transaction": { + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "abcdef99-3456-4890-abcd-ef1234567890", + "transaction_number": 4087, + "txn_id": "your-txn-2026-10-02-017", + "transaction_type": "finance", + "action_type": "deposit", + "status": "APPROVED", + "source": "LIVE", + "direction": "INBOUND", + "amount": "250", + "currency": "EUR", + "currency_type": "fiat", + "preferred_currency_amount": "270.08", + "preferred_currency_code": "USD", + "payment_details": "Card top-up", + "txn_date": "2026-10-02T14:10:00Z", + "created_at": "2026-10-02T14:10:03.512244Z", + "score": 12, + "severity": "LOW", + "decision_reason_code": null, + "decision_reason_label": null, + "tags": [], + "subject_name": "Maria Garcia", + "subject_vendor_data": "user-12345", + "subject_type": "user", + "subject_uuid": "e1111111-2222-4333-8444-555555555555", + "counterparty_name": null, + "counterparty_vendor_data": null, + "counterparty_country": null, + "subject_country": "ESP", + "assigned_to_name": null, + "travel_rule_status": null, + "application_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "application_name": "Production app", + "organization_id": "11111111-2222-3333-4444-555555555555", + "organization_name": "Example Org" + } + ] + } + }, + "No party has vendor_data": { + "value": { + "count": 0, + "next": null, + "previous": null, + "results": [] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is above 1000000. The body is a JSON array of messages, not an object.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "examples": { + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + }, + "Offset too large": { + "value": [ + "offset must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing key gets the generic message; a wrong, revoked or expired key gets `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without transactions read": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists in the application (never created, deleted, or owned by another application). A `transaction_id` that is not a UUID does not reach the API: it returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "No Transaction matches the given query." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/businesses/{business_session_id}/pdf/": { + "get": { + "summary": "Download a business session PDF report", + "description": "Render the PDF report of one Business Verification (KYB) session: company registry data, key people and beneficial owners, documents, AML results and the other workflow features, plus the session's tags and review activity. With white-label customization on, the report carries your logo and privacy policy URL. The body is the PDF itself (`application/pdf`), not JSON.\n\nThe session must be `Approved`, `Declined` or `In Review`; any other status returns `403`, so wait for the `status.updated` [webhook](https://docs.didit.me/integration/webhooks) first. The report is rendered on every call and nothing is billed, so store the file instead of fetching it repeatedly. `GET /v3/session/{sessionId}/generate-pdf/` renders the same kind of report by session ID alone.\n\nA PDF report counts as media: a scoped API key needs `businesses: read` and `media: read`, and a key limited to some workflows or to approved sessions cannot call it.", + "operationId": "get_business_session_pdf", + "x-didit-sdk": { + "resource": "businesses", + "method": "generate_session_pdf", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID.", + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID. Must be the application the API key belongs to: a key used on any other application gets `403`.", + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + }, + { + "name": "business_session_id", + "in": "path", + "required": true, + "description": "`session_id` of the Business Verification (KYB) session, as returned when the session was created. A session of another application returns `404`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl --fail -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/businesses/22222222-3333-4444-5555-666666666666/pdf/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n --output business_report.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\ncontent = client.businesses.generate_session_pdf(\n \"22222222-3333-4444-5555-666666666666\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst content = await didit.businesses.generateSessionPdf(\"22222222-3333-4444-5555-666666666666\");\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresp, err := client.Businesses.GenerateSessionPDF(ctx, \"22222222-3333-4444-5555-666666666666\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.businesses().generateSessionPdf(\"22222222-3333-4444-5555-666666666666\")\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->generateSessionPdf('22222222-3333-4444-5555-666666666666');\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ncontent = didit.businesses.generate_session_pdf(\"22222222-3333-4444-5555-666666666666\")\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar content = await client.Businesses.GenerateSessionPdfAsync(\"22222222-3333-4444-5555-666666666666\");\nawait content.SaveToAsync(\"download.pdf\");" + } + ], + "responses": { + "200": { + "description": "The rendered PDF, returned directly as binary `application/pdf`.", + "headers": { + "Content-Disposition": { + "description": "Always `attachment; filename=business_{business_session_id}.pdf`.", + "schema": { + "type": "string", + "example": "attachment; filename=business_22222222-3333-4444-5555-666666666666.pdf" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed or the session is not finished. Read `detail`: a session that is not `Approved`, `Declined` or `In Review`, a missing API key (a wrong, revoked or expired key gets `401`), an `application_id` that is not the key's application, a scoped key without `media: read` or `businesses: read`, a key limited to workflows or approved sessions, or a request from outside the key's IP list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Session not finished": { + "value": { + "detail": "You can only generate a PDF for sessions in review, declined or approved." + } + }, + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without media read": { + "value": { + "detail": "This API key has no read access to media." + } + }, + "Scoped key without businesses read": { + "value": { + "detail": "This API key has no read access to companies." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No business session with this ID exists in the application (never created, deleted, or owned by another application). A value that is not a UUID returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "Business session not found." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/transactions/{transaction_uuid}/pdf/": { + "get": { + "summary": "Download a transaction PDF report", + "description": "Render one monitored transaction as a PDF report: overview and verdict, parties and payment methods, custom properties, alerts and rule runs, screening and Travel Rule results, the network summary, the cost breakdown, up to 50 related transactions and the 20 most recent activity entries. The body is the PDF itself (`application/pdf`), not JSON.\n\nThe report is rendered from the transaction's current data on every call, so request it again after a status change. Nothing is stored and nothing is billed. Rendering can take a few seconds, so use a generous client timeout.\n\nThis endpoint lives on an application-scoped path: put your organization ID and the ID of the application your API key belongs to in the URL ([find your organization ID](https://docs.didit.me/getting-started/api-authentication#find-your-organization-id)). A PDF report counts as media: a scoped API key needs `media: read` and `transactions: read`, and a key limited to some workflows or to approved sessions cannot call it.", + "operationId": "get_transaction_pdf", + "x-didit-sdk": { + "resource": "transactions", + "method": "generate_pdf", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID.", + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID. Must be the application the API key belongs to: a key used on any other application gets `403`.", + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + }, + { + "name": "transaction_uuid", + "in": "path", + "required": true, + "description": "Didit transaction UUID: the `uuid` returned by `POST /v3/transactions/` and `GET /v3/transactions/`. Your own `txn_id` is not accepted.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl --fail -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/transactions/abcdef12-3456-7890-abcd-ef1234567890/pdf/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n --output transaction_report.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\ncontent = client.transactions.generate_pdf(\n \"abcdef12-3456-7890-abcd-ef1234567890\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst content = await didit.transactions.generatePdf(\"abcdef12-3456-7890-abcd-ef1234567890\");\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresp, err := client.Transactions.GeneratePDF(ctx, \"abcdef12-3456-7890-abcd-ef1234567890\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.transactions().generatePdf(\"abcdef12-3456-7890-abcd-ef1234567890\")\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->generatePdf('abcdef12-3456-7890-abcd-ef1234567890');\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ncontent = didit.transactions.generate_pdf(\"abcdef12-3456-7890-abcd-ef1234567890\")\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar content = await client.Transactions.GeneratePdfAsync(\"abcdef12-3456-7890-abcd-ef1234567890\");\nawait content.SaveToAsync(\"download.pdf\");" + } + ], + "responses": { + "200": { + "description": "The rendered PDF, returned directly as binary `application/pdf`.", + "headers": { + "Content-Disposition": { + "description": "Always `attachment; filename=transaction_{transaction_uuid}.pdf`.", + "schema": { + "type": "string", + "example": "attachment; filename=transaction_abcdef12-3456-7890-abcd-ef1234567890.pdf" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. Read `detail`: a missing API key (a wrong, revoked or expired key gets `401`), an `application_id` that is not the key's application, a scoped key without `media: read` or `transactions: read`, a key limited to workflows or approved sessions, or a request from outside the key's IP list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without media read": { + "value": { + "detail": "This API key has no read access to media." + } + }, + "Scoped key without transactions read": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists in the application (never created, deleted, or owned by another application). A `transaction_uuid` that is not a UUID does not reach the API: it returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "No Transaction matches the given query." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/analytics/": { + "get": { + "summary": "Get application analytics", + "description": "Read the numbers behind the Business Console [analytics dashboard](https://docs.didit.me/console/analytics) for a date range. Without `scope` and `widget_keys` the response is the user verification (KYC) summary (`ApplicationAnalyticsSummary`): daily session counts, status totals, conversion rate, the same figures for the preceding period of equal length, locations, demographics, devices, workflow funnels, active verification time, resubmissions, warnings and time series. With `scope` or `widget_keys` it is the widgets of one dashboard tab (`ApplicationAnalyticsWidgetResponse`): `kyc`, `kyb`, `transactions` or `home`.\n\nSession counts are by session creation date (UTC) and reflect each session's current status. They come from daily rollups refreshed in the background, so the newest activity can take a short while to appear. Invalid workflow IDs and versions are ignored rather than rejected. Nothing is billed.\n\nOnly a full-access API key can call this endpoint: every scoped key gets `403`, whatever access it holds, and so does a key of another application. See [API keys](https://docs.didit.me/console/api-keys).", + "operationId": "get_application_analytics", + "x-didit-sdk": { + "resource": "analytics", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Analytics" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID.", + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID. Must be the application the API key belongs to: a key used on any other application gets `403`.", + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + }, + { + "name": "start_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date", + "example": "2026-10-01" + }, + "description": "First day of the range, `YYYY-MM-DD`, inclusive (UTC). Send it together with `end_date`: when either is missing, both are ignored and the range runs from 30 days before today through today." + }, + { + "name": "end_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date", + "example": "2026-10-15" + }, + "description": "Last day of the range, `YYYY-MM-DD`, inclusive (UTC). A future date counts as today. The range may not exceed 730 days." + }, + { + "name": "scope", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "home", + "kyc", + "kyb", + "transactions" + ] + }, + "description": "Dashboard tab to return widgets for (case-insensitive). Omit it, together with `widget_keys`, for the KYC summary. Sending only `widget_keys` implies `kyc`. `home` accepts any KYC, KYB or transaction widget and defaults to the application's saved home layout." + }, + { + "name": "widget_keys", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "tx_count_total,tx_count_daily" + }, + "description": "Comma-separated widget keys, answered in this order. Keys the scope does not accept are dropped; when none is left you get the scope's defaults. KYC: `kyc_sessions_volume`, `kyc_warning_insights`, `kyc_id_locations`, `kyc_ip_locations`, `kyc_workflow_funnel`, `kyc_average_verification_time`, `kyc_conversion_rate`, `kyc_conversion_by_country`, `kyc_resubmission_insights`, `kyc_original_status_distribution`, `kyc_database_validation_coverage`, `kyc_database_validation_latency`, `kyc_demographics`, `kyc_device_details`. KYB: `kyb_sessions_volume`, `kyb_workflow_funnel`, `kyb_average_verification_time`, `kyb_ip_locations`, `kyb_business_country_distribution`, `kyb_warning_insights`, `kyb_conversion_rate`, `kyb_conversion_by_country`, `kyb_industry_breakdown`, `kyb_resubmission_insights`, `kyb_original_status_distribution`, `kyb_registry_coverage`, `kyb_registry_data_completeness`, `kyb_device_details`. Transactions: `tx_count_total`, `tx_count_daily`, `tx_volume_total_preferred_currency`, `tx_volume_daily_preferred_currency`, `tx_average_amount`, `tx_average_amount_daily`, `tx_transaction_category_breakdown`, `tx_action_type_breakdown`, `tx_status_volume_trend`, `tx_user_ip_or_country_distribution`, `tx_tag_breakdown`, `tx_top_rules_triggered`, `tx_team_member_performance`, `tx_score_histogram`." + }, + { + "name": "workflow_ids", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "33333333-4444-5555-6666-777777777777" + }, + "description": "Comma-separated workflow IDs. Limits the KYC summary and the KYC and KYB widgets to those workflows. Values that are not UUIDs are ignored; when none is valid the filter is dropped. The summary also accepts `workflow_id`." + }, + { + "name": "versions", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "3,4" + }, + "description": "Comma-separated workflow version numbers. Applies only together with `workflow_ids`; values that are not whole numbers are ignored. `version` is accepted as an alias." + }, + { + "name": "branch_path_ids", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "default" + }, + "description": "Comma-separated branch path IDs. Applies only together with `workflow_ids`, and only to KYC figures. Figures are currently recorded only for `default` (all paths combined), so any other value returns empty figures. `branch_path_id` is accepted as an alias." + }, + { + "name": "tag", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Transaction widgets only: comma-separated tag UUIDs or tag names." + }, + { + "name": "transaction_type", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "finance" + }, + "description": "Transaction widgets only: comma-separated transaction categories." + }, + { + "name": "action_type", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "withdrawal" + }, + "description": "Transaction widgets only: comma-separated action types (case-insensitive)." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/analytics/?start_date=2026-10-01&end_date=2026-10-15' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.analytics.retrieve()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.analytics.retrieve();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Analytics.Retrieve(ctx, didit.AnalyticsRetrieveParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.analytics().retrieve();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "analytics->retrieve();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.analytics.retrieve\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Analytics.RetrieveAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The KYC summary, or the requested widgets when `scope` or `widget_keys` is sent.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/ApplicationAnalyticsSummary" + }, + { + "$ref": "#/components/schemas/ApplicationAnalyticsWidgetResponse" + } + ] + }, + "examples": { + "KYC summary": { + "value": { + "grouping_mode": "day", + "data_points": [ + { + "rounded_date": "2026-10-14", + "total": 12, + "approved": 8, + "declined": 1, + "canceled": 1, + "pending": 2, + "in_review": 1, + "expired": 1, + "abandoned": 0, + "in_progress": 1, + "not_started": 0, + "manually_approved": 1, + "manually_declined": 0 + }, + { + "rounded_date": "2026-10-15", + "total": 10, + "approved": 6, + "declined": 2, + "canceled": 0, + "pending": 2, + "in_review": 2, + "expired": 0, + "abandoned": 0, + "in_progress": 0, + "not_started": 0, + "manually_approved": 0, + "manually_declined": 1 + } + ], + "request_breakdown": { + "total_approved": 14, + "total_in_review": 3, + "total_declined": 3, + "total_canceled": 1, + "total_pending": 4, + "total_expired": 1, + "total_abandoned": 0, + "total_in_progress": 1, + "total_not_started": 0, + "total_manually_approved": 1, + "total_manually_declined": 1 + }, + "conversion_rate": 70.0, + "location_stats": { + "countries": [ + { + "session_count": 15, + "country": "Spain", + "country_code": "ES" + }, + { + "session_count": 7, + "country": "Portugal", + "country_code": "PT" + } + ], + "ip_countries": [ + { + "session_count": 15, + "country": "Spain", + "country_code": "ES" + }, + { + "session_count": 7, + "country": "Portugal", + "country_code": "PT" + } + ], + "id_countries": [ + { + "country": "Spain", + "country_code": "ES", + "session_count": 14 + }, + { + "country": "Portugal", + "country_code": "PT", + "session_count": 6 + } + ], + "conversion_by_country": [ + { + "country": "Spain", + "country_code": "ES", + "finished": 13, + "approved": 10 + }, + { + "country": "Portugal", + "country_code": "PT", + "finished": 6, + "approved": 4 + } + ], + "cities": [ + { + "session_count": 9, + "location": "Madrid, Spain", + "latitude": 40.4168, + "longitude": -3.7038 + } + ] + }, + "demographic_stats": { + "age_distribution": { + "under_18": 0, + "18_24": 5, + "25_34": 8, + "35_44": 4, + "45_64": 2, + "65_plus": 1 + }, + "gender_distribution": { + "M": 11, + "F": 9 + } + }, + "device_details": { + "device_models": [ + { + "device": "Apple", + "count": 12 + }, + { + "device": "Android", + "count": 10 + } + ], + "browsers": [ + { + "browser": "Safari", + "count": 12 + }, + { + "browser": "Chrome", + "count": 10 + } + ], + "operating_systems": [ + { + "os": "iOS", + "count": 12 + }, + { + "os": "Android", + "count": 10 + } + ] + }, + "workflow_funnel_stats": { + "33333333-4444-5555-6666-777777777777": { + "total": 22, + "steps": { + "OCR": { + "reached": 21 + }, + "LIVENESS": { + "reached": 19 + }, + "FACE_MATCH": { + "reached": 19 + }, + "AML": { + "reached": 18 + } + } + } + }, + "verification_time_stats": { + "overall_avg_seconds": 64.0, + "overall_count": 18, + "per_feature": { + "OCR": { + "avg_seconds": 41.2, + "total_seconds": 741.6, + "count": 18 + }, + "LIVENESS": { + "avg_seconds": 22.8, + "total_seconds": 410.4, + "count": 18 + } + }, + "per_sub_step": { + "OCR": { + "front_id_photo": { + "avg_seconds": 18.3, + "total_seconds": 329.4, + "count": 18 + } + } + } + }, + "resubmission_stats": { + "first_pass": 17, + "second": 2, + "third_plus": 1, + "abandoned": 0 + }, + "warning_stats": { + "ID_VERIFICATION": { + "total": 21, + "risks": { + "DOCUMENT_EXPIRED": { + "error": 1, + "warning": 0, + "information": 0 + } + } + }, + "LIVENESS": { + "total": 19, + "risks": { + "LOW_LIVENESS_SCORE": { + "error": 0, + "warning": 2, + "information": 0 + } + } + } + }, + "original_status_stats": { + "approved": 13, + "in_review": 5, + "declined": 2 + }, + "demographic_time_series": [ + { + "date": "2026-10-14", + "age_distribution": { + "under_18": 0, + "18_24": 3, + "25_34": 4, + "35_44": 2, + "45_64": 1, + "65_plus": 0 + }, + "gender_distribution": { + "M": 6, + "F": 4 + } + }, + { + "date": "2026-10-15", + "age_distribution": { + "under_18": 0, + "18_24": 2, + "25_34": 4, + "35_44": 2, + "45_64": 1, + "65_plus": 1 + }, + "gender_distribution": { + "M": 5, + "F": 5 + } + } + ], + "device_time_series": [ + { + "date": "2026-10-14", + "devices": { + "Apple": 7, + "Android": 5 + }, + "browsers": { + "Safari": 7, + "Chrome": 5 + }, + "operating_systems": { + "iOS": 7, + "Android": 5 + } + }, + { + "date": "2026-10-15", + "devices": { + "Apple": 5, + "Android": 5 + }, + "browsers": { + "Safari": 5, + "Chrome": 5 + }, + "operating_systems": { + "iOS": 5, + "Android": 5 + } + } + ], + "verification_time_time_series": [ + { + "date": "2026-10-14", + "overall_avg": 66.1, + "per_feature": { + "OCR": 42.0, + "LIVENESS": 24.1 + } + }, + { + "date": "2026-10-15", + "overall_avg": 61.5, + "per_feature": { + "OCR": 40.2, + "LIVENESS": 21.3 + } + } + ], + "resubmission_time_series": [ + { + "date": "2026-10-14", + "first_pass": 9, + "second": 1, + "third_plus": 0, + "abandoned": 0 + }, + { + "date": "2026-10-15", + "first_pass": 8, + "second": 1, + "third_plus": 1, + "abandoned": 0 + } + ], + "original_status_time_series": [ + { + "date": "2026-10-14", + "approved": 7, + "in_review": 3, + "declined": 1 + }, + { + "date": "2026-10-15", + "approved": 6, + "in_review": 2, + "declined": 1 + } + ], + "previous_data_points": [ + { + "rounded_date": "2026-10-12", + "total": 9, + "approved": 6, + "declined": 1, + "canceled": 1, + "pending": 1 + }, + { + "rounded_date": "2026-10-13", + "total": 11, + "approved": 7, + "declined": 2, + "canceled": 0, + "pending": 2 + } + ], + "previous_request_breakdown": { + "total_approved": 13, + "total_in_review": 2, + "total_declined": 3, + "total_canceled": 1, + "total_pending": 3 + } + } + }, + "Transaction widgets": { + "value": { + "scope": "transactions", + "available_widget_keys": [ + "tx_count_total", + "tx_count_daily", + "tx_volume_total_preferred_currency", + "tx_volume_daily_preferred_currency", + "tx_average_amount", + "tx_average_amount_daily", + "tx_transaction_category_breakdown", + "tx_action_type_breakdown", + "tx_status_volume_trend", + "tx_user_ip_or_country_distribution", + "tx_tag_breakdown", + "tx_top_rules_triggered", + "tx_team_member_performance", + "tx_score_histogram" + ], + "requested_widget_keys": [ + "tx_count_total", + "tx_count_daily" + ], + "widgets": { + "tx_count_total": { + "kind": "metric", + "title": "Transaction count", + "value": 42 + }, + "tx_count_daily": { + "kind": "time_series", + "title": "Transaction count", + "items": [ + { + "date": "2026-10-14", + "value": 20 + }, + { + "date": "2026-10-15", + "value": 22 + } + ], + "previous_items": [ + { + "date": "2026-10-12", + "value": 18 + }, + { + "date": "2026-10-13", + "value": 15 + } + ] + } + } + } + } + } + } + } + }, + "400": { + "description": "Invalid date range or scope. The body is an object keyed by the offending parameter, with a string message.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "examples": { + "Bad date format": { + "value": { + "start_date": "Invalid date format. Use YYYY-MM-DD (e.g., 2024-01-15)." + } + }, + "Start after end": { + "value": { + "start_date": "Start date must be before or equal to end date." + } + }, + "Range too long": { + "value": { + "date_range": "Date range cannot exceed 730 days (2 years)." + } + }, + "Unknown scope": { + "value": { + "scope": "Unsupported analytics scope." + } + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. Read `detail`: a missing API key (a wrong, revoked or expired key gets `401`), a key of another application, any scoped key, or a request from outside the key's IP list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key": { + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/analytics/today/": { + "get": { + "summary": "Get today's verifications by hour", + "operationId": "get_application_analytics_today", + "x-didit-sdk": { + "resource": "analytics", + "method": "retrieve_today", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Analytics" + ], + "description": "Today's user verification (KYC) sessions, from local midnight in `tz`, counted per local hour they were created in, next to the same hours of the same weekday one week before, and split by the outcome each one holds now: approved, in review or declined. This is the volume card of the Business Console's Home.\n\n`total` is today so far. `previous_total_so_far` counts the comparison day only up to the same local time, so a change between them compares the same part of both days. The hours still to come today are `0`. A day on which the clocks change has 23 or 25 hours: the missing hour stays `0` and the repeated hour holds both.", + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID.", + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID. Must be the application the API key belongs to: a key used on any other application gets `403`.", + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + }, + { + "name": "tz", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "UTC", + "example": "Europe/Madrid" + }, + "description": "IANA time zone whose midnight starts today, for example `Europe/Madrid` or `America/Mexico_City`. Omit it for UTC." + } + ], + "responses": { + "200": { + "description": "Today's verifications by local hour, the comparison day's, and today's outcomes.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApplicationAnalyticsToday" + }, + "examples": { + "Madrid, 14:00": { + "value": { + "time_zone": "Europe/Madrid", + "date": "2026-10-30", + "current_hour": 14, + "previous_date": "2026-10-23", + "hours": [ + 3, + 1, + 0, + 0, + 1, + 2, + 4, + 9, + 14, + 18, + 21, + 19, + 22, + 24, + 27, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "previous_hours": [ + 2, + 1, + 1, + 0, + 1, + 3, + 5, + 8, + 12, + 16, + 19, + 18, + 20, + 22, + 24, + 26, + 23, + 19, + 15, + 12, + 9, + 7, + 5, + 3 + ], + "total": 165, + "previous_total_so_far": 151, + "outcomes": { + "approved": 139, + "in_review": 6, + "declined": 18 + } + } + } + } + } + } + }, + "400": { + "description": "Unknown time zone. The body is keyed by the parameter, with a list of messages.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "Unknown time zone": { + "value": { + "tz": [ + "Unknown time zone: Europe/Atlantis." + ] + } + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. Read `detail`: a missing or invalid API key (never `401`), a key of another application, any scoped key, or a request from outside the key's IP list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key": { + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/analytics/today/?tz=Europe%2FMadrid' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/webhooks/": { + "get": { + "summary": "List session webhook deliveries", + "description": "List every webhook delivery Didit recorded for one verification session, user (KYC) or business (KYB), newest first, as a plain JSON array (not paginated). Each entry is one attempt to one destination: the first send, each automatic [retry](https://docs.didit.me/integration/webhooks#retry-policy) and each resend are separate entries with their own `uuid`, and retries and resends keep the payload's `event_id`. `request_header` and `request_body` are JSON text; parse them to read the signatures and the payload. Delivery records are available for about 30 days, so an empty array means no webhook has been sent for the session yet or every delivery is older than that. A scoped API key needs `sessions: read` and `decisions: read`.", + "operationId": "get_v3_session_webhooks", + "x-didit-sdk": { + "resource": "sessions.webhooks", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session, user (KYC) or business (KYB): the `session_id` returned by `POST /v3/session/`. The server resolves it against both session types.\n\n**Must be a valid UUID.** The route only matches well-formed UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/22222222-3333-4444-5555-666666666666/webhooks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.webhooks.list(\n \"22222222-3333-4444-5555-666666666666\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.webhooks.list(\"22222222-3333-4444-5555-666666666666\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Webhooks.List(ctx, \"22222222-3333-4444-5555-666666666666\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().webhooks().list(\"22222222-3333-4444-5555-666666666666\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->webhooks->list('22222222-3333-4444-5555-666666666666');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.webhooks.list(\"22222222-3333-4444-5555-666666666666\").each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Webhooks.ListAsync(\"22222222-3333-4444-5555-666666666666\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Every delivery recorded for the session, newest first.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionWebhookDelivery" + } + }, + "examples": { + "Failed delivery and its successful retry": { + "value": [ + { + "uuid": "66666666-7777-8888-9999-000000000002", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 200, + "status": "In Progress", + "webhook_type": "status.updated", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"9188851d160b5b3aacfc946e4dde1f061b7e568662518942c927ddaf507f533d\", \"X-Signature-V2\": \"381236af514d3fb538579365ac5a24f2464ce1b8224c6e770c60d8201c43ecc5\", \"X-Signature-Simple\": \"4740131dc38f29e78a8508371979be74967d5307464494bd3148a03b4d6c8716\", \"X-Timestamp\": \"1792056139\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792056139,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}", + "created_at": "2026-10-15T09:22:19.104233Z" + }, + { + "uuid": "66666666-7777-8888-9999-000000000001", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 503, + "status": "In Progress", + "webhook_type": "status.updated", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"a5e02a03404e1337a0198e6be15d34abbd053c658a0e51e5d0e78554213ef29b\", \"X-Signature-V2\": \"927e7cb770c94ce745939bff73f04aa200015f3bc64795ba46c2aaf78a6cedc5\", \"X-Signature-Simple\": \"e37b0d4c716c72bb5ae7127b9cc455d28ab4686e9e22312b4ec2327409a73302\", \"X-Timestamp\": \"1792056078\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792056078,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}", + "created_at": "2026-10-15T09:21:18.412345Z" + } + ] + }, + "No webhook sent yet": { + "value": [] + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. No `x-api-key`, or an invalid or revoked key, gets `Authentication credentials were not provided or are invalid.` A key of another application gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` An unknown `sessionId` returns `404` before the credential is checked.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Key of another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions read": { + "value": { + "detail": "This API key has no read access to sessions." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist or has been deleted (`Not found.`, returned before the credential is checked), or it is outside the workflow or status limits of a scoped key (`Session not found.`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "value": { + "detail": "Not found." + } + }, + "Outside the key's limits": { + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/webhook/{webhook_uuid}": { + "get": { + "summary": "Get session webhook delivery", + "description": "Retrieve one webhook delivery of a verification session: the URL it went to, the HTTP status your endpoint returned, and the exact headers and JSON body Didit sent. `request_header` and `request_body` are JSON text; to check your [signature verification](https://docs.didit.me/integration/webhooks#signature-verification), recompute `X-Signature-V2` from the parsed body with the secret of the destination it was sent to (or the application-level secret for a delivery to the legacy webhook URL). The path has no trailing slash: with one it returns `404`. A scoped API key needs `sessions: read` and `decisions: read`.", + "operationId": "get_v3_session_webhook", + "x-didit-sdk": { + "resource": "sessions.webhooks", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session, user (KYC) or business (KYB): the `session_id` returned by `POST /v3/session/`. The server resolves it against both session types.\n\n**Must be a valid UUID.** The route only matches well-formed UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + }, + { + "name": "webhook_uuid", + "in": "path", + "required": true, + "description": "UUID of the delivery: the `uuid` of an entry returned by `GET /v3/session/{sessionId}/webhooks/`. It must belong to the session in the path.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "66666666-7777-8888-9999-000000000001" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/22222222-3333-4444-5555-666666666666/webhook/66666666-7777-8888-9999-000000000001' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.webhooks.retrieve(\n \"22222222-3333-4444-5555-666666666666\",\n \"66666666-7777-8888-9999-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.webhooks.retrieve(\n \"22222222-3333-4444-5555-666666666666\",\n \"66666666-7777-8888-9999-000000000001\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Webhooks.Retrieve(ctx, \"22222222-3333-4444-5555-666666666666\", \"66666666-7777-8888-9999-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().webhooks().retrieve(\"22222222-3333-4444-5555-666666666666\", \"66666666-7777-8888-9999-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->webhooks->retrieve('22222222-3333-4444-5555-666666666666', '66666666-7777-8888-9999-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.webhooks.retrieve(\n \"22222222-3333-4444-5555-666666666666\",\n \"66666666-7777-8888-9999-000000000001\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Webhooks.RetrieveAsync(\"22222222-3333-4444-5555-666666666666\", \"66666666-7777-8888-9999-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The delivery, with its request headers and body.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionWebhookDelivery" + }, + "examples": { + "Connection failure": { + "value": { + "uuid": "66666666-7777-8888-9999-000000000001", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 503, + "status": "In Progress", + "webhook_type": "status.updated", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"a5e02a03404e1337a0198e6be15d34abbd053c658a0e51e5d0e78554213ef29b\", \"X-Signature-V2\": \"927e7cb770c94ce745939bff73f04aa200015f3bc64795ba46c2aaf78a6cedc5\", \"X-Signature-Simple\": \"e37b0d4c716c72bb5ae7127b9cc455d28ab4686e9e22312b4ec2327409a73302\", \"X-Timestamp\": \"1792056078\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792056078,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}", + "created_at": "2026-10-15T09:21:18.412345Z" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. No `x-api-key`, or an invalid or revoked key, gets `Authentication credentials were not provided or are invalid.` A key of another application gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` An unknown `sessionId` returns `404` before the credential is checked.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Key of another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions read": { + "value": { + "detail": "This API key has no read access to sessions." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist or has been deleted (`Not found.`, returned before the credential is checked), or it is outside the workflow or status limits of a scoped key (`Session not found.`). A delivery that does not exist, belongs to another session or is no longer available returns `No WebhookLog matches the given query.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "value": { + "detail": "Not found." + } + }, + "Outside the key's limits": { + "value": { + "detail": "Session not found." + } + }, + "Delivery not in this session": { + "value": { + "detail": "No WebhookLog matches the given query." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/webhook/{webhook_uuid}/resend": { + "post": { + "summary": "Resend session webhook delivery", + "description": "Send a recorded session webhook again to the URL it first went to, with the same payload and `event_id` and a fresh `timestamp`, `X-Timestamp` header and signatures. Didit signs it with the current secret of the destination the delivery belongs to (or the application-level secret for a delivery to the legacy webhook URL), waits up to 5 seconds for your endpoint, records the attempt as a new delivery and returns it with `200` whatever your endpoint answered: read `response_status`. A resend is not retried automatically, and every call sends again, so make your endpoint idempotent on `event_id`. The path has no trailing slash and the request has no body. A scoped API key needs `webhooks: write` and `decisions: read`.", + "operationId": "post_v3_session_webhook_resend", + "x-didit-sdk": { + "resource": "sessions.webhooks", + "method": "resend", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session, user (KYC) or business (KYB): the `session_id` returned by `POST /v3/session/`. The server resolves it against both session types.\n\n**Must be a valid UUID.** The route only matches well-formed UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + }, + { + "name": "webhook_uuid", + "in": "path", + "required": true, + "description": "UUID of the delivery to resend: the `uuid` of an entry returned by `GET /v3/session/{sessionId}/webhooks/`. It must belong to the session in the path.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "66666666-7777-8888-9999-000000000001" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/session/22222222-3333-4444-5555-666666666666/webhook/66666666-7777-8888-9999-000000000001/resend' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.webhooks.resend(\n \"22222222-3333-4444-5555-666666666666\",\n \"66666666-7777-8888-9999-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.webhooks.resend(\n \"22222222-3333-4444-5555-666666666666\",\n \"66666666-7777-8888-9999-000000000001\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Webhooks.Resend(ctx, \"22222222-3333-4444-5555-666666666666\", \"66666666-7777-8888-9999-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().webhooks().resend(\"22222222-3333-4444-5555-666666666666\", \"66666666-7777-8888-9999-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->webhooks->resend('22222222-3333-4444-5555-666666666666', '66666666-7777-8888-9999-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.webhooks.resend(\n \"22222222-3333-4444-5555-666666666666\",\n \"66666666-7777-8888-9999-000000000001\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Webhooks.ResendAsync(\"22222222-3333-4444-5555-666666666666\", \"66666666-7777-8888-9999-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The new delivery, already attempted. `response_status` is what your endpoint answered this time. The original delivery is unchanged.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionWebhookDelivery" + }, + "examples": { + "Accepted on resend": { + "value": { + "uuid": "66666666-7777-8888-9999-000000000004", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 200, + "status": "In Progress", + "webhook_type": "status.updated", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"b8eb1e376517ee0cad8ba98ff7f8fd35fc72eca231000963a96a335f82328835\", \"X-Signature-V2\": \"9efd2ce9ed1303e1d05273ef9608a59363ed9789a05253db4748b3e8e50cb109\", \"X-Signature-Simple\": \"bf3a9dc04b2c20ab539203b0cd5cca2a10f630bf62d8feb6d68d82af80f64774\", \"X-Timestamp\": \"1792159542\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792159542,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}", + "created_at": "2026-10-16T14:05:42.330981Z" + } + } + } + } + } + }, + "400": { + "description": "The delivery could not be resent. Branch on `code`: `webhook_url_missing` means the delivery has no recorded URL and nothing is sent; `webhook_resend_error` means the stored delivery could not be signed again or the attempt could not be completed. After `webhook_resend_error`, `detail` starts with `Error processing webhook resend:` followed by a diagnostic text that can change.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string", + "description": "Stable machine-readable error code. Branch on this, not on `detail`." + } + } + }, + "examples": { + "No recorded URL": { + "value": { + "detail": "The webhook delivery does not have a request URL configured", + "code": "webhook_url_missing" + } + }, + "Resend failed": { + "value": { + "detail": "Error processing webhook resend: Invalid webhook data format", + "code": "webhook_resend_error" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. No `x-api-key`, or an invalid or revoked key, gets `Authentication credentials were not provided or are invalid.` A key of another application gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` An unknown `sessionId` returns `404` before the credential is checked.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Key of another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks write": { + "value": { + "detail": "This API key has no write access to webhooks." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist or has been deleted (`Not found.`, returned before the credential is checked), or it is outside the workflow or status limits of a scoped key (`Session not found.`). A delivery that does not exist, belongs to another session or is no longer available returns `Webhook log not found`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "value": { + "detail": "Not found." + } + }, + "Outside the key's limits": { + "value": { + "detail": "Session not found." + } + }, + "Delivery not in this session": { + "value": { + "detail": "Webhook log not found" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 300 write requests (POST, PATCH, PUT, DELETE) in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (300).", + "schema": { + "type": "integer", + "example": 300 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-write" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/events/": { + "get": { + "summary": "List session events", + "description": "Read the event timeline of one verification session, newest first by `sent_at`: the screens the user saw, buttons and uploads, failed attempts, status and feature-step changes, and the workflow routing trace. Works for User Verification (KYC) and Business Verification (KYB) session IDs. Events are stored asynchronously, so a new event can take a few seconds to appear. Results are paginated with `limit` and `offset`; follow `next` until it is `null`. A scoped API key needs `sessions: read` and `decisions: read`, and without `media: read` it receives `file_url` as `null`. The routing events are described in [Retrieve session](https://docs.didit.me/sessions-api/retrieve-session).", + "operationId": "get_v3_session_events", + "x-didit-sdk": { + "resource": "sessions", + "method": "list_events", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of events per page. Defaults to 50. A value below 1 or a non-numeric value falls back to 50; a value above 1,000,000 returns `400`.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000, + "example": 50 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of results to skip before the first returned one. Defaults to 0; a negative or non-numeric value falls back to 0, a value above 1,000,000 returns `400`. Prefer following the `next` and `previous` URLs.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000, + "example": 0 + } + }, + { + "name": "ordering", + "in": "query", + "required": false, + "description": "Optional sort override. The default order is `-sent_at`. Pass a result field name (for example `sent_at`, `created_at`, `event_type`, `feature_step` or `step`) to sort ascending, prefix it with `-` to sort descending, and separate several fields with commas. Unknown fields are ignored.", + "schema": { + "type": "string", + "example": "sent_at" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/events/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.sessions.list_events(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.sessions.listEvents(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Sessions.ListEventsAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsListEventsParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.sessions().listEvents(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->listEvents('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.list_events(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Sessions.ListEventsAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "One page of the session's events, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Total number of events of the session." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionEvent" + } + } + } + }, + "examples": { + "Completed session": { + "value": { + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "uuid": "aaaaaaaa-1111-2222-3333-444444444444", + "event_type": "SESSION_STATUS_CHANGED", + "created_at": "2026-09-14T10:02:13.904512Z", + "component": null, + "feature_step": null, + "step": null, + "details": { + "old_status": "In Progress", + "current_status": "Approved" + }, + "ip_address": null, + "user_agent": null, + "device_info": null, + "file_url": null, + "sent_at": 1789380131482 + }, + { + "uuid": "bbbbbbbb-1111-2222-3333-444444444444", + "event_type": "FEATURE_STEP_CHANGED", + "created_at": "2026-09-14T10:01:49.210377Z", + "component": null, + "feature_step": null, + "step": null, + "details": { + "old_feature_step": "OCR", + "current_feature_step": "LIVENESS" + }, + "ip_address": null, + "user_agent": null, + "device_info": null, + "file_url": null, + "sent_at": 1789380107103 + }, + { + "uuid": "cccccccc-1111-2222-3333-444444444444", + "event_type": "STEP_RENDERED", + "created_at": "2026-09-14T10:01:00.581930Z", + "component": "DocumentCapture", + "feature_step": "OCR", + "step": "DOCUMENT_CAPTURE_FRONT", + "details": {}, + "ip_address": "203.0.113.24", + "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", + "device_info": { + "device_model": "iPhone" + }, + "file_url": null, + "sent_at": 1789380058776 + } + ] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is above 1,000,000. The body is a JSON array with one message.", + "content": { + "application/json": { + "examples": { + "Limit too large": { + "summary": "limit=2000000", + "value": [ + "limit must not exceed 1000000." + ] + }, + "Offset too large": { + "summary": "offset=2000000", + "value": [ + "offset must not exceed 1000000." + ] + } + }, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions read": { + "summary": "Scoped key lacks sessions: read", + "value": { + "detail": "This API key has no read access to sessions." + } + }, + "Scoped key without decisions read": { + "summary": "Scoped key lacks decisions: read", + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted, or is outside a scoped key's workflow or approved-only limits. The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's workflow or approved-only limits", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 600 GET requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/aml-screening-history/": { + "get": { + "summary": "List AML screening history", + "description": "List every completed ongoing-monitoring re-screen of a session's AML check, newest first by `completed_at`, including the runs where nothing changed: alerts, activity entries and webhooks fire only when a result moves, so this history is the evidence that a clean subject was still being screened. Works for KYC and KYB session IDs; pass `node_id` when the session ran more than one AML step. Only ongoing-monitoring re-screens are listed (the first screening is part of the session decision) and runs from before this history existed are not reconstructed, so a session without ongoing monitoring returns an empty list. `count` is exact up to 100 and capped at 100 above that, so follow `next` until it is `null`. Pass `month` to read one calendar month at a time. A scoped API key needs `decisions: read`; see [Continuous AML monitoring](https://docs.didit.me/core-technology/aml-screening/continuous-monitoring-aml-screening).", + "operationId": "get_v3_session_aml_screening_history", + "x-didit-sdk": { + "resource": "sessions", + "method": "list_aml_screening_history", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node ID of the AML check. Needed only when the session ran more than one AML step; without it the most recent AML check is used. An unknown node returns `404`.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + }, + { + "name": "month", + "in": "query", + "required": false, + "description": "A UTC calendar month as `YYYY-MM`. Only screenings completed in that month are returned; without it every month is returned. A malformed month returns `400`.", + "schema": { + "type": "string", + "pattern": "^\\d{4}-(0[1-9]|1[0-2])$", + "example": "2026-09" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of screening runs per page. Defaults to 50. A value below 1 or a non-numeric value falls back to 50; a value above 1,000,000 returns `400`.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000, + "example": 50 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of results to skip before the first returned one. Defaults to 0; a negative or non-numeric value falls back to 0, a value above 1,000,000 returns `400`. Prefer following the `next` and `previous` URLs.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000, + "example": 0 + } + }, + { + "name": "ordering", + "in": "query", + "required": false, + "description": "Optional sort override. The default order is `-completed_at`. Pass a result field name (for example `completed_at`, `started_at`, `outcome` or `score`) to sort ascending, prefix it with `-` to sort descending, and separate several fields with commas. Unknown fields are ignored.", + "schema": { + "type": "string", + "example": "completed_at" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/aml-screening-history/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.sessions.list_aml_screening_history(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.sessions.listAmlScreeningHistory(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Sessions.ListAMLScreeningHistoryAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsListAMLScreeningHistoryParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.sessions().listAmlScreeningHistory(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->listAmlScreeningHistory('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.list_aml_screening_history(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Sessions.ListAmlScreeningHistoryAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "One page of screening runs, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "maximum": 100, + "description": "Number of runs, exact up to 100 and capped at 100 above that." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AMLScreeningRun" + } + } + } + }, + "examples": { + "Monitored subject": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "uuid": "dddddddd-1111-2222-3333-444444444444", + "run_id": "aaaaaaaa-1111-2222-3333-444444444444", + "completed_at": "2026-09-14T04:12:33.481207Z", + "started_at": "2026-09-14T04:00:01.002311Z", + "outcome": "NO_CHANGE", + "trigger": "ONGOING_MONITORING", + "provider": "aml_data_source", + "status": "Approved", + "score": 0.0, + "total_hits": 0, + "new_hits": 0, + "entities_screened": 1, + "dataset_freshness": { + "captured_at": "2026-09-14T03:58:02.114820+00:00", + "provider": "aml_data_source", + "catalog_version": "2026-09-13", + "sources": { + "sanctions": { + "last_updated_at": "2026-09-13T21:10:00+00:00", + "oldest_updated_at": "2026-09-11T06:00:00+00:00", + "dataset_count": 120, + "dated_dataset_count": 120 + }, + "pep": { + "last_updated_at": "2026-09-13T08:00:00+00:00", + "oldest_updated_at": null, + "dataset_count": 45, + "dated_dataset_count": 44 + }, + "warnings": { + "last_updated_at": "2026-09-12T18:45:00+00:00", + "oldest_updated_at": "2026-09-01T00:00:00+00:00", + "dataset_count": 30, + "dated_dataset_count": 30 + } + } + } + }, + { + "uuid": "eeeeeeee-1111-2222-3333-444444444444", + "run_id": "bbbbbbbb-1111-2222-3333-444444444444", + "completed_at": "2026-09-13T04:11:58.020914Z", + "started_at": "2026-09-13T04:00:00.871022Z", + "outcome": "FAILED", + "trigger": "ONGOING_MONITORING", + "provider": "aml_data_source", + "status": "Approved", + "score": 0.0, + "total_hits": 0, + "new_hits": null, + "entities_screened": 0, + "dataset_freshness": null + } + ] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is above 1,000,000 (a JSON array with one message), or `month` is not a `YYYY-MM` calendar month (an object keyed by `month`).", + "content": { + "application/json": { + "examples": { + "Limit too large": { + "summary": "limit=2000000", + "value": [ + "limit must not exceed 1000000." + ] + }, + "Offset too large": { + "summary": "offset=2000000", + "value": [ + "offset must not exceed 1000000." + ] + }, + "Malformed month": { + "summary": "month=2026-13", + "value": { + "month": [ + "Not a calendar month. Use YYYY-MM." + ] + } + } + }, + "schema": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "object", + "properties": { + "month": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + ] + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions read": { + "summary": "Scoped key lacks decisions: read", + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits, or it has no AML check (for the given `node_id`). The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's limits (also returned for a session deleted moments ago)", + "value": { + "detail": "Session not found." + } + }, + "No AML check": { + "summary": "The session never ran an AML step", + "value": { + "detail": "The session does not have an AML check." + } + }, + "No AML check for node": { + "summary": "node_id does not match an AML check of the session", + "value": { + "detail": "The session does not have an AML check for node 'feature_aml_2'." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 600 GET requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/aml-screening-history/export/": { + "get": { + "summary": "Export AML screening history as CSV", + "description": "Download the same ongoing-monitoring history as `GET /v3/session/{sessionId}/aml-screening-history/` as a CSV file, one line per completed re-screen, newest first, ready to hand to an auditor. The file is streamed whole (no pagination) and starts with a header line; an empty cell means unknown, dataset dates are never estimated, and a value that would start a spreadsheet formula is prefixed with an apostrophe. Works for KYC and KYB session IDs; pass `node_id` when the session ran more than one AML step. Pass `month` to export one calendar month. Errors are returned as JSON, not CSV. A scoped API key needs `decisions: read` and `media: read`: the response is a file, so a key without `media: read` gets `403`.", + "operationId": "get_v3_session_aml_screening_history_export", + "x-didit-sdk": { + "resource": "sessions", + "method": "export_aml_screening_history", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node ID of the AML check. Needed only when the session ran more than one AML step; without it the most recent AML check is used. An unknown node returns `404`.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + }, + { + "name": "month", + "in": "query", + "required": false, + "description": "A UTC calendar month as `YYYY-MM`. Only screenings completed in that month are returned; without it every month is returned. A malformed month returns `400`.", + "schema": { + "type": "string", + "pattern": "^\\d{4}-(0[1-9]|1[0-2])$", + "example": "2026-09" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/aml-screening-history/export/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -o aml-screening-history.csv" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.sessions.export_aml_screening_history(\n \"11111111-2222-3333-4444-555555555555\",\n)\nwith open(\"download.csv\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.sessions.exportAmlScreeningHistory(\"11111111-2222-3333-4444-555555555555\");\nawait writeFile(\"download.csv\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Sessions.ExportAMLScreeningHistory(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsExportAMLScreeningHistoryParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.csv\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().exportAmlScreeningHistory(\"11111111-2222-3333-4444-555555555555\")\n .writeTo(Path.of(\"download.csv\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->exportAmlScreeningHistory('11111111-2222-3333-4444-555555555555');\n$content->saveTo('download.csv');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.sessions.export_aml_screening_history(\"11111111-2222-3333-4444-555555555555\")\nFile.binwrite(\"download.csv\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Sessions.ExportAmlScreeningHistoryAsync(\"11111111-2222-3333-4444-555555555555\");\nawait content.SaveToAsync(\"download.csv\");" + } + ], + "responses": { + "200": { + "description": "The CSV file, streamed as `text/csv` with CRLF line endings. Columns, in order: `screened_at` (when the re-screen finished), `outcome` (`NO_CHANGE`, `CHANGED` or `FAILED`), `provider` (opaque identifier of the AML data source; do not branch on it), `aml_status`, `aml_score`, `total_hits`, `datasets_captured_at`, `datasets_sanctions_last_updated_at`, `datasets_pep_last_updated_at`, `datasets_warnings_last_updated_at`, `datasets_adverse_media_last_updated_at`, `datasets_catalog_version`, `run_id` (the monitoring run), `entities_screened` (`1`, or `0` for a failed run) and `new_hits` (empty when unknown). Dates are ISO 8601 with a UTC offset. A session with no re-screens yet returns the header line only.", + "headers": { + "Content-Disposition": { + "description": "`attachment; filename=\"aml-screening-history-{sessionId}.csv\"`, or `aml-screening-history-{sessionId}-{month}.csv` when `month` is passed.", + "schema": { + "type": "string", + "example": "attachment; filename=\"aml-screening-history-11111111-2222-3333-4444-555555555555.csv\"" + } + } + }, + "content": { + "text/csv": { + "schema": { + "type": "string", + "format": "binary" + }, + "example": "screened_at,outcome,provider,aml_status,aml_score,total_hits,datasets_captured_at,datasets_sanctions_last_updated_at,datasets_pep_last_updated_at,datasets_warnings_last_updated_at,datasets_adverse_media_last_updated_at,datasets_catalog_version,run_id,entities_screened,new_hits\r\n2026-09-14T04:12:33.481207+00:00,NO_CHANGE,aml_data_source,Approved,0.0,0,2026-09-14T03:58:02.114820+00:00,2026-09-13T21:10:00+00:00,2026-09-13T08:00:00+00:00,2026-09-12T18:45:00+00:00,,2026-09-13,aaaaaaaa-1111-2222-3333-444444444444,1,0\r\n2026-09-13T04:11:58.020914+00:00,FAILED,aml_data_source,Approved,0.0,0,,,,,,,bbbbbbbb-1111-2222-3333-444444444444,0,\r\n" + } + } + }, + "400": { + "description": "`month` is not a `YYYY-MM` calendar month.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "month": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "examples": { + "Malformed month": { + "summary": "month=2026-13", + "value": { + "month": [ + "Not a calendar month. Use YYYY-MM." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions read": { + "summary": "Scoped key lacks decisions: read", + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Scoped key without media read": { + "summary": "Scoped key lacks media: read (the response is a file)", + "value": { + "detail": "This API key has no read access to media." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits, or it has no AML check (for the given `node_id`). The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's limits (also returned for a session deleted moments ago)", + "value": { + "detail": "Session not found." + } + }, + "No AML check": { + "summary": "The session never ran an AML step", + "value": { + "detail": "The session does not have an AML check." + } + }, + "No AML check for node": { + "summary": "node_id does not match an AML check of the session", + "value": { + "detail": "The session does not have an AML check for node 'feature_aml_2'." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 600 GET requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/add-images/": { + "patch": { + "summary": "Add an image to a session", + "description": "Attach one extra image or PDF to the ID verification (`model_type=kyc`) or Proof of Address (`model_type=poa`) result of a finished User Verification (KYC) session, for example evidence collected outside the flow. The session must be `Approved`, `Declined`, `In Review` or `Kyc Expired` and carry the result named by `model_type`; Business Verification (KYB) sessions are not supported. Each call appends one file (to the most recent result when the step ran more than once), keeps the existing files and records a `FILE_ADDED` entry in the session activity log; the upload sends no webhook and does not change the session status. The file is compressed on upload and is returned in `extra_files` of the session decision. This endpoint is limited to 10 requests per minute per API key. A scoped API key needs `sessions: write`, and without `media: read` it receives the `extra_files` links as `null`.", + "operationId": "patch_v3_session_add_images", + "x-didit-sdk": { + "resource": "sessions", + "method": "add_images", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session to add the file to (the `session_id` returned by `POST /v3/session/`). A Business Verification (KYB) session ID returns `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "file", + "model_type" + ], + "properties": { + "file": { + "type": "string", + "format": "binary", + "description": "The image or PDF. Allowed extensions: `jpg`, `jpeg`, `png`, `webp`, `tiff`, `pdf`. At most 15 MB as uploaded; the file is then compressed and rejected with `400` if it is still larger than 2 MB (2,097,152 bytes). Empty files are rejected." + }, + "model_type": { + "type": "string", + "enum": [ + "kyc", + "poa" + ], + "description": "`kyc` adds the file to the ID verification result, `poa` to the Proof of Address result.", + "example": "kyc" + }, + "file_password": { + "type": "string", + "description": "Password of an encrypted PDF. Without it, or with a wrong one, an encrypted PDF returns `400`." + } + } + }, + "example": { + "file": "(binary)", + "model_type": "kyc" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/add-images/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'file=@./back-of-id.jpg' \\\n -F 'model_type=kyc'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.add_images(\n \"11111111-2222-3333-4444-555555555555\",\n file=open(\"image.jpg\", \"rb\"),\n model_type=\"kyc\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.addImages(\"11111111-2222-3333-4444-555555555555\", {\n file: await fileFromPath(\"image.jpg\"),\n model_type: \"kyc\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.AddImages(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsAddImagesParams{\n\tFile: didit.FileFromPath(\"image.jpg\"),\n\tModelType: \"kyc\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.types.SessionsAddImagesParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().addImages(\"11111111-2222-3333-4444-555555555555\", SessionsAddImagesParams.builder()\n .file(Path.of(\"image.jpg\"))\n .modelType(SessionsAddImagesParams.ModelType.of(\"kyc\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->addImages('11111111-2222-3333-4444-555555555555', [\n 'file' => File::fromPath('image.jpg'),\n 'model_type' => 'kyc',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.add_images(\n \"11111111-2222-3333-4444-555555555555\",\n file: Pathname(\"image.jpg\"),\n model_type: \"kyc\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.AddImagesAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsAddImagesParams\n{\n File = FileUpload.FromPath(\"image.jpg\"),\n ModelType = \"kyc\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "File added. Returns every extra file of that result, including the new one.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "extra_files" + ], + "properties": { + "extra_files": { + "type": "array", + "description": "Short-lived presigned links to all extra files of the result named by `model_type`, oldest first. Each link is `null` for a scoped API key without `media: read`. Read fresh links from the session decision later.", + "items": { + "type": "string", + "format": "uri", + "nullable": true + } + } + } + }, + "example": { + "extra_files": [ + "https://media.example.com/session/11111111-2222-3333-4444-555555555555/kyc-extra-files/66666666-7777-8888-9999-000000000000.jpg?expires=1789383731&signature=abc123" + ] + } + } + } + }, + "400": { + "description": "Validation error. Field errors arrive as `{\"\": [\"\"]}`; encrypted-PDF errors as `{\"detail\": \"...\"}`.", + "content": { + "application/json": { + "examples": { + "Missing file": { + "summary": "No file part in the form", + "value": { + "file": [ + "No file was submitted." + ] + } + }, + "Missing model_type": { + "summary": "model_type omitted", + "value": { + "model_type": [ + "This field is required." + ] + } + }, + "Invalid model_type": { + "summary": "model_type is not kyc or poa", + "value": { + "model_type": [ + "\"passport\" is not a valid choice." + ] + } + }, + "File too large": { + "summary": "Upload over 15 MB", + "value": { + "file": [ + "File size should not exceed 15 MB" + ] + } + }, + "Too large after compression": { + "summary": "Still over 2 MB after compression", + "value": { + "file": [ + "File size is too big. Max allowed size is 2097152 bytes." + ] + } + }, + "Encrypted PDF": { + "summary": "Encrypted PDF without file_password", + "value": { + "detail": "The PDF is encrypted. Please upload a decrypted PDF or a photo instead." + } + }, + "Wrong PDF password": { + "summary": "file_password does not open the PDF", + "value": { + "detail": "The PDF password is incorrect. Please provide the correct password." + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions write": { + "summary": "Scoped key lacks sessions: write", + "value": { + "detail": "This API key has no write access to sessions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits; it is a KYB session or not in `Approved`, `Declined`, `In Review` or `Kyc Expired`; or it has no result of the requested `model_type` (checked before the file is validated). The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's workflow or approved-only limits", + "value": { + "detail": "Session not found." + } + }, + "Not eligible": { + "summary": "Session not finished yet, or a KYB session ID", + "value": { + "detail": "No Session matches the given query." + } + }, + "No ID verification": { + "summary": "model_type=kyc on a session without ID verification", + "value": { + "detail": "The session does not have KYC data." + } + }, + "No Proof of Address": { + "summary": "model_type=poa on a session without Proof of Address", + "value": { + "detail": "The session does not have POA data." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 10 add-images requests per minute per API key (rejected requests count too). The shared budget of 300 write requests per minute also applies. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Session add-images rate limit exceeded. You can make up to 10 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/update-aml-status/": { + "patch": { + "summary": "Update an AML check status", + "description": "Set the status of a session's AML screening check as a whole (the `status` of an `aml_screenings[]` entry in the session decision), for example `Approved` after your team cleared every hit. Works for KYC and KYB session IDs; pass `node_id` (query parameter or body field) when the session ran more than one AML step, otherwise the most recent AML check is updated. When the status actually changes, an `AML_STATUS_UPDATED` activity entry is recorded and a `data.updated` [webhook](https://docs.didit.me/integration/webhooks) is sent; sending the current status returns `200` and changes nothing. The session status is not recomputed: use `PATCH /v3/session/{sessionId}/update-status/` for the session decision and `PATCH /v3/session/{sessionId}/update-aml-hit-status/` for single hits. A scoped API key needs `decisions: write`.", + "operationId": "patch_v3_session_update_aml_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_aml_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node ID of the AML check to update. Needed only when the session ran more than one AML step. Takes precedence over `node_id` in the body.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "New status of the AML check.", + "example": "Approved" + }, + "node_id": { + "type": "string", + "description": "Workflow graph node ID of the AML check, as an alternative to the query parameter.", + "example": "feature_aml_1" + } + } + }, + "example": { + "status": "Approved" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-aml-status/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"status\": \"Approved\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_aml_status(\n \"11111111-2222-3333-4444-555555555555\",\n status=\"Approved\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateAmlStatus(\"11111111-2222-3333-4444-555555555555\", {\n status: \"Approved\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateAMLStatus(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsUpdateAMLStatusParams{\n\tStatus: \"Approved\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsUpdateAmlStatusParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateAmlStatus(\"11111111-2222-3333-4444-555555555555\", SessionsUpdateAmlStatusParams.builder()\n .status(SessionsUpdateAmlStatusParams.Status.of(\"Approved\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateAmlStatus('11111111-2222-3333-4444-555555555555', [\n 'status' => 'Approved',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_aml_status(\n \"11111111-2222-3333-4444-555555555555\",\n status: \"Approved\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateAmlStatusAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsUpdateAmlStatusParams\n{\n Status = \"Approved\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "AML check status set (or unchanged when it already had this value).", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message", + "status", + "previous_status" + ], + "properties": { + "message": { + "type": "string", + "description": "Always `AML status updated successfully`." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Status of the AML check now." + }, + "previous_status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Status before the call. Equal to `status` when nothing changed." + } + } + }, + "example": { + "message": "AML status updated successfully", + "status": "Approved", + "previous_status": "In Review" + } + } + } + }, + "400": { + "description": "Validation error: `status` missing or not one of the allowed values. The AML check lookup runs first, so a session without an AML check returns `404` even with an invalid body.", + "content": { + "application/json": { + "examples": { + "Missing status": { + "summary": "Empty body or no status", + "value": { + "status": [ + "This field is required." + ] + } + }, + "Invalid status": { + "summary": "status not in the enum", + "value": { + "status": [ + "\"Cleared\" is not a valid choice." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits, or it has no AML check (for the given `node_id`). The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's limits (also returned for a session deleted moments ago)", + "value": { + "detail": "Session not found." + } + }, + "No AML check": { + "summary": "The session never ran an AML step", + "value": { + "detail": "The session does not have an AML check." + } + }, + "No AML check for node": { + "summary": "node_id does not match an AML check of the session", + "value": { + "detail": "The session does not have an AML check for node 'feature_aml_2'." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/status/": { + "get": { + "summary": "Get service status (public, unauthenticated)", + "description": "Returns the state of each Didit service (`operational`, `degraded` or `down`), the overall state (the worst of them), and the incidents that are ongoing or were resolved in the last 7 days. Use it to power a status widget or to check Didit's health from your integration.\n\nPublic: send no auth header. The response contains no organization data and is the same for every caller. Responses are cached for 30 seconds, so poll no more than once every 30 seconds.", + "operationId": "get_v3_status", + "tags": [ + "Status" + ], + "security": [ + {} + ], + "parameters": [], + "responses": { + "200": { + "description": "Current service status.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServiceStatusFeed" + }, + "examples": { + "One service degraded": { + "summary": "An ongoing incident and one resolved this week", + "value": { + "status": "degraded", + "updated_at": "2026-10-13T09:41:07.512345Z", + "services": [ + { + "key": "service_id_verification", + "name": "ID verification", + "status": "operational" + }, + { + "key": "service_liveness_face_match", + "name": "Liveness and face match", + "status": "operational" + }, + { + "key": "service_aml_screening", + "name": "AML screening", + "status": "operational" + }, + { + "key": "service_kyb_registries", + "name": "KYB registries", + "status": "operational" + }, + { + "key": "service_database_validation", + "name": "Database validation", + "status": "degraded" + }, + { + "key": "service_phone_delivery", + "name": "Phone verification delivery", + "status": "operational" + }, + { + "key": "service_email_delivery", + "name": "Email verification delivery", + "status": "operational" + }, + { + "key": "service_transaction_monitoring", + "name": "Transaction monitoring", + "status": "operational" + } + ], + "incidents": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "service": "service_database_validation", + "service_name": "Database validation", + "state": "degraded", + "status": "ongoing", + "title": "Database validation is degraded in Brazil.", + "body": "Checks against Brazil (CPF) may be slow or fail since 08:55 UTC.", + "affected": "Brazil (CPF)", + "country_name": "Brazil", + "started_at": "2026-10-13T08:55:00Z", + "resolved_at": null, + "duration_minutes": null + }, + { + "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "service": "service_aml_screening", + "service_name": "AML screening", + "state": "down", + "status": "resolved", + "title": "AML screening is back to normal.", + "body": "Recovered at 14:32 UTC after 17 minutes.", + "affected": "", + "country_name": "", + "started_at": "2026-10-11T14:15:00Z", + "resolved_at": "2026-10-11T14:32:00Z", + "duration_minutes": 17 + } + ] + } + }, + "All operational": { + "summary": "No incident in the last 7 days", + "value": { + "status": "operational", + "updated_at": "2026-10-13T09:41:07.512345Z", + "services": [ + { + "key": "service_id_verification", + "name": "ID verification", + "status": "operational" + }, + { + "key": "service_liveness_face_match", + "name": "Liveness and face match", + "status": "operational" + }, + { + "key": "service_aml_screening", + "name": "AML screening", + "status": "operational" + }, + { + "key": "service_kyb_registries", + "name": "KYB registries", + "status": "operational" + }, + { + "key": "service_database_validation", + "name": "Database validation", + "status": "operational" + }, + { + "key": "service_phone_delivery", + "name": "Phone verification delivery", + "status": "operational" + }, + { + "key": "service_email_delivery", + "name": "Email verification delivery", + "status": "operational" + }, + { + "key": "service_transaction_monitoring", + "name": "Transaction monitoring", + "status": "operational" + } + ], + "incidents": [] + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests in a minute from the same IP address (or the same API key, if you send one). Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "detail" + ], + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -fsS https://verification.didit.me/v3/status/" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.get('https://verification.didit.me/v3/status/', timeout=5)\nresp.raise_for_status()\nfeed = resp.json()\nprint(feed['status'])\nfor service in feed['services']:\n if service['status'] != 'operational':\n print(service['key'], service['status'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const feed = await fetch('https://verification.didit.me/v3/status/').then((r) => r.json());\nconsole.log(feed.status);\nconst affected = feed.services.filter((s) => s.status !== 'operational');\nconsole.log(affected.map((s) => `${s.key}: ${s.status}`));" + } + ], + "x-didit-sdk": { + "resource": "system", + "method": "retrieve_status", + "auth": "public", + "server": false + } + } + }, + "/v3/organization/bank-verification-pricing/": { + "get": { + "summary": "Get bank verification prices (public, unauthenticated)", + "description": "Returns the list price of each Bank Verification method in every country: bank sign-in with its balance and transactions add-ons, and the account check. Bank Verification is not enabled in production yet, so a bank step is refused on production workflows until it is; treat these prices as a list price, not as an offer you can buy today. Use it to quote a bank step before you build a workflow, or to show bank prices in your own product.\n\nPublic: send no auth header. The response contains no organization data and is the same for every caller. It is cached publicly for one hour and carries an `ETag`, so send `If-None-Match` to get a `304 Not Modified` when nothing changed.\n\nA method is `available` only where Didit serves it in production today, and its `price_usd` is `null` everywhere else, so the feed never shows a price you cannot buy. `available: false` covers a country with no approved price, a method the country does not support, and a route that is not switched on yet.\n\nThese are list prices before your plan discount. Your plan discount applies to the whole bank price, and a negotiated or contract price replaces the list price for your organization.", + "operationId": "get_v3_bank_verification_pricing", + "tags": [ + "Pricing" + ], + "security": [ + {} + ], + "parameters": [], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -fsS https://verification.didit.me/v3/organization/bank-verification-pricing/" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# This call needs no API key.\nclient = Didit()\n\nresult = client.pricing.bank_verification()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// This call needs no API key.\nconst didit = new Didit();\n\nconst result = await didit.pricing.bankVerification();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// This call needs no API key.\nclient := didit.NewClient()\n\nresult, err := client.Pricing.BankVerification(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// This call needs no API key.\nDidit client = new Didit();\n\nvar result = client.pricing().bankVerification();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "pricing->bankVerification();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# This call needs no API key.\ndidit = Didit::Client.new\n\nresult = didit.pricing.bank_verification\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// This call needs no API key.\nusing var client = new DiditClient();\n\nvar result = await client.Pricing.BankVerificationAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-sdk": { + "resource": "pricing", + "method": "bank_verification", + "auth": "none", + "server": true + }, + "responses": { + "200": { + "description": "Bank Verification list prices by country.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankVerificationPricing" + }, + "examples": { + "Two countries": { + "summary": "One country with both methods, one with neither", + "value": { + "currency": "USD", + "statement_upload": { + "price_usd": "0.0000", + "included_after_charged_bank_attempt": true, + "standalone_available": false + }, + "own_provider_fee_usd": "0.0200", + "countries": { + "GBR": { + "name": "United Kingdom", + "methods": { + "open_banking": { + "available": true, + "market": "UK and EEA", + "price_usd": "0.7900", + "add_ons": { + "balance": "0.3500", + "transactions": "1.3000" + } + }, + "account_lookup": { + "available": true, + "price_usd": "0.4000" + } + } + }, + "CAN": { + "name": "Canada", + "methods": { + "open_banking": { + "available": false, + "market": null, + "price_usd": null, + "add_ons": {} + }, + "account_lookup": { + "available": false, + "price_usd": null + } + } + } + } + } + } + } + } + } + }, + "304": { + "description": "Not modified: the `ETag` you sent in `If-None-Match` still matches. No body." + } + } + } + }, + "/v3/session/{sessionId}/simulate/": { + "post": { + "summary": "Simulate a sandbox session's status", + "description": "Force a terminal status on a sandbox session after the fact, from your backend, without running the flow. Sandbox applications only: a live session returns `404`. Works for User Verification (KYC) and Business Verification (KYB) sessions.\n\nSet `new_status`, or let `scenario` or `risk` supply it. `risk` (or the first risk the scenario's magic values carry) adds that warning to the session. The change is recorded in the session's activity.\n\n`send_webhooks` (default `true`) sends exactly the webhooks a real status change sends. With `false` the status still changes and the console still updates live, but no webhook is logged or sent, the missed-webhook recovery never re-sends that decision, and the next status change sends webhooks as usual. The response echoes the value applied.\n\nRequires the `write:sessions` privilege; a scoped API key needs write access to sessions. An unknown `session_id` returns `404` and another application's session returns `403`, both before the body is read; a live session returns `404` only after the body is validated. To simulate from the hosted flow with the session's own token, use `POST /v3/session/{sessionId}/sandbox/simulate/`. See [Sandbox & Test Data](/integration/sandbox-testing) for the scenario catalog.", + "operationId": "post_v3_session_simulate", + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the sandbox verification session. Accepts both user (KYC) and business (KYB) session IDs.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSimulateRequest" + }, + "examples": { + "Scenario Without Webhooks": { + "summary": "Decline through a scenario without sending webhooks", + "value": { + "scenario": "decline_aml_hit", + "send_webhooks": false + } + }, + "Status Only": { + "summary": "Approve and send the usual webhooks", + "value": { + "new_status": "Approved" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The status changed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSimulateResponse" + }, + "examples": { + "Simulated": { + "summary": "Declined through a scenario, webhooks not sent", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "previous_status": "In Progress", + "status": "Declined", + "scenario": "decline_aml_hit", + "risk": "POSSIBLE_MATCH_FOUND", + "send_webhooks": false + } + } + } + } + } + }, + "400": { + "description": "Validation error: an unknown scenario or risk, no `new_status` when neither `scenario` nor `risk` supplies one, a status this endpoint does not set, or the session already has the requested status (a bare JSON array).", + "content": { + "application/json": { + "examples": { + "Unknown Scenario": { + "summary": "scenario is not in the catalog", + "value": { + "scenario": [ + "Unknown sandbox scenario: 'not-a-scenario'." + ] + } + }, + "Unknown Risk": { + "summary": "risk is not in the catalog", + "value": { + "risk": [ + "Unknown sandbox risk case: 'NOT_A_RISK'." + ] + } + }, + "Missing new_status": { + "summary": "No new_status, scenario or risk", + "value": { + "new_status": [ + "This field is required." + ] + } + }, + "Invalid new_status": { + "summary": "A status the simulation does not set", + "value": { + "new_status": [ + "\"Resubmitted\" is not a valid choice." + ] + } + }, + "Same Status": { + "summary": "The session already has this status", + "value": [ + "The new status is the same as the current status." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials, a caller without the `write:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no write access to sessions." + } + } + } + } + } + }, + "404": { + "description": "No session with this `session_id` exists, or it is a live (non-sandbox) session.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown or live session", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/simulate/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"scenario\": \"decline_aml_hit\", \"send_webhooks\": false}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.simulate(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n scenario=\"decline_aml_hit\",\n send_webhooks=False,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.simulate(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", {\n scenario: \"decline_aml_hit\",\n send_webhooks: false,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Simulate(ctx, \"3fa85f64-5717-4562-b3fc-2c963f66afa6\", didit.SandboxSimulateRequest{\n\tScenario: didit.String(\"decline_aml_hit\"),\n\tSendWebhooks: didit.Bool(false),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SandboxSimulateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().simulate(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", SandboxSimulateRequest.builder()\n .scenario(\"decline_aml_hit\")\n .sendWebhooks(false)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->simulate('3fa85f64-5717-4562-b3fc-2c963f66afa6', [\n 'scenario' => 'decline_aml_hit',\n 'send_webhooks' => false,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.simulate(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n scenario: \"decline_aml_hit\",\n send_webhooks: false\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.SimulateAsync(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", new SandboxSimulateRequest\n{\n Scenario = \"decline_aml_hit\",\n SendWebhooks = false,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "simulate", + "auth": "api_key", + "server": true + } + } + }, + "/v3/session/{sessionId}/sandbox/simulate/": { + "post": { + "summary": "Simulate a sandbox session's status from the client", + "description": "The same simulation as `POST /v3/session/{sessionId}/simulate/`, authenticated with the session's own `Session-Token` (returned in `session_token` by [Create Session](/sessions-api/create-session)) instead of your API key, so a test harness or the hosted flow can call it from the client. The body, the response and `send_webhooks` behave exactly as on that endpoint.\n\nA `Session-Token` of a live (non-sandbox) application, or one that does not match the `sessionId` in the path, returns `404`. See [Sandbox & Test Data](/integration/sandbox-testing).", + "operationId": "post_v3_session_sandbox_simulate", + "tags": [ + "Sessions" + ], + "security": [ + { + "SessionTokenAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the sandbox verification session. Must match the session encoded in the Session-Token used to authenticate.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSimulateRequest" + }, + "examples": { + "Scenario Without Webhooks": { + "summary": "Decline through a scenario without sending webhooks", + "value": { + "scenario": "decline_aml_hit", + "send_webhooks": false + } + }, + "Status Only": { + "summary": "Approve and send the usual webhooks", + "value": { + "new_status": "Approved" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The status changed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSimulateResponse" + }, + "examples": { + "Simulated": { + "summary": "Declined through a scenario, webhooks not sent", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "previous_status": "In Progress", + "status": "Declined", + "scenario": "decline_aml_hit", + "risk": "POSSIBLE_MATCH_FOUND", + "send_webhooks": false + } + } + } + } + } + }, + "400": { + "description": "Validation error: an unknown scenario or risk, no `new_status` when neither `scenario` nor `risk` supplies one, a status this endpoint does not set, or the session already has the requested status (a bare JSON array).", + "content": { + "application/json": { + "examples": { + "Unknown Scenario": { + "summary": "scenario is not in the catalog", + "value": { + "scenario": [ + "Unknown sandbox scenario: 'not-a-scenario'." + ] + } + }, + "Unknown Risk": { + "summary": "risk is not in the catalog", + "value": { + "risk": [ + "Unknown sandbox risk case: 'NOT_A_RISK'." + ] + } + }, + "Missing new_status": { + "summary": "No new_status, scenario or risk", + "value": { + "new_status": [ + "This field is required." + ] + } + }, + "Invalid new_status": { + "summary": "A status the simulation does not set", + "value": { + "new_status": [ + "\"Resubmitted\" is not a valid choice." + ] + } + }, + "Same Status": { + "summary": "The session already has this status", + "value": [ + "The new status is the same as the current status." + ] + } + } + } + } + }, + "403": { + "description": "The `Session-Token` header is missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid Session Token": { + "summary": "Missing or invalid Session-Token", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "The `Session-Token` belongs to a live (non-sandbox) application, or does not match the `sessionId` in the path.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Live session, or session_id / token mismatch", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/sandbox/simulate/ \\\n -H 'Session-Token: YOUR_SESSION_TOKEN' \\\n -H 'Content-Type: application/json' \\\n -d '{\"new_status\": \"Approved\", \"send_webhooks\": false}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/sandbox/simulate/\",\n headers={\"Session-Token\": \"YOUR_SESSION_TOKEN\"},\n json={\"new_status\": \"Approved\", \"send_webhooks\": False},\n)\nresponse.raise_for_status()\nprint(response.json())" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "simulate_sandbox", + "auth": "device_token", + "server": false + } + } + }, + "/v3/session/{sessionId}/features/overrides/": { + "get": { + "summary": "List a session's check overrides", + "description": "Every override of an individual feature (step) status on a User Verification (KYC) or Business Verification (KYB) session, newest first and not paginated: applied ones, ones waiting for the person to finish the flow (`pending_until_flow_finishes`) and cancelled ones, each with who asked, their reason, and whether it applied while the person was still in the flow (`forced`).\n\n`person_in_flow` tells you whether a new override with `defer_until_flow_finishes: true` would wait. `flow_finished_at` is when the person last finished the flow; it is `null` while they are still in it (a resubmission included) and when the session closed before they finished.\n\nRequires the `read:sessions` privilege; a scoped API key needs read access to decisions.", + "operationId": "get_v3_session_feature_overrides", + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + ], + "responses": { + "200": { + "description": "The session's check overrides, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "session_id", + "person_in_flow", + "flow_finished_at", + "overrides" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "person_in_flow": { + "type": "boolean", + "description": "Whether the person is still in the flow, so a new override with `defer_until_flow_finishes: true` would wait.", + "example": true + }, + "flow_finished_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the person last finished the flow. `null` while they are still in it (a resubmission included) and when the session closed before they finished (abandoned, expired or decided mid-flow).", + "example": null + }, + "overrides": { + "type": "array", + "description": "Every override of the session, newest first.", + "items": { + "$ref": "#/components/schemas/FeatureStatusOverride" + } + } + } + }, + "examples": { + "Overrides": { + "summary": "One override waiting, one applied", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "person_in_flow": true, + "flow_finished_at": null, + "overrides": [ + { + "id": "9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3", + "node_id": "feature_liveness_1", + "feature_type": "", + "feature_model": "Face", + "previous_status": "Declined", + "new_status": "Approved", + "state": "pending_until_flow_finishes", + "forced": false, + "reason": "Liveness retried with a reviewer present.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": null, + "cancelled_at": null, + "cancel_reason": null + }, + { + "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "node_id": "feature_ocr_1", + "feature_type": "", + "feature_model": "KYC", + "previous_status": "In Review", + "new_status": "Approved", + "state": "applied", + "forced": true, + "reason": "Document manually verified by compliance.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": "2026-10-13T09:15:00Z", + "cancelled_at": null, + "cancel_reason": null + } + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials, a caller without the `read:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks read:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no read access to decisions." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/features/overrides/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.list_feature_overrides(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.listFeatureOverrides(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.ListFeatureOverrides(ctx, \"3fa85f64-5717-4562-b3fc-2c963f66afa6\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().listFeatureOverrides(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->listFeatureOverrides('3fa85f64-5717-4562-b3fc-2c963f66afa6');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.list_feature_overrides(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ListFeatureOverridesAsync(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "list_feature_overrides", + "auth": "api_key", + "server": true + } + } + }, + "/v3/session/{sessionId}/features/overrides/{overrideId}/cancel/": { + "post": { + "summary": "Cancel a check override that waits for the flow to finish", + "description": "Cancel an override of a feature (step) status that is still `pending_until_flow_finishes`, so it never applies. The check keeps its status, the cancellation is recorded in the session's activity as `FEATURE_OVERRIDE_CANCELLED`, and no webhook is sent. The response is the override with `state: cancelled` and `cancel_reason: cancelled_by_reviewer`.\n\nAn override that already applied or was cancelled returns `409` with the code `feature_override_not_pending`. Find override IDs with `GET /v3/session/{sessionId}/features/overrides/` or in the `override` returned by the feature status update.\n\nRequires the `write:sessions` privilege; a scoped API key needs write access to decisions.", + "operationId": "post_v3_session_feature_override_cancel", + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + }, + { + "in": "path", + "name": "overrideId", + "required": true, + "description": "The `id` of the override to cancel.", + "schema": { + "type": "string", + "format": "uuid", + "example": "9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3" + } + } + ], + "responses": { + "200": { + "description": "The override is cancelled and will never apply.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FeatureStatusOverride" + }, + "examples": { + "Cancelled": { + "summary": "The cancelled override", + "value": { + "id": "9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3", + "node_id": "feature_liveness_1", + "feature_type": "", + "feature_model": "Face", + "previous_status": "Declined", + "new_status": "Approved", + "state": "cancelled", + "forced": false, + "reason": "Liveness retried with a reviewer present.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": null, + "cancelled_at": "2026-10-13T09:20:00Z", + "cancel_reason": "cancelled_by_reviewer" + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials, a caller without the `write:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no write access to decisions." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists, or the session has no override with this `overrideId`.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + }, + "Override Not Found": { + "summary": "No override with this id on the session", + "value": { + "detail": "Override not found." + } + } + } + } + } + }, + "409": { + "description": "The override already applied or was cancelled.", + "content": { + "application/json": { + "examples": { + "Not Pending": { + "summary": "Only a waiting override can be cancelled", + "value": { + "detail": "This override is no longer pending: it already applied or was cancelled.", + "code": "feature_override_not_pending" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/features/overrides/9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3/cancel/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.cancel_feature_override(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n \"9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.cancelFeatureOverride(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n \"9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.CancelFeatureOverride(ctx, \"3fa85f64-5717-4562-b3fc-2c963f66afa6\", \"9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().cancelFeatureOverride(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", \"9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->cancelFeatureOverride('3fa85f64-5717-4562-b3fc-2c963f66afa6', '9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.cancel_feature_override(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n \"9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.CancelFeatureOverrideAsync(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", \"9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "cancel_feature_override", + "auth": "api_key", + "server": true + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/tags/add/": { + "post": { + "summary": "Add a tag to a network", + "description": "Put one of your application's tags on the network. Returns the network detail, whose `tags` lists your application's tags on the network, with `201` when the tag was added and `200` when it was already on the network. A tag that belongs to another application, or does not exist, returns 404 `Tag not found.`. Requires a full-access API key, or `write:networks` for a console member.", + "operationId": "add_network_tag", + "tags": [ + "Networks" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "description": "Network UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkTagRequest" + }, + "example": { + "tag_uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/3fa85f64-5717-4562-b3fc-2c963f66afa6/tags/add/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"tag_uuid\": \"7c9e6679-7425-40de-944b-e07fc1f90ae7\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.add_tag(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n tag_uuid=\"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.addTag(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", {\n tag_uuid: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.AddTag(ctx, \"3fa85f64-5717-4562-b3fc-2c963f66afa6\", didit.NetworkTagRequest{\n\tTagUUID: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.NetworkTagRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().addTag(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", NetworkTagRequest.builder()\n .tagUuid(\"7c9e6679-7425-40de-944b-e07fc1f90ae7\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->addTag('3fa85f64-5717-4562-b3fc-2c963f66afa6', [\n 'tag_uuid' => '7c9e6679-7425-40de-944b-e07fc1f90ae7',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.add_tag(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n tag_uuid: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.AddTagAsync(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", new NetworkTagRequest\n{\n TagUuid = \"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The tag was added. Returns the network detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkDetailResponse" + }, + "example": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "network_id": "N-0042", + "name": null, + "status": "in_review", + "risk": { + "score": 82, + "band": "high", + "bar": 0.82 + }, + "pattern_types": [ + "exact_same_device", + "same_email" + ], + "signal_types": [ + "device", + "email" + ], + "member_counts": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signal_coverage": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + }, + "first_activity_at": "2026-10-01T09:12:44Z", + "last_activity_at": "2026-10-12T16:03:10Z", + "stats": { + "members": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signals": { + "total": 2, + "by_type": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + } + } + }, + "fraud_tags": [], + "risk_signals": { + "residential_proxy_reuse": { + "detected": false, + "signal_count": 0, + "max_subject_count": 0 + }, + "email_intelligence": { + "enriched_signal_count": 1, + "high_risk_count": 0, + "social_profile_count": 0 + }, + "phone_intelligence": { + "enriched_signal_count": 0, + "high_risk_count": 0 + } + }, + "reviewed_at": "2026-10-12T16:30:00Z", + "reviewed_by": "reviewer@example.com", + "review_reason": "Shared device across three new accounts.", + "notes_count": 0, + "sharing_mode": "global", + "cross_org_available": true, + "cross_org_unavailable_reason": null, + "tags": [ + { + "uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "name": "SUSPICIOUS", + "color": "#6366F1", + "source": "custom", + "added_by": null, + "added_at": "2026-10-13T08:15:00Z" + } + ], + "outcomes": { + "approved": 1, + "declined": 1, + "in_review": 0, + "total": 3 + }, + "patterns": [ + { + "type": "background", + "score": 0.8125, + "members": 3, + "reason": null, + "computed_at": "2026-10-13T08:00:00Z" + } + ] + } + } + } + }, + "200": { + "description": "The tag was already on the network and is left as it is. Returns the network detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkDetailResponse" + }, + "example": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "network_id": "N-0042", + "name": null, + "status": "in_review", + "risk": { + "score": 82, + "band": "high", + "bar": 0.82 + }, + "pattern_types": [ + "exact_same_device", + "same_email" + ], + "signal_types": [ + "device", + "email" + ], + "member_counts": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signal_coverage": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + }, + "first_activity_at": "2026-10-01T09:12:44Z", + "last_activity_at": "2026-10-12T16:03:10Z", + "stats": { + "members": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signals": { + "total": 2, + "by_type": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + } + } + }, + "fraud_tags": [], + "risk_signals": { + "residential_proxy_reuse": { + "detected": false, + "signal_count": 0, + "max_subject_count": 0 + }, + "email_intelligence": { + "enriched_signal_count": 1, + "high_risk_count": 0, + "social_profile_count": 0 + }, + "phone_intelligence": { + "enriched_signal_count": 0, + "high_risk_count": 0 + } + }, + "reviewed_at": "2026-10-12T16:30:00Z", + "reviewed_by": "reviewer@example.com", + "review_reason": "Shared device across three new accounts.", + "notes_count": 0, + "sharing_mode": "global", + "cross_org_available": true, + "cross_org_unavailable_reason": null, + "tags": [ + { + "uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "name": "SUSPICIOUS", + "color": "#6366F1", + "source": "custom", + "added_by": null, + "added_at": "2026-10-13T08:15:00Z" + } + ], + "outcomes": { + "approved": 1, + "declined": 1, + "in_review": 0, + "total": 3 + }, + "patterns": [ + { + "type": "background", + "score": 0.8125, + "members": 3, + "reason": null, + "computed_at": "2026-10-13T08:00:00Z" + } + ] + } + } + } + }, + "400": { + "description": "Invalid body: `tag_uuid` is missing or is not a UUID.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tag_uuid": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "examples": { + "Missing tag_uuid": { + "summary": "Missing tag_uuid", + "value": { + "tag_uuid": [ + "This field is required." + ] + } + }, + "Invalid tag_uuid": { + "summary": "Invalid tag_uuid", + "value": { + "tag_uuid": [ + "Must be a valid UUID." + ] + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `write:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Network not found": { + "summary": "Network not found", + "value": { + "detail": "Network not found." + } + }, + "Tag not found": { + "summary": "Tag not found", + "value": { + "detail": "Tag not found." + } + } + } + } + } + } + }, + "x-didit-sdk": { + "resource": "networks", + "method": "add_tag", + "auth": "api_key", + "server": true, + "scope": "application" + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/tags/remove/": { + "post": { + "summary": "Remove a tag from a network", + "description": "Remove one of your application's tags from the network. Returns the network detail. 404 when the tag is not on the network, or does not belong to your application. Requires a full-access API key, or `write:networks` for a console member.", + "operationId": "remove_network_tag", + "tags": [ + "Networks" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "description": "Network UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkTagRequest" + }, + "example": { + "tag_uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/3fa85f64-5717-4562-b3fc-2c963f66afa6/tags/remove/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"tag_uuid\": \"7c9e6679-7425-40de-944b-e07fc1f90ae7\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.remove_tag(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n tag_uuid=\"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.removeTag(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", {\n tag_uuid: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.RemoveTag(ctx, \"3fa85f64-5717-4562-b3fc-2c963f66afa6\", didit.NetworkTagRequest{\n\tTagUUID: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.NetworkTagRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().removeTag(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", NetworkTagRequest.builder()\n .tagUuid(\"7c9e6679-7425-40de-944b-e07fc1f90ae7\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->removeTag('3fa85f64-5717-4562-b3fc-2c963f66afa6', [\n 'tag_uuid' => '7c9e6679-7425-40de-944b-e07fc1f90ae7',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.remove_tag(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n tag_uuid: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.RemoveTagAsync(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", new NetworkTagRequest\n{\n TagUuid = \"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The tag was removed from the network. Returns the network detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkDetailResponse" + }, + "example": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "network_id": "N-0042", + "name": null, + "status": "in_review", + "risk": { + "score": 82, + "band": "high", + "bar": 0.82 + }, + "pattern_types": [ + "exact_same_device", + "same_email" + ], + "signal_types": [ + "device", + "email" + ], + "member_counts": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signal_coverage": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + }, + "first_activity_at": "2026-10-01T09:12:44Z", + "last_activity_at": "2026-10-12T16:03:10Z", + "stats": { + "members": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signals": { + "total": 2, + "by_type": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + } + } + }, + "fraud_tags": [], + "risk_signals": { + "residential_proxy_reuse": { + "detected": false, + "signal_count": 0, + "max_subject_count": 0 + }, + "email_intelligence": { + "enriched_signal_count": 1, + "high_risk_count": 0, + "social_profile_count": 0 + }, + "phone_intelligence": { + "enriched_signal_count": 0, + "high_risk_count": 0 + } + }, + "reviewed_at": "2026-10-12T16:30:00Z", + "reviewed_by": "reviewer@example.com", + "review_reason": "Shared device across three new accounts.", + "notes_count": 0, + "sharing_mode": "global", + "cross_org_available": true, + "cross_org_unavailable_reason": null, + "tags": [], + "outcomes": { + "approved": 1, + "declined": 1, + "in_review": 0, + "total": 3 + }, + "patterns": [ + { + "type": "background", + "score": 0.8125, + "members": 3, + "reason": null, + "computed_at": "2026-10-13T08:00:00Z" + } + ] + } + } + } + }, + "400": { + "description": "Invalid body: `tag_uuid` is missing or is not a UUID.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tag_uuid": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "examples": { + "Missing tag_uuid": { + "summary": "Missing tag_uuid", + "value": { + "tag_uuid": [ + "This field is required." + ] + } + }, + "Invalid tag_uuid": { + "summary": "Invalid tag_uuid", + "value": { + "tag_uuid": [ + "Must be a valid UUID." + ] + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `write:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Network not found": { + "summary": "Network not found", + "value": { + "detail": "Network not found." + } + }, + "Tag not found": { + "summary": "Tag not found", + "value": { + "detail": "Tag not found." + } + }, + "Tag not on the network": { + "summary": "Tag not on the network", + "value": { + "detail": "This tag is not on the network." + } + } + } + } + } + } + }, + "x-didit-sdk": { + "resource": "networks", + "method": "remove_tag", + "auth": "api_key", + "server": true, + "scope": "application" + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/{request_uuid}/remind/": { + "post": { + "summary": "Remind the customer of a request", + "description": "Send a waiting customer request's email again, to the same verified recipient, with the same content. You can remind a request at most once every 24 hours. A reminder changes nothing else: the request keeps its deadline and the case keeps its status. Each reminder is logged on the case as a CUSTOMER_REQUEST event with action `reminded`. The response's `delivery_status` is how this reminder's delivery ended: SENT, SIMULATED (the email was not sent), or UNKNOWN (the email might have been accepted; it is not sent again automatically). `next_reminder_at` is when you can remind this request again. Returns 409 with a `reason` when the customer no longer has the request to answer, and 429 with `retry_at` and a `Retry-After` header when the request was reminded in the last 24 hours. Requires `write:cases`.", + "operationId": "organization_application_cases_requests_remind_create", + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "description": "Organization UUID." + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "description": "Application UUID." + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "description": "Case UUID." + }, + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "description": "Customer request UUID, from the case's request list." + } + ], + "tags": [ + "Case Management" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/requests/3fa85f64-5717-4562-b3fc-2c963f66afa6/remind/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.requests.remind(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.requests.remind(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Requests.Remind(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().requests().remind(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->remind('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.requests.remind(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Requests.RemindAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The reminder was recorded and its email sent.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "request", + "reminded_at", + "delivery_status", + "next_reminder_at" + ], + "properties": { + "request": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequest" + }, + "reminded_at": { + "type": "string", + "format": "date-time", + "description": "When this reminder was recorded." + }, + "delivery_status": { + "type": "string", + "enum": [ + "SENT", + "SIMULATED", + "UNKNOWN" + ], + "description": "How this reminder's delivery ended. SIMULATED: the email was not sent. UNKNOWN: the email might have been accepted; it is not sent again automatically." + }, + "next_reminder_at": { + "type": "string", + "format": "date-time", + "description": "When you can remind this request again: `reminded_at` plus 24 hours." + } + } + }, + "example": { + "request": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "purpose": "confirm_details", + "language": "en", + "recipient": "customer@example.com", + "expires_at": "2026-10-20T09:00:00Z", + "actor_email": "reviewer@example.com", + "delivery_status": "SENT", + "delivered_at": "2026-10-13T09:00:04Z", + "cancelled_at": null, + "responded_at": null, + "response": null, + "has_attachment": false, + "created_at": "2026-10-13T09:00:00Z" + }, + "reminded_at": "2026-10-14T10:30:00Z", + "delivery_status": "SENT", + "next_reminder_at": "2026-10-15T10:30:00Z" + } + } + } + }, + "400": { + "description": "The case has a resolution pending approval. The body is a list of strings.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "example": [ + "Customer messages require an active case without a pending resolution." + ] + } + } + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "409": { + "description": "The request no longer waits on the customer, so there is nothing to remind them of. `reason` says why: `answered` (the customer responded), `cancelled`, `expired`, `closed` (the case was resolved after the request went out, even if it was reopened since), `not_actionable` (a review-in-progress update asks nothing), `not_delivered` (the first email has not gone out), or `contact_unavailable` (the verified email is no longer available).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "case_request_not_waiting" + ] + }, + "reason": { + "type": "string", + "enum": [ + "answered", + "cancelled", + "expired", + "closed", + "not_actionable", + "not_delivered", + "contact_unavailable" + ] + } + } + }, + "example": { + "detail": "This request no longer waits on the customer, so there is nothing to remind them of.", + "code": "case_request_not_waiting", + "reason": "answered" + } + } + } + }, + "429": { + "description": "The request was already reminded in the last 24 hours. `retry_at` and the `Retry-After` header say when you can remind it again.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before you can remind this request again.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 3600 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "case_request_reminder_too_soon" + ] + }, + "retry_at": { + "type": "string", + "format": "date-time" + } + } + }, + "example": { + "detail": "This request was already reminded in the last 24 hours.", + "code": "case_request_reminder_too_soon", + "retry_at": "2026-10-15T10:30:00Z" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-didit-sdk": { + "resource": "cases.requests", + "method": "remind", + "auth": "api_key", + "server": true, + "scope": "application" + } + } + }, + "/v3/session/{sessionId}/decision-email-preview/": { + "post": { + "summary": "Preview a session's decision email", + "description": "Render the email the person receives when you set a session to `Approved` or `Declined` with `send_email: true`, without changing the session or sending anything. The response shows the subject and HTML exactly as they are sent, the recipient, the sender, the language, and whether confirming the change sends the email at all (`will_be_sent`, `not_sent_reason`).\n\nThe recipient is the person's own address: the one they verified in an email verification step of the flow, else the one given in `contact_details` when the session was created. The sender is your application's verified sender domain when white label applies (`sender_kind: customer`), else Didit's sender (`sender_kind: didit`).\n\nTo send exactly what you previewed, call [Update Session Status](/sessions-api/update-status) with the same `new_status`, `send_email: true`, `email_address` set to `recipient`, `email_language` set to `language` and `email_preview_token` set to `preview_token`. The receipt expires after 15 minutes. When the recipient, language, session status, sender or email changed since the preview, the update returns `409` before anything changes; preview again and confirm with the new receipt.\n\nWorks for User Verification (KYC) and Business Verification (KYB) sessions in `Approved`, `Declined`, `In Review`, `Kyc Expired`, `Abandoned` or `Resubmitted` status. The status check runs before body validation. Requires the `write:sessions` privilege; a scoped API key needs write access to decisions. Responses use `Cache-Control: no-store`: treat them as sensitive verification content.", + "operationId": "post_v3_session_decision_email_preview", + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "new_status" + ], + "properties": { + "new_status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "The decision whose email you want to preview.", + "example": "Approved" + }, + "email_language": { + "type": "string", + "default": "en", + "description": "Language of the email, the same default as Update Session Status. A language that is not offered falls back to English; `language` in the response says which one is used.", + "example": "en" + } + } + }, + "examples": { + "Approved": { + "summary": "Preview the Approved email in English", + "value": { + "new_status": "Approved" + } + }, + "Declined in Spanish": { + "summary": "Preview the Declined email in Spanish", + "value": { + "new_status": "Declined", + "email_language": "es" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Preview only. The session is unchanged and no email is sent.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: the preview carries verification content and must not be cached.", + "schema": { + "type": "string", + "example": "no-store" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "recipient", + "recipient_source", + "sender", + "sender_name", + "sender_email", + "sender_kind", + "subject", + "html", + "text", + "language", + "will_be_sent", + "not_sent_reason", + "preview_token" + ], + "properties": { + "recipient": { + "type": "string", + "description": "The address the email goes to. An empty string when no address is known for the person: then no email can be sent and `preview_token` is empty too. Pass it as `email_address` when you confirm.", + "example": "person@example.com" + }, + "recipient_source": { + "type": "string", + "enum": [ + "email_verification", + "contact_details", + "" + ], + "description": "Where `recipient` comes from. `email_verification`: the address the person verified in an email verification step of the flow. `contact_details`: the address given in `contact_details` when the session was created. An empty string when there is no address.", + "example": "email_verification" + }, + "sender": { + "type": "string", + "description": "The `From` header the email goes out with.", + "example": "Example App " + }, + "sender_name": { + "type": "string", + "description": "The display name part of `sender`.", + "example": "Example App" + }, + "sender_email": { + "type": "string", + "format": "email", + "description": "The address part of `sender`.", + "example": "no-reply@mail.example.com" + }, + "sender_kind": { + "type": "string", + "enum": [ + "customer", + "didit" + ], + "description": "`customer`: the email goes out from your application's verified sender domain (white label). `didit`: it goes out from Didit's own sender.", + "example": "customer" + }, + "subject": { + "type": "string", + "description": "The subject line exactly as it is sent.", + "example": "Your verification has been approved in Example App" + }, + "html": { + "type": "string", + "description": "The email's HTML exactly as it is sent, with its logo embedded as a data URL. Render it in a sandboxed frame, never directly in your application DOM.", + "example": "..." + }, + "text": { + "type": "string", + "description": "The email's words as plain text, as the person's Messages thread keeps them, for a compact preview (the HTML stays the email the person receives).", + "example": "Verification Approved\n\nYour identity has been successfully verified with Acme Pay. No further action is needed on your part." + }, + "language": { + "type": "string", + "description": "The language the email is written in. English (`en`) when the `email_language` you asked for is not offered.", + "example": "en" + }, + "will_be_sent": { + "type": "boolean", + "description": "Whether confirming the change with `send_email: true` sends this email. `false` when `not_sent_reason` is set.", + "example": true + }, + "not_sent_reason": { + "type": "string", + "nullable": true, + "enum": [ + "status_unchanged", + "notification_emails_disabled", + "no_recipient", + "sandbox", + null + ], + "description": "Why confirming would send no email, or `null` when it will be sent. `status_unchanged`: the session already has this status, so the update changes nothing and sends nothing. `notification_emails_disabled`: the session was created without `contact_details.send_notification_emails: true`, so no decision email goes to the address given in `contact_details`. `no_recipient`: no address is known for the person. `sandbox`: a sandbox session whose recipient has not opted into sandbox email delivery, so the send is only simulated.", + "example": null + }, + "preview_token": { + "type": "string", + "description": "Signed receipt for this exact email, valid for 15 minutes. Pass it as `email_preview_token` to [Update Session Status](/sessions-api/update-status). An empty string when there is no recipient.", + "example": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + } + }, + "examples": { + "Will Be Sent": { + "summary": "The Approved email goes to the address verified in the flow", + "value": { + "recipient": "person@example.com", + "recipient_source": "email_verification", + "sender": "Example App ", + "sender_name": "Example App", + "sender_email": "no-reply@mail.example.com", + "sender_kind": "customer", + "subject": "Your verification has been approved in Example App", + "html": "...", + "language": "en", + "will_be_sent": true, + "not_sent_reason": null, + "preview_token": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + }, + "No Recipient": { + "summary": "No address is known for the person", + "value": { + "recipient": "", + "recipient_source": "", + "sender": "Example App ", + "sender_name": "Example App", + "sender_email": "no-reply@mail.example.com", + "sender_kind": "customer", + "subject": "Your verification has been declined in Example App", + "html": "...", + "language": "en", + "will_be_sent": false, + "not_sent_reason": "no_recipient", + "preview_token": "" + } + } + } + } + } + }, + "400": { + "description": "The session is not in a status this endpoint accepts (`{\"detail\": ...}`), or the body is invalid (`{\"\": [\"\"]}`).", + "content": { + "application/json": { + "examples": { + "Wrong Current Status": { + "summary": "Session is not in a status this endpoint accepts", + "value": { + "detail": "Only sessions with status Approved, Declined, In Review, Kyc Expired, Abandoned, Resubmitted can be updated. Current status: In Progress" + } + }, + "Invalid new_status": { + "summary": "new_status is not Approved or Declined", + "value": { + "new_status": [ + "\"Resubmitted\" is not a valid choice." + ] + } + }, + "Missing new_status": { + "summary": "Required field omitted", + "value": { + "new_status": [ + "This field is required." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials, a caller without the `write:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no write access to decisions." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/decision-email-preview/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"new_status\": \"Approved\", \"email_language\": \"en\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.preview_decision_email(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n new_status=\"Approved\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.previewDecisionEmail(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", {\n new_status: \"Approved\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.PreviewDecisionEmail(ctx, \"3fa85f64-5717-4562-b3fc-2c963f66afa6\", didit.SessionsPreviewDecisionEmailParams{\n\tNewStatus: \"Approved\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsPreviewDecisionEmailParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().previewDecisionEmail(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", SessionsPreviewDecisionEmailParams.builder()\n .newStatus(SessionsPreviewDecisionEmailParams.NewStatus.of(\"Approved\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->previewDecisionEmail('3fa85f64-5717-4562-b3fc-2c963f66afa6', [\n 'new_status' => 'Approved',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.preview_decision_email(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n new_status: \"Approved\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.PreviewDecisionEmailAsync(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", new SessionsPreviewDecisionEmailParams\n{\n NewStatus = \"Approved\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "preview_decision_email", + "auth": "api_key", + "server": true + } + } + }, + "/v3/travel-rule/join/{token}/": { + "get": { + "summary": "Get a free-join invitation", + "description": "Public, unauthenticated. Shows the invitation a join link is for while it is open: who asked, the address it went to, and the directory values that will be prefilled. The token is the authentication.", + "operationId": "getTravelRuleJoinInvitation", + "x-didit-sdk": { + "resource": "travel_rule.join", + "method": "get_invitation", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The invitation token from the join link." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/join/{token}/" + } + ], + "responses": { + "200": { + "description": "The invitation.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleJoinInvitation" + } + } + } + }, + "404": { + "description": "No invitation has this link." + }, + "410": { + "description": "The invitation is no longer open: expired, already used, or withdrawn." + } + } + }, + "post": { + "summary": "Register a free Travel Rule account", + "description": "Public, unauthenticated. Creates a free Travel Rule account for a counterparty VASP that received a request. The email must be the address the invitation went to. A 6-character code is emailed to it; send it to the verify endpoint to finish. Registration attempts are rate limited per address and per IP.", + "operationId": "registerTravelRuleJoin", + "x-didit-sdk": { + "resource": "travel_rule.join", + "method": "register", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The invitation token from the join link." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleJoinRegisterRequest" + }, + "example": { + "email": "compliance@example.com", + "password": "a-long-passphrase" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/join/{token}/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"email\": \"compliance@example.com\", \"password\": \"a-long-passphrase\"}'" + } + ], + "responses": { + "202": { + "description": "The account was created and a code was emailed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Tells the counterparty to check their email for the verification code." + }, + "email": { + "type": "string", + "format": "email", + "description": "The address the code was sent to." + } + } + } + } + } + }, + "400": { + "description": "The email is not the invited address, or the password or address was refused." + }, + "404": { + "description": "No invitation has this link." + }, + "410": { + "description": "The invitation is no longer open: expired, already used, or withdrawn." + }, + "429": { + "description": "Too many attempts to join. The `X-RateLimit-*` headers name the budget that was spent." + } + } + } + }, + "/v3/travel-rule/join/{token}/verify/": { + "post": { + "summary": "Verify a free Travel Rule account", + "description": "Public, unauthenticated. Spends the emailed code, creates the free organization and answers with the credentials its application uses on the workspace endpoints. Receiving Travel Rule data is free.", + "operationId": "verifyTravelRuleJoin", + "x-didit-sdk": { + "resource": "travel_rule.join", + "method": "verify", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The invitation token from the join link." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleJoinVerifyRequest" + }, + "example": { + "email": "compliance@example.com", + "code": "123456" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/join/{token}/verify/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"email\": \"compliance@example.com\", \"code\": \"123456\"}'" + } + ], + "responses": { + "201": { + "description": "The organization and application credentials.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleJoinCredentials" + } + } + } + }, + "400": { + "description": "The code or email was refused." + }, + "404": { + "description": "No invitation has this link." + }, + "410": { + "description": "The invitation is no longer open: expired, already used, or withdrawn." + }, + "429": { + "description": "Too many attempts to join. The `X-RateLimit-*` headers name the budget that was spent." + } + } + } + }, + "/v3/travel-rule/workspace/": { + "get": { + "summary": "Get the free Travel Rule workspace", + "description": "Reads the free workspace of an application that joined free: directory claim, domain proof, billing and Didit network listing.", + "operationId": "getTravelRuleWorkspace", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "get", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/workspace/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.workspace.get()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.workspace.get();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.Workspace.Get(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().workspace().get();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->workspace->get();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.workspace.get\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Workspace.GetAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/claim/": { + "post": { + "summary": "Claim a directory entry", + "description": "Claims the directory entry this workspace is. When the work email is at a domain the entry lists, the claim is proved and applies at once: the VASP profile and Travel Rule settings are filled from the entry and Travel Rule is turned on, so the next transfer to it routes over the Didit network. Otherwise the claim waits for review (`pending_review`).", + "operationId": "claimTravelRuleWorkspace", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "claim", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspaceClaimRequest" + }, + "example": { + "vasp_id": "0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/claim/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vasp_id\": \"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.workspace.claim(\n vasp_id=\"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.workspace.claim({\n vasp_id: \"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.Workspace.Claim(ctx, didit.TravelRuleWorkspaceClaimRequest{\n\tVASPID: \"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleWorkspaceClaimRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().workspace().claim(TravelRuleWorkspaceClaimRequest.builder()\n .vaspId(\"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->workspace->claim([\n 'vasp_id' => '0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.workspace.claim(vasp_id: \"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Workspace.ClaimAsync(new TravelRuleWorkspaceClaimRequest\n{\n VaspId = \"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The workspace with its claim status.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "400": { + "description": "No directory entry has this id." + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/domain/code/": { + "post": { + "summary": "Email a domain verification code", + "description": "Emails a single-use code to the workspace's work address to prove it controls the email domain. Throttled, and a limited number of wrong attempts is accepted per code. Also returns the TXT record for the DNS alternative.", + "operationId": "sendTravelRuleWorkspaceDomainCode", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "send_domain_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/domain/code/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.workspace.send_domain_code()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.workspace.sendDomainCode();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.Workspace.SendDomainCode(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().workspace().sendDomainCode();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->workspace->sendDomainCode();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.workspace.send_domain_code\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Workspace.SendDomainCodeAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The domain, its status and the TXT record.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspaceDomainCodeResponse" + } + } + } + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/domain/confirm/": { + "post": { + "summary": "Confirm the domain verification code", + "description": "Spends the emailed code and marks the email domain verified.", + "operationId": "confirmTravelRuleWorkspaceDomain", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "confirm_domain", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspaceDomainConfirmRequest" + }, + "example": { + "code": "123456" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/domain/confirm/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"code\": \"123456\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.workspace.confirm_domain(\n code=\"123456\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.workspace.confirmDomain({\n code: \"123456\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.Workspace.ConfirmDomain(ctx, didit.TravelRuleWorkspaceDomainConfirmRequest{\n\tCode: \"123456\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleWorkspaceDomainConfirmRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().workspace().confirmDomain(TravelRuleWorkspaceDomainConfirmRequest.builder()\n .code(\"123456\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->workspace->confirmDomain([\n 'code' => '123456',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.workspace.confirm_domain(code: \"123456\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Workspace.ConfirmDomainAsync(new TravelRuleWorkspaceDomainConfirmRequest\n{\n Code = \"123456\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "400": { + "description": "The code is wrong, expired or already used." + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/domain/dns/": { + "post": { + "summary": "Verify the domain from its TXT record", + "description": "Checks for the `didit-tr-verify.` TXT record returned by the code endpoint and marks the domain verified when it matches. One of the two proofs, the emailed code or the TXT record, is enough.", + "operationId": "verifyTravelRuleWorkspaceDomainDns", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "verify_domain_dns", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/domain/dns/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.workspace.verify_domain_dns()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.workspace.verifyDomainDns();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.Workspace.VerifyDomainDNS(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().workspace().verifyDomainDns();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->workspace->verifyDomainDns();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.workspace.verify_domain_dns\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Workspace.VerifyDomainDnsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "400": { + "description": "The TXT record was not found or does not match." + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/billing/": { + "post": { + "summary": "Enable outbound billing", + "description": "Turns outbound Travel Rule billing on for the workspace. Receiving stays free. This cannot be turned back off through the API: contact Didit support.", + "operationId": "enableTravelRuleWorkspaceBilling", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "enable_billing", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspaceBillingRequest" + }, + "example": { + "enabled": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/billing/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"enabled\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.workspace.enable_billing(\n enabled=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.workspace.enableBilling({\n enabled: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.Workspace.EnableBilling(ctx, didit.TravelRuleWorkspaceBillingRequest{\n\tEnabled: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleWorkspaceBillingRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().workspace().enableBilling(TravelRuleWorkspaceBillingRequest.builder()\n .enabled(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->workspace->enableBilling([\n 'enabled' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.workspace.enable_billing(enabled: true)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Workspace.EnableBillingAsync(new TravelRuleWorkspaceBillingRequest\n{\n Enabled = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The workspace with `billing_enabled` true.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "400": { + "description": "`enabled` was false." + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/investigator/summary/": { + "get": { + "operationId": "investigator_summary", + "summary": "Investigator summary", + "description": "Stage, queue counts, reports prepared and decided today, 30-day agreement, prices and the autonomy level of each queue. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "stage": { + "type": "string" + }, + "last_report_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "prepared_today": { + "type": "integer" + }, + "ready_to_accept": { + "type": "integer" + }, + "open": { + "type": "integer" + }, + "decided_today": { + "type": "integer" + }, + "automation_rate_month": { + "type": "number", + "nullable": true + }, + "preparing": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "timezone", + "schema": { + "type": "string" + }, + "description": "IANA time zone (for example `Europe/Madrid`) that \"today\" counts from. UTC by default." + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_summary", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.retrieve_summary()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.retrieveSummary();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.RetrieveSummary(ctx, didit.InvestigatorRetrieveSummaryParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().retrieveSummary();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->retrieveSummary();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.retrieve_summary\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.RetrieveSummaryAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/queue/": { + "get": { + "operationId": "investigator_queue", + "summary": "List the Investigator queue", + "description": "Open reports, **Ready to accept** first, then by deadline, risk and wait. `counts` covers every kind in scope. `count` is capped at 100. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "counts": { + "type": "object", + "additionalProperties": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/InvestigatorQueueRow" + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "kind", + "schema": { + "type": "string" + }, + "description": "`session`, `business_session`, `transaction` or `case`, or several separated by commas." + }, + { + "in": "query", + "name": "queue", + "schema": { + "type": "string", + "enum": [ + "kyc", + "kyb", + "aml", + "transactions", + "cases" + ] + } + }, + { + "in": "query", + "name": "recommendation", + "schema": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ] + } + }, + { + "in": "query", + "name": "confidence", + "schema": { + "type": "string", + "enum": [ + "high", + "moderate", + "low" + ] + } + }, + { + "in": "query", + "name": "ready", + "schema": { + "type": "boolean" + } + }, + { + "in": "query", + "name": "search", + "schema": { + "type": "string" + }, + "description": "Name, company, case title, number, transaction ID or item ID." + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "list_queue", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.investigator.list_queue():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.investigator.listQueue()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Investigator.ListQueueAutoPaging(ctx, didit.InvestigatorListQueueParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.investigator().listQueue()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->listQueue() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.investigator.list_queue.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Investigator.ListQueueAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/investigator/runs/{run_id}/": { + "get": { + "operationId": "investigator_run_retrieve", + "summary": "Get an Investigator report", + "description": "The full report of one run. 404 when the run has no completed report, or its item was deleted. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorRunDetail" + } + } + } + }, + "404": { + "description": "Not found, or no report." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "path", + "name": "run_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_run", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.retrieve_run(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.retrieveRun(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.RetrieveRun(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().retrieveRun(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->retrieveRun('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.retrieve_run(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.RetrieveRunAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/runs/{run_id}/accept/": { + "post": { + "operationId": "investigator_run_accept", + "summary": "Accept a recommendation", + "description": "Applies the recommended action through the item's normal path, so your usual webhooks fire. A `narrative` makes it edited and accepted. Restricted keys need the `decisions` scope (write); transaction alerts also need `transactions` (write). Escalating a verification or transaction opens a case.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorDecision" + } + } + } + }, + "409": { + "description": "Already decided, report not ready, or a guardrail holds the item." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "path", + "name": "run_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorAcceptRequest" + }, + "example": { + "send_email": false + } + } + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "accept_run", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.accept_run(\n \"11111111-2222-3333-4444-555555555555\",\n send_email=False,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.acceptRun(\"11111111-2222-3333-4444-555555555555\", {\n send_email: false,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.AcceptRun(ctx, \"11111111-2222-3333-4444-555555555555\", didit.InvestigatorAcceptRequest{\n\tSendEmail: didit.Bool(false),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.InvestigatorAcceptRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().acceptRun(\"11111111-2222-3333-4444-555555555555\", InvestigatorAcceptRequest.builder()\n .sendEmail(false)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->acceptRun('11111111-2222-3333-4444-555555555555', [\n 'send_email' => false,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.accept_run(\n \"11111111-2222-3333-4444-555555555555\",\n send_email: false\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.AcceptRunAsync(\"11111111-2222-3333-4444-555555555555\", new InvestigatorAcceptRequest\n{\n SendEmail = false,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/runs/{run_id}/override/": { + "post": { + "operationId": "investigator_run_override", + "summary": "Override a recommendation", + "description": "Takes another action with a category and a reason. Also overrides a decision the Investigator took on its own while it still stands. Restricted keys need the `decisions` scope (write); transaction alerts also need `transactions` (write). Escalating a verification or transaction opens a case.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorDecision" + } + } + } + }, + "409": { + "description": "Already decided, or a guardrail holds the item." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "path", + "name": "run_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorOverrideRequest" + }, + "example": { + "action": "decline", + "category": "evidence_misread", + "reason": "The proof of address is older than three months." + } + } + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "override_run", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.override_run(\n \"11111111-2222-3333-4444-555555555555\",\n action=\"decline\",\n category=\"evidence_misread\",\n reason=\"The proof of address is older than three months.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.overrideRun(\"11111111-2222-3333-4444-555555555555\", {\n action: \"decline\",\n category: \"evidence_misread\",\n reason: \"The proof of address is older than three months.\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.OverrideRun(ctx, \"11111111-2222-3333-4444-555555555555\", didit.InvestigatorOverrideRequest{\n\tAction: \"decline\",\n\tCategory: \"evidence_misread\",\n\tReason: \"The proof of address is older than three months.\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.InvestigatorOverrideRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().overrideRun(\"11111111-2222-3333-4444-555555555555\", InvestigatorOverrideRequest.builder()\n .action(InvestigatorOverrideRequest.Action.of(\"decline\"))\n .category(InvestigatorOverrideRequest.Category.of(\"evidence_misread\"))\n .reason(\"The proof of address is older than three months.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->overrideRun('11111111-2222-3333-4444-555555555555', [\n 'action' => 'decline',\n 'category' => 'evidence_misread',\n 'reason' => 'The proof of address is older than three months.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.override_run(\n \"11111111-2222-3333-4444-555555555555\",\n action: \"decline\",\n category: \"evidence_misread\",\n reason: \"The proof of address is older than three months.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.OverrideRunAsync(\"11111111-2222-3333-4444-555555555555\", new InvestigatorOverrideRequest\n{\n Action = \"decline\",\n Category = \"evidence_misread\",\n Reason = \"The proof of address is older than three months.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/runs/batch-accept/": { + "post": { + "operationId": "investigator_batch_accept", + "summary": "Batch accept recommendations", + "description": "Accepts up to 50 reports marked Ready to accept. Each is its own decision: one that cannot be accepted is skipped with its reason. Restricted keys need the `decisions` scope (write); transaction alerts also need `transactions` (write). Escalating a verification or transaction opens a case.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorBatchAcceptResponse" + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorBatchAcceptRequest" + } + } + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "batch_accept_runs", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.batch_accept_runs(\n run_ids=[\"11111111-2222-3333-4444-555555555555\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.batchAcceptRuns({\n run_ids: [\"11111111-2222-3333-4444-555555555555\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.BatchAcceptRuns(ctx, didit.InvestigatorBatchAcceptRequest{\n\tRunIDs: []string{\"11111111-2222-3333-4444-555555555555\"},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.InvestigatorBatchAcceptRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().batchAcceptRuns(InvestigatorBatchAcceptRequest.builder()\n .runIds(List.of(\"11111111-2222-3333-4444-555555555555\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->batchAcceptRuns([\n 'run_ids' => ['11111111-2222-3333-4444-555555555555'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.batch_accept_runs(run_ids: [\"11111111-2222-3333-4444-555555555555\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.BatchAcceptRunsAsync(new InvestigatorBatchAcceptRequest\n{\n RunIds = new string[] { \"11111111-2222-3333-4444-555555555555\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/transactions/{transaction_id}/": { + "get": { + "operationId": "investigator_transaction_retrieve", + "summary": "Get a transaction's Investigator review", + "description": "The transaction's latest review, or `investigator: null` when it never had one. Restricted keys need the `decisions` and `transactions` scopes (read).", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "transaction_id": { + "type": "string", + "format": "uuid" + }, + "investigator": { + "$ref": "#/components/schemas/InvestigatorBlock" + } + } + } + } + } + }, + "404": { + "description": "Not this application's transaction." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "path", + "name": "transaction_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_transaction", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.retrieve_transaction(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.retrieveTransaction(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.RetrieveTransaction(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().retrieveTransaction(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->retrieveTransaction('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.retrieve_transaction(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.RetrieveTransactionAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/guardrails/": { + "get": { + "operationId": "investigator_guardrails", + "summary": "List the locked guardrails", + "description": "The five guardrails that apply to every queue. They cannot be switched off. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "enum": [ + "sanctions_to_person", + "no_tipping_off", + "no_protected_characteristics", + "never_files_reports", + "every_decision_logged" + ] + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "locked": { + "type": "boolean" + } + } + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "list_guardrails", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.list_guardrails()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.listGuardrails();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.ListGuardrails(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().listGuardrails();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->listGuardrails();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.list_guardrails\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.ListGuardrailsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/dashboard/": { + "get": { + "operationId": "investigator_dashboard", + "summary": "Investigator dashboard", + "description": "The last 30 days: reviews, decisions, automation rate, hours saved, spend, overrides, quality samples since go-live, per-queue and weekly figures. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "stage": { + "type": "string" + }, + "window_days": { + "type": "integer" + }, + "reviews": { + "type": "integer" + }, + "decided": { + "type": "integer" + }, + "automated": { + "type": "integer" + }, + "automation_rate": { + "type": "number", + "nullable": true + }, + "hours_saved": { + "type": "number", + "nullable": true + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_dashboard", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.retrieve_dashboard()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.retrieveDashboard();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.RetrieveDashboard(ctx, didit.InvestigatorRetrieveDashboardParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().retrieveDashboard();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->retrieveDashboard();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.retrieve_dashboard\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.RetrieveDashboardAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/performance/": { + "get": { + "operationId": "investigator_performance", + "summary": "Investigator performance", + "description": "Agreement with your team, volume, time saved, confidence calibration, per-queue results, override reasons and disagreements for a period. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "period": { + "type": "integer" + }, + "start": { + "type": "string", + "format": "date" + }, + "end": { + "type": "string", + "format": "date" + }, + "investigated": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "period", + "schema": { + "type": "string", + "enum": [ + "7", + "30", + "90" + ], + "default": "30" + }, + "description": "Days, ending today." + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_performance", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.retrieve_performance()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.retrievePerformance();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.RetrievePerformance(ctx, didit.InvestigatorRetrievePerformanceParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().retrievePerformance();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->retrievePerformance();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.retrieve_performance\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.RetrievePerformanceAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/activity/": { + "get": { + "operationId": "investigator_activity", + "summary": "List Investigator activity", + "description": "The append-only audit trail, newest first. A batch accept is one entry. Adds `counts` per group and `actors`. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "counts": { + "type": "object", + "additionalProperties": true + }, + "actors": { + "type": "array", + "items": {} + }, + "results": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true, + "description": "`id`, `at`, `kind`, `group`, `queue`, `run_id`, `item`, `actor`, `agent_version`, `data`, `report_version`, `steps`, `sources`, `batch`." + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "group", + "schema": { + "type": "string", + "enum": [ + "agent", + "decisions", + "overrides", + "autonomy" + ] + } + }, + { + "in": "query", + "name": "actor", + "schema": { + "type": "string" + }, + "description": "`agent` for the Investigator, or a person's email." + }, + { + "in": "query", + "name": "since", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "until", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "list_activity", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.investigator.list_activity():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.investigator.listActivity()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Investigator.ListActivityAutoPaging(ctx, didit.InvestigatorListActivityParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.investigator().listActivity()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->listActivity() as $item) {\n print_r($item);\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.investigator.list_activity.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Investigator.ListActivityAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/investigator/activity/export/": { + "get": { + "operationId": "investigator_activity_export", + "summary": "Export Investigator activity", + "description": "The same entries as CSV, one row per decision, newest first, up to 10,000 entries. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "text/csv": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "group", + "schema": { + "type": "string", + "enum": [ + "agent", + "decisions", + "overrides", + "autonomy" + ] + } + }, + { + "in": "query", + "name": "actor", + "schema": { + "type": "string" + }, + "description": "`agent` for the Investigator, or a person's email." + }, + { + "in": "query", + "name": "since", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "until", + "schema": { + "type": "string", + "format": "date-time" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "export_activity", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.investigator.export_activity()\nwith open(\"download.csv\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.investigator.exportActivity();\nawait writeFile(\"download.csv\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Investigator.ExportActivity(ctx, didit.InvestigatorExportActivityParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.csv\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.investigator().exportActivity()\n .writeTo(Path.of(\"download.csv\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->exportActivity();\n$content->saveTo('download.csv');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.investigator.export_activity\nFile.binwrite(\"download.csv\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Investigator.ExportActivityAsync();\nawait content.SaveToAsync(\"download.csv\");" + } + ] + } + }, + "/v3/investigator/settings/": { + "get": { + "operationId": "investigator_settings_retrieve", + "summary": "Get Investigator settings", + "description": "The stage (`none`, `shadow` or `live`) and the monthly spend limit with this month's spend. Restricted keys need the `applications` scope (read).", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorSettings" + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_settings", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.retrieve_settings()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.retrieveSettings();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.RetrieveSettings(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().retrieveSettings();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->retrieveSettings();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.retrieve_settings\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.RetrieveSettingsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "patch": { + "operationId": "investigator_settings_update", + "summary": "Update Investigator settings", + "description": "Change the stage, the monthly spend limit, or both. Moving past `none` needs a plan that includes the Investigator (Growth or higher); `live` also needs a contract that prices it, if you have one. Restricted keys need the `applications` scope (write).", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorSettings" + } + } + } + }, + "400": { + "description": "Neither field sent, or an invalid value." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorSettings" + }, + "example": { + "stage": "shadow", + "monthly_spend_limit": "500.00" + } + } + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "update_settings", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.update_settings(\n stage=\"shadow\",\n monthly_spend_limit=\"500.00\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.updateSettings({\n stage: \"shadow\",\n monthly_spend_limit: \"500.00\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.UpdateSettings(ctx, didit.InvestigatorSettingsParam{\n\tStage: didit.Some(didit.InvestigatorSettingsStage(\"shadow\")),\n\tMonthlySpendLimit: didit.String(\"500.00\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.InvestigatorSettings;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().updateSettings(InvestigatorSettings.builder()\n .stage(InvestigatorSettings.Stage.of(\"shadow\"))\n .monthlySpendLimit(\"500.00\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->updateSettings([\n 'stage' => 'shadow',\n 'monthly_spend_limit' => '500.00',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.update_settings(\n stage: \"shadow\",\n monthly_spend_limit: \"500.00\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.UpdateSettingsAsync(new InvestigatorSettings\n{\n Stage = new InvestigatorSettingsStage(\"shadow\"),\n MonthlySpendLimit = \"500.00\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/travel-rule/wallet-addresses/import/": { + "post": { + "summary": "Import wallet addresses in bulk", + "description": "Add up to 10,000 wallets to your Travel Rule address book in one call, as JSON or as a CSV file. A wallet can be sent by its address, by the SHA-256 of its lowercased address (`address_hash`) to keep the address private, or both. The import is idempotent: a wallet already in the book is reported as `existing` and left untouched, so the same file can be sent again. Each row is validated on its own, so one bad row never fails the others. A wallet imported by its hash only matches transfers to that address and can be proven with the wallet-ownership widget like any other entry.", + "operationId": "importTravelRuleWalletAddresses", + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "bulk_import", + "auth": "api_key", + "server": true + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "required": [ + "entries" + ], + "properties": { + "entries": { + "type": "array", + "minItems": 1, + "maxItems": 10000, + "items": { + "$ref": "#/components/schemas/WalletAddressImportRow" + } + } + } + }, + { + "type": "array", + "minItems": 1, + "maxItems": 10000, + "items": { + "$ref": "#/components/schemas/WalletAddressImportRow" + } + } + ] + }, + "example": { + "entries": [ + { + "address": "0xBeneficiaryWallet01", + "chain": "ETH", + "holder_name": "Ana Diaz", + "holder_vendor_data": "user-042" + }, + { + "address_hash": "4f7d2e1c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e", + "chain": "bitcoin", + "holder_vendor_data": "user-043" + }, + { + "address": "rExchangeSharedAddress01", + "chain": "XRP", + "memo": "104729", + "holder_name": "Ana Diaz" + } + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "file" + ], + "properties": { + "file": { + "type": "string", + "format": "binary", + "description": "A UTF-8 CSV file of at most 5 MB with a header row naming its columns (the row fields or their aliases). Empty cells count as not sent; columns the import does not read are ignored." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl (JSON)", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/wallet-addresses/import/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"entries\": [{\"address\": \"0xBeneficiaryWallet01\", \"chain\": \"ETH\", \"holder_name\": \"Ana Diaz\"}, {\"address\": \"rExchangeSharedAddress01\", \"chain\": \"XRP\", \"memo\": \"104729\"}]}'" + }, + { + "lang": "curl", + "label": "curl (CSV)", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/wallet-addresses/import/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'file=@wallets.csv;type=text/csv'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.wallet_addresses.bulk_import(\n body={\n \"entries\": [{\n \"address\": \"0xBeneficiaryWallet01\",\n \"chain\": \"ETH\",\n \"holder_name\": \"Ana Diaz\",\n \"holder_vendor_data\": \"user-042\",\n }, {\n \"address_hash\": \"4f7d2e1c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e\",\n \"chain\": \"bitcoin\",\n \"holder_vendor_data\": \"user-043\",\n }, {\n \"address\": \"rExchangeSharedAddress01\",\n \"chain\": \"XRP\",\n \"memo\": \"104729\",\n \"holder_name\": \"Ana Diaz\",\n }],\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.walletAddresses.bulkImport({\n file: await fileFromPath(\"wallet-addresses.csv\"),\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.WalletAddresses.BulkImport(ctx, didit.TravelRuleWalletAddressesBulkImportParams{\n\tFile: didit.Some(didit.FileFromPath(\"wallet-addresses.csv\")),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.types.TravelRuleWalletAddressesBulkImportParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().walletAddresses().bulkImport(TravelRuleWalletAddressesBulkImportParams.builder()\n .file(Path.of(\"wallet-addresses.csv\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletAddresses->bulkImport([\n 'file' => File::fromPath('wallet-addresses.csv'),\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.wallet_addresses.bulk_import(\n body: {\n entries: [{\n address: \"0xBeneficiaryWallet01\",\n chain: \"ETH\",\n holder_name: \"Ana Diaz\",\n holder_vendor_data: \"user-042\",\n }, {\n address_hash: \"4f7d2e1c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e\",\n chain: \"bitcoin\",\n holder_vendor_data: \"user-043\",\n }, {\n address: \"rExchangeSharedAddress01\",\n chain: \"XRP\",\n memo: \"104729\",\n holder_name: \"Ana Diaz\",\n }],\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.WalletAddresses.BulkImportAsync(new TravelRuleWalletAddressesBulkImportParams\n{\n File = FileUpload.FromPath(\"wallet-addresses.csv\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Every row was created, already in the book, or a repeat of an earlier row.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressImportReport" + }, + "example": { + "total": 3, + "created": 2, + "existing": 1, + "duplicate": 0, + "failed": 0, + "rows": [ + { + "row": 1, + "status": "existing", + "entry_id": "11111111-2222-3333-4444-555555555555", + "errors": {}, + "duplicate_of_row": null + }, + { + "row": 2, + "status": "created", + "entry_id": "22222222-3333-4444-5555-666666666666", + "errors": {}, + "duplicate_of_row": null + }, + { + "row": 3, + "status": "created", + "entry_id": "33333333-4444-5555-6666-777777777777", + "errors": {}, + "duplicate_of_row": null + } + ] + } + } + } + }, + "207": { + "description": "At least one row failed; the others were imported. Each failed row names its errors.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressImportReport" + }, + "example": { + "total": 2, + "created": 1, + "existing": 0, + "duplicate": 0, + "failed": 1, + "rows": [ + { + "row": 1, + "status": "created", + "entry_id": "44444444-5555-6666-7777-888888888888", + "errors": {}, + "duplicate_of_row": null + }, + { + "row": 2, + "status": "failed", + "entry_id": null, + "errors": { + "address": [ + "Send address, address_hash or both." + ] + }, + "duplicate_of_row": null + } + ] + } + } + } + }, + "400": { + "description": "The request itself cannot be read: both `entries` and a `file`, or neither; more than 10,000 rows; a file over 5 MB, not UTF-8 CSV, without a header row or without rows.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "file": [ + "At most 10000 rows per import." + ] + } + } + } + } + } + } + }, + "/v3/travel-rule/counterparty-contacts/": { + "get": { + "summary": "List counterparty contacts", + "description": "The compliance addresses the EMAIL rail may write to for your application: Didit's platform contacts plus your application's own private ones, bounced ones included, newest first. Paginated with `limit` (default 50) and `offset`; `count` stops at 100, so 100 means 100 or more.", + "operationId": "listTravelRuleCounterpartyContacts", + "x-didit-sdk": { + "resource": "travel_rule.counterparty_contacts", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "counterparty", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Only the contacts of this directory entry. Must be a UUID; an empty value means no filter." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1 + }, + "description": "Page size, default 50." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + }, + "description": "Rows to skip." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/travel-rule/counterparty-contacts/?counterparty={vasp_id}' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.travel_rule.counterparty_contacts.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.travelRule.counterpartyContacts.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.TravelRule.CounterpartyContacts.ListAutoPaging(ctx, didit.TravelRuleCounterpartyContactsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.travelRule().counterpartyContacts().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->counterpartyContacts->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.counterparty_contacts.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.TravelRule.CounterpartyContacts.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "The contacts.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleCounterpartyContact" + } + } + } + }, + "example": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "6c1f9b8e-2d4a-4f7e-8b3c-5a9d0e1f2a3b", + "counterparty": "0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91", + "counterparty_name": "Example Counterparty VASP", + "email": "compliance@counterparty.example", + "name": "Compliance desk", + "source": "application", + "scope": "application", + "created_by": "", + "verified_at": null, + "is_invalid": false, + "invalid_reason": "", + "invalid_detail": "", + "invalid_at": null, + "created_at": "2026-10-19T09:00:00Z" + } + ] + } + } + } + }, + "400": { + "description": "`counterparty` is not a UUID.", + "content": { + "application/json": { + "example": { + "counterparty": [ + "Must be a valid UUID." + ] + } + } + } + } + } + }, + "post": { + "summary": "Add a counterparty contact", + "description": "Adds a private compliance address for one directory entry. Only your application sees and uses it, and the EMAIL rail writes to it before any platform contact. Adding an address the entry already has among your own contacts returns that contact.", + "operationId": "createTravelRuleCounterpartyContact", + "x-didit-sdk": { + "resource": "travel_rule.counterparty_contacts", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyContactCreate" + }, + "example": { + "counterparty": "0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91", + "email": "compliance@counterparty.example", + "name": "Compliance desk" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/counterparty-contacts/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"counterparty\": \"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\", \"email\": \"compliance@counterparty.example\", \"name\": \"Compliance desk\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.counterparty_contacts.create(\n counterparty=\"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\",\n email=\"compliance@counterparty.example\",\n name=\"Compliance desk\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.counterpartyContacts.create({\n counterparty: \"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\",\n email: \"compliance@counterparty.example\",\n name: \"Compliance desk\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.CounterpartyContacts.Create(ctx, didit.TravelRuleCounterpartyContactCreate{\n\tCounterparty: \"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\",\n\tEmail: \"compliance@counterparty.example\",\n\tName: didit.String(\"Compliance desk\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleCounterpartyContactCreate;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().counterpartyContacts().create(TravelRuleCounterpartyContactCreate.builder()\n .counterparty(\"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\")\n .email(\"compliance@counterparty.example\")\n .name(\"Compliance desk\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->counterpartyContacts->create([\n 'counterparty' => '0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91',\n 'email' => 'compliance@counterparty.example',\n 'name' => 'Compliance desk',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.counterparty_contacts.create(\n counterparty: \"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\",\n email: \"compliance@counterparty.example\",\n name: \"Compliance desk\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.CounterpartyContacts.CreateAsync(new TravelRuleCounterpartyContactCreate\n{\n Counterparty = \"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\",\n Email = \"compliance@counterparty.example\",\n Name = \"Compliance desk\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The contact was added.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyContact" + }, + "example": { + "uuid": "6c1f9b8e-2d4a-4f7e-8b3c-5a9d0e1f2a3b", + "counterparty": "0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91", + "counterparty_name": "Example Counterparty VASP", + "email": "compliance@counterparty.example", + "name": "Compliance desk", + "source": "application", + "scope": "application", + "created_by": "", + "verified_at": null, + "is_invalid": false, + "invalid_reason": "", + "invalid_detail": "", + "invalid_at": null, + "created_at": "2026-10-19T09:00:00Z" + } + } + } + }, + "400": { + "description": "A field is missing or invalid, or no directory entry has this id.", + "content": { + "application/json": { + "example": { + "counterparty": [ + "No directory entry with this id." + ] + } + } + } + } + } + } + }, + "/v3/travel-rule/counterparty-contacts/{contact_uuid}/": { + "delete": { + "summary": "Delete a counterparty contact", + "description": "Deletes one of your application's own contacts. The EMAIL rail stops writing to it at once. Platform contacts cannot be deleted.", + "operationId": "deleteTravelRuleCounterpartyContact", + "x-didit-sdk": { + "resource": "travel_rule.counterparty_contacts", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "contact_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE https://verification.didit.me/v3/travel-rule/counterparty-contacts/{contact_uuid}/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.travel_rule.counterparty_contacts.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.travelRule.counterpartyContacts.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.TravelRule.CounterpartyContacts.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.travelRule().counterpartyContacts().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->counterpartyContacts->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.counterparty_contacts.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.TravelRule.CounterpartyContacts.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ], + "responses": { + "204": { + "description": "Deleted." + }, + "404": { + "description": "No contact of your application has this id. A platform contact answers 404 too." + } + } + } + }, + "/v3/travel-rule/transfers/": { + "get": { + "summary": "List Travel Rule transfers", + "description": "Your application's Travel Rule transfers, newest first, paginated with `limit` (default 50) and `offset`. Rows carry VASP names, wallets and memos only, never person data. Comma-separated values select several. An unknown value answers 400 naming the parameter, for example `{\"direction\": [\"Unknown value(s): inbound.\"]}`.", + "operationId": "listTravelRuleTransfers", + "x-didit-sdk": { + "resource": "travel_rule.transfers", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "direction", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "INBOUND, OUTBOUND, or both comma-separated. Case-sensitive." + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "One or more transfer statuses, comma-separated." + }, + { + "name": "rail", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "One or more of INTERNAL, TRP, GTR, TRUST, VERIFYVASP, SYGNA, EMAIL, TEST_VASP, comma-separated." + }, + { + "name": "kind", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "withdrawal, deposit or message, comma-separated." + }, + { + "name": "currency", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "The transaction's currency, case-insensitive." + }, + { + "name": "start_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date" + }, + "description": "Transfers created on or after this day (UTC), YYYY-MM-DD." + }, + { + "name": "end_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date" + }, + "description": "Transfers created on or before this day (UTC), YYYY-MM-DD." + }, + { + "name": "counterparty", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The `id` of a directory entry or Didit network member from GET /v3/travel-rule/vasps/: your transfers with that counterparty. A value that is not a valid id answers 400." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1 + }, + "description": "Page size, default 50." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + }, + "description": "Rows to skip." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/travel-rule/transfers/?direction=OUTBOUND&status=AWAITING_COUNTERPARTY' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.travel_rule.transfers.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.travelRule.transfers.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.TravelRule.Transfers.ListAutoPaging(ctx, didit.TravelRuleTransfersListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.travelRule().transfers().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->transfers->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.transfers.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.TravelRule.Transfers.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "A page of transfers.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleTransferRow" + } + } + } + }, + "example": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "id": "5b0f6c2e-2a4d-4f0e-9a41-0c3f9a7e1d22", + "transaction_id": "a3c1e8f4-7b62-4d1a-8e0b-2f9d6c4b7a10", + "txn_id": "wd-2026-10-19-0042", + "txn_date": "2026-10-19T08:41:12Z", + "kind": "withdrawal", + "direction": "OUTBOUND", + "status": "AWAITING_COUNTERPARTY", + "rail": "TRP", + "asset": "ETH", + "amount": "1.25", + "originator_vasp": "Origin CASP SL", + "beneficiary_vasp": "Example Counterparty VASP", + "originator_wallet": "0x77ccff0cc624a53cd8b8d8703ced826710211ec7", + "beneficiary_wallet": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb", + "originator_memo": "", + "beneficiary_memo": "", + "originator_wallet_type": "vasp", + "beneficiary_wallet_type": "vasp", + "matched_transaction_id": null, + "deadline_at": "2026-10-21T08:41:13Z", + "created_at": "2026-10-19T08:41:13Z" + } + ] + } + } + } + }, + "400": { + "description": "A filter value is not valid.", + "content": { + "application/json": { + "example": { + "start_date": [ + "Use the YYYY-MM-DD format." + ] + } + } + } + } + } + } + }, + "/v3/session/imports/providers/": { + "get": { + "summary": "List migration providers", + "description": "The 15 providers you can migrate verifications from: what each needs to connect, whether its API can list records, the permissions its key needs and its default status mapping.", + "operationId": "get_v3_session_imports_providers_list", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "list_providers", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "200": { + "description": "Migration providers.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "providers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImportProvider" + } + }, + "mapping_targets": { + "type": "array", + "items": { + "type": "string" + } + }, + "skipped_statuses": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.list_providers()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.listProviders();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.ListProviders(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().listProviders();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->listProviders();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.list_providers\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.ListProvidersAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/providers/check/": { + "post": { + "summary": "Check a provider key", + "description": "Read the provider with your key before creating a migration. Nothing is stored.", + "operationId": "post_v3_session_imports_providers_check", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "check_provider", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "provider": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ], + "description": "Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`." + }, + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "options": { + "type": "object", + "properties": { + "region": { + "type": "string", + "description": "The provider region, one of its `regions` (Onfido: `eu`, `us`, `ca`)." + } + } + }, + "record_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "For `api_ids` providers: the first record ID of your export." + } + }, + "required": [ + "provider", + "credentials" + ] + }, + "example": { + "provider": "sumsub", + "credentials": { + "app_token": "YOUR_PROVIDER_APP_TOKEN", + "secret_key": "YOUR_PROVIDER_SECRET_KEY" + } + } + } + } + }, + "responses": { + "200": { + "description": "The key can read records.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean" + }, + "provider": { + "type": "string" + }, + "credential_hint": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "400": { + "description": "The provider refused the key: `code`, `detail`, `missing_permissions`, `suggested_region`." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.check_provider(\n provider=\"sumsub\",\n credentials={\"app_token\": \"YOUR_PROVIDER_APP_TOKEN\", \"secret_key\": \"YOUR_PROVIDER_SECRET_KEY\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.checkProvider({\n provider: \"sumsub\",\n credentials: { app_token: \"YOUR_PROVIDER_APP_TOKEN\", secret_key: \"YOUR_PROVIDER_SECRET_KEY\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.CheckProvider(ctx, didit.SessionsImportsCheckProviderParams{\n\tProvider: \"sumsub\",\n\tCredentials: map[string]string{\"app_token\": \"YOUR_PROVIDER_APP_TOKEN\", \"secret_key\": \"YOUR_PROVIDER_SECRET_KEY\"},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SessionsImportsCheckProviderParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().checkProvider(SessionsImportsCheckProviderParams.builder()\n .provider(SessionsImportsCheckProviderParams.Provider.of(\"sumsub\"))\n .credentials(Map.of(\"app_token\", \"YOUR_PROVIDER_APP_TOKEN\", \"secret_key\", \"YOUR_PROVIDER_SECRET_KEY\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->checkProvider([\n 'provider' => 'sumsub',\n 'credentials' => ['app_token' => 'YOUR_PROVIDER_APP_TOKEN', 'secret_key' => 'YOUR_PROVIDER_SECRET_KEY'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.check_provider(\n provider: \"sumsub\",\n credentials: { app_token: \"YOUR_PROVIDER_APP_TOKEN\", secret_key: \"YOUR_PROVIDER_SECRET_KEY\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.CheckProviderAsync(new SessionsImportsCheckProviderParams\n{\n Provider = \"sumsub\",\n Credentials = new Dictionary { [\"app_token\"] = \"YOUR_PROVIDER_APP_TOKEN\", [\"secret_key\"] = \"YOUR_PROVIDER_SECRET_KEY\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/{importId}/start/": { + "post": { + "summary": "Start a dry run", + "description": "Start a migration whose dry run finished (`READY`): the same job imports for real, with the status mapping and face templates choice as sent now.", + "operationId": "post_v3_session_imports_start", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "start", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + }, + "description": "Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported." + }, + "face_templates": { + "type": "boolean", + "default": false, + "description": "Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`." + }, + "lawful_basis_confirmed": { + "type": "boolean", + "default": false, + "description": "Confirms you have a lawful basis to share these selfies with Didit as processor." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.start(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.start(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Start(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsImportsStartParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().start(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->start('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.start(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.StartAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/{importId}/pause/": { + "post": { + "summary": "Pause an import job", + "description": "Pause a queued or running import.", + "operationId": "post_v3_session_imports_pause", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "pause", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.pause(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.pause(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Pause(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().pause(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->pause('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.pause(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.PauseAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/{importId}/resume/": { + "post": { + "summary": "Resume an import job", + "description": "Resume an import you paused. A job paused by the provider's rate limit resumes by itself.", + "operationId": "post_v3_session_imports_resume", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "resume", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.resume(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.resume(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Resume(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().resume(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->resume('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.resume(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.ResumeAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/{importId}/cancel/": { + "post": { + "summary": "Cancel an import job", + "description": "Cancel an import. What was imported stays; the uploaded file and the key are erased.", + "operationId": "post_v3_session_imports_cancel", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "cancel", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.cancel(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.cancel(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Cancel(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().cancel(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->cancel('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.cancel(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.CancelAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/{importId}/retry/": { + "post": { + "summary": "Retry a migration", + "description": "Run a migration that ended again as a new job, with a new key (the old one was erased). It keeps the provider, workflow, region, status mapping and face templates choice; everything already in Didit is skipped.", + "operationId": "post_v3_session_imports_retry", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "retry", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": false, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "file": { + "type": "string", + "format": "binary", + "description": "The record IDs or export file again, for `api_ids` and `export_file` providers." + } + } + }, + "example": { + "credentials": { + "app_token": "YOUR_PROVIDER_APP_TOKEN", + "secret_key": "YOUR_PROVIDER_SECRET_KEY" + } + } + } + } + }, + "responses": { + "201": { + "description": "The new import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "Not a migration, not ended, or the provider refused the key." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.retry(\n \"11111111-2222-3333-4444-555555555555\",\n credentials={\"app_token\": \"YOUR_PROVIDER_APP_TOKEN\", \"secret_key\": \"YOUR_PROVIDER_SECRET_KEY\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.retry(\"11111111-2222-3333-4444-555555555555\", {\n credentials: { app_token: \"YOUR_PROVIDER_APP_TOKEN\", secret_key: \"YOUR_PROVIDER_SECRET_KEY\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Retry(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsImportsRetryParams{\n\tCredentials: didit.Some(map[string]string{\"app_token\": \"YOUR_PROVIDER_APP_TOKEN\", \"secret_key\": \"YOUR_PROVIDER_SECRET_KEY\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SessionsImportsRetryParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().retry(\"11111111-2222-3333-4444-555555555555\", SessionsImportsRetryParams.builder()\n .credentials(Map.of(\"app_token\", \"YOUR_PROVIDER_APP_TOKEN\", \"secret_key\", \"YOUR_PROVIDER_SECRET_KEY\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->retry('11111111-2222-3333-4444-555555555555', [\n 'credentials' => ['app_token' => 'YOUR_PROVIDER_APP_TOKEN', 'secret_key' => 'YOUR_PROVIDER_SECRET_KEY'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.retry(\n \"11111111-2222-3333-4444-555555555555\",\n credentials: { app_token: \"YOUR_PROVIDER_APP_TOKEN\", secret_key: \"YOUR_PROVIDER_SECRET_KEY\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.RetryAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsImportsRetryParams\n{\n Credentials = new Dictionary { [\"app_token\"] = \"YOUR_PROVIDER_APP_TOKEN\", [\"secret_key\"] = \"YOUR_PROVIDER_SECRET_KEY\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/import-jobs/": { + "get": { + "summary": "List import jobs", + "description": "Every import of the application in one shape: verification imports and migrations, people and businesses.", + "operationId": "get_v3_import_jobs_list", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "list_jobs", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "scope", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "all", + "sessions", + "users", + "businesses" + ], + "default": "all" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + } + } + ], + "responses": { + "200": { + "description": "Import jobs, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImportJobSummary" + } + }, + "counts": { + "type": "object", + "properties": { + "all": { + "type": "integer" + }, + "running_and_waiting": { + "type": "integer" + }, + "needs_attention": { + "type": "integer" + }, + "done": { + "type": "integer" + } + } + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.list_jobs()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.listJobs();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.ListJobs(ctx, didit.SessionsImportsListJobsParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().listJobs();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->listJobs();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.list_jobs\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.ListJobsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/import-jobs/active/": { + "get": { + "summary": "List active import jobs", + "description": "What is running or waiting, plus what just ended, for a progress indicator.", + "operationId": "get_v3_import_jobs_active", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "list_active_jobs", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "200": { + "description": "Active import jobs.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImportJobSummary" + } + }, + "running": { + "type": "integer" + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.list_active_jobs()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.listActiveJobs();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.ListActiveJobs(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().listActiveJobs();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->listActiveJobs();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.list_active_jobs\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.ListActiveJobsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/transactions/delete/": { + "delete": { + "summary": "Batch delete transactions", + "description": "Delete one or more transactions permanently. Select them with `transaction_uuids`, or delete every transaction in the application with `delete_all: true`.\n\nA deleted transaction goes with the records that are only its own: its parties, tags, notes, payment method links, documents and every stored version of their files, rule runs, alerts, provider results, network snapshot and Travel Rule data. Cases, people and companies stay; their link to the deleted transaction is cleared. Draft regulatory reports stop listing it and are rebuilt without it. Deletion is permanent and irreversible; there is no undo endpoint.\n\nIf any selected transaction is listed in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each transaction and the reports that list it. Delete those reports, then repeat the request.", + "operationId": "batch_delete_transactions", + "x-didit-sdk": { + "resource": "transactions", + "method": "batch_delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "oneOf": [ + { + "title": "Selected transactions", + "required": [ + "transaction_uuids" + ], + "properties": { + "transaction_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit transaction UUIDs to delete. Required unless `delete_all` is true.", + "minItems": 1 + }, + "delete_all": { + "type": "boolean", + "enum": [ + false + ], + "default": false, + "description": "Leave out or send `false` when you select transactions with `transaction_uuids`." + } + } + }, + { + "title": "All transactions", + "required": [ + "delete_all" + ], + "properties": { + "delete_all": { + "type": "boolean", + "enum": [ + true + ], + "description": "Delete every transaction in the application. `transaction_uuids` is ignored." + }, + "transaction_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Ignored when `delete_all` is true." + } + } + } + ] + }, + "examples": { + "Selected transactions": { + "value": { + "transaction_uuids": [ + "abcdef12-3456-7890-abcd-ef1234567890", + "12345678-90ab-cdef-1234-567890abcdef" + ] + } + }, + "All transactions": { + "value": { + "delete_all": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl (selected transactions)", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/transactions/delete/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"transaction_uuids\": [\"abcdef12-3456-7890-abcd-ef1234567890\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.transactions.batch_delete(\n body={\n \"transaction_uuids\": [\"abcdef12-3456-7890-abcd-ef1234567890\", \"12345678-90ab-cdef-1234-567890abcdef\"],\n },\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.transactions.batchDelete({\n transaction_uuids: [\"abcdef12-3456-7890-abcd-ef1234567890\", \"12345678-90ab-cdef-1234-567890abcdef\"],\n});" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.TransactionsBatchDeleteParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.transactions().batchDelete(TransactionsBatchDeleteParams.builder()\n .putAdditionalProperty(\"transaction_uuids\", List.of(\"abcdef12-3456-7890-abcd-ef1234567890\", \"12345678-90ab-cdef-1234-567890abcdef\"))\n .build());" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->batchDelete([\n 'transaction_uuids' => ['abcdef12-3456-7890-abcd-ef1234567890', '12345678-90ab-cdef-1234-567890abcdef'],\n]);" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.transactions.batch_delete(\n body: {\n transaction_uuids: [\"abcdef12-3456-7890-abcd-ef1234567890\", \"12345678-90ab-cdef-1234-567890abcdef\"],\n }\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Transactions.BatchDeleteAsync(new TransactionsBatchDeleteParams\n{\n TransactionUuids = new string[] { \"abcdef12-3456-7890-abcd-ef1234567890\", \"12345678-90ab-cdef-1234-567890abcdef\" },\n});" + } + ], + "responses": { + "204": { + "description": "Selected transactions deleted." + }, + "400": { + "description": "No transaction selector was supplied.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "transaction_uuids": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "example": { + "transaction_uuids": [ + "This field is required unless delete_all is true." + ] + } + } + } + }, + "403": { + "description": "The API key cannot delete transactions. The endpoint requires `write:transactions`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "409": { + "description": "At least one selected transaction is listed in a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/travel-rule/networks/": { + "get": { + "summary": "Get Travel Rule networks", + "description": "Read, for this application, every network's true state: whether it sends through your own membership or through Didit's, and why it cannot yet. Also returns the due-diligence questionnaire and your participants. Reading creates nothing.", + "operationId": "getTravelRuleNetworks", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/networks/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.networks.retrieve()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.networks.retrieve();\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().networks().retrieve();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->networks->retrieve();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.networks.retrieve\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Networks.RetrieveAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Network states, questionnaire and participants.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworksOverview" + }, + "examples": { + "Before the questionnaire": { + "value": { + "platform_networks_enabled": false, + "questionnaire": { + "status": "draft", + "answers": {}, + "submitted_at": null, + "reviewed_at": null, + "review_note": "", + "history": [], + "updated_at": null + }, + "participants": [], + "networks": { + "gtr": { + "rail": "GTR", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "trust": { + "rail": "TRUST", + "mode": "unavailable", + "state": "unavailable", + "reason": "own_membership_only", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "verifyvasp": { + "rail": "VERIFYVASP", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "sygna": { + "rail": "SYGNA", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + } + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + } + } + } + }, + "/v3/travel-rule/networks/questionnaire/": { + "get": { + "summary": "Get the network questionnaire", + "description": "Read Didit's network due-diligence questionnaire for this application, its status and Didit's review note. An application that never saved one reads an empty draft.", + "operationId": "getTravelRuleNetworkQuestionnaire", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "retrieve_questionnaire", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/networks/questionnaire/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.networks.retrieve_questionnaire()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.networks.retrieveQuestionnaire();\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().networks().retrieveQuestionnaire();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->networks->retrieveQuestionnaire();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.networks.retrieve_questionnaire\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Networks.RetrieveQuestionnaireAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The questionnaire.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaire" + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + } + } + }, + "put": { + "summary": "Save network questionnaire answers", + "description": "Save answers to the questionnaire while it is draft or changes_requested. Saves are partial: answers you do not send are kept.", + "operationId": "updateTravelRuleNetworkQuestionnaire", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "update_questionnaire", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PUT https://verification.didit.me/v3/travel-rule/networks/questionnaire/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"legal_name\": \"Example VASP Ltd\", \"incorporation_country\": \"ES\", \"aml_ctf_policy_attested\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.networks.update_questionnaire(\n legal_name=\"Example VASP Ltd\",\n incorporation_country=\"ES\",\n aml_ctf_policy_attested=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.networks.updateQuestionnaire({\n legal_name: \"Example VASP Ltd\",\n incorporation_country: \"ES\",\n aml_ctf_policy_attested: true,\n});\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleNetworkQuestionnaireAnswers;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().networks().updateQuestionnaire(TravelRuleNetworkQuestionnaireAnswers.builder()\n .legalName(\"Example VASP Ltd\")\n .incorporationCountry(\"ES\")\n .amlCtfPolicyAttested(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->networks->updateQuestionnaire([\n 'legal_name' => 'Example VASP Ltd',\n 'incorporation_country' => 'ES',\n 'aml_ctf_policy_attested' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.networks.update_questionnaire(\n legal_name: \"Example VASP Ltd\",\n incorporation_country: \"ES\",\n aml_ctf_policy_attested: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Networks.UpdateQuestionnaireAsync(new TravelRuleNetworkQuestionnaireAnswers\n{\n LegalName = \"Example VASP Ltd\",\n IncorporationCountry = \"ES\",\n AmlCtfPolicyAttested = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaireAnswers" + }, + "example": { + "legal_name": "Example VASP Ltd", + "incorporation_country": "ES", + "aml_ctf_policy_attested": true + } + } + } + }, + "responses": { + "200": { + "description": "The questionnaire with the merged answers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaire" + } + } + } + }, + "400": { + "description": "Validation error, keyed by field." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Network access through Didit's membership is not open yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_platform_networks_disabled" + ] + } + } + } + } + } + }, + "409": { + "description": "The questionnaire is with Didit or decided: it can only change while draft or changes_requested.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_questionnaire_not_editable" + ] + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/networks/questionnaire/submit/": { + "post": { + "summary": "Submit the network questionnaire", + "description": "Send the saved questionnaire to Didit's review. The saved answers must be complete: every field except lei and data_protection_contact_email, with the three attestations true. Didit approves it, asks for changes (edit and submit again) or rejects it. Networks you requested while it was pending are handed to their onboarding when it is approved.", + "operationId": "submitTravelRuleNetworkQuestionnaire", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "submit_questionnaire", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/networks/questionnaire/submit/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.networks.submit_questionnaire()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.networks.submitQuestionnaire();\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().networks().submitQuestionnaire();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->networks->submitQuestionnaire();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.networks.submit_questionnaire\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Networks.SubmitQuestionnaireAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The submitted questionnaire.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaire" + } + } + } + }, + "400": { + "description": "The saved answers are incomplete or invalid, keyed by field." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Network access through Didit's membership is not open yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_platform_networks_disabled" + ] + } + } + } + } + } + }, + "409": { + "description": "The questionnaire is with Didit or decided: it can only change while draft or changes_requested.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_questionnaire_not_editable" + ] + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/networks/{network}/request/": { + "post": { + "summary": "Request a Travel Rule network", + "description": "Ask for this application's own participant on a network Didit's membership carries (GTR or Sygna). Without an approved questionnaire the participant waits for it (questionnaire_pending); with one it goes to the network's onboarding (submitted_to_network) and becomes active once the network gives it a code. Idempotent: a pending, onboarding or live participant is returned unchanged. If the network refused the registration (last_error is set), requesting again retries. TRUST and VerifyVASP are reachable only through your own membership: TRUST admits members per VASP, and a VerifyVASP Enclave serves one VASP id.", + "operationId": "requestTravelRuleNetworkAccess", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "request_access", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "network", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "gtr", + "sygna" + ] + }, + "description": "The network to join through Didit's membership." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/networks/gtr/request/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.networks.request_access(\n \"your-network\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.networks.requestAccess(\"your-network\");\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().networks().requestAccess(\"your-network\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->networks->requestAccess('your-network');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.networks.request_access(\"your-network\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Networks.RequestAccessAsync(\"your-network\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Your participant on the network.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkParticipant" + } + } + } + }, + "400": { + "description": "Unknown network, a network Didit does not carry (connect your own membership), or a sandbox application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_network_own_membership_only", + "travel_rule_network_sandbox_application" + ] + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Network access through Didit's membership is not open yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_platform_networks_disabled" + ] + } + } + } + } + } + } + } + } + }, + "/v3/address-validation/": { + "post": { + "summary": "Validate an address", + "description": "Confirm that an address exists and get it back normalised: its delivery lines, components and coordinates. It confirms the address only; it never links the address to a person.\n\n**Request.** Send the address on one line (`address`) or as structured fields (`address_line_1` and, optionally, `address_line_2`, `city`, `region`, `postal_code`), never both, and always its `country` (ISO 3166-1 alpha-2 or alpha-3).\n\n**Result.** `address_validation.status` is `verified` (confirmed to the building), `partially_verified` (confirmed only to the street or above), `not_verified` (only the region or city matched, or nothing did) or `unavailable` (the validation could not run). HTTP is `200` for every completed request, `unavailable` included; retry an `unavailable` result later.\n\n**Billing.** A live call costs $0.10 per validated address. An `unavailable` result is not charged. Nothing is stored apart from the usage record: no session is created and the result is returned only in this response.\n\n**Sandbox.** Sandbox keys validate the request and always return a deterministic `verified` result that echoes the address sent, without consuming credits.\n\n**Authentication.** Send your application's API key in `x-api-key`. Missing or invalid credentials return `403` - this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests per minute per API key across POST/PATCH/DELETE endpoints (transaction creates and event ingestion have a separate budget of 600).", + "operationId": "post_v3_address_validation", + "x-didit-sdk": { + "resource": "address_validation", + "method": "validate", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/address-validation/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"address_line_1\": \"1600 Amphitheatre Pkwy\",\n \"city\": \"Mountain View\",\n \"region\": \"CA\",\n \"postal_code\": \"94043\",\n \"country\": \"US\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.address_validation.validate(\n address_line_1=\"1600 Amphitheatre Pkwy\",\n city=\"Mountain View\",\n region=\"CA\",\n postal_code=\"94043\",\n country=\"US\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.addressValidation.validate({\n address_line_1: \"1600 Amphitheatre Pkwy\",\n city: \"Mountain View\",\n region: \"CA\",\n postal_code: \"94043\",\n country: \"US\",\n});\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.AddressValidationRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.addressValidation().validate(AddressValidationRequest.builder()\n .addressLine1(\"1600 Amphitheatre Pkwy\")\n .city(\"Mountain View\")\n .region(\"CA\")\n .postalCode(\"94043\")\n .country(\"US\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "addressValidation->validate([\n 'address_line_1' => '1600 Amphitheatre Pkwy',\n 'city' => 'Mountain View',\n 'region' => 'CA',\n 'postal_code' => '94043',\n 'country' => 'US',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.address_validation.validate(\n address_line_1: \"1600 Amphitheatre Pkwy\",\n city: \"Mountain View\",\n region: \"CA\",\n postal_code: \"94043\",\n country: \"US\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.AddressValidation.ValidateAsync(new AddressValidationRequest\n{\n AddressLine1 = \"1600 Amphitheatre Pkwy\",\n City = \"Mountain View\",\n Region = \"CA\",\n PostalCode = \"94043\",\n Country = \"US\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddressValidationRequest" + }, + "examples": { + "Structured": { + "summary": "Structured address fields", + "value": { + "address_line_1": "1600 Amphitheatre Pkwy", + "city": "Mountain View", + "region": "CA", + "postal_code": "94043", + "country": "US" + } + }, + "One line": { + "summary": "The whole address on one line", + "value": { + "address": "Paseo de la Castellana 259, 28046 Madrid", + "country": "ES" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The validation completed. Read `address_validation.status`; HTTP is always `200` for a completed request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddressValidationResponse" + }, + "examples": { + "Verified": { + "summary": "A real address, confirmed to the building", + "value": { + "request_id": "6f7d2c1e-1c5a-4a1e-9b8f-2a1f0f4b9c3d", + "address_validation": { + "status": "verified", + "verification_level": "premise", + "address": { + "address_line_1": "1600 Amphitheatre Pkwy", + "address_line_2": null, + "city": "Mountain View", + "region": "CA", + "postal_code": "94043-1351", + "country": "US" + }, + "formatted_address": "1600 Amphitheatre Pkwy, Mountain View CA 94043-1351", + "latitude": 37.422022, + "longitude": -122.08429 + }, + "created_at": "2026-10-27T11:20:00.000000+00:00" + } + }, + "Not verified": { + "summary": "An invented street: only the city matched", + "value": { + "request_id": "0b9e3f4a-6c2d-4e8b-a1f7-5d3c2b1a0e9f", + "address_validation": { + "status": "not_verified", + "verification_level": "locality", + "address": { + "address_line_1": "Calle Inventada Que No Existe 99999 28046", + "address_line_2": null, + "city": "Madrid", + "region": "Madrid", + "postal_code": null, + "country": "ES" + }, + "formatted_address": "Calle Inventada Que No Existe 99999 28046, Madrid, MADRID, SPAIN", + "latitude": 40.42603054, + "longitude": -3.68645331 + }, + "created_at": "2026-10-27T11:21:00.000000+00:00" + } + }, + "Unavailable": { + "summary": "The validation could not run; not charged", + "value": { + "request_id": "3c1b2a0f-9e8d-4c7b-b6a5-1f0e9d8c7b6a", + "address_validation": { + "status": "unavailable", + "verification_level": null, + "address": null, + "formatted_address": null, + "latitude": null, + "longitude": null + }, + "created_at": "2026-10-27T11:22:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/AddressValidationValidationError" + }, + "403": { + "$ref": "#/components/responses/AddressValidationPermissionDenied" + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + } + } + }, + "/v3/travel-rule/wallet-proofs/": { + "get": { + "summary": "List screenshot wallet proofs to review", + "description": "Your review queue for screenshot wallet-ownership proofs. A screenshot is the one proof Didit cannot check itself: once the end user uploads it from the widget it waits PENDING until you approve or reject it with POST /v3/travel-rule/wallet-proofs/{proof_uuid}/decision/. Oldest first, so the queue is worked from the proof that has waited longest. Only uploaded screenshots appear, never a proof whose end user has not uploaded a file yet, and never one on a wallet you deleted from your address book (Didit closes those as OWNERSHIP_CHANGED).", + "operationId": "listTravelRuleWalletProofs", + "x-didit-sdk": { + "resource": "travel_rule.wallet_proofs", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Which proofs to list. Defaults to PENDING, the ones still to decide.", + "schema": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ], + "default": "PENDING" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/wallet-proofs/?status=PENDING \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.travel_rule.wallet_proofs.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.travelRule.walletProofs.list()) {\n console.log(item);\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.travelRule().walletProofs().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletProofs->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.wallet_proofs.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.TravelRule.WalletProofs.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated screenshot proofs. count is exact up to 100 and capped at 100 above it.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleWalletProofReview" + } + } + } + } + } + } + }, + "400": { + "description": "Unknown status." + }, + "401": { + "description": "Missing or invalid x-api-key." + }, + "403": { + "description": "The caller lacks the transactions permission." + } + } + } + }, + "/v3/travel-rule/wallet-proofs/{proof_uuid}/decision/": { + "post": { + "summary": "Approve or reject a screenshot wallet proof", + "description": "Decide a screenshot proof from your review queue. approve verifies the proof and the address book entry and applies the ownership to the transfer the widget session was minted for, exactly as a verified signature does. A screenshot never confirms a transfer that another Didit customer sent over the INTERNAL rail: that takes a signed message or a test deposit. reject closes the proof as REJECTED with failure_reason REVIEW_REJECTED; the end user can start a new proof from the widget. The note is kept with the proof.", + "operationId": "decideTravelRuleWalletProof", + "x-didit-sdk": { + "resource": "travel_rule.wallet_proofs", + "method": "decide", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "proof_uuid", + "in": "path", + "required": true, + "description": "proof_id from the review queue.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWalletProofDecision" + }, + "example": { + "decision": "approve", + "note": "Address matches the wallet app." + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/wallet-proofs/{proof_uuid}/decision/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"decision\": \"approve\", \"note\": \"Address matches the wallet app.\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.wallet_proofs.decide(\n \"11111111-2222-3333-4444-555555555555\",\n decision=\"approve\",\n note=\"Address matches the wallet app.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.walletProofs.decide(\"11111111-2222-3333-4444-555555555555\", {\n decision: \"approve\",\n note: \"Address matches the wallet app.\",\n});\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleWalletProofDecision;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().walletProofs().decide(\"11111111-2222-3333-4444-555555555555\", TravelRuleWalletProofDecision.builder()\n .decision(TravelRuleWalletProofDecision.Decision.of(\"approve\"))\n .note(\"Address matches the wallet app.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletProofs->decide('11111111-2222-3333-4444-555555555555', [\n 'decision' => 'approve',\n 'note' => 'Address matches the wallet app.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.wallet_proofs.decide(\n \"11111111-2222-3333-4444-555555555555\",\n decision: \"approve\",\n note: \"Address matches the wallet app.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.WalletProofs.DecideAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleWalletProofDecision\n{\n Decision = \"approve\",\n Note = \"Address matches the wallet app.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The decided proof.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWalletProofReview" + } + } + } + }, + "400": { + "description": "Invalid decision, or a note longer than 512 characters." + }, + "401": { + "description": "Missing or invalid x-api-key." + }, + "403": { + "description": "The caller lacks the transactions permission." + }, + "404": { + "description": "No submitted screenshot proof with this id in your application." + }, + "409": { + "description": "The proof is already decided (the body carries its status), or code OWNERSHIP_CHANGED: the wallet's holder changed or the wallet was deleted after the widget session was minted, so the proof is closed and the current holder needs a new one.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + } + } + } + }, + "/v3/travel-rule/widget/{token}/proofs/": { + "post": { + "summary": "Start a wallet-ownership proof", + "description": "Public endpoint keyed by the widget session token, no API key required: the hosted widget calls it when the end user picks a proof method. A proof exists, and counts in your proof funnel, only from this call. Picking the same method again returns the proof already open (200, same challenge or deposit amount) instead of starting a second one; picking another method closes the unfinished one as METHOD_SWITCHED, except a submitted screenshot or a test deposit still being checked. MESSAGE_SIGNING returns the challenge to sign; SATOSHI_TEST the deposit address, exact amount and window; SELF_DECLARATION is verified at once (status VERIFIED). Signature formats verified per chain family: EVM chains take a personal_sign (EIP-191) signature, hex encoded; Bitcoin takes a BIP-137 signmessage signature (P2PKH, P2SH-P2WPKH and P2WPKH addresses) or a BIP-322 simple signature (P2WPKH and P2TR addresses), base64 encoded; Solana takes an Ed25519 signature over the challenge, base58 encoded; Tron takes a signMessageV2 (TIP-191) signature, hex encoded. A BIP-322 full or proof-of-funds signature, or a multisig address, fails with SIGNATURE_UNSUPPORTED_SCHEME.", + "operationId": "createTravelRuleWidgetProof", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "create_widget_proof", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofCreate" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/widget/{token}/proofs/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"method\": \"MESSAGE_SIGNING\"}'" + } + ], + "responses": { + "201": { + "description": "A new PENDING proof (VERIFIED for SELF_DECLARATION).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofSummary" + } + } + } + }, + "200": { + "description": "The proof already open for this method, resumed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofSummary" + } + } + } + }, + "400": { + "description": "Invalid body, a wallet that does not match the transfer, SCREENSHOT not allowed, or code CHAIN_UNSUPPORTED when the method is not available on the chain.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + }, + "403": { + "description": "SELF_DECLARATION is not allowed for this application." + }, + "404": { + "description": "Unknown token." + }, + "409": { + "description": "code OWNERSHIP_CHANGED: the wallet's holder changed or the wallet was deleted after the session was minted; mint a new session for the current holder. Also returned for SATOSHI_TEST when the session has no satoshi_deposit_address, and when the session has no attempts left (no code; the body carries max_attempts, attempts_used and attempts_remaining 0): mint a new session.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + }, + "410": { + "description": "The session expired." + } + } + } + }, + "/v3/travel-rule/widget/{token}/proofs/{proof_id}/verify/": { + "post": { + "summary": "Submit wallet-ownership proof evidence", + "description": "Public endpoint keyed by the widget session token, no API key required. Send the evidence the proof's method needs: signature for MESSAGE_SIGNING, txid for SATOSHI_TEST (JSON), or a file for SCREENSHOT (multipart). Every submission counts as an attempt. A signature that does not verify, or a deposit that does not match, answers 400 with a code and leaves the proof PENDING so the end user can try again. A deposit seen but not confirmed yet answers 200 with status PENDING and is rechecked automatically until the window closes. A screenshot answers 200 with status PENDING and waits in your review queue (GET /v3/travel-rule/wallet-proofs/). An EXPIRED or REJECTED proof never verifies later: start a new one. Signature formats verified per chain family: EVM chains take a personal_sign (EIP-191) signature, hex encoded; Bitcoin takes a BIP-137 signmessage signature (P2PKH, P2SH-P2WPKH and P2WPKH addresses) or a BIP-322 simple signature (P2WPKH and P2TR addresses), base64 encoded; Solana takes an Ed25519 signature over the challenge, base58 encoded; Tron takes a signMessageV2 (TIP-191) signature, hex encoded. A BIP-322 full or proof-of-funds signature, or a multisig address, fails with SIGNATURE_UNSUPPORTED_SCHEME.", + "operationId": "verifyTravelRuleWidgetProof", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "verify_widget_proof", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + }, + { + "name": "proof_id", + "in": "path", + "required": true, + "description": "proof_id returned when the proof was started.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "signature": { + "type": "string", + "description": "MESSAGE_SIGNING: the wallet's signature over the proof's challenge." + }, + "txid": { + "type": "string", + "description": "SATOSHI_TEST: the transaction id of the test deposit." + } + } + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "file" + ], + "properties": { + "file": { + "type": "string", + "format": "binary", + "description": "SCREENSHOT: an image showing the wallet in the end user's wallet app." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/widget/{token}/proofs/{proof_id}/verify/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"signature\": \"0x...\"}'" + } + ], + "responses": { + "200": { + "description": "VERIFIED, or still PENDING (a screenshot awaiting review, a deposit not indexed or not confirmed yet, with code DEPOSIT_NOT_FOUND or DEPOSIT_UNCONFIRMED).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofVerifyResult" + } + } + } + }, + "400": { + "description": "Missing evidence; a failed attempt that keeps the proof PENDING so the end user can try again (code SIGNATURE_INVALID, SIGNATURE_UNSUPPORTED_SCHEME or DEPOSIT_LOOKUP_FAILED); or a test deposit that rejects the proof (code DEPOSIT_MISMATCH, DEPOSIT_STALE or TXID_REUSED, status REJECTED, with the session's attempt counts): picking a method again starts a new proof while attempts_remaining is above 0.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + }, + "404": { + "description": "Unknown token, or no such proof on this session." + }, + "409": { + "description": "code OWNERSHIP_CHANGED (the proof is closed with it); a closed proof (REJECTED or EXPIRED), with its status, the code it closed with and the session's attempt counts: pick a method again to start a new proof; or a SELF_DECLARATION proof, which is verified when started.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + }, + "410": { + "description": "The session expired, or code WINDOW_EXPIRED when the satoshi test window closed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + } + } + } + }, + "/v3/id-verification/wallets/verifications/{request_id}/response/": { + "post": { + "operationId": "submit_wallet_verification_response", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "submit_verification_response", + "auth": "api_key", + "server": true + }, + "description": "Digital Credentials API wallets (Google Wallet): send what the wallet returned to your page or app for a verification whose next_action is request_digital_credential. Didit decrypts it, checks the issuer's signature against the trusted issuer roots, every returned value against its signed digest, and the device signature over your origin and this request, then decides the verification: verified (billed once) or declined with the error code. Single use: a second submit returns the decided verification.", + "summary": "Submit a wallet response", + "parameters": [ + { + "in": "path", + "name": "request_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The request_id the start endpoint returned.", + "required": true + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletDigitalCredentialResponseRequest" + }, + "example": { + "response": { + "protocol": "openid4vp-v1-signed", + "data": { + "response": "" + } + } + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletDigitalCredentialResponseRequest" + }, + "example": { + "response": { + "protocol": "openid4vp-v1-signed", + "data": { + "response": "" + } + } + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletDigitalCredentialResponseRequest" + }, + "example": { + "response": { + "protocol": "openid4vp-v1-signed", + "data": { + "response": "" + } + } + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "google_wallet", + "name": "Google Wallet", + "issuing_authority": "Google Wallet (ID pass) and US state issuers (mobile driving licence)" + }, + "country": "USA", + "status": "Approved", + "outcome": "verified", + "error": null, + "interaction": "digital_credentials", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": null, + "identity": { + "full_name": "Alex Example", + "first_name": "Alex", + "last_name": "Example", + "date_of_birth": "1990-01-31", + "nationality": "USA", + "issuing_state": "USA" + }, + "attributes": { + "document_number": "X0000000", + "document_expiry_date": "2030-01-31" + }, + "level_of_assurance": "substantial", + "verified_at": "2026-01-01T10:01:00Z", + "signature_valid": true, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "Missing credentials (x-api-key or Authorization: Bearer)." + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error not_found: no wallet verification of this application has this request_id." + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]})." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error not_a_digital_credentials_wallet: this verification does not take a wallet response." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot verify the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/verifications/REQUEST_ID/response/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"response\": {\n \"protocol\": \"openid4vp-v1-signed\",\n \"data\": {\n \"response\": \"\"\n }\n }\n}'" + } + ] + } + }, + "/v3/id-verification/wallets/google-wallet/": { + "post": { + "operationId": "start_google_wallet_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_google_wallet", + "auth": "api_key", + "server": true + }, + "description": "Start a Google Wallet sign-in (Google Wallet (ID pass) and US state issuers (mobile driving licence); USA, GBR, BRA, SGP, TWN) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=request_digital_credential with a request Didit signed for your application, usable only from origin. In a user gesture there, pass next_action.digital_credentials.request to navigator.credentials.get({digital: ...}) (or to Android Credential Manager as GetDigitalCredentialOption requestJson), then POST what the wallet returns to /v3/id-verification/wallets/verifications/{request_id}/response/. The browser offers its own QR code when the wallet is on another device. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Google Wallet verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGoogleWalletWalletVerificationStartRequest" + }, + "example": { + "country": "USA", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "origin": "https://app.example.com" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGoogleWalletWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGoogleWalletWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "google_wallet", + "name": "Google Wallet", + "issuing_authority": "Google Wallet (ID pass) and US state issuers (mobile driving licence)" + }, + "country": "USA", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "digital_credentials", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "request_digital_credential", + "digital_credentials": { + "protocol": "openid4vp-v1-signed", + "request": { + "requests": [ + { + "protocol": "openid4vp-v1-signed", + "data": { + "request": "" + } + } + ] + } + } + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, origin_invalid, origin_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/google-wallet/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"USA\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"origin\": \"https://app.example.com\"\n}'" + } + ], + "x-didit-wallet-id": "google_wallet" + } + }, + "/v3/session/{sessionId}/kyb/documents/": { + "post": { + "summary": "Upload KYB documents to a session", + "description": "Upload one or more corporate documents (articles of incorporation, shareholder register, certificate of incorporation, ...) to the KYB Documents step of a Business Verification session, from your own server instead of the hosted page. Didit runs the hosted pipeline: compression, OCR, tampering and cross-document checks, document grouping, and advances the session when the required document groups are satisfied. The step costs one flat fee however many files you send. The session must be open and on the KYB Documents step, and the company chosen at the registry step must exist. Read the people the documents name with `GET /v3/session/{sessionId}/kyb/extracted-parties/`. Requires `write:businesses`.", + "operationId": "post_v3_session_kyb_documents", + "x-didit-sdk": { + "resource": "sessions", + "method": "upload_kyb_documents", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "documents" + ], + "properties": { + "documents": { + "type": "array", + "items": { + "type": "string", + "format": "binary" + }, + "description": "Files to upload: JPG, JPEG, PNG, TIFF, WEBP, PDF or ZIP archives of them. PDFs up to 50 MB, photos up to 6 MB before compression." + }, + "document_passwords": { + "type": "array", + "items": { + "type": "string" + }, + "description": "One entry per file in `documents`, in the same order: the password of a protected PDF, empty for any other file." + }, + "document_group": { + "type": "string", + "enum": [ + "LEGAL_PRESENCE", + "COMPANY_DETAILS", + "OWNERSHIP_STRUCTURE", + "REPRESENTATIVES_AUTHORIZATION" + ], + "description": "Required document group this upload answers." + }, + "locale": { + "type": "string", + "maxLength": 10, + "description": "Locale used to translate the response of the next step." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Documents processed. When every required group is satisfied the session advances and `next_step` names the next step; otherwise it stays on the Documents step and `document_groups` and `document_answer` say what is still missing.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "valid": { + "type": "boolean" + }, + "next_step": { + "type": "string", + "nullable": true, + "description": "Step the session is on after the upload." + }, + "kyb_status": { + "type": "string", + "description": "Status of the company check." + }, + "document_answer": { + "type": "object", + "additionalProperties": true, + "description": "Result per uploaded document and the uploads left." + } + }, + "additionalProperties": true + }, + "example": { + "valid": true, + "next_step": "KYB_KEY_PEOPLE", + "kyb_status": "In Progress" + } + } + } + }, + "400": { + "description": "A document was rejected (tampering, unreadable or unsupported) or the session has no KYB company yet. Retry with another file while attempts remain.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "message": "No KYB company found. Submit company data first." + } + } + } + }, + "402": { + "description": "The organization has no credit left for the documents step.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Insufficient credits." + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted, belongs to another application or is outside a scoped key's limits; no detail distinguishes them.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + }, + "409": { + "description": "The session is finished (`kyb_session_not_writable`) or is not on the KYB Documents step (`kyb_step_mismatch`, with `next_step` naming the step it is on).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string" + }, + "next_step": { + "type": "string" + } + } + }, + "example": { + "detail": "The session is not on the step this operation completes.", + "code": "kyb_step_mismatch", + "next_step": "KYB_REGISTRY" + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.upload_kyb_documents(\n \"33333333-4444-5555-6666-777777777777\",\n documents=[open(\"document.pdf\", \"rb\")],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.uploadKybDocuments(\"33333333-4444-5555-6666-777777777777\", {\n documents: [await fileFromPath(\"document.pdf\")],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UploadKYBDocuments(ctx, \"33333333-4444-5555-6666-777777777777\", didit.SessionsUploadKYBDocumentsParams{\n\tDocuments: []didit.File{didit.FileFromPath(\"document.pdf\")},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.SessionsUploadKybDocumentsParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().uploadKybDocuments(\"33333333-4444-5555-6666-777777777777\", SessionsUploadKybDocumentsParams.builder()\n .documents(List.of(Path.of(\"document.pdf\")))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->uploadKybDocuments('33333333-4444-5555-6666-777777777777', [\n 'documents' => [File::fromPath('document.pdf')],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.upload_kyb_documents(\n \"33333333-4444-5555-6666-777777777777\",\n documents: [Pathname(\"document.pdf\")]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UploadKybDocumentsAsync(\"33333333-4444-5555-6666-777777777777\", new SessionsUploadKybDocumentsParams\n{\n Documents = new FileUpload[] { FileUpload.FromPath(\"document.pdf\") },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/extracted-parties/": { + "get": { + "summary": "List the parties named by the uploaded documents", + "description": "Return the shareholders and directors that the documents uploaded to this KYB session name, merged per person and attributed to the documents that named them. Show them to your applicant to confirm, then declare them with the party draft endpoints. A range stays a range: `ownership_percent.text` keeps the verbatim figure and `min` / `max` the numbers read from it. Nobody is labelled a UBO here; that is decided when the parties are submitted, from the role settings and ownership thresholds of the workflow. The same list is in the session decision as `document_verifications[].extracted_parties`. Requires `read:businesses` or `read:sessions`.", + "operationId": "get_v3_session_kyb_extracted_parties", + "x-didit-sdk": { + "resource": "sessions", + "method": "list_kyb_extracted_parties", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Parties named by the current documents.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "parties", + "document_count" + ], + "properties": { + "parties": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ] + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "appointment_date": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "roles": { + "type": "array", + "items": { + "type": "object", + "properties": { + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "ownership_percent": { + "type": "object", + "properties": { + "text": { + "type": "string", + "nullable": true, + "description": "The ownership figure exactly as the document states it, such as `25-50%`, `>50%` or `NO EXHIBE`." + }, + "min": { + "type": "number", + "nullable": true, + "description": "Lower number read out of `text`; null when it states none." + }, + "max": { + "type": "number", + "nullable": true, + "description": "Upper number read out of `text`; null when it states none." + } + } + } + } + } + }, + "documents": { + "type": "array", + "description": "The uploaded documents that named this party.", + "items": { + "type": "object", + "properties": { + "document_uuid": { + "type": "string", + "format": "uuid" + }, + "document_type": { + "type": "string", + "nullable": true + }, + "document_subtype": { + "type": "string", + "nullable": true + }, + "original_filename": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "description": "The shareholders and directors the uploaded documents name, merged per person. Nobody is labelled a UBO here: that is decided when the parties are submitted." + }, + "document_count": { + "type": "integer", + "description": "Number of uploaded documents the list was read from." + }, + "node_id": { + "type": "string", + "nullable": true + } + } + }, + "example": { + "parties": [ + { + "name": "Ana Lopez", + "entity_type": "person", + "first_name": "Ana", + "last_name": "Lopez", + "appointment_date": null, + "designation": "Administrador unico", + "roles": [ + { + "role": "shareholder", + "ownership_percent": { + "text": "60%", + "min": 60, + "max": 60 + } + } + ], + "documents": [ + { + "document_uuid": "0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41", + "document_type": "OWNERSHIP_STRUCTURE", + "document_subtype": "ARTICLES_OF_INCORPORATION", + "original_filename": "acta_constitutiva.pdf", + "status": "Approved" + } + ] + } + ], + "document_count": 1, + "node_id": "node_kyb_documents" + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted, belongs to another application or is outside a scoped key's limits; no detail distinguishes them.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.list_kyb_extracted_parties(\n \"33333333-4444-5555-6666-777777777777\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.listKybExtractedParties(\"33333333-4444-5555-6666-777777777777\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.ListKYBExtractedParties(ctx, \"33333333-4444-5555-6666-777777777777\", didit.SessionsListKYBExtractedPartiesParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().listKybExtractedParties(\"33333333-4444-5555-6666-777777777777\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->listKybExtractedParties('33333333-4444-5555-6666-777777777777');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.list_kyb_extracted_parties(\"33333333-4444-5555-6666-777777777777\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ListKybExtractedPartiesAsync(\"33333333-4444-5555-6666-777777777777\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/parties/drafts/": { + "get": { + "summary": "List the KYB party drafts", + "description": "List the parties you are preparing for this KYB session, oldest first. A draft is not a declared party: it does not appear in the decision or the console and creates no verification session until you submit it with `POST /v3/session/{sessionId}/kyb/parties/submit/`. Requires `write:businesses`.", + "operationId": "get_v3_session_kyb_party_drafts", + "x-didit-sdk": { + "resource": "sessions", + "method": "list_kyb_party_drafts", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The stored drafts.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "parties": { + "type": "array", + "items": { + "type": "object", + "required": [ + "draft_id", + "created_at", + "updated_at", + "party" + ], + "properties": { + "draft_id": { + "type": "string", + "format": "uuid" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "party": { + "type": "object", + "required": [ + "entity_type", + "nationality" + ], + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "full_name": { + "type": "string", + "maxLength": 255, + "description": "Full name of a person. Send it, or `first_name` and `last_name`." + }, + "first_name": { + "type": "string", + "maxLength": 128 + }, + "last_name": { + "type": "string", + "maxLength": 128 + }, + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Role the party holds. Interchangeable with `roles`; when both are sent they are merged." + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "description": "Every role the party holds. A person needs at least one role (here or in `role`)." + }, + "position": { + "type": "string", + "maxLength": 128, + "description": "Job title of a person." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Name of a company party. Required when `entity_type` is `company`." + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Registration number of a company party." + }, + "email": { + "type": "string", + "format": "email", + "description": "Where the verification link is sent. Required when the party has to complete verification, which depends on the role settings and ownership thresholds of the workflow." + }, + "ownership_percent": { + "type": "number", + "nullable": true, + "description": "Share of the company the party owns." + }, + "nationality": { + "type": "string", + "maxLength": 100, + "description": "Nationality of a person, or country of incorporation of a company. Required for every party." + }, + "phone_number": { + "type": "string", + "maxLength": 32 + }, + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined extra fields." + }, + "skip_verification": { + "type": "boolean", + "default": false, + "description": "Ask to skip this party's verification. Honoured only when the party's role allows skipping in the workflow's Key People settings." + } + } + } + } + } + }, + "node_id": { + "type": "string", + "nullable": true + } + } + }, + "example": { + "parties": [ + { + "draft_id": "5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d", + "created_at": "2026-10-31T10:00:00Z", + "updated_at": "2026-10-31T10:00:00Z", + "party": { + "entity_type": "person", + "first_name": "Ana", + "last_name": "Lopez", + "roles": [ + "ubo", + "director" + ], + "ownership_percent": 60, + "nationality": "MEX", + "email": "ana.lopez@example.com", + "date_of_birth": "1984-06-12" + } + } + ], + "node_id": "node_kyb_key_people" + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted, belongs to another application or is outside a scoped key's limits; no detail distinguishes them.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.list_kyb_party_drafts(\n \"33333333-4444-5555-6666-777777777777\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.listKybPartyDrafts(\"33333333-4444-5555-6666-777777777777\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.ListKYBPartyDrafts(ctx, \"33333333-4444-5555-6666-777777777777\", didit.SessionsListKYBPartyDraftsParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().listKybPartyDrafts(\"33333333-4444-5555-6666-777777777777\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->listKybPartyDrafts('33333333-4444-5555-6666-777777777777');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.list_kyb_party_drafts(\"33333333-4444-5555-6666-777777777777\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ListKybPartyDraftsAsync(\"33333333-4444-5555-6666-777777777777\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "post": { + "summary": "Create a KYB party draft", + "description": "Add a party (a person or a company) to the list you are preparing for this KYB session. The same fields and rules as the hosted Key People screen apply: a person needs `full_name` or `first_name` and `last_name`, at least one role and a nationality; a company needs `company_name` and the country of incorporation in `nationality`. Nothing is declared or billed until `POST /v3/session/{sessionId}/kyb/parties/submit/`. Requires `write:businesses`.", + "operationId": "post_v3_session_kyb_party_drafts", + "x-didit-sdk": { + "resource": "sessions", + "method": "create_kyb_party_draft", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "entity_type", + "nationality" + ], + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "full_name": { + "type": "string", + "maxLength": 255, + "description": "Full name of a person. Send it, or `first_name` and `last_name`." + }, + "first_name": { + "type": "string", + "maxLength": 128 + }, + "last_name": { + "type": "string", + "maxLength": 128 + }, + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Role the party holds. Interchangeable with `roles`; when both are sent they are merged." + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "description": "Every role the party holds. A person needs at least one role (here or in `role`)." + }, + "position": { + "type": "string", + "maxLength": 128, + "description": "Job title of a person." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Name of a company party. Required when `entity_type` is `company`." + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Registration number of a company party." + }, + "email": { + "type": "string", + "format": "email", + "description": "Where the verification link is sent. Required when the party has to complete verification, which depends on the role settings and ownership thresholds of the workflow." + }, + "ownership_percent": { + "type": "number", + "nullable": true, + "description": "Share of the company the party owns." + }, + "nationality": { + "type": "string", + "maxLength": 100, + "description": "Nationality of a person, or country of incorporation of a company. Required for every party." + }, + "phone_number": { + "type": "string", + "maxLength": 32 + }, + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined extra fields." + }, + "skip_verification": { + "type": "boolean", + "default": false, + "description": "Ask to skip this party's verification. Honoured only when the party's role allows skipping in the workflow's Key People settings." + } + } + }, + "example": { + "entity_type": "person", + "first_name": "Ana", + "last_name": "Lopez", + "roles": [ + "ubo", + "director" + ], + "ownership_percent": 60, + "nationality": "MEX", + "email": "ana.lopez@example.com", + "date_of_birth": "1984-06-12" + } + } + } + }, + "responses": { + "201": { + "description": "Draft created.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "draft_id", + "created_at", + "updated_at", + "party" + ], + "properties": { + "draft_id": { + "type": "string", + "format": "uuid" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "party": { + "type": "object", + "required": [ + "entity_type", + "nationality" + ], + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "full_name": { + "type": "string", + "maxLength": 255, + "description": "Full name of a person. Send it, or `first_name` and `last_name`." + }, + "first_name": { + "type": "string", + "maxLength": 128 + }, + "last_name": { + "type": "string", + "maxLength": 128 + }, + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Role the party holds. Interchangeable with `roles`; when both are sent they are merged." + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "description": "Every role the party holds. A person needs at least one role (here or in `role`)." + }, + "position": { + "type": "string", + "maxLength": 128, + "description": "Job title of a person." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Name of a company party. Required when `entity_type` is `company`." + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Registration number of a company party." + }, + "email": { + "type": "string", + "format": "email", + "description": "Where the verification link is sent. Required when the party has to complete verification, which depends on the role settings and ownership thresholds of the workflow." + }, + "ownership_percent": { + "type": "number", + "nullable": true, + "description": "Share of the company the party owns." + }, + "nationality": { + "type": "string", + "maxLength": 100, + "description": "Nationality of a person, or country of incorporation of a company. Required for every party." + }, + "phone_number": { + "type": "string", + "maxLength": 32 + }, + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined extra fields." + }, + "skip_verification": { + "type": "boolean", + "default": false, + "description": "Ask to skip this party's verification. Honoured only when the party's role allows skipping in the workflow's Key People settings." + } + } + } + } + }, + "example": { + "draft_id": "5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d", + "created_at": "2026-10-31T10:00:00Z", + "updated_at": "2026-10-31T10:00:00Z", + "party": { + "entity_type": "person", + "first_name": "Ana", + "last_name": "Lopez", + "roles": [ + "ubo", + "director" + ], + "ownership_percent": 60, + "nationality": "MEX", + "email": "ana.lopez@example.com", + "date_of_birth": "1984-06-12" + } + } + } + } + }, + "400": { + "description": "A party field is invalid or a required one is missing.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "non_field_errors": [ + "Person parties require at least one role." + ] + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted, belongs to another application or is outside a scoped key's limits; no detail distinguishes them.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + }, + "409": { + "description": "The session is finished (`kyb_session_not_writable`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "This verification session is no longer accepting changes.", + "code": "kyb_session_not_writable" + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.create_kyb_party_draft(\n \"33333333-4444-5555-6666-777777777777\",\n entity_type=\"person\",\n first_name=\"Ana\",\n last_name=\"Lopez\",\n roles=[\"ubo\", \"director\"],\n date_of_birth=\"1984-06-12\",\n email=\"ana.lopez@example.com\",\n ownership_percent=60.0,\n nationality=\"MEX\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.createKybPartyDraft(\"33333333-4444-5555-6666-777777777777\", {\n entity_type: \"person\",\n first_name: \"Ana\",\n last_name: \"Lopez\",\n roles: [\"ubo\", \"director\"],\n date_of_birth: \"1984-06-12\",\n email: \"ana.lopez@example.com\",\n ownership_percent: 60.0,\n nationality: \"MEX\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.CreateKYBPartyDraft(ctx, \"33333333-4444-5555-6666-777777777777\", didit.SessionsCreateKYBPartyDraftParams{\n\tEntityType: \"person\",\n\tFirstName: didit.String(\"Ana\"),\n\tLastName: didit.String(\"Lopez\"),\n\tRoles: didit.Some([]didit.SessionsCreateKYBPartyDraftParamsRoles{\"ubo\", \"director\"}),\n\tDateOfBirth: didit.String(\"1984-06-12\"),\n\tEmail: didit.String(\"ana.lopez@example.com\"),\n\tOwnershipPercent: didit.Float(60.0),\n\tNationality: \"MEX\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.SessionsCreateKybPartyDraftParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().createKybPartyDraft(\"33333333-4444-5555-6666-777777777777\", SessionsCreateKybPartyDraftParams.builder()\n .entityType(SessionsCreateKybPartyDraftParams.EntityType.of(\"person\"))\n .firstName(\"Ana\")\n .lastName(\"Lopez\")\n .roles(List.of(SessionsCreateKybPartyDraftParams.Roles.of(\"ubo\"), SessionsCreateKybPartyDraftParams.Roles.of(\"director\")))\n .dateOfBirth(LocalDate.parse(\"1984-06-12\"))\n .email(\"ana.lopez@example.com\")\n .ownershipPercent(60.0)\n .nationality(\"MEX\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->createKybPartyDraft('33333333-4444-5555-6666-777777777777', [\n 'entity_type' => 'person',\n 'first_name' => 'Ana',\n 'last_name' => 'Lopez',\n 'roles' => ['ubo', 'director'],\n 'date_of_birth' => '1984-06-12',\n 'email' => 'ana.lopez@example.com',\n 'ownership_percent' => 60.0,\n 'nationality' => 'MEX',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.create_kyb_party_draft(\n \"33333333-4444-5555-6666-777777777777\",\n entity_type: \"person\",\n first_name: \"Ana\",\n last_name: \"Lopez\",\n roles: [\"ubo\", \"director\"],\n date_of_birth: \"1984-06-12\",\n email: \"ana.lopez@example.com\",\n ownership_percent: 60.0,\n nationality: \"MEX\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.CreateKybPartyDraftAsync(\"33333333-4444-5555-6666-777777777777\", new SessionsCreateKybPartyDraftParams\n{\n EntityType = \"person\",\n FirstName = \"Ana\",\n LastName = \"Lopez\",\n Roles = new SessionsCreateKybPartyDraftParamsRoles[] { \"ubo\", \"director\" },\n DateOfBirth = \"1984-06-12\",\n Email = \"ana.lopez@example.com\",\n OwnershipPercent = 60.0,\n Nationality = \"MEX\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/parties/drafts/{draftId}/": { + "patch": { + "summary": "Edit a KYB party draft", + "description": "Change fields of one party draft. The edit is merged into the stored draft and the merged party is validated as a whole, so an edit that would leave the party incomplete is rejected. Changing `first_name` or `last_name` replaces the stored full name. Requires `write:businesses`.", + "operationId": "patch_v3_session_kyb_party_draft", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_kyb_party_draft", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "draftId", + "in": "path", + "required": true, + "description": "UUID of the party draft (`draft_id` returned when it was created).", + "schema": { + "type": "string", + "format": "uuid", + "example": "5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Any subset of the party fields. The edit is merged into the stored draft and the merged party is validated as a whole, so a draft that could be submitted before an edit can still be submitted after it.", + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "full_name": { + "type": "string", + "maxLength": 255, + "description": "Full name of a person. Send it, or `first_name` and `last_name`." + }, + "first_name": { + "type": "string", + "maxLength": 128 + }, + "last_name": { + "type": "string", + "maxLength": 128 + }, + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Role the party holds. Interchangeable with `roles`; when both are sent they are merged." + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "description": "Every role the party holds. A person needs at least one role (here or in `role`)." + }, + "position": { + "type": "string", + "maxLength": 128, + "description": "Job title of a person." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Name of a company party. Required when `entity_type` is `company`." + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Registration number of a company party." + }, + "email": { + "type": "string", + "format": "email", + "description": "Where the verification link is sent. Required when the party has to complete verification, which depends on the role settings and ownership thresholds of the workflow." + }, + "ownership_percent": { + "type": "number", + "nullable": true, + "description": "Share of the company the party owns." + }, + "nationality": { + "type": "string", + "maxLength": 100, + "description": "Nationality of a person, or country of incorporation of a company. Required for every party." + }, + "phone_number": { + "type": "string", + "maxLength": 32 + }, + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined extra fields." + }, + "skip_verification": { + "type": "boolean", + "default": false, + "description": "Ask to skip this party's verification. Honoured only when the party's role allows skipping in the workflow's Key People settings." + } + } + }, + "example": { + "ownership_percent": 55 + } + } + } + }, + "responses": { + "200": { + "description": "Draft updated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "draft_id", + "created_at", + "updated_at", + "party" + ], + "properties": { + "draft_id": { + "type": "string", + "format": "uuid" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "party": { + "type": "object", + "required": [ + "entity_type", + "nationality" + ], + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "full_name": { + "type": "string", + "maxLength": 255, + "description": "Full name of a person. Send it, or `first_name` and `last_name`." + }, + "first_name": { + "type": "string", + "maxLength": 128 + }, + "last_name": { + "type": "string", + "maxLength": 128 + }, + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Role the party holds. Interchangeable with `roles`; when both are sent they are merged." + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "description": "Every role the party holds. A person needs at least one role (here or in `role`)." + }, + "position": { + "type": "string", + "maxLength": 128, + "description": "Job title of a person." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Name of a company party. Required when `entity_type` is `company`." + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Registration number of a company party." + }, + "email": { + "type": "string", + "format": "email", + "description": "Where the verification link is sent. Required when the party has to complete verification, which depends on the role settings and ownership thresholds of the workflow." + }, + "ownership_percent": { + "type": "number", + "nullable": true, + "description": "Share of the company the party owns." + }, + "nationality": { + "type": "string", + "maxLength": 100, + "description": "Nationality of a person, or country of incorporation of a company. Required for every party." + }, + "phone_number": { + "type": "string", + "maxLength": 32 + }, + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined extra fields." + }, + "skip_verification": { + "type": "boolean", + "default": false, + "description": "Ask to skip this party's verification. Honoured only when the party's role allows skipping in the workflow's Key People settings." + } + } + } + } + }, + "example": { + "draft_id": "5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d", + "created_at": "2026-10-31T10:00:00Z", + "updated_at": "2026-10-31T10:00:00Z", + "party": { + "entity_type": "person", + "first_name": "Ana", + "last_name": "Lopez", + "roles": [ + "ubo", + "director" + ], + "ownership_percent": 55, + "nationality": "MEX", + "email": "ana.lopez@example.com", + "date_of_birth": "1984-06-12" + } + } + } + } + }, + "400": { + "description": "The merged party is invalid.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "non_field_errors": [ + "Person parties require at least one role." + ] + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session or the draft does not exist, or the session is outside the key's application or a scoped key's limits.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + }, + "409": { + "description": "The session is finished (`kyb_session_not_writable`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "This verification session is no longer accepting changes.", + "code": "kyb_session_not_writable" + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_kyb_party_draft(\n \"33333333-4444-5555-6666-777777777777\",\n \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\",\n ownership_percent=55.0,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateKybPartyDraft(\n \"33333333-4444-5555-6666-777777777777\",\n \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\",\n {\n ownership_percent: 55.0,\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateKYBPartyDraft(ctx, \"33333333-4444-5555-6666-777777777777\", \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\", didit.SessionsUpdateKYBPartyDraftParams{\n\tOwnershipPercent: didit.Float(55.0),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsUpdateKybPartyDraftParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateKybPartyDraft(\"33333333-4444-5555-6666-777777777777\", \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\", SessionsUpdateKybPartyDraftParams.builder()\n .ownershipPercent(55.0)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateKybPartyDraft('33333333-4444-5555-6666-777777777777', '5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d', [\n 'ownership_percent' => 55.0,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_kyb_party_draft(\n \"33333333-4444-5555-6666-777777777777\",\n \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\",\n ownership_percent: 55.0\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateKybPartyDraftAsync(\"33333333-4444-5555-6666-777777777777\", \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\", new SessionsUpdateKybPartyDraftParams\n{\n OwnershipPercent = 55.0,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "summary": "Delete a KYB party draft", + "description": "Drop one party draft. Declared parties are not affected. Requires `write:businesses`.", + "operationId": "delete_v3_session_kyb_party_draft", + "x-didit-sdk": { + "resource": "sessions", + "method": "delete_kyb_party_draft", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "draftId", + "in": "path", + "required": true, + "description": "UUID of the party draft (`draft_id` returned when it was created).", + "schema": { + "type": "string", + "format": "uuid", + "example": "5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Draft deleted. No body." + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session or the draft does not exist, or the session is outside the key's application or a scoped key's limits.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + }, + "409": { + "description": "The session is finished (`kyb_session_not_writable`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "This verification session is no longer accepting changes.", + "code": "kyb_session_not_writable" + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.sessions.delete_kyb_party_draft(\n \"33333333-4444-5555-6666-777777777777\",\n \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.sessions.deleteKybPartyDraft(\n \"33333333-4444-5555-6666-777777777777\",\n \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\",\n);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Sessions.DeleteKYBPartyDraft(ctx, \"33333333-4444-5555-6666-777777777777\", \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\", didit.SessionsDeleteKYBPartyDraftParams{}); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().deleteKybPartyDraft(\"33333333-4444-5555-6666-777777777777\", \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->deleteKybPartyDraft('33333333-4444-5555-6666-777777777777', '5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.delete_kyb_party_draft(\n \"33333333-4444-5555-6666-777777777777\",\n \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\"\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Sessions.DeleteKybPartyDraftAsync(\"33333333-4444-5555-6666-777777777777\", \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\");" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/parties/submit/": { + "post": { + "summary": "Submit the KYB party drafts", + "description": "Declare the stored drafts as the company's key people. This is the hosted Key People step under your API key: Didit decides from the role settings and ownership thresholds of the workflow which parties must be verified, creates one individual KYC session (or a KYB sub-session for a corporate owner) for each of them, sends the emails the workflow asks for, and advances the session. Each child session is billed like in the hosted flow. The drafts are consumed. Repeating the call after a successful submit changes nothing and answers `200` with `already_submitted: true` and the parties that already exist. The session must be on the Key People step. Send each person's `kyc_session_url` to them, or embed it. Requires `write:businesses`.", + "operationId": "post_v3_session_kyb_parties_submit", + "x-didit-sdk": { + "resource": "sessions", + "method": "submit_kyb_parties", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "locale": { + "type": "string", + "maxLength": 10, + "description": "Locale used to translate the response of the step the session advances to." + } + } + }, + "example": { + "locale": "es" + } + } + } + }, + "responses": { + "200": { + "description": "Parties declared (or already declared).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "valid": { + "type": "boolean" + }, + "next_step": { + "type": "string", + "nullable": true, + "description": "Step the session is on." + }, + "status": { + "type": "string", + "description": "Status of the business session." + }, + "advanced": { + "type": "boolean", + "description": "`true` when this call declared the parties and advanced the session." + }, + "already_submitted": { + "type": "boolean", + "description": "`true` when the parties had been declared by an earlier call." + }, + "kyb_key_people_status": { + "type": "string" + }, + "parties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/KYBKeyPeopleParty" + } + } + } + }, + "example": { + "valid": true, + "next_step": "FINISHED", + "status": "In Progress", + "advanced": true, + "already_submitted": false, + "kyb_key_people_status": "In Progress", + "parties": [] + } + } + } + }, + "402": { + "description": "The organization has no credit left for the child verification sessions.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Insufficient credits." + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted, belongs to another application or is outside a scoped key's limits; no detail distinguishes them.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + }, + "409": { + "description": "`kyb_no_party_drafts`: there is nothing to submit. `kyb_parties_already_submitted`: drafts were added after the parties were declared. `kyb_step_mismatch`: the session is not on the Key People step (`next_step` names its step). `kyb_session_not_writable`: the session is finished.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string" + }, + "next_step": { + "type": "string" + } + } + }, + "example": { + "detail": "There are no draft parties to submit.", + "code": "kyb_no_party_drafts" + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.submit_kyb_parties(\n \"33333333-4444-5555-6666-777777777777\",\n locale=\"es\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.submitKybParties(\"33333333-4444-5555-6666-777777777777\", {\n locale: \"es\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.SubmitKYBParties(ctx, \"33333333-4444-5555-6666-777777777777\", didit.SessionsSubmitKYBPartiesParams{\n\tLocale: didit.String(\"es\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsSubmitKybPartiesParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().submitKybParties(\"33333333-4444-5555-6666-777777777777\", SessionsSubmitKybPartiesParams.builder()\n .locale(\"es\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->submitKybParties('33333333-4444-5555-6666-777777777777', [\n 'locale' => 'es',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.submit_kyb_parties(\n \"33333333-4444-5555-6666-777777777777\",\n locale: \"es\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.SubmitKybPartiesAsync(\"33333333-4444-5555-6666-777777777777\", new SessionsSubmitKybPartiesParams\n{\n Locale = \"es\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/files-export/": { + "post": { + "summary": "Export every file of a session", + "description": "Start preparing one ZIP with every image, video and document of a User Verification (KYC) or Business Verification (KYB) session, in original quality: the files are stored in the ZIP as they were captured, never recompressed. Answers `202` with the export; read it with `GET /v3/session/{sessionId}/files-export/{exportId}/` until `status` is `READY`, then download `download_url`. While an export of the same session is still being prepared, that export is returned instead of a new one.\n\nThe ZIP is deleted 24 hours after it is ready, and at once when the session is deleted. A deleted session cannot be exported. Every export and every download link handed out is written to the session's audit log. A scoped API key needs `media: read` and `decisions: read`; a console user needs the privilege to read sessions.", + "operationId": "post_v3_session_files_export", + "x-didit-sdk": { + "resource": "sessions", + "method": "export_files", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs. A deleted session returns `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/files-export/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.export_files(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.exportFiles(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.ExportFiles(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().exportFiles(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->exportFiles('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.export_files(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ExportFilesAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "202": { + "description": "The export, being prepared.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionFilesExport" + }, + "example": { + "export_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "session_id": "11111111-2222-3333-4444-555555555555", + "status": "PENDING", + "file_count": null, + "size_bytes": null, + "download_url": null, + "expires_at": null, + "error": null, + "created_at": "2026-10-29T12:00:03Z", + "completed_at": null + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/files-export/{exportId}/": { + "get": { + "summary": "Get a session files export", + "description": "Progress of an export started with `POST /v3/session/{sessionId}/files-export/`. Once `status` is `READY`, `download_url` is a signed URL of the ZIP that stays valid for a short time; read the export again for a new one until `expires_at`. Each read that hands out a download link is written to the session's audit log. An export of a deleted session returns `404`.", + "operationId": "get_v3_session_files_export", + "x-didit-sdk": { + "resource": "sessions", + "method": "get_files_export", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs. A deleted session returns `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "exportId", + "in": "path", + "required": true, + "description": "The `export_id` returned by `POST /v3/session/{sessionId}/files-export/`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/files-export/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.get_files_export(\n \"11111111-2222-3333-4444-555555555555\",\n \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.getFilesExport(\n \"11111111-2222-3333-4444-555555555555\",\n \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.GetFilesExport(ctx, \"11111111-2222-3333-4444-555555555555\", \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().getFilesExport(\"11111111-2222-3333-4444-555555555555\", \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->getFilesExport('11111111-2222-3333-4444-555555555555', 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.get_files_export(\n \"11111111-2222-3333-4444-555555555555\",\n \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.GetFilesExportAsync(\"11111111-2222-3333-4444-555555555555\", \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The export.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionFilesExport" + }, + "example": { + "export_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "session_id": "11111111-2222-3333-4444-555555555555", + "status": "READY", + "file_count": 9, + "size_bytes": 18734120, + "download_url": "https://storage.example.com/session/11111111-2222-3333-4444-555555555555/files-export/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/session-42-files.zip?X-Amz-Signature=...", + "expires_at": "2026-10-30T12:00:41Z", + "error": null, + "created_at": "2026-10-29T12:00:03Z", + "completed_at": "2026-10-29T12:00:41Z" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/travel-rule/unmatched/": { + "get": { + "summary": "List unmatched Travel Rule deposits", + "description": "Inbound deposits that are still waiting for the originating VASP's Travel Rule data: their transfer is `AWAITING_COUNTERPARTY`, `NOT_ENOUGH_COUNTERPARTY_DATA` or `COUNTERPARTY_VASP_NOT_FOUND`, no received message is paired with them, and nobody matched them by hand. The rows and the filters are the same as in `GET /v3/travel-rule/transfers/`, newest first, paginated with `limit` (default 50) and `offset`. Pair a deposit with the message the originating VASP sent with `POST /v3/travel-rule/unmatched/{transaction_uuid}/match/`.", + "operationId": "listUnmatchedTravelRuleDeposits", + "x-didit-sdk": { + "resource": "travel_rule.unmatched_deposits", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "direction", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "INBOUND, OUTBOUND, or both comma-separated. Case-sensitive." + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "One or more transfer statuses, comma-separated." + }, + { + "name": "rail", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "One or more of INTERNAL, TRP, GTR, TRUST, VERIFYVASP, SYGNA, EMAIL, TEST_VASP, comma-separated." + }, + { + "name": "kind", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "withdrawal, deposit or message, comma-separated." + }, + { + "name": "currency", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "The transaction's currency, case-insensitive." + }, + { + "name": "start_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date" + }, + "description": "Transfers created on or after this day (UTC), YYYY-MM-DD." + }, + { + "name": "end_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date" + }, + "description": "Transfers created on or before this day (UTC), YYYY-MM-DD." + }, + { + "name": "counterparty", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The `id` of a directory entry or Didit network member from GET /v3/travel-rule/vasps/: your transfers with that counterparty. A value that is not a valid id answers 400." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 1000000 + }, + "description": "Page size, default 50." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000000 + }, + "description": "Rows to skip." + } + ], + "responses": { + "200": { + "description": "A page of unmatched deposits.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleTransferRow" + } + } + } + }, + "example": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "id": "7d3a1c55-0b9e-4d52-8a3c-1f6e2b9d4a08", + "transaction_id": "c41f9b2e-5a8d-4e0c-9f17-3b6d8a2e5c91", + "txn_id": "dep-2026-10-20-0007", + "txn_date": "2026-10-19T08:41:12Z", + "kind": "deposit", + "direction": "INBOUND", + "status": "AWAITING_COUNTERPARTY", + "rail": "TRP", + "asset": "ETH", + "amount": "1.25", + "originator_vasp": "", + "beneficiary_vasp": "Example Counterparty VASP", + "originator_wallet": "0x77ccff0cc624a53cd8b8d8703ced826710211ec7", + "beneficiary_wallet": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb", + "originator_memo": "", + "beneficiary_memo": "", + "originator_wallet_type": "vasp", + "beneficiary_wallet_type": "vasp", + "matched_transaction_id": null, + "deadline_at": null, + "created_at": "2026-10-19T08:41:13Z" + } + ] + } + } + } + }, + "400": { + "description": "A filter value is not valid.", + "content": { + "application/json": { + "example": { + "start_date": [ + "Use the YYYY-MM-DD format." + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.travel_rule.unmatched_deposits.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.travelRule.unmatchedDeposits.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.TravelRule.UnmatchedDeposits.ListAutoPaging(ctx, didit.TravelRuleUnmatchedDepositsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.travelRule().unmatchedDeposits().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->unmatchedDeposits->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.unmatched_deposits.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.TravelRule.UnmatchedDeposits.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/travel-rule/unmatched/{transaction_uuid}/match/": { + "post": { + "summary": "Match a Travel Rule deposit to a received message", + "operationId": "matchUnmatchedTravelRuleDeposit", + "x-didit-sdk": { + "resource": "travel_rule.unmatched_deposits", + "method": "match", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Pair an inbound deposit that is still waiting for originator data with the Travel Rule message the originating VASP sent for it. The deposit takes the message's originator data, counterparty VASP and rail, and both transfers record the match on their timelines. `transaction_uuid` is the deposit's `transaction_id` from `GET /v3/travel-rule/unmatched/`. `transaction_id` in the body is the received message's `transaction_id`, from a row of `GET /v3/travel-rule/transfers/?kind=message`; the transfer's own `id` is not found. The message must be for the same asset as the deposit. A deposit or message deleted from the Console answers `404`; one that has already moved on (matched, or no longer waiting) answers `409`.", + "parameters": [ + { + "name": "transaction_uuid", + "in": "path", + "required": true, + "description": "The `transaction_id` of the unmatched deposit.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleManualMatchRequest" + }, + "example": { + "transaction_id": "e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24" + } + } + } + }, + "responses": { + "200": { + "description": "Both transfers after the match.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleManualMatchResponse" + }, + "example": { + "deposit": { + "id": "7d3a1c55-0b9e-4d52-8a3c-1f6e2b9d4a08", + "transaction_id": "c41f9b2e-5a8d-4e0c-9f17-3b6d8a2e5c91", + "txn_id": "dep-2026-10-20-0007", + "txn_date": "2026-10-19T08:41:12Z", + "kind": "deposit", + "direction": "INBOUND", + "status": "AWAITING_COUNTERPARTY", + "rail": "TRP", + "asset": "ETH", + "amount": "1.25", + "originator_vasp": "Example Counterparty VASP", + "beneficiary_vasp": "Example Counterparty VASP", + "originator_wallet": "0x77ccff0cc624a53cd8b8d8703ced826710211ec7", + "beneficiary_wallet": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb", + "originator_memo": "", + "beneficiary_memo": "", + "originator_wallet_type": "vasp", + "beneficiary_wallet_type": "vasp", + "matched_transaction_id": "e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24", + "deadline_at": null, + "created_at": "2026-10-19T08:41:13Z" + }, + "message": { + "id": "2b8e6f10-9c3a-47d1-b5e2-0a4c7d9f3e66", + "transaction_id": "e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24", + "txn_id": "msg-2026-10-20-0003", + "txn_date": "2026-10-19T08:41:12Z", + "kind": "message", + "direction": "INBOUND", + "status": "COMPLETED", + "rail": "TRP", + "asset": "ETH", + "amount": "1.25", + "originator_vasp": "Origin CASP SL", + "beneficiary_vasp": "Example Counterparty VASP", + "originator_wallet": "0x77ccff0cc624a53cd8b8d8703ced826710211ec7", + "beneficiary_wallet": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb", + "originator_memo": "", + "beneficiary_memo": "", + "originator_wallet_type": "vasp", + "beneficiary_wallet_type": "vasp", + "matched_transaction_id": null, + "deadline_at": "2026-10-21T08:41:13Z", + "created_at": "2026-10-19T08:41:13Z" + } + } + } + } + }, + "400": { + "description": "Validation error: `transaction_id` is missing, is the deposit itself, is not a received Travel Rule message, or is for a different asset than the deposit; or the transaction in the path is not an inbound deposit.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Error": { + "value": { + "transaction_id": [ + "The travel rule message is for a different asset than the deposit." + ] + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "description": "Unknown deposit or message, or one of them was deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No TravelRuleTransfer matches the given query." + } + } + } + }, + "409": { + "description": "The deposit is no longer waiting for originator data, or the message is already matched to a deposit.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Error": { + "value": { + "detail": "This deposit is no longer waiting for originator data." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.unmatched_deposits.match(\n \"11111111-2222-3333-4444-555555555555\",\n transaction_id=\"e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.unmatchedDeposits.match(\"11111111-2222-3333-4444-555555555555\", {\n transaction_id: \"e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.UnmatchedDeposits.Match(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleManualMatchRequest{\n\tTransactionID: \"e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleManualMatchRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().unmatchedDeposits().match(\"11111111-2222-3333-4444-555555555555\", TravelRuleManualMatchRequest.builder()\n .transactionId(\"e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->unmatchedDeposits->match('11111111-2222-3333-4444-555555555555', [\n 'transaction_id' => 'e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.unmatched_deposits.match(\n \"11111111-2222-3333-4444-555555555555\",\n transaction_id: \"e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.UnmatchedDeposits.MatchAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleManualMatchRequest\n{\n TransactionId = \"e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + } + }, + "components": { + "schemas": { + "DocumentAIDetectedCode": { + "type": "object", + "required": [ + "type", + "page_number", + "position", + "x", + "y", + "width", + "height", + "page_width", + "page_height", + "raw_payload", + "parsed_payload" + ], + "properties": { + "type": { + "type": "string", + "description": "Detected symbology, for example `QR_CODE`, `CODE_128`, `EAN_13`, `DATA_MATRIX`, or `PDF_417`." + }, + "page_number": { + "type": "integer", + "minimum": 1, + "description": "1-based page number." + }, + "position": { + "type": "array", + "nullable": true, + "description": "Detected polygon in rendered-page pixel coordinates. `null` when no reliable location is available.", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "type": "integer" + } + } + }, + "x": { + "type": "integer", + "nullable": true, + "description": "Left edge of the bounding box in rendered-page pixels." + }, + "y": { + "type": "integer", + "nullable": true, + "description": "Top edge of the bounding box in rendered-page pixels." + }, + "width": { + "type": "integer", + "nullable": true, + "description": "Bounding-box width in rendered-page pixels." + }, + "height": { + "type": "integer", + "nullable": true, + "description": "Bounding-box height in rendered-page pixels." + }, + "page_width": { + "type": "integer", + "description": "Rendered page width in pixels." + }, + "page_height": { + "type": "integer", + "description": "Rendered page height in pixels." + }, + "raw_payload": { + "type": "string", + "description": "Raw decoder payload as a base64 string when available; otherwise an empty string." + }, + "parsed_payload": { + "nullable": true, + "description": "Decoded string or structured value. `null` when the code was located but could not be decoded." + } + } + }, + "SessionImportJob": { + "type": "object", + "required": [ + "uuid", + "status", + "import_type", + "source_format", + "source_filename", + "total_rows", + "processed_rows", + "created_count", + "updated_count", + "skipped_count", + "failed_count" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Import job id. Use this value to poll progress and fetch row errors." + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED", + "PAUSED", + "CANCELLED", + "READY" + ] + }, + "import_type": { + "type": "string", + "enum": [ + "user_verification", + "business_verification", + "status_rules", + "transactions" + ] + }, + "source_format": { + "type": "string", + "enum": [ + "csv", + "ndjson" + ] + }, + "source_filename": { + "type": "string" + }, + "total_rows": { + "type": "integer", + "format": "int64" + }, + "processed_rows": { + "type": "integer", + "format": "int64" + }, + "checkpoint_row": { + "type": "integer", + "format": "int64" + }, + "created_count": { + "type": "integer", + "format": "int64" + }, + "updated_count": { + "type": "integer", + "format": "int64" + }, + "skipped_count": { + "type": "integer", + "format": "int64" + }, + "failed_count": { + "type": "integer", + "format": "int64" + }, + "total_media_bytes": { + "type": "integer", + "format": "int64" + }, + "last_error": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "started_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "state": { + "type": "string", + "enum": [ + "ready", + "queued", + "running", + "paused", + "completed", + "errors", + "failed", + "cancelled" + ], + "description": "The job's state as the console shows it; `errors` is a completed job with rejected rows." + }, + "source": { + "type": "string", + "enum": [ + "file", + "api", + "provider" + ] + }, + "provider": { + "type": "string", + "description": "Migration source; empty for a file or API import." + }, + "provider_options": { + "type": "object" + }, + "credential_hint": { + "type": "string", + "description": "The first characters of the key the migration holds. The key itself is never returned." + }, + "credentials_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resumes_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When a job paused by the provider's rate limit resumes by itself." + }, + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "is_dry_run": { + "type": "boolean" + }, + "dry_run_summary": { + "type": "object", + "description": "What a dry run found: `total`, `by_status`, `already_in_didit`, `same_person`, `not_finished`, `failed`, `unmapped_statuses`." + }, + "face_templates_enabled": { + "type": "boolean" + }, + "lawful_basis_confirmed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "not_finished_count": { + "type": "integer", + "format": "int64" + }, + "same_person_count": { + "type": "integer", + "format": "int64" + }, + "face_templates_count": { + "type": "integer", + "format": "int64" + }, + "face_duplicates_count": { + "type": "integer", + "format": "int64" + }, + "face_blocklist_hits_count": { + "type": "integer", + "format": "int64" + }, + "created_by_name": { + "type": "string", + "nullable": true + }, + "created_by_email": { + "type": "string", + "nullable": true + } + } + }, + "SessionImportError": { + "type": "object", + "required": [ + "uuid", + "row_number", + "error", + "raw_row" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "row_number": { + "type": "integer", + "format": "int64" + }, + "external_id": { + "type": "string", + "nullable": true + }, + "error": { + "type": "string" + }, + "raw_row": { + "type": "object", + "additionalProperties": true + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BrandingCustomizationUpdate": { + "type": "object", + "description": "Partial branding update body. Provide only the fields you want to change. Hex colors accept `#RRGGBB` or `#RRGGBBAA`. Custom domain and white-label email are not accepted here.", + "properties": { + "color_primary": { + "type": "string", + "description": "Headers / primary text color." + }, + "color_secondary": { + "type": "string", + "description": "Paragraph / secondary text color." + }, + "color_background": { + "type": "string", + "description": "Page background color." + }, + "color_panel": { + "type": "string", + "description": "Panel / accent color." + }, + "color_panel_10": { + "type": "string", + "description": "Panel color at 10% opacity." + }, + "color_on_panel_1": { + "type": "string", + "description": "Support text color on panels." + }, + "color_on_panel_2": { + "type": "string", + "description": "Pill / badge color on panels." + }, + "color_on_background": { + "type": "string", + "description": "Text color on background." + }, + "color_button_1": { + "type": "string", + "description": "Primary button color." + }, + "color_button_2": { + "type": "string", + "description": "Secondary button color." + }, + "color_button_text_1": { + "type": "string", + "description": "Primary button text color." + }, + "color_button_text_2": { + "type": "string", + "description": "Secondary button text color." + }, + "color_pill_text": { + "type": "string", + "description": "Text color for pills, inputs and selectors." + }, + "border_radius_panel": { + "type": "integer", + "minimum": 0, + "description": "Panel corner radius in pixels." + }, + "border_radius_buttons": { + "type": "integer", + "minimum": 0, + "description": "Button corner radius in pixels." + }, + "font_family": { + "type": "string", + "description": "Google Font family name (e.g. `Inter`, `AR One Sans`)." + }, + "font_weight": { + "type": "string", + "description": "Font weight available for the selected family (e.g. `regular`, `500`, `700`)." + }, + "app_public_name": { + "type": "string", + "nullable": true, + "description": "Public name shown in the verification flow." + }, + "privacy_policy_url": { + "type": "string", + "nullable": true, + "description": "Privacy policy URL shown to the end user." + }, + "skip_welcome_screen": { + "type": "boolean", + "description": "Skip the initial welcome screen." + }, + "disable_login_with_didit": { + "type": "boolean", + "description": "Continue as guest instead of showing the login-with-Didit screen. Cannot be set back to `false` while a custom domain is configured (400)." + }, + "hide_progress_bar": { + "type": "boolean", + "description": "Hide the progress bar during the flow." + }, + "callback_seconds": { + "type": "integer", + "minimum": 0, + "description": "Delay (seconds) before redirecting to the callback URL." + }, + "delete_logo_square": { + "type": "boolean", + "description": "When `true`, removes the current square logo." + }, + "delete_logo_rectangular": { + "type": "boolean", + "description": "When `true`, removes the current rectangular logo." + }, + "delete_logo_rectangular_dark": { + "type": "boolean", + "description": "When `true`, removes the current dark-mode logo, and `logo_rectangular_dark` then reads `null`. If the same multipart request also uploads `image_rectangular_dark`, the upload wins." + }, + "delete_favicon": { + "type": "boolean", + "description": "When `true`, removes the current favicon." + } + } + }, + "BrandingCustomization": { + "type": "object", + "description": "White-label branding for an application: colors, fonts, radii, logo URLs and UI/UX options. Logo URLs are read-only (set them by uploading images). Custom domain and white-label email are not included.", + "properties": { + "color_primary": { + "type": "string" + }, + "color_secondary": { + "type": "string" + }, + "color_background": { + "type": "string" + }, + "color_panel": { + "type": "string" + }, + "color_panel_10": { + "type": "string" + }, + "color_on_panel_1": { + "type": "string" + }, + "color_on_panel_2": { + "type": "string" + }, + "color_on_background": { + "type": "string" + }, + "color_button_1": { + "type": "string" + }, + "color_button_2": { + "type": "string" + }, + "color_button_text_1": { + "type": "string" + }, + "color_button_text_2": { + "type": "string" + }, + "color_pill_text": { + "type": "string" + }, + "border_radius_panel": { + "type": "integer" + }, + "border_radius_buttons": { + "type": "integer" + }, + "font_family": { + "type": "string" + }, + "font_weight": { + "type": "string" + }, + "font_url": { + "type": "string", + "nullable": true, + "description": "Read-only. Resolved Google Fonts stylesheet URL for the selected family." + }, + "logo_square": { + "type": "string", + "nullable": true, + "description": "Read-only URL of the square logo. Upload via `image_square`." + }, + "logo_rectangular": { + "type": "string", + "nullable": true, + "description": "Read-only URL of the rectangular logo. Upload via `image_rectangular`." + }, + "logo_rectangular_dark": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Read-only. Temporary signed URL of the rectangular logo for dark mode, shown on a dark background. `null` when no dark logo is set or rectangular logos are not shown for your account. Upload it with `image_rectangular_dark`." + }, + "favicon": { + "type": "string", + "nullable": true, + "description": "Read-only URL of the favicon. Upload via `image_favicon`." + }, + "app_public_name": { + "type": "string", + "nullable": true + }, + "privacy_policy_url": { + "type": "string", + "nullable": true + }, + "skip_welcome_screen": { + "type": "boolean" + }, + "disable_login_with_didit": { + "type": "boolean" + }, + "hide_progress_bar": { + "type": "boolean" + }, + "callback_seconds": { + "type": "integer" + } + } + }, + "WebhookEvent": { + "type": "string", + "description": "Supported webhook event name. Use these exact strings in `subscribed_events`; unsupported values are rejected.", + "enum": [ + "status.updated", + "data.updated", + "user.status.updated", + "user.data.updated", + "business.status.updated", + "business.data.updated", + "activity.created", + "transaction.created", + "transaction.status.updated", + "travel_rule.status.updated", + "workflow.compliance_changed", + "transaction_rule_suggestions.created", + "auth.signup", + "auth.login", + "auth.login_failed", + "auth.password_reset_requested", + "auth.password_reset_completed", + "auth.mfa_changed", + "auth.new_device", + "auth.account_locked", + "investigator.report.ready", + "investigator.decision.automated", + "investigator.decision.overridden", + "investigator.decision.contested", + "investigator.question.raised", + "investigator.automation.paused", + "investigator.rfi.answered", + "investigator.rfi.overdue", + "investigator.drift.detected", + "investigator.sla.breached", + "investigator.qa.failed", + "investigator.external.report.ready" + ] + }, + "WebhookSubscribedEvents": { + "type": "array", + "description": "Event filter for this webhook destination. Didit delivers only webhooks whose event type exactly matches one of these values — there is no wildcard subscription. When sent, the list must contain at least one valid event (an explicit `[]` is rejected with 400); a destination whose list is empty (field omitted at create) receives nothing. On update the list is replaced wholesale, never merged.", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "example": [ + "status.updated", + "data.updated" + ] + }, + "PhoneVerificationSendResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the verification. A `Retry` send returns the same `request_id` as the original send. This id appears in the Business Console, is returned again by a finalized `POST /v3/phone/check/`, and can be passed to `GET /v3/session/{sessionId}/decision/`." + }, + "status": { + "type": "string", + "enum": [ + "Success", + "Retry", + "Blocked" + ], + "description": "`Success` — OTP sent to a new verification. `Retry` — OTP re-sent for the pending verification created by a previous send. `Blocked` — the anti-fraud layer refused to send; the verification is immediately finalized as `Declined` and nothing is billed." + }, + "reason": { + "type": "string", + "nullable": true, + "description": "Why a send was `Blocked` (e.g., `repeated_attempts`, `suspicious`, `spam`). `null` on `Success` and `Retry`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` stored on the session (from the first send)." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` stored on the session (from the first send)." + } + } + }, + "PhoneVerificationCheckResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "On `Approved`/`Declined`: the session id of the matched verification — identical to the `request_id` returned by `POST /v3/phone/send/`. On `Failed` and `Expired or Not Found`: a random one-off UUID that cannot be looked up later." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "Failed", + "Expired or Not Found" + ], + "description": "`Approved` — correct code, no declining risk. `Declined` — terminal: a declining risk matched or the attempt budget (3) was exhausted. `Failed` — wrong code, attempts remaining. `Expired or Not Found` — no pending verification for this number in the last 5 minutes." + }, + "message": { + "type": "string", + "description": "Human-readable explanation of the outcome, including the number of attempts remaining after a wrong code." + }, + "phone": { + "type": "object", + "nullable": true, + "description": "Full phone report. Present (non-null) only on finalized outcomes (`Approved`/`Declined`); `null` on `Failed` and absent on `Expired or Not Found`.", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Not Finished", + "Expired" + ], + "description": "Final status of the phone verification. Matches the top-level `status` for finalized checks." + }, + "phone_number_prefix": { + "type": "string", + "description": "Country calling code with leading `+` (e.g., `+1`).", + "example": "+1" + }, + "phone_number": { + "type": "string", + "description": "National number without the country calling code.", + "example": "4155552671" + }, + "full_number": { + "type": "string", + "description": "Complete number in E.164 format.", + "example": "+14155552671" + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of the number.", + "example": "US" + }, + "country_name": { + "type": "string", + "description": "Full country name derived from `country_code`.", + "example": "United States" + }, + "carrier": { + "type": "object", + "description": "Current network serving the number, from the carrier lookup run at finalization.", + "properties": { + "name": { + "type": "string", + "description": "Carrier name. Empty string when the lookup could not identify it." + }, + "type": { + "type": "string", + "description": "Line type reported by the lookup: `mobile`, `fixed_line`, `voip`, `isp`, `vpn`, `toll_free`, `premium_rate`, `pager`, `payphone`, `satellite`, `service`, `shared_cost`, `short_codes_commercial`, `calling_cards`, `local_rate`, `universal_access`, `voice_mail`, `other`, or `unknown`." + } + } + }, + "is_disposable": { + "type": "boolean", + "description": "`true` when the number belongs to a temporary/burner number service." + }, + "is_virtual": { + "type": "boolean", + "description": "`true` when the line type is virtual (`voip`, `isp`, or `vpn`)." + }, + "verification_method": { + "type": "string", + "description": "Channel that actually delivered the OTP once delivery is confirmed (e.g., `whatsapp`, `sms`); until then, the requested channel." + }, + "verification_attempts": { + "type": "integer", + "description": "Number of OTP sends for this verification (1, or 2 after a retry)." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the correct code was entered. `null` if the verification was never approved." + }, + "warnings": { + "type": "array", + "description": "Risk warnings recorded during the verification (duplicate, disposable, VoIP, attempts exceeded, blocklist, etc.). Empty array when there are none.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature that produced the warning. Always `PHONE` here." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk code." + }, + "additional_data": { + "type": "object", + "nullable": true, + "description": "Risk-specific context (e.g., the duplicated session id for duplicate risks). `null` when the risk carries no extra data." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` risks decline the verification, `warning` flags it for review, `information` is recorded without affecting the status." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "lifecycle": { + "type": "array", + "description": "Chronological audit trail of the verification. Event `type` is one of `PHONE_VERIFICATION_MESSAGE_SENT`, `PHONE_VERIFICATION_RETRY_MESSAGE_SENT`, `PHONE_VERIFICATION_BLOCKED`, `PHONE_DELIVERY_DELIVERED`, `PHONE_DELIVERY_UNDELIVERABLE`, `VALID_CODE_ENTERED`, `INVALID_CODE_ENTERED`, `PHONE_VERIFICATION_APPROVED`, `PHONE_VERIFICATION_DECLINED`, `PHONE_VERIFICATION_IN_REVIEW`, `PHONE_VERIFICATION_EXPIRED`.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Event type." + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the event happened." + }, + "details": { + "type": "object", + "nullable": true, + "description": "Event-specific payload. Send events: `{status, reason, channel, actual_channel}` (`actual_channel` is filled once delivery is confirmed). Delivery events: `{channel, status}`. Code-entry events: `{code_tried, status}`. Final-status events: `null`, or `{reason}` when declined or in review." + }, + "fee": { + "type": "number", + "description": "USD amount billed for this event. Non-zero only on send events (the delivery price); 0 for code entries, delivery confirmations, and status events." + } + } + } + }, + "matches": { + "type": "array", + "description": "Other sessions of your application (KYC, KYB, or API) where the same phone number was used by a different user (different `vendor_data`). Up to 5 entries, a synthetic list-entry blocklist match first when present (session-derived matches keep query order). Empty array when there are no matches.", + "items": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the other verification that used this phone number." + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of that session." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of that session." + }, + "verification_date": { + "type": "string", + "description": "When that session was created (`YYYY-MM-DDTHH:MM:SSZ`)." + }, + "phone_number": { + "type": "string", + "description": "The matched phone number." + }, + "status": { + "type": "string", + "description": "Status of that session (e.g., `Approved`, `Declined`, `In Progress`)." + }, + "is_blocklisted": { + "type": "boolean", + "description": "Whether the phone number is on your application's blocklist." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "For API-type sessions, the standalone API that created it (e.g., `PHONE_VERIFICATION`); `null` for regular verification sessions." + }, + "source": { + "type": "string", + "description": "Where the match was found. Usually `session`; a blocklist hit via an organization list entry prepends a synthetic match with `source: \"list_entry\"` and null `session_id`/`session_number`." + } + } + } + }, + "phone_social": { + "$ref": "#/components/schemas/PhoneSocialFootprint" + }, + "phone_intelligence": { + "allOf": [ + { + "$ref": "#/components/schemas/PhoneIntelligence" + } + ], + "description": "Phone intelligence signals. Present only when `enable_enrichment` was `true` on the check." + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of the matched verification's session. `null` on `Expired or Not Found`." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "`metadata` of the matched verification's session. `null` on `Expired or Not Found`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this check response." + } + } + }, + "EmailVerificationSendResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the verification. A `Retry` send returns the same `request_id` as the original send. This id appears in the Business Console, is returned again by a finalized `POST /v3/email/check/`, and can be passed to `GET /v3/session/{sessionId}/decision/`." + }, + "status": { + "type": "string", + "enum": [ + "Success", + "Retry", + "Undeliverable" + ], + "description": "`Success` — OTP emailed to a new verification (billed). `Retry` — fresh OTP emailed for the pending verification created by a previous send (free). `Undeliverable` — the address failed deliverability validation or the message could not be sent; the verification is immediately finalized as `Declined` and nothing is billed." + }, + "reason": { + "type": "string", + "nullable": true, + "enum": [ + "email_can_not_be_delivered", + null + ], + "description": "`email_can_not_be_delivered` when `status` is `Undeliverable`; `null` otherwise." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` stored on the session (from the first send)." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` stored on the session (from the first send)." + } + } + }, + "EmailVerificationCheckResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "On `Approved`/`Declined`: the session id of the matched verification — identical to the `request_id` returned by `POST /v3/email/send/`. On `Failed` and `Expired or Not Found`: a random one-off UUID that cannot be looked up later." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "Failed", + "Expired or Not Found" + ], + "description": "`Approved` — correct code, no declining risk. `Declined` — terminal: a declining risk matched or the attempt budget (3) was exhausted. `Failed` — wrong code, attempts remaining. `Expired or Not Found` — no pending verification for this address in the last 5 minutes." + }, + "message": { + "type": "string", + "description": "Human-readable explanation of the outcome, including the number of attempts remaining after a wrong code." + }, + "email": { + "type": "object", + "nullable": true, + "description": "Full email report. Present (non-null) only on finalized outcomes (`Approved`/`Declined`); `null` on `Failed` and absent on `Expired or Not Found`.", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Not Finished", + "Expired" + ], + "description": "Final status of the email verification. Matches the top-level `status` for finalized checks." + }, + "email": { + "type": "string", + "format": "email", + "description": "The verified email address.", + "example": "alice@example.com" + }, + "is_breached": { + "type": "boolean", + "description": "`true` when the address appears in known data breaches (breach intelligence lookup run at finalization)." + }, + "breaches": { + "type": "array", + "description": "Up to 5 most recent known breaches containing this address. Empty array (or omitted) when `is_breached` is `false`.", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Breach identifier." + }, + "domain": { + "type": "string", + "description": "Domain of the breached service." + }, + "breach_date": { + "type": "string", + "format": "date", + "description": "Date the breach occurred." + }, + "breach_emails_count": { + "type": "integer", + "description": "Total number of accounts exposed in the breach." + }, + "description": { + "type": "string", + "description": "Human-readable description of the breach (may contain HTML markup)." + }, + "logo_path": { + "type": "string", + "description": "URL of the breached service's logo." + }, + "data_classes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Categories of data exposed, in snake_case (e.g., `email_addresses`, `passwords`)." + }, + "is_verified": { + "type": "boolean", + "description": "Whether the breach has been verified as legitimate." + } + } + } + }, + "is_disposable": { + "type": "boolean", + "description": "`true` when the domain belongs to a known disposable/temporary-mail provider." + }, + "is_undeliverable": { + "type": "boolean", + "description": "`true` when the address cannot receive mail (failed syntax or DNS/MX validation, or the message bounced)." + }, + "verification_attempts": { + "type": "integer", + "description": "Number of OTP sends for this verification (1, or 2 after a retry)." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the correct code was entered. `null` if the verification was never approved." + }, + "warnings": { + "type": "array", + "description": "Risk warnings recorded during the verification (duplicate, breached, disposable, undeliverable, attempts exceeded, blocklist, etc.). Empty array when there are none.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature that produced the warning. Always `EMAIL` here." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk code." + }, + "additional_data": { + "type": "object", + "nullable": true, + "description": "Risk-specific context (e.g., the duplicated session id for duplicate risks). `null` when the risk carries no extra data." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` risks decline the verification, `warning` flags it for review, `information` is recorded without affecting the status." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "lifecycle": { + "type": "array", + "description": "Chronological audit trail of the verification. Event `type` is one of `EMAIL_VERIFICATION_MESSAGE_SENT`, `EMAIL_VERIFICATION_RETRY_MESSAGE_SENT`, `VALID_CODE_ENTERED`, `INVALID_CODE_ENTERED`, `EMAIL_VERIFICATION_APPROVED`, `EMAIL_VERIFICATION_DECLINED`, `EMAIL_VERIFICATION_IN_REVIEW`, `EMAIL_VERIFICATION_EXPIRED`.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Event type." + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the event happened." + }, + "details": { + "type": "object", + "nullable": true, + "description": "Event-specific payload. Send events: `{status, reason}`. Code-entry events: `{code_tried, status}`. Final-status events: `null`, or `{reason}` when declined or in review." + }, + "fee": { + "type": "number", + "description": "USD amount billed for this event. Non-zero only on the first successful send; 0 for retries, code entries, and status events." + } + } + } + }, + "matches": { + "type": "array", + "description": "Other sessions of your application (KYC, KYB, or API) where the same email address was used by a different user (different `vendor_data`). Up to 5 entries; a synthetic list-entry blocklist match is prepended when present (and only when no session-derived blocklisted email exists) — session-derived matches keep created_at order. Empty array when there are no matches.", + "items": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the other verification that used this email address." + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of that session." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of that session." + }, + "verification_date": { + "type": "string", + "description": "When that session was created (`YYYY-MM-DDTHH:MM:SSZ`)." + }, + "email": { + "type": "string", + "description": "The matched email address." + }, + "status": { + "type": "string", + "description": "Status of that session (e.g., `Approved`, `Declined`, `In Progress`)." + }, + "is_blocklisted": { + "type": "boolean", + "description": "Whether the email address is on your application's blocklist." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "For API-type sessions, the standalone API that created it (e.g., `PHONE_VERIFICATION`); `null` for regular verification sessions." + }, + "source": { + "type": "string", + "description": "Where the match was found. Usually `session`; a blocklist hit via an organization list entry prepends a synthetic match with `source: \"list_entry\"` and null `session_id`/`session_number`." + } + } + } + }, + "email_social": { + "$ref": "#/components/schemas/EmailSocialFootprint" + }, + "email_intelligence": { + "allOf": [ + { + "$ref": "#/components/schemas/EmailIntelligence" + } + ], + "description": "Email intelligence signals. Present only when `enable_enrichment` was `true` on the check." + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of the matched verification's session. `null` on `Expired or Not Found`." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "`metadata` of the matched verification's session. `null` on `Expired or Not Found`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this check response." + } + } + }, + "WorkflowListItem": { + "type": "object", + "description": "One workflow version in the list view.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Per-version UUID. Use as `{settings_uuid}` for get/update/delete, and as `workflow_id` when creating sessions." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable group ID shared by every version of the same workflow. Equals `uuid` for version 1." + }, + "workflow_label": { + "type": "string", + "description": "Display name for the workflow." + }, + "workflow_type": { + "type": "string", + "nullable": true, + "description": "Base type for simple workflows: `kyc`, `kyb`, `adaptive_age_verification`, `biometric_authentication`, etc. `null` for graph-based workflows without a KYB feature." + }, + "is_default": { + "type": "boolean", + "description": "Whether this is the default workflow for new sessions." + }, + "is_archived": { + "type": "boolean" + }, + "is_white_label_enabled": { + "type": "boolean", + "description": "Whether white-label customization applies to sessions created with this workflow." + }, + "total_price": { + "type": "number", + "description": "Fixed price per verification in USD (JSON number, excludes variable-priced features such as DATABASE_VALIDATION and PHONE_VERIFICATION)." + }, + "min_price": { + "type": "number", + "description": "Cheapest fixed price across workflow-graph paths, in USD." + }, + "max_price": { + "type": "number", + "description": "Most expensive fixed price across workflow-graph paths, in USD." + }, + "features": { + "type": "string", + "description": "Enabled features as a single `\" + \"`-joined string, e.g. `\"OCR + LIVENESS + FACE_MATCH\"`. Split on `\" + \"` to get a list." + }, + "is_simple_workflow": { + "type": "boolean", + "description": "True when the workflow has no custom workflow graph." + }, + "is_editable": { + "type": "boolean", + "description": "True when this version is a `draft` (drafts are editable in place; published versions are immutable in the Console)." + }, + "workflow_url": { + "type": "string", + "format": "uri", + "description": "Public shareable verification URL for this workflow (`https://verify.didit.me/u/`)." + }, + "max_retry_attempts": { + "type": "integer", + "description": "Maximum retry attempts allowed after a declined verification (0-10)." + }, + "retry_window_days": { + "type": "integer", + "nullable": true, + "description": "Rolling window in days used to count retries (1-365). `null` = all-time limit." + }, + "session_expiration_time": { + "type": "integer", + "description": "Seconds before an unfinished session expires (3600-2419200)." + }, + "version": { + "type": "integer", + "description": "Version number within the `workflow_id` group, starting at 1." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Version status. New sessions use the latest `published` version of a group." + }, + "has_draft": { + "type": "boolean", + "description": "True when any version in this `workflow_id` group is currently a draft." + }, + "created_by": { + "type": "object", + "nullable": true, + "description": "Who created this version: `{name, email, type}` where `type` is `user` (Console user, `email` set) or `api_key` (`name` is the client id, `email` is null). `null` when no creator was recorded.", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string", + "nullable": true + }, + "type": { + "type": "string", + "enum": [ + "user", + "api_key" + ] + } + } + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "SimpleWorkflowRequest": { + "type": "object", + "description": "Create a simple linear workflow. The API converts this feature list into a node-based workflow graph internally. STRICT field whitelist: any key not listed here (including `workflow_type`) is rejected with a 400.", + "required": [ + "features" + ], + "properties": { + "workflow_label": { + "type": "string", + "maxLength": 50, + "default": "ID Verification", + "description": "Display name for the workflow (max 50 characters). Defaults to `\"ID Verification\"` when omitted — always set it so workflows stay distinguishable.", + "example": "Standard KYC" + }, + "is_default": { + "type": "boolean", + "description": "Set this workflow as the default for new sessions." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Omit this field to create a published workflow ready for sessions. Use `draft` only when you want to save without publishing." + }, + "features": { + "type": "array", + "minItems": 1, + "description": "Verification features in execution order. The API links each item to the next one and adds one final status node automatically.", + "items": { + "$ref": "#/components/schemas/SimpleWorkflowFeature" + } + }, + "is_white_label_enabled": { + "type": "boolean", + "description": "Enable white-label customization for sessions created with this workflow.", + "default": false + }, + "is_desktop_allowed": { + "type": "boolean", + "description": "Allow the verification flow to run on desktop browsers.", + "default": false + }, + "max_retry_attempts": { + "type": "integer", + "minimum": 0, + "maximum": 10, + "default": 3, + "description": "Maximum retry attempts allowed after a declined verification. `0` blocks the user after the first decline." + }, + "retry_window_days": { + "type": "integer", + "minimum": 1, + "maximum": 365, + "nullable": true, + "default": 7, + "description": "Rolling window in days used to count retries. `null` enforces an all-time limit." + }, + "face_liveness_max_attempts": { + "type": "integer", + "minimum": 1, + "maximum": 3, + "default": 3, + "description": "Maximum liveness submissions per session. Default 3: one initial attempt plus two retries. This value is also copied into generated liveness and age-estimation workflow nodes unless the feature-level config overrides it." + }, + "face_match_max_attempts": { + "type": "integer", + "minimum": 1, + "maximum": 3, + "default": 3, + "description": "Maximum face-match submissions per session. Default 3: one initial attempt plus two retries. This value is also copied into generated face-match workflow nodes unless the feature-level config overrides it." + }, + "session_expiration_time": { + "type": "integer", + "minimum": 3600, + "maximum": 2419200, + "default": 604800, + "description": "Seconds before an unfinished session expires. Minimum 1 hour (3600), maximum 4 weeks (2419200)." + } + } + }, + "SimpleWorkflowUpdateRequest": { + "type": "object", + "description": "Update workflow metadata or replace the full linear feature list. STRICT field whitelist: any key not listed here is rejected with a 400.", + "properties": { + "workflow_label": { + "type": "string", + "maxLength": 50, + "description": "Display name for the workflow (max 50 characters)." + }, + "is_default": { + "type": "boolean", + "description": "Set this workflow as the default for new sessions." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Controls versioning. Omit to update the current version in place (drafts auto-publish). `published` on an already-published version creates and returns a NEW version. `draft` is only valid while the version is still a draft." + }, + "features": { + "type": "array", + "minItems": 1, + "description": "If provided, this replaces the full feature order and configuration.", + "items": { + "$ref": "#/components/schemas/SimpleWorkflowFeature" + } + }, + "is_white_label_enabled": { + "type": "boolean", + "description": "Enable white-label customization for sessions created with this workflow." + }, + "is_desktop_allowed": { + "type": "boolean", + "description": "Allow the verification flow to run on desktop browsers." + }, + "max_retry_attempts": { + "type": "integer", + "minimum": 0, + "maximum": 10, + "description": "Maximum retry attempts allowed after a declined verification. `0` blocks the user after the first decline." + }, + "retry_window_days": { + "type": "integer", + "minimum": 1, + "maximum": 365, + "nullable": true, + "description": "Rolling window in days used to count retries. `null` enforces an all-time limit." + }, + "face_liveness_max_attempts": { + "type": "integer", + "minimum": 1, + "maximum": 3, + "description": "Maximum liveness submissions per session. This value is also copied into generated liveness and age-estimation workflow nodes unless the feature-level config overrides it." + }, + "face_match_max_attempts": { + "type": "integer", + "minimum": 1, + "maximum": 3, + "description": "Maximum face-match submissions per session. This value is also copied into generated face-match workflow nodes unless the feature-level config overrides it." + }, + "session_expiration_time": { + "type": "integer", + "minimum": 3600, + "maximum": 2419200, + "description": "Seconds before an unfinished session expires. Minimum 1 hour (3600), maximum 4 weeks (2419200)." + } + } + }, + "SimpleWorkflowFeature": { + "type": "object", + "required": [ + "feature" + ], + "properties": { + "feature": { + "type": "string", + "description": "Feature to run. Use the uppercase values shown here.", + "enum": [ + "OCR", + "NFC", + "LIVENESS", + "FACE_MATCH", + "PROOF_OF_ADDRESS", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "DATABASE_VALIDATION", + "AML", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "SIGNATURE" + ] + }, + "config": { + "type": "object", + "description": "Feature-specific settings. For `QUESTIONNAIRE`, include `questionnaire_uuid` with the `questionnaire_id` returned by the questionnaire create endpoint. For `SIGNATURE`, a workflow that goes live needs a document to sign: set `signature_documents_source` to `session` or `both` so each session can bring its own, or send `status: \"draft\"` and add the documents before you publish. `signature_level: \"aes\"` is refused with `aes_not_available` while `signature_availability.aes` is `false` on the workflow. For `OCR`, `methods` selects which ID Verification methods each country may use (document capture, non-doc lookup against a government or other authoritative source, and digital identity wallets); omit it and every country is document capture only. For `SIGNATURE`, see [E-signature settings](/management-api/workflows/feature-configs#e-signature): `signature_level` accepts `simple` (the default, no ID verification, email or phone needed) and `aes` for now, and workflow documents and their fields are managed in the console, so through the API send documents and fields per session with `signature_documents_source: \"session\"`.", + "additionalProperties": true + }, + "label": { + "type": "string", + "description": "Optional internal label for this workflow node." + } + } + }, + "ReviewItem": { + "type": "object", + "description": "One entry in a session's review / activity feed. Status changes carry `previous_status`, `new_status`, `previous_value`, `new_value`, and `changed_fields`; comments carry `comment` and (for Console comments) `mentioned_emails`; the remaining fields are `null` or empty when they do not apply to the activity type.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of this feed entry.", + "example": "de0561bc-5583-4409-9171-1a31a4c8a5f4" + }, + "activity_type": { + "type": "string", + "enum": [ + "COMMENT", + "STATUS_UPDATED", + "KYC_DATA_UPDATED", + "POA_DATA_UPDATED", + "FILE_ADDED", + "FILE_REMOVED", + "AML_ONGOING_MATCH", + "AML_STATUS_UPDATED", + "AML_HIT_STATUS_UPDATED", + "KYB_DATA_UPDATED", + "KYB_DOCUMENT_UPLOADED", + "TAG_ADDED", + "TAG_REMOVED", + "FEATURE_OVERRIDE_PENDING", + "FEATURE_OVERRIDE_APPLIED", + "FEATURE_OVERRIDE_CANCELLED" + ], + "description": "What kind of activity this entry records. `STATUS_UPDATED` rows come from manual decisions (`PATCH /v3/session/{session_id}/update-status/`) and from entries added via `POST …/reviews/` (which default to this type even when no status is recorded). `COMMENT` rows come from Console reviewers; Console detail-page views are also stored as `COMMENT` rows flagged with `metadata.system_event = \"DETAIL_VIEWED\"`. The remaining types track data edits, file changes, tags, and AML monitoring activity. STATUS_UPDATED rows are also written by system actors (e.g. AML ongoing monitoring with actor_type SYSTEM, and automatic feature-status updates), not only by the update-status endpoint and manual review posts. `FEATURE_OVERRIDE_PENDING`, `FEATURE_OVERRIDE_APPLIED` and `FEATURE_OVERRIDE_CANCELLED` record an override of one check's status (`PATCH /v3/session/{session_id}/features/{node_id}/update-status/`): requested to wait for the flow to finish, applied, or cancelled. They carry the check's status in `previous_value` / `new_value` (`{\"status\": ...}`), leave `previous_status` and `new_status` `null`, and name the override in `metadata` (`feature_override_id`, `node_id`, `feature_model`, `forced`, `state`, `cancel_reason`). The entry for the request carries the reviewer's `comment`; an override applied when the flow finishes is recorded with `actor_type` `SYSTEM`.", + "example": "STATUS_UPDATED" + }, + "actor_display": { + "type": "string", + "description": "Human-readable author label, resolved in order: actor's full name → Console account identifier → local part of `actor_email` → `\"API Client\"` (for `API_KEY` actors) → `\"System\"` (for `SYSTEM` actors) → `\"Unknown\"`. Entries created via `POST …/reviews/` always display `\"Unknown\"` because that endpoint records no actor attribution.", + "example": "API Client" + }, + "actor_email": { + "type": "string", + "nullable": true, + "description": "Email of the Console user who performed the action. `null` for API-key and system actors.", + "example": null + }, + "actor_type": { + "type": "string", + "enum": [ + "API_KEY", + "CONSOLE_USER", + "SYSTEM" + ], + "description": "Who performed the action: an API key, a Console user, or Didit itself (`SYSTEM`, e.g. AML ongoing monitoring). Entries created via `POST …/reviews/` carry the default `CONSOLE_USER` regardless of the caller.", + "example": "API_KEY" + }, + "new_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Session status recorded by this entry (for status updates). `null` for comments and data-change entries.", + "example": "Approved" + }, + "previous_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Status the session had before the change. Populated on `STATUS_UPDATED` entries written by the update-status endpoint; `null` on entries added via `POST …/reviews/`. Also populated on system-written STATUS_UPDATED rows.", + "example": "In Review" + }, + "comment": { + "type": "string", + "nullable": true, + "description": "Free-text note attached to the entry, or `null`.", + "example": "Document re-checked manually; address matches." + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + }, + "description": "Console users `@email`-mentioned in the comment. Always empty for entries created via this API — mention processing is a Console-only feature.", + "example": [] + }, + "previous_value": { + "type": "object", + "nullable": true, + "description": "Snapshot of the changed data before the action, e.g. `{\"status\": \"In Review\"}` on a status change. `null` when nothing was changed.", + "example": { + "status": "In Review" + } + }, + "new_value": { + "type": "object", + "nullable": true, + "description": "Snapshot of the changed data after the action, e.g. `{\"status\": \"Approved\"}`. `null` when nothing was changed.", + "example": { + "status": "Approved" + } + }, + "changed_fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the fields modified by this activity (e.g. `[\"status\"]`). Empty for comments and for entries added via `POST …/reviews/`.", + "example": [ + "status" + ] + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Extra context for system-generated entries. Console detail-page views carry `{\"system_event\": \"DETAIL_VIEWED\", \"detail_type\": \"verification\"}` so they can be filtered out of notification logic. A `COMMENT` its author edited in the Console carries `edited_at`, the ISO 8601 time of the last edit (`comment` and `mentioned_emails` then hold the edited text and its mentions). `null` for ordinary entries.", + "example": null + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the entry was written, ISO 8601 UTC.", + "example": "2026-06-12T00:19:31.918735Z" + } + } + }, + "QuestionnaireListItem": { + "type": "object", + "description": "One questionnaire version in the list view.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Per-version UUID. Use as `{questionnaire_uuid}` for get/update/delete and as `questionnaire_uuid` in a workflow's QUESTIONNAIRE feature config." + }, + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, + "default_language": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "is_simple_questionnaire": { + "type": "boolean", + "description": "True for linear questionnaires created through this API; false for graph-based questionnaires built in the Console." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "question_types": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Uppercase element types of the answerable questions, in creation order (e.g. `SHORT_TEXT`, `MULTIPLE_CHOICE`, `FILE_UPLOAD`). Structural `SECTION_HEADER` and `SEPARATOR` elements are excluded. One entry per question, so values can repeat." + }, + "workflow_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of workflows using this questionnaire. Currently always `[]` on this endpoint (populated only in the Console listing)." + }, + "questionnaire_group_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier that groups all versions of the same questionnaire." + }, + "version": { + "type": "integer", + "description": "Version number within the group, starting at 1." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ] + }, + "published_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this version was published. `null` for drafts." + }, + "is_editable": { + "type": "boolean", + "description": "True when this version is a `draft`." + } + } + }, + "SimpleQuestionnaireRequest": { + "type": "object", + "description": "Create a simple linear questionnaire. The API converts `form_elements` into the internal questionnaire graph.", + "required": [ + "title", + "languages", + "form_elements" + ], + "properties": { + "title": { + "type": "string", + "description": "Internal name for the questionnaire." + }, + "description": { + "type": "string", + "nullable": true + }, + "languages": { + "type": "array", + "description": "Languages included in every label. Must include `en`.", + "items": { + "type": "string" + }, + "example": [ + "en" + ] + }, + "default_language": { + "type": "string", + "description": "Default language. Must be included in `languages`.", + "example": "en" + }, + "is_active": { + "type": "boolean", + "default": true + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Omit this field to create a published questionnaire ready for workflows." + }, + "form_elements": { + "type": "array", + "minItems": 1, + "description": "Questions in the exact order users should see them. Branching and conditional fields are not supported.", + "items": { + "$ref": "#/components/schemas/SimpleQuestionnaireElement" + } + } + } + }, + "SimpleQuestionnaireUpdateRequest": { + "type": "object", + "description": "Update questionnaire metadata or replace the full linear `form_elements` list.", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, + "default_language": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ] + }, + "form_elements": { + "type": "array", + "minItems": 1, + "description": "If provided, this replaces the full linear question list.", + "items": { + "$ref": "#/components/schemas/SimpleQuestionnaireElement" + } + } + } + }, + "SimpleQuestionnaireElement": { + "type": "object", + "required": [ + "element_type", + "label" + ], + "properties": { + "id": { + "type": "string", + "description": "Optional stable question id. If omitted, the API generates `q1`, `q2`, etc. Use lowercase letters, numbers, and underscores for readability.", + "example": "source_of_funds" + }, + "element_type": { + "type": "string", + "description": "Question type. The API accepts these lowercase values and their uppercase equivalents.", + "enum": [ + "short_text", + "long_text", + "dropdown", + "single_choice", + "multiple_choice", + "number", + "image", + "file_upload", + "time", + "email", + "address", + "phone", + "country", + "date_picker", + "consent", + "section_header", + "separator", + "heading", + "paragraph" + ] + }, + "label": { + "type": "object", + "description": "Question label by language code, for example `{ \"en\": \"Source of funds\" }`.", + "additionalProperties": { + "type": "string" + } + }, + "description": { + "type": "object", + "nullable": true, + "additionalProperties": { + "type": "string" + } + }, + "placeholder": { + "type": "object", + "nullable": true, + "additionalProperties": { + "type": "string" + } + }, + "is_required": { + "type": "boolean", + "default": false + }, + "options": { + "type": "array", + "description": "Use for `dropdown`, `single_choice`, and `multiple_choice`. If `value` is omitted, the API generates one from the option label.", + "items": { + "type": "object", + "required": [ + "label" + ], + "properties": { + "value": { + "type": "string" + }, + "label": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "max_files": { + "type": "integer", + "minimum": 1, + "maximum": 5, + "description": "Use with `file_upload` or `image`." + }, + "validation": { + "type": "object", + "nullable": true, + "description": "Optional format validation for `short_text`, `long_text`, or `number`. Enforced while the user fills the field (they cannot proceed until it passes) and re-checked by the API on submission.", + "properties": { + "pattern": { + "type": "string", + "description": "Regular expression (ECMA-262 subset) the value must fully match. For `short_text` and `long_text`. Do not add ^/$ anchors; the whole value is matched." + }, + "min_length": { + "type": "integer", + "minimum": 0, + "description": "Minimum number of characters. For `short_text` and `long_text`." + }, + "max_length": { + "type": "integer", + "minimum": 0, + "description": "Maximum number of characters. For `short_text` and `long_text`." + }, + "min": { + "type": "number", + "description": "Minimum numeric value. For `number`." + }, + "max": { + "type": "number", + "description": "Maximum numeric value. For `number`." + }, + "error_message": { + "type": "object", + "description": "Translated message shown when validation fails, keyed by language code.", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "QuestionnaireDetail": { + "type": "object", + "description": "Full questionnaire detail returned from GET, POST (create) and PATCH (update) endpoints.", + "properties": { + "questionnaire_id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of the questionnaire. Use this id when referencing the questionnaire from a workflow or in subsequent update/delete requests." + }, + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, + "default_language": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "is_simple_questionnaire": { + "type": "boolean" + }, + "graph": { + "type": "object", + "description": "Graph structure with start_node and nodes map." + }, + "sections": { + "type": "array", + "description": "Questionnaire content grouped into sections (derived from the graph).", + "items": { + "type": "object" + } + }, + "questionnaire_group_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier that groups all versions of the same questionnaire." + }, + "version": { + "type": "integer" + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ] + }, + "published_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "is_editable": { + "type": "boolean", + "description": "True when the questionnaire can still be edited in place (draft versions)." + } + } + }, + "UserListItem": { + "type": "object", + "description": "A verified user.", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit's stable internal UUID for this user." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your unique identifier for this user (passed when creating sessions). This can be null when no vendor identifier was supplied." + }, + "display_name": { + "type": "string", + "nullable": true, + "description": "Custom display name set by you" + }, + "full_name": { + "type": "string", + "nullable": true, + "description": "Full name extracted from verified documents" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "effective_name": { + "type": "string", + "nullable": true, + "description": "Best available name: display_name if set, otherwise full_name" + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "Lifecycle status of the user record (NOT a session status). `ACTIVE` is the default, `FLAGGED` marks the user for manual attention, `BLOCKED` prevents new sessions for this `vendor_data`." + }, + "portrait_image_url": { + "type": "string", + "nullable": true, + "description": "Presigned URL of the user's portrait photo (expires after a few hours)" + }, + "session_count": { + "type": "integer", + "description": "Total number of verification sessions for this user" + }, + "approved_count": { + "type": "integer", + "description": "Number of approved sessions" + }, + "declined_count": { + "type": "integer", + "description": "Number of declined sessions" + }, + "in_review_count": { + "type": "integer", + "description": "Number of sessions in review" + }, + "issuing_states": { + "type": "array", + "items": { + "type": "string" + }, + "description": "ISO 3166-1 alpha-3 codes of issuing countries seen on this user's approved ID documents, e.g. `[\"USA\", \"ESP\"]`. Empty array when none." + }, + "approved_emails": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Verified email addresses collected from this user's approved sessions, e.g. `[\"john@example.com\"]`." + }, + "approved_phones": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Verified phone numbers collected from this user's approved sessions, e.g. `[\"+14155551234\"]`." + }, + "features": { + "type": "object", + "description": "Aggregated per-feature status across all of this user's sessions. Possible keys: `ID_VERIFICATION`, `NFC`, `LIVENESS`, `FACE_MATCH`, `POA`, `QUESTIONNAIRE`, `EMAIL_VERIFICATION`, `PHONE`, `AML`, `IP_ANALYSIS`, `AGE_ESTIMATION`, `DATABASE_VALIDATION`. Possible values: `Approved`, `Declined`, `In Review`, `Not Finished`, `Resub Requested`." + }, + "features_list": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "description": "Same data as `features`, as an ordered array of `{feature, status}` objects." + }, + "last_session_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent session" + }, + "first_session_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the first session" + }, + "last_activity_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent activity on this user (session, transaction, status change, data edit, etc.)." + }, + "tags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + } + } + }, + "description": "Tags assigned to this user" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BlocklistItem": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email" + ] + }, + "session_id": { + "type": "string", + "format": "uuid" + }, + "session_number": { + "type": "integer" + }, + "blocklisted_at": { + "type": "string", + "format": "date-time" + }, + "face_image": { + "type": "string", + "nullable": true + }, + "document_type": { + "type": "string", + "nullable": true + }, + "document_number": { + "type": "string", + "nullable": true + }, + "phone_number": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + } + } + }, + "UserDetailItem": { + "type": "object", + "description": "Full user detail. Extends UserListItem with metadata, comments, and updated_at.", + "allOf": [ + { + "$ref": "#/components/schemas/UserListItem" + }, + { + "type": "object", + "properties": { + "metadata": { + "type": "object", + "description": "Custom metadata JSON you attached to this user. Defaults to `{}`." + }, + "tags": { + "type": "array", + "description": "Tag assignments. NOTE: on detail responses each entry is a tag *link* object (`{uuid, tag: {...}, added_by_email, added_by_name, created_at}`), unlike the flat `{uuid, name, color}` shape used on list responses.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the tag assignment (not the tag itself)." + }, + "tag": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ] + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "added_by_email": { + "type": "string", + "nullable": true + }, + "added_by_name": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + } + }, + "comments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "comment_type": { + "type": "string", + "enum": [ + "COMMENT", + "STATUS_CHANGED", + "METADATA_UPDATED", + "TAG_ADDED", + "TAG_REMOVED", + "PROFILE_UPDATED" + ] + }, + "comment": { + "type": "string", + "nullable": true + }, + "actor_email": { + "type": "string", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "previous_status": { + "type": "string", + "nullable": true, + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED", + null + ] + }, + "new_status": { + "type": "string", + "nullable": true, + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED", + null + ] + }, + "previous_value": { + "type": "object", + "nullable": true, + "description": "Previous field values for PROFILE_UPDATED entries." + }, + "new_value": { + "type": "object", + "nullable": true, + "description": "New field values for PROFILE_UPDATED entries." + }, + "changed_fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the fields changed by a PROFILE_UPDATED entry." + }, + "metadata": { + "type": "object", + "nullable": true + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string" + } + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "description": "Activity log and comments for this user (status changes, profile edits, manual notes)." + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + ] + }, + "BusinessListItem": { + "type": "object", + "description": "A verified business.", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit's stable internal UUID for this business." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your unique identifier for this business (passed when creating sessions). This can be null when no vendor identifier was supplied." + }, + "display_name": { + "type": "string", + "nullable": true, + "description": "Custom display name set by you" + }, + "legal_name": { + "type": "string", + "nullable": true, + "description": "Official legal name from registry or manual entry" + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Company registration or incorporation number" + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "Country of incorporation (ISO 3166-1 alpha-2, e.g. `GB`, `US`)." + }, + "effective_name": { + "type": "string", + "nullable": true, + "description": "Best available name: display_name if set, otherwise legal_name" + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "Lifecycle status of the business record (NOT a session status). `ACTIVE` is the default, `FLAGGED` marks it for manual attention, `BLOCKED` prevents new sessions for this `vendor_data`." + }, + "session_count": { + "type": "integer", + "description": "Total number of verification sessions for this business" + }, + "approved_count": { + "type": "integer", + "description": "Number of approved sessions" + }, + "declined_count": { + "type": "integer", + "description": "Number of declined sessions" + }, + "in_review_count": { + "type": "integer", + "description": "Number of sessions in review" + }, + "features": { + "type": "object", + "description": "Aggregated per-feature status across all of this business's KYB sessions. Currently the only key is `KYB` (the registry company check status). Possible values: `Approved`, `Declined`, `In Review`, `Not Finished`, `Resub Requested`." + }, + "features_list": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "description": "Same data as `features`, as an ordered array of `{feature, status}` objects." + }, + "last_session_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent session" + }, + "first_session_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the first session" + }, + "last_activity_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent activity (status change, session update, etc.)" + }, + "tags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + } + } + }, + "description": "Tags assigned to this business" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BusinessDetailItem": { + "type": "object", + "description": "Full business detail. Extends BusinessListItem with metadata and updated_at.", + "allOf": [ + { + "$ref": "#/components/schemas/BusinessListItem" + }, + { + "type": "object", + "properties": { + "metadata": { + "type": "object", + "description": "Custom metadata JSON you attached to this business. Defaults to `{}`." + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + ] + }, + "TransactionDetail": { + "type": "object", + "description": "Full monitoring record for one transaction, as returned by `GET /v3/transactions/{transaction_id}/` and `POST /v3/transactions/`.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit-stable transaction identifier. Use as `{transaction_id}` for follow-up calls." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the transaction. A `sandbox` transaction is stored but never evaluated, screened, notified or billed, so its empty `rule_runs` means \"not evaluated\"." + }, + "transaction_number": { + "type": "integer", + "description": "Application-scoped sequential transaction number shown in Console (e.g. `4123`)." + }, + "txn_id": { + "type": "string", + "description": "The `transaction_id` you supplied at create time (max 128 chars). Unique per application." + }, + "txn_date": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred (from `transaction_at`; defaults to submission time)." + }, + "zone_id": { + "type": "string", + "nullable": true, + "description": "IANA time zone identifier provided at create time." + }, + "transaction_type": { + "type": "string", + "description": "Top-level category as stored: `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet`, `gamblingLimitChange`, `gamblingBonusChange`, `auditTrailEvent`. Note multi-word values are echoed back in camelCase even when submitted in snake_case." + }, + "action_type": { + "type": "string", + "description": "Sub-type within the category (e.g. `deposit`, `withdrawal`, `transfer`). Defaults to the category when not supplied." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject. Stored uppercase regardless of the casing submitted (`in`/`out`/`inbound`/`outbound` are accepted on input)." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Current monitoring verdict. Transactions are created `APPROVED`; rules may flip them to `IN_REVIEW`/`DECLINED` synchronously." + }, + "source": { + "type": "string", + "enum": [ + "LIVE", + "IMPORTED" + ], + "description": "How the record originated: `LIVE` for transactions submitted through the API or SDK, `IMPORTED` for transactions loaded through the console import." + }, + "amount": { + "type": "string", + "description": "Transaction amount as a decimal string with trailing zeros stripped (e.g. `\"1500\"`, `\"0.5\"`, `\"0.123456789012345678\"`). Up to 18 decimal places." + }, + "currency": { + "type": "string", + "description": "Currency code of `amount`. Any ISO 4217 alphabetic code is accepted (e.g. `EUR`, `USD`, `NGN`), as is a crypto asset symbol (e.g. `BTC`). Prefer a currently-circulating code. Not every accepted code has an exchange rate: when none is available for the code (for example the ISO accounting unit `XSU`, or a crypto symbol Didit does not price), automatic conversion fails and `preferred_currency_amount` is returned null unless the request supplied a pre-converted `amount_in_default_currency`." + }, + "currency_type": { + "type": "string", + "nullable": true, + "description": "`fiat` or `crypto`, as submitted in `currency_kind`." + }, + "amount_in_default_currency": { + "type": "string", + "nullable": true, + "description": "Pre-converted amount you supplied, as a decimal string with trailing zeros stripped." + }, + "default_currency_code": { + "type": "string", + "nullable": true + }, + "preferred_currency_amount": { + "type": "string", + "nullable": true, + "description": "`amount` converted to the application's preferred currency, when available." + }, + "preferred_currency_code": { + "type": "string", + "nullable": true + }, + "payment_details": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference or memo from the submission." + }, + "payment_txn_id": { + "type": "string", + "nullable": true, + "description": "External payment system reference (e.g. blockchain transaction hash) from `payment_reference_id`." + }, + "score": { + "type": "integer", + "description": "Risk score accumulated by rules (typically 0–100). Higher = riskier." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ], + "description": "Categorical risk severity set by rules/providers (`UNKNOWN`, `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`). `null` until something sets it." + }, + "decision_reason_code": { + "type": "string", + "nullable": true, + "description": "Machine-readable reason for the current `status`." + }, + "decision_reason_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label for `decision_reason_code`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Convenience copy of the applicant's `vendor_data`." + }, + "metadata": { + "type": "object", + "description": "Snapshot of the `subject` and `counterparty` payloads as submitted at create time." + }, + "props": { + "type": "object", + "description": "The `custom_properties` you supplied at create time. Each key is addressable in rule conditions as `custom_values.`." + }, + "tags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ], + "description": "Whether the tag is a Didit preset or a custom tag." + } + } + }, + "description": "Tags attached to the transaction (manually or by rules)." + }, + "parties": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "role": { + "type": "string", + "enum": [ + "APPLICANT", + "REMITTER", + "BENEFICIARY", + "COUNTERPARTY" + ], + "description": "Party role. The create endpoint assigns `APPLICANT` (the subject) and `COUNTERPARTY`." + }, + "entity_type": { + "type": "string", + "enum": [ + "individual", + "company", + "external", + "unhostedWallet" + ] + }, + "kind": { + "type": "string", + "enum": [ + "USER", + "BUSINESS", + "EXTERNAL" + ], + "description": "`USER`/`BUSINESS` parties link back to a Didit-owned entity via `vendor_data`. `EXTERNAL` parties have no Didit entity behind them." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your internal user/business identifier." + }, + "full_name": { + "type": "string", + "nullable": true + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "Country code from the submitted `address.country` (typically ISO 3166-1 alpha-3)." + }, + "dob": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth from the submitted `date_of_birth`." + }, + "address": { + "type": "object", + "description": "Address object as submitted. `{}` when omitted." + }, + "institution_info": { + "type": "object", + "description": "Institution details as submitted. `{}` when omitted." + }, + "device": { + "type": "object", + "description": "Normalized device context, including server-side IP enrichment under network_context (geolocation, VPN/data-center flags) when an IP was provided.", + "properties": { + "fingerprint": { + "type": "string", + "description": "Unique device fingerprint." + }, + "persistent_device_id": { + "type": "string", + "description": "Stable cross-session device identifier (v2 fingerprint schema)." + }, + "user_agent": { + "type": "string", + "description": "Browser or app user agent string." + }, + "browser_family": { + "type": "string", + "description": "Browser family (e.g. Chrome, Firefox, Safari)." + }, + "browser_version": { + "type": "string", + "description": "Browser version string." + }, + "platform": { + "type": "string", + "description": "Platform identifier (web, ios, android, macos, windows, linux)." + }, + "os_family": { + "type": "string", + "description": "Operating system family (e.g. iOS, Android, Windows)." + }, + "os_version": { + "type": "string", + "description": "Operating system version string." + }, + "accept_language": { + "type": "string", + "description": "Accept-Language header value." + }, + "session_id": { + "type": "string", + "description": "Session identifier (stored in metadata)." + }, + "session_age_ms": { + "type": "number", + "description": "Session age in milliseconds." + }, + "network_context": { + "type": "object", + "description": "Network context with IP address and geo-location.", + "properties": { + "ip_address": { + "type": "string", + "description": "Client IP address." + }, + "ip_country_code": { + "type": "string", + "description": "Geo-resolved country of the IP (ISO 3166-1 alpha-3)." + }, + "latitude": { + "type": "number", + "description": "Geographic latitude of the IP." + }, + "longitude": { + "type": "number", + "description": "Geographic longitude of the IP." + } + } + } + } + }, + "external_party_snapshot": { + "type": "object", + "nullable": true, + "description": "Frozen copy of the party data for `EXTERNAL` parties. `null` while the party is linked to a Didit user or business." + } + } + }, + "description": "Transaction parties (applicant, counterparty)." + }, + "payment_methods": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "payment_method_type": { + "type": "string", + "enum": [ + "bank_card", + "bank_account", + "e_wallet", + "crypto_wallet", + "unhosted_wallet", + "other" + ] + }, + "label": { + "type": "string", + "nullable": true + }, + "fingerprint": { + "type": "string", + "nullable": true, + "description": "SHA-256 over `type:account_id:issuing_country` — stable identifier to correlate reuse of the same instrument." + }, + "account_id": { + "type": "string", + "nullable": true, + "description": "Account identifier (IBAN, wallet address, card token, …)." + }, + "issuing_country": { + "type": "string", + "nullable": true + }, + "owner_kind": { + "type": "string", + "enum": [ + "USER", + "BUSINESS", + "EXTERNAL" + ] + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your identifier for the owning user/business." + }, + "owner_name": { + "type": "string", + "nullable": true + }, + "institution_info": { + "type": "object" + }, + "details": { + "type": "object", + "description": "Raw `payment_method` object as submitted." + }, + "external_owner_snapshot": { + "type": "object", + "nullable": true + }, + "role": { + "type": "string", + "enum": [ + "SOURCE", + "DESTINATION", + "FUNDING", + "BENEFICIARY" + ], + "description": "How this method was used in the transaction. The create endpoint assigns `SOURCE`/`DESTINATION` from the direction (outbound: subject = SOURCE; inbound: subject = DESTINATION)." + } + } + }, + "description": "Payment methods linked to the transaction." + }, + "documents": { + "type": "array", + "description": "Files attached to the transaction from the console. `[]` when none.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string", + "nullable": true + }, + "note": { + "type": "string", + "nullable": true + }, + "uploaded_by": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "files": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "file_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived signed download URL; fetch it when needed rather than storing it." + }, + "original_filename": { + "type": "string", + "nullable": true + }, + "file_size": { + "type": "integer", + "nullable": true + } + } + } + } + } + } + }, + "activities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "activity_type": { + "type": "string", + "description": "E.g. `TRANSACTION_CREATED`, `TRANSACTION_NOTE_ADDED`, `TRANSACTION_STATUS_CHANGED`." + }, + "source": { + "type": "string" + }, + "status": { + "type": "string", + "nullable": true + }, + "title": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object" + }, + "occurred_at": { + "type": "string", + "format": "date-time" + } + } + }, + "description": "Activity log entries (creation, notes, manual reviews, status changes)." + }, + "alerts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "severity": { + "type": "string", + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ] + }, + "status": { + "type": "string", + "enum": [ + "OPEN", + "INVESTIGATING", + "AWAITING_USER", + "PENDING_SAR", + "SAR_FILED", + "RESOLVED", + "DISMISSED" + ] + }, + "source": { + "type": "string", + "enum": [ + "RULE", + "PROVIDER", + "MANUAL" + ] + }, + "metadata": { + "type": "object" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "description": "Always empty today. An open alert is a transaction with status `IN_REVIEW` or `AWAITING_USER`." + }, + "rule_runs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "rule": { + "type": "string", + "format": "uuid", + "description": "UUID of the rule that ran." + }, + "rule_title": { + "type": "string" + }, + "rule_description": { + "type": "string", + "nullable": true + }, + "rule_category": { + "type": "string" + }, + "matched": { + "type": "boolean", + "description": "Whether the rule's conditions matched this transaction. A run is recorded only for matched rules, so live evaluations always carry `true`." + }, + "is_test": { + "type": "boolean", + "description": "True when the rule ran in TEST mode (no effect on status/score)." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ] + }, + "severity": { + "type": "string", + "nullable": true + }, + "score_delta": { + "type": "integer", + "description": "Score added by this rule's `add_score` actions." + }, + "status_target": { + "type": "string", + "nullable": true, + "description": "Status set by a `change_status` action, if any." + }, + "action_summary": { + "type": "string", + "nullable": true, + "description": "Summary of the actions the rule executed, joined with `; ` (e.g. `score +30; change_status:IN_REVIEW; add_tag:high_risk_country`). Parts: `score +N`, `change_status:`, `add_tag:`, `add_note`, `add_to_list`, `open_case`. `null` when the rule executed no action." + }, + "metadata": { + "type": "object", + "description": "Evaluation detail: `scope_match`, `conditions[]` and `aggregations[]`. Each condition entry carries `field`, `operator`, `expected_value`, the `current_value` read from the transaction, `matched`, `group_index` and `group_logic`. Each aggregation entry (velocity / count / sum) carries `metric`, `field`, `operator`, `expected_value`, the `computed_value` the aggregate evaluated to, `window`, `filters`, `matched` and `sample_references`; deferred async aggregations have `computed_value: null` and `deferred: true`. A nested condition group is one `conditions[]` entry with `operator` `any_of` or `all_of`, `field` null, and its members' results under its own `conditions`." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "description": "Per-rule execution results — which rule fired, with what score impact." + }, + "provider_results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "provider": { + "type": "string" + }, + "result_type": { + "type": "string", + "enum": [ + "FIAT_MONITORING", + "WALLET_SCREENING", + "TRANSACTION_SCREENING", + "TRAVEL_RULE", + "NETWORK_GRAPH", + "CUSTOM" + ] + }, + "status": { + "type": "string" + }, + "severity": { + "type": "string", + "nullable": true + }, + "score": { + "type": "integer" + }, + "summary": { + "type": "string", + "nullable": true + }, + "payload": { + "type": "object", + "description": "Raw provider response payload." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "description": "Raw provider payloads (AML screening, blockchain analytics, etc.)." + }, + "travel_rule": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TransactionTravelRule" + } + ], + "description": "Travel Rule transfer or check. Populated when `travel_rule_details` was submitted, and also when Travel Rule is enabled and the transaction was created with `transaction_category: travelRule` (Didit initiates the exchange itself, even without `travel_rule_details`). `null` when no transfer or check exists. On an inbound transfer, the counterparty's `originator_data` and `beneficiary_data` stay masked (`data_masked: true`) until wallet ownership is confirmed." + }, + "network_snapshot": { + "type": "object", + "nullable": true, + "description": "Graph snapshot of related transactions/parties used by the rules engine. Either the `network_snapshot` submitted with the transaction or the graph Didit builds from a successful AML wallet screening (which replaces a submitted one). `null` when neither exists.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "nodes": { + "type": "array", + "items": { + "type": "object" + } + }, + "edges": { + "type": "array", + "items": { + "type": "object" + } + }, + "metrics": { + "type": "object" + } + } + }, + "remediation": { + "type": "object", + "nullable": true, + "description": "Remediation session offered to the user when re-verification is required. Superseded by `action_required`, which is the canonical block; kept for backward compatibility.", + "properties": { + "session_id": { + "type": "string" + }, + "session_token": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "status": { + "type": "string" + } + } + }, + "action_required": { + "type": "object", + "nullable": true, + "description": "Pending end-user action required to complete this transaction, or null when there is none. The Didit SDKs auto-launch the action by default and refresh the transaction when it finishes. Two variants, discriminated by `type`: `verification_session` (a hosted verification session created by a rule action) and `wallet_ownership` (a wallet-ownership widget session auto-minted for a Travel Rule transfer that needs proof of wallet control, gated by the `auto_wallet_verification` Travel Rule setting). When both could apply, `wallet_ownership` takes precedence because it blocks the Travel Rule exchange.", + "oneOf": [ + { + "$ref": "#/components/schemas/TransactionVerificationSessionAction" + }, + { + "$ref": "#/components/schemas/TransactionWalletOwnershipAction" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "verification_session": "#/components/schemas/TransactionVerificationSessionAction", + "wallet_ownership": "#/components/schemas/TransactionWalletOwnershipAction" + } + } + }, + "cost_breakdown": { + "type": "object", + "nullable": true, + "description": "Cost of this transaction: `total_price` and `items`, one per charge, each with `usage_type`, `price`, `details` and `created_at`. A re-screen of the same transaction is a new item." + } + } + }, + "TravelRuleSettingsDetail": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean", + "description": "Whether the managed Travel Rule negotiation flow is active for outbound `travelRule` transactions on this application." + }, + "jurisdiction": { + "type": "string", + "maxLength": 8, + "description": "Your VASP's operating jurisdiction code. Default `\"EU\"`. Didit maps it to the regime it stamps on your Travel Rule transactions as `travel_rule_regime` (for example `EU` or an EU member code becomes `eu`), which transaction rules can key on. Other Didit customers see it as your jurisdiction in the VASP directory once the Didit network lists your profile." + }, + "name_matching_strictness": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "How strictly beneficiary names must match before a transfer can reach COMPLETED. `CONTAINS` accepts one name whose words all appear in the other." + }, + "confirmation_timeout_hours": { + "type": "integer", + "minimum": 1, + "description": "Hours an AWAITING_COUNTERPARTY transfer may remain unconfirmed before the periodic sweeper expires it. Default 48." + }, + "timeout_outcome": { + "type": "string", + "enum": [ + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "What happens to the underlying transaction when a transfer expires. Default HOLD. sunrise_policy and non_responsive_policy take precedence when they apply and are set." + }, + "threshold_amount": { + "type": "string", + "description": "Decimal string, in your application's default currency. Minimum transfer amount that triggers a managed exchange; transfers below it resolve straight to NOT_APPLICABLE. Didit compares the transaction amount converted to your default currency, or the `amount_in_default_currency` you send; when no converted amount is available, the threshold does not apply. Defaults to \"0.00\" (EU TFR applies no minimum amount)." + }, + "ownership_validation_mode": { + "type": "string", + "enum": [ + "FULL", + "SIMPLIFIED", + "AUTOMATED" + ], + "description": "How much proof a wallet's ownership takes. `FULL` always needs a proof (signed message, test deposit or reviewed screenshot), never the end user's word. `SIMPLIFIED` accepts a self-declaration below `simplified_validation_threshold` and a proof above it. `AUTOMATED` lets the address book and earlier proofs answer on their own, self-declarations included while `allow_self_declaration` is true. Default `AUTOMATED`." + }, + "simplified_validation_threshold": { + "type": "string", + "description": "Decimal string, default \"0.00\". In `SIMPLIFIED` mode a transfer below this amount (in the threshold currency) accepts a self-declaration. A transfer with no amount in that currency counts as above it." + }, + "data_sharing_fields": { + "$ref": "#/components/schemas/TravelRuleDataSharingFields" + }, + "matching_rules": { + "$ref": "#/components/schemas/TravelRuleMatchingRules" + }, + "legal_name": { + "type": "string", + "description": "Your VASP's legal name. Required (non-blank) to set is_enabled to true." + }, + "lei": { + "type": "string", + "description": "Your Legal Entity Identifier, surfaced in the VASP directory." + }, + "compliance_email": { + "type": "string", + "format": "email", + "description": "Compliance contact email for your VASP profile. The Didit network lists your profile only once this address is confirmed through an emailed link (POST /v3/travel-rule/settings/compliance-email/confirmation/); changing it needs a new confirmation." + }, + "is_discoverable": { + "type": "boolean", + "description": "Whether your VASP profile can be listed in the Didit network: found in GET /v3/travel-rule/vasps/ by other Didit customers and reached on the INTERNAL rail. Listing also needs a live application, Travel Rule on, a legal name and a confirmed compliance email; didit_network says which are missing. Default true." + }, + "inbound_auto_accept": { + "type": "boolean", + "description": "When true, an inbound TRP inquiry whose beneficiary wallet is a verified address-book entry with a matching name is approved automatically. When false, that inquiry waits in ON_HOLD for you to approve or deny it with the ownership endpoint. INTERNAL-rail and after-deposit transfers are not affected. Default true." + }, + "allow_self_declaration": { + "type": "boolean", + "description": "When true, the wallet-ownership widget offers self-declaration as a fallback proof method. Default true." + }, + "allow_screenshot_proof": { + "type": "boolean", + "description": "When true, the wallet-ownership widget offers screenshot upload as an ownership evidence method. Default true." + }, + "auto_wallet_verification": { + "type": "boolean", + "description": "When true, submitting an outbound Travel Rule transfer that needs end-user proof of wallet control auto-mints a wallet-ownership widget session bound to the transaction and exposes it in the transaction's action_required block. Default true." + }, + "vasp_attribution_enabled": { + "type": "boolean", + "description": "When true, an outbound Travel Rule transfer that no rail can route is attributed through blockchain analytics: the destination wallet is resolved to its owning VASP, the entity is recorded on the transfer and in the VASP directory, and the rails are re-run once with the attributed name. Default true." + }, + "counterparty_screening_enabled": { + "type": "boolean", + "description": "Opt-in sanctions and adverse-media screening of counterparty VASPs, billed per screening at the Company AML Screening price. When on, Didit screens the counterparty an outbound exchange reaches (reusing a screening younger than 30 days) and you can screen one with POST /v3/travel-rule/vasps/{vasp_id}/screening/. Default false: nothing is screened or billed." + }, + "sunrise_policy": { + "type": "string", + "enum": [ + "", + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "What an expired transfer does to its transaction when the counterparty is a directory entry in a jurisdiction on sunrise_jurisdictions, which does not enforce the Travel Rule yet: HOLD moves it to review, REJECT declines it, PROCEED leaves its status untouched. Empty (the default) follows timeout_outcome. Applies before non_responsive_policy." + }, + "sunrise_jurisdictions": { + "type": "array", + "items": { + "type": "string" + }, + "readOnly": true, + "description": "Read-only. The countries (ISO 3166 alpha-2) Didit treats as in a Travel Rule sunrise period, where sunrise_policy applies. Currently CO, KH and VN. Any other jurisdiction, and an empty or unknown one, follows timeout_outcome." + }, + "non_responsive_policy": { + "type": "string", + "enum": [ + "", + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "What an expired transfer does to its transaction when the counterparty is a directory entry whose responsiveness is UNRESPONSIVE: HOLD, REJECT or PROCEED, as for timeout_outcome. Empty (the default) follows timeout_outcome. sunrise_policy applies first when both fit." + }, + "travel_address": { + "type": "string", + "description": "Read-only. Your VASP's own travel address - the encoded inbound TRP inquiry endpoint other VASPs use to reach you. Empty while `is_enabled` is false." + }, + "disabled_networks": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA" + ] + }, + "description": "Networks opted out of platform membership. Does not disable your connected own memberships or provision unavailable networks.", + "default": [] + }, + "networks": { + "type": "object", + "readOnly": true, + "description": "Per network, how this application would send and why it cannot. No network, including GTR, is included by default: a network is reachable through your own connected membership, or through Didit's membership once Didit's membership of that network is live and your own participant on it is active. Not a live health or delivery check.", + "properties": { + "gtr": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "trust": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "verifyvasp": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "sygna": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + } + } + }, + "didit_network": { + "type": "object", + "readOnly": true, + "description": "Whether the Didit network lists your VASP profile, so other Didit customers find it in the directory and reach it on the INTERNAL rail, and each condition it still misses.", + "properties": { + "listed": { + "type": "boolean" + }, + "missing": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "travel_rule_off", + "sandbox_application", + "not_discoverable", + "legal_name_missing", + "compliance_email_missing", + "compliance_email_unconfirmed", + "excluded_by_staff", + "internal_organization" + ] + }, + "description": "Empty when listed. compliance_email_unconfirmed: send the confirmation with POST /v3/travel-rule/settings/compliance-email/confirmation/ and open the emailed link. excluded_by_staff: Didit marked the application as a test, demo or internal one." + }, + "compliance_email_confirmed": { + "type": "boolean", + "description": "Whether the current compliance_email was confirmed through the emailed link." + } + } + } + } + }, + "TravelRuleSettingsUpdate": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean" + }, + "legal_name": { + "type": "string", + "maxLength": 255 + }, + "lei": { + "type": "string", + "maxLength": 20 + }, + "jurisdiction": { + "type": "string", + "maxLength": 8 + }, + "name_matching_strictness": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "`CONTAINS` accepts one name whose words all appear in the other." + }, + "confirmation_timeout_hours": { + "type": "integer", + "minimum": 1 + }, + "timeout_outcome": { + "type": "string", + "enum": [ + "HOLD", + "REJECT", + "PROCEED" + ] + }, + "compliance_email": { + "type": "string", + "format": "email", + "description": "Compliance contact email for your VASP profile. The Didit network lists your profile only once this address is confirmed through an emailed link (POST /v3/travel-rule/settings/compliance-email/confirmation/); changing it needs a new confirmation." + }, + "is_discoverable": { + "type": "boolean", + "description": "Whether your VASP profile can be listed in the Didit network: found in GET /v3/travel-rule/vasps/ by other Didit customers and reached on the INTERNAL rail. Listing also needs a live application, Travel Rule on, a legal name and a confirmed compliance email; didit_network says which are missing. Default true." + }, + "threshold_amount": { + "type": "string", + "description": "Decimal string, zero or greater, in your application's default currency. Minimum transfer amount that triggers a managed exchange." + }, + "ownership_validation_mode": { + "type": "string", + "enum": [ + "FULL", + "SIMPLIFIED", + "AUTOMATED" + ], + "description": "See the response field of the same name." + }, + "simplified_validation_threshold": { + "type": "string", + "description": "Decimal string, 0 or more." + }, + "data_sharing_fields": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleDataSharingFields" + } + ], + "description": "Merged per entity type: an entity type you leave out keeps its current choice. A choice that cannot meet the originator obligations of your `jurisdiction` regime (EU TFR or FATF Recommendation 16), an empty list included, is refused with `400`." + }, + "matching_rules": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleMatchingRules" + } + ], + "description": "Merged per entity type, and per field inside it. A field that entity type does not compare (for example `date_of_birth` for `company`) is refused with `400`." + }, + "inbound_auto_accept": { + "type": "boolean" + }, + "allow_self_declaration": { + "type": "boolean" + }, + "allow_screenshot_proof": { + "type": "boolean", + "description": "When true, the wallet-ownership widget offers screenshot upload as an ownership evidence method. Default true." + }, + "auto_wallet_verification": { + "type": "boolean" + }, + "vasp_attribution_enabled": { + "type": "boolean" + }, + "counterparty_screening_enabled": { + "type": "boolean", + "description": "Opt-in sanctions and adverse-media screening of counterparty VASPs, billed per screening at the Company AML Screening price. When on, Didit screens the counterparty an outbound exchange reaches (reusing a screening younger than 30 days) and you can screen one with POST /v3/travel-rule/vasps/{vasp_id}/screening/. Default false: nothing is screened or billed." + }, + "sunrise_policy": { + "type": "string", + "enum": [ + "", + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "HOLD, REJECT, PROCEED, or empty to follow timeout_outcome. See the response field of the same name." + }, + "non_responsive_policy": { + "type": "string", + "enum": [ + "", + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "HOLD, REJECT, PROCEED, or empty to follow timeout_outcome. See the response field of the same name." + }, + "disabled_networks": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA" + ] + }, + "description": "Networks opted out of platform membership. Does not disable your connected own memberships or provision unavailable networks.", + "default": [] + } + }, + "description": "All fields optional; PUT applies a partial update." + }, + "WalletAddressEntry": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "address": { + "type": "string", + "description": "The wallet address; `\"\"` for an entry imported by its hash only. Unique per application, chain and memo, comparing the address without regard to case. After you delete an entry, you can register the same address again." + }, + "chain": { + "type": "string", + "description": "Chain token for the address, for example `ETH` or `BTC`. When Didit routes or registers a transfer, it matches address book entries on the wallet address and, when the transfer carries a chain, on this value too, ignoring case. A transfer's chain is `beneficiary_data.chain`, filled from the transaction's crypto chain or currency symbol (for example `ETH`) when you do not send it, or the `chain` of an after-deposit registration, so use the same token everywhere. An entry with an empty chain matches any chain. A Lightning payee (a node id or a Lightning Address) registered on bitcoin is stored lowercased on `LIGHTNING`, where Lightning transfers are matched. Known chains are stored under one canonical identifier whatever spelling you send: \"ethereum\", \"Ethereum\" and \"ERC20\" are all stored as ETH, \"BNB Smart Chain\" and \"BEP20\" as BNB, \"tron\" as TRX. An unknown chain name is kept as given." + }, + "holder_vendor_data": { + "type": "string", + "description": "Your internal identifier for the holder." + }, + "holder_name": { + "type": "string", + "description": "Name of the wallet holder, used for the beneficiary name match." + }, + "entity_type": { + "type": "string", + "description": "Free text, defaults to \"individual\"." + }, + "is_ownership_verified": { + "type": "boolean", + "description": "Read-only. Whether ownership of this address has been proven." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "travel_address": { + "type": "string", + "description": "The counterparty VASP's travel address for this wallet, if known. Lets Didit route this beneficiary's transfers over the TRP rail, for the assets TRP carries (BTC, ETH, and USDC on a named network)." + }, + "address_hash": { + "type": "string", + "description": "Read-only. SHA-256 of the lowercased address, in lowercase hex. An entry imported by its hash only is matched on it." + }, + "memo": { + "type": "string", + "description": "The destination tag or memo that is part of the wallet on a memo network (XRP, XLM, EOS, ATOM, HBAR); `\"\"` for the whole address. An entry with a memo matches only transfers naming that memo; one without matches the whole address." + }, + "receiving_travel_address": { + "type": "string", + "readOnly": true, + "description": "Your per-wallet travel address for this wallet: give it to the sending VASP so its TRP inquiry names this wallet. Empty for a wallet recorded as another VASP's, while Travel Rule is off, and before your VASP profile is saved." + } + } + }, + "WalletAddressEntryCreate": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string", + "maxLength": 128, + "description": "The wallet address. An XRP X-address is stored as its classic address and its tag, so it matches transfers that name either form." + }, + "chain": { + "type": "string", + "maxLength": 32, + "description": "Chain token for the address, for example `ETH` or `BTC`. When Didit routes or registers a transfer, it matches address book entries on the wallet address and, when the transfer carries a chain, on this value too, ignoring case. A transfer's chain is `beneficiary_data.chain`, filled from the transaction's crypto chain or currency symbol (for example `ETH`) when you do not send it, or the `chain` of an after-deposit registration, so use the same token everywhere. An entry with an empty chain matches any chain. Known chains are stored under one canonical identifier whatever spelling you send: \"ethereum\", \"Ethereum\" and \"ERC20\" are all stored as ETH, \"BNB Smart Chain\" and \"BEP20\" as BNB, \"tron\" as TRX. An unknown chain name is kept as given." + }, + "holder_vendor_data": { + "type": "string", + "maxLength": 255 + }, + "holder_name": { + "type": "string", + "maxLength": 255 + }, + "entity_type": { + "type": "string", + "default": "individual", + "maxLength": 16 + }, + "self_declared": { + "type": "boolean", + "default": false, + "description": "Write-only. When true, the entry is created already ownership-verified via a SELF_DECLARATION proof. Returns 400 when `allow_self_declaration` is off in your Travel Rule settings." + }, + "travel_address": { + "type": "string", + "description": "Optional. The counterparty VASP's travel address for this wallet, enabling TRP routing for the assets TRP carries (BTC, ETH, and USDC on a named network). An invalid travel address returns 400.", + "maxLength": 512 + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "Optional. The destination tag or memo that is part of the wallet. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`. Alias: `destinationTag` or `destination_tag`." + } + } + }, + "WalletAddressEntryUpdate": { + "type": "object", + "properties": { + "holder_name": { + "type": "string", + "maxLength": 255 + }, + "holder_vendor_data": { + "type": "string", + "maxLength": 255 + }, + "entity_type": { + "type": "string", + "maxLength": 16 + }, + "travel_address": { + "type": "string", + "maxLength": 512 + } + }, + "description": "PATCH is a partial update; the response echoes only holder_name, holder_vendor_data, entity_type, travel_address, receiving_travel_address, label, and owner_type." + }, + "TravelRuleTransferDetail": { + "type": "object", + "description": "Full transfer object returned by the ownership-confirmation and finish/cancel endpoints. While an inbound transfer's wallet ownership is unconfirmed, the counterparty's `originator_data` and `beneficiary_data` come back masked (the first letter of each word is kept; the wallet, chain and VASP routing stay readable) and `data_masked` is true.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "AWAITING_COUNTERPARTY", + "ON_HOLD", + "COMPLETED", + "FINISHED", + "CANCELLED", + "EXPIRED", + "COUNTERPARTY_MISMATCHED_DATA", + "COUNTERPARTY_VASP_NOT_FOUND", + "COUNTERPARTY_VASP_NOT_REACHABLE", + "UNCONFIRMED_OWNERSHIP", + "COUNTERPARTY_VASP_GENERAL_DECLINE", + "NOT_ENOUGH_COUNTERPARTY_DATA", + "NOT_APPLICABLE" + ] + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ] + }, + "rail": { + "type": "string", + "nullable": true, + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL", + "TEST_VASP", + null + ], + "description": "The rail the transfer was routed over, or null when no rail was selected (for example below the threshold, a self-hosted counterparty wallet, an after-deposit registration, or no reachable counterparty). INTERNAL means both sides are Didit applications; EMAIL means the counterparty was reached by an emailed pickup link. TEST_VASP means the Didit Test VASP answered the sandbox transfer. Over GTR, VERIFYVASP and SYGNA, Didit reads the counterparty's answer from the network's response or by polling it every 5 minutes. TRUST only sends, so a TRUST transfer stays AWAITING_COUNTERPARTY until it expires. A network rail is used only when you have connected your own membership for that network. It is chosen after the transaction create returns, so it is null in that response." + }, + "protocol": { + "type": "string", + "nullable": true + }, + "ivms_version": { + "type": "string", + "example": "ivms101.2023" + }, + "required": { + "type": "boolean" + }, + "ownership_confirmed": { + "type": "boolean", + "nullable": true + }, + "deadline_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "timeline": { + "type": "array", + "items": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "at": { + "type": "string", + "format": "date-time" + }, + "detail": { + "type": "string" + }, + "name_match": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleNameMatch" + } + ], + "nullable": true, + "description": "The name comparison this entry decided, next to `detail`. Absent on entries that compared no name." + }, + "reason": { + "type": "string", + "description": "Present only on the entry of a transition a failed delivery caused: `email_undeliverable` or `delivery_failed`, the value the transfer's `reason` reports." + } + } + } + }, + "counterparty_vasp": { + "type": "object", + "nullable": true, + "properties": { + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "dd_score": { + "type": "integer", + "description": "Due-diligence score from 0 to 100, where higher is better. Same scale as in the VASP directory." + }, + "dd_score_breakdown": { + "type": "object", + "description": "Per-component contributions to dd_score, one object per component as in the VASP directory (jurisdiction, verification, protocol, lei, history, essentials, responsiveness), each {points, max, reason, detail}. {} until the counterparty has been assessed.", + "additionalProperties": { + "type": "object", + "properties": { + "points": { + "type": "number", + "description": "Points awarded for this component." + }, + "max": { + "type": "integer", + "description": "Maximum points this component can award." + }, + "reason": { + "type": "string", + "description": "Machine-readable code of the component's verdict." + }, + "detail": { + "type": "string", + "description": "Human-readable reason for the awarded points." + } + } + } + }, + "dd_assessed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the due-diligence assessment last ran. Null until the counterparty has been assessed." + } + } + }, + "originator_data": { + "type": "object", + "description": "Originator party. For an inbound transfer the counterparty's values are masked until ownership is confirmed." + }, + "beneficiary_data": { + "type": "object", + "description": "Beneficiary party. For an inbound transfer the counterparty's values are masked until ownership is confirmed." + }, + "name_match": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleNameMatch" + } + ], + "nullable": true, + "description": "The name comparison that decided the transfer, or null until one ran. While the parties are masked, and on the other customer's copy of an INTERNAL exchange, `transfer_name`, `compared_name`, `matched_names` and `mismatched_names` are hidden. `score` and `matched` can disagree: `matched` follows the strictness, `score` is the similarity of the two full names." + }, + "data_match": { + "$ref": "#/components/schemas/TravelRuleDataMatch" + }, + "mismatched_fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Shortcut for `data_match.mismatched_fields`. Empty when nothing differs." + }, + "data_unmasked": { + "$ref": "#/components/schemas/TravelRuleDataUnmasked" + }, + "data_masked": { + "type": "boolean", + "description": "True while the counterparty's personal data is masked in this response." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "delivery": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleDelivery" + } + ], + "description": "Where the latest message this transfer owes the other side stands. Null when the transfer never owed one (for example below `threshold_amount`, a declared self-hosted wallet, or a sunrise registration)." + }, + "discovery": { + "type": "array", + "description": "How Didit looked for the counterparty, one entry per step that ran. Empty when no discovery ran, for example on an inbound transfer.", + "items": { + "$ref": "#/components/schemas/TravelRuleDiscoveryStep" + } + }, + "reason": { + "type": "string", + "description": "Why a `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE` transfer is stuck; `\"\"` for any other status. A failed delivery decides it first, on `COUNTERPARTY_VASP_NOT_REACHABLE` only: `email_undeliverable` (the mail provider refused the compliance email and no address is left to try: when it was sent, on a reminder, or as a permanent bounce reported later) or `delivery_failed` (the rail that was delivering failed for good: a network rail refused the message or ran out of retries, a `TRP` identity host did not answer, or the counterparty's `TRP` host did not accept the inquiry). Otherwise the `discovery` trace decides it: `awaiting_end_user` (no step named a counterparty Didit can reach), `directory_match_no_route` (a step named a directory VASP that has no route), `picked_member_without_proven_wallet` (the end user picked a Didit customer that has not proven control of the wallet), or `no_directory_match` when the trace gives no other reason. A resend moves the transfer on, so the reason clears. See [Why a transfer is stuck](/transaction-monitoring/travel-rule#why-a-transfer-is-stuck)." + }, + "next_actions": { + "type": "array", + "description": "What moves the transfer on; `[]` for any status but `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE`. `email_undeliverable` offers `update_compliance_email`, then `resend`. `delivery_failed` offers `resend`. A discovery reason offers `select_counterparty_vasp`, `declare_self_hosted`, `add_vasp_hint` and `resend`.", + "items": { + "$ref": "#/components/schemas/TravelRuleNextAction" + } + }, + "chain_details": { + "$ref": "#/components/schemas/TravelRuleChainDetails" + } + } + }, + "TravelRuleOwnershipConfirmRequest": { + "type": "object", + "required": [ + "confirmed" + ], + "properties": { + "confirmed": { + "type": "boolean", + "description": "true to confirm ownership, false to deny it." + }, + "wallet_address": { + "type": "string", + "default": "", + "description": "Inbound TRP transfers: the payment address Didit returns to the originating VASP when you approve. Defaults to the matched address book entry's address, then beneficiary_data.wallet_address. On a declared self-hosted wallet transfer that names no counterparty wallet, the wallet whose ownership you verified; without it the request is rejected with 400. Ignored for other transfers." + } + } + }, + "TravelRuleTransferPatchRequest": { + "type": "object", + "description": "Provide exactly one of: payment_txn_id (finish), action=cancel, action=resend, action=update_compliance_email with compliance_email, action=update_originator_wallet with wallet_address, or action=beneficiary_vasp with beneficiary_vasp.", + "properties": { + "payment_txn_id": { + "type": "string", + "description": "The on-chain transaction hash. Requires the transfer to be an outbound transfer in COMPLETED status." + }, + "action": { + "type": "string", + "enum": [ + "cancel", + "resend", + "update_compliance_email", + "update_originator_wallet", + "beneficiary_vasp" + ], + "description": "\"cancel\" cancels a non-terminal transfer. \"resend\" re-runs routing for an outbound transfer stuck in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE, or NOT_ENOUGH_COUNTERPARTY_DATA. \"update_compliance_email\" replaces the compliance email the request goes to, on an outbound transfer in one of those three statuses; send \"resend\" afterwards. \"update_originator_wallet\" records the wallet the funds actually left from (`wallet_address`, `memo`). \"beneficiary_vasp\" names the counterparty VASP yourself (requires `beneficiary_vasp`) for an outbound transfer in one of those statuses: it is routed to that VASP and sent again." + }, + "compliance_email": { + "type": "string", + "format": "email", + "maxLength": 254, + "description": "Required with `action: update_compliance_email`. Kept as this transfer's `beneficiary_data.counterparty_vasp_email`: it belongs to this transfer only and joins no directory entry and no contact list." + }, + "wallet_address": { + "type": "string", + "maxLength": 128, + "description": "With `action: update_originator_wallet`: the wallet the funds left from. When the transfer's UTXO inputs are known it must be one of them." + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "With `action: update_originator_wallet`: the wallet's memo or tag on a memo network. Optional." + }, + "beneficiary_vasp": { + "type": "string", + "format": "uuid", + "description": "Required with action=beneficiary_vasp: the `id` of a directory VASP, from `GET /v3/travel-rule/vasps/`. Your choice outranks every automatic discovery step, which never overrides it." + } + } + }, + "VaspDirectoryEntry": { + "type": "object", + "description": "A VASP of the directory or a member of the Didit network, with the routes your application has to it. A customer is a member of the Didit network only while its application is live, its Travel Rule is on, its profile is discoverable, has a legal name and a confirmed compliance email, and Didit has not marked the application as a test, demo or internal one. Each row also carries the shared due-diligence assessment (dd_score, risk_level, responsiveness, essential_checks) and your application's own decision, entity screening and questionnaires, which no other customer sees.", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The entry's id, for GET /v3/travel-rule/vasps/{vasp_id}/." + }, + "kind": { + "type": "string", + "enum": [ + "directory", + "didit" + ], + "description": "directory for an entry of the shared directory; didit for a member of the Didit network." + }, + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "description": "ISO 3166 country code, as the source gives it." + }, + "domains": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The VASP's web domains. Always empty for Didit network members." + }, + "reachable": { + "type": "boolean", + "description": "Whether your application has an available route to this VASP, or to one of its entities for a brand. Always true for a Didit network member, except on a sandbox application, where every row is false." + }, + "aliases": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "trading_name", + "other_legal_name", + "network_name", + "attribution", + "merged", + "staff" + ], + "description": "trading_name, other_legal_name (from the global LEI register), network_name (the name on a Travel Rule network), attribution (the entity name blockchain analytics gives the VASP's wallets), merged (the name of a duplicate entry merged into this one) or staff." + } + } + }, + "description": "Other names the VASP is found by." + }, + "networks": { + "type": "array", + "items": { + "type": "object", + "description": "A current listing of this VASP in a Travel Rule network's member directory. A TRISA listing says who the VASP is on TRISA; Didit has no TRISA rail, so it is never a route.", + "properties": { + "network": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "TRISA" + ] + }, + "code": { + "type": "string", + "description": "The VASP's identifier on that network." + }, + "listed_name": { + "type": "string", + "description": "The name the network lists the VASP under." + }, + "country": { + "type": "string", + "description": "The country the network lists, when it gives one." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When Didit staff verified the listing. Null for listings read from a public directory." + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the source last showed the listing. A listing the source has not shown for three weeks is removed." + }, + "source": { + "type": "string", + "enum": [ + "trisa_directory", + "staff" + ], + "description": "Where the listing comes from: the public TRISA directory, or Didit staff." + } + } + } + }, + "routes": { + "type": "array", + "items": { + "type": "object", + "description": "One way your application can deliver Travel Rule data to this VASP, in the order dispatch tries them. A network route needs a membership on that network: your own (connected in the Marketplace) or Didit's once Didit holds one. Without it the route is listed as unavailable with the reason, never as a way to reach the VASP.", + "properties": { + "rail": { + "type": "string", + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL" + ] + }, + "status": { + "type": "string", + "enum": [ + "available", + "unavailable" + ] + }, + "reason": { + "type": "string", + "enum": [ + "", + "no_membership", + "network_switched_off", + "sandbox_application" + ], + "description": "Why an unavailable route cannot be used: no_membership (neither you nor Didit holds a membership on that network), network_switched_off (you turned that network off in your settings) or sandbox_application (a sandbox application sends only to the Didit Test VASP, so no route is available to it). Empty when the route is available." + }, + "via": { + "type": "string", + "enum": [ + "", + "own_membership", + "didit_membership", + "directory", + "contact", + "didit_network" + ], + "description": "How an available route is reached: own_membership or didit_membership for a network rail, directory for a TRP endpoint the directory verified, contact for the EMAIL rail, didit_network for a Didit network member. Empty when the route is unavailable." + } + } + } + }, + "umbrella": { + "type": "object", + "nullable": true, + "description": "The brand this entry is a regulated entity of. Null when it is not part of one.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + } + } + }, + "entities": { + "type": "array", + "items": { + "type": "object", + "description": "A regulated entity of a brand: the legal entity that receives a transfer.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "jurisdiction": { + "type": "string" + }, + "reachable": { + "type": "boolean", + "description": "Whether your application has an available route to it." + } + } + }, + "description": "For a brand, the regulated entities that receive its transfers. A transfer naming the brand goes to the reachable entity in the jurisdiction it states (beneficiary_data.counterparty_vasp_jurisdiction), else to the entity blockchain analytics attribution linked to the destination wallet, else to the only reachable entity when no jurisdiction is stated. Two entities that fit equally are never guessed between." + }, + "contacts": { + "type": "array", + "items": { + "type": "object", + "description": "A compliance contact the EMAIL rail can write to.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "email": { + "type": "string", + "format": "email" + }, + "name": { + "type": "string" + }, + "source": { + "type": "string", + "enum": [ + "staff", + "application", + "learned" + ], + "description": "staff: curated by Didit; application: added by your application; learned: an address that answered a Travel Rule request through its response link." + }, + "scope": { + "type": "string", + "enum": [ + "platform", + "application" + ], + "description": "platform contacts are used for every Didit customer's transfers; application contacts are private to your application, which sees only its own." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When someone at this address answered a request. Null when it has not yet." + } + } + }, + "description": "The platform's contacts for this VASP and your application's own." + }, + "dd_status": { + "type": "string", + "enum": [ + "NOT_STARTED", + "IN_PROGRESS", + "COMPLETED", + "REJECTED" + ], + "description": "Didit's verification status of the entry, scored by the verification component of dd_score. Always COMPLETED for a Didit network member." + }, + "dd_score": { + "type": "integer", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "The shared due-diligence score of a directory entry, 0 to 100, where higher is safer (see dd_score_semantic): the sum of the points in dd_score_breakdown. Null for a Didit network member, which is not scored. An entry not assessed yet (dd_assessed_at null) shows 0 with an empty risk_level, so read risk_level rather than the raw score." + }, + "reachable_rails": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL" + ] + }, + "description": "Rails recorded on the entry. Read routes for what your application can use today." + }, + "is_didit_customer": { + "type": "boolean", + "description": "true for a member of the Didit network (dd_status is always COMPLETED, dd_score null and risk_level empty for these) and for a directory entry linked to a Didit customer." + }, + "dd_score_breakdown": { + "type": "object", + "description": "One object per component of dd_score. Components, their weight (max) and their reason codes: jurisdiction, 25 (jurisdiction_not_high_risk, jurisdiction_high_risk, jurisdiction_unknown); verification, 15 (dd_status_completed, dd_status_in_progress, dd_status_not_started, dd_status_rejected); protocol, 15 (native_rail, email_only, no_rail); lei, 10 (lei_valid, lei_missing); history, 10 (history, no_history); essentials, 15 (essential_checks, no_essential_checks); responsiveness, 10 (responsive, unresponsive, responsiveness_unknown). Empty for a Didit network member.", + "additionalProperties": { + "type": "object", + "properties": { + "points": { + "type": "number", + "description": "Points the component contributes, rounded to one decimal." + }, + "max": { + "type": "integer", + "description": "The component's weight: the points it contributes at best." + }, + "reason": { + "type": "string", + "description": "Machine-readable code of the component's verdict." + }, + "detail": { + "type": "string", + "description": "One-line English explanation." + } + } + } + }, + "dd_assessed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When dd_score, risk_level and responsiveness were last computed: after an exchange with the entry completes, finishes, expires, mismatches or is declined, when Didit refreshes or curates the entry, and in the weekly directory refresh. Null for a Didit network member." + }, + "dd_score_semantic": { + "type": "string", + "enum": [ + "higher_is_safer" + ], + "description": "Which way dd_score points: always higher_is_safer." + }, + "risk_level": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH", + "" + ], + "description": "The band dd_score falls in: LOW (70 or more), MEDIUM (40 to 69) or HIGH (below 40). Empty for a Didit network member and for an entry not assessed yet. Filter on it with risk. Your own flagged screening never changes this shared value: it makes counterparty_risk_level HIGH in your rules instead." + }, + "responsiveness": { + "type": "object", + "description": "Whether the VASP answers Travel Rule requests: the share of the requests Didit customers started towards it in the last 90 days that it answered. Only requests sent over a network or protocol rail, or by email to a shared address (the entry's compliance address or a platform contact), count; a request sent to an address a customer typed, or to a customer's private contact, never does. An expired request is unanswered; a completed exchange, a decline or a data mismatch is an answer; a cancelled request or an unreachable rail is neither. Always UNKNOWN with zero counts for a Didit network member.", + "properties": { + "status": { + "type": "string", + "enum": [ + "RESPONSIVE", + "UNRESPONSIVE", + "UNKNOWN" + ], + "description": "UNKNOWN with fewer than 5 decided requests in the window; UNRESPONSIVE when less than 10 percent of them were answered; RESPONSIVE otherwise." + }, + "answered": { + "type": "integer", + "description": "Decided requests in the window that the VASP answered." + }, + "total": { + "type": "integer", + "description": "Decided requests in the window: answered plus expired." + }, + "computed_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "essential_checks": { + "type": "object", + "description": "The essential-checks report of a directory entry, built only from the platform's verified sources: registration (an official register lists the VASP: the EU's interim MiCA register, the public TRISA directory or the global LEI register), licence (a MiCA authorisation is on record and current), domain (the entry's domain matches its TRP endpoint or the website its register gives) and lei (its LEI record is active, issued and consistent with the entry's names and country). The essentials component of dd_score is the share of decided checks that pass. Empty until the weekly directory refresh first checks the entry, and always empty for a Didit network member.", + "properties": { + "level": { + "type": "string", + "enum": [ + "essential" + ] + }, + "checked_at": { + "type": "string", + "format": "date-time" + }, + "checks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "enum": [ + "registration", + "licence", + "domain", + "lei" + ] + }, + "status": { + "type": "string", + "enum": [ + "pass", + "fail", + "unknown" + ], + "description": "unknown when no source on record can decide the check." + }, + "reason": { + "type": "string", + "description": "Machine-readable code of the verdict." + }, + "detail": { + "type": "string", + "description": "One-line English explanation." + } + } + } + }, + "passed": { + "type": "integer" + }, + "failed": { + "type": "integer" + }, + "unknown": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "decision": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecision" + } + ], + "nullable": true, + "description": "Your application's decision on this VASP, or for an entity with no decision of its own its brand's (inherited_from names the brand). Null when there is none. Private to your application." + }, + "entity_screening": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreening" + } + ], + "nullable": true, + "description": "Your application's latest sanctions and adverse-media screening of this VASP. Null when you never screened it. Private to your application." + }, + "questionnaires": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaire" + }, + "description": "Your application's due-diligence questionnaires with this VASP, newest first. Private to your application." + } + }, + "example": { + "id": "0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91", + "kind": "directory", + "name": "Example Counterparty VASP", + "lei": "529900EXAMPLECPTY001", + "jurisdiction": "DE", + "domains": [ + "counterparty.example" + ], + "reachable": true, + "aliases": [ + { + "name": "Example Counterparty", + "kind": "trading_name" + } + ], + "networks": [ + { + "network": "GTR", + "code": "EXAMPLE01", + "listed_name": "Example Counterparty VASP", + "country": "DE", + "verified_at": "2026-10-12T09:30:00Z", + "last_seen_at": "2026-10-12T09:30:00Z", + "source": "staff" + } + ], + "routes": [ + { + "rail": "GTR", + "status": "unavailable", + "reason": "no_membership", + "via": "" + }, + { + "rail": "EMAIL", + "status": "available", + "reason": "", + "via": "contact" + } + ], + "umbrella": null, + "entities": [], + "contacts": [ + { + "id": "6c1f9b8e-2d4a-4f7e-8b3c-5a9d0e1f2a3b", + "email": "travelrule@counterparty.example", + "name": "Travel Rule desk", + "source": "staff", + "scope": "platform", + "verified_at": null + } + ], + "dd_status": "NOT_STARTED", + "dd_score": 62, + "dd_score_breakdown": { + "jurisdiction": { + "points": 25.0, + "max": 25, + "reason": "jurisdiction_not_high_risk", + "detail": "DEU is not on the high-risk jurisdiction list" + }, + "verification": { + "points": 3.8, + "max": 15, + "reason": "dd_status_not_started", + "detail": "due diligence status NOT_STARTED" + }, + "protocol": { + "points": 6.0, + "max": 15, + "reason": "email_only", + "detail": "reachable over the email fallback only" + }, + "lei": { + "points": 10.0, + "max": 10, + "reason": "lei_valid", + "detail": "valid LEI on record" + }, + "history": { + "points": 5.0, + "max": 10, + "reason": "no_history", + "detail": "no exchange history" + }, + "essentials": { + "points": 7.5, + "max": 15, + "reason": "no_essential_checks", + "detail": "no decided essential check yet" + }, + "responsiveness": { + "points": 5.0, + "max": 10, + "reason": "responsiveness_unknown", + "detail": "0 decided recent requests, too few to judge" + } + }, + "dd_assessed_at": "2026-10-12T09:30:00Z", + "dd_score_semantic": "higher_is_safer", + "risk_level": "MEDIUM", + "responsiveness": { + "status": "UNKNOWN", + "answered": 0, + "total": 0, + "computed_at": "2026-10-12T09:30:00Z" + }, + "essential_checks": {}, + "reachable_rails": [ + "GTR", + "EMAIL" + ], + "is_didit_customer": false, + "decision": { + "decision": "APPROVED", + "reason": "Licensed in DE, questionnaire reviewed", + "decided_by": "", + "updated_at": "2026-10-14T10:05:00Z", + "inherited_from": null + }, + "entity_screening": { + "id": "7a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d", + "status": "Approved", + "total_hits": 0, + "score": null, + "screened_at": "2026-10-14T09:58:00Z", + "fresh": true, + "flagged": false + }, + "questionnaires": [ + { + "id": "8b7a6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d", + "status": "ANSWERED", + "form_version": "1", + "sent_to": "travelrule@counterparty.example", + "sent_at": "2026-09-02T08:00:00Z", + "expires_at": "2026-10-02T08:00:00Z", + "answered_at": "2026-09-04T15:20:00Z", + "valid_until": "2027-09-04T15:20:00Z", + "current": true, + "answers": { + "legal_name": "Example Counterparty VASP, incorporated in the example jurisdiction.", + "licensing": "Crypto-asset service provider authorised under MiCA by its national regulator, licence active.", + "jurisdictions": "EU and EEA. We do not serve the United States.", + "aml_program": "Written AML/CFT policy, annual independent review, last completed in March 2026.", + "compliance_officer": "Head of Compliance, reachable at compliance@counterparty.example.", + "sanctions_screening": "Customers at onboarding and daily; counterparties on every transfer.", + "transaction_monitoring": "Rule-based monitoring with analyst review; suspicious activity reports filed with the FIU.", + "travel_rule": "TRP and email; we answer requests within one business day.", + "custody": "Segregated customer wallets held with a qualified custodian, insured.", + "ownership": "No shareholder holds 25 percent or more; parent company Example Holding Group.", + "record_keeping": "Five-year record-keeping for Travel Rule and transaction data, as our regulator requires.", + "data_protection": "Encrypted at rest and in transit, stored in the EU." + } + } + ] + } + }, + "TravelRuleCounterpartyDecision": { + "type": "object", + "description": "Your application's decision on a counterparty VASP. Private to your application: it never changes the shared directory entry and another customer never sees it. It acts only through your transaction rules (the counterparty_dd_status rule field and the travel-rule-counterparty-blocked and travel-rule-counterparty-restricted presets); it never changes how a transfer is routed.", + "properties": { + "decision": { + "type": "string", + "enum": [ + "APPROVED", + "RESTRICTED", + "BLOCKED" + ], + "description": "APPROVED: your team cleared the counterparty. RESTRICTED: transfers to it may go ahead after review. BLOCKED: your team refused it." + }, + "reason": { + "type": "string", + "maxLength": 255, + "description": "Free-text reason you gave, empty when none." + }, + "decided_by": { + "type": "string", + "description": "The console user who made or last changed the decision. Empty when an API key made it." + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "inherited_from": { + "type": "object", + "nullable": true, + "description": "The brand whose decision this entity follows because you made none on the entity itself. Null when the decision is the counterparty's own.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + } + } + } + } + }, + "TravelRuleCounterpartyDecisionResponse": { + "type": "object", + "properties": { + "decision": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecision" + } + ], + "nullable": true, + "description": "The decision that applies to the counterparty for your application: its own, else (for an entity of a brand) the brand's. Null when there is none." + } + } + }, + "TravelRuleCounterpartyDecisionRequest": { + "type": "object", + "required": [ + "decision" + ], + "properties": { + "decision": { + "type": "string", + "enum": [ + "APPROVED", + "RESTRICTED", + "BLOCKED" + ] + }, + "reason": { + "type": "string", + "maxLength": 255, + "description": "Optional. Replaces the previous reason; omit it to clear it." + } + } + }, + "TravelRuleCounterpartyScreening": { + "type": "object", + "description": "One sanctions and adverse-media screening of the counterparty entity, run for and billed to your application. Private to your application: another customer never sees it.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Approved: no possible match needs attention. In Review or Declined: a possible sanctions or adverse-media match." + }, + "total_hits": { + "type": "integer", + "description": "How many possible matches the screening returned." + }, + "score": { + "type": "number", + "nullable": true, + "description": "The highest risk score among the possible matches. Null when nothing matched." + }, + "screened_at": { + "type": "string", + "format": "date-time" + }, + "fresh": { + "type": "boolean", + "description": "Whether the screening is younger than fresh_days (30 days). A fresh screening is reused instead of billed again." + }, + "flagged": { + "type": "boolean", + "description": "true while the screening is fresh and its status is In Review or Declined. While your latest screening is flagged and counterparty screening stays on, counterparty_risk_level is HIGH in your rules." + } + } + }, + "TravelRuleCounterpartyScreeningStatus": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Your counterparty_screening_enabled Travel Rule setting. Off by default; nothing is screened or billed while it is off." + }, + "price": { + "type": "string", + "description": "Decimal string, in USD: what one screening costs your organization, the Company AML Screening price." + }, + "fresh_days": { + "type": "integer", + "description": "How many days a screening stays fresh and is reused." + }, + "screening": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreening" + } + ], + "nullable": true, + "description": "Your latest screening of the counterparty. Null when you never screened it." + } + } + }, + "TravelRuleCounterpartyScreeningRequest": { + "type": "object", + "properties": { + "refresh": { + "type": "boolean", + "default": false, + "description": "true screens again (and bills again) even when a fresh screening exists." + } + } + }, + "TravelRuleCounterpartyScreeningResult": { + "type": "object", + "properties": { + "outcome": { + "type": "string", + "enum": [ + "screened", + "reused" + ], + "description": "screened: a new screening ran and was billed (201). reused: a fresh screening was returned and nothing was billed (200)." + }, + "screening": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreening" + } + } + }, + "TravelRuleCounterpartyQuestionnaire": { + "type": "object", + "description": "One due-diligence questionnaire your application sent the counterparty: Didit's standard questionnaire, emailed to a compliance address on record with a single-use answer link. Private to your application.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "SENT", + "ANSWERED", + "EXPIRED" + ], + "description": "SENT: waiting for answers. ANSWERED: the counterparty answered every question. EXPIRED: the answer link closed unanswered (its deadline passed, or a newer questionnaire replaced it), or answers passed valid_until." + }, + "form_version": { + "type": "string", + "description": "Version of Didit's standard questionnaire the answers belong to. Currently 1." + }, + "sent_to": { + "type": "string", + "format": "email" + }, + "sent_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "The answer-by deadline, 30 days after sending. The link stops working after it." + }, + "answered_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "valid_until": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "How long the answers stay current: 365 days after answering. Null until answered." + }, + "current": { + "type": "boolean", + "description": "true while the questionnaire is ANSWERED and valid_until has not passed." + }, + "answers": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The answers keyed by question id: legal_name, licensing, jurisdictions, aml_program, compliance_officer, sanctions_screening, transaction_monitoring, travel_rule, custody, ownership, record_keeping, data_protection. Each answer is up to 2,000 characters. Empty until answered." + } + } + }, + "TravelRuleCounterpartyQuestionnaireRequest": { + "type": "object", + "properties": { + "sent_to": { + "type": "string", + "format": "email", + "description": "Optional. A compliance address on record for the counterparty: for a directory entry its compliance address or a contact your application sees, for a Didit network member its confirmed compliance email. Default: the address a Travel Rule email to the counterparty goes to (your own contact first, then a platform contact, then the entry's compliance address)." + } + } + }, + "TravelRuleCounterpartyQuestionnaireList": { + "type": "object", + "properties": { + "questionnaires": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaire" + }, + "description": "Newest first." + } + } + }, + "TravelRulePickupInfo": { + "type": "object", + "description": "The pending exchange behind an emailed pickup link, as the counterparty's response page shows it.", + "properties": { + "reference": { + "type": "string", + "format": "uuid", + "description": "The transfer's uuid, as the sending VASP's console shows it." + }, + "requested_at": { + "type": "string", + "format": "date-time" + }, + "originator_vasp_name": { + "type": "string" + }, + "originator_vasp_lei": { + "type": "string" + }, + "recipient_vasp_name": { + "type": "string", + "description": "The counterparty VASP the request is addressed to." + }, + "language": { + "type": "string", + "description": "The language of the request email and the response page, from the counterparty's jurisdiction." + }, + "amount": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "fiat_amount": { + "type": "string", + "description": "The converted value the sender gave, or empty." + }, + "fiat_currency": { + "type": "string" + }, + "chain": { + "type": "string" + }, + "beneficiary_wallet": { + "type": "string" + }, + "status": { + "type": "string" + }, + "ivms_version": { + "type": "string" + }, + "deadline_at": { + "type": "string", + "format": "date-time", + "description": "When the link stops answering." + }, + "reminders_sent": { + "type": "integer" + }, + "contact": { + "type": "string", + "description": "The `?contact=` value of the link, echoed back; empty when the link has none." + }, + "decline_reasons": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "data_mismatch", + "not_our_customer", + "wallet_not_hosted", + "sanctions", + "risk_appetite", + "jurisdiction", + "insufficient_data", + "other" + ] + }, + "description": "The values `decline_reason` accepts." + }, + "join_invitation": { + "type": "object", + "description": "Whether the counterparty holds a live invitation to join Didit for free. The invitation link travels only in the email to the invited address, so `url` is empty here.", + "properties": { + "eligible": { + "type": "boolean" + }, + "url": { + "type": "string" + } + } + }, + "originator_data": { + "type": "object", + "description": "The originator fields the sending VASP shares (`data_sharing_fields`). The name and the wallet are always included." + }, + "beneficiary_data": { + "type": "object" + }, + "ivms_request": { + "type": "object", + "description": "The same request as an IVMS 101 object. Left out while `data_masked` is true." + }, + "data_masked": { + "type": "boolean", + "description": "Always false on this endpoint: an email request is outbound, and the sender's own user is never masked." + } + } + }, + "TravelRulePickupRespondRequest": { + "type": "object", + "required": [ + "decision" + ], + "properties": { + "decision": { + "type": "string", + "enum": [ + "accept", + "decline" + ] + }, + "beneficiary_name": { + "type": "string", + "description": "The beneficiary's name as your records hold it, run through the sending VASP's name rule. `ivms_response` takes precedence when it names the beneficiary.", + "maxLength": 255 + }, + "decline_reason": { + "type": "string", + "enum": [ + "", + "data_mismatch", + "not_our_customer", + "wallet_not_hosted", + "sanctions", + "risk_appetite", + "jurisdiction", + "insufficient_data", + "other" + ], + "description": "Why you decline. Recorded on the sending VASP's transfer timeline. Empty (the default) when you give no reason." + }, + "decline_detail": { + "type": "string", + "maxLength": 512, + "description": "Free text; required when `decision` is `decline` and `decline_reason` is `other`." + }, + "ivms_response": { + "type": "object", + "description": "Your full IVMS 101 response. Its beneficiary name wins over `beneficiary_name`." + }, + "contact": { + "type": "string", + "format": "uuid", + "description": "The contact the link was sent to, from the link's `?contact=` value. When the body leaves it out, the `contact` query parameter is used." + } + } + }, + "TravelRuleInboundRequest": { + "type": "object", + "required": [ + "chain", + "txid", + "wallet_address", + "amount", + "currency" + ], + "properties": { + "chain": { + "type": "string", + "description": "Chain the deposit settled on, for example `ETH`. Stored lowercase and compared case-insensitively with your wallet address book entries, so use the same chain token there.", + "maxLength": 32 + }, + "txid": { + "type": "string", + "description": "On-chain transaction hash. Deduped together with wallet_address.", + "maxLength": 128 + }, + "wallet_address": { + "type": "string", + "description": "The destination wallet you received the deposit on. An XRP X-address is split into its classic address and tag.", + "maxLength": 128 + }, + "amount": { + "type": "string", + "description": "Deposit amount as a decimal string, zero or greater, with up to 18 decimal places." + }, + "currency": { + "type": "string", + "description": "Deposit asset, e.g. \"USDC\".", + "maxLength": 16 + }, + "originator_data": { + "type": "object", + "description": "Originator details from the sending VASP, if known, as a flat object such as `{\"name\": \"...\"}`. Didit reads name (or full_name), first_name, last_name and vendor_data from it.", + "additionalProperties": true + }, + "beneficiary_data": { + "type": "object", + "description": "Beneficiary details (your customer), as a flat object such as `{\"name\": \"...\"}`. Didit matches name against the holder_name of the matched address book entry, and sets wallet_address and chain from the request.", + "additionalProperties": true + }, + "originating_vasp": { + "type": "object", + "description": "Optional description of the sending VASP. It stays on this transfer only (`metadata.originating_vasp`, and the transfer's originator VASP in GET /v3/travel-rule/transfers/); it never creates, edits or links a shared VASP directory entry.", + "properties": { + "name": { + "type": "string", + "maxLength": 255 + }, + "lei": { + "type": "string", + "maxLength": 20, + "description": "Optional. A valid 20-character ISO 17442 LEI, or empty." + }, + "travel_address": { + "type": "string", + "description": "The sender's travel address, recorded on this transfer.", + "maxLength": 512 + } + } + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "Optional. The deposit's destination tag or memo on a memo network: on a shared deposit address it is what tells your customers apart, and the address-book entry for that memo is matched. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`. Alias: `destinationTag` or `destination_tag`." + }, + "utxo": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleUtxo" + } + ], + "description": "Optional, UTXO chains only. The deposit transaction's inputs and outputs: the sender's wallet is the declared `originator_data.wallet_address`, else the largest input." + } + } + }, + "TravelRuleInboundResponse": { + "type": "object", + "properties": { + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Didit transaction UUID for the inbound deposit." + }, + "txn_id": { + "type": "string", + "description": "txn_id of the inbound transaction: Didit-generated when Didit created the transaction, or your own when an unclaimed deposit was reused." + }, + "created": { + "type": "boolean", + "description": "true when this call registered the transfer (HTTP 201); false when a transfer for the same txid and wallet_address already existed (HTTP 200)." + }, + "travel_rule": { + "$ref": "#/components/schemas/TravelRuleTransferDetail" + } + } + }, + "TravelRuleTestVaspInboundRequest": { + "type": "object", + "required": [ + "beneficiary_wallet_address" + ], + "properties": { + "beneficiary_wallet_address": { + "type": "string", + "description": "The destination wallet address. The Didit Test VASP sends to it, matched against your wallet address book on the chain of the asset. A wallet with no matching entry is accepted and the inbound transaction is booked ON_HOLD." + }, + "scenario": { + "type": "string", + "enum": [ + "accept", + "name_mismatch" + ], + "default": "accept", + "description": "What the Didit Test VASP declares the beneficiary to be. `accept` declares the holder's own name; `name_mismatch` declares a name no holder agrees with. The decision itself is always yours." + }, + "chain": { + "type": "string", + "default": "ETH", + "description": "Chain of the asset." + }, + "amount": { + "type": "string", + "default": "1", + "description": "Amount as a decimal string." + }, + "currency": { + "type": "string", + "default": "ETH", + "description": "Asset code." + }, + "reference": { + "type": "string", + "description": "Optional idempotency key. A repeat with the same value returns the transfer the first call booked." + } + } + }, + "TravelRuleTestVaspInboundResponse": { + "type": "object", + "properties": { + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Didit transaction UUID of the inbound transfer." + }, + "txn_id": { + "type": "string", + "description": "Didit-generated txn_id." + }, + "scenario": { + "type": "string", + "description": "The scenario that ran." + }, + "travel_rule_status": { + "type": "string", + "description": "The status your application's own decision resolved to." + }, + "detail": { + "type": "string", + "description": "Why the transfer resolved to that status." + }, + "test_vasp": { + "type": "boolean", + "description": "Always true: the sender was the Didit Test VASP." + }, + "travel_rule": { + "$ref": "#/components/schemas/TravelRuleTransferDetail" + } + } + }, + "TravelRuleWidgetSessionCreate": { + "type": "object", + "properties": { + "wallet_address": { + "type": "string", + "description": "The wallet address to verify. Optional; when empty, the address must be supplied when a proof is started.", + "maxLength": 128 + }, + "chain": { + "type": "string", + "description": "Chain token for the address, for example `ETH`, `BTC` or `SOL`. Use the same token as in your wallet address book. Message signing and the Satoshi test are offered only for a supported chain. The address book entry the proof verifies stores it under its canonical identifier (ETH, BTC, SOL, TRX).", + "maxLength": 32 + }, + "holder_name": { + "type": "string", + "description": "Name of the wallet holder, used for the beneficiary name match.", + "maxLength": 255 + }, + "vendor_data": { + "type": "string", + "description": "Your internal identifier for the holder, carried onto the created address-book entry.", + "maxLength": 255 + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Optional. Didit transaction UUID of a Travel Rule transfer to advance when the proof verifies.", + "nullable": true + }, + "satoshi_deposit_address": { + "type": "string", + "description": "Optional. A deposit address you control; supplying it enables the SATOSHI_TEST proof method.", + "maxLength": 128 + }, + "callback_url": { + "type": "string", + "description": "Optional. Absolute http(s) URL where the widget returns the customer after completion.", + "maxLength": 512 + }, + "expires_in_minutes": { + "type": "integer", + "description": "Optional. Lifetime of the minted link, in minutes. Automatically extended to cover the Satoshi test window when SATOSHI_TEST is offered.", + "default": 60, + "minimum": 5, + "maximum": 10080 + } + } + }, + "TravelRuleWidgetSessionResponse": { + "type": "object", + "properties": { + "widget_session_id": { + "type": "string", + "format": "uuid" + }, + "token": { + "type": "string", + "description": "Opaque session token embedded in the widget URL." + }, + "url": { + "type": "string", + "description": "The hosted wallet-ownership widget URL. Open it verbatim in the system browser (never inside a mobile SDK webview)." + }, + "expires_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleWidgetContext": { + "type": "object", + "properties": { + "vasp_name": { + "type": "string", + "description": "Legal name from your VASP profile, shown to the customer in the widget. Empty when no profile exists." + }, + "wallet_address": { + "type": "string" + }, + "chain": { + "type": "string", + "description": "The chain the session was minted with, as you sent it." + }, + "chain_family": { + "type": "string", + "nullable": true, + "enum": [ + "EVM", + "BTC", + "SOL", + "TRON", + null + ], + "description": "The key family behind chain: EVM (Ethereum, BNB Chain, Polygon, Arbitrum, Base, Optimism, Avalanche, Linea and the other EVM chains), BTC, SOL or TRON. One EVM key controls the same address on every EVM chain. Null when no signature can be verified on the chain." + }, + "holder_name": { + "type": "string" + }, + "allowed_methods": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SCREENSHOT", + "SATOSHI_TEST", + "SELF_DECLARATION" + ] + }, + "description": "Proof methods available for this session. MESSAGE_SIGNING requires a chain with a chain_family, SATOSHI_TEST requires a satoshi_deposit_address on the session and a chain whose deposits Didit can read back, SCREENSHOT requires the allow_screenshot_proof setting, and SELF_DECLARATION requires the allow_self_declaration setting." + }, + "transfer_status": { + "type": "string", + "nullable": true, + "description": "Status of the linked Travel Rule transfer. Null when the session was minted without a transaction_id." + }, + "callback_url": { + "type": "string", + "description": "Where the widget returns the customer after completion. Empty when not set." + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Non-null once a proof has been verified." + }, + "proofs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleWidgetProofSummary" + } + }, + "max_attempts": { + "type": "integer", + "description": "Rejected wallet proofs this session allows." + }, + "attempts_used": { + "type": "integer", + "description": "Rejected proofs so far." + }, + "attempts_remaining": { + "type": "integer", + "description": "When 0, starting another proof answers 409 and you mint a new widget session." + }, + "customization": { + "type": "object", + "nullable": true, + "description": "The application's whitelabel customization (colors, logo, font family) when white label is enabled - the widget renders with the same branding as the verification flow. Null otherwise." + } + } + }, + "TravelRuleWidgetProofSummary": { + "type": "object", + "properties": { + "proof_id": { + "type": "string", + "format": "uuid" + }, + "method": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SATOSHI_TEST", + "SCREENSHOT", + "SELF_DECLARATION" + ] + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ], + "description": "PENDING: the end user picked this method and the proof is not decided yet. A failed signature or a test deposit that is not readable or confirmed yet keeps the proof PENDING with a failure_reason, so the end user can try again; a submitted SATOSHI_TEST deposit is rechecked automatically until its window closes, and a submitted SCREENSHOT waits for your review. VERIFIED: ownership is proven. REJECTED: the test deposit is genuinely wrong (another sender, not the expected amount to the deposit address, older than the challenge, or a transaction that already proved another wallet) or your reviewer rejected the screenshot; failure_reason says which. A rejected proof is final and uses one of the session's attempts. EXPIRED: the proof can no longer be decided (its window closed, the end user switched method, the widget session lapsed, or the wallet's holder changed); failure_reason says which." + }, + "wallet_address": { + "type": "string" + }, + "chain": { + "type": "string", + "description": "Canonical chain identifier of the address book entry the proof is for, e.g. ETH, BTC, SOL, TRX." + }, + "deposit_address": { + "type": "string", + "nullable": true, + "description": "Deposit address the customer must send the Satoshi test amount to. Present only on a SATOSHI_TEST proof." + }, + "expected_amount": { + "type": "string", + "nullable": true, + "description": "Exact on-chain amount, in the deposit's native currency, the Satoshi test deposit must match. Present only on a SATOSHI_TEST proof." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Deadline for this proof's own Satoshi test window. Present only on a SATOSHI_TEST proof; the widget session's own expires_at is extended to cover it when SATOSHI_TEST is offered." + }, + "challenge": { + "type": "string", + "nullable": true, + "description": "Didit-issued message the customer signs with their wallet. Present only on a MESSAGE_SIGNING proof." + }, + "rejection_reason": { + "type": "string", + "description": "Why the proof was rejected, as an English sentence for the end user. Present only when `status` is REJECTED; empty when your reviewer rejected a screenshot (your note stays with you)." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "attempts": { + "type": "integer", + "description": "How many times evidence was submitted for this proof (signatures, transaction ids, uploads)." + }, + "failure_reason": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleWalletProofFailureReason" + } + ], + "description": "Why the last attempt fell short, or why the proof closed. Empty when nothing failed." + } + } + }, + "CaseSubject": { + "type": "object", + "nullable": true, + "description": "The single user or business the case is anchored to.", + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "business" + ] + }, + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "external_id": { + "type": "string", + "nullable": true, + "description": "The subject's vendor_data." + }, + "country": { + "type": "string", + "nullable": true, + "description": "Three-letter country code of the subject (ISO 3166-1 alpha-3), or null when it is not known. For a business, its country of incorporation. For a person, the issuing country of an identity document from their verifications; a person with documents from several countries shows one of them, and ICAO document codes such as `XXA` can appear.", + "example": "ESP" + }, + "portrait": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived signed URL of the person's portrait image. Always null for a business, and null for a person without a portrait. The URL expires, so download the image when you read the case instead of storing the URL." + } + } + }, + "CaseAssignee": { + "type": "object", + "nullable": true, + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "full_name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + } + } + }, + "CaseBlueprintSummary": { + "type": "object", + "nullable": true, + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "maxLength": 24 + } + } + }, + "CaseContentConfig": { + "type": "object", + "description": "The six togglable sections that drive which tabs and fields appear on the case page. Unknown sections or keys are rejected.", + "properties": { + "checklist": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "optional": { + "type": "boolean", + "description": "When true, a case can be resolved with incomplete checklist items." + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "maxLength": 255 + }, + "order": { + "type": "integer" + } + } + } + } + } + }, + "aml_control": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "financial": { + "type": "boolean" + }, + "identity": { + "type": "boolean" + } + } + }, + "identity": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "personal_information": { + "type": "boolean" + }, + "applicant_tags": { + "type": "boolean" + }, + "contact_information": { + "type": "boolean" + }, + "risk_overview": { + "type": "boolean" + }, + "poi_documents": { + "type": "boolean" + }, + "poa_documents": { + "type": "boolean" + } + } + }, + "verifications": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "summary": { + "type": "boolean" + } + } + }, + "reporting": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "fiu_reports": { + "type": "boolean" + } + } + }, + "financial": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "payment_methods": { + "type": "boolean" + }, + "transactions": { + "type": "boolean" + } + } + } + } + }, + "CaseNote": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "comment": { + "type": "string" + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + } + }, + "is_internal": { + "type": "boolean" + }, + "attachments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "s3_key": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "file_size": { + "type": "integer" + }, + "url": { + "type": "string", + "format": "uri" + } + } + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "CaseEvent": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "event_type": { + "type": "string", + "enum": [ + "CREATED", + "STATUS_CHANGED", + "ASSIGNED", + "UNASSIGNED", + "RESOLVED", + "REOPENED", + "LINK_ADDED", + "LINK_REMOVED", + "NOTE_ADDED", + "NOTE_PINNED", + "NOTE_UNPINNED", + "PRIORITY_CHANGED", + "DUE_DATE_CHANGED", + "TAG_ADDED", + "TAG_REMOVED", + "ESCALATED", + "TRANSFERRED", + "CHECKLIST_COMPLETED", + "REPORT_CREATED", + "REPORT_FINALIZED", + "SUBMITTED_FOR_APPROVAL", + "APPROVAL_GRANTED", + "APPROVAL_REJECTED", + "ENTITY_ACTION", + "APPROVAL_INVALIDATED", + "CUSTOMER_REQUEST", + "STAGE_CHANGED", + "TEAM_CHANGED", + "CHECKER_ASSIGNED", + "CHECKER_UNASSIGNED" + ] + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "previous_status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ], + "nullable": true + }, + "new_status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ], + "nullable": true + }, + "metadata": { + "type": "object" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "CaseLink": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "linked_entity_type": { + "type": "string", + "enum": [ + "session", + "business_session", + "transaction" + ], + "nullable": true + }, + "linked_entity_uuid": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "linked_entity_summary": { + "type": "object", + "nullable": true, + "description": "session_id/session_number/status for session and business_session links; uuid/txn_id/status for transaction links." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "CaseChecklistItem": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string", + "maxLength": 255 + }, + "is_completed": { + "type": "boolean" + }, + "completed_by": { + "type": "string", + "format": "email", + "nullable": true + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "order": { + "type": "integer" + } + } + }, + "CaseBlueprintListItem": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "maxLength": 24 + }, + "description": { + "type": "string", + "nullable": true + }, + "assignment_method": { + "type": "string", + "enum": [ + "MANUAL", + "AUTOMATIC" + ] + }, + "assignee_pool": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "max_active_cases": { + "type": "integer", + "nullable": true + }, + "escalation_enabled": { + "type": "boolean" + }, + "transfer_enabled": { + "type": "boolean" + }, + "require_resolution_note": { + "type": "boolean" + }, + "is_active": { + "type": "boolean" + }, + "preset_key": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "version": { + "type": "integer", + "readOnly": true + }, + "default_deadline_hours": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 8760 + } + } + }, + "CaseBlueprintDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "maxLength": 24 + }, + "description": { + "type": "string", + "nullable": true + }, + "assignment_method": { + "type": "string", + "enum": [ + "MANUAL", + "AUTOMATIC" + ] + }, + "assignee_pool": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "max_active_cases": { + "type": "integer", + "nullable": true + }, + "escalation_enabled": { + "type": "boolean" + }, + "transfer_enabled": { + "type": "boolean" + }, + "require_resolution_note": { + "type": "boolean" + }, + "is_active": { + "type": "boolean" + }, + "preset_key": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "escalation_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "escalation_blueprint_name": { + "type": "string", + "nullable": true + }, + "four_eyes_checker_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "four_eyes_checker_blueprint_name": { + "type": "string", + "nullable": true + }, + "content_config": { + "type": "object", + "description": "The six togglable sections that drive which tabs and fields appear on the case page. Unknown sections or keys are rejected.", + "properties": { + "checklist": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "optional": { + "type": "boolean", + "description": "When true, a case can be resolved with incomplete checklist items." + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "maxLength": 255 + }, + "order": { + "type": "integer" + } + } + } + } + } + }, + "aml_control": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "financial": { + "type": "boolean" + }, + "identity": { + "type": "boolean" + } + } + }, + "identity": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "personal_information": { + "type": "boolean" + }, + "applicant_tags": { + "type": "boolean" + }, + "contact_information": { + "type": "boolean" + }, + "risk_overview": { + "type": "boolean" + }, + "poi_documents": { + "type": "boolean" + }, + "poa_documents": { + "type": "boolean" + } + } + }, + "verifications": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "summary": { + "type": "boolean" + } + } + }, + "reporting": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "fiu_reports": { + "type": "boolean" + } + } + }, + "financial": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "payment_methods": { + "type": "boolean" + }, + "transactions": { + "type": "boolean" + } + } + } + } + }, + "creation_sources": { + "type": "array", + "description": "Connectors currently routing new cases into this blueprint.", + "items": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "aml", + "rule", + "workflow" + ] + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + }, + "receives_escalation_from": { + "type": "array", + "description": "Other blueprints whose Escalation target points at this one.", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "version": { + "type": "integer", + "readOnly": true + }, + "default_deadline_hours": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 8760 + } + } + }, + "CaseListItem": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "case_number": { + "type": "string", + "nullable": true, + "description": "Auto-assigned, e.g. CS-0001." + }, + "title": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ] + }, + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ], + "nullable": true + }, + "pending_resolution": { + "type": "object", + "nullable": true, + "description": "Set only while a 4-eyes resolution is staged on the checker blueprint, awaiting approve or reject-approval.", + "properties": { + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ] + }, + "resolution_notes": { + "type": "string", + "nullable": true + }, + "maker_email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email of the officer who submitted the resolution. Null when an API key submitted it: that resolution has no recorded maker, so approve and reject-approval return 403 for every caller." + }, + "maker_name": { + "type": "string", + "nullable": true + }, + "maker_user_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Member user uuid of the officer who submitted the resolution. Null when an API key submitted it. That member cannot be named as `checker`." + }, + "maker_blueprint_id": { + "type": "string", + "format": "uuid" + }, + "staged_at": { + "type": "string", + "format": "date-time" + } + } + }, + "checker": { + "$ref": "#/components/schemas/CaseChecker" + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "source": { + "type": "string", + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ] + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "subject": { + "$ref": "#/components/schemas/CaseSubject" + }, + "assigned_to": { + "$ref": "#/components/schemas/CaseAssignee" + }, + "assigned_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "team": { + "$ref": "#/components/schemas/CaseTeamSummary" + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "created_by_email": { + "type": "string", + "nullable": true + }, + "created_by_name": { + "type": "string", + "nullable": true + }, + "blueprint": { + "$ref": "#/components/schemas/CaseBlueprintSummary" + }, + "links_count": { + "type": "integer" + }, + "notes_count": { + "type": "integer" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "application_id": { + "type": "string", + "format": "uuid" + }, + "application_name": { + "type": "string" + }, + "organization_id": { + "type": "string", + "format": "uuid" + }, + "organization_name": { + "type": "string" + } + } + }, + "CaseDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "case_number": { + "type": "string", + "nullable": true + }, + "title": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ] + }, + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ], + "nullable": true + }, + "resolution_notes": { + "type": "string", + "nullable": true + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "source": { + "type": "string", + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ] + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "subject": { + "$ref": "#/components/schemas/CaseSubject" + }, + "assigned_to": { + "$ref": "#/components/schemas/CaseAssignee" + }, + "assigned_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "team": { + "$ref": "#/components/schemas/CaseTeamSummary" + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resolved_by_email": { + "type": "string", + "nullable": true + }, + "pending_resolution": { + "type": "object", + "nullable": true, + "description": "Set only while a 4-eyes resolution is staged on the checker blueprint, awaiting approve or reject-approval.", + "properties": { + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ] + }, + "resolution_notes": { + "type": "string", + "nullable": true + }, + "maker_email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email of the officer who submitted the resolution. Null when an API key submitted it: that resolution has no recorded maker, so approve and reject-approval return 403 for every caller." + }, + "maker_name": { + "type": "string", + "nullable": true + }, + "maker_user_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Member user uuid of the officer who submitted the resolution. Null when an API key submitted it. That member cannot be named as `checker`." + }, + "maker_blueprint_id": { + "type": "string", + "format": "uuid" + }, + "staged_at": { + "type": "string", + "format": "date-time" + } + } + }, + "checker": { + "$ref": "#/components/schemas/CaseChecker" + }, + "metadata": { + "type": "object" + }, + "blueprint": { + "$ref": "#/components/schemas/CaseBlueprintDetail" + }, + "notes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseNote" + }, + "description": "Newest 50 notes." + }, + "events": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseEvent" + }, + "description": "Newest 100 events." + }, + "links": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseLink" + } + }, + "links_count": { + "type": "integer" + }, + "notes_count": { + "type": "integer" + }, + "checklist_items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseChecklistItem" + } + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "ReportTemplateDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code." + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "report_type_display": { + "type": "string" + }, + "field_config": { + "type": "object", + "description": "Reusable goAML header defaults. Template-level obligatory fields: rentity_id, submission_code, and currency_code_local (3-letter). Optional: enabled_fields (which optional report blocks to include) plus reporting_person, location, reason, and action defaults. Per-filing header fields (action, submission_date, transactions, jurisdiction) are enforced when the report is validated or finalized, not on the template." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "RegulatoryReportDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "case_uuid": { + "type": "string", + "format": "uuid" + }, + "case_number": { + "type": "string", + "nullable": true + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "report_type_display": { + "type": "string" + }, + "report_code": { + "type": "string", + "description": "goAML report code derived from report_type, e.g. STR, CTR, BCR." + }, + "report_number": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "enum": [ + "DRAFT", + "VALIDATED", + "FINALIZED" + ] + }, + "status_display": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2 + }, + "rentity_id": { + "type": "string", + "nullable": true, + "description": "Reporting entity ID issued by the FIU." + }, + "rentity_branch": { + "type": "string", + "nullable": true + }, + "submission_code": { + "type": "string", + "enum": [ + "E", + "M" + ] + }, + "entity_reference": { + "type": "string", + "nullable": true + }, + "fiu_ref_number": { + "type": "string", + "nullable": true + }, + "currency_code_local": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "nullable": true + }, + "submission_date": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reporting_person": { + "type": "object" + }, + "location": { + "type": "object" + }, + "reason": { + "type": "string", + "nullable": true + }, + "action": { + "type": "string", + "nullable": true + }, + "activity": { + "type": "string", + "nullable": true + }, + "narrative": { + "type": "object", + "description": "Five Ws narrative sections.", + "properties": { + "who": { + "type": "string" + }, + "what": { + "type": "string" + }, + "when": { + "type": "string" + }, + "where": { + "type": "string" + }, + "why": { + "type": "string" + }, + "how": { + "type": "string" + } + } + }, + "report_indicators": { + "type": "array", + "items": { + "type": "string" + } + }, + "included_fields": { + "type": "array", + "items": { + "type": "string" + } + }, + "transactions": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "validation_errors": { + "type": "array", + "description": "Result of the most recent validation; each entry is a {field, message} object.", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + }, + "validated_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "finalized_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "created_by_email": { + "type": "string", + "nullable": true + }, + "created_by_name": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TransactionRuleCondition": { + "type": "object", + "required": [ + "field", + "operator" + ], + "properties": { + "field": { + "type": "string", + "description": "Transaction field path to evaluate. Core fields: amount, currency, direction, action_type, score, severity, transaction_type, payment_details, preferred_currency_amount (falls back to amount_in_default_currency), default_currency_code, subject_country, counterparty_country, subject_vendor_data, counterparty_vendor_data, subject_device_fingerprint, counterparty_device_fingerprint, subject_browser_family, subject_browser_version, subject_platform, subject_accept_language, subject_session_age_ms, subject_ip_country, counterparty_ip_country, subject_ip_address, counterparty_ip_address, subject_payment_method_type, subject_payment_method_country, subject_payment_method_fingerprint, subject_payment_method_account_id (and the counterparty_payment_method_* equivalents), travel_rule_status, travel_rule_required, travel_rule_obligations_count, counterparty_dd_status (your decision on the Travel Rule counterparty: APPROVED, RESTRICTED or BLOCKED, an entity following its brand's), counterparty_risk_level (LOW, MEDIUM or HIGH; HIGH while your own counterparty screening is flagged), counterparty_jurisdiction (the counterparty VASP's country), counterparty_responsiveness (RESPONSIVE, UNRESPONSIVE or UNKNOWN), subject_days_since_previous_transaction, tags, and custom_values. for any key submitted in custom_properties on transaction create. Country-code fields accept ISO-2 or ISO-3; both sides are normalized to ISO-3 before comparison." + }, + "operator": { + "type": "string", + "enum": [ + "eq", + "ne", + "gt", + "gte", + "lt", + "lte", + "in", + "not_in", + "contains", + "not_contains", + "contains_any", + "regex", + "fuzzy_match", + "exists", + "is_not_empty", + "is_not_null", + "not_exists", + "is_empty", + "is_null" + ], + "description": "Comparison operator. in/not_in test membership (value may be a list or a single scalar). contains/not_contains are case-insensitive substring checks against a string value. contains_any takes a list of strings and matches when any is a case-insensitive substring of the field value. regex matches the value pattern against the field. fuzzy_match compares against the value string with the required `score` threshold (0-100). gt/gte/lt/lte never match when either side is missing. exists/is_not_empty/is_not_null (aliases) and not_exists/is_empty/is_null (aliases) are presence checks that ignore `value`." + }, + "value": { + "description": "Comparison value. Its shape depends on the operator and value_type; omit it for the presence-check operators. Every other operator needs a value that is not `null` or an empty list, otherwise the rule is refused with `400`. `contains_any` needs a list of strings, and `contains`, `not_contains` and `regex` need a single string." + }, + "value_type": { + "type": "string", + "enum": [ + "list", + "field", + "relative_date" + ], + "description": "Optional value interpretation. `list`: value is a List UUID and the list's entries become the comparison value. `field`: value is another field path, compared field-to-field. `relative_date`: value is a relative-date object, e.g. {\"direction\": \"past\", \"unit\": \"days\", \"amount\": 30}." + }, + "score": { + "type": "number", + "minimum": 0, + "maximum": 100, + "description": "Required when operator is fuzzy_match: the 0-100 similarity threshold." + }, + "group_index": { + "type": "integer", + "description": "Optional grouped logic. When any condition carries group_index, conditions sharing an index form a group and grouped evaluation replaces evaluation_mode." + }, + "group_logic": { + "type": "string", + "enum": [ + "AND", + "OR" + ], + "default": "AND", + "description": "How conditions combine WITHIN a group." + }, + "groups_logic": { + "type": "string", + "enum": [ + "AND", + "OR" + ], + "default": "OR", + "description": "How group outcomes combine ACROSS groups. Read from the first condition." + } + }, + "description": "A condition reads only the transaction being evaluated - it never filters the aggregation window." + }, + "TransactionRuleAggregation": { + "type": "object", + "required": [ + "value" + ], + "properties": { + "metric": { + "type": "string", + "enum": [ + "count", + "sum", + "max", + "min", + "avg", + "distinct_count" + ], + "default": "count", + "description": "How the matching historical transactions are aggregated. unique_count is accepted as an alias of distinct_count." + }, + "field": { + "type": "string", + "default": "amount", + "description": "Field aggregated by sum/max/min/avg/distinct_count. Ignored for count." + }, + "operator": { + "type": "string", + "enum": [ + "eq", + "ne", + "gt", + "gte", + "lt", + "lte" + ], + "default": "eq", + "description": "Comparison applied to the computed metric." + }, + "value": { + "type": "number", + "description": "Threshold the computed metric is compared against." + }, + "window": { + "type": "string", + "pattern": "^\\d+(m|h|d)$", + "default": "1d", + "description": "Look-back window ending at the transaction's txn_date: m (MINUTES, not months), h (hours) or d (days), e.g. 30m, 24h, 7d, 30d." + }, + "filters": { + "type": "object", + "additionalProperties": true, + "description": "Narrows which historical transactions are aggregated. Keys are field paths from the condition field catalog; a value of \"__current__\" resolves to the same field's value on the transaction being evaluated (e.g. {\"subject_vendor_data\": \"__current__\"} scopes the window to the same subject), and a list value means membership. Records without the grouped value are never grouped together: when the transaction being evaluated has no value for a \"__current__\" field (no counterparty, no device fingerprint), the aggregation does not match, whatever its operator. Filters compare by equality or membership ONLY - there is no lt/gte filter, so a range such as \"historical transactions under 10,000\" cannot be expressed here. Put the range on the metric instead (metric max with operator lt keeps every transaction in the window below the threshold)." + } + }, + "description": "Velocity check over the application's historical transactions inside the window. Every aggregation entry must match for the rule to match, regardless of evaluation_mode. An aggregation constrains only the window; the transaction being evaluated is constrained by scope and conditions, so repeat direction, currency or transaction_type on both halves when the rule promises them." + }, + "TransactionRuleScope": { + "type": "object", + "properties": { + "transaction_types": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Restrict to transaction types, e.g. finance, kyc, travel_rule, user_event. Empty or missing = no restriction." + }, + "directions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ] + }, + "description": "Restrict to transaction directions (case-insensitive on write)." + }, + "action_types": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Restrict to your action_type values, e.g. withdrawal, deposit." + } + }, + "description": "Which transactions the rule applies to. Only these three keys are accepted." + }, + "TransactionRuleAction": { + "oneOf": [ + { + "$ref": "#/components/schemas/TransactionRuleActionAddScore" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionChangeStatus" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionAddTags" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionAddNote" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionAddToList" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionOpenCase" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "add_score": "#/components/schemas/TransactionRuleActionAddScore", + "change_status": "#/components/schemas/TransactionRuleActionChangeStatus", + "add_tags": "#/components/schemas/TransactionRuleActionAddTags", + "add_note": "#/components/schemas/TransactionRuleActionAddNote", + "add_to_list": "#/components/schemas/TransactionRuleActionAddToList", + "open_case": "#/components/schemas/TransactionRuleActionOpenCase" + } + }, + "description": "One rule action. A rule can carry several; all are applied together when it matches. Rules in TEST mode record what they would have done without applying any action." + }, + "TransactionRuleCreateRequest": { + "type": "object", + "required": [ + "title", + "category", + "mode" + ], + "properties": { + "title": { + "type": "string", + "description": "Rule name. Unique per application among non-deleted rules; duplicates return 400." + }, + "description": { + "type": "string", + "nullable": true + }, + "category": { + "type": "string", + "description": "Risk theme used for grouping and reporting, e.g. finance, aml_ctf, anomaly_detection, fatf, device_intelligence, crypto_monitoring, travel_rule, responsible_gaming, e_commerce." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ], + "description": "ACTIVE evaluates live. TEST evaluates and records rule runs but never touches score or status - the recommended starting mode. DISABLED is skipped." + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ], + "default": "ALL", + "description": "Whether every condition must match (ALL) or at least one (ANY). Ignored when conditions use group_index." + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + } + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + } + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAction" + } + }, + "metadata": { + "type": "object", + "description": "Free-form metadata stored on the rule." + } + }, + "description": "Creates a CUSTOM rule. Rules in ACTIVE mode take effect immediately for new transactions - create in TEST mode and backtest first." + }, + "TransactionRuleUpdateRequest": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Rule name. Unique per application among non-deleted rules; duplicates return 400." + }, + "description": { + "type": "string", + "nullable": true + }, + "category": { + "type": "string", + "description": "Risk theme used for grouping and reporting, e.g. finance, aml_ctf, anomaly_detection, fatf, device_intelligence, crypto_monitoring, travel_rule, responsible_gaming, e_commerce." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ], + "description": "ACTIVE evaluates live. TEST evaluates and records rule runs but never touches score or status - the recommended starting mode. DISABLED is skipped." + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ], + "default": "ALL", + "description": "Whether every condition must match (ALL) or at least one (ANY). Ignored when conditions use group_index." + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + } + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + } + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAction" + } + }, + "metadata": { + "type": "object", + "description": "Free-form metadata stored on the rule." + } + }, + "description": "Partial update. Custom rules accept every field; library (PRESET) rules accept only mode - anything else returns 400." + }, + "TransactionRuleDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "title": { + "type": "string", + "description": "Rule name. Unique per application among non-deleted rules; duplicates return 400." + }, + "description": { + "type": "string", + "nullable": true + }, + "category": { + "type": "string", + "description": "Risk theme used for grouping and reporting, e.g. finance, aml_ctf, anomaly_detection, fatf, device_intelligence, crypto_monitoring, travel_rule, responsible_gaming, e_commerce." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ], + "description": "ACTIVE evaluates live. TEST evaluates and records rule runs but never touches score or status - the recommended starting mode. DISABLED is skipped." + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ], + "default": "ALL", + "description": "Whether every condition must match (ALL) or at least one (ANY). Ignored when conditions use group_index." + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + } + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + } + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAction" + } + }, + "metadata": { + "type": "object", + "description": "Free-form metadata stored on the rule." + }, + "severity": { + "type": "string", + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ], + "nullable": true, + "description": "Informational severity carried by library rules; not settable on custom rules." + }, + "source": { + "type": "string", + "enum": [ + "PRESET", + "CUSTOM" + ], + "description": "PRESET rules come from the library (install/uninstall, mode-only edits); CUSTOM rules are fully yours." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Stable library identifier; null for custom rules." + }, + "latest_triggered_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "run_count": { + "type": "integer", + "description": "How many transactions matched this rule." + }, + "approved_count": { + "type": "integer" + }, + "reviewed_count": { + "type": "integer" + }, + "declined_count": { + "type": "integer" + }, + "awaiting_user_count": { + "type": "integer" + }, + "approved_pct": { + "type": "number" + }, + "reviewed_pct": { + "type": "number" + }, + "declined_pct": { + "type": "number" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TransactionRuleBacktestRequest": { + "type": "object", + "properties": { + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + }, + "default": [] + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + }, + "default": [] + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ], + "default": "ALL" + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "period_days": { + "type": "integer", + "minimum": 1, + "maximum": 365, + "default": 90 + } + }, + "description": "Hypothetical rule configuration to evaluate against recent transactions. Read-only and free: nothing is created or modified. Send the same conditions, aggregation, evaluation_mode AND scope as the rule you intend to activate. Omitted conditions, aggregation and scope add no constraints of their own; dropping scope can include directions or transaction types the saved rule skips. Omitted evaluation_mode defaults to ALL (which can reduce matches for an ANY rule), and omitted period_days defaults to 90 days, not unlimited history." + }, + "TransactionRuleBacktestResult": { + "type": "object", + "properties": { + "evaluated": { + "type": "integer", + "description": "Transactions evaluated (most recent first, capped)." + }, + "matched": { + "type": "integer", + "description": "Transactions the configuration would have matched." + }, + "affected_entities": { + "type": "integer", + "description": "Distinct subjects (by vendor_data) among the matches." + }, + "period_days": { + "type": "integer" + } + } + }, + "TransactionRuleLibraryEntry": { + "type": "object", + "properties": { + "library_key": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "category": { + "type": "string" + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ] + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + } + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + } + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAction" + } + }, + "metadata": { + "type": "object" + }, + "bundle": { + "type": "string", + "nullable": true, + "description": "Functional family, e.g. finance, aml_ctf, crypto_monitoring, travel_rule, responsible_gaming." + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "industries": { + "type": "array", + "items": { + "type": "string" + } + }, + "source": { + "type": "string" + }, + "is_installed": { + "type": "boolean", + "description": "Whether this preset is currently installed on the application." + } + } + }, + "TransactionRuleInstallRequest": { + "type": "object", + "properties": { + "library_keys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Specific presets to install or uninstall." + }, + "bundle": { + "type": "string", + "description": "Install or uninstall every preset in this bundle instead." + } + }, + "description": "Provide library_keys OR bundle." + }, + "TransactionRuleInstallResult": { + "type": "object", + "properties": { + "installed_library_keys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "POST (install) only: presets installed by this call." + }, + "installed_count": { + "type": "integer", + "description": "POST (install) only." + }, + "uninstalled_count": { + "type": "integer", + "description": "DELETE (uninstall) only: presets removed by this call." + } + } + }, + "WalletScreeningResult": { + "type": "object", + "properties": { + "provider": { + "type": "string", + "description": "Provider that performed the screening (e.g. `merklescience`, `crystal`)." + }, + "screening_type": { + "type": "string", + "enum": [ + "WALLET_SCREENING" + ], + "description": "Always `WALLET_SCREENING` for this endpoint." + }, + "risk_score": { + "type": "integer", + "description": "Normalised 0-100 risk score. Higher means greater exposure to risky entities. Merkle Science grades an address 0-5, which maps to 0, 15, 35, 55, 75 or 95." + }, + "severity": { + "type": "string", + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ], + "description": "Risk bucket derived from `risk_score`: `0-9` UNKNOWN, `10-39` LOW, `40-69` MEDIUM, `70-89` HIGH, `90-100` CRITICAL. `UNKNOWN` is the lowest band, not a separate no-data state: `risk_score` 0 (the common clean-address case) means no adverse assessment, while a non-zero score in the 1-9 range is a real but sub-LOW signal - read the `risk_score`, not just the band. Never treat `UNKNOWN` as an affirmative low-risk or clear rating; do not display it as a pass." + }, + "status": { + "type": "string", + "enum": [ + "SCREENED", + "PENDING", + "ERROR" + ], + "description": "Screening outcome status." + }, + "summary": { + "type": "string", + "description": "Human-readable summary of the screening result." + }, + "wallet_address": { + "type": "string", + "description": "The screened address, echoed back." + }, + "transaction_hash": { + "type": "string", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "blockchain": { + "type": "string", + "description": "The blockchain that was screened." + }, + "block_timestamp": { + "type": "string", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "total_value_usd": { + "type": "number", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "fee_usd": { + "type": "number", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "sender_rows": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Transaction screening field, shared with the result shape. Always empty on a wallet screening result." + }, + "receiver_rows": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Transaction screening field, shared with the result shape. Always empty on a wallet screening result." + }, + "transaction_flows": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Transaction screening field, shared with the result shape. Always empty on a wallet screening result." + }, + "transaction_flows_status": { + "type": "string", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "transaction_flows_error": { + "type": "string", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "source_of_funds": { + "type": "array", + "description": "Where the address received funds from, attributed by entity. Each entry is an exposure breakdown.", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "description": "Normalised risk category, e.g. `exchange_licensed`, `dex`, `defi`, `payment_processor`, `mixer`, `darknet_market`, `sanctioned`, `stolen_funds`, `high_risk_exchange`." + }, + "percentage": { + "type": "number", + "description": "Share of the direction's total exposure, 0-100." + }, + "amount_usd": { + "type": "number", + "nullable": true, + "description": "USD value attributed to this entity." + }, + "entity_name": { + "type": "string", + "nullable": true, + "description": "Name of the attributed entity, for example an exchange, a mixer or a sanctioned group." + }, + "entity_type": { + "type": "string", + "nullable": true, + "description": "Provider entity type (e.g. `Exchange`, `DeFi`, `Sanctions`, `Theft`)." + }, + "entity_subtype": { + "type": "string", + "nullable": true, + "description": "More granular subtype (e.g. `Mandatory KYC and AML`, `OFAC`, `Yield Aggregator`)." + }, + "hops": { + "type": "integer", + "nullable": true, + "description": "Number of hops to the entity (1 for a direct counterparty), when the provider reports one. Always `null` for Merkle Science, which reports only whether exposure is direct or indirect: use `exposure_type`." + }, + "description": { + "type": "string", + "nullable": true, + "description": "`entity_type / entity_subtype`, as the provider labels the entity." + }, + "is_direct": { + "type": "boolean", + "description": "True when `exposure_type` is `direct`." + }, + "exposure_direction": { + "type": "string", + "enum": [ + "incoming", + "outgoing", + "connected" + ], + "description": "Whether the funds came from (`incoming`) or went to (`outgoing`) this entity." + }, + "exposure_type": { + "type": "string", + "enum": [ + "direct", + "indirect" + ], + "description": "`direct` (1 hop) or `indirect` (multi-hop) exposure." + }, + "provider_entity_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for the entity." + }, + "provider_entity_type_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for `entity_type`." + }, + "provider_entity_subtype_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for `entity_subtype`." + }, + "country": { + "type": "string", + "nullable": true, + "description": "Primary ISO 3166-1 alpha-2 country of the entity. Empty or `null` when the provider does not know it." + }, + "raw_amount": { + "type": "number", + "nullable": true, + "description": "Amount in the asset's own unit, when the provider reports it." + }, + "raw_currency": { + "type": "string", + "nullable": true, + "description": "Unit of `raw_amount`, when the provider reports it." + } + } + } + }, + "destination_of_funds": { + "type": "array", + "description": "Where the address sent funds to, attributed by entity. Same item shape as `source_of_funds` with `exposure_direction` = `outgoing`.", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "description": "Normalised risk category, e.g. `exchange_licensed`, `dex`, `defi`, `payment_processor`, `mixer`, `darknet_market`, `sanctioned`, `stolen_funds`, `high_risk_exchange`." + }, + "percentage": { + "type": "number", + "description": "Share of the direction's total exposure, 0-100." + }, + "amount_usd": { + "type": "number", + "nullable": true, + "description": "USD value attributed to this entity." + }, + "entity_name": { + "type": "string", + "nullable": true, + "description": "Name of the attributed entity, for example an exchange, a mixer or a sanctioned group." + }, + "entity_type": { + "type": "string", + "nullable": true, + "description": "Provider entity type (e.g. `Exchange`, `DeFi`, `Sanctions`, `Theft`)." + }, + "entity_subtype": { + "type": "string", + "nullable": true, + "description": "More granular subtype (e.g. `Mandatory KYC and AML`, `OFAC`, `Yield Aggregator`)." + }, + "hops": { + "type": "integer", + "nullable": true, + "description": "Number of hops to the entity (1 for a direct counterparty), when the provider reports one. Always `null` for Merkle Science, which reports only whether exposure is direct or indirect: use `exposure_type`." + }, + "description": { + "type": "string", + "nullable": true, + "description": "`entity_type / entity_subtype`, as the provider labels the entity." + }, + "is_direct": { + "type": "boolean", + "description": "True when `exposure_type` is `direct`." + }, + "exposure_direction": { + "type": "string", + "enum": [ + "incoming", + "outgoing", + "connected" + ], + "description": "Whether the funds came from (`incoming`) or went to (`outgoing`) this entity." + }, + "exposure_type": { + "type": "string", + "enum": [ + "direct", + "indirect" + ], + "description": "`direct` (1 hop) or `indirect` (multi-hop) exposure." + }, + "provider_entity_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for the entity." + }, + "provider_entity_type_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for `entity_type`." + }, + "provider_entity_subtype_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for `entity_subtype`." + }, + "country": { + "type": "string", + "nullable": true, + "description": "Primary ISO 3166-1 alpha-2 country of the entity. Empty or `null` when the provider does not know it." + }, + "raw_amount": { + "type": "number", + "nullable": true, + "description": "Amount in the asset's own unit, when the provider reports it." + }, + "raw_currency": { + "type": "string", + "nullable": true, + "description": "Unit of `raw_amount`, when the provider reports it." + } + } + } + }, + "counterparty_connections": { + "type": "array", + "description": "Entities the provider attributes the screened address to. For Merkle Science these come from the address's own owner and user tags, so the same entity can appear twice, and they carry no fund flows: `received_usd`, `sent_usd`, `received_hops` and `sent_hops` are `null` and `percentage` is 0. Do not route on `risk_level` or `is_direct` here: use `sanctions_hit`, `dominant_risk_category` and `risk_factors[].is_high_risk` for the verdict, and `exposure_type` in `source_of_funds`/`destination_of_funds` for directness.", + "items": { + "type": "object", + "properties": { + "entity_name": { + "type": "string", + "nullable": true, + "description": "Attributed entity name." + }, + "entity_type": { + "type": "string", + "nullable": true, + "description": "Provider entity type." + }, + "entity_subtype": { + "type": "string", + "nullable": true, + "description": "Provider entity subtype." + }, + "risk_level": { + "type": "string", + "description": "Lowercase and provider-dependent. For Merkle Science it is the address's own 0-5 risk grade (`no_risk`, `low`, `medium_low`, `medium`, `high`, `critical`), raised to `high` when the entity's category is high risk; it is not a separate grade of the entity." + }, + "categories": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Normalised risk categories of the entity." + }, + "received_usd": { + "type": "number", + "nullable": true, + "description": "USD received from the entity, when the provider reports it." + }, + "sent_usd": { + "type": "number", + "nullable": true, + "description": "USD sent to the entity, when the provider reports it." + }, + "received_hops": { + "type": "integer", + "nullable": true, + "description": "Hops to the entity on the receiving side, when the provider reports them." + }, + "sent_hops": { + "type": "integer", + "nullable": true, + "description": "Hops to the entity on the sending side, when the provider reports them." + }, + "percentage": { + "type": "number", + "description": "Share of the address's exposure, when the provider reports it; otherwise 0." + }, + "is_direct": { + "type": "boolean", + "description": "Always `true` for Merkle Science, whose connections are attributions rather than counterparties." + }, + "country": { + "type": "string", + "nullable": true, + "description": "Country of the entity, when known." + }, + "provider_entity_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for the entity." + } + } + } + }, + "network_nodes": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Exposure graph nodes for transaction screening. Empty on a wallet screening result." + }, + "network_edges": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Exposure graph edges for transaction screening. Empty on a wallet screening result." + }, + "sanctions_hit": { + "type": "boolean", + "description": "True if the address has direct or indirect sanctions exposure." + }, + "pep_counterparty": { + "type": "boolean", + "description": "Politically exposed person exposure. `null` when no PEP finding is available, which is every result today because no provider reports one; `false` only for an explicit negative screening result. Do not read `null` as a completed PEP check.", + "nullable": true + }, + "dominant_risk_category": { + "type": "string", + "nullable": true, + "description": "Highest-weighted high-risk category, or `null` when none is dominant (e.g. `sanctioned`, `mixer`, `stolen_funds`)." + }, + "risk_factors": { + "type": "array", + "description": "Ranked explanation of `risk_score`: what produced this verdict. Providers grade an address on their own model and return only a verdict (Merkle Science returns an integer 0-5), so the score alone is not actionable. Ordered high-risk first, then by the value that moved through each driver, capped at the five strongest; `source_of_funds` and `destination_of_funds` always carry the complete breakdown. A high `risk_score` with no `is_high_risk` entry means the provider reported no sanctions or attributed-entity match and graded the address purely on its own exposure model - `summary` states this explicitly.", + "items": { + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": [ + "attribution", + "exposure" + ], + "description": "`attribution` is what the provider says the address itself is (its own tag or cluster); `exposure` is where its funds actually moved." + }, + "category": { + "type": "string", + "description": "Normalised risk category, same vocabulary as the exposure breakdown." + }, + "label": { + "type": "string", + "description": "The provider's own wording for the driver (e.g. `Gambling / Crypto only`), which is what you would also see in the provider's console." + }, + "entity_name": { + "type": "string", + "nullable": true, + "description": "Attributed entity behind the driver, for example a gambling site or an exchange." + }, + "direction": { + "type": "string", + "enum": [ + "incoming", + "outgoing", + "connected", + "unknown", + "attribution" + ], + "description": "Direction of the fund flow, `attribution` when the driver is the address's own tag, or `unknown` when the provider did not report a direction." + }, + "exposure_type": { + "type": "string", + "enum": [ + "direct", + "indirect", + "unknown", + "attribution" + ], + "description": "`direct` (1 hop) or `indirect` (multi-hop), `attribution` for the address's own tag, or `unknown` when the provider did not state how many hops away the counterparty is. Treat `unknown` as unclassified, not as `direct`." + }, + "amount_usd": { + "type": "number", + "format": "float", + "nullable": true, + "description": "USD value attributed to this driver." + }, + "percentage": { + "type": "number", + "format": "float", + "description": "Share of the address's TOTAL screened volume, both directions combined - unlike `percentage` inside `source_of_funds`/`destination_of_funds`, which is normalised per direction. Comparable and summable across the whole list." + }, + "is_high_risk": { + "type": "boolean", + "description": "Whether the category is one Didit treats as high risk (`sanctioned`, `mixer`, `stolen_funds`, `ransomware`, `scam`, `darknet_market`, `terrorist_financing`, `child_exploitation`, `hacked_exchange`, `high_risk_exchange`, `seized_funds`)." + }, + "description": { + "type": "string", + "description": "One-line rendering of the driver, e.g. `9.01% indirect outgoing exposure to Gambling / Crypto only (Example Gambling)`." + } + } + } + }, + "raw_response": { + "type": "object", + "additionalProperties": true, + "description": "The provider's own payload. Its shape is provider-specific and can change without notice: read the normalised fields instead of parsing it." + }, + "report_signature": { + "type": "string", + "description": "Opaque HMAC-SHA256 signature over this exact result, scoped to your application. Not meaningful on its own — repost the full response body, unmodified and including this field, to `POST /v3/wallet-screening/pdf/` to render it as a PDF. The PDF endpoint rejects the request with `400` if `report_signature` is missing or if any signed field (e.g. `risk_score`, `severity`, `sanctions_hit`) was edited before repost." + } + }, + "required": [ + "provider", + "screening_type", + "risk_score", + "severity", + "status", + "summary", + "wallet_address", + "blockchain", + "sanctions_hit", + "dominant_risk_category", + "source_of_funds", + "destination_of_funds", + "counterparty_connections", + "report_signature" + ] + }, + "NetworkRiskDisplay": { + "type": "object", + "properties": { + "score": { + "type": "integer", + "description": "Risk score, 0-100." + }, + "band": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ], + "description": "Band the score renders as. high >= 70, medium >= 40, otherwise low." + }, + "bar": { + "type": "number", + "format": "float", + "description": "Normalized 0-1 value for rendering a risk bar." + } + } + }, + "NetworkNodeTag": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string", + "nullable": true, + "description": "Null for guard tags, whose color token the renderer owns." + }, + "source": { + "type": "string", + "enum": [ + "subject_tag", + "session_tag", + "transaction_tag", + "signal_guard" + ], + "description": "Where the tag came from: a tag on the user/business, a tag on a session in this network, a tag on a transaction a party belongs to, or a signal guard condition (e.g. hub, datacenter IP)." + }, + "category": { + "type": "string", + "enum": [ + "customer", + "quality" + ], + "nullable": true, + "description": "customer for customer-defined tags, quality for signal-quality guard tags. Null when not applicable." + } + } + }, + "NetworkListItem": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "network_id": { + "type": "string", + "description": "Human-readable per-organization sequential id, e.g. N-0001." + }, + "name": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "enum": [ + "active", + "in_review", + "resolved", + "dismissed" + ], + "description": "Network lifecycle status. Distinct from the per-applicant outcome_status inside a network (approved/in_review/declined/not_completed)." + }, + "risk": { + "$ref": "#/components/schemas/NetworkRiskDisplay" + }, + "pattern_types": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "exact_same_device", + "similar_device", + "same_ip_address", + "same_address", + "similar_selfie_backgrounds", + "similar_poa_documents", + "same_document_number", + "same_phone_number", + "same_email" + ] + }, + "description": "The clustering patterns detected in this network." + }, + "signal_types": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ip_address", + "device", + "face", + "document_number", + "phone", + "email", + "address" + ] + }, + "description": "The distinct signal types present in this network." + }, + "member_counts": { + "type": "object", + "description": "Counts of members by category (e.g. total, per outcome_status)." + }, + "member_status": { + "type": "object", + "description": "Breakdown of members by outcome_status." + }, + "signal_coverage": { + "type": "object", + "description": "Per signal_type coverage of this network's members." + }, + "first_activity_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_activity_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "NetworkDetail": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkListItem" + }, + { + "type": "object", + "properties": { + "stats": { + "type": "object", + "description": "Aggregate stats for the network (e.g. member totals)." + }, + "fraud_tags": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Fraud-pattern tags surfaced on the network." + }, + "risk_signals": { + "type": "object", + "description": "Risk-signal summaries: residential_proxy_reuse, email_intelligence, phone_intelligence." + }, + "reviewed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reviewed_by": { + "type": "string", + "nullable": true + }, + "review_reason": { + "type": "string", + "nullable": true + } + } + } + ], + "properties": { + "notes_count": { + "type": "integer", + "minimum": 0, + "readOnly": true, + "description": "Number of private analyst notes in this investigation." + } + } + }, + "NetworkGraphNode": { + "type": "object", + "description": "A subject node or a signal node, distinguished by kind. Subject nodes (kind: vendor_user | vendor_business | transaction_party) carry id, kind, subject, node_status (approved | in_review | declined | not_completed), and tags. Signal nodes (kind: signal) carry id, kind, uuid, signal_type, subject_count, coverage, is_hub, is_datacenter_ip, is_high_collision_device, node_status (signal_active | signal_guarded), tags, risk_evidence, first_seen_at, and last_seen_at.", + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "vendor_user", + "vendor_business", + "transaction_party", + "signal" + ] + } + } + }, + "NetworkGraphEdge": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "source": { + "type": "string", + "description": "Subject node id." + }, + "target": { + "type": "string", + "description": "Signal node id." + }, + "subject_kind": { + "type": "string", + "enum": [ + "vendor_user", + "vendor_business", + "transaction_party" + ] + }, + "signal_type": { + "type": "string", + "enum": [ + "ip_address", + "device", + "face", + "document_number", + "phone", + "email", + "address" + ] + }, + "confidence": { + "type": "number", + "format": "float", + "description": "1.0 for an exact match." + }, + "observation_count": { + "type": "integer" + }, + "first_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "NetworkGraph": { + "type": "object", + "properties": { + "network": { + "$ref": "#/components/schemas/NetworkDetail" + }, + "depth": { + "type": "integer", + "nullable": true, + "description": "Effective depth applied, or null when unbounded (depth=all)." + }, + "truncated": { + "type": "boolean", + "description": "True if the neighborhood was cut off by depth." + }, + "nodes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkGraphNode" + } + }, + "edges": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkGraphEdge" + } + }, + "metrics": { + "type": "object", + "properties": { + "node_count": { + "type": "integer" + }, + "edge_count": { + "type": "integer" + }, + "hub_signal_count": { + "type": "integer" + } + } + } + } + }, + "NetworkMember": { + "type": "object", + "properties": { + "subject": { + "type": "object", + "description": "The vendor_user, vendor_business, or transaction_party this member represents." + }, + "outcome_status": { + "type": "string", + "description": "The subject's verification/decision outcome." + }, + "node_status": { + "type": "string", + "enum": [ + "approved", + "in_review", + "declined", + "not_completed", + "signal_active", + "signal_guarded" + ], + "description": "For a member row this is always one of the subject-outcome values: approved, in_review, declined, or not_completed." + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkNodeTag" + } + }, + "risk": { + "$ref": "#/components/schemas/NetworkRiskDisplay" + }, + "signal_coverage": { + "type": "object" + }, + "shared_infrastructure": { + "type": "object" + }, + "joined_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "provenance": { + "type": "object", + "properties": { + "session_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "business_session_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "transaction_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "NetworkSignal": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "signal_type": { + "type": "string", + "enum": [ + "ip_address", + "device", + "face", + "document_number", + "phone", + "email", + "address" + ] + }, + "subject_count": { + "type": "integer" + }, + "coverage": { + "type": "number", + "format": "float" + }, + "is_hub": { + "type": "boolean", + "description": "True when this signal links an unusually large number of subjects." + }, + "is_datacenter_ip": { + "type": "boolean" + }, + "is_high_collision_device": { + "type": "boolean" + }, + "node_status": { + "type": "string", + "enum": [ + "signal_active", + "signal_guarded" + ] + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkNodeTag" + } + }, + "risk_evidence": { + "type": "object" + }, + "first_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "NetworkTimelineEvent": { + "type": "object", + "properties": { + "event_type": { + "type": "string", + "description": "e.g. network_detected, signal_observed, network_in_review, network_resolved, network_dismissed." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "label": { + "type": "string" + }, + "metadata": { + "type": "object" + } + } + }, + "NetworkMap": { + "type": "object", + "properties": { + "network": { + "$ref": "#/components/schemas/NetworkDetail" + }, + "convergence_count": { + "type": "integer", + "description": "Number of distinct address/location points members converge on." + }, + "countries": { + "type": "array", + "items": { + "type": "string" + } + }, + "points": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "country": { + "type": "string", + "nullable": true + }, + "country_code": { + "type": "string", + "nullable": true + }, + "city": { + "type": "string", + "nullable": true + }, + "latitude": { + "type": "number", + "format": "float", + "nullable": true + }, + "longitude": { + "type": "number", + "format": "float", + "nullable": true + }, + "observed_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + } + } + } + }, + "EntityNetworkMembership": { + "type": "object", + "properties": { + "network": { + "$ref": "#/components/schemas/NetworkListItem" + }, + "subject": { + "type": "object" + }, + "signal_types": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ip_address", + "device", + "face", + "document_number", + "phone", + "email", + "address" + ] + } + }, + "signal_count": { + "type": "integer" + }, + "joined_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "RiskSignals": { + "type": "object", + "description": "Optional device and network context observed by your application. All fields are optional. Passing them feeds cross-session match detection in Networks.", + "properties": { + "ip": { + "type": "string", + "format": "ip", + "description": "IPv4 or IPv6 address observed for the end user.", + "example": "192.0.2.1" + }, + "device_id": { + "type": "string", + "maxLength": 255, + "description": "Stable device identifier collected by your application (Android `ANDROID_ID`, iOS `identifierForVendor`)." + }, + "device_platform": { + "type": "string", + "enum": [ + "android", + "ios", + "ipados", + "tvos", + "web" + ], + "description": "Platform of the end user's device." + }, + "device_model": { + "type": "string", + "maxLength": 255, + "description": "Device model reported by your application." + }, + "os_version": { + "type": "string", + "maxLength": 64, + "description": "Operating-system version." + }, + "app_version": { + "type": "string", + "maxLength": 64, + "description": "Version of your application." + }, + "user_agent": { + "type": "string", + "maxLength": 512, + "description": "Browser or app user agent." + } + } + }, + "PhoneRiskRequest": { + "type": "object", + "required": [ + "phone_number" + ], + "properties": { + "phone_number": { + "type": "string", + "maxLength": 20, + "description": "Phone number to score. It is validated and normalized to E.164.", + "example": "+14155552671" + }, + "country_code": { + "type": "string", + "maxLength": 3, + "description": "Optional ISO 3166-1 alpha-2 or alpha-3 country hint for intelligence coverage.", + "example": "US" + }, + "signals": { + "$ref": "#/components/schemas/RiskSignals" + }, + "vendor_data": { + "type": "string", + "description": "Your stable reference for this request. Stored on the API session and echoed in the response." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Your non-sensitive JSON metadata. Stored on the API session and echoed in the response." + }, + "enable_social": { + "type": "boolean", + "default": false, + "description": "When true, also look up the social footprint of the number - the messaging, social, e-commerce and professional platforms it is registered on. Billed as one `phone_social` unit per request in addition to this endpoint's own price.", + "example": true + } + } + }, + "EmailRiskRequest": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "Email address to score.", + "example": "alice@example.com" + }, + "country_code": { + "type": "string", + "maxLength": 3, + "description": "Optional ISO 3166-1 alpha-2 or alpha-3 country hint for intelligence coverage.", + "example": "US" + }, + "signals": { + "$ref": "#/components/schemas/RiskSignals" + }, + "vendor_data": { + "type": "string", + "description": "Your stable reference for this request. Stored on the API session and echoed in the response." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Your non-sensitive JSON metadata. Stored on the API session and echoed in the response." + }, + "enable_social": { + "type": "boolean", + "default": false, + "description": "When true, also look up the social footprint of the address - the email, social, professional, e-commerce and entertainment platforms it is registered on. Billed as one `email_social` unit per request in addition to this endpoint's own price.", + "example": true + } + } + }, + "IPRiskRequest": { + "type": "object", + "required": [ + "ip_address" + ], + "properties": { + "ip_address": { + "type": "string", + "format": "ip", + "description": "IPv4 or IPv6 address to score.", + "example": "8.8.8.8" + }, + "claimed_country": { + "type": "string", + "maxLength": 3, + "description": "Optional ISO 3166-1 alpha-2 or alpha-3 country claimed by the user or session. Used to calculate `country_mismatch`.", + "example": "ESP" + }, + "user_agent": { + "type": "string", + "maxLength": 512, + "description": "User agent observed by your backend. Stored as request context; it does not provide device fingerprinting." + }, + "vendor_data": { + "type": "string", + "description": "Your stable reference for this request. Stored on the API session and echoed in the response." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Your non-sensitive JSON metadata. Stored on the API session and echoed in the response." + } + } + }, + "RiskWarning": { + "type": "object", + "required": [ + "feature", + "risk", + "log_type", + "short_description", + "long_description" + ], + "properties": { + "feature": { + "type": "string", + "description": "Feature that produced the warning." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk code. New values can be added over time." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Risk-specific context, or `null`." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity used by the decision rules." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation." + } + } + }, + "RiskLifecycleEvent": { + "type": "object", + "required": [ + "type", + "timestamp", + "details", + "fee" + ], + "properties": { + "type": { + "type": "string", + "description": "Machine-readable event type. New values can be added over time." + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the event happened." + }, + "details": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Event-specific payload, or `null`." + }, + "fee": { + "type": "number", + "description": "USD amount billed for this event." + } + } + }, + "PhoneMatchItem": { + "type": "object", + "description": "Another session of your application that used the same phone number with a different `vendor_data`.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the other verification that used this phone number." + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of that session." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of that session." + }, + "verification_date": { + "type": "string", + "description": "When that session was created (`YYYY-MM-DDTHH:MM:SSZ`)." + }, + "phone_number": { + "type": "string", + "description": "The matched phone number." + }, + "status": { + "type": "string", + "description": "Status of that session (e.g., `Approved`, `Declined`, `In Progress`)." + }, + "is_blocklisted": { + "type": "boolean", + "description": "Whether the phone number is on your application's blocklist." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "For API-type sessions, the standalone API that created it; `null` for regular verification sessions." + }, + "source": { + "type": "string", + "description": "Where the match was found. Usually `session`; a blocklist hit via an organization list entry uses `list_entry`." + } + } + }, + "EmailMatchItem": { + "type": "object", + "description": "Another session of your application that used the same email address with a different `vendor_data`.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the other verification that used this email address." + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of that session." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of that session." + }, + "verification_date": { + "type": "string", + "description": "When that session was created (`YYYY-MM-DDTHH:MM:SSZ`)." + }, + "email": { + "type": "string", + "description": "The matched email address." + }, + "status": { + "type": "string", + "description": "Status of that session (e.g., `Approved`, `Declined`, `In Progress`)." + }, + "is_blocklisted": { + "type": "boolean", + "description": "Whether the email address is on your application's blocklist." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "For API-type sessions, the standalone API that created it; `null` for regular verification sessions." + }, + "source": { + "type": "string", + "description": "Where the match was found. Usually `session`; a blocklist hit via an organization list entry uses `list_entry`." + } + } + }, + "PhoneIntelligence": { + "type": "object", + "description": "Normalized phone-intelligence block. Present only when the intelligence add-on ran for this lookup; when present it is also exposed under the `enrichment` alias with identical values.", + "properties": { + "score": { + "type": "number", + "nullable": true, + "description": "Normalized 0-100 risk score. Higher is riskier." + }, + "phone_type_risk": { + "type": "string", + "nullable": true, + "description": "`Low`, `Medium` or `High`, derived from the score." + }, + "phone_trust_index": { + "type": "number", + "nullable": true, + "description": "Inverse of the score, for dashboards that prefer higher is better." + }, + "network_stability_index": { + "type": "number", + "nullable": true, + "description": "Stability of the line's network." + }, + "is_disposable": { + "type": "boolean", + "nullable": true, + "description": "`true` when the number looks disposable or virtual." + }, + "is_ported": { + "type": "boolean", + "nullable": true, + "description": "`true` when the number has moved carrier." + }, + "is_recently_ported": { + "type": "boolean", + "nullable": true, + "description": "`true` when the port happened recently." + }, + "carrier_type": { + "type": "string", + "nullable": true, + "description": "Line classification reported by the lookup (e.g., `mobile`)." + }, + "reason_codes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Stable Didit identifier of the signal, for example `IR-5AC67A32`. Opaque: group and filter on it, do not parse it." + }, + "value": { + "nullable": true, + "description": "For a risk flag (for example `voip`, `leaked`, `disposable`, `spam_trap`), its value: a boolean, a level or a date. Null for a scored reason code." + }, + "direction": { + "type": "string", + "enum": [ + "risk", + "trust" + ], + "description": "Whether the signal raised the risk or added trust." + }, + "message": { + "type": "string", + "description": "Human-readable explanation of the signal, in English." + }, + "impact_level": { + "type": "string", + "nullable": true, + "description": "How strongly the signal moved the score, for example `low`, `medium` or `high`." + } + } + }, + "description": "Signals that explain the score, each with a stable Didit code, a direction, a message and an impact level. New codes can appear over time, so do not reject unknown codes." + } + } + }, + "EmailIntelligence": { + "type": "object", + "description": "Normalized email-intelligence block. Present only when the intelligence add-on ran for this email; when present it is also exposed as the `enrichment` alias with identical values.", + "properties": { + "score": { + "type": "number", + "nullable": true, + "description": "Normalized 0-100 risk score. Higher is riskier." + }, + "deliverable": { + "type": "string", + "nullable": true, + "description": "Deliverability band (e.g., `high`)." + }, + "breach_risk_level": { + "type": "string", + "nullable": true, + "description": "`Low`, `Medium` or `High`." + }, + "breach_risk_index": { + "type": "number", + "nullable": true, + "description": "Numeric breach-risk index." + }, + "is_recent_breach": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address appears in a recent breach." + }, + "is_frequent_breached": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address appears in many breaches." + }, + "is_disposable": { + "type": "boolean", + "nullable": true, + "description": "`true` when the domain is a throwaway provider." + }, + "is_free_domain": { + "type": "boolean", + "nullable": true, + "description": "`true` when the domain is a free mail provider." + }, + "is_breached": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address appears in known breaches." + }, + "no_of_breaches": { + "type": "number", + "nullable": true, + "description": "Number of known breaches containing the address." + }, + "reason_codes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Stable Didit identifier of the signal, for example `IR-5AC67A32`. Opaque: group and filter on it, do not parse it." + }, + "value": { + "nullable": true, + "description": "For a risk flag (for example `voip`, `leaked`, `disposable`, `spam_trap`), its value: a boolean, a level or a date. Null for a scored reason code." + }, + "direction": { + "type": "string", + "enum": [ + "risk", + "trust" + ], + "description": "Whether the signal raised the risk or added trust." + }, + "message": { + "type": "string", + "description": "Human-readable explanation of the signal, in English." + }, + "impact_level": { + "type": "string", + "nullable": true, + "description": "How strongly the signal moved the score, for example `low`, `medium` or `high`." + } + } + }, + "description": "Signals that explain the score, each with a stable Didit code, a direction, a message and an impact level. New codes can appear over time, so do not reject unknown codes." + } + } + }, + "IPRiskResult": { + "type": "object", + "description": "IP risk result. Individual enrichment fields can be `null` when the lookup returned no usable data.", + "required": [ + "status", + "threat" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review" + ], + "description": "`In Review` when the resolved country conflicts with `claimed_country`, or when the address is associated with VPN, Tor, or data-center infrastructure. Otherwise `Approved`." + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "The scored address. `null` when no enrichment source returned usable data." + }, + "ip_country": { + "type": "string", + "nullable": true, + "description": "Resolved country name." + }, + "ip_country_code": { + "type": "string", + "nullable": true, + "description": "Resolved country code. Compare countries through `country_mismatch` instead of comparing raw country strings yourself." + }, + "ip_state": { + "type": "string", + "nullable": true, + "description": "Resolved state or region." + }, + "ip_city": { + "type": "string", + "nullable": true, + "description": "Resolved city." + }, + "latitude": { + "type": "number", + "nullable": true, + "description": "Approximate network geolocation, not a precise device location." + }, + "longitude": { + "type": "number", + "nullable": true, + "description": "Approximate network geolocation, not a precise device location." + }, + "isp": { + "type": "string", + "nullable": true, + "description": "Internet service provider serving the address." + }, + "organization": { + "type": "string", + "nullable": true, + "description": "Organization owning the address block." + }, + "is_vpn_or_tor": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address is associated with VPN or Tor infrastructure." + }, + "is_data_center": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address belongs to a hosting or data-center network." + }, + "time_zone": { + "type": "string", + "nullable": true, + "description": "Time zone of the resolved location." + }, + "time_zone_offset": { + "type": "string", + "nullable": true, + "description": "UTC offset of the resolved location." + }, + "asn_number": { + "type": "number", + "nullable": true, + "description": "Autonomous system number." + }, + "asn_organization": { + "type": "string", + "nullable": true, + "description": "Organization operating the autonomous system." + }, + "connection_type": { + "type": "string", + "nullable": true, + "description": "Connection classification (e.g., `cellular`, `hosting`)." + }, + "carrier": { + "type": "string", + "nullable": true, + "description": "Mobile carrier when the address is on a mobile network." + }, + "proxy_type": { + "type": "string", + "nullable": true, + "description": "Proxy classification when the address is behind a proxy." + }, + "claimed_country": { + "type": "string", + "nullable": true, + "description": "Echo of the `claimed_country` you sent." + }, + "country_mismatch": { + "type": "boolean", + "nullable": true, + "description": "`true` when the resolved country differs from `claimed_country`. `null` when no enrichment data or no claimed country is available." + }, + "threat": { + "type": "object", + "additionalProperties": true, + "description": "Free-form network-threat evidence from the enrichment source (e.g., `is_proxy`, `is_tor`). Empty object when no enrichment data is available. Treat as diagnostic detail, not a stable contract." + } + } + }, + "PhoneRiskResponse": { + "type": "object", + "required": [ + "request_id", + "status", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the API session created for this request. Store it with `vendor_data` if you need to correlate the result with later webhooks or console activity." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Final decision for this API session. Normally `Approved`; the risk evaluation can produce `In Review` or `Declined`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, `null` when you did not send one." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` you sent, `null` when you did not send one." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this response." + }, + "phone": { + "type": "object", + "description": "Normalized phone report, the same block used by Phone Verification responses.", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Decision for this phone number. Mirrors the top-level `status`." + }, + "phone_number_prefix": { + "type": "string", + "description": "Country calling code with leading `+` (e.g., `+1`).", + "example": "+1" + }, + "phone_number": { + "type": "string", + "description": "National number without the country calling code.", + "example": "4155552671" + }, + "full_number": { + "type": "string", + "description": "Complete number in E.164 format.", + "example": "+14155552671" + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of the number.", + "example": "US" + }, + "country_name": { + "type": "string", + "description": "Full country name derived from `country_code`.", + "example": "United States" + }, + "carrier": { + "type": "object", + "nullable": true, + "description": "Current network serving the number. `null` when the lookup could not resolve a carrier.", + "properties": { + "name": { + "type": "string", + "description": "Carrier name. Empty string when the lookup could not identify it." + }, + "type": { + "type": "string", + "description": "Line type reported by the lookup: `mobile`, `fixed_line`, `voip`, `isp`, `vpn`, `toll_free`, `premium_rate`, or similar." + } + } + }, + "is_disposable": { + "type": "boolean", + "description": "`true` when the number belongs to a temporary or burner number service." + }, + "is_virtual": { + "type": "boolean", + "description": "`true` when the line type is virtual (`voip`, `isp`, or `vpn`)." + }, + "verification_method": { + "type": "string", + "nullable": true, + "description": "OTP channel of the underlying verification. `null` for risk lookups, which never send a code." + }, + "verification_attempts": { + "type": "integer", + "description": "OTP sends for this number. `0` for risk lookups." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the number was last verified by an OTP. `null` for risk lookups." + }, + "warnings": { + "type": "array", + "description": "Risk warnings recorded for this request. Empty array when there are none.", + "items": { + "$ref": "#/components/schemas/RiskWarning" + } + }, + "lifecycle": { + "type": "array", + "description": "Chronological audit trail of the request. Empty array for risk lookups without send events.", + "items": { + "$ref": "#/components/schemas/RiskLifecycleEvent" + } + }, + "matches": { + "type": "array", + "description": "Other sessions of your application where the same phone number was used by a different user (different `vendor_data`). Up to 5 entries. Empty array when there are no matches.", + "items": { + "$ref": "#/components/schemas/PhoneMatchItem" + } + }, + "phone_intelligence": { + "$ref": "#/components/schemas/PhoneIntelligence" + }, + "enrichment": { + "$ref": "#/components/schemas/PhoneIntelligence" + }, + "phone_social": { + "$ref": "#/components/schemas/PhoneSocialFootprint" + } + } + } + } + }, + "EmailRiskResponse": { + "type": "object", + "required": [ + "request_id", + "status", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the API session created for this request. Store it with `vendor_data` if you need to correlate the result with later webhooks or console activity." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Final decision for this API session. Normally `Approved`; the risk evaluation can produce `In Review` or `Declined`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, `null` when you did not send one." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` you sent, `null` when you did not send one." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this response." + }, + "email": { + "type": "object", + "description": "Normalized email report, the same block used by Email Verification responses.", + "required": [ + "status", + "email", + "is_breached", + "breaches", + "is_disposable", + "is_undeliverable" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Decision for this email address. Mirrors the top-level `status`." + }, + "email": { + "type": "string", + "description": "The scored address.", + "example": "alice@example.com" + }, + "is_breached": { + "type": "boolean", + "description": "Whether the address appears in a known credential breach." + }, + "breaches": { + "type": "array", + "description": "Up to 5 most recent known breaches containing this address. Empty array when `is_breached` is `false`.", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Breach identifier." + }, + "domain": { + "type": "string", + "description": "Domain of the breached service." + }, + "breach_date": { + "type": "string", + "format": "date", + "description": "Date the breach occurred." + }, + "breach_emails_count": { + "type": "integer", + "description": "Total number of accounts exposed in the breach." + }, + "description": { + "type": "string", + "description": "Human-readable description of the breach (may contain HTML markup)." + }, + "logo_path": { + "type": "string", + "description": "URL of the breached service's logo." + }, + "data_classes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Categories of data exposed, in snake_case (e.g., `email_addresses`, `passwords`)." + }, + "is_verified": { + "type": "boolean", + "description": "Whether the breach has been verified as legitimate." + } + } + } + }, + "is_disposable": { + "type": "boolean", + "description": "Whether the domain belongs to a throwaway provider." + }, + "is_undeliverable": { + "type": "boolean", + "description": "Whether the address was found undeliverable." + }, + "verification_attempts": { + "type": "integer", + "description": "OTP sends for this address. `0` for risk lookups." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the address was last verified by an OTP. `null` for risk lookups." + }, + "warnings": { + "type": "array", + "description": "Risk warnings recorded for this request. Empty array when there are none.", + "items": { + "$ref": "#/components/schemas/RiskWarning" + } + }, + "lifecycle": { + "type": "array", + "description": "Chronological audit trail of the request. Empty array for risk lookups without sends.", + "items": { + "$ref": "#/components/schemas/RiskLifecycleEvent" + } + }, + "matches": { + "type": "array", + "description": "Other sessions of your application where the same email address was used by a different user (different `vendor_data`). Up to 5 entries. Empty array when there are no matches.", + "items": { + "$ref": "#/components/schemas/EmailMatchItem" + } + }, + "email_intelligence": { + "$ref": "#/components/schemas/EmailIntelligence" + }, + "enrichment": { + "$ref": "#/components/schemas/EmailIntelligence" + }, + "email_social": { + "$ref": "#/components/schemas/EmailSocialFootprint" + } + } + } + } + }, + "IPRiskResponse": { + "type": "object", + "required": [ + "request_id", + "status", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the API session created for this request. Store it with `vendor_data` if you need to correlate the result with later webhooks or console activity." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review" + ], + "description": "Decision for this API session: `In Review` on network or country-mismatch risk, otherwise `Approved`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, `null` when you did not send one." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` you sent, `null` when you did not send one." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this response." + }, + "ip": { + "type": "object", + "description": "IP risk result. Individual enrichment fields can be `null` when the lookup returned no usable data.", + "required": [ + "status", + "threat" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review" + ], + "description": "`In Review` when the resolved country conflicts with `claimed_country`, or when the address is associated with VPN, Tor, or data-center infrastructure. Otherwise `Approved`." + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "The scored address. `null` when no enrichment source returned usable data." + }, + "ip_country": { + "type": "string", + "nullable": true, + "description": "Resolved country name." + }, + "ip_country_code": { + "type": "string", + "nullable": true, + "description": "Resolved country code. Compare countries through `country_mismatch` instead of comparing raw country strings yourself." + }, + "ip_state": { + "type": "string", + "nullable": true, + "description": "Resolved state or region." + }, + "ip_city": { + "type": "string", + "nullable": true, + "description": "Resolved city." + }, + "latitude": { + "type": "number", + "nullable": true, + "description": "Approximate network geolocation, not a precise device location." + }, + "longitude": { + "type": "number", + "nullable": true, + "description": "Approximate network geolocation, not a precise device location." + }, + "isp": { + "type": "string", + "nullable": true, + "description": "Internet service provider serving the address." + }, + "organization": { + "type": "string", + "nullable": true, + "description": "Organization owning the address block." + }, + "is_vpn_or_tor": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address is associated with VPN or Tor infrastructure." + }, + "is_data_center": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address belongs to a hosting or data-center network." + }, + "time_zone": { + "type": "string", + "nullable": true, + "description": "Time zone of the resolved location." + }, + "time_zone_offset": { + "type": "string", + "nullable": true, + "description": "UTC offset of the resolved location." + }, + "asn_number": { + "type": "number", + "nullable": true, + "description": "Autonomous system number." + }, + "asn_organization": { + "type": "string", + "nullable": true, + "description": "Organization operating the autonomous system." + }, + "connection_type": { + "type": "string", + "nullable": true, + "description": "Connection classification (e.g., `cellular`, `hosting`)." + }, + "carrier": { + "type": "string", + "nullable": true, + "description": "Mobile carrier when the address is on a mobile network." + }, + "proxy_type": { + "type": "string", + "nullable": true, + "description": "Proxy classification when the address is behind a proxy." + }, + "claimed_country": { + "type": "string", + "nullable": true, + "description": "Echo of the `claimed_country` you sent." + }, + "country_mismatch": { + "type": "boolean", + "nullable": true, + "description": "`true` when the resolved country differs from `claimed_country`. `null` when no enrichment data or no claimed country is available." + }, + "threat": { + "type": "object", + "additionalProperties": true, + "description": "Free-form network-threat evidence from the enrichment source (e.g., `is_proxy`, `is_tor`). Empty object when no enrichment data is available. Treat as diagnostic detail, not a stable contract." + } + } + } + } + }, + "SocialFootprintPlatform": { + "type": "object", + "properties": { + "platform": { + "type": "string", + "description": "Platform identifier.", + "example": "whatsapp" + }, + "category": { + "type": "string", + "description": "Platform category, e.g. MESSAGING, SOCIAL_MEDIA, ECOMMERCE, PROFESSIONAL, EMAIL_PROVIDER.", + "example": "MESSAGING" + }, + "scope": { + "type": "string", + "enum": [ + "global", + "local" + ], + "description": "`global` for worldwide platforms, `local` for region-specific ones.", + "example": "global" + } + } + }, + "SocialFootprintCategorySummary": { + "type": "object", + "properties": { + "registered": { + "type": "integer", + "nullable": true, + "description": "Platforms in this category where registration is confirmed.", + "example": 2 + }, + "ratio": { + "type": "number", + "format": "float", + "nullable": true, + "description": "Share of the category's checked platforms with a registration, 0.0-1.0.", + "example": 0.6667 + } + } + }, + "SocialFootprintProfile": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "description": "Profile name, when the platform exposes one." + }, + "photo": { + "type": "string", + "nullable": true, + "description": "Profile photo URL, when the platform exposes one." + }, + "about": { + "type": "string", + "nullable": true, + "description": "Profile about/bio text, when the platform exposes one." + }, + "privacy_status": { + "type": "string", + "nullable": true, + "enum": [ + "PUBLIC", + "PRIVATE", + null + ], + "description": "Profile visibility, when the platform exposes it." + }, + "business_account": { + "type": "boolean", + "nullable": true, + "description": "Whether the profile is a business account, when the platform exposes it." + } + } + }, + "PhoneSocialFootprint": { + "type": "object", + "nullable": true, + "description": "Social footprint of the phone number: the online platforms it is registered on. Present only when `enable_social` was set and the check completed. Every count may be null when no data was returned for it. `profiles_registered: 0` means the platforms were checked and the phone number was found on none of them - a valid, billable answer and a common synthetic-identity signal.", + "properties": { + "profiles_registered": { + "type": "integer", + "nullable": true, + "description": "Platforms where the phone number is confirmed registered.", + "example": 5 + }, + "profiles_checked": { + "type": "integer", + "nullable": true, + "description": "Platforms evaluated for registration.", + "example": 14 + }, + "names_found": { + "type": "integer", + "nullable": true, + "description": "Platforms that returned an associated name.", + "example": 0 + }, + "photos_found": { + "type": "integer", + "nullable": true, + "description": "Platforms that returned a profile photo.", + "example": 0 + }, + "communication_breadth": { + "type": "integer", + "nullable": true, + "description": "Communication platforms with detected presence.", + "example": 2 + }, + "digital_footprint_diversity": { + "type": "integer", + "nullable": true, + "description": "Distinct platform categories with detected presence.", + "example": 5 + }, + "digital_sophistication_index": { + "type": "integer", + "nullable": true, + "description": "Composite digital-maturity indicator, 0-10.", + "example": 5 + }, + "engagement_depth_index": { + "type": "integer", + "nullable": true, + "description": "Profile-richness indicator, 0-10.", + "example": 3 + }, + "subscription_affordability_index": { + "type": "integer", + "nullable": true, + "description": "Free vs paid platform mix, 0-10.", + "example": 2 + }, + "free_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on free platforms.", + "example": 4 + }, + "full_paid_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on fully paid platforms.", + "example": 0 + }, + "partial_paid_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on freemium platforms.", + "example": 1 + }, + "whatsapp_profile_completeness": { + "type": "string", + "nullable": true, + "enum": [ + "Basic", + "Partial", + "Complete", + "Not Present", + null + ], + "description": "Completeness of the WhatsApp profile. Phone only; null for email.", + "example": "Partial" + }, + "registered_platforms": { + "type": "array", + "description": "Platforms where registration is confirmed.", + "items": { + "$ref": "#/components/schemas/SocialFootprintPlatform" + } + }, + "not_registered_platforms": { + "type": "array", + "description": "Platforms checked with a negative result.", + "items": { + "$ref": "#/components/schemas/SocialFootprintPlatform" + } + }, + "categories": { + "type": "object", + "description": "Registered count and ratio per platform category.", + "additionalProperties": { + "$ref": "#/components/schemas/SocialFootprintCategorySummary" + } + }, + "profiles": { + "type": "object", + "description": "Per-platform profile details, keyed by platform, when the platform exposes them.", + "additionalProperties": { + "$ref": "#/components/schemas/SocialFootprintProfile" + } + } + } + }, + "EmailSocialFootprint": { + "type": "object", + "nullable": true, + "description": "Social footprint of the email address: the online platforms it is registered on. Present only when `enable_social` was set and the check completed. Every count may be null when no data was returned for it. `profiles_registered: 0` means the platforms were checked and the email address was found on none of them - a valid, billable answer and a common synthetic-identity signal.", + "properties": { + "profiles_registered": { + "type": "integer", + "nullable": true, + "description": "Platforms where the email address is confirmed registered.", + "example": 5 + }, + "profiles_checked": { + "type": "integer", + "nullable": true, + "description": "Platforms evaluated for registration.", + "example": 14 + }, + "names_found": { + "type": "integer", + "nullable": true, + "description": "Platforms that returned an associated name.", + "example": 0 + }, + "photos_found": { + "type": "integer", + "nullable": true, + "description": "Platforms that returned a profile photo.", + "example": 0 + }, + "communication_breadth": { + "type": "integer", + "nullable": true, + "description": "Communication platforms with detected presence.", + "example": 2 + }, + "digital_footprint_diversity": { + "type": "integer", + "nullable": true, + "description": "Distinct platform categories with detected presence.", + "example": 5 + }, + "digital_sophistication_index": { + "type": "integer", + "nullable": true, + "description": "Composite digital-maturity indicator, 0-10.", + "example": 5 + }, + "engagement_depth_index": { + "type": "integer", + "nullable": true, + "description": "Profile-richness indicator, 0-10.", + "example": 3 + }, + "subscription_affordability_index": { + "type": "integer", + "nullable": true, + "description": "Free vs paid platform mix, 0-10.", + "example": 2 + }, + "free_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on free platforms.", + "example": 4 + }, + "full_paid_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on fully paid platforms.", + "example": 0 + }, + "partial_paid_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on freemium platforms.", + "example": 1 + }, + "whatsapp_profile_completeness": { + "type": "string", + "nullable": true, + "enum": [ + "Basic", + "Partial", + "Complete", + "Not Present", + null + ], + "description": "Completeness of the WhatsApp profile. Phone only; null for email.", + "example": "Partial" + }, + "registered_platforms": { + "type": "array", + "description": "Platforms where registration is confirmed.", + "items": { + "$ref": "#/components/schemas/SocialFootprintPlatform" + } + }, + "not_registered_platforms": { + "type": "array", + "description": "Platforms checked with a negative result.", + "items": { + "$ref": "#/components/schemas/SocialFootprintPlatform" + } + }, + "categories": { + "type": "object", + "description": "Registered count and ratio per platform category.", + "additionalProperties": { + "$ref": "#/components/schemas/SocialFootprintCategorySummary" + } + }, + "profiles": { + "type": "object", + "description": "Per-platform profile details, keyed by platform, when the platform exposes them.", + "additionalProperties": { + "$ref": "#/components/schemas/SocialFootprintProfile" + } + } + } + }, + "KYBRegistryAddress": { + "type": "object", + "description": "An address the registry states, in the same shape for every country and registry source. Every key is always present; a part the registry does not publish is null.", + "properties": { + "type": { + "type": "string", + "nullable": true, + "description": "Address type in lower case: `registered` for the registered office; other types as the registry names them (for example `postal`, `office`). `null` when the registry does not say what the address is." + }, + "address": { + "type": "string", + "description": "The full address as one line. Always present on every address." + }, + "street": { + "type": "string", + "nullable": true, + "description": "Street and number." + }, + "city": { + "type": "string", + "nullable": true, + "description": "City or locality." + }, + "postal_code": { + "type": "string", + "nullable": true, + "description": "Postal code." + }, + "region": { + "type": "string", + "nullable": true, + "description": "Region, state or province." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code." + }, + "description": { + "type": "string", + "nullable": true, + "description": "The registry's own label for the address, when it gives one." + } + }, + "required": [ + "type", + "address", + "street", + "city", + "postal_code", + "region", + "country_code", + "description" + ] + }, + "KYBRegistryIndustry": { + "type": "object", + "description": "An activity the registry classifies the company under, in the same shape for every country and registry source.", + "properties": { + "code": { + "type": "string", + "nullable": true, + "description": "Activity code as the registry prints it (for example `73110`)." + }, + "description": { + "type": "string", + "nullable": true, + "description": "Activity label, when the registry publishes one." + }, + "scheme": { + "type": "string", + "nullable": true, + "description": "Classification system, one name per system whichever source reported it: `UK SIC 2007`, `US SIC`, `NACE Rev. 2`, `NACE Rev. 2.1`, `NAF Rev. 2`, `CNAE`, `SBI`, `NOGA 2008`, `CAE Rev. 4`, `Canada SIC 1977`; other national systems keep the registry's own name. Null when the registry does not name the system." + }, + "is_primary": { + "type": "boolean", + "description": "True for the company's main activity. Exactly one entry is primary when the list is not empty." + } + }, + "required": [ + "code", + "description", + "scheme", + "is_primary" + ] + }, + "KYBRegistryShareholding": { + "type": "object", + "nullable": true, + "description": "What an owner holds, in the same shape for every country and registry source. Null when the registry states no holding. A band (for example a UK PSC's 25-50%) is not a stake: `percentage` is null and the band stays in `ownership_min_shares` / `ownership_max_shares`.", + "properties": { + "share_class": { + "type": "string", + "nullable": true, + "description": "Share class or type as the registry names it." + }, + "number_of_shares": { + "type": "number", + "nullable": true, + "description": "Number of shares held." + }, + "currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 currency of the values below." + }, + "nominal_value": { + "type": "number", + "nullable": true, + "description": "Nominal value per share." + }, + "total_value": { + "type": "number", + "nullable": true, + "description": "Total value of the holding." + }, + "percentage": { + "type": "number", + "nullable": true, + "description": "Exact stake in percent, to four decimal places." + } + }, + "required": [ + "share_class", + "number_of_shares", + "currency", + "nominal_value", + "total_value", + "percentage" + ] + }, + "KYBRegistryOwnership": { + "type": "object", + "description": "The outcome of the ownership (Shareholders / UBO) tier, in the same shape for every country and registry source.", + "properties": { + "status": { + "type": "string", + "nullable": true, + "enum": [ + "not_requested", + "pending", + "delivered", + "not_held", + "unavailable", + null + ], + "description": "`delivered` when the registry published ownership; `not_held` when the ownership registry answered and does not list the company at all (see `unavailable_reason`); `unavailable` when it did not serve the ownership data for any other reason (see `unavailable_reason`); `pending` while the registry is still fetching it; `not_requested` for the Basic tier." + }, + "tier_requested": { + "type": "string", + "nullable": true, + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "The tier the workflow ordered." + }, + "tier_delivered": { + "type": "string", + "nullable": true, + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "The tier the registry delivered (and that is billed). A UBO request answered with direct shareholders only is `shareholders`." + }, + "unavailable_reason": { + "type": "string", + "nullable": true, + "enum": [ + "no_owners_on_record", + "no_natural_person_owner", + "ownership_section_unavailable", + "company_not_found", + "ambiguous_company_match", + "missing_registration_number", + "entity_type_not_covered", + "provider_unavailable", + "other", + null + ], + "description": "Why a requested ownership tier was not delivered. Null unless `status` is `unavailable` or `pending`." + }, + "direct_ownership_complete": { + "type": "boolean", + "nullable": true, + "description": "True when the direct shareholders' stated stakes add up to 100% (within one percentage point of rounding); false when they do not; null when ownership was not delivered or a stake is not stated." + }, + "unresolved": { + "type": "array", + "description": "Parts of the ownership chain the registry could not resolve, for example no group structure on record above the direct owners.", + "items": { + "type": "object", + "properties": { + "reason": { + "type": "string" + }, + "section": { + "type": "string", + "nullable": true + } + } + } + } + }, + "required": [ + "status", + "tier_requested", + "tier_delivered", + "unavailable_reason", + "direct_ownership_complete", + "unresolved" + ] + }, + "KYBRegistryData": { + "type": "object", + "nullable": true, + "description": "Didit's normalized registry detail for the company: the ownership outcome (`ownership_error`, `section_errors`, `ubo_resolution`, `lite_enrichment`), the corrections made with update-data, and registry facts that have no field of their own. It never carries the registry source's own record. Every key is optional and present only when the company has a value for it; new keys may be added. Read the canonical fields (`addresses`, `industries`, `ownership`, `beneficial_owners[].shareholding`) for anything they cover.", + "properties": { + "company_name": { + "type": "string", + "description": "Company name as corrected in the console." + }, + "registration_number": { + "type": "string", + "description": "Registration number as the registry or a console correction states it." + }, + "country_code": { + "type": "string", + "description": "Country of the registration as the registry or a console correction states it." + }, + "region": { + "type": "string", + "description": "Region or state of the registration." + }, + "incorporation_date": { + "type": "string", + "description": "Incorporation date (ISO 8601)." + }, + "registration_date": { + "type": "string", + "description": "Registration date (ISO 8601) when the registry states one apart from the incorporation date." + }, + "dissolution_date": { + "type": "string", + "description": "Dissolution date (ISO 8601) when the registry states one." + }, + "tax_number": { + "type": "string", + "description": "Tax identification number." + }, + "vat_number": { + "type": "string", + "description": "VAT number." + }, + "legal_address": { + "type": "string", + "description": "Legal address as corrected in the console." + }, + "registered_address": { + "type": "string", + "description": "Registered address as the registry states it." + }, + "company_type": { + "type": "string", + "description": "Legal form." + }, + "registry_status": { + "type": "string", + "description": "Company status at the registry." + }, + "verification_status": { + "type": "string", + "description": "Verification status." + }, + "alternative_names": { + "type": "string", + "description": "Other names the company trades or traded under." + }, + "nature_of_business": { + "type": "string", + "description": "Activity description." + }, + "registered_capital": { + "type": "string", + "description": "Registered capital as printed." + }, + "registered_capital_amount": { + "type": "string", + "description": "Registered capital amount, as a decimal string." + }, + "registered_capital_currency": { + "type": "string", + "description": "Registered capital currency (ISO 4217)." + }, + "website": { + "type": "string", + "description": "Website." + }, + "email": { + "type": "string", + "description": "Contact email." + }, + "phone": { + "type": "string", + "description": "Contact phone." + }, + "legal_entity_identifier": { + "type": "string", + "description": "Legal Entity Identifier (LEI)." + }, + "location_of_registration": { + "type": "string", + "description": "Place of registration." + }, + "control_scheme": { + "type": "string", + "description": "Governance scheme when the registry exposes it." + }, + "ownership_error": { + "type": "string", + "enum": [ + "no_owners_on_record", + "no_natural_person_owner", + "ownership_section_unavailable", + "company_not_found", + "ambiguous_company_match", + "missing_registration_number", + "entity_type_not_covered", + "provider_unavailable", + "other" + ], + "description": "Why a requested ownership tier was not delivered, in the same codes as `ownership.unavailable_reason`. `no_owners_on_record` (the registry answered and names no owner) and `ownership_section_unavailable` (the registry did not return the ownership section) are different findings." + }, + "section_errors": { + "type": "object", + "description": "Sections the registry did not return, keyed by section (`officers`, `shareholders`, `group_structure`). A section listed here was never delivered: an empty list next to it says nothing about the company.", + "additionalProperties": { + "$ref": "#/components/schemas/KYBRegistrySectionError" + } + }, + "ubo_resolution": { + "$ref": "#/components/schemas/KYBRegistryUboResolution" + }, + "lite_enrichment": { + "$ref": "#/components/schemas/KYBRegistryLiteEnrichment" + }, + "registry_metadata": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ] + }, + "description": "The register's own fields for the company, keyed as the register names them, as plain values (legacy records). Only fields reviewed as registry facts are included." + }, + "registry_sources": { + "type": "array", + "description": "The official registers the record was read from (legacy records). Other sources are not named.", + "items": { + "$ref": "#/components/schemas/KYBRegistrySource" + } + }, + "previous_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names the company was registered under before (legacy records)." + }, + "parent_companies": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Parent companies the registry names (legacy records)." + }, + "documents_available": { + "type": "integer", + "description": "How many filings the registry offers for the company (legacy records)." + } + } + }, + "KYBRegistryGroupCompany": { + "type": "object", + "description": "A company of the corporate group, by its registry identity.", + "properties": { + "name": { + "type": "string", + "nullable": true, + "description": "Company name." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "Country of registration." + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Registration number." + } + }, + "required": [ + "name", + "country_code", + "registration_number" + ] + }, + "KYBRegistryUboResolution": { + "type": "object", + "description": "How the UBO tier walked the ownership chain above the company.", + "properties": { + "parent_chain": { + "type": "array", + "description": "Corporate parents from the ultimate parent down to the company.", + "items": { + "$ref": "#/components/schemas/KYBRegistryGroupCompany" + } + }, + "subsidiaries": { + "type": "array", + "description": "Companies directly below the company in its corporate group.", + "items": { + "$ref": "#/components/schemas/KYBRegistryGroupCompany" + } + }, + "unresolved": { + "type": "array", + "description": "Parts of the chain the registry could not resolve (the same entries as `ownership.unresolved`).", + "items": { + "type": "object", + "properties": { + "reason": { + "type": "string", + "description": "Why, for example `no_group_structure_on_record`, `selected_company_not_in_group_structure` or `section_error`." + }, + "section": { + "type": "string", + "nullable": true, + "description": "The section concerned, for example `group_structure`." + } + }, + "required": [ + "reason", + "section" + ] + } + } + }, + "required": [ + "parent_chain", + "subsidiaries", + "unresolved" + ] + }, + "KYBRegistrySectionError": { + "type": "object", + "description": "A section the registry did not return.", + "properties": { + "status_code": { + "type": "integer", + "nullable": true, + "description": "The status the registry answered for the section." + } + }, + "required": [ + "status_code" + ] + }, + "KYBRegistryLiteEnrichment": { + "type": "object", + "description": "Blank Lite profile fields another of the country's registry sources filled. The source is not named.", + "properties": { + "fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The company fields that were filled." + }, + "people_added": { + "type": "integer", + "description": "How many officers or owners were added." + } + }, + "required": [ + "fields", + "people_added" + ] + }, + "KYBRegistrySource": { + "type": "object", + "description": "An official register the record was read from. Only official government registers, regulators and the official identifier system are named; any other source is left out.", + "properties": { + "name": { + "type": "string", + "description": "Register name, for example `UK Companies House`." + }, + "source_type": { + "type": "string", + "enum": [ + "official" + ], + "description": "Always `official`." + } + }, + "required": [ + "name", + "source_type" + ] + }, + "KYBOwnershipStructure": { + "type": "object", + "nullable": true, + "description": "The cap table the registry proved, as it stood when the ownership tier was delivered: direct `shareholders` and `beneficial_owners`. Null when the registry proved no ownership. It never carries the registry source's own rows; read `company.beneficial_owners` and `company.ownership` for the current record.", + "properties": { + "shareholders": { + "type": "array", + "items": { + "$ref": "#/components/schemas/KYBOwnershipStructureOwner" + } + }, + "beneficial_owners": { + "type": "array", + "items": { + "$ref": "#/components/schemas/KYBOwnershipStructureOwner" + } + } + }, + "required": [ + "shareholders", + "beneficial_owners" + ] + }, + "KYBOwnershipStructureOwner": { + "type": "object", + "description": "One owner of the ownership snapshot: the same registry identity and stake as `company.beneficial_owners[]`, and its canonical `shareholding`.", + "properties": { + "name": { + "type": "string", + "nullable": true, + "description": "Owner name as the registry states it." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name, for a natural person, when the registry states it." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name, for a natural person, when the registry states it." + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Date of birth (ISO 8601) when the registry publishes it." + }, + "country_of_birth": { + "type": "string", + "nullable": true, + "description": "Country of birth when the registry publishes it." + }, + "company_name": { + "type": "string", + "nullable": true, + "description": "Company name, for a corporate owner." + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Registration number, for a corporate owner." + }, + "entity_type": { + "type": "string", + "nullable": true, + "enum": [ + "person", + "company", + null + ], + "description": "Whether the owner is a natural person or a company." + }, + "nationality": { + "type": "string", + "nullable": true, + "description": "Nationality when the registry states it." + }, + "designation": { + "type": "string", + "nullable": true, + "description": "The owner's title or role as the registry prints it." + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "`shareholder` for a direct holder, `ubo` for a beneficial owner." + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the stake, in percent (equal to the upper bound for an exact stake)." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the stake, in percent." + }, + "voting_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the voting rights, in percent." + }, + "voting_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the voting rights, in percent." + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the holding or appointment started (ISO 8601)." + }, + "is_active": { + "type": "boolean", + "nullable": true, + "description": "False for a former owner." + }, + "shareholding": { + "$ref": "#/components/schemas/KYBRegistryShareholding" + } + }, + "required": [ + "name", + "first_name", + "last_name", + "date_of_birth", + "country_of_birth", + "company_name", + "registration_number", + "entity_type", + "nationality", + "designation", + "roles", + "ownership_min_shares", + "ownership_max_shares", + "voting_min_shares", + "voting_max_shares", + "appointment_date", + "is_active", + "shareholding" + ] + }, + "DigitalIdWalletApiWalletVerificationError": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Stable machine-readable code, e.g. wallet_unavailable." + }, + "detail": { + "type": "string" + } + }, + "required": [ + "detail", + "error" + ] + }, + "DigitalIdWalletApiWalletVerificationStartForbidden": { + "oneOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + }, + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + ] + }, + "DigitalIdWalletApiClientCredentialsForbiddenErrorMessage": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "default": "Forbidden - User doesn't have permission" + } + } + }, + "DigitalIdWalletApiWalletVerificationStartBadRequest": { + "oneOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + }, + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationFieldErrors" + } + ] + }, + "DigitalIdWalletApiWalletVerificationFieldErrors": { + "type": "object", + "description": "Request validation errors: each rejected field with its messages.", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "DigitalIdWalletApiWalletVerificationResponse": { + "type": "object", + "description": "One standalone wallet verification, as every wallet endpoint answers it.", + "properties": { + "request_id": { + "type": "string", + "format": "uuid" + }, + "wallet_verification": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResult" + }, + "vendor_data": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "The metadata object sent at start, or null." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "created_at", + "metadata", + "request_id", + "vendor_data", + "wallet_verification" + ] + }, + "DigitalIdWalletApiWalletVerificationResult": { + "type": "object", + "properties": { + "wallet": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWallet" + } + ], + "readOnly": true + }, + "country": { + "type": "string" + }, + "status": { + "type": "string", + "description": "Not Finished while pending, then Approved (verified) or Declined." + }, + "outcome": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResultOutcomeEnum" + }, + "error": { + "type": "string", + "nullable": true, + "readOnly": true + }, + "interaction": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiInteractionEnum" + } + ], + "readOnly": true + }, + "simulated": { + "type": "boolean", + "readOnly": true + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "next_action": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletNextAction" + } + ], + "nullable": true, + "readOnly": true + }, + "identity": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletIdentity" + } + ], + "nullable": true, + "readOnly": true + }, + "attributes": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "readOnly": true + }, + "level_of_assurance": { + "type": "string", + "nullable": true + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "signature_valid": { + "type": "boolean", + "nullable": true, + "description": "True once the credential's signature was verified; null for a simulated one." + }, + "credential_type": { + "type": "string", + "nullable": true + } + }, + "required": [ + "attributes", + "country", + "credential_type", + "error", + "expires_at", + "identity", + "interaction", + "level_of_assurance", + "next_action", + "outcome", + "signature_valid", + "simulated", + "status", + "verified_at", + "wallet" + ] + }, + "DigitalIdWalletApiWalletIdentity": { + "type": "object", + "properties": { + "full_name": { + "type": "string", + "nullable": true + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true, + "description": "ISO3 nationality, only when the wallet asserts one (never inferred)." + }, + "issuing_state": { + "type": "string", + "description": "ISO3 country whose scheme issued the identity." + } + }, + "required": [ + "date_of_birth", + "first_name", + "full_name", + "issuing_state", + "last_name", + "nationality" + ] + }, + "DigitalIdWalletApiWalletNextAction": { + "type": "object", + "properties": { + "type": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletNextActionTypeEnum" + } + ], + "description": "redirect: send the user's browser to url. confirm_in_app: show verification_code to the user, who checks it matches the code in their wallet app and confirms there; then poll. request_digital_credential: in a user gesture on the origin you started with, ask the wallet with digital_credentials.request, then send what it returns to POST .../verifications/{request_id}/response/.\n\n* `redirect` - redirect\n* `confirm_in_app` - confirm_in_app\n* `request_digital_credential` - request_digital_credential" + }, + "url": { + "type": "string", + "format": "uri", + "description": "redirect only: where the user signs in with the wallet." + }, + "digital_credentials": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletDigitalCredentialsRequest" + } + ], + "description": "request_digital_credential only: the request to hand to the wallet." + }, + "verification_code": { + "type": "string", + "description": "confirm_in_app only: the 4-digit comparison code the wallet app shows too." + }, + "poll_interval_seconds": { + "type": "integer", + "description": "confirm_in_app only: how often to call POST .../verifications/{request_id}/poll/ for prompt feedback. Didit also polls pending sign-ins in the background, so the webhook reports the outcome even when you do not poll." + } + }, + "required": [ + "type" + ] + }, + "DigitalIdWalletApiWalletNextActionTypeEnum": { + "enum": [ + "redirect", + "confirm_in_app", + "request_digital_credential" + ], + "type": "string", + "description": "* `redirect` - redirect\n* `confirm_in_app` - confirm_in_app\n* `request_digital_credential` - request_digital_credential" + }, + "DigitalIdWalletApiInteractionEnum": { + "enum": [ + "redirect", + "in_app", + "digital_credentials" + ], + "type": "string", + "description": "* `redirect` - redirect\n* `in_app` - in_app\n* `digital_credentials` - digital_credentials" + }, + "DigitalIdWalletApiWalletVerificationResultOutcomeEnum": { + "enum": [ + "pending", + "verified", + "cancelled", + "timeout", + "failed" + ], + "type": "string", + "description": "* `pending` - Pending\n* `verified` - Verified\n* `cancelled` - Cancelled by the user\n* `timeout` - Timed out\n* `failed` - Failed" + }, + "DigitalIdWalletApiWallet": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Catalog id of the wallet, e.g. bankid_se." + }, + "name": { + "type": "string" + }, + "issuing_authority": { + "type": "string", + "nullable": true + } + }, + "required": [ + "id", + "issuing_authority", + "name" + ] + }, + "DigitalIdWalletApiEudiWalletVerificationStartRequest": { + "type": "object", + "description": "Start a EUDI Wallet verification (AUT, BEL, BGR, HRV, CYP, CZE, DNK, EST, FIN, FRA, DEU, GRC, HUN, IRL, ITA, LVA, LTU, LUX, MLT, NLD, POL, PRT, ROU, SVK, SVN, ESP, SWE, ISL, LIE, NOR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiEudiWalletVerificationStartCountryEnum" + } + ], + "description": "ISO 3166-1 alpha-3 country of the EUDI Wallet identity.\n\n* `AUT` - AUT\n* `BEL` - BEL\n* `BGR` - BGR\n* `HRV` - HRV\n* `CYP` - CYP\n* `CZE` - CZE\n* `DNK` - DNK\n* `EST` - EST\n* `FIN` - FIN\n* `FRA` - FRA\n* `DEU` - DEU\n* `GRC` - GRC\n* `HUN` - HUN\n* `IRL` - IRL\n* `ITA` - ITA\n* `LVA` - LVA\n* `LTU` - LTU\n* `LUX` - LUX\n* `MLT` - MLT\n* `NLD` - NLD\n* `POL` - POL\n* `PRT` - PRT\n* `ROU` - ROU\n* `SVK` - SVK\n* `SVN` - SVN\n* `ESP` - ESP\n* `SWE` - SWE\n* `ISL` - ISL\n* `LIE` - LIE\n* `NOR` - NOR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "country", + "return_url" + ] + }, + "DigitalIdWalletApiDeviceEnum": { + "enum": [ + "same_device", + "cross_device" + ], + "type": "string", + "description": "* `same_device` - same_device\n* `cross_device` - cross_device" + }, + "DigitalIdWalletApiEudiWalletVerificationStartCountryEnum": { + "enum": [ + "AUT", + "BEL", + "BGR", + "HRV", + "CYP", + "CZE", + "DNK", + "EST", + "FIN", + "FRA", + "DEU", + "GRC", + "HUN", + "IRL", + "ITA", + "LVA", + "LTU", + "LUX", + "MLT", + "NLD", + "POL", + "PRT", + "ROU", + "SVK", + "SVN", + "ESP", + "SWE", + "ISL", + "LIE", + "NOR" + ], + "type": "string", + "description": "* `AUT` - AUT\n* `BEL` - BEL\n* `BGR` - BGR\n* `HRV` - HRV\n* `CYP` - CYP\n* `CZE` - CZE\n* `DNK` - DNK\n* `EST` - EST\n* `FIN` - FIN\n* `FRA` - FRA\n* `DEU` - DEU\n* `GRC` - GRC\n* `HUN` - HUN\n* `IRL` - IRL\n* `ITA` - ITA\n* `LVA` - LVA\n* `LTU` - LTU\n* `LUX` - LUX\n* `MLT` - MLT\n* `NLD` - NLD\n* `POL` - POL\n* `PRT` - PRT\n* `ROU` - ROU\n* `SVK` - SVK\n* `SVN` - SVN\n* `ESP` - ESP\n* `SWE` - SWE\n* `ISL` - ISL\n* `LIE` - LIE\n* `NOR` - NOR" + }, + "DigitalIdWalletApiConnectidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a ConnectID verification (AUS).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiConnectidWalletVerificationStartCountryEnum" + } + ], + "default": "AUS", + "description": "ISO 3166-1 alpha-3 country of the ConnectID identity. Defaults to AUS, the only country it serves.\n\n* `AUS` - AUS" + }, + "authorisation_server_id": { + "type": "string", + "minLength": 1, + "description": "Authorisation server ID selected with ConnectID's bank selector. Sandbox defaults to Mock Bank A.", + "maxLength": 36 + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiConnectidWalletVerificationStartCountryEnum": { + "enum": [ + "AUS" + ], + "type": "string", + "description": "* `AUS` - AUS" + }, + "DigitalIdWalletApiAudkenniWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Auðkenni verification (ISL).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiAudkenniWalletVerificationStartCountryEnum" + } + ], + "default": "ISL", + "description": "ISO 3166-1 alpha-3 country of the Auðkenni identity. Defaults to ISL, the only country it serves.\n\n* `ISL` - ISL" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiAudkenniWalletVerificationStartCountryEnum": { + "enum": [ + "ISL" + ], + "type": "string", + "description": "* `ISL` - ISL" + }, + "DigitalIdWalletApiFranceconnectWalletVerificationStartRequest": { + "type": "object", + "description": "Start a FranceConnect verification (FRA).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiFranceconnectWalletVerificationStartCountryEnum" + } + ], + "default": "FRA", + "description": "ISO 3166-1 alpha-3 country of the FranceConnect identity. Defaults to FRA, the only country it serves.\n\n* `FRA` - FRA" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiFranceconnectWalletVerificationStartCountryEnum": { + "enum": [ + "FRA" + ], + "type": "string", + "description": "* `FRA` - FRA" + }, + "DigitalIdWalletApiDiiaWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Diia verification (UKR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDiiaWalletVerificationStartCountryEnum" + } + ], + "default": "UKR", + "description": "ISO 3166-1 alpha-3 country of the Diia identity. Defaults to UKR, the only country it serves.\n\n* `UKR` - UKR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiDiiaWalletVerificationStartCountryEnum": { + "enum": [ + "UKR" + ], + "type": "string", + "description": "* `UKR` - UKR" + }, + "DigitalIdWalletApiMojeidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a MojeID verification (CZE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryE2dEnum" + } + ], + "default": "CZE", + "description": "ISO 3166-1 alpha-3 country of the MojeID identity. Defaults to CZE, the only country it serves.\n\n* `CZE` - CZE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiCountryE2dEnum": { + "enum": [ + "CZE" + ], + "type": "string", + "description": "* `CZE` - CZE" + }, + "DigitalIdWalletApiBankidCzWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Bank iD verification (CZE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryE2dEnum" + } + ], + "default": "CZE", + "description": "ISO 3166-1 alpha-3 country of the Bank iD identity. Defaults to CZE, the only country it serves.\n\n* `CZE` - CZE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest": { + "type": "object", + "description": "Start a eParaksts mobile verification (LVA).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiEparakstsMobileWalletVerificationStartCountryEnum" + } + ], + "default": "LVA", + "description": "ISO 3166-1 alpha-3 country of the eParaksts mobile identity. Defaults to LVA, the only country it serves.\n\n* `LVA` - LVA" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiEparakstsMobileWalletVerificationStartCountryEnum": { + "enum": [ + "LVA" + ], + "type": "string", + "description": "* `LVA` - LVA" + }, + "DigitalIdWalletApiIdcardEeWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Estonian ID-card verification (EST).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiIdcardEeWalletVerificationStartCountryEnum" + } + ], + "default": "EST", + "description": "ISO 3166-1 alpha-3 country of the Estonian ID-card identity. Defaults to EST, the only country it serves.\n\n* `EST` - EST" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiIdcardEeWalletVerificationStartCountryEnum": { + "enum": [ + "EST" + ], + "type": "string", + "description": "* `EST` - EST" + }, + "DigitalIdWalletApiMobileIdWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Mobile-ID verification (EST, LTU).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiMobileIdWalletVerificationStartCountryEnum" + } + ], + "description": "ISO 3166-1 alpha-3 country of the Mobile-ID identity.\n\n* `EST` - EST\n* `LTU` - LTU" + }, + "personal_code": { + "type": "string", + "minLength": 1, + "description": "The user's national personal identification code. It addresses the user's Mobile-ID app; the user confirms the comparison code and enters their PIN in the app. Never collect the PIN.", + "pattern": "^[0-9-]{6,20}$" + }, + "phone_number": { + "type": "string", + "minLength": 1, + "description": "The Mobile-ID phone number in E.164 format (+372... or +370...).", + "maxLength": 16 + } + }, + "required": [ + "country", + "personal_code", + "phone_number" + ] + }, + "DigitalIdWalletApiMobileIdWalletVerificationStartCountryEnum": { + "enum": [ + "EST", + "LTU" + ], + "type": "string", + "description": "* `EST` - EST\n* `LTU` - LTU" + }, + "DigitalIdWalletApiSmartIdWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Smart-ID verification (EST, LVA, LTU, BEL).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiSmartIdWalletVerificationStartCountryEnum" + } + ], + "description": "ISO 3166-1 alpha-3 country of the Smart-ID identity.\n\n* `EST` - EST\n* `LVA` - LVA\n* `LTU` - LTU\n* `BEL` - BEL" + }, + "personal_code": { + "type": "string", + "minLength": 1, + "description": "The user's national personal identification code. It addresses the user's Smart-ID app; the user confirms the comparison code and enters their PIN in the app. Never collect the PIN.", + "pattern": "^[0-9-]{6,20}$" + } + }, + "required": [ + "country", + "personal_code" + ] + }, + "DigitalIdWalletApiSmartIdWalletVerificationStartCountryEnum": { + "enum": [ + "EST", + "LVA", + "LTU", + "BEL" + ], + "type": "string", + "description": "* `EST` - EST\n* `LVA` - LVA\n* `LTU` - LTU\n* `BEL` - BEL" + }, + "DigitalIdWalletApiGovukWalletWalletVerificationStartRequest": { + "type": "object", + "description": "Start a GOV.UK Wallet verification (GBR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountry520Enum" + } + ], + "default": "GBR", + "description": "ISO 3166-1 alpha-3 country of the GOV.UK Wallet identity. Defaults to GBR, the only country it serves.\n\n* `GBR` - GBR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiCountry520Enum": { + "enum": [ + "GBR" + ], + "type": "string", + "description": "* `GBR` - GBR" + }, + "DigitalIdWalletApiOneidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a OneID verification (GBR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountry520Enum" + } + ], + "default": "GBR", + "description": "ISO 3166-1 alpha-3 country of the OneID identity. Defaults to GBR, the only country it serves.\n\n* `GBR` - GBR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiGovbrWalletVerificationStartRequest": { + "type": "object", + "description": "Start a gov.br verification (BRA).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiGovbrWalletVerificationStartCountryEnum" + } + ], + "default": "BRA", + "description": "ISO 3166-1 alpha-3 country of the gov.br identity. Defaults to BRA, the only country it serves.\n\n* `BRA` - BRA" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiGovbrWalletVerificationStartCountryEnum": { + "enum": [ + "BRA" + ], + "type": "string", + "description": "* `BRA` - BRA" + }, + "DigitalIdWalletApiUaePassWalletVerificationStartRequest": { + "type": "object", + "description": "Start a UAE PASS verification (ARE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiUaePassWalletVerificationStartCountryEnum" + } + ], + "default": "ARE", + "description": "ISO 3166-1 alpha-3 country of the UAE PASS identity. Defaults to ARE, the only country it serves.\n\n* `ARE` - ARE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiUaePassWalletVerificationStartCountryEnum": { + "enum": [ + "ARE" + ], + "type": "string", + "description": "* `ARE` - ARE" + }, + "DigitalIdWalletApiFrejaidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Freja eID verification (SWE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryEe9Enum" + } + ], + "default": "SWE", + "description": "ISO 3166-1 alpha-3 country of the Freja eID identity. Defaults to SWE, the only country it serves.\n\n* `SWE` - SWE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiCountryEe9Enum": { + "enum": [ + "SWE" + ], + "type": "string", + "description": "* `SWE` - SWE" + }, + "DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Personalausweis verification (DEU).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiPersonalausweisWalletVerificationStartCountryEnum" + } + ], + "default": "DEU", + "description": "ISO 3166-1 alpha-3 country of the Personalausweis identity. Defaults to DEU, the only country it serves.\n\n* `DEU` - DEU" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiPersonalausweisWalletVerificationStartCountryEnum": { + "enum": [ + "DEU" + ], + "type": "string", + "description": "* `DEU` - DEU" + }, + "DigitalIdWalletApiFtnWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Finnish Trust Network verification (FIN).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiFtnWalletVerificationStartCountryEnum" + } + ], + "default": "FIN", + "description": "ISO 3166-1 alpha-3 country of the Finnish Trust Network identity. Defaults to FIN, the only country it serves.\n\n* `FIN` - FIN" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiFtnWalletVerificationStartCountryEnum": { + "enum": [ + "FIN" + ], + "type": "string", + "description": "* `FIN` - FIN" + }, + "DigitalIdWalletApiIdinWalletVerificationStartRequest": { + "type": "object", + "description": "Start a iDIN verification (NLD).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiIdinWalletVerificationStartCountryEnum" + } + ], + "default": "NLD", + "description": "ISO 3166-1 alpha-3 country of the iDIN identity. Defaults to NLD, the only country it serves.\n\n* `NLD` - NLD" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiIdinWalletVerificationStartCountryEnum": { + "enum": [ + "NLD" + ], + "type": "string", + "description": "* `NLD` - NLD" + }, + "DigitalIdWalletApiItsmeWalletVerificationStartRequest": { + "type": "object", + "description": "Start a itsme verification (BEL, LUX, NLD).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiItsmeWalletVerificationStartCountryEnum" + } + ], + "description": "ISO 3166-1 alpha-3 country of the itsme identity.\n\n* `BEL` - BEL\n* `LUX` - LUX\n* `NLD` - NLD" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "country", + "return_url" + ] + }, + "DigitalIdWalletApiItsmeWalletVerificationStartCountryEnum": { + "enum": [ + "BEL", + "LUX", + "NLD" + ], + "type": "string", + "description": "* `BEL` - BEL\n* `LUX` - LUX\n* `NLD` - NLD" + }, + "DigitalIdWalletApiBuypassWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Buypass ID verification (NOR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryAfcEnum" + } + ], + "default": "NOR", + "description": "ISO 3166-1 alpha-3 country of the Buypass ID identity. Defaults to NOR, the only country it serves.\n\n* `NOR` - NOR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiCountryAfcEnum": { + "enum": [ + "NOR" + ], + "type": "string", + "description": "* `NOR` - NOR" + }, + "DigitalIdWalletApiVippsWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Vipps verification (NOR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryAfcEnum" + } + ], + "default": "NOR", + "description": "ISO 3166-1 alpha-3 country of the Vipps identity. Defaults to NOR, the only country it serves.\n\n* `NOR` - NOR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiBankidNoWalletVerificationStartRequest": { + "type": "object", + "description": "Start a BankID verification (NOR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryAfcEnum" + } + ], + "default": "NOR", + "description": "ISO 3166-1 alpha-3 country of the BankID identity. Defaults to NOR, the only country it serves.\n\n* `NOR` - NOR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiBankidSeWalletVerificationStartRequest": { + "type": "object", + "description": "Start a BankID verification (SWE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryEe9Enum" + } + ], + "default": "SWE", + "description": "ISO 3166-1 alpha-3 country of the BankID identity. Defaults to SWE, the only country it serves.\n\n* `SWE` - SWE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiMitidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a MitID verification (DNK).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiMitidWalletVerificationStartCountryEnum" + } + ], + "default": "DNK", + "description": "ISO 3166-1 alpha-3 country of the MitID identity. Defaults to DNK, the only country it serves.\n\n* `DNK` - DNK" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiMitidWalletVerificationStartCountryEnum": { + "enum": [ + "DNK" + ], + "type": "string", + "description": "* `DNK` - DNK" + }, + "DigitalIdWalletApiWalletCatalogResponse": { + "type": "object", + "properties": { + "wallets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletCatalogEntry" + } + } + }, + "required": [ + "wallets" + ] + }, + "DigitalIdWalletApiWalletCatalogEntry": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string", + "description": "The wallet's own start endpoint." + }, + "countries": { + "type": "array", + "items": { + "type": "string" + } + }, + "issuing_authority": { + "type": "string" + }, + "level_of_assurance": { + "type": "string", + "nullable": true + }, + "interaction": { + "$ref": "#/components/schemas/DigitalIdWalletApiInteractionEnum" + }, + "required_fields": { + "type": "array", + "items": { + "type": "string" + } + }, + "available": { + "type": "boolean", + "description": "Whether this application can start it in this environment now. Listed does not mean live." + } + }, + "required": [ + "available", + "countries", + "id", + "interaction", + "issuing_authority", + "level_of_assurance", + "name", + "path", + "required_fields" + ] + }, + "CaseDuplicateCaseDuplicate": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "case_number": { + "type": "string", + "nullable": true, + "maxLength": 32 + }, + "title": { + "type": "string", + "maxLength": 255 + }, + "status": { + "$ref": "#/components/schemas/CaseDuplicateStatusEnum" + }, + "priority": { + "$ref": "#/components/schemas/CaseDuplicatePriorityEnum" + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "created_at", + "title", + "uuid" + ] + }, + "CaseDuplicateCaseDuplicatePage": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next_offset": { + "type": "integer", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicate" + } + } + }, + "required": [ + "count", + "next_offset", + "results" + ] + }, + "RoutingPublicationPolicyDraftResponse": { + "type": "object", + "properties": { + "draft": { + "allOf": [ + { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraft" + } + ], + "nullable": true + } + }, + "required": [ + "draft" + ] + }, + "RoutingPublicationPolicyDraftWriteRequest": { + "type": "object", + "properties": { + "expected_version": { + "type": "integer", + "minimum": 0 + }, + "expected_revision": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "changes": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "changes", + "expected_revision", + "expected_version" + ] + }, + "CasePublicationPolicyDraft": { + "type": "object", + "properties": { + "revision": { + "type": "string", + "format": "uuid" + }, + "base_version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "changes": {}, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "base_version", + "updated_at" + ] + }, + "CasePublicationPolicyDraftPreconditionRequest": { + "type": "object", + "properties": { + "expected_version": { + "type": "integer", + "minimum": 0 + }, + "expected_revision": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "required": [ + "expected_revision", + "expected_version" + ] + }, + "CaseCollaborationCaseNote": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "comment": { + "type": "string" + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 255 + }, + "actor_name": { + "type": "string", + "nullable": true, + "maxLength": 255 + }, + "mentioned_emails": {}, + "is_internal": { + "type": "boolean" + }, + "attachments": {}, + "tags": {}, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "reply_to": { + "allOf": [ + { + "$ref": "#/components/schemas/CaseCollaborationCaseNoteReply" + } + ], + "nullable": true, + "readOnly": true + }, + "is_pinned": { + "type": "boolean" + }, + "client_reference": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "required": [ + "comment", + "created_at", + "reply_to", + "uuid" + ] + }, + "CustomerMessagingPaginatedCaseRequestList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=400&limit=100" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=200&limit=100" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequest" + } + } + } + }, + "FraudCheckResponse": { + "type": "object", + "properties": { + "check_id": { + "type": "string", + "format": "uuid" + }, + "request_id": { + "type": "string", + "format": "uuid", + "description": "Native verification session created for this check." + }, + "client_reference": { + "type": "string", + "nullable": true + }, + "processing_status": { + "$ref": "#/components/schemas/FraudProcessingStatusEnum" + }, + "profile_id": { + "type": "string" + }, + "profile_version": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "inputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckInput" + } + }, + "modules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckModuleReport" + } + }, + "attribute_evidence": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "compound_evidence": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "risk": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckRisk" + } + }, + "network": { + "type": "object", + "additionalProperties": {} + }, + "enrichment": { + "type": "object", + "additionalProperties": {} + }, + "identity_relationship": { + "allOf": [ + { + "$ref": "#/components/schemas/FraudCheckIdentityRelationship" + } + ], + "nullable": true + }, + "recommendation": { + "$ref": "#/components/schemas/FraudRecommendationEnum" + }, + "next_action": { + "$ref": "#/components/schemas/FraudNextActionEnum" + }, + "decision_reasons": { + "type": "array", + "items": { + "type": "string" + } + }, + "coverage": { + "type": "object", + "additionalProperties": {} + }, + "usage": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "vendor_data": { + "type": "string", + "nullable": true + }, + "metadata": { + "nullable": true + } + }, + "required": [ + "attribute_evidence", + "check_id", + "client_reference", + "completed_at", + "compound_evidence", + "coverage", + "created_at", + "decision_reasons", + "inputs", + "modules", + "network", + "next_action", + "processing_status", + "profile_id", + "profile_version", + "recommendation", + "risk", + "usage" + ] + }, + "CaseReviewTeam": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseReviewTeamMember" + }, + "maxItems": 250 + }, + "is_active": { + "type": "boolean" + }, + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + } + }, + "CaseActionCaseEntityActionRequest": { + "type": "object", + "properties": { + "client_reference": { + "type": "string", + "format": "uuid" + }, + "retry": { + "type": "boolean", + "default": false + }, + "action": { + "$ref": "#/components/schemas/CaseActionActionEnum" + }, + "status": { + "type": "string", + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "note": { + "type": "string", + "nullable": true + } + }, + "required": [ + "action" + ] + }, + "RoutingPublicationPaginatedPolicyRevisionList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=400&limit=100" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=200&limit=100" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RoutingPublicationPolicyRevision" + } + } + } + }, + "FraudRecommendationEnum": { + "enum": [ + "approve", + "review", + "reject" + ], + "type": "string", + "description": "* `approve` - Approve\n* `review` - Review\n* `reject` - Reject" + }, + "CustomerMessagingCaseRequestPreviewResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid" + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "recipient": { + "type": "string", + "format": "email" + }, + "sender": { + "type": "string" + }, + "subject": { + "type": "string" + }, + "html": { + "type": "string" + }, + "requires_user_action": { + "type": "boolean" + }, + "preview_token": { + "type": "string" + } + }, + "required": [ + "expires_at", + "html", + "preview_token", + "recipient", + "request_id", + "requires_user_action", + "sender", + "subject" + ] + }, + "CustomerMessagingCaseCustomerResponseRequest": { + "type": "object", + "properties": { + "full_name": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "file": { + "type": "string", + "format": "binary", + "writeOnly": true + } + } + }, + "CaseCollaborationCaseNoteReply": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "comment": { + "type": "string", + "maxLength": 300 + } + }, + "required": [ + "actor_email", + "actor_name", + "comment", + "uuid" + ] + }, + "FraudCheckModuleReport": { + "type": "object", + "properties": { + "module": { + "$ref": "#/components/schemas/FraudExcludeEnum" + }, + "status": { + "$ref": "#/components/schemas/FraudStatusEnum" + }, + "reason": { + "type": "string", + "nullable": true + }, + "latency_ms": { + "type": "integer" + }, + "freshness": { + "type": "string", + "nullable": true + }, + "billable_units": { + "type": "integer" + } + }, + "required": [ + "billable_units", + "freshness", + "latency_ms", + "module", + "reason", + "status" + ] + }, + "CaseDuplicateEntityTypeEnum": { + "enum": [ + "session", + "business_session", + "transaction" + ], + "type": "string", + "description": "* `session` - Session\n* `business_session` - Business Session\n* `transaction` - Transaction" + }, + "FraudClientCredentialsErrorMessage": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "default": "Authentication failed: Invalid client access token." + } + } + }, + "FraudAvailabilityEnum": { + "enum": [ + "available", + "not_requested", + "no_reference", + "insufficient_history", + "unsupported_region", + "source_unavailable" + ], + "type": "string", + "description": "* `available` - Available\n* `not_requested` - Not Requested\n* `no_reference` - No Reference\n* `insufficient_history` - Insufficient History\n* `unsupported_region` - Unsupported Region\n* `source_unavailable` - Source Unavailable" + }, + "CasePublicationPolicyDraftResponse": { + "type": "object", + "properties": { + "draft": { + "allOf": [ + { + "$ref": "#/components/schemas/CasePublicationPolicyDraft" + } + ], + "nullable": true + } + }, + "required": [ + "draft" + ] + }, + "FraudCheckAttributesRequest": { + "type": "object", + "description": "Corroborating attributes. All optional; they widen module coverage.", + "properties": { + "first_name": { + "type": "string", + "maxLength": 128 + }, + "last_name": { + "type": "string", + "maxLength": 128 + }, + "full_name": { + "type": "string", + "maxLength": 256 + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "nationality": { + "type": "string", + "maxLength": 3 + }, + "gender": { + "type": "string", + "maxLength": 16 + }, + "address": { + "type": "string", + "maxLength": 512 + }, + "city": { + "type": "string", + "maxLength": 128 + }, + "state": { + "type": "string", + "maxLength": 128 + }, + "postal_code": { + "type": "string", + "maxLength": 32 + } + } + }, + "NetworkNote": { + "type": "object", + "required": [ + "uuid", + "actor_email", + "comment", + "client_reference", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "actor_email": { + "type": "string", + "format": "email", + "readOnly": true, + "description": "Authenticated author, assigned by the server." + }, + "comment": { + "type": "string", + "minLength": 1, + "maxLength": 10000 + }, + "client_reference": { + "type": "string", + "format": "uuid" + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + } + }, + "CaseActionEntityTypeEnum": { + "enum": [ + "user", + "business" + ], + "type": "string", + "description": "* `user` - user\n* `business` - business" + }, + "CaseRoutingCondition": { + "type": "object", + "properties": { + "field": { + "type": "string", + "enum": [ + "source", + "priority", + "tags" + ] + }, + "operator": { + "type": "string", + "enum": [ + "equals", + "in", + "contains" + ] + }, + "value": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": [ + "field", + "operator", + "value" + ], + "description": "Source and priority support equals or in with supported values. Tags support contains with one non-empty tag." + }, + "RoutingPublicationPolicyRevision": { + "type": "object", + "properties": { + "version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "snapshot": {}, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "published_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "published_at", + "snapshot", + "version" + ] + }, + "CasePublicationPolicyRevision": { + "type": "object", + "properties": { + "version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "snapshot": {}, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "published_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "published_at", + "snapshot", + "version" + ] + }, + "FraudCheckIdentityRelationship": { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/FraudResultEnum" + }, + "reference_type": { + "type": "string" + }, + "country": { + "type": "string" + }, + "availability": { + "$ref": "#/components/schemas/FraudAvailabilityEnum" + }, + "reason_codes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "availability", + "country", + "reason_codes", + "reference_type", + "result" + ] + }, + "CasePublicationPaginatedPolicyRevisionList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=400&limit=100" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=200&limit=100" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CasePublicationPolicyRevision" + } + } + } + }, + "CaseDuplicateBlankEnum": { + "enum": [ + "" + ] + }, + "FraudCheckPlan": { + "type": "object", + "properties": { + "profile_id": { + "type": "string" + }, + "profile_version": { + "type": "string" + }, + "modules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckPlannedModule" + } + }, + "estimated_price": { + "type": "string", + "nullable": true + }, + "currency": { + "type": "string", + "nullable": true + }, + "is_sandbox": { + "type": "boolean" + }, + "can_afford": { + "type": "boolean" + } + }, + "required": [ + "can_afford", + "currency", + "estimated_price", + "is_sandbox", + "modules", + "profile_id", + "profile_version" + ] + }, + "CaseReviewTeamUpdate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseReviewTeamMember" + }, + "maxItems": 250 + }, + "is_active": { + "type": "boolean" + } + } + }, + "RoutingPublicationPolicyDraft": { + "type": "object", + "properties": { + "revision": { + "type": "string", + "format": "uuid" + }, + "base_version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "changes": {}, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "base_version", + "updated_at" + ] + }, + "FraudCheckRequestRequest": { + "type": "object", + "properties": { + "client_reference": { + "type": "string", + "minLength": 1, + "description": "Your own reference for this check. Doubles as the idempotency key: reusing it with the same body returns the original check without running or billing the modules again; reusing it with a different body returns 409.", + "maxLength": 255 + }, + "profile": { + "type": "string", + "minLength": 1, + "default": "standard", + "description": "Orchestration profile id. Defaults to the Didit `standard` profile.", + "maxLength": 64 + }, + "profile_version": { + "type": "string", + "minLength": 1, + "description": "Pin a frozen profile version. Defaults to the current version of the profile.", + "maxLength": 32 + }, + "subject": { + "$ref": "#/components/schemas/FraudCheckSubjectRequest" + }, + "context": { + "$ref": "#/components/schemas/FraudCheckContextRequest" + }, + "checks": { + "$ref": "#/components/schemas/FraudCheckModuleSelectionRequest" + }, + "vendor_data": { + "type": "string", + "maxLength": 1000 + }, + "metadata": {} + }, + "required": [ + "subject" + ] + }, + "CaseDuplicateCaseSubjectInputRequest": { + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/CaseDuplicateTypeEnum" + }, + "uuid": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "uuid" + ] + }, + "CaseReviewTeamMember": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "format": "uuid" + }, + "availability": { + "type": "string", + "enum": [ + "available", + "unavailable" + ], + "default": "available" + }, + "capacity": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 10 + }, + "skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "available_from": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "available_until": { + "type": "string", + "format": "date-time", + "nullable": true + } + }, + "required": [ + "user_id" + ] + }, + "CaseRoutingPreviewRequest": { + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ] + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ], + "default": "MEDIUM" + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 50 + }, + "rule_uuid": { + "type": "string", + "format": "uuid" + }, + "draft": { + "$ref": "#/components/schemas/CaseRoutingRuleCreate" + } + }, + "required": [ + "source" + ] + }, + "FraudExcludeEnum": { + "enum": [ + "normalization", + "email_risk", + "email_social", + "phone_social", + "phone_risk", + "ip_risk", + "database_validation", + "networks_same_org", + "networks_cross_org", + "biometric_identity" + ], + "type": "string", + "description": "* `normalization` - Normalization\n* `email_risk` - Email Risk\n* `email_social` - Email Social\n* `phone_social` - Phone Social\n* `phone_risk` - Phone Risk\n* `ip_risk` - Ip Risk\n* `database_validation` - Database Validation\n* `networks_same_org` - Networks Same Org\n* `networks_cross_org` - Networks Cross Org\n* `biometric_identity` - Biometric Identity" + }, + "CaseDuplicateCaseLinkCreateRequest": { + "type": "object", + "properties": { + "entity_type": { + "$ref": "#/components/schemas/CaseDuplicateEntityTypeEnum" + }, + "entity_uuid": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "entity_type", + "entity_uuid" + ] + }, + "CaseDuplicateStatusEnum": { + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ], + "type": "string", + "description": "* `OPEN` - Open\n* `AWAITING_USER` - Awaiting user\n* `RESOLVED` - Resolved" + }, + "FraudBandEnum": { + "enum": [ + "low", + "medium", + "high", + "unknown" + ], + "type": "string", + "description": "* `low` - Low\n* `medium` - Medium\n* `high` - High\n* `unknown` - Unknown" + }, + "CaseDuplicatePriorityEnum": { + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ], + "type": "string", + "description": "* `LOW` - Low\n* `MEDIUM` - Medium\n* `HIGH` - High" + }, + "FraudStatusEnum": { + "enum": [ + "planned", + "completed", + "skipped", + "failed" + ], + "type": "string", + "description": "* `planned` - Planned\n* `completed` - Completed\n* `skipped` - Skipped\n* `failed` - Failed" + }, + "FraudCheckInput": { + "type": "object", + "properties": { + "reference": { + "type": "string", + "description": "Redacted handle for the submitted identifier." + }, + "type": { + "$ref": "#/components/schemas/FraudTypeEnum" + }, + "namespace": { + "type": "object", + "additionalProperties": {} + }, + "quality": { + "type": "string" + }, + "normalization": { + "type": "object", + "additionalProperties": {} + }, + "availability": { + "$ref": "#/components/schemas/FraudAvailabilityEnum" + } + }, + "required": [ + "availability", + "namespace", + "normalization", + "quality", + "reference", + "type" + ] + }, + "CustomerMessagingPurposesEnum": { + "enum": [ + "identity_document", + "proof_of_address", + "business_document", + "confirm_details", + "review_in_progress" + ], + "type": "string", + "description": "* `identity_document` - identity_document\n* `proof_of_address` - proof_of_address\n* `business_document` - business_document\n* `confirm_details` - confirm_details\n* `review_in_progress` - review_in_progress" + }, + "FraudTypeEnum": { + "enum": [ + "email", + "phone", + "national_id", + "tax_number", + "passport_number", + "driving_licence_number", + "residence_permit_number", + "voter_id", + "external_id" + ], + "type": "string", + "description": "* `email` - Email\n* `phone` - Phone\n* `national_id` - National Id\n* `tax_number` - Tax Number\n* `passport_number` - Passport Number\n* `driving_licence_number` - Driving Licence Number\n* `residence_permit_number` - Residence Permit Number\n* `voter_id` - Voter Id\n* `external_id` - External Id" + }, + "CaseDuplicateCaseSubjectInput": { + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/CaseDuplicateTypeEnum" + }, + "uuid": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "uuid" + ] + }, + "CaseReviewTeamCreate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseReviewTeamMember" + }, + "maxItems": 250 + }, + "is_active": { + "type": "boolean" + } + }, + "required": [ + "name", + "members" + ] + }, + "CaseDuplicateCaseDuplicateQueryRequest": { + "type": "object", + "properties": { + "subject": { + "$ref": "#/components/schemas/CaseDuplicateCaseSubjectInputRequest" + }, + "links": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseDuplicateCaseLinkCreateRequest" + } + }, + "offset": { + "type": "integer", + "maximum": 1000000, + "minimum": 0, + "default": 0 + } + } + }, + "CaseDuplicateResolutionEnum": { + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT" + ], + "type": "string", + "description": "* `FALSE_POSITIVE` - False positive\n* `VALID_THREAT` - Valid threat" + }, + "CasePublicationPolicyDraftWriteRequest": { + "type": "object", + "properties": { + "expected_version": { + "type": "integer", + "minimum": 0 + }, + "expected_revision": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "changes": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "changes", + "expected_revision", + "expected_version" + ] + }, + "FraudCheckSubjectRequest": { + "type": "object", + "properties": { + "selfie": { + "type": "string", + "minLength": 1, + "description": "Base64 JPEG, PNG or WebP selfie, up to 6 MB. No reference image or URL.", + "maxLength": 8388672 + }, + "identifiers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckIdentifierRequest" + }, + "description": "One or more typed identifiers. One is enough.", + "maxItems": 10, + "minItems": 1 + }, + "attributes": { + "$ref": "#/components/schemas/FraudCheckAttributesRequest" + } + }, + "required": [ + "identifiers" + ] + }, + "RoutingPublicationRoutingCondition": { + "type": "object", + "properties": { + "field": { + "$ref": "#/components/schemas/RoutingPublicationFieldEnum" + }, + "operator": { + "$ref": "#/components/schemas/RoutingPublicationOperatorEnum" + }, + "value": {} + }, + "required": [ + "field", + "operator", + "value" + ] + }, + "CaseActionCaseActionReceipt": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "client_reference": { + "type": "string", + "format": "uuid" + }, + "action": { + "type": "string", + "maxLength": 32 + }, + "status": { + "$ref": "#/components/schemas/CaseActionStatusEnum" + }, + "result": {}, + "error": { + "type": "string", + "maxLength": 255 + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "attempts": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "action", + "client_reference", + "created_at", + "status", + "updated_at", + "uuid" + ] + }, + "FraudCheckContextRequest": { + "type": "object", + "properties": { + "ip_address": { + "type": "string", + "minLength": 1 + }, + "user_agent": { + "type": "string", + "maxLength": 512 + }, + "country": { + "type": "string", + "description": "Country the subject claims to be in. Contextual only - never proof of identity ownership.", + "maxLength": 3 + }, + "purpose": { + "type": "string", + "maxLength": 128 + }, + "consent_obtained": { + "type": "boolean" + } + } + }, + "CustomerMessagingCaseCustomerResponseResult": { + "type": "object", + "properties": { + "responded_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "responded_at" + ] + }, + "CustomerMessagingCaseRequestContact": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "email": { + "type": "string", + "format": "email" + }, + "verified_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "email", + "uuid", + "verified_at" + ] + }, + "FraudClientCredentialsForbiddenErrorMessage": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "default": "Forbidden - User doesn't have permission" + } + } + }, + "CasePublicationAssignmentMethodEnum": { + "enum": [ + "MANUAL", + "AUTOMATIC" + ], + "type": "string", + "description": "* `MANUAL` - Manual\n* `AUTOMATIC` - Automatic" + }, + "CaseDuplicateNullEnum": { + "enum": [ + null + ] + }, + "CustomerMessagingLanguageEnum": { + "enum": [ + "en", + "es", + "pt" + ], + "type": "string", + "description": "* `en` - en\n* `es` - es\n* `pt` - pt" + }, + "CaseDuplicateCaseFraudSourceContextRequest": { + "type": "object", + "properties": { + "check_id": { + "type": "string", + "format": "uuid" + }, + "request_id": { + "type": "string", + "format": "uuid" + }, + "source_section": { + "type": "string", + "minLength": 1, + "pattern": "^[A-Z][A-Z0-9_:-]{0,63}$" + } + }, + "required": [ + "check_id", + "request_id", + "source_section" + ] + }, + "FraudCheckPlannedModule": { + "type": "object", + "properties": { + "module": { + "$ref": "#/components/schemas/FraudExcludeEnum" + }, + "available": { + "type": "boolean" + }, + "availability_reason": { + "type": "string", + "nullable": true + }, + "will_run": { + "type": "boolean" + }, + "selection_reason": { + "type": "string", + "nullable": true + }, + "estimated_price": { + "type": "string", + "nullable": true + } + }, + "required": [ + "availability_reason", + "available", + "estimated_price", + "module", + "selection_reason", + "will_run" + ] + }, + "CustomerMessagingCaseRequestOptions": { + "type": "object", + "properties": { + "contacts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestContact" + } + }, + "purposes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerMessagingPurposesEnum" + } + }, + "languages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerMessagingLanguagesEnum" + } + } + }, + "required": [ + "contacts", + "languages", + "purposes" + ] + }, + "CaseCollaborationPaginatedCaseNoteList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=400&limit=100" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=200&limit=100" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseCollaborationCaseNote" + } + } + } + }, + "FraudProcessingStatusEnum": { + "enum": [ + "pending", + "running", + "completed", + "failed" + ], + "type": "string", + "description": "* `pending` - Pending\n* `running` - Running\n* `completed` - Completed\n* `failed` - Failed" + }, + "CasePublicationCaseBlueprint": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "name": { + "type": "string", + "maxLength": 24 + }, + "description": { + "type": "string", + "nullable": true + }, + "assignment_method": { + "$ref": "#/components/schemas/CasePublicationAssignmentMethodEnum" + }, + "assignee_pool": {}, + "max_active_cases": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true + }, + "escalation_enabled": { + "type": "boolean" + }, + "escalation_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "escalation_blueprint_name": { + "type": "string", + "readOnly": true + }, + "transfer_enabled": { + "type": "boolean" + }, + "four_eyes_checker_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "four_eyes_checker_blueprint_name": { + "type": "string", + "readOnly": true + }, + "require_resolution_note": { + "type": "boolean" + }, + "content_config": {}, + "version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "default_deadline_hours": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "preset_key": { + "type": "string", + "nullable": true, + "maxLength": 64 + }, + "creation_sources": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + }, + "readOnly": true + }, + "receives_escalation_from": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + }, + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "created_at", + "creation_sources", + "escalation_blueprint_name", + "four_eyes_checker_blueprint_name", + "name", + "receives_escalation_from", + "updated_at", + "uuid" + ] + }, + "CaseRoutingRuleUpdate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "priority": { + "type": "integer", + "minimum": 0 + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseRoutingCondition" + }, + "maxItems": 20 + }, + "required_skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "blueprint": { + "type": "string", + "format": "uuid" + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "expected_version": { + "type": "integer", + "minimum": 1, + "writeOnly": true, + "description": "Version opened by the editor. When supplied, stale writes return 409 without changing the rule." + } + } + }, + "CustomerMessagingCaseRequestPreviewRequest": { + "type": "object", + "properties": { + "contact_id": { + "type": "string", + "format": "uuid" + }, + "purpose": { + "$ref": "#/components/schemas/CustomerMessagingPurposeEnum" + }, + "language": { + "allOf": [ + { + "$ref": "#/components/schemas/CustomerMessagingLanguageEnum" + } + ], + "default": "en" + } + }, + "required": [ + "contact_id", + "purpose" + ] + }, + "CaseDuplicateCaseFraudSourceContext": { + "type": "object", + "properties": { + "check_id": { + "type": "string", + "format": "uuid" + }, + "request_id": { + "type": "string", + "format": "uuid" + }, + "source_section": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_:-]{0,63}$" + } + }, + "required": [ + "check_id", + "request_id", + "source_section" + ] + }, + "RoutingPublicationOperatorEnum": { + "enum": [ + "equals", + "in", + "contains" + ], + "type": "string", + "description": "* `equals` - equals\n* `in` - in\n* `contains` - contains" + }, + "CaseRoutingPreview": { + "type": "object", + "properties": { + "rule_uuid": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "rule_name": { + "type": "string", + "nullable": true + }, + "rule_version": { + "type": "integer", + "nullable": true + }, + "blueprint_uuid": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "team_uuid": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "assignee_user_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "reason": { + "type": "string" + }, + "candidates": { + "type": "array", + "items": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "format": "uuid" + }, + "eligible": { + "type": "boolean" + }, + "active_cases": { + "type": "integer", + "nullable": true + }, + "reason": { + "type": "string", + "nullable": true + } + } + } + }, + "matched_rules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "priority": { + "type": "integer" + } + } + } + } + } + }, + "FraudDimensionEnum": { + "enum": [ + "identity", + "contact", + "device_network", + "behavioral", + "fraud_network" + ], + "type": "string", + "description": "* `identity` - Identity\n* `contact` - Contact\n* `device_network` - Device Network\n* `behavioral` - Behavioral\n* `fraud_network` - Fraud Network" + }, + "FraudBadRequestErrorMessage": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "default": "Bad Request - Invalid request data" + } + } + }, + "FraudNextActionEnum": { + "enum": [ + "none", + "manual_review", + "step_up", + "resubmit_capture" + ], + "type": "string", + "description": "* `none` - None\n* `manual_review` - Manual Review\n* `step_up` - Step Up\n* `resubmit_capture` - Resubmit Capture" + }, + "RoutingPublicationPolicyDraftPreconditionRequest": { + "type": "object", + "properties": { + "expected_version": { + "type": "integer", + "minimum": 0 + }, + "expected_revision": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "required": [ + "expected_revision", + "expected_version" + ] + }, + "FraudCheckRisk": { + "type": "object", + "properties": { + "dimension": { + "$ref": "#/components/schemas/FraudDimensionEnum" + }, + "score": { + "type": "integer", + "nullable": true + }, + "band": { + "$ref": "#/components/schemas/FraudBandEnum" + }, + "availability": { + "$ref": "#/components/schemas/FraudAvailabilityEnum" + }, + "requested_but_unavailable": { + "type": "boolean", + "description": "True when the request supplied everything this dimension needed and the platform still could not answer it. Such a check is never recommended for approval on the strength of the dimensions that did answer." + }, + "reason_codes": { + "type": "array", + "items": { + "type": "string" + } + }, + "evidence_indexes": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "availability", + "band", + "dimension", + "evidence_indexes", + "reason_codes", + "requested_but_unavailable", + "score" + ] + }, + "CaseDuplicateSourceEnum": { + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ], + "type": "string", + "description": "* `MANUAL` - Manual\n* `AML_SCREENING` - AML screening\n* `TRANSACTION_RULE` - Transaction rule\n* `WORKFLOW_BUILDER` - Workflow builder" + }, + "CaseCollaborationPatchedCaseNoteUpdateRequest": { + "type": "object", + "properties": { + "is_pinned": { + "type": "boolean" + } + } + }, + "CustomerMessagingPurposeEnum": { + "enum": [ + "identity_document", + "proof_of_address", + "business_document", + "confirm_details", + "review_in_progress" + ], + "type": "string", + "description": "* `identity_document` - identity_document\n* `proof_of_address` - proof_of_address\n* `business_document` - business_document\n* `confirm_details` - confirm_details\n* `review_in_progress` - review_in_progress" + }, + "CaseDuplicateTypeEnum": { + "enum": [ + "user", + "business" + ], + "type": "string", + "description": "* `user` - user\n* `business` - business" + }, + "CaseCollaborationCaseNoteCreateRequest": { + "type": "object", + "properties": { + "comment": { + "type": "string", + "minLength": 1, + "maxLength": 10000 + }, + "reply_to": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "client_reference": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email", + "minLength": 1 + } + }, + "is_internal": { + "type": "boolean", + "default": false + }, + "attachments": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "required": [ + "comment" + ] + }, + "CustomerMessagingLanguagesEnum": { + "enum": [ + "en", + "es", + "pt" + ], + "type": "string", + "description": "* `en` - en\n* `es` - es\n* `pt` - pt" + }, + "CustomerMessagingCaseRequestConfirmRequest": { + "type": "object", + "properties": { + "contact_id": { + "type": "string", + "format": "uuid" + }, + "purpose": { + "$ref": "#/components/schemas/CustomerMessagingPurposeEnum" + }, + "language": { + "allOf": [ + { + "$ref": "#/components/schemas/CustomerMessagingLanguageEnum" + } + ], + "default": "en" + }, + "request_id": { + "type": "string", + "format": "uuid" + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "preview_token": { + "type": "string", + "minLength": 1, + "maxLength": 512 + } + }, + "required": [ + "contact_id", + "expires_at", + "preview_token", + "purpose", + "request_id" + ] + }, + "CaseRoutingRule": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "priority": { + "type": "integer", + "minimum": 0 + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseRoutingCondition" + }, + "maxItems": 20 + }, + "required_skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "blueprint": { + "type": "string", + "format": "uuid" + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "version": { + "type": "integer", + "readOnly": true + }, + "blueprint_name": { + "type": "string", + "readOnly": true + }, + "team_name": { + "type": "string", + "nullable": true, + "readOnly": true + } + } + }, + "FraudResultEnum": { + "enum": [ + "match", + "mismatch", + "inconclusive", + "no_reference" + ], + "type": "string", + "description": "* `match` - Match\n* `mismatch` - Mismatch\n* `inconclusive` - Inconclusive\n* `no_reference` - No Reference" + }, + "CaseRoutingRuleCreate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "priority": { + "type": "integer", + "minimum": 0 + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseRoutingCondition" + }, + "maxItems": 20 + }, + "required_skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "blueprint": { + "type": "string", + "format": "uuid" + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "publish": { + "type": "boolean", + "writeOnly": true, + "description": "Set false to save a never-published draft. The record remains inactive at version 0 until the draft is explicitly published. Omitting this field preserves immediate creation for existing integrations." + } + }, + "required": [ + "name", + "conditions", + "blueprint" + ] + }, + "FraudCheckIdentifierRequest": { + "type": "object", + "properties": { + "type": { + "allOf": [ + { + "$ref": "#/components/schemas/FraudTypeEnum" + } + ], + "description": "Identifier type. Determines which namespace fields are required.\n\n* `email` - Email\n* `phone` - Phone\n* `national_id` - National Id\n* `tax_number` - Tax Number\n* `passport_number` - Passport Number\n* `driving_licence_number` - Driving Licence Number\n* `residence_permit_number` - Residence Permit Number\n* `voter_id` - Voter Id\n* `external_id` - External Id" + }, + "value": { + "type": "string", + "minLength": 1, + "description": "The identifier value, in any customary format.", + "maxLength": 255 + }, + "country": { + "type": "string", + "description": "ISO-3166 alpha-2 or alpha-3 issuing country. Required for government identifier types: the same digits mean different people in different countries.", + "maxLength": 3 + }, + "issuer": { + "type": "string", + "description": "Optional issuing authority or sub-national issuer, when the country alone is not unique.", + "maxLength": 128 + } + }, + "required": [ + "type", + "value" + ] + }, + "CaseActionActionEnum": { + "enum": [ + "set_status", + "add_tags", + "add_note", + "add_to_blocklist" + ], + "type": "string", + "description": "* `set_status` - set_status\n* `add_tags` - add_tags\n* `add_note` - add_note\n* `add_to_blocklist` - add_to_blocklist" + }, + "CaseActionCaseActionReceiptPage": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next_offset": { + "type": "integer", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseActionCaseActionReceipt" + } + } + }, + "required": [ + "count", + "next_offset", + "results" + ] + }, + "CaseActionCaseEntityActionResult": { + "type": "object", + "properties": { + "receipt": { + "$ref": "#/components/schemas/CaseActionCaseActionReceipt" + }, + "entity_type": { + "$ref": "#/components/schemas/CaseActionEntityTypeEnum" + }, + "entity_uuid": { + "type": "string", + "format": "uuid" + }, + "action": { + "type": "string" + }, + "previous_status": { + "type": "string" + }, + "new_status": { + "type": "string" + }, + "tags_added": { + "type": "array", + "items": { + "type": "string" + } + }, + "comment_uuid": { + "type": "string", + "format": "uuid" + }, + "blocklisted": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "CustomerMessagingCaseRequest": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "purpose": { + "type": "string", + "readOnly": true + }, + "language": { + "type": "string", + "readOnly": true + }, + "recipient": { + "type": "string", + "format": "email", + "readOnly": true + }, + "expires_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "actor_email": { + "type": "string", + "format": "email", + "readOnly": true, + "nullable": true + }, + "delivery_status": { + "type": "string", + "readOnly": true + }, + "delivered_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "nullable": true + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "nullable": true + }, + "responded_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "nullable": true + }, + "response": { + "readOnly": true, + "nullable": true + }, + "has_attachment": { + "type": "boolean", + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "actor_email", + "cancelled_at", + "created_at", + "delivered_at", + "delivery_status", + "expires_at", + "has_attachment", + "language", + "purpose", + "recipient", + "responded_at", + "response", + "uuid" + ] + }, + "CustomerMessagingCaseRequestPublic": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid" + }, + "purpose": { + "$ref": "#/components/schemas/CustomerMessagingPurposeEnum" + }, + "language": { + "$ref": "#/components/schemas/CustomerMessagingLanguageEnum" + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "responded_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "copy": { + "type": "object", + "additionalProperties": {} + }, + "organization_name": { + "type": "string" + }, + "is_white_label": { + "type": "boolean" + }, + "customization": { + "type": "object", + "additionalProperties": {}, + "nullable": true + } + }, + "required": [ + "copy", + "customization", + "expires_at", + "is_white_label", + "language", + "organization_name", + "purpose", + "request_id", + "responded_at" + ] + }, + "RoutingPublicationFieldEnum": { + "enum": [ + "source", + "priority", + "tags" + ], + "type": "string", + "description": "* `source` - source\n* `priority` - priority\n* `tags` - tags" + }, + "RoutingPublicationCaseRoutingRule": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "priority": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RoutingPublicationRoutingCondition" + } + }, + "required_skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "blueprint": { + "type": "string", + "format": "uuid" + }, + "blueprint_name": { + "type": "string", + "readOnly": true + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "team_name": { + "type": "string", + "readOnly": true + }, + "is_active": { + "type": "boolean" + }, + "version": { + "type": "integer", + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "blueprint", + "blueprint_name", + "conditions", + "created_at", + "name", + "team_name", + "updated_at", + "uuid", + "version" + ] + }, + "CaseActionStatusEnum": { + "enum": [ + "APPLIED", + "FAILED" + ], + "type": "string", + "description": "* `APPLIED` - Applied\n* `FAILED` - Failed" + }, + "FraudCheckModuleSelectionRequest": { + "type": "object", + "properties": { + "include": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudExcludeEnum" + } + }, + "exclude": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudExcludeEnum" + } + } + } + }, + "SignatureField": { + "type": "object", + "description": "A field to place on a session document: which signer fills it, how, and where. Give either a position (`page`, `x`, `y`, `width` and `height`) or an `anchor`. Positions are fractions of the page as it's displayed, from its top-left corner, rounded to 4 decimals, and the field must fit inside the page. A document takes up to 200 fields and one signer up to 500, and every role with fields on a document needs a `signature` or `initials` field there. See [Fields on the document](/core-technology/electronic-signature/overview#fields-on-the-document).", + "required": [ + "key", + "type" + ], + "properties": { + "key": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]{0,63}$", + "description": "Names the field in the results and in `sign.field_values`. Unique within the document." + }, + "type": { + "type": "string", + "enum": [ + "signature", + "initials", + "full_name", + "date_signed", + "text", + "checkbox", + "email", + "data" + ], + "description": "What the field holds. The signer applies their adopted `signature` and `initials`, types `text`, ticks a `checkbox`, and types an `email` when the session has no verified email. Didit fills `full_name` (the verified name), `date_signed` (the date of signing, in the signer's time zone), `data` (from `prefill`) and a verified `email`, and shows them read-only. There is no date picker type: use `text`." + }, + "role": { + "type": "string", + "enum": [ + "verified_person", + "legal_representative", + "key_people" + ], + "description": "Who fills the field, with the `signature_signer` values. A step fills and stamps only the fields of its own signer. Defaults to the step's signer." + }, + "page": { + "type": "integer", + "minimum": 1, + "description": "Page number, from 1 to the document's page count. For a Word document, a page of the converted PDF." + }, + "x": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Left edge, as a fraction of the page width." + }, + "y": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Top edge, as a fraction of the page height." + }, + "width": { + "type": "number", + "minimum": 0.02, + "maximum": 1, + "description": "Width, as a fraction of the page width. A signature is at least `0.12` wide." + }, + "height": { + "type": "number", + "minimum": 0.012, + "maximum": 1, + "description": "Height, as a fraction of the page height. A signature is at least `0.035` high." + }, + "anchor": { + "$ref": "#/components/schemas/SignatureFieldAnchor" + }, + "required": { + "type": "boolean", + "description": "`signature` and `initials` are required unless you set `false`. `full_name`, `date_signed` and `data` are always required. The signer can't finish a document until its required fields have a value, and a required checkbox must be ticked." + }, + "label": { + "type": "string", + "minLength": 1, + "maxLength": 80, + "description": "Shown to the signer and in the results. Defaults from the type." + }, + "placeholder": { + "type": "string", + "nullable": true, + "maxLength": 80, + "description": "`text` and `email` only." + }, + "validation": { + "$ref": "#/components/schemas/SignatureFieldValidation" + }, + "prefill": { + "type": "string", + "nullable": true, + "description": "Where the value comes from: `identity.full_name`, `identity.first_name`, `identity.last_name`, `identity.date_of_birth` (`DD/MM/YYYY`), `identity.nationality`, `identity.document_number`, `identity.document_type`, `identity.issuing_country`, `identity.address`, `contact.email`, `contact.phone`, `company.name`, `company.registration_number`, `company.address`, `company.country` (KYB), `session.vendor_data`, `metadata.` (a key of the session's `metadata`) or `api.` (a key of `sign.field_values`). Required for `data`. `full_name` always uses `identity.full_name` and `email` uses `contact.email`. The values of `data`, `full_name` and verified `email` fields are fixed when the signer starts signing." + }, + "editable_when_prefilled": { + "type": "boolean", + "default": false, + "description": "`text` and `email` only: the signer can change a pre-filled value." + }, + "checked_by_default": { + "type": "boolean", + "default": false, + "description": "`checkbox` only: the box starts ticked." + }, + "date_format": { + "type": "string", + "enum": [ + "DD/MM/YYYY", + "MM/DD/YYYY", + "YYYY-MM-DD", + "D MMMM YYYY" + ], + "default": "DD/MM/YYYY", + "description": "`date_signed` only." + } + } + }, + "SessionSignDocument": { + "type": "object", + "required": [ + "file_name", + "content_base64" + ], + "properties": { + "title": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "Title shown to the signer. Defaults to `file_name`." + }, + "file_name": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "File name, ending in `.pdf` or `.docx`." + }, + "content_base64": { + "type": "string", + "format": "byte", + "description": "The file, base64-encoded. A PDF can have up to 20 MB and 100 pages, without a password, scripts or attached files. A Word file (`.docx`) can have up to 20 MB and is converted to PDF while the request runs, in at most 25 seconds; the PDF then has the same limits. Up to 5 Word files per session. A Word file with macros, tracked changes, links to external content or embedded objects is refused with `signature_document_not_supported`, and one that can't be converted returns `signature_document_conversion_failed`, each with a `reason`." + }, + "fields": { + "type": "array", + "description": "Optional fields to place on this document, up to 200. Pages and positions refer to the PDF, or to the converted PDF for a Word file. Without fields, the signer reads the document and agrees to it.", + "items": { + "$ref": "#/components/schemas/SignatureField" + }, + "maxItems": 200 + } + } + }, + "SignatureResult": { + "type": "object", + "description": "One signer's result for one E-signature step. See the [E-signature report](/core-technology/electronic-signature/report-electronic-signature).", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Pass it as `signature_id` to download this signer's evidence or validate their signature." + }, + "node_id": { + "type": "string", + "description": "The workflow step that produced it." + }, + "status": { + "type": "string", + "enum": [ + "intro", + "review", + "code", + "signing", + "signed", + "declined" + ], + "description": "Where the signer is in the step. Only `signed` means the documents are signed." + }, + "verdict": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "In Review", + "Declined" + ], + "description": "The step's outcome. A warning sets it through its rule; custom status rules apply after." + }, + "level": { + "type": "string", + "enum": [ + "simple", + "aes", + "qes" + ], + "description": "`simple` (default) or `aes`. `qes` (Qualified Electronic Signature, QES) is an add-on that is not available yet." + }, + "format": { + "type": "string", + "enum": [ + "pdf", + "pades" + ], + "description": "`pdf` for a Simple signature, `pades` for Advanced (AES)." + }, + "profile": { + "type": "string", + "nullable": true, + "description": "`PAdES-B-LT` once an Advanced (AES) signature is signed, otherwise null." + }, + "signer": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "description": "The signer's name. Verified (from the approved ID verification or the session) except at the Simple level without a verified session name, where it is the name the signer typed and is not verified." + }, + "capacity": { + "type": "string", + "enum": [ + "self", + "company" + ] + }, + "company": { + "type": "string", + "nullable": true + }, + "role": { + "type": "string", + "nullable": true, + "description": "The key person's designation, as declared." + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The key person's structured roles, as declared for the company." + }, + "identity_session_id": { + "type": "string", + "format": "uuid", + "description": "The KYC session that verified the signer." + } + } + }, + "identity_used": { + "type": "object", + "description": "The verified identity the signature relies on. Empty for a Simple signature with no verified identity.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid" + }, + "kyc_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "verified": { + "type": "boolean" + }, + "expected_name": { + "type": "string", + "nullable": true, + "description": "The name the signer had to match." + } + } + }, + "certificate": { + "type": "object", + "nullable": true, + "description": "The certificate the documents were signed with. `null` until signed, and always `null` for a Simple signature.", + "properties": { + "issuer": { + "type": "string" + }, + "issued_to": { + "type": "string" + }, + "serial": { + "type": "string", + "description": "Serial number, hexadecimal." + }, + "valid_from": { + "type": "string", + "format": "date-time" + }, + "valid_until": { + "type": "string", + "format": "date-time" + } + } + }, + "confirmation": { + "type": "object", + "properties": { + "channel": { + "type": "string", + "nullable": true, + "enum": [ + "email", + "sms" + ], + "description": "`null` for a Simple signature, which has no code." + }, + "destination": { + "type": "string", + "nullable": true, + "description": "The masked contact the code went to." + }, + "confirmed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "attempts": { + "type": "integer", + "description": "Codes entered, the right one included." + } + } + }, + "timestamp": { + "type": "object", + "nullable": true, + "description": "The RFC 3161 timestamp in the signature, issued by Didit. `null` until signed, and always `null` for a Simple signature.", + "properties": { + "time": { + "type": "string", + "format": "date-time" + }, + "qualified": { + "type": "boolean", + "description": "Always `false` for AES." + }, + "valid": { + "type": "boolean" + } + } + }, + "documents": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The document on this signer's copy. Use it to download the signed file." + }, + "uuid": { + "type": "string", + "format": "uuid", + "description": "Same as `id`." + }, + "title": { + "type": "string" + }, + "file_name": { + "type": "string" + }, + "pages": { + "type": "integer", + "description": "Pages of the document signed. For Word, of the converted PDF." + }, + "size_bytes": { + "type": "integer" + }, + "sha256_original": { + "type": "string", + "description": "SHA-256 of the document as the signer was shown it." + }, + "sha256_signed": { + "type": "string", + "nullable": true, + "description": "SHA-256 of the signed PDF, once signed." + }, + "status": { + "type": "string", + "enum": [ + "signed", + "declined", + "not_signed", + "not_reached" + ], + "description": "`not_signed`: opened but not signed. `not_reached`: never opened." + }, + "opened_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "read_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the signer reached the end of a document without fields, when the step requires it." + }, + "read_to_end": { + "type": "boolean" + }, + "consented_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the signer agreed to a document without fields, or finished the fields of a document with fields." + }, + "consent": { + "type": "boolean" + }, + "signed_file": { + "type": "string", + "nullable": true, + "description": "Path of the signed PDF, once signed. See [Download a signed document](/sessions-api/electronic-signature/download-signed-document)." + }, + "mode": { + "type": "string", + "enum": [ + "fields", + "read_and_agree" + ], + "description": "`fields` for a document with fields, `read_and_agree` for a document without." + }, + "source_format": { + "type": "string", + "enum": [ + "pdf", + "docx" + ], + "description": "`pdf`, or `docx` for a Word document Didit converted to PDF." + }, + "conversion": { + "type": "object", + "nullable": true, + "description": "`null` for a PDF. For a Word document, the original file and the conversion.", + "properties": { + "from": { + "type": "string", + "enum": [ + "docx" + ] + }, + "file_name": { + "type": "string", + "description": "Your original Word file name." + }, + "sha256": { + "type": "string", + "description": "SHA-256 of your original Word file." + }, + "size_bytes": { + "type": "integer", + "description": "Size of your original Word file." + }, + "converter": { + "type": "string", + "description": "Version of the converter that produced the PDF." + } + } + }, + "fields": { + "type": "array", + "description": "Fields on the document, with their values. Empty for a document without fields.", + "items": { + "$ref": "#/components/schemas/SignatureFieldResult" + } + } + } + } + }, + "adopted_signature": { + "type": "object", + "nullable": true, + "description": "The signature and initials the signer adopted for signature and initials fields. `null` until the signer adopts one.", + "properties": { + "method": { + "type": "string", + "enum": [ + "typed", + "drawn" + ], + "description": "`typed`: the verified name in a handwriting style. `drawn`: drawn with a finger or a mouse." + }, + "font": { + "type": "string", + "nullable": true, + "description": "The handwriting style of a typed signature. `null` when drawn." + }, + "adopted_at": { + "type": "string", + "format": "date-time" + } + } + }, + "electronic_signature_consent_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the signer agreed, on the review screen, to sign the documents electronically. Set when any document has fields." + }, + "evidence_file": { + "type": "string", + "nullable": true, + "description": "Path of the evidence PDF, once signed. See [Download the signature evidence](/sessions-api/electronic-signature/download-evidence)." + }, + "warnings": { + "type": "array", + "description": "Warnings raised for this signer. See [E-signature warnings](/core-technology/electronic-signature/warnings-electronic-signature).", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": [ + "SIGNATURE_DECLINED", + "SIGNATURE_OTP_FAILED", + "SIGNATURE_NAME_MISMATCH", + "SIGNATURE_IDENTITY_NOT_VERIFIED", + "SIGNATURE_DOCUMENT_CHANGED", + "SIGNATURE_FIELD_DATA_MISSING" + ] + }, + "node_id": { + "type": "string" + } + } + } + }, + "validation": { + "type": "object", + "nullable": true, + "description": "The last cryptographic check of the signed documents. `null` until the documents are signed. If Didit can't confirm the outcome of signing, it's `{\"valid\": null, \"reason\": \"signature_provider_outcome_unknown\", \"checked_at\": ...}` and `status` stays `signing`: treat it as not signed.", + "properties": { + "valid": { + "type": "boolean", + "nullable": true, + "description": "`true` when every document is valid. `null` when the outcome of signing couldn't be confirmed." + }, + "reason": { + "type": "string", + "description": "Set when `valid` is `null`: `signature_provider_outcome_unknown`." + }, + "checked_at": { + "type": "string", + "format": "date-time" + }, + "documents": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "checked_at": { + "type": "string", + "format": "date-time" + }, + "valid": { + "type": "boolean", + "description": "`true` when the signature and the timestamp are both valid and trusted." + }, + "integrity": { + "type": "boolean", + "description": "`true` when the document is unchanged since signing." + }, + "trusted": { + "type": "boolean", + "description": "`true` when the signing certificate chains to a trusted root." + }, + "revoked": { + "type": "boolean" + }, + "trusted_list": { + "type": "object", + "description": "EU Trusted List check. For AES: `{\"status\": \"not_applicable\", \"reason\": \"aes_not_qualified\"}`.", + "properties": { + "status": { + "type": "string" + }, + "reason": { + "type": "string" + } + } + }, + "certificate": { + "type": "object", + "nullable": true, + "description": "The certificate the documents were signed with. `null` until signed, and always `null` for a Simple signature.", + "properties": { + "issuer": { + "type": "string" + }, + "issued_to": { + "type": "string" + }, + "serial": { + "type": "string", + "description": "Serial number, hexadecimal." + }, + "valid_from": { + "type": "string", + "format": "date-time" + }, + "valid_until": { + "type": "string", + "format": "date-time" + } + } + }, + "timestamp": { + "type": "object", + "nullable": true, + "description": "The RFC 3161 timestamp in the signature, issued by Didit. `null` until signed, and always `null` for a Simple signature.", + "properties": { + "time": { + "type": "string", + "format": "date-time" + }, + "qualified": { + "type": "boolean", + "description": "Always `false` for AES." + }, + "valid": { + "type": "boolean" + } + } + } + } + } + } + } + }, + "declined_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "declined_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ] + }, + "signed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the signing link stops working." + }, + "email_sent_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "email_unavailable_reason": { + "type": "string", + "nullable": true + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "The signer's IP address when they asked for their code." + }, + "place": { + "type": "string", + "nullable": true, + "description": "Not collected: always `null`." + }, + "place_unavailable_reason": { + "type": "string", + "nullable": true, + "enum": [ + "not_collected" + ] + } + } + }, + "SignatureFieldValidation": { + "type": "object", + "nullable": true, + "description": "Rules for a `text` field's value. The console preview, the signing flow and the API apply the same rules.", + "properties": { + "kind": { + "type": "string", + "enum": [ + "any", + "letters", + "number", + "email", + "pattern" + ], + "description": "`letters`: letters in any script, spaces, hyphens and apostrophes. `number`: digits with an optional decimal separator. `pattern`: the value must match `pattern`." + }, + "min_length": { + "type": "integer", + "minimum": 0, + "maximum": 500, + "description": "From `0` up to `max_length`." + }, + "max_length": { + "type": "integer", + "minimum": 1, + "maximum": 500, + "default": 120 + }, + "pattern": { + "type": "string", + "nullable": true, + "minLength": 1, + "maxLength": 200, + "description": "Required with `kind: \"pattern\"`. A regular expression the whole value must match. Lookarounds and backreferences aren't supported." + }, + "message": { + "type": "string", + "nullable": true, + "maxLength": 120, + "description": "Shown to the signer instead of the default error." + } + } + }, + "SignatureFieldAnchor": { + "type": "object", + "description": "Places a field by a piece of text in the document instead of a position. Didit doesn't remove the text from the PDF, so write it in the same color as the page background. When the text isn't found, the call returns `400` with `field_anchor_not_found`. See [Place fields by anchor text](/sessions-api/create-session#place-fields-by-anchor-text).", + "required": [ + "text" + ], + "properties": { + "text": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "description": "Text searched in the document. For a Word file, in the converted PDF." + }, + "occurrence": { + "type": "string", + "enum": [ + "first", + "all" + ], + "default": "first", + "description": "`all` places one field per match; the keys after the first get `_2`, `_3` and so on." + }, + "offset_x": { + "type": "number", + "description": "Added to the left edge of the text, as a fraction of the page width." + }, + "offset_y": { + "type": "number", + "description": "Added to the top edge of the text, as a fraction of the page height." + }, + "width": { + "type": "number", + "minimum": 0.02, + "maximum": 1, + "description": "Width of the field, as a fraction of the page width." + }, + "height": { + "type": "number", + "minimum": 0.012, + "maximum": 1, + "description": "Height of the field, as a fraction of the page height." + } + } + }, + "SignatureFieldResult": { + "type": "object", + "description": "A field of a signed document, with its value on this signer's copy. See [Fields](/core-technology/electronic-signature/report-electronic-signature#fields).", + "properties": { + "key": { + "type": "string", + "description": "The field's key, as used in `sign.field_values`." + }, + "type": { + "type": "string", + "enum": [ + "signature", + "initials", + "full_name", + "date_signed", + "text", + "checkbox", + "email", + "data" + ] + }, + "role": { + "type": "string", + "enum": [ + "verified_person", + "legal_representative", + "key_people" + ] + }, + "label": { + "type": "string" + }, + "page": { + "type": "integer", + "description": "Page number, from 1. For a Word document, a page of the converted PDF." + }, + "x": { + "type": "number", + "description": "Left edge, as a fraction of the page width." + }, + "y": { + "type": "number", + "description": "Top edge, as a fraction of the page height." + }, + "width": { + "type": "number", + "description": "Width, as a fraction of the page width." + }, + "height": { + "type": "number", + "description": "Height, as a fraction of the page height." + }, + "required": { + "type": "boolean" + }, + "value": { + "nullable": true, + "description": "A string for `text`, `email`, `full_name`, `date_signed` and `data`, a boolean for a `checkbox`, and `true` for an applied signature or initials. `null` when empty." + }, + "display_value": { + "type": "string", + "nullable": true, + "description": "The value as stamped on the page: dates in the field's `date_format`, `Yes` or `No` for a checkbox, `Signed` for a signature or initials." + }, + "value_source": { + "type": "string", + "nullable": true, + "enum": [ + "signer", + "identity", + "contact", + "company", + "session", + "metadata", + "api", + "signing_time", + null + ], + "description": "Where the value came from: `signer` (typed, ticked or applied by the signer), `identity` (the verified ID), `contact` (the verified email or phone), `company` (the verified company), `session` (`vendor_data`), `metadata`, `api` (`sign.field_values`) or `signing_time` (the date signed)." + }, + "filled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "image_url": { + "type": "string", + "nullable": true, + "description": "Signature and initials only: the path of the applied image. See [Download a signature image](/sessions-api/electronic-signature/download-field-image)." + }, + "value_unavailable_reason": { + "type": "string", + "nullable": true, + "enum": [ + "source_empty", + null + ], + "description": "`source_empty` when a pre-filled field had no value in the session and `signature_field_data_missing_action` let the signer sign anyway." + } + } + }, + "VaspDirectoryWidgetEntry": { + "type": "object", + "description": "What the end user's VASP picker shows: who the VASP is and whether it can be reached.", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The entry's id, for GET /v3/travel-rule/vasps/{vasp_id}/." + }, + "kind": { + "type": "string", + "enum": [ + "directory", + "didit" + ], + "description": "directory for an entry of the shared directory; didit for a member of the Didit network." + }, + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "description": "ISO 3166 country code, as the source gives it." + }, + "domains": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The VASP's web domains. Always empty for Didit network members." + }, + "reachable": { + "type": "boolean", + "description": "Whether your application has an available route to this VASP, or to one of its entities for a brand. Always true for a Didit network member." + } + } + }, + "TravelRuleDataSharingFields": { + "type": "object", + "description": "The fields Didit sends to the counterparty VASP about your own users, per entity type. The `name` and the `wallet_address` are always sent, whatever the list says. An entity type you never chose shares every field. An empty list is a choice too: it shares the name and the wallet only.", + "properties": { + "individual": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "name", + "wallet_address", + "address", + "national_id", + "customer_identification", + "date_of_birth", + "place_of_birth" + ] + }, + "description": "Fields shared about a natural person." + }, + "company": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "name", + "wallet_address", + "address", + "national_id", + "customer_identification" + ] + }, + "description": "Fields shared about a legal person." + } + } + }, + "TravelRuleMatchingRules": { + "type": "object", + "description": "How strictly each field of the counterparty's data is compared with what you hold, per entity type. A field you leave out keeps its default. `NONE` skips the comparison, `STRICT` needs exactly the same words, `CONTAINS` accepts one value whose words all appear in the other, `DEFAULT` is the standard subset match, and `FUZZY` accepts close spellings (and, for dates of birth, a swapped day and month).", + "properties": { + "individual": { + "type": "object", + "description": "Rules for a natural person.", + "properties": { + "name": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default: follows `name_matching_strictness`." + }, + "date_of_birth": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default `NONE`." + }, + "address": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default `NONE`." + }, + "account": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "The wallet the exchange was routed by. Default `STRICT`." + } + } + }, + "company": { + "type": "object", + "description": "Rules for a legal person.", + "properties": { + "name": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default: follows `name_matching_strictness`." + }, + "address": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default `NONE`." + }, + "account": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "The wallet the exchange was routed by. Default `STRICT`." + } + } + } + } + }, + "TravelRuleDataMatch": { + "type": "object", + "nullable": true, + "description": "Field-by-field comparison of the transfer's party data with what the other side knows about the wallet holder. It names fields and results, never values, so it stays readable while the counterparty's data is masked. Null until a comparison ran.", + "properties": { + "matched": { + "type": "boolean", + "description": "True when no compared field differs." + }, + "mismatched_fields": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "name", + "date_of_birth", + "address", + "account" + ] + }, + "description": "The fields that differ." + }, + "fields": { + "type": "object", + "description": "One entry per field compared for the party's entity type (`name`, `date_of_birth`, `address`, `account`; a company has no `date_of_birth`).", + "additionalProperties": { + "type": "object", + "properties": { + "result": { + "type": "string", + "enum": [ + "matched", + "mismatched", + "not_compared" + ], + "description": "`not_compared` when the rule is `NONE` or either side has no value for the field." + }, + "strictness": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ] + } + } + } + } + } + }, + "TravelRuleDataUnmasked": { + "type": "object", + "nullable": true, + "description": "Audit record of the moment the counterparty's data was unmasked. Null while the data is still masked, and for outbound transfers.", + "properties": { + "at": { + "type": "string", + "format": "date-time" + }, + "reason": { + "type": "string", + "enum": [ + "ownership_confirmed", + "wallet_proof", + "address_book_match", + "own_verified_wallet", + "inquiry_approved", + "known_payment_method", + "travel_address" + ], + "description": "What confirmed ownership: ownership_confirmed (your confirmation through the console or the API), wallet_proof (a wallet proof in the widget, or an INTERNAL inbound transfer, set when it is created), address_book_match (a verified address-book wallet), own_verified_wallet (your user's own verified wallet), inquiry_approved (your approval of an inbound TRP inquiry or a GTR or Sygna message), known_payment_method (a wallet your user already paid with) or travel_address (the per-wallet travel address named the wallet)." + }, + "actor": { + "type": "string", + "description": "Who confirmed it: the console user's email or the API key's label. Blank for an automatic decision." + } + } + }, + "TravelRuleMessageAttachment": { + "type": "object", + "nullable": true, + "description": "The document attached to a message, stored like any other transaction document.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string", + "nullable": true + }, + "note": { + "type": "string", + "nullable": true + }, + "uploaded_by": { + "type": "string", + "nullable": true, + "description": "Who attached it. In the pickup portal, a file you sent shows your VASP name, never the person or API key behind it." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "files": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "file_url": { + "type": "string", + "nullable": true, + "description": "Time-limited download URL." + }, + "original_filename": { + "type": "string" + }, + "file_size": { + "type": "integer" + } + } + } + } + } + }, + "TravelRuleMessage": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "kind": { + "type": "string", + "enum": [ + "MESSAGE", + "DOCUMENT_REQUEST" + ] + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "`OUTBOUND` is what the reader's own side sent. In the pickup portal the direction is from the counterparty's side." + }, + "author": { + "type": "string", + "description": "Who wrote it on its own side: a console user's email or an API key label, or the other VASP's name." + }, + "body": { + "type": "string" + }, + "requested_documents": { + "type": "array", + "items": { + "type": "string" + }, + "description": "What a DOCUMENT_REQUEST asks for, in the requester's words." + }, + "reply_to": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "fulfilled_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set on a DOCUMENT_REQUEST once a reply to it carries a document." + }, + "attachment": { + "$ref": "#/components/schemas/TravelRuleMessageAttachment" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleMessageCreateRequest": { + "type": "object", + "description": "Send as JSON, or as multipart/form-data with the attachments in `files`.", + "properties": { + "kind": { + "type": "string", + "enum": [ + "MESSAGE", + "DOCUMENT_REQUEST" + ], + "default": "MESSAGE" + }, + "body": { + "type": "string", + "maxLength": 5000, + "description": "Required for a MESSAGE unless a file is attached." + }, + "requested_documents": { + "type": "array", + "items": { + "type": "string", + "maxLength": 255 + }, + "maxItems": 10, + "description": "Required (at least one) for a DOCUMENT_REQUEST, and only allowed there." + }, + "reply_to": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "A message of the same transfer. A reply that carries a document fulfils the document request it answers." + }, + "files": { + "type": "array", + "items": { + "type": "string", + "format": "binary" + }, + "maxItems": 5, + "description": "Multipart only. Up to 5 files of 10 MB each." + } + } + }, + "SessionDeletionJob": { + "type": "object", + "description": "A bulk session deletion running in the background: progress and counts per outcome, never which sessions it deleted.", + "properties": { + "job_id": { + "type": "string", + "format": "uuid", + "description": "Poll `GET /v3/sessions/delete/{job_id}/` with this id." + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ], + "description": "`PENDING`: accepted, not started yet. `PROCESSING`: deleting; `processed` and `outcomes` are updating. `COMPLETED`: every selected session is accounted for. `FAILED`: the job stopped early; see `error`." + }, + "total": { + "type": "integer", + "description": "Sessions selected when the deletion was requested." + }, + "processed": { + "type": "integer", + "description": "Sessions the job has finished with so far." + }, + "outcomes": { + "type": "object", + "description": "How many sessions ended in each outcome. Every key is always present.", + "properties": { + "deleted": { + "type": "integer", + "description": "Sessions deleted together with their face embedding." + }, + "none": { + "type": "integer", + "description": "Sessions deleted that had no face embedding." + }, + "retained_with_user": { + "type": "integer", + "description": "Sessions deleted while one image-free biometric template was retained for their User, as the request or the application policy asked." + }, + "ineligible_no_vendor_user": { + "type": "integer", + "description": "Sessions deleted, with their face embedding, because retention was requested but they had no linked User." + }, + "failed_retryable": { + "type": "integer", + "description": "Sessions reported as not deleted. A job retries a group it could not delete by itself instead of counting it here." + }, + "already_deleted": { + "type": "integer", + "description": "Selected sessions that were already deleted, for example by another request, when the job reached them." + } + } + }, + "deleted_child_verifications": { + "type": "integer", + "description": "Verifications that the deleted business verifications created, deleted with them. Always `0` for a job started by `POST /v3/sessions/delete/`, which deletes KYC sessions only." + }, + "error": { + "type": "string", + "nullable": true, + "description": "`null` unless `status` is `FAILED`; then why the deletion stopped. The sessions the job had not reached are still in place: repeat the request to delete them." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the deletion was requested." + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the job finished; `null` while it runs." + } + } + }, + "EntityStatusUpdateRequest": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "New lifecycle status. `BLOCKED` also adds the `vendor_data` to the system blocklist." + } + } + }, + "AMLMonitoringToggleResult": { + "oneOf": [ + { + "type": "object", + "required": [ + "users_total", + "already_enabled", + "with_existing_aml", + "needing_screening", + "skipped_no_identity_data", + "skipped_blocked", + "estimated_screening_cost_usd", + "estimated_annual_monitoring_cost_usd", + "balance_sufficient", + "unmatched" + ], + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + }, + "unmatched": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Identifiers of the request that matched no profile in this application, exactly as sent." + } + } + }, + { + "type": "object", + "required": [ + "job_id", + "status", + "is_enabled", + "target_type", + "total", + "processed", + "outcomes", + "results", + "error", + "created_at", + "completed_at", + "estimate", + "unmatched" + ], + "properties": { + "job_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ] + }, + "is_enabled": { + "type": "boolean", + "description": "true when the job enables monitoring, false when it disables it." + }, + "target_type": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Which profile family the job toggles." + }, + "total": { + "type": "integer", + "description": "Profiles the job processes." + }, + "processed": { + "type": "integer", + "description": "Profiles processed so far." + }, + "outcomes": { + "type": "object", + "description": "Per-record outcome counters. Every selected profile resolves to exactly one counter; nothing is silently skipped.", + "properties": { + "enabled": { + "type": "integer", + "description": "Monitoring enabled on an existing AML screening." + }, + "screened_and_enabled": { + "type": "integer", + "description": "A new AML screening was created (billed) and monitoring enabled on it." + }, + "already_enabled": { + "type": "integer", + "description": "Monitoring was already active; nothing changed and nothing was billed." + }, + "disabled": { + "type": "integer", + "description": "Monitoring turned off across every AML screening of the profile." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Skipped: the profile lacks the full name, date of birth and country (users) or a name (businesses) needed to screen." + }, + "skipped_blocked": { + "type": "integer", + "description": "Skipped: the user is blocked." + }, + "skipped_sandbox": { + "type": "integer", + "description": "Skipped: sandbox application, no screening is created." + }, + "failed_provider": { + "type": "integer", + "description": "The screening could not be completed; retry later." + }, + "failed_insufficient_balance": { + "type": "integer", + "description": "Balance ran out while the job was running." + } + } + }, + "results": { + "type": "array", + "description": "One entry per processed profile, in processing order. Grows while the job runs; complete once status is COMPLETED. Recorded for jobs started through the API.", + "items": { + "type": "object", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit internal id of the profile." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the profile." + }, + "outcome": { + "type": "string", + "enum": [ + "enabled", + "screened_and_enabled", + "already_enabled", + "disabled", + "skipped_no_identity_data", + "skipped_blocked", + "skipped_sandbox", + "failed_provider", + "failed_insufficient_balance" + ] + } + } + } + }, + "error": { + "type": "string", + "nullable": true, + "description": "Generic failure message when status is FAILED; the detailed cause is kept on the platform side." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + } + } + }, + "unmatched": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Identifiers of the request that matched no profile in this application, exactly as sent. They are not part of the job." + } + } + } + ] + }, + "AMLMonitoringJob": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ] + }, + "is_enabled": { + "type": "boolean", + "description": "true when the job enables monitoring, false when it disables it." + }, + "target_type": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Which profile family the job toggles." + }, + "total": { + "type": "integer", + "description": "Profiles the job processes." + }, + "processed": { + "type": "integer", + "description": "Profiles processed so far." + }, + "outcomes": { + "type": "object", + "description": "Per-record outcome counters. Every selected profile resolves to exactly one counter; nothing is silently skipped.", + "properties": { + "enabled": { + "type": "integer", + "description": "Monitoring enabled on an existing AML screening." + }, + "screened_and_enabled": { + "type": "integer", + "description": "A new AML screening was created (billed) and monitoring enabled on it." + }, + "already_enabled": { + "type": "integer", + "description": "Monitoring was already active; nothing changed and nothing was billed." + }, + "disabled": { + "type": "integer", + "description": "Monitoring turned off across every AML screening of the profile." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Skipped: the profile lacks the full name, date of birth and country (users) or a name (businesses) needed to screen." + }, + "skipped_blocked": { + "type": "integer", + "description": "Skipped: the user is blocked." + }, + "skipped_sandbox": { + "type": "integer", + "description": "Skipped: sandbox application, no screening is created." + }, + "failed_provider": { + "type": "integer", + "description": "The screening could not be completed; retry later." + }, + "failed_insufficient_balance": { + "type": "integer", + "description": "Balance ran out while the job was running." + } + } + }, + "results": { + "type": "array", + "description": "One entry per processed profile, in processing order. Grows while the job runs; complete once status is COMPLETED. Recorded for jobs started through the API.", + "items": { + "type": "object", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit internal id of the profile." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the profile." + }, + "outcome": { + "type": "string", + "enum": [ + "enabled", + "screened_and_enabled", + "already_enabled", + "disabled", + "skipped_no_identity_data", + "skipped_blocked", + "skipped_sandbox", + "failed_provider", + "failed_insufficient_balance" + ] + } + } + } + }, + "error": { + "type": "string", + "nullable": true, + "description": "Generic failure message when status is FAILED; the detailed cause is kept on the platform side." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + } + } + }, + "unmatched": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Identifiers of the request that matched no profile in this application, exactly as sent. They are not part of the job." + } + } + }, + "OngoingMonitoringToggleRequest": { + "type": "object", + "required": [ + "is_enabled" + ], + "properties": { + "is_enabled": { + "type": "boolean", + "description": "true to enable monitoring, false to disable it." + }, + "estimate_only": { + "type": "boolean", + "default": false, + "description": "When true, returns the cost estimate and performs no writes." + }, + "update_all": { + "type": "boolean", + "default": false, + "description": "Apply to every entity of the application (scoped by the filter_* params when present)." + }, + "filter_search": { + "type": "string" + }, + "filter_status": { + "type": "array", + "items": { + "type": "string" + } + }, + "filter_country": { + "type": "string" + }, + "filter_date_from": { + "type": "string", + "format": "date" + }, + "filter_date_to": { + "type": "string", + "format": "date" + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Entities to toggle. Required unless update_all is true." + } + } + }, + "OngoingMonitoringJob": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ] + }, + "is_enabled": { + "type": "boolean" + }, + "total": { + "type": "integer" + }, + "processed": { + "type": "integer" + }, + "outcomes": { + "type": "object", + "description": "Per-entity outcome counters. Nothing is silently skipped: every requested entity resolves to exactly one counter.", + "properties": { + "enabled": { + "type": "integer", + "description": "Monitoring enabled on an existing AML screening." + }, + "screened_and_enabled": { + "type": "integer", + "description": "A new AML screening was created (billed) and monitoring enabled." + }, + "already_enabled": { + "type": "integer", + "description": "Monitoring was already active (legacy duplicate rows are consolidated)." + }, + "disabled": { + "type": "integer", + "description": "Monitoring disabled on every AML row of the entity." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Skipped: no full name + date of birth + country available to screen." + }, + "skipped_blocked": { + "type": "integer", + "description": "Skipped: the user is blocked." + }, + "skipped_sandbox": { + "type": "integer", + "description": "Skipped: sandbox application." + }, + "failed_provider": { + "type": "integer", + "description": "The screening provider call failed." + }, + "failed_insufficient_balance": { + "type": "integer", + "description": "Balance ran out mid-job." + } + } + }, + "error": { + "type": "string", + "nullable": true + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (no writes are performed).", + "properties": { + "users_total": { + "type": "integer", + "description": "Entities in the selection." + }, + "already_enabled": { + "type": "integer" + }, + "with_existing_aml": { + "type": "integer", + "description": "Entities whose existing AML screening will be switched to monitored." + }, + "needing_screening": { + "type": "integer", + "description": "Entities without any AML screening that will be screened first (billed)." + }, + "skipped_no_identity_data": { + "type": "integer" + }, + "skipped_blocked": { + "type": "integer" + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2 + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07 + }, + "balance_sufficient": { + "type": "boolean" + } + } + } + } + }, + "OngoingMonitoringToggleResult": { + "oneOf": [ + { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (no writes are performed).", + "properties": { + "users_total": { + "type": "integer", + "description": "Entities in the selection." + }, + "already_enabled": { + "type": "integer" + }, + "with_existing_aml": { + "type": "integer", + "description": "Entities whose existing AML screening will be switched to monitored." + }, + "needing_screening": { + "type": "integer", + "description": "Entities without any AML screening that will be screened first (billed)." + }, + "skipped_no_identity_data": { + "type": "integer" + }, + "skipped_blocked": { + "type": "integer" + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2 + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07 + }, + "balance_sufficient": { + "type": "boolean" + } + } + }, + { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + ] + }, + "CaseBlueprintPresetInstallResult": { + "type": "object", + "properties": { + "created": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "CaseBulkUpdateResult": { + "type": "object", + "properties": { + "updated": { + "type": "integer" + }, + "failures": { + "type": "array", + "items": { + "type": "object", + "properties": { + "case_uuid": { + "type": "string", + "format": "uuid" + }, + "detail": { + "type": "string" + } + } + } + } + } + }, + "RegulatoryReportList": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows, capped at 100 for performance." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "EntityNetworkMembershipList": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EntityNetworkMembership" + } + } + } + }, + "SessionDecision": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The unique identifier of the session." + }, + "session_kind": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Discriminator indicating whether this is a User Verification (KYC) or Business Verification (KYB) session. When `user`, the KYC feature arrays (`id_verifications`, `nfc_verifications`, `liveness_checks`, `face_matches`, `poa_verifications`, `document_ai_documents`, `database_validations`, ...) apply and the business arrays are absent. When `business`, the payload instead carries `registry_checks`, `document_verifications`, `key_people_checks` plus the shared arrays (`aml_screenings`, `phone_verifications`, `email_verifications`, `ip_analyses`, `questionnaire_responses`). **Omitted when `include=events` is passed** — the events shape only exists for user sessions, so the discriminator is dropped from that variant." + }, + "shared_from_session": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "For a session cloned with `POST /v3/session/import-shared/`: the `session_id` of the original session in the source application. `null` for every other session. Omitted when `include=events` is passed." + }, + "shared_from_session_number": { + "type": "integer", + "nullable": true, + "description": "`session_number` of the source session when this session was imported from another application of your organization. `null` otherwise." + }, + "session_number": { + "type": "integer", + "description": "The number of the session.", + "nullable": true + }, + "session_url": { + "type": "string", + "format": "uri", + "description": "The URL of the session.", + "nullable": true + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Awaiting User", + "In Review", + "Approved", + "Declined", + "Resubmitted", + "Expired", + "Kyc Expired", + "Abandoned" + ], + "description": "Overall lifecycle status of the session — the rolled-up workflow result. Terminal values are `Approved`, `Declined`, `In Review`, `Expired`, `Kyc Expired`, and `Abandoned`; `Not Started`, `In Progress`, `Awaiting User`, and `Resubmitted` indicate the session is still moving through the workflow." + }, + "status_override": { + "type": "object", + "nullable": true, + "description": "Present when a reviewer set `status` by hand. `null` when the status is the automatic one. Use it to tell a manual approval from an automatic one.", + "properties": { + "overridden_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "automatic_status": { + "type": "string", + "nullable": true, + "description": "The status the platform's own checks conclude." + } + } + }, + "decision_trace": { + "type": "object", + "description": "How the status was reached: who set it and which warnings did.", + "properties": { + "status": { + "type": "string", + "description": "The session status." + }, + "decided_by": { + "type": "string", + "nullable": true, + "enum": [ + "manual", + "automatic", + "investigator", + null + ], + "description": "Who set the status. `null` while the session is not decided." + }, + "decided_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "automatic_status": { + "type": "string", + "nullable": true, + "description": "What the platform's own checks conclude, which differs from `status` after a manual decision." + }, + "terminal_node_id": { + "type": "string", + "nullable": true, + "description": "The workflow status node that ended the session, when one did." + }, + "actor": { + "type": "object", + "nullable": true, + "description": "The reviewer behind a manual decision.", + "properties": { + "user": { + "type": "string", + "nullable": true + }, + "actor_type": { + "type": "string", + "nullable": true + } + } + }, + "warning_counts": { + "type": "object", + "description": "Warnings per `log_type` (`error`, `warning`, `information`) and `total`.", + "additionalProperties": { + "type": "integer" + } + }, + "status_warnings": { + "type": "array", + "description": "The warnings that set a `Declined` or `In Review` status.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string" + }, + "feature": { + "type": "string", + "nullable": true + }, + "node_id": { + "type": "string", + "nullable": true + }, + "log_type": { + "type": "string", + "nullable": true + }, + "check_status": { + "type": "string", + "nullable": true + }, + "source": { + "type": "string", + "description": "The payload list the warning came from, for example `id_verifications`." + }, + "short_description": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "linked_records": { + "type": "object", + "nullable": true, + "description": "A summary of other records linked to the session. `null` when there is nothing to show, and for workflows that declare an age assurance standard.", + "properties": { + "same_face": { + "type": "object", + "nullable": true, + "description": "Other people whose face matched one of this session's face searches.", + "properties": { + "count": { + "type": "integer" + }, + "best_match": { + "type": "object", + "nullable": true, + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "session_number": { + "type": "integer", + "nullable": true + }, + "vendor_user_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "similarity_percentage": { + "type": "number", + "nullable": true + } + } + } + } + }, + "earlier_verifications": { + "type": "object", + "nullable": true, + "properties": { + "count": { + "type": "integer" + }, + "vendor_user_id": { + "type": "string", + "format": "uuid", + "nullable": true + } + } + }, + "networks": { + "type": "object", + "nullable": true, + "properties": { + "count": { + "type": "integer" + }, + "open": { + "type": "integer" + }, + "dismissed": { + "type": "integer" + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "network_id": { + "type": "string", + "format": "uuid" + }, + "display_number": { + "type": "integer", + "nullable": true + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "risk_score": { + "type": "number", + "nullable": true + } + } + } + } + } + } + } + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier of the workflow group that produced this session. Workflows are versioned — this value identifies the workflow itself, not the specific version.", + "nullable": true + }, + "features": { + "type": "array", + "items": { + "type": "string", + "description": "The feature of the session." + }, + "description": "List of verification features configured for this session, as display names: `ID_VERIFICATION`, `NFC`, `LIVENESS`, `FACE_MATCH`, `POA`, `PHONE`, `EMAIL_VERIFICATION`, `AML`, `IP_ANALYSIS`, `DATABASE_VALIDATION`, `QUESTIONNAIRE`, `AGE_ESTIMATION`, `FACE_SEARCH`, `GEOLOCATION` (Location verification) (Business Verification sessions use the KYB feature set: `KYB_REGISTRY`, `KYB_DOCUMENTS`, `KYB_KEY_PEOPLE`, `AML`, `PHONE`, `EMAIL_VERIFICATION`, `IP_ANALYSIS`, `QUESTIONNAIRE`). With `include=events` each entry becomes an object `{feature, node_id}` instead of a plain string, and the email feature is renamed: it appears as `EMAIL` in events mode instead of the `EMAIL_VERIFICATION` used here." + }, + "vendor_data": { + "type": "string", + "description": "The vendor data of the session.", + "nullable": true + }, + "metadata": { + "type": "object", + "description": "The metadata of the session.", + "nullable": true + }, + "expected_details": { + "type": "object", + "description": "Expected details supplied at session creation — used for cross-validation against extracted data (KYC) or to pre-fill the company registry search (KYB). Echoes back exactly the keys that were provided; `null` when none were supplied. KYC sessions carry the person fields; Business (KYB) sessions carry `company_name`, `registry_country` and `registration_number`.", + "properties": { + "first_name": { + "type": "string", + "description": "User's first name. For example, `John`." + }, + "last_name": { + "type": "string", + "description": "User's last name. For example, `Doe`." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "User's date of birth with format: YYYY-MM-DD. For example, `1990-05-15`." + }, + "gender": { + "type": "string", + "nullable": true, + "enum": [ + "M", + "F" + ], + "default": null, + "description": "User's gender. Must be either 'M', 'F', or null." + }, + "nationality": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the applicant's country of origin. For example, `USA`." + }, + "country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the country of the Proof of Address document, or the country of the applicant's ID document, which may differ from nationality. For example, `GBR`." + }, + "address": { + "type": "string", + "description": "The address in a human readable format, including as much information as possible. For example, `123 Main St, San Francisco, CA 94105, USA`." + }, + "identification_number": { + "type": "string", + "description": "The user's document number, personal number, or tax number. For example, `123456789`." + }, + "ip_address": { + "type": "string", + "format": "ipv4", + "description": "Expected IP address for the session. If the actual IP address differs from this value, a warning will be logged. For example, `192.168.1.100`." + }, + "expected_document_types": { + "type": "array", + "description": "Document types the ID verification step was restricted to at session creation. Empty/unset means no restriction. Codes: `P` (passport), `ID` (national ID), `DL` (driver's license), `RP` (residence permit), `HIC` (health insurance card), `TC` (tax card), `SSC` (social security card).", + "items": { + "type": "string", + "enum": [ + "P", + "ID", + "DL", + "RP", + "HIC", + "TC", + "SSC" + ] + } + }, + "id_country": { + "type": "string", + "nullable": true, + "description": "Expected ID-document issuing country (ISO 3166-1 alpha-3), when restricted at creation time.", + "example": "ESP" + }, + "poa_country": { + "type": "string", + "nullable": true, + "description": "Expected Proof of Address document country (ISO 3166-1 alpha-3), when restricted at creation time.", + "example": "ESP" + }, + "company_name": { + "type": "string", + "description": "Expected legal name of the business. Business (KYB) sessions only." + }, + "registry_country": { + "type": "string", + "description": "Expected registry country as `XX` or `XX-YY` (ISO 3166-1 alpha-2, optionally with an ISO 3166-2 subdivision for state-level registries, e.g. `US-CA`). Business (KYB) sessions only." + }, + "registration_number": { + "type": "string", + "description": "Expected company registration number. Business (KYB) sessions only." + } + }, + "nullable": true + }, + "contact_details": { + "type": "object", + "description": "Contact details supplied when the session was created (used for notification emails, prefilled phone/email verification, and communication language). `null` when neither an email nor a phone number was provided.", + "properties": { + "email": { + "type": "string", + "description": "Email address of the user (e.g., \"john.doe@example.com\") that will be used during the Email Verification step. If not provided, the user must provide it during the verification flow." + }, + "email_lang": { + "type": "string", + "description": "Language code (ISO 639-1) for email notifications. Controls the language of all email communications (e.g., \"en\", \"es\", \"fr\")." + }, + "send_notification_emails": { + "type": "boolean", + "default": false, + "description": "If true and an email is provided, Didit sends the initial \"Verify your identity\" email asynchronously when the User Verification (KYC) or Business Verification (KYB) session is created. Didit also sends verification status notifications for sessions requiring manual review to the provided email address." + }, + "phone": { + "type": "string", + "nullable": true, + "description": "Phone number of the user in E.164 format if available (e.g., +14155552671). `null` when only an email was supplied at session creation. Note: This field returns the original phone number provided during session creation. If the number was invalid, it may not be in E.164 format." + } + }, + "nullable": true + }, + "callback": { + "type": "string", + "format": "uri", + "description": "The callback URL of the session.", + "nullable": true + }, + "id_verifications": { + "type": "array", + "nullable": true, + "description": "**Array of ID verification (OCR) reports** produced by ID-document steps in the workflow graph. Always a JSON array — read `id_verifications[0]` and iterate if your workflow can run more than one ID check (for example a primary check plus a step-up). `null` until at least one ID step has produced data. Each item is the full report, including `node_id`, feature-level `status`, extracted document fields, image URLs, quality scores, MRZ, address parsing, cross-session matches, and `warnings[]`.", + "items": { + "type": "object", + "description": "One ID verification report - the full report for a single ID Verification step. `verification_method` says which of the three methods produced it (document capture, non-doc lookup or wallet sign-in) and the method-specific evidence block is populated accordingly.", + "properties": { + "node_id": { + "type": "string", + "description": "Workflow graph node id of the OCR step that produced this report." + }, + "verification_method": { + "type": "string", + "enum": [ + "document", + "id_lookup", + "wallet" + ], + "description": "Which ID Verification method produced this report. `document` - the user photographed a physical ID (the default, and the value on every session created before the other methods existed). `id_lookup` - the user typed their national ID number and it was checked against a government register or another authoritative source for that country (`id_lookup.source` names which). `wallet` - the user signed in with a government or bank digital identity." + }, + "assurance": { + "type": "string", + "enum": [ + "documentary", + "data_match", + "cryptographic" + ], + "description": "Assurance tier derived from `verification_method`: `documentary` for a document that was captured and checked, `data_match` for details that matched a record held by the source queried, `cryptographic` for attributes an identity provider authenticated and signed." + }, + "wallet_provider": { + "type": "string", + "nullable": true, + "description": "Catalog wallet id when `verification_method` is `wallet` (`mitid`, `bankid_se`, `itsme`, ...). `null` otherwise." + }, + "fallback_from": { + "type": "object", + "nullable": true, + "description": "Why a non-document method did not end the ID step. Set when a lookup or a wallet sign-in failed: on the document verification the user continued with when the country's fallback switch was `fallback_to_document` (`action` is `fallback_to_document`), or on the declined report when it was `decline`. `null` otherwise.", + "properties": { + "method": { + "type": "string", + "enum": [ + "id_lookup", + "wallet" + ], + "description": "The method that failed." + }, + "reason": { + "type": "string", + "description": "The outcome that ended it, e.g. `no_match`, `provider_error`, `cancelled`, `timeout`." + }, + "action": { + "type": "string", + "enum": [ + "fallback_to_document", + "decline" + ], + "description": "The configured action that was taken: `fallback_to_document` or `decline`." + } + } + }, + "id_lookup": { + "type": "object", + "nullable": true, + "description": "Registry evidence. Present only when `verification_method` is `id_lookup`; `null` otherwise.", + "properties": { + "source": { + "type": "string", + "description": "Customer-facing name of the source queried, e.g. `Department of Home Affairs` (a government register) or `UK credit bureau and financial services records` (not a government register). This is the only statement of provenance for the lookup; do not assume a government source." + }, + "checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the register answered." + }, + "attempts": { + "type": "integer", + "description": "1-based attempt number this report came from." + }, + "max_attempts": { + "type": "integer", + "description": "Attempt budget configured for the country (1 to 5, default 1)." + }, + "outcome": { + "type": "string", + "enum": [ + "match", + "partial_match", + "no_match", + "provider_error", + "format_rejected" + ], + "nullable": true, + "description": "What the register answered. Only `match`, `partial_match` and `no_match` count against the attempt budget and bill." + }, + "comparison": { + "type": "array", + "description": "One row per comparable field the source answered on, plus one row per source error. Comparison rows use the normalised field names below rather than the country's request field keys, because several request fields (first and last name, or any of the identifier aliases) collapse into a single comparison. A source that failed appends a row with `field: \"provider\"` and `result: \"error\"`, so read `result` before treating a row as a field verdict.", + "items": { + "type": "object", + "required": [ + "field", + "label", + "provided", + "record", + "result" + ], + "properties": { + "field": { + "type": "string", + "enum": [ + "identification_number", + "full_name", + "date_of_birth", + "address", + "provider" + ], + "description": "Normalised comparison field. `provider` is not a user field: it marks a source error row." + }, + "label": { + "type": "string", + "description": "Display label for the row, e.g. `ID number`, `Full name`, `Provider`." + }, + "provided": { + "type": "string", + "nullable": true, + "description": "What the user typed. `null` on a `provider` error row." + }, + "record": { + "type": "string", + "nullable": true, + "description": "What the source held. On a `provider` error row this carries the error message instead." + }, + "result": { + "type": "string", + "enum": [ + "match", + "partial", + "no_match", + "error" + ], + "description": "Per-field comparison result. `error` appears only on a `provider` row and means that source did not answer; treat it as evidence about the query, never as a field mismatch." + } + } + } + }, + "registry_portrait": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived presigned URL to the portrait the source returned. Only the four government registers that return one (South Africa, Nigeria, Argentina, Panama)." + }, + "face_match_score": { + "type": "number", + "nullable": true, + "description": "Selfie against the registry portrait, 0-100. Only where the register returns a portrait." + } + } + }, + "wallet_verification": { + "type": "object", + "nullable": true, + "description": "Credential evidence. Present only when `verification_method` is `wallet`; `null` otherwise.", + "properties": { + "provider": { + "type": "string", + "description": "Catalog wallet id, e.g. `mitid`." + }, + "provider_name": { + "type": "string", + "nullable": true, + "description": "Wallet name as the end user knows it, e.g. `MitID`." + }, + "issuing_authority": { + "type": "string", + "nullable": true, + "description": "The authority behind the wallet, e.g. `Danish Agency for Digital Government`." + }, + "issuing_country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country the wallet was offered in." + }, + "credential_type": { + "type": "string", + "nullable": true, + "description": "Credential the wallet presented, e.g. `person_identification`." + }, + "level_of_assurance": { + "type": "string", + "enum": [ + "low", + "substantial", + "high" + ], + "nullable": true, + "description": "The level of assurance the wallet asserted. A sign-in that asserts less than the level requested fails instead of downgrading." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "The wallet's authentication instant." + }, + "signature_valid": { + "type": "boolean", + "nullable": true, + "description": "Whether the signed assertion the wallet returned validated." + }, + "attributes": { + "type": "object", + "additionalProperties": true, + "description": "The attributes the wallet shared, keyed as the wallet's catalog entry lists them." + }, + "portrait": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived presigned URL to the portrait the wallet shared, where it shares one." + }, + "face_match_score": { + "type": "number", + "nullable": true, + "description": "Selfie against the wallet portrait, 0-100, where the wallet shares one." + } + } + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Expired" + ], + "description": "Feature-level status of this ID verification step. Independent of the top-level session `status` — a session can reach its overall decision while an individual feature stays `In Review`." + }, + "document_type": { + "type": "string", + "description": "Type of identity document" + }, + "document_subtype": { + "type": "string", + "nullable": true, + "description": "Accepted document subtype code from the workflow document settings, including the document region when applicable. Examples: `ID_CARD_GENERIC`, `QUEENSLAND_DRIVER_LICENSE_GENERIC`." + }, + "document_number": { + "type": "string", + "description": "Document identification number" + }, + "personal_number": { + "type": "string", + "description": "Personal identification number" + }, + "portrait_image": { + "type": "string", + "format": "uri", + "description": "URL of the portrait image" + }, + "front_image": { + "type": "string", + "format": "uri", + "description": "URL of the front document image" + }, + "front_video": { + "type": "string", + "format": "uri", + "description": "URL of the front document video" + }, + "back_image": { + "type": "string", + "format": "uri", + "description": "URL of the back document image" + }, + "back_video": { + "type": "string", + "format": "uri", + "description": "URL of the back document video" + }, + "full_front_image": { + "type": "string", + "format": "uri", + "description": "URL of the full front document image" + }, + "full_back_image": { + "type": "string", + "format": "uri", + "description": "URL of the full back document image" + }, + "front_image_camera_front": { + "type": "string", + "format": "uri", + "description": "URL of the front document image captured from the front-facing camera" + }, + "back_image_camera_front": { + "type": "string", + "format": "uri", + "description": "URL of the back document image captured from the front-facing camera" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth from document" + }, + "age": { + "type": "integer", + "description": "Calculated age from date of birth" + }, + "expiration_date": { + "type": "string", + "format": "date", + "description": "Document expiration date" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "description": "Document issue date" + }, + "issuing_state": { + "type": "string", + "description": "ISO code of issuing state" + }, + "issuing_state_name": { + "type": "string", + "description": "Full name of issuing state" + }, + "first_name": { + "type": "string", + "description": "First name from document" + }, + "last_name": { + "type": "string", + "description": "Last name from document" + }, + "full_name": { + "type": "string", + "description": "Full name from document" + }, + "gender": { + "type": "string", + "description": "Gender from document" + }, + "address": { + "type": "string", + "description": "Raw address from document" + }, + "formatted_address": { + "type": "string", + "description": "Formatted full address" + }, + "place_of_birth": { + "type": "string", + "description": "Place of birth from document" + }, + "marital_status": { + "type": "string", + "description": "Marital status from document" + }, + "nationality": { + "type": "string", + "description": "ISO nationality code" + }, + "extra_fields": { + "type": "object", + "description": "Additional document fields. Includes document-specific attributes (e.g. `dl_categories`, `blood_group`, `sponsor`, `employer`, `dl_class_code_b_from`, `dl_class_code_b_to`). Driver license class validity dates are returned as `dl_class_code__from` and `dl_class_code__to`, and per-class restriction/information codes (e.g. column 12 of UK licences) as `dl_class_code__notes`. When the document carries names, address, or place of birth in multiple scripts, this object also includes alternate-script versions of those fields. The workflow setting `preferred_characters` (`latin` or `non_latin`) controls which script populates the top-level fields (`first_name`, `last_name`, `full_name`, `address`, `place_of_birth`); the opposite script lands here as `first_name_non_latin` / `last_name_non_latin` / `full_name_non_latin` / `middle_name_non_latin` / `address_non_latin` / `place_of_birth_non_latin` (when `preferred_characters` is `latin`) or with a `_latin` suffix (when `preferred_characters` is `non_latin`). Keys appear only when the OCR actually extracts a real alternate-script value — a field whose alternate value is in the same script as the preferred set (for example, a Cyrillic-labelled field whose OCR value came back in Latin homoglyphs) is skipped. When `preferred_characters` is `latin` but the document only carries non-Latin text, Didit transliterates the extracted values and exposes `first_name_latin` / `last_name_latin` / etc. as a fallback.", + "additionalProperties": true, + "properties": { + "dl_categories": { + "type": "array", + "items": { + "type": "string" + } + }, + "blood_group": { + "type": "string", + "nullable": true + }, + "first_name_non_latin": { + "type": "string", + "description": "First name in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document (e.g. `Айна` for a Kyrgyz ID)." + }, + "last_name_non_latin": { + "type": "string", + "description": "Surname in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document." + }, + "middle_name_non_latin": { + "type": "string", + "description": "Middle name / patronymic in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document." + }, + "full_name_non_latin": { + "type": "string", + "description": "Composed full name in the non-Latin script. Reconstructed from the individual components when both first and last names are available." + }, + "address_non_latin": { + "type": "string", + "description": "Address in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document." + }, + "place_of_birth_non_latin": { + "type": "string", + "description": "Place of birth in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document." + }, + "first_name_latin": { + "type": "string", + "description": "First name in Latin script. Populated when `preferred_characters=non_latin` and a Latin version is present on the document, or — as a transliteration fallback — when `preferred_characters=latin` but the document only carries non-Latin text." + }, + "last_name_latin": { + "type": "string", + "description": "Surname in Latin script. Same rules as `first_name_latin`." + }, + "middle_name_latin": { + "type": "string", + "description": "Middle name / patronymic in Latin script. Same rules as `first_name_latin`." + }, + "full_name_latin": { + "type": "string", + "description": "Composed full name in Latin script. Always reconstructed from the individual components when both first and last names are available." + }, + "address_latin": { + "type": "string", + "description": "Address in Latin script. Same rules as `first_name_latin`." + }, + "place_of_birth_latin": { + "type": "string", + "description": "Place of birth in Latin script. Same rules as `first_name_latin`." + } + } + }, + "mrz": { + "type": "object", + "nullable": true, + "description": "Parsed Machine-Readable Zone (MRZ) fields extracted from the document. `null` if no MRZ was detected.", + "properties": { + "surname": { + "type": "string", + "example": "MARTINEZ_families` / `very_high_similarity_plus__families`. Present only when `match_source` is `recovered_high`." + } + } + } + }, + "browser_version": { + "type": "string", + "nullable": true + }, + "os_version": { + "type": "string", + "nullable": true + }, + "user_agent": { + "type": "string", + "nullable": true, + "description": "The browser's user agent string." + }, + "accept_language": { + "type": "string", + "nullable": true, + "description": "The `Accept-Language` header the browser sent." + }, + "session_identifier": { + "type": "string", + "nullable": true, + "description": "Identifier of the browsing session the verification ran in." + }, + "session_age_ms": { + "type": "integer", + "nullable": true, + "description": "Age of that browsing session in milliseconds when the step ran." + }, + "coords_accuracy": { + "type": "number", + "nullable": true, + "description": "Accuracy radius, in metres, of the coordinates the device reported." + }, + "raw_device_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Raw device signals collected for the step." + } + } + } + }, + "geolocation_checks": { + "type": "array", + "nullable": true, + "description": "**Array of Location verification results**, one per Location verification step: that step's accepted attempt. `null` until a Location verification step has produced data. Earlier attempts of the same step are in `geolocation_attempts`.", + "items": { + "$ref": "#/components/schemas/GeolocationCheck" + } + }, + "geolocation_attempts": { + "type": "array", + "nullable": true, + "description": "Every attempt of every Location verification step of this session, oldest first. Attempts that a resubmission replaced are left out. A workflow that withholds `geolocation_checks` through its response attributes withholds this list too.", + "items": { + "$ref": "#/components/schemas/GeolocationAttempt" + } + }, + "database_validations": { + "type": "array", + "nullable": true, + "description": "**Array of Database Validation reports** — always a JSON array, never a singular `database_validation` object. `null` until at least one DB validation step has produced data. Each item is the full report, including `node_id`, feature-level `status`, `screened_data`, the per-service `validations[]` (each with its registry `source_data`), and `warnings[]`. The `screened_data` and `source_data` fields vary by country:\n\n**ARG (Argentina)**: screened_data: {document_number, selfie, gender, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, date_of_death, tax_id, tax_id_type, face_match_score, sit_1_since, banks, last_position, highest_position, rejected_checks}. Uses RENAPER biometric face-match validation. Gender is auto-inferred from face analysis or given name when not present in the document (e.g., driver licenses)\n\n**BOL (Bolivia)**: screened_data: {document_number, date_of_birth, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, gender}\n\n**BRA (Brazil)**: screened_data: {tax_number, first_name, last_name, date_of_birth}. source_data always includes {identification_number, first_name, last_name, date_of_birth, lgpd_minor, minor_under_18, minor_under_16} for successful CPF matches. Successful adult lookups set those flags to false. The Receita Federal Consulta CPF service may return HTTP 422 (minor under 18) or 451 (under 16) with LGPD-withheld data; in those cases source_data sets the minor flags to true as appropriate, includes the upstream HTTP status, and field-level validation is no_match. See the Brazil section of the Database Validation Outcome Codes reference for the full code list.\n\n**CHL (Chile)**: screened_data: {personal_number, first_name, last_name, date_of_birth}. source_data: {identification_number, first_name, last_name, date_of_birth, gender}\n\n**COL (Colombia)**: screened_data: {document_number, document_type, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, document_type}\n\n**CRI (Costa Rica)**: screened_data: {personal_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**DOM (Dominican Republic)**: screened_data: {personal_number}. source_data: {identification_number} (returns valid/invalid only)\n\n**ECU (Ecuador)**: screened_data: {personal_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, street, formatted_address, gender, nationality, education, marital_status, spouse, mother_name, father_name, profession}\n\n**ESP (Spain)**: screened_data: {personal_number, document_type, expiration_date, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, document_type, expiration_date}\n\n**GTM (Guatemala)**: screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**HND (Honduras)**: screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**MEX (Mexico)**: screened_data: {personal_number (CURP), first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, gender, nationality, curp_status, state_of_birth, doc_probatorio, registration_year, registration_state, registration_municipality, num_acta, crip, issuing_state_code, foreign_registry_number, folio_carta, folio_certificado}. Uses the RENAPO civil registry. A separate INE validity-check service is also available for MEX as an additional one-by-one validation.\n\n**PAN (Panama)**: screened_data: {personal_number, selfie}. source_data: {identification_number}. Uses SIB biometric validation\n\n**PER (Peru)**: screened_data: {personal_number (DNI), first_name, last_name}. source_data: {identification_number, first_name, last_name, paternal_name, maternal_name, verification_number, verification_letter}\n\n**PRY (Paraguay)**: screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**SLV (El Salvador)**: screened_data: {document_number, date_of_birth, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**URY (Uruguay)**: screened_data: {personal_number, date_of_birth, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**VEN (Venezuela)**: screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, gender, date_of_birth, document_type}", + "items": { + "type": "object", + "properties": { + "node_id": { + "type": "string", + "description": "Unique identifier for this database validation step in the workflow graph." + }, + "issuing_state": { + "type": "string", + "description": "ISO 3166-1 alpha-3 code of the country whose government/registry sources were queried (e.g. `BRA`, `MEX`, `ESP`, `NGA`, `IDN`). Database Validation covers 60+ countries across the Americas, Europe, Africa, the Middle East and Asia-Pacific, plus global multi-country sources — coverage expands regularly, so treat the value as an open set rather than a fixed enum." + }, + "validation_type": { + "type": "string", + "enum": [ + "one_by_one", + "two_by_two", + "not_enabled" + ], + "description": "How the result was corroborated: `one_by_one` (single-service outcome), `two_by_two` (two or more distinct services independently full-matched the identification number), or `not_enabled` (persisted when no service produced a usable match). The value is never `null`. Dedicated biometric 2x2 validation modes are currently available for `ECU` and `PER`." + }, + "screened_data": { + "type": "object", + "description": "The input data used for validation. Fields vary by country — see the Database Validation docs (`/core-technology/database-validation/overview`) for country-specific formats.", + "properties": { + "last_name": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "tax_number": { + "type": "string", + "description": "Brazil: CPF (11 digits)" + }, + "personal_number": { + "type": "string", + "description": "Chile: RUT, DOM: Cédula (11 digits), ECU: Cédula (10 digits), ESP: DNI/NIE, MEX: CURP (18 chars), PER: DNI (8 digits), URY: CI" + }, + "document_number": { + "type": "string", + "description": "ARG: DNI, BOL: CI, COL: Cédula, GTM: DPI, HND: DNI, PAN: Cédula, PRY: CI, SLV: DUI, VEN: Cédula" + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "document_type": { + "type": "string", + "description": "COL: CC/CE/TI/NIT, ESP: ID/PASSPORT" + }, + "expiration_date": { + "type": "string", + "format": "date", + "description": "Required for ESP" + } + } + }, + "validations": { + "type": "array", + "description": "One entry per registry service that returned a validation payload. Each entry identifies the service (`service_id`, `service_name`) and carries the per-field `validation` comparison, the service `outcome_code` / `outcome_detail` when reported, and the normalised registry record in `source_data`.", + "items": { + "type": "object", + "properties": { + "service_id": { + "type": "string", + "description": "Identifier of the registry service that produced this validation result.", + "example": "bra_cpf" + }, + "service_name": { + "type": "string", + "description": "Human-readable name of the registry service.", + "example": "Brazil - CPF status check" + }, + "validation": { + "type": "object", + "description": "The results of the field validations.", + "properties": { + "full_name": { + "type": "string", + "description": "Match result for full name (e.g., 'full_match', 'partial_match', 'no_match')." + }, + "date_of_birth": { + "type": "string", + "description": "Match result for date of birth." + }, + "identification_number": { + "type": "string", + "description": "Match result for identification number." + } + } + }, + "outcome_code": { + "type": "string", + "nullable": true, + "description": "Machine-readable outcome of the registry lookup (e.g. `MATCH`, `NO_MATCH`, `PARTIAL_MATCH`, `INCONCLUSIVE`, source-specific codes). Present when the service reports one." + }, + "outcome_detail": { + "type": "string", + "nullable": true, + "description": "Human-readable explanation of `outcome_code`, when provided by the service." + }, + "source_data": { + "type": "object", + "description": "The data returned from the government database. Common fields: identification_number, first_name, last_name, full_name, date_of_birth, gender. Additional fields by country: BRA always includes lgpd_minor, minor_under_18, and minor_under_16 on successful CPF matches; it also includes the upstream HTTP status when CPF data is withheld under LGPD (HTTP 422/451). ECU adds street, formatted_address, nationality, education, marital_status, spouse, mother_name, father_name, profession. PER adds paternal_name, maternal_name, verification_number, verification_letter. ESP adds document_type, expiration_date. VEN/COL add document_type.", + "additionalProperties": true + } + } + } + }, + "errors": { + "type": "array", + "description": "Errors returned by registry services while validating, each tagged with the `service_id` that produced it. **This key is omitted entirely when there are no errors.**", + "items": { + "type": "object", + "description": "The upstream service's error object spread as-is — its keys vary by service (commonly a message/code and any provider-specific details) — plus `service_id` identifying the service that errored.", + "properties": { + "service_id": { + "type": "string", + "nullable": true, + "description": "Identifier of the registry service that errored." + } + }, + "additionalProperties": true + } + }, + "match_type": { + "type": "string", + "enum": [ + "no_match", + "partial_match", + "full_match" + ], + "nullable": true, + "description": "The aggregate match outcome across the queried services. `null` until the validation has been evaluated." + }, + "status": { + "type": "string", + "description": "Feature-level status of this database validation. Independent of the top-level session `status` — a session can reach its overall decision while an individual feature stays `In Review`.", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ] + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "NFC", + "LIVENESS", + "FACEMATCH", + "PROOF_OF_ADDRESS", + "PHONE", + "EMAIL", + "AML", + "LOCATION", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "GEOLOCATION", + "SIGNATURE" + ], + "description": "The feature that generated this warning. The value identifies the feature block the warning belongs to. Each module's warnings array typically uses the value matching its module (e.g. `PHONE` inside `phone_verifications[].warnings`), but the enum above is authoritative across all feature warnings on the decision payload." + }, + "risk": { + "type": "string" + }, + "additional_data": { + "type": "object", + "nullable": true + }, + "log_type": { + "type": "string" + }, + "short_description": { + "type": "string" + }, + "long_description": { + "type": "string" + }, + "node_id": { + "type": "string", + "description": "The workflow graph node ID that generated this warning" + } + } + }, + "description": "Warnings related to the database validation." + } + } + } + }, + "database_validation_not_performed_reason": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Why a configured database validation produced no result, as `{code, message}` plus context fields. Present only when `database_validations` is `null` for a workflow that has the step, so you can tell \"the registry agreed\" from \"nothing ran\". `null` otherwise.", + "properties": { + "code": { + "type": "string", + "description": "Stable machine-readable reason." + }, + "message": { + "type": "string", + "description": "Human-readable explanation." + } + } + }, + "reviews": { + "type": "array", + "items": { + "type": "object", + "properties": { + "user": { + "type": "string", + "description": "Who performed the review. Usually the console reviewer's identifier (their email), but the value is `\"API Client\"` for status updates made with an API key and `\"Unknown\"` when no actor could be resolved — so do not parse this field as an email address." + }, + "new_status": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + } + }, + "assignee": { + "type": "object", + "nullable": true, + "description": "The Console reviewer the session is assigned to, or `null` when nobody holds it.", + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "assigned_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "waiting_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the session started waiting for review. `null` unless the session is `In Review` and your application sets a review deadline." + }, + "sla_due_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the review is due. `null` unless the session is `In Review` and your application sets a review deadline." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp at which the session was created (ISO 8601, UTC)." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp at which this session is scheduled to expire. Set on User Verification (KYC) sessions when an expiration was configured at creation time (or once `Approved` status triggers KYC expiry computation). `null` for sessions without a configured expiration, and for kinds that do not use expiry (e.g. Business Verification (KYB) sessions that never received an `expires_at`)." + }, + "registry_checks": { + "type": "array", + "nullable": true, + "description": "Company registry checks. Present only when `session_kind = business`. Each item wraps the full company-registry payload.", + "items": { + "type": "object", + "description": "One company registry check — the full company-registry payload enriched with ownership structure and warnings. Present only when `session_kind = business`.", + "properties": { + "status": { + "type": "string", + "description": "Feature lifecycle status." + }, + "node_id": { + "type": "string", + "nullable": true + }, + "data_resolved": { + "type": "boolean" + }, + "company": { + "type": "object", + "description": "Full registry payload for the company (registry record merged with any user-provided or console-edited data).", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "node_id": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string" + }, + "registry_status": { + "type": "string", + "nullable": true + }, + "data_resolved": { + "type": "boolean" + }, + "company_name": { + "type": "string" + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_code": { + "type": "string", + "description": "Country of incorporation (ISO 3166-1 alpha-2, e.g. `GB`, `DE`, `US`)." + }, + "region": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision code when applicable (e.g. `CA`, `NY` for US states)." + }, + "company_type": { + "type": "string", + "nullable": true + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "registered_address": { + "type": "string", + "nullable": true + }, + "tax_number": { + "type": "string", + "nullable": true + }, + "risk_level": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ], + "nullable": true + }, + "verification_status": { + "type": "string", + "nullable": true + }, + "is_from_registry": { + "type": "boolean" + }, + "fetch_status": { + "type": "string", + "nullable": true + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "format": "decimal", + "nullable": true, + "description": "Registered/share capital amount as a decimal string (e.g. `\"100.00\"`), parsed from the registry filing. `null` when not available." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 currency code of `registered_capital_amount` (e.g. `\"GBP\"`). `null` when not available." + }, + "website": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "format": "email", + "nullable": true + }, + "phone": { + "type": "string", + "nullable": true + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true + }, + "location_of_registration": { + "type": "string", + "nullable": true + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "designation": { + "type": "string", + "nullable": true + }, + "role": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "Pending" + ], + "nullable": true + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + } + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ] + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership percentage as reported by the registry. Free-form string (e.g. `\"25.00\"`, `\"50\"`); use `effective_ownership_percent` for a parsed numeric value." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership percentage as reported by the registry. Free-form string; use `effective_ownership_percent` for a parsed numeric value." + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "Pending" + ], + "nullable": true + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + }, + "shareholding": { + "$ref": "#/components/schemas/KYBRegistryShareholding" + } + } + } + }, + "addresses": { + "type": "array", + "nullable": true, + "description": "Every address the registry states, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryAddress" + } + }, + "industries": { + "type": "array", + "nullable": true, + "description": "The activities the registry classifies the company under, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryIndustry" + } + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "$ref": "#/components/schemas/KYBRegistryData" + }, + "user_provided_data": { + "type": "object", + "description": "Data confirmed or edited by the end user." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "is_editable": { + "type": "boolean" + }, + "ownership": { + "$ref": "#/components/schemas/KYBRegistryOwnership" + }, + "registry_tier": { + "type": "string", + "nullable": true, + "description": "The registry tier the workflow ordered: `basic`, `shareholders` or `ubo`. Also in `ownership.tier_requested`." + }, + "registry_tier_delivered": { + "type": "string", + "nullable": true, + "description": "The tier the registry delivered and that is billed. Also in `ownership.tier_delivered`." + }, + "registry_ownership_status": { + "type": "string", + "nullable": true, + "description": "`not_requested`, `pending`, `delivered`, `not_held` or `unavailable`. Also in `ownership.status`." + }, + "registry_monitoring": { + "type": "object", + "nullable": true, + "description": "The latest registry monitor for the company, or `null` when none exists.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string" + }, + "unavailable_reason": { + "type": "string", + "nullable": true + }, + "enrolled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "next_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "last_event_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "events_count": { + "type": "integer" + }, + "watched_fields": { + "description": "The company fields the monitor watches." + } + } + } + } + }, + "ownership_structure": { + "$ref": "#/components/schemas/KYBOwnershipStructure" + }, + "warnings": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + }, + "key_people_checks": { + "type": "array", + "nullable": true, + "description": "Aggregate key-people checks. Present only when `session_kind = business`. Each item uses the two-bucket shape (`registry`, `submitted`, `ubo_kyc_summary`).", + "items": { + "type": "object", + "description": "Aggregate check over the company's officers + UBOs. Uses the `registry` / `submitted` shape. Present only when session_kind = business.", + "properties": { + "status": { + "type": "string" + }, + "node_id": { + "type": "string", + "nullable": true + }, + "registry": { + "type": "object", + "description": "Parties discovered via the registry check.", + "properties": { + "officers": { + "type": "array", + "description": "Officers discovered via the registry check, each enriched with the status of any child KYC session and AML screening spawned for them.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "description": "Officer's full name as recorded in the registry." + }, + "designation": { + "type": "string", + "nullable": true, + "description": "Free-text designation from the registry (e.g. `Director`, `Company Secretary`)." + }, + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Normalized officer role." + }, + "nationality": { + "type": "string", + "nullable": true + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "is_active": { + "type": "boolean" + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "aml_status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "nullable": true, + "description": "Status of the AML screening run for this officer. `null` when no AML screening was run." + }, + "kyc_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "nullable": true, + "description": "Raw lifecycle status of the child KYC session spawned for this officer. This is the full session status enum (not a reduced Approved/Declined/Pending set). `null` when no child KYC session exists." + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Shareable URL of the child KYC session. `null` when no child session exists." + }, + "kyc_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Session id of the child KYC session. Pass it to this same decision endpoint to fetch the person's full KYC report. `null` when no child session exists." + }, + "verification_workflow_type": { + "type": "string", + "nullable": true, + "description": "Base workflow type of the child session's workflow (e.g. `kyc`). `null` for graph-based workflows or when no child session exists." + }, + "verification_workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Workflow id used by the child session. `null` when no child session exists." + }, + "verification_workflow_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label of the child session's workflow. `null` when unset." + }, + "verification_workflow_features": { + "type": "array", + "nullable": true, + "description": "Per-feature progress of the child verification session, e.g. `[{\"feature\": \"ID_VERIFICATION\", \"status\": \"Approved\"}]`. `null` when the child session has no resolvable feature list.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature display name (e.g. `ID_VERIFICATION`, `LIVENESS`, `AML`)." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Feature-level status inside the child session." + } + } + } + }, + "didit_internal_id": { + "type": "string", + "nullable": true, + "description": "Stable internal identifier of the verified individual behind the child session — useful for cross-session linking. `null` when no child session exists or the user has not been resolved yet." + }, + "kyc_document_type": { + "type": "string", + "nullable": true, + "description": "Document type presented in the child KYC session (e.g. `P`, `ID`, `DL`). `null` until the child session has an ID verification result." + }, + "kyc_nationality": { + "type": "string", + "nullable": true, + "description": "Nationality extracted from the document in the child KYC session. `null` until available." + }, + "kyc_liveness_passed": { + "type": "boolean", + "nullable": true, + "description": "Whether the child KYC session's liveness check passed. `null` when no liveness check has run." + }, + "kyc_verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the child KYC session reached `Approved`. `null` while the child session has any other status." + }, + "kyc_aml_categories": { + "type": "object", + "nullable": true, + "description": "Per-category summary derived from the person's AML hits. `null` when there are no hits.", + "properties": { + "sanctions": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "pep": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "adverse_media": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "warnings": { + "type": "string", + "enum": [ + "match", + "clear" + ] + } + } + }, + "kyc_notification_sent_at": { + "type": "string", + "nullable": true, + "description": "ISO 8601 timestamp of the last verification-invite notification sent for this person's child session. `null` when none was sent." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + } + }, + "beneficial_owners": { + "type": "array", + "description": "Beneficial owners discovered via the registry check, each enriched with the status of any child KYC session and AML screening spawned for them.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "description": "Beneficial owner's full name (or company name for corporate UBOs)." + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ] + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "roles": { + "type": "array", + "description": "Role names held by this owner, e.g. `[\"ubo\", \"shareholder\"]`.", + "items": { + "type": "string" + } + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership percentage as reported by the registry. Free-form string (e.g. `\"25\"`, `\"25-50%\"`); use `effective_ownership_percent` for a parsed numeric value." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership percentage as reported by the registry. Free-form string; use `effective_ownership_percent` for a parsed numeric value." + }, + "is_active": { + "type": "boolean" + }, + "aml_status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "nullable": true, + "description": "Status of the AML screening run for this beneficial owner. `null` when no AML screening was run." + }, + "kyc_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "nullable": true, + "description": "Raw lifecycle status of the child KYC session spawned for this beneficial owner. This is the full session status enum (not a reduced Approved/Declined/Pending set). `null` when no child KYC session exists." + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Shareable URL of the child KYC session. `null` when no child session exists." + }, + "kyc_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Session id of the child KYC session. Pass it to this same decision endpoint to fetch the person's full KYC report. `null` when no child session exists." + }, + "verification_workflow_type": { + "type": "string", + "nullable": true, + "description": "Base workflow type of the child session's workflow (e.g. `kyc`). `null` for graph-based workflows or when no child session exists." + }, + "verification_workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Workflow id used by the child session. `null` when no child session exists." + }, + "verification_workflow_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label of the child session's workflow. `null` when unset." + }, + "verification_workflow_features": { + "type": "array", + "nullable": true, + "description": "Per-feature progress of the child verification session, e.g. `[{\"feature\": \"ID_VERIFICATION\", \"status\": \"Approved\"}]`. `null` when the child session has no resolvable feature list.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature display name (e.g. `ID_VERIFICATION`, `LIVENESS`, `AML`)." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Feature-level status inside the child session." + } + } + } + }, + "didit_internal_id": { + "type": "string", + "nullable": true, + "description": "Stable internal identifier of the verified individual behind the child session — useful for cross-session linking. `null` when no child session exists or the user has not been resolved yet." + }, + "kyc_document_type": { + "type": "string", + "nullable": true, + "description": "Document type presented in the child KYC session (e.g. `P`, `ID`, `DL`). `null` until the child session has an ID verification result." + }, + "kyc_nationality": { + "type": "string", + "nullable": true, + "description": "Nationality extracted from the document in the child KYC session. `null` until available." + }, + "kyc_liveness_passed": { + "type": "boolean", + "nullable": true, + "description": "Whether the child KYC session's liveness check passed. `null` when no liveness check has run." + }, + "kyc_verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the child KYC session reached `Approved`. `null` while the child session has any other status." + }, + "kyc_aml_categories": { + "type": "object", + "nullable": true, + "description": "Per-category summary derived from the person's AML hits. `null` when there are no hits.", + "properties": { + "sanctions": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "pep": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "adverse_media": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "warnings": { + "type": "string", + "enum": [ + "match", + "clear" + ] + } + } + }, + "kyc_notification_sent_at": { + "type": "string", + "nullable": true, + "description": "ISO 8601 timestamp of the last verification-invite notification sent for this person's child session. `null` when none was sent." + }, + "child_beneficial_owners": { + "type": "array", + "description": "For corporate UBOs: the owning entity's own beneficial owners, as raw objects extracted from the registry. Empty array when not applicable.", + "items": { + "type": "object" + } + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true, + "description": "Parsed numeric ownership percentage — the max bound when available, otherwise the min bound. `null` when neither bound could be parsed." + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + } + } + } + } + } + }, + "submitted": { + "type": "object", + "description": "Parties submitted by the business admin during the Key People flow (BusinessParty rows where source=USER).", + "properties": { + "parties": { + "type": "array", + "items": { + "type": "object", + "description": "Party submitted by the business admin via the Key People flow (source=USER).", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ] + }, + "source": { + "type": "string", + "enum": [ + "USER" + ], + "description": "Always `USER` here — this bucket only contains parties entered or confirmed by the end user in the Key People flow (registry-discovered people appear under `registry`)." + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "format": "email", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true, + "description": "Nationality for persons; country of incorporation for company parties." + }, + "phone_number": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "position": { + "type": "string", + "nullable": true, + "description": "Free-text job title / position (persons only)." + }, + "custom_fields": { + "type": "object", + "description": "Workflow-defined custom fields collected for this party; keys are defined by the workflow's key-people field descriptors." + }, + "is_active": { + "type": "boolean" + }, + "is_skipped": { + "type": "boolean", + "description": "True when the end user skipped verification for this party (only possible when the role allows skipping)." + }, + "requires_verification": { + "type": "boolean", + "description": "True when a child KYC / KYB sub-session is required for this party based on role settings and ownership thresholds." + }, + "roles": { + "type": "array", + "description": "All roles held by this party, each with its own ownership and voting percentages. There is no scalar `role`/`ownership_percent` on the party itself.", + "items": { + "type": "object", + "properties": { + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "ownership_percent": { + "type": "number", + "nullable": true + }, + "voting_percent": { + "type": "number", + "nullable": true + } + } + } + }, + "kyc_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Child KYC session id for person parties requiring verification. `null` otherwise." + }, + "kyc_session_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "nullable": true, + "description": "Lifecycle status of the child KYC session. `null` when no child KYC session exists." + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true + }, + "kyb_sub_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Child KYB sub-session id for corporate parties requiring verification. `null` otherwise." + }, + "kyb_sub_session_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "nullable": true, + "description": "Lifecycle status of the child KYB sub-session. `null` when no sub-session exists." + }, + "kyb_sub_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Shareable URL of the child KYB sub-session. `null` when no sub-session exists." + }, + "verification_workflow_type": { + "type": "string", + "nullable": true, + "description": "Base workflow type of the child session's workflow (KYC session preferred, falling back to the KYB sub-session). `null` for graph-based workflows or when no child session exists." + }, + "verification_workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Workflow id used by the child session. `null` when no child session exists." + }, + "verification_workflow_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label of the child session's workflow. `null` when unset." + }, + "verification_workflow_features": { + "type": "array", + "nullable": true, + "description": "Per-feature progress of the child verification session, e.g. `[{\"feature\": \"ID_VERIFICATION\", \"status\": \"Approved\"}]`. `null` when the child session has no resolvable feature list.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature display name (e.g. `ID_VERIFICATION`, `LIVENESS`, `AML`)." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Feature-level status inside the child session." + } + } + } + }, + "didit_internal_id": { + "type": "string", + "nullable": true, + "description": "Stable internal identifier of the verified individual behind the child session. `null` when not resolved." + } + } + } + } + } + }, + "ubo_kyc_summary": { + "type": "object", + "nullable": true, + "description": "Aggregate UBO KYC progress, or null when no UBOs have linked KYC sessions.", + "properties": { + "total": { + "type": "integer" + }, + "approved": { + "type": "integer" + }, + "flagged": { + "type": "integer", + "description": "UBOs whose KYC is Declined or In Review." + }, + "pending": { + "type": "integer" + } + } + }, + "warnings": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + }, + "document_verifications": { + "type": "array", + "nullable": true, + "description": "Document verification checks grouped by workflow node. Present only when `session_kind = business`.", + "items": { + "type": "object", + "description": "Documents grouped by workflow node. Present only when session_kind = business.", + "properties": { + "status": { + "type": "string" + }, + "node_id": { + "type": "string", + "nullable": true + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "document_type": { + "type": "string", + "enum": [ + "LEGAL_PRESENCE", + "COMPANY_DETAILS", + "OWNERSHIP_STRUCTURE", + "REPRESENTATIVES_AUTHORIZATION", + "OTHER" + ], + "nullable": true, + "description": "High-level KYB document group the file was classified into. `null` while unclassified." + }, + "document_subtype": { + "type": "string", + "nullable": true, + "description": "Specific OCR-detected subtype (e.g. `CERTIFICATE_OF_INCORPORATION`, `SHAREHOLDER_REGISTRY`); `UNKNOWN` when the OCR could not confidently classify the document." + }, + "document_group": { + "type": "string", + "enum": [ + "LEGAL_PRESENCE", + "COMPANY_DETAILS", + "OWNERSHIP_STRUCTURE", + "REPRESENTATIVES_AUTHORIZATION", + "OTHER" + ], + "nullable": true, + "description": "Alias of `document_type` — the group name used by `groups` and `required_groups`." + }, + "file_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived presigned link to the uploaded file. `null` for analyst-created document request placeholders (`is_requested = true`) that have not been fulfilled yet." + }, + "original_filename": { + "type": "string", + "nullable": true, + "description": "Filename as uploaded by the user or analyst." + }, + "file_size": { + "type": "integer", + "nullable": true, + "description": "File size in bytes." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Per-document verification status." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "cross_check_result": { + "type": "object", + "nullable": true, + "description": "Cross-reference of the OCR-extracted document fields against the registry company profile. An object with `critical`, `non_critical` and `people` sections, each mapping a field name (e.g. `company_name`, `registration_number`, `incorporation_date`, `registered_address`) to a comparison record like `{\"document\": \"...\", \"registry\": \"...\", \"match\": true, \"score\": 0.97}`. `null` when the document had no OCR data or there was no registry company to compare against." + }, + "document_metadata": { + "type": "object", + "nullable": true, + "description": "Metadata extracted from the uploaded corporate document. When metadata extraction succeeded, all 16 keys are always present (individual values are `null` when not applicable to the file type); the whole object is `null` when nothing could be extracted. For PDFs, `overlay_manipulation` carries forensic overlay-text analysis.", + "properties": { + "file_size": { + "type": "integer", + "nullable": true, + "description": "Size of the document." + }, + "content_type": { + "type": "string", + "nullable": true, + "description": "Content type of the document." + }, + "creation_date": { + "type": "string", + "nullable": true, + "description": "Date when the document was created." + }, + "modified_date": { + "type": "string", + "nullable": true, + "description": "Date when the document was modified." + }, + "creator": { + "type": "string", + "nullable": true, + "description": "Authoring tool/user recorded in the file metadata (PDF `Creator`)." + }, + "producer": { + "type": "string", + "nullable": true, + "description": "Software that produced the file (PDF `Producer`)." + }, + "software": { + "type": "string", + "nullable": true, + "description": "Editing software recorded in the file metadata." + }, + "encryption": { + "type": "string", + "nullable": true, + "description": "Encryption scheme of the file, when encrypted." + }, + "is_signed": { + "type": "boolean", + "nullable": true, + "description": "Whether the document carries a digital signature." + }, + "is_tampered": { + "type": "boolean", + "nullable": true, + "description": "Whether the digital signature failed validation (post-signing modification)." + }, + "signature_info": { + "type": "object", + "nullable": true, + "description": "Details of the digital signature(s), when present." + }, + "exif_original_date": { + "type": "string", + "nullable": true, + "description": "EXIF original capture date, for image uploads." + }, + "exif_digitized_date": { + "type": "string", + "nullable": true, + "description": "EXIF digitization date, for image uploads." + }, + "processed_by_known_editor": { + "type": "boolean", + "nullable": true, + "description": "Whether metadata indicates the file passed through a known image/PDF editor." + }, + "has_different_creation_mod_date": { + "type": "boolean", + "nullable": true, + "description": "Whether creation and modification dates differ — a possible (but not conclusive) editing signal." + }, + "overlay_manipulation": { + "type": "object", + "nullable": true, + "description": "PDF forensic analysis for suspected overlay-text manipulation. Null when no overlay manipulation evidence was found or the document was not a PDF.", + "properties": { + "detected": { + "type": "boolean", + "description": "Whether overlay-text manipulation evidence was detected." + }, + "analyzed": { + "type": "boolean", + "description": "Whether the PDF could be analyzed." + }, + "signals": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Forensic signals that fired, such as duplicate_font_subset or glyph_fragmentation." + }, + "manipulated_regions": { + "type": "array", + "description": "Suspected manipulated areas in PDF page coordinates.", + "items": { + "type": "object", + "properties": { + "page": { + "type": "integer" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + }, + "width": { + "type": "number" + }, + "height": { + "type": "number" + }, + "page_width": { + "type": "number" + }, + "page_height": { + "type": "number" + } + } + } + } + } + } + } + }, + "description": { + "type": "string", + "nullable": true, + "description": "Analyst-provided label or reason for a document request. `null` for regular uploads." + }, + "is_requested": { + "type": "boolean", + "description": "True for analyst-created document request placeholders awaiting an upload." + }, + "ocr_data": { + "type": "object", + "nullable": true + }, + "is_console_upload": { + "type": "boolean", + "description": "True for documents uploaded from the console rather than the end-user flow. Console uploads never change the feature-level aggregate status." + }, + "is_confirmed": { + "type": "boolean", + "description": "Console uploads start `false` and flip `true` once the admin confirms them; end-user uploads are always `true`." + } + } + } + }, + "groups": { + "type": "object", + "description": "Per-group progress counters keyed by group name (e.g. legal_presence, ownership_structure).", + "additionalProperties": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "approved": { + "type": "integer" + }, + "pending": { + "type": "integer" + }, + "declined": { + "type": "integer" + }, + "in_review": { + "type": "integer" + }, + "other": { + "type": "integer" + }, + "missing": { + "type": "integer", + "description": "Requested placeholders with no file uploaded yet." + } + } + } + }, + "required_groups": { + "type": "array", + "items": { + "type": "string" + } + }, + "warnings": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Whether the session was created by a live application (`live`) or a sandbox application (`sandbox`). Sandbox sessions are free, fully simulated, and never bill credits." + }, + "sandbox_scenario": { + "type": "string", + "nullable": true, + "description": "The sandbox scenario the session was created with. `null` for live sessions and for sandbox sessions created without one." + }, + "resubmissions": { + "type": "array", + "description": "Returned only with `include=resubmissions`, for user and business sessions. One entry per resubmission request, oldest first. Each entry carries the results the resubmitted steps returned before the request, so earlier submissions stay readable. Deleting the session deletes every submission.", + "items": { + "type": "object", + "properties": { + "number": { + "type": "integer", + "description": "1 for the first resubmission of the session, 2 for the second, and so on." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the resubmission was requested." + }, + "previous_status": { + "type": "string", + "description": "The session status just before the request." + }, + "nodes": { + "type": "array", + "description": "The workflow steps sent back to the user.", + "items": { + "type": "object", + "properties": { + "node_id": { + "type": "string" + }, + "feature": { + "type": "string" + } + } + } + }, + "reasons": { + "type": "object", + "nullable": true, + "additionalProperties": { + "type": "string" + }, + "description": "The reason given for each step, keyed by `node_id`." + }, + "requested_by": { + "type": "object", + "nullable": true, + "description": "Who requested the resubmission.", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "results": { + "type": "object", + "additionalProperties": true, + "description": "What the resubmitted steps returned before the request, under the same keys and in the same shape as the current results of this response (`id_verifications`, `nfc_verifications`, `liveness_checks`, `face_matches`, `poa_verifications`, `phone_verifications`, `email_verifications`, `aml_screenings`, `ip_analyses`, `database_validations`, `questionnaire_responses`, `document_ai_documents`, `geolocation_checks`; for business sessions also `document_verifications`). Only the keys of the steps that were sent back appear." + } + } + } + }, + "biometric_consent": { + "type": "object", + "nullable": true, + "description": "Whether the session required explicit biometric consent before a face capture, why, and the evidence of the answer. User Verification (KYC) sessions only; the same object is in the `decision` of session webhooks. `null` when the session was never evaluated, for example a workflow without a face capture. Fields with no recorded value are `null`. A native SDK below 4.10.0, or one that does not report its version, cannot show the consent step: its face captures run as before, and a session that required consent then reports `status: \"pending\"` with `reason: \"BIOMETRIC_CONSENT_UNSUPPORTED_SDK\"`. See [Biometric consent](/getting-started/biometric-consent).", + "properties": { + "required": { + "type": "boolean", + "description": "Whether the session must collect explicit biometric consent. Never goes back to `false`." + }, + "trigger": { + "type": "string", + "nullable": true, + "enum": [ + "declared_state", + "ip_state", + "ip_unknown", + "document_issuer", + "forced", + "customer_collected" + ], + "description": "Why consent is required, or how it was satisfied. `declared_state`: you declared a covered state (`user_state`, or a US address in `expected_details`). `ip_state`: the person's IP is in a covered state (Illinois, Texas, Colorado). `ip_unknown`: the evidence places the person in the US but the state cannot be told (the IP is in the US with no state, or cannot be located while your details or the document place the person in the US). `document_issuer`: the identity document was issued by a covered state. `forced`: the application's mode is Always. `customer_collected`: you sent `biometric_consent` when creating the session. `null` while not required." + }, + "status": { + "type": "string", + "enum": [ + "not_required", + "pending", + "given", + "declined" + ], + "description": "`not_required`; `pending` (required and not given: the person has not answered yet, or `reason` says why it was not collected); `given`; `declined` (a decline was recorded and the session ended as `Declined`; no Didit screen offers a decline today)." + }, + "given_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When consent was given. For `customer_collected`, the `given_at` you sent." + }, + "declined_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When a decline was recorded." + }, + "text_version": { + "type": "string", + "nullable": true, + "description": "Version id of the consent text the person answered, or the one you sent." + }, + "text_hash": { + "type": "string", + "nullable": true, + "description": "SHA-256 (hex) of the exact consent text shown, in the language it was shown in." + }, + "locale": { + "type": "string", + "nullable": true, + "description": "Language the consent text was shown in." + }, + "policy_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The biometric data policy the consent step linked to." + }, + "step": { + "type": "string", + "nullable": true, + "description": "Workflow step the session was on when the person answered, for example `LIVENESS`." + }, + "node_id": { + "type": "string", + "nullable": true, + "description": "Workflow graph node the session was on when the person answered." + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "IP the answer came from." + }, + "ip_country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of the answer's IP." + }, + "ip_state": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision of the answer's IP, for example `US-IL`." + }, + "ip_city": { + "type": "string", + "nullable": true, + "description": "City of the answer's IP." + }, + "user_agent": { + "type": "string", + "nullable": true, + "description": "User agent of the answer's request." + }, + "detected_ip_address": { + "type": "string", + "nullable": true, + "description": "The person's IP the requirement was last evaluated from. Can differ from `ip_address` when the person answered on another device." + }, + "detected_country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of the detected IP." + }, + "detected_state": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision of the detected IP." + }, + "detected_city": { + "type": "string", + "nullable": true, + "description": "City of the detected IP." + }, + "declared_state": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision you declared: `user_state`, or the state of a US address in `expected_details`." + }, + "document_state": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision that issued the identity document, read at ID capture." + }, + "jurisdictions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "US states (USPS codes, for example `IL`) whose biometric law covers the person. `US` when the evidence places the person in the US but no state is known. Empty when none applies." + }, + "retention_months": { + "type": "integer", + "nullable": true, + "description": "The shortest biometric retention limit, in months, among `jurisdictions`. `null` when none applies." + }, + "reason": { + "type": "string", + "nullable": true, + "enum": [ + "BIOMETRIC_CONSENT_UNSUPPORTED_SDK", + "BIOMETRIC_CONSENT_DECLINED" + ], + "description": "`BIOMETRIC_CONSENT_UNSUPPORTED_SDK`: consent was required and a native SDK below 4.10.0, or one that did not report its version, captured a face without the consent step; `status` stays `pending`, or `given` if the person later agreed in the hosted flow. `BIOMETRIC_CONSENT_DECLINED`: a decline was recorded (it replaces the SDK reason). `null` otherwise, and always `null` when the session did not require consent." + }, + "decline_reason": { + "type": "string", + "nullable": true, + "enum": [ + "BIOMETRIC_CONSENT_DECLINED" + ], + "description": "`BIOMETRIC_CONSENT_DECLINED` when a recorded decline ended the session, otherwise `null`." + }, + "face_data_deleted_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the decline deleted the session's face images, videos and face templates. `null` unless a decline was recorded." + } + } + }, + "investigator": { + "$ref": "#/components/schemas/InvestigatorBlock" + }, + "biometric_retention": { + "$ref": "#/components/schemas/BiometricRetention" + }, + "fraud_check": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "The result of a Fraud Check API call. `null` for every other session." + }, + "full_session_video": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Expiring URL of the full-session recording. Returned only when your organization records full-session video." + }, + "full_session_video_recorded_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the full-session recording was made. Returned only when your organization records full-session video." + } + } + }, + "WorkflowDetail": { + "type": "object", + "description": "The fields below are typed; the object also carries every other column of the workflow configuration (feature toggles, thresholds, warning actions, allowed documents, callbacks) as additional properties. Fields that do not apply to the `workflow_type` are `null`.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Per-version identifier (`settings_uuid`). Every version of a workflow has its own." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier shared by every version of the workflow. Sessions created with it use the latest published version." + }, + "workflow_label": { + "type": "string", + "description": "Display name of the workflow." + }, + "workflow_type": { + "type": "string", + "nullable": true, + "enum": [ + "kyc", + "kyb", + "adaptive_age_verification", + "biometric_authentication", + "address_verification", + "questionnaire_verification", + "email_verification", + "phone_verification", + null + ], + "description": "Workflow template. `null` for graph workflows created without a template." + }, + "is_default": { + "type": "boolean", + "description": "Whether this is the application's default workflow." + }, + "is_archived": { + "type": "boolean", + "description": "Archived workflows cannot start new sessions." + }, + "version": { + "type": "integer", + "description": "Version number within the workflow." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Draft versions are editable; published versions are immutable and used for new sessions." + }, + "published_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this version was published. `null` for drafts." + }, + "is_simple_workflow": { + "type": "boolean", + "description": "`true` when the workflow has no custom `workflow_graph`." + }, + "is_editable": { + "type": "boolean", + "description": "`true` only for `draft` versions." + }, + "workflow_url": { + "type": "string", + "format": "uri", + "description": "Public URL that starts a session with this workflow." + }, + "workflow_graph": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Graph structure of the workflow (nodes and edges). `null` for simple workflows." + }, + "features": { + "type": "string", + "description": "Enabled features joined with `\" + \"`, for example `\"OCR + LIVENESS\"`." + }, + "total_price": { + "type": "number", + "nullable": true, + "description": "Price per session; for graph workflows the most expensive path." + }, + "min_price": { + "type": "number", + "nullable": true, + "description": "Price of the cheapest path through the workflow." + }, + "max_price": { + "type": "number", + "nullable": true, + "description": "Price of the most expensive path through the workflow." + }, + "special_add_on_prices": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Organization-specific add-on prices, or `null` when none are set." + }, + "max_retry_attempts": { + "type": "integer", + "description": "Retries allowed after a declined verification." + }, + "retry_window_days": { + "type": "integer", + "nullable": true, + "description": "Days during which the retries are allowed." + }, + "session_expiration_time": { + "type": "integer", + "description": "Seconds before an unfinished session expires." + }, + "questionnaire": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "The questionnaire attached to the workflow, or `null`." + }, + "kyb_role_settings": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Per-role verification settings for KYB key people." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "signature_availability": { + "type": "object", + "description": "Which E-signature levels can sign right now. Check it before you set `signature_level`. Returned on `kyc`, `kyb` and graph workflows.", + "properties": { + "simple": { + "type": "boolean", + "description": "Simple is always available." + }, + "aes": { + "type": "boolean", + "description": "Whether Advanced (AES) can sign now. While it is `false`, saving a new step at `signature_level: \"aes\"` is refused with `aes_not_available`." + }, + "qes": { + "type": "boolean", + "description": "Whether Qualified (QES) can sign now. It is `false` until a qualified provider is connected." + } + } + }, + "is_bank_verification_enabled": { + "type": "boolean", + "description": "Whether a simple KYC or KYB workflow runs Bank verification. It runs after the contact steps (and Location verification) and before database validation, AML and IP analysis. Bank verification is not generally available yet: turning it on is refused wherever it is not enabled." + }, + "bank_verification_config": { + "type": "object", + "additionalProperties": true, + "description": "Bank verification settings of a simple workflow, with the same keys a `BANK_VERIFICATION` node takes (for example `bank_countries` and `bank_method_order`). Unknown keys are dropped. Every key is optional and an absent key keeps its default, so `{}` means all defaults. See [Bank verification settings](/management-api/workflows/feature-configs#bank-verification).", + "example": {} + }, + "is_geolocation_enabled": { + "type": "boolean", + "description": "Whether a simple KYC workflow runs Location verification. It runs after the contact steps and before Bank verification and database validation. Business (KYB) workflows cannot run it." + }, + "geolocation_config": { + "type": "object", + "additionalProperties": true, + "description": "Location verification settings of a simple workflow, with the same keys a `GEOLOCATION` node takes (for example `is_geolocation_geofencing_enabled` and `geolocation_max_attempts`). Unknown keys are dropped. Every key is optional and an absent key keeps its default, so `{}` means all defaults.", + "example": {} + } + }, + "additionalProperties": true + }, + "UserSessionListItem": { + "title": "KYC session row (session_kind: user)", + "type": "object", + "description": "Summary row for a User Verification (KYC) session. Identity fields (`full_name`, `country`, `document_type`, `portrait_image`, contact fields) are populated progressively as the user completes verification steps, and are `null` until the relevant step finishes. Fields restricted by the workflow's response-attributes configuration are also returned as `null`.", + "required": [ + "session_id", + "session_kind", + "session_number", + "session_url", + "portrait_image", + "document_type", + "full_name", + "country", + "status", + "vendor_data", + "didit_internal_id", + "created_at", + "features", + "phone_number", + "email_address", + "additional_details" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of the verification session. Use it with `GET /v3/session/{sessionId}/decision/` to fetch the full decision payload.", + "example": "5b3720ed-d429-42ef-b67f-37ea805f48ee" + }, + "session_kind": { + "type": "string", + "enum": [ + "user" + ], + "description": "Row discriminator. Always `\"user\"` for KYC rows; Business Verification (KYB) rows have `\"business\"` and a different shape.", + "example": "user" + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of the session within your application. Also matched by the `search` parameter.", + "example": 720 + }, + "session_url": { + "type": "string", + "nullable": true, + "description": "Hosted verification URL the end user opens to complete the flow. `null` when the session has no hosted URL.", + "example": "https://verify.didit.me/session/CuXoBYCYVO5Y" + }, + "portrait_image": { + "type": "string", + "nullable": true, + "description": "Expiring (presigned) URL of the best available portrait: the NFC chip photo when available, otherwise the ID document portrait, otherwise the liveness reference image. `null` until one of those exists. Download promptly; the link expires.", + "example": "https:///portraits/5b3720ed.jpg?X-Amz-Expires=3600&X-Amz-Signature=..." + }, + "document_type": { + "type": "string", + "nullable": true, + "enum": [ + "Passport", + "Identity Card", + "Driver's License", + "Residence Permit", + "Health Insurance Card", + "Tax Card" + ], + "description": "Human-readable type of the verified ID document. `null` before document verification completes, and also `null` after completion when the verified code has no display mapping (`SSC`, `WP`, `VISA`, `PSC`, `BC`, `OTHER`). Note: the `document_type` query parameter filters by short codes (`P`, `ID`, `DL`, `RP`, ...), not by these display values.", + "example": "Passport" + }, + "full_name": { + "type": "string", + "nullable": true, + "description": "Best available full name for the session, resolved in order: ID document OCR, AML screened data, database-validation screened data, proof-of-address document, then the expected details provided at session creation. `null` when none is available yet.", + "example": "Jane Marie Doe" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-3 country associated with the session, resolved in order: ID document issuing state, database-validation issuing state, proof-of-address issuing state, expected details, then the verified phone's country. May differ from nationality.", + "example": "ESP" + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Overall session status. See the [Verification Statuses](/integration/verification-statuses) page for the lifecycle.", + "example": "Approved" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the end user, echoed back exactly as sent at session creation.", + "example": "your-user-id-123" + }, + "didit_internal_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Didit's internal identifier for the verified user profile behind this session. Stable across multiple sessions of the same user; filterable via the `didit_internal_id` query parameter.", + "example": "d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "UTC timestamp when the session was created.", + "example": "2026-06-09T12:26:54.328364Z" + }, + "features": { + "type": "array", + "description": "Per-feature status for every verification step configured in the session's workflow, in workflow order.", + "items": { + "type": "object", + "required": [ + "feature", + "status" + ], + "properties": { + "feature": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "NFC", + "LIVENESS", + "FACE_MATCH", + "POA", + "QUESTIONNAIRE", + "EMAIL_VERIFICATION", + "PHONE", + "AML", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "DATABASE_VALIDATION", + "GEOLOCATION" + ], + "description": "Verification feature identifier.", + "example": "ID_VERIFICATION" + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Status of this feature. `Not Finished` until the step completes.", + "example": "Approved" + } + } + } + }, + "phone_number": { + "type": "object", + "nullable": true, + "description": "Phone associated with the session: the verified phone when phone verification ran, otherwise the contact phone provided at session creation. `null` when neither exists.", + "required": [ + "number", + "is_verified" + ], + "properties": { + "number": { + "type": "string", + "description": "Phone number in E.164 format.", + "example": "+14155552671" + }, + "is_verified": { + "type": "boolean", + "description": "`true` only when the phone-verification feature approved this number.", + "example": true + } + } + }, + "email_address": { + "type": "object", + "nullable": true, + "description": "Email associated with the session: the verified email when email verification ran, otherwise the contact email provided at session creation. `null` when neither exists.", + "required": [ + "email", + "is_verified" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "example": "jane@example.com" + }, + "is_verified": { + "type": "boolean", + "description": "`true` only when the email-verification feature approved this address.", + "example": true + } + } + }, + "additional_details": { + "type": "object", + "nullable": true, + "description": "Compact per-feature extras. Only features that produced data appear as keys; `null` when no feature produced extras. Fields excluded by the workflow's response-attributes configuration are omitted.", + "properties": { + "aml": { + "type": "object", + "description": "Present when AML screening ran.", + "properties": { + "total_hits": { + "type": "integer", + "description": "Number of AML screening hits.", + "example": 0 + }, + "entity_type": { + "type": "string", + "description": "Screened entity type.", + "example": "person" + }, + "is_adverse_media_fetched": { + "type": "boolean", + "example": false + }, + "screened_data": { + "type": "object", + "description": "Subset of the data that was screened. Only non-empty keys are included.", + "properties": { + "nationality": { + "type": "string", + "example": "ESP" + }, + "document_number": { + "type": "string", + "example": "XX1234567" + }, + "date_of_birth": { + "type": "string", + "example": "1990-05-21" + } + } + } + } + }, + "database_validation": { + "type": "object", + "description": "Present when database validation ran.", + "properties": { + "validation_type": { + "type": "string", + "example": "DOCUMENT_VALIDATION" + }, + "match_type": { + "type": "string", + "example": "FULL_MATCH" + }, + "identification_number": { + "type": "string", + "description": "Tax, personal, or document number that was validated (first available).", + "example": "12345678Z" + } + } + }, + "poa": { + "type": "object", + "description": "Present when proof of address ran.", + "properties": { + "document_type": { + "type": "string", + "example": "UTILITY_BILL" + }, + "issuer": { + "type": "string", + "example": "Energy Co" + }, + "address": { + "type": "string", + "example": "Calle Mayor 1, 28013 Madrid" + }, + "name_on_document": { + "type": "string", + "example": "Jane Marie Doe" + } + } + }, + "liveness": { + "type": "object", + "description": "Present when a liveness check ran.", + "properties": { + "method": { + "type": "string", + "example": "PASSIVE" + }, + "score": { + "type": "number", + "description": "Liveness confidence score, rounded to 2 decimals.", + "example": 0.98 + } + } + }, + "face_match": { + "type": "object", + "description": "Present when face match ran.", + "properties": { + "score": { + "type": "number", + "description": "Face-match similarity score, rounded to 2 decimals.", + "example": 0.95 + }, + "source_image": { + "type": "string", + "description": "Expiring (presigned) URL of the source face image.", + "example": "https:///faces/source.jpg?X-Amz-Expires=3600&..." + }, + "target_image": { + "type": "string", + "description": "Expiring (presigned) URL of the target face image.", + "example": "https:///faces/target.jpg?X-Amz-Expires=3600&..." + } + } + }, + "phone_verification": { + "type": "object", + "description": "Present when phone verification ran.", + "properties": { + "country_code": { + "type": "string", + "description": "ISO 3166-1 alpha-2 country of the phone number.", + "example": "US" + }, + "is_temporary": { + "type": "boolean", + "example": false + }, + "is_ported": { + "type": "boolean", + "example": false + }, + "is_blocked": { + "type": "boolean", + "example": false + } + } + }, + "email_verification": { + "type": "object", + "description": "Present when email verification ran.", + "properties": { + "is_breached": { + "type": "boolean", + "example": false + }, + "is_disposable": { + "type": "boolean", + "example": false + }, + "is_undeliverable": { + "type": "boolean", + "example": false + }, + "is_blocklisted": { + "type": "boolean", + "example": false + } + } + } + } + }, + "assignee": { + "type": "object", + "nullable": true, + "description": "The Console reviewer the session is assigned to, or `null` when nobody holds it.", + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "assigned_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "waiting_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the session started waiting for review. `null` unless the session is `In Review`." + }, + "sla_due_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the review is due, from the review deadline set for your application in the Console. `null` unless the session is `In Review` and a deadline is set." + }, + "top_warnings": { + "type": "array", + "description": "Up to a few of the session's most important warnings, for a quick triage view. The full list is in the decision.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "description": "Warning code, for example `FACE_MATCH_LOW_SIMILARITY`." + }, + "feature": { + "type": "string", + "description": "The feature that raised it." + }, + "log_type": { + "type": "string", + "nullable": true, + "description": "`error` or `warning`; `null` for a legacy entry." + }, + "short_description": { + "type": "string" + } + } + } + }, + "warnings_count": { + "type": "integer", + "description": "Number of warnings on the session." + } + } + }, + "BusinessSessionListItem": { + "title": "KYB session row (session_kind: business)", + "type": "object", + "description": "Summary row for a Business Verification (KYB) session. Returned only when `session_kind` is `business` or `all`.", + "required": [ + "session_id", + "session_kind", + "session_number", + "status", + "vendor_data", + "created_at", + "workflow_id", + "workflow_type", + "workflow_label", + "company_name", + "registration_number", + "country" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of the business verification session.", + "example": "8a1f63c2-1b9e-4f3a-9c41-2f6a1f0b7d52" + }, + "session_kind": { + "type": "string", + "enum": [ + "business" + ], + "description": "Row discriminator. Always `\"business\"` for KYB rows.", + "example": "business" + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of the business session within your application. Numbered independently from KYC sessions.", + "example": 42 + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Overall session status. See the [Verification Statuses](/integration/verification-statuses) page.", + "example": "Approved" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the business, echoed back exactly as sent at session creation.", + "example": "your-company-id-77" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "UTC timestamp when the session was created.", + "example": "2026-06-08T09:14:02.118450Z" + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Identifier of the workflow this session was created with.", + "example": "9f9b1234-aaaa-bbbb-cccc-1234567890ab" + }, + "workflow_type": { + "type": "string", + "nullable": true, + "description": "Workflow type. `kyb` for business workflows.", + "example": "kyb" + }, + "workflow_label": { + "type": "string", + "nullable": true, + "description": "Display name of the workflow.", + "example": "Standard KYB" + }, + "company_name": { + "type": "string", + "nullable": true, + "description": "Name of the company being verified, from the registry company linked to the session. `null` until a company is selected.", + "example": "Acme Holdings S.L." + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Company registration number from the registry company linked to the session.", + "example": "B12345678" + }, + "country": { + "type": "string", + "nullable": true, + "description": "Country code of the registry company linked to the session.", + "example": "ES" + } + } + }, + "TransactionVerificationSessionAction": { + "title": "Verification session", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "verification_session" + ] + }, + "url": { + "type": "string", + "format": "uri", + "description": "Hosted verification URL to open for the end user." + }, + "session_id": { + "type": "string", + "format": "uuid", + "description": "Verification session id." + }, + "session_token": { + "type": "string", + "description": "Session token, used by the mobile SDKs to launch the flow natively." + }, + "status": { + "type": "string", + "description": "Verification session status." + } + }, + "required": [ + "type", + "url" + ] + }, + "TransactionWalletOwnershipAction": { + "title": "Wallet ownership", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "wallet_ownership" + ] + }, + "url": { + "type": "string", + "format": "uri", + "description": "Hosted wallet-ownership widget URL. Open it in a real browser surface - on mobile use SFSafariViewController or a Chrome Custom Tab, never an embedded webview." + }, + "widget_session_id": { + "type": "string", + "format": "uuid", + "description": "Wallet-ownership widget session id." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the widget session expires." + } + }, + "required": [ + "type", + "url" + ] + }, + "TransactionRuleActionAddScore": { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "add_score" + ] + }, + "value": { + "type": "integer", + "description": "Points added to the transaction's risk score. May be negative." + } + }, + "description": "Add points to the transaction's cumulative risk score; the total decides the status via the application's review/decline thresholds." + }, + "TransactionRuleActionChangeStatus": { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "change_status" + ] + }, + "value": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Status to set immediately, regardless of score. Alias key: status." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "REQUIRED when value is AWAITING_USER: the verification workflow the subject is asked to complete. Alias key: remediation_workflow_id. Didit creates a hosted remediation session for the subject when the rule fires." + } + }, + "description": "Directly set the transaction status. DECLINED always wins across matched rules." + }, + "TransactionRuleActionAddTags": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "add_tags" + ] + }, + "tag_uuid": { + "type": "string", + "format": "uuid", + "description": "Existing application tag to attach." + }, + "tag_name": { + "type": "string", + "description": "Tag name to attach; created on first use (stored uppercased). Provide tag_uuid or tag_name." + }, + "tag_color": { + "type": "string", + "description": "Hex color used when tag_name creates a new tag, e.g. #007AFE." + } + }, + "description": "Attach an application tag to the matched transaction." + }, + "TransactionRuleActionAddNote": { + "type": "object", + "required": [ + "type", + "note" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "add_note" + ] + }, + "note": { + "type": "string", + "description": "Note text attached to the transaction (author: Rule Engine). Alias key: value." + } + }, + "description": "Attach an automated analyst note to the matched transaction." + }, + "TransactionRuleActionAddToList": { + "type": "object", + "required": [ + "type", + "list_id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "add_to_list" + ] + }, + "list_id": { + "type": "string", + "format": "uuid", + "description": "List to add the subject's vendor_data to." + } + }, + "description": "Add the subject's vendor_data to a blocklist, allowlist or custom list." + }, + "TransactionRuleActionOpenCase": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "open_case" + ] + }, + "blueprint": { + "type": "string", + "format": "uuid", + "description": "Case blueprint UUID; defaults to the application's default blueprint." + }, + "grouping": { + "type": "string", + "enum": [ + "by_applicant", + "by_rule_and_applicant" + ], + "description": "Whether repeat matches join one case per applicant or one per rule+applicant." + }, + "attach_matched_transaction": { + "type": "boolean", + "description": "Attach the matched transaction to the case." + } + }, + "description": "Open (or add to) an investigation case. Only the first open_case action on a rule is applied." + }, + "KYBRegistrySearchResults": { + "type": "object", + "properties": { + "companies": { + "type": "array", + "items": { + "type": "object", + "properties": { + "kyb_response_id": { + "type": "string", + "nullable": true, + "description": "Handle for this candidate — pass it to `POST /v3/kyb/select/` to retrieve the full profile. Ephemeral and scoped to this search: do not store it long-term; re-search to get a fresh one.", + "example": "69aeeb95febb0f1704042259" + }, + "name": { + "type": "string", + "nullable": true, + "description": "Registered company name.", + "example": "Tesco PLC" + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Registry registration number.", + "example": "00445790" + }, + "status": { + "type": "string", + "nullable": true, + "description": "Normalized registry status of the company (e.g. `active`, `dissolved`, `inactive`, `struck off`).", + "example": "active" + }, + "type": { + "type": "string", + "nullable": true, + "description": "Legal form as reported by the registry.", + "example": "Public Limited Company" + }, + "registered_address": { + "type": "string", + "nullable": true, + "description": "Registered address of the candidate, when the registry returns one. Use it, with the registration number, to tell same-named companies apart: in the United States a candidate can be listed under the state where the company operates rather than the state you searched.", + "example": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, AL7 1GA" + }, + "risk_level": { + "type": "string", + "nullable": true, + "description": "Registry-reported risk level, when available. Usually `null` at search time." + }, + "fetch_status": { + "type": "string", + "nullable": true, + "description": "Always `pending` for search candidates — the full company profile is only fetched (and billed) when you select the candidate.", + "example": "pending" + } + } + }, + "description": "Deduplicated candidate list. Empty while `search_resolved=false` (webhook mode) or when nothing matched." + }, + "pagination": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Number of candidates returned." + }, + "page": { + "type": "integer" + }, + "per_page": { + "type": "integer", + "example": 25 + } + } + }, + "search_status": { + "type": "string", + "nullable": true, + "enum": [ + "pending", + "resolved" + ], + "description": "Registry search lifecycle status." + }, + "search_resolved": { + "type": "boolean", + "description": "`true` once the candidate list is final." + } + } + }, + "KYBRegistryCandidateRequest": { + "type": "object", + "required": [ + "kyb_response_id" + ], + "properties": { + "kyb_response_id": { + "type": "string", + "maxLength": 1024, + "description": "Candidate handle from `POST /v3/kyb/search/` (`kyb_registry.companies[].kyb_response_id`). Ephemeral: use it shortly after the search that produced it. Handles can be long, so store and send the value exactly as returned, without trimming or assuming a length.", + "example": "69aeeb95febb0f1704042259" + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for this company; echoed back and stored with the business session." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON stored with the business session and echoed back." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "Must be `true` (the default). KYB registry selects are always persisted so billing and retrieval can be tracked; `false` returns `400`." + } + } + }, + "WebhookEventEnvelope": { + "type": "object", + "description": "Fields Didit adds to every signed webhook delivery, whatever the event type. Read `webhook_type` to know which payload you received.", + "required": [ + "event_id", + "webhook_type", + "timestamp", + "created_at", + "application_id", + "environment" + ], + "properties": { + "event_id": { + "type": "string", + "format": "uuid", + "description": "Identifier of the event. Every destination receives the same value and a redelivery keeps it, so use it to deduplicate." + }, + "webhook_type": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "timestamp": { + "type": "integer", + "description": "Unix time in seconds at which this delivery was signed. Equal to the `X-Timestamp` header; a redelivery carries a new value." + }, + "created_at": { + "type": "integer", + "description": "Unix time in seconds of the change that produced the event." + }, + "application_id": { + "type": "string", + "format": "uuid", + "description": "The application the event belongs to." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "`sandbox` for sandbox sessions and sandbox applications, otherwise `live`." + } + } + }, + "SessionWebhookEventData": { + "type": "object", + "description": "Session fields of `status.updated` and `data.updated`. Optional fields are omitted, never sent as `null`, when they do not apply. Business (KYB) sessions add `business_session_id`, `session_kind` and `vendor_business_id`.", + "required": [ + "session_id", + "status", + "sandbox_scenario" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The verification session." + }, + "business_session_id": { + "type": "string", + "format": "uuid", + "description": "Business (KYB) sessions only: the same value as `session_id`." + }, + "session_kind": { + "type": "string", + "enum": [ + "business" + ], + "description": "Business (KYB) sessions only. User (KYC) session events carry no top-level `session_kind`; read `decision.session_kind` instead." + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Session status after the change." + }, + "trigger": { + "type": "string", + "description": "Present when the event was not produced by the verification flow itself, for example `manual_review`, `manual_step_update`, `ongoing_monitoring`, `webhook_reconciliation` (a `status.updated` resent because the first terminal webhook was never created, so you may receive a second event for a decision you already got: deduplicate on `session_id` and `status`), `manual_webhook_recovery`, `kyb_registry_change`, `biometric_retention` (a `data.updated` sent when the session's live biometric data is deleted on schedule) or a `console_*` edit such as `console_registry_edit`, `console_key_people_skip` or `console_key_people_regenerate`." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "The session's workflow. Omitted when the session has none." + }, + "workflow_version": { + "type": "integer", + "description": "Version of the workflow the session ran. Sent together with `workflow_id`." + }, + "vendor_data": { + "type": "string", + "description": "Your `vendor_data` for the session. Omitted when empty." + }, + "vendor_business_id": { + "type": "string", + "description": "Business (KYB) sessions only: the business the session belongs to. Omitted when none." + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Your session `metadata`. Omitted when empty." + }, + "decision": { + "$ref": "#/components/schemas/SessionDecision" + }, + "resubmit_info": { + "type": "object", + "description": "Only when `status` is `Resubmitted`: the steps the user must redo and why.", + "required": [ + "nodes_to_resubmit", + "reasons" + ], + "properties": { + "nodes_to_resubmit": { + "type": "array", + "items": { + "type": "object", + "required": [ + "node_id", + "feature" + ], + "properties": { + "node_id": { + "type": "string", + "description": "Node identifier as it appears in the session's workflow definition (for example `feature_ocr`, `feature_liveness`).", + "example": "feature_poa" + }, + "feature": { + "type": "string", + "description": "Feature type of the node, for example `OCR`, `LIVENESS` or `PROOF_OF_ADDRESS`.", + "example": "PROOF_OF_ADDRESS" + } + } + }, + "description": "Workflow steps the user must redo. The same `{node_id, feature}` entries as `nodes_to_resubmit` in [Update Status](#patch-/v3/session/-sessionId-/update-status/)." + }, + "reasons": { + "type": "object", + "additionalProperties": true, + "description": "Reason per `node_id` in `nodes_to_resubmit`." + } + } + }, + "sandbox_scenario": { + "type": "string", + "nullable": true, + "description": "The simulated outcome armed on a sandbox session, or `null`. Always `null` for live sessions." + } + } + }, + "SessionStatusUpdatedEvent": { + "description": "A verification session changed status. `decision` is sent only for `Approved`, `Declined`, `In Review` and `Abandoned`; it has the shape of `GET /v3/session/{sessionId}/decision/` for destinations on webhook version 3 (the default), and the older reduced shape for version 1 or 2 destinations. Business sessions always receive the version 3 business decision.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "$ref": "#/components/schemas/SessionWebhookEventData" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "status.updated" + ], + "description": "Always `status.updated`." + } + }, + "required": [ + "webhook_type" + ] + } + ] + }, + "SessionDataUpdatedEvent": { + "description": "Data of a verification session changed without a status change, for example after a manual review edit, a step override or an ongoing-monitoring rescreen. `decision` is sent only for `Approved`, `Declined`, `In Review` and `Abandoned`; it has the shape of `GET /v3/session/{sessionId}/decision/` for destinations on webhook version 3 (the default), and the older reduced shape for version 1 or 2 destinations. Business sessions always receive the version 3 business decision.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "$ref": "#/components/schemas/SessionWebhookEventData" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "data.updated" + ], + "description": "Always `data.updated`." + } + }, + "required": [ + "webhook_type" + ] + } + ] + }, + "UserStatusUpdatedEvent": { + "description": "A user changed status. Not sent when the user is created.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "user.status.updated" + ], + "description": "Always `user.status.updated`." + }, + "vendor_user_id": { + "type": "string", + "format": "uuid", + "description": "The user the event is about." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the user, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "User status after the change." + }, + "previous_status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "Status before the change." + } + }, + "required": [ + "webhook_type", + "vendor_user_id", + "vendor_data", + "status", + "previous_status" + ] + } + ] + }, + "UserDataUpdatedEvent": { + "description": "Fields of a user changed. Not sent when the user is created. One save can produce both a status and a data event.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "user.data.updated" + ], + "description": "Always `user.data.updated`." + }, + "vendor_user_id": { + "type": "string", + "format": "uuid", + "description": "The user the event is about." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the user, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "User status after the change." + }, + "changed_fields": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "vendor_data", + "display_name", + "full_name", + "date_of_birth", + "metadata", + "portrait_image", + "issuing_states", + "approved_emails", + "approved_phones", + "features", + "session_count", + "approved_count", + "declined_count", + "in_review_count", + "first_session_at", + "last_session_at" + ] + }, + "description": "Names of the fields that changed." + }, + "changes": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "previous": { + "nullable": true, + "description": "Value before the change. Dates are ISO 8601 strings." + }, + "current": { + "nullable": true, + "description": "Value after the change. Dates are ISO 8601 strings." + } + } + }, + "description": "Previous and current value of each changed field, keyed by field name. Values can be personal data (names, dates of birth, portrait links, emails, phone numbers); handle them accordingly." + } + }, + "required": [ + "webhook_type", + "vendor_user_id", + "vendor_data", + "status", + "changed_fields", + "changes" + ] + } + ] + }, + "BusinessStatusUpdatedEvent": { + "description": "A business changed status. Not sent when the business is created.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "business.status.updated" + ], + "description": "Always `business.status.updated`." + }, + "vendor_business_id": { + "type": "string", + "format": "uuid", + "description": "The business the event is about." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the business, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "Business status after the change." + }, + "previous_status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "Status before the change." + } + }, + "required": [ + "webhook_type", + "vendor_business_id", + "vendor_data", + "status", + "previous_status" + ] + } + ] + }, + "BusinessDataUpdatedEvent": { + "description": "Fields of a business changed. Not sent when the business is created. One save can produce both a status and a data event.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "business.data.updated" + ], + "description": "Always `business.data.updated`." + }, + "vendor_business_id": { + "type": "string", + "format": "uuid", + "description": "The business the event is about." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the business, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "Business status after the change." + }, + "changed_fields": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "vendor_data", + "display_name", + "metadata", + "legal_name", + "registration_number", + "country_code", + "session_count", + "approved_count", + "declined_count", + "in_review_count", + "features", + "first_session_at", + "last_session_at" + ] + }, + "description": "Names of the fields that changed." + }, + "changes": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "previous": { + "nullable": true, + "description": "Value before the change. Dates are ISO 8601 strings." + }, + "current": { + "nullable": true, + "description": "Value after the change. Dates are ISO 8601 strings." + } + } + }, + "description": "Previous and current value of each changed field, keyed by field name." + } + }, + "required": [ + "webhook_type", + "vendor_business_id", + "vendor_data", + "status", + "changed_fields", + "changes" + ] + } + ] + }, + "TransactionCreatedEvent": { + "description": "A transaction was created and screened. Sent for live applications only.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "transaction.created" + ], + "description": "Always `transaction.created`." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Didit's identifier of the transaction." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "The `transaction_id` you supplied at create time, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Monitoring status after the change." + }, + "score": { + "type": "integer", + "description": "Risk score accumulated by rules." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL", + null + ], + "description": "Risk severity, or `null` until a rule or provider sets one." + }, + "amount": { + "type": "string", + "description": "Transaction amount as a decimal string. Unlike the API it is not normalized, so it can carry trailing zeros (up to 18 decimal places)." + }, + "currency": { + "type": "string", + "description": "Currency code or crypto asset symbol of `amount`." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject." + }, + "action_required": { + "type": "object", + "nullable": true, + "oneOf": [ + { + "$ref": "#/components/schemas/TransactionVerificationSessionAction" + }, + { + "$ref": "#/components/schemas/TransactionWalletOwnershipAction" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "verification_session": "#/components/schemas/TransactionVerificationSessionAction", + "wallet_ownership": "#/components/schemas/TransactionWalletOwnershipAction" + } + }, + "description": "Pending end-user action, or `null`. Same object as `action_required` on the transaction; `session_token` is a credential, so never log it." + } + }, + "required": [ + "webhook_type", + "transaction_id", + "txn_id", + "status", + "score", + "severity", + "amount", + "currency", + "direction", + "action_required" + ] + } + ] + }, + "TransactionStatusUpdatedEvent": { + "description": "A transaction changed status, from a rule, a review or an end-user action. Sent for live applications only.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "transaction.status.updated" + ], + "description": "Always `transaction.status.updated`." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Didit's identifier of the transaction." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "The `transaction_id` you supplied at create time, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Monitoring status after the change." + }, + "score": { + "type": "integer", + "description": "Risk score accumulated by rules." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL", + null + ], + "description": "Risk severity, or `null` until a rule or provider sets one." + }, + "amount": { + "type": "string", + "description": "Transaction amount as a decimal string. Unlike the API it is not normalized, so it can carry trailing zeros (up to 18 decimal places)." + }, + "currency": { + "type": "string", + "description": "Currency code or crypto asset symbol of `amount`." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject." + }, + "action_required": { + "type": "object", + "nullable": true, + "oneOf": [ + { + "$ref": "#/components/schemas/TransactionVerificationSessionAction" + }, + { + "$ref": "#/components/schemas/TransactionWalletOwnershipAction" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "verification_session": "#/components/schemas/TransactionVerificationSessionAction", + "wallet_ownership": "#/components/schemas/TransactionWalletOwnershipAction" + } + }, + "description": "Pending end-user action, or `null`. Same object as `action_required` on the transaction; `session_token` is a credential, so never log it." + } + }, + "required": [ + "webhook_type", + "transaction_id", + "txn_id", + "status", + "score", + "severity", + "amount", + "currency", + "direction", + "action_required" + ] + } + ] + }, + "TravelRuleStatusUpdatedEvent": { + "description": "A Travel Rule exchange changed status. Sent on every transition up to and including `FINISHED`, `CANCELLED` or `EXPIRED`, never after. Sent for live applications and, for Travel Rule exchanges, for sandbox applications too (`environment: sandbox`). There is no top-level `status`.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "travel_rule.status.updated" + ], + "description": "Always `travel_rule.status.updated`." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "The transaction the exchange belongs to." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "The `transaction_id` you supplied at create time, or `null`." + }, + "travel_rule_status": { + "type": "string", + "enum": [ + "AWAITING_COUNTERPARTY", + "ON_HOLD", + "COMPLETED", + "FINISHED", + "CANCELLED", + "EXPIRED", + "COUNTERPARTY_MISMATCHED_DATA", + "COUNTERPARTY_VASP_NOT_FOUND", + "COUNTERPARTY_VASP_NOT_REACHABLE", + "UNCONFIRMED_OWNERSHIP", + "COUNTERPARTY_VASP_GENERAL_DECLINE", + "NOT_ENOUGH_COUNTERPARTY_DATA", + "NOT_APPLICABLE" + ], + "description": "Exchange status after the change." + }, + "rail": { + "type": "string", + "enum": [ + "", + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL", + "TEST_VASP" + ], + "description": "Network the exchange runs on, or an empty string when none could be resolved. `TEST_VASP` is a sandbox transfer the Didit Test VASP answered." + }, + "test_vasp": { + "type": "boolean", + "description": "Present and `true` only on a sandbox transfer the Didit Test VASP answered." + }, + "test_vasp_scenario": { + "type": "string", + "description": "Present only with `test_vasp`: the sandbox scenario that decided the outcome, or an empty string." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction of the transfer." + } + }, + "required": [ + "webhook_type", + "transaction_id", + "txn_id", + "travel_rule_status", + "rail", + "direction" + ] + } + ] + }, + "WorkflowComplianceChangedEvent": { + "description": "The compliance status of a published workflow changed after a re-evaluation. One event per workflow. There is no top-level `status`.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "workflow.compliance_changed" + ], + "description": "Always `workflow.compliance_changed`." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "The workflow that changed." + }, + "workflow_version": { + "type": "integer", + "description": "Version that was evaluated." + }, + "workflow_label": { + "type": "string", + "nullable": true, + "description": "Display name of the workflow." + }, + "transition": { + "type": "object", + "required": [ + "from", + "to" + ], + "properties": { + "from": { + "type": "string", + "enum": [ + "compliant", + "gaps_detected", + "pending_change", + "unknown_coverage", + "not_evaluated" + ], + "description": "Status before the re-evaluation." + }, + "to": { + "type": "string", + "enum": [ + "compliant", + "gaps_detected", + "pending_change", + "unknown_coverage", + "not_evaluated" + ], + "description": "Status after the re-evaluation." + } + } + }, + "severity": { + "type": "string", + "enum": [ + "actionable", + "indicative", + "informative" + ], + "description": "`actionable` when gaps were detected, `indicative` for a pending change, `informative` when gaps were resolved." + }, + "kb_version": { + "type": "string", + "description": "Version of the regulatory knowledge base used." + } + }, + "required": [ + "webhook_type", + "workflow_id", + "workflow_version", + "workflow_label", + "transition", + "severity", + "kb_version" + ] + } + ] + }, + "WebhookEventPayload": { + "description": "Body of a signed webhook delivery, discriminated by `webhook_type`. `activity.created` can be subscribed to but is not sent today. Treat any other `webhook_type` as a new event type and keep the raw body.", + "oneOf": [ + { + "$ref": "#/components/schemas/SessionStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/SessionDataUpdatedEvent" + }, + { + "$ref": "#/components/schemas/UserStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/UserDataUpdatedEvent" + }, + { + "$ref": "#/components/schemas/BusinessStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/BusinessDataUpdatedEvent" + }, + { + "$ref": "#/components/schemas/TransactionCreatedEvent" + }, + { + "$ref": "#/components/schemas/TransactionStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/TravelRuleStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/WorkflowComplianceChangedEvent" + }, + { + "$ref": "#/components/schemas/TransactionRuleSuggestionsCreatedEvent" + } + ], + "discriminator": { + "propertyName": "webhook_type", + "mapping": { + "status.updated": "#/components/schemas/SessionStatusUpdatedEvent", + "data.updated": "#/components/schemas/SessionDataUpdatedEvent", + "user.status.updated": "#/components/schemas/UserStatusUpdatedEvent", + "user.data.updated": "#/components/schemas/UserDataUpdatedEvent", + "business.status.updated": "#/components/schemas/BusinessStatusUpdatedEvent", + "business.data.updated": "#/components/schemas/BusinessDataUpdatedEvent", + "transaction.created": "#/components/schemas/TransactionCreatedEvent", + "transaction.status.updated": "#/components/schemas/TransactionStatusUpdatedEvent", + "travel_rule.status.updated": "#/components/schemas/TravelRuleStatusUpdatedEvent", + "workflow.compliance_changed": "#/components/schemas/WorkflowComplianceChangedEvent", + "transaction_rule_suggestions.created": "#/components/schemas/TransactionRuleSuggestionsCreatedEvent" + } + } + }, + "KYBRegistrySearchResolvedCallback": { + "type": "object", + "description": "Body of the callback sent to the `webhook_url` of `POST /v3/kyb/search/` once the registry search resolves. It is not signed: the request carries `X-Didit-Unsigned-Callback: true` and none of the signature headers, so confirm it by matching `request_id` against a search you started. Uses `event_type`, not `webhook_type`.", + "required": [ + "event_id", + "event_type", + "request_id", + "vendor_data", + "metadata", + "search_status", + "search_resolved", + "kyb_registry", + "timestamp", + "created_at" + ], + "properties": { + "event_id": { + "type": "string", + "format": "uuid", + "description": "Identifier of the callback." + }, + "event_type": { + "type": "string", + "enum": [ + "kyb.registry_search.resolved" + ], + "description": "Always `kyb.registry_search.resolved`." + }, + "request_id": { + "type": "string", + "format": "uuid", + "description": "The `request_id` returned by `POST /v3/kyb/search/`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` you sent." + }, + "search_status": { + "type": "string", + "description": "Always `resolved` today." + }, + "search_resolved": { + "type": "boolean", + "description": "`true` when `search_status` is `resolved`." + }, + "kyb_registry": { + "$ref": "#/components/schemas/KYBRegistrySearchResults" + }, + "timestamp": { + "type": "integer", + "description": "Unix time in seconds at which the callback was queued." + }, + "created_at": { + "type": "integer", + "description": "Unix time in seconds at which the callback was queued." + } + } + }, + "AMLScreening": { + "type": "object", + "properties": { + "node_id": { + "type": "string", + "description": "Unique identifier for this AML step in the workflow graph." + }, + "status": { + "type": "string", + "description": "Feature-level status of this AML screening. Independent of the top-level session `status` — a session can reach its overall decision while an individual feature stays `In Review`.", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ] + }, + "total_hits": { + "type": "integer", + "description": "Total number of matches found" + }, + "entity_type": { + "type": "string", + "description": "Type of entity screened: person or company" + }, + "hits": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Didit's stable identifier for the hit: `hit_` followed by 24 hexadecimal characters. The same matched entity keeps the same `id` when the subject is screened again. Pass it as `hit_id` to the AML hit review endpoints." + }, + "url": { + "type": "string", + "format": "uri", + "description": "Source URL for the match" + }, + "match": { + "type": "boolean", + "description": "Whether this is a confirmed match" + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 1, + "description": "Deprecated weighted match score in the 0-1 range (e.g. `0.73`). Use `match_score` (0-100) instead.", + "deprecated": true + }, + "target": { + "type": "boolean", + "nullable": true, + "description": "Whether the matched entity is the direct target of the listing (as opposed to a related party). `null` when the source does not specify it." + }, + "caption": { + "type": "string", + "description": "Caption or title of the match" + }, + "datasets": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "PEP", + "PEP Level 1", + "PEP Level 2", + "PEP Level 3", + "PEP Level 4", + "Sanctions", + "Adverse Media", + "Warnings and Regulatory Enforcement", + "Fitness and Probity", + "SIP", + "SIE", + "Insolvency", + "Other Lists" + ] + }, + "description": "List of dataset categories where the match was found. Indicates whether the entity appeared in PEP lists (with level), sanctions databases, adverse media, warnings/regulatory enforcement, or other compliance datasets. `Other Lists` when the entity's source states none of these categories." + }, + "features": { + "type": "object", + "nullable": true, + "description": "Additional features of the match" + }, + "rca_name": { + "type": "string", + "description": "RCA name if applicable" + }, + "last_seen": { + "type": "string", + "format": "date-time", + "description": "Date when match was last seen" + }, + "risk_view": { + "type": "object", + "properties": { + "crimes": { + "type": "object", + "properties": { + "score": { + "type": "integer" + }, + "weightage": { + "type": "integer" + }, + "risk_level": { + "type": "string" + }, + "risk_scores": { + "type": "object" + } + } + }, + "countries": { + "type": "object", + "properties": { + "score": { + "type": "integer" + }, + "weightage": { + "type": "integer" + }, + "risk_level": { + "type": "string" + }, + "risk_scores": { + "type": "object" + } + } + }, + "categories": { + "type": "object", + "properties": { + "score": { + "type": "integer" + }, + "weightage": { + "type": "integer" + }, + "risk_level": { + "type": "string" + }, + "risk_scores": { + "type": "object" + } + } + }, + "custom_list": { + "type": "object" + } + } + }, + "first_seen": { + "type": "string", + "format": "date-time", + "description": "Date when match was first seen" + }, + "properties": { + "type": "object", + "properties": { + "name": { + "type": "array", + "items": { + "type": "string" + } + }, + "alias": { + "type": "array", + "items": { + "type": "string" + } + }, + "notes": { + "type": "array", + "items": { + "type": "string" + } + }, + "title": { + "type": "string", + "nullable": true + }, + "gender": { + "type": "array", + "items": { + "type": "string" + } + }, + "height": { + "type": "string", + "nullable": true + }, + "topics": { + "type": "string", + "nullable": true + }, + "weight": { + "type": "string", + "nullable": true + }, + "address": { + "type": "string", + "nullable": true + }, + "country": { + "type": "string", + "nullable": true + }, + "website": { + "type": "string", + "nullable": true + }, + "eyeColor": { + "type": "string", + "nullable": true + }, + "keywords": { + "type": "string", + "nullable": true + }, + "lastName": { + "type": "array", + "items": { + "type": "string" + } + }, + "position": { + "type": "string", + "nullable": true + }, + "religion": { + "type": "string", + "nullable": true + }, + "birthDate": { + "type": "array", + "items": { + "type": "string" + } + }, + "education": { + "type": "array", + "items": { + "type": "string" + } + }, + "ethnicity": { + "type": "string", + "nullable": true + }, + "firstName": { + "type": "array", + "items": { + "type": "string" + } + }, + "hairColor": { + "type": "string", + "nullable": true + }, + "weakAlias": { + "type": "string", + "nullable": true + }, + "birthPlace": { + "type": "array", + "items": { + "type": "string" + } + }, + "modifiedAt": { + "type": "string", + "nullable": true + }, + "wikidataId": { + "type": "string", + "nullable": true + }, + "citizenship": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + } + } + }, + "match_score": { + "type": "number", + "description": "Weighted identity confidence score (0-100) calculated from name similarity, date of birth, and country matching. Used to determine if hit is a False Positive or Possible Match.", + "format": "float", + "nullable": true + }, + "risk_score": { + "type": "number", + "description": "Entity risk score (0-100). Used to determine final AML status (Approved/In Review/Declined) based on the highest risk_score among non-False-Positive hits." + }, + "review_status": { + "type": "string", + "description": "Review status for this hit. Can be 'Unreviewed', 'Confirmed Match', 'False Positive', or 'Inconclusive'.", + "enum": [ + "Unreviewed", + "Confirmed Match", + "False Positive", + "Inconclusive" + ] + }, + "score_breakdown": { + "type": "object", + "description": "Detailed breakdown of how the match_score was calculated.", + "properties": { + "name_score": { + "type": "integer", + "description": "Raw name similarity percentage (0-100)." + }, + "name_weight": { + "type": "integer", + "description": "Original weight for name from config (0-100)." + }, + "name_weight_normalized": { + "type": "number", + "description": "Normalized weight for name after re-weighting for missing data (0-100)." + }, + "name_contribution": { + "type": "number", + "description": "Points contributed by name to the total score." + }, + "dob_score": { + "type": "integer", + "description": "DOB match score (-100 to 100)." + }, + "dob_weight": { + "type": "integer", + "description": "Original weight for DOB from config (0-100)." + }, + "dob_weight_normalized": { + "type": "number", + "description": "Normalized weight for DOB after re-weighting for missing data (0-100)." + }, + "dob_contribution": { + "type": "number", + "description": "Points contributed by DOB to the total score." + }, + "country_score": { + "type": "integer", + "description": "Country match score (-100 to 100)." + }, + "country_weight": { + "type": "integer", + "description": "Original weight for country from config (0-100)." + }, + "country_weight_normalized": { + "type": "number", + "description": "Normalized weight for country after re-weighting for missing data (0-100)." + }, + "country_contribution": { + "type": "number", + "description": "Points contributed by country to the total score." + }, + "document_number_match_type": { + "type": "string", + "description": "Result of document number 'Golden Key' evaluation.", + "enum": [ + "MATCH", + "NEUTRAL", + "HARD_MISMATCH" + ] + }, + "document_number_effect": { + "type": "string", + "description": "Human-readable description of the document number effect on the score." + }, + "total_score": { + "type": "integer", + "description": "Final weighted match score (0-100)." + } + } + }, + "pep_matches": { + "type": "array", + "items": { + "type": "object", + "properties": { + "aliases": { + "type": "array", + "items": { + "type": "string" + } + }, + "education": { + "type": "array", + "items": { + "type": "string" + } + }, + "list_name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "source_url": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + }, + "matched_name": { + "type": "string" + }, + "pep_position": { + "type": "string" + }, + "date_of_birth": { + "type": "string" + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + } + }, + "place_of_birth": { + "type": "string" + } + } + } + }, + "linked_entities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "array", + "items": { + "type": "string" + } + }, + "active": { + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "type": "array", + "items": { + "type": "string" + } + }, + "details": { + "type": "array", + "items": { + "type": "string" + } + }, + "relation": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "warning_matches": { + "type": "array", + "description": "List of warning and regulatory enforcement matches. Includes entries from categories: Warning, SIP, Warnings and Regulatory Enforcement, Fitness and Probity, SIE, and Insolvency.", + "items": { + "type": "object", + "properties": { + "list_name": { + "type": "string", + "description": "Name of the warning list publisher" + }, + "matched_name": { + "type": "string", + "description": "Name matched in the warning list" + }, + "description": { + "type": "string", + "description": "Description of the warning entry" + }, + "publisher": { + "type": "string", + "description": "Publisher or source of the warning" + }, + "source_url": { + "type": "string", + "format": "uri", + "description": "URL to the source of the warning" + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Additional source URLs" + }, + "countries": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Countries associated with the warning" + }, + "created_at": { + "type": "string", + "nullable": true, + "description": "Date the warning entry was created" + }, + "updated_at": { + "type": "string", + "nullable": true, + "description": "Date the warning entry was last updated" + }, + "additional_data": { + "type": "object", + "description": "Additional data from the warning source. May contain a `data` object with `case_details` array.", + "properties": { + "data": { + "type": "object", + "properties": { + "case_details": { + "type": "array", + "description": "Details of warning cases associated with this match", + "items": { + "type": "object", + "properties": { + "case_type": { + "type": "string", + "description": "Type of the warning case", + "enum": [ + "Accusation", + "Arrest", + "Bribery", + "Criminal", + "Fraud", + "Illegal Fundraising", + "Non-conviction Terror", + "Proclaimed Offender", + "Regulatory Enforcement", + "Terrorism", + "Terrorism-related", + "Terrorism-related listing" + ] + } + } + } + } + } + } + } + } + } + } + }, + "sanction_matches": { + "type": "array", + "description": "List of sanction list matches found for this entity.", + "items": { + "type": "object", + "properties": { + "list_name": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Name(s) of the sanction list(s)" + }, + "matched_name": { + "type": "string", + "description": "Name matched in the sanction list" + }, + "description": { + "type": "string", + "description": "Description of the sanction entry" + }, + "reason": { + "type": "string", + "nullable": true, + "description": "Free-text reason why the entity was sanctioned. Joined from the sanction list's reason entries. Examples: 'Executive Order 13224 (Terrorism)', 'Support for actions undermining Ukraine's sovereignty', 'Listed due to national security concerns.'" + }, + "source_url": { + "type": "string", + "format": "uri", + "description": "URL to the source of the sanction" + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Additional source URLs" + }, + "details": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Additional details about the sanction" + }, + "legal_basis": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Legal basis for the sanction" + }, + "listed_on": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Date(s) when the entity was listed" + }, + "sanction_list": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Sanction list identifier(s)" + }, + "sanction_program": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Sanction program(s) the entity is listed under" + }, + "sanctioning_authority": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Authority/authorities that imposed the sanction" + }, + "updated_on": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Date(s) when the sanction entry was last updated" + }, + "additional_data": { + "type": "object", + "description": "Additional data from the sanction source not captured in the main fields" + } + } + } + }, + "adverse_media_details": { + "type": "object", + "properties": { + "sentiment": { + "type": "string" + }, + "entity_type": { + "type": "string" + }, + "sentiment_score": { + "type": "integer" + }, + "adverse_keywords": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + } + }, + "adverse_media_matches": { + "type": "array", + "items": { + "type": "object", + "properties": { + "country": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "headline": { + "type": "string" + }, + "sentiment": { + "type": "string" + }, + "thumbnail": { + "type": "string", + "format": "uri" + }, + "source_url": { + "type": "string", + "format": "uri" + }, + "author_name": { + "type": "string", + "nullable": true + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + } + }, + "adverse_keywords": { + "type": "array", + "items": { + "type": "string" + } + }, + "sentiment_score": { + "type": "integer" + }, + "publication_date": { + "type": "string", + "format": "date-time" + } + } + } + }, + "additional_information": { + "type": "object" + } + } + } + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Overall AML screening score — the highest risk score across all hits (float, 0-100). `null` until computed." + }, + "screened_data": { + "type": "object", + "properties": { + "full_name": { + "type": "string" + }, + "nationality": { + "type": "string" + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "document_number": { + "type": "string", + "nullable": true + } + } + }, + "is_ongoing_monitoring_enabled": { + "type": "boolean", + "description": "Whether AML ongoing monitoring is enabled for this screening. When `true`, the entity is re-screened periodically and any new hits update the report." + }, + "next_ongoing_monitoring_bill_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date (YYYY-MM-DD) when this ongoing-monitoring subscription will next be billed. Computed as the date of the last ongoing-monitoring billing plus 365 days when `is_ongoing_monitoring_enabled = true`; otherwise `null`. This is a billing date, not a screening date - use `last_ongoing_screening_at` for that." + }, + "last_ongoing_screening_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When ongoing monitoring last completed a re-screening of this record, whether or not the result changed. `null` when the record has never been re-screened; it is never back-filled from the enablement or billing date." + }, + "last_ongoing_screening_datasets_updated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "The most recent AML dataset update date the provider reported at the time of that screening. `null` when the provider reported no dataset dates; never estimated." + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "NFC", + "LIVENESS", + "FACEMATCH", + "PROOF_OF_ADDRESS", + "PHONE", + "EMAIL", + "AML", + "LOCATION", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "GEOLOCATION", + "SIGNATURE" + ], + "description": "The feature that generated this warning. The value identifies the feature block the warning belongs to. Within an AML screening this is typically `AML`, but the enum above is authoritative across all feature warnings on the decision payload." + }, + "risk": { + "type": "string" + }, + "additional_data": { + "type": "object", + "nullable": true + }, + "log_type": { + "type": "string" + }, + "short_description": { + "type": "string" + }, + "long_description": { + "type": "string" + }, + "node_id": { + "type": "string", + "description": "The workflow graph node ID that generated this warning" + } + } + } + } + } + }, + "MonitoringEvent": { + "type": "object", + "description": "One stored monitoring event as returned by the list and subject-timeline endpoints. Restricted evidence (payload, device context, rule evidence) is only returned by `GET /v3/events/{event_uuid}/`.", + "required": [ + "event_uuid", + "event_id", + "event_category", + "action_type", + "occurred_at", + "received_at", + "status", + "score", + "subject_vendor_data", + "subject_entity_type", + "subject_uuid", + "counterparty_vendor_data", + "session_id", + "device_fingerprint", + "ip_address", + "ip_country", + "amount", + "currency", + "game_id", + "round_id", + "bonus_id", + "affiliate_code", + "payment_reference_id", + "retention_class", + "rules_matched_count", + "correlation_pending", + "is_droppable" + ], + "properties": { + "event_uuid": { + "type": "string", + "format": "uuid", + "description": "Didit identifier of the event. Use it as `{event_uuid}` in the retrieve and delete calls." + }, + "event_id": { + "type": "string", + "maxLength": 128, + "description": "Your own identifier, the idempotency key you sent." + }, + "event_category": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ], + "description": "Event category." + }, + "action_type": { + "type": "string", + "maxLength": 128, + "description": "What happened within the category, as you sent it." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "description": "When the event happened on your side." + }, + "received_at": { + "type": "string", + "format": "date-time", + "description": "When Didit accepted the event." + }, + "status": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ], + "description": "`ACCEPTED`: stored, no matched rule changed its standing (the normal answer for most events, and always the answer in a sandbox application, where no rules run). `APPROVED`: a rule matched and the score stayed under your review threshold. `IN_REVIEW`: a rule pushed the score over the review threshold or set the status. `DECLINED`: a rule declined it or the score crossed your decline threshold." + }, + "score": { + "type": "integer", + "description": "Accumulated rule score. Starts at 0; rule `add_score` actions move it (negative deltas are allowed)." + }, + "subject_vendor_data": { + "type": "string", + "maxLength": 255, + "description": "`subject.vendor_data` of the event: your id for the user or company." + }, + "subject_entity_type": { + "type": "string", + "description": "`individual` or `company`: which kind of subject `subject_vendor_data` names.", + "example": "individual" + }, + "subject_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "UUID of the user (individual) or company the event resolved to, or null when that record no longer exists." + }, + "counterparty_vendor_data": { + "type": "string", + "nullable": true, + "description": "The other player of a P2P event (promoted from `payload.counterparty_vendor_data`).", + "maxLength": 255 + }, + "session_id": { + "type": "string", + "nullable": true, + "description": "`session.session_id` sent with the event.", + "maxLength": 255 + }, + "device_fingerprint": { + "type": "string", + "nullable": true, + "description": "`device_context.fingerprint` sent with the event.", + "maxLength": 255 + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "IP address of the event. On the server lane this is `device_context.network_context.ip_address` as you sent it; on the device lane it is the IP of the request.", + "maxLength": 64 + }, + "ip_country": { + "type": "string", + "nullable": true, + "description": "Country code of `ip_address` from IP enrichment, or null when the address was not enriched (missing, private, or low-value telemetry whose address was not already known).", + "example": "ES" + }, + "amount": { + "type": "string", + "nullable": true, + "description": "Amount as a decimal string with 18 decimal places, from `transaction_context.amount`, `payload.amount` or `payload.stake_amount`, in that order.", + "example": "250.000000000000000000" + }, + "currency": { + "type": "string", + "nullable": true, + "description": "Currency from `transaction_context.currency` or `payload.currency`.", + "maxLength": 10 + }, + "game_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.game_id`.", + "maxLength": 128 + }, + "round_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.round_id`.", + "maxLength": 128 + }, + "bonus_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.bonus_id`.", + "maxLength": 128 + }, + "affiliate_code": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.affiliate_code`.", + "maxLength": 128 + }, + "payment_reference_id": { + "type": "string", + "nullable": true, + "description": "Payment reference from `transaction_context.payment_reference_id` or `payload.payment_reference_id`.", + "maxLength": 255 + }, + "retention_class": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ], + "description": "Retention class. Payment and dispute events are `monetary_or_compliance`; security and responsible-gambling events are `risk_relevant`; `session.page_view`, `session.sdk_heartbeat`, `session.sdk_loaded`, `session.device_seen`, `session.bot_signal_observed`, `bonus.promotion_viewed` and `affiliate.referral_clicked` are `low_value`; everything else is `standard`. An event an active rule matched is promoted to `risk_relevant` (never demoted)." + }, + "rules_matched_count": { + "type": "integer", + "minimum": 0, + "description": "Number of rules that matched the event, including rules in `TEST` mode (which record evidence but apply no actions)." + }, + "correlation_pending": { + "type": "boolean", + "description": "`true` while an asynchronous rule (cross-subject correlation, or a sequence longer than 24 hours) still has to run for this event. It resolves within minutes and `status`, `score` and `rules_matched_count` update in place." + }, + "is_droppable": { + "type": "boolean", + "description": "Whether an SDK under backpressure may drop events of this kind. `false` for every `security`, `payment`, `dispute` and `responsible_gambling` event and for `gameplay.bet_placed`, `gameplay.bet_settled` and `gameplay.p2p_settlement`." + } + } + }, + "MonitoringEventDetail": { + "description": "One monitoring event with its full envelope, enrichment and rule evidence. Every field of `MonitoringEvent` plus the fields below.", + "allOf": [ + { + "$ref": "#/components/schemas/MonitoringEvent" + }, + { + "type": "object", + "required": [ + "payload", + "custom_values", + "device_context", + "field_sources", + "session_sequence", + "table_id", + "hand_id", + "match_id", + "market_id", + "side", + "role", + "net_result_amount", + "payment_fingerprint", + "instrument_reference_hash", + "withdrawal_address_hash", + "dispute_id", + "chargeback_id", + "changed_field", + "identity_match_ref", + "schema_version", + "expires_at", + "rules_evaluated_count", + "rule_runs", + "ip_enrichment", + "subject_profile", + "tags" + ], + "properties": { + "payload": { + "type": "object", + "additionalProperties": true, + "description": "The category fields you sent in `payload`." + }, + "custom_values": { + "type": "object", + "additionalProperties": true, + "description": "The scalar `custom_values` you sent. Didit adds `vpn_or_proxy_detected: true` or `is_data_center: true` when IP enrichment flags the address and you did not set them." + }, + "device_context": { + "type": "object", + "additionalProperties": true, + "description": "The device context as stored: your `device_context` (including `fingerprint`, `persistent_device_id` and `bot_score` when sent) with `network_context` completed by IP enrichment (`ip_country`, `ip_country_code`, `ip_city`, `asn_number`, `asn_organization`, `is_vpn_or_tor`, `is_data_center`, `proxy_type`, `connection_type`) for keys you left empty." + }, + "field_sources": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "customer", + "sdk", + "server" + ] + }, + "description": "Field path to provenance: `customer` (sent by your backend), `sdk` (sent through the device lane) or `server` (derived by Didit, such as `received_at` and enriched network fields). Check it before acting on a match." + }, + "session_sequence": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "`session.sequence` sent with the event." + }, + "table_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.table_id`.", + "maxLength": 128 + }, + "hand_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.hand_id`.", + "maxLength": 128 + }, + "match_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.match_id`.", + "maxLength": 128 + }, + "market_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.market_id`.", + "maxLength": 128 + }, + "side": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.side`.", + "maxLength": 64 + }, + "role": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.role`.", + "maxLength": 64 + }, + "net_result_amount": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.net_result_amount`, as a decimal string with 18 decimal places.", + "example": "-120.000000000000000000" + }, + "payment_fingerprint": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.payment_fingerprint`.", + "maxLength": 255 + }, + "instrument_reference_hash": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.instrument_reference_hash`.", + "maxLength": 255 + }, + "withdrawal_address_hash": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.withdrawal_address_hash` (or `payload.destination_reference_hash`).", + "maxLength": 255 + }, + "dispute_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.dispute_id`.", + "maxLength": 255 + }, + "chargeback_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.chargeback_id`.", + "maxLength": 255 + }, + "changed_field": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.changed_field` (security change events).", + "maxLength": 128 + }, + "identity_match_ref": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.identity_match_ref` (responsible-gambling registry matches).", + "maxLength": 255 + }, + "schema_version": { + "type": "integer", + "description": "Version of the event envelope schema. Currently `1`.", + "example": 1 + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the event expires under its retention class and is deleted: `occurred_at` plus the class period (14, 30 or 90 days by default, adjustable per application). Null for `monetary_or_compliance` events, which follow transaction retention." + }, + "rules_evaluated_count": { + "type": "integer", + "minimum": 0, + "description": "Number of event rules evaluated for this event." + }, + "rule_runs": { + "type": "array", + "description": "Every rule that matched the event, newest first.", + "items": { + "$ref": "#/components/schemas/MonitoringEventRuleRun" + } + }, + "ip_enrichment": { + "type": "object", + "nullable": true, + "description": "Enrichment of the event's IP address, or null when the address was not enriched. See [IP enrichment](https://docs.didit.me/transaction-monitoring/ip-enrichment).", + "properties": { + "ip_country_code": { + "type": "string", + "nullable": true, + "description": "Country code of the address." + }, + "ip_city": { + "type": "string", + "nullable": true, + "description": "City of the address." + }, + "asn_number": { + "type": "integer", + "nullable": true, + "description": "Autonomous system number." + }, + "asn_organization": { + "type": "string", + "nullable": true, + "description": "Organization that operates the autonomous system." + }, + "is_vpn_or_tor": { + "type": "boolean", + "description": "Whether the address is a known VPN or Tor exit." + }, + "is_data_center": { + "type": "boolean", + "description": "Whether the address belongs to a hosting or data-centre network." + }, + "proxy_type": { + "type": "string", + "nullable": true, + "description": "Proxy classification of the address as an opaque string, or null." + }, + "last_enriched_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the address was last enriched." + } + }, + "required": [ + "ip_country_code", + "ip_city", + "asn_number", + "asn_organization", + "is_vpn_or_tor", + "is_data_center", + "proxy_type", + "last_enriched_at" + ] + }, + "subject_profile": { + "type": "object", + "nullable": true, + "description": "Cached email and phone risk signals of the subject, or null when no profile exists for the subject.", + "properties": { + "email_risk_score": { + "type": "integer", + "nullable": true, + "description": "Email risk score." + }, + "email_deliverability": { + "type": "string", + "nullable": true, + "description": "Email deliverability verdict." + }, + "email_is_disposable": { + "type": "boolean", + "nullable": true, + "description": "Whether the email address is disposable." + }, + "email_is_breached": { + "type": "boolean", + "nullable": true, + "description": "Whether the email address appears in known data breaches." + }, + "email_social_count": { + "type": "integer", + "nullable": true, + "description": "Number of online accounts linked to the email address." + }, + "email_social_providers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the online services where an account linked to the email address was found." + }, + "phone_risk_score": { + "type": "integer", + "nullable": true, + "description": "Phone risk score." + }, + "phone_line_type": { + "type": "string", + "nullable": true, + "description": "Phone line type." + }, + "phone_carrier": { + "type": "string", + "nullable": true, + "description": "Phone carrier name." + }, + "phone_social_count": { + "type": "integer", + "nullable": true, + "description": "Number of online accounts linked to the phone number." + }, + "phone_social_providers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the online services where an account linked to the phone number was found." + }, + "source_provider": { + "type": "string", + "nullable": true, + "description": "Opaque identifier of the source of the profile." + }, + "last_enriched_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the profile was last refreshed." + } + }, + "required": [ + "email_risk_score", + "email_deliverability", + "email_is_disposable", + "email_is_breached", + "email_social_count", + "email_social_providers", + "phone_risk_score", + "phone_line_type", + "phone_carrier", + "phone_social_count", + "phone_social_providers", + "source_provider", + "last_enriched_at" + ] + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the tags attached to the event (rule `add_tags` actions)." + } + } + } + ] + }, + "MonitoringEventRuleRun": { + "type": "object", + "properties": { + "rule_uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the transaction monitoring rule." + }, + "rule_title": { + "type": "string", + "description": "Title of the rule." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Library key when the rule was installed from the rules library, otherwise null." + }, + "matched": { + "type": "boolean", + "description": "Always `true`: only matching runs are recorded." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "TEST" + ], + "description": "Mode of the rule when it ran. A `TEST` run records evidence but applied no actions." + }, + "is_async": { + "type": "boolean", + "description": "`true` when the run came from the asynchronous correlation pass rather than the ingest request." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL", + null + ], + "description": "Severity of the rule. Null for `TEST` runs and for rules without a severity." + }, + "score_delta": { + "type": "integer", + "description": "How much this run changed the event's score (0 for `TEST` runs)." + }, + "action_summary": { + "type": "string", + "nullable": true, + "description": "Short summary of the actions applied, for example `score +40; add_tag:VPN_LOGIN`. Null when nothing was applied." + }, + "matched_payload": { + "type": "array", + "description": "The conditions and aggregations that matched. Condition entries carry `field`, `operator`, `expected_value`, `current_value`, `matched`, `group_index`, `group_logic` and `field_source` (`server`, `sdk` or `customer`); aggregation entries carry `metric`, `field`, `operator`, `expected_value`, `computed_value`, `window`, `filters`, `matched` and `sample_references`.", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the rule run was recorded." + } + }, + "required": [ + "rule_uuid", + "rule_title", + "library_key", + "matched", + "mode", + "is_async", + "severity", + "score_delta", + "action_summary", + "matched_payload", + "created_at" + ] + }, + "MonitoringEventPage": { + "type": "object", + "description": "One page of monitoring events, newest `occurred_at` first.", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Number of matching events, exact up to 100 and capped at 100 above that. Do not compute page counts from it; follow `next` until it is null." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or null on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or null on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MonitoringEvent" + } + } + } + }, + "MonitoringEventCreateRequest": { + "type": "object", + "description": "The event envelope, the same for every category. camelCase aliases are accepted: `eventId`, `eventCategory` (or `category`), `actionType`, `occurredAt`, `timeZone` (or `zoneId`), `deviceContext` (or `device`), `transactionContext`, `customValues` (or `props`); inside `subject`: `vendorData`, `entityType`, `fullName`; inside `session`: `sessionId`, `pageUrl`; inside `transaction_context`: `paymentReferenceId`.", + "required": [ + "event_id", + "event_category", + "action_type", + "occurred_at", + "subject" + ], + "properties": { + "event_id": { + "type": "string", + "maxLength": 128, + "description": "Your identifier for the event, unique per application. It is the idempotency key: resending the same `event_category`, `action_type`, `occurred_at`, `subject.vendor_data` and `payload` under it is a replay, anything different is a conflict.", + "example": "login-2026-10-17-000001" + }, + "event_category": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ], + "description": "Event category.", + "example": "auth" + }, + "action_type": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "What happened within the category. Open vocabulary; the managed actions listed in [Event ingestion](https://docs.didit.me/transaction-monitoring/events) (for example `login`, `deposit_completed`, `chargeback_opened`) unlock typed validation and managed rule fields. Leading and trailing whitespace is trimmed.", + "example": "login" + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "description": "When the event happened on your side, ISO 8601 (a value without an offset is read as UTC). Must be no more than 7 days in the past and no more than 5 minutes in the future.", + "example": "2026-10-17T09:15:02.481Z" + }, + "time_zone": { + "type": "string", + "nullable": true, + "maxLength": 64, + "description": "Time zone of the occurrence on your side, for example `Europe/Madrid`. Stored with the event; not returned by the read endpoints." + }, + "subject": { + "type": "object", + "description": "The user or company the event belongs to. It resolves to the user (or company) with this `vendor_data` in your application; one is created when none exists.", + "required": [ + "vendor_data" + ], + "properties": { + "vendor_data": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "Your id for the subject.", + "example": "user-42" + }, + "entity_type": { + "type": "string", + "maxLength": 32, + "default": "individual", + "description": "`company` resolves the subject to a company; any other value (default `individual`) resolves it to an individual. The same `vendor_data` can name one of each.", + "example": "individual" + }, + "full_name": { + "type": "string", + "nullable": true, + "maxLength": 512, + "description": "Display name, used only when this event creates the user or company." + }, + "email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Validated as an email address; not stored on the event." + }, + "phone": { + "type": "string", + "nullable": true, + "maxLength": 32, + "description": "Validated; not stored on the event." + }, + "country": { + "type": "string", + "nullable": true, + "maxLength": 3, + "description": "Validated; not stored on the event." + } + } + }, + "business": { + "type": "object", + "description": "The business the event belongs to, when it differs from the subject. Only `vendor_data` is read.", + "additionalProperties": true, + "properties": { + "vendor_data": { + "type": "string", + "maxLength": 255, + "description": "Your id for the business. Filterable as `business_vendor_data`.", + "example": "merchant-7" + } + } + }, + "session": { + "type": "object", + "description": "Your session context for the event.", + "properties": { + "session_id": { + "type": "string", + "nullable": true, + "maxLength": 255, + "description": "Your session identifier. Filterable and returned as `session_id`." + }, + "sequence": { + "type": "integer", + "nullable": true, + "description": "Position of the event within the session. Returned as `session_sequence`." + }, + "page_url": { + "type": "string", + "nullable": true, + "maxLength": 2048, + "description": "Validated; not stored on the event." + }, + "referrer": { + "type": "string", + "nullable": true, + "maxLength": 2048, + "description": "Validated; not stored on the event." + } + } + }, + "device_context": { + "type": "object", + "additionalProperties": true, + "description": "Device and network context, stored as sent and returned by `GET /v3/events/{event_uuid}/`. On this server lane Didit does not see the end user's connection, so the IP enriched and used by rules is `network_context.ip_address` as you send it.", + "properties": { + "fingerprint": { + "type": "string", + "maxLength": 255, + "description": "Device fingerprint. Filterable as `device_fingerprint`." + }, + "persistent_device_id": { + "type": "string", + "maxLength": 255, + "description": "Your persistent device identifier." + }, + "bot_score": { + "type": "integer", + "description": "Bot score from your own detection, as an integer." + }, + "network_context": { + "type": "object", + "additionalProperties": true, + "properties": { + "ip_address": { + "type": "string", + "maxLength": 64, + "description": "End user's IP address. Public addresses are enriched once per unique address.", + "example": "203.0.113.42" + } + } + } + } + }, + "transaction_context": { + "type": "object", + "description": "Money context. Non-empty values here also satisfy the required payload keys of the action.", + "properties": { + "amount": { + "type": "string", + "nullable": true, + "description": "Amount as a decimal string (a number is also accepted).", + "example": "1000.00" + }, + "currency": { + "type": "string", + "nullable": true, + "maxLength": 10, + "description": "Currency code." + }, + "direction": { + "type": "string", + "nullable": true, + "maxLength": 16, + "description": "Direction of the money movement, for example `inbound` or `outbound`." + }, + "payment_reference_id": { + "type": "string", + "nullable": true, + "maxLength": 255, + "description": "Your payment reference. Filterable as `payment_reference_id`." + } + } + }, + "payload": { + "type": "object", + "additionalProperties": true, + "description": "The category's domain fields, at most 16384 bytes once JSON-encoded. Some actions require keys (in `payload` or `transaction_context`): `gameplay.p2p_settlement` needs `game_id`, `counterparty_vendor_data`, `stake_amount`, `net_result_amount`, `role` and one of `hand_id`, `round_id`, `match_id`; `gameplay.bet_placed` and `gameplay.bet_settled` need `game_id`; `payment.deposit_initiated`, `payment.deposit_completed`, `payment.withdrawal_requested` and `payment.withdrawal_completed` need `payment_reference_id`, `amount`, `currency`, `payment_method`, `status`; `payment.withdrawal_address_changed` needs `withdrawal_address_hash`; `dispute.chargeback_opened`, `dispute.chargeback_won` and `dispute.chargeback_lost` need `payment_reference_id` and one of `dispute_id`, `chargeback_id`; `session.bot_signal_observed` needs `telemetry_schema_version` and one of `bot_score`, `actions_per_minute`, `timing_variance_ms`, `pointer_entropy`, `keystroke_entropy`, `reaction_time_ms_p50`; `responsible_gambling.self_exclusion_match` needs `registry_source`, `exclusion_status`, `identity_match_ref`. Every `security` action ending in `_changed` needs `changed_field`, plus `previous_reference_hash` or `new_reference_hash` when the field is `password`, `mfa_method`, `mfa`, `email`, `phone`, `withdrawal_address`, `payout_method` or `api_key`. Send hashes and references, never a card number, a full withdrawal address, a password hash or an MFA secret." + }, + "custom_values": { + "type": "object", + "description": "Up to 100 keys of your own, addressable from rules as `custom_values.`. Values must be scalars (string, number, boolean or null); nested objects and arrays are rejected.", + "additionalProperties": { + "nullable": true, + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ] + } + } + }, + "example": { + "event_id": "login-2026-10-17-000001", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-17T09:15:02.481Z", + "time_zone": "Europe/Madrid", + "subject": { + "vendor_data": "user-42", + "entity_type": "individual" + }, + "session": { + "session_id": "web-session-abc", + "sequence": 142 + }, + "device_context": { + "fingerprint": "fp-a8c1", + "network_context": { + "ip_address": "203.0.113.42" + } + }, + "payload": { + "method": "password" + }, + "custom_values": { + "vip_tier": "gold" + } + } + }, + "MonitoringEventIngestResult": { + "type": "object", + "description": "Outcome of ingesting one event. The same shape is returned for a new event and for an idempotent replay.", + "required": [ + "event_uuid", + "event_id", + "status", + "score", + "rules_evaluated_count", + "rules_matched_count", + "correlation_pending", + "retention_class", + "expires_at", + "idempotent_replay", + "rule_matches" + ], + "properties": { + "event_uuid": { + "type": "string", + "format": "uuid", + "description": "Didit identifier of the stored event." + }, + "event_id": { + "type": "string", + "description": "Your `event_id`." + }, + "status": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ], + "description": "Status after the real-time rules ran. See `MonitoringEvent.status`." + }, + "score": { + "type": "integer", + "description": "Score after the real-time rules ran." + }, + "rules_evaluated_count": { + "type": "integer", + "minimum": 0, + "description": "Number of event rules evaluated in the request. 0 in a sandbox application, where no rules run." + }, + "rules_matched_count": { + "type": "integer", + "minimum": 0, + "description": "Number of rules that matched, including `TEST` mode rules." + }, + "correlation_pending": { + "type": "boolean", + "description": "`true` when an asynchronous rule still has to run. Read `GET /v3/events/{event_uuid}/` a few minutes later for the final `status` and `score`." + }, + "retention_class": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ], + "description": "Retention class, after any promotion by a matched rule." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the event expires under its retention class and is deleted. Null for `monetary_or_compliance` (payment and dispute) events." + }, + "idempotent_replay": { + "type": "boolean", + "description": "`true` when an event with this `event_id` and the same content was already stored; the values are those of the stored event and no rule ran again." + }, + "rule_matches": { + "type": "array", + "description": "Rules that matched the event.", + "items": { + "type": "object", + "required": [ + "rule_id", + "library_key", + "mode", + "is_async" + ], + "properties": { + "rule_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the rule." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Library key when the rule was installed from the rules library, otherwise null." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "TEST" + ], + "description": "`TEST` rules record the match but apply no actions." + }, + "is_async": { + "type": "boolean", + "description": "`true` when the match came from the asynchronous pass." + } + } + } + } + } + }, + "ComplianceList": { + "type": "object", + "description": "A blocklist, allowlist or custom list of one application.", + "required": [ + "uuid", + "name", + "description", + "list_type", + "entry_type", + "is_system", + "entry_count", + "last_entry_added_at", + "created_at", + "updated_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "List identifier. Use it as `list_uuid` in the list and entry endpoints, and as the value of a workflow or transaction rule condition with `value_type: \"list\"`." + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "List name, unique among the application's lists." + }, + "description": { + "type": "string", + "description": "Free-text description. Empty string when not set." + }, + "list_type": { + "type": "string", + "enum": [ + "blocklist", + "allowlist", + "custom" + ], + "description": "`blocklist`: a match declines the session or transaction (system lists only, one per entry type). `allowlist`: matching items are trusted, for example a face allowlist skips the duplicated-face action; never overrides a blocklist match. `custom`: used in workflow and transaction rule conditions (`value_type: \"list\"`)." + }, + "entry_type": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email", + "ip_address", + "device_fingerprint", + "wallet_address", + "bank_account", + "country", + "user", + "business", + "key" + ], + "description": "What the list holds: `face`, `document` (document number), `phone`, `email`, `ip_address` (IP address or CIDR range), `device_fingerprint`, `wallet_address`, `bank_account`, `country` (ISO 3166-1 alpha-3), `user` or `business` (your `vendor_data`), or `key` (any string). It decides how entry values are validated and matched." + }, + "is_system": { + "type": "boolean", + "description": "`true` for the lists Didit provisions: one blocklist per entry type (`face`, `document`, `phone`, `email`, `ip_address`, `device_fingerprint`, `wallet_address`, `bank_account`, `user`, `business`), the **High Risk Countries** custom list, and system allowlists. System lists cannot be renamed, edited or deleted; you can add and delete their entries." + }, + "entry_count": { + "type": "integer", + "minimum": 0, + "description": "Number of entries currently in the list." + }, + "last_entry_added_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the newest current entry was added. `null` for an empty list." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the list was created." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When the list was last updated." + } + } + }, + "ComplianceListEntry": { + "type": "object", + "description": "One value in a list.", + "required": [ + "uuid", + "value", + "display_label", + "comment", + "added_by", + "reference_session_id", + "reference_session_number", + "reference_session_type", + "reference_object_uuid", + "metadata", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Entry identifier. Use it as `entry_uuid` in Delete a list entry." + }, + "value": { + "type": "string", + "description": "The listed value: a phone number in E.164, an email address, a document number, an IP address or CIDR range, a device fingerprint, a wallet address, a bank account, an ISO 3166-1 alpha-3 country code, a `vendor_data`, any string on a `key` list, or the UUID of the stored face on a face list. Values you send are stored in lower case, except phone numbers and country codes. Matching is case-insensitive." + }, + "display_label": { + "type": "string", + "nullable": true, + "maxLength": 512, + "description": "Human-readable label. Didit fills it for entries taken from a session (for example the holder's name and document number) when you send none." + }, + "comment": { + "type": "string", + "description": "Free-text note. Empty string when not set." + }, + "added_by": { + "type": "string", + "nullable": true, + "description": "Who added the entry: `api` (an API key), a console user's email address or `console`, `system` or `migration` (entries Didit created), or `rule` (a transaction or monitoring rule action)." + }, + "reference_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Verification session the entry was taken from. `null` when the entry was added by value." + }, + "reference_session_number": { + "type": "integer", + "nullable": true, + "description": "Application-scoped number of that session, as shown in the console." + }, + "reference_session_type": { + "type": "string", + "nullable": true, + "enum": [ + "kyc", + "kyb", + null + ], + "description": "`kyc` for a person verification session, `kyb` for a business verification session, `null` when there is no session." + }, + "reference_object_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The record the entry stands for: the face, document, phone or email record read from a session, the face created by Upload a face to a list, or the UUID you sent (for example a transaction UUID or a user's `didit_internal_id`)." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Extra context. Holds the keys Didit sets (listed here) merged with any `metadata` you sent when creating the entry, returned as stored. `null` when there is none.", + "properties": { + "reference_image_url": { + "type": "string", + "description": "Face entries: short-lived signed URL, issued again on every call; `null` for a scoped API key without `media: read`. Absent when no image belongs to the entry." + }, + "front_image_url": { + "type": "string", + "description": "Document entries that carry a document front image: short-lived signed URL, issued again on every call; `null` for a scoped API key without `media: read`." + }, + "full_name": { + "type": "string", + "description": "Document entries taken from a session: holder's full name." + }, + "issuing_state": { + "type": "string", + "description": "Document entries taken from a session: ISO 3166-1 alpha-3 issuing country." + }, + "document_type": { + "type": "string", + "description": "Document entries taken from a session: document type code, for example `P`, `ID` or `DL`." + }, + "region": { + "type": "string", + "description": "Document entries taken from a session: issuing state or province of a regionally issued document, empty when not applicable. Not set by Add session items to the blocklist." + }, + "document_number": { + "type": "string", + "description": "Document entries taken from a session: the document number." + }, + "face_image_source": { + "type": "string", + "enum": [ + "front_image_camera_front", + "back_image_camera_front" + ], + "description": "Face entries taken from a document-capture selfie: which selfie. Absent for liveness faces." + }, + "reference_node_id": { + "type": "string", + "nullable": true, + "description": "Face entries taken from a document-capture selfie: the ID verification node the selfie came from." + }, + "fingerprint_tier": { + "type": "string", + "nullable": true, + "enum": [ + "persistent_id", + "composite_hash", + "legacy_fp", + "unknown", + null + ], + "description": "Device fingerprint entries taken from a session: how the fingerprint was derived. `persistent_id` identifies one device; `composite_hash` and `legacy_fp` can be shared by devices of the same model." + }, + "sandbox": { + "type": "boolean", + "description": "`true` on a face entry uploaded in a sandbox application; such an entry matches nothing." + }, + "reference_type": { + "type": "string", + "enum": [ + "transaction", + "vendor_user", + "vendor_business" + ], + "description": "Set by you: what `reference_object_uuid` points to, so the console links back to it." + }, + "reference_id": { + "type": "string", + "description": "Set by you: identifier of the source record." + } + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the entry was added." + } + } + }, + "BlocklistedItem": { + "type": "object", + "description": "One blocklisted face, identity document, phone number or email address of a verification session. Fields that do not apply to the item's `type` are `null`.", + "required": [ + "type", + "session_id", + "session_number", + "blocklisted_at", + "face_image", + "document_type", + "document_number", + "front_image", + "phone_number", + "country_code", + "email" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email" + ], + "description": "What was blocklisted." + }, + "session_id": { + "type": "string", + "format": "uuid", + "description": "Verification session the item belongs to." + }, + "session_number": { + "type": "integer", + "nullable": true, + "description": "Application-scoped number of that session, as shown in the console." + }, + "blocklisted_at": { + "type": "string", + "format": "date-time", + "description": "When the item was last blocklisted." + }, + "face_image": { + "type": "string", + "nullable": true, + "description": "Face items: short-lived signed URL, issued again on every call; `null` for a scoped API key without `media: read`. Also `null` when no reference image is stored." + }, + "document_type": { + "type": "string", + "nullable": true, + "description": "Document items: document type code, for example `P` (passport), `ID` (national ID), `DL` (driver's license) or `RP` (residence permit)." + }, + "document_number": { + "type": "string", + "nullable": true, + "description": "Document items: the document number, or the personal number when the document has none." + }, + "front_image": { + "type": "string", + "nullable": true, + "description": "Document items: front image of the document, short-lived signed URL, issued again on every call; `null` for a scoped API key without `media: read`." + }, + "phone_number": { + "type": "string", + "nullable": true, + "description": "Phone items: the phone number in E.164." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "Phone items: ISO 3166-1 alpha-2 country of the number." + }, + "email": { + "type": "string", + "nullable": true, + "description": "Email items: the email address." + } + } + }, + "TransactionNote": { + "type": "object", + "description": "One note on a monitored transaction's review timeline.", + "required": [ + "uuid", + "note", + "actor_email", + "actor_name", + "metadata", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Note identifier." + }, + "note": { + "type": "string", + "description": "Plain-text note body." + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email of the Console user who wrote the note or changed the status. `null` for notes created with an API key and for notes Didit adds automatically." + }, + "actor_name": { + "type": "string", + "nullable": true, + "description": "Display name of the Console user (their email when they have no name). `Rule Engine` on notes Didit adds automatically. `null` for notes created with an API key." + }, + "metadata": { + "type": "object", + "description": "Tells the note types apart. Notes written through the API or the Console carry `mentioned_emails`. A status change made in the Console carries `type: status_change` with `previous_status`, `new_status`, `reason_code` and `reason_label`. Notes Didit adds carry `source`: `rule_action` (with `rule_uuid`) for a rule's `add_note` action, or `remediation_session` for a problem creating the user-action verification session.", + "properties": { + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + }, + "description": "Mentioned organization members, lower-cased and de-duplicated. Empty when nobody was mentioned." + }, + "type": { + "type": "string", + "enum": [ + "status_change" + ], + "description": "Present on notes recording a status change made in the Console." + }, + "previous_status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Status before the change." + }, + "new_status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Status after the change." + }, + "reason_code": { + "type": "string", + "nullable": true, + "description": "Reason code given with the change." + }, + "reason_label": { + "type": "string", + "nullable": true, + "description": "Reason label given with the change." + }, + "source": { + "type": "string", + "enum": [ + "rule_action", + "remediation_session" + ], + "description": "Present on notes Didit adds automatically." + }, + "rule_uuid": { + "type": "string", + "format": "uuid", + "description": "Rule whose `add_note` action wrote the note (with `source: rule_action`)." + } + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the note was stored." + } + } + }, + "TransactionSummary": { + "type": "object", + "description": "Summary of a monitored transaction, the shape `GET /v3/transactions/` lists. Call `GET /v3/transactions/{transaction_id}/` with `uuid` for parties, alerts and rule runs.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit transaction UUID. Use it as `{transaction_id}` on the transaction endpoints." + }, + "transaction_number": { + "type": "integer", + "nullable": true, + "description": "Application-scoped sequential number shown in the Console." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "Your own identifier from the create call. Unique per application." + }, + "transaction_type": { + "type": "string", + "description": "Category as stored: `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet`, `gamblingLimitChange`, `gamblingBonusChange` or `auditTrailEvent`." + }, + "action_type": { + "type": "string", + "description": "Sub-type within the category, for example `withdrawal`." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Current monitoring verdict." + }, + "source": { + "type": "string", + "enum": [ + "LIVE", + "IMPORTED" + ], + "description": "`LIVE` for transactions submitted for monitoring, `IMPORTED` for historical imports." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject." + }, + "amount": { + "type": "string", + "description": "Amount as a decimal string with trailing zeros removed (up to 18 decimal places)." + }, + "currency": { + "type": "string", + "description": "Currency code or crypto asset symbol of `amount`." + }, + "currency_type": { + "type": "string", + "nullable": true, + "description": "`fiat` or `crypto` when known." + }, + "preferred_currency_amount": { + "type": "string", + "nullable": true, + "description": "`amount` converted to the application's preferred currency, when a rate was available." + }, + "preferred_currency_code": { + "type": "string", + "nullable": true, + "description": "Currency of `preferred_currency_amount`." + }, + "payment_details": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference." + }, + "txn_date": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When Didit recorded it." + }, + "score": { + "type": "integer", + "description": "Risk score. Higher is riskier." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL", + null + ], + "description": "Risk severity, `null` until something sets it." + }, + "decision_reason_code": { + "type": "string", + "nullable": true, + "description": "Machine-readable reason for the current `status`." + }, + "decision_reason_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label for `decision_reason_code`." + }, + "tags": { + "type": "array", + "description": "Tags on the transaction.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string", + "description": "Hex color, for example `#F59E0B`." + }, + "description": { + "type": "string", + "nullable": true + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ], + "description": "Didit preset or custom tag." + } + } + } + }, + "subject_name": { + "type": "string", + "nullable": true, + "description": "Full name of the `APPLICANT` party." + }, + "subject_vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the applicant." + }, + "subject_type": { + "type": "string", + "nullable": true, + "enum": [ + "user", + "business", + null + ], + "description": "Whether the applicant is linked to a Didit user or business; `null` when it is linked to neither." + }, + "subject_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "UUID of the Didit user or business the applicant is linked to." + }, + "subject_country": { + "type": "string", + "nullable": true, + "description": "Country code of the applicant." + }, + "counterparty_name": { + "type": "string", + "nullable": true, + "description": "Full name of the `COUNTERPARTY` party, if any." + }, + "counterparty_vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the counterparty." + }, + "counterparty_country": { + "type": "string", + "nullable": true, + "description": "Country code of the counterparty." + }, + "assigned_to_name": { + "type": "string", + "nullable": true, + "description": "Name of the Console reviewer assigned to the transaction." + }, + "travel_rule_status": { + "type": "string", + "nullable": true, + "description": "Status of the transaction's Travel Rule transfer, or `null` when it has none." + }, + "application_id": { + "type": "string", + "format": "uuid", + "description": "Application that owns the transaction." + }, + "application_name": { + "type": "string", + "nullable": true, + "description": "Name of that application." + }, + "organization_id": { + "type": "string", + "format": "uuid", + "description": "Organization that owns the application." + }, + "organization_name": { + "type": "string", + "nullable": true, + "description": "Name of that organization." + } + } + }, + "ApplicationAnalyticsDataPoint": { + "type": "object", + "description": "Session counts for one day, by creation date (UTC). Each count reflects the session's current status. Points of the previous period carry at least the required fields.", + "required": [ + "rounded_date", + "total", + "approved", + "declined", + "canceled", + "pending" + ], + "properties": { + "rounded_date": { + "type": "string", + "format": "date", + "description": "The day, `YYYY-MM-DD`." + }, + "total": { + "type": "integer", + "description": "Sessions created that day." + }, + "approved": { + "type": "integer", + "description": "Of those, sessions `Approved` or `Kyc Expired`." + }, + "declined": { + "type": "integer", + "description": "Sessions `Declined`." + }, + "canceled": { + "type": "integer", + "description": "Sessions `Expired` or `Abandoned`." + }, + "pending": { + "type": "integer", + "description": "Sessions `Not Started`, `In Progress` or `In Review`." + }, + "in_review": { + "type": "integer", + "description": "Sessions `In Review`." + }, + "expired": { + "type": "integer", + "description": "Sessions `Expired`." + }, + "abandoned": { + "type": "integer", + "description": "Sessions `Abandoned`." + }, + "in_progress": { + "type": "integer", + "description": "Sessions `In Progress`." + }, + "not_started": { + "type": "integer", + "description": "Sessions `Not Started`." + }, + "manually_approved": { + "type": "integer", + "description": "Sessions a reviewer approved after the workflow gave another verdict." + }, + "manually_declined": { + "type": "integer", + "description": "Sessions a reviewer declined after the workflow gave another verdict." + } + } + }, + "ApplicationAnalyticsRequestBreakdown": { + "type": "object", + "description": "Status totals for the range. The previous-period breakdown carries at least the required fields.", + "required": [ + "total_approved", + "total_in_review", + "total_declined", + "total_canceled", + "total_pending" + ], + "properties": { + "total_approved": { + "type": "integer", + "description": "Sessions `Approved` or `Kyc Expired`." + }, + "total_in_review": { + "type": "integer", + "description": "Sessions `In Review`." + }, + "total_declined": { + "type": "integer", + "description": "Sessions `Declined`." + }, + "total_canceled": { + "type": "integer", + "description": "Sessions `Expired` or `Abandoned`." + }, + "total_pending": { + "type": "integer", + "description": "Sessions `Not Started`, `In Progress` or `In Review` (includes `total_in_review`)." + }, + "total_expired": { + "type": "integer", + "description": "Sessions `Expired`." + }, + "total_abandoned": { + "type": "integer", + "description": "Sessions `Abandoned`." + }, + "total_in_progress": { + "type": "integer", + "description": "Sessions `In Progress`." + }, + "total_not_started": { + "type": "integer", + "description": "Sessions `Not Started`." + }, + "total_manually_approved": { + "type": "integer", + "description": "Approved by a reviewer." + }, + "total_manually_declined": { + "type": "integer", + "description": "Declined by a reviewer." + } + } + }, + "ApplicationAnalyticsCountryCount": { + "type": "object", + "properties": { + "country": { + "type": "string", + "description": "Country name." + }, + "country_code": { + "type": "string", + "description": "Two-letter country code." + }, + "session_count": { + "type": "integer", + "description": "Sessions from that country." + } + } + }, + "ApplicationAnalyticsLocationStats": { + "type": "object", + "properties": { + "ip_countries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsCountryCount" + }, + "description": "Countries of the users' IP addresses, most sessions first." + }, + "countries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsCountryCount" + }, + "description": "Same list as `ip_countries`." + }, + "id_countries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsCountryCount" + }, + "description": "Issuing countries of the verified ID documents, most sessions first." + }, + "conversion_by_country": { + "type": "array", + "description": "Per document issuing country: finished sessions (approved, declined or in review) and how many were approved. Most finished first.", + "items": { + "type": "object", + "properties": { + "country": { + "type": "string" + }, + "country_code": { + "type": "string" + }, + "finished": { + "type": "integer" + }, + "approved": { + "type": "integer" + } + } + } + }, + "cities": { + "type": "array", + "description": "Up to 100 IP cities, most sessions first.", + "items": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "`City, Country`." + }, + "session_count": { + "type": "integer" + }, + "latitude": { + "type": "number", + "nullable": true + }, + "longitude": { + "type": "number", + "nullable": true + } + } + } + } + } + }, + "ApplicationAnalyticsDemographicStats": { + "type": "object", + "properties": { + "age_distribution": { + "type": "object", + "description": "Sessions per age band from the ID document. Empty when there is no data.", + "properties": { + "under_18": { + "type": "integer" + }, + "18_24": { + "type": "integer" + }, + "25_34": { + "type": "integer" + }, + "35_44": { + "type": "integer" + }, + "45_64": { + "type": "integer" + }, + "65_plus": { + "type": "integer" + } + }, + "additionalProperties": { + "type": "integer" + } + }, + "gender_distribution": { + "type": "object", + "description": "Sessions per document gender code: `M`, `F` or `U` (unknown).", + "additionalProperties": { + "type": "integer" + } + } + } + }, + "ApplicationAnalyticsDeviceDetails": { + "type": "object", + "description": "Top five of each, most sessions first.", + "properties": { + "device_models": { + "type": "array", + "description": "Device brands: `Apple`, `Android`, `LG`, everything else grouped as `Other`.", + "items": { + "type": "object", + "properties": { + "device": { + "type": "string" + }, + "count": { + "type": "integer" + } + } + } + }, + "browsers": { + "type": "array", + "description": "Browsers: `Chrome`, `Safari`, `Didit App`, `Firefox`, everything else grouped as `Other`.", + "items": { + "type": "object", + "properties": { + "browser": { + "type": "string" + }, + "count": { + "type": "integer" + } + } + } + }, + "operating_systems": { + "type": "array", + "description": "Operating systems.", + "items": { + "type": "object", + "properties": { + "os": { + "type": "string" + }, + "count": { + "type": "integer" + } + } + } + } + } + }, + "ApplicationAnalyticsWorkflowFunnel": { + "type": "object", + "description": "Keyed by workflow ID. `total` is the sessions started on that workflow; `steps` counts the sessions that reached each step (`OCR`, `LIVENESS`, `FACE_MATCH`, `AML`, `POA`, `PHONE`, `EMAIL`, `NFC`, `DATABASE_VALIDATION`, `QUESTIONNAIRE`, `IP_ANALYSIS`, `AGE_ESTIMATION`, `KYB_REGISTRY`, `KYB_DOCUMENTS`, `KYB_KEY_PEOPLE`). Reaching a step is not passing it.", + "additionalProperties": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "steps": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "reached": { + "type": "integer" + } + } + } + } + } + } + }, + "ApplicationAnalyticsVerificationTimeStats": { + "type": "object", + "description": "Active time users spent verifying, ignoring idle gaps. An empty object when there is no data.", + "properties": { + "overall_avg_seconds": { + "type": "number", + "description": "Average per session, one decimal." + }, + "overall_count": { + "type": "integer", + "description": "Sessions measured." + }, + "per_feature": { + "type": "object", + "description": "Keyed by feature, for example `OCR` or `LIVENESS`.", + "additionalProperties": { + "type": "object", + "properties": { + "avg_seconds": { + "type": "number" + }, + "total_seconds": { + "type": "number" + }, + "count": { + "type": "integer" + } + } + } + }, + "per_sub_step": { + "type": "object", + "description": "Keyed by feature, then by sub-step, for example `front_id_photo`.", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "avg_seconds": { + "type": "number" + }, + "total_seconds": { + "type": "number" + }, + "count": { + "type": "integer" + } + } + } + } + } + } + }, + "ApplicationAnalyticsResubmissionStats": { + "type": "object", + "properties": { + "first_pass": { + "type": "integer", + "description": "Finished sessions with no resubmission." + }, + "second": { + "type": "integer", + "description": "Finished sessions resubmitted once." + }, + "third_plus": { + "type": "integer", + "description": "Finished sessions resubmitted two or more times." + }, + "abandoned": { + "type": "integer", + "description": "Abandoned sessions." + } + } + }, + "ApplicationAnalyticsOriginalStatusStats": { + "type": "object", + "description": "The workflow's automatic verdict, before any reviewer changed it.", + "properties": { + "approved": { + "type": "integer" + }, + "in_review": { + "type": "integer" + }, + "declined": { + "type": "integer" + } + } + }, + "ApplicationAnalyticsWarningStats": { + "type": "object", + "description": "Keyed by feature (`ID_VERIFICATION`, `LIVENESS`, `FACEMATCH`, `AML`, `PROOF_OF_ADDRESS`, `PHONE`, `EMAIL`, `NFC`, `LOCATION`, `DATABASE_VALIDATION`, `QUESTIONNAIRE` and the KYB features). `total` is the sessions that reached the feature; `risks` counts sessions per warning code and level.", + "additionalProperties": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "risks": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "error": { + "type": "integer" + }, + "warning": { + "type": "integer" + }, + "information": { + "type": "integer" + } + } + } + } + } + } + }, + "ApplicationAnalyticsDemographicPoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "age_distribution": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + }, + "gender_distribution": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + } + }, + "ApplicationAnalyticsDevicePoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "devices": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + }, + "browsers": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + }, + "operating_systems": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + } + }, + "ApplicationAnalyticsVerificationTimePoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "overall_avg": { + "type": "number", + "description": "Average active seconds per session." + }, + "per_feature": { + "type": "object", + "description": "Average active seconds per feature.", + "additionalProperties": { + "type": "number" + } + } + } + }, + "ApplicationAnalyticsResubmissionPoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "first_pass": { + "type": "integer" + }, + "second": { + "type": "integer" + }, + "third_plus": { + "type": "integer" + }, + "abandoned": { + "type": "integer" + } + } + }, + "ApplicationAnalyticsOriginalStatusPoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "approved": { + "type": "integer" + }, + "in_review": { + "type": "integer" + }, + "declined": { + "type": "integer" + } + } + }, + "ApplicationAnalyticsSummary": { + "type": "object", + "description": "User verification (KYC) summary, returned when neither `scope` nor `widget_keys` is sent.", + "required": [ + "data_points", + "request_breakdown", + "previous_data_points", + "previous_request_breakdown", + "conversion_rate", + "location_stats", + "demographic_stats", + "device_details", + "workflow_funnel_stats", + "verification_time_stats", + "resubmission_stats", + "warning_stats", + "original_status_stats", + "grouping_mode", + "demographic_time_series", + "device_time_series", + "verification_time_time_series", + "resubmission_time_series", + "original_status_time_series" + ], + "properties": { + "data_points": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDataPoint" + }, + "description": "One point per day, from the first day with sessions in the range to `end_date`. Always daily, whatever `grouping_mode` says." + }, + "request_breakdown": { + "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" + }, + "previous_data_points": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDataPoint" + }, + "description": "Daily points for the period of the same length right before `start_date`." + }, + "previous_request_breakdown": { + "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" + }, + "conversion_rate": { + "type": "number", + "nullable": true, + "description": "`total_approved / (total_approved + total_declined + total_in_review) x 100`, one decimal. `null` when nothing finished." + }, + "location_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsLocationStats" + }, + "demographic_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsDemographicStats" + }, + "device_details": { + "$ref": "#/components/schemas/ApplicationAnalyticsDeviceDetails" + }, + "workflow_funnel_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsWorkflowFunnel" + }, + "verification_time_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimeStats" + }, + "resubmission_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionStats" + }, + "warning_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsWarningStats" + }, + "original_status_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusStats" + }, + "grouping_mode": { + "type": "string", + "enum": [ + "day", + "week", + "month" + ], + "description": "How the `*_time_series` lists are grouped: `day` for up to 14 days, `week` up to 90, `month` beyond. Measured from the first day with sessions in the range." + }, + "demographic_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDemographicPoint" + } + }, + "device_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDevicePoint" + } + }, + "verification_time_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimePoint" + } + }, + "resubmission_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionPoint" + } + }, + "original_status_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusPoint" + } + } + } + }, + "ApplicationAnalyticsWidget": { + "type": "object", + "description": "One dashboard widget. Every widget has `kind` and `title`; the other fields depend on `kind`. The session widgets reuse the summary objects (`data_points`, `request_breakdown`, `workflow_funnel_stats` and so on); list widgets put their entries in `items`; transaction metrics use `value`.", + "required": [ + "kind", + "title" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "sessions_volume", + "workflow_funnel", + "average_verification_time", + "country_distribution", + "warning_insights", + "conversion_rate", + "conversion_by_country", + "resubmission_insights", + "original_status_distribution", + "database_validation_coverage", + "database_validation_latency", + "demographics", + "device_details", + "horizontal_bar", + "kyb_registry_coverage", + "kyb_registry_data_completeness", + "metric", + "time_series", + "breakdown", + "status_time_series", + "leaderboard", + "histogram" + ], + "description": "Widget type." + }, + "title": { + "type": "string", + "description": "Display title, for example `Verification volume`." + }, + "grouping_mode": { + "type": "string", + "enum": [ + "day", + "week", + "month" + ], + "description": "How the `*_time_series` lists are grouped: `day` for up to 14 days, `week` up to 90, `month` beyond. Measured from the first day with sessions in the range." + }, + "data_points": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDataPoint" + } + }, + "request_breakdown": { + "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" + }, + "previous_data_points": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDataPoint" + } + }, + "previous_request_breakdown": { + "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" + }, + "conversion_rate": { + "type": "number", + "nullable": true + }, + "workflow_funnel_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsWorkflowFunnel" + }, + "verification_time_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimeStats" + }, + "verification_time_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimePoint" + } + }, + "warning_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsWarningStats" + }, + "resubmission_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionStats" + }, + "resubmission_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionPoint" + } + }, + "original_status_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusStats" + }, + "original_status_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusPoint" + } + }, + "demographic_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsDemographicStats" + }, + "demographic_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDemographicPoint" + } + }, + "device_details": { + "$ref": "#/components/schemas/ApplicationAnalyticsDeviceDetails" + }, + "device_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDevicePoint" + } + }, + "items": { + "type": "array", + "description": "Entries of a list widget. `country_distribution` (KYC): `country`, `country_code`, `session_count`. `conversion_by_country`: `country`, `country_code`, `finished`, `approved`. `time_series`: `date`, `value`. `status_time_series`: `date`, `approved`, `declined`, `flagged`. `breakdown`: `transaction_type` or `action_type` with `count` and `volume`, or `tag_id`, `tag_name`, `count`. `leaderboard`: `rule_title`, `trigger_count` or `assignee`, `handled_count`, `average_processing_seconds`, each with `approved_percentage`, `declined_percentage`, `flagged_percentage`. `histogram`: `score`, `count`.", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "previous_items": { + "type": "array", + "description": "Transaction time series for the period of the same length right before `start_date`.", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "value": { + "type": "number", + "description": "`metric` widgets: the figure for the range." + }, + "currency": { + "type": "string", + "nullable": true, + "description": "Transaction volume and amount widgets: the application's default transaction currency." + }, + "thresholds": { + "type": "object", + "description": "`histogram` widget: the application's review and decline score thresholds.", + "properties": { + "review": { + "type": "integer" + }, + "decline": { + "type": "integer" + } + } + }, + "search": { + "type": "object", + "description": "`kyb_registry_coverage` widget: registry search totals.", + "properties": { + "searches_total": { + "type": "integer" + }, + "searches_with_no_candidates": { + "type": "integer" + } + } + } + } + }, + "ApplicationAnalyticsWidgetResponse": { + "type": "object", + "description": "Dashboard widgets, returned when `scope` or `widget_keys` is sent.", + "required": [ + "scope", + "available_widget_keys", + "requested_widget_keys", + "widgets" + ], + "properties": { + "scope": { + "type": "string", + "enum": [ + "home", + "kyc", + "kyb", + "transactions" + ], + "description": "Dashboard tab." + }, + "available_widget_keys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Every widget key this scope accepts." + }, + "requested_widget_keys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The widget keys answered, in order: your valid `widget_keys`, or the scope's defaults." + }, + "widgets": { + "type": "object", + "description": "Keyed by widget key, in the order of `requested_widget_keys`.", + "additionalProperties": { + "$ref": "#/components/schemas/ApplicationAnalyticsWidget" + } + } + } + }, + "ApplicationAnalyticsToday": { + "type": "object", + "description": "Today's verifications by local hour against the same weekday last week.", + "required": [ + "time_zone", + "date", + "current_hour", + "previous_date", + "hours", + "previous_hours", + "total", + "previous_total_so_far", + "outcomes" + ], + "properties": { + "time_zone": { + "type": "string", + "description": "The IANA time zone the day and its hours are in.", + "example": "Europe/Madrid" + }, + "date": { + "type": "string", + "format": "date", + "description": "Today in that time zone.", + "example": "2026-10-30" + }, + "current_hour": { + "type": "integer", + "minimum": 0, + "maximum": 23, + "description": "The local hour it is now.", + "example": 14 + }, + "previous_date": { + "type": "string", + "format": "date", + "description": "The comparison day: the same weekday one week before.", + "example": "2026-10-23" + }, + "hours": { + "type": "array", + "items": { + "type": "integer", + "minimum": 0 + }, + "minItems": 24, + "maxItems": 24, + "description": "Verifications created today, per local hour from 00:00. The hours still to come are 0." + }, + "previous_hours": { + "type": "array", + "items": { + "type": "integer", + "minimum": 0 + }, + "minItems": 24, + "maxItems": 24, + "description": "Verifications created on the comparison day, per local hour from 00:00." + }, + "total": { + "type": "integer", + "minimum": 0, + "description": "Verifications created today so far.", + "example": 165 + }, + "previous_total_so_far": { + "type": "integer", + "minimum": 0, + "description": "Verifications created on the comparison day up to the same local time.", + "example": 151 + }, + "outcomes": { + "type": "object", + "description": "Of today's verifications, how many are approved, waiting in review and declined now. The rest are not finished yet.", + "required": [ + "approved", + "in_review", + "declined" + ], + "properties": { + "approved": { + "type": "integer", + "minimum": 0 + }, + "in_review": { + "type": "integer", + "minimum": 0 + }, + "declined": { + "type": "integer", + "minimum": 0 + } + } + } + } + }, + "WebhookConfigDestination": { + "type": "object", + "description": "A webhook destination as listed in the webhook configuration, without delivery metrics. `GET /v3/webhook/destinations/{destination_uuid}/` adds its delivery summary.", + "required": [ + "uuid", + "label", + "url", + "enabled", + "webhook_version", + "secret_shared_key", + "subscribed_events", + "created_at", + "updated_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Destination ID." + }, + "label": { + "type": "string", + "maxLength": 255, + "description": "Human-readable name." + }, + "url": { + "type": "string", + "format": "uri", + "maxLength": 2000, + "description": "HTTPS endpoint Didit posts events to." + }, + "enabled": { + "type": "boolean", + "description": "`false` pauses deliveries to this destination." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload version sent to this destination." + }, + "secret_shared_key": { + "type": "string", + "description": "This destination's signing secret (43-character URL-safe token). Verify each webhook with the secret of the destination it was sent to. API-key callers always receive it in full; Console users without `read:webhooks` receive an empty string." + }, + "subscribed_events": { + "type": "array", + "uniqueItems": true, + "description": "Event types delivered to this destination. Empty when the destination was created without events, in which case it receives nothing.", + "items": { + "$ref": "#/components/schemas/WebhookEvent" + } + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "WebhookConfig": { + "type": "object", + "description": "Application-level webhook configuration, retention policy and Digital ID wallet return hosts, with every webhook destination.", + "required": [ + "webhook_url", + "webhook_version", + "secret_shared_key", + "capture_method", + "data_retention_months", + "face_retention_policy", + "face_retention_days", + "available_events", + "allowed_return_hosts", + "endpoints" + ], + "properties": { + "webhook_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Application-level (legacy) webhook URL. Didit sends to it only while the application has no enabled destination, and then only `status.updated` and `data.updated`. Every create, update or delete of a destination sets it to the URL of the oldest enabled destination, or `null` when none is enabled." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload version of the legacy `webhook_url`. Each destination has its own `webhook_version`." + }, + "secret_shared_key": { + "type": "string", + "description": "Application-level signing secret (43-character URL-safe token). It signs only deliveries to the legacy `webhook_url`; each destination signs with its own `secret_shared_key`. API-key callers always receive it in full; Console users without `read:webhooks` receive an empty string." + }, + "capture_method": { + "type": "string", + "enum": [ + "MOBILE", + "MOBILE_AND_DESKTOP" + ], + "description": "Legacy application setting. The verification flow does not read it: capture options are configured per workflow." + }, + "data_retention_months": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 120, + "description": "Automatic deletion window. User verification sessions created more than this many months ago (a month counts as 30 days) are deleted automatically, and that deletion is irreversible. `null` means sessions are never deleted automatically. See [Data retention](https://docs.didit.me/console/data-retention)." + }, + "face_retention_policy": { + "type": "string", + "enum": [ + "delete_with_session", + "retain_until_user_deleted" + ], + "description": "What happens to a session's face embedding when the session is deleted. `delete_with_session` (default) deletes it with the session. `retain_until_user_deleted` is the opt-in [biometric template retention](https://docs.didit.me/console/data-retention#biometric-template-retention): an image-free biometric template linked to the User is managed separately until its retention period ends, the User is deleted, or you purge it." + }, + "face_retention_days": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 3650, + "description": "Retention period, in days, of biometric templates under `retain_until_user_deleted`. Required when that policy is set." + }, + "biometric_consent_mode": { + "type": "string", + "enum": [ + "auto", + "always", + "never" + ], + "description": "When the hosted flow and the native SDKs ask the person for explicit biometric consent before a face capture. `auto` (default) asks only where a trigger applies: a declared state, the person's IP, or the identity document's issuer is in Illinois, Texas or Colorado, or the person looks to be in the US without a known state. `always` asks every person whose workflow captures a face. `never` never shows the step, and you collect consent yourself before the selfie. Each change is recorded in the audit log. See [Biometric consent](https://docs.didit.me/getting-started/biometric-consent)." + }, + "available_events": { + "type": "array", + "description": "Every event type a destination can subscribe to, with a display label.", + "items": { + "type": "object", + "required": [ + "value", + "label" + ], + "properties": { + "value": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "label": { + "type": "string", + "description": "Display label, for example `Session status updated`." + } + } + } + }, + "allowed_return_hosts": { + "type": "array", + "maxItems": 20, + "description": "Hostnames (exact match, HTTPS only) a standalone Digital ID wallet verification may send the user back to with its `return_url`. Your white-label domain is always allowed as well. See [Register your return destination](https://docs.didit.me/standalone-apis/digital-id-wallets#register-your-return-destination).", + "items": { + "type": "string", + "maxLength": 253 + } + }, + "endpoints": { + "type": "array", + "description": "Every webhook destination of the application, oldest first, each with its signing secret. Read only here: manage destinations with the `/v3/webhook/destinations/` endpoints.", + "items": { + "$ref": "#/components/schemas/WebhookConfigDestination" + } + }, + "allowed_domains": { + "type": "array", + "maxItems": 50, + "items": { + "type": "string", + "maxLength": 300 + }, + "description": "Origins (for example `https://app.example.com`; `http` only for `localhost`) the hosted verification flow may be embedded in and may send the user back to with a session callback. Server-to-server calls are not affected. An empty list means no restriction. Didit stores each origin in lower case without a default port. An invalid entry fails with `400` on `allowed_domains`.", + "example": [ + "https://app.example.com" + ] + } + } + }, + "WebhookDestinationDelivery": { + "type": "object", + "description": "One delivery attempt to a webhook destination. The first send of an event, each automatic retry and each resend are separate deliveries.", + "required": [ + "uuid", + "session_id", + "request_url", + "response_status", + "response_time_ms", + "status", + "webhook_type", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Delivery ID, for the detail and resend calls." + }, + "session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "User verification session the event is about. `null` for business session, user, business, transaction, Travel Rule and workflow events." + }, + "request_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL the delivery was sent to: the destination's URL when the event was generated." + }, + "response_status": { + "type": "integer", + "nullable": true, + "description": "HTTP status your endpoint returned. `null` while the delivery is queued and not yet attempted. When no HTTP answer was received Didit records a status of its own: `504` when your endpoint did not answer within 5 seconds, `503` for a connection failure, `400` when the URL was refused as unsafe (for example a private or internal address), and `404` for any other request error. `400` or above counts as a failed delivery." + }, + "response_time_ms": { + "type": "integer", + "nullable": true, + "minimum": 0, + "description": "Time the attempt took, in milliseconds. `null` while the delivery is queued." + }, + "status": { + "type": "string", + "nullable": true, + "description": "Status the event reported when it was generated: the session status for session events (for example `Approved` or `In Review`), the person's or company's status for user and business events, and the transaction status for transaction events. `null` for events that carry no status." + }, + "webhook_type": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the delivery was created." + } + } + }, + "WebhookDestinationDeliveryDetail": { + "type": "object", + "description": "One delivery attempt to a webhook destination, with the exact request headers and body Didit sent.", + "required": [ + "uuid", + "session_id", + "request_url", + "response_status", + "response_time_ms", + "status", + "webhook_type", + "created_at", + "request_header", + "request_body" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Delivery ID, for the detail and resend calls." + }, + "session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "User verification session the event is about. `null` for business session, user, business, transaction, Travel Rule and workflow events." + }, + "request_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL the delivery was sent to: the destination's URL when the event was generated." + }, + "response_status": { + "type": "integer", + "nullable": true, + "description": "HTTP status your endpoint returned. `null` while the delivery is queued and not yet attempted. When no HTTP answer was received Didit records a status of its own: `504` when your endpoint did not answer within 5 seconds, `503` for a connection failure, `400` when the URL was refused as unsafe (for example a private or internal address), and `404` for any other request error. `400` or above counts as a failed delivery." + }, + "response_time_ms": { + "type": "integer", + "nullable": true, + "minimum": 0, + "description": "Time the attempt took, in milliseconds. `null` while the delivery is queued." + }, + "status": { + "type": "string", + "nullable": true, + "description": "Status the event reported when it was generated: the session status for session events (for example `Approved` or `In Review`), the person's or company's status for user and business events, and the transaction status for transaction events. `null` for events that carry no status." + }, + "webhook_type": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the delivery was created." + }, + "request_header": { + "type": "string", + "description": "JSON text (a string) of the request headers Didit sent: `Content-Type`, `X-Signature`, `X-Signature-V2`, `X-Signature-Simple`, `X-Timestamp` and `User-Agent`. Parse it with a JSON parser. It holds signatures, never the signing secret." + }, + "request_body": { + "type": "string", + "description": "JSON text (a string) of the exact payload Didit sent, keys sorted, including `event_id` and `timestamp`. Parse it with a JSON parser; see [Payload shape](https://docs.didit.me/integration/webhooks#payload-shape). For a scoped key without `media: read`, media URLs inside it are `null`; without `sessions: write`, session tokens and hosted verification links are `null`. A body changed that way no longer matches its signatures. Didit does not record the response body your endpoint returned." + } + } + }, + "SessionWebhookDelivery": { + "type": "object", + "description": "One webhook delivery attempt recorded for a verification session, with the exact request headers and body Didit sent.", + "required": [ + "uuid", + "session_id", + "request_url", + "response_status", + "status", + "webhook_type", + "request_header", + "request_body", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Delivery ID. Use it as `{webhook_uuid}` to get or resend this delivery." + }, + "session_id": { + "type": "string", + "format": "uuid", + "description": "The session the delivery belongs to (the session in the path)." + }, + "request_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL the delivery was sent to." + }, + "response_status": { + "type": "integer", + "nullable": true, + "description": "HTTP status your endpoint returned. `null` while the delivery is queued and not yet attempted. When no HTTP answer was received Didit records a status of its own: `504` when your endpoint did not answer within 5 seconds, `503` for a connection failure, `400` when the URL was refused as unsafe (for example a private or internal address), and `404` for any other request error. `400` or above counts as a failed delivery." + }, + "status": { + "type": "string", + "nullable": true, + "description": "Session status when the event was generated, for example `In Progress`, `Approved` or `Declined`." + }, + "webhook_type": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "request_header": { + "type": "string", + "description": "JSON text (a string) of the request headers Didit sent: `Content-Type`, `X-Signature`, `X-Signature-V2`, `X-Signature-Simple`, `X-Timestamp` and `User-Agent`. Parse it with a JSON parser. It holds signatures, never the signing secret." + }, + "request_body": { + "type": "string", + "description": "JSON text (a string) of the exact payload Didit sent, keys sorted, including `event_id` and `timestamp`. Parse it with a JSON parser; see [Payload shape](https://docs.didit.me/integration/webhooks#payload-shape). For a scoped key without `media: read`, media URLs inside it are `null`; without `sessions: write`, session tokens and hosted verification links are `null`. A body changed that way no longer matches its signatures. Didit does not record the response body your endpoint returned." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the delivery was created." + } + } + }, + "SessionEvent": { + "type": "object", + "description": "One entry of a verification session's event timeline.", + "required": [ + "uuid", + "event_type", + "created_at", + "component", + "feature_step", + "step", + "details", + "ip_address", + "user_agent", + "device_info", + "file_url", + "sent_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Event identifier." + }, + "event_type": { + "type": "string", + "enum": [ + "SESSION_CREATED", + "SESSION_COMPLETED", + "SESSION_STATUS_CHANGED", + "LINK_OPENED", + "FORM_SUBMITTED", + "BUTTON_CLICKED", + "FILE_UPLOADED", + "ATTEMPT_FAILED", + "FEATURE_STEP_CHANGED", + "STEP_RENDERED", + "NOTE_ADDED", + "MANUAL_REVIEW_STARTED", + "WORKFLOW_BRANCH_EVALUATED", + "WORKFLOW_STATUS_NODE_REACHED", + "LOG" + ], + "description": "What happened. Reported by the device during the hosted flow or SDK: `LINK_OPENED`, `STEP_RENDERED`, `BUTTON_CLICKED`, `FORM_SUBMITTED`, `FILE_UPLOADED`, `ATTEMPT_FAILED` and `LOG` (debugging). Recorded by the server: `SESSION_CREATED`, `SESSION_STATUS_CHANGED` (`details.old_status`, `details.current_status`), `FEATURE_STEP_CHANGED` (`details.old_feature_step`, `details.current_feature_step`), `WORKFLOW_BRANCH_EVALUATED` and `WORKFLOW_STATUS_NODE_REACHED` (the workflow routing trace). `SESSION_COMPLETED`, `NOTE_ADDED` and `MANUAL_REVIEW_STARTED` can appear on older sessions." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When Didit stored the event. Events are stored asynchronously, a few seconds after `sent_at`." + }, + "component": { + "type": "string", + "nullable": true, + "description": "UI component that reported the event (device events only)." + }, + "feature_step": { + "type": "string", + "nullable": true, + "enum": [ + "OCR", + "OCR_BACK", + "NFC", + "AML", + "FACE", + "LIVENESS", + "FACE_MATCH", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "PROOF_OF_ADDRESS", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "FACE_SEARCH", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "DOCUMENT_AI", + "GEOLOCATION", + null + ], + "description": "Verification feature the session was on when the event happened." + }, + "step": { + "type": "string", + "nullable": true, + "description": "Screen within the feature, for example `DOCUMENT_CAPTURE_FRONT` or `FACE_PASSIVE_LIVENESS`." + }, + "details": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Event-specific data. The keys depend on `event_type`." + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "IP address the device reported the event from. `null` for server-recorded events." + }, + "user_agent": { + "type": "string", + "nullable": true, + "description": "User agent of the device. `null` for server-recorded events." + }, + "device_info": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Device details the SDK or browser reported." + }, + "file_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived presigned link to a file attached to the event (for example a captured image). `null` when there is none, and always `null` for a scoped API key without `media: read`." + }, + "sent_at": { + "type": "integer", + "format": "int64", + "description": "When the event happened, as a Unix timestamp in milliseconds. Results are ordered by it." + } + } + }, + "AMLDatasetSourceFreshness": { + "type": "object", + "description": "Freshness of one family of AML datasets.", + "required": [ + "last_updated_at", + "oldest_updated_at", + "dataset_count", + "dated_dataset_count" + ], + "properties": { + "last_updated_at": { + "type": "string", + "format": "date-time", + "description": "Most recent update of any dataset in the family." + }, + "oldest_updated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Staleness bound: every dataset in the family is at least this current. `null` when at least one dataset in the family reported no update date, because the bound is then unknown." + }, + "dataset_count": { + "type": "integer", + "description": "Number of datasets in the family." + }, + "dated_dataset_count": { + "type": "integer", + "description": "Number of those datasets that reported an update date." + } + } + }, + "AMLDatasetFreshness": { + "type": "object", + "description": "How current the AML datasets were when a screening ran. Dates are never estimated.", + "required": [ + "captured_at", + "provider", + "catalog_version", + "sources" + ], + "properties": { + "captured_at": { + "type": "string", + "format": "date-time", + "description": "When Didit read the dataset catalog this freshness comes from." + }, + "provider": { + "type": "string", + "description": "Opaque identifier of the AML data source. Treat it as an opaque string; do not branch on it.", + "example": "aml_data_source" + }, + "catalog_version": { + "type": "string", + "nullable": true, + "description": "Version identifier of the dataset catalog, when the source reports one." + }, + "sources": { + "type": "object", + "description": "Freshness per dataset family. A family whose datasets reported no update date is absent rather than filled with `null`.", + "properties": { + "sanctions": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" + }, + "pep": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" + }, + "warnings": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" + }, + "adverse_media": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" + } + } + } + } + }, + "AMLScreeningRun": { + "type": "object", + "description": "One completed ongoing-monitoring re-screen of an AML check.", + "required": [ + "uuid", + "run_id", + "completed_at", + "started_at", + "outcome", + "trigger", + "provider", + "status", + "score", + "total_hits", + "new_hits", + "entities_screened", + "dataset_freshness" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Identifier of this screening run." + }, + "run_id": { + "type": "string", + "format": "uuid", + "description": "The monitoring run this screening belongs to. Every verification screened in the same run shares it." + }, + "completed_at": { + "type": "string", + "format": "date-time", + "description": "When this subject's re-screen finished. Results are ordered by it, newest first." + }, + "started_at": { + "type": "string", + "format": "date-time", + "description": "When the monitoring batch this re-screen belongs to started." + }, + "outcome": { + "type": "string", + "enum": [ + "NO_CHANGE", + "CHANGED", + "FAILED" + ], + "description": "`NO_CHANGE`: completed and the result did not move. `CHANGED`: completed and the score, status, hit count or hits changed. `FAILED`: no usable answer for this subject, so the check was left as it was." + }, + "trigger": { + "type": "string", + "enum": [ + "ONGOING_MONITORING" + ], + "description": "What started the screening. Always `ONGOING_MONITORING` today." + }, + "provider": { + "type": "string", + "description": "Opaque identifier of the AML data source that answered. Treat it as an opaque string; do not branch on it.", + "example": "aml_data_source" + }, + "status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested", + null + ], + "description": "AML check status this run left the check in." + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "AML score this run left the check in." + }, + "total_hits": { + "type": "integer", + "nullable": true, + "description": "Number of hits this run left the check in." + }, + "new_hits": { + "type": "integer", + "nullable": true, + "description": "Number of hits this run found that the check did not have before it. `0` when nothing new was found; `null` when the run failed or was recorded before this count existed." + }, + "entities_screened": { + "type": "integer", + "minimum": 0, + "maximum": 1, + "description": "Number of entities this run screened for the verification: `1` when the subject was screened, `0` when the run failed." + }, + "dataset_freshness": { + "allOf": [ + { + "$ref": "#/components/schemas/AMLDatasetFreshness" + } + ], + "nullable": true, + "description": "Dataset freshness behind this run, or `null` when no dates were available." + } + } + }, + "KYBKeyPeopleParty": { + "type": "object", + "description": "A Key People party of a Business Verification (KYB) session, with its child verification. The same party appears in `key_people_checks[].submitted.parties[]` of the business session decision.", + "required": [ + "uuid", + "entity_type", + "source", + "name", + "is_active", + "is_skipped", + "requires_verification", + "roles", + "kyc_session_deleted", + "kyb_sub_session_deleted" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Party identifier." + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "source": { + "type": "string", + "enum": [ + "REGISTRY", + "USER" + ], + "description": "`REGISTRY`: taken from the company registry. `USER`: entered in the Key People screen." + }, + "name": { + "type": "string", + "description": "Display name of the party." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "First name (persons)." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Last name (persons)." + }, + "company_name": { + "type": "string", + "nullable": true, + "description": "Company name (company parties)." + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Company registration number (company parties)." + }, + "email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email the verification link is sent to." + }, + "nationality": { + "type": "string", + "nullable": true, + "description": "Nationality for persons, country of incorporation for companies." + }, + "phone_number": { + "type": "string", + "nullable": true, + "description": "Phone number collected for the party." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth (persons)." + }, + "position": { + "type": "string", + "nullable": true, + "description": "Free-text job title (persons)." + }, + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined custom fields collected for the party, keyed by field key." + }, + "is_active": { + "type": "boolean", + "description": "Whether the party is active." + }, + "is_skipped": { + "type": "boolean", + "description": "Whether the party is exempt from verification. Regenerating clears it." + }, + "requires_verification": { + "type": "boolean", + "description": "Whether the role and ownership rules require a verification for this party." + }, + "roles": { + "type": "array", + "description": "Roles the party holds in the company.", + "items": { + "type": "object", + "required": [ + "role", + "ownership_percent", + "voting_percent" + ], + "properties": { + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Role code." + }, + "ownership_percent": { + "type": "number", + "nullable": true, + "description": "Ownership percentage for this role, when known." + }, + "voting_percent": { + "type": "number", + "nullable": true, + "description": "Voting-rights percentage for this role, when known." + } + } + } + }, + "kyc_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Child individual KYC session (persons)." + }, + "kyc_session_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User", + null + ], + "description": "Status of the child KYC session. A deleted session keeps the status it had when it was deleted." + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Hosted link of the child KYC session. `null` when there is none or it was deleted, and for a scoped API key without `sessions: write`." + }, + "kyc_session_deleted": { + "type": "boolean", + "description": "The child KYC session was deleted, so it can never settle." + }, + "kyb_sub_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Child KYB session (company parties)." + }, + "kyb_sub_session_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User", + null + ], + "description": "Status of the child KYB session." + }, + "kyb_sub_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Hosted link of the child KYB session, with the same `null` rules as `kyc_session_url`." + }, + "kyb_sub_session_deleted": { + "type": "boolean", + "description": "Company-party counterpart of `kyc_session_deleted`." + }, + "verification_workflow_type": { + "type": "string", + "nullable": true, + "enum": [ + "kyc", + "kyb", + "adaptive_age_verification", + "biometric_authentication", + "address_verification", + "questionnaire_verification", + "email_verification", + "phone_verification", + null + ], + "description": "Type of the workflow the child session runs." + }, + "verification_workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Workflow the child session runs." + }, + "verification_workflow_label": { + "type": "string", + "nullable": true, + "description": "Label of that workflow." + }, + "verification_workflow_features": { + "type": "array", + "nullable": true, + "description": "Features of the child session's workflow with their current status.", + "items": { + "type": "object", + "required": [ + "feature", + "status" + ], + "properties": { + "feature": { + "type": "string", + "description": "Feature code, for example `ID_VERIFICATION` or `LIVENESS`." + }, + "status": { + "type": "string", + "description": "Feature status, `Not Finished` until it runs." + } + } + } + }, + "didit_internal_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Didit's identifier of the verified person, once known." + } + } + }, + "TravelRuleDelivery": { + "type": "object", + "description": "Where the latest message the transfer owes the other side stands. Didit contacts the counterparty after the request that created or changed the transfer has returned, so this object says whether that message got through: queued, being sent, waiting to be retried after an error, delivered, or failed. A failure the network may still recover from (no answer in time, a failed connection, HTTP `408`, `429` or `5xx`) is tried again after 30 seconds, 2 minutes, 10 minutes, 30 minutes and 1 hour. When a first message (`kind: SEND`) fails for good, because the network refused it or the sixth attempt failed too, the transfer moves to `COUNTERPARTY_VASP_NOT_REACHABLE` with `last_error` in its `timeline`; any other message that fails for good is noted in the `timeline`. The transfer's `status` stays the source of truth for the exchange itself.", + "required": [ + "kind", + "status", + "rail", + "attempts", + "last_error", + "next_attempt_at", + "delivered_at" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "SEND", + "ANSWER", + "TXID", + "CANCEL", + "POLL" + ], + "description": "What the message carries. `SEND`: the transfer's first message to the counterparty (outbound; the rail is chosen when it is sent). `ANSWER`: your accept or decline, owed to the network that asked (inbound). `TXID`: the on-chain transaction hash, after you finish the transfer. `CANCEL`: the cancellation, after you cancel it. `POLL`: a status query to a network that offers one, repeated every 5 minutes while the transfer waits on the counterparty." + }, + "status": { + "type": "string", + "enum": [ + "QUEUED", + "SENDING", + "RETRYING", + "DELIVERED", + "FAILED" + ], + "description": "`QUEUED`: waiting to be sent. `SENDING`: an attempt is running. `RETRYING`: an attempt failed and the next one runs at `next_attempt_at`. `DELIVERED`: the other side took the message. `FAILED`: the other side refused it, or the last attempt failed." + }, + "rail": { + "type": "string", + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL", + "" + ], + "description": "The rail that carries the message. Empty until Didit has chosen one for a first message." + }, + "attempts": { + "type": "integer", + "minimum": 0, + "description": "Attempts made so far for this message. A message that is not a status poll is attempted at most 6 times." + }, + "last_error": { + "type": "string", + "description": "The kind of failure the last attempt hit, `\"\"` when none did: for example `email undeliverable`, `trp identity endpoint unreachable` or `trp transfer inquiry delivery failed`. It never carries the counterparty's own answer, which stays on the transfer's `timeline`.\nWhen GTR answers with an error, its own status code and message are included. Response bodies are never included.", + "example": "email undeliverable" + }, + "next_attempt_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the next attempt runs while the message is `QUEUED` or `RETRYING`." + }, + "delivered_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the other side took the message." + } + } + }, + "TravelRuleDiscoveryStep": { + "type": "object", + "description": "One step of the counterparty discovery Didit ran for an outbound transfer, in the order it ran them. The first step that names a VASP with a route ends the chain.", + "properties": { + "step": { + "type": "string", + "enum": [ + "internal_address_book", + "wallet_attribution", + "network_lookup", + "customer_hint", + "end_user" + ], + "description": "`internal_address_book`: another Didit customer that proved control of the destination wallet. `wallet_attribution`: the wallet's blockchain analytics attribution. `network_lookup`: a network that can say which VASP holds the address. `customer_hint`: the VASP name, LEI or jurisdiction you sent in `beneficiary_data`. `end_user`: the counterparty the end user (or you) picked, or the request to pick one." + }, + "outcome": { + "type": "string", + "enum": [ + "match", + "miss", + "skipped" + ], + "description": "`match`: the step named a counterparty. `miss`: it ran and found none. `skipped`: it could not run, and `reason` says why." + }, + "reason": { + "type": "string", + "description": "Machine-readable reason, `\"\"` on a match: `no_proven_internal_wallet`, `attribution_disabled`, `already_attributed`, `no_attributed_entity`, `directory_match_no_route`, `gtr_address_lookup_runs_on_dispatch`, `no_membership`, `network_switched_off`, `no_network_address_lookup`, `no_directory_match`, `awaiting_end_user` or `picked_member_without_proven_wallet`." + }, + "vasp": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The directory VASP the step named, when it named one." + }, + "rail": { + "type": "string", + "nullable": true, + "description": "The rail that reaches that counterparty, when the step found one." + }, + "at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleNextAction": { + "type": "object", + "description": "One thing that moves a `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE` transfer on. See [Why a transfer is stuck](/transaction-monitoring/travel-rule#why-a-transfer-is-stuck).", + "properties": { + "action": { + "type": "string", + "enum": [ + "select_counterparty_vasp", + "declare_self_hosted", + "add_vasp_hint", + "update_compliance_email", + "resend" + ], + "description": "`select_counterparty_vasp`: the end user picks the destination VASP in the wallet-ownership widget, or you PATCH `action=beneficiary_vasp`. `declare_self_hosted`: the end user declares the wallet self-hosted in the widget. `add_vasp_hint`: tell Didit who the counterparty is, with a VASP name, LEI or travel address (for example a `travel_address` on the destination wallet's address-book entry), then resend. `update_compliance_email` and `resend` are `action` values of `PATCH /v3/transactions/{transaction_id}/travel-rule/`: give the counterparty's compliance email again, and re-run routing." + }, + "url": { + "type": "string", + "format": "uri", + "description": "The wallet-ownership widget link, on `select_counterparty_vasp` and `declare_self_hosted` while the transfer has an open widget session." + } + }, + "required": [ + "action" + ] + }, + "TransactionTravelRule": { + "type": "object", + "description": "A transaction's Travel Rule transfer, as `GET /v3/transactions/{transaction_id}/`, the `201` of `POST /v3/transactions/` and each row of `GET /v3/transactions/` return it. `direction`, `rail`, `counterparty_vasp`, `delivery`, `discovery`, `reason` and `next_actions` have the same shape as on `PATCH /v3/transactions/{transaction_id}/travel-rule/`, so one read of a transaction says why its transfer is stuck and what moves it on.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string" + }, + "protocol": { + "type": "string", + "nullable": true + }, + "required": { + "type": "boolean" + }, + "obligations_count": { + "type": "integer" + }, + "originator_data": { + "type": "object", + "description": "Originator party. For an inbound transfer the counterparty's values are masked until ownership is confirmed." + }, + "beneficiary_data": { + "type": "object", + "description": "Beneficiary party. For an inbound transfer the counterparty's values are masked until ownership is confirmed." + }, + "name_match": { + "type": "object", + "nullable": true, + "description": "The name comparison that decided the transfer (score and strictness). Null until one ran. The compared names are masked together with the parties." + }, + "data_match": { + "$ref": "#/components/schemas/TravelRuleDataMatch" + }, + "mismatched_fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Shortcut for `data_match.mismatched_fields`. Empty when nothing differs." + }, + "data_unmasked": { + "$ref": "#/components/schemas/TravelRuleDataUnmasked" + }, + "data_masked": { + "type": "boolean", + "description": "True while the counterparty's personal data is masked in this response." + }, + "metadata": { + "type": "object" + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Which side of the exchange this transfer is." + }, + "rail": { + "type": "string", + "nullable": true, + "description": "The rail the transfer went over: `INTERNAL`, `TRP`, `GTR`, `TRUST`, `VERIFYVASP`, `SYGNA` or `EMAIL` (`TEST_VASP` on a sandbox application, when the Didit Test VASP answered), or `null` until a rail is chosen." + }, + "counterparty_vasp": { + "type": "object", + "nullable": true, + "properties": { + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "dd_score": { + "type": "integer", + "description": "Due-diligence score from 0 to 100, where higher is better. Same scale as in the VASP directory." + }, + "dd_score_breakdown": { + "type": "object", + "description": "Per-component contributions to dd_score, one object per component as in the VASP directory (jurisdiction, verification, protocol, lei, history, essentials, responsiveness), each {points, max, reason, detail}. {} until the counterparty has been assessed.", + "additionalProperties": { + "type": "object", + "properties": { + "points": { + "type": "number", + "description": "Points awarded for this component." + }, + "max": { + "type": "integer", + "description": "Maximum points this component can award." + }, + "reason": { + "type": "string", + "description": "Machine-readable code of the component's verdict." + }, + "detail": { + "type": "string", + "description": "Human-readable reason for the awarded points." + } + } + } + }, + "dd_assessed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the due-diligence assessment last ran. Null until the counterparty has been assessed." + } + }, + "description": "The counterparty's directory entry, or `null` when the transfer links none." + }, + "delivery": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleDelivery" + } + ], + "description": "The newest message the transfer owes another party. `null` when it has owed none, for example below `threshold_amount` or for a declared self-hosted wallet." + }, + "discovery": { + "type": "array", + "description": "How Didit looked for the counterparty, one entry per step that ran. Empty when no discovery ran, for example on an inbound transfer.", + "items": { + "$ref": "#/components/schemas/TravelRuleDiscoveryStep" + } + }, + "reason": { + "type": "string", + "description": "Why a `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE` transfer is stuck; `\"\"` for any other status. A failed delivery decides it first, on `COUNTERPARTY_VASP_NOT_REACHABLE` only: `email_undeliverable` (the mail provider refused the compliance email and no address is left to try: when it was sent, on a reminder, or as a permanent bounce reported later) or `delivery_failed` (the rail that was delivering failed for good: a network rail refused the message or ran out of retries, a `TRP` identity host did not answer, or the counterparty's `TRP` host did not accept the inquiry). Otherwise the `discovery` trace decides it: `awaiting_end_user` (no step named a counterparty Didit can reach), `directory_match_no_route` (a step named a directory VASP that has no route), `picked_member_without_proven_wallet` (the end user picked a Didit customer that has not proven control of the wallet), or `no_directory_match` when the trace gives no other reason. A resend moves the transfer on, so the reason clears. See [Why a transfer is stuck](/transaction-monitoring/travel-rule#why-a-transfer-is-stuck)." + }, + "next_actions": { + "type": "array", + "description": "What moves the transfer on; `[]` for any status but `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE`. `email_undeliverable` offers `update_compliance_email`, then `resend`. `delivery_failed` offers `resend`. A discovery reason offers `select_counterparty_vasp`, `declare_self_hosted`, `add_vasp_hint` and `resend`.", + "items": { + "$ref": "#/components/schemas/TravelRuleNextAction" + } + }, + "chain_details": { + "$ref": "#/components/schemas/TravelRuleChainDetails" + } + } + }, + "NetworkListCounts": { + "type": "object", + "description": "Network counts for the list's views and filter options, each network counted once. Returned only when `summary` is not `false`. `scopes` count every network visible to the application. `views` count the visible networks inside the selected `scope`. `facets` count the networks in the selected `scope` and `status` view (dismissed networks are left out unless `status` includes them). The other list filters do not change these counts.", + "properties": { + "scopes": { + "type": "object", + "description": "Visible networks by product.", + "properties": { + "all": { + "type": "integer" + }, + "identity": { + "type": "integer", + "description": "Every other network." + }, + "transactions": { + "type": "integer", + "description": "Networks whose members are all transaction parties." + } + } + }, + "views": { + "type": "object", + "description": "Visible networks inside the selected `scope`, by status, plus two saved views.", + "properties": { + "all": { + "type": "integer" + }, + "active": { + "type": "integer" + }, + "in_review": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "dismissed": { + "type": "integer" + }, + "mine": { + "type": "integer", + "description": "Networks assigned to the calling console member. Always 0 for an API key." + }, + "high_risk": { + "type": "integer", + "description": "Networks with a risk score of 80 or more that are not dismissed." + } + } + }, + "facets": { + "type": "object", + "description": "Counts for each filter option, inside the selected `scope` and status view.", + "properties": { + "total": { + "type": "integer", + "description": "Networks in the selected `scope` and status view." + }, + "last_activity_days": { + "type": "object", + "description": "Networks with activity in the last 7, 30 and 90 days, keyed by the number of days.", + "properties": { + "7": { + "type": "integer" + }, + "30": { + "type": "integer" + }, + "90": { + "type": "integer" + } + } + }, + "risk_min": { + "type": "object", + "description": "Networks with a risk score of at least 50 and at least 80, keyed by the threshold.", + "properties": { + "50": { + "type": "integer" + }, + "80": { + "type": "integer" + } + } + }, + "signal_types": { + "type": "object", + "description": "Networks with at least one signal of each type.", + "properties": { + "ip_address": { + "type": "integer" + }, + "device": { + "type": "integer" + }, + "face": { + "type": "integer" + }, + "document_number": { + "type": "integer" + }, + "phone": { + "type": "integer" + }, + "email": { + "type": "integer" + }, + "address": { + "type": "integer" + } + } + }, + "priority": { + "type": "object", + "description": "Networks by priority. `none` counts networks without a priority.", + "properties": { + "low": { + "type": "integer" + }, + "medium": { + "type": "integer" + }, + "high": { + "type": "integer" + }, + "none": { + "type": "integer" + } + } + }, + "assignee": { + "type": "object", + "description": "Networks by assignee.", + "properties": { + "me": { + "type": "integer", + "description": "Assigned to the calling console member. Always 0 for an API key." + }, + "unassigned": { + "type": "integer", + "description": "Assigned to nobody." + }, + "users": { + "type": "array", + "description": "One entry per assignee, most networks first.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "full_name": { + "type": "string", + "description": "The assignee's name, or their email when no name is set." + }, + "email": { + "type": "string" + }, + "count": { + "type": "integer" + } + } + } + } + } + }, + "has_case": { + "type": "integer", + "description": "Networks this application opened a case for." + }, + "min_size": { + "type": "object", + "description": "Networks with at least 10 members, keyed by the minimum size.", + "properties": { + "10": { + "type": "integer" + } + } + } + } + } + } + }, + "NetworkTag": { + "type": "object", + "description": "One of your application's tags on the network.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "The tag's UUID, the same value you pass as `tag_uuid`." + }, + "name": { + "type": "string" + }, + "color": { + "type": "string", + "description": "Hex color, for example `#6366F1`." + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ], + "description": "`didit` for a Didit preset tag, `custom` for a tag your team created." + }, + "added_by": { + "type": "string", + "nullable": true, + "description": "Email of the console member who added the tag. Null when an API key added it." + }, + "added_at": { + "type": "string", + "format": "date-time" + } + } + }, + "NetworkOutcomes": { + "type": "object", + "description": "Where the network's people and businesses stand. Each member you may see counts once, by the status of their most recent verification that reached a decision. Transaction parties are not counted.", + "properties": { + "approved": { + "type": "integer", + "description": "Members whose most recent decided verification was approved." + }, + "declined": { + "type": "integer", + "description": "Members whose most recent decided verification was declined." + }, + "in_review": { + "type": "integer", + "description": "Members whose most recent decided verification is in review." + }, + "total": { + "type": "integer", + "description": "Members with at least one verification, decided or not. A member whose verifications are all undecided (not started, in progress, expired or abandoned) counts here and in no other key." + } + } + }, + "NetworkPatternScore": { + "type": "object", + "description": "How alike one kind of the members' data is across the whole network. A pattern is not a signal: no shared value links the members.", + "properties": { + "type": { + "type": "string", + "enum": [ + "background" + ], + "description": "`background`: the mean similarity of every pair of member selfie backgrounds, with the person masked out. It reads the newest selfie of each person, at most the 50 newest, and leaves out age assurance sessions. It covers the whole network, not only the members of your applications." + }, + "score": { + "type": "number", + "nullable": true, + "minimum": 0, + "maximum": 1, + "description": "From 0 to 1, rounded to 4 decimals. Null when `reason` is set." + }, + "members": { + "type": "integer", + "nullable": true, + "description": "How many members' selfies the score compared. A selfie that cannot be read, or shows too little background, is not compared. Null while `computing`; 0 or 1 with `not_enough_selfies`." + }, + "reason": { + "type": "string", + "enum": [ + "computing", + "not_enough_selfies" + ], + "nullable": true, + "description": "Why there is no score. `computing`: the score for the current members is being computed; read the network again later. `not_enough_selfies`: fewer than two members have a usable selfie. Null when there is a score." + }, + "computed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the score was computed. Null while `computing`." + } + } + }, + "NetworkDetailResponse": { + "description": "The network detail, plus the organization's sharing mode, whether cross-organization signals apply to it, your application's tags on the network, member outcomes and pattern scores.", + "allOf": [ + { + "$ref": "#/components/schemas/NetworkDetail" + }, + { + "type": "object", + "properties": { + "sharing_mode": { + "type": "string", + "enum": [ + "off", + "private", + "global" + ], + "description": "The organization's cross-organization sharing mode. Only `global` compares networks across organizations; `private` and `off` keep them to the organization's own history.", + "example": "global" + }, + "cross_org_available": { + "type": "boolean", + "description": "Whether cross-organization signals apply to this organization. They need both the Enterprise plan and the `global` sharing mode.", + "example": true + }, + "cross_org_unavailable_reason": { + "type": "string", + "enum": [ + "plan", + "sharing_mode" + ], + "nullable": true, + "description": "Why cross-organization signals do not apply, checked in this order. `plan`: the organization's plan does not include the cross-organization fraud network (it needs the Enterprise plan), whatever the sharing mode. `sharing_mode`: the plan includes it, but the sharing mode is `private` or `off`. Null when `cross_org_available` is `true`.", + "example": null + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkTag" + }, + "description": "Your application's tags on the network, oldest first. Each application sees only its own tags. `fraud_tags` is separate and unchanged: the tags the network's members carry." + }, + "outcomes": { + "$ref": "#/components/schemas/NetworkOutcomes" + }, + "patterns": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkPatternScore" + }, + "description": "Pattern scores over the whole network. Today one item, `background`." + } + } + } + ] + }, + "NetworkTagRequest": { + "type": "object", + "required": [ + "tag_uuid" + ], + "properties": { + "tag_uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of one of your application's tags." + } + }, + "example": { + "tag_uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7" + } + }, + "NetworkTimeline": { + "title": "Default timeline (no contract)", + "type": "object", + "description": "Without `contract`: the network's own events, oldest first, not paginated.", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkTimelineEvent" + } + } + } + }, + "NetworkTimelinePage": { + "title": "Typed timeline page (contract=2)", + "type": "object", + "description": "With `contract=2`: one page of typed events, newest first.", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkTypedTimelineEvent" + } + }, + "next_cursor": { + "type": "string", + "nullable": true, + "description": "Pass as `cursor` to read the next page. Null on the last page." + } + } + }, + "NetworkTypedTimelineEvent": { + "type": "object", + "description": "One typed timeline event: the four default fields, plus its id, when Didit stored it, the member account and record it belongs to, and the detail block of its event type. The other detail blocks are null.", + "properties": { + "id": { + "type": "string", + "description": "`:`, unique within the timeline.", + "example": "session:3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "event_type": { + "type": "string", + "enum": [ + "network_detected", + "signal_observed", + "network_active", + "network_in_review", + "network_resolved", + "network_dismissed", + "verification_attempt", + "transaction", + "login", + "rule_hit", + "case_linked", + "note_added" + ] + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "description": "When it happened." + }, + "ingested_at": { + "type": "string", + "format": "date-time", + "description": "When Didit stored it." + }, + "label": { + "type": "string", + "description": "A short English label, for example `Verification attempt 2 of 3`." + }, + "metadata": { + "type": "object", + "description": "For the network's own events, the same metadata as the default timeline. Empty otherwise." + }, + "account": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineAccount" + } + ], + "nullable": true, + "description": "The member account. Null for the network's own events, `case_linked` and `note_added`." + }, + "source_ref": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineSourceRef" + } + ], + "nullable": true, + "description": "The source record. Null for the network's own events and `note_added`." + }, + "verification_attempt": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineAttempt" + } + ], + "nullable": true, + "description": "Set on `verification_attempt` events." + }, + "transaction": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineTransaction" + } + ], + "nullable": true, + "description": "Set on `transaction` events." + }, + "login": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineLogin" + } + ], + "nullable": true, + "description": "Set on `login` events." + }, + "rule_hit": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineRuleHit" + } + ], + "nullable": true, + "description": "Set on `rule_hit` events." + }, + "case_linked": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineCaseLinked" + } + ], + "nullable": true, + "description": "Set on `case_linked` events." + }, + "note_added": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineNoteAdded" + } + ], + "nullable": true, + "description": "Set on `note_added` events. Never returned to an API key." + } + } + }, + "NetworkTimelineAccount": { + "type": "object", + "description": "The member account the event belongs to.", + "properties": { + "node_id": { + "type": "string", + "description": "`:`, where kind is `vendor_user`, `vendor_business` or `transaction_party`.", + "example": "vendor_user:3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "node_type": { + "type": "string", + "enum": [ + "account", + "business_account", + "transaction_party" + ] + }, + "uuid": { + "type": "string", + "format": "uuid" + }, + "vendor_data": { + "type": "string", + "nullable": true + }, + "display_name": { + "type": "string", + "nullable": true + }, + "ownership": { + "type": "string", + "enum": [ + "owned", + "organization" + ], + "description": "`owned` when the record belongs to the application you called (for a console member, to an application they were granted). `organization` when it belongs to another application of your organization." + }, + "route": { + "$ref": "#/components/schemas/NetworkTimelineRoute" + } + } + }, + "NetworkTimelineSourceRef": { + "type": "object", + "description": "The record the event was read from.", + "properties": { + "kind": { + "type": "string", + "enum": [ + "session", + "business_session", + "transaction", + "monitoring_event", + "case" + ] + }, + "id": { + "type": "string", + "format": "uuid" + }, + "ownership": { + "type": "string", + "enum": [ + "owned", + "organization" + ], + "description": "`owned` when the record belongs to the application you called (for a console member, to an application they were granted). `organization` when it belongs to another application of your organization." + }, + "route": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineRoute" + } + ], + "description": "Absent for `monitoring_event` and `case`." + } + } + }, + "NetworkTimelineRoute": { + "type": "object", + "description": "Where the record opens: its kind, its UUID and the application it belongs to.", + "properties": { + "kind": { + "type": "string", + "description": "`user`, `business` or `transaction_party` for an account; `session`, `business_session` or `transaction` for a source record." + }, + "id": { + "type": "string", + "format": "uuid" + }, + "application_id": { + "type": "string", + "format": "uuid" + } + } + }, + "NetworkTimelineAttempt": { + "type": "object", + "description": "One verification session of the account.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The session UUID, or the business session UUID for a company." + }, + "session_number": { + "type": "integer", + "nullable": true + }, + "attempt": { + "type": "integer", + "description": "This session's position among the account's sessions, in creation order." + }, + "attempt_count": { + "type": "integer", + "description": "How many sessions the account has." + }, + "status": { + "type": "string", + "description": "The session status, for example `Approved` or `Declined`." + }, + "status_changed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "decline_reasons": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkTimelineDeclineReason" + }, + "description": "For a declined session only: its warnings, oldest first, once per warning. Empty otherwise." + } + } + }, + "NetworkTimelineDeclineReason": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "description": "The warning code." + }, + "feature": { + "type": "string", + "description": "The feature that raised the warning." + }, + "short_description": { + "type": "string" + } + } + }, + "NetworkTimelineTransaction": { + "type": "object", + "description": "A transaction a member account took part in, dated by its transaction date.", + "properties": { + "transaction_id": { + "type": "string", + "format": "uuid" + }, + "transaction_number": { + "type": "integer", + "nullable": true + }, + "transaction_type": { + "type": "string" + }, + "action_type": { + "type": "string" + }, + "amount": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineAmount" + } + ], + "nullable": true, + "description": "Null when the transaction has no amount." + }, + "status": { + "type": "string" + }, + "decision_reason_code": { + "type": "string", + "nullable": true + }, + "decision_reason_label": { + "type": "string", + "nullable": true + }, + "party_role": { + "type": "string", + "description": "The account's role in the transaction: `APPLICANT`, `REMITTER`, `BENEFICIARY` or `COUNTERPARTY`." + } + } + }, + "NetworkTimelineAmount": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Decimal amount as a string." + }, + "currency": { + "type": "string" + }, + "direction": { + "type": "string", + "nullable": true + } + } + }, + "NetworkTimelineLogin": { + "type": "object", + "description": "A sign-in event you posted for a person through the events API.", + "properties": { + "action": { + "type": "string", + "description": "`.`: `auth.login` or `security.login_succeeded` for a sign-in, `auth.login_failed` or `security.login_failed` for a failed one." + }, + "succeeded": { + "type": "boolean" + }, + "status": { + "type": "string", + "description": "The event's status." + }, + "ip": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineIP" + } + ], + "nullable": true, + "description": "Null when the event has no IP address." + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineDevice" + } + ], + "nullable": true, + "description": "Null when the event has no device fingerprint or context." + }, + "is_first_login": { + "type": "boolean", + "description": "The account's first successful sign-in on record." + }, + "is_new_device": { + "type": "boolean", + "description": "A successful sign-in from a device none of the account's earlier successful sign-ins came from. Never true on the first sign-in." + } + } + }, + "NetworkTimelineIP": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "country_code": { + "type": "string", + "nullable": true + }, + "city": { + "type": "string", + "nullable": true + }, + "connection_type": { + "type": "string", + "nullable": true, + "description": "The connection type label for the address, as free text." + }, + "is_vpn_or_tor": { + "type": "boolean" + }, + "is_data_center": { + "type": "boolean" + } + } + }, + "NetworkTimelineDevice": { + "type": "object", + "properties": { + "fingerprint": { + "type": "string", + "nullable": true + }, + "label": { + "type": "string", + "nullable": true, + "description": "A readable device label built from the event's device context." + } + } + }, + "NetworkTimelineRuleHit": { + "type": "object", + "description": "A live rule that matched a member's transaction or event. Test-mode matches are not included.", + "properties": { + "rule_id": { + "type": "string", + "format": "uuid" + }, + "rule_title": { + "type": "string" + }, + "rule_category": { + "type": "string" + }, + "rule_source": { + "type": "string" + }, + "severity": { + "type": "string", + "nullable": true + }, + "score_delta": { + "type": "integer" + }, + "status_target": { + "type": "string", + "nullable": true + }, + "action_summary": { + "type": "string", + "nullable": true + } + } + }, + "NetworkTimelineCaseLinked": { + "type": "object", + "description": "A case opened for the network in your organization. `source_ref.ownership` says whether it belongs to the application you called.", + "properties": { + "case_id": { + "type": "string", + "format": "uuid" + }, + "case_number": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string" + }, + "author_email": { + "type": "string", + "nullable": true + }, + "author_name": { + "type": "string", + "nullable": true + } + } + }, + "NetworkTimelineNoteAdded": { + "type": "object", + "description": "A note left on the network. The note text is not included.", + "properties": { + "note_id": { + "type": "string", + "format": "uuid" + }, + "author_email": { + "type": "string" + } + } + }, + "FeatureStatusOverride": { + "type": "object", + "description": "One override of a check (feature) status made with `PATCH /v3/session/{sessionId}/features/{nodeId}/update-status/`: what it changes, whether it applied or still waits for the person to finish the flow, who asked and why.", + "required": [ + "id", + "node_id", + "feature_type", + "feature_model", + "previous_status", + "new_status", + "state", + "forced", + "reason", + "requested_by", + "requested_at", + "applied_at", + "cancelled_at", + "cancel_reason" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Identifier of the override. Pass it to the cancel endpoint.", + "example": "7c9e6679-7425-40de-944b-e07fc1f90ae7" + }, + "node_id": { + "type": "string", + "description": "The workflow node the request named (`default` for a step without a node).", + "example": "feature_ocr_1" + }, + "feature_type": { + "type": "string", + "description": "The `feature_type` the request passed, or an empty string when it passed none.", + "example": "" + }, + "feature_model": { + "type": "string", + "description": "The kind of check that was overridden, for example `KYC`, `Face`, `AML` or `GeolocationCheck`.", + "example": "KYC" + }, + "previous_status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "The check's status when the override was requested.", + "example": "In Review" + }, + "new_status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "description": "The status the override sets.", + "example": "Approved" + }, + "state": { + "type": "string", + "enum": [ + "applied", + "pending_until_flow_finishes", + "cancelled" + ], + "description": "`applied`: the check has the new status. `pending_until_flow_finishes`: the override waits and applies when the person finishes the flow. `cancelled`: it waited and never applied (see `cancel_reason`).", + "example": "applied" + }, + "forced": { + "type": "boolean", + "description": "`true` when the override applied at once while the person was still in the flow.", + "example": false + }, + "reason": { + "type": "string", + "nullable": true, + "description": "The `comment` sent with the request, or `null`.", + "example": "Document manually verified by compliance." + }, + "requested_by": { + "type": "object", + "nullable": true, + "description": "Who requested the override, or `null` when that is not known.", + "properties": { + "email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email of the console user, or `null` for API keys and the system.", + "example": "reviewer@example.com" + }, + "name": { + "type": "string", + "nullable": true, + "description": "Display name of the requester, or `null`.", + "example": "Alex Reviewer" + }, + "type": { + "type": "string", + "enum": [ + "CONSOLE_USER", + "API_KEY", + "SYSTEM" + ], + "description": "Who made the request: a console user, an API key, or Didit itself.", + "example": "CONSOLE_USER" + } + } + }, + "requested_at": { + "type": "string", + "format": "date-time", + "description": "When the override was requested.", + "example": "2026-10-13T09:15:00Z" + }, + "applied_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the check took the new status, or `null` while it waits or once it is cancelled.", + "example": "2026-10-13T09:15:00Z" + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When a waiting override was cancelled, or `null`.", + "example": null + }, + "cancel_reason": { + "type": "string", + "nullable": true, + "enum": [ + "cancelled_by_reviewer", + "replaced", + "check_changed", + "session_closed", + null + ], + "description": "Why a waiting override never applied, or `null`. `cancelled_by_reviewer`: cancelled with the cancel endpoint. `replaced`: a newer override on the same check replaced it. `check_changed`: the person redid the check before finishing the flow. `session_closed`: the session left the flow before the person finished it (abandoned, expired, or decided mid-flow).", + "example": null + } + } + }, + "SandboxSimulateRequest": { + "type": "object", + "description": "Force a sandbox session's status. Every field is optional, but at least one of `new_status`, `scenario` or `risk` is required.", + "properties": { + "new_status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned" + ], + "description": "The status to set. When you leave it out, it defaults to the default status of the risk case (`risk`, or the first risk the scenario's magic values carry), else to the scenario's default status.", + "example": "Declined" + }, + "comment": { + "type": "string", + "maxLength": 500, + "description": "Note added to the activity entry the status change records.", + "example": "Testing the decline path" + }, + "scenario": { + "type": "string", + "maxLength": 100, + "description": "A scenario slug from the catalog (`GET /v1/sandbox/scenarios/`). It supplies the default status and, without `risk`, the warning its magic values carry. An unknown slug returns `400`.", + "example": "decline_aml_hit" + }, + "risk": { + "type": "string", + "maxLength": 128, + "description": "A risk case from the catalog. Its warning is added to the session, with a log type that follows `new_status`. An unknown value returns `400`.", + "example": "POSSIBLE_MATCH_FOUND" + }, + "node_id": { + "type": "string", + "maxLength": 128, + "description": "Workflow node the risk warning is attached to. Defaults to the session's current node.", + "example": "feature_aml_1" + }, + "send_webhooks": { + "type": "boolean", + "default": true, + "description": "`true` sends exactly the webhooks a real status change sends to the sandbox application's destinations: `status.updated`, plus the `user.status.updated` / `user.data.updated` (or `business.status.updated` / `business.data.updated`) events the change causes, for destinations subscribed to them. `false` changes the status without logging or sending any webhook: the missed-webhook recovery never re-sends that decision, the console still updates live, and the next status change of the session sends webhooks as usual.", + "example": true + } + } + }, + "SandboxSimulateResponse": { + "type": "object", + "required": [ + "session_id", + "previous_status", + "status", + "scenario", + "risk", + "send_webhooks" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "previous_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "The session's status before the simulation.", + "example": "In Progress" + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned" + ], + "description": "The status the simulation set.", + "example": "Declined" + }, + "scenario": { + "type": "string", + "nullable": true, + "description": "The scenario slug you passed, or `null`.", + "example": "decline_aml_hit" + }, + "risk": { + "type": "string", + "nullable": true, + "description": "The risk case applied (`risk`, or the one taken from the scenario), or `null` when none.", + "example": "POSSIBLE_MATCH_FOUND" + }, + "send_webhooks": { + "type": "boolean", + "description": "The `send_webhooks` value the simulation applied.", + "example": false + } + } + }, + "CaseTeamSummary": { + "type": "object", + "nullable": true, + "description": "The review team that owns the case, or null when no team owns it. Routing sets it when a routing rule places the case with one of its teams. Change or clear it with `PATCH .../cases/{case_uuid}/` (`team`).", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + } + } + }, + "CaseChecker": { + "type": "object", + "nullable": true, + "description": "The member named to approve or reject the pending resolution. Set only while a four-eyes resolution waits on a named checker. Null when nothing is pending, while the pending resolution waits on the checker queue, and once the resolution is approved, rejected, or returned for a new review because the evidence changed.", + "properties": { + "member_uuid": { + "type": "string", + "format": "uuid", + "description": "The member's user uuid: the value you pass as `checker` when you resolve the case." + }, + "name": { + "type": "string", + "nullable": true, + "description": "The member's full name, or null when it is not known." + }, + "email": { + "type": "string", + "format": "email" + } + } + }, + "ServiceStatusFeed": { + "type": "object", + "description": "Public service status: the state of each Didit service, the overall state, and the incidents that are ongoing or were resolved in the last 7 days.", + "required": [ + "status", + "updated_at", + "services", + "incidents" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "operational", + "degraded", + "down" + ], + "description": "The worst state of any service right now, decided by the ongoing incidents only.", + "example": "degraded" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When Didit built this response (ISO 8601, UTC). Responses are cached for 30 seconds, so it can be up to 30 seconds old.", + "example": "2026-10-13T09:41:07.512345Z" + }, + "services": { + "type": "array", + "description": "One entry per service, every service always listed.", + "items": { + "$ref": "#/components/schemas/ServiceStatusService" + } + }, + "incidents": { + "type": "array", + "description": "Newest `started_at` first. Every ongoing incident, plus the incidents resolved in the last 7 days (at most 50 resolved incidents). Empty when nothing happened in that time.", + "items": { + "$ref": "#/components/schemas/ServiceStatusIncident" + } + } + } + }, + "ServiceStatusService": { + "type": "object", + "required": [ + "key", + "name", + "status" + ], + "properties": { + "key": { + "type": "string", + "enum": [ + "service_id_verification", + "service_liveness_face_match", + "service_aml_screening", + "service_kyb_registries", + "service_database_validation", + "service_phone_delivery", + "service_email_delivery", + "service_transaction_monitoring" + ], + "description": "Stable service key. Match on this, never on the display name. `service_id_verification`: ID verification, `service_liveness_face_match`: Liveness and face match, `service_aml_screening`: AML screening, `service_kyb_registries`: KYB registries, `service_database_validation`: Database validation, `service_phone_delivery`: Phone verification delivery, `service_email_delivery`: Email verification delivery, `service_transaction_monitoring`: Transaction monitoring.", + "example": "service_database_validation" + }, + "name": { + "type": "string", + "description": "English display name of the service.", + "example": "Database validation" + }, + "status": { + "type": "string", + "enum": [ + "operational", + "degraded", + "down" + ], + "description": "`operational` when no incident is ongoing for the service, otherwise the worst state of its ongoing incidents.", + "example": "degraded" + } + } + }, + "ServiceStatusIncident": { + "type": "object", + "required": [ + "id", + "service", + "service_name", + "state", + "status", + "title", + "body", + "affected", + "country_name", + "started_at", + "resolved_at", + "duration_minutes" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Incident ID.", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "service": { + "type": "string", + "enum": [ + "service_id_verification", + "service_liveness_face_match", + "service_aml_screening", + "service_kyb_registries", + "service_database_validation", + "service_phone_delivery", + "service_email_delivery", + "service_transaction_monitoring" + ], + "description": "Key of the affected service (see `services[].key`).", + "example": "service_database_validation" + }, + "service_name": { + "type": "string", + "description": "English display name of the affected service.", + "example": "Database validation" + }, + "state": { + "type": "string", + "enum": [ + "degraded", + "down" + ], + "description": "How the service is affected. A resolved incident keeps the last state it had.", + "example": "degraded" + }, + "status": { + "type": "string", + "enum": [ + "ongoing", + "resolved" + ], + "description": "`ongoing` until the service recovers, then `resolved`.", + "example": "ongoing" + }, + "title": { + "type": "string", + "description": "English headline. To translate it, build your own text from `service`, `state` and `country_name`.", + "example": "Database validation is degraded in Brazil." + }, + "body": { + "type": "string", + "description": "English detail line: since when checks may be slow or fail, or when it recovered.", + "example": "Checks against Brazil (CPF) may be slow or fail since 08:55 UTC." + }, + "affected": { + "type": "string", + "description": "Countries or registries affected, for example `Brazil (CPF)`. Empty when the incident is not tied to a country or registry.", + "example": "Brazil (CPF)" + }, + "country_name": { + "type": "string", + "description": "Country affected, in English. Empty when the incident is not tied to a country.", + "example": "Brazil" + }, + "started_at": { + "type": "string", + "format": "date-time", + "description": "When the incident started (ISO 8601, UTC).", + "example": "2026-10-13T08:55:00Z" + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the service recovered (ISO 8601, UTC). `null` while the incident is ongoing." + }, + "duration_minutes": { + "type": "integer", + "nullable": true, + "description": "Whole minutes from `started_at` to `resolved_at`. `null` while the incident is ongoing." + } + } + }, + "TravelRuleJoinInvitation": { + "type": "object", + "description": "What the join page shows before anything is typed.", + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "The address the invitation went to." + }, + "vasp_name": { + "type": "string" + }, + "source": { + "type": "string", + "description": "Where the invitation came from, for example `pickup`." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "prefill": { + "type": "object", + "properties": { + "vasp_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "legal_name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "jurisdiction": { + "type": "string" + }, + "domains": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "TravelRuleJoinRegisterRequest": { + "type": "object", + "required": [ + "email", + "password" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "maxLength": 254, + "description": "Must be the address the invitation went to." + }, + "password": { + "type": "string", + "minLength": 8, + "maxLength": 128 + } + } + }, + "TravelRuleJoinVerifyRequest": { + "type": "object", + "required": [ + "email", + "code" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "maxLength": 254 + }, + "code": { + "type": "string", + "minLength": 6, + "maxLength": 6, + "description": "The 6-character code emailed after registering." + } + } + }, + "TravelRuleJoinCredentials": { + "type": "object", + "description": "The new free organization and the credentials its application calls the API with.", + "properties": { + "organization": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + } + } + }, + "application": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + } + } + }, + "client_id": { + "type": "string" + }, + "api_key": { + "type": "string", + "description": "Send as the `x-api-key` header on the workspace endpoints." + } + } + }, + "TravelRuleWorkspaceClaimRequest": { + "type": "object", + "required": [ + "vasp_id" + ], + "properties": { + "vasp_id": { + "type": "string", + "format": "uuid", + "description": "The directory entry this workspace is. Read ids from `GET /v3/travel-rule/vasps/`." + } + } + }, + "TravelRuleWorkspaceDomainConfirmRequest": { + "type": "object", + "required": [ + "code" + ], + "properties": { + "code": { + "type": "string", + "minLength": 6, + "maxLength": 6 + } + } + }, + "TravelRuleWorkspaceBillingRequest": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": true, + "description": "Only `true` is accepted: outbound billing cannot be turned back off through the API." + } + } + }, + "TravelRuleWorkspaceDomainCodeResponse": { + "type": "object", + "properties": { + "domain": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "unverified", + "code_sent", + "verified" + ] + }, + "txt": { + "$ref": "#/components/schemas/TravelRuleWorkspaceTxtRecord" + } + } + }, + "TravelRuleWorkspaceTxtRecord": { + "type": "object", + "description": "The TXT record to publish for the DNS proof. Empty until a proof has been started.", + "properties": { + "host": { + "type": "string", + "example": "didit-tr-verify.example.com" + }, + "value": { + "type": "string" + } + } + }, + "TravelRuleWorkspace": { + "type": "object", + "description": "The free Travel Rule workspace of an application that joined free.", + "properties": { + "joined_free": { + "type": "boolean" + }, + "work_email": { + "type": "string", + "format": "email" + }, + "joined_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "claim": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "unclaimed", + "pending_review", + "claimed", + "rejected" + ] + }, + "proved_by_domain": { + "type": "boolean" + }, + "claimed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reviewed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "review_note": { + "type": "string" + }, + "vasp_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "vasp_name": { + "type": "string" + } + } + }, + "prefill": { + "type": "object", + "description": "Profile values taken from the directory entry: `vasp_id`, `legal_name`, `lei`, `jurisdiction`, `compliance_email`, `domains`. Nothing is written until the claim is made." + }, + "domain": { + "type": "object", + "properties": { + "domain": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "unverified", + "code_sent", + "verified" + ] + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "txt": { + "$ref": "#/components/schemas/TravelRuleWorkspaceTxtRecord" + } + } + }, + "billing_enabled": { + "type": "boolean", + "description": "Receiving Travel Rule data is free. Sending is billed only after billing is enabled." + }, + "billing_enabled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "didit_network": { + "type": "object", + "description": "Whether the Didit network lists this VASP and, when not, each condition it still misses." + }, + "settings": { + "$ref": "#/components/schemas/TravelRuleSettingsDetail" + } + } + }, + "InvestigatorDecision": { + "type": "object", + "description": "How the item left the Investigator queue. `decided_by`, `original_outcome` and `overrides` are returned by the accept and override endpoints and the report; the `investigator` block of a session decision carries the other fields.", + "properties": { + "outcome": { + "type": "string", + "enum": [ + "accepted", + "edited", + "overridden", + "decided_elsewhere", + "automated" + ], + "description": "`accepted` or `edited` took the recommendation (`edited` with a rewritten narrative), `overridden` took another action, `decided_elsewhere` is a decision taken outside the Investigator while the report was open, `automated` is a decision the Investigator took on its own." + }, + "action": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ] + }, + "actor_email": { + "type": "string", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "override_category": { + "type": "string", + "enum": [ + "missing_context", + "needs_information", + "policy_differs", + "evidence_misread", + "other", + "" + ], + "nullable": true + }, + "override_reason": { + "type": "string", + "nullable": true + }, + "final_status": { + "type": "string", + "description": "The item's status after the decision." + }, + "agreed": { + "type": "boolean", + "description": "Whether the decision matched the recommendation.", + "nullable": true + }, + "decided_at": { + "type": "string", + "format": "date-time" + }, + "billed": { + "type": "boolean", + "description": "The review was charged." + }, + "decided_by": { + "type": "string", + "enum": [ + "investigator", + "member" + ] + }, + "original_outcome": { + "type": "object", + "description": "The decision the Investigator took on its own, unchanged by later overrides. Null for a decision the team took.", + "properties": { + "action": { + "type": "string" + }, + "final_status": { + "type": "string" + }, + "decided_at": { + "type": "string", + "format": "date-time" + } + }, + "nullable": true + }, + "overrides": { + "type": "array", + "description": "Every override of a decision the Investigator took on its own, oldest first.", + "items": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "actor_email": { + "type": "string", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "override_category": { + "type": "string" + }, + "override_reason": { + "type": "string" + }, + "previous_status": { + "type": "string" + }, + "final_status": { + "type": "string" + }, + "agreed": { + "type": "boolean", + "nullable": true + }, + "overridden_at": { + "type": "string", + "format": "date-time" + } + } + } + } + } + }, + "InvestigatorBlock": { + "type": "object", + "nullable": true, + "description": "The item's latest Investigator review, or null when it never had one. Carried by the V3 session and business session decision (and so by `status.updated` webhooks).", + "properties": { + "run_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "running", + "completed", + "failed" + ] + }, + "stage": { + "type": "string", + "enum": [ + "shadow", + "live" + ] + }, + "queue": { + "type": "string", + "enum": [ + "kyc", + "kyb", + "aml", + "transactions", + "cases" + ] + }, + "is_open": { + "type": "boolean" + }, + "recommendation": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ], + "nullable": true + }, + "recommendation_detail": { + "type": "string", + "nullable": true + }, + "confidence": { + "type": "string", + "enum": [ + "high", + "moderate", + "low" + ], + "nullable": true + }, + "summary": { + "type": "string", + "nullable": true + }, + "ready_to_accept": { + "type": "boolean" + }, + "prepared_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "automated": { + "type": "boolean", + "description": "The Investigator decided on its own, even if the decision was later overridden." + }, + "decided_by": { + "type": "string", + "enum": [ + "investigator", + "member" + ], + "nullable": true + }, + "decision": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/InvestigatorDecision" + } + ], + "type": "object" + } + } + }, + "InvestigatorQueueRow": { + "type": "object", + "properties": { + "run_id": { + "type": "string", + "format": "uuid" + }, + "kind": { + "type": "string", + "enum": [ + "session", + "business_session", + "transaction", + "case" + ] + }, + "queue": { + "type": "string", + "enum": [ + "kyc", + "kyb", + "aml", + "transactions", + "cases" + ] + }, + "stage": { + "type": "string", + "enum": [ + "shadow", + "live" + ] + }, + "subject": { + "type": "object", + "description": "The item under review: `id`, `number`, `status`, `name` and, depending on the kind, `country`, `workflow`, `vendor_data`, `txn_id`, `amount`, `currency`, `direction`, `title` or `priority`.", + "additionalProperties": true + }, + "recommendation": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ] + }, + "recommendation_detail": { + "type": "string" + }, + "confidence": { + "type": "string", + "enum": [ + "high", + "moderate", + "low" + ] + }, + "summary": { + "type": "string" + }, + "sources_count": { + "type": "integer" + }, + "ready_to_accept": { + "type": "boolean" + }, + "guardrails": { + "description": "The locked guardrails that hold this item, if any." + }, + "waiting_since": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "prepared_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "InvestigatorRunDetail": { + "allOf": [ + { + "$ref": "#/components/schemas/InvestigatorQueueRow" + }, + { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "is_open": { + "type": "boolean" + }, + "lookups": { + "type": "integer", + "description": "How many sections and linked records it read." + }, + "duration_ms": { + "type": "integer", + "nullable": true + }, + "agent_version": { + "type": "string" + }, + "prepared_before_opened": { + "type": "boolean", + "nullable": true + }, + "report": { + "type": "object", + "additionalProperties": true, + "description": "The report: reasoning, evidence points with their verdict (`ok`, `risk`, `look`, `explained`, `context`) and cited sources, screening hit verdicts (`same`, `not_same`, `unclear`) and suggested next steps." + }, + "trail": { + "description": "What I checked: the overview, sections and records it read, in order." + }, + "decision": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/InvestigatorDecision" + } + ], + "type": "object" + }, + "autonomy": { + "type": "object", + "additionalProperties": true, + "description": "What autonomy made of the report (`result`, `reason`, `tier`, `policy_version`, `rule`, `agreement`, `decisions`, `threshold`, `at`). Null when the queue is not automated.", + "nullable": true + } + } + } + ] + }, + "InvestigatorAcceptRequest": { + "type": "object", + "properties": { + "narrative": { + "type": "string", + "maxLength": 4000, + "description": "A rewritten narrative. Sending one records the decision as edited and accepted." + }, + "send_email": { + "type": "boolean", + "default": false, + "description": "Email the person about the decision." + }, + "email_address": { + "type": "string", + "format": "email", + "description": "Required when `send_email` is true." + }, + "email_language": { + "type": "string", + "default": "en", + "maxLength": 16 + } + } + }, + "InvestigatorOverrideRequest": { + "type": "object", + "required": [ + "action", + "category", + "reason" + ], + "properties": { + "action": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ] + }, + "category": { + "type": "string", + "enum": [ + "missing_context", + "needs_information", + "policy_differs", + "evidence_misread", + "other" + ] + }, + "reason": { + "type": "string", + "minLength": 10, + "maxLength": 500 + }, + "send_email": { + "type": "boolean", + "default": false, + "description": "Email the person about the decision." + }, + "email_address": { + "type": "string", + "format": "email", + "description": "Required when `send_email` is true." + }, + "email_language": { + "type": "string", + "default": "en", + "maxLength": 16 + } + } + }, + "InvestigatorBatchAcceptRequest": { + "type": "object", + "required": [ + "run_ids" + ], + "properties": { + "run_ids": { + "type": "array", + "minItems": 1, + "maxItems": 50, + "items": { + "type": "string", + "format": "uuid" + } + } + } + }, + "InvestigatorBatchAcceptResponse": { + "type": "object", + "properties": { + "accepted": { + "type": "integer" + }, + "skipped": { + "type": "integer" + }, + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "run_id": { + "type": "string", + "format": "uuid" + }, + "result": { + "type": "string", + "enum": [ + "accepted", + "skipped" + ] + }, + "reason": { + "type": "string", + "enum": [ + "not_found", + "forbidden", + "already_decided", + "not_ready", + "guardrail", + "invalid" + ] + }, + "detail": {}, + "action": { + "type": "string" + }, + "final_status": { + "type": "string" + } + } + } + } + } + }, + "InvestigatorSettings": { + "type": "object", + "properties": { + "stage": { + "type": "string", + "enum": [ + "none", + "shadow", + "live" + ] + }, + "stage_changed_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "nullable": true + }, + "stage_changed_by": { + "type": "string", + "readOnly": true + }, + "monthly_spend_limit": { + "type": "string", + "format": "decimal", + "description": "Monthly limit in your billing currency, 0.01 to 1,000,000. Null turns it off.", + "nullable": true + }, + "spend_limit": { + "type": "object", + "readOnly": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "limit": { + "type": "string", + "format": "decimal", + "nullable": true + }, + "spent": { + "type": "string", + "format": "decimal" + }, + "reviews": { + "type": "integer" + }, + "reserved": { + "type": "string", + "format": "decimal", + "description": "The price of every open live report, held back because accepting it charges it." + }, + "currency": { + "type": "string" + }, + "paused": { + "type": "boolean", + "description": "At the limit: no new review starts." + } + } + } + } + }, + "TravelRuleUtxoEntry": { + "type": "object", + "description": "One input or output of a UTXO transaction, as the chain recorded it.", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string", + "maxLength": 128 + }, + "amount": { + "type": "string", + "nullable": true, + "description": "Decimal string in the coin's own unit (BTC, not satoshis). Optional.", + "example": "0.0125" + }, + "txid": { + "type": "string", + "maxLength": 128, + "description": "Optional. The transaction an input spends." + }, + "vout": { + "type": "integer", + "nullable": true, + "minimum": 0, + "description": "Optional. The output index an input spends." + } + } + }, + "TravelRuleUtxo": { + "type": "object", + "description": "The inputs and outputs of a UTXO transfer (BTC, LTC, BCH, DOGE). Only accepted on those chains: on any other chain it is a `400`. From them Didit infers the originator wallet (the declared one, else the input address that contributed the most) and the beneficiary output (the declared one, else the only output that does not pay an input address back as change, else the one output paying the transfer amount). The result is on the transfer's `chain_details`.", + "required": [ + "outputs" + ], + "properties": { + "inputs": { + "type": "array", + "maxItems": 1000, + "items": { + "$ref": "#/components/schemas/TravelRuleUtxoEntry" + }, + "description": "Optional. Without inputs no originator is inferred and no change output is recognised." + }, + "outputs": { + "type": "array", + "minItems": 1, + "maxItems": 1000, + "items": { + "$ref": "#/components/schemas/TravelRuleUtxoEntry" + } + } + } + }, + "TravelRuleChainSide": { + "type": "object", + "description": "One side's wallet as Didit resolved it from the chain data.", + "properties": { + "address": { + "type": "string", + "description": "The wallet. On a memo network the classic address (an XRP X-address is split into its classic address and its tag); on Lightning the payee node id or Lightning Address." + }, + "memo": { + "type": "string", + "description": "The destination tag or memo that is part of the wallet; `\"\"` for none." + }, + "source": { + "type": "string", + "enum": [ + "declared", + "x_address", + "largest_input", + "only_non_change_output", + "amount_match", + "invoice_payee", + "updated" + ], + "description": "Where the wallet came from: `declared` (your request), `x_address` (split from an XRP X-address), `largest_input` (the UTXO input address that contributed the most), `only_non_change_output` (the only UTXO output that is not change), `amount_match` (the one UTXO output paying the transfer amount), `invoice_payee` (the payee a Lightning payment request names), `updated` (set later with `action: update_originator_wallet`)." + } + } + }, + "TravelRuleChainDetails": { + "type": "object", + "description": "How the transfer's chain names its two wallets, when that is more than one address paying another. Empty (`{}`) for an account-chain transfer (EVM, Solana, Tron), where the two addresses are the wallets. Matching, routing and the address book read the wallets from here first.", + "properties": { + "chain": { + "type": "string", + "description": "Canonical chain, e.g. `BTC`, `XRP`, or `LIGHTNING` for a Lightning transfer." + }, + "shape": { + "type": "string", + "enum": [ + "utxo", + "memo", + "lightning" + ] + }, + "originator": { + "$ref": "#/components/schemas/TravelRuleChainSide" + }, + "beneficiary": { + "$ref": "#/components/schemas/TravelRuleChainSide" + }, + "utxo": { + "type": "object", + "description": "On a UTXO transfer: the inputs and outputs as stored (amounts as decimal strings) and which outputs Didit read as what.", + "properties": { + "inputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleUtxoEntry" + } + }, + "outputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleUtxoEntry" + } + }, + "change_output_indexes": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "Outputs that pay an input address back." + }, + "beneficiary_output_index": { + "type": "integer", + "nullable": true, + "description": "The output that pays the beneficiary; null when none could be told apart." + } + } + }, + "lightning": { + "type": "object", + "description": "On a Lightning transfer: what the payment request says. Always `kind` (`bolt11`, `bolt12_offer`, `bolt12_invoice`, `bolt12_invoice_request`, `lnurl`, `lightning_address`, `node_id` or `unknown`); a BOLT11 invoice adds `network`, `payee_node_id`, `payment_hash`, `amount_msat`, `created_at` and `expires_at`; a BOLT12 string `blinded_paths` and `payee_node_id`; an LNURL or a Lightning Address its `domain`. Nothing is resolved over the network: the request is decoded only.", + "additionalProperties": true + }, + "warnings": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "declared_originator_not_an_input", + "declared_beneficiary_not_an_output", + "beneficiary_output_ambiguous", + "beneficiary_output_amount_differs" + ] + }, + "description": "UTXO inconsistencies worth a look: the declared originator is not an input, the declared beneficiary is not an output, no single beneficiary output could be told apart, or the beneficiary output pays a different amount than the transaction states." + }, + "not_applicable_reason": { + "type": "string", + "enum": [ + "LIGHTNING_NO_PAYEE_IDENTIFIER", + "LIGHTNING_BOLT12_BLINDED_PAYEE", + "LIGHTNING_LNURL_NAMES_NO_PAYEE" + ], + "description": "On an outbound Lightning transfer whose payment request names no payee: why the transfer is `NOT_APPLICABLE`. `LIGHTNING_NO_PAYEE_IDENTIFIER` (the request names no payee node, or could not be decoded), `LIGHTNING_BOLT12_BLINDED_PAYEE` (the offer hides its payee behind blinded paths), `LIGHTNING_LNURL_NAMES_NO_PAYEE` (an LNURL names a service, not a payee). The same code opens the transfer's last `timeline` entry." + } + } + }, + "WalletAddressImportRow": { + "type": "object", + "description": "One wallet to import: by its `address`, its `address_hash`, or both. The camelCase forms of these fields are accepted too, as are `walletAddress`, `walletAddressHash`, `destinationTag` and `externalUserId`.", + "properties": { + "address": { + "type": "string", + "maxLength": 128, + "description": "The wallet address. Alias: `walletAddress`." + }, + "address_hash": { + "type": "string", + "description": "SHA-256 of the lowercased address, as 64 hex characters or 44 base64 characters. Send it alone to register a wallet without disclosing its address; sent with `address`, it must be that address's hash. Alias: `walletAddressHash`." + }, + "chain": { + "type": "string", + "maxLength": 32 + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "The destination tag or memo. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`." + }, + "holder_name": { + "type": "string", + "maxLength": 255 + }, + "holder_vendor_data": { + "type": "string", + "maxLength": 255, + "description": "Your internal identifier for the holder. Alias: `externalUserId`." + }, + "entity_type": { + "type": "string", + "enum": [ + "individual", + "company" + ], + "default": "individual" + }, + "label": { + "type": "string", + "maxLength": 255 + }, + "owner_type": { + "type": "string", + "enum": [ + "customer", + "internal", + "vasp" + ], + "default": "customer" + }, + "travel_address": { + "type": "string", + "maxLength": 512 + }, + "self_declared": { + "type": "boolean", + "default": false, + "description": "Create the entry already verified through a self-declaration, when your settings allow it." + } + } + }, + "WalletAddressImportReport": { + "type": "object", + "description": "What happened to each row of an import, in the order sent.", + "properties": { + "total": { + "type": "integer" + }, + "created": { + "type": "integer" + }, + "existing": { + "type": "integer" + }, + "duplicate": { + "type": "integer" + }, + "failed": { + "type": "integer" + }, + "rows": { + "type": "array", + "items": { + "type": "object", + "properties": { + "row": { + "type": "integer", + "description": "The row's number, from 1." + }, + "status": { + "type": "string", + "enum": [ + "created", + "existing", + "duplicate", + "failed" + ], + "description": "`created`: added to the book. `existing`: the wallet is already in the book and was left untouched. `duplicate`: repeats an earlier row of this import. `failed`: see `errors`." + }, + "entry_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The address-book entry the row created or found." + }, + "errors": { + "type": "object", + "description": "Field errors of a `failed` row; empty otherwise." + }, + "duplicate_of_row": { + "type": "integer", + "nullable": true, + "description": "On a `duplicate` row, the row it repeats." + } + } + } + } + } + }, + "TravelRuleCounterpartyContact": { + "type": "object", + "description": "A compliance address the EMAIL rail writes to for one directory entry.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "counterparty": { + "type": "string", + "format": "uuid", + "description": "The directory entry's `id`, as GET /v3/travel-rule/vasps/ shows it." + }, + "counterparty_name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "name": { + "type": "string", + "description": "Optional label, for example the team name." + }, + "source": { + "type": "string", + "enum": [ + "staff", + "application", + "learned" + ], + "description": "staff: curated by Didit. learned: an address at one of the VASP's own domains that answered a request through its response link. application: added by your application." + }, + "scope": { + "type": "string", + "enum": [ + "platform", + "application" + ], + "description": "platform: curated by Didit or learned from a verified answer, used for every Didit customer, read-only for you. application: private to your application." + }, + "created_by": { + "type": "string", + "description": "Empty for platform contacts and for contacts an API key added." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this address last answered a request through its response link." + }, + "is_invalid": { + "type": "boolean", + "description": "true once the address bounced or complained; the EMAIL rail skips it." + }, + "invalid_reason": { + "type": "string", + "enum": [ + "", + "hard_bounce", + "soft_bounce", + "spam_complaint", + "suppressed" + ] + }, + "invalid_detail": { + "type": "string", + "description": "The receiving mail server's reason, when it gave one." + }, + "invalid_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleCounterpartyContactCreate": { + "type": "object", + "required": [ + "counterparty", + "email" + ], + "properties": { + "counterparty": { + "type": "string", + "format": "uuid", + "description": "The directory entry's `id`." + }, + "email": { + "type": "string", + "format": "email", + "maxLength": 254 + }, + "name": { + "type": "string", + "description": "Optional label.", + "maxLength": 255 + } + } + }, + "TravelRuleNameMatch": { + "type": "object", + "nullable": true, + "description": "The name comparison that decided the transfer, or null until one ran. While the parties are masked, and on the other customer's copy of an INTERNAL exchange, `transfer_name`, `compared_name`, `matched_names` and `mismatched_names` are hidden. `score` and `matched` can disagree: `matched` follows the strictness, `score` is the similarity of the two full names.", + "properties": { + "score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Similarity of the two full names, 0 to 100. 0 when either name is blank." + }, + "matched": { + "type": "boolean", + "description": "Whether the names satisfy `strictness`. The transfer's outcome follows `data_match`, which compares every field, so a transfer can be COUNTERPARTY_MISMATCHED_DATA while this is true." + }, + "method": { + "type": "string", + "enum": [ + "none", + "exact_token", + "token_subset", + "token_containment", + "sequence_ratio" + ], + "description": "How `matched` was decided: none (`NONE`), exact_token (`STRICT`), token_subset (`DEFAULT`), token_containment (`CONTAINS`) or sequence_ratio (`FUZZY`)." + }, + "strictness": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ] + }, + "compared_with": { + "type": "string", + "enum": [ + "wallet_holder", + "counterparty_vasp" + ], + "description": "wallet_holder: the holder recorded for the wallet. counterparty_vasp: the name the counterparty answered on the email response page, or the Didit Test VASP's answer in sandbox." + }, + "transfer_name": { + "type": "string", + "description": "The name on the transfer." + }, + "compared_name": { + "type": "string", + "description": "The name it was compared with." + }, + "matched_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Words of `transfer_name` found in `compared_name`." + }, + "mismatched_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Words of `transfer_name` missing from `compared_name`, then words of `compared_name` missing from `transfer_name`." + } + } + }, + "TravelRuleTransferRow": { + "type": "object", + "description": "One Travel Rule transfer as the transfers list shows it: VASP names, wallets and memos only, never the originator or beneficiary person data.", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The transfer's id." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "The Didit transaction the transfer belongs to." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "Your own id for the transaction. Transfers Didit books for you, such as a Didit Test VASP inbound transfer, carry a generated one." + }, + "txn_date": { + "type": "string", + "format": "date-time" + }, + "kind": { + "type": "string", + "enum": [ + "withdrawal", + "deposit", + "message" + ], + "description": "withdrawal: outbound. message: an inbound transfer a rail created from a counterparty's message (a TRP inquiry, the INTERNAL inbound leg, or a GTR, VerifyVASP or Sygna Bridge message). deposit: any other inbound transfer." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ] + }, + "status": { + "type": "string", + "enum": [ + "AWAITING_COUNTERPARTY", + "ON_HOLD", + "COMPLETED", + "FINISHED", + "CANCELLED", + "EXPIRED", + "COUNTERPARTY_MISMATCHED_DATA", + "COUNTERPARTY_VASP_NOT_FOUND", + "COUNTERPARTY_VASP_NOT_REACHABLE", + "UNCONFIRMED_OWNERSHIP", + "COUNTERPARTY_VASP_GENERAL_DECLINE", + "NOT_ENOUGH_COUNTERPARTY_DATA", + "NOT_APPLICABLE" + ] + }, + "reason": { + "type": "string", + "description": "Why a `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE` transfer is stuck, the same value as the transfer's own `reason`, so the list tells a bounced compliance email from a counterparty with no route without opening each transfer. `\"\"` for any other status. See [Why a transfer is stuck](/transaction-monitoring/travel-rule#why-a-transfer-is-stuck)." + }, + "rail": { + "type": "string", + "nullable": true, + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL", + "TEST_VASP", + null + ] + }, + "asset": { + "type": "string", + "description": "The transaction's currency." + }, + "amount": { + "type": "string", + "description": "The amount as a plain decimal, for example \"1.25\"." + }, + "originator_vasp": { + "type": "string", + "description": "Outbound: your VASP's legal name. Inbound: the counterparty's name, from its directory entry, the Didit network member, the TRP sender, the Didit Test VASP, or the name the transfer declared. Empty when none is known." + }, + "beneficiary_vasp": { + "type": "string", + "description": "Outbound: the counterparty's name, worked out the same way. Inbound: your VASP's legal name." + }, + "originator_wallet": { + "type": "string" + }, + "beneficiary_wallet": { + "type": "string" + }, + "originator_memo": { + "type": "string", + "description": "Memo or destination tag, empty on chains without one." + }, + "beneficiary_memo": { + "type": "string" + }, + "originator_wallet_type": { + "type": "string", + "enum": [ + "vasp", + "self_hosted", + "own" + ], + "description": "vasp, self_hosted (declared unhosted) or own (a wallet your address book records as yours). Your own side is always vasp." + }, + "beneficiary_wallet_type": { + "type": "string", + "enum": [ + "vasp", + "self_hosted", + "own" + ] + }, + "matched_transaction_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The other side of a deposit you matched to an announced transfer by hand." + }, + "deadline_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the transfer expires if it is still waiting on its counterparty. Set on every transfer you create with `POST /v3/transactions/` that needs an exchange, outbound or inbound. Null below the threshold, on a Lightning payment the Travel Rule does not apply to, and on inbound transfers Didit creates when a counterparty reaches you (an inbound TRP inquiry, a network message or the INTERNAL inbound leg)." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BankAccountCheckHolder": { + "description": "The name the account is checked against. Send exactly one of `full_name` and `business_name`.", + "oneOf": [ + { + "$ref": "#/components/schemas/BankAccountCheckPersonHolder" + }, + { + "$ref": "#/components/schemas/BankAccountCheckBusinessHolder" + } + ] + }, + "BankAccountCheckRequest": { + "type": "object", + "required": [ + "country", + "account", + "holder" + ], + "properties": { + "country": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "description": "The account's country, as an ISO 3166-1 alpha-3 code, for example `GBR`. Lowercase is accepted.", + "example": "GBR" + }, + "account": { + "type": "object", + "description": "The account the way its country writes it: an `iban`, or an `account_number` with the bank or branch code the country needs (`sort_code`, `routing_number`, `transit_institution`, `bsb`, `ifsc`, `bank_code` or `bic`). Mexico takes a `clabe` and Argentina a `cbu_cvu`, which already name the bank. A business account adds a `registration_id` where the country asks for one. Spaces, dashes and dots are ignored. Keys the country's check does not use are ignored; a key it needs and does not get is a `400` that names it.", + "properties": { + "iban": { + "type": "string", + "maxLength": 64, + "description": "The IBAN, in a country that uses IBANs. It must belong to `country`." + }, + "account_number": { + "type": "string", + "maxLength": 64, + "description": "The account number, in a country that writes the account with a separate bank or branch code." + }, + "sort_code": { + "type": "string", + "maxLength": 64, + "description": "United Kingdom: the 6-digit sort code." + }, + "routing_number": { + "type": "string", + "maxLength": 64, + "description": "United States: the 9-digit ABA routing number." + }, + "transit_institution": { + "type": "string", + "maxLength": 64, + "description": "Canada: the 8-digit transit and institution number." + }, + "bsb": { + "type": "string", + "maxLength": 64, + "description": "Australia: the 6-digit BSB." + }, + "ifsc": { + "type": "string", + "maxLength": 64, + "description": "India: the 11-character IFSC." + }, + "bank_code": { + "type": "string", + "maxLength": 64, + "description": "The bank's code, where the country needs one, for example in Nigeria." + }, + "bic": { + "type": "string", + "maxLength": 64, + "description": "The bank's BIC, where the country's check asks for one." + }, + "clabe": { + "type": "string", + "maxLength": 64, + "description": "Mexico: the 18-digit CLABE. It already names the bank." + }, + "cbu_cvu": { + "type": "string", + "maxLength": 64, + "description": "Argentina: the 22-digit CBU or CVU. It already names the bank." + }, + "registration_id": { + "type": "string", + "maxLength": 64, + "description": "A business account's registration number, where the country asks for one." + } + }, + "additionalProperties": { + "type": "string", + "maxLength": 64 + }, + "example": { + "sort_code": "20-45-67", + "account_number": "31926819" + } + }, + "holder": { + "$ref": "#/components/schemas/BankAccountCheckHolder" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this check, returned unchanged.", + "example": "payout_20481" + }, + "consent": { + "type": "boolean", + "default": false, + "description": "Set to `true` when the account holder has explicitly consented to this check. Required in the countries whose account check needs that consent: without it the check is refused with a `400` whose `field` is `consent` and whose `reason` is `required`, and nothing is billed. Elsewhere it is optional, and Didit records it with the check's charge when you send it.", + "example": true + } + } + }, + "BankAccountCheckResponse": { + "type": "object", + "required": [ + "request_id", + "result", + "holder_name", + "account", + "vendor_data", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "The ID of this check." + }, + "result": { + "type": "string", + "enum": [ + "match", + "close_match", + "no_match", + "cannot_verify" + ], + "description": "`match`: the bank holds the account under the name you sent. `close_match`: under a close name, for example a missing middle name or a typo. `no_match`: under another name, or there is no such account. `cannot_verify`: the bank answered without comparing the name, for example for a blocked or closed account." + }, + "holder_name": { + "type": "string", + "nullable": true, + "description": "Only on a `close_match`, and only when the bank returned it: the name on the account, as the bank holds it. `null` otherwise." + }, + "account": { + "type": "string", + "description": "The account, masked to its last 4 characters." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference, as you sent it." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the check ran, in UTC." + } + } + }, + "BankAccountCheckError": { + "type": "object", + "required": [ + "detail" + ], + "properties": { + "detail": { + "type": "string", + "description": "What went wrong, in English." + }, + "code": { + "type": "string", + "description": "A stable code to branch on. Absent on `401` and `403`." + }, + "field": { + "type": "string", + "description": "`400` only: the body field to correct, as a dotted path, for example `account.sort_code`." + }, + "reason": { + "type": "string", + "enum": [ + "required", + "length", + "format", + "checksum", + "country_mismatch", + "rejected" + ], + "description": "`400` only: why the field was refused. `required`, `length`, `format`, `checksum` and `country_mismatch` (an IBAN of another country) are Didit's own checks; `rejected` means the bank refused the account as sent." + } + } + }, + "BankVerificationResult": { + "type": "object", + "description": "One Bank Verification step's result, for User and Business Verification. `method` says how the account was verified, and `none` that the person continued without a bank before any attempt was evaluated. `continued_without_bank` says the person continued without a bank, also after an evaluated attempt. See [Bank verification methods](/core-technology/bank-verification/verification-methods) and [When the person cannot verify their bank](/core-technology/bank-verification/fallbacks).", + "properties": { + "schema_version": { + "type": "integer", + "description": "Version of this result shape. `1` today." + }, + "bank_verification_id": { + "type": "string", + "format": "uuid", + "description": "The ID of this result." + }, + "node_id": { + "type": "string", + "description": "The workflow node this result belongs to." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Expired" + ], + "description": "The step's verdict, after its actions and status rules." + }, + "original_status": { + "type": "string", + "nullable": true, + "description": "The first verdict the step reached, before any later change such as a manual review." + }, + "method": { + "type": "string", + "nullable": true, + "enum": [ + "open_banking", + "account_lookup", + "microdeposits", + "statement", + "none" + ], + "description": "How the account was verified: `open_banking` (bank sign-in: the person signed in at their bank), `account_lookup` (account check: the person typed the account details and the bank confirmed the name), `microdeposits` or `statement` (a statement upload). `none` when the person continued without a bank before any attempt was evaluated, so no account was verified. After an evaluated attempt, it stays the method of that attempt. `null` until an account is evaluated, and when the workflow narrows the data it returns. Workflow rules read the same value as `bank.method`." + }, + "provider": { + "type": "string", + "nullable": true, + "description": "The key of the route that ran the connection. Read `method`, not this key, to tell how the account was verified." + }, + "credential_source": { + "type": "string", + "nullable": true, + "enum": [ + "native", + "byok" + ], + "description": "`native`: Didit's own route, billed to your Didit balance. `byok`: a marketplace provider you connected with your own credentials." + }, + "route": { + "type": "object", + "nullable": true, + "description": "The route chosen before consent. It never changes afterwards.", + "properties": { + "provider_key": { + "type": "string", + "nullable": true, + "description": "The route's key." + }, + "credential_source": { + "type": "string", + "enum": [ + "native", + "byok" + ] + }, + "is_fallback": { + "type": "boolean", + "description": "Whether Didit's own route stood in for a marketplace provider that could not connect." + }, + "provider_connection_id": { + "type": "string", + "nullable": true, + "description": "The marketplace connection used, for `byok`." + }, + "requested_scopes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The data the connection asked for, for example `ownership` or `balances`." + } + } + }, + "connection": { + "type": "object", + "nullable": true, + "description": "The latest attempt's connection. `null` before the first attempt.", + "properties": { + "status": { + "type": "string", + "enum": [ + "pending", + "connected", + "cancelled", + "timeout", + "failed", + "unsupported", + "revoked", + "expired" + ] + }, + "institution_id": { + "type": "string", + "nullable": true, + "description": "The bank's ID. `null` for an account check, where no bank is picked." + }, + "institution_name": { + "type": "string", + "nullable": true, + "description": "The bank's name. `null` for an account check." + }, + "institution_country": { + "type": "string", + "nullable": true, + "description": "The account's country, ISO 3166-1 alpha-3." + }, + "oauth": { + "type": "boolean", + "nullable": true, + "description": "Whether the bank sign-in ran over OAuth or app-to-app. `null` for an account check." + }, + "connected_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the connection succeeded." + }, + "consent_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the bank consent ends, for bank sign-in." + }, + "last_refreshed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the financial data was last read again." + }, + "attempts": { + "type": "integer", + "description": "The attempts the person spent on this step, every method together." + }, + "reason_code": { + "type": "string", + "nullable": true, + "description": "Why the connection is not simply `connected`, for example `USER_CANCELLED`, `INSTITUTION_DOWN`, `ACCOUNT_NOT_FOUND`, `REFERENCE_IDENTITY_MISSING` or `VERIFICATION_COMPLETED`." + } + } + }, + "account": { + "type": "object", + "nullable": true, + "description": "The evaluated account. `null` until an account is evaluated.", + "properties": { + "account_id_masked": { + "type": "string", + "nullable": true, + "description": "The account number, masked." + }, + "iban_masked": { + "type": "string", + "nullable": true, + "description": "The IBAN, masked." + }, + "sort_code": { + "type": "string", + "nullable": true, + "description": "The sort code, in the United Kingdom." + }, + "routing_number_masked": { + "type": "string", + "nullable": true, + "description": "The routing number, masked." + }, + "type": { + "type": "string", + "nullable": true, + "enum": [ + "checking", + "current", + "savings", + "business", + "other" + ] + }, + "subtype": { + "type": "string", + "nullable": true, + "description": "The bank's own account subtype." + }, + "currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 currency of the account." + }, + "holder_type": { + "type": "string", + "nullable": true, + "enum": [ + "person", + "business", + "unknown" + ] + }, + "holders_count": { + "type": "integer", + "nullable": true + }, + "is_joint": { + "type": "boolean", + "nullable": true + }, + "is_business": { + "type": "boolean", + "nullable": true + } + } + }, + "ownership": { + "type": "object", + "nullable": true, + "description": "How the account holder compared with the verified identity. `null` until an account is evaluated.", + "properties": { + "result": { + "type": "string", + "enum": [ + "match", + "partial_match", + "no_match", + "unknown", + "not_verifiable" + ], + "description": "`partial_match` is a close match. `not_verifiable`: the bank answered with no holder to compare. `unknown`: the evidence could not be read, or there was no identity to compare." + }, + "reference_identity_source": { + "type": "string", + "nullable": true, + "description": "The step whose identity the holder was compared against: `kyc`, `database_validation`, `expected_details`, `questionnaire`, `document_ai` or `kyb`." + }, + "name_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "Didit's holder name score." + }, + "provider_name_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The bank's own holder name score, when it returned one." + }, + "address_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The holder address score, when address matching ran." + }, + "email_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The holder email score, when email matching ran." + }, + "phone_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The holder phone score, when phone matching ran." + }, + "dob_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The holder date of birth score, when the bank shared one." + }, + "close_match": { + "type": "boolean", + "nullable": true, + "description": "Whether the name landed in the close-match band." + }, + "holder_confirmation": { + "type": "string", + "nullable": true, + "enum": [ + "pending", + "confirmed", + "rejected" + ], + "description": "The person's answer when asked to confirm a close-match name. `null` when never asked." + }, + "method": { + "type": "string", + "nullable": true, + "enum": [ + "exact", + "token_set", + "nickname", + "transliteration", + "fuzzy", + "provider_only", + "microdeposits" + ], + "description": "Which name comparison produced the score. This is not the verification method: read the top-level `method` for that." + }, + "matched_holder_index": { + "type": "integer", + "nullable": true + }, + "holders": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Each holder the bank listed, with its scores." + }, + "owner_identity_available": { + "type": "boolean", + "nullable": true + }, + "evidence_freshness": { + "type": "string", + "nullable": true, + "enum": [ + "live", + "cached", + "refreshed" + ] + }, + "evaluated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the verdict was computed." + } + } + }, + "financial_data": { + "type": "object", + "description": "The financial data the step read. Only bank sign-in reads it.", + "properties": { + "balances": { + "type": "object", + "nullable": true, + "description": "With the `balances` scope." + }, + "transactions": { + "type": "object", + "nullable": true, + "description": "With the `transactions` scope." + }, + "income": { + "type": "object", + "nullable": true, + "description": "With the `income` scope." + }, + "categorization": { + "type": "object", + "nullable": true, + "description": "Didit's categorization of the transactions read. `null` without them." + }, + "not_collected": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "balances", + "income", + "transactions" + ] + }, + "description": "The data the step asked for that the method which verified the account does not collect. Balances, transactions and income come from bank sign-in only, so an account verified by account check lists them here. Never a failure. Empty otherwise." + } + } + }, + "billing": { + "type": "object", + "nullable": true, + "properties": { + "route": { + "type": "string", + "enum": [ + "native", + "byok" + ] + }, + "billed": { + "type": "boolean" + }, + "marketplace_fee": { + "type": "string", + "nullable": true, + "description": "The marketplace platform fee charged, for `byok`." + }, + "usage_type": { + "type": "string", + "nullable": true, + "description": "The usage line the charge was booked on: `bank_verification` for a bank sign-in or an account check, `bank_statement_upload` for a statement upload with no charged bank attempt before it, and `bank_byok_fee` for the marketplace fee on your own provider. `null` when nothing was billed." + } + } + }, + "reference_identity": { + "type": "object", + "nullable": true, + "description": "The identity the holder was compared against, with its source." + }, + "signals": { + "type": "object", + "nullable": true, + "description": "Account signals read from the transaction history, when the step read one." + }, + "connection_abandoned": { + "type": "boolean", + "nullable": true, + "description": "Whether the step ended without a match after the person left at their bank." + }, + "not_covered": { + "type": "boolean", + "nullable": true, + "description": "Whether the person's bank or country could not be reached on this step. Also `true` when the person continued without a bank because no method could verify their country or bank." + }, + "continued_without_bank": { + "type": "boolean", + "description": "Whether the person left the step with **Continue without your bank**. `continuation` then says why. `method` is `none` when no attempt was evaluated before they continued. After an evaluated attempt, such as a holder that does not match, `method` stays the method of that attempt and its verdict and reason codes stand. Returned even when the workflow narrows the data it returns." + }, + "continuation": { + "type": "object", + "nullable": true, + "description": "How the person continued without a bank. `null` when they did not. When the workflow narrows the data it returns, `reason` is still returned and every other key is `null`.", + "properties": { + "reason": { + "type": "string", + "enum": [ + "no_route", + "institution_unsupported", + "attempts_exhausted", + "person_choice" + ], + "description": "Why, as Didit worked it out when the person continued: `no_route` (no bank sign-in or account check serves their country), `institution_unsupported` (the bank they picked cannot be used on this step), `attempts_exhausted` (every attempt was already spent and none was in progress), or `person_choice` (a way to verify was there and they chose to continue, also when their bank could not be reached right now). `no_route` and `institution_unsupported` follow `bank_unsupported_action`, `attempts_exhausted` follows `bank_attempts_exhausted_action`, and `person_choice` follows `bank_skipped_action`, unless an evaluated attempt already decided the step." + }, + "country": { + "type": "string", + "nullable": true, + "description": "The country the person was verifying in, ISO 3166-1 alpha-3. `null` when they continued before picking one." + }, + "offered": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "statement", + "microdeposits" + ] + }, + "nullable": true, + "description": "The other ways to verify that country offered the person when they continued, in the order it offered them: `statement` (a statement upload), then `microdeposits`. Empty when it offered none." + }, + "institution_id": { + "type": "string", + "nullable": true, + "description": "For `institution_unsupported`, the ID of the bank the person could not use: the bank they picked, or the bank their last attempt ended on. Didit takes it from its own list of banks, never from the person's device, so a bank that list does not have is `null`. `null` for every other reason." + }, + "institution_name": { + "type": "string", + "nullable": true, + "description": "For `institution_unsupported`, the name of that bank, from the same list as `institution_id`. `null` when that list does not have the bank, and for every other reason." + }, + "continued_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the person continued." + } + } + }, + "evidence_purged_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the bank evidence was deleted under `bank_evidence_retention_days`. `null` otherwise." + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Informational codes, for example `holder_declared`. A warning never changes the status on its own." + }, + "reason_codes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Why the verdict came out the way it did, for example `OWNER_NAME_PARTIAL`. Several may apply. `NOT_COVERED`: the person continued without a bank because no method could verify their country or bank. `SKIPPED`: the person chose to continue without a bank while a way to verify was there. Neither is added when an evaluated attempt already decided the step." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BankAccountCheckPersonHolder": { + "type": "object", + "description": "The person who should hold the account.", + "required": [ + "full_name" + ], + "properties": { + "full_name": { + "type": "string", + "maxLength": 255, + "description": "The full name of the person who should hold the account.", + "example": "Maria Oliveira" + } + } + }, + "BankAccountCheckBusinessHolder": { + "type": "object", + "description": "The company that should hold the account. Checks a business account.", + "required": [ + "business_name" + ], + "properties": { + "business_name": { + "type": "string", + "maxLength": 255, + "description": "The name of the company that should hold the account. Checks a business account." + } + } + }, + "BankVerificationPricing": { + "type": "object", + "description": "Public Bank Verification list prices: the statement upload, the own provider key fee and each country's methods.", + "required": [ + "currency", + "statement_upload", + "own_provider_fee_usd", + "countries" + ], + "properties": { + "currency": { + "type": "string", + "enum": [ + "USD" + ], + "description": "Currency of every price in the response.", + "example": "USD" + }, + "statement_upload": { + "$ref": "#/components/schemas/BankStatementUploadPricing" + }, + "own_provider_fee_usd": { + "type": "string", + "description": "Fee for each billable ownership check on a route that uses your own provider contract instead of Didit's, charged whatever the result. A cancelled, timed out or abandoned connection is not billed. Decimal string with 4 decimals, in USD.", + "example": "0.0200" + }, + "countries": { + "type": "object", + "description": "Every country, keyed by ISO 3166-1 alpha-3 code.", + "additionalProperties": { + "$ref": "#/components/schemas/BankVerificationPricingCountry" + } + } + } + }, + "BankStatementUploadPricing": { + "type": "object", + "description": "How the bank statement upload is priced.", + "required": [ + "price_usd", + "included_after_charged_bank_attempt", + "standalone_available" + ], + "properties": { + "price_usd": { + "type": "string", + "description": "Price of a statement upload after a charged bank attempt. Decimal string with 4 decimals, in USD.", + "example": "0.0000" + }, + "included_after_charged_bank_attempt": { + "type": "boolean", + "description": "`true` when the upload is included at no extra charge after a charged bank attempt in the same session.", + "example": true + }, + "standalone_available": { + "type": "boolean", + "description": "`true` when a statement upload can run on its own, with no bank attempt first. `false` in production; sandbox sessions can test it without billing.", + "example": false + } + } + }, + "BankVerificationPricingCountry": { + "type": "object", + "description": "One country's Bank Verification methods and their list prices.", + "required": [ + "name", + "methods" + ], + "properties": { + "name": { + "type": "string", + "description": "Country name in English.", + "example": "United Kingdom" + }, + "methods": { + "$ref": "#/components/schemas/BankVerificationPricingMethods" + } + } + }, + "BankVerificationPricingMethods": { + "type": "object", + "description": "The two Bank Verification methods in a country.", + "required": [ + "open_banking", + "account_lookup" + ], + "properties": { + "open_banking": { + "$ref": "#/components/schemas/BankSignInPricing" + }, + "account_lookup": { + "$ref": "#/components/schemas/BankAccountCheckPricing" + } + } + }, + "BankSignInPricing": { + "type": "object", + "description": "Bank sign-in: the person signs in to their bank. Priced by market, the same for every bank in it.", + "required": [ + "available", + "market", + "price_usd", + "add_ons" + ], + "properties": { + "available": { + "type": "boolean", + "description": "`true` only where Didit serves bank sign-in in this country in production.", + "example": true + }, + "market": { + "type": "string", + "nullable": true, + "description": "The market whose price applies. `null` when not available.", + "example": "UK and EEA" + }, + "price_usd": { + "type": "string", + "nullable": true, + "description": "List price of one bank sign-in. `null` when not available. Decimal string with 4 decimals, in USD.", + "example": "0.7900" + }, + "add_ons": { + "type": "object", + "description": "Add-ons priced in this country, each a decimal string with 4 decimals in USD, added to the bank sign-in price. Only the add-ons the country offers are listed; empty when bank sign-in is not available.", + "properties": { + "balance": { + "type": "string", + "description": "Account balance add-on.", + "example": "0.3500" + }, + "transactions": { + "type": "string", + "description": "Transactions add-on.", + "example": "1.3000" + } + } + } + } + }, + "BankAccountCheckPricing": { + "type": "object", + "description": "Account check: the person types their account number and Didit checks it with the bank. Priced per country.", + "required": [ + "available", + "price_usd" + ], + "properties": { + "available": { + "type": "boolean", + "description": "`true` only where Didit serves the account check in this country in production.", + "example": true + }, + "price_usd": { + "type": "string", + "nullable": true, + "description": "List price of one account check the bank answers. `null` when not available. Decimal string with 4 decimals, in USD.", + "example": "0.4000" + } + } + }, + "ImportProvider": { + "type": "object", + "properties": { + "key": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ] + }, + "name": { + "type": "string" + }, + "mode": { + "type": "string", + "enum": [ + "api_list", + "api_ids", + "export_file" + ], + "description": "`api_list`: Didit lists records with the key. `api_ids`: upload the record IDs from an export. `export_file`: upload an export in Didit's format." + }, + "record_noun": { + "type": "string" + }, + "credential_fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "label": { + "type": "string" + }, + "secret": { + "type": "boolean" + } + } + } + }, + "regions": { + "type": "array", + "items": { + "type": "string" + } + }, + "required_permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "docs_url": { + "type": "string", + "nullable": true + }, + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + } + } + } + }, + "ImportJobSummary": { + "type": "object", + "description": "One import job of any kind, in the shape the console's Import jobs drawer reads.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "kind": { + "type": "string", + "enum": [ + "sessions", + "businesses", + "users", + "status_rules", + "transactions" + ] + }, + "source": { + "type": "string", + "enum": [ + "file", + "api", + "provider" + ] + }, + "provider": { + "type": "string", + "nullable": true + }, + "provider_name": { + "type": "string", + "nullable": true + }, + "filename": { + "type": "string", + "nullable": true + }, + "state": { + "type": "string", + "enum": [ + "ready", + "queued", + "running", + "paused", + "completed", + "errors", + "failed", + "cancelled" + ], + "description": "The job's state as the console shows it; `errors` is a completed job with rejected rows." + }, + "status": { + "type": "string" + }, + "is_dry_run": { + "type": "boolean" + }, + "credential_hint": { + "type": "string", + "nullable": true + }, + "resumes_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_error": { + "type": "string", + "nullable": true + }, + "started_by": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + } + } + }, + "progress": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int64" + }, + "processed": { + "type": "integer", + "format": "int64" + }, + "imported": { + "type": "integer", + "format": "int64" + }, + "updated": { + "type": "integer", + "format": "int64" + }, + "already_in_didit": { + "type": "integer", + "format": "int64" + }, + "failed": { + "type": "integer", + "format": "int64" + }, + "not_finished": { + "type": "integer", + "format": "int64" + }, + "same_person": { + "type": "integer", + "format": "int64" + } + } + }, + "face_templates": { + "type": "object", + "nullable": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "built": { + "type": "integer", + "format": "int64" + }, + "duplicates": { + "type": "integer", + "format": "int64" + }, + "blocklisted": { + "type": "integer", + "format": "int64" + } + } + }, + "eta_seconds": { + "type": "integer", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "started_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "FiledRegulatoryReport": { + "type": "object", + "description": "A finalized regulatory report that prevents the requested deletion.", + "required": [ + "uuid", + "report_number", + "report_type", + "case_uuid" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Report UUID." + }, + "report_number": { + "type": "string", + "description": "Report number shown in the Console (for example, SAR-0001)." + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ], + "description": "Regulatory report type." + }, + "case_uuid": { + "type": "string", + "format": "uuid", + "description": "Case the report belongs to." + } + } + }, + "FiledReportTransaction": { + "type": "object", + "description": "A transaction that cannot be deleted because a finalized regulatory report lists it.", + "required": [ + "uuid", + "transaction_id", + "reports" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit transaction UUID." + }, + "transaction_id": { + "type": "string", + "description": "The transaction identifier supplied when the transaction was created." + }, + "reports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FiledRegulatoryReport" + }, + "description": "Finalized reports that list this transaction." + } + } + }, + "InFiledReportError": { + "type": "object", + "description": "The deletion is refused because the selected data is in a finalized regulatory report. Nothing is deleted. Delete the report, then repeat the request.", + "required": [ + "detail", + "code" + ], + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "in_filed_report" + ], + "description": "Stable error code for a deletion blocked by a finalized regulatory report." + }, + "reports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FiledRegulatoryReport" + }, + "description": "Reports that block a person, company, case or session deletion." + }, + "transactions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FiledReportTransaction" + }, + "description": "Transactions that block a transaction deletion, each with the reports that list it." + } + } + }, + "SubjectDeletionInProgressError": { + "type": "object", + "description": "Another request is still deleting this person or company. This request deleted nothing and can be repeated once that deletion has finished; a repeat then deletes whatever is still selected.", + "required": [ + "detail", + "code" + ], + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "subject_deletion_in_progress" + ], + "description": "Stable error code for a person or company another request is still deleting." + } + } + }, + "TravelRuleNetworkParticipantSummary": { + "type": "object", + "nullable": true, + "description": "This application's own participant on the network, or null when it has none.", + "properties": { + "mode": { + "type": "string", + "enum": [ + "platform", + "own" + ], + "description": "platform: carried by Didit's membership; own: onboarded under the application's own membership." + }, + "status": { + "type": "string", + "enum": [ + "not_requested", + "questionnaire_pending", + "submitted_to_network", + "active", + "suspended", + "rejected" + ], + "description": "not_requested: never requested, or the network refused the first request (see last_error); questionnaire_pending: waiting for the due-diligence questionnaire to be approved; submitted_to_network: handed to the network's onboarding; active: the network gave this application its own participant code; suspended / rejected: Didit suspended or rejected it." + }, + "participant_code": { + "type": "string", + "description": "The code the network gave this application (for GTR, its VASP code). Outbound messages carry this code, never Didit's own." + }, + "requested_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "activated_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_error": { + "type": "string", + "description": "The network's answer when it refused the registration; empty otherwise." + } + } + }, + "TravelRuleNetworkState": { + "type": "object", + "description": "How Travel Rule messages on one network would be sent for this application, and why they cannot be. Configuration status, not a live health or delivery check.", + "properties": { + "rail": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA" + ] + }, + "mode": { + "type": "string", + "enum": [ + "own", + "didit", + "off", + "unavailable" + ], + "description": "own: the application's connected membership is used; didit: Didit's membership carries the application as its own active participant; off: platform routing disabled in disabled_networks with no own membership; unavailable: nothing can send on this network yet (see state and reason)." + }, + "state": { + "type": "string", + "enum": [ + "own_membership", + "live_through_didit", + "off", + "didit_onboarding", + "questionnaire_needed", + "questionnaire_in_review", + "request_access", + "request_sent", + "unavailable", + "error" + ], + "description": "own_membership: your connected membership sends; live_through_didit: your own participant on Didit's membership sends; off: you switched platform routing off; didit_onboarding: Didit's own membership of this network is not live yet, so it cannot carry anyone; questionnaire_needed / questionnaire_in_review: the due-diligence questionnaire must be submitted or is with Didit; request_access: the questionnaire is approved, request the network; request_sent: your participant is being onboarded; unavailable: see reason; error: see reason." + }, + "reason": { + "type": "string", + "enum": [ + "own_membership_connected", + "platform_network_switched_off", + "own_membership_only", + "sandbox_application", + "didit_membership_not_live", + "participant_suspended", + "participant_rejected", + "questionnaire_not_submitted", + "questionnaire_submitted", + "questionnaire_changes_requested", + "questionnaire_rejected", + "participant_not_requested", + "participant_active", + "participant_key_missing", + "participant_key_unresolved", + "provisioning_failed", + "questionnaire_pending", + "submitted_to_network" + ], + "description": "Machine-readable detail of state. own_membership_only: Didit does not carry applications on this network (TRUST), connect your own membership; sandbox_application: sandbox transfers never reach a network; provisioning_failed: the network refused the registration, the participant's last_error says why and requesting the network again retries." + }, + "platform_available": { + "type": "boolean", + "description": "Whether Didit's own membership of this network is configured; not a live health check." + }, + "connected": { + "type": "boolean", + "description": "Whether a connected customer membership exists. Credentials are validated locally; network validation occurs on a live exchange." + }, + "participant": { + "$ref": "#/components/schemas/TravelRuleNetworkParticipantSummary" + }, + "vasps_reachable": { + "type": "integer", + "description": "Directory VASPs reachable on this network." + }, + "routed_30d": { + "type": "integer", + "description": "This application's transfers carried on this network in the last 30 days." + } + } + }, + "TravelRuleNetworkQuestionnaireAnswers": { + "type": "object", + "description": "Didit's network due-diligence questionnaire. Saves are partial: a save keeps every answer it does not name. Submitting requires every field except lei and data_protection_contact_email, and the three attestations true.", + "properties": { + "legal_name": { + "type": "string", + "maxLength": 255 + }, + "registration_number": { + "type": "string", + "maxLength": 255 + }, + "incorporation_country": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code." + }, + "registered_address": { + "type": "string", + "maxLength": 1000 + }, + "website": { + "type": "string", + "format": "uri", + "maxLength": 500 + }, + "lei": { + "type": "string", + "maxLength": 20, + "description": "Optional. A valid ISO 17442 LEI when given." + }, + "licences": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "regulator", + "jurisdiction", + "licence_type", + "licence_number" + ], + "properties": { + "regulator": { + "type": "string", + "maxLength": 255 + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code." + }, + "licence_type": { + "type": "string", + "maxLength": 255 + }, + "licence_number": { + "type": "string", + "maxLength": 255 + } + } + }, + "description": "Every licence or registration the VASP holds." + }, + "operating_jurisdictions": { + "type": "array", + "minItems": 1, + "maxItems": 250, + "items": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code." + } + }, + "compliance_officer_name": { + "type": "string", + "maxLength": 255 + }, + "compliance_officer_email": { + "type": "string", + "format": "email" + }, + "aml_ctf_policy_attested": { + "type": "boolean", + "description": "Must be true to submit." + }, + "sanctions_screening_attested": { + "type": "boolean", + "description": "Must be true to submit." + }, + "data_protection_attested": { + "type": "boolean", + "description": "Must be true to submit." + }, + "data_protection_contact_email": { + "type": "string", + "format": "email", + "description": "Optional." + }, + "expected_monthly_transfers": { + "type": "integer", + "minimum": 0 + }, + "expected_monthly_volume_usd": { + "type": "string", + "format": "decimal", + "description": "Expected monthly volume in USD, up to 2 decimal places. Returned as a string." + }, + "assets": { + "type": "array", + "minItems": 1, + "maxItems": 200, + "items": { + "type": "string", + "maxLength": 32 + }, + "description": "Asset tickers, stored upper-case, sorted and de-duplicated." + } + } + }, + "TravelRuleNetworkQuestionnaire": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "draft", + "submitted", + "changes_requested", + "approved", + "rejected" + ], + "description": "Answers can be saved while draft or changes_requested; submitted, approved and rejected are Didit's to move." + }, + "answers": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaireAnswers" + }, + "submitted_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reviewed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "review_note": { + "type": "string", + "description": "Didit's note on its last decision, for example what to change." + }, + "history": { + "type": "array", + "readOnly": true, + "description": "Every status change, oldest first.", + "items": { + "type": "object", + "properties": { + "at": { + "type": "string", + "format": "date-time" + }, + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "note": { + "type": "string", + "description": "Review note or the network's answer, when there is one." + } + } + } + }, + "updated_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "TravelRuleNetworkParticipant": { + "type": "object", + "description": "This application's own participant on one network.", + "properties": { + "network": { + "type": "string", + "enum": [ + "gtr", + "trust", + "verifyvasp", + "sygna" + ] + }, + "rail": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA" + ] + }, + "mode": { + "type": "string", + "enum": [ + "platform", + "own" + ] + }, + "status": { + "type": "string", + "enum": [ + "not_requested", + "questionnaire_pending", + "submitted_to_network", + "active", + "suspended", + "rejected" + ], + "description": "not_requested: never requested, or the network refused the first request (see last_error); questionnaire_pending: waiting for the due-diligence questionnaire to be approved; submitted_to_network: handed to the network's onboarding; active: the network gave this application its own participant code; suspended / rejected: Didit suspended or rejected it." + }, + "participant_code": { + "type": "string" + }, + "requested_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "submitted_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "activated_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "suspended_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_error": { + "type": "string", + "description": "The network's answer when it refused the registration; empty otherwise." + }, + "history": { + "type": "array", + "readOnly": true, + "description": "Every status change, oldest first.", + "items": { + "type": "object", + "properties": { + "at": { + "type": "string", + "format": "date-time" + }, + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "note": { + "type": "string", + "description": "Review note or the network's answer, when there is one." + } + } + } + } + } + }, + "TravelRuleNetworksOverview": { + "type": "object", + "properties": { + "platform_networks_enabled": { + "type": "boolean", + "description": "Whether network access through Didit's membership is open. While false, questionnaire saves, submissions and network requests answer 403." + }, + "questionnaire": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaire" + }, + "participants": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleNetworkParticipant" + } + }, + "networks": { + "type": "object", + "readOnly": true, + "description": "Per network, how this application would send and why it cannot. No network, including GTR, is included by default: a network is reachable through your own connected membership, or through Didit's membership once Didit's membership of that network is live and your own participant on it is active. Not a live health or delivery check.", + "properties": { + "gtr": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "trust": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "verifyvasp": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "sygna": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + } + } + } + } + }, + "AddressValidationResult": { + "type": "object", + "description": "The result of one Address Validation: whether the address exists, and its normalised form. It confirms the address only; it never links the address to a person.", + "required": [ + "status", + "verification_level", + "address", + "formatted_address", + "latitude", + "longitude" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "verified", + "partially_verified", + "not_verified", + "unavailable" + ], + "description": "`verified`: the address was confirmed down to the building (or the flat or PO box). `partially_verified`: it was confirmed only to the street, or to the most precise level the postal reference data holds for that place. `not_verified`: only the region or the city matched, or nothing did. `unavailable`: the validation could not run (for example the reference service did not answer); it is not charged and never affects a verification." + }, + "verification_level": { + "type": "string", + "nullable": true, + "enum": [ + "administrative_area", + "locality", + "thoroughfare", + "premise", + "delivery_point", + null + ], + "description": "The most precise level the address was matched to. `null` when nothing matched or `status` is `unavailable`." + }, + "address": { + "type": "object", + "nullable": true, + "description": "The normalised address. `null` when `status` is `unavailable`.", + "properties": { + "address_line_1": { + "type": "string", + "nullable": true, + "description": "First delivery line, written the way the country writes it.", + "example": "1600 Amphitheatre Pkwy" + }, + "address_line_2": { + "type": "string", + "nullable": true, + "description": "Second delivery line (a flat, building or neighbourhood), when the address has one." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Mountain View" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State, province or region.", + "example": "CA" + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "94043-1351" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code.", + "example": "US" + } + } + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "The normalised address on one line.", + "example": "1600 Amphitheatre Pkwy, Mountain View CA 94043-1351" + }, + "latitude": { + "type": "number", + "nullable": true, + "example": 37.422022 + }, + "longitude": { + "type": "number", + "nullable": true, + "example": -122.08429 + } + } + }, + "AddressValidationRequest": { + "type": "object", + "required": [ + "country" + ], + "description": "One address: on one line (`address`) or as structured fields with at least `address_line_1`, never both.", + "properties": { + "address": { + "type": "string", + "maxLength": 500, + "description": "The whole address on one line.", + "example": "Paseo de la Castellana 259, 28046 Madrid" + }, + "address_line_1": { + "type": "string", + "maxLength": 255, + "description": "Street and number.", + "example": "1600 Amphitheatre Pkwy" + }, + "address_line_2": { + "type": "string", + "maxLength": 255, + "description": "Apartment, suite, floor or building." + }, + "city": { + "type": "string", + "maxLength": 128, + "description": "City or locality.", + "example": "Mountain View" + }, + "region": { + "type": "string", + "maxLength": 128, + "description": "State, province or region.", + "example": "CA" + }, + "postal_code": { + "type": "string", + "maxLength": 32, + "example": "94043" + }, + "country": { + "type": "string", + "maxLength": 3, + "description": "ISO 3166-1 alpha-2 or alpha-3 code of the address's country.", + "example": "US" + } + } + }, + "AddressValidationResponse": { + "type": "object", + "required": [ + "request_id", + "address_validation", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Identifier of this request; quote it to support. No session is created." + }, + "address_validation": { + "$ref": "#/components/schemas/AddressValidationResult" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleWalletProofFailureReason": { + "type": "string", + "enum": [ + "", + "SIGNATURE_INVALID", + "SIGNATURE_UNSUPPORTED_SCHEME", + "DEPOSIT_NOT_FOUND", + "DEPOSIT_UNCONFIRMED", + "DEPOSIT_LOOKUP_FAILED", + "DEPOSIT_MISMATCH", + "DEPOSIT_STALE", + "TXID_REUSED", + "CHAIN_UNSUPPORTED", + "WINDOW_EXPIRED", + "OWNERSHIP_CHANGED", + "METHOD_SWITCHED", + "REVIEW_REJECTED", + "ABANDONED" + ], + "description": "Why a wallet-ownership proof, or its last attempt, fell short of VERIFIED. Empty when nothing failed. A PENDING proof can carry one: the last attempt failed and the end user can try again. DEPOSIT_MISMATCH, DEPOSIT_STALE, TXID_REUSED and REVIEW_REJECTED close the proof as REJECTED. SIGNATURE_INVALID: the signature does not match the wallet and challenge. SIGNATURE_UNSUPPORTED_SCHEME: the wallet signed in a format Didit does not verify. DEPOSIT_NOT_FOUND, DEPOSIT_UNCONFIRMED, DEPOSIT_LOOKUP_FAILED: the test deposit is not readable or not confirmed yet; Didit keeps checking it until the test window closes. DEPOSIT_MISMATCH: the transaction does not send the expected amount from the wallet to the deposit address. DEPOSIT_STALE: the transaction predates the challenge. TXID_REUSED: the transaction already proved another wallet. CHAIN_UNSUPPORTED: the method is not available on this chain. WINDOW_EXPIRED: the test window closed. OWNERSHIP_CHANGED: the wallet's holder changed, or the wallet was deleted from your address book, after the widget session was minted. METHOD_SWITCHED: the end user picked another method before finishing this one. REVIEW_REJECTED: your reviewer rejected the screenshot. ABANDONED: the widget session lapsed before the end user finished." + }, + "TravelRuleWidgetProofError": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable message in English." + }, + "code": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleWalletProofFailureReason" + } + ], + "description": "Machine-readable reason, so a client can show its own translated message." + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ], + "description": "Present when the proof is closed: REJECTED by the deposit just submitted (400), or REJECTED or EXPIRED when evidence is sent to a proof that is already closed (409)." + }, + "proof_id": { + "type": "string", + "format": "uuid", + "description": "Present with status: the closed proof." + }, + "method": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SATOSHI_TEST", + "SCREENSHOT", + "SELF_DECLARATION" + ], + "description": "Present with status: the closed proof's method." + }, + "max_attempts": { + "type": "integer", + "description": "Present with status: rejected wallet proofs this session allows." + }, + "attempts_used": { + "type": "integer", + "description": "Present with status: rejected proofs so far." + }, + "attempts_remaining": { + "type": "integer", + "description": "Present with status: when 0, starting another proof answers 409 and you mint a new widget session." + } + } + }, + "TravelRuleWidgetProofCreate": { + "type": "object", + "required": [ + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SATOSHI_TEST", + "SCREENSHOT", + "SELF_DECLARATION" + ], + "description": "The proof method the end user picked. It must be one of the context's allowed_methods." + }, + "wallet_address": { + "type": "string", + "maxLength": 128, + "description": "Defaults to the session's wallet_address. Must match the transfer's wallet when the session is linked to a transfer." + }, + "chain": { + "type": "string", + "maxLength": 32, + "description": "Defaults to the session's chain." + } + } + }, + "TravelRuleWidgetProofVerifyResult": { + "type": "object", + "properties": { + "proof_id": { + "type": "string", + "format": "uuid" + }, + "method": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SATOSHI_TEST", + "SCREENSHOT", + "SELF_DECLARATION" + ] + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ] + }, + "is_ownership_verified": { + "type": "boolean", + "description": "Present when the proof is VERIFIED: the address book entry is now ownership-verified." + }, + "transfer_status": { + "type": "string", + "nullable": true, + "description": "Present when the proof is VERIFIED: the linked transfer's status, null without one." + }, + "detail": { + "type": "string", + "description": "Present while the proof stays PENDING: a submitted screenshot awaiting review, or a test deposit that is not confirmed yet." + }, + "code": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleWalletProofFailureReason" + } + ], + "description": "Present on a pending test deposit: why it is not confirmed yet." + } + } + }, + "TravelRuleWalletProofReview": { + "type": "object", + "properties": { + "proof_id": { + "type": "string", + "format": "uuid" + }, + "method": { + "type": "string", + "enum": [ + "SCREENSHOT" + ] + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ] + }, + "wallet_address": { + "type": "string" + }, + "chain": { + "type": "string", + "description": "Canonical chain identifier, e.g. ETH, BTC, SOL, TRX." + }, + "holder_name": { + "type": "string" + }, + "is_ownership_verified": { + "type": "boolean", + "description": "Whether the address book entry is ownership-verified, by this proof or another one." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The transaction whose Travel Rule transfer the widget session was minted for. Null for a session minted without a transaction_id." + }, + "file_name": { + "type": "string", + "nullable": true + }, + "content_type": { + "type": "string", + "nullable": true + }, + "screenshot_url": { + "type": "string", + "nullable": true, + "description": "Time-limited link to the uploaded screenshot. List the queue again for a fresh link." + }, + "attempts": { + "type": "integer", + "description": "How many files the end user uploaded for this proof." + }, + "failure_reason": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleWalletProofFailureReason" + } + ], + "description": "REVIEW_REJECTED after a rejection, OWNERSHIP_CHANGED when the wallet changed holder or was deleted before a decision. Empty otherwise." + }, + "method_chosen_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the end user picked the screenshot method." + }, + "decided_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "decided_by": { + "type": "string", + "description": "Who closed the proof: review: for a console reviewer, review for an API-key decision, widget or sweeper when Didit closed it automatically. Empty while PENDING." + }, + "review_note": { + "type": "string", + "description": "The note sent with the decision. Empty when none." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleWalletProofDecision": { + "type": "object", + "required": [ + "decision" + ], + "properties": { + "decision": { + "type": "string", + "enum": [ + "approve", + "reject" + ] + }, + "note": { + "type": "string", + "maxLength": 512, + "description": "Optional. Kept with the proof and returned as review_note." + } + } + }, + "DigitalIdWalletApiWalletDigitalCredentialsRequest": { + "type": "object", + "properties": { + "protocol": { + "type": "string", + "description": "openid4vp-v1-signed." + }, + "request": { + "type": "object", + "additionalProperties": {}, + "description": "Pass as is: navigator.credentials.get({digital: request}) in the browser, or JSON.stringify(request) as GetDigitalCredentialOption's requestJson on Android. Signed by Didit for your application." + } + }, + "required": [ + "protocol", + "request" + ] + }, + "DigitalIdWalletApiGoogleWalletWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Google Wallet verification (USA, GBR, BRA, SGP, TWN).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiGoogleWalletWalletVerificationStartCountryEnum" + } + ], + "description": "ISO 3166-1 alpha-3 country of the Google Wallet identity.\n\n* `USA` - USA\n* `GBR` - GBR\n* `BRA` - BRA\n* `SGP` - SGP\n* `TWN` - TWN" + }, + "origin": { + "type": "string", + "minLength": 1, + "description": "Where your page or app will ask the wallet: the https origin of your page (its host must be in the application's allowed_return_hosts, or be its white-label domain), e.g. https://app.example.com, or android:apk-key-hash:. The wallet refuses the request anywhere else.", + "maxLength": 256 + } + }, + "required": [ + "country", + "origin" + ] + }, + "DigitalIdWalletApiGoogleWalletWalletVerificationStartCountryEnum": { + "enum": [ + "USA", + "GBR", + "BRA", + "SGP", + "TWN" + ], + "type": "string", + "description": "* `USA` - USA\n* `GBR` - GBR\n* `BRA` - BRA\n* `SGP` - SGP\n* `TWN` - TWN" + }, + "DigitalIdWalletApiWalletDigitalCredentialResponseRequest": { + "type": "object", + "description": "What the wallet answered your page or app, for a ``request_digital_credential`` verification.", + "properties": { + "response": { + "type": "object", + "additionalProperties": {}, + "description": "Exactly what the wallet returned: {protocol, data} of the DigitalCredential from navigator.credentials.get, or the parsed credentialJson of Android's DigitalCredential. Encrypted to Didit; do not alter it." + } + }, + "required": [ + "response" + ] + }, + "SessionFilesExport": { + "type": "object", + "description": "One ZIP with every file of a session, in original quality.", + "required": [ + "export_id", + "session_id", + "status", + "file_count", + "size_bytes", + "download_url", + "expires_at", + "error", + "created_at", + "completed_at" + ], + "properties": { + "export_id": { + "type": "string", + "format": "uuid", + "description": "ID of the export. Pass it to `GET /v3/session/{sessionId}/files-export/{exportId}/`." + }, + "session_id": { + "type": "string", + "format": "uuid", + "description": "The session the files are from." + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "READY", + "FAILED", + "EXPIRED" + ], + "description": "`PENDING` and `PROCESSING` while the ZIP is prepared, `READY` once it can be downloaded, `FAILED` when it could not be prepared (request a new export), `EXPIRED` once the ZIP was deleted." + }, + "file_count": { + "type": "integer", + "nullable": true, + "description": "Files in the ZIP. Set only while `status` is `READY`." + }, + "size_bytes": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "Size of the ZIP in bytes. Set only while `status` is `READY`." + }, + "download_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Signed URL of the ZIP, valid for a short time. Set only while `status` is `READY`; read the export again for a new one." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the ZIP is deleted, 24 hours after it was ready. Set only while `status` is `READY`." + }, + "error": { + "type": "string", + "nullable": true, + "description": "Why the export failed, when `status` is `FAILED`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the export was requested." + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the ZIP was ready or the export failed." + } + } + }, + "TransactionRuleSuggestionsCreatedEvent": { + "description": "A transaction rule suggestion run proposed new rule changes for your application. Sent when the rule-suggestion settings have webhook notifications on. There is no top-level `status`.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "transaction_rule_suggestions.created" + ], + "description": "Always `transaction_rule_suggestions.created`." + }, + "run_id": { + "type": "string", + "format": "uuid", + "description": "The suggestion run that produced the new suggestions." + }, + "open_count": { + "type": "integer", + "description": "Number of suggestions that are still open after this run, new ones included." + }, + "suggestions": { + "type": "array", + "description": "The suggestions this run created.", + "items": { + "type": "object", + "required": [ + "suggestion_id", + "kind", + "title", + "rule_id", + "expected_impact" + ], + "properties": { + "suggestion_id": { + "type": "string", + "format": "uuid" + }, + "kind": { + "type": "string", + "enum": [ + "ADD", + "TWEAK", + "DISABLE" + ], + "description": "`ADD` proposes a new rule, `TWEAK` changes an existing one, `DISABLE` turns one off." + }, + "title": { + "type": "string" + }, + "rule_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The rule a `TWEAK` or `DISABLE` suggestion applies to; `null` for `ADD`." + }, + "expected_impact": { + "type": "object", + "additionalProperties": true, + "description": "What the change would do, as the suggestion shows it in the Console." + } + } + } + } + }, + "required": [ + "webhook_type", + "run_id", + "open_count", + "suggestions" + ] + } + ] + }, + "GeolocationCheck": { + "type": "object", + "description": "One Location verification result, the accepted attempt of one workflow node. Measurement, uncertainty and authenticity are separate keys, so a precise unattested fix reads differently from an imprecise attested one.", + "properties": { + "node_id": { + "type": "string", + "nullable": true, + "description": "Workflow node that produced this result, so a workflow with two Location verification steps stays readable." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Feature-level status of this Location verification step, resolved from its warnings. Independent of the session `status`." + }, + "outcome": { + "type": "string", + "nullable": true, + "description": "What happened on this attempt.", + "enum": [ + "verified", + "low_accuracy", + "accuracy_undetermined", + "stale", + "approximate", + "denied", + "restricted", + "unavailable", + "timeout", + "mock_detected", + "outside_geofence", + "in_blocked_area", + "anonymizer_detected", + "skipped", + "replay_rejected", + "unsupported_client", + null + ] + }, + "permission": { + "type": "string", + "nullable": true, + "description": "What the operating system or browser granted. `not_requested` means the step uses the connection's IP location only, so the person is never asked for a permission.", + "enum": [ + "granted_precise", + "granted_approximate", + "denied", + "restricted", + "unavailable", + "timeout", + "dismissed", + "not_requested", + null + ] + }, + "latitude": { + "type": "number", + "nullable": true, + "description": "WGS84 latitude of the fix. `null` when there is no fix.", + "format": "double" + }, + "longitude": { + "type": "number", + "nullable": true, + "description": "WGS84 longitude of the fix. `null` when there is no fix.", + "format": "double" + }, + "accuracy_m": { + "type": "number", + "nullable": true, + "description": "Radius of uncertainty in metres, at the confidence level in `accuracy_confidence`.", + "format": "double" + }, + "accuracy_confidence": { + "type": "string", + "nullable": true, + "description": "Confidence level the platform attaches to `accuracy_m`: `p95` (browsers), `p68` (Android) or `unspecified` (iOS).", + "enum": [ + "p95", + "p68", + "unspecified", + null + ] + }, + "measured_at": { + "type": "string", + "nullable": true, + "description": "When the device took the fix, by its own clock.", + "format": "date-time" + }, + "age_seconds": { + "type": "integer", + "nullable": true, + "description": "Seconds between the device taking the fix and Didit receiving it." + }, + "source": { + "type": "string", + "nullable": true, + "description": "Where the fix came from, when the platform says. `ip` means the position is the connection's IP location.", + "enum": [ + "gps", + "fused", + "network", + "wifi", + "cell", + "browser", + "unknown", + "ip", + null + ] + }, + "platform": { + "type": "string", + "nullable": true, + "description": "The client that produced the fix.", + "enum": [ + "web", + "ios", + "android", + "react_native", + "flutter", + null + ] + }, + "sdk_version": { + "type": "string", + "nullable": true, + "description": "Version of the SDK that produced the fix." + }, + "is_mock": { + "type": "boolean", + "nullable": true, + "description": "`true` when the device says the fix came from a mock location provider or simulation software. `null` when the platform does not report it (browsers cannot tell); `null` is not `false`." + }, + "produced_by_accessory": { + "type": "boolean", + "nullable": true, + "description": "iOS only: `true` when the fix came from an external accessory. `null` elsewhere." + }, + "integrity": { + "type": "string", + "enum": [ + "attested", + "not_attested", + "failed", + "unsupported", + "not_determined" + ], + "description": "Whether device attestation backs the fix. `unsupported` is what browsers report, because the web has no attestation mechanism; it is not a failure." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country the fix is in." + }, + "region_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision the fix is in." + }, + "city": { + "type": "string", + "nullable": true, + "description": "Nearest populated place, a label for reviewers." + }, + "geofence": { + "type": "object", + "description": "Result of the country and area rules of the step.", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether the step applies any country or area rule." + }, + "result": { + "type": "string", + "nullable": true, + "description": "Where the position sits relative to the rules. `undetermined` means the accuracy circle straddles the border or the position resolved to no country; it is not evidence of being outside.", + "enum": [ + "inside", + "outside", + "undetermined", + null + ] + } + } + }, + "inside_geofence": { + "type": "boolean", + "nullable": true, + "description": "`true` when `geofence.result` is `inside`, `false` when `outside`, `null` otherwise." + }, + "geofence_basis": { + "type": "string", + "nullable": true, + "description": "Which position the area result was judged from: the `device` fix, or the connection's `ip` location.", + "enum": [ + "device", + "ip", + null + ] + }, + "inside_areas": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Ids of the configured allowed areas the position is inside." + }, + "blocked_areas": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Ids of the configured blocked areas the position is in." + }, + "position_source": { + "type": "string", + "nullable": true, + "description": "Where the step takes the position from, as configured: the device checked against the IP location, the device only, or the IP location only.", + "enum": [ + "device_and_ip", + "device_only", + "ip_only", + null + ] + }, + "anonymizers": { + "type": "object", + "nullable": true, + "description": "What the connection and device look like, each `true`, `false` or `null` when the source cannot tell.", + "properties": { + "vpn": { + "type": "boolean", + "nullable": true + }, + "proxy": { + "type": "boolean", + "nullable": true + }, + "tor": { + "type": "boolean", + "nullable": true + }, + "spoofing": { + "type": "boolean", + "nullable": true + }, + "emulator": { + "type": "boolean", + "nullable": true + } + } + }, + "ip_distance_km": { + "type": "number", + "nullable": true, + "description": "Distance to the session's IP location after both uncertainty radii are subtracted; `0` when they overlap.", + "format": "double" + }, + "ip_distance_uncertainty_km": { + "type": "number", + "nullable": true, + "description": "Combined radius that was subtracted: the IP location's radius plus this fix's accuracy.", + "format": "double" + }, + "ip_consistent": { + "type": "boolean", + "nullable": true, + "description": "Whether `ip_distance_km` is within the distance configured on the step. `null` when either side is unknown." + }, + "document_distance_km": { + "type": "number", + "nullable": true, + "description": "Distance to the geocoded address on the ID document or proof of address. `null` when no address is on file.", + "format": "double" + }, + "travel_excused": { + "type": "boolean", + "nullable": true, + "description": "Whether the step's travel tolerance excused the distance from the address. `null` when tolerance is off." + }, + "consecutive_mismatches": { + "type": "integer", + "nullable": true, + "description": "On the final attempt: this check plus the same person's earlier mismatches in a row." + }, + "attempts": { + "type": "integer", + "description": "Number of this attempt within the node, starting at 1." + }, + "consent": { + "type": "object", + "nullable": true, + "description": "The consent the person gave before the system prompt, recorded with the wording version and language they saw.", + "properties": { + "accepted_at": { + "type": "string", + "format": "date-time" + }, + "version": { + "type": "string", + "nullable": true + }, + "locale": { + "type": "string", + "nullable": true + } + } + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "NFC", + "LIVENESS", + "FACEMATCH", + "PROOF_OF_ADDRESS", + "PHONE", + "EMAIL", + "AML", + "LOCATION", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "GEOLOCATION", + "SIGNATURE" + ], + "description": "The feature that generated this warning. The value identifies the feature block the warning belongs to. Each module's warnings array typically uses the value matching its module (e.g. `PHONE` inside `phone_verifications[].warnings`), but the enum above is authoritative across all feature warnings on the decision payload." + }, + "risk": { + "type": "string" + }, + "additional_data": { + "type": "object", + "nullable": true + }, + "log_type": { + "type": "string" + }, + "short_description": { + "type": "string" + }, + "long_description": { + "type": "string" + }, + "node_id": { + "type": "string", + "description": "The workflow graph node ID that generated this warning" + } + } + }, + "description": "Warnings this Location verification step raised, with `feature` set to `GEOLOCATION`. Warning codes start with `LOCATION_`." + } + } + }, + "GeolocationAttempt": { + "type": "object", + "description": "One attempt of a Location verification step: the same keys as `geolocation_checks[]` except `warnings`, plus when Didit received it.", + "properties": { + "node_id": { + "type": "string", + "nullable": true, + "description": "Workflow node that produced this result, so a workflow with two Location verification steps stays readable." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Feature-level status of this Location verification step, resolved from its warnings. Independent of the session `status`." + }, + "outcome": { + "type": "string", + "nullable": true, + "description": "What happened on this attempt.", + "enum": [ + "verified", + "low_accuracy", + "accuracy_undetermined", + "stale", + "approximate", + "denied", + "restricted", + "unavailable", + "timeout", + "mock_detected", + "outside_geofence", + "in_blocked_area", + "anonymizer_detected", + "skipped", + "replay_rejected", + "unsupported_client", + null + ] + }, + "permission": { + "type": "string", + "nullable": true, + "description": "What the operating system or browser granted. `not_requested` means the step uses the connection's IP location only, so the person is never asked for a permission.", + "enum": [ + "granted_precise", + "granted_approximate", + "denied", + "restricted", + "unavailable", + "timeout", + "dismissed", + "not_requested", + null + ] + }, + "latitude": { + "type": "number", + "nullable": true, + "description": "WGS84 latitude of the fix. `null` when there is no fix.", + "format": "double" + }, + "longitude": { + "type": "number", + "nullable": true, + "description": "WGS84 longitude of the fix. `null` when there is no fix.", + "format": "double" + }, + "accuracy_m": { + "type": "number", + "nullable": true, + "description": "Radius of uncertainty in metres, at the confidence level in `accuracy_confidence`.", + "format": "double" + }, + "accuracy_confidence": { + "type": "string", + "nullable": true, + "description": "Confidence level the platform attaches to `accuracy_m`: `p95` (browsers), `p68` (Android) or `unspecified` (iOS).", + "enum": [ + "p95", + "p68", + "unspecified", + null + ] + }, + "measured_at": { + "type": "string", + "nullable": true, + "description": "When the device took the fix, by its own clock.", + "format": "date-time" + }, + "age_seconds": { + "type": "integer", + "nullable": true, + "description": "Seconds between the device taking the fix and Didit receiving it." + }, + "source": { + "type": "string", + "nullable": true, + "description": "Where the fix came from, when the platform says. `ip` means the position is the connection's IP location.", + "enum": [ + "gps", + "fused", + "network", + "wifi", + "cell", + "browser", + "unknown", + "ip", + null + ] + }, + "platform": { + "type": "string", + "nullable": true, + "description": "The client that produced the fix.", + "enum": [ + "web", + "ios", + "android", + "react_native", + "flutter", + null + ] + }, + "sdk_version": { + "type": "string", + "nullable": true, + "description": "Version of the SDK that produced the fix." + }, + "is_mock": { + "type": "boolean", + "nullable": true, + "description": "`true` when the device says the fix came from a mock location provider or simulation software. `null` when the platform does not report it (browsers cannot tell); `null` is not `false`." + }, + "produced_by_accessory": { + "type": "boolean", + "nullable": true, + "description": "iOS only: `true` when the fix came from an external accessory. `null` elsewhere." + }, + "integrity": { + "type": "string", + "enum": [ + "attested", + "not_attested", + "failed", + "unsupported", + "not_determined" + ], + "description": "Whether device attestation backs the fix. `unsupported` is what browsers report, because the web has no attestation mechanism; it is not a failure." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country the fix is in." + }, + "region_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision the fix is in." + }, + "city": { + "type": "string", + "nullable": true, + "description": "Nearest populated place, a label for reviewers." + }, + "geofence": { + "type": "object", + "description": "Result of the country and area rules of the step.", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether the step applies any country or area rule." + }, + "result": { + "type": "string", + "nullable": true, + "description": "Where the position sits relative to the rules. `undetermined` means the accuracy circle straddles the border or the position resolved to no country; it is not evidence of being outside.", + "enum": [ + "inside", + "outside", + "undetermined", + null + ] + } + } + }, + "inside_geofence": { + "type": "boolean", + "nullable": true, + "description": "`true` when `geofence.result` is `inside`, `false` when `outside`, `null` otherwise." + }, + "geofence_basis": { + "type": "string", + "nullable": true, + "description": "Which position the area result was judged from: the `device` fix, or the connection's `ip` location.", + "enum": [ + "device", + "ip", + null + ] + }, + "inside_areas": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Ids of the configured allowed areas the position is inside." + }, + "blocked_areas": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Ids of the configured blocked areas the position is in." + }, + "position_source": { + "type": "string", + "nullable": true, + "description": "Where the step takes the position from, as configured: the device checked against the IP location, the device only, or the IP location only.", + "enum": [ + "device_and_ip", + "device_only", + "ip_only", + null + ] + }, + "anonymizers": { + "type": "object", + "nullable": true, + "description": "What the connection and device look like, each `true`, `false` or `null` when the source cannot tell.", + "properties": { + "vpn": { + "type": "boolean", + "nullable": true + }, + "proxy": { + "type": "boolean", + "nullable": true + }, + "tor": { + "type": "boolean", + "nullable": true + }, + "spoofing": { + "type": "boolean", + "nullable": true + }, + "emulator": { + "type": "boolean", + "nullable": true + } + } + }, + "ip_distance_km": { + "type": "number", + "nullable": true, + "description": "Distance to the session's IP location after both uncertainty radii are subtracted; `0` when they overlap.", + "format": "double" + }, + "ip_distance_uncertainty_km": { + "type": "number", + "nullable": true, + "description": "Combined radius that was subtracted: the IP location's radius plus this fix's accuracy.", + "format": "double" + }, + "ip_consistent": { + "type": "boolean", + "nullable": true, + "description": "Whether `ip_distance_km` is within the distance configured on the step. `null` when either side is unknown." + }, + "document_distance_km": { + "type": "number", + "nullable": true, + "description": "Distance to the geocoded address on the ID document or proof of address. `null` when no address is on file.", + "format": "double" + }, + "travel_excused": { + "type": "boolean", + "nullable": true, + "description": "Whether the step's travel tolerance excused the distance from the address. `null` when tolerance is off." + }, + "consecutive_mismatches": { + "type": "integer", + "nullable": true, + "description": "On the final attempt: this check plus the same person's earlier mismatches in a row." + }, + "attempts": { + "type": "integer", + "description": "Number of this attempt within the node, starting at 1." + }, + "consent": { + "type": "object", + "nullable": true, + "description": "The consent the person gave before the system prompt, recorded with the wording version and language they saw.", + "properties": { + "accepted_at": { + "type": "string", + "format": "date-time" + }, + "version": { + "type": "string", + "nullable": true + }, + "locale": { + "type": "string", + "nullable": true + } + } + }, + "received_at": { + "type": "string", + "nullable": true, + "description": "When Didit received the attempt.", + "format": "date-time" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BiometricRetention": { + "type": "object", + "description": "When the live biometric data of this session is deleted.", + "required": [ + "jurisdictions", + "deletes_at" + ], + "properties": { + "jurisdictions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Codes of the US states whose biometric rules set a deletion limit for this person (`US` when the state is unknown). Empty when none applies." + }, + "deletes_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "The date the live biometric data (face images and templates) of this session is deleted, from your application's retention setting and, where one applies, the legal limit for the person's jurisdiction. `null` when no date applies yet." + } + } + }, + "TravelRuleManualMatchRequest": { + "type": "object", + "required": [ + "transaction_id" + ], + "properties": { + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "The `transaction_id` of the received Travel Rule message (a row with `kind: message`)." + } + } + }, + "TravelRuleManualMatchResponse": { + "type": "object", + "required": [ + "deposit", + "message" + ], + "properties": { + "deposit": { + "$ref": "#/components/schemas/TravelRuleTransferRow" + }, + "message": { + "$ref": "#/components/schemas/TravelRuleTransferRow" + } + } + } + }, + "securitySchemes": { + "ApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "x-api-key", + "description": "Your application's API key, from Developers -> API keys in the Business Console. The primary key has full access. A named key can be scoped: none, read or write per resource, limited to some workflows or to approved sessions, to a list of IP addresses, and to an expiry date. 401 means the key is wrong, revoked or expired; 403 means no key was sent, the key has no access to this resource or action, or the request came from an address outside its IP list; 404 on a session route means the session is outside the key's workflows or statuses. A key without media access receives image, video and PDF URLs as null, and a key without sessions write receives session links and tokens as null. See https://docs.didit.me/console/api-keys." + }, + "TransactionTokenAuth": { + "type": "apiKey", + "in": "header", + "name": "X-Transaction-Token", + "description": "Short-lived scoped token minted by your backend via POST /v3/transactions/sdk-token/. Used by the Didit SDKs on the device-facing /v1/transactions/ endpoints." + }, + "SessionTokenAuth": { + "type": "apiKey", + "in": "header", + "name": "Session-Token", + "description": "Short-lived token returned in the `session_token` field of POST /v3/session/. Used by the hosted verification flow (and custom sandbox UIs) to call session-scoped endpoints on behalf of the verifying user, without your server-side API key." + }, + "BearerAuth": { + "type": "http", + "scheme": "bearer", + "description": "The application's client-credentials access token." + }, + "CaseRequestToken": { + "type": "apiKey", + "in": "header", + "name": "X-Case-Request-Token", + "description": "Request-specific credential from the secure link. Grants access only to this request." + } + }, + "responses": { + "Unauthorized": { + "description": "Unauthorized", + "content": { + "application/json": { + "examples": { + "Invalid API Key": { + "summary": "Invalid API Key", + "value": { + "detail": "Invalid API Key." + } + } + } + } + } + }, + "Forbidden": { + "description": "Forbidden", + "content": { + "application/json": { + "examples": { + "No Permission": { + "summary": "No Permission", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not Enough Credits": { + "summary": "Not Enough Credits", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "NotFound": { + "description": "Not Found", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Not Found", + "value": { + "detail": "Resource not found." + } + } + } + } + } + }, + "PhoneRiskValidationError": { + "description": "Request validation failed.", + "content": { + "application/json": { + "examples": { + "Missing phone number": { + "summary": "Missing phone number", + "value": { + "phone_number": [ + "This field is required." + ] + } + }, + "Invalid phone number": { + "summary": "Invalid phone number", + "value": { + "phone_number": [ + "Invalid phone number provided." + ] + } + }, + "Invalid signal IP": { + "summary": "Invalid signal IP", + "value": { + "signals": { + "ip": [ + "Enter a valid IPv4 or IPv6 address." + ] + } + } + } + } + } + } + }, + "EmailRiskValidationError": { + "description": "Request validation failed.", + "content": { + "application/json": { + "examples": { + "Missing email": { + "summary": "Missing email", + "value": { + "email": [ + "This field is required." + ] + } + }, + "Invalid email": { + "summary": "Invalid email", + "value": { + "email": [ + "Enter a valid email address." + ] + } + }, + "Invalid country hint": { + "summary": "Invalid country hint", + "value": { + "country_code": [ + "Ensure this field has no more than 3 characters." + ] + } + } + } + } + } + }, + "IPRiskValidationError": { + "description": "Request validation failed.", + "content": { + "application/json": { + "examples": { + "Missing IP address": { + "summary": "Missing IP address", + "value": { + "ip_address": [ + "This field is required." + ] + } + }, + "Invalid IP address": { + "summary": "Invalid IP address", + "value": { + "ip_address": [ + "Enter a valid IPv4 or IPv6 address." + ] + } + }, + "Country hint too long": { + "summary": "Country hint too long", + "value": { + "claimed_country": [ + "Ensure this field has no more than 3 characters." + ] + } + } + } + } + } + }, + "RiskPermissionDenied": { + "description": "The API key is missing or invalid, or the organization cannot cover the live lookup. This API never returns `401`.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid or revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the lookup price", + "value": { + "error": "You don't have enough credits to perform this request." + } + } + } + } + } + }, + "RateLimitedWrite": { + "description": "The shared write limit of 300 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "detail" + ], + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + }, + "AddressValidationValidationError": { + "description": "The request body is invalid: a missing or unknown `country`, no address, or both `address` and the structured fields.", + "content": { + "application/json": { + "examples": { + "Unknown country": { + "value": { + "country": [ + "Use an ISO 3166-1 alpha-2 or alpha-3 country code, e.g. 'US' or 'USA'." + ] + } + }, + "No address": { + "value": { + "address": [ + "Send the address: 'address' on one line, or the structured fields with 'address_line_1'." + ] + } + }, + "Both shapes": { + "value": { + "address": [ + "Send either 'address' or the structured fields (address_line_1, ...), not both." + ] + } + } + } + } + } + }, + "AddressValidationPermissionDenied": { + "description": "The API key is missing or invalid, a scoped key lacks `sessions` write access, or the organization cannot cover the validation. This API never returns `401`.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "value": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me", + "code": "insufficient_credits" + } + } + } + } + } + } + }, + "parameters": { + "WebhookSignature": { + "name": "X-Signature", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Lowercase hex HMAC-SHA256, keyed with the destination's secret, of the exact raw request body." + }, + "WebhookSignatureV2": { + "name": "X-Signature-V2", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Lowercase hex HMAC-SHA256, keyed with the destination's secret, of the canonical JSON of the body (keys sorted, no whitespace, non-ASCII characters unescaped). Recommended, because it survives middleware that re-encodes the body." + }, + "WebhookSignatureSimple": { + "name": "X-Signature-Simple", + "in": "header", + "required": true, + "deprecated": true, + "schema": { + "type": "string" + }, + "description": "Lowercase hex HMAC-SHA256, keyed with the destination's secret, of `{timestamp}:{session_id}:{status}:{webhook_type}` (missing fields as empty strings). Covers the envelope only, never the payload." + }, + "WebhookTimestamp": { + "name": "X-Timestamp", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Unix time in seconds, equal to the body's `timestamp`. Reject deliveries older than a few minutes." + } + } + }, + "x-webhooks": { + "status.updated": { + "post": { + "summary": "Session status changed", + "description": "A verification session changed status. `decision` is sent only for `Approved`, `Declined`, `In Review` and `Abandoned`; it has the shape of `GET /v3/session/{sessionId}/decision/` for destinations on webhook version 3 (the default), and the older reduced shape for version 1 or 2 destinations. Business sessions always receive the version 3 business decision.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "data.updated": { + "post": { + "summary": "Session data changed", + "description": "Data of a verification session changed without a status change, for example after a manual review edit, a step override or an ongoing-monitoring rescreen. `decision` is sent only for `Approved`, `Declined`, `In Review` and `Abandoned`; it has the shape of `GET /v3/session/{sessionId}/decision/` for destinations on webhook version 3 (the default), and the older reduced shape for version 1 or 2 destinations. Business sessions always receive the version 3 business decision.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionDataUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "user.status.updated": { + "post": { + "summary": "User status changed", + "description": "A user changed status. Not sent when the user is created.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "user.data.updated": { + "post": { + "summary": "User data changed", + "description": "Fields of a user changed. Not sent when the user is created. One save can produce both a status and a data event.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDataUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "business.status.updated": { + "post": { + "summary": "Business status changed", + "description": "A business changed status. Not sent when the business is created.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "business.data.updated": { + "post": { + "summary": "Business data changed", + "description": "Fields of a business changed. Not sent when the business is created. One save can produce both a status and a data event.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDataUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "transaction.created": { + "post": { + "summary": "Transaction created", + "description": "A transaction was created and screened. Sent for live applications only.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionCreatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "transaction.status.updated": { + "post": { + "summary": "Transaction status changed", + "description": "A transaction changed status, from a rule, a review or an end-user action. Sent for live applications only.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "travel_rule.status.updated": { + "post": { + "summary": "Travel Rule status changed", + "description": "A Travel Rule exchange changed status. Sent on every transition up to and including `FINISHED`, `CANCELLED` or `EXPIRED`, never after. Sent for live applications and, for Travel Rule exchanges, for sandbox applications too (`environment: sandbox`). There is no top-level `status`.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "workflow.compliance_changed": { + "post": { + "summary": "Workflow compliance changed", + "description": "The compliance status of a published workflow changed after a re-evaluation. One event per workflow. There is no top-level `status`.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowComplianceChangedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "kyb.registry_search.resolved": { + "post": { + "summary": "Company search resolved (unsigned callback)", + "description": "Body of the callback sent to the `webhook_url` of `POST /v3/kyb/search/` once the registry search resolves. It is not signed: the request carries `X-Didit-Unsigned-Callback: true` and none of the signature headers, so confirm it by matching `request_id` against a search you started. Uses `event_type`, not `webhook_type`.", + "tags": [ + "Webhook" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBRegistrySearchResolvedCallback" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "transaction_rule_suggestions.created": { + "post": { + "summary": "Transaction rule suggestions created", + "description": "A transaction rule suggestion run proposed new rule changes for your application. Sent when the rule-suggestion settings have webhook notifications on. There is no top-level `status`.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleSuggestionsCreatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + } + } +} diff --git a/codegen/tests/fixtures/verification-e002a815773bbd1599256e2774046ee76520c6c8.json b/codegen/tests/fixtures/verification-e002a815773bbd1599256e2774046ee76520c6c8.json new file mode 100644 index 0000000..2ea03e9 --- /dev/null +++ b/codegen/tests/fixtures/verification-e002a815773bbd1599256e2774046ee76520c6c8.json @@ -0,0 +1,128132 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "3.0.0", + "title": "Didit Verification API", + "description": "Identity verification API. Authenticate with x-api-key header." + }, + "servers": [ + { + "url": "https://verification.didit.me" + } + ], + "tags": [], + "paths": { + "/system/healthcheck": { + "get": { + "summary": "Service healthcheck (public, unauthenticated)", + "description": "Public unauthenticated liveness probe. Returns `HTTP 200` with `{status: \"ok\", timestamp}` when service, DB, cache, and broker are healthy.", + "operationId": "get_system_healthcheck", + "x-didit-sdk": { + "resource": "system", + "method": "healthcheck", + "auth": "public", + "server": false + }, + "tags": [ + "System" + ], + "security": [ + {} + ], + "parameters": [], + "responses": { + "200": { + "description": "Service is healthy and accepting requests. Body is always `{\"status\": \"ok\", \"timestamp\": \"\"}`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "status", + "timestamp" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "ok" + ], + "description": "Liveness marker. Always the literal string `\"ok\"` on a 200 response.", + "example": "ok" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Server time at which the healthcheck was evaluated, in ISO 8601 with millisecond precision and a `Z` (UTC) suffix.", + "example": "2026-04-21T16:42:50.445Z" + } + } + }, + "examples": { + "Healthy": { + "summary": "Service is up", + "value": { + "status": "ok", + "timestamp": "2026-04-21T16:42:50.445Z" + } + } + } + } + } + }, + "503": { + "description": "Service is unhealthy — one of the configured health-check plugins (database, cache, broker) is failing. The response body is intentionally minimal; the HTTP status is the load-bearing signal. Retry with exponential backoff." + }, + "5XX": { + "description": "Verification service is degraded or unreachable. Treat any non-2xx response as \"unhealthy\" and retry with exponential backoff." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -i https://verification.didit.me/system/healthcheck" + }, + { + "lang": "Python", + "label": "Python (requests)", + "source": "import requests\n\nresp = requests.get(\"https://verification.didit.me/system/healthcheck\", timeout=5)\nresp.raise_for_status()\nprint(resp.json()) # {'status': 'ok', 'timestamp': '2026-04-21T16:42:50.445Z'}" + }, + { + "lang": "JavaScript", + "label": "Node.js (fetch)", + "source": "const res = await fetch(\"https://verification.didit.me/system/healthcheck\");\nif (!res.ok) throw new Error(`Didit unhealthy: ${res.status}`);\nconst body = await res.json();\nconsole.log(body); // { status: 'ok', timestamp: '2026-04-21T16:42:50.445Z' }" + } + ] + } + }, + "/v3/email/risk/": { + "post": { + "summary": "Risk-score an email address", + "description": "Return deliverability, disposable-address, breach, cross-session match, and normalized risk-intelligence signals without sending an OTP. The endpoint creates an API session and returns its id as `request_id`.\n\n**Decision.** The top-level `status` mirrors `email.status`. It is normally `Approved`; the risk evaluation can produce `In Review` or `Declined`.\n\n**Compatibility aliases.** Live responses expose the normalized intelligence object as both `email.email_intelligence` and `email.enrichment`. The values are identical. New integrations can read `email.enrichment`. Both keys are absent when no usable intelligence data was returned.\n\n**Billing.** Live calls use Email Risk API pricing. The request is billable once the lookup starts, even when some best-effort fields are unavailable.\n\n**Sandbox.** Sandbox keys validate the request and return deterministic approved data without sending an email, consuming credits, creating a session, or contributing to Networks.\n\n**Authentication.** Send your application's API key in `x-api-key`. Missing or invalid credentials return `403` - this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests per minute per API key across POST/PATCH/DELETE endpoints (transaction creates and event ingestion have a separate budget of 600).\n\n**Social footprint.** Set `enable_social` to `true` to add a `email_social` block: the online platforms this address is registered on, with per-category counts and any profile details available. `profiles_registered: 0` means the platforms were checked and the address was found on none of them - a valid, billable answer. The block is absent when the add-on was not requested or the check could not be completed, and it is not billed in that case. Billing adds one `email_social` unit per request on top of this endpoint's own price.", + "operationId": "post_v3email_risk", + "x-didit-sdk": { + "resource": "email", + "method": "assess_risk", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/email/risk/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"email\": \"alice@example.com\",\n \"country_code\": \"US\",\n \"vendor_data\": \"lead-123\",\n \"metadata\": {\"source\": \"signup_prescreen\"}\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.email.assess_risk(\n email=\"alice@example.com\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.email.assessRisk({\n email: \"alice@example.com\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Email.AssessRisk(ctx, didit.EmailRiskRequest{\n\tEmail: \"alice@example.com\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.EmailRiskRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.email().assessRisk(EmailRiskRequest.builder()\n .email(\"alice@example.com\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "email->assessRisk([\n 'email' => 'alice@example.com',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.email.assess_risk(email: \"alice@example.com\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Email.AssessRiskAsync(new EmailRiskRequest\n{\n Email = \"alice@example.com\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailRiskRequest" + }, + "examples": { + "Minimal": { + "summary": "Score an address with defaults", + "value": { + "email": "alice@example.com" + } + }, + "Full context": { + "summary": "Score with signals, country hint, and correlation ids", + "value": { + "email": "alice@example.com", + "country_code": "US", + "signals": { + "ip": "192.0.2.1", + "device_id": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2", + "device_platform": "web", + "os_version": "14.4", + "app_version": "1.2.34", + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15" + }, + "vendor_data": "lead-123", + "metadata": { + "source": "signup_prescreen" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The risk evaluation completed. Inspect `status` for the decision; HTTP is always `200` for completed lookups.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailRiskResponse" + }, + "examples": { + "Clean address": { + "summary": "Deliverable address with no breach exposure", + "value": { + "request_id": "82397be9-5921-4970-a979-2d5b43e08b33", + "status": "Approved", + "email": { + "status": "Approved", + "email": "alice@example.com", + "is_breached": false, + "breaches": [], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 0, + "verified_at": null, + "warnings": [], + "lifecycle": [], + "matches": [], + "email_intelligence": { + "score": 4.0, + "deliverable": "high", + "breach_risk_level": "Low", + "breach_risk_index": 4, + "is_recent_breach": false, + "is_frequent_breached": false, + "is_disposable": false, + "is_free_domain": false, + "is_breached": false, + "no_of_breaches": 0, + "reason_codes": [] + }, + "enrichment": { + "score": 4.0, + "deliverable": "high", + "breach_risk_level": "Low", + "breach_risk_index": 4, + "is_recent_breach": false, + "is_frequent_breached": false, + "is_disposable": false, + "is_free_domain": false, + "is_breached": false, + "no_of_breaches": 0, + "reason_codes": [] + } + }, + "vendor_data": "lead-123", + "metadata": { + "source": "signup_prescreen" + }, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + }, + "Breached address": { + "summary": "Address found in known breaches", + "value": { + "request_id": "d1e5c2a8-6f30-4c1a-8b2d-3e4f5a6b7c8d", + "status": "In Review", + "email": { + "status": "In Review", + "email": "bob@example.org", + "is_breached": true, + "breaches": [ + { + "name": "ExampleBreach2023", + "domain": "example.org", + "breach_date": "2023-06-01", + "breach_emails_count": 1200000, + "description": "A data breach affecting example.org accounts.", + "logo_path": "https://logos.example.org/example.png", + "data_classes": [ + "email_addresses", + "passwords" + ], + "is_verified": true + } + ], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 0, + "verified_at": null, + "warnings": [], + "lifecycle": [], + "matches": [], + "email_intelligence": { + "score": 58.0, + "deliverable": "high", + "breach_risk_level": "High", + "breach_risk_index": 72, + "is_recent_breach": true, + "is_frequent_breached": true, + "is_disposable": false, + "is_free_domain": true, + "is_breached": true, + "no_of_breaches": 4, + "reason_codes": [ + { + "code": "IR-F784B630", + "direction": "risk", + "message": "Email's digital footprint diversity is Very Low", + "impact_level": "low" + } + ] + }, + "enrichment": { + "score": 58.0, + "deliverable": "high", + "breach_risk_level": "High", + "breach_risk_index": 72, + "is_recent_breach": true, + "is_frequent_breached": true, + "is_disposable": false, + "is_free_domain": true, + "is_breached": true, + "no_of_breaches": 4, + "reason_codes": [ + { + "code": "IR-F784B630", + "direction": "risk", + "message": "Email's digital footprint diversity is Very Low", + "impact_level": "low" + } + ] + } + }, + "vendor_data": "lead-125", + "metadata": null, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/EmailRiskValidationError" + }, + "403": { + "$ref": "#/components/responses/RiskPermissionDenied" + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/ip/risk/": { + "post": { + "summary": "Risk-score an IP address", + "description": "Return geolocation, network ownership, VPN/Tor and data-center flags, threat data, and `country_mismatch` for a supplied IP address. The endpoint creates an API session and returns its id as `request_id`.\n\n**Decision.** `status` is `In Review` when the resolved country conflicts with `claimed_country`, or when the address is associated with VPN, Tor, or data-center infrastructure. Otherwise it is `Approved`.\n\n**Best effort.** IP enrichment is best effort. If no enrichment source returns usable data, the endpoint returns an approved response with `ip.ip_address: null`, `ip.country_mismatch: null`, and an empty `ip.threat` object.\n\n**Billing.** Live calls use IP Risk API pricing. The request is billable once the lookup starts.\n\n**Sandbox.** Sandbox keys validate the request and return deterministic approved data without consuming credits, creating a session, or contributing to Networks.\n\n**Authentication.** Send your application's API key in `x-api-key`. Missing or invalid credentials return `403` - this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests per minute per API key across POST/PATCH/DELETE endpoints (transaction creates and event ingestion have a separate budget of 600).", + "operationId": "post_v3ip_risk", + "x-didit-sdk": { + "resource": "ip", + "method": "assess_risk", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/ip/risk/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"ip_address\": \"8.8.8.8\",\n \"claimed_country\": \"ESP\",\n \"user_agent\": \"Mozilla/5.0\",\n \"vendor_data\": \"lead-123\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.ip.assess_risk(\n ip_address=\"8.8.8.8\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.ip.assessRisk({\n ip_address: \"8.8.8.8\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IP.AssessRisk(ctx, didit.IPRiskRequest{\n\tIPAddress: \"8.8.8.8\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.IPRiskRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.ip().assessRisk(IPRiskRequest.builder()\n .ipAddress(\"8.8.8.8\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "ip->assessRisk([\n 'ip_address' => '8.8.8.8',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.ip.assess_risk(ip_address: \"8.8.8.8\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Ip.AssessRiskAsync(new IPRiskRequest\n{\n IpAddress = \"8.8.8.8\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IPRiskRequest" + }, + "examples": { + "Minimal": { + "summary": "Score an address with defaults", + "value": { + "ip_address": "8.8.8.8" + } + }, + "With claimed country": { + "summary": "Score with a claimed country for mismatch detection", + "value": { + "ip_address": "8.8.8.8", + "claimed_country": "ESP", + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15", + "vendor_data": "lead-123" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The risk evaluation completed. Inspect `status` for the decision; HTTP is always `200` for completed lookups.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IPRiskResponse" + }, + "examples": { + "Clean residential IP": { + "summary": "Residential address, claimed country matches", + "value": { + "request_id": "9a96ed2a-721e-4b86-8cbf-d61eda529279", + "status": "Approved", + "ip": { + "status": "Approved", + "ip_address": "192.0.2.10", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_state": "Madrid", + "ip_city": "Madrid", + "latitude": 40.4168, + "longitude": -3.7038, + "isp": "Example Telecom", + "organization": "Example Telecom SA", + "is_vpn_or_tor": false, + "is_data_center": false, + "time_zone": "Europe/Madrid", + "time_zone_offset": "+0200", + "asn_number": 3352, + "asn_organization": "EXAMPLE-TELECOM", + "connection_type": "broadband", + "carrier": "Example Telecom", + "proxy_type": null, + "claimed_country": "ESP", + "country_mismatch": false, + "threat": {} + }, + "vendor_data": "lead-123", + "metadata": null, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + }, + "Datacenter mismatch": { + "summary": "Data-center address conflicting with the claimed country", + "value": { + "request_id": "5b1d4e9a-3c7b-4f8e-9a2d-1b0c9d8e7f6a", + "status": "In Review", + "ip": { + "status": "In Review", + "ip_address": "8.8.8.8", + "ip_country": "United States", + "ip_country_code": "US", + "ip_state": "California", + "ip_city": "Mountain View", + "latitude": 37.386, + "longitude": -122.0838, + "isp": "Google", + "organization": "Google LLC", + "is_vpn_or_tor": true, + "is_data_center": true, + "time_zone": "America/Los_Angeles", + "time_zone_offset": "-0700", + "asn_number": 15169, + "asn_organization": "GOOGLE, US", + "connection_type": "hosting", + "carrier": null, + "proxy_type": "VPN", + "claimed_country": "ESP", + "country_mismatch": true, + "threat": { + "is_proxy": true, + "is_data_center": true, + "is_tor": false + } + }, + "vendor_data": "lead-126", + "metadata": null, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/IPRiskValidationError" + }, + "403": { + "$ref": "#/components/responses/RiskPermissionDenied" + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/phone/risk/": { + "post": { + "summary": "Risk-score a phone number", + "description": "Resolve a phone number and return carrier, line-type, porting, disposable-number, cross-session match, and normalized risk-intelligence signals without sending an OTP. The endpoint creates an API session and returns its id as `request_id`.\n\n**Decision.** The top-level `status` mirrors `phone.status`. It is normally `Approved`; the risk evaluation can produce `In Review` or `Declined`.\n\n**Compatibility aliases.** Live responses expose the normalized intelligence object as both `phone.phone_intelligence` and `phone.enrichment`. The values are identical. New integrations can read `phone.enrichment`. Both keys are absent when no usable intelligence data was returned.\n\n**Billing.** Live calls use Phone Risk API pricing. The request is billable once the lookup starts, even when some best-effort fields are unavailable.\n\n**Sandbox.** Sandbox keys validate the request and return deterministic approved data without sending an OTP, consuming credits, creating a session, or contributing to Networks.\n\n**Authentication.** Send your application's API key in `x-api-key`. Missing or invalid credentials return `403` - this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests per minute per API key across POST/PATCH/DELETE endpoints (transaction creates and event ingestion have a separate budget of 600).\n\n**Social footprint.** Set `enable_social` to `true` to add a `phone_social` block: the online platforms this number is registered on, with per-category counts and any profile details available. `profiles_registered: 0` means the platforms were checked and the number was found on none of them - a valid, billable answer. The block is absent when the add-on was not requested or the check could not be completed, and it is not billed in that case. Billing adds one `phone_social` unit per request on top of this endpoint's own price.", + "operationId": "post_v3phone_risk", + "x-didit-sdk": { + "resource": "phone", + "method": "assess_risk", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/phone/risk/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"phone_number\": \"+14155552671\",\n \"country_code\": \"US\",\n \"vendor_data\": \"lead-123\",\n \"metadata\": {\"source\": \"signup_prescreen\"}\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.phone.assess_risk(\n phone_number=\"+14155552671\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.phone.assessRisk({\n phone_number: \"+14155552671\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Phone.AssessRisk(ctx, didit.PhoneRiskRequest{\n\tPhoneNumber: \"+14155552671\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.PhoneRiskRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.phone().assessRisk(PhoneRiskRequest.builder()\n .phoneNumber(\"+14155552671\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "phone->assessRisk([\n 'phone_number' => '+14155552671',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.phone.assess_risk(phone_number: \"+14155552671\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Phone.AssessRiskAsync(new PhoneRiskRequest\n{\n PhoneNumber = \"+14155552671\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PhoneRiskRequest" + }, + "examples": { + "Minimal": { + "summary": "Score a number with defaults", + "value": { + "phone_number": "+14155552671" + } + }, + "Full context": { + "summary": "Score with signals, country hint, and correlation ids", + "value": { + "phone_number": "+14155552671", + "country_code": "US", + "signals": { + "ip": "192.0.2.1", + "device_id": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2", + "device_platform": "ios", + "device_model": "iPhone17,2", + "os_version": "18.0.1", + "app_version": "1.2.34", + "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1" + }, + "vendor_data": "lead-123", + "metadata": { + "source": "signup_prescreen" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The risk evaluation completed. Inspect `status` for the decision; HTTP is always `200` for completed lookups.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PhoneRiskResponse" + }, + "examples": { + "Clean mobile number": { + "summary": "Clean mobile number with intelligence coverage", + "value": { + "request_id": "4fd09d2e-9bc9-4c4e-9c46-7f625ac0ab62", + "status": "Approved", + "phone": { + "status": "Approved", + "phone_number_prefix": "+1", + "phone_number": "4155552671", + "full_number": "+14155552671", + "country_code": "US", + "country_name": "United States", + "carrier": { + "name": "Example Mobile", + "type": "mobile" + }, + "is_disposable": false, + "is_virtual": false, + "verification_method": null, + "verification_attempts": 0, + "verified_at": null, + "warnings": [], + "lifecycle": [], + "matches": [], + "phone_intelligence": { + "score": 3.0, + "phone_type_risk": "Low", + "phone_trust_index": 97, + "network_stability_index": 95, + "is_disposable": false, + "is_ported": false, + "is_recently_ported": false, + "carrier_type": "mobile", + "reason_codes": [] + }, + "enrichment": { + "score": 3.0, + "phone_type_risk": "Low", + "phone_trust_index": 97, + "network_stability_index": 95, + "is_disposable": false, + "is_ported": false, + "is_recently_ported": false, + "carrier_type": "mobile", + "reason_codes": [] + } + }, + "vendor_data": "lead-123", + "metadata": { + "source": "signup_prescreen" + }, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + }, + "Number in review": { + "summary": "Disposable or virtual number flagged for review", + "value": { + "request_id": "8c22e1f0-4b3d-4a3a-9d1e-2f6b7c8d9e0f", + "status": "In Review", + "phone": { + "status": "In Review", + "phone_number_prefix": "+44", + "phone_number": "7700900123", + "full_number": "+447700900123", + "country_code": "GB", + "country_name": "United Kingdom", + "carrier": { + "name": "Example VoIP", + "type": "voip" + }, + "is_disposable": true, + "is_virtual": true, + "verification_method": null, + "verification_attempts": 0, + "verified_at": null, + "warnings": [], + "lifecycle": [], + "matches": [], + "phone_intelligence": { + "score": 72.0, + "phone_type_risk": "High", + "phone_trust_index": 28, + "network_stability_index": 40, + "is_disposable": true, + "is_ported": true, + "is_recently_ported": true, + "carrier_type": "voip", + "reason_codes": [ + { + "code": "IR-5AC67A32", + "direction": "risk", + "message": "Phone type identified as non-mobile", + "impact_level": "medium" + } + ] + }, + "enrichment": { + "score": 72.0, + "phone_type_risk": "High", + "phone_trust_index": 28, + "network_stability_index": 40, + "is_disposable": true, + "is_ported": true, + "is_recently_ported": true, + "carrier_type": "voip", + "reason_codes": [ + { + "code": "IR-5AC67A32", + "direction": "risk", + "message": "Phone type identified as non-mobile", + "impact_level": "medium" + } + ] + } + }, + "vendor_data": "lead-124", + "metadata": null, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/PhoneRiskValidationError" + }, + "403": { + "$ref": "#/components/responses/RiskPermissionDenied" + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/": { + "post": { + "summary": "Create a verification session", + "description": "Create a User Verification (KYC) or Business Verification (KYB) session and receive a hosted verification `url` plus a `session_token` to redirect or embed for your end user. The `workflow_id` selects which verification steps run and whether the session is KYC or KYB.\n\nCall this from your backend (never the browser — the API key is a secret) whenever a user or business needs to be verified: at signup, before a sensitive action, or when re-verification is required. Prerequisites: a published workflow (created in the Console [Workflows](https://docs.didit.me/console/workflows) page) and an application API key with available credits. Sandbox applications bypass the credit check.\n\n**Idempotency:** when `vendor_data` is provided and an unfinished session (`Not Started`, `In Progress`, `Resubmitted`, or `Awaiting User`) with the same `vendor_data` already exists on the workflow's **current latest published version** (and that session already has a hosted verification URL), the existing session is returned (still `201`) instead of creating a duplicate — with its `callback` and `metadata` updated to the newly provided values. An unfinished session created on an older version of the workflow (one that has since been republished) is **not** reused; a new session pinned to the latest published version is created instead. Sessions in `Approved`, `Declined`, `In Review`, `Expired`, `Abandoned`, or `Kyc Expired` are never reused.\n\n**Side effects:** the hosted verification URL is generated and stored; if `contact_details.send_notification_emails` is `true` and an email is provided, a verification invite email is sent. The session expires after the workflow's configured session expiration time. For KYB workflows, `contact_details.phone` and `expected_details` are ignored.", + "operationId": "post_v3_session_create", + "x-didit-sdk": { + "resource": "sessions", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "201": { + "description": "Session created (or returned, if an unfinished session with the same `vendor_data` already exists on the workflow's latest published version). The response is the serialized session, including the hosted verification `url` to redirect the user to.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "session_id", + "session_number", + "session_token", + "url", + "vendor_data", + "metadata", + "status", + "workflow_id", + "workflow_version", + "callback" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of the verification session. Use this id when calling `GET /v3/session/{sessionId}/decision/`.", + "example": "11111111-2222-3333-4444-555555555555" + }, + "session_number": { + "type": "integer", + "description": "Sequential, human-friendly number assigned to the session inside your application. Useful for support and dashboards.", + "example": 43762 + }, + "session_token": { + "type": "string", + "description": "12-character URL-safe token that authorizes the end user to access the hosted verification flow at `url`. Valid until the session expires. Treat it as a secret — anyone holding it can open the verification flow for this session.", + "example": "3FaJ9wLqX2Mz" + }, + "url": { + "type": "string", + "format": "uri", + "description": "Hosted verification URL to redirect the end user to. The URL embeds the `session_token` and, if configured, uses your white-label domain. When the request includes `language`, the URL contains a language path segment — `https://verify.didit.me/{language}/session/{session_token}` (e.g. `/es/session/...`); without `language`, the segment is omitted.", + "example": "https://verify.didit.me/session/3FaJ9wLqX2Mz" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Identifier you passed in the request to link the session to a user or business in your own system. Echoed back verbatim. Null when not provided.", + "example": "user-123" + }, + "metadata": { + "description": "Arbitrary JSON payload you stored with the session at creation time. Echoed back verbatim — whatever JSON value you sent (object, string, number, array) is returned as-is, though a JSON object is recommended. Not shown to the end user. Always present in responses; `null` when not provided at creation time.", + "example": { + "user_type": "premium", + "account_id": "ABC123" + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The tags on the session, by name.", + "example": [ + "VIP", + "ONBOARDING Q4" + ] + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Current status of the session. Newly created sessions return `Not Started`. If a non-finished session already existed for the same `vendor_data` on the workflow's latest published version, the returned status reflects that existing session.", + "example": "Not Started" + }, + "callback": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Final redirect URL the user is sent to after completing the flow. Didit appends `?verificationSessionId={session_id}&status={status}` to this URL. Falls back to the workflow's configured callback URL when not provided in the request; `null` when neither is set. When the application has allowed domains and this URL is a web URL outside them (for example a workflow default set before the list changed), the hosted flow does not redirect the user to it.", + "example": "https://example.com/verification/callback" + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier of the workflow this session runs on. Always the workflow's stable group identifier — even if you referenced a specific workflow version UUID in the request (backward-compatible lookup), the response carries the stable `workflow_id`. Use it to correlate the session with the workflow you configured in the Console.", + "example": "11111111-2222-3333-4444-555555555555" + }, + "workflow_version": { + "type": "integer", + "description": "Version number of the published workflow version the session was pinned to at creation. Didit resolves `workflow_id` to the workflow's latest published version when the session is created, and the session keeps running that version even if the workflow is republished later.", + "example": 3 + } + } + }, + "examples": { + "KYC session": { + "summary": "KYC session created", + "value": { + "session_id": "11111111-2222-3333-4444-555555555555", + "session_number": 43762, + "session_token": "3FaJ9wLqX2Mz", + "url": "https://verify.didit.me/en/session/3FaJ9wLqX2Mz", + "vendor_data": "user-123", + "metadata": { + "user_type": "premium", + "account_id": "ABC123" + }, + "status": "Not Started", + "workflow_id": "11111111-2222-3333-4444-555555555555", + "workflow_version": 3, + "callback": "https://example.com/verification/callback" + } + }, + "KYB session": { + "summary": "KYB session created", + "value": { + "session_id": "22222222-3333-4444-5555-666666666666", + "session_number": 43763, + "session_token": "Yk7pQ2vN8aBc", + "url": "https://verify.didit.me/en/session/Yk7pQ2vN8aBc", + "vendor_data": "company-acme-001", + "metadata": { + "tier": "enterprise" + }, + "status": "Not Started", + "workflow_id": "33333333-4444-5555-6666-777777777777", + "workflow_version": 1, + "callback": "https://example.com/kyb/callback" + } + } + } + } + } + }, + "400": { + "description": "Bad request — the payload failed validation (missing or unknown `workflow_id`, invalid `language`, blocklisted `vendor_data`, malformed `portrait_image`, `address` without `country`/`poa_country`, invalid `sandbox_scenario`, `sign` documents, fields or values the workflow's E-signature step refuses, such as `signature_document_not_supported`, `signature_document_conversion_failed`, `field_anchor_not_found` or `field_value_missing`, etc.) or your organization does not have enough credits to start the session.\n\nThe body is an object keyed by the offending field name (or `detail` for non-field errors). Values are an **array of strings** for input-validation failures (e.g. missing required field, invalid language, oversized portrait image) and a **plain string** for failures detected while creating the session (unknown `workflow_id`, blocklisted `vendor_data`, no reference face available for a Face Match workflow). Handle both shapes.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message for non-field errors (e.g. insufficient credits). Field-level failures use the field name as the key instead, with a string or array-of-strings value.", + "example": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + }, + "examples": { + "Missing workflow_id": { + "summary": "Required field omitted (array-of-strings shape)", + "value": { + "workflow_id": [ + "This field is required." + ] + } + }, + "Invalid workflow_id": { + "summary": "Unknown workflow_id (plain-string shape)", + "value": { + "workflow_id": "Invalid workflow_id." + } + }, + "Insufficient credits": { + "summary": "Organization is out of credits", + "value": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + }, + "Blocklisted vendor_data": { + "summary": "vendor_data is blocklisted", + "value": { + "vendor_data": "This user or business has been blocked and cannot create new verification sessions." + } + }, + "Portrait image too large": { + "summary": "portrait_image fails size validation", + "value": { + "portrait_image": [ + "Image size exceeds 2MB." + ] + } + }, + "No stored face for vendor_data": { + "summary": "Face Match workflow without portrait_image and no stored face for the vendor_data user", + "value": { + "portrait_image": "No stored face image was found for this user. Send a portrait_image, or complete an approved verification with face liveness or an ID document for this vendor_data first." + } + }, + "Portrait image required without vendor_data": { + "summary": "Face Match workflow without portrait_image and without vendor_data", + "value": { + "portrait_image": "A portrait image is required to perform face match for this workflow. Send a portrait_image, or provide the vendor_data of a user with a previously stored face image to reuse it." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failed. A wrong, revoked or expired key returns `401`. The endpoint returns `403` when the `x-api-key` header is missing or the API key is valid but the client does not have permission to create sessions in this application (for example the API key belongs to a different organization). These failures share the body `{\"detail\": \"You do not have permission to perform this action.\"}`. Check that the `x-api-key` header is present and belongs to the intended application, re-fetch the application API key via the [Auth API](/auth-api/get-credentials) if needed, and retry.\n\n**Deleted application:** for a few minutes after an application is deleted, a request made with one of its API keys can answer `{\"detail\": \"This application was deleted.\", \"code\": \"application_deleted\"}`. Do not retry: the application and its API keys no longer exist. After those few minutes, the key is refused like any other invalid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You do not have permission to perform this action." + }, + "code": { + "type": "string", + "description": "Machine-readable reason, present only on some failures. `application_deleted`: the API key belongs to an application that was deleted.", + "example": "application_deleted" + } + }, + "required": [ + "detail" + ] + }, + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header or invalid API key", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "No permission": { + "summary": "Client cannot create sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Application deleted": { + "summary": "API key of a deleted application", + "value": { + "detail": "This application was deleted.", + "code": "application_deleted" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded — more than 600 session-create requests in a 60-second window from the same API key / IP. Inspect `Retry-After` and back off before retrying.\n\n**Note:** The source code also defines a `FREE_SESSION_RATE_LIMIT` of 10 calls/minute alongside `PAID_SESSION_RATE_LIMIT=600` (`sessions/serializers/session.py`), but that free-tier ceiling is **not applied to this V3 endpoint** — the rate-limit middleware enforces the 600/min `SESSION_CREATE_RATE_LIMIT` regardless of plan. 600/min is the only limit you can hit here.", + "headers": { + "Retry-After": { + "description": "Seconds the caller must wait before retrying the request.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 42 + } + }, + "X-RateLimit-Limit": { + "description": "Maximum number of requests allowed in the current 60-second window for the limit that was breached (600 for session-create). Absent on the sandbox-quota variant.", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Number of requests still allowed in the current window for the breached limit. Absent on the sandbox-quota variant.", + "schema": { + "type": "integer", + "minimum": 0, + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "UTC epoch seconds when the current rate-limit window resets. Absent on the sandbox-quota variant.", + "schema": { + "type": "integer", + "minimum": 0, + "example": 1747497600 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable explanation of the rate-limit breach. Use this message to distinguish which of the three limits was hit.", + "example": "Session creation rate limit exceeded. You can make up to 600 requests per minute." + } + } + }, + "examples": { + "Session-create limit": { + "summary": "More than 600 session-create requests per minute", + "value": { + "detail": "Session creation rate limit exceeded. You can make up to 600 requests per minute." + } + }, + "Sandbox session quota": { + "summary": "Sandbox application exceeded 500 sessions in 24 hours", + "value": { + "detail": "Sandbox session quota exceeded (500 sessions per 24h). Wait for the window to reset. Expected available in 3600 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/session/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"workflow_id\": \"11111111-2222-3333-4444-555555555555\",\n \"vendor_data\": \"user-123\",\n \"callback\": \"https://example.com/verification/callback\",\n \"callback_method\": \"both\",\n \"metadata\": {\"user_type\": \"premium\", \"account_id\": \"ABC123\"},\n \"language\": \"en\",\n \"contact_details\": {\n \"email\": \"john.doe@example.com\",\n \"send_notification_emails\": true,\n \"email_lang\": \"en\",\n \"phone\": \"+14155552671\"\n },\n \"expected_details\": {\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"date_of_birth\": \"1990-05-15\",\n \"id_country\": \"USA\",\n \"expected_document_types\": [\"P\", \"ID\"]\n }\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.create(\n workflow_id=\"11111111-2222-3333-4444-555555555555\",\n vendor_data=\"user-123\",\n callback=\"https://example.com/verification/callback\",\n callback_method=\"both\",\n metadata={\"user_type\": \"premium\", \"account_id\": \"ABC123\"},\n language=\"en\",\n contact_details={\n \"email\": \"john.doe@example.com\",\n \"send_notification_emails\": True,\n \"email_lang\": \"en\",\n \"phone\": \"+14155552671\",\n },\n expected_details={\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"date_of_birth\": \"1990-05-15\",\n \"nationality\": \"USA\",\n \"id_country\": \"USA\",\n \"expected_document_types\": [\"P\", \"ID\"],\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.create({\n workflow_id: \"11111111-2222-3333-4444-555555555555\",\n vendor_data: \"user-123\",\n callback: \"https://example.com/verification/callback\",\n callback_method: \"both\",\n metadata: { user_type: \"premium\", account_id: \"ABC123\" },\n language: \"en\",\n contact_details: {\n email: \"john.doe@example.com\",\n send_notification_emails: true,\n email_lang: \"en\",\n phone: \"+14155552671\",\n },\n expected_details: {\n first_name: \"John\",\n last_name: \"Doe\",\n date_of_birth: \"1990-05-15\",\n nationality: \"USA\",\n id_country: \"USA\",\n expected_document_types: [\"P\", \"ID\"],\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Create(ctx, didit.SessionsCreateParams{\n\tWorkflowID: \"11111111-2222-3333-4444-555555555555\",\n\tVendorData: didit.String(\"user-123\"),\n\tCallback: didit.String(\"https://example.com/verification/callback\"),\n\tCallbackMethod: didit.Some(didit.SessionsCreateParamsCallbackMethod(\"both\")),\n\tMetadata: didit.Some[any](map[string]any{\"user_type\": \"premium\", \"account_id\": \"ABC123\"}),\n\tLanguage: didit.Some(didit.SessionsCreateParamsLanguage(\"en\")),\n\tContactDetails: didit.Some(didit.SessionsCreateParamsContactDetails{\n\t\tEmail: didit.String(\"john.doe@example.com\"),\n\t\tSendNotificationEmails: didit.Bool(true),\n\t\tEmailLang: didit.Some(didit.SessionsCreateParamsContactDetailsEmailLang(\"en\")),\n\t\tPhone: didit.String(\"+14155552671\"),\n\t}),\n\tExpectedDetails: didit.Some(didit.SessionsCreateParamsExpectedDetails{\n\t\tFirstName: didit.String(\"John\"),\n\t\tLastName: didit.String(\"Doe\"),\n\t\tDateOfBirth: didit.String(\"1990-05-15\"),\n\t\tNationality: didit.String(\"USA\"),\n\t\tIDCountry: didit.String(\"USA\"),\n\t\tExpectedDocumentTypes: didit.Some([]didit.SessionsCreateParamsExpectedDetailsExpectedDocumentTypes{\"P\", \"ID\"}),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SessionsCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().create(SessionsCreateParams.builder()\n .workflowId(\"11111111-2222-3333-4444-555555555555\")\n .vendorData(\"user-123\")\n .callback(\"https://example.com/verification/callback\")\n .callbackMethod(SessionsCreateParams.CallbackMethod.of(\"both\"))\n .metadata(Map.of(\"user_type\", \"premium\", \"account_id\", \"ABC123\"))\n .language(SessionsCreateParams.Language.of(\"en\"))\n .contactDetails(SessionsCreateParams.ContactDetails.builder()\n .email(\"john.doe@example.com\")\n .sendNotificationEmails(true)\n .emailLang(SessionsCreateParams.ContactDetails.EmailLang.of(\"en\"))\n .phone(\"+14155552671\")\n .build())\n .expectedDetails(SessionsCreateParams.ExpectedDetails.builder()\n .firstName(\"John\")\n .lastName(\"Doe\")\n .dateOfBirth(LocalDate.parse(\"1990-05-15\"))\n .nationality(\"USA\")\n .idCountry(\"USA\")\n .expectedDocumentTypes(List.of(SessionsCreateParams.ExpectedDetails.ExpectedDocumentTypes.of(\"P\"), SessionsCreateParams.ExpectedDetails.ExpectedDocumentTypes.of(\"ID\")))\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->create([\n 'workflow_id' => '11111111-2222-3333-4444-555555555555',\n 'vendor_data' => 'user-123',\n 'callback' => 'https://example.com/verification/callback',\n 'callback_method' => 'both',\n 'metadata' => ['user_type' => 'premium', 'account_id' => 'ABC123'],\n 'language' => 'en',\n 'contact_details' => [\n 'email' => 'john.doe@example.com',\n 'send_notification_emails' => true,\n 'email_lang' => 'en',\n 'phone' => '+14155552671',\n ],\n 'expected_details' => [\n 'first_name' => 'John',\n 'last_name' => 'Doe',\n 'date_of_birth' => '1990-05-15',\n 'nationality' => 'USA',\n 'id_country' => 'USA',\n 'expected_document_types' => ['P', 'ID'],\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.create(\n workflow_id: \"11111111-2222-3333-4444-555555555555\",\n vendor_data: \"user-123\",\n callback: \"https://example.com/verification/callback\",\n callback_method: \"both\",\n metadata: { user_type: \"premium\", account_id: \"ABC123\" },\n language: \"en\",\n contact_details: {\n email: \"john.doe@example.com\",\n send_notification_emails: true,\n email_lang: \"en\",\n phone: \"+14155552671\",\n },\n expected_details: {\n first_name: \"John\",\n last_name: \"Doe\",\n date_of_birth: \"1990-05-15\",\n nationality: \"USA\",\n id_country: \"USA\",\n expected_document_types: [\"P\", \"ID\"],\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.CreateAsync(new SessionsCreateParams\n{\n WorkflowId = \"11111111-2222-3333-4444-555555555555\",\n VendorData = \"user-123\",\n Callback = \"https://example.com/verification/callback\",\n CallbackMethod = new SessionsCreateParamsCallbackMethod(\"both\"),\n Metadata = new Dictionary { [\"user_type\"] = \"premium\", [\"account_id\"] = \"ABC123\" },\n Language = new SessionsCreateParamsLanguage(\"en\"),\n ContactDetails = new SessionsCreateParamsContactDetails\n {\n Email = \"john.doe@example.com\",\n SendNotificationEmails = true,\n EmailLang = new SessionsCreateParamsContactDetailsEmailLang(\"en\"),\n Phone = \"+14155552671\",\n },\n ExpectedDetails = new SessionsCreateParamsExpectedDetails\n {\n FirstName = \"John\",\n LastName = \"Doe\",\n DateOfBirth = \"1990-05-15\",\n Nationality = \"USA\",\n IdCountry = \"USA\",\n ExpectedDocumentTypes = new SessionsCreateParamsExpectedDetailsExpectedDocumentTypes[] { \"P\", \"ID\" },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "workflow_id" + ], + "properties": { + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier of the workflow that defines which verification steps the session will run. Workflows are created and managed in the [Workflows](/console/workflows) page of the Console. The `workflow_id` also implicitly selects whether the session is KYC or KYB.", + "example": "11111111-2222-3333-4444-555555555555", + "x-readme-id": "0.0" + }, + "vendor_data": { + "type": "string", + "description": "A unique identifier for the user being verified, such as a UUID, email, or internal user ID. This field is used for: (1) **User grouping** — sessions with the same vendor_data are linked to the same user profile in the Users tab. (2) **Cross-session duplicate detection** — when checking for duplicated faces, documents, phone numbers, emails, IP addresses, or device fingerprints, sessions with the same vendor_data are treated as the same user and excluded from matches. Without vendor_data, every session is treated as a unique user and all potential duplicates are surfaced. We strongly recommend always providing a vendor_data to reduce noise in duplicate detection.", + "example": "user-123", + "x-readme-id": "0.1" + }, + "multi_account_segment": { + "type": "string", + "maxLength": 64, + "description": "Optional duplicate-detection realm inside your organization. Duplicate face checks compare this session only with other sessions that carry the same segment, so separate account bases (personal and business accounts, or one per B2B sub-client) do not flag a person who legitimately holds one account in each. Omit it to use the single default realm, which is what every session uses today. Blocklist and allowlist matching ignore this field.", + "example": "business-accounts" + }, + "callback": { + "type": "string", + "description": "URL to redirect the user to after verification completes. Didit automatically appends `verificationSessionId` and `status` (Approved, Declined, In Review) as query parameters. Custom URL schemes (e.g. `myapp://`) are supported for mobile callbacks. If omitted, the workflow's default `callback_url` is used. When the application has allowed domains (`allowed_domains` on `PATCH /v3/webhook/`), an `http` or `https` callback must be on one of those origins (same scheme, host and port), otherwise the request fails with 400 on `callback`: `callback must be on one of the application's allowed domains: https://app.example.com`. Custom URL schemes are always accepted.", + "example": "https://example.com/verification/callback", + "x-readme-id": "0.2" + }, + "callback_method": { + "type": "string", + "description": "Determines which device should handle the redirect to the provided callback URL. Use `initiator` to redirect only the device that started the flow, `completer` for the device that finishes it, or `both` to allow either device to trigger the callback. If you ever notice the callback not triggering reliably, we recommend setting this value to `both`.", + "enum": [ + "initiator", + "completer", + "both" + ], + "default": "initiator", + "example": "both", + "x-readme-id": "0.3" + }, + "metadata": { + "description": "Arbitrary JSON stored with the session and echoed back in the response and webhooks. Any JSON value is accepted (object, string, number, array), but a JSON object of key/value pairs is recommended. Not shown to the end user. Use it to pass your own correlation ids, A/B variants, or business context.", + "example": { + "user_type": "premium", + "account_id": "ABC123" + }, + "x-readme-id": "0.4" + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "maxLength": 50 + }, + "maxItems": 10, + "description": "Tags added to the session when it is created, by name. Up to 10, each up to 50 characters. Names are case-insensitive and stored in upper case. A name that does not exist yet in the application is created as a new tag. The tags show on the session in the console, where you can filter by them.", + "example": [ + "VIP", + "ONBOARDING Q4" + ] + }, + "language": { + "type": "string", + "description": "Language code (ISO 639-1) for the verification process interface. Controls the language displayed to the end user during verification. If not provided, the browser's language will be automatically detected and used. Check all the supported languages [here](/integration/supported-languages).", + "enum": [ + "en", + "ar", + "bg", + "bn", + "bs", + "ca", + "cnr", + "cs", + "da", + "de", + "el", + "es", + "et", + "fa", + "fi", + "fr", + "he", + "hi", + "hr", + "hu", + "hy", + "id", + "it", + "ja", + "ka", + "kk", + "ko", + "ky", + "lt", + "lv", + "mk", + "mn", + "ms", + "nl", + "no", + "pl", + "pt-BR", + "pt", + "ro", + "ru", + "sk", + "sl", + "so", + "sq", + "sr", + "sv", + "th", + "tr", + "uk", + "uz", + "vi", + "zh-CN", + "zh-TW", + "zh" + ], + "example": "en", + "x-readme-id": "0.5" + }, + "contact_details": { + "type": "object", + "description": "User contact information that can be used for notifications, prefilling verification forms, and phone verification. This includes email address, preferred language for communications, and phone number.", + "example": { + "email": "john.doe@example.com", + "send_notification_emails": true, + "email_lang": "en", + "phone": "+14155552671" + }, + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "Email address of the user (e.g., \"john.doe@example.com\") that will be used during the [Email Verification](/core-technology/email-verification/overview) step. If not provided, the user must provide it during the verification flow.", + "example": "john.doe@example.com", + "x-readme-id": "1.0" + }, + "send_notification_emails": { + "type": "boolean", + "default": false, + "description": "If true and an email is provided, Didit sends the initial \"Verify your identity\" email asynchronously when the User Verification (KYC) or Business Verification (KYB) session is created. Didit also sends verification status notifications for sessions requiring manual review to the provided email address (e.g., from 'In Review' to 'Approved' or 'Declined'). This helps users return to your application once their verification is complete. If you have white-label activated for the session, the email sent will be white-labeled.", + "example": true, + "x-readme-id": "1.1" + }, + "email_lang": { + "type": "string", + "description": "Language code (ISO 639-1) for email notifications. Controls the language of all email communications (e.g., \"en\", \"es\", \"fr\"). There is no stored default — when omitted, the verification invite email simply falls back to English (`en`) at send time. The enum below is a snapshot; the live source of truth for accepted values is the [supported languages](/integration/supported-languages) doc — always consult it before relying on a specific code.", + "enum": [ + "en", + "ar", + "bg", + "bn", + "bs", + "ca", + "cnr", + "cs", + "da", + "de", + "el", + "es", + "et", + "fa", + "fi", + "fr", + "he", + "hi", + "hr", + "hu", + "hy", + "id", + "it", + "ja", + "ka", + "kk", + "ko", + "ky", + "lt", + "lv", + "mk", + "mn", + "ms", + "nl", + "no", + "pl", + "pt-BR", + "pt", + "ro", + "ru", + "sk", + "sl", + "so", + "sq", + "sr", + "sv", + "th", + "tr", + "uk", + "uz", + "vi", + "zh-CN", + "zh-TW", + "zh" + ], + "example": "en", + "x-readme-id": "1.2" + }, + "phone": { + "type": "string", + "description": "Phone number in E.164 format (e.g., \"+14155552671\") that will be used during the [Phone Verification](/core-technology/phone-verification/overview) step. If not provided, the user must provide it during the verification flow. Ignored for Business Verification (KYB) workflows. **Important:** This phone number is only enforced if it is a valid E.164 phone number. If the provided number is invalid or cannot be parsed, it will be ignored and the user will be able to input any valid phone number during the Phone Verification step.", + "example": "+14155552671", + "x-readme-id": "1.3" + } + }, + "x-readme-id": "0.4" + }, + "expected_details": { + "type": "object", + "description": "Expected user details used to cross-validate against the data extracted from the user's ID document, Proof of Address, and other verification steps. Mismatches are surfaced as warnings on the decision; some fields (e.g. `id_country`, `expected_document_types`) also alter the user-facing flow. For Business Verification (KYB) workflows only the business fields (`company_name`, `registry_country`, `registration_number`) are used — they pre-fill the company registry search for the user — and all person-level fields are ignored.", + "example": { + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1990-05-15", + "nationality": "USA", + "id_country": "USA", + "expected_document_types": [ + "P", + "ID" + ] + }, + "properties": { + "first_name": { + "type": "string", + "description": "User's first name. For example, `John`. The matching uses fuzzy comparison, and you can tune the strictness by configuring the name match score threshold in the Console for both ID Document and Proof of Address workflows.", + "example": "John", + "x-readme-id": "1.0" + }, + "last_name": { + "type": "string", + "description": "User's last name. For example, `Doe`. The matching uses fuzzy comparison, and you can tune the strictness by configuring the name match score threshold in the Console for both ID Document and Proof of Address workflows.", + "example": "Doe", + "x-readme-id": "1.1" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "User's date of birth with format: YYYY-MM-DD. For example, `1990-05-15`.", + "example": "1990-05-15", + "x-readme-id": "1.2" + }, + "gender": { + "type": "string", + "nullable": true, + "enum": [ + "M", + "F" + ], + "default": null, + "description": "User's gender. Must be either 'M', 'F', or null.", + "example": "M", + "x-readme-id": "1.3" + }, + "nationality": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the applicant's country of origin. For example, `USA`. See the [full list of supported country codes](https://docs.didit.me/core-technology/id-verification/supported-documents-id-verification#supported-documents-by-country).", + "example": "USA", + "x-readme-id": "1.4" + }, + "country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code used as a fallback for both ID Verification and Proof of Address country-mismatch checks. Required when `address` is provided and neither `id_country` nor `poa_country` is set.", + "example": "USA", + "x-readme-id": "1.4.1" + }, + "id_country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the expected country of the applicant's ID document, which may differ from nationality. For example, `GBR`. Takes priority over `country` for ID verification country mismatch checks. See the [full list of supported country codes](https://docs.didit.me/core-technology/id-verification/supported-documents-id-verification#supported-documents-by-country).", + "example": "GBR", + "x-readme-id": "1.5" + }, + "poa_country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the expected country of the Proof of Address document. For example, `USA`. Takes priority over `country` for POA country mismatch checks. See the [full list of supported country codes](https://docs.didit.me/core-technology/id-verification/supported-documents-id-verification#supported-documents-by-country).", + "example": "USA", + "x-readme-id": "1.5.1" + }, + "address": { + "type": "string", + "description": "The address in a human readable format, including as much information as possible. For example, `123 Main St, San Francisco, CA 94105, USA`. When provided, you must also pass `country` or `poa_country`, otherwise the request fails with `400`.", + "example": "123 Main St, San Francisco, CA 94105, USA", + "x-readme-id": "1.6" + }, + "identification_number": { + "type": "string", + "description": "The user's document number, personal number, or tax number. For example, `123456789`.", + "example": "123456789", + "x-readme-id": "1.7" + }, + "ip_address": { + "type": "string", + "description": "Expected IP address for the session, in IPv4 or IPv6 format. If the actual IP address differs from this value, a warning will be logged. For example, `192.168.1.100` or `2001:db8::1`.", + "example": "192.168.1.100", + "x-readme-id": "1.8" + }, + "expected_document_types": { + "type": "array", + "description": "Restricts the document types the user can present during the ID verification (OCR) step. When set, the document selection screen only shows the requested types and the returned `documents_allowed` is filtered accordingly. Values are case-insensitive and deduplicated; unknown values are rejected with `400`. Allowed codes: `P` (passport), `ID` (national ID), `DL` (driver's license), `RP` (residence permit), `HIC` (health insurance card), `TC` (tax card), `SSC` (social security card).", + "items": { + "type": "string", + "enum": [ + "P", + "ID", + "DL", + "RP", + "HIC", + "TC", + "SSC" + ] + }, + "example": [ + "P", + "ID" + ], + "x-readme-id": "1.9" + }, + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Expected legal name of the business being verified. For example, `Tesco PLC`. Pre-fills the company-name field of the registry search in Business Verification (KYB) workflows; when `registration_number` is also provided, the registry lookup combines both identifiers. Ignored for KYC workflows.", + "example": "Tesco PLC", + "x-readme-id": "1.10" + }, + "registry_country": { + "type": "string", + "maxLength": 10, + "description": "ISO 3166-1 alpha-2 code of the country whose company registry the business is registered in. For example, `GB`. Where company registries operate at state or province level, append the ISO 3166-2 subdivision code as `XX-YY`: `US-CA` targets the California registry. Same format as `country_code` on the [KYB Registry Search API](https://docs.didit.me/standalone-apis/kyb-registry-search). Pre-selects the registry country in Business Verification (KYB) workflows; ignored for KYC workflows.", + "example": "US-CA", + "x-readme-id": "1.11" + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Expected registration number of the business in the company registry. For example, `00445790`. Pre-fills the registration-number field of the registry search in Business Verification (KYB) workflows; when `company_name` is also provided, the registry lookup combines both identifiers. Ignored for KYC workflows.", + "example": "00445790", + "x-readme-id": "1.12" + } + }, + "x-readme-id": "0.5" + }, + "portrait_image": { + "type": "string", + "format": "byte", + "description": "Base64-encoded portrait image of the end user's face (max 2MB; JPEG, PNG, WebP, or TIFF). Used as the reference image to match against the liveness capture when the workflow is a `Biometric Authentication` workflow with Face Match enabled, or any graph workflow where Face Match runs before ID Verification (OCR). Optional when the `vendor_data` user already has a stored face image: Didit reuses it automatically, resolved in priority order: approved liveness face, ePassport chip photo, ID document portrait, manually enrolled profile face (only faces from approved sessions or faces you enrolled yourself are reused). When provided, `portrait_image` always takes precedence over the stored face. If omitted and no stored face exists (or `vendor_data` is not sent), session creation fails with `400`. Ignored for other workflow types.", + "example": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", + "x-readme-id": "0.6" + }, + "sandbox_scenario": { + "type": "string", + "description": "Sandbox-only scenario slug. Only accepted when the API key belongs to a sandbox-mode application; rejected on live applications. When set, the create flow expands the scenario's bundled magic values onto the session's input fields (contact_details, expected_details) so the mocked providers reproduce the scenario's outcome through the real workflow pipeline. See the scenario catalog at GET /v1/sandbox/scenarios/ or [Sandbox & Test Data](/integration/sandbox-testing). You can also arm (or re-arm) a scenario after create via POST /v3/session/{sessionId}/sandbox/arm/, as long as the session is still `Not Started`. For non-doc lookup, use `lookup_partial_match`, `lookup_no_match`, or `lookup_provider_error` to test the configured attempt budget and fallback. For identity wallets, use `wallet_cancelled`, `wallet_timeout`, or `wallet_provider_error`; `approve` simulates a verified wallet. All catalog wallets can be tested in sandbox, including schemes unavailable on live applications. Simulated attempts are not charged.", + "example": "decline_document_expired" + }, + "sign": { + "type": "object", + "description": "E-signature input for this session. `documents` are the documents to sign, for a workflow whose E-signature step takes documents from the session (`signature_documents_source` `session` or `both`): up to 20 documents and 50 MB in total. `field_values` fills the fields whose `prefill` is `api.`, on workflow and session documents alike. Creating a session again for the same `workflow_id` and `vendor_data` returns the pending session; if that call sends `sign`, its documents, fields and values must be identical to the first ones, and anything different returns `400` with `{\"sign\": \"signature_document_changed\"}`. See [E-signature documents](/sessions-api/create-session#e-signature-documents).", + "properties": { + "documents": { + "type": "array", + "minItems": 1, + "maxItems": 20, + "items": { + "$ref": "#/components/schemas/SessionSignDocument" + } + }, + "field_values": { + "type": "object", + "description": "Values keyed by the `` of each `api.` pre-fill source: strings of 1 to 500 characters for `text` and `data` fields, booleans for a `checkbox`. `400` with `field_value_unknown` for a key no field uses, `field_value_missing` for a required field without its value, and `field_value_invalid` for a value that fails its field's validation.", + "additionalProperties": { + "oneOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 500 + }, + { + "type": "boolean" + } + ] + }, + "example": { + "credit_limit": "3,000 EUR" + } + } + } + }, + "user_state": { + "type": "string", + "maxLength": 16, + "description": "Where the person lives, when you know it: an ISO 3166-2 subdivision code (`US-IL`, `ES-M`) or a US state code or name (`IL`, `Illinois`). A state whose law requires explicit biometric consent (Illinois, Texas, Colorado) makes the verification flow ask for it before capturing a face, with trigger `declared_state`. An unrecognized value returns `400`. See [Biometric consent](/getting-started/biometric-consent).", + "example": "US-IL" + }, + "biometric_consent": { + "type": "object", + "description": "Explicit biometric consent you already collected from the person. It satisfies the requirement, so the verification flow does not ask again, and the decision records it as `status: \"given\"` with trigger `customer_collected`. An answer the person already gave in the flow is never replaced. See [Biometric consent](/getting-started/biometric-consent).", + "required": [ + "given_at", + "text_version" + ], + "properties": { + "given_at": { + "type": "string", + "format": "date-time", + "description": "When the person gave explicit biometric consent (ISO 8601). Cannot be in the future." + }, + "text_version": { + "type": "string", + "maxLength": 64, + "description": "Version id of the consent text the person accepted, in your own records." + } + } + } + }, + "example": { + "workflow_id": "11111111-2222-3333-4444-555555555555", + "vendor_data": "user-123", + "callback": "https://example.com/verification/callback", + "callback_method": "both", + "metadata": { + "user_type": "premium", + "account_id": "ABC123" + }, + "language": "en", + "contact_details": { + "email": "john.doe@example.com", + "send_notification_emails": true, + "email_lang": "en", + "phone": "+14155552671" + }, + "expected_details": { + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1990-05-15", + "id_country": "USA", + "expected_document_types": [ + "P", + "ID" + ] + } + } + }, + "examples": { + "Create KYC session": { + "summary": "Full happy-path payload for a User Verification (KYC) session", + "description": "Realistic create call that uses every commonly-set field: workflow selection, vendor linkage, callback routing, metadata, locale, contact prefill, and expected user details. Copy-paste this directly into the cURL sample above.", + "value": { + "workflow_id": "11111111-2222-3333-4444-555555555555", + "vendor_data": "user-123", + "callback": "https://example.com/verification/callback", + "callback_method": "both", + "metadata": { + "user_type": "premium", + "account_id": "ABC123" + }, + "language": "en", + "contact_details": { + "email": "john.doe@example.com", + "send_notification_emails": true, + "email_lang": "en", + "phone": "+14155552671" + }, + "expected_details": { + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1990-05-15", + "nationality": "USA", + "id_country": "USA", + "expected_document_types": [ + "P", + "ID" + ] + } + } + }, + "Create KYB session": { + "summary": "Happy-path payload for a Business Verification (KYB) session", + "description": "Same shape as the KYC call — the `workflow_id` is what makes the session KYB. `vendor_data` typically references the company in your system and `contact_details.email` targets the legal representative who will complete the flow. `expected_details` accepts the business fields (`company_name`, `registry_country`, `registration_number`), which pre-fill the company registry search; person-level fields and `contact_details.phone` are ignored for KYB workflows.", + "value": { + "workflow_id": "33333333-4444-5555-6666-777777777777", + "vendor_data": "company-acme-001", + "callback": "https://example.com/kyb/callback", + "callback_method": "both", + "metadata": { + "tier": "enterprise", + "company_id": "ACME-001" + }, + "expected_details": { + "company_name": "Tesco PLC", + "registry_country": "GB", + "registration_number": "00445790" + }, + "language": "en", + "contact_details": { + "email": "legal-rep@acme.example.com", + "send_notification_emails": true, + "email_lang": "en" + } + } + } + } + } + } + } + } + }, + "/v3/session/imports/": { + "post": { + "summary": "Create a verification import job", + "description": "Create an asynchronous import job for historical User Verification sessions, Business Verification sessions, transaction monitoring records, or workflow custom status rules. Set `source=provider` to migrate from another provider: Didit reads Sumsub, Onfido, Persona and ComplyCube with your read-only key, reads Veriff, iDenfy and MetaMap records by the IDs you upload in `file`, and imports an export file in Didit's format for the other providers. Record IDs are namespaced by provider (`sumsub:`) so a re-run skips everything already in Didit, and a record imported through a file with the provider's bare ID is recognised too, in both directions, so moving between a file import and a provider migration never imports a record twice. An application can run up to 4 import jobs at once; a job interrupted by a deploy or a worker restart continues from its last imported row by itself. Imports are disabled by default and must be enabled per organization by Didit.", + "operationId": "post_v3_session_imports_create", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "import_type": { + "type": "string", + "enum": [ + "user_verification", + "business_verification", + "status_rules", + "transactions" + ], + "default": "user_verification", + "description": "What to import: historical User Verification sessions, historical Business Verification sessions, workflow custom status rules, or historical transaction monitoring records." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Workflow UUID that imported sessions or status rules should be attached to. Required for user_verification, business_verification, and status_rules. Omit for transactions." + }, + "source_format": { + "type": "string", + "enum": [ + "csv", + "ndjson" + ], + "description": "Optional. Inferred from the uploaded file extension when omitted." + }, + "file": { + "type": "string", + "format": "binary", + "description": "Canonical CSV or NDJSON file. Use NDJSON for large imports. Maximum upload size 25 MB (use `source_file_url` for bigger files). Mutually exclusive with `source_file_url`." + }, + "source_file_url": { + "type": "string", + "format": "uri", + "description": "Alternative to file upload. Must be an HTTPS URL resolving to a public address (private/loopback networks are rejected). Mutually exclusive with `file`." + }, + "source": { + "type": "string", + "enum": [ + "file", + "provider" + ], + "description": "`provider` makes this a migration from another identity verification provider. Omit for a file import." + }, + "provider": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ], + "description": "Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`." + }, + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "options": { + "type": "object", + "properties": { + "region": { + "type": "string", + "description": "The provider region, one of its `regions` (Onfido: `eu`, `us`, `ca`)." + } + } + }, + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + }, + "description": "Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported." + }, + "dry_run": { + "type": "boolean", + "default": false, + "description": "Count what the migration would import (by status, already in Didit, same person, not finished) and write nothing. The job ends `READY`; start it with `POST /v3/session/imports/{importId}/start/`." + }, + "face_templates": { + "type": "boolean", + "default": false, + "description": "Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`." + }, + "lawful_basis_confirmed": { + "type": "boolean", + "default": false, + "description": "Confirms you have a lawful basis to share these selfies with Didit as processor." + } + } + } + }, + "application/json": { + "schema": { + "type": "object", + "required": [ + "source_file_url" + ], + "properties": { + "import_type": { + "type": "string", + "enum": [ + "user_verification", + "business_verification", + "status_rules", + "transactions" + ], + "default": "user_verification", + "description": "What to import: historical User Verification sessions, historical Business Verification sessions, workflow custom status rules, or historical transaction monitoring records." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Workflow UUID that imported sessions or status rules should be attached to. Required for user_verification, business_verification, and status_rules. Omit for transactions." + }, + "source_format": { + "type": "string", + "enum": [ + "csv", + "ndjson" + ], + "description": "Optional. Inferred from the URL's file extension when omitted." + }, + "source_file_url": { + "type": "string", + "format": "uri", + "description": "Alternative to file upload. Must be an HTTPS URL resolving to a public address (private/loopback networks are rejected). Mutually exclusive with `file`." + }, + "source": { + "type": "string", + "enum": [ + "file", + "provider" + ], + "description": "`provider` makes this a migration from another identity verification provider. Omit for a file import." + }, + "provider": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ], + "description": "Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`." + }, + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "options": { + "type": "object", + "properties": { + "region": { + "type": "string", + "description": "The provider region, one of its `regions` (Onfido: `eu`, `us`, `ca`)." + } + } + }, + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + }, + "description": "Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported." + }, + "dry_run": { + "type": "boolean", + "default": false, + "description": "Count what the migration would import (by status, already in Didit, same person, not finished) and write nothing. The job ends `READY`; start it with `POST /v3/session/imports/{importId}/start/`." + }, + "face_templates": { + "type": "boolean", + "default": false, + "description": "Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`." + }, + "lawful_basis_confirmed": { + "type": "boolean", + "default": false, + "description": "Confirms you have a lawful basis to share these selfies with Didit as processor." + } + } + }, + "examples": { + "URL-based import": { + "value": { + "import_type": "user_verification", + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "source_file_url": "https://files.example.com/exports/sessions.ndjson" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Import job accepted. Poll the returned `uuid` until `status` is `COMPLETED` or `FAILED`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + }, + "examples": { + "Job accepted": { + "value": { + "uuid": "3f9c1a2b-4d5e-4f60-8a7b-9c0d1e2f3a4b", + "status": "PENDING", + "import_type": "user_verification", + "source_format": "ndjson", + "source_filename": "sessions.ndjson", + "total_rows": 0, + "processed_rows": 0, + "checkpoint_row": 0, + "created_count": 0, + "updated_count": 0, + "skipped_count": 0, + "failed_count": 0, + "total_media_bytes": 0, + "last_error": null, + "created_at": "2026-05-17T08:42:11Z", + "updated_at": "2026-05-17T08:42:11Z", + "started_at": null, + "completed_at": null + } + } + } + } + } + }, + "400": { + "description": "Invalid workflow, file type, file size, source URL, or selector combination. A migration whose key the provider refuses answers `code` (`invalid_credentials`, `missing_permission`, `wrong_region`, `provider_unavailable`, `rate_limited`, `ids_required`, `record_not_found`), `missing_permissions` and `suggested_region`.", + "content": { + "application/json": { + "examples": { + "Invalid workflow": { + "value": { + "workflow_id": [ + "Invalid workflow_id." + ] + } + }, + "Workflow required": { + "value": { + "workflow_id": [ + "This field is required for this import type." + ] + } + }, + "Wrong file type": { + "value": { + "file": [ + "Only CSV and NDJSON files are supported." + ] + } + }, + "File too large": { + "value": { + "file": [ + "Import file exceeds the maximum allowed size." + ] + } + }, + "No source": { + "value": { + "file": [ + "Upload a file or provide source_file_url." + ] + } + }, + "Both sources": { + "value": { + "file": [ + "Use either file or source_file_url, not both." + ] + } + } + } + } + } + }, + "403": { + "description": "Imports not enabled, sandbox application, or missing API key (a wrong, revoked or expired key gets `401`). Verification imports are disabled by default and must be enabled per organization by Didit.", + "content": { + "application/json": { + "examples": { + "Imports not enabled": { + "value": { + "detail": "Verification imports are not enabled for this organization." + } + }, + "Sandbox": { + "value": { + "detail": "Verification imports are not enabled for sandbox applications." + } + }, + "Bad API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "The application already has 4 active import jobs (PENDING/PROCESSING/PAUSED), the most it can run at once. Wait for one to finish before starting another.", + "content": { + "application/json": { + "examples": { + "Active job": { + "value": { + "detail": "This application already has 4 active import jobs, the most it can run at once. Wait for one to finish before starting another." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.create(\n import_type=\"user_verification\",\n workflow_id=\"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n source_file_url=\"https://files.example.com/exports/sessions.ndjson\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.create({\n import_type: \"user_verification\",\n workflow_id: \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n source_file_url: \"https://files.example.com/exports/sessions.ndjson\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Create(ctx, didit.SessionsImportsCreateParams{\n\tImportType: didit.Some(didit.SessionsImportsCreateParamsImportType(\"user_verification\")),\n\tWorkflowID: didit.String(\"9f9b1234-aaaa-bbbb-cccc-1234567890ab\"),\n\tSourceFileURL: didit.String(\"https://files.example.com/exports/sessions.ndjson\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsImportsCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().create(SessionsImportsCreateParams.builder()\n .importType(SessionsImportsCreateParams.ImportType.of(\"user_verification\"))\n .workflowId(\"9f9b1234-aaaa-bbbb-cccc-1234567890ab\")\n .sourceFileUrl(\"https://files.example.com/exports/sessions.ndjson\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->create([\n 'import_type' => 'user_verification',\n 'workflow_id' => '9f9b1234-aaaa-bbbb-cccc-1234567890ab',\n 'source_file_url' => 'https://files.example.com/exports/sessions.ndjson',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.create(\n import_type: \"user_verification\",\n workflow_id: \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n source_file_url: \"https://files.example.com/exports/sessions.ndjson\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.CreateAsync(new SessionsImportsCreateParams\n{\n ImportType = new SessionsImportsCreateParamsImportType(\"user_verification\"),\n WorkflowId = \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n SourceFileUrl = \"https://files.example.com/exports/sessions.ndjson\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/template/": { + "get": { + "summary": "Download verification import template", + "description": "Download the canonical CSV header for verification imports. The column set is determined by `import_type`. The first column of session templates is the idempotency key `external_id`.", + "operationId": "get_v3_session_imports_template", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "retrieve_template", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "import_type", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "user_verification", + "business_verification", + "status_rules", + "transactions" + ], + "default": "user_verification" + } + } + ], + "responses": { + "200": { + "description": "CSV template file.", + "content": { + "text/csv": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot download import templates. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "summary": "Missing, invalid, or revoked API key", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.sessions.imports.retrieve_template()\nwith open(\"download.csv\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.sessions.imports.retrieveTemplate();\nawait writeFile(\"download.csv\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Sessions.Imports.RetrieveTemplate(ctx, didit.SessionsImportsRetrieveTemplateParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.csv\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().imports().retrieveTemplate()\n .writeTo(Path.of(\"download.csv\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->retrieveTemplate();\n$content->saveTo('download.csv');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.sessions.imports.retrieve_template\nFile.binwrite(\"download.csv\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Sessions.Imports.RetrieveTemplateAsync();\nawait content.SaveToAsync(\"download.csv\");" + } + ] + } + }, + "/v3/session/imports/{importId}/": { + "get": { + "summary": "Retrieve a verification import job", + "description": "Retrieve import progress counters and terminal status for one job. Poll until `status` is `COMPLETED` or `FAILED`; `failed_count` > 0 means some rows were rejected — list them via the errors endpoint.", + "operationId": "get_v3_session_imports_retrieve", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Import job status.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "404": { + "description": "Import job not found for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read import jobs for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "summary": "Missing, invalid, or revoked API key", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/{importId}/errors/": { + "get": { + "summary": "List verification import row errors", + "description": "List row-level errors for a verification import job, ordered by `row_number`. Use this endpoint to produce a corrected retry file containing only failed rows.", + "operationId": "get_v3_session_imports_errors", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "list_errors", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size. Defaults to 50 when omitted." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0 + }, + "description": "Zero-based offset of the first record to return." + } + ], + "responses": { + "200": { + "description": "Paginated row errors.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of error rows — exact up to 100, capped at 100 beyond that. Rely on `next` being `null` to detect the last page." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionImportError" + } + } + } + }, + "examples": { + "Row errors": { + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d", + "row_number": 17, + "external_id": "ext-000017", + "error": "status: invalid value 'unknown_status'", + "raw_row": { + "external_id": "ext-000017", + "status": "unknown_status", + "first_name": "Jane" + }, + "created_at": "2026-05-17T08:44:02Z" + } + ] + } + } + } + } + } + }, + "404": { + "description": "Import job not found for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read import jobs for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "summary": "Missing, invalid, or revoked API key", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.sessions.imports.list_errors(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.sessions.imports.listErrors(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Sessions.Imports.ListErrorsAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsImportsListErrorsParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.sessions().imports().listErrors(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->listErrors('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.imports.list_errors(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Sessions.Imports.ListErrorsAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/session/{sessionId}/decision/": { + "get": { + "summary": "Retrieve the full decision for a verification session", + "description": "Retrieve the complete decision report for a verification session — the rolled-up session `status` plus one report per verification feature the workflow executed. This is the canonical read endpoint for both User Verification (KYC) and Business Verification (KYB) sessions: the same path serves both, and the top-level `session_kind` discriminator tells you which shape you received (`\"user\"` or `\"business\"`).\n\n**Every feature report is a plural array.** V3 workflows are graphs that can run the same feature more than once, so each block (`id_verifications`, `nfc_verifications`, `liveness_checks`, `face_matches`, `poa_verifications`, `document_ai_documents`, `phone_verifications`, `email_verifications`, `aml_screenings`, `ip_analyses`, `database_validations`, `questionnaire_responses`) is a JSON array whose items each carry a `node_id` identifying the workflow graph node that produced them. Read NFC as `response.nfc_verifications[0]` — never as a singular `nfc` field. A block is `null` until the workflow has run that feature at least once; features the workflow does not include never appear with data. E-signature results are in `signatures`, which is present only when the workflow has an E-signature step.\n\n**When to call.** The recommended pattern is event-driven: wait for the `status.updated` webhook that fires when the session reaches a decision (`Approved`, `Declined`, `In Review`), then call this endpoint once to fetch the full report. Polling also works — the endpoint can be called at any point in the session lifecycle and returns the data produced so far — but webhook-then-fetch is cheaper and faster. Media URLs in the response (document images, videos, PDFs) are short-lived presigned links: fetch them promptly or re-request the decision to get fresh ones, and do not persist them as long-term references.\n\n**Statuses.** The top-level `status` is the session lifecycle status (`Not Started`, `In Progress`, `Awaiting User`, `In Review`, `Approved`, `Declined`, `Resubmitted`, `Expired`, `Kyc Expired`, `Abandoned`). Each feature item additionally carries its own feature-level `status` (`Not Finished`, `Approved`, `Declined`, `In Review`, ...). A session can be `Approved` overall while an individual feature is `In Review`, depending on the workflow's decision rules.\n\n**Authentication and visibility.** Authenticate with the application API key in the `x-api-key` header (a console user Bearer token with the `read:sessions` permission also works). Only sessions owned by the authenticated application are visible. If your workflow restricts response attributes, non-allowed fields inside feature items are returned as `null` while `status`, `warnings`, and `node_id` are always preserved.\n\n**Rate limits.** GET requests are limited to 600 per minute per API key. Exceeding the limit returns `429` with `Retry-After` and `X-RateLimit-*` headers.", + "operationId": "get_v3_session_decision", + "x-didit-sdk": { + "resource": "sessions", + "method": "retrieve_decision", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "200": { + "description": "Full decision report. `session_kind` discriminates the two shapes: `user` sessions carry the KYC feature arrays shown below; `business` sessions instead carry `registry_checks`, `document_verifications` and `key_people_checks` alongside the shared arrays. Feature arrays are `null` until the workflow has run that feature; media URLs are short-lived presigned links.", + "content": { + "application/json": { + "examples": { + "Example": { + "summary": "Full Example", + "value": { + "session_id": "11111111-2222-3333-4444-555555555555", + "session_kind": "user", + "session_number": 43762, + "session_url": "https://verify.didit.me/session/11111111-2222-3333-4444-555555555555", + "status": "In Review", + "environment": "live", + "workflow_id": "11111111-2222-3333-4444-555555555555", + "features": [ + "ID_VERIFICATION", + "NFC", + "LIVENESS", + "FACE_MATCH", + "POA", + "PHONE", + "DATABASE_VALIDATION", + "AML", + "IP_ANALYSIS" + ], + "vendor_data": "11111111-1111-1111-1111-111111111111", + "metadata": { + "user_type": "premium", + "account_id": "ABC123" + }, + "expected_details": { + "first_name": "Carmen", + "last_name": "Española Española" + }, + "contact_details": { + "email": "carmen@example.com", + "email_lang": "es", + "send_notification_emails": false, + "phone": null + }, + "callback": "https://verify.didit.me/", + "id_verifications": [ + { + "node_id": "feature_ocr_1", + "status": "Approved", + "document_type": "Identity Card", + "document_subtype": "ID_CARD_GENERIC", + "document_number": "CAA000000", + "personal_number": "99999999R", + "portrait_image": "https:///portrait.jpg", + "front_image": "https:///front.jpg", + "front_video": "https:///front.mp4", + "back_image": "https:///back.jpg", + "back_video": "https:///back.mp4", + "full_front_image": "https:///full_front.jpg", + "full_back_image": "https:///full_back.jpg", + "front_image_camera_front": "https:///front_camera_front.jpg", + "back_image_camera_front": "https:///back_camera_front.jpg", + "date_of_birth": "1980-01-01", + "age": 45, + "expiration_date": "2031-06-02", + "date_of_issue": "2021-06-02", + "issuing_state": "ESP", + "issuing_state_name": "Spain", + "first_name": "Carmen", + "last_name": "Española Española", + "full_name": "Carmen Española Española", + "gender": "F", + "address": "Avda de Madrid 34, Madrid, Madrid", + "formatted_address": "Avda de Madrid 34, Madrid, Madrid 28822, Spain", + "place_of_birth": "Madrid", + "marital_status": "Single", + "nationality": "ESP", + "extra_fields": { + "dl_categories": [], + "blood_group": null + }, + "mrz": { + "surname": "ESPANOLA ESPANOLA", + "name": "CARMEN", + "country": "ESP", + "nationality": "ESP", + "birth_date": "800101", + "expiry_date": "310602", + "sex": "F", + "document_type": "ID", + "document_number": "CAA000000", + "optional_data": "99999999R", + "optional_data_2": "", + "birth_date_hash": "4", + "expiry_date_hash": "8", + "document_number_hash": "4", + "final_hash": "1", + "personal_number": "99999999R", + "warnings": [], + "errors": [], + "mrz_type": "TD1", + "mrz_string": "IDESPCAA000000499999999R<<<<<<\n8001014F3106028ESP<<<<<<<<<<<1\nESPANOLA/match_face.jpg", + "status": "Approved", + "is_blocklisted": false, + "is_allowlisted": false, + "api_service": null, + "source": "session" + } + ], + "front_image_quality_score": { + "focus_score": 85.3, + "brightness_score": 92.1, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 72.4, + "overall_score": 88.7 + }, + "back_image_quality_score": { + "focus_score": 79.1, + "brightness_score": 90.5, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 72.4, + "overall_score": 85.2 + }, + "extra_files": [ + "https:///extra_id_verification.jpg" + ], + "matches": [ + { + "session_id": "7d4f1f60-9c2e-4b8a-8f3d-2a1b5c6d7e8f", + "session_number": 1234, + "vendor_data": "user_ref_001", + "verification_date": "2024-06-15T10:30:00Z", + "user_details": { + "name": "John Smith", + "document_type": "ID", + "document_number": "CLM148418" + }, + "status": "Approved", + "is_blocklisted": false, + "api_service": null, + "front_image_url": "https:///document_front.jpg" + }, + { + "session_id": "b2c8e4d1-6a3f-4e7b-95c0-1d8f2a6b4c9e", + "session_number": 1235, + "vendor_data": null, + "verification_date": "2024-05-20T14:45:00Z", + "user_details": { + "name": "John Smith", + "document_type": "ID", + "document_number": "CLM148418" + }, + "status": "Declined", + "is_blocklisted": true, + "api_service": "ID_VERIFICATION", + "front_image_url": "https:///document_front_2.jpg" + } + ], + "warnings": [ + { + "feature": "ID_VERIFICATION", + "risk": "QR_NOT_DETECTED", + "additional_data": null, + "log_type": "information", + "short_description": "QR not detected", + "long_description": "The system couldn't find or read the QR code on the document, which is necessary for document verification. This could be due to poor image quality or an unsupported document type.", + "node_id": "feature_ocr_1" + } + ], + "verification_method": "document", + "assurance": "documentary", + "wallet_provider": null, + "fallback_from": null, + "id_lookup": null, + "wallet_verification": null, + "document_liveness": { + "portrait_replace": { + "score": 96.46, + "front": 96.46, + "back": 98.2, + "decline_threshold": 35, + "review_threshold": 70, + "bucket": "approve" + }, + "printed_copy": { + "score": 91.2, + "front": 91.2, + "back": 93.7, + "decline_threshold": 35, + "review_threshold": 77, + "bucket": "approve" + }, + "screen_replay": { + "score": 99.9, + "front": 99.9, + "back": 99.5, + "decline_threshold": 30, + "review_threshold": 70, + "bucket": "approve" + } + } + }, + { + "node_id": "feature_ocr_2", + "status": "Approved", + "issuing_state": "ZAF", + "issuing_state_name": "South Africa", + "first_name": "Thabo", + "last_name": "Mokoena", + "full_name": "Thabo Mokoena", + "date_of_birth": "1991-06-15", + "personal_number": "9106155043088", + "verification_method": "id_lookup", + "assurance": "data_match", + "wallet_provider": null, + "fallback_from": null, + "id_lookup": { + "source": "Department of Home Affairs", + "checked_at": "2026-09-04T09:12:44Z", + "attempts": 1, + "max_attempts": 2, + "outcome": "match", + "comparison": [ + { + "field": "identification_number", + "label": "ID number", + "provided": "9106155043088", + "record": "9106155043088", + "result": "match" + }, + { + "field": "full_name", + "label": "Full name", + "provided": "Thabo Mokoena", + "record": "Thabo Mokoena", + "result": "match" + }, + { + "field": "date_of_birth", + "label": "Date of birth", + "provided": "1991-06-15", + "record": "1991-06-15", + "result": "match" + } + ], + "registry_portrait": "https:///registry_portrait.jpg", + "face_match_score": 96.4, + "source_errors": [] + }, + "wallet_verification": null, + "warnings": [] + }, + { + "node_id": "feature_ocr_3", + "status": "Approved", + "issuing_state": "DNK", + "issuing_state_name": "Denmark", + "first_name": "Freja", + "last_name": "Nielsen", + "full_name": "Freja Nielsen", + "date_of_birth": "1988-03-02", + "verification_method": "wallet", + "assurance": "cryptographic", + "wallet_provider": "mitid", + "fallback_from": null, + "id_lookup": null, + "wallet_verification": { + "provider": "mitid", + "provider_name": "MitID", + "issuing_authority": "Danish Agency for Digital Government", + "issuing_country": "DNK", + "credential_type": "person_identification", + "level_of_assurance": "substantial", + "verified_at": "2026-09-04T09:12:44Z", + "signature_valid": true, + "attributes": { + "full_name": "Freja Nielsen", + "date_of_birth": "1988-03-02", + "cpr_alias": "b0f1c2d3-e4f5-4678-9abc-def012345678" + }, + "portrait": null, + "face_match_score": null + }, + "warnings": [] + } + ], + "nfc_verifications": [ + { + "node_id": "feature_nfc_1", + "status": "In Review", + "is_nfc_skipped": false, + "skip_reason": null, + "chip_protocol": "icao", + "jpki": null, + "portrait_image": "https:///portrait.jpg", + "signature_image": "https:///signature.jpg", + "chip_data": { + "document_type": "ID", + "issuing_country": "ESP", + "document_number": "123456789", + "expiration_date": "2030-01-01", + "first_name": "John", + "last_name": "Smith", + "birth_date": "1990-05-15", + "gender": "M", + "nationality": "ESP", + "address": "CALLE MAYOR 123 4B, MADRID, MADRID", + "place_of_birth": "MADRID, MADRID" + }, + "authenticity": { + "sod_integrity": true, + "dg_integrity": true + }, + "certificate_summary": { + "issuer": "Common Name: CSCA SPAIN, Serial Number: 3, Organization: DIRECCION GENERAL DE LA POLICIA, Country: ES", + "subject": "Common Name: DS n-eID SPAIN 2, Organizational Unit: PASSPORT, Organization: DIRECCION GENERAL DE LA POLICIA, Country: ES", + "serial_number": "118120836164494130086420187336801405660", + "not_valid_after": "2031-02-18 10:21:11", + "not_valid_before": "2020-11-18 10:21:11" + }, + "warnings": [ + { + "feature": "NFC", + "risk": "DATA_INCONSISTENT", + "additional_data": null, + "log_type": "warning", + "short_description": "OCR and NFC mrz code extracted are not the same", + "long_description": "The Optical Character Recognition (OCR) data and the NFC chip data don't match, indicating potential document tampering or data inconsistency.", + "node_id": "feature_nfc_1" + } + ] + } + ], + "liveness_checks": [ + { + "node_id": "feature_liveness_1", + "status": "Approved", + "method": "ACTIVE_3D", + "score": 89.92, + "reference_image": "https:///reference.jpg", + "video_url": "https:///video.mp4", + "age_estimation": 24.3, + "matches": [ + { + "session_id": "7d4f1f60-9c2e-4b8a-8f3d-2a1b5c6d7e8f", + "session_number": 1234, + "vendor_data": "user_ref_001", + "verification_date": "2024-06-15T10:30:00Z", + "user_details": { + "full_name": "John Smith", + "document_type": "ID", + "document_number": "CLM148418" + }, + "status": "Approved", + "is_blocklisted": false, + "is_allowlisted": false, + "api_service": null, + "source": "session", + "match_image_url": "https:///face_match.jpg", + "similarity_percentage": 98.5 + }, + { + "session_id": "3e9a7c21-5b48-4f0e-9c6a-8d2f4b1e0a35", + "session_number": 1100, + "vendor_data": "customer_xyz", + "verification_date": "2024-04-10T09:15:00Z", + "user_details": { + "full_name": "John Smith", + "document_type": "P", + "document_number": "DZ8375806" + }, + "status": "Approved", + "is_blocklisted": false, + "is_allowlisted": false, + "api_service": "PASSIVE_LIVENESS", + "source": "session", + "match_image_url": "https:///face_match_2.jpg", + "similarity_percentage": 95.2 + } + ], + "warnings": [ + { + "feature": "LIVENESS", + "risk": "LOW_LIVENESS_SCORE", + "additional_data": null, + "log_type": "information", + "short_description": "Low liveness score", + "long_description": "The liveness check resulted in a low score, indicating potential use of non-live facial representations or poor-quality biometric data.", + "node_id": "feature_liveness_1" + } + ], + "face_quality": null, + "face_luminance": null + } + ], + "face_matches": [ + { + "node_id": "feature_face_match_1", + "status": "In Review", + "score": 65.43, + "source_image_session_id": null, + "source_image": "https:///source-image.jpg", + "target_image": "https:///target-image.jpg", + "warnings": [ + { + "feature": "FACEMATCH", + "risk": "LOW_FACE_MATCH_SIMILARITY", + "additional_data": null, + "log_type": "warning", + "short_description": "Low face match similarity", + "long_description": "The facial features of the provided image don't closely match the reference image, suggesting a potential identity mismatch.", + "node_id": "feature_face_match_1" + } + ] + } + ], + "phone_verifications": [ + { + "node_id": "feature_phone_1", + "status": "Approved", + "phone_number_prefix": "+34", + "phone_number": "600600600", + "full_number": "+34600600600", + "country_code": "ES", + "country_name": "Spain", + "carrier": { + "name": "Orange", + "type": "mobile" + }, + "is_disposable": false, + "is_virtual": false, + "verification_method": "sms", + "verification_attempts": 1, + "verified_at": "2024-07-28T06:47:35.654321Z", + "lifecycle": [ + { + "type": "PHONE_VERIFICATION_MESSAGE_SENT", + "timestamp": "2025-08-24T09:12:39.580554+00:00", + "details": { + "status": "Success", + "reason": null, + "channel": "sms", + "actual_channel": "sms" + }, + "fee": 0.1 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2025-08-24T09:12:39.662157+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "PHONE_VERIFICATION_APPROVED", + "timestamp": "2025-08-24T09:12:39.684292+00:00", + "details": null, + "fee": 0 + } + ], + "warnings": [], + "matches": [ + { + "session_id": null, + "session_number": null, + "vendor_data": null, + "verification_date": null, + "phone_number": "+34600000000", + "status": null, + "is_blocklisted": true, + "api_service": null, + "source": "list_entry" + } + ] + } + ], + "email_verifications": [ + { + "node_id": "feature_email_1", + "status": "Approved", + "email": "test@example.com", + "is_breached": true, + "breaches": [ + { + "name": "TAPAirPortugal", + "domain": "flytap.com", + "logo_path": "https://logos.example.com/TAPAirPortugal.png", + "breach_date": "2022-08-25", + "description": "In August 2022, the Portuguese airline TAP Air Portugal was the target of a ransomware attack perpetrated by the Ragnar Locker gang who later leaked the compromised data via a public dark web site. Over 5M unique email addresses were exposed alongside other personal data including names, genders, DoBs, phone numbers and physical addresses.", + "is_verified": true, + "data_classes": [ + "dates_of_birth", + "email_addresses", + "genders", + "names", + "nationalities", + "phone_numbers", + "physical_addresses", + "salutations", + "spoken_languages" + ], + "breach_emails_count": 6083479 + } + ], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 1, + "verified_at": "2025-09-15T17:36:19.963451Z", + "warnings": [ + { + "feature": "EMAIL", + "risk": "BREACHED_EMAIL_DETECTED", + "additional_data": null, + "log_type": "information", + "short_description": "Breached email detected", + "long_description": "This email address was found in one or more known data breaches.", + "node_id": "feature_email_1" + } + ], + "lifecycle": [ + { + "type": "EMAIL_VERIFICATION_MESSAGE_SENT", + "timestamp": "2025-09-15T17:36:19.792684+00:00", + "details": { + "status": "Success", + "reason": null + }, + "fee": 0.02 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2025-09-15T17:36:19.963427+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "EMAIL_VERIFICATION_DECLINED", + "timestamp": "2025-09-15T17:36:20.081471+00:00", + "details": { + "reason": "BREACHED_EMAIL_DETECTED" + }, + "fee": 0 + } + ], + "matches": [ + { + "session_id": null, + "session_number": null, + "vendor_data": null, + "verification_date": null, + "email": "test@example.com", + "status": null, + "is_blocklisted": true, + "api_service": null, + "source": "list_entry" + } + ] + } + ], + "poa_verifications": [ + { + "node_id": "feature_poa_1", + "status": "Declined", + "document_file": "https:///poa_document.pdf", + "issuing_state": "AR", + "document_type": "UTILITY_BILL", + "document_subtype": null, + "document_language": "es", + "document_metadata": { + "file_size": 1000, + "content_type": "application/pdf", + "creation_date": "2021-01-01", + "modified_date": "2021-01-01", + "overlay_manipulation": null, + "creator": null, + "producer": "PDF Generator", + "software": null, + "encryption": null, + "is_signed": false, + "is_tampered": false, + "signature_info": null, + "exif_original_date": null, + "exif_digitized_date": null, + "processed_by_known_editor": false, + "has_different_creation_mod_date": true + }, + "issuer": "Aguas del Norte", + "issue_date": "2021-02-01", + "expiration_date": null, + "poa_address": "AVDA. MONSEÑOR TAVELLA N° 3396, SALTA", + "poa_formatted_address": "Av. Monseñor Tavella 3396, A4400 Salta, Argentina", + "poa_parsed_address": { + "address_type": "Avenida", + "street_1": "Avenida Monseñor Tavella 3396", + "street_2": null, + "city": "Salta", + "region": "Salta", + "country": "AR", + "postal_code": "A4400", + "document_location": { + "latitude": -24.8208664, + "longitude": -65.4131 + } + }, + "name_on_document": "Fernando De Lima", + "name_match_score_expected_details": null, + "name_match_score_id_verification": 38.5, + "expected_details_address": null, + "expected_details_formatted_address": null, + "expected_details_parsed_address": {}, + "extra_fields": { + "bank_account_number": "1234567890", + "bank_iban": null, + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": "+5491112345678", + "additional_names": [] + }, + "extra_files": [ + "https:///extra_poa.pdf" + ], + "detected_codes": [ + { + "type": "QR_CODE", + "page_number": 1, + "position": [ + [ + 820, + 1120 + ], + [ + 1002, + 1120 + ], + [ + 1002, + 1302 + ], + [ + 820, + 1302 + ] + ], + "x": 820, + "y": 1120, + "width": 182, + "height": 182, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "aHR0cHM6Ly9hZ3Vhc2RlbG5vcnRlLmNvbS9mYWN0dXJhLzk5ODgxMQ==", + "parsed_payload": "https://aguasdelnorte.com/factura/998811" + }, + { + "type": "CODE_128", + "page_number": 1, + "position": [ + [ + 112, + 1480 + ], + [ + 560, + 1480 + ], + [ + 560, + 1540 + ], + [ + 112, + 1540 + ] + ], + "x": 112, + "y": 1480, + "width": 448, + "height": 60, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "MDA5OTg4MTEwMDA0Mg==", + "parsed_payload": "0099881100042" + } + ], + "warnings": [] + } + ], + "document_ai_documents": [ + { + "status": "Approved", + "node_id": "feature_document_ai_1", + "items": [ + { + "uuid": "c1a2b3c4-d5e6-7f80-9a1b-2c3d4e5f6071", + "node_id": "feature_document_ai_1", + "document_key": "proof_of_funds", + "original_filename": "bank_statement.pdf", + "file_size": 184320, + "file_url": "https:///document_ai/proof_of_funds.pdf", + "status": "Approved", + "extracted_data": { + "account_holder": "John Doe", + "balance": 15230.55, + "currency": "EUR", + "statement_date": "2026-05-31" + }, + "detected_codes": [ + { + "type": "QR_CODE", + "page_number": 2, + "position": [ + [ + 124, + 86 + ], + [ + 306, + 86 + ], + [ + 306, + 268 + ], + [ + 124, + 268 + ] + ], + "x": 124, + "y": 86, + "width": 182, + "height": 182, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "SU5WLTIwMjYtMDA0Mg==", + "parsed_payload": "INV-2026-0042" + } + ], + "fields": [ + { + "key": "account_holder", + "name": "Account holder", + "instruction": "Extract the full name of the account holder.", + "type": "text", + "required": true + }, + { + "key": "balance", + "name": "Closing balance", + "instruction": "Extract the closing balance amount.", + "type": "number", + "required": true + } + ], + "document_metadata": { + "is_tampered": false + }, + "cross_check_result": null, + "created_at": "2026-06-01T12:34:56Z" + } + ], + "warnings": [] + } + ], + "questionnaire_responses": [ + { + "node_id": "feature_questionnaire_1", + "questionnaire_id": "9f7485e0-b00b-4d56-9d3e-7859b68d1213", + "title": "Source of Funds", + "description": "Reply to this questionnaire to help us understand the source of funds.", + "languages": [ + "en" + ], + "default_language": "en", + "is_active": true, + "is_simple_questionnaire": true, + "questionnaire_group_id": "2b6a9d40-13a8-4b1e-9a6c-3f1d2e4b5a60", + "version": 1, + "published_at": "2025-11-02T09:14:55.000000Z", + "sections": [ + { + "title": null, + "description": null, + "items": [ + { + "uuid": "618636d9-7d5c-41ce-b8d0-1ab2a4be20db", + "value": "question_1", + "element_type": "DROPDOWN", + "is_required": true, + "title": "What is your primary source of funds?", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Salary", + "value": "salary" + }, + { + "label": "Business Income", + "value": "business_income" + }, + { + "label": "Savings", + "value": "savings" + }, + { + "label": "Investments", + "value": "investments" + }, + { + "label": "Sale of Asset", + "value": "sale_of_asset" + }, + { + "label": "Inheritance/Gift", + "value": "inheritance_gift" + }, + { + "label": "Pension", + "value": "pension" + }, + { + "label": "Crypto Proceeds", + "value": "crypto_proceeds" + }, + { + "label": "Other", + "value": "other", + "requires_text_input": true + } + ], + "max_files": 1, + "answer": { + "value": "salary" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "2d16b527-72be-42e0-8e96-b164b818386c", + "value": "question_2", + "element_type": "LONG_TEXT", + "is_required": true, + "title": "Provide details of the primary source.", + "description": null, + "placeholder": null, + "choices": null, + "max_files": 1, + "answer": { + "value": "Test answer" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "a9239b7c-fbfb-4ea7-b7e4-c835cd4016e0", + "value": "question_3", + "element_type": "DROPDOWN", + "is_required": true, + "title": "What is your annual income range (pre-tax)?", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Less than $25,000", + "value": "<25k" + }, + { + "label": "$25,000 - $50,000", + "value": "25k-50k" + }, + { + "label": "$50,001 - $100,000", + "value": "50k-100k" + }, + { + "label": "$100,001 - $250,000", + "value": "100k-250k" + }, + { + "label": "More than $250,000", + "value": ">250k" + } + ], + "max_files": 1, + "answer": { + "value": "<25k" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "93dede62-6d0c-4a7f-934d-42175eb94011", + "value": "question_4", + "element_type": "DROPDOWN", + "is_required": true, + "title": "What is your approximate net worth (range)?", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Less than $50,000", + "value": "<50k" + }, + { + "label": "$50,000 - $250,000", + "value": "50k-250k" + }, + { + "label": "$250,001 - $1,000,000", + "value": "250k-1m" + }, + { + "label": "$1,000,001 - $5,000,000", + "value": "1m-5m" + }, + { + "label": "More than $5,000,000", + "value": ">5m" + } + ], + "max_files": 1, + "answer": { + "value": "<50k" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "2af60d6f-6961-4692-9ee7-24b3033610a6", + "value": "question_5", + "element_type": "LONG_TEXT", + "is_required": true, + "title": "For your initial deposit/funding, indicate the exact source and amount.", + "description": null, + "placeholder": null, + "choices": null, + "max_files": 1, + "answer": { + "value": "Test answer" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "0ad9d1aa-736f-46cf-947b-9e1241b53e40", + "value": "question_6", + "element_type": "LONG_TEXT", + "is_required": true, + "title": "Breakdown of sources by percentage.", + "description": null, + "placeholder": null, + "choices": null, + "max_files": 1, + "answer": { + "value": "Test answer" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "1bf8a2a3-14ee-48a8-bb3d-175481aae4aa", + "value": "question_7", + "element_type": "SINGLE_CHOICE", + "is_required": true, + "title": "Will any funds originate from third parties? If yes, please provide details.", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Yes", + "value": "yes", + "requires_text_input": true + }, + { + "label": "No", + "value": "no" + } + ], + "max_files": 1, + "answer": { + "text": "Additional details", + "value": "yes" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "e18d4dd3-b7bf-4883-ba18-d6046adbe313", + "value": "question_8", + "element_type": "SINGLE_CHOICE", + "is_required": true, + "title": "Do you hold or plan to use virtual assets (e.g., crypto) to fund this account? If yes, please provide details.", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Yes", + "value": "yes", + "requires_text_input": true + }, + { + "label": "No", + "value": "no" + } + ], + "max_files": 1, + "answer": { + "text": "Additional details", + "value": "yes" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "a90a073d-e098-404c-bbce-5130d8b12a13", + "value": "question_9", + "element_type": "FILE_UPLOAD", + "is_required": true, + "title": "Upload a proof of funds document", + "description": "You can upload your three (3) most recent bank statements, three (3) most recent payslips, your most recent tax return, proof of company ownership, trading exchange statement, sale of property, or proof of inheritance.", + "placeholder": null, + "choices": null, + "max_files": 3, + "answer": { + "files": [ + "/media/ocr/", + "/media/ocr/" + ] + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "9503b486-7cb8-4c3b-a97d-4b64926a5a47", + "value": "question_10", + "element_type": "LONG_TEXT", + "is_required": false, + "title": "Would you like to add any details to help us expedite our review of the documents you submitted?", + "description": null, + "placeholder": null, + "choices": null, + "max_files": 1, + "answer": { + "value": "Test answer" + }, + "required_if": null, + "repeatable_config": null + } + ] + } + ], + "status": "Approved" + } + ], + "aml_screenings": [ + { + "node_id": "feature_aml_1", + "status": "In Review", + "total_hits": 1, + "entity_type": "person", + "hits": [ + { + "id": "hit_7c1e9a40b2d35f86e0a4c912", + "url": "https://news.example.org/articles/sample", + "match": false, + "score": 0.73, + "target": null, + "caption": "Javier Ejemplo Modelo", + "datasets": [ + "PEP" + ], + "features": null, + "rca_name": "", + "last_seen": "2025-06-13T00:00:00", + "risk_view": { + "crimes": { + "score": 0, + "weightage": 20, + "risk_level": "Low", + "risk_scores": {} + }, + "countries": { + "score": 0, + "weightage": 30, + "risk_level": "Low", + "risk_scores": {} + }, + "categories": { + "score": 100, + "weightage": 50, + "risk_level": "High", + "risk_scores": { + "PEP": 100 + } + }, + "custom_list": {} + }, + "first_seen": "2025-01-18T00:00:00", + "properties": { + "name": [ + "Javier Ejemplo Modelo" + ], + "alias": [ + "Javier Ejemplo Modelo", + "David Azagra" + ], + "notes": [ + "Spanish orchestra conductor" + ], + "title": null, + "gender": [ + "male" + ], + "height": null, + "topics": null, + "weight": null, + "address": null, + "country": null, + "website": null, + "eyeColor": null, + "keywords": null, + "lastName": [ + "Ejemplo", + "Modelo" + ], + "position": null, + "religion": null, + "birthDate": [ + "1974" + ], + "education": [ + "Tokyo Arts and Space (2012-2012)", + "Saint Petersburg Conservatory (-2010)", + "Comillas Pontifical University" + ], + "ethnicity": null, + "firstName": [ + "David" + ], + "hairColor": null, + "weakAlias": null, + "birthPlace": [ + "Madrid" + ], + "modifiedAt": null, + "wikidataId": null, + "citizenship": null, + "nationality": null + }, + "match_score": 98, + "risk_score": 73, + "review_status": "Unreviewed", + "score_breakdown": { + "name_score": 95, + "name_weight": 60, + "name_weight_normalized": 70.59, + "name_contribution": 67.06, + "dob_score": 100, + "dob_weight": 25, + "dob_weight_normalized": 29.41, + "dob_contribution": 29.41, + "country_score": 0, + "country_weight": 15, + "country_weight_normalized": 0, + "country_contribution": 0, + "document_number_match_type": "NEUTRAL", + "document_number_effect": "No document number provided for screening", + "total_score": 98 + }, + "pep_matches": [ + { + "aliases": [ + "Javier Ejemplo Modelo", + "David Azagra" + ], + "education": [ + "Tokyo Arts and Space (2012-2012)", + "Saint Petersburg Conservatory (-2010)", + "Comillas Pontifical University" + ], + "list_name": "Wikidata", + "publisher": "Wikidata", + "source_url": "https://news.example.org/articles/sample", + "description": "Wikidata is the structured data project of the Wikipedia community, providing fact-based information edited by humans and machines", + "matched_name": "Javier Ejemplo Modelo", + "pep_position": "", + "date_of_birth": "1974", + "other_sources": [], + "place_of_birth": "Madrid" + } + ], + "linked_entities": [ + { + "name": [ + "Antonio Ejemplo" + ], + "active": [], + "status": [], + "details": [], + "relation": [] + } + ], + "warning_matches": [], + "sanction_matches": [], + "adverse_media_details": { + "sentiment": "Moderately Negative", + "entity_type": "person", + "sentiment_score": -2, + "adverse_keywords": { + "fraud": 3, + "juicio": 1, + "delitos": 2, + "imputado": 1, + "impunidad": 1, + "corruption": 2, + "favoritism": 2, + "privilegio": 1, + "corrupción": 1, + "persecution": 2, + "desconfianza": 1, + "desprestigio": 1, + "fraude de ley": 2, + "manipulación": 1, + "investigación": 3, + "prevaricación": 8, + "trato de favor": 1, + "irregularidades": 2, + "crisis de confianza": 1, + "tráfico de influencias": 8 + } + }, + "adverse_media_matches": [ + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in legal violations related to favoritism and influence trafficking, leading to a negative portrayal in the article.", + "headline": "La Diputación de Villaejemplo consolida la plaza a medida para el ex asesor de Moncloa amigo del \"hermanito\" de Ejemplo", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "fraud", + "prevaricación", + "tráfico de influencias", + "irregularidades", + "imputado" + ], + "sentiment_score": -2, + "publication_date": "2025-06-10T20:46:38" + }, + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in legal issues regarding favoritism and corruption, leading to a negative portrayal in the article.", + "headline": "La Diputación de Villaejemplo consolida la plaza a medida para el ex asesor de Moncloa amigo del \"hermanito\" de Ejemplo", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "fraud", + "prevaricación", + "tráfico de influencias", + "irregularidades", + "trato de favor" + ], + "sentiment_score": -2, + "publication_date": "2025-06-10T20:46:38" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is mentioned in connection with Carlos Ficticio's corruption case, indicating his involvement in alleged influence peddling.", + "headline": "España, paraíso del aforado", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias", + "corrupción", + "impunidad", + "privilegio", + "desconfianza", + "manipulación", + "fraude de ley" + ], + "sentiment_score": -2, + "publication_date": "2025-06-10T05:05:34" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is implicated in the same investigation as Ficticio, which raises questions about his conduct and political ethics.", + "headline": "Aforamientos: esa figura incómoda", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias", + "investigación", + "juicio", + "fraude de ley", + "desprestigio", + "crisis de confianza", + "delitos" + ], + "sentiment_score": -2, + "publication_date": "2025-06-08T06:01:00" + }, + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in a legal investigation regarding irregularities in his hiring, which raises serious concerns about his conduct.", + "headline": "La jueza del 'caso Javier Ejemplo' denuncia un «fraude de ley» en el aforamiento de Ficticio (Partido Ejemplo)", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "fraud", + "irregularities", + "prevaricación", + "tráfico de influencias" + ], + "sentiment_score": -2, + "publication_date": "2025-06-09T10:19:10" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is mentioned in connection with legal allegations of corruption, contributing to a negative perception.", + "headline": "'Caso aforamiento': la letrada mayor de la Asamblea certifica a la juez que Ficticio es diputado desde antes de prometer su cargo en el Pleno", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias" + ], + "sentiment_score": -2, + "publication_date": "2025-06-09T08:08:30" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is implicated in the same investigation as Ficticio, raising concerns about political corruption and influence.", + "headline": "Aforamientos: esa figura incómoda", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias", + "investigación" + ], + "sentiment_score": -2, + "publication_date": "2025-06-08T06:01:00" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is implicated in the same investigation as Ficticio, which raises concerns about political misconduct.", + "headline": "Aforamientos: esa figura incómoda", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias", + "investigación", + "delitos" + ], + "sentiment_score": -2, + "publication_date": "2025-06-08T06:00:59" + }, + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in a case of favoritism and corruption, which casts a negative light on his character and actions.", + "headline": "'Caso Javier Ejemplo': las asociaciones judiciales salen en defensa de la juez Luisa Inventada ante las \"presiones externas\" que sufre", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "favoritism", + "corruption", + "persecution" + ], + "sentiment_score": -2, + "publication_date": "2025-06-06T10:08:15" + }, + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in a case of alleged favoritism and corruption, which casts a negative light on his character and actions.", + "headline": "'Caso Javier Ejemplo': las asociaciones judiciales salen en defensa de la juez Luisa Inventada ante las \"presiones externas\" que sufre", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "favoritism", + "corruption", + "persecution" + ], + "sentiment_score": -2, + "publication_date": "2025-06-06T08:08:15" + } + ], + "additional_information": {} + } + ], + "score": 80, + "screened_data": { + "full_name": "Javier Ejemplo Modelo", + "nationality": "ES", + "date_of_birth": "1974-01-01", + "document_number": null + }, + "is_ongoing_monitoring_enabled": true, + "next_ongoing_monitoring_bill_date": "2026-07-24", + "last_ongoing_screening_at": "2026-09-12T04:03:11Z", + "last_ongoing_screening_datasets_updated_at": "2026-09-11T06:00:00Z", + "warnings": [ + { + "feature": "AML", + "risk": "POSSIBLE_MATCH_FOUND", + "additional_data": null, + "log_type": "warning", + "short_description": "Possible match found in AML screening", + "long_description": "The Anti-Money Laundering (AML) screening process identified potential matches with watchlists or high-risk databases, requiring further review.", + "node_id": "feature_aml_1" + } + ] + } + ], + "ip_analyses": [ + { + "node_id": "feature_ip_1", + "status": "Approved", + "device_brand": "Apple", + "device_model": "iPhone", + "browser_family": "Mobile Safari", + "os_family": "iOS", + "platform": "mobile", + "device_fingerprint": "didit-fp-a1b2c3d4e5f6g7h8", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_state": "Barcelona", + "ip_city": "Barcelona", + "latitude": 41.4022, + "longitude": 2.1407, + "ip_address": "83.50.226.71", + "isp": null, + "organization": null, + "is_vpn_or_tor": false, + "is_data_center": false, + "time_zone": "Europe/Madrid", + "time_zone_offset": "+0100", + "ip": { + "location": { + "latitude": 40.2206327, + "longitude": 1.5770097 + }, + "distance_from_id_document": 23.4, + "distance_from_poa_document": 12.3 + }, + "id_document": { + "location": { + "latitude": 41.2706327, + "longitude": 1.9770097 + }, + "distance_from_ip": 23.4, + "distance_from_poa_document": 18.7 + }, + "poa_document": { + "location": { + "latitude": 41.2706327, + "longitude": 1.9770097 + }, + "distance_from_ip": 12.3, + "distance_from_id_document": 18.7 + }, + "warnings": [], + "matches": [ + { + "session_id": "99999999-8888-7777-6666-555555555555", + "session_number": 41205, + "vendor_data": "22222222-2222-2222-2222-222222222222", + "verification_date": "2024-07-20T10:15:30Z", + "match_type": "ip_address", + "match_source": "ip_address", + "matched_value": "83.50.226.71", + "status": "Approved", + "is_blocklisted": false, + "api_service": null, + "source": "session", + "device_info": { + "device_brand": "Apple", + "device_model": "iPhone", + "browser_family": "Mobile Safari", + "os_family": "iOS", + "platform": "mobile", + "device_fingerprint": "didit-fp-f9e8d7c6b5a40312" + }, + "location_info": { + "ip_address": "83.50.226.71", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_state": "Barcelona", + "ip_city": "Barcelona", + "is_vpn_or_tor": false, + "is_data_center": false + }, + "confidence": 0.0, + "match_mode": "co_occurrence" + }, + { + "session_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "session_number": 42830, + "vendor_data": "33333333-3333-3333-3333-333333333333", + "verification_date": "2024-06-30T18:22:05Z", + "match_type": "device_fingerprint", + "match_source": "recovered_high", + "matched_value": "f47ac10b-58cc-4372-a567-0e02b2c3d479", + "status": "Declined", + "is_blocklisted": false, + "api_service": null, + "source": "session", + "device_info": { + "device_brand": "Samsung", + "device_model": "SM-G991B", + "browser_family": "Chrome Mobile", + "os_family": "Android", + "platform": "mobile", + "device_fingerprint": null + }, + "location_info": { + "ip_address": "83.50.231.18", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_state": "Madrid", + "ip_city": "Madrid", + "is_vpn_or_tor": false, + "is_data_center": false + }, + "confidence": 1.0, + "match_mode": "deterministic", + "recovery_similarity": 0.9874, + "tls_ja4_corroborated": true, + "recovery_gate_reason": "hardware_root_match" + } + ] + } + ], + "database_validations": [ + { + "node_id": "feature_db_validation_1", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "last_name": "De Lima", + "first_name": "Fernando", + "tax_number": "01234567890", + "date_of_birth": "1980-01-01", + "document_type": "ID", + "expiration_date": "2030-01-01" + }, + "validations": [ + { + "service_id": "bra_cpf", + "service_name": "Brazil - CPF status check", + "outcome_code": "MATCH", + "validation": { + "full_name": "full_match", + "date_of_birth": "full_match", + "identification_number": "full_match" + }, + "source_data": { + "identification_number": "01234567890", + "first_name": "FERNANDO", + "last_name": "DE LIMA", + "date_of_birth": "1980-01-01", + "gender": "M", + "nationality": "BRA" + } + } + ], + "match_type": "full_match", + "status": "Approved", + "warnings": [] + } + ], + "reviews": [ + { + "user": "compliance@example.com", + "new_status": "Declined", + "comment": "Possible match found in AML screening", + "created_at": "2024-07-18T13:29:00.366811Z" + } + ], + "created_at": "2024-07-24T08:54:25.443172Z", + "expires_at": "2025-07-24T08:54:25.443172Z", + "biometric_consent": { + "required": false, + "trigger": null, + "status": "not_required", + "given_at": null, + "declined_at": null, + "text_version": null, + "text_hash": null, + "locale": null, + "policy_url": null, + "step": null, + "node_id": null, + "ip_address": null, + "ip_country_code": null, + "ip_state": null, + "ip_city": null, + "user_agent": null, + "detected_ip_address": "83.50.226.71", + "detected_country_code": "ES", + "detected_state": "ES-B", + "detected_city": "Barcelona", + "declared_state": null, + "document_state": null, + "jurisdictions": [], + "retention_months": null, + "reason": null, + "decline_reason": null, + "face_data_deleted_at": null + } + } + }, + "BusinessExample": { + "summary": "Business (KYB) session example", + "description": "A `session_kind = \"business\"` decision: KYB sessions replace the KYC feature arrays with `registry_checks`, `document_verifications` and `key_people_checks`, while sharing `aml_screenings`, `phone_verifications`, `email_verifications`, `questionnaire_responses`, `ip_analyses`, `reviews` and the session envelope.", + "value": { + "session_id": "66666666-7777-8888-9999-000000000000", + "session_kind": "business", + "session_number": 31415, + "session_url": "https://verify.didit.me/session/business123", + "status": "In Review", + "environment": "live", + "workflow_id": "abababab-cdcd-efef-0101-232323232323", + "features": [ + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE" + ], + "vendor_data": "merchant-001234", + "metadata": null, + "callback": "https://example.com/verification/callback", + "registry_checks": [ + { + "status": "Approved", + "node_id": "feature_kyb_registry_1", + "data_resolved": true, + "company": { + "uuid": "dddd4444-eeee-5555-ffff-666677778888", + "node_id": "feature_kyb_registry_1", + "status": "Approved", + "registry_status": "active", + "data_resolved": true, + "company_name": "ACME HOLDINGS LTD", + "registration_number": "12345678", + "country_code": "GB", + "region": null, + "company_type": "Private Limited Company", + "incorporation_date": "2015-03-12", + "registered_address": "1 Example Street, London, EC1A 1AA", + "tax_number": null, + "risk_level": null, + "verification_status": null, + "is_from_registry": true, + "fetch_status": "resolved", + "alternative_names": null, + "nature_of_business": "62012 - Business and domestic software development", + "registered_capital": "100 GBP", + "registered_capital_amount": "100.00", + "registered_capital_currency": "GBP", + "website": null, + "email": null, + "phone": null, + "legal_entity_identifier": null, + "location_of_registration": "England and Wales", + "financial_summary": null, + "officers": [ + { + "uuid": "aaaa1111-bbbb-2222-cccc-333344445555", + "name": "Jane Smith", + "designation": "Director", + "role": "director", + "nationality": "British", + "is_active": true, + "kyc_status": "Approved", + "kyc_session_url": "https://verify.didit.me/session/abc123def456" + } + ], + "beneficial_owners": [ + { + "uuid": "bbbb2222-cccc-3333-dddd-444455556666", + "name": "John Doe", + "first_name": "John", + "last_name": "Doe", + "entity_type": "person", + "roles": [ + "ubo", + "shareholder" + ], + "ownership_min_shares": "50", + "ownership_max_shares": "75", + "is_active": true, + "kyc_status": "Pending", + "kyc_session_url": "https://verify.didit.me/session/def456ghi789", + "effective_ownership_percent": 75.0 + } + ], + "addresses": [ + { + "type": "registered", + "address": "1 Example Street, London, EC1A 1AA", + "country": "GB" + } + ], + "industries": [ + { + "code": "62012", + "description": "Business and domestic software development" + } + ], + "accounts": null, + "registry_data": {}, + "user_provided_data": { + "company_name": "ACME HOLDINGS", + "registration_number": "12345678" + }, + "confirmed_by_user_at": "2026-06-10T12:01:30.000000Z", + "last_console_edit_at": null, + "is_editable": false + }, + "ownership_structure": { + "shareholders": [ + { + "name": "John Doe", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": null, + "country_of_birth": null, + "company_name": null, + "registration_number": null, + "entity_type": "person", + "nationality": null, + "designation": null, + "roles": [ + "shareholder" + ], + "ownership_min_shares": "75", + "ownership_max_shares": "75", + "voting_min_shares": null, + "voting_max_shares": null, + "appointment_date": null, + "is_active": true, + "shareholding": { + "share_class": "ORDINARY", + "number_of_shares": 750.0, + "currency": "GBP", + "nominal_value": 1.0, + "total_value": null, + "percentage": 75.0 + } + } + ], + "beneficial_owners": [] + }, + "warnings": [] + } + ], + "aml_screenings": null, + "document_verifications": [ + { + "status": "In Review", + "node_id": "feature_kyb_documents_1", + "items": [ + { + "uuid": "eeee5555-ffff-6666-0000-777788889999", + "document_type": "LEGAL_PRESENCE", + "document_subtype": "CERTIFICATE_OF_INCORPORATION", + "document_group": "LEGAL_PRESENCE", + "file_url": "https:///kyb/certificate-of-incorporation.pdf", + "original_filename": "certificate_of_incorporation.pdf", + "file_size": 482133, + "status": "Approved", + "created_at": "2026-06-10T12:03:11.000000Z", + "cross_check_result": { + "critical": { + "company_name": { + "document": "ACME HOLDINGS LTD", + "registry": "ACME HOLDINGS LTD", + "score": 1.0, + "match": true + }, + "registration_number": { + "document": "12345678", + "registry": "12345678", + "match": true + } + }, + "non_critical": {}, + "people": {} + }, + "document_metadata": { + "file_size": 482133, + "content_type": "application/pdf", + "creation_date": "2015-03-12T09:00:00", + "modified_date": "2015-03-12T09:00:00", + "creator": null, + "producer": null, + "software": null, + "encryption": null, + "is_signed": true, + "is_tampered": false, + "signature_info": null, + "exif_original_date": null, + "exif_digitized_date": null, + "processed_by_known_editor": false, + "has_different_creation_mod_date": false, + "overlay_manipulation": null + }, + "description": null, + "is_requested": false, + "ocr_data": { + "company_name": "ACME HOLDINGS LTD", + "registration_number": "12345678", + "incorporation_date": "2015-03-12" + }, + "is_console_upload": false, + "is_confirmed": true + }, + { + "uuid": "ffff6666-0000-7777-1111-888899990000", + "document_type": "OWNERSHIP_STRUCTURE", + "document_subtype": null, + "document_group": "OWNERSHIP_STRUCTURE", + "file_url": null, + "original_filename": null, + "file_size": null, + "status": "Not Finished", + "created_at": "2026-06-10T15:40:02.000000Z", + "cross_check_result": null, + "document_metadata": null, + "description": "Please upload an up-to-date shareholder registry", + "is_requested": true, + "ocr_data": null, + "is_console_upload": false, + "is_confirmed": true + } + ], + "groups": { + "LEGAL_PRESENCE": { + "total": 1, + "approved": 1, + "pending": 0, + "declined": 0, + "in_review": 0, + "other": 0, + "missing": 0 + }, + "OWNERSHIP_STRUCTURE": { + "total": 1, + "approved": 0, + "pending": 0, + "declined": 0, + "in_review": 0, + "other": 0, + "missing": 1 + } + }, + "required_groups": [ + "LEGAL_PRESENCE", + "OWNERSHIP_STRUCTURE" + ], + "warnings": [] + } + ], + "key_people_checks": [ + { + "status": "In Review", + "node_id": "feature_kyb_key_people_1", + "registry": { + "officers": [ + { + "uuid": "aaaa1111-bbbb-2222-cccc-333344445555", + "name": "Jane Smith", + "designation": "Director", + "role": "director", + "nationality": "British", + "appointment_date": "2015-03-12", + "termination_date": null, + "is_active": true, + "address": "1 Example Street, London, EC1A 1AA", + "aml_status": "Approved", + "kyc_status": "Approved", + "kyc_session_url": "https://verify.didit.me/session/abc123def456", + "kyc_session_id": "77777777-8888-9999-aaaa-bbbbbbbbbbbb", + "verification_workflow_type": "kyc", + "verification_workflow_id": "12121212-3434-5656-7878-909090909090", + "verification_workflow_label": "Key People KYC", + "verification_workflow_features": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "didit_internal_id": "99999999-0000-1111-2222-333333333333", + "kyc_document_type": "P", + "kyc_nationality": "GBR", + "kyc_liveness_passed": true, + "kyc_verified_at": "2026-06-10T14:22:31.123456Z", + "kyc_aml_categories": { + "sanctions": "clear", + "pep": "clear", + "adverse_media": "clear", + "warnings": "clear" + }, + "kyc_notification_sent_at": "2026-06-09T09:00:00.000000Z" + } + ], + "beneficial_owners": [ + { + "uuid": "bbbb2222-cccc-3333-dddd-444455556666", + "name": "John Doe", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-05-04", + "country_of_birth": null, + "email": null, + "company_name": null, + "registration_number": null, + "entity_type": "person", + "nationality": "British", + "designation": null, + "roles": [ + "ubo", + "shareholder" + ], + "ownership_min_shares": "50", + "ownership_max_shares": "75", + "is_active": true, + "aml_status": null, + "kyc_status": "In Progress", + "kyc_session_url": "https://verify.didit.me/session/def456ghi789", + "kyc_session_id": "88888888-9999-aaaa-bbbb-cccccccccccc", + "verification_workflow_type": "kyc", + "verification_workflow_id": "12121212-3434-5656-7878-909090909090", + "verification_workflow_label": "Key People KYC", + "verification_workflow_features": [ + { + "feature": "ID_VERIFICATION", + "status": "Not Finished" + }, + { + "feature": "LIVENESS", + "status": "Not Finished" + }, + { + "feature": "AML", + "status": "Not Finished" + } + ], + "didit_internal_id": null, + "kyc_document_type": null, + "kyc_nationality": null, + "kyc_liveness_passed": null, + "kyc_verified_at": null, + "kyc_aml_categories": null, + "kyc_notification_sent_at": null, + "child_beneficial_owners": [], + "effective_ownership_percent": 75.0 + } + ] + }, + "submitted": { + "parties": [ + { + "uuid": "cccc3333-dddd-4444-eeee-555566667777", + "entity_type": "person", + "source": "USER", + "name": "John Doe", + "first_name": "John", + "last_name": "Doe", + "company_name": null, + "registration_number": null, + "email": "john.doe@example.com", + "nationality": "GBR", + "phone_number": "+447700900123", + "date_of_birth": "1980-05-04", + "position": "Chief Executive Officer", + "custom_fields": {}, + "is_active": true, + "is_skipped": false, + "requires_verification": true, + "roles": [ + { + "role": "ubo", + "ownership_percent": 75.0, + "voting_percent": 75.0 + }, + { + "role": "director", + "ownership_percent": null, + "voting_percent": null + } + ], + "kyc_session_id": "88888888-9999-aaaa-bbbb-cccccccccccc", + "kyc_session_status": "In Progress", + "kyc_session_url": "https://verify.didit.me/session/def456ghi789", + "kyb_sub_session_id": null, + "kyb_sub_session_status": null, + "kyb_sub_session_url": null, + "verification_workflow_type": "kyc", + "verification_workflow_id": "12121212-3434-5656-7878-909090909090", + "verification_workflow_label": "Key People KYC", + "verification_workflow_features": [ + { + "feature": "ID_VERIFICATION", + "status": "Not Finished" + }, + { + "feature": "LIVENESS", + "status": "Not Finished" + }, + { + "feature": "AML", + "status": "Not Finished" + } + ], + "didit_internal_id": null + } + ] + }, + "ubo_kyc_summary": { + "total": 1, + "approved": 0, + "flagged": 0, + "pending": 1 + }, + "warnings": [] + } + ], + "phone_verifications": null, + "email_verifications": null, + "questionnaire_responses": null, + "ip_analyses": null, + "reviews": [], + "contact_details": null, + "expected_details": { + "company_name": "Tesco PLC", + "registry_country": "GB", + "registration_number": "00445790" + }, + "created_at": "2026-06-10T11:58:04.000000Z", + "expires_at": "2026-06-24T11:58:04.000000Z" + } + } + }, + "schema": { + "$ref": "#/components/schemas/SessionDecision" + } + } + } + }, + "403": { + "description": "Authentication or authorization failed. A missing key returns `403`; a wrong, revoked or expired key returns `401`. Two `detail` strings distinguish the failure modes:\n\n- `\"Authentication credentials were not provided or are invalid.\"` — no `x-api-key` header (and no `Authorization: Bearer` token), or the supplied credential failed introspection (revoked, expired, or malformed).\n- `\"You do not have permission to perform this action.\"` — the credential is valid but not allowed to read this session: a console-user Bearer token lacks the `read:sessions` permission or cannot access the owning application, or an API key belongs to a different organization than the session.\n\nNote that for a completely unknown `sessionId` the endpoint resolves the owning application before validating credentials, so a missing session returns `404` even when the credential is missing or invalid.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Authentication credentials were not provided or are invalid." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed introspection", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Insufficient permissions": { + "summary": "Valid credential lacking read:sessions or access to the owning application", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Session not found. Returned when no live session matches the supplied `sessionId` as either a User Verification (KYC) or a Business Verification (KYB) session — a session that has been deleted also lands here. Body is the standard `{\"detail\": ...}` envelope.\n\n**Two distinct `detail` strings.** If the `sessionId` is unknown as both a KYC and a KYB session, the pre-auth owner lookup fails and you receive `{\"detail\": \"Not found.\"}` — this happens before credential validation, so it is returned even for unauthenticated calls. If the session exists but cannot be retrieved under the authenticated application (for example a stale or just-deleted record), the view returns `{\"detail\": \"Session not found.\"}`. Both indicate the same outcome — the session is not accessible — so match on the `404` status code rather than the string.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Not found." + } + } + }, + "examples": { + "Unknown session id": { + "summary": "sessionId is unknown to this application", + "value": { + "detail": "Not found." + } + }, + "Session not retrievable": { + "summary": "Session exists but is not retrievable for this application", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. GET requests are limited to **600 per minute per credential** (API key, Bearer token, or source IP when unauthenticated). Inspect `Retry-After` / `X-RateLimit-Reset` and back off before retrying. If you hit this limit while polling for decisions, switch to the `status.updated` webhook and fetch the decision once per status change instead.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying the request.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Maximum number of requests allowed in the current window (600 for GET requests).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests remaining in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix timestamp (seconds) at which the current window resets.", + "schema": { + "type": "integer", + "example": 1750000000 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable explanation of the rate-limit breach.", + "example": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + }, + "examples": { + "Throttled": { + "summary": "More than 600 GET requests in one minute from the same credential", + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session whose decision you want to retrieve. This is the `session_id` returned by `POST /v3/session/`. The same path works for both User Verification (KYC) and Business Verification (KYB) sessions — the server resolves the id against both session types.\n\n**Must be a valid UUID.** The route only matches well-formed UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below. Validate the UUID format client-side so you can distinguish an input error from a genuine missing-session `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "include", + "in": "query", + "required": false, + "description": "Comma-separated list of extra payload sections. Supported values: `events` and `resubmissions`, alone or together (`events,resubmissions`). When `include=events` is passed, the response additionally contains the session event timeline (`events`, including the `WORKFLOW_BRANCH_EVALUATED` / `WORKFLOW_STATUS_NODE_REACHED` routing events described on the Retrieve Session page), the full activity log (`activity_reviews`), blocklist flags (`blocklisted`), a per-session `cost_breakdown`, workflow metadata (`workflow_type`, `workflow_version`, `workflow_version_id`, and `workflow_terminal_node_id` - the last recorded workflow status node that set a session status, or `null` if none has been recorded; historical provenance that is not cleared by manual approvals, declines or resubmissions, and is replaced by a later workflow status node), and session metadata (`api_service` — string or `null`, set only for standalone-API sessions; `session_type` — `API`, `HOSTED`, or `MIGRATED`; `has_device_nfc_support` — boolean). Two fields change relative to the plain shape: the top-level `session_kind` discriminator is **omitted** (the events shape only exists for user sessions), and the `features` array switches from plain strings to `{feature, node_id}` objects — where the email feature is reported as `EMAIL` instead of `EMAIL_VERIFICATION`. This expanded shape is primarily used by the Didit Console; most API integrations should omit the parameter. **KYB limitation:** `include=events` only applies to user (KYC) sessions. Business sessions (`session_kind = \"business\"`) always return the standard business decision shape — `events`, `activity_reviews`, `blocklisted`, `cost_breakdown` and the extra `workflow_*`/session metadata are never returned for business sessions, and their `features` array stays a plain string array. With `include=resubmissions` (user and business sessions alike) the response also contains `resubmissions`: one entry per resubmission request, oldest first, each with `number`, `created_at`, `previous_status`, `nodes`, `reasons`, `requested_by` and `results` (what the resubmitted steps returned before the request, under the same keys and in the same shape as the current results). Each `cost_breakdown` item carries `usage_type`, `price`, `details` and `created_at`: a resubmitted step that runs again is billed as a new item.", + "schema": { + "type": "string", + "enum": [ + "events", + "resubmissions", + "events,resubmissions" + ], + "example": "events" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/decision/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Accept: application/json'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.retrieve_decision(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.retrieveDecision(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.RetrieveDecision(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsRetrieveDecisionParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().retrieveDecision(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->retrieveDecision('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.retrieve_decision(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.RetrieveDecisionAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/delete/": { + "delete": { + "summary": "Delete a verification session (KYC or KYB)", + "description": "Delete a single verification session, User Verification (KYC) or Business Verification (KYB), by its `session_id`. The id is resolved against KYC sessions first, then KYB sessions, so both kinds are deleted through this one URL.\n\n**What is deleted:**\n- The session and every verification attempt it holds, including results that a resubmission replaced: the decision, extracted data, all related feature records (ID verification, face, liveness, face match, AML, registry checks, IP analysis, and so on), its reviews and its activity log.\n- All stored files owned by the session: document front/back photos (full, cropped, and privacy-blurred variants), document-capture videos, portrait crops, NFC chip portrait and signature images, face reference images, liveness videos, face-match source/target images, Proof of Address documents, and any extra uploaded files (KYC); uploaded company documents and extra files (KYB). E-signature documents, signed PDFs, evidence PDFs and signature images (KYC and KYB): their download paths stop working. Previously issued media URLs (`https:///...`) stop resolving.\n- The session's face data. Its face embedding is deleted too, unless you choose to keep an image-free template (see **Face biometric data**).\n- Webhook deliveries still queued for the session, and their retries. Webhooks already delivered are not affected.\n- For a Business Verification (KYB) session: the verifications it created for its key people (KYC) and its corporate owners (KYB), recursively, reported as `deleted_child_verifications`. A verification it only links to, such as an existing approved KYC it reused, is not deleted.\n\nThe session disappears from every read endpoint immediately: `GET /v3/session/{sessionId}/decision/` returns `404` and `GET /v3/sessions/` stops listing it. Ongoing AML monitoring and KYB registry monitoring started by the deleted verifications stop.\n\n**What is not affected:** blocklist entries created from this session (face or document) are **not** removed; manage those with the blocklist endpoints. Share tokens issued for the session are not revoked, but there is nothing left for them to import: an import with one returns `400` with `{\"share_token\": [\"Original session does not exist.\"]}`. Credits already consumed are **not** refunded. The parent User or Business entity is **not** deleted; use `POST /v3/users/delete/` or `POST /v3/businesses/delete/` for those. If the User's profile portrait came from this session, the portrait is removed with it.\n\n**Face biometric data:** by default the session's face embedding is deleted with the session (`face_retention_outcome: deleted`). An application that opted in to biometric-template retention (`face_retention_policy: retain_until_user_deleted` on `PATCH /v3/webhook/`), or a request with `retain_face_embeddings: true`, keeps one separately managed, image-free face biometric template anchored to the session's User (`retained_with_user`) so that duplicate detection, Face Search, and biometric authentication keep working. The template never contains the face image, media, identity fields, the decision, or the deleted session's id; it is biometric data deleted at `biometric_template_expires_at`, or earlier on User deletion, privacy erasure, or an explicit purge through `/v3/biometric-templates/`. A session without a linked User cannot retain a template (`ineligible_no_vendor_user`). Send `deletion_instruction: privacy_erasure` for data-subject requests: it purges every retained template for that User and never retains a new one.\n\n**Irreversible:** deletion is permanent and there is no restore endpoint. Export anything you need (for example a decision PDF) before you call it.\n\n**Side effects:** no webhook is emitted for deletions.\n\n**Response:** `200 OK` once the deletion has completed, with a JSON body reporting the `face_retention_outcome`, the retained template's expiry and, for a KYB session, `deleted_child_verifications`. Earlier versions of this endpoint returned `204 No Content`; update clients that assert on `204`. Keep your `instruction_id` in your own erasure log.\n\n**Idempotency:** not idempotent at the HTTP level: the first call returns `200`; repeating it returns `404` because the session no longer resolves. After a `503` the session is still in place; repeat the same request to finish deleting it.\n\n**Authentication:** send your application's API key in the `x-api-key` header; the session must belong to that application. Console user access tokens (`Authorization: Bearer ...`) may also call this endpoint when they carry the `delete:sessions` permission. Authentication and permission failures both return `403`; this API never returns `401`.\n\n**Rate limit:** shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.\n\nTo bulk-delete KYC sessions by their numeric `session_number`, use `POST /v3/sessions/delete/` instead.\n\n**Filed regulatory reports:** if the selected data is in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "delete_v3_session_by_id", + "x-didit-sdk": { + "resource": "sessions", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID (`session_id`) of the User Verification (KYC) or Business Verification (KYB) session to delete, as returned when the session was created. Must be a canonical hyphenated UUID — a non-UUID value does not match the route and returns `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "responses": { + "200": { + "description": "Session deleted. The body reports what happened to the session's face biometric data and, for a KYB session, how many verifications it created were deleted with it.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The deleted session." + }, + "session_number": { + "type": "integer", + "nullable": true, + "description": "The deleted session's number." + }, + "face_retention_outcome": { + "type": "string", + "enum": [ + "retained_with_user", + "deleted", + "none", + "ineligible_no_vendor_user" + ], + "description": "What happened to the session's face biometric data. `deleted`: the face embedding was deleted with the session (default). `retained_with_user`: the session is deleted and one image-free biometric template stays anchored to the User; see `biometric_template_uuid`. `none`: the session had no face embedding (for example a KYB session, or a KYC session without a liveness selfie). `ineligible_no_vendor_user`: retention was requested but the session has no linked User, so the embedding was deleted with the session. A biometric-store failure is never reported through this field on this endpoint: it returns `503` and the session is not deleted." + }, + "biometric_template_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Id of the retained biometric template when `face_retention_outcome` is `retained_with_user`; `null` otherwise. Manage it through `/v3/biometric-templates/{template_uuid}/`." + }, + "biometric_template_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the retained biometric template is deleted, when `face_retention_outcome` is `retained_with_user`; `null` when no template is retained." + }, + "deleted_child_verifications": { + "type": "integer", + "description": "For a Business Verification (KYB) session: how many verifications it created (its key people's KYC sessions and its corporate owners' KYB sessions, recursively) were deleted with it. Always `0` for a KYC session." + } + } + }, + "examples": { + "Deleted (default)": { + "value": { + "session_id": "4c5c7f3a-3b1d-4b7e-9c39-2b4f7e6a1d02", + "session_number": 1024, + "face_retention_outcome": "deleted", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + } + }, + "Retained with User": { + "value": { + "session_id": "4c5c7f3a-3b1d-4b7e-9c39-2b4f7e6a1d02", + "session_number": 1024, + "face_retention_outcome": "retained_with_user", + "biometric_template_uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "biometric_template_expires_at": "2027-10-09T10:15:00Z", + "deleted_child_verifications": 0 + } + }, + "Ineligible: no User": { + "summary": "Retention requested on a session created without vendor_data", + "value": { + "session_id": "7a1e9c2d-5f4b-4c3a-8e2d-1b0a9f8e7d6c", + "session_number": 1031, + "face_retention_outcome": "ineligible_no_vendor_user", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + } + }, + "KYB session": { + "value": { + "session_id": "0f3c9b7e-2d1a-4e5f-8c6b-7a9d8e1f2c3b", + "session_number": 88, + "face_retention_outcome": "none", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 3 + }, + "summary": "A business verification deleted with the three verifications it created for its key people" + } + } + } + } + }, + "400": { + "description": "Validation error — nothing is deleted.", + "content": { + "application/json": { + "examples": { + "Privacy erasure cannot retain": { + "value": { + "retain_face_embeddings": [ + "Privacy erasure cannot retain biometric templates." + ] + } + }, + "No finite retention duration": { + "summary": "Retention requested without face_retention_days on the request or the application", + "value": { + "detail": "A finite biometric-template retention duration is required." + } + } + } + } + } + }, + "403": { + "description": "Authentication or permission failure. A missing key also returns `403`; a wrong, revoked or expired key returns `401`. Exception: when the `sessionId` does not resolve to a live session, the pre-auth owner lookup returns `404` first, even with missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid API key": { + "summary": "No x-api-key header, or the key is invalid", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Insufficient permission": { + "summary": "User token without delete:sessions, or credentials for an application that does not own the session", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No live KYC or KYB session with this `session_id` exists — the id is unknown or the session was already deleted (repeating a successful delete lands here).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not Found": { + "summary": "Unknown or already-deleted session", + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Shared write rate limit exceeded (300 POST/PATCH/DELETE requests per minute per API key). Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "Write budget exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + }, + "503": { + "description": "The deletion could not finish because a storage service, a biometric store, or a monitoring provider was unavailable. The session is still in place; repeat the same request until it returns `200`. Some of its stored files or its face embedding may already be deleted, so do not rely on them after a `503`. Retries converge on a single template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Session deletion unavailable": { + "summary": "A storage service or a monitoring provider was unavailable", + "value": { + "detail": "The session could not be deleted because a storage service or a monitoring provider is unavailable. The session is still in place; repeat the request to finish deleting it." + } + }, + "Biometric store unavailable": { + "summary": "A biometric store was unavailable while retaining or purging a template", + "value": { + "detail": "The biometric-template operation could not finish because an external store is unavailable. The operation is retryable." + } + } + } + } + } + }, + "409": { + "description": "The session is part of a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl (default policy)", + "source": "curl -X DELETE \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/delete/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "curl", + "label": "curl (retain template)", + "source": "curl -X DELETE \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/delete/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"retain_face_embeddings\": true, \"face_retention_days\": 365}'" + }, + { + "lang": "curl", + "label": "curl (privacy erasure)", + "source": "curl -X DELETE \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/delete/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"deletion_instruction\": \"privacy_erasure\", \"instruction_id\": \"dsar-2026-0142\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.delete(\n \"11111111-2222-3333-4444-555555555555\",\n retain_face_embeddings=True,\n face_retention_days=365,\n instruction_id=\"post-approval-cleanup-2026-08\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.delete(\"11111111-2222-3333-4444-555555555555\", {\n retain_face_embeddings: true,\n face_retention_days: 365,\n instruction_id: \"post-approval-cleanup-2026-08\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Delete(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsDeleteParams{\n\tRetainFaceEmbeddings: didit.Bool(true),\n\tFaceRetentionDays: didit.Int(365),\n\tInstructionID: didit.String(\"post-approval-cleanup-2026-08\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsDeleteParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().delete(\"11111111-2222-3333-4444-555555555555\", SessionsDeleteParams.builder()\n .retainFaceEmbeddings(true)\n .faceRetentionDays(365L)\n .instructionId(\"post-approval-cleanup-2026-08\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->delete('11111111-2222-3333-4444-555555555555', [\n 'retain_face_embeddings' => true,\n 'face_retention_days' => 365,\n 'instruction_id' => 'post-approval-cleanup-2026-08',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.delete(\n \"11111111-2222-3333-4444-555555555555\",\n retain_face_embeddings: true,\n face_retention_days: 365,\n instruction_id: \"post-approval-cleanup-2026-08\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.DeleteAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsDeleteParams\n{\n RetainFaceEmbeddings = true,\n FaceRetentionDays = 365,\n InstructionId = \"post-approval-cleanup-2026-08\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": false, + "description": "Optional. Omit the body entirely to follow the application's retention policy with an operational deletion.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "retain_face_embeddings": { + "type": "boolean", + "nullable": true, + "description": "Override the application's `face_retention_policy` for this deletion. `true` keeps one image-free face biometric template anchored to the session's User after the session is deleted; `false` deletes the face embedding with the session; omit or `null` to follow the application policy. Ignored for KYB sessions and for sessions without a face embedding.", + "example": true + }, + "face_retention_days": { + "type": "integer", + "minimum": 1, + "maximum": 3650, + "description": "Finite retention duration, in days, for a retained template. Required when a template is retained unless the application already sets `face_retention_days`. The template's `expires_at` is the earliest of this duration, the application's general data-retention window, and `face_retention_deadline`.", + "example": 365 + }, + "face_retention_deadline": { + "type": "string", + "format": "date-time", + "description": "Optional hard expiry for a retained template. Must be in the future.", + "example": "2027-08-28T00:00:00Z" + }, + "deletion_instruction": { + "type": "string", + "enum": [ + "operational_session_delete", + "privacy_erasure" + ], + "default": "operational_session_delete", + "description": "The class of instruction you are giving. `operational_session_delete` deletes the session and retains a template only when the policy or `retain_face_embeddings` says so. `privacy_erasure` purges every retained biometric template anchored to the session's User before deleting the session, cannot be overridden by the application policy, and cannot be combined with `retain_face_embeddings: true` (`400`).", + "example": "operational_session_delete" + }, + "instruction_id": { + "type": "string", + "maxLength": 255, + "description": "Your durable reference for this deletion instruction (for example an erasure-ticket id). Recorded on the audit trail and on any retained template. Generated by Didit when omitted.", + "example": "dsar-2026-0142" + } + } + }, + "examples": { + "Retain a biometric template": { + "summary": "Delete the session, keep an image-free template for 365 days", + "value": { + "retain_face_embeddings": true, + "face_retention_days": 365, + "instruction_id": "post-approval-cleanup-2026-08" + } + }, + "Force deletion on an opted-in application": { + "summary": "Delete the face embedding regardless of the application policy", + "value": { + "retain_face_embeddings": false + } + }, + "Privacy erasure": { + "summary": "Data-subject request: delete the session and purge every retained template for that person", + "value": { + "deletion_instruction": "privacy_erasure", + "instruction_id": "dsar-2026-0142" + } + } + } + } + } + } + } + }, + "/v3/session/{sessionId}/generate-pdf/": { + "get": { + "summary": "Download a PDF report for a verification session", + "description": "Render and download a compliance-ready PDF report for a finished verification session. The same path serves both User Verification (KYC) and Business Verification (KYB) sessions — the server resolves the `sessionId` against both session types and picks the matching report template.\n\n**Eligible statuses.** The session must already carry a final or reviewable status:\n\n- KYC sessions: `Approved`, `Declined`, `In Review`, or `Kyc Expired`.\n- KYB sessions: `Approved`, `Declined`, or `In Review`.\n\nAny other status (`Not Started`, `In Progress`, `Expired`, `Abandoned`, …) returns `403` with an explanatory `detail` string — see the `403` response below.\n\n**What the report contains.** The PDF mirrors the console session view at the moment of the request:\n\n- **KYC** — rolled-up session status, the warnings list, and one section per executed feature: ID Verification (document images plus extracted fields), NFC, Liveness, Face Match, Email Verification, Phone Verification, Proof of Address, Questionnaire answers, Database Validation, AML Screening, and IP Analysis — followed by session tags, console review activity (comments and status changes), and the session event timeline.\n- **KYB** — registry checks (company data), AML screenings, business document verifications, key-people checks, questionnaire responses, phone/email verifications, IP analyses, and the session event timeline, followed by tags and review activity.\n\n**Branding.** If your application has white-label customization, the report is rendered with your logo and links to your privacy-policy URL; otherwise it carries Didit branding. Reports are rendered in English — there is no language parameter.\n\n**No caching.** Every call re-renders the PDF from the current session data, so a report generated after a manual review reflects the reviewer’s decision. Two calls for the same session can produce byte-different files — archive the downloaded file if you need an immutable copy.\n\n**Latency.** Rendering downloads every stored image (document sides, selfies, extra files) before composing the PDF, so media-heavy sessions can take several seconds. Use a generous client read timeout (60 s recommended) and stream the body to disk.\n\n**Trailing slash.** The canonical route ends with a trailing slash (`…/generate-pdf/`). A request without it is served directly with the same response — there is no `301` redirect, so no client has to follow redirects and `curl` does not need `-L`. Use the slashed URL as in the samples.\n\nIf you need the underlying data as JSON instead of a rendered report, use `GET /v3/session/{sessionId}/decision/`. For every reportable session of one user in a single document, use `GET /v3/users/{vendor_data}/generate-pdf/`.", + "operationId": "get_v3_session_generate_pdf", + "x-didit-sdk": { + "resource": "sessions", + "method": "generate_pdf", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) or Business Verification (KYB) session to render — the `session_id` returned by `POST /v3/session/`. The same path works for both session kinds; use the `Content-Disposition` filename on the response to tell which report type you received.\n\n**Must be a well-formed, lowercase UUID.** The route only matches valid UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below. The route's UUID converter only matches the canonical lowercase form — uppercase-hex UUIDs are rejected with the HTML 404.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "responses": { + "200": { + "description": "The rendered PDF document, returned directly as binary `application/pdf` — there is no JSON wrapper and no download-URL indirection. The body starts with the `%PDF` magic bytes (PDF 1.7). Save it to a `.pdf` file or stream it through to your caller.", + "headers": { + "Content-Disposition": { + "description": "`attachment; filename=session_{sessionId}.pdf` for KYC sessions, `attachment; filename=business_{sessionId}.pdf` for KYB sessions — the prefix tells you which report template was rendered.", + "schema": { + "type": "string", + "example": "attachment; filename=session_11111111-2222-3333-4444-555555555555.pdf" + } + }, + "Content-Length": { + "description": "Total size of the PDF in bytes; the body is not chunked.", + "schema": { + "type": "integer", + "example": 25191 + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "403": { + "description": "Authentication, authorization, or session-status failure. A missing key returns `403`; a wrong, revoked or expired key returns `401`. Distinguish the failure modes by the `detail` string:\n\n- `\"Authentication credentials were not provided or are invalid.\"` — no `x-api-key` header (and no `Authorization: Bearer` token), or the supplied credential failed introspection (revoked, expired, or malformed).\n- `\"You do not have permission to perform this action.\"` — the credential is valid but cannot read this session: a console-user Bearer token lacks the `read:sessions` permission or cannot access the owning application, or an API key belongs to a different organization than the session.\n- `\"You can only generate a PDF for sessions in review, declined, approved or kyc expired\"` — the KYC session exists but is not in an eligible status yet (for example `Not Started` or `In Progress`). Wait for the `status.updated` webhook before requesting the report.\n- `\"You can only generate a PDF for sessions in review, declined or approved\"` — same condition for KYB sessions.\n\nNote that for a completely unknown `sessionId` the endpoint resolves the owning application before validating credentials, so a missing session returns `404` even when the credential is missing or invalid.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You can only generate a PDF for sessions in review, declined, approved or kyc expired" + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed introspection", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Insufficient permissions": { + "summary": "Valid credential lacking read:sessions or access to the owning application", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "KYC session not finished": { + "summary": "KYC session still Not Started / In Progress", + "value": { + "detail": "You can only generate a PDF for sessions in review, declined, approved or kyc expired" + } + }, + "KYB session not finished": { + "summary": "KYB session still Not Started / In Progress", + "value": { + "detail": "You can only generate a PDF for sessions in review, declined or approved" + } + } + } + } + } + }, + "404": { + "description": "No active session with this `sessionId` exists — it was never created, or it has been deleted. Owner resolution happens before credential validation, so this `404` is returned even for unauthenticated requests. Depending on which lookup misses, the `detail` string is `\"Not found.\"` (the common case) or `\"Session not found.\"`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Not found." + } + } + }, + "examples": { + "Unknown session": { + "summary": "sessionId does not match any KYC or KYB session", + "value": { + "detail": "Not found." + } + }, + "Recently deleted session": { + "summary": "Session was deleted between lookups", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. PDF generation has a dedicated limit of **50 requests per minute per credential** (API key, Bearer token, or source IP when unauthenticated), in addition to the global 600/min GET limit. Wait `Retry-After` seconds (also reflected in the `X-RateLimit-*` headers), then retry. Reports are not cached server-side, so download each report once and store the file instead of re-fetching it.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying the request.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Maximum number of PDF generations allowed in the current window (50).", + "schema": { + "type": "integer", + "example": 50 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests remaining in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix timestamp (seconds) at which the current window resets.", + "schema": { + "type": "integer", + "example": 1750000000 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable explanation of the rate-limit breach.", + "example": "Session PDF generation rate limit exceeded. You can make up to 50 requests per minute." + } + } + }, + "examples": { + "Throttled": { + "summary": "More than 50 PDF generations in one minute from the same credential", + "value": { + "detail": "Session PDF generation rate limit exceeded. You can make up to 50 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --fail \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/generate-pdf/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n --output report.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.sessions.generate_pdf(\n \"11111111-2222-3333-4444-555555555555\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.sessions.generatePdf(\"11111111-2222-3333-4444-555555555555\");\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Sessions.GeneratePDF(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().generatePdf(\"11111111-2222-3333-4444-555555555555\")\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->generatePdf('11111111-2222-3333-4444-555555555555');\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.sessions.generate_pdf(\"11111111-2222-3333-4444-555555555555\")\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Sessions.GeneratePdfAsync(\"11111111-2222-3333-4444-555555555555\");\nawait content.SaveToAsync(\"download.pdf\");" + } + ] + } + }, + "/v3/session/{sessionId}/sandbox/arm/": { + "post": { + "summary": "Arm a sandbox scenario on a session", + "description": "Persist a sandbox scenario on a `Not Started` session and expand its bundled magic values (email, phone, or `expected_details` fields) onto the session, so the mocked providers reproduce the scenario's outcome once the flow runs. This is the endpoint the pre-flow scenario picker in Didit's hosted verification UI calls when a tester chooses an outcome before starting capture. Build your own picker against it, or call it directly from a sandbox test harness.\n\nAuthenticate with the session's own `Session-Token` (returned in `session_token` by [Create Session](/sessions-api/create-session)), not your application `x-api-key`: this endpoint is meant to be called from the client, not your backend. A scenario is only accepted while `status` is `Not Started`. Calling it again while still `Not Started` re-arms the session: it swaps in the new scenario's magic values and clears only the values the *previous* scenario wrote, so any value the user already typed by hand (email, first name, etc.) survives the re-arm. Arming a session that has moved past `Not Started`, or that belongs to a live (non-sandbox) application, is rejected.\n\nSee [Sandbox & Test Data](/integration/sandbox-testing) for the full scenario catalog and the other two ways to drive an outcome: `sandbox_scenario` at session create, and typing magic values directly.", + "operationId": "post_v3_session_sandbox_arm", + "x-didit-sdk": { + "resource": "sessions", + "method": "arm_sandbox", + "auth": "device_token", + "server": false + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "SessionTokenAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the sandbox verification session to arm. Must match the session encoded in the Session-Token used to authenticate.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "scenario" + ], + "properties": { + "scenario": { + "type": "string", + "description": "Slug of the scenario to arm. See the catalog at GET /v1/sandbox/scenarios/ or the scenario table in [Sandbox & Test Data](/integration/sandbox-testing).", + "example": "decline_document_expired" + } + } + }, + "examples": { + "Arm": { + "summary": "Arm the AML PEP-hit scenario", + "value": { + "scenario": "decline_aml_hit" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Scenario armed. Its magic values are now on the session. `status` reflects the session's current status, which this call never changes: it is always `Not Started` on success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid" + }, + "scenario": { + "type": "string", + "description": "The armed scenario slug (echoes the request)." + }, + "status": { + "type": "string", + "example": "Not Started" + } + } + }, + "examples": { + "Armed": { + "summary": "Scenario armed on a Not Started session", + "value": { + "session_id": "11111111-2222-3333-4444-555555555555", + "scenario": "decline_aml_hit", + "status": "Not Started" + } + } + } + } + } + }, + "400": { + "description": "Validation error: an unknown scenario slug, or the session has already progressed past `Not Started`.", + "content": { + "application/json": { + "examples": { + "Unknown Scenario": { + "summary": "scenario is not in the catalog", + "value": { + "scenario": [ + "Unknown sandbox scenario: 'not-a-real-scenario'." + ] + } + }, + "Already Started": { + "summary": "Session moved past Not Started", + "value": [ + "Scenario can only be armed before the verification starts." + ] + } + } + } + } + }, + "404": { + "description": "The Session-Token does not resolve to this `session_id`, or the session belongs to a live (non-sandbox) application.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Live session, or session_id / token mismatch", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/sandbox/arm/ \\\n -H 'Session-Token: YOUR_SESSION_TOKEN' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"scenario\": \"decline_aml_hit\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/sandbox/arm/\",\n headers={\n \"Session-Token\": \"YOUR_SESSION_TOKEN\",\n \"Content-Type\": \"application/json\",\n },\n json={\"scenario\": \"decline_aml_hit\"},\n)\nresponse.raise_for_status()\nprint(response.json()) # {'session_id': '...', 'scenario': 'decline_aml_hit', 'status': 'Not Started'}" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const response = await fetch(\n 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/sandbox/arm/',\n {\n method: 'POST',\n headers: {\n 'Session-Token': sessionToken,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ scenario: 'decline_aml_hit' }),\n },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst { session_id, scenario, status } = await response.json();" + } + ] + } + }, + "/v3/session/{sessionId}/update-status/": { + "patch": { + "summary": "Approve, decline, or request resubmission for a verification session", + "description": "Manually override the final decision of a KYC or KYB verification session. Set `new_status` to `Approved` or `Declined` to record a manual decision, or to `Resubmitted` to clear specific verification steps and let the user redo them through the same verification URL. The endpoint accepts both user (KYC) and business (KYB) session IDs.\n\nCall it once a session has reached a reviewable state: the current status must be `Approved`, `Declined`, `In Review`, `Kyc Expired`, `Abandoned`, or `Resubmitted`. Sessions that are still `Not Started`, `In Progress`, or `Awaiting User`, or that ended as `Expired`, return `400`. Your API key needs the `write:sessions` permission (a scoped API key needs write access to decisions). The eligible-state check runs before body validation, so an ineligible session returns the `Wrong Current Status` error even when the payload is also invalid.\n\nFor `Resubmitted`, pass `nodes_to_resubmit` to choose which workflow steps the user must redo, or omit it to auto-select existing feature attempts in a resubmittable state (`Declined`, `In Review`, `Not Finished`, `Expired`). Never-attempted features are not auto-selected: list them explicitly in `nodes_to_resubmit`, or the request returns `400` when no recorded attempt needs resubmission. The selected steps' previous results move to the resubmission's history (returned by Get Decision with `include=resubmissions`), the steps are reordered to match the workflow graph, the session's expiration window restarts, and the original verification URL becomes usable again. Backend-only steps (`AML`, `DATABASE_VALIDATION`, `IP_ANALYSIS`) at the head of the list run immediately without user interaction; if every resubmitted step is backend-only, the session re-finalizes within the same request. `KYB_REGISTRY` and `KYB_KEY_PEOPLE` cannot be resubmitted directly: resubmit the relevant child KYC sessions instead and the parent business session recomputes automatically.\n\nEvery call that changes the status fires the `status.updated` [webhook](/integration/webhooks) once the change commits and appends an activity entry (with actor attribution and your `comment`) to the session's `reviews`, visible in [Get Decision](#get-/v3/session/-sessionId-/decision/) and in the console activity timeline. The user-profile aggregates linked to the session's `vendor_data` update as well. Declining disables ongoing AML monitoring for the session; re-approving re-enables it (from `Resubmitted`, `Declined`, or `Kyc Expired`) when the workflow has monitoring turned on. Approving or declining a child KYC that belongs to a KYB key-people check recomputes the parent business session's status. Set `send_email: true` (with `email_address`) to notify the user: a status notice for `Approved`/`Declined`, or a resubmission email containing the verification link and per-step reasons for `Resubmitted`. To check the email first, preview it with [Preview Decision Email](/sessions-api/preview-decision-email) (`Approved`, `Declined`) or [Preview Resubmission Email](/sessions-api/preview-resubmission) (`Resubmitted`) and pass its `preview_token` as `email_preview_token`: the update then sends exactly the previewed email, or returns `409` before changing anything when the email or the session changed since the preview or the receipt is older than 15 minutes.\n\nRepeating `Approved` or `Declined` on a session that already has that status changes nothing and returns `200`: no webhook, no activity entry and no email. Repeating `Resubmitted` returns `400` (`The new status is the same as the current status.`). Missing or invalid credentials and missing permissions return `403`; an expired scoped API key returns `401`.", + "operationId": "patch_v3_session_update_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session to update. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "new_status" + ], + "properties": { + "new_status": { + "type": "string", + "description": "Target status. `Approved` and `Declined` record a final manual decision (each can also overturn the other). `Resubmitted` clears the selected steps and sends the session back to the user. Any other value returns `400`.", + "enum": [ + "Approved", + "Declined", + "Resubmitted" + ], + "example": "Approved" + }, + "comment": { + "type": "string", + "description": "Free-text reason for the change, stored on the session's review trail and returned in the `reviews` array of [Get Decision](#get-/v3/session/-sessionId-/decision/). For example `Duplicated user`.", + "example": "All checks passed manual review" + }, + "nodes_to_resubmit": { + "type": "array", + "description": "Workflow steps the user must redo. Only acted on when `new_status` is `Resubmitted`. For `Approved`/`Declined` the entries are still schema-validated (an invalid `feature` value returns `400`) but schema-valid entries are semantically ignored. If omitted, the server auto-selects existing OCR, Liveness, Face Match, POA, Phone, Email, AML, Database Validation, and Questionnaire attempts whose status is `Declined`, `In Review`, `Not Finished`, or `Expired` (NFC, IP analysis, age estimation, face search, and KYB document attempts are never auto-selected; non-face-match face attempts are selected as `LIVENESS`) — features the user never attempted (no recorded attempt) are NOT selected, so a session with zero attempts returns `400` (\"No features found that need resubmission\") even though nothing was approved; pass `nodes_to_resubmit` explicitly in that case. Steps are executed in workflow-graph order regardless of the order you send them. `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and the `KYB` alias are rejected with `400` — those parent checks recompute from their child KYC sessions.", + "items": { + "type": "object", + "required": [ + "node_id", + "feature" + ], + "properties": { + "node_id": { + "type": "string", + "description": "Node identifier as it appears in the session's workflow definition (for example `feature_ocr`, `feature_liveness`).", + "example": "feature_ocr" + }, + "feature": { + "type": "string", + "description": "Feature type of the node. The aliases `ID_VERIFICATION`, `POA`, `PHONE`, and `EMAIL` are normalized server-side to `OCR`, `PROOF_OF_ADDRESS`, `PHONE_VERIFICATION`, and `EMAIL_VERIFICATION`. `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and `KYB` pass schema validation but are always rejected with the business-rule `400` shown in the examples.", + "enum": [ + "OCR", + "OCR_BACK", + "NFC", + "AML", + "FACE", + "LIVENESS", + "FACE_MATCH", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "PROOF_OF_ADDRESS", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "FACE_SEARCH", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_DOCUMENTS", + "ID_VERIFICATION", + "POA", + "PHONE", + "EMAIL", + "KYB_REGISTRY", + "KYB_KEY_PEOPLE", + "KYB", + "GEOLOCATION" + ], + "example": "OCR" + } + } + } + }, + "send_email": { + "type": "boolean", + "description": "Whether to email the user about the change. Requires `email_address`. For `Approved`/`Declined` the user receives a status notice; for `Resubmitted` the email includes the verification link and the per-step resubmission reasons.", + "default": false, + "example": false + }, + "email_address": { + "type": "string", + "format": "email", + "description": "Recipient for the notification email. **Required when `send_email` is `true`** — omitting it returns `400`.", + "example": "user@example.com" + }, + "email_language": { + "type": "string", + "description": "Language for the notification email. Accepts any string at schema level; unsupported codes silently fall back to English (`en`).", + "default": "en", + "example": "en" + }, + "email_preview_token": { + "type": "string", + "writeOnly": true, + "minLength": 1, + "maxLength": 512, + "description": "Optional receipt from [Preview Decision Email](/sessions-api/preview-decision-email) (for `Approved` and `Declined`) or [Preview Resubmission Email](/sessions-api/preview-resubmission) (for `Resubmitted`): its `preview_token`. Valid only with `send_email: true`; without it the request returns `400`. The recipient, language, session status, sender and email (and, for a resubmission, the selected steps) must still match the preview, and the receipt expires after 15 minutes. Otherwise the request returns `409` before the session changes or an email is sent. Existing integrations may omit this field." + } + } + }, + "examples": { + "Approve Session": { + "summary": "Approve a session after manual review", + "value": { + "new_status": "Approved", + "comment": "All checks passed manual review" + } + }, + "Decline Session": { + "summary": "Decline a session", + "value": { + "new_status": "Declined", + "comment": "Suspected fraud" + } + }, + "Resubmit Specific Steps": { + "summary": "Request resubmission of chosen steps, with email", + "value": { + "new_status": "Resubmitted", + "nodes_to_resubmit": [ + { + "node_id": "feature_ocr", + "feature": "OCR" + }, + { + "node_id": "feature_liveness", + "feature": "LIVENESS" + } + ], + "send_email": true, + "email_address": "user@example.com", + "email_language": "en" + } + }, + "Resubmit Auto-select": { + "summary": "Request resubmission (auto-select non-approved steps)", + "value": { + "new_status": "Resubmitted" + } + }, + "Approve With Previewed Email": { + "summary": "Approve and send the email returned by Preview Decision Email", + "value": { + "new_status": "Approved", + "send_email": true, + "email_address": "person@example.com", + "email_language": "en", + "email_preview_token": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Status updated. The response contains only the `session_id`; fetch the updated session via [Get Decision](#get-/v3/session/-sessionId-/decision/). The `status.updated` webhook fires once the change commits. Repeating `Approved` or `Declined` returns the same body and changes nothing.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the updated session." + } + } + }, + "examples": { + "Updated": { + "summary": "Returned for Approved, Declined, and Resubmitted alike", + "value": { + "session_id": "3472fb7c-8f7c-4d1a-9cf4-cf3d74ce1a60" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field errors arrive as `{\"\": [\"\"]}` (some business-rule rejections use a string value instead: `{\"nodes_to_resubmit\": \"\"}`), cross-state errors as `{\"detail\": \"\"}`, and the same-status error as a bare JSON array.", + "content": { + "application/json": { + "examples": { + "Wrong Current Status": { + "summary": "Session is not in an updatable state", + "value": { + "detail": "Only sessions with status Approved, Declined, In Review, Kyc Expired, Abandoned, Resubmitted can be updated. Current status: In Progress" + } + }, + "Invalid Status Value": { + "summary": "new_status is a real session status but not one of Approved/Declined/Resubmitted", + "value": { + "new_status": [ + "You can only update the status to 'Approved', 'Declined', or 'Resubmitted'." + ] + } + }, + "Missing new_status": { + "summary": "Required field omitted", + "value": { + "new_status": [ + "This field is required." + ] + } + }, + "Same Status": { + "summary": "Resubmitted requested on a session that is already Resubmitted", + "value": [ + "The new status is the same as the current status." + ] + }, + "Email Address Required": { + "summary": "send_email true without email_address", + "value": { + "email_address": [ + "Email address is required when send_email is true." + ] + } + }, + "Non-resubmittable Feature": { + "summary": "Resubmit targets a KYB parent check", + "value": { + "nodes_to_resubmit": "The following features cannot be resubmitted directly: KYB_REGISTRY. Resubmit the relevant child KYC sessions instead; the parent will recompute automatically." + } + }, + "Nothing To Resubmit": { + "summary": "Auto-select found no non-approved features", + "value": { + "nodes_to_resubmit": "No features found that need resubmission. All features are already approved." + } + }, + "Invalid Feature Choice": { + "summary": "Unknown feature in nodes_to_resubmit", + "value": { + "nodes_to_resubmit": [ + { + "feature": [ + "\"BOGUS\" is not a valid choice. Valid choices are: OCR, OCR_BACK, NFC, AML, FACE, LIVENESS, FACE_MATCH, IP_ANALYSIS, AGE_ESTIMATION, PROOF_OF_ADDRESS, PHONE_VERIFICATION, EMAIL_VERIFICATION, FACE_SEARCH, DATABASE_VALIDATION, QUESTIONNAIRE, KYB_REGISTRY, KYB_DOCUMENTS, KYB_KEY_PEOPLE, ID_VERIFICATION, POA, PHONE, EMAIL, KYB" + ] + } + ] + } + }, + "Invalid Status Value (not a status at all)": { + "summary": "new_status is not a valid StatusChoices value", + "value": { + "new_status": [ + "\"Bogus\" is not a valid choice." + ] + } + }, + "Preview Receipt Without Email": { + "summary": "email_preview_token sent without send_email: true", + "value": { + "email_preview_token": [ + "A preview receipt is only valid when the email is sent." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "API key lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists (deleted sessions also return `404`).", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "A resubmit targeting only backend-only features (AML, DATABASE_VALIDATION, IP_ANALYSIS) was repeated within the 30-second cooldown window; an immediate repeat cannot change the outcome. Back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Backend-only resubmit throttled": { + "value": { + "detail": "This session was already resubmitted for backend-only features within the last 30 seconds; repeating it cannot change the outcome. Wait before resubmitting again." + } + } + } + } + } + }, + "409": { + "description": "The email preview changed or expired. Generate a new preview before confirming; no session changes or email delivery occur.", + "content": { + "application/json": { + "examples": { + "Preview Changed": { + "summary": "The receipt no longer matches, or is older than 15 minutes", + "value": { + "detail": "This email preview changed or expired. Preview the message again before confirming." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"new_status\": \"Approved\",\n \"comment\": \"All checks passed manual review\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_status(\n \"11111111-2222-3333-4444-555555555555\",\n new_status=\"Approved\",\n comment=\"All checks passed manual review\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateStatus(\"11111111-2222-3333-4444-555555555555\", {\n new_status: \"Approved\",\n comment: \"All checks passed manual review\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateStatus(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsUpdateStatusParams{\n\tNewStatus: \"Approved\",\n\tComment: didit.String(\"All checks passed manual review\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsUpdateStatusParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateStatus(\"11111111-2222-3333-4444-555555555555\", SessionsUpdateStatusParams.builder()\n .newStatus(SessionsUpdateStatusParams.NewStatus.of(\"Approved\"))\n .comment(\"All checks passed manual review\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateStatus('11111111-2222-3333-4444-555555555555', [\n 'new_status' => 'Approved',\n 'comment' => 'All checks passed manual review',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_status(\n \"11111111-2222-3333-4444-555555555555\",\n new_status: \"Approved\",\n comment: \"All checks passed manual review\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateStatusAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsUpdateStatusParams\n{\n NewStatus = \"Approved\",\n Comment = \"All checks passed manual review\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/id-verification/": { + "post": { + "summary": "ID Verification (document OCR + fraud checks)", + "description": "OCR an identity document and run document fraud checks in one call — returns the extracted holder fields, the parsed MRZ, an `Approved`/`Declined`/`In Review` `status`, and a `warnings` list explaining every issue found. Supports 14,000+ document types from 220+ countries and territories.\n\n**How it works.** Send the document's `front_image` (required) and, for two-sided documents, the `back_image`. Upload uncropped photos with all four corners of the document visible — the service detects, aligns, and crops the document itself, so do not pre-crop. The document is classified automatically (you never declare the country or document type), the visual zone is read with OCR, and the MRZ and any barcodes are decoded. Cross-checks then validate dates, number formats, and MRZ check digits, and compare the visual zone against the MRZ. With `perform_document_liveness=true`, the images are additionally screened for screen replays, printed copies, and portrait manipulation.\n\n**Decision logic.** `status` is `Approved` unless at least one warning resolves to a decline. Fraud and hard-failure risks always decline: `DOCUMENT_EXPIRED`, `SCREEN_CAPTURE_DETECTED`, `PRINTED_COPY_DETECTED`, `PORTRAIT_MANIPULATION_DETECTED`, `PUBLIC_DOCUMENT_IMAGE_DETECTED`, plus extraction failures (`NAME_NOT_DETECTED`, `DATE_OF_BIRTH_NOT_DETECTED`, `DOCUMENT_NUMBER_NOT_DETECTED`). (`PORTRAIT_IMAGE_NOT_DETECTED`, `INVALID_DATE`, and `MRZ_NOT_DETECTED` exist in workflow sessions but are never produced by this standalone endpoint. `COULD_NOT_DETECT_DOCUMENT_TYPE` is returned here when the front image does not match a known official document design with high confidence, and declines the request.) Three risk groups are configurable per request via `invalid_mrz_action`, `inconsistent_data_action`, and `expiration_date_not_detected_action` (`DECLINE` or `NO_ACTION`). All other warnings (e.g. `POSSIBLE_DUPLICATED_USER`) are informational and never decline on their own. When `perform_document_liveness=true` (the default) and the forgery check returns no usable score — the provider failed, answered non-200, or answered HTTP 200 with `status=\"ERR\"` - the call returns `200` with `status: \"In Review\"` and a `DOCUMENT_LIVENESS_NOT_COMPUTED` warning (`log_type: \"warning\"`) instead of `Approved`: a check that did not run is not evidence that the document is genuine. This escalation is not configurable and never overrides a `Declined`; with `perform_document_liveness=false` the check is skipped and the status follows the remaining warnings as before. A document that cannot be processed at all returns `400` with `{\"error\": \"COULD_NOT_RECOGNIZE_DOCUMENT\"}`; a readable but problematic document returns `200` with `status: \"Declined\"` — always inspect `id_verification.status` and `id_verification.warnings`, not just the HTTP code.\n\n**Billing.** Each `200` response consumes one ID Verification API credit (paid from your plan credit or top-up balance). When the organization's balance cannot cover the call, the endpoint returns `403` with the not-enough-credits error before any image processing.\n\n**Session persistence (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session: it appears in the Business Console, the returned `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/`, the cropped document/portrait images are stored and returned as short-lived media URLs (`https:///ocr/...`), and a `status.updated` webhook is emitted to your configured webhook endpoints. When `false`, nothing is stored, `request_id` is a one-off correlation UUID, and `portrait_image`/`front_image`/`back_image` are returned inline as base64-encoded JPEG strings.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload (a fictional \"Sandbox User\" USA identity card), no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3id-verification", + "x-didit-sdk": { + "resource": "id_verification", + "method": "verify", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "front_image" + ], + "properties": { + "front_image": { + "type": "string", + "format": "binary", + "description": "Front side of the identity document. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp`, `pdf`. Maximum upload size: **10 MB** (larger files are rejected with `400`). PDFs are rendered to an image before OCR; encrypted PDFs require `front_image_password`. Images are automatically compressed to ~0.5 MB and EXIF orientation is applied. Upload the full, uncropped capture with all four corners of the document visible." + }, + "front_image_password": { + "type": "string", + "description": "Password to decrypt `front_image` when it is an encrypted PDF. Sending an encrypted PDF without (or with a wrong) password returns `400`.", + "writeOnly": true + }, + "back_image": { + "type": "string", + "format": "binary", + "description": "Back side of the document — send it whenever the document has one (many ID cards carry the MRZ or a barcode on the back; omitting it simply leaves the MRZ and back-side fields empty in the response — this endpoint does not raise a missing-MRZ warning). Same format and size limits as `front_image`. When omitted, `back_image` and back-side fields are `null` in the response." + }, + "back_image_password": { + "type": "string", + "description": "Password to decrypt `back_image` when it is an encrypted PDF.", + "writeOnly": true + }, + "perform_document_liveness": { + "type": "boolean", + "default": true, + "description": "Screens both images for presentation fraud: screen replays (`SCREEN_CAPTURE_DETECTED`), printed copies (`PRINTED_COPY_DETECTED`), and portrait manipulation (`PORTRAIT_MANIPULATION_DETECTED`). Any of these auto-declines. Enabled by default; set to `false` to skip fraud screening and reduce latency. When enabled and the provider returns no usable score, the response is `In Review` with a `DOCUMENT_LIVENESS_NOT_COMPUTED` warning rather than `Approved`.", + "example": true + }, + "minimum_age": { + "type": "integer", + "nullable": true, + "default": null, + "minimum": 1, + "maximum": 120, + "description": "Accepted and validated (1–120) but **currently not applied** by this endpoint — it never produces a `MINIMUM_AGE_NOT_MET` warning from this field. Enforce age rules from the returned `date_of_birth`/`age`, or use a verification-session workflow with age restrictions." + }, + "expiration_date_not_detected_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when no expiration date can be read (`EXPIRATION_DATE_NOT_DETECTED`). Default `NO_ACTION` because many identity documents print no expiry date. Note: a *detected and past* expiry date always declines (`DOCUMENT_EXPIRED`), regardless of this option." + }, + "invalid_mrz_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "DECLINE", + "description": "What to do when the extracted MRZ fails check-digit validation (`MRZ_VALIDATION_FAILED`). A missing MRZ raises no warning on this endpoint. Only relevant for documents that carry an MRZ. `REVIEW` is not supported on this endpoint and returns `400`." + }, + "inconsistent_data_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "DECLINE", + "description": "What to do when extracted data is internally inconsistent: `DATA_INCONSISTENT`, `MRZ_AND_DATA_EXTRACTED_FROM_OCR_NOT_SAME` (visual zone disagrees with the MRZ), or `DOCUMENT_NAME_DIFFERENT_FROM_OTHER_APPROVED_DOCUMENTS`." + }, + "critical_field_occlusion_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "DECLINE", + "description": "What to do when a critical printed field was physically covered on the capture (`CRITICAL_FIELD_OCCLUSION_DETECTED`) - an identity number, a name, the date of birth, or the expiry date. Declines by default, like this endpoint's other document-integrity risks: covering the numbers a report is reconciled against is what a redacted document looks like. The value read for a covered field is dropped from the extraction either way, so the matching `*_NOT_DETECTED` warning usually accompanies it. `REVIEW` is not supported on this endpoint and returns `400`." + }, + "document_or_personal_number_format_mismatch_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when a number does not match the format its issuing authority uses for that document type (`DOCUMENT_NUMBER_FORMAT_MISMATCH`, `PERSONAL_NUMBER_FORMAT_MISMATCH`). Default `NO_ACTION`, which reports the warning without changing the status. `REVIEW` is not supported on this endpoint and returns `400`." + }, + "preferred_characters": { + "type": "string", + "enum": [ + "latin", + "non_latin" + ], + "default": "latin", + "description": "Preferred script for name/address fields on documents that print both Latin and non-Latin text (Arabic, Cyrillic, CJK, …). `latin` returns transliterated/Latin values; `non_latin` prefers the native script." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, announced through a `status.updated` webhook, and with document images returned as media URLs. When `false`, nothing is stored, `request_id` is a transient UUID, and images are returned inline as base64 JPEG strings.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"onboarding\"}`) — it is parsed into an object.", + "example": { + "flow": "onboarding" + } + } + } + }, + "example": { + "front_image": "(binary JPEG/PNG/PDF of the document front)", + "back_image": "(binary JPEG/PNG/PDF of the document back)", + "perform_document_liveness": true, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "onboarding" + } + } + } + } + }, + "responses": { + "200": { + "description": "Document processed. `id_verification.status` is `Approved`, `Declined`, or `In Review`; every detected issue is itemized in `id_verification.warnings`. `In Review` is returned when a requested document-liveness check produced no usable result (`DOCUMENT_LIVENESS_NOT_COMPUTED`). A problematic document still returns `200` with `status: \"Declined\"` — inspect the body, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id and image fields are short-lived media URLs; with `save_api_request=false` they are inline base64 JPEG strings.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Two-sided ID card read cleanly (saved — images as media URLs)", + "value": { + "request_id": "11d219ed-d59c-4b1d-8d65-9b933f12d5b8", + "id_verification": { + "status": "Approved", + "document_type": "Identity Card", + "document_subtype": "ID_CARD_GENERIC", + "document_number": "CBX164224", + "personal_number": "20446581H", + "portrait_image": "https:///ocr/11d219ed-d59c-4b1d-8d65-9b933f12d5b8-portrait_image-8c2f.jpg?signature=...", + "front_image": "https:///ocr/11d219ed-d59c-4b1d-8d65-9b933f12d5b8-front_image-4a1e.jpg?signature=...", + "back_image": "https:///ocr/11d219ed-d59c-4b1d-8d65-9b933f12d5b8-back_image-77b0.jpg?signature=...", + "front_image_camera_front": null, + "back_image_camera_front": null, + "front_image_camera_front_face_match_score": null, + "back_image_camera_front_face_match_score": null, + "front_image_quality_score": { + "focus_score": 78.0, + "brightness_score": 83.2, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 40.7, + "overall_score": 70.2 + }, + "back_image_quality_score": { + "focus_score": 100.0, + "brightness_score": 95.6, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 43.0, + "overall_score": 84.4 + }, + "date_of_birth": "1980-01-13", + "age": 46, + "expiration_date": "2032-03-31", + "date_of_issue": "2022-03-31", + "issuing_state": "ESP", + "issuing_state_name": "Spain", + "first_name": "Julio Francisco", + "last_name": "Fores Sena", + "full_name": "Julio Francisco Fores Sena", + "gender": "M", + "address": "Brda. Urb. El Cardonal 0052 52 Po3 B,Taco,San Cristobal De La Laguna,Santa Cruz De Tenerife", + "formatted_address": "Av. el Cardonal, 52, b, 38108 La Laguna, Santa Cruz de Tenerife, Spain", + "place_of_birth": "Valencia, Valencia", + "marital_status": "UNKNOWN", + "nationality": "ESP", + "extra_fields": { + "first_surname": "Fores", + "second_surname": "Sena" + }, + "mrz": { + "surname": "FORES SENA", + "name": "JULIO FRANCISCO", + "country": "ESP", + "nationality": "ESP", + "birth_date": "800113", + "expiry_date": "320331", + "sex": "M", + "document_type": "ID", + "document_number": "CBX164224", + "optional_data": "20446581H", + "optional_data_2": "", + "birth_date_hash": "9", + "expiry_date_hash": "8", + "document_number_hash": "3", + "final_hash": "3", + "personal_number": "20446581H", + "warnings": [], + "errors": [], + "mrz_type": "TD1", + "mrz_string": "IDESPCBX164224320446581H<<<<<<\n8001139M3203318ESP<<<<<<<<<<<3\nFORES/ocr/f61681b8-9422-4378-ac3e-04c6a247fb45-portrait_image-1d9a.jpg?signature=...", + "front_image": "https:///ocr/f61681b8-9422-4378-ac3e-04c6a247fb45-front_image-5c2b.jpg?signature=...", + "back_image": null, + "front_image_camera_front": null, + "back_image_camera_front": null, + "front_image_camera_front_face_match_score": null, + "back_image_camera_front_face_match_score": null, + "front_image_quality_score": { + "focus_score": 100.0, + "brightness_score": 95.7, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 22.2, + "overall_score": 79.3 + }, + "back_image_quality_score": null, + "date_of_birth": "1997-01-30", + "age": 29, + "expiration_date": "2025-11-26", + "date_of_issue": null, + "issuing_state": "ESP", + "issuing_state_name": "Spain", + "first_name": "Lucia", + "last_name": "Marin Castro", + "full_name": "Lucia Marin Castro", + "gender": "F", + "address": null, + "formatted_address": null, + "place_of_birth": null, + "marital_status": "UNKNOWN", + "nationality": "ESP", + "extra_fields": { + "first_surname": "Marin", + "second_surname": "Castro" + }, + "mrz": {}, + "parsed_address": null, + "warnings": [ + { + "risk": "DOCUMENT_EXPIRED", + "feature": "ID_VERIFICATION", + "additional_data": null, + "log_type": "error", + "short_description": "Document expired", + "long_description": "The document's expiration date has passed, rendering it no longer valid for use." + } + ], + "barcodes": [] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:25:48.117204+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "id_verification": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "description": "`Approved` when no warning resolves to a decline and the requested document-liveness check produced a usable result; `In Review` when that requested check produced no usable result (`DOCUMENT_LIVENESS_NOT_COMPUTED`); `Declined` when a warning resolves to a decline — the `warnings` list explains why." + }, + "document_type": { + "type": "string", + "nullable": true, + "description": "Detected document type display name: `Identity Card`, `Passport`, `Driver's License`, `Residence Permit`, `Health Insurance Card`, or `Tax Card`. `null` when the document could not be classified.", + "example": "Identity Card" + }, + "document_subtype": { + "type": "string", + "nullable": true, + "description": "Finer-grained subtype when available (e.g. `ID_CARD_GENERIC`, `EPASSPORT`).", + "example": "ID_CARD_GENERIC" + }, + "document_number": { + "type": "string", + "nullable": true, + "example": "CBX164224" + }, + "personal_number": { + "type": "string", + "nullable": true, + "description": "Secondary personal/national number printed on some documents.", + "example": "20446581H" + }, + "portrait_image": { + "type": "string", + "nullable": true, + "description": "Cropped portrait photo from the document. A short-lived media URL (`https:///ocr/...`) when `save_api_request=true` (default); an inline base64-encoded JPEG string when `save_api_request=false`. `null` when no portrait was detected." + }, + "front_image": { + "type": "string", + "nullable": true, + "description": "Aligned crop of the document front. Media URL when saved, inline base64 JPEG otherwise." + }, + "back_image": { + "type": "string", + "nullable": true, + "description": "Aligned crop of the document back. `null` when no `back_image` was uploaded." + }, + "front_image_camera_front": { + "type": "string", + "nullable": true, + "description": "Always `null` on this endpoint (populated only in camera-capture verification sessions)." + }, + "back_image_camera_front": { + "type": "string", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "front_image_camera_front_face_match_score": { + "type": "number", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "back_image_camera_front_face_match_score": { + "type": "number", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "front_image_quality_score": { + "type": "object", + "nullable": true, + "description": "Capture-quality diagnostics for the uploaded image (`null` when the side was not provided or scoring failed). All scores are 0–100.", + "properties": { + "focus_score": { + "type": "number", + "format": "float", + "description": "Sharpness of the document crop (higher is sharper)." + }, + "brightness_score": { + "type": "number", + "format": "float", + "description": "Exposure quality (higher is better)." + }, + "brightness_issue": { + "type": "string", + "nullable": true, + "description": "`ok` when exposure is fine, otherwise the detected issue (e.g. too dark / too bright)." + }, + "is_document_fully_visible": { + "type": "boolean", + "nullable": true, + "description": "Whether all four corners of the document are inside the frame." + }, + "resolution_score": { + "type": "number", + "format": "float", + "description": "Effective resolution of the document area." + }, + "overall_score": { + "type": "number", + "format": "float", + "description": "Aggregate quality score." + } + } + }, + "back_image_quality_score": { + "type": "object", + "nullable": true, + "description": "Capture-quality diagnostics for the uploaded image (`null` when the side was not provided or scoring failed). All scores are 0–100.", + "properties": { + "focus_score": { + "type": "number", + "format": "float", + "description": "Sharpness of the document crop (higher is sharper)." + }, + "brightness_score": { + "type": "number", + "format": "float", + "description": "Exposure quality (higher is better)." + }, + "brightness_issue": { + "type": "string", + "nullable": true, + "description": "`ok` when exposure is fine, otherwise the detected issue (e.g. too dark / too bright)." + }, + "is_document_fully_visible": { + "type": "boolean", + "nullable": true, + "description": "Whether all four corners of the document are inside the frame." + }, + "resolution_score": { + "type": "number", + "format": "float", + "description": "Effective resolution of the document area." + }, + "overall_score": { + "type": "number", + "format": "float", + "description": "Aggregate quality score." + } + } + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "example": "1980-01-13" + }, + "age": { + "type": "integer", + "nullable": true, + "description": "Holder age in years, computed from `date_of_birth`.", + "example": 46 + }, + "expiration_date": { + "type": "string", + "format": "date", + "nullable": true, + "example": "2032-03-31" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "nullable": true, + "example": "2022-03-31" + }, + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Issuing country (ISO 3166-1 alpha-3).", + "example": "ESP" + }, + "issuing_state_name": { + "type": "string", + "nullable": true, + "example": "Spain" + }, + "first_name": { + "type": "string", + "nullable": true, + "example": "Julio Francisco" + }, + "last_name": { + "type": "string", + "nullable": true, + "example": "Fores Sena" + }, + "full_name": { + "type": "string", + "nullable": true, + "example": "Julio Francisco Fores Sena" + }, + "gender": { + "type": "string", + "nullable": true, + "description": "`M`, `F`, or `null` when not printed/detected.", + "example": "M" + }, + "address": { + "type": "string", + "nullable": true, + "description": "Address exactly as printed on the document." + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "Geocoded, normalized version of `address`." + }, + "place_of_birth": { + "type": "string", + "nullable": true, + "example": "Valencia, Valencia" + }, + "marital_status": { + "type": "string", + "nullable": true, + "description": "`UNKNOWN` unless the document prints marital status.", + "example": "UNKNOWN" + }, + "nationality": { + "type": "string", + "nullable": true, + "description": "Holder nationality (ISO 3166-1 alpha-3).", + "example": "ESP" + }, + "extra_fields": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Document-specific extras that have no dedicated column (e.g. `first_surname`/`second_surname` on Spanish IDs, license classes on driving licenses)." + }, + "mrz": { + "type": "object", + "description": "Parsed machine-readable zone. An **empty object** (`{}`) when the document has no MRZ or it could not be read — also check the `warnings` list in that case. Dates inside the MRZ use the raw `YYMMDD` format.", + "properties": { + "surname": { + "type": "string", + "example": "FORES SENA" + }, + "name": { + "type": "string", + "example": "JULIO FRANCISCO" + }, + "country": { + "type": "string", + "description": "Issuing country (ISO 3166-1 alpha-3).", + "example": "ESP" + }, + "nationality": { + "type": "string", + "description": "Holder nationality (ISO 3166-1 alpha-3).", + "example": "ESP" + }, + "birth_date": { + "type": "string", + "description": "Raw MRZ date of birth (`YYMMDD`).", + "example": "800113" + }, + "expiry_date": { + "type": "string", + "description": "Raw MRZ expiry date (`YYMMDD`).", + "example": "320331" + }, + "sex": { + "type": "string", + "example": "M" + }, + "document_type": { + "type": "string", + "description": "MRZ document-type code (e.g. `ID`, `P`).", + "example": "ID" + }, + "document_number": { + "type": "string", + "example": "CBX164224" + }, + "optional_data": { + "type": "string", + "nullable": true, + "example": "20446581H" + }, + "optional_data_2": { + "type": "string", + "nullable": true, + "example": "" + }, + "birth_date_hash": { + "type": "string", + "description": "MRZ check digit for the birth date.", + "example": "9" + }, + "expiry_date_hash": { + "type": "string", + "description": "MRZ check digit for the expiry date.", + "example": "8" + }, + "document_number_hash": { + "type": "string", + "description": "MRZ check digit for the document number.", + "example": "3" + }, + "final_hash": { + "type": "string", + "description": "Composite MRZ check digit.", + "example": "3" + }, + "personal_number": { + "type": "string", + "nullable": true, + "example": "20446581H" + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Non-fatal MRZ parsing warnings." + }, + "errors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "MRZ check-digit/parsing errors. Non-empty errors surface as an `MRZ_VALIDATION_FAILED` warning." + }, + "mrz_type": { + "type": "string", + "description": "MRZ layout: `TD1` (ID cards, 3 lines), `TD2`, or `TD3` (passports, 2 lines).", + "example": "TD1" + }, + "mrz_string": { + "type": "string", + "description": "Raw MRZ text as read, lines separated by `\\n`." + }, + "mrz_key": { + "type": "string", + "description": "Composite key (document number + check digits) used for cross-checks." + } + } + }, + "parsed_address": { + "type": "object", + "nullable": true, + "description": "Structured, geocoded breakdown of `address`. `null` when the document carries no address or it could not be parsed.", + "properties": { + "street_1": { + "type": "string", + "nullable": true, + "example": "Avenida el Cardonal 52" + }, + "street_2": { + "type": "string", + "nullable": true, + "example": "b" + }, + "city": { + "type": "string", + "nullable": true, + "example": "La Laguna" + }, + "region": { + "type": "string", + "nullable": true, + "example": "Canarias" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code.", + "example": "ES" + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "38108" + }, + "address_type": { + "type": "string", + "nullable": true, + "example": "Avenida" + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "Canonical geocoded address string.", + "example": "Av. el Cardonal, 52, b, 38108 La Laguna, Santa Cruz de Tenerife, Spain" + }, + "raw_results": { + "type": "object", + "additionalProperties": true, + "description": "Raw geocoding payload (`address_components`, `geometry.location`, `place_id`, …) for advanced consumers." + }, + "document_location": { + "type": "object", + "nullable": true, + "properties": { + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + } + }, + "description": "Geocoded coordinates of the document address." + }, + "label": { + "type": "string", + "nullable": true, + "description": "Which document field the address was read from.", + "example": "Spain Identity Card Address" + }, + "is_verified": { + "type": "boolean", + "description": "Whether geocoding confirmed the address exists." + }, + "category": { + "type": "string", + "nullable": true, + "description": "Address category (e.g. `Residential`, `Commercial`).", + "example": "Residential" + } + } + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. Every entry explains one detected risk; entries with `log_type: \"error\"` set `status` to `Declined`, and `DOCUMENT_LIVENESS_NOT_COMPUTED` (`log_type: \"warning\"`) sets it to `In Review`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "description": "Machine-readable risk code. Always-decline risks: `DOCUMENT_EXPIRED`, `MINIMUM_AGE_NOT_MET`, `PORTRAIT_IMAGE_NOT_DETECTED`, `SCREEN_CAPTURE_DETECTED`, `PRINTED_COPY_DETECTED`, `PORTRAIT_MANIPULATION_DETECTED`, `PUBLIC_DOCUMENT_IMAGE_DETECTED`, `INVALID_DATE`, `COULD_NOT_RECOGNIZE_DOCUMENT` (400-only, never a warning), `DOCUMENT_NUMBER_NOT_DETECTED`, `COULD_NOT_DETECT_DOCUMENT_TYPE`, `NAME_NOT_DETECTED`, `DATE_OF_BIRTH_NOT_DETECTED`. Governed by request options: `MRZ_NOT_DETECTED` / `MRZ_VALIDATION_FAILED` (`invalid_mrz_action`), `DATA_INCONSISTENT` / `MRZ_AND_DATA_EXTRACTED_FROM_OCR_NOT_SAME` / `DOCUMENT_NAME_DIFFERENT_FROM_OTHER_APPROVED_DOCUMENTS` (`inconsistent_data_action`), `EXPIRATION_DATE_NOT_DETECTED` (`expiration_date_not_detected_action`). Other risks (e.g. `POSSIBLE_DUPLICATED_USER`) are informational. `DOCUMENT_LIVENESS_NOT_COMPUTED` is raised when `perform_document_liveness=true` but the forgery check returned no usable score; it sets `status` to `In Review`. `COULD_NOT_DETECT_DOCUMENT_TYPE` is also returned when the front of the document does not match a known official document design with high confidence, for example an edited or computer-generated document, or a design that is not supported yet.", + "example": "DOCUMENT_EXPIRED" + }, + "feature": { + "type": "string", + "enum": [ + "ID_VERIFICATION" + ], + "description": "Feature that raised the warning. Always `ID_VERIFICATION` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Extra context for some risks (e.g. `POSSIBLE_DUPLICATED_USER` includes `duplicated_session_id`); `null` for most." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `warning` (only `DOCUMENT_LIVENESS_NOT_COMPUTED`, when the requested forgery check produced no usable score) drives `status` to `In Review`; `information` entries are advisory and never change the status." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "barcodes": { + "type": "array", + "description": "Barcodes decoded from the document (PDF417, QR, …). Empty when the document has none or none could be read.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Barcode type (e.g. `PDF417`, `QR_CODE`, `UNKNOWN`)." + }, + "position": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Location of the barcode in the image, when available." + }, + "data": { + "type": "string", + "description": "Decoded payload." + }, + "data_raw": { + "type": "string", + "description": "Raw (undecoded) payload, when different." + }, + "side": { + "type": "string", + "nullable": true, + "description": "`front` or `back`." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated." + } + } + } + } + } + }, + "400": { + "description": "Validation or processing error. Field-level problems return DRF's standard envelope (one array of messages per offending field). Encrypted-PDF problems return `{\"detail\": ...}`. A document image in which no document can be recognized at all returns `{\"error\": \"COULD_NOT_RECOGNIZE_DOCUMENT\"}`. PDF uploads can additionally fail with \"The submitted PDF file is corrupted or invalid.\" or a no-pages error.", + "content": { + "application/json": { + "examples": { + "Missing front image": { + "summary": "`front_image` not included in the form data", + "value": { + "front_image": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp/pdf", + "value": { + "front_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp, pdf." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 10 MB limit", + "value": { + "front_image": [ + "File size should not exceed 10 MB" + ] + } + }, + "Invalid option value": { + "summary": "Options only accept DECLINE or NO_ACTION", + "value": { + "invalid_mrz_action": [ + "\"REVIEW\" is not a valid choice." + ] + } + }, + "Encrypted PDF": { + "summary": "PDF uploaded without the required password", + "value": { + "detail": "The PDF is encrypted. Please upload a decrypted PDF or a photo instead." + } + }, + "Wrong PDF password": { + "summary": "`front_image_password` / `back_image_password` does not decrypt the PDF", + "value": { + "detail": "The PDF password is incorrect. Please provide the correct password." + } + }, + "Document not recognized": { + "summary": "No identity document could be detected in the image", + "value": { + "error": "COULD_NOT_RECOGNIZE_DOCUMENT" + } + }, + "PDF conversion failure": { + "summary": "Uploaded PDF could not be converted to an image", + "value": { + "error": "Failed to convert front image PDF." + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/id-verification/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'front_image=@./id_front.jpg' \\\n -F 'back_image=@./id_back.jpg' \\\n -F 'perform_document_liveness=true' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_verification.verify(\n front_image=open(\"front_image.jpg\", \"rb\"),\n back_image=open(\"back_image.jpg\", \"rb\"),\n perform_document_liveness=True,\n save_api_request=True,\n vendor_data=\"user-123\",\n metadata={\"flow\": \"onboarding\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idVerification.verify({\n front_image: await fileFromPath(\"front_image.jpg\"),\n back_image: await fileFromPath(\"back_image.jpg\"),\n perform_document_liveness: true,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"onboarding\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDVerification.Verify(ctx, didit.IDVerificationVerifyParams{\n\tFrontImage: didit.FileFromPath(\"front_image.jpg\"),\n\tBackImage: didit.Some(didit.FileFromPath(\"back_image.jpg\")),\n\tPerformDocumentLiveness: didit.Bool(true),\n\tSaveAPIRequest: didit.Bool(true),\n\tVendorData: didit.String(\"user-123\"),\n\tMetadata: didit.Some(map[string]any{\"flow\": \"onboarding\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.IdVerificationVerifyParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idVerification().verify(IdVerificationVerifyParams.builder()\n .frontImage(Path.of(\"front_image.jpg\"))\n .backImage(Path.of(\"back_image.jpg\"))\n .performDocumentLiveness(true)\n .saveApiRequest(true)\n .vendorData(\"user-123\")\n .metadata(Map.of(\"flow\", \"onboarding\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idVerification->verify([\n 'front_image' => File::fromPath('front_image.jpg'),\n 'back_image' => File::fromPath('back_image.jpg'),\n 'perform_document_liveness' => true,\n 'save_api_request' => true,\n 'vendor_data' => 'user-123',\n 'metadata' => ['flow' => 'onboarding'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_verification.verify(\n front_image: Pathname(\"front_image.jpg\"),\n back_image: Pathname(\"back_image.jpg\"),\n perform_document_liveness: true,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"onboarding\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdVerification.VerifyAsync(new IdVerificationVerifyParams\n{\n FrontImage = FileUpload.FromPath(\"front_image.jpg\"),\n BackImage = FileUpload.FromPath(\"back_image.jpg\"),\n PerformDocumentLiveness = true,\n SaveApiRequest = true,\n VendorData = \"user-123\",\n Metadata = new Dictionary { [\"flow\"] = \"onboarding\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/face-match/": { + "post": { + "summary": "Face Match", + "description": "Compare two face images (1:1) and get back a similarity `score` (0–100) plus an `Approved`/`Declined` `status`.\n\n**How it works.** Both images are decoded and analyzed by a biometric model (set `rotate_image=true` if captures may be sideways or upside down), then a similarity score between the two faces is computed. A score **strictly above** `face_match_score_decline_threshold` (default `30`) returns `status: \"Approved\"`; a score at or below it returns `Declined` with a `LOW_FACE_MATCH_SIMILARITY` warning. If no face pair can be scored at all, the call still returns `200` with `status: \"Declined\"` and a `NO_REFERENCE_IMAGE` warning — `score` is `null` on the default save path, or `0` with `save_api_request=false`. Submit one clear, front-facing face per image for best results.\n\n**Billing.** Each `200` response consumes one Face Match API credit (paid from your plan credit or top-up balance). When the organization's balance cannot cover the call, the endpoint returns `403` with the not-enough-credits error before any image processing.\n\n**Session persistence (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session: it appears in the Business Console, the returned `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/`, both images are stored as the session's face-match source/target images, and a `status.updated` webhook is emitted to your configured webhook endpoints. When `false`, nothing is stored and `request_id` is a one-off correlation UUID that cannot be looked up later.\n\n**Face search index.** This endpoint never enrolls faces into your face search index and never runs blocklist or duplicate screening — use `POST /v3/passive-liveness/` or `POST /v3/face-search/` for those.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload, no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3face-match", + "x-didit-sdk": { + "resource": "face_match", + "method": "compare", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "user_image", + "ref_image" + ], + "properties": { + "user_image": { + "type": "string", + "format": "binary", + "description": "Live or candidate face image to verify (e.g. a selfie). Submit a single front-facing photo with the subject's face clearly visible and well lit. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp`. Maximum upload size: **5 MB** (larger files are rejected with `400`). Images are automatically compressed to ~0.5 MB before processing, so very high resolutions do not improve accuracy." + }, + "ref_image": { + "type": "string", + "format": "binary", + "description": "Reference face image to compare against (e.g. an ID document portrait or a previously enrolled photo). Same format and size limits as `user_image`." + }, + "face_match_score_decline_threshold": { + "type": "number", + "format": "float", + "default": 30, + "minimum": 0, + "maximum": 100, + "description": "Similarity threshold (0–100) that drives the response `status`. A computed score **at or below** this value sets `status` to `Declined` and adds a `LOW_FACE_MATCH_SIMILARITY` warning; a score strictly above it returns `Approved`. Default `30` is permissive — raise it (e.g. `50`–`60`) for stricter verification. Values outside 0–100 return `400`.", + "example": 50 + }, + "rotate_image": { + "type": "boolean", + "default": false, + "description": "When `true`, the service tries the input images in 90-degree increments (0, 90, 180, 270) and uses the orientation that yields the best face detection — useful for mobile captures with missing EXIF orientation. Adds latency, so only enable it if you cannot guarantee upright images.", + "example": false + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, and announced through a `status.updated` webhook. When `false`, nothing is stored and `request_id` is a transient UUID for response correlation only.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"login\"}`) — it is parsed into an object.", + "example": { + "flow": "login", + "device_id": "abc123" + } + } + } + }, + "example": { + "user_image": "(binary JPEG/PNG of the live selfie)", + "ref_image": "(binary JPEG/PNG of the reference photo)", + "face_match_score_decline_threshold": 50, + "rotate_image": false, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "login" + } + } + } + } + }, + "responses": { + "200": { + "description": "Face match computed. `face_match.score` is a 0–100 float (two decimals); `status` is `Approved` when the score is strictly above `face_match_score_decline_threshold`, otherwise `Declined` with an explanatory warning. A low similarity or unscorable pair still returns `200` — inspect `face_match.status` and `face_match.warnings`, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Same person — score above the threshold", + "value": { + "request_id": "f9f5a777-8002-44c6-baaf-128bcfd6e226", + "face_match": { + "status": "Approved", + "score": 99.41, + "user_image": { + "entities": [ + { + "bbox": [ + 661, + 728, + 1688, + 2188 + ], + "confidence": 0.732973, + "age": 26.91, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "ref_image": { + "entities": [ + { + "bbox": [ + 653, + 745, + 1691, + 2185 + ], + "confidence": 0.722082, + "age": 27.0, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": { + "flow": "login" + }, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "Declined - low similarity": { + "summary": "Score at or below the decline threshold", + "value": { + "request_id": "7cdda24e-311a-46d3-a3bb-e5d9b564ce73", + "face_match": { + "status": "Declined", + "score": 24.87, + "user_image": { + "entities": [ + { + "bbox": [ + 661, + 728, + 1688, + 2188 + ], + "confidence": 0.732973, + "age": 26.91, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "ref_image": { + "entities": [ + { + "bbox": [ + 156, + 234, + 679, + 898 + ], + "confidence": 0.717775, + "age": 41.2, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "LOW_FACE_MATCH_SIMILARITY", + "feature": "FACEMATCH", + "additional_data": null, + "log_type": "error", + "short_description": "Low face match similarity", + "long_description": "The facial features of the provided image don't closely match the reference image, suggesting a potential identity mismatch." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:08:19.859116+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "face_match": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Approved` when the similarity score is strictly above `face_match_score_decline_threshold` and a score could be computed; `Declined` otherwise (a warning explains why)." + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Similarity score from 0 to 100 — the model's confidence that both images depict the same person. Rounded to two decimals only when `save_api_request` is true; with `save_api_request=false` the raw float is returned. `null` when no face pair could be scored (with the default `save_api_request=true`); with `save_api_request=false` the same condition can surface as `score: 0`. Either way a `NO_REFERENCE_IMAGE` warning is present and `status` is `Declined`.", + "example": 99.41 + }, + "user_image": { + "type": "object", + "description": "Face-detection results for the analyzed images.", + "properties": { + "entities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box of the detected face as `[x_min, y_min, x_max, y_max]` pixel coordinates in the processed image.", + "example": [ + 661, + 728, + 1688, + 2188 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.732973 + }, + "age": { + "type": "number", + "format": "float", + "description": "Model-estimated age of the detected face, in years. Informational only — it does not affect the face-match decision.", + "example": 26.91 + }, + "gender": { + "type": "string", + "description": "Model-predicted gender of the detected face (`male` or `female`). Informational only.", + "example": "male" + }, + "race": { + "type": "string", + "nullable": true, + "description": "Reserved field — always `null` in responses from this endpoint.", + "example": null + } + } + }, + "description": "One entry per detected face." + }, + "best_angle": { + "type": "integer", + "description": "Rotation (degrees: 0, 90, 180, or 270) that produced the best face detection. Only non-zero when `rotate_image=true` corrected the orientation.", + "example": 0 + } + } + }, + "ref_image": { + "type": "object", + "description": "Face-detection results for the reference image, same shape as `user_image`.", + "properties": { + "entities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box of the detected face as `[x_min, y_min, x_max, y_max]` pixel coordinates in the processed image.", + "example": [ + 661, + 728, + 1688, + 2188 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.732973 + }, + "age": { + "type": "number", + "format": "float", + "description": "Model-estimated age of the detected face, in years. Informational only — it does not affect the face-match decision.", + "example": 26.91 + }, + "gender": { + "type": "string", + "description": "Model-predicted gender of the detected face (`male` or `female`). Informational only.", + "example": "male" + }, + "race": { + "type": "string", + "nullable": true, + "description": "Reserved field — always `null` in responses from this endpoint.", + "example": null + } + } + }, + "description": "One entry per detected face." + }, + "best_angle": { + "type": "integer", + "example": 0 + } + } + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. `NO_REFERENCE_IMAGE` (`error`) — no face pair could be scored; `LOW_FACE_MATCH_SIMILARITY` (`error`) — score at or below the decline threshold. Any of these sets `status` to `Declined`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "NO_REFERENCE_IMAGE", + "LOW_FACE_MATCH_SIMILARITY" + ], + "description": "Machine-readable risk code." + }, + "feature": { + "type": "string", + "enum": [ + "FACEMATCH" + ], + "description": "Feature that raised the warning. Always `FACEMATCH` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Always `null` for face-match warnings." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `warning` and `information` entries are advisory and never decline on their own." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated, e.g. `2026-06-12T01:04:42.763237+00:00`." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when a required image is missing, exceeds 5 MB, has an unsupported extension, or when an option is out of range. The body is DRF's standard field-error envelope: one array of messages per offending field.", + "content": { + "application/json": { + "examples": { + "Missing required image": { + "summary": "`ref_image` (or `user_image`) not included in the form data", + "value": { + "ref_image": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp", + "value": { + "user_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 5 MB limit", + "value": { + "user_image": [ + "File size should not exceed 5 MB" + ] + } + }, + "Threshold out of range": { + "summary": "`face_match_score_decline_threshold` outside 0–100", + "value": { + "face_match_score_decline_threshold": [ + "Ensure this value is less than or equal to 100." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/face-match/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'user_image=@./selfie.jpg' \\\n -F 'ref_image=@./id_portrait.jpg' \\\n -F 'face_match_score_decline_threshold=50' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.face_match.compare(\n user_image=open(\"user_image.jpg\", \"rb\"),\n ref_image=open(\"ref_image.jpg\", \"rb\"),\n face_match_score_decline_threshold=50.0,\n rotate_image=False,\n save_api_request=True,\n vendor_data=\"user-123\",\n metadata={\"flow\": \"login\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.faceMatch.compare({\n user_image: await fileFromPath(\"user_image.jpg\"),\n ref_image: await fileFromPath(\"ref_image.jpg\"),\n face_match_score_decline_threshold: 50.0,\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"login\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.FaceMatch.Compare(ctx, didit.FaceMatchCompareParams{\n\tUserImage: didit.FileFromPath(\"user_image.jpg\"),\n\tRefImage: didit.FileFromPath(\"ref_image.jpg\"),\n\tFaceMatchScoreDeclineThreshold: didit.Float(50.0),\n\tRotateImage: didit.Bool(false),\n\tSaveAPIRequest: didit.Bool(true),\n\tVendorData: didit.String(\"user-123\"),\n\tMetadata: didit.Some(map[string]any{\"flow\": \"login\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.FaceMatchCompareParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.faceMatch().compare(FaceMatchCompareParams.builder()\n .userImage(Path.of(\"user_image.jpg\"))\n .refImage(Path.of(\"ref_image.jpg\"))\n .faceMatchScoreDeclineThreshold(50.0)\n .rotateImage(false)\n .saveApiRequest(true)\n .vendorData(\"user-123\")\n .metadata(Map.of(\"flow\", \"login\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "faceMatch->compare([\n 'user_image' => File::fromPath('user_image.jpg'),\n 'ref_image' => File::fromPath('ref_image.jpg'),\n 'face_match_score_decline_threshold' => 50.0,\n 'rotate_image' => false,\n 'save_api_request' => true,\n 'vendor_data' => 'user-123',\n 'metadata' => ['flow' => 'login'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.face_match.compare(\n user_image: Pathname(\"user_image.jpg\"),\n ref_image: Pathname(\"ref_image.jpg\"),\n face_match_score_decline_threshold: 50.0,\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"login\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.FaceMatch.CompareAsync(new FaceMatchCompareParams\n{\n UserImage = FileUpload.FromPath(\"user_image.jpg\"),\n RefImage = FileUpload.FromPath(\"ref_image.jpg\"),\n FaceMatchScoreDeclineThreshold = 50.0,\n RotateImage = false,\n SaveApiRequest = true,\n VendorData = \"user-123\",\n Metadata = new Dictionary { [\"flow\"] = \"login\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/age-estimation/": { + "post": { + "summary": "Age Estimation (face age + passive liveness)", + "description": "Estimate a person's age from a single face photo and run a passive liveness check in the same call — returns the model-predicted `age_estimation` (in years), a liveness `score` (0–100), and an `Approved`/`Declined` `status`. The age is **model-predicted from the face, not verified against a document** — use it for age gates and low-friction checks, not as legal proof of age.\n\n**How it works.** The image is analyzed for faces (set `rotate_image=true` if captures may be sideways or upside down); each detected face gets an estimated age, and the largest face in the frame drives `age_estimation`. In parallel, a passive liveness model scores whether the photo shows a live person (no user challenge required) and detects presentation attacks (photos of photos, screens, masks). Submit one clear, front-facing, well-lit face per image for best results.\n\n**Decision logic.** Any warning declines: `status` is `Approved` only when the `warnings` array is empty. A liveness `score` at or below `face_liveness_score_decline_threshold` (default `30`) adds `LOW_LIVENESS_SCORE`; a detected attack adds `LIVENESS_FACE_ATTACK`; no face found by the liveness model adds `NO_FACE_DETECTED`; no estimable face age adds `AGE_NOT_DETECTED` (`age_estimation` is `null`); an estimated age below `age_estimation_decline_threshold` (default `18`) adds `AGE_BELOW_MINIMUM`. Borderline ages near your threshold deserve a fallback to document-based verification — the model's error grows for faces close to the limit.\n\n**Billing.** Each `200` response consumes one Age Estimation API credit (paid from your plan credit or top-up balance). When the organization's balance cannot cover the call, the endpoint returns `403` with the not-enough-credits error before any image processing.\n\n**Session persistence (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session: it appears in the Business Console, the returned `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/` (the result is exposed there in `liveness_checks[]`, with `features: [\"AGE_ESTIMATION\"]`), the face crop and reference image are stored, the face embedding is added to your application's face index (used by face search and duplicate detection), and a `status.updated` webhook is emitted. When `false`, nothing is stored and `request_id` is a one-off correlation UUID that cannot be looked up later.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload (`age_estimation: 30`, no warnings), no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3age-estimation", + "x-didit-sdk": { + "resource": "age_estimation", + "method": "estimate", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "user_image" + ], + "properties": { + "user_image": { + "type": "string", + "format": "binary", + "description": "Face photo to analyze (e.g. a selfie). Submit a single front-facing photo with the subject's face clearly visible and well lit. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp` (no PDF). Maximum upload size: **5 MB** (larger files are rejected with `400`). Images are automatically compressed to ~0.3 MB before processing, so very high resolutions do not improve accuracy." + }, + "face_liveness_score_decline_threshold": { + "type": "number", + "format": "float", + "default": 30, + "minimum": 0, + "maximum": 100, + "description": "Liveness threshold (0–100). A liveness `score` **at or below** this value adds a `LOW_LIVENESS_SCORE` warning and declines. Raise it for stricter anti-spoofing; values outside 0–100 return `400`.", + "example": 30 + }, + "age_estimation_decline_threshold": { + "type": "integer", + "default": 18, + "minimum": 0, + "maximum": 100, + "description": "Minimum acceptable estimated age in years. An estimated age **below** this value adds an `AGE_BELOW_MINIMUM` warning and declines. Set `0` to disable the age check (the call then only runs liveness + age estimation without an age-based decision).", + "example": 18 + }, + "rotate_image": { + "type": "boolean", + "default": false, + "description": "When `true`, the service tries the input image in 90-degree increments (0, 90, 180, 270) and uses the orientation that yields the best face detection — useful for mobile captures with missing EXIF orientation. Adds latency, so only enable it if you cannot guarantee upright images.", + "example": false + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, announced through a `status.updated` webhook, and with the detected face added to your application's face index. When `false`, nothing is stored and `request_id` is a transient UUID for response correlation only.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"age-gate\"}`) — it is parsed into an object.", + "example": { + "flow": "age-gate" + } + } + } + }, + "example": { + "user_image": "(binary JPEG/PNG of the selfie)", + "face_liveness_score_decline_threshold": 30, + "age_estimation_decline_threshold": 18, + "rotate_image": false, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "age-gate" + } + } + } + } + }, + "responses": { + "200": { + "description": "Age estimation and passive liveness completed. `age_estimation.age_estimation` is the model-predicted age in years of the largest detected face (`null` if none); `age_estimation.score` is the passive-liveness score (0–100); `status` is `Approved` only when `warnings` is empty. An underage or spoofed capture still returns `200` with `status: \"Declined\"` — inspect the body, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Live adult face — both checks passed", + "value": { + "request_id": "0c40ba43-64ab-4e2e-b4b8-7d1f12f81bc1", + "age_estimation": { + "status": "Approved", + "method": "PASSIVE", + "score": 97.5, + "user_image": { + "entities": [ + { + "age": 27.33, + "bbox": [ + 40, + 40, + 100, + 100 + ], + "confidence": 0.7177750468254089, + "gender": "male" + } + ], + "best_angle": 0 + }, + "age_estimation": 27.33, + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": { + "flow": "age-gate" + }, + "created_at": "2026-06-12T02:24:11.512941+00:00" + } + }, + "Declined - age below threshold": { + "summary": "Estimated age below `age_estimation_decline_threshold`", + "value": { + "request_id": "9be41a6f-2f4e-4f57-92f4-b3a4f6f0a1c2", + "age_estimation": { + "status": "Declined", + "method": "PASSIVE", + "score": 95.0, + "user_image": { + "entities": [ + { + "age": 16.5, + "bbox": [ + 40, + 40, + 100, + 100 + ], + "confidence": 0.7177750468254089, + "gender": "female" + } + ], + "best_angle": 0 + }, + "age_estimation": 16.5, + "warnings": [ + { + "risk": "AGE_BELOW_MINIMUM", + "feature": "LIVENESS", + "additional_data": null, + "log_type": "error", + "short_description": "Age below minimum", + "long_description": "The age of the face is below the minimum age threshold for the application." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:26:40.118332+00:00" + } + }, + "Declined - liveness too low": { + "summary": "Liveness score at or below the decline threshold (possible spoof)", + "value": { + "request_id": "5f6f2f1f-7c4f-43b9-8d62-0a8f4c2d9e77", + "age_estimation": { + "status": "Declined", + "method": "PASSIVE", + "score": 5.0, + "user_image": { + "entities": [ + { + "age": 25.0, + "bbox": [ + 40, + 40, + 100, + 100 + ], + "confidence": 0.7177750468254089, + "gender": "male" + } + ], + "best_angle": 0 + }, + "age_estimation": 25.0, + "warnings": [ + { + "risk": "LOW_LIVENESS_SCORE", + "feature": "LIVENESS", + "additional_data": null, + "log_type": "error", + "short_description": "Low liveness score", + "long_description": "The liveness check resulted in a low score, indicating potential use of non-live facial representations or poor-quality biometric data." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:27:02.901547+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "age_estimation": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Approved` when no warnings were raised; `Declined` otherwise (every warning this endpoint produces declines)." + }, + "method": { + "type": "string", + "enum": [ + "PASSIVE" + ], + "description": "Liveness method. Always `PASSIVE` on this endpoint (single-image, no user challenge)." + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Passive-liveness score from 0 to 100 — the model's confidence that the photo shows a live person. Compared against `face_liveness_score_decline_threshold`; at or below it the call declines with `LOW_LIVENESS_SCORE`. `null` when the liveness model returns no confidence — the call still declines with `LOW_LIVENESS_SCORE` because null is treated as 0 for the threshold comparison.", + "example": 97.5 + }, + "user_image": { + "type": "object", + "description": "Face-detection results for the analyzed image.", + "properties": { + "entities": { + "type": "array", + "description": "One entry per detected face. Empty when no face was found (then `age_estimation` is `null` and an `AGE_NOT_DETECTED` warning is added).", + "items": { + "type": "object", + "properties": { + "age": { + "type": "number", + "format": "float", + "description": "Model-estimated age of this face, in years.", + "example": 27.33 + }, + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box of the detected face as `[x_min, y_min, x_max, y_max]` pixel coordinates in the processed image.", + "example": [ + 40, + 40, + 100, + 100 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.7177750468254089 + }, + "gender": { + "type": "string", + "description": "Model-predicted gender of the detected face (`male` or `female`). Informational only.", + "example": "male" + } + } + } + }, + "best_angle": { + "type": "integer", + "nullable": true, + "description": "Rotation (degrees: 0, 90, 180, or 270) that produced the best face detection. Only non-zero when `rotate_image=true` corrected the orientation.", + "example": 0 + } + } + }, + "age_estimation": { + "type": "number", + "format": "float", + "nullable": true, + "description": "Model-predicted age in years of the **largest** detected face. `null` when no face age could be estimated (an `AGE_NOT_DETECTED` warning is added and the call declines).", + "example": 27.33 + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. Any entry sets `status` to `Declined`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "NO_FACE_DETECTED", + "LOW_LIVENESS_SCORE", + "LIVENESS_FACE_ATTACK", + "AGE_NOT_DETECTED", + "AGE_BELOW_MINIMUM" + ], + "description": "Machine-readable risk code. `NO_FACE_DETECTED` — the liveness model found no face; `LOW_LIVENESS_SCORE` — liveness `score` at or below `face_liveness_score_decline_threshold`; `LIVENESS_FACE_ATTACK` — a presentation attack (photo/screen/mask) was detected; `AGE_NOT_DETECTED` — no face age could be estimated; `AGE_BELOW_MINIMUM` — estimated age below `age_estimation_decline_threshold`. `NO_FACE_DETECTED` and `LOW_LIVENESS_SCORE` are mutually exclusive — when no face is found only `NO_FACE_DETECTED` is emitted." + }, + "feature": { + "type": "string", + "enum": [ + "LIVENESS" + ], + "description": "Feature that raised the warning. Always `LIVENESS` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Always `null` for age-estimation warnings." + }, + "log_type": { + "type": "string", + "enum": [ + "error" + ], + "description": "Severity. Every warning this endpoint produces is an `error` and sets `status` to `Declined`." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when `user_image` is missing, exceeds 5 MB, has an unsupported extension, or when a threshold is out of range. The body is DRF's standard field-error envelope: one array of messages per offending field.", + "content": { + "application/json": { + "examples": { + "Missing user image": { + "summary": "`user_image` not included in the form data", + "value": { + "user_image": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp", + "value": { + "user_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 5 MB limit", + "value": { + "user_image": [ + "File size should not exceed 5 MB" + ] + } + }, + "Threshold out of range": { + "summary": "`face_liveness_score_decline_threshold` outside 0–100", + "value": { + "face_liveness_score_decline_threshold": [ + "Ensure this value is less than or equal to 100." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/age-estimation/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'user_image=@./selfie.jpg' \\\n -F 'age_estimation_decline_threshold=18' \\\n -F 'face_liveness_score_decline_threshold=30' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.age_estimation.estimate(\n user_image=open(\"user_image.jpg\", \"rb\"),\n face_liveness_score_decline_threshold=30.0,\n age_estimation_decline_threshold=18,\n rotate_image=False,\n save_api_request=True,\n vendor_data=\"user-123\",\n metadata={\"flow\": \"age-gate\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.ageEstimation.estimate({\n user_image: await fileFromPath(\"user_image.jpg\"),\n face_liveness_score_decline_threshold: 30.0,\n age_estimation_decline_threshold: 18,\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"age-gate\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.AgeEstimation.Estimate(ctx, didit.AgeEstimationEstimateParams{\n\tUserImage: didit.FileFromPath(\"user_image.jpg\"),\n\tFaceLivenessScoreDeclineThreshold: didit.Float(30.0),\n\tAgeEstimationDeclineThreshold: didit.Int(18),\n\tRotateImage: didit.Bool(false),\n\tSaveAPIRequest: didit.Bool(true),\n\tVendorData: didit.String(\"user-123\"),\n\tMetadata: didit.Some(map[string]any{\"flow\": \"age-gate\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.AgeEstimationEstimateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.ageEstimation().estimate(AgeEstimationEstimateParams.builder()\n .userImage(Path.of(\"user_image.jpg\"))\n .faceLivenessScoreDeclineThreshold(30.0)\n .ageEstimationDeclineThreshold(18L)\n .rotateImage(false)\n .saveApiRequest(true)\n .vendorData(\"user-123\")\n .metadata(Map.of(\"flow\", \"age-gate\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "ageEstimation->estimate([\n 'user_image' => File::fromPath('user_image.jpg'),\n 'face_liveness_score_decline_threshold' => 30.0,\n 'age_estimation_decline_threshold' => 18,\n 'rotate_image' => false,\n 'save_api_request' => true,\n 'vendor_data' => 'user-123',\n 'metadata' => ['flow' => 'age-gate'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.age_estimation.estimate(\n user_image: Pathname(\"user_image.jpg\"),\n face_liveness_score_decline_threshold: 30.0,\n age_estimation_decline_threshold: 18,\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"age-gate\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.AgeEstimation.EstimateAsync(new AgeEstimationEstimateParams\n{\n UserImage = FileUpload.FromPath(\"user_image.jpg\"),\n FaceLivenessScoreDeclineThreshold = 30.0,\n AgeEstimationDeclineThreshold = 18,\n RotateImage = false,\n SaveApiRequest = true,\n VendorData = \"user-123\",\n Metadata = new Dictionary { [\"flow\"] = \"age-gate\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/poa/": { + "post": { + "summary": "Proof of Address (document OCR + address checks)", + "description": "Extract and validate a Proof of Address document — utility bills, bank statements, and government-issued documents — in one call. Returns the issuer, issue/expiry dates, the holder name, the raw and parsed/geocoded address, an `Approved`/`Declined` `status`, and a `warnings` list explaining every issue found.\n\n**Latency.** Extraction is LLM-based: typical calls take **5–15 seconds**, multi-page PDFs up to **~30 seconds**. Configure a client timeout of **at least 45 seconds** and do not retry before the call completes.\n\n**How it works.** The document (image or PDF, up to 15 MB; encrypted PDFs require `document_password`) is classified into a document type and subtype (e.g. `UTILITY_BILL` / `WATER_BILL`), and the issuer, dates, holder name, and address are extracted with LLM-based extraction. The address is parsed and geocoded into `poa_parsed_address`. PDF/EXIF forensics screen for tampering — modification after digital signing, known PDF editors, overlay-text manipulation, suspicious re-exports — surfacing `SUSPECTED_DOCUMENT_MANIPULATION` or `DOCUMENT_METADATA_MISMATCH`. Document age is validated against `poa_document_age_months`, and the language against `poa_languages_allowed`. If you pass `expected_address`, `expected_country`, or `expected_first_name`/`expected_last_name`, the extracted values are cross-checked and mismatches are flagged.\n\n**Decision logic.** `status` is `Approved` unless at least one warning resolves to a decline. Hard failures always decline: `MISSING_ADDRESS_INFORMATION`, `POA_DOCUMENT_EXPIRED`, `INVALID_DOCUMENT_TYPE`, `UNABLE_TO_VALIDATE_DOCUMENT_AGE`, plus `UNABLE_TO_EXTRACT_ISSUE_DATE` and `POA_NAME_NOT_DETECTED` (not configurable on this endpoint). Five risk groups are configurable per request via the `poa_*_action` options (`DECLINE` or `NO_ACTION`, all defaulting to `DECLINE`). `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION` and `UNPARSABLE_OR_INVALID_ADDRESS` are informational on this endpoint and never decline. A document that cannot be processed at all returns `400`; a readable but problematic document returns `200` with `status: \"Declined\"` — always inspect `poa.status` and `poa.warnings`, not just the HTTP code.\n\n**Billing.** Each `200` response consumes one Proof of Address API credit (paid from your plan credit or top-up balance). When the organization's balance cannot cover the call, the endpoint returns `403` with the not-enough-credits error before any document processing.\n\n**Session persistence (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session: it appears in the Business Console, the uploaded document is stored, the returned `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/` (the decision additionally exposes `document_file` and the `document_metadata` forensics, which this response omits), and a `status.updated` webhook is emitted. When `false`, nothing is stored and `request_id` is a one-off correlation UUID that cannot be looked up later.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload (a fictional \"Sandbox Power Co.\" electricity bill), no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.\n\nNote: `FUTURE_ISSUE_DATE` and `POOR_DOCUMENT_QUALITY` exist in workflow sessions but are never produced by this standalone endpoint — a future-dated document is not auto-declined here.", + "operationId": "post_v3poa", + "x-didit-sdk": { + "resource": "proof_of_address", + "method": "verify", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "document" + ], + "properties": { + "document": { + "type": "string", + "format": "binary", + "description": "The Proof of Address document. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `pdf`, `webp`. Maximum upload size: **15 MB** (larger files are rejected with `400`). Multi-page PDFs are supported (expect higher latency); encrypted PDFs require `document_password`. Images are automatically compressed before processing." + }, + "document_password": { + "type": "string", + "description": "Password to decrypt `document` when it is an encrypted PDF. Sending an encrypted PDF without (or with a wrong) password returns `400`.", + "writeOnly": true + }, + "expected_address": { + "type": "string", + "description": "Address to verify against the document. It is geocoded and compared with the extracted address; a mismatch (or an expected address that cannot be geocoded/verified) adds `ADDRESS_MISMATCH_WITH_PROVIDED`, governed by `poa_address_mismatch_action`. The geocoded form is echoed back in `expected_details_formatted_address` / `expected_details_parsed_address`.", + "example": "Av. Monseñor Tavella 1291, Salta, Argentina" + }, + "expected_country": { + "type": "string", + "description": "Country to verify against the document (ISO 3166-1 code, e.g. `ARG` or `AR`). A mismatch with the document's country adds `POA_COUNTRY_MISMATCH_WITH_PROVIDED`, governed by `poa_address_mismatch_action`.", + "example": "ARG" + }, + "expected_first_name": { + "type": "string", + "description": "First name to verify against `name_on_document` (fuzzy match, transliteration-aware). A score below the match threshold adds `NAME_MISMATCH_WITH_PROVIDED`, governed by `poa_address_mismatch_action`.", + "example": "Sophia" + }, + "expected_last_name": { + "type": "string", + "description": "Last name to verify against `name_on_document`, combined with `expected_first_name` for the match score.", + "example": "Martinez" + }, + "poa_languages_allowed": { + "type": "string", + "description": "Comma-separated list of accepted document languages as ISO 639-1 codes (e.g. `en,es,fr`). When omitted or blank, **all 51 supported languages** are accepted. A document in a language outside the list adds `UNSUPPORTED_DOCUMENT_LANGUAGE` (governed by `poa_unsupported_language_action`); an unknown code in the list returns `400` with the full set of supported codes.", + "example": "en,es" + }, + "poa_document_age_months": { + "type": "string", + "description": "Comma-separated `key:value` pairs setting the maximum document age in months per document type — keys: `utility_bill`, `bank_statement`, `government_issued_document`, `other_poa_document`; values: `1`–`120`, or `-1` for unlimited (no age check). Defaults when omitted: `utility_bill:3,bank_statement:3,government_issued_document:12,other_poa_document:12`. A document older than its limit declines with `POA_DOCUMENT_EXPIRED`. **Caution:** when you provide this field, list *every* type you accept — omitted types are treated as not accepted (informational `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION`) and their age check is skipped.", + "example": "utility_bill:3,bank_statement:6" + }, + "poa_name_mismatch_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "Accepted but **currently not applied** — name-mismatch warnings (`NAME_MISMATCH_WITH_PROVIDED`) follow `poa_address_mismatch_action` instead. Set `poa_address_mismatch_action` to control both name and address mismatch behavior." + }, + "poa_document_issues_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do on file-integrity problems — in practice only `DOCUMENT_METADATA_MISMATCH` is produced by this endpoint." + }, + "poa_document_authenticity_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when document manipulation is suspected (`SUSPECTED_DOCUMENT_MANIPULATION`) — e.g. the PDF was modified after digital signing, processed by a known PDF editor, shows overlay-text manipulation, or has inconsistent EXIF dates." + }, + "poa_unsupported_language_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when the document language is outside `poa_languages_allowed` (`UNSUPPORTED_DOCUMENT_LANGUAGE`)." + }, + "poa_address_mismatch_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when the extracted details disagree with the `expected_*` fields: `ADDRESS_MISMATCH_WITH_PROVIDED`, `NAME_MISMATCH_WITH_PROVIDED`, and `POA_COUNTRY_MISMATCH_WITH_PROVIDED` all follow this action." + }, + "poa_issuer_not_identified_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when no issuing company/organization can be identified on the document (`ISSUER_NOT_IDENTIFIED`)." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, announced through a `status.updated` webhook, and with the uploaded document stored. When `false`, nothing is stored and `request_id` is a transient UUID for response correlation only.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"onboarding\"}`) — it is parsed into an object.", + "example": { + "flow": "onboarding" + } + } + } + }, + "example": { + "document": "(binary JPEG/PNG/PDF of the utility bill or bank statement)", + "expected_address": "Av. Monseñor Tavella 1291, Salta, Argentina", + "expected_country": "ARG", + "expected_first_name": "Sophia", + "expected_last_name": "Martinez", + "poa_languages_allowed": "en,es", + "save_api_request": true, + "vendor_data": "user-123" + } + } + } + }, + "responses": { + "200": { + "description": "Document processed. `poa.status` is `Approved` or `Declined`; every detected issue is itemized in `poa.warnings`. An expired, mismatched, or suspicious document still returns `200` with `status: \"Declined\"` — inspect the body, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Utility bill read cleanly, no expected-details checks", + "value": { + "request_id": "4b523a8d-ed32-45e4-8aa8-a89eda459d88", + "poa": { + "issuing_state": "ARG", + "document_type": "UTILITY_BILL", + "document_subtype": "WATER_BILL", + "document_language": "es", + "issuer": "Aguas del Norte", + "issue_date": "2026-04-10", + "expiration_date": null, + "poa_address": "AVDA. MONSEÑOR TAVELLA N° 1291, B° VILLA LAVALLE CP: 4400, CAPITAL - SALTA", + "poa_formatted_address": "Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina", + "poa_parsed_address": { + "street_1": "Avenida Monseñor Tavella 1291", + "street_2": "b", + "city": "Salta", + "region": "Salta", + "country": "AR", + "postal_code": "A4400", + "document_location": { + "latitude": -24.8208682, + "longitude": -65.4130954 + } + }, + "expected_details_address": null, + "expected_details_formatted_address": null, + "expected_details_parsed_address": null, + "name_on_document": "OESTE EMBOTELLADORA S.A.", + "extra_fields": { + "bank_account_number": null, + "bank_iban": null, + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": null, + "additional_names": [ + "LA EMBOTELLADORA DEL NORTE SA" + ] + }, + "detected_codes": [ + { + "type": "QR_CODE", + "page_number": 1, + "position": [ + [ + 820, + 1120 + ], + [ + 1002, + 1120 + ], + [ + 1002, + 1302 + ], + [ + 820, + 1302 + ] + ], + "x": 820, + "y": 1120, + "width": 182, + "height": 182, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "aHR0cHM6Ly9hZ3Vhc2RlbG5vcnRlLmNvbS9mYWN0dXJhLzk5ODgxMQ==", + "parsed_payload": "https://aguasdelnorte.com/factura/998811" + }, + { + "type": "CODE_128", + "page_number": 1, + "position": [ + [ + 112, + 1480 + ], + [ + 560, + 1480 + ], + [ + 560, + 1540 + ], + [ + 112, + 1540 + ] + ], + "x": 112, + "y": 1480, + "width": 448, + "height": 60, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "MDA5OTg4MTEwMDA0Mg==", + "parsed_payload": "0099881100042" + } + ], + "status": "Approved", + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:29:02.439655+00:00" + } + }, + "Declined - document too old": { + "summary": "Issue date older than the allowed `poa_document_age_months`", + "value": { + "request_id": "f61681b8-9422-4378-ac3e-04c6a247fb45", + "poa": { + "issuing_state": "ARG", + "document_type": "UTILITY_BILL", + "document_subtype": "WATER_BILL", + "document_language": "es", + "issuer": "Aguas del Norte", + "issue_date": "2021-02-10", + "expiration_date": null, + "poa_address": "AVDA. MONSEÑOR TAVELLA N° 1291, B° VILLA LAVALLE CP: 4400, CAPITAL - SALTA", + "poa_formatted_address": "Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina", + "poa_parsed_address": { + "street_1": "Avenida Monseñor Tavella 1291", + "street_2": "b", + "city": "Salta", + "region": "Salta", + "country": "AR", + "postal_code": "A4400", + "document_location": { + "latitude": -24.8208682, + "longitude": -65.4130954 + } + }, + "expected_details_address": null, + "expected_details_formatted_address": null, + "expected_details_parsed_address": null, + "name_on_document": "OESTE EMBOTELLADORA S.A.", + "extra_fields": { + "bank_account_number": null, + "bank_iban": null, + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": null, + "additional_names": [ + "LA EMBOTELLADORA DEL NORTE SA" + ] + }, + "status": "Declined", + "warnings": [ + { + "risk": "POA_DOCUMENT_EXPIRED", + "feature": "PROOF_OF_ADDRESS", + "additional_data": { + "expiration_reason": "age_window", + "max_age_months": 3, + "document_subtype": "WATER_BILL", + "document_type": "UTILITY_BILL", + "issue_date": "2021-02-10" + }, + "log_type": "error", + "short_description": "Document expired", + "long_description": "The submitted document is older than the maximum accepted age of 3 months from its issue date, which exceeds the acceptable time period for validity." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:23:02.165493+00:00" + } + }, + "Declined - name mismatch": { + "summary": "`expected_first_name`/`expected_last_name` do not match `name_on_document`", + "value": { + "request_id": "0a7f2f9c-58f1-4b8e-b1e3-2f6f9d4f7a21", + "poa": { + "issuing_state": "ARG", + "document_type": "UTILITY_BILL", + "document_subtype": "WATER_BILL", + "document_language": "es", + "issuer": "Aguas del Norte", + "issue_date": "2021-02-10", + "expiration_date": null, + "poa_address": "AVDA. MONSEÑOR TAVELLA N° 1291, B° VILLA LAVALLE CP: 4400, CAPITAL - SALTA", + "poa_formatted_address": "Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina", + "poa_parsed_address": { + "street_1": "Avenida Monseñor Tavella 1291", + "street_2": "b", + "city": "Salta", + "region": "Salta", + "country": "AR", + "postal_code": "A4400", + "document_location": { + "latitude": -24.8208682, + "longitude": -65.4130954 + } + }, + "expected_details_address": null, + "expected_details_formatted_address": null, + "expected_details_parsed_address": null, + "name_on_document": "OESTE EMBOTELLADORA S.A.", + "extra_fields": { + "bank_account_number": null, + "bank_iban": null, + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": null, + "additional_names": [ + "LA EMBOTELLADORA DEL NORTE SA" + ] + }, + "status": "Declined", + "warnings": [ + { + "risk": "NAME_MISMATCH_WITH_PROVIDED", + "feature": "PROOF_OF_ADDRESS", + "additional_data": null, + "log_type": "error", + "short_description": "Name mismatch with provided information", + "long_description": "The full name on the document does not match the name from the user's verified identity documents, or the full name sent by API." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:27:21.330172+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "poa": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Approved` when no warning resolves to a decline; `Declined` otherwise — the `warnings` list explains why." + }, + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Country the document was issued in, as extracted from the document — usually ISO 3166-1 alpha-3 (e.g. `ARG`).", + "example": "ARG" + }, + "document_type": { + "type": "string", + "enum": [ + "UTILITY_BILL", + "BANK_STATEMENT", + "GOVERNMENT_ISSUED_DOCUMENT", + "OTHER_POA_DOCUMENT", + "UNKNOWN" + ], + "description": "Detected document category. `UNKNOWN` means the document could not be classified as an acceptable Proof of Address and auto-declines with `INVALID_DOCUMENT_TYPE`. Never null — classification failures fall back to `UNKNOWN`." + }, + "document_subtype": { + "type": "string", + "description": "Finer-grained classification within `document_type` — e.g. `ELECTRICITY_BILL`, `WATER_BILL`, `GAS_BILL`, `INTERNET_BILL`, `PHONE_BILL` (utility bills); `ACCOUNT_STATEMENT`, `CREDIT_CARD_STATEMENT` (bank statements); `TAX_ASSESSMENT`, `RESIDENCY_CERTIFICATE` (government documents). `UNKNOWN` when no subtype could be determined. Never null — classification failures fall back to `UNKNOWN`.", + "example": "WATER_BILL" + }, + "document_language": { + "type": "string", + "nullable": true, + "description": "Detected document language (ISO 639-1).", + "example": "es" + }, + "issuer": { + "type": "string", + "nullable": true, + "description": "Company or organization that issued the document. `null` adds `ISSUER_NOT_IDENTIFIED`.", + "example": "Aguas del Norte" + }, + "issue_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Document issue date (`YYYY-MM-DD`). `null` when it could not be extracted — that declines with `UNABLE_TO_EXTRACT_ISSUE_DATE` on this endpoint.", + "example": "2021-02-10" + }, + "expiration_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Explicit expiry/validity date printed on the document, when present. Independent of the `poa_document_age_months` age check." + }, + "poa_address": { + "type": "string", + "nullable": true, + "description": "Address exactly as printed on the document. `null` auto-declines with `MISSING_ADDRESS_INFORMATION`." + }, + "poa_formatted_address": { + "type": "string", + "nullable": true, + "description": "Geocoded, normalized version of `poa_address`.", + "example": "Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina" + }, + "poa_parsed_address": { + "type": "object", + "nullable": true, + "description": "Structured, geocoded breakdown of the document address. `null` when the address could not be parsed.", + "properties": { + "street_1": { + "type": "string", + "nullable": true, + "example": "Avenida Monseñor Tavella 1291" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "city": { + "type": "string", + "nullable": true, + "example": "Salta" + }, + "region": { + "type": "string", + "nullable": true, + "example": "Salta" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code.", + "example": "AR" + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "A4400" + }, + "document_location": { + "type": "object", + "nullable": true, + "properties": { + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + } + }, + "description": "Geocoded coordinates of the document address." + } + } + }, + "expected_details_address": { + "type": "string", + "nullable": true, + "description": "Echo of the `expected_address` you sent, or `null`." + }, + "expected_details_formatted_address": { + "type": "string", + "nullable": true, + "description": "Geocoded, normalized version of `expected_address`; `null` when not provided or not geocodable." + }, + "expected_details_parsed_address": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Full geocoding result for `expected_address` (street/city/region/country/postal code plus raw geocoder output and an `is_verified` flag). `null` when `expected_address` was not provided." + }, + "name_on_document": { + "type": "string", + "nullable": true, + "description": "Primary holder name extracted from the document; compared against `expected_first_name`/`expected_last_name` when provided. `null` declines with `POA_NAME_NOT_DETECTED` on this endpoint.", + "example": "OESTE EMBOTELLADORA S.A." + }, + "extra_fields": { + "type": "object", + "description": "Document-specific extras. Bank fields are populated for bank statements when printed on the document; `null` otherwise.", + "properties": { + "bank_account_number": { + "type": "string", + "nullable": true + }, + "bank_iban": { + "type": "string", + "nullable": true + }, + "bank_sort_code": { + "type": "string", + "nullable": true + }, + "bank_routing_number": { + "type": "string", + "nullable": true + }, + "bank_swift_bic": { + "type": "string", + "nullable": true + }, + "bank_branch_name": { + "type": "string", + "nullable": true + }, + "bank_branch_address": { + "type": "string", + "nullable": true + }, + "document_phone_number": { + "type": "string", + "nullable": true, + "description": "Contact phone number printed on the document." + }, + "additional_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Other holder names found on the document (e.g. co-account holders, previous account names)." + } + } + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. Every entry explains one detected risk; entries with `log_type: \"error\"` set `status` to `Declined`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "description": "Machine-readable risk code. Always-decline: `MISSING_ADDRESS_INFORMATION`, `POA_DOCUMENT_EXPIRED`, `INVALID_DOCUMENT_TYPE`, `UNABLE_TO_VALIDATE_DOCUMENT_AGE`, plus `UNABLE_TO_EXTRACT_ISSUE_DATE` and `POA_NAME_NOT_DETECTED` (fixed to decline on this endpoint). Governed by request options: `NAME_MISMATCH_WITH_PROVIDED` / `ADDRESS_MISMATCH_WITH_PROVIDED` / `POA_COUNTRY_MISMATCH_WITH_PROVIDED` (`poa_address_mismatch_action`), `POOR_DOCUMENT_QUALITY` (never produced by this endpoint) / `DOCUMENT_METADATA_MISMATCH` (`poa_document_issues_action`), `SUSPECTED_DOCUMENT_MANIPULATION` (`poa_document_authenticity_action`), `UNSUPPORTED_DOCUMENT_LANGUAGE` (`poa_unsupported_language_action`), `ISSUER_NOT_IDENTIFIED` (`poa_issuer_not_identified_action`). Informational on this endpoint: `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION`, `UNPARSABLE_OR_INVALID_ADDRESS`.", + "example": "POA_DOCUMENT_EXPIRED" + }, + "feature": { + "type": "string", + "enum": [ + "PROOF_OF_ADDRESS" + ], + "description": "Feature that raised the warning. Always `PROOF_OF_ADDRESS` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Extra context for some risks — e.g. `POA_DOCUMENT_EXPIRED` includes `expiration_reason` (`printed_expiration` or `age_window`), `document_type`, `document_subtype`, and `issue_date`, plus `expiration_date` on the `printed_expiration` path or `max_age_months` on the `age_window` path; `SUSPECTED_DOCUMENT_MANIPULATION` includes `reason` and `detection_method`. `null` for most risks." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `information` entries are advisory and never decline on their own. (`warning` is not produced by this endpoint — its options only accept `DECLINE` or `NO_ACTION`.)" + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "detected_codes": { + "type": "array", + "description": "QR codes and barcodes detected across every processed page of the document. Empty when none are found. A located but undecodable QR code has `parsed_payload: null`.", + "items": { + "$ref": "#/components/schemas/DocumentAIDetectedCode" + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated." + } + } + } + } + } + }, + "400": { + "description": "Validation or processing error. Field-level problems return DRF's standard envelope (one array of messages per offending field). Encrypted-PDF problems return `{\"detail\": ...}`. A document the extraction pipeline cannot process returns `{\"error\": [\"Error extracting POA information\"]}`.", + "content": { + "application/json": { + "examples": { + "Missing document": { + "summary": "`document` not included in the form data", + "value": { + "document": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/pdf/webp", + "value": { + "document": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, pdf, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 15 MB limit", + "value": { + "document": [ + "File size should not exceed 15 MB" + ] + } + }, + "Encrypted PDF": { + "summary": "PDF uploaded without the required password", + "value": { + "detail": "The PDF is encrypted. Please upload a decrypted PDF or a photo instead." + } + }, + "Wrong PDF password": { + "summary": "`document_password` does not decrypt the PDF", + "value": { + "detail": "The PDF password is incorrect. Please provide the correct password." + } + }, + "Invalid language code": { + "summary": "`poa_languages_allowed` contains an unsupported code", + "value": { + "poa_languages_allowed": [ + "Invalid language code: 'xx'. Must be one of the supported languages: ['ar', 'bn', 'hy', 'bg', 'bs', 'ca', 'cnr', 'sq', 'zh', 'hr', 'cs', 'da', 'nl', 'en', 'et', 'fi', 'fr', 'ka', 'kk', 'de', 'el', 'he', 'hi', 'hu', 'id', 'it', 'ja', 'ko', 'ky', 'lv', 'lt', 'mk', 'mn', 'ms', 'no', 'fa', 'pl', 'pt', 'ro', 'ru', 'sr', 'sk', 'sl', 'so', 'es', 'sv', 'th', 'tr', 'uk', 'uz', 'vi']." + ] + } + }, + "Invalid document age value": { + "summary": "`poa_document_age_months` value outside 1–120 / -1", + "value": { + "poa_document_age_months": [ + "Invalid integer value for 'utility_bill': '999'. Must be -1 (unlimited) or a positive integer between 1 and 120." + ] + } + }, + "Extraction failure": { + "summary": "The document could not be processed by the extraction pipeline", + "value": { + "error": [ + "Error extracting POA information" + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any document processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/poa/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'document=@./utility-bill.pdf' \\\n -F 'expected_address=Av. Monseñor Tavella 1291, Salta, Argentina' \\\n -F 'expected_country=ARG' \\\n -F 'expected_first_name=Sophia' \\\n -F 'expected_last_name=Martinez' \\\n -F 'poa_languages_allowed=en,es' \\\n -F 'vendor_data=user-123' \\\n --max-time 45" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.proof_of_address.verify(\n document=open(\"document.pdf\", \"rb\"),\n expected_address=\"Av. Monseñor Tavella 1291, Salta, Argentina\",\n expected_country=\"ARG\",\n expected_first_name=\"Sophia\",\n expected_last_name=\"Martinez\",\n poa_languages_allowed=\"en,es\",\n save_api_request=True,\n vendor_data=\"user-123\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.proofOfAddress.verify({\n document: await fileFromPath(\"document.pdf\"),\n expected_address: \"Av. Monseñor Tavella 1291, Salta, Argentina\",\n expected_country: \"ARG\",\n expected_first_name: \"Sophia\",\n expected_last_name: \"Martinez\",\n poa_languages_allowed: \"en,es\",\n save_api_request: true,\n vendor_data: \"user-123\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.ProofOfAddress.Verify(ctx, didit.ProofOfAddressVerifyParams{\n\tDocument: didit.FileFromPath(\"document.pdf\"),\n\tExpectedAddress: didit.String(\"Av. Monseñor Tavella 1291, Salta, Argentina\"),\n\tExpectedCountry: didit.String(\"ARG\"),\n\tExpectedFirstName: didit.String(\"Sophia\"),\n\tExpectedLastName: didit.String(\"Martinez\"),\n\tPOALanguagesAllowed: didit.String(\"en,es\"),\n\tSaveAPIRequest: didit.Bool(true),\n\tVendorData: didit.String(\"user-123\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.types.ProofOfAddressVerifyParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.proofOfAddress().verify(ProofOfAddressVerifyParams.builder()\n .document(Path.of(\"document.pdf\"))\n .expectedAddress(\"Av. Monseñor Tavella 1291, Salta, Argentina\")\n .expectedCountry(\"ARG\")\n .expectedFirstName(\"Sophia\")\n .expectedLastName(\"Martinez\")\n .poaLanguagesAllowed(\"en,es\")\n .saveApiRequest(true)\n .vendorData(\"user-123\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "proofOfAddress->verify([\n 'document' => File::fromPath('document.pdf'),\n 'expected_address' => 'Av. Monseñor Tavella 1291, Salta, Argentina',\n 'expected_country' => 'ARG',\n 'expected_first_name' => 'Sophia',\n 'expected_last_name' => 'Martinez',\n 'poa_languages_allowed' => 'en,es',\n 'save_api_request' => true,\n 'vendor_data' => 'user-123',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.proof_of_address.verify(\n document: Pathname(\"document.pdf\"),\n expected_address: \"Av. Monseñor Tavella 1291, Salta, Argentina\",\n expected_country: \"ARG\",\n expected_first_name: \"Sophia\",\n expected_last_name: \"Martinez\",\n poa_languages_allowed: \"en,es\",\n save_api_request: true,\n vendor_data: \"user-123\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.ProofOfAddress.VerifyAsync(new ProofOfAddressVerifyParams\n{\n Document = FileUpload.FromPath(\"document.pdf\"),\n ExpectedAddress = \"Av. Monseñor Tavella 1291, Salta, Argentina\",\n ExpectedCountry = \"ARG\",\n ExpectedFirstName = \"Sophia\",\n ExpectedLastName = \"Martinez\",\n PoaLanguagesAllowed = \"en,es\",\n SaveApiRequest = true,\n VendorData = \"user-123\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/document-ai/": { + "post": { + "summary": "Document AI (custom field extraction + tamper checks)", + "description": "Extract the fields **you** define from any supporting document — payslips, proof of funds, source-of-wealth letters, tax statements, contracts, invoices — in one call. Send the file plus a `fields` definition (key, name, extraction instruction, type) and get back typed values (`text`, `number`, `date` as `YYYY-MM-DD`), an optional name-match score against the expected person or company, PDF/EXIF tamper signals, an `Approved`/`Declined`/`In Review` `status`, and a `warnings` list explaining every issue found. This is the server-to-server counterpart of the hosted Document AI workflow step (see `/core-technology/document-ai/overview`); it uses Didit's default extraction model and does not expose provider selection.\n\n**Latency.** Extraction is LLM-based: typical calls take **5–15 seconds**, multi-page PDFs up to **~30 seconds**. Configure a client timeout of **at least 45 seconds** and do not retry before the call completes.\n\n**Request format.** `multipart/form-data` only. `fields` is **one JSON-encoded array string** (1–30 items, at most 16 KB); bracket-style form arrays are rejected with `400`. Each item is `{key, name, instruction, type, required, is_full_name}` — `key` is the JSON key you get back (`^[a-z0-9_]{1,64}$`, unique), `instruction` tells the model what to read, `type` is `text` (default) | `number` | `date`, `required` raises `DOCUMENT_AI_MISSING_REQUIRED_FIELDS` when the value cannot be read, and at most one field may set `is_full_name: true` to mark the value compared against the expected name.\n\n**Name matching.** Pass `expected_first_name` + `expected_last_name` (person) **or** `expected_company_name` (+ optional `expected_company_country`) — not both. The value of the `is_full_name` field (or, when none is marked, the first text field whose key looks like a name) is compared fuzzily; `name_match_score` (0–100) and `name_on_document` are always returned when both sides exist, and a score below `document_ai_name_match_score_threshold` (default 80) adds `DOCUMENT_AI_NAME_MISMATCH`, governed by `document_ai_name_mismatch_action`.\n\n**Decision logic.** Each risk maps to a configurable action: `DECLINE` → `status: \"Declined\"`, `REVIEW` → `status: \"In Review\"`, `NO_ACTION` → informational only. Defaults mirror the workflow step: unreadable document `REVIEW`, missing required fields `REVIEW`, suspected manipulation `DECLINE`, name mismatch `NO_ACTION`, unsupported file `REVIEW`. A document that cannot be processed at all returns `400`; a readable but problematic document returns `200` with `Declined`/`In Review` — always inspect `document_ai.status` and `document_ai.warnings`, not just the HTTP code.\n\n**Billing.** Each `200` response consumes one Document AI API credit — **$0.20 per document** at list, paid from your plan credit or top-up balance. When the organization's balance cannot cover the call, the endpoint returns `403` before any processing. Sandbox API keys return a deterministic mock response and are not charged.\n\n**Persistence.** With `save_api_request=true` (default) the call is stored as an API-type session: the document, the extracted values, the field definitions and the name-match snapshot are kept, the session shows up in the Business Console, and `request_id` works with `GET /v3/session/{sessionId}/decision/`.", + "operationId": "post_v3document-ai", + "x-didit-sdk": { + "resource": "document_ai", + "method": "analyze", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "document", + "fields" + ], + "properties": { + "document": { + "type": "string", + "format": "binary", + "description": "The document to read. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `pdf`, `webp`, `zip`. Maximum upload size: **30 MB** (larger files are rejected with `400`). Multi-page PDFs are supported (expect higher latency); encrypted PDFs are rejected with `400`. Images are automatically compressed before processing." + }, + "fields": { + "type": "string", + "description": "One JSON-encoded array string with **1–30** field definitions (maximum 16 KB). Each item: `key` (required, `^[a-z0-9_]{1,64}$`, unique — the key returned in `extracted_data`), `name` (≤100 chars, shown in messages), `instruction` (≤500 chars, what the model should read), `type` (`text` default | `number` | `date`), `required` (boolean, default `false`), `is_full_name` (boolean, default `false`, at most one per request). Bracket-style form arrays, invalid JSON, duplicate keys, or more than one `is_full_name` return `400` before any processing or billing.", + "example": "[{\"key\": \"employer_name\", \"name\": \"Employer name\", \"instruction\": \"The employer or company issuing the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": false}, {\"key\": \"employee_name\", \"name\": \"Employee name\", \"instruction\": \"The full name of the employee as printed on the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": true}, {\"key\": \"net_pay\", \"name\": \"Net pay\", \"instruction\": \"The net amount paid to the employee for the period.\", \"type\": \"number\", \"required\": true, \"is_full_name\": false}, {\"key\": \"pay_date\", \"name\": \"Pay date\", \"instruction\": \"The payment date shown on the payslip.\", \"type\": \"date\", \"required\": false, \"is_full_name\": false}]" + }, + "expected_first_name": { + "type": "string", + "description": "First name to verify against the document's name field (fuzzy match, transliteration-aware). Use together with `expected_last_name`; cannot be combined with `expected_company_name`.", + "example": "Sophia" + }, + "expected_last_name": { + "type": "string", + "description": "Last name to verify against the document's name field, combined with `expected_first_name` for `name_match_score`.", + "example": "Martinez" + }, + "expected_company_name": { + "type": "string", + "description": "Company name to verify against the document's name field (for business documents). Cannot be combined with `expected_first_name` / `expected_last_name` (no example value on purpose — the generated request sample uses the person fields; swap them for this one when verifying a business document)." + }, + "expected_company_country": { + "type": "string", + "description": "Optional ISO 3166-1 alpha-2 country code (two ASCII letters, e.g. `FR`) that tunes company-name normalization for `expected_company_name`. Only used with `expected_company_name`." + }, + "document_ai_name_match_score_threshold": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 80, + "description": "Minimum `name_match_score` (0–100) to consider the name matched. A lower score adds `DOCUMENT_AI_NAME_MISMATCH`, governed by `document_ai_name_mismatch_action`.", + "example": 80 + }, + "document_ai_unreadable_document_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "REVIEW", + "description": "What to do when no field could be read from the document (`DOCUMENT_AI_UNREADABLE_DOCUMENT`)." + }, + "document_ai_missing_required_fields_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "REVIEW", + "description": "What to do when at least one `required` field could not be read (`DOCUMENT_AI_MISSING_REQUIRED_FIELDS`; `additional_data.missing_fields` lists them)." + }, + "document_ai_document_tampering_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when PDF/EXIF forensics suspect manipulation — modified after digital signing, known PDF editor, overlay-text manipulation, suspicious re-export, image anomalies (`DOCUMENT_AI_SUSPECTED_MANIPULATION`)." + }, + "document_ai_name_mismatch_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "NO_ACTION", + "description": "What to do when `name_match_score` is below the threshold (`DOCUMENT_AI_NAME_MISMATCH`)." + }, + "document_ai_unsupported_file_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "REVIEW", + "description": "What to do when the upload is empty or not a processable file (`DOCUMENT_AI_UNSUPPORTED_FILE`)." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, with the uploaded document, the extracted values, the field definitions and the name-match snapshot stored. When `false`, nothing is stored and `request_id` is a transient UUID for response correlation only; the call is still billed.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"onboarding\"}`) — it is parsed into an object.", + "example": { + "flow": "onboarding" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Document processed. `document_ai.status` is `Approved`, `Declined`, or `In Review`; every detected issue is itemized in `document_ai.warnings`. A problematic document still returns `200` — inspect the body, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Payslip read cleanly, name matched", + "value": { + "request_id": "9d6a3b1e-2f0c-4a8e-9b7d-1c2e3f4a5b6c", + "document_ai": { + "status": "Approved", + "extracted_data": { + "employer_name": "Acme Payments Ltd", + "employee_name": "Sophia Martinez", + "net_pay": 3120.5, + "pay_date": "2026-07-31" + }, + "detected_codes": [ + { + "type": "CODE_128", + "page_number": 1, + "position": [ + [ + 96, + 456 + ], + [ + 96, + 616 + ], + [ + 1012, + 611 + ], + [ + 1015, + 456 + ] + ], + "x": 96, + "y": 456, + "width": 919, + "height": 160, + "page_width": 1200, + "page_height": 1700, + "raw_payload": "SU5WLTIwMjYtMDA0Mg==", + "parsed_payload": "INV-2026-0042" + } + ], + "fields": [ + { + "key": "employer_name", + "name": "Employer name", + "instruction": "The employer or company issuing the payslip.", + "type": "text", + "required": true, + "is_full_name": false + }, + { + "key": "employee_name", + "name": "Employee name", + "instruction": "The full name of the employee as printed on the payslip.", + "type": "text", + "required": true, + "is_full_name": true + }, + { + "key": "net_pay", + "name": "Net pay", + "instruction": "The net amount paid to the employee for the period.", + "type": "number", + "required": true, + "is_full_name": false + }, + { + "key": "pay_date", + "name": "Pay date", + "instruction": "The payment date shown on the payslip.", + "type": "date", + "required": false, + "is_full_name": false + } + ], + "name_match_score": 100.0, + "name_on_document": "Sophia Martinez", + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": { + "flow": "onboarding" + }, + "created_at": "2026-08-19T16:40:12.318Z" + } + }, + "In Review — missing required field": { + "summary": "`net_pay` could not be read; default action REVIEW", + "value": { + "request_id": "3f2a9c7b-5d4e-4b1a-8c6f-0e9d8c7b6a5f", + "document_ai": { + "status": "In Review", + "extracted_data": { + "employer_name": "Acme Payments Ltd", + "employee_name": "Sophia Martinez", + "net_pay": null, + "pay_date": "2026-07-31" + }, + "detected_codes": [], + "fields": [ + { + "key": "employer_name", + "name": "Employer name", + "instruction": "The employer or company issuing the payslip.", + "type": "text", + "required": true, + "is_full_name": false + }, + { + "key": "employee_name", + "name": "Employee name", + "instruction": "The full name of the employee as printed on the payslip.", + "type": "text", + "required": true, + "is_full_name": true + }, + { + "key": "net_pay", + "name": "Net pay", + "instruction": "The net amount paid to the employee for the period.", + "type": "number", + "required": true, + "is_full_name": false + }, + { + "key": "pay_date", + "name": "Pay date", + "instruction": "The payment date shown on the payslip.", + "type": "date", + "required": false, + "is_full_name": false + } + ], + "name_match_score": 100.0, + "name_on_document": "Sophia Martinez", + "warnings": [ + { + "risk": "DOCUMENT_AI_MISSING_REQUIRED_FIELDS", + "feature": "DOCUMENT_AI", + "additional_data": { + "missing_fields": [ + "Net pay" + ] + }, + "log_type": "warning", + "short_description": "Required fields could not be extracted", + "long_description": "One or more fields configured as required could not be extracted from the uploaded document." + } + ] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-08-19T16:41:05.002Z" + } + }, + "Declined — suspected manipulation": { + "summary": "PDF forensics flagged a known editor; default action DECLINE", + "value": { + "request_id": "7c1d2e3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f", + "document_ai": { + "status": "Declined", + "extracted_data": { + "employer_name": "Acme Payments Ltd", + "employee_name": "Sophia Martinez", + "net_pay": 3120.5, + "pay_date": "2026-07-31" + }, + "detected_codes": [], + "fields": [ + { + "key": "employer_name", + "name": "Employer name", + "instruction": "The employer or company issuing the payslip.", + "type": "text", + "required": true, + "is_full_name": false + }, + { + "key": "employee_name", + "name": "Employee name", + "instruction": "The full name of the employee as printed on the payslip.", + "type": "text", + "required": true, + "is_full_name": true + }, + { + "key": "net_pay", + "name": "Net pay", + "instruction": "The net amount paid to the employee for the period.", + "type": "number", + "required": true, + "is_full_name": false + }, + { + "key": "pay_date", + "name": "Pay date", + "instruction": "The payment date shown on the payslip.", + "type": "date", + "required": false, + "is_full_name": false + } + ], + "name_match_score": null, + "name_on_document": "Sophia Martinez", + "warnings": [ + { + "risk": "DOCUMENT_AI_SUSPECTED_MANIPULATION", + "feature": "DOCUMENT_AI", + "additional_data": { + "detection_method": "known_editor", + "processed_by_known_editor": "ilovepdf" + }, + "log_type": "error", + "short_description": "Suspected document manipulation", + "long_description": "The system detected signs of potential manipulation or editing in the uploaded document." + } + ] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-08-19T16:42:40.771Z" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "document_ai": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "description": "`Approved` when no warning resolves to a decline or review; `Declined` when at least one risk maps to `DECLINE`; otherwise `In Review` when at least one maps to `REVIEW`. The `warnings` list explains why." + }, + "extracted_data": { + "type": "object", + "additionalProperties": true, + "description": "One entry per requested field key. Values are typed per the field `type` — strings for `text`, numbers for `number`, `YYYY-MM-DD` strings for `date` — or `null` when the value could not be read.", + "example": { + "employer_name": "Acme Payments Ltd", + "employee_name": "Sophia Martinez", + "net_pay": 3120.5, + "pay_date": "2026-07-31" + } + }, + "detected_codes": { + "type": "array", + "description": "QR codes and barcodes detected across every processed page. Empty when none are found. A located but undecodable QR code has `parsed_payload: null`.", + "items": { + "$ref": "#/components/schemas/DocumentAIDetectedCode" + } + }, + "fields": { + "type": "array", + "description": "The normalized field definitions that were used for this extraction, echoed back (also stored with the session when `save_api_request=true`).", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "instruction": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "text", + "number", + "date" + ] + }, + "required": { + "type": "boolean" + }, + "is_full_name": { + "type": "boolean" + } + } + } + }, + "name_match_score": { + "type": "number", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Fuzzy similarity (0–100) between `name_on_document` and the expected person/company name. Returned whenever both a name value and an expected name exist — on matches **and** mismatches; `null` otherwise.", + "example": 96.0 + }, + "name_on_document": { + "type": "string", + "nullable": true, + "description": "The name value taken from the document (the `is_full_name` field, or the first name-like text field) that was compared against the expected name. `null` when no name value was available.", + "example": "Sophia Martinez" + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. Every entry explains one detected risk; entries with `log_type: \"error\"` set `status` to `Declined`, entries with `log_type: \"warning\"` set it to `In Review`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "DOCUMENT_AI_UNREADABLE_DOCUMENT", + "DOCUMENT_AI_MISSING_REQUIRED_FIELDS", + "DOCUMENT_AI_SUSPECTED_MANIPULATION", + "DOCUMENT_AI_NAME_MISMATCH", + "DOCUMENT_AI_UNSUPPORTED_FILE" + ], + "description": "Machine-readable risk code. Each one is governed by the matching `document_ai_*_action` request option.", + "example": "DOCUMENT_AI_MISSING_REQUIRED_FIELDS" + }, + "feature": { + "type": "string", + "enum": [ + "DOCUMENT_AI" + ], + "description": "Feature that raised the warning. Always `DOCUMENT_AI` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Extra context for the risk: `{\"missing_fields\": [\"Net pay\"]}` for missing required fields, `{\"name_on_document\", \"score\", \"threshold\"}` for a name mismatch, and the forensic finding (`detection_method`, producer/editor details) for suspected manipulation. `null` when there is nothing to add.", + "example": { + "missing_fields": [ + "Net pay" + ] + } + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity derived from the configured action: `error` (DECLINE), `warning` (REVIEW), `information` (NO_ACTION)." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-liner for the risk.", + "example": "Required fields could not be extracted" + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation, interpolated with `additional_data` where relevant." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the request was processed (UTC, ISO 8601)." + } + } + } + } + } + }, + "400": { + "description": "Validation or processing error. Field-level problems return DRF's standard envelope (one array of messages per offending field). Encrypted or unreadable documents and extraction failures return `{\"error\": ...}`.", + "content": { + "application/json": { + "examples": { + "Missing document": { + "summary": "`document` not included in the form data", + "value": { + "document": [ + "No file was submitted." + ] + } + }, + "Invalid fields payload": { + "summary": "`fields` is not one JSON-encoded array string", + "value": { + "fields": [ + "fields must be a valid JSON array string." + ] + } + }, + "Duplicate field keys": { + "summary": "Two items share the same `key`", + "value": { + "fields": [ + "Every fields item must have a unique key." + ] + } + }, + "Conflicting expectations": { + "summary": "Person and company expectations sent together", + "value": { + "non_field_errors": [ + "Provide either person expected-name fields or expected_company_name, not both." + ] + } + }, + "Invalid metadata": { + "summary": "`metadata` is not a JSON object (or contains a NUL character)", + "value": { + "metadata": [ + "Metadata must be a JSON object." + ] + } + }, + "Oversized vendor_data": { + "summary": "`vendor_data` over the 2,000-byte cap", + "value": { + "vendor_data": [ + "vendor_data is too long (maximum 2000 bytes)." + ] + } + }, + "Encrypted PDF": { + "summary": "Password-protected PDF", + "value": { + "error": "The PDF is encrypted. Please upload a decrypted PDF or a photo instead." + } + }, + "Extraction failed": { + "summary": "The extraction pipeline could not process the document", + "value": { + "error": [ + "Error extracting Document AI information" + ] + } + } + }, + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls — and contract-billed organizations whose rate card has no Document AI API entry — return `403` with `{\"error\": ...}` before any document processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + }, + "Contract rate missing": { + "summary": "Contract-billed organization whose rate card has no Document AI API entry", + "value": { + "error": "Document AI API is not included in your contract rate card. Contact support@didit.me to enable it." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "error": { + "type": "string" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.document_ai.analyze(\n document=open(\"document.pdf\", \"rb\"),\n fields=\"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.documentAi.analyze({\n document: await fileFromPath(\"document.pdf\"),\n fields: \"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.DocumentAi.Analyze(ctx, didit.DocumentAiAnalyzeParams{\n\tDocument: didit.FileFromPath(\"document.pdf\"),\n\tFields: \"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.types.DocumentAiAnalyzeParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.documentAi().analyze(DocumentAiAnalyzeParams.builder()\n .document(Path.of(\"document.pdf\"))\n .fields(\"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "documentAi->analyze([\n 'document' => File::fromPath('document.pdf'),\n 'fields' => '[{\"key\": \"employer_name\", \"name\": \"Employer name\", \"instruction\": \"The employer or company issuing the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": false}, {\"key\": \"employee_name\", \"name\": \"Employee name\", \"instruction\": \"The full name of the employee as printed on the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": true}, {\"key\": \"net_pay\", \"name\": \"Net pay\", \"instruction\": \"The net amount paid to the employee for the period.\", \"type\": \"number\", \"required\": true, \"is_full_name\": false}, {\"key\": \"pay_date\", \"name\": \"Pay date\", \"instruction\": \"The payment date shown on the payslip.\", \"type\": \"date\", \"required\": false, \"is_full_name\": false}]',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.document_ai.analyze(\n document: Pathname(\"document.pdf\"),\n fields: \"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.DocumentAi.AnalyzeAsync(new DocumentAiAnalyzeParams\n{\n Document = FileUpload.FromPath(\"document.pdf\"),\n Fields = \"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/aml/": { + "post": { + "summary": "AML screening (standalone)", + "operationId": "post_v3aml", + "x-didit-sdk": { + "resource": "aml", + "method": "screen", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "Idempotency-Key", + "in": "header", + "required": false, + "description": "Optional retry-safety key, up to 255 characters. Reusing the same key with the same request body returns the original response without running or billing a second screening. Reusing the key with a different body returns `409`.", + "schema": { + "type": "string", + "maxLength": 255 + } + } + ], + "description": "Screen a person or company against PEP, sanctions, warning/watchlist, and adverse-media datasets in one call.\n\n**Two-score model.** Each hit carries a `match_score` (0–100, identity confidence) and a `risk_score` (0–100, entity risk). Hits whose `match_score` is below `aml_match_score_threshold` (default **93**) are tagged `review_status: \"False Positive\"` and ignored for risk purposes; the highest `risk_score` among the remaining hits becomes `aml.score`. The final `aml.status` follows your thresholds: `score` > `aml_score_review_threshold` (default **100**) → `Declined`; `score` > `aml_score_approve_threshold` (default **80**) → `In Review`; otherwise `Approved`. Whenever `score` is non-zero and ≥ the approve threshold a `POSSIBLE_MATCH_FOUND` warning is added (a score of exactly 0 never warns).\n\n**Match-score weights.** `aml_name_weight` + `aml_dob_weight` + `aml_country_weight` must sum to exactly 100 (defaults 60/25/15). Components missing from the request are re-normalized away; a matching `document_number` acts as a golden key that overrides `match_score` to 100.\n\n**Latency.** Plain screenings typically complete in a few seconds. With `include_adverse_media=true` the API waits for adverse-media enrichment (an initial ~5 s pause plus up to ~25 s of polling) — configure a client timeout of **at least 60 seconds**.\n\n**Persistence.** `save_api_request` defaults to **true**: the screening is stored as a session (`request_id` works with `GET /v3/session/{sessionId}/decision/`), it appears in the console, a `status.updated` webhook fires, and hit review statuses can be updated later. With `save_api_request=false` nothing is stored and `request_id` is a transient correlation id. `include_ongoing_monitoring=true` keeps re-screening the profile and requires `save_api_request=true` (enforced with a `400`).\n\n**Idempotency.** Send an `Idempotency-Key` header when retrying after a timeout or transient error. The key is scoped to your application and the exact request body. The first completed request stores its response; later identical retries return that same response and do not run or bill another AML screening. If the original request is still running, retry later with the same key. If the same key is reused with a different body, the API returns `409`.\n\n**Billing.** One AML API credit is charged after the first successful screening. Replayed idempotent responses are not charged again; an insufficient balance returns `403` before any new screening happens.\n\n**Sandbox.** Keys from sandbox applications skip screening and billing entirely and return a mocked result — `Approved` with zero hits unless you pass `sandbox_scenario`. `save_api_request` works the same as on live keys, so a saved sandbox screening is retrievable through `GET /v3/session/{sessionId}/decision/` and `GET /v3/session/{sessionId}/generate-pdf/`.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "full_name" + ], + "properties": { + "full_name": { + "type": "string", + "minLength": 2, + "description": "Full name of the person or company to screen. Minimum **2 characters** - the screening provider's own floor. A shorter or blank name returns `400` on this field before any screening runs, so nothing is billed.", + "example": "Antonio Ejemplo Modelo" + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "default": "person", + "description": "Type of entity to screen. Defaults to `person`." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth (persons) or incorporation date (companies), `YYYY-MM-DD`. Improves match precision via the DOB weight.", + "example": "1972-02-29" + }, + "nationality": { + "type": "string", + "description": "ISO 3166-1 **alpha-2** country code (e.g. `ES`). Invalid codes return `400`. Improves match precision via the country weight.", + "example": "ES" + }, + "document_number": { + "type": "string", + "description": "Identity document number. Not weighted — acts as a golden key: an exact match overrides the hit's `match_score` to 100, a hard mismatch penalizes it. Sent to the screening provider as a search filter only when it is **2-50 characters**; a value outside that range is accepted and stored, and the screening still runs and is billed, just not narrowed by document number." + }, + "aml_score_approve_threshold": { + "type": "number", + "minimum": 0, + "maximum": 100, + "default": 80, + "description": "Risk-score threshold at or below which the result is `Approved`. Must be ≤ `aml_score_review_threshold` (else `400`)." + }, + "aml_score_review_threshold": { + "type": "number", + "minimum": 0, + "maximum": 100, + "default": 100, + "description": "Risk-score threshold above which the result is `Declined`. Scores between the two thresholds produce `In Review`." + }, + "aml_name_weight": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 60, + "description": "Weight of name similarity in the per-hit match score. The three weights must sum to exactly 100 (else `400`)." + }, + "aml_dob_weight": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 25, + "description": "Weight of date of birth in the per-hit match score." + }, + "aml_country_weight": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 15, + "description": "Weight of country/nationality in the per-hit match score." + }, + "aml_match_score_threshold": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 93, + "description": "Per-hit cutoff: hits with `match_score` below this are `False Positive` (excluded from risk assessment); at or above are `Unreviewed` possible matches." + }, + "include_adverse_media": { + "type": "boolean", + "default": false, + "description": "Also search news media for negative coverage. Adds up to ~30 s of latency while results are gathered — use a ≥60 s client timeout." + }, + "include_ongoing_monitoring": { + "type": "boolean", + "default": false, + "description": "Keep re-screening this profile and push changes via webhook. Requires `save_api_request=true` (else `400`)." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "Persist the screening as a session (console visibility, decision endpoint, webhooks, hit review). Set to `false` for a stateless check." + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for the screened user; echoed back and stored with the session." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON stored with the request and echoed back." + }, + "sandbox_scenario": { + "type": "string", + "description": "Sandbox-only scenario slug. Only accepted when the API key belongs to a sandbox-mode application; rejected on live applications. A standalone screening has no session to arm, so the slug goes on the screening request itself: `decline_aml_hit` returns one production-shaped hit and settles the screening as `Declined`, `review_aml_possible_match` settles it `In Review`, and a scenario that arms no AML outcome leaves the screening clean. See the scenario catalog at GET /v1/sandbox/scenarios/ or [Sandbox & Test Data](/integration/sandbox-testing).", + "example": "decline_aml_hit" + } + } + }, + "example": { + "full_name": "Antonio Ejemplo Modelo", + "entity_type": "person", + "date_of_birth": "1972-02-29", + "nationality": "ES", + "include_adverse_media": false, + "vendor_data": "user-1234" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/aml/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -H 'Idempotency-Key: aml-user-1234-2026-08-20' \\\n -d '{\n \"full_name\": \"Antonio Ejemplo Modelo\",\n \"entity_type\": \"person\",\n \"date_of_birth\": \"1972-02-29\",\n \"nationality\": \"ES\",\n \"include_adverse_media\": false,\n \"vendor_data\": \"user-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.aml.screen(\n full_name=\"Antonio Ejemplo Modelo\",\n entity_type=\"person\",\n date_of_birth=\"1972-02-29\",\n nationality=\"ES\",\n include_adverse_media=False,\n vendor_data=\"user-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.aml.screen({\n full_name: \"Antonio Ejemplo Modelo\",\n entity_type: \"person\",\n date_of_birth: \"1972-02-29\",\n nationality: \"ES\",\n include_adverse_media: false,\n vendor_data: \"user-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.AML.Screen(ctx, didit.AMLScreenParams{\n\tFullName: \"Antonio Ejemplo Modelo\",\n\tEntityType: didit.Some(didit.AMLScreenParamsEntityType(\"person\")),\n\tDateOfBirth: didit.String(\"1972-02-29\"),\n\tNationality: didit.String(\"ES\"),\n\tIncludeAdverseMedia: didit.Bool(false),\n\tVendorData: didit.String(\"user-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport me.didit.Didit;\nimport me.didit.types.AmlScreenParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.aml().screen(AmlScreenParams.builder()\n .fullName(\"Antonio Ejemplo Modelo\")\n .entityType(AmlScreenParams.EntityType.of(\"person\"))\n .dateOfBirth(LocalDate.parse(\"1972-02-29\"))\n .nationality(\"ES\")\n .includeAdverseMedia(false)\n .vendorData(\"user-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "aml->screen([\n 'full_name' => 'Antonio Ejemplo Modelo',\n 'entity_type' => 'person',\n 'date_of_birth' => '1972-02-29',\n 'nationality' => 'ES',\n 'include_adverse_media' => false,\n 'vendor_data' => 'user-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.aml.screen(\n full_name: \"Antonio Ejemplo Modelo\",\n entity_type: \"person\",\n date_of_birth: \"1972-02-29\",\n nationality: \"ES\",\n include_adverse_media: false,\n vendor_data: \"user-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Aml.ScreenAsync(new AmlScreenParams\n{\n FullName = \"Antonio Ejemplo Modelo\",\n EntityType = new AmlScreenParamsEntityType(\"person\"),\n DateOfBirth = \"1972-02-29\",\n Nationality = \"ES\",\n IncludeAdverseMedia = false,\n VendorData = \"user-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Screening completed. `aml.status` reflects your thresholds; every hit at or above the internal inclusion floor is listed in `aml.hits` with its identity `match_score`, entity `risk_score`, and a `score_breakdown` explaining the match.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "aml": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "`Declined` when `score` > `aml_score_review_threshold`; `In Review` when `score` > `aml_score_approve_threshold`; `Approved` otherwise (including when no score is available)." + }, + "total_hits": { + "type": "integer", + "description": "Number of entries in `hits`." + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Echo of the screened entity type." + }, + "hits": { + "type": "array", + "items": { + "type": "object", + "description": "One screening hit. `match_score` (0–100) measures identity confidence — hits below `aml_match_score_threshold` are tagged `review_status: \"False Positive\"` and excluded from the risk assessment; hits at or above it are `\"Unreviewed\"` possible matches. `risk_score` (0–100) measures how risky the matched entity is and drives the top-level `aml.score`.", + "properties": { + "id": { + "type": "string", + "nullable": true, + "description": "Didit's stable identifier for the hit: `hit_` followed by 24 hexadecimal characters. The same matched entity keeps the same `id` when the subject is screened again. Pass it as `hit_id` to the AML hit review endpoints." + }, + "url": { + "type": "string", + "nullable": true, + "description": "Source URL for the matched entity, when available." + }, + "match": { + "type": "boolean", + "nullable": true, + "description": "Legacy exact-match flag. Prefer `match_score` + `review_status`." + }, + "score": { + "type": "number", + "nullable": true, + "description": "DEPRECATED weighted match score in the 0–1 range. Use `match_score` instead." + }, + "match_score": { + "type": "number", + "nullable": true, + "description": "Identity confidence (0–100). Compared against `aml_match_score_threshold` to split False Positives from Possible Matches." + }, + "risk_score": { + "type": "number", + "nullable": true, + "description": "Entity risk score (0–100). The highest `risk_score` among non-false-positive hits becomes the top-level `aml.score`." + }, + "target": { + "type": "boolean", + "nullable": true + }, + "caption": { + "type": "string", + "nullable": true, + "description": "Display name of the matched entity." + }, + "datasets": { + "type": "array", + "nullable": true, + "items": { + "type": "string", + "enum": [ + "PEP", + "PEP Level 1", + "PEP Level 2", + "PEP Level 3", + "PEP Level 4", + "Sanctions", + "Adverse Media", + "Warnings and Regulatory Enforcement", + "Fitness and Probity", + "SIP", + "SIE", + "Insolvency", + "Other Lists" + ] + }, + "description": "Risk categories the entity is listed under: `PEP` (with its level, `PEP Level 1` to `PEP Level 4`), `Sanctions`, `Adverse Media`, `Warnings and Regulatory Enforcement`, `Fitness and Probity`, `SIP`, `SIE` or `Insolvency`. `Other Lists` when the entity's source states none of these categories." + }, + "features": { + "type": "object", + "nullable": true + }, + "properties": { + "type": "object", + "nullable": true, + "description": "Entity profile: `name`, `alias`, `notes`, `gender`, `birthDate`, `birthPlace`, `nationality`, `position`, and other biographical arrays (each nullable)." + }, + "rca_name": { + "type": "string", + "nullable": true, + "description": "Name of the related close associate through which the entity was matched, when the hit is an RCA." + }, + "pep_matches": { + "type": "array", + "description": "PEP list entries backing this hit.", + "items": { + "type": "object", + "properties": { + "list_name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "source_url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "matched_name": { + "type": "string" + }, + "pep_position": { + "type": "string" + }, + "date_of_birth": { + "type": "string" + }, + "place_of_birth": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "type": "string" + } + }, + "education": { + "type": "array", + "items": { + "type": "string" + } + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "sanction_matches": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Sanctions list entries backing this hit (same backing-list shape as `pep_matches`, plus sanction-specific fields)." + }, + "warning_matches": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Warning/watchlist entries backing this hit." + }, + "adverse_media_matches": { + "type": "array", + "description": "Adverse-media articles. Populated only when `include_adverse_media=true`.", + "items": { + "type": "object", + "properties": { + "headline": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "sentiment": { + "type": "string" + }, + "sentiment_score": { + "type": "number" + }, + "source_url": { + "type": "string" + }, + "thumbnail": { + "type": "string", + "nullable": true + }, + "country": { + "type": "string", + "nullable": true + }, + "author_name": { + "type": "string", + "nullable": true + }, + "publication_date": { + "type": "string", + "nullable": true + }, + "adverse_keywords": { + "type": "array", + "items": { + "type": "string" + } + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "adverse_media_details": { + "type": "object", + "nullable": true, + "description": "Aggregated adverse-media sentiment for the entity (`sentiment`, `sentiment_score`, `adverse_keywords` frequency map). Populated only when `include_adverse_media=true`." + }, + "first_seen": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the entity first appeared in the datasets." + }, + "last_seen": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Most recent dataset refresh that included the entity." + }, + "linked_entities": { + "type": "array", + "description": "Known relationships of the matched entity (family, associates, companies).", + "items": { + "type": "object", + "properties": { + "name": { + "type": "array", + "items": { + "type": "string" + } + }, + "relation": { + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "type": "array", + "items": { + "type": "string" + } + }, + "details": { + "type": "array", + "items": { + "type": "string" + } + }, + "active": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "risk_view": { + "type": "object", + "nullable": true, + "description": "Per-dimension risk breakdown (`crimes`, `countries`, `categories`, `custom_list`), each with `score`, `weightage`, `risk_level`, and a `risk_scores` map." + }, + "additional_information": { + "type": "object", + "nullable": true, + "description": "Free-form extra context, e.g. a `flag_summary` array explaining why the entity is flagged." + }, + "review_status": { + "type": "string", + "nullable": true, + "enum": [ + "False Positive", + "Unreviewed", + "Confirmed Match", + "Inconclusive" + ], + "description": "`False Positive` — `match_score` below `aml_match_score_threshold`, excluded from risk assessment. `Unreviewed` — possible match awaiting review. `Confirmed Match` / `Inconclusive` — set manually by a reviewer (via the Business Console) on saved sessions; that review capability is not part of this spec." + }, + "score_breakdown": { + "type": "object", + "nullable": true, + "description": "How `match_score` was computed. Name, date of birth, and country each contribute `_score` × `_weight_normalized` points; weights are re-normalized when a component is missing from the screened data. The document number is not weighted — it acts as a 'golden key': `MATCH` overrides the score to 100, `NEUTRAL` leaves it unchanged, `HARD_MISMATCH` applies a penalty.", + "properties": { + "name_score": { + "type": "integer", + "nullable": true, + "description": "Raw name similarity (0–100)." + }, + "name_weight": { + "type": "integer", + "nullable": true, + "description": "Configured name weight (`aml_name_weight`)." + }, + "name_weight_normalized": { + "type": "number", + "nullable": true, + "description": "Name weight after re-normalization (0–100)." + }, + "name_contribution": { + "type": "number", + "nullable": true, + "description": "Points the name contributed to `total_score`." + }, + "dob_score": { + "type": "integer", + "nullable": true, + "description": "Date-of-birth score as a percentage of its weight (−100 to 100)." + }, + "dob_weight": { + "type": "integer", + "nullable": true, + "description": "Configured DOB weight (`aml_dob_weight`)." + }, + "dob_weight_normalized": { + "type": "number", + "nullable": true, + "description": "DOB weight after re-normalization (0–100)." + }, + "dob_contribution": { + "type": "number", + "nullable": true, + "description": "Points the DOB contributed to `total_score`." + }, + "country_score": { + "type": "integer", + "nullable": true, + "description": "Country score as a percentage of its weight (−100 to 100)." + }, + "country_weight": { + "type": "integer", + "nullable": true, + "description": "Configured country weight (`aml_country_weight`)." + }, + "country_weight_normalized": { + "type": "number", + "nullable": true, + "description": "Country weight after re-normalization (0–100)." + }, + "country_contribution": { + "type": "number", + "nullable": true, + "description": "Points the country contributed to `total_score`." + }, + "document_number_match_type": { + "type": "string", + "nullable": true, + "enum": [ + "MATCH", + "NEUTRAL", + "HARD_MISMATCH" + ], + "description": "Golden-key result for the document number comparison." + }, + "document_number_effect": { + "type": "string", + "nullable": true, + "description": "Human-readable description of the document-number effect." + }, + "total_score": { + "type": "integer", + "nullable": true, + "description": "Final weighted match score (0–100) — mirrors `match_score`." + } + } + } + } + } + }, + "score": { + "type": "number", + "nullable": true, + "description": "Overall AML risk score (0–100): the highest `risk_score` among hits that are not False Positives. Drives `status` via the two thresholds." + }, + "screened_data": { + "type": "object", + "description": "Echo of what was screened: `full_name`, `date_of_birth`, `nationality`, `document_number` (absent fields are `null`).", + "properties": { + "full_name": { + "type": "string" + }, + "date_of_birth": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "document_number": { + "type": "string", + "nullable": true + } + } + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature that raised the warning." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk identifier." + }, + "additional_data": { + "type": "object", + "nullable": true, + "description": "Extra context for the warning, when available." + }, + "log_type": { + "type": "string", + "description": "`warning`, `information`, or `error` — how the risk affected the outcome." + }, + "short_description": { + "type": "string", + "description": "One-line human-readable summary." + }, + "long_description": { + "type": "string", + "description": "Full human-readable explanation." + } + } + }, + "description": "Contains a `POSSIBLE_MATCH_FOUND` warning whenever `score` is non-zero and ≥ `aml_score_approve_threshold` (a score of exactly 0 never warns); empty otherwise." + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "No hits (Approved)": { + "summary": "Clean screening — zero hits", + "value": { + "request_id": "d27c5509-7a66-45a9-b05e-d69d9f4c336a", + "aml": { + "status": "Approved", + "total_hits": 0, + "entity_type": "person", + "hits": [], + "score": 0, + "screened_data": { + "full_name": "Maximilian Mustermann-Doe", + "date_of_birth": "1992-03-14", + "nationality": "DE", + "document_number": null + }, + "warnings": [] + }, + "vendor_data": "user-1234", + "metadata": { + "internal_ref": "abc-123" + }, + "created_at": "2026-06-11T10:21:07.013938+00:00" + } + }, + "PEP hit (Approved — risk below approve threshold)": { + "summary": "One PEP possible match; risk score 73 stays under the default approve threshold of 80", + "value": { + "request_id": "0b7e7a3e-65a1-4f3d-9c7a-1de2f9a40111", + "aml": { + "status": "Approved", + "total_hits": 1, + "entity_type": "person", + "hits": [ + { + "id": "hit_7c1e9a40b2d35f86e0a4c912", + "url": "https://news.example.org/articles/sample", + "match": false, + "score": 0.93, + "match_score": 93.0, + "risk_score": 73.0, + "target": null, + "caption": "Antonio Ejemplo Modelo", + "datasets": [ + "PEP Level 1", + "PEP" + ], + "features": null, + "rca_name": "", + "first_seen": "2026-01-07T00:00:00", + "last_seen": "2026-01-07T00:00:00", + "review_status": "Unreviewed", + "properties": { + "name": [ + "Antonio Ejemplo Modelo", + "Antonio Ejemplo" + ], + "alias": [ + "Antonio Ejemplo Modelo", + "Ejemplo Modelo, Pedro" + ], + "notes": [ + "Prime Minister of Spain" + ], + "gender": [ + "male" + ], + "birthDate": [ + "1972-02-29" + ], + "birthPlace": [ + "Madrid, Spain" + ], + "nationality": null, + "position": null + }, + "risk_view": { + "crimes": { + "score": 0.0, + "weightage": 20, + "risk_level": "Low", + "risk_scores": {} + }, + "countries": { + "score": 9.64, + "weightage": 30, + "risk_level": "Low", + "risk_scores": { + "Spain": 25.71 + } + }, + "categories": { + "score": 62.5, + "weightage": 50, + "risk_level": "High", + "risk_scores": { + "PEP": 100.0, + "PEP Level 1": 100.0 + } + }, + "custom_list": {} + }, + "score_breakdown": { + "name_score": 89, + "name_weight": 60, + "name_weight_normalized": 60.0, + "name_contribution": 53.4, + "dob_score": 100, + "dob_weight": 25, + "dob_weight_normalized": 25.0, + "dob_contribution": 25.0, + "country_score": 100, + "country_weight": 15, + "country_weight_normalized": 15.0, + "country_contribution": 15.0, + "document_number_match_type": "NEUTRAL", + "document_number_effect": "No document number provided for screening", + "total_score": 93 + }, + "pep_matches": [ + { + "aliases": [], + "education": [], + "list_name": "Presidency of the Government of Spain", + "publisher": "Presidency of the Government of Spain", + "source_url": "https://news.example.org/articles/sample", + "description": "The Presidency of the Government is the central institutional body in Spain that supports the Prime Minister and coordinates the general action of the central government.", + "matched_name": "Antonio Ejemplo Modelo", + "pep_position": "", + "date_of_birth": "1972-02-29", + "other_sources": [], + "place_of_birth": "Madrid, Spain" + } + ], + "sanction_matches": [], + "warning_matches": [], + "adverse_media_matches": [], + "adverse_media_details": null, + "linked_entities": [ + { + "name": [ + "Maria Begona Gomez Fernandez" + ], + "active": [], + "status": [], + "details": [], + "relation": [ + "spouse" + ] + } + ], + "additional_information": { + "flag_summary": [ + "Antonio Ejemplo Modelo has been flagged as a Level 1 Politically Exposed Person (PEP) from Spain, serving as the President of the Government of Spain." + ] + } + } + ], + "score": 73.0, + "screened_data": { + "full_name": "Pedro Sanchez Perez-Castejon", + "date_of_birth": "1972-02-29", + "nationality": "ES", + "document_number": null + }, + "warnings": [] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-11T10:23:44.812000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems return DRF's standard envelope (one array of messages per offending field); cross-field rules (weight sum, threshold ordering, monitoring-without-save) return `{\"error\": [...]}`.", + "content": { + "application/json": { + "examples": { + "Missing full_name": { + "summary": "`full_name` is the only required field", + "value": { + "full_name": [ + "This field is required." + ] + } + }, + "Short full_name": { + "summary": "`full_name` must be at least 2 characters", + "value": { + "full_name": [ + "Ensure this field has at least 2 characters." + ] + } + }, + "Invalid nationality": { + "summary": "`nationality` must be ISO 3166-1 alpha-2", + "value": { + "nationality": [ + "Invalid ISO 3166-1 alpha-2 country code." + ] + } + }, + "Weights do not sum to 100": { + "summary": "`aml_name_weight` + `aml_dob_weight` + `aml_country_weight` ≠ 100", + "value": { + "error": [ + "AML weights must sum to 100. Current sum: 90 (name: 50, dob: 25, country: 15)" + ] + } + }, + "Thresholds out of order": { + "summary": "Approve threshold above review threshold", + "value": { + "error": [ + "Approve threshold (90.0) cannot be greater than review threshold (80.0)" + ] + } + }, + "Monitoring without saving": { + "summary": "`include_ongoing_monitoring=true` with `save_api_request=false`", + "value": { + "error": [ + "Ongoing monitoring can only be enabled when the API request is saved." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/face-search/": { + "post": { + "summary": "Face Search", + "description": "Search a face image (1:N) against your application's face search index — faces enrolled from verification sessions, saved standalone API calls, direct user-profile face uploads, and your block/allow lists — and get back ranked similarity matches plus blocklist and duplicate warnings.\n\n**How it works.** The largest detected face in `user_image` is embedded and searched against the index, scoped to your application. Up to **5 matches** above the similarity floor are returned in `matches`, each with `similarity_percentage`, the originating session (`session_id`, `session_number`, `status`, `vendor_data`), `user_details` extracted during that session's document verification, `is_blocklisted` / `is_allowlisted` flags, and a `source` discriminator (`session`, `imported`, or `list_entry`).\n\n- `search_type=most_similar` (default) ranks every enrolled face by similarity — use it for deduplication and fraud-ring investigation.\n- `search_type=blocklisted_or_approved` restricts candidates to blocklisted faces, allowlisted faces, faces from approved sessions, and imported user-profile faces, ranking blocklisted entries first — use it when the primary goal is blocklist screening.\n\n`status` is `Declined` only when a `FACE_IN_BLOCKLIST` or `POSSIBLE_FACE_IN_BLOCKLIST` warning fires; `DUPLICATED_FACE` / `POSSIBLE_DUPLICATED_FACE` (`information`) and `MULTIPLE_FACES_DETECTED` (`warning`) never decline. Unlike Passive Liveness, this endpoint does **not** exclude prior sessions with the same `vendor_data` from matching. A passive-liveness analysis also runs internally and is stored with the persisted session, but its result is not part of this response.\n\n**Billing.** Each `200` response consumes one Face Search API credit (paid from your plan credit or top-up balance). Insufficient balance returns `403` before any image processing.\n\n**Session persistence and face enrollment (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session (Business Console, `GET /v3/session/{sessionId}/decision/` via the returned `request_id`, `status.updated` webhook) and the searched face is enrolled into your face search index. Faces enrolled by Face Search calls are excluded from future Face Search results, so repeated searches never match each other. When `false`, the call is one-shot: nothing is stored, the face is not enrolled, and `match_image_url` values are internal storage paths rather than downloadable URLs.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload, no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3face-search", + "x-didit-sdk": { + "resource": "face_search", + "method": "search", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "user_image" + ], + "properties": { + "user_image": { + "type": "string", + "format": "binary", + "description": "Front-facing face image to search with. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp`. Maximum upload size: **5 MB** (larger files are rejected with `400`). Images are automatically compressed to ~0.5 MB before processing, so very high resolutions do not improve accuracy. The image must contain at least one detectable face — otherwise the endpoint returns `400`. When several faces are present, the largest one is searched and a `MULTIPLE_FACES_DETECTED` warning is added." + }, + "search_type": { + "type": "string", + "default": "most_similar", + "enum": [ + "most_similar", + "blocklisted_or_approved" + ], + "description": "Search policy. `most_similar` (default) ranks every enrolled face in your application by similarity. `blocklisted_or_approved` restricts candidates to blocklisted faces, allowlisted faces, faces from approved sessions, and imported user-profile faces — with blocklisted entries ranked first.", + "example": "most_similar" + }, + "rotate_image": { + "type": "boolean", + "default": false, + "description": "When `true`, the service tries 90-degree rotations of the input and uses the orientation that yields the best face detection. Useful when EXIF orientation is missing. Adds latency.", + "example": false + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session, emits a `status.updated` webhook, presigns `match_image_url` values, and enrolls the searched face into your face search index (Face Search enrollments are excluded from future searches). When `false`, the search is one-shot — no session is stored and the face is not enrolled.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque identifier (your user id, email, UUID…) stored on the persisted session and echoed back. It does not exclude same-user faces from the search results.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back. In multipart requests, send it as a JSON-encoded string field — it is parsed into an object.", + "example": { + "flow": "dedup_check" + } + } + } + }, + "example": { + "user_image": "(binary JPEG/PNG selfie)", + "search_type": "most_similar", + "rotate_image": false, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "dedup_check" + } + } + } + } + }, + "responses": { + "200": { + "description": "Face search completed. `face_search.matches` holds up to 5 similar faces ordered by similarity; an empty array means no enrolled face exceeded the similarity floor. `status` is `Declined` only on blocklist hits — duplicate matches alone return `Approved`, so inspect `matches` and `warnings`, not just `status`. When `save_api_request=true`, `request_id` is the persisted session id. Each match carries `source` (`session`, `imported`, `list_entry`, or `retained_template`), `vendor_user_id`, and `biometric_template_id`; a `retained_template` match comes from an image-free biometric template kept after the source session was deleted and has `null` session fields, `match_image_url`, and `user_details`.", + "content": { + "application/json": { + "examples": { + "Blocklist hit (Declined)": { + "summary": "The face matches a blocklisted face", + "value": { + "request_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef", + "face_search": { + "status": "Declined", + "total_matches": 1, + "matches": [ + { + "session_id": "882c42d5-8a4d-4d20-8080-a22f57822c86", + "session_number": 323442, + "similarity_percentage": 99.99, + "source": "session", + "vendor_data": "user-1", + "verification_date": "2025-01-01T00:00:00Z", + "user_details": { + "full_name": "Jane Marie Doe", + "document_type": "ID", + "document_number": "X1234567" + }, + "match_image_url": "https:///face/3f6a1c2e/reference.jpg", + "status": "Approved", + "is_blocklisted": true, + "is_allowlisted": false, + "api_service": null, + "vendor_user_id": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f", + "biometric_template_id": null + } + ], + "user_image": { + "entities": [ + { + "bbox": [ + 40, + 40, + 120, + 120 + ], + "confidence": 0.732973 + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "FACE_IN_BLOCKLIST", + "feature": "LIVENESS", + "additional_data": { + "blocklisted_session_id": "882c42d5-8a4d-4d20-8080-a22f57822c86", + "blocklisted_session_number": 323442, + "api_service": null + }, + "log_type": "error", + "short_description": "Face in blocklist", + "long_description": "The system identified a face in the blocklist, which means the face is not allowed to be verified." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "Duplicate found (Approved)": { + "summary": "The face already appeared in another approved session — informational, no decline", + "value": { + "request_id": "5e0c3a1f-7b2d-4c8e-9f10-2a3b4c5d6e7f", + "face_search": { + "status": "Approved", + "total_matches": 1, + "matches": [ + { + "session_id": "1f2e3d4c-5b6a-7980-1122-334455667788", + "session_number": 1024, + "similarity_percentage": 87.42, + "source": "session", + "vendor_data": "user-9", + "verification_date": "2025-11-20T09:15:00Z", + "user_details": null, + "match_image_url": "https:///face/9c8b7a6d/reference.jpg", + "status": "Approved", + "is_blocklisted": false, + "is_allowlisted": false, + "api_service": "PASSIVE_LIVENESS", + "vendor_user_id": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f", + "biometric_template_id": null + } + ], + "user_image": { + "entities": [ + { + "bbox": [ + 40, + 40, + 120, + 120 + ], + "confidence": 0.732973 + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "DUPLICATED_FACE", + "feature": "LIVENESS", + "additional_data": { + "duplicated_session_id": "1f2e3d4c-5b6a-7980-1122-334455667788", + "duplicated_session_number": 1024, + "api_service": "PASSIVE_LIVENESS" + }, + "log_type": "information", + "short_description": "Duplicated face from other approved session", + "long_description": "The system identified a duplicated face from another approved session, requiring further investigation." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "No matches (Approved)": { + "summary": "No enrolled face exceeded the similarity floor", + "value": { + "request_id": "9d8c7b6a-5f4e-3d2c-1b0a-998877665544", + "face_search": { + "status": "Approved", + "total_matches": 0, + "matches": [], + "user_image": { + "entities": [ + { + "bbox": [ + 40, + 40, + 120, + 120 + ], + "confidence": 0.732973 + } + ], + "best_angle": 0 + }, + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "face_search": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Declined` only when a `FACE_IN_BLOCKLIST` or `POSSIBLE_FACE_IN_BLOCKLIST` warning fired. Duplicate matches and multiple-face warnings never decline." + }, + "total_matches": { + "type": "integer", + "description": "Number of entries in `matches` (0–5).", + "example": 1 + }, + "matches": { + "type": "array", + "maxItems": 5, + "description": "Up to 5 faces from your face search index that exceed the similarity floor, ordered by similarity (with blocklisted entries ranked first when `search_type=blocklisted_or_approved`). Empty when no enrolled face is similar enough.", + "items": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Id of the verification session the matched face belongs to. `null` when `source` is `imported`, `list_entry`, or `retained_template`.", + "example": "882c42d5-8a4d-4d20-8080-a22f57822c86" + }, + "session_number": { + "type": "integer", + "nullable": true, + "description": "Human-friendly session number shown in the Business Console. `null` for non-session matches.", + "example": 323442 + }, + "similarity_percentage": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 100, + "description": "Similarity between the submitted face and the matched face (0–100).", + "example": 99.99 + }, + "source": { + "type": "string", + "enum": [ + "session", + "imported", + "list_entry", + "retained_template" + ], + "description": "Where the matched face was enrolled from: `session` — a verification session or a saved standalone API call; `imported` — a face uploaded directly to a User profile; `list_entry` — a face attached to one of your lists; `retained_template` — an image-free biometric template stored on a User, listed under `/v3/biometric-templates/`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of the matched session or linked User for imported and retained-template faces.", + "example": "user-1" + }, + "vendor_user_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Linked User id. `null` for list entries and session matches without a User.", + "example": "2f7c1a9e-4b28-4a6d-97f1-83576f80e1b4" + }, + "biometric_template_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Id of the retained biometric template. Set only when `source` is `retained_template`; `null` otherwise.", + "example": null + }, + "verification_date": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the matched face was originally captured, or when a retained template was created (`YYYY-MM-DDThh:mm:ssZ`). `null` for `list_entry` matches.", + "example": "2025-01-01T00:00:00Z" + }, + "user_details": { + "type": "object", + "nullable": true, + "description": "Identity data extracted during the matched session's document verification. `null` when the matched session has no document data (e.g. liveness-only sessions) and always `null` for `retained_template` matches. For `source: imported` matches it is populated from the User profile instead: `full_name` set, `document_type`/`document_number` null.", + "properties": { + "full_name": { + "type": "string", + "nullable": true, + "example": "Jane Marie Doe" + }, + "document_type": { + "type": "string", + "nullable": true, + "example": "ID" + }, + "document_number": { + "type": "string", + "nullable": true, + "example": "X1234567" + } + } + }, + "match_image_url": { + "type": "string", + "nullable": true, + "description": "Time-limited URL (`https:///...`) of the matched face's reference image when `save_api_request=true`. With `save_api_request=false` this is an internal storage path that is not directly downloadable. Always `null` for `retained_template` matches because retained templates contain no image.", + "example": "https:///face/3f6a1c2e/reference.jpg" + }, + "status": { + "type": "string", + "nullable": true, + "description": "Status of the matched session (`Approved`, `Declined`, `In Review`, …). `null` for `imported`/`list_entry` matches.", + "example": "Approved" + }, + "is_blocklisted": { + "type": "boolean", + "description": "`true` when the matched face is on your face blocklist. Any sufficiently similar blocklisted match declines the search." + }, + "is_allowlisted": { + "type": "boolean", + "description": "`true` when the matched face is on one of your allowlists. Allowlisted matches suppress duplicate warnings." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "Set when the matched face was enrolled by a standalone API call (e.g. `PASSIVE_LIVENESS`); `null` for regular verification sessions and imported faces.", + "example": null + } + } + } + }, + "user_image": { + "type": "object", + "description": "Face-detection results for the submitted image. Entries here carry only `bbox` and `confidence`.", + "properties": { + "entities": { + "type": "array", + "description": "One entry per detected face. The largest face is the one searched.", + "items": { + "type": "object", + "properties": { + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box `[x_min, y_min, x_max, y_max]` in pixels.", + "example": [ + 40, + 40, + 120, + 120 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.732973 + } + } + } + }, + "best_angle": { + "type": "integer", + "description": "Rotation (degrees) that produced the best face detection; non-zero only when `rotate_image=true` corrected the orientation. Never null on this endpoint (defaults to 0 when the model omits it).", + "example": 0 + } + } + }, + "warnings": { + "type": "array", + "description": "Risk signals. `FACE_IN_BLOCKLIST` / `POSSIBLE_FACE_IN_BLOCKLIST` (`error`) decline the search; `MULTIPLE_FACES_DETECTED` (`warning`) and `DUPLICATED_FACE` / `POSSIBLE_DUPLICATED_FACE` (`information`) are advisory.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "MULTIPLE_FACES_DETECTED", + "FACE_IN_BLOCKLIST", + "POSSIBLE_FACE_IN_BLOCKLIST", + "DUPLICATED_FACE", + "POSSIBLE_DUPLICATED_FACE" + ], + "description": "Machine-readable risk code." + }, + "feature": { + "type": "string", + "enum": [ + "LIVENESS" + ], + "description": "Feature that raised the warning. Always `LIVENESS` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "`null` for `MULTIPLE_FACES_DETECTED`. Blocklist hits carry `{blocklisted_session_id, blocklisted_session_number, api_service}`; duplicate hits carry `{duplicated_session_id, duplicated_session_number, api_service}` pointing at the first matching session." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `warning` and `information` entries are advisory and never decline on their own." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated, e.g. `2026-06-12T01:04:42.763237+00:00`." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when `user_image` is missing, exceeds 5 MB, has an unsupported extension, or when **no face is detected** in the submitted image. Field errors use DRF's `{field: [messages]}` envelope; face-detection failures use `{\"error\": ...}`.", + "content": { + "application/json": { + "examples": { + "No face detected": { + "summary": "The image decodes fine but contains no detectable face", + "value": { + "error": "No face detected in the image" + } + }, + "Missing image": { + "summary": "`user_image` not included in the form data", + "value": { + "user_image": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp", + "value": { + "user_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 5 MB limit", + "value": { + "user_image": [ + "File size should not exceed 5 MB" + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/face-search/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'user_image=@./selfie.jpg' \\\n -F 'search_type=most_similar' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.face_search.search(\n user_image=open(\"user_image.jpg\", \"rb\"),\n search_type=\"most_similar\",\n rotate_image=False,\n save_api_request=True,\n vendor_data=\"user-123\",\n metadata={\"flow\": \"dedup_check\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.faceSearch.search({\n user_image: await fileFromPath(\"user_image.jpg\"),\n search_type: \"most_similar\",\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"dedup_check\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.FaceSearch.Search(ctx, didit.FaceSearchSearchParams{\n\tUserImage: didit.FileFromPath(\"user_image.jpg\"),\n\tSearchType: didit.Some(didit.FaceSearchSearchParamsSearchType(\"most_similar\")),\n\tRotateImage: didit.Bool(false),\n\tSaveAPIRequest: didit.Bool(true),\n\tVendorData: didit.String(\"user-123\"),\n\tMetadata: didit.Some(map[string]any{\"flow\": \"dedup_check\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.FaceSearchSearchParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.faceSearch().search(FaceSearchSearchParams.builder()\n .userImage(Path.of(\"user_image.jpg\"))\n .searchType(FaceSearchSearchParams.SearchType.of(\"most_similar\"))\n .rotateImage(false)\n .saveApiRequest(true)\n .vendorData(\"user-123\")\n .metadata(Map.of(\"flow\", \"dedup_check\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "faceSearch->search([\n 'user_image' => File::fromPath('user_image.jpg'),\n 'search_type' => 'most_similar',\n 'rotate_image' => false,\n 'save_api_request' => true,\n 'vendor_data' => 'user-123',\n 'metadata' => ['flow' => 'dedup_check'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.face_search.search(\n user_image: Pathname(\"user_image.jpg\"),\n search_type: \"most_similar\",\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"dedup_check\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.FaceSearch.SearchAsync(new FaceSearchSearchParams\n{\n UserImage = FileUpload.FromPath(\"user_image.jpg\"),\n SearchType = new FaceSearchSearchParamsSearchType(\"most_similar\"),\n RotateImage = false,\n SaveApiRequest = true,\n VendorData = \"user-123\",\n Metadata = new Dictionary { [\"flow\"] = \"dedup_check\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/sessions/": { + "get": { + "summary": "List verification sessions (KYC and/or KYB) with filters and pagination", + "description": "Returns a paginated list of verification sessions for the application identified by your API key, newest first (descending `session_number`).\n\n**Canonical URL:** the route is `/v3/sessions/` (trailing slash). Requests to `/v3/sessions` without the slash receive a `301` redirect to the slashed URL with the query string preserved; most HTTP clients follow it transparently on GET.\n\n**When to use this endpoint vs webhooks.** Use this endpoint for dashboards, back-office search, reconciliation, and batch exports. Do not poll it to track the progress of individual verifications - configure [webhooks](/integration/webhooks) instead, which push `status.updated` events the moment a session changes. For one session's full result, call `GET /v3/session/{sessionId}/decision/`.\n\n**KYC and KYB sessions.** `session_kind` selects which kinds are listed:\n- `user` (default) - User Verification (KYC) sessions only. Backward-compatible behavior.\n- `business` - Business Verification (KYB) sessions only. Rows have a different, smaller shape (see the response schema).\n- `all` - both kinds in one response: the KYC page and the KYB page for the same `limit`/`offset` are concatenated in `results` (KYC rows first), so a page can contain up to `2 x limit` rows. `count` is the sum of both kinds and `next`/`previous` reflect the KYC-side pagination. For strict pagination of mixed data, call `user` and `business` separately.\n\nEvery row carries a `session_kind` discriminator (`\"user\"` or `\"business\"`).\n\n**Pagination.** Standard limit/offset: `limit` (default 50) and `offset` (default 0), with `next`/`previous` ready-made page URLs. For KYC rows `count` is exact. For KYB rows `count` is computed with a bounded scan and is capped at 100 - when there are more than 100 matches it stays at 100, so iterate using `next` (or by checking whether a full page came back) rather than computing total pages from `count`.\n\n**Filter semantics.** All filters combine with AND and apply to KYC rows. When `session_kind` is `business` or `all`, the KYB side honors the shared parameters `vendor_data`, `status`, `workflow_id`, `country` (registry-company country code), `date_from`, `date_to`, `search` (company name, registration number — type registration numbers without separators, e.g. `DPC001` not `DPC-001` — vendor_data, session number, or session UUID), `warning`, and `organization_name`; the remaining KYC-only filters are ignored for KYB rows. Comma-separated parameters (`status`, `country`, `document_type`, `region`, `workflow_id`, `document_name`, `warning`, `organization_name`, `session_type`) match any of the listed values (OR within the parameter). `vendor_data` is an exact match - use `search` for partial matching. Unknown values in list filters simply return no rows (no error); malformed typed values (`date_from`, `date_to`, `didit_internal_id`, `api_service`, `session_kind`) return `400` with a per-field error object.\n\n**Search.** `search` adapts to the input: a full UUID looks up `session_id`; digits (with optional `+ - . space`) match session-number prefix, document/personal numbers, phone-number prefix, or `vendor_data`; values containing `@` match email prefixes; any other text matches names (accent-insensitive) and `vendor_data`.\n\nNo fixed per-endpoint rate limit is enforced, but keep list polling infrequent and rely on webhooks for real-time updates.", + "operationId": "get_v3_sessions", + "x-didit-sdk": { + "resource": "sessions", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "session_kind", + "in": "query", + "required": false, + "description": "Which kind of sessions to list: `user` (KYC only, default), `business` (KYB only), or `all` (both, concatenated - see the description for merged-pagination semantics). Matching is case-insensitive (`BUSINESS` works); any value other than a case variant of `user`/`business`/`all` returns `400`.", + "schema": { + "type": "string", + "enum": [ + "user", + "business", + "all" + ], + "default": "user" + }, + "example": "all" + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of rows per page (per kind when `session_kind=all`). Defaults to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "example": 20 + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of rows to skip before the first returned row. Defaults to 0. Prefer following the `next`/`previous` URLs from the response.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "example": 20 + }, + { + "name": "status", + "in": "query", + "required": false, + "description": "Comma-separated list of session statuses to include, e.g. `Approved,In Review`. Values are case-sensitive; see [Verification Statuses](/integration/verification-statuses). Unknown values match nothing.", + "schema": { + "type": "string" + }, + "example": "Approved,In Review" + }, + { + "name": "vendor_data", + "in": "query", + "required": false, + "description": "Exact-match filter on the `vendor_data` you sent at session creation (your own user/business identifier). For partial matches use `search` instead.", + "schema": { + "type": "string" + }, + "example": "your-user-id-123" + }, + { + "name": "search", + "in": "query", + "required": false, + "description": "A complete displayed reference such as `S-0001` (user) or `B-0001` (business) matches only that session number and kind; leading zeros and letter case do not affect matching. Smart free-text search. A full UUID matches `session_id`; numeric input matches session-number prefix, document/personal numbers, phone prefix, or `vendor_data`; input containing `@` matches email prefixes; other text matches names (accent-insensitive) and `vendor_data`. Free text containing digits additionally matches document/personal-number search fields and session-number prefixes.", + "schema": { + "type": "string" + }, + "example": "jane doe" + }, + { + "name": "workflow_id", + "in": "query", + "required": false, + "description": "Comma-separated workflow UUIDs (find them on the Workflows page in the Console). Malformed UUIDs in the list are ignored; if none are valid the result is empty.", + "schema": { + "type": "string" + }, + "example": "9f9b1234-aaaa-bbbb-cccc-1234567890ab" + }, + { + "name": "date_from", + "in": "query", + "required": false, + "description": "Only sessions created on or after this date (UTC), format `YYYY-MM-DD`. Malformed dates return `400`.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-06-01" + }, + { + "name": "date_to", + "in": "query", + "required": false, + "description": "Only sessions created up to this date (UTC), format `YYYY-MM-DD`. For KYC rows the entire day is included; for KYB rows the comparison is `created_at <= date_to` (midnight), so use the next day's date to include a full day of KYB sessions.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-06-30" + }, + { + "name": "tags", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated tag ids (`tags` of your application). Returns the sessions that carry any of them. Ids that are not valid UUIDs are ignored, and when none is valid the list is empty." + }, + { + "name": "decided_from", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + }, + "description": "ISO 8601 datetime. Returns the sessions whose status last changed at or after it. It filters on the time of the last status change, not on creation time." + }, + { + "name": "decided_to", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + }, + "description": "ISO 8601 datetime. Returns the sessions whose status last changed before it. Combine with `decided_from` for a half-open window." + }, + { + "name": "country", + "in": "query", + "required": false, + "description": "Comma-separated country filter. For KYC rows: ISO 3166-1 alpha-3 codes of the ID document's issuing state (e.g. `ESP,USA`), which may differ from nationality. For KYB rows: the registry company's country code.", + "schema": { + "type": "string" + }, + "example": "ESP,USA" + }, + { + "name": "document_type", + "in": "query", + "required": false, + "description": "Comma-separated ID-document type codes verified in the session (KYC rows only): `P` (Passport), `ID` (Identity Card), `DL` (Driver's License), `RP` (Residence Permit), `SSC` (Social Security Card), `HIC` (Health Insurance Card), `WP` (Work Permit), `TC` (Tax Card), `VISA`, `PSC` (Public Service Card), `BC` (Birth Certificate), `OTHER`. Note the response field `document_type` contains the display name, not the code.", + "schema": { + "type": "string" + }, + "example": "P,ID" + }, + { + "name": "document_name", + "in": "query", + "required": false, + "description": "Exact document names; multiple names can be passed comma-separated, but the value is only split on commas when it contains more than one ` - ` separator (a lone name with an embedded comma is matched whole). Names are as recognized by document verification (front or back side), e.g. `Spain - Id Card (2021)`. KYC rows only.", + "schema": { + "type": "string" + }, + "example": "Spain - Id Card (2021)" + }, + { + "name": "region", + "in": "query", + "required": false, + "description": "Comma-separated document region/state values (for documents issued per region, such as US driver's licenses). KYC rows only.", + "schema": { + "type": "string" + }, + "example": "CA,TX" + }, + { + "name": "didit_internal_id", + "in": "query", + "required": false, + "description": "Filter by Didit's internal user identifier (the `didit_internal_id` response field) to list all sessions of one verified user. Must be a valid UUID; malformed values return `400`. KYC rows only.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147" + }, + { + "name": "session_type", + "in": "query", + "required": false, + "description": "Comma-separated session creation channels: `API`, `HOSTED`, `MIGRATED`. The special value `ALL` (or omitting the parameter) disables the filter. KYC rows only.", + "schema": { + "type": "string" + }, + "example": "HOSTED" + }, + { + "name": "api_service", + "in": "query", + "required": false, + "description": "Filter standalone API sessions by service. Invalid values return `400`. KYC rows only.", + "schema": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "FACE_MATCH", + "AGE_ESTIMATION", + "FACE_SEARCH", + "POA", + "AML", + "KYB_REGISTRY", + "PASSIVE_LIVENESS", + "DATABASE_VALIDATION", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION" + ] + }, + "example": "AML" + }, + { + "name": "screened_full_name", + "in": "query", + "required": false, + "description": "Accent-insensitive name search inside AML and database-validation screened data. When combined with `api_service=AML` or `api_service=DATABASE_VALIDATION`, the search is restricted to that feature's screened data. KYC rows only.", + "schema": { + "type": "string" + }, + "example": "Jane Doe" + }, + { + "name": "warning", + "in": "query", + "required": false, + "description": "Comma-separated warning risk codes; returns sessions that raised any of them (e.g. `POSSIBLE_FRAUD`). Risk codes are listed per feature in the [warnings documentation](/integration/webhooks). Applies to KYC rows and, with business warning codes, to KYB rows.", + "schema": { + "type": "string" + }, + "example": "POSSIBLE_FRAUD" + }, + { + "name": "status_initialized_as_in_review", + "in": "query", + "required": false, + "description": "When `true`, only sessions whose decision initially landed in `In Review` (useful to audit manual-review volume). KYC rows only.", + "schema": { + "type": "boolean" + }, + "example": true + }, + { + "name": "is_kyc_to_be_reviewed", + "in": "query", + "required": false, + "description": "`true` returns sessions whose document verification is flagged for manual review; `false` returns sessions with document verification explicitly not flagged. Omit to disable. KYC rows only.", + "schema": { + "type": "boolean" + }, + "example": true + }, + { + "name": "euuid", + "in": "query", + "required": false, + "description": "Filter by the end user's euuid (UUID assigned to the verifying user, used by Console deep links). Must be a valid UUID; malformed values return a 400 field error. KYC rows only.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "0f8fad5b-d9cb-469f-a165-70867728950e" + }, + { + "name": "organization_name", + "in": "query", + "required": false, + "description": "Comma-separated organization names. Console-oriented; with an API key (already scoped to one application) it only matches your own organization's name.", + "schema": { + "type": "string" + }, + "example": "Acme Inc" + } + ], + "responses": { + "200": { + "description": "Paginated session list. `results` items are KYC rows, KYB rows, or both depending on `session_kind`; discriminate with each row's `session_kind` field.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Number of sessions matching the filters: exact up to 100 and capped at 100 beyond that, so do not use it as a total. Use `next` to know whether more pages exist. For `session_kind=all` it is the sum of both kinds, each capped at 100.", + "example": 2 + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page.", + "example": "https://verification.didit.me/v3/sessions/?limit=20&offset=20" + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page.", + "example": null + }, + "results": { + "type": "array", + "description": "Session rows, newest first. With `session_kind=all`, KYC rows come first, then KYB rows.", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/UserSessionListItem" + }, + { + "$ref": "#/components/schemas/BusinessSessionListItem" + } + ], + "discriminator": { + "propertyName": "session_kind", + "mapping": { + "user": "#/components/schemas/UserSessionListItem", + "business": "#/components/schemas/BusinessSessionListItem" + } + } + } + } + } + }, + "examples": { + "KYC sessions (default)": { + "summary": "session_kind=user - one completed and one not-started session", + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "session_id": "5b3720ed-d429-42ef-b67f-37ea805f48ee", + "session_kind": "user", + "session_number": 720, + "session_url": "https://verify.didit.me/session/CuXoBYCYVO5Y", + "portrait_image": "https:///portraits/5b3720ed.jpg?X-Amz-Expires=3600&X-Amz-Signature=...", + "document_type": "Passport", + "full_name": "Jane Marie Doe", + "country": "ESP", + "status": "Approved", + "vendor_data": "your-user-id-123", + "didit_internal_id": "d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147", + "created_at": "2026-06-09T12:26:54.328364Z", + "features": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + }, + { + "feature": "IP_ANALYSIS", + "status": "Approved" + } + ], + "phone_number": { + "number": "+14155552671", + "is_verified": true + }, + "email_address": { + "email": "jane@example.com", + "is_verified": true + }, + "additional_details": { + "aml": { + "total_hits": 0, + "entity_type": "person", + "is_adverse_media_fetched": false + }, + "liveness": { + "method": "PASSIVE", + "score": 0.98 + }, + "face_match": { + "score": 0.95 + } + } + }, + { + "session_id": "038ad62f-5ebd-46c2-afe9-4b2956bf687f", + "session_kind": "user", + "session_number": 719, + "session_url": "https://verify.didit.me/session/7RGKiO6ioGH6", + "portrait_image": null, + "document_type": null, + "full_name": null, + "country": null, + "status": "Not Started", + "vendor_data": "your-user-id-124", + "didit_internal_id": "eb4e679d-ca4b-461f-9685-687bac29ebdb", + "created_at": "2026-06-09T08:02:23.343053Z", + "features": [ + { + "feature": "ID_VERIFICATION", + "status": "Not Finished" + }, + { + "feature": "LIVENESS", + "status": "Not Finished" + }, + { + "feature": "FACE_MATCH", + "status": "Not Finished" + }, + { + "feature": "AML", + "status": "Not Finished" + }, + { + "feature": "IP_ANALYSIS", + "status": "Not Finished" + } + ], + "phone_number": null, + "email_address": null, + "additional_details": null + } + ] + } + }, + "Mixed KYC + KYB (session_kind=all)": { + "summary": "session_kind=all - KYC row followed by a KYB row", + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "session_id": "5b3720ed-d429-42ef-b67f-37ea805f48ee", + "session_kind": "user", + "session_number": 720, + "session_url": "https://verify.didit.me/session/CuXoBYCYVO5Y", + "portrait_image": null, + "document_type": "Identity Card", + "full_name": "Jane Marie Doe", + "country": "ESP", + "status": "In Review", + "vendor_data": "your-user-id-123", + "didit_internal_id": "d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147", + "created_at": "2026-06-09T12:26:54.328364Z", + "features": [ + { + "feature": "ID_VERIFICATION", + "status": "In Review" + }, + { + "feature": "LIVENESS", + "status": "Approved" + } + ], + "phone_number": null, + "email_address": null, + "additional_details": { + "liveness": { + "method": "PASSIVE", + "score": 0.98 + } + } + }, + { + "session_id": "8a1f63c2-1b9e-4f3a-9c41-2f6a1f0b7d52", + "session_kind": "business", + "session_number": 42, + "status": "Approved", + "vendor_data": "your-company-id-77", + "created_at": "2026-06-08T09:14:02.118450Z", + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "workflow_type": "kyb", + "workflow_label": "Standard KYB", + "company_name": "Acme Holdings S.L.", + "registration_number": "B12345678", + "country": "ES" + } + ] + } + } + } + } + } + }, + "301": { + "description": "Redirect to the canonical trailing-slash URL. Returned when the request path is `/v3/sessions` (no trailing slash); the `Location` header points to `/v3/sessions/` with the query string preserved. GET clients typically follow it automatically." + }, + "400": { + "description": "Invalid query parameter. The body is an object keyed by the offending parameter.", + "content": { + "application/json": { + "examples": { + "Invalid session_kind": { + "summary": "session_kind not one of user/business/all", + "value": { + "session_kind": "Must be \"user\", \"business\", or \"all\"." + } + }, + "Invalid date": { + "summary": "Malformed date_from/date_to", + "value": { + "date_from": [ + "Enter a valid date." + ] + } + }, + "Invalid didit_internal_id": { + "summary": "didit_internal_id is not a UUID", + "value": { + "didit_internal_id": [ + "Enter a valid UUID." + ] + } + }, + "Invalid api_service": { + "summary": "api_service not in the allowed choices", + "value": { + "api_service": [ + "Select a valid choice. FOO is not one of the available choices." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, malformed, or revoked API key. A missing key returns `403`; a wrong, revoked or expired key returns `401`.\n\n**Deleted application:** for a few minutes after an application is deleted, a request made with one of its API keys can answer `{\"detail\": \"This application was deleted.\", \"code\": \"application_deleted\"}`. Do not retry: the application and its API keys no longer exist. After those few minutes, the key is refused like any other invalid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You do not have permission to perform this action." + }, + "code": { + "type": "string", + "description": "Machine-readable reason, present only on some failures. `application_deleted`: the API key belongs to an application that was deleted.", + "example": "application_deleted" + } + }, + "required": [ + "detail" + ] + }, + "examples": { + "Forbidden": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "ApplicationDeleted": { + "summary": "API key of a deleted application", + "value": { + "detail": "This application was deleted.", + "code": "application_deleted" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -s 'https://verification.didit.me/v3/sessions/?status=Approved,In%20Review&date_from=2026-06-01&limit=20' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.sessions.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.sessions.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Sessions.ListAutoPaging(ctx, didit.SessionsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.sessions().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->list() as $item) {\n print_r($item);\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Sessions.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/session/{sessionId}/share/": { + "post": { + "summary": "Mint a share token for a finished verification session", + "description": "Mint a short-lived JWT that lets a specific Didit application import this finished session. Pair with `POST /v3/session/import-shared/`.", + "operationId": "post_v3_session_share", + "x-didit-sdk": { + "resource": "sessions", + "method": "share", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "200": { + "description": "Share token minted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "share_token": { + "type": "string", + "description": "HS256-signed JWT. Pass this verbatim as `share_token` to [`POST /v3/session/import-shared/`](/sessions-api/import-shared-session) on the target application.", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + }, + "for_application_id": { + "type": "string", + "format": "uuid", + "description": "Echoes the target application UUID that the token is bound to.", + "example": "a5f3bca2-46e2-411e-90ef-a580900a57ee" + }, + "session_kind": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Whether the source session is a User Verification (KYC) or Business Verification (KYB) session." + } + } + }, + "examples": { + "User session": { + "summary": "Share a KYC session", + "value": { + "share_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uX2lkIjoiMjBiYjNjM2ItNjE0Ni00MzlmLTg0YTQtYmQzMGQwMGFjNmEyIiwiZnJvbV9hcHBsaWNhdGlvbl9pZCI6ImRiZDIwZTM0LTQyZTktNGYyYy1iYTkxLWNmMDc2MjAxNmY2NCIsImZvcl9hcHBsaWNhdGlvbl9pZCI6ImE1ZjNiY2EyLTQ2ZTItNDExZS05MGVmLWE1ODA5MDBhNTdlZSIsImlhdCI6MTc1MzYzMDY2NiwiZXhwIjoxNzUzNjM0MjY2fQ.JJ9pNE_hqZsOtbR0XYZIWw4JzidjdEl279iUrsIkhGE", + "for_application_id": "a5f3bca2-46e2-411e-90ef-a580900a57ee", + "session_kind": "user" + } + }, + "Business session": { + "summary": "Share a KYB session", + "value": { + "share_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "for_application_id": "a5f3bca2-46e2-411e-90ef-a580900a57ee", + "session_kind": "business" + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials. Unlike most v3 endpoints, this endpoint authenticates through the permission decorator and returns `401` when no valid token/key is presented.", + "content": { + "application/json": { + "examples": { + "Unauthorized": { + "summary": "Unauthorized", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "404": { + "description": "No session with the given `session_id` exists in your application.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Session not found", + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field errors are keyed by field name; the not-finished error arrives under a `detail` key (as an array).", + "content": { + "application/json": { + "examples": { + "Wrong status": { + "summary": "Source session is not finished", + "value": { + "detail": [ + "Only finished sessions (\"Approved\", \"Declined\", \"In Review\") can be shared." + ] + } + }, + "Target missing": { + "summary": "Target application does not exist", + "value": { + "for_application_id": [ + "Target application does not exist." + ] + } + }, + "Self-share": { + "summary": "Target is the calling application", + "value": { + "for_application_id": [ + "Cannot share a session with the same application." + ] + } + }, + "TTL bounds": { + "summary": "ttl_in_seconds outside bounds", + "value": { + "ttl_in_seconds": [ + "Ensure this value is greater than or equal to 60." + ] + } + } + } + } + } + }, + "403": { + "description": "The credentials are valid but lack the `write:sessions` permission for this application.", + "content": { + "application/json": { + "examples": { + "No Permission": { + "value": { + "detail": "You do not have permission to perform this action." + }, + "summary": "No Permission" + } + } + } + } + } + }, + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the source verification session to mint a token for.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "for_application_id" + ], + "properties": { + "for_application_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the Didit application that will redeem the token. Must exist, must not be deleted, and must differ from the calling application. Find it in the Business Console under **Settings → Application**.", + "example": "a5f3bca2-46e2-411e-90ef-a580900a57ee" + }, + "ttl_in_seconds": { + "type": "integer", + "description": "Token lifetime, in seconds. Minimum `60`, maximum `86400` (24 h). Defaults to `3600` (1 h).", + "minimum": 60, + "maximum": 86400, + "default": 3600, + "example": 3600 + } + } + }, + "example": { + "for_application_id": "a5f3bca2-46e2-411e-90ef-a580900a57ee", + "ttl_in_seconds": 7200 + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/share/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"for_application_id\": \"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n \"ttl_in_seconds\": 7200\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.share(\n \"11111111-2222-3333-4444-555555555555\",\n for_application_id=\"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n ttl_in_seconds=7200,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.share(\"11111111-2222-3333-4444-555555555555\", {\n for_application_id: \"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n ttl_in_seconds: 7200,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Share(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsShareParams{\n\tForApplicationID: \"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n\tTTLInSeconds: didit.Int(7200),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsShareParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().share(\"11111111-2222-3333-4444-555555555555\", SessionsShareParams.builder()\n .forApplicationId(\"a5f3bca2-46e2-411e-90ef-a580900a57ee\")\n .ttlInSeconds(7200L)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->share('11111111-2222-3333-4444-555555555555', [\n 'for_application_id' => 'a5f3bca2-46e2-411e-90ef-a580900a57ee',\n 'ttl_in_seconds' => 7200,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.share(\n \"11111111-2222-3333-4444-555555555555\",\n for_application_id: \"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n ttl_in_seconds: 7200\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ShareAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsShareParams\n{\n ForApplicationId = \"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n TtlInSeconds = 7200,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/import-shared/": { + "post": { + "summary": "Redeem a share token to clone a verification session into your application", + "description": "Redeem a share token to clone a KYC or KYB session into the calling application with a fresh `session_id`. Not idempotent — a token redeems once per receiver.", + "operationId": "post_v3_session_import_shared", + "x-didit-sdk": { + "resource": "sessions", + "method": "import_shared", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "201": { + "description": "Session cloned. The body is the V3 decision payload for both kinds, discriminated by `session_kind`: `\"user\"` (KYC) with list-based feature blocks such as `id_verifications`, `liveness_checks`, `aml_screenings`; `\"business\"` (KYB) with blocks like `registry_checks` and `key_people_checks`. Both include `shared_from_session` — the `session_id` of the original session in the source application. With `trust_review: false` the cloned session's `status` is forced to `In Review`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionDecision" + }, + "examples": { + "User session imported": { + "summary": "Cloned KYC session (truncated — full V3 decision payload in reality)", + "value": { + "session_id": "11111111-2222-3333-4444-555555555555", + "session_kind": "user", + "shared_from_session": "99999999-8888-7777-6666-555555555555", + "session_number": 43762, + "session_url": null, + "status": "In Review", + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "vendor_data": "user-1", + "created_at": "2026-05-17T08:42:11Z", + "expires_at": "2026-05-24T08:42:11Z" + } + }, + "Business session imported": { + "summary": "Cloned KYB session (truncated — full V3 KYB decision payload in reality)", + "value": { + "session_id": "22222222-3333-4444-5555-666666666666", + "session_kind": "business", + "shared_from_session": "88888888-7777-6666-5555-444444444444", + "session_number": 982, + "session_url": null, + "status": "Approved", + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "vendor_data": "company-1" + } + } + } + } + } + }, + "400": { + "description": "Validation error (token invalid, expired, wrong target, or references a session that no longer exists).", + "content": { + "application/json": { + "examples": { + "Invalid Share Token": { + "summary": "Invalid JWT", + "value": { + "share_token": [ + "Invalid share token." + ] + } + }, + "Expired Share Token": { + "summary": "Token past `exp`", + "value": { + "share_token": [ + "Share token has expired." + ] + } + }, + "Wrong audience": { + "summary": "Token not bound to this application", + "value": { + "share_token": [ + "This token is not valid for this application." + ] + } + }, + "Original missing": { + "summary": "Source session was deleted", + "value": { + "share_token": [ + "Original session does not exist." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing API key (a wrong, revoked or expired key gets `401`), or this source session has already been imported into the calling application (one redeem per receiver).", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "summary": "Forbidden", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Already imported": { + "summary": "Source already imported once", + "value": { + "detail": "This session has already been shared with your application." + } + } + } + } + } + }, + "404": { + "description": "`workflow_id` does not belong to the calling application.", + "content": { + "application/json": { + "examples": { + "Workflow not found": { + "summary": "Workflow unknown", + "value": { + "detail": "Workflow does not exist for this application." + } + } + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "share_token", + "trust_review", + "workflow_id" + ], + "properties": { + "share_token": { + "type": "string", + "description": "JWT share token issued by [`POST /v3/session/{sessionId}/share/`](/sessions-api/share-session).", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + }, + "trust_review": { + "type": "boolean", + "description": "If `true`, the cloned session keeps the source's final `status`. If `false`, it is forced into `In Review`.", + "example": false + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "UUID of a workflow in the calling application. Cross-application IDs are rejected with `404`.", + "example": "9f9b1234-aaaa-bbbb-cccc-1234567890ab" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Optional override for the cloned session's `vendor_data`.", + "example": "user-1" + } + } + }, + "example": { + "share_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "trust_review": false, + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "vendor_data": "user-1" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/import-shared/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"share_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n \"trust_review\": false,\n \"workflow_id\": \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n \"vendor_data\": \"user-1\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.import_shared(\n share_token=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n trust_review=False,\n workflow_id=\"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n vendor_data=\"user-1\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.importShared({\n share_token: \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n trust_review: false,\n workflow_id: \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n vendor_data: \"user-1\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.ImportShared(ctx, didit.SessionsImportSharedParams{\n\tShareToken: \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n\tTrustReview: false,\n\tWorkflowID: \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n\tVendorData: didit.String(\"user-1\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsImportSharedParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().importShared(SessionsImportSharedParams.builder()\n .shareToken(\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\")\n .trustReview(false)\n .workflowId(\"9f9b1234-aaaa-bbbb-cccc-1234567890ab\")\n .vendorData(\"user-1\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->importShared([\n 'share_token' => 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',\n 'trust_review' => false,\n 'workflow_id' => '9f9b1234-aaaa-bbbb-cccc-1234567890ab',\n 'vendor_data' => 'user-1',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.import_shared(\n share_token: \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n trust_review: false,\n workflow_id: \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n vendor_data: \"user-1\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ImportSharedAsync(new SessionsImportSharedParams\n{\n ShareToken = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n TrustReview = false,\n WorkflowId = \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n VendorData = \"user-1\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/kyb/search/": { + "post": { + "summary": "KYB registry company search (standalone)", + "operationId": "post_v3_kyb_search", + "x-didit-sdk": { + "resource": "kyb", + "method": "search", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Search official company registries for candidate companies by name and/or registration number. This is step 1 of the two-step registry flow: **search** returns lightweight candidates, then `POST /v3/kyb/select/` retrieves the full profile of one candidate.\n\n**Search billing.** Successful searches using a paid company-data source cost USD 0.50 per request, regardless of candidate count. Empty results, failures and results served only by other sources are not charged. The applicable price is reserved before searching; insufficient balance returns 403 with code `insufficient_credits`. Contract rates apply where agreed, and an unconfigured contract search rate returns 403. Sandbox searches are free. Repeating a successful search can incur another charge; callback delivery does not add a charge. Profile retrieval is billed separately. This search fee also applies to v2. Candidates always come back with `fetch_status: \"pending\"`: the full company data is only fetched (and billed) on select.\n\n**`kyb_response_id` is ephemeral.** It is a per-search handle, not a stable company id. Select promptly after searching and re-search to get a fresh handle instead of storing it.\n\n**`tier` picks the provider chain.** `tier` names the registry tier the search runs on: `basic` (the default, the Lite chain), `shareholders` or `ubo`. The search uses the provider chain that serves that tier, and it is not always the same chain the Lite search reads, so an empty Lite search is not proof that the registry does not hold the company — search again at the tier you need. A country that does not offer the tier you name is searched at the highest tier it offers up to that one. Retrieve the candidate from the endpoint that sells that tier: `POST /v3/kyb/select/` (Lite), `POST /v3/kyb/shareholders/` or `POST /v3/kyb/ubo/`.\n\n**Authentication.** `POST /v3/kyb/search/` (and `/v2/`) takes your application's API key in the `x-api-key` header. The hosted `POST /v1/kyb/search/` is the session-authenticated route a hosted verification flow calls: it answers `403` to an API key and takes no `tier` — the workflow's KYB registry configuration picks the chain.\n\n**Two modes.**\n- *Synchronous (default):* the API polls the registry until the search resolves and returns the final candidate list (`search_resolved: true`).\n- *Asynchronous:* pass `webhook_url` and the API returns immediately — usually with `search_status: \"pending\"` and an empty candidate list — then POSTs a `kyb.registry_search.resolved` callback to your URL when the registry finishes. The callback body is `{\"event_id\", \"event_type\": \"kyb.registry_search.resolved\", \"request_id\", \"vendor_data\", \"metadata\", \"search_status\", \"search_resolved\", \"kyb_registry\", \"timestamp\", \"created_at\"}` — note `timestamp` and `created_at` in the callback are epoch integers, unlike this endpoint's ISO `created_at` — and is sent **unsigned** (header `X-Didit-Unsigned-Callback: true`, no `X-Signature`).\n\n**Country codes.** ISO 3166-1 alpha-2 (`GB`, `DE`), plus `XX-YY` subdivision codes where registries are regional — e.g. `US-CA` for California.\n\n**Sandbox.** Keys from sandbox applications return one static candidate without contacting any registry.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "country_code" + ], + "properties": { + "country_code": { + "type": "string", + "maxLength": 10, + "description": "ISO 3166-1 alpha-2 code of the country whose company registry to search. For example, `GB`. Where company registries operate at state or province level, append the ISO 3166-2 subdivision code as `XX-YY`: `US-CA` searches the California registry. United States searches always require the subdivision — a bare `US` is rejected. Case-insensitive; normalized to upper case.", + "example": "GB" + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "Company name to search for. Either `name` or `registration_number` is required.", + "example": "Tesco" + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Registry registration number to search for. Either `name` or `registration_number` is required." + }, + "tier": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "default": "basic", + "description": "Registry tier whose provider chain this search runs on. `basic` (default) searches the country's Lite chain; `shareholders` and `ubo` search that tier's ownership chain, which can be a different source, so an empty Lite search does not prove the registry lacks the company. A country that does not offer the named tier is searched at the highest tier it offers up to that one. Retrieve the candidate from the endpoint that sells the tier you searched: `POST /v3/kyb/select/` (Lite), `POST /v3/kyb/shareholders/` or `POST /v3/kyb/ubo/`.", + "example": "basic" + }, + "search_type": { + "type": "string", + "enum": [ + "contains" + ], + "default": "contains", + "description": "Name-matching strategy applied by the registry search. `contains` is the only accepted value: no registry source behind the search offers a prefix or fuzzy mode, so `start_with` and `fuzzy` are rejected with `400` (`not a valid choice`)." + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for this lookup; echoed back and included in the webhook callback." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON; echoed back and included in the webhook callback." + }, + "webhook_url": { + "type": "string", + "format": "uri", + "maxLength": 500, + "description": "Switches the search to asynchronous mode: the API returns immediately and POSTs an unsigned `kyb.registry_search.resolved` callback to this URL when the candidate list is ready." + } + } + }, + "example": { + "country_code": "GB", + "name": "Tesco", + "search_type": "contains", + "vendor_data": "company-1234" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/kyb/search/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"country_code\": \"GB\",\n \"name\": \"Tesco\",\n \"search_type\": \"contains\",\n \"vendor_data\": \"company-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.kyb.search(\n country_code=\"GB\",\n name=\"Tesco\",\n search_type=\"contains\",\n vendor_data=\"company-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.kyb.search({\n country_code: \"GB\",\n name: \"Tesco\",\n search_type: \"contains\",\n vendor_data: \"company-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.KYB.Search(ctx, didit.KYBSearchParams{\n\tCountryCode: \"GB\",\n\tName: didit.String(\"Tesco\"),\n\tSearchType: didit.Some(didit.KYBSearchParamsSearchType(\"contains\")),\n\tVendorData: didit.String(\"company-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.KybSearchParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.kyb().search(KybSearchParams.builder()\n .countryCode(\"GB\")\n .name(\"Tesco\")\n .searchType(KybSearchParams.SearchType.of(\"contains\"))\n .vendorData(\"company-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "kyb->search([\n 'country_code' => 'GB',\n 'name' => 'Tesco',\n 'search_type' => 'contains',\n 'vendor_data' => 'company-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.kyb.search(\n country_code: \"GB\",\n name: \"Tesco\",\n search_type: \"contains\",\n vendor_data: \"company-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Kyb.SearchAsync(new KybSearchParams\n{\n CountryCode = \"GB\",\n Name = \"Tesco\",\n SearchType = new KybSearchParamsSearchType(\"contains\"),\n VendorData = \"company-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Search accepted. Synchronous mode returns the resolved candidate list; webhook mode returns immediately (often `search_status: \"pending\"`) and delivers the final list via the `kyb.registry_search.resolved` callback.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "With `webhook_url`: the persisted search-request id, echoed as `request_id` in the `kyb.registry_search.resolved` callback. Without `webhook_url`: a transient correlation UUID (the search is not stored)." + }, + "kyb_registry": { + "$ref": "#/components/schemas/KYBRegistrySearchResults" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Resolved search": { + "summary": "Synchronous mode — final candidate list", + "value": { + "request_id": "0c7e9a40-3a13-4f7e-8a44-9d2e2f1c5b6a", + "kyb_registry": { + "companies": [ + { + "kyb_response_id": "69aeeb95febb0f1704042259", + "name": "Tesco PLC", + "registration_number": "00445790", + "status": "active", + "type": "Public Limited Company", + "risk_level": null, + "fetch_status": "pending" + }, + { + "kyb_response_id": "69aeeb95febb0f170404225a", + "name": "E-TESCO LTD", + "registration_number": "15168476", + "status": "active", + "type": "Private Limited Company", + "risk_level": null, + "fetch_status": "pending" + } + ], + "pagination": { + "total": 2, + "page": 1, + "per_page": 25 + }, + "search_status": "resolved", + "search_resolved": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:40:00.000000+00:00" + } + }, + "Pending search (webhook mode)": { + "summary": "`webhook_url` provided — candidates arrive via the kyb.registry_search.resolved callback", + "value": { + "request_id": "5f2d8c1b-7e4a-4b9c-9f0d-3a6e8c2b1d4f", + "kyb_registry": { + "companies": [], + "pagination": { + "total": 0, + "page": 1, + "per_page": 25 + }, + "search_status": "pending", + "search_resolved": false + }, + "vendor_data": "company-1234", + "metadata": { + "source": "onboarding" + }, + "created_at": "2026-06-11T10:41:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems use DRF's standard envelope; the name/registration-number rule reports under `non_field_errors`. A country the registries do not cover answers `400` with `code: kyb_registry_country_unsupported`.", + "content": { + "application/json": { + "examples": { + "Missing search criteria": { + "summary": "Neither `name` nor `registration_number` provided", + "value": { + "non_field_errors": [ + "Either 'name' or 'registration_number' is required." + ] + } + }, + "Invalid country_code": { + "summary": "Must be `XX` (alpha-2) or `XX-YY` subdivision", + "value": { + "country_code": [ + "country_code must be a 2-letter ISO code." + ] + } + }, + "Invalid search_type": { + "summary": "Only `contains` is accepted", + "value": { + "search_type": [ + "\"start_with\" is not a valid choice." + ] + } + }, + "Invalid webhook_url": { + "summary": "`webhook_url` must be a valid URL", + "value": { + "webhook_url": [ + "Enter a valid URL." + ] + } + }, + "Country not covered": { + "summary": "No company registry covers the country", + "value": { + "detail": "Company registry search is not available for this country. Enter the company details manually to continue.", + "code": "kyb_registry_country_unsupported" + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Also returned before searching when the applicable search fee cannot be covered (`code: \"insufficient_credits\"`), or when a contract search rate is missing.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded, or the search allowance is spent. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600), with `X-RateLimit-*` and `Retry-After` headers. Separately, paid registry searches are limited to 600 per application per hour: past that the response is `429` with `code: kyb_registry_search_limit_reached`, and searches served only by free sources still work. Wait and search again later.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + }, + "Search allowance spent": { + "summary": "More than 600 paid searches in the last hour", + "value": { + "detail": "The maximum number of company registry searches for this session has been reached. Enter the company details manually to continue.", + "code": "kyb_registry_search_limit_reached" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + } + } + } + }, + "502": { + "description": "The KYB registry provider is temporarily unavailable - a 5xx response, timeout, or connection failure from the upstream registry. The search is not billed. Retry later, or, in Didit's hosted verification flow, the user is automatically offered manual company entry instead of a retry-only error.", + "content": { + "application/json": { + "examples": { + "Registry provider outage": { + "summary": "The registry provider did not answer the search", + "value": { + "detail": "The KYB registry provider is temporarily unavailable. Please try again shortly.", + "code": "kyb_registry_provider_unavailable" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/kyb/select/": { + "post": { + "summary": "KYB registry company select — Lite tier (standalone)", + "operationId": "post_v3_kyb_select", + "x-didit-sdk": { + "resource": "kyb", + "method": "select", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Retrieve the **Lite** registry profile of one candidate returned by `POST /v3/kyb/search/` — company details, registered address, identifiers, status, activity and the officers, plus the raw registry payload. Shareholders and ultimate beneficial owners are sold by `POST /v3/kyb/shareholders/` and `POST /v3/kyb/ubo/`.\n\n**Pairing with search.** Pass the candidate's `kyb_response_id` exactly as returned by a search of the same application. The handle is ephemeral and scoped to that search — select promptly, and re-search for a fresh handle rather than storing it.\n\n**Billing.** Each select is billed at the country's Lite price (any search fee is separate); see [KYB registry pricing](/getting-started/kyb-registry-pricing). The balance is checked before any registry call (`403` when short) and the charge is recorded against the created business session.\n\n**Persistence is mandatory.** Every select creates a business session (its id is the `request_id`) so billing and retrieval can be tracked; it appears in the console. `save_api_request` is accepted for symmetry but must be `true` (the default) — sending `false` returns `400`.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBRegistryCandidateRequest" + }, + "example": { + "kyb_response_id": "69aeeb95febb0f1704042259", + "vendor_data": "company-1234" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/kyb/select/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"kyb_response_id\": \"69aeeb95febb0f1704042259\",\n \"vendor_data\": \"company-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.kyb.select(\n kyb_response_id=\"69aeeb95febb0f1704042259\",\n vendor_data=\"company-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.kyb.select({\n kyb_response_id: \"69aeeb95febb0f1704042259\",\n vendor_data: \"company-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.KYB.Select(ctx, didit.KYBRegistryCandidateRequest{\n\tKYBResponseID: \"69aeeb95febb0f1704042259\",\n\tVendorData: didit.String(\"company-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.KYBRegistryCandidateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.kyb().select(KYBRegistryCandidateRequest.builder()\n .kybResponseId(\"69aeeb95febb0f1704042259\")\n .vendorData(\"company-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "kyb->select([\n 'kyb_response_id' => '69aeeb95febb0f1704042259',\n 'vendor_data' => 'company-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.kyb.select(\n kyb_response_id: \"69aeeb95febb0f1704042259\",\n vendor_data: \"company-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Kyb.SelectAsync(new KYBRegistryCandidateRequest\n{\n KybResponseId = \"69aeeb95febb0f1704042259\",\n VendorData = \"company-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Company retrieved and billed. `kyb_registry` carries the full profile; check `data_resolved` — when `false` the registry was still processing and the stored record completes automatically once it resolves.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the business session created for this retrieval. The stored record (visible in the console) keeps updating if the registry resolves after the response." + }, + "registry_tier_requested": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "The tier this endpoint sells: `basic` (Lite) for select, `shareholders`, `ubo`." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "The tier actually delivered and billed. Lower than requested when the country's registries hold no ownership data, or the registry has no ownership record for this company." + }, + "kyb_registry": { + "type": "object", + "description": "Full registry company profile.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit's id for this company record." + }, + "node_id": { + "type": "string", + "example": "feature_kyb_registry" + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review" + ], + "description": "Didit's assessment of the registry check (e.g. `In Review` when the company is not active in the registry or ownership data could not be derived)." + }, + "registry_status": { + "type": "string", + "nullable": true, + "enum": [ + "active", + "dissolved", + "deregistered", + "see full details", + "authorised", + "appointed representative", + "unauthorised", + "inactive", + "no longer authorised", + "closed", + "struck off" + ], + "description": "Normalized company status as reported by the registry." + }, + "data_resolved": { + "type": "boolean", + "description": "`true` once the registry returned the full profile. `false` when the registry was still processing after polling — the stored record completes automatically once the registry resolves." + }, + "company_name": { + "type": "string", + "example": "Tesco PLC" + }, + "registration_number": { + "type": "string", + "nullable": true, + "example": "00445790" + }, + "country_code": { + "type": "string", + "description": "ISO 3166-1 alpha-2.", + "example": "GB" + }, + "region": { + "type": "string", + "nullable": true, + "description": "Subdivision code when the registry is regional (e.g. `CA` for `US-CA`); empty otherwise." + }, + "company_type": { + "type": "string", + "nullable": true, + "example": "Public Limited Company" + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true, + "example": "1947-11-27" + }, + "registered_address": { + "type": "string", + "nullable": true, + "example": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA" + }, + "tax_number": { + "type": "string", + "nullable": true, + "example": "GB412 8925 93" + }, + "risk_level": { + "type": "string", + "nullable": true, + "description": "Registry-reported risk level, when available." + }, + "verification_status": { + "type": "string", + "nullable": true, + "description": "Registry verification status, when available." + }, + "is_from_registry": { + "type": "boolean", + "description": "Always `true` for this endpoint — the profile was retrieved from an official registry." + }, + "fetch_status": { + "type": "string", + "enum": [ + "pending", + "resolved" + ], + "description": "Registry fetch lifecycle. Mirrored by `data_resolved`." + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true, + "description": "Activity description or classification code, when reported." + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "nullable": true, + "description": "Decimal amount as a string, when reported." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 code, when reported." + }, + "website": { + "type": "string", + "nullable": true, + "example": "https://www.tesco.com/" + }, + "email": { + "type": "string", + "nullable": true, + "example": "customer.service@tesco.co.uk" + }, + "phone": { + "type": "string", + "nullable": true, + "example": "03301231688" + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true, + "description": "LEI, when reported." + }, + "location_of_registration": { + "type": "string", + "nullable": true, + "description": "Registry/jurisdiction the company is registered with, when reported." + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards. At select time this is typically not yet set — VAT and tax ID validation runs when the end user submits the registry step in the hosted flow." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true, + "description": "Registry-reported share/capital details (currency, total shares, share classes), when available." + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "example": "MELISSA BETHELL" + }, + "designation": { + "type": "string", + "nullable": true, + "description": "Raw registry designation (e.g. `Director`, `Secretary`)." + }, + "role": { + "type": "string", + "description": "Normalized role derived from the designation. Full 17-value model set; registry parsing on this endpoint typically emits director, non_executive_director, secretary, chairman, or company_officer.", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "company_officer", + "settlor", + "protector", + "investor", + "other" + ] + }, + "nationality": { + "type": "string", + "nullable": true + }, + "is_active": { + "type": "boolean", + "description": "`false` when the registry marks the appointment as resigned." + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending" + ], + "description": "Status of an associated KYC session, when one was started for this officer. `null` otherwise." + }, + "kyc_session_url": { + "type": "string", + "nullable": true, + "description": "Verification URL of the associated KYC session, when one exists." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + }, + "description": "Directors, secretaries, and other officers extracted from the registry." + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the owner is a natural person or a corporate entity." + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Normalized roles (e.g. `ubo`, `shareholder`)." + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership band reported by the registry (e.g. `25%`)." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership band reported by the registry." + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending" + ] + }, + "kyc_session_url": { + "type": "string", + "nullable": true + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true, + "description": "Parsed ownership percentage (max bound, falling back to min bound)." + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + }, + "shareholding": { + "$ref": "#/components/schemas/KYBRegistryShareholding" + } + } + }, + "description": "Beneficial owners / persons with significant control extracted from the registry. Can be empty when the registry does not expose ownership." + }, + "addresses": { + "type": "array", + "nullable": true, + "description": "Every address the registry states, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryAddress" + } + }, + "industries": { + "type": "array", + "nullable": true, + "description": "The activities the registry classifies the company under, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryIndustry" + } + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "$ref": "#/components/schemas/KYBRegistryData" + }, + "user_provided_data": { + "type": "object", + "nullable": true, + "description": "Always `null` on this endpoint (used by the hosted KYB flow when end users edit registry data)." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set when a console reviewer edits the record later." + }, + "is_editable": { + "type": "boolean", + "description": "Whether the registry-sourced data can still be edited in the hosted flow/console." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "Same as the top-level `registry_tier_delivered`." + }, + "registry_ownership_status": { + "type": "string", + "nullable": true, + "enum": [ + "not_requested", + "delivered", + "not_held", + "unavailable" + ], + "description": "`delivered` when the ownership data came back, `not_held` when the ownership registry answered and does not list this company (see `registry_data.ownership_error`), `unavailable` when an ownership tier was requested but the registry had none for this company or could not be asked (see `registry_data.ownership_error`), `not_requested` on the Lite tier." + }, + "ownership": { + "$ref": "#/components/schemas/KYBRegistryOwnership" + }, + "registry_tier": { + "type": "string", + "nullable": true, + "description": "The registry tier the workflow ordered: `basic`, `shareholders` or `ubo`. Also in `ownership.tier_requested`." + }, + "registry_monitoring": { + "type": "object", + "nullable": true, + "description": "The latest registry monitor for the company, or `null` when none exists.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string" + }, + "unavailable_reason": { + "type": "string", + "nullable": true + }, + "enrolled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "next_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "last_event_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "events_count": { + "type": "integer" + }, + "watched_fields": { + "description": "The company fields the monitor watches." + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Resolved company": { + "summary": "Full registry profile (officer list truncated for brevity)", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "f1e2d3c4-b5a6-4789-9c0d-1e2f3a4b5c6d", + "node_id": "feature_kyb_registry", + "status": "In Review", + "registry_status": "active", + "data_resolved": true, + "company_name": "Tesco PLC", + "registration_number": "00445790", + "country_code": "GB", + "region": "", + "company_type": "Public Limited Company", + "incorporation_date": "1947-11-27", + "registered_address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "tax_number": "GB412 8925 93", + "risk_level": "low risk", + "verification_status": "verified", + "is_from_registry": true, + "fetch_status": "resolved", + "alternative_names": "TESCO STORES (HOLDINGS) PUBLIC LIMITED COMPANY", + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": "https://www.tesco.com/", + "email": "customer.service@tesco.co.uk", + "phone": "03301231688", + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": { + "currency": "Pound (£)", + "total_number_of_shares": "8174378721", + "meta_detail": { + "Class_Of_Share": "Ordinary" + } + }, + "officers": [ + { + "uuid": "a1b2c3d4-e5f6-4789-9a0b-1c2d3e4f5a6b", + "name": "CHRISTOPHER JON TAYLOR", + "designation": "Secretary", + "role": "secretary", + "nationality": null, + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + }, + { + "uuid": "b2c3d4e5-f6a7-4890-8b1c-2d3e4f5a6b7c", + "name": "MELISSA BETHELL", + "designation": "Director", + "role": "director", + "nationality": "British", + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + } + ], + "beneficial_owners": [], + "addresses": [ + { + "address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "type": "legal_entity_registry_address", + "description": "registered office address" + } + ], + "industries": null, + "accounts": { + "last_account": "26 February 2025", + "next_account": "26 February 2026", + "due_by_date": "26 August 2026" + }, + "registry_data": {}, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00" + } + }, + "Pending (registry still resolving)": { + "summary": "Select stayed pending after polling — sparse fields, auto-completes server-side", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "7c0a4f7e-2b1d-4f6a-9c3e-8d5b2a1f0e9d", + "node_id": "feature_kyb_registry", + "status": "Not Finished", + "registry_status": null, + "data_resolved": false, + "company_name": "Tesco PLC", + "registration_number": "00445790", + "country_code": "GB", + "region": null, + "company_type": null, + "incorporation_date": null, + "registered_address": null, + "tax_number": null, + "risk_level": null, + "verification_status": null, + "is_from_registry": true, + "fetch_status": "pending", + "alternative_names": null, + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": null, + "email": null, + "phone": null, + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": {}, + "officers": [], + "beneficial_owners": [], + "addresses": null, + "industries": null, + "accounts": null, + "registry_data": {}, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems use DRF's standard envelope. A country the registries do not cover answers `400` with `code: kyb_registry_country_unsupported`.", + "content": { + "application/json": { + "examples": { + "Missing kyb_response_id": { + "summary": "`kyb_response_id` is required", + "value": { + "kyb_response_id": [ + "This field is required." + ] + } + }, + "save_api_request=false rejected": { + "summary": "Selects are always persisted", + "value": { + "save_api_request": [ + "KYB registry select must be saved so billing and retrieval can be tracked." + ] + } + }, + "Country not covered": { + "summary": "No company registry covers the country", + "value": { + "detail": "Company registry search is not available for this country. Enter the company details manually to continue.", + "code": "kyb_registry_country_unsupported" + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/passive-liveness/": { + "post": { + "summary": "Passive Liveness", + "description": "Detect presentation attacks — printed photos, screen replays, masks, deepfakes — from a single face image. No video, motion, or user interaction required.\n\n**How it works.** The image is analyzed by a biometric model; the largest detected face is evaluated and a liveness `score` (0–100) is computed. `status` is `Declined` when any `error`-severity warning fires:\n- `NO_FACE_DETECTED` — the liveness model found no face,\n- `LOW_LIVENESS_SCORE` — score at or below `face_liveness_score_decline_threshold` (default `30`),\n- `LIVENESS_FACE_ATTACK` — the model detected a presentation attack,\n- `FACE_IN_BLOCKLIST` / `POSSIBLE_FACE_IN_BLOCKLIST` — the face matches an entry on your face blocklist.\n\n`MULTIPLE_FACES_DETECTED` is a `warning`-severity signal (does not decline), and `DUPLICATED_FACE` / `POSSIBLE_DUPLICATED_FACE` are `information`-severity signals that the same face already appeared in another approved session (they never decline). `face_quality` and `face_luminance` (0–100, nullable) describe capture quality.\n\n**Blocklist and duplicate screening.** Runs on **every** call — with or without `save_api_request` — by matching the detected face against your application's face search index. Prior faces with the same `vendor_data` are excluded from the entire index search — so re-running liveness for the same user does not trigger `DUPLICATED_FACE`, and a blocklisted face originating from a session with the caller's own `vendor_data` is also NOT flagged (`FACE_IN_BLOCKLIST` screening is bypassed for same-`vendor_data` faces).\n\n**Billing.** Each `200` response consumes one Passive Liveness API credit (paid from your plan credit or top-up balance). Insufficient balance returns `403` before any image processing.\n\n**Session persistence and face enrollment (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session (Business Console, `GET /v3/session/{sessionId}/decision/` via the returned `request_id`, `status.updated` webhook), the reference image and face crop are stored, and the detected face is **enrolled into your face search index** so later Face Search calls and duplicate checks can match it. When `false`, the call is one-shot: nothing is stored and the face is not enrolled (blocklist/duplicate screening still runs).\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload, no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3passive-liveness", + "x-didit-sdk": { + "resource": "passive_liveness", + "method": "check", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "user_image" + ], + "properties": { + "user_image": { + "type": "string", + "format": "binary", + "description": "Front-facing face image (e.g. a selfie). Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp`. Maximum upload size: **5 MB** (larger files are rejected with `400`). Images are automatically compressed to ~0.5 MB before processing, so very high resolutions do not improve accuracy. The image should contain a single subject — when several faces are present, the largest one is evaluated and a `MULTIPLE_FACES_DETECTED` warning is added." + }, + "face_liveness_score_decline_threshold": { + "type": "number", + "format": "float", + "default": 30, + "minimum": 0, + "maximum": 100, + "description": "Liveness score threshold (0–100). A computed score **at or below** this value emits a `LOW_LIVENESS_SCORE` warning and sets `status` to `Declined`. Default `30`; raise it for a stricter anti-spoofing posture. Values outside 0–100 return `400`.", + "example": 30 + }, + "rotate_image": { + "type": "boolean", + "default": false, + "description": "When `true`, the service tries 90-degree rotations of the input and uses the orientation that yields the best face detection. Useful when EXIF orientation is missing. Adds latency.", + "example": false + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session, stores the face images, emits a `status.updated` webhook, and enrolls the detected face into your face search index. When `false`, the call is one-shot — nothing is stored and the face is not enrolled. Blocklist and duplicate screening run either way.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque identifier (your user id, email, UUID…) stored on the persisted session and echoed back. Also used to exclude prior sessions with the same `vendor_data` from the duplicate-face check, so re-verifying the same user does not raise `DUPLICATED_FACE`.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back. In multipart requests, send it as a JSON-encoded string field — it is parsed into an object.", + "example": { + "flow": "withdrawal" + } + } + } + }, + "example": { + "user_image": "(binary JPEG/PNG selfie)", + "face_liveness_score_decline_threshold": 30, + "rotate_image": false, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "withdrawal" + } + } + } + } + }, + "responses": { + "200": { + "description": "Passive liveness completed. `liveness.score` is the model's 0–100 confidence that the image shows a live person; `status` is `Approved` unless an `error`-severity warning fired. A spoofed or blocklisted face still returns `200` — inspect `liveness.status` and `liveness.warnings`, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Live face — no warnings", + "value": { + "request_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef", + "liveness": { + "status": "Approved", + "method": "PASSIVE", + "score": 95.0, + "user_image": { + "entities": [ + { + "bbox": [ + 661, + 728, + 1688, + 2188 + ], + "confidence": 0.732973, + "age": 26.91, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [], + "face_quality": 84.21, + "face_luminance": 50.33 + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "Declined - presentation attack": { + "summary": "The model flagged a spoof (print, replay, mask…)", + "value": { + "request_id": "f1c8c9b2-d3e4-4f5a-9b8c-7d6e5f4a3b2c", + "liveness": { + "status": "Declined", + "method": "PASSIVE", + "score": 12.5, + "user_image": { + "entities": [ + { + "bbox": [ + 100, + 120, + 420, + 540 + ], + "confidence": 0.81, + "age": 30.1, + "gender": "female", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "LOW_LIVENESS_SCORE", + "feature": "LIVENESS", + "additional_data": null, + "log_type": "error", + "short_description": "Low liveness score", + "long_description": "The liveness check resulted in a low score, indicating potential use of non-live facial representations or poor-quality biometric data." + }, + { + "risk": "LIVENESS_FACE_ATTACK", + "feature": "LIVENESS", + "additional_data": null, + "log_type": "error", + "short_description": "Liveness Face Attack", + "long_description": "The system detected a potential attempt to bypass the liveness check." + } + ], + "face_quality": 72.4, + "face_luminance": 48.2 + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "Declined - face on blocklist": { + "summary": "Live face, but it matches a blocklist entry", + "value": { + "request_id": "0b54a1de-22aa-4e0f-9c84-5a2b1f3c4d5e", + "liveness": { + "status": "Declined", + "method": "PASSIVE", + "score": 93.75, + "user_image": { + "entities": [ + { + "bbox": [ + 40, + 40, + 100, + 100 + ], + "confidence": 0.722082, + "age": 27.0, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "FACE_IN_BLOCKLIST", + "feature": "LIVENESS", + "additional_data": { + "blocklisted_session_id": "882c42d5-8a4d-4d20-8080-a22f57822c86", + "blocklisted_session_number": 3242, + "api_service": null + }, + "log_type": "error", + "short_description": "Face in blocklist", + "long_description": "The system identified a face in the blocklist, which means the face is not allowed to be verified." + } + ], + "face_quality": null, + "face_luminance": null + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "liveness": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Declined` when at least one `error`-severity warning fired (no face, low score, presentation attack, or a blocklist hit); `Approved` otherwise. `warning`/`information` entries (multiple faces, duplicates) never decline on their own." + }, + "method": { + "type": "string", + "enum": [ + "PASSIVE" + ], + "description": "Always `PASSIVE` for this endpoint." + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Liveness confidence (0–100, two decimals) that the image depicts a live person rather than a spoof. `null` when the model returned no confidence.", + "example": 95.0 + }, + "user_image": { + "type": "object", + "description": "Face-detection results for the submitted image.", + "properties": { + "entities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box of the detected face as `[x_min, y_min, x_max, y_max]` pixel coordinates in the processed image.", + "example": [ + 661, + 728, + 1688, + 2188 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.732973 + }, + "age": { + "type": "number", + "format": "float", + "description": "Model-estimated age of the detected face, in years. Informational only — it does not affect the liveness decision.", + "example": 26.91 + }, + "gender": { + "type": "string", + "description": "Model-predicted gender of the detected face (`male` or `female`). Informational only.", + "example": "male" + }, + "race": { + "type": "string", + "nullable": true, + "description": "Reserved field — always `null` in responses from this endpoint.", + "example": null + } + } + }, + "description": "One entry per detected face. The largest face is the one evaluated." + }, + "best_angle": { + "type": "integer", + "nullable": true, + "description": "Rotation (degrees) that produced the best face detection; non-zero only when `rotate_image=true` corrected the orientation.", + "example": 0 + } + } + }, + "warnings": { + "type": "array", + "description": "Risk signals. `error` severity (declines): `NO_FACE_DETECTED`, `LOW_LIVENESS_SCORE` (score at or below the threshold), `LIVENESS_FACE_ATTACK`, `FACE_IN_BLOCKLIST`, `POSSIBLE_FACE_IN_BLOCKLIST`. `warning` severity (review, no decline): `MULTIPLE_FACES_DETECTED`. `information` severity (no decline): `DUPLICATED_FACE`, `POSSIBLE_DUPLICATED_FACE` — the same face already appeared in another approved session.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "NO_FACE_DETECTED", + "LOW_LIVENESS_SCORE", + "LIVENESS_FACE_ATTACK", + "MULTIPLE_FACES_DETECTED", + "FACE_IN_BLOCKLIST", + "POSSIBLE_FACE_IN_BLOCKLIST", + "DUPLICATED_FACE", + "POSSIBLE_DUPLICATED_FACE" + ], + "description": "Machine-readable risk code." + }, + "feature": { + "type": "string", + "enum": [ + "LIVENESS" + ], + "description": "Feature that raised the warning. Always `LIVENESS` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "`null` for most warnings. Blocklist hits carry `{blocklisted_session_id, blocklisted_session_number, api_service}`; duplicate hits carry `{duplicated_session_id, duplicated_session_number, api_service}` pointing at the matching session." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `warning` and `information` entries are advisory and never decline on their own." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "face_quality": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Capture-quality score of the detected face, normalized to 0–100. `null` when the model could not produce a reliable value.", + "example": 84.21 + }, + "face_luminance": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Brightness of the detected face, normalized to 0–100. `null` when unavailable.", + "example": 50.33 + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated, e.g. `2026-06-12T01:04:42.763237+00:00`." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when `user_image` is missing, exceeds 5 MB, has an unsupported extension, fails server-side image decoding, or when an option is out of range. Field errors use DRF's `{field: [messages]}` envelope; image-decoding failures use `{\"error\": ...}`.", + "content": { + "application/json": { + "examples": { + "Missing image": { + "summary": "`user_image` not included in the form data", + "value": { + "user_image": [ + "No file was submitted." + ] + } + }, + "Invalid image format": { + "summary": "Allowed extension but undecodable image content", + "value": { + "error": "Invalid user image format.", + "code": "invalid_image" + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp", + "value": { + "user_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 5 MB limit", + "value": { + "user_image": [ + "File size should not exceed 5 MB" + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/passive-liveness/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'user_image=@./selfie.jpg' \\\n -F 'face_liveness_score_decline_threshold=30' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.passive_liveness.check(\n user_image=open(\"user_image.jpg\", \"rb\"),\n face_liveness_score_decline_threshold=30.0,\n rotate_image=False,\n save_api_request=True,\n vendor_data=\"user-123\",\n metadata={\"flow\": \"withdrawal\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.passiveLiveness.check({\n user_image: await fileFromPath(\"user_image.jpg\"),\n face_liveness_score_decline_threshold: 30.0,\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"withdrawal\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.PassiveLiveness.Check(ctx, didit.PassiveLivenessCheckParams{\n\tUserImage: didit.FileFromPath(\"user_image.jpg\"),\n\tFaceLivenessScoreDeclineThreshold: didit.Float(30.0),\n\tRotateImage: didit.Bool(false),\n\tSaveAPIRequest: didit.Bool(true),\n\tVendorData: didit.String(\"user-123\"),\n\tMetadata: didit.Some(map[string]any{\"flow\": \"withdrawal\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.PassiveLivenessCheckParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.passiveLiveness().check(PassiveLivenessCheckParams.builder()\n .userImage(Path.of(\"user_image.jpg\"))\n .faceLivenessScoreDeclineThreshold(30.0)\n .rotateImage(false)\n .saveApiRequest(true)\n .vendorData(\"user-123\")\n .metadata(Map.of(\"flow\", \"withdrawal\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "passiveLiveness->check([\n 'user_image' => File::fromPath('user_image.jpg'),\n 'face_liveness_score_decline_threshold' => 30.0,\n 'rotate_image' => false,\n 'save_api_request' => true,\n 'vendor_data' => 'user-123',\n 'metadata' => ['flow' => 'withdrawal'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.passive_liveness.check(\n user_image: Pathname(\"user_image.jpg\"),\n face_liveness_score_decline_threshold: 30.0,\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"withdrawal\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.PassiveLiveness.CheckAsync(new PassiveLivenessCheckParams\n{\n UserImage = FileUpload.FromPath(\"user_image.jpg\"),\n FaceLivenessScoreDeclineThreshold = 30.0,\n RotateImage = false,\n SaveApiRequest = true,\n VendorData = \"user-123\",\n Metadata = new Dictionary { [\"flow\"] = \"withdrawal\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/database-validation/": { + "post": { + "summary": "Database Validation (standalone)", + "operationId": "post_v3database-validation", + "x-didit-sdk": { + "resource": "database_validation", + "method": "validate", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Validate a person's identity data against official government and registry sources — CPF in Brazil, RENAPER in Argentina, DNI registries in Spain and Peru, INE in Mexico, and 100+ more services across 60+ countries.\n\n**Service selection.** Each country exposes one or more catalog services identified by a `service_id` (e.g. `bra_cpf`, `arg_renaper`, `pan_cedula_sib`). Pin the services you want with the `services` array — if you omit it, exactly **one** default service runs (the longest-established live service for the country). Sending `services` also switches the response to the extended shape with `services_used` and `match_score`. Discover services, their required fields, and prices via `GET /v1/organization/database-validation-countries/` (a catalog endpoint not documented in this spec) or the Business Console workflow editor. Some services are flagged `requires_consent=true` in the catalog and demand `consent=true`; others require onboarding for your organization before they can be called.\n\n**Input fields.** `identification_number` is a universal field that maps to the right country-specific field automatically (see its description). Country format rules are enforced before any provider is called (e.g. BRA CPF must be exactly 11 digits) and format failures are never billed. Biometric services need a `selfie` upload (multipart only); Argentina RENAPER additionally requires `gender`.\n\n**Results.** Each service returns a field-by-field `validation` map plus a vendor-neutral `outcome_code` (`MATCH`, `PARTIAL_MATCH`, `NO_MATCH`, `DOCUMENT_NOT_FOUND`, `INVALID_DOCUMENT_FORMAT`, `INVALID_INPUT`, `MINOR_BLOCKED`, `DECEASED`, `BIOMETRIC_NO_MATCH`, `BIOMETRIC_IMAGE_UNUSABLE`, `INCONCLUSIVE`, `DOCUMENT_SUPERSEDED`, `REGISTRY_UNAVAILABLE`, `REGISTRY_ERROR`). Note the distinctions: `NO_MATCH` is a definitive mismatch, `INCONCLUSIVE` means the registry could not confirm either way (review, not decline), and `BIOMETRIC_IMAGE_UNUSABLE` is a technical selfie problem (retake), unlike the definitive `BIOMETRIC_NO_MATCH`. The overall `match_type` aggregates across services (any full → `full_match`, else any partial → `partial_match`, else `no_match`) and `no_match_action`/`partial_match_action` translate it into the final `status`. `validation_type` is derived: `two_by_two` when two or more distinct services produced a full match (on the identification number, on full name + date of birth together, or on the address without contradictions), else `one_by_one`.\n\n**Persistence.** `save_api_request` defaults to **true**: the result is stored as a session (`request_id` works with `GET /v3/session/{sessionId}/decision/`), appears in the console, and fires a `status.updated` webhook. It also controls the `validations` shape — per-service objects when saved, a merged `{field: match}` map when not.\n\n**Billing.** Per-service: each service that returns a billable result is charged its own catalog price. Failed or skipped services are not billed, and requests rejected with `400` cost nothing. The pre-flight balance check covers the sum of the selected services' prices (`403` when short).\n\n**Failures.** When no selected service returns a usable result, the API returns `400` with `validation_errors` if every unanswered attempt explicitly refused the input (`provider_rejected_input`, `retryable: false`). Correct the input before trying again. Otherwise it returns `502` with `validation_errors` (`empty_provider_response`, `retryable: true`), including mixed input rejection and unavailable fallback attempts; nothing is billed (a session is still recorded with status `Not Finished` when `save_api_request=true`). When only some services fail, the `200` response carries their failures in `database_validation.errors`.\n\n**Sandbox.** Keys from sandbox applications skip the registry calls and billing and return a static `full_match` response — the sandbox payload always includes `services_used` and `match_score: 100` regardless of how `services` was sent, so don't validate the live extended-shape gating or the 0.0–1.0 `match_score` fraction against sandbox responses.\n\nSend the request as `application/json`, or as `multipart/form-data` when uploading a `selfie`.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "issuing_state" + ], + "properties": { + "issuing_state": { + "type": "string", + "description": "ISO 3166-1 **alpha-3** country code of the registry to validate against (e.g. `BRA`, `ESP`, `COL`). Determines which services are available. Unsupported codes return `400` with the full list of valid options.", + "example": "BRA" + }, + "services": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Catalog `service_id`s to run for this country (e.g. `[\"bra_cpf\"]`). Also accepts a single string or a comma-separated/JSON-encoded string. **If omitted or empty, exactly one default service runs** — the longest-established live service for the country; newer, biometric, and pay-as-you-go services must be named explicitly. Sending `services` switches the response to the extended shape (adds `services_used` and `match_score`). Every id must exist and be live for the `issuing_state`; services that require onboarding return `400` until activated for your organization. Discover available services per country via `GET /v1/organization/database-validation-countries/` (a catalog endpoint not documented in this spec) or the Business Console workflow editor.", + "example": [ + "bra_cpf" + ] + }, + "validation_type": { + "type": "string", + "deprecated": true, + "description": "DEPRECATED. Accepted for backward compatibility but ignored — the response `validation_type` is now derived from how many services full-matched. Use `services` to pin specific services." + }, + "consent": { + "type": "boolean", + "default": false, + "description": "Set to `true` when the end user has explicitly consented to the selected validation services. Required for services flagged `requires_consent=true` in the catalog — without it those services return `400` with `services_requiring_consent`." + }, + "identification_number": { + "type": "string", + "description": "Universal identification number — automatically mapped to the correct country-specific field, so you can use it instead of `personal_number`/`tax_number`/`document_number`: ARG→document_number (DNI), BOL→document_number (CI), BRA→tax_number (CPF, 11 digits), CHL→personal_number (RUT), COL→personal_number (Cédula), CRI→personal_number (Cédula), DOM→personal_number (Cédula, 11 digits), ECU→personal_number (Cédula, 10 digits), ESP→personal_number (DNI/NIE), GTM→document_number (DPI), HND→document_number (DNI), MEX→personal_number (CURP, 18 chars), PAN→personal_number (Cédula), PER→personal_number (DNI, 8 digits), PRY→document_number (CI), SLV→document_number (DUI), URY→personal_number (CI), VEN→document_number (Cédula). It never overrides an explicitly provided country-specific field." + }, + "first_name": { + "type": "string", + "description": "The individual's first name. Required by some services/countries.", + "example": "John" + }, + "last_name": { + "type": "string", + "description": "The individual's last name. Required by some services/countries.", + "example": "Doe" + }, + "middle_name": { + "type": "string", + "description": "Middle name, used by some country services (AUS, NZL, …)." + }, + "full_name": { + "type": "string", + "description": "Full name — some services accept this in lieu of first/last name (e.g. CHN services, which match the native-script name)." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth, `YYYY-MM-DD`. Required by many services.", + "example": "1980-01-01" + }, + "personal_number": { + "type": "string", + "description": "Government-issued unique personal identifier. Used by: CHL (RUT), COL (Cédula), CRI, DOM, ECU, ESP (DNI/NIE), MEX (CURP), PAN (Cédula), PER (DNI), URY. Consider `identification_number` instead." + }, + "tax_number": { + "type": "string", + "description": "Tax identification number. Used by: BRA (CPF, 11 digits). Consider `identification_number` instead." + }, + "document_number": { + "type": "string", + "description": "Document number. Used by: ARG (DNI), BOL (CI), GTM (DPI), HND (DNI), PRY (CI), SLV (DUI), VEN (Cédula). Consider `identification_number` instead." + }, + "document_type": { + "type": "string", + "enum": [ + "P", + "DL", + "ID", + "RP", + "SSC", + "HIC", + "WP", + "TC", + "VISA", + "PSC", + "BC", + "OTHER" + ], + "description": "Type of document being validated: `P` passport, `DL` driver license, `ID` national ID, `RP` residence permit (plus `SSC`, `HIC`, `WP`, `TC`, `VISA`, `PSC`, `BC`, `OTHER`). Required by some services (e.g. ESP)." + }, + "expiration_date": { + "type": "string", + "format": "date", + "description": "Document expiration date, `YYYY-MM-DD`. Required for ESP (Spain) validation.", + "example": "2030-01-15" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "description": "Document issue date, `YYYY-MM-DD` (fecha de expedición). Required for COL (Colombia) cédula validation.", + "example": "2015-06-20" + }, + "nationality": { + "type": "string", + "description": "Nationality as ISO 3166-1 alpha-3. Required by some services." + }, + "gender": { + "type": "string", + "enum": [ + "M", + "F", + "X" + ], + "description": "Gender: `M`, `F`, or `X` (other/unknown). Required for Argentina's RENAPER validation (`arg_renaper`)." + }, + "address": { + "description": "Residential address. Prefer the structured object `{\"street_1\":\"123 Main St\",\"street_2\":\"Apt 4B\",\"city\":\"Springfield\",\"region\":\"IL\",\"postal_code\":\"62701\",\"country\":\"US\"}`; a complete single-line string is still accepted. Required by address-verification services; when a service defines address requirements, missing parts return `400` with `address_fields_required_by`.", + "oneOf": [ + { + "type": "object", + "properties": { + "street_1": { + "type": "string" + }, + "street_2": { + "type": "string" + }, + "city": { + "type": "string" + }, + "region": { + "type": "string" + }, + "postal_code": { + "type": "string" + }, + "country": { + "type": "string" + } + } + }, + { + "type": "string", + "description": "Complete single-line address (legacy)." + } + ] + }, + "address_element_1": { + "type": "string", + "description": "Street address including street number and type. If omitted, derived from `address`." + }, + "address_element_2": { + "type": "string", + "description": "Apartment, unit, building, floor, or extra address line. Only send when you have it explicitly." + }, + "address_element_3": { + "type": "string", + "description": "City, suburb, district, locality, or neighborhood. If omitted, derived from `address`." + }, + "address_element_4": { + "type": "string", + "description": "State, province, region, or town. If omitted, derived from `address`." + }, + "address_element_5": { + "type": "string", + "description": "Postcode or postal code. `postal_code` is accepted as an alias." + }, + "postal_code": { + "type": "string", + "description": "Postal code for address-based services (alias of `address_element_5`)." + }, + "driver_license_number": { + "type": "string", + "description": "Driver licence number for government document-verification services (AUS, NZL, IND)." + }, + "driver_license_card_number": { + "type": "string", + "description": "Physical driver licence card number, where required separately from the licence number (AUS)." + }, + "driver_license_state": { + "type": "string", + "description": "Driver licence state or territory of issue (AUS)." + }, + "driver_license_version": { + "type": "string", + "description": "Driver licence version code (NZL)." + }, + "passport_number": { + "type": "string", + "description": "Passport number for government passport-verification services (AUS, NZL, IND, CHN)." + }, + "passport_expiration_date": { + "type": "string", + "format": "date", + "description": "Passport expiry date, `YYYY-MM-DD`, for passport-verification services." + }, + "passport_file_number": { + "type": "string", + "description": "Passport file number (IND)." + }, + "passport_issue_country": { + "type": "string", + "description": "Issuing country of the passport (ISO 3166-1 alpha-3) for passport-verification services." + }, + "medicare_card_number": { + "type": "string", + "description": "Medicare card number (AUS)." + }, + "immi_card_number": { + "type": "string", + "description": "Australian ImmiCard number." + }, + "immi_card_expiry_date": { + "type": "string", + "format": "date", + "description": "Australian ImmiCard expiry date." + }, + "citizenship_certificate_number": { + "type": "string", + "description": "Citizenship certificate number (AUS)." + }, + "birth_registration_number": { + "type": "string", + "description": "Birth-certificate registration number (AUS)." + }, + "birth_registration_date": { + "type": "string", + "format": "date", + "description": "Birth-certificate registration date (AUS)." + }, + "birth_registration_state": { + "type": "string", + "description": "Birth-certificate registration state (AUS)." + }, + "marriage_certificate_number": { + "type": "string", + "description": "Marriage certificate number (AUS)." + }, + "change_of_name_certificate_number": { + "type": "string", + "description": "Change-of-name certificate number (AUS)." + }, + "first_partner_name": { + "type": "string", + "description": "First name of the partner on a marriage certificate (AUS)." + }, + "last_partner_name": { + "type": "string", + "description": "Last name of the partner on a marriage certificate (AUS)." + }, + "voter_id": { + "type": "string", + "description": "Voter registration number (IND EPIC, IRL)." + }, + "epic_card": { + "type": "string", + "description": "India EPIC voter card number." + }, + "pan": { + "type": "string", + "description": "India PAN (Permanent Account Number)." + }, + "national_id": { + "type": "string", + "description": "National ID number (CHN, MYS, KEN, KHM, NGA, ZAF, …)." + }, + "cic": { + "type": "string", + "description": "Mexican INE/IFE Código de Identificación de Credencial (CIC), 9 digits. Used by the MEX INE credential-validity service (`mex_ine_vigencia`)." + }, + "identificador_ciudadano": { + "type": "string", + "description": "Mexican INE Identificador del Ciudadano (9 digits). Used with `cic` for modern INE models (E/F/G/H) in `mex_ine_vigencia`." + }, + "ocr": { + "type": "string", + "description": "Mexican INE OCR number (13 digits, back of card). Used with `cic` for Model D in `mex_ine_vigencia`." + }, + "voter_number": { + "type": "string", + "description": "Mexican INE Clave de Elector (18 chars). Used with `emission_number` for legacy IFE models (A/B/C) in `mex_ine_vigencia`." + }, + "emission_number": { + "type": "string", + "description": "Mexican INE Número de Emisión. Used with `voter_number` for legacy IFE models (A/B/C) in `mex_ine_vigencia`." + }, + "bvn": { + "type": "string", + "description": "Nigerian Bank Verification Number." + }, + "bank_card_number": { + "type": "string", + "description": "Bank card number (CHN bank-card verification)." + }, + "company_registration_number": { + "type": "string", + "description": "Company registration number as issued by the companies register: the CNPJ in Brazil (e.g. `11222333000181`), the registration number in South Africa (e.g. `1936/007721/06`). Required by the company registry and company directors services (`bra_company_registry`, `bra_company_directors`, `zaf_company_registry`, `zaf_company_directors`)." + }, + "company_name": { + "type": "string", + "description": "Registered company name, cross-checked against the register entry. Optional for `zaf_company_registry`." + }, + "initials": { + "type": "string", + "description": "The person's initials as printed on the document. Required by `zaf_drivers_license`; accepted by `zaf_bank_account_holder`." + }, + "licence_number": { + "type": "string", + "description": "Driving licence number as printed on the card, for `zaf_drivers_license`. When omitted, `document_number` is used." + }, + "refugee_file_number": { + "type": "string", + "description": "Refugee file number issued by South Africa's Department of Home Affairs. Required by `zaf_refugee`." + }, + "bank_name": { + "type": "string", + "description": "Name of the bank that holds the account. Required by `zaf_bank_account_holder`." + }, + "bank_account_number": { + "type": "string", + "description": "Bank account number. Required by `zaf_bank_account_holder`; accepted by `zaf_fraud_prevention`." + }, + "account_type": { + "type": "string", + "description": "Bank account type for `zaf_bank_account_holder`: `current`, `savings`, `transmission`, `subscriptionShare` or `notKnown`, case-insensitive. Any other value returns `400`. Omit it when you do not know the type: the account-type cross-check is then reported as not confirmed." + }, + "phone_number": { + "type": "string", + "description": "Contact mobile number, for the services that cross-check contactability (`zaf_bank_account_holder`, `zaf_fraud_prevention`, `zaf_refugee`)." + }, + "right_thumb": { + "type": "string", + "description": "Base64-encoded right thumb fingerprint for `zaf_dha_fingerprint_match`, with `left_thumb` or on its own." + }, + "left_thumb": { + "type": "string", + "description": "Base64-encoded left thumb fingerprint for `zaf_dha_fingerprint_match`, with `right_thumb` or on its own." + }, + "score_level": { + "type": "integer", + "description": "Minimum biometric score level for `zaf_criminal_face_screening`. Defaults to `3`." + }, + "max_matches": { + "type": "integer", + "description": "Maximum number of candidate matches `zaf_criminal_face_screening` returns. Defaults to `1`." + }, + "vehicle_register_number": { + "type": "string", + "description": "Vehicle register number. Required, with `vehicle_licence_plate` and `vin`, by `zaf_vehicle_ownership`." + }, + "vehicle_licence_plate": { + "type": "string", + "description": "Vehicle licence plate number. Required, with `vehicle_register_number` and `vin`, by `zaf_vehicle_ownership`." + }, + "vin": { + "type": "string", + "description": "Vehicle Identification Number (VIN or chassis number). Required, with `vehicle_register_number` and `vehicle_licence_plate`, by `zaf_vehicle_ownership`." + }, + "ssn": { + "type": "string", + "description": "Social Security Number (USA)." + }, + "phone": { + "type": "string", + "description": "Phone number for phone-verification services." + }, + "landline": { + "type": "string", + "description": "Landline number for phone-verification services." + }, + "email": { + "type": "string", + "format": "email", + "description": "Email address for identity-verification services." + }, + "country_of_residence": { + "type": "string", + "description": "ISO 3166-1 alpha-2 country of residence. Used by global identity-enrichment services (e.g. `glb_identity_enrichment`) to focus the lookup.", + "example": "US" + }, + "partial_match_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "NO_ACTION", + "description": "What a `partial_match` does to `database_validation.status`. Defaults to `NO_ACTION` (status stays `Approved`, with a warning)." + }, + "no_match_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What a `no_match` does to `database_validation.status`. Defaults to `DECLINE`." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "Persist the validation as a session (console visibility, decision endpoint, `status.updated` webhook). Also changes the `validations` response shape: per-service objects when `true` (default), a merged field map when `false`." + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for the validated user; echoed back and stored with the session." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON stored with the request and echoed back." + } + } + }, + "example": { + "issuing_state": "BRA", + "services": [ + "bra_cpf" + ], + "identification_number": "12345678900", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-01-01", + "vendor_data": "user-1234" + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "issuing_state" + ], + "properties": { + "issuing_state": { + "type": "string", + "description": "ISO 3166-1 **alpha-3** country code of the registry to validate against (e.g. `BRA`, `ESP`, `COL`). Determines which services are available. Unsupported codes return `400` with the full list of valid options.", + "example": "BRA" + }, + "services": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Catalog `service_id`s to run for this country (e.g. `[\"bra_cpf\"]`). Also accepts a single string or a comma-separated/JSON-encoded string. **If omitted or empty, exactly one default service runs** — the longest-established live service for the country; newer, biometric, and pay-as-you-go services must be named explicitly. Sending `services` switches the response to the extended shape (adds `services_used` and `match_score`). Every id must exist and be live for the `issuing_state`; services that require onboarding return `400` until activated for your organization. Discover available services per country via `GET /v1/organization/database-validation-countries/` (a catalog endpoint not documented in this spec) or the Business Console workflow editor.", + "example": [ + "bra_cpf" + ] + }, + "validation_type": { + "type": "string", + "deprecated": true, + "description": "DEPRECATED. Accepted for backward compatibility but ignored — the response `validation_type` is now derived from how many services full-matched. Use `services` to pin specific services." + }, + "consent": { + "type": "boolean", + "default": false, + "description": "Set to `true` when the end user has explicitly consented to the selected validation services. Required for services flagged `requires_consent=true` in the catalog — without it those services return `400` with `services_requiring_consent`." + }, + "identification_number": { + "type": "string", + "description": "Universal identification number — automatically mapped to the correct country-specific field, so you can use it instead of `personal_number`/`tax_number`/`document_number`: ARG→document_number (DNI), BOL→document_number (CI), BRA→tax_number (CPF, 11 digits), CHL→personal_number (RUT), COL→personal_number (Cédula), CRI→personal_number (Cédula), DOM→personal_number (Cédula, 11 digits), ECU→personal_number (Cédula, 10 digits), ESP→personal_number (DNI/NIE), GTM→document_number (DPI), HND→document_number (DNI), MEX→personal_number (CURP, 18 chars), PAN→personal_number (Cédula), PER→personal_number (DNI, 8 digits), PRY→document_number (CI), SLV→document_number (DUI), URY→personal_number (CI), VEN→document_number (Cédula). It never overrides an explicitly provided country-specific field." + }, + "first_name": { + "type": "string", + "description": "The individual's first name. Required by some services/countries.", + "example": "John" + }, + "last_name": { + "type": "string", + "description": "The individual's last name. Required by some services/countries.", + "example": "Doe" + }, + "middle_name": { + "type": "string", + "description": "Middle name, used by some country services (AUS, NZL, …)." + }, + "full_name": { + "type": "string", + "description": "Full name — some services accept this in lieu of first/last name (e.g. CHN services, which match the native-script name)." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth, `YYYY-MM-DD`. Required by many services.", + "example": "1980-01-01" + }, + "personal_number": { + "type": "string", + "description": "Government-issued unique personal identifier. Used by: CHL (RUT), COL (Cédula), CRI, DOM, ECU, ESP (DNI/NIE), MEX (CURP), PAN (Cédula), PER (DNI), URY. Consider `identification_number` instead." + }, + "tax_number": { + "type": "string", + "description": "Tax identification number. Used by: BRA (CPF, 11 digits). Consider `identification_number` instead." + }, + "document_number": { + "type": "string", + "description": "Document number. Used by: ARG (DNI), BOL (CI), GTM (DPI), HND (DNI), PRY (CI), SLV (DUI), VEN (Cédula). Consider `identification_number` instead." + }, + "document_type": { + "type": "string", + "enum": [ + "P", + "DL", + "ID", + "RP", + "SSC", + "HIC", + "WP", + "TC", + "VISA", + "PSC", + "BC", + "OTHER" + ], + "description": "Type of document being validated: `P` passport, `DL` driver license, `ID` national ID, `RP` residence permit (plus `SSC`, `HIC`, `WP`, `TC`, `VISA`, `PSC`, `BC`, `OTHER`). Required by some services (e.g. ESP)." + }, + "expiration_date": { + "type": "string", + "format": "date", + "description": "Document expiration date, `YYYY-MM-DD`. Required for ESP (Spain) validation.", + "example": "2030-01-15" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "description": "Document issue date, `YYYY-MM-DD` (fecha de expedición). Required for COL (Colombia) cédula validation.", + "example": "2015-06-20" + }, + "nationality": { + "type": "string", + "description": "Nationality as ISO 3166-1 alpha-3. Required by some services." + }, + "gender": { + "type": "string", + "enum": [ + "M", + "F", + "X" + ], + "description": "Gender: `M`, `F`, or `X` (other/unknown). Required for Argentina's RENAPER validation (`arg_renaper`)." + }, + "address": { + "description": "Residential address. Prefer the structured object `{\"street_1\":\"123 Main St\",\"street_2\":\"Apt 4B\",\"city\":\"Springfield\",\"region\":\"IL\",\"postal_code\":\"62701\",\"country\":\"US\"}`; a complete single-line string is still accepted. Required by address-verification services; when a service defines address requirements, missing parts return `400` with `address_fields_required_by`.", + "oneOf": [ + { + "type": "object", + "properties": { + "street_1": { + "type": "string" + }, + "street_2": { + "type": "string" + }, + "city": { + "type": "string" + }, + "region": { + "type": "string" + }, + "postal_code": { + "type": "string" + }, + "country": { + "type": "string" + } + } + }, + { + "type": "string", + "description": "Complete single-line address (legacy)." + } + ] + }, + "address_element_1": { + "type": "string", + "description": "Street address including street number and type. If omitted, derived from `address`." + }, + "address_element_2": { + "type": "string", + "description": "Apartment, unit, building, floor, or extra address line. Only send when you have it explicitly." + }, + "address_element_3": { + "type": "string", + "description": "City, suburb, district, locality, or neighborhood. If omitted, derived from `address`." + }, + "address_element_4": { + "type": "string", + "description": "State, province, region, or town. If omitted, derived from `address`." + }, + "address_element_5": { + "type": "string", + "description": "Postcode or postal code. `postal_code` is accepted as an alias." + }, + "postal_code": { + "type": "string", + "description": "Postal code for address-based services (alias of `address_element_5`)." + }, + "driver_license_number": { + "type": "string", + "description": "Driver licence number for government document-verification services (AUS, NZL, IND)." + }, + "driver_license_card_number": { + "type": "string", + "description": "Physical driver licence card number, where required separately from the licence number (AUS)." + }, + "driver_license_state": { + "type": "string", + "description": "Driver licence state or territory of issue (AUS)." + }, + "driver_license_version": { + "type": "string", + "description": "Driver licence version code (NZL)." + }, + "passport_number": { + "type": "string", + "description": "Passport number for government passport-verification services (AUS, NZL, IND, CHN)." + }, + "passport_expiration_date": { + "type": "string", + "format": "date", + "description": "Passport expiry date, `YYYY-MM-DD`, for passport-verification services." + }, + "passport_file_number": { + "type": "string", + "description": "Passport file number (IND)." + }, + "passport_issue_country": { + "type": "string", + "description": "Issuing country of the passport (ISO 3166-1 alpha-3) for passport-verification services." + }, + "medicare_card_number": { + "type": "string", + "description": "Medicare card number (AUS)." + }, + "immi_card_number": { + "type": "string", + "description": "Australian ImmiCard number." + }, + "immi_card_expiry_date": { + "type": "string", + "format": "date", + "description": "Australian ImmiCard expiry date." + }, + "citizenship_certificate_number": { + "type": "string", + "description": "Citizenship certificate number (AUS)." + }, + "birth_registration_number": { + "type": "string", + "description": "Birth-certificate registration number (AUS)." + }, + "birth_registration_date": { + "type": "string", + "format": "date", + "description": "Birth-certificate registration date (AUS)." + }, + "birth_registration_state": { + "type": "string", + "description": "Birth-certificate registration state (AUS)." + }, + "marriage_certificate_number": { + "type": "string", + "description": "Marriage certificate number (AUS)." + }, + "change_of_name_certificate_number": { + "type": "string", + "description": "Change-of-name certificate number (AUS)." + }, + "first_partner_name": { + "type": "string", + "description": "First name of the partner on a marriage certificate (AUS)." + }, + "last_partner_name": { + "type": "string", + "description": "Last name of the partner on a marriage certificate (AUS)." + }, + "voter_id": { + "type": "string", + "description": "Voter registration number (IND EPIC, IRL)." + }, + "epic_card": { + "type": "string", + "description": "India EPIC voter card number." + }, + "pan": { + "type": "string", + "description": "India PAN (Permanent Account Number)." + }, + "national_id": { + "type": "string", + "description": "National ID number (CHN, MYS, KEN, KHM, NGA, ZAF, …)." + }, + "cic": { + "type": "string", + "description": "Mexican INE/IFE Código de Identificación de Credencial (CIC), 9 digits. Used by the MEX INE credential-validity service (`mex_ine_vigencia`)." + }, + "identificador_ciudadano": { + "type": "string", + "description": "Mexican INE Identificador del Ciudadano (9 digits). Used with `cic` for modern INE models (E/F/G/H) in `mex_ine_vigencia`." + }, + "ocr": { + "type": "string", + "description": "Mexican INE OCR number (13 digits, back of card). Used with `cic` for Model D in `mex_ine_vigencia`." + }, + "voter_number": { + "type": "string", + "description": "Mexican INE Clave de Elector (18 chars). Used with `emission_number` for legacy IFE models (A/B/C) in `mex_ine_vigencia`." + }, + "emission_number": { + "type": "string", + "description": "Mexican INE Número de Emisión. Used with `voter_number` for legacy IFE models (A/B/C) in `mex_ine_vigencia`." + }, + "bvn": { + "type": "string", + "description": "Nigerian Bank Verification Number." + }, + "bank_card_number": { + "type": "string", + "description": "Bank card number (CHN bank-card verification)." + }, + "company_registration_number": { + "type": "string", + "description": "Company registration number as issued by the companies register: the CNPJ in Brazil (e.g. `11222333000181`), the registration number in South Africa (e.g. `1936/007721/06`). Required by the company registry and company directors services (`bra_company_registry`, `bra_company_directors`, `zaf_company_registry`, `zaf_company_directors`)." + }, + "company_name": { + "type": "string", + "description": "Registered company name, cross-checked against the register entry. Optional for `zaf_company_registry`." + }, + "initials": { + "type": "string", + "description": "The person's initials as printed on the document. Required by `zaf_drivers_license`; accepted by `zaf_bank_account_holder`." + }, + "licence_number": { + "type": "string", + "description": "Driving licence number as printed on the card, for `zaf_drivers_license`. When omitted, `document_number` is used." + }, + "refugee_file_number": { + "type": "string", + "description": "Refugee file number issued by South Africa's Department of Home Affairs. Required by `zaf_refugee`." + }, + "bank_name": { + "type": "string", + "description": "Name of the bank that holds the account. Required by `zaf_bank_account_holder`." + }, + "bank_account_number": { + "type": "string", + "description": "Bank account number. Required by `zaf_bank_account_holder`; accepted by `zaf_fraud_prevention`." + }, + "account_type": { + "type": "string", + "description": "Bank account type for `zaf_bank_account_holder`: `current`, `savings`, `transmission`, `subscriptionShare` or `notKnown`, case-insensitive. Any other value returns `400`. Omit it when you do not know the type: the account-type cross-check is then reported as not confirmed." + }, + "phone_number": { + "type": "string", + "description": "Contact mobile number, for the services that cross-check contactability (`zaf_bank_account_holder`, `zaf_fraud_prevention`, `zaf_refugee`)." + }, + "right_thumb": { + "type": "string", + "description": "Base64-encoded right thumb fingerprint for `zaf_dha_fingerprint_match`, with `left_thumb` or on its own." + }, + "left_thumb": { + "type": "string", + "description": "Base64-encoded left thumb fingerprint for `zaf_dha_fingerprint_match`, with `right_thumb` or on its own." + }, + "score_level": { + "type": "integer", + "description": "Minimum biometric score level for `zaf_criminal_face_screening`. Defaults to `3`." + }, + "max_matches": { + "type": "integer", + "description": "Maximum number of candidate matches `zaf_criminal_face_screening` returns. Defaults to `1`." + }, + "vehicle_register_number": { + "type": "string", + "description": "Vehicle register number. Required, with `vehicle_licence_plate` and `vin`, by `zaf_vehicle_ownership`." + }, + "vehicle_licence_plate": { + "type": "string", + "description": "Vehicle licence plate number. Required, with `vehicle_register_number` and `vin`, by `zaf_vehicle_ownership`." + }, + "vin": { + "type": "string", + "description": "Vehicle Identification Number (VIN or chassis number). Required, with `vehicle_register_number` and `vehicle_licence_plate`, by `zaf_vehicle_ownership`." + }, + "ssn": { + "type": "string", + "description": "Social Security Number (USA)." + }, + "phone": { + "type": "string", + "description": "Phone number for phone-verification services." + }, + "landline": { + "type": "string", + "description": "Landline number for phone-verification services." + }, + "email": { + "type": "string", + "format": "email", + "description": "Email address for identity-verification services." + }, + "country_of_residence": { + "type": "string", + "description": "ISO 3166-1 alpha-2 country of residence. Used by global identity-enrichment services (e.g. `glb_identity_enrichment`) to focus the lookup.", + "example": "US" + }, + "partial_match_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "NO_ACTION", + "description": "What a `partial_match` does to `database_validation.status`. Defaults to `NO_ACTION` (status stays `Approved`, with a warning)." + }, + "no_match_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What a `no_match` does to `database_validation.status`. Defaults to `DECLINE`." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "Persist the validation as a session (console visibility, decision endpoint, `status.updated` webhook). Also changes the `validations` response shape: per-service objects when `true` (default), a merged field map when `false`." + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for the validated user; echoed back and stored with the session." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON stored with the request and echoed back." + }, + "selfie": { + "type": "string", + "format": "binary", + "description": "Selfie image (`jpg`, `jpeg`, `png`, `webp`; max 2 MB — images are compressed to ~1.5 MB before upload to the registry) for biometric validation services, e.g. Argentina RENAPER (`arg_renaper`) and Panama SIB (`pan_cedula_sib`, `pan_cedula_sib_plus`). Required whenever a selected service lists it as a required field. Only available via `multipart/form-data`." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/database-validation/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"issuing_state\": \"BRA\",\n \"services\": [\"bra_cpf\"],\n \"identification_number\": \"12345678900\",\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"date_of_birth\": \"1980-01-01\",\n \"vendor_data\": \"user-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.database_validation.validate(\n issuing_state=\"BRA\",\n services=[\"bra_cpf\"],\n identification_number=\"12345678900\",\n first_name=\"John\",\n last_name=\"Doe\",\n date_of_birth=\"1980-01-01\",\n vendor_data=\"user-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.databaseValidation.validate({\n issuing_state: \"BRA\",\n services: [\"bra_cpf\"],\n identification_number: \"12345678900\",\n first_name: \"John\",\n last_name: \"Doe\",\n date_of_birth: \"1980-01-01\",\n vendor_data: \"user-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.DatabaseValidation.Validate(ctx, didit.DatabaseValidationValidateParams{\n\tIssuingState: \"BRA\",\n\tServices: didit.Some([]string{\"bra_cpf\"}),\n\tIdentificationNumber: didit.String(\"12345678900\"),\n\tFirstName: didit.String(\"John\"),\n\tLastName: didit.String(\"Doe\"),\n\tDateOfBirth: didit.String(\"1980-01-01\"),\n\tVendorData: didit.String(\"user-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.DatabaseValidationValidateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.databaseValidation().validate(DatabaseValidationValidateParams.builder()\n .issuingState(\"BRA\")\n .services(List.of(\"bra_cpf\"))\n .identificationNumber(\"12345678900\")\n .firstName(\"John\")\n .lastName(\"Doe\")\n .dateOfBirth(LocalDate.parse(\"1980-01-01\"))\n .vendorData(\"user-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "databaseValidation->validate([\n 'issuing_state' => 'BRA',\n 'services' => ['bra_cpf'],\n 'identification_number' => '12345678900',\n 'first_name' => 'John',\n 'last_name' => 'Doe',\n 'date_of_birth' => '1980-01-01',\n 'vendor_data' => 'user-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.database_validation.validate(\n issuing_state: \"BRA\",\n services: [\"bra_cpf\"],\n identification_number: \"12345678900\",\n first_name: \"John\",\n last_name: \"Doe\",\n date_of_birth: \"1980-01-01\",\n vendor_data: \"user-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.DatabaseValidation.ValidateAsync(new DatabaseValidationValidateParams\n{\n IssuingState = \"BRA\",\n Services = new string[] { \"bra_cpf\" },\n IdentificationNumber = \"12345678900\",\n FirstName = \"John\",\n LastName = \"Doe\",\n DateOfBirth = \"1980-01-01\",\n VendorData = \"user-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Validation completed — at least one selected service returned a usable result. Inspect `database_validation.match_type` for the aggregate outcome, `validations` for the per-service field comparisons and `outcome_code`s, and `errors` (when present) for services that failed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "database_validation": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "description": "`Declined` when the overall `match_type` triggers a `DECLINE` action (`no_match_action` defaults to `DECLINE`, `partial_match_action` to `NO_ACTION`); `In Review` when no service produced a usable comparison, for example when the only selected service came back `REGISTRY_UNAVAILABLE` or `INCONCLUSIVE`; `Approved` otherwise. A request that selected several services can be `Approved` on one service's match while another was unavailable, so check `errors` and the per-service `outcome_code`s too." + }, + "issuing_state": { + "type": "string", + "description": "Echo of the ISO 3166-1 alpha-3 country validated against.", + "example": "BRA" + }, + "validation_type": { + "type": "string", + "enum": [ + "one_by_one", + "two_by_two" + ], + "description": "Derived from the results: `two_by_two` when ≥2 distinct services produced a full match (on the identification number, on full name + date of birth together, or on the address without contradictions); `one_by_one` otherwise. (The request field of the same name is deprecated and ignored.)" + }, + "screened_data": { + "type": "object", + "description": "Echo of the input fields that were sent to the selected services (dates normalized to `YYYY-MM-DD`; a `selfie`, when sent, is returned as a pre-signed URL; `consent_obtained: true` is added when consent was given)." + }, + "match_type": { + "type": "string", + "nullable": true, + "enum": [ + "full_match", + "partial_match", + "no_match" + ], + "description": "Overall result aggregated across services with field-level comparisons: any `full_match` → `full_match`; else any `partial_match` → `partial_match`; else `no_match`. Outcome-only services, such as biometric criminal screening, can return `null`; inspect each item in `validations` for its `outcome_code`." + }, + "validations": { + "description": "Shape depends on `save_api_request`. **`true` (default):** an array of per-service objects (`service_id`, `service_name`, `validation`, `outcome_code`, `outcome_detail`, `source_data`). **`false`:** a single flat object merging the field-level results of all services (`{\"full_name\": \"full_match\", ...}`).", + "oneOf": [ + { + "type": "array", + "items": { + "type": "object", + "description": "Per-service validation result (returned when `save_api_request=true`, the default).", + "properties": { + "service_id": { + "type": "string", + "description": "Catalog id of the service that produced this result (e.g. `bra_cpf`).", + "example": "bra_cpf" + }, + "service_name": { + "type": "string", + "description": "Human-readable catalog name of the service.", + "example": "Brazil - CPF status check" + }, + "validation": { + "type": "object", + "description": "Field-by-field comparison between your input and the registry record. Keys are canonical field names (`full_name`, `date_of_birth`, `identification_number`, `address`, …); values are `full_match`, `partial_match`, or `no_match`.", + "additionalProperties": { + "type": "string", + "enum": [ + "full_match", + "partial_match", + "no_match" + ] + } + }, + "outcome_code": { + "type": "string", + "enum": [ + "MATCH", + "PARTIAL_MATCH", + "NO_MATCH", + "DOCUMENT_NOT_FOUND", + "INVALID_DOCUMENT_FORMAT", + "INVALID_INPUT", + "MINOR_BLOCKED", + "DECEASED", + "BIOMETRIC_NO_MATCH", + "BIOMETRIC_IMAGE_UNUSABLE", + "INCONCLUSIVE", + "DOCUMENT_SUPERSEDED", + "REGISTRY_UNAVAILABLE", + "REGISTRY_ERROR" + ], + "description": "Vendor-neutral outcome of the registry lookup. `MATCH`/`PARTIAL_MATCH`/`NO_MATCH` describe the comparison; `DOCUMENT_NOT_FOUND` means the identifier is not in the registry; `INCONCLUSIVE` means the registry could not confirm either way (routed to review — NOT a no-match); `BIOMETRIC_NO_MATCH` is a definitive face mismatch while `BIOMETRIC_IMAGE_UNUSABLE` is a technical image problem (retake the selfie); `DECEASED`/`MINOR_BLOCKED` are registry-policy outcomes; `DOCUMENT_SUPERSEDED` means the identity matched but the edition of the document presented is not the one on file (a newer edition has been issued); `INVALID_DOCUMENT_FORMAT`/`INVALID_INPUT` mean the registry itself rejected the data it was sent; `REGISTRY_UNAVAILABLE`/`REGISTRY_ERROR` indicate upstream failures." + }, + "outcome_detail": { + "type": "string", + "nullable": true, + "description": "Raw upstream status detail backing the outcome code (e.g. an HTTP or registry status code)." + }, + "source_data": { + "type": "object", + "nullable": true, + "description": "Cleaned, normalized view of the registry record using canonical field names (`identification_number`, `first_name`, `last_name`, `date_of_birth`, plus registry-specific flags). Image fields, when present, are returned as pre-signed URLs." + } + } + } + }, + { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "full_match", + "partial_match", + "no_match" + ] + }, + "description": "Merged field-level results (stateless `save_api_request=false` shape)." + } + ] + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature that raised the warning." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk identifier." + }, + "additional_data": { + "type": "object", + "nullable": true, + "description": "Extra context for the warning, when available." + }, + "log_type": { + "type": "string", + "description": "`warning`, `information`, or `error` — how the risk affected the outcome." + }, + "short_description": { + "type": "string", + "description": "One-line human-readable summary." + }, + "long_description": { + "type": "string", + "description": "Full human-readable explanation." + } + } + }, + "description": "`DATABASE_VALIDATION_NO_MATCH` or `DATABASE_VALIDATION_PARTIAL_MATCH` when applicable; empty on a full match." + }, + "errors": { + "type": "array", + "description": "Present only when some (but not all) selected services failed. Each entry carries `service_id`, `code`, `message`, and `retryable`. A service reported here is absent from `validations` and `services_used`, and is not billed. This array is the only place it appears, so inspect it even when `status` is `Approved`.", + "items": { + "type": "object", + "properties": { + "service_id": { + "type": "string" + }, + "code": { + "type": "string", + "example": "empty_provider_response" + }, + "message": { + "type": "string" + }, + "retryable": { + "type": "boolean", + "description": "`true` when the service produced no answer and retrying it later can succeed; `false` when the source refused the data it was sent and the input must be corrected first.", + "example": true + } + } + } + }, + "services_used": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Catalog ids of the services that returned a billable result. **Only present when the request explicitly sent `services`.**" + }, + "match_score": { + "type": "number", + "description": "Fraction (0.0–1.0) of attempted services that produced a full match (on the identification number, on full name + date of birth together, or on the address without contradictions). **Only present when the request explicitly sent `services`.**" + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Full match (Approved)": { + "summary": "Default save_api_request=true — per-service `validations` objects", + "value": { + "request_id": "7f9a1c0e-1f2b-4f6e-9d3a-2b1c0e7f9a1c", + "database_validation": { + "status": "Approved", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "tax_number": "12345678900", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-01-01" + }, + "match_type": "full_match", + "validations": [ + { + "validation": { + "full_name": "full_match", + "date_of_birth": "full_match", + "identification_number": "full_match" + }, + "outcome_code": "MATCH", + "outcome_detail": "200", + "service_id": "bra_cpf", + "service_name": "Brazil - CPF status check", + "source_data": { + "identification_number": "12345678900", + "first_name": "JOHN", + "last_name": "DOE", + "date_of_birth": "1980-01-01", + "lgpd_minor": false, + "minor_under_16": false, + "minor_under_18": false + } + } + ], + "warnings": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-11T10:30:00.000000+00:00" + } + }, + "Explicit services (extended shape)": { + "summary": "Request sent `services` — response adds `services_used` and `match_score`", + "value": { + "request_id": "b3e7c1a2-9d4f-4f0b-8a6c-5e2d1f0a9b3e", + "database_validation": { + "status": "Approved", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "tax_number": "12345678900", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-01-01" + }, + "match_type": "full_match", + "validations": [ + { + "validation": { + "full_name": "full_match", + "date_of_birth": "full_match", + "identification_number": "full_match" + }, + "outcome_code": "MATCH", + "outcome_detail": "200", + "service_id": "bra_cpf", + "service_name": "Brazil - CPF status check", + "source_data": { + "identification_number": "12345678900", + "first_name": "JOHN", + "last_name": "DOE", + "date_of_birth": "1980-01-01", + "lgpd_minor": false, + "minor_under_16": false, + "minor_under_18": false + } + } + ], + "warnings": [], + "services_used": [ + "bra_cpf" + ], + "match_score": 1.0 + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-11T10:30:00.000000+00:00" + } + }, + "No match (Declined)": { + "summary": "Registry record does not match — default `no_match_action=DECLINE`", + "value": { + "request_id": "0a1b2c3d-4e5f-6789-abcd-ef0123456789", + "database_validation": { + "status": "Declined", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "tax_number": "12345678900", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-01-01" + }, + "match_type": "no_match", + "validations": [ + { + "validation": { + "full_name": "no_match", + "date_of_birth": "no_match", + "identification_number": "no_match" + }, + "outcome_code": "NO_MATCH", + "outcome_detail": "200", + "service_id": "bra_cpf", + "service_name": "Brazil - CPF status check", + "source_data": { + "identification_number": "00987654321", + "first_name": "TOTALLY", + "last_name": "DIFFERENT NAME", + "date_of_birth": "1999-01-01", + "lgpd_minor": false, + "minor_under_16": false, + "minor_under_18": false + } + } + ], + "warnings": [ + { + "feature": "DATABASE_VALIDATION", + "risk": "DATABASE_VALIDATION_NO_MATCH", + "additional_data": null, + "log_type": "error", + "short_description": "Database validation no match", + "long_description": "The system identified a no match in the database validation, requiring further investigation." + } + ] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-11T10:31:00.000000+00:00" + } + }, + "Stateless (save_api_request=false)": { + "summary": "`validations` collapses to a merged field map; nothing is stored", + "value": { + "request_id": "c4d5e6f7-8901-2345-6789-abcdef012345", + "database_validation": { + "status": "Approved", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "tax_number": "12345678900", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-01-01" + }, + "match_type": "full_match", + "validations": { + "full_name": "full_match", + "date_of_birth": "full_match", + "identification_number": "full_match" + }, + "warnings": [] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-11T10:32:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error - nothing is billed. Preflight field errors occur before any external call. If every unanswered attempt explicitly refuses the input, the response instead includes `validation_errors` with `code: provider_rejected_input` and `retryable: false`; correct the input before retrying. Field-level problems use DRF's standard envelope; catalog problems return a `services` array (plus `requires_onboarding` or `services_requiring_consent` when relevant); missing-field errors include `fields_required_by` mapping each missing field to the services that demand it; services that need at least one extra identifier (e.g. `usa_states_credit_bureau`, which cannot verify a name alone) return `any_field_required_by` mapping each service to the fields of which at least one must be present; format problems checked per service return `invalid_fields_by_service`.", + "content": { + "application/json": { + "examples": { + "Invalid issuing_state": { + "summary": "Country not supported — message lists every valid alpha-3 code", + "value": { + "error": [ + "Invalid issuing state. Valid options are: ARE, ARG, AUS, AUT, BEL, BGD, BOL, BRA, CAN, CHE, CHL, CHN, CIV, COL, CRI, CZE, DEU, DNK, DOM, ECU, ESP, FIN, FRA, GBR, GHA, GLB, GRC, GTM, HKG, HND, IDN, IND, IRL, ITA, KEN, KHM, MAR, MEX, MYS, NGA, NLD, NOR, NZL, OMN, PAN, PER, PHL, POL, PRT, PRY, QAT, SGP, SLV, SVK, SWE, THA, UGA, URY, USA, VEN, ZAF, ZMB, ZWE" + ] + } + }, + "Missing required fields": { + "summary": "Union of required fields across selected services, with the universal-field hint", + "value": { + "error": [ + "Missing required fields: tax_number (or use 'identification_number' which maps to 'tax_number' for BRA)" + ], + "fields_required_by": { + "tax_number": [ + "bra_cpf" + ] + } + } + }, + "Unknown service id": { + "summary": "`services` contains an id that is not in the catalog", + "value": { + "services": [ + "Unknown service_id(s): ['bogus_service']" + ] + } + }, + "Service not live for country": { + "summary": "`services` names a service from another country (or a stub entry)", + "value": { + "services": [ + "Services not currently live for PER: ['bra_cpf']. Available services: ['per_dni', 'per_residential', 'per_tax_registration']" + ] + } + }, + "Service requires onboarding": { + "summary": "Service exists but is not activated for your organization yet", + "value": { + "services": [ + "Services require onboarding before they can be enabled for AUS: ['aus_australia_driver_licence']. Contact Didit support to activate them." + ], + "requires_onboarding": [ + "aus_australia_driver_licence" + ] + } + }, + "Consent missing": { + "summary": "A selected service is flagged requires_consent=true and `consent` was not `true`", + "value": { + "consent": [ + "Explicit end-user consent is required for these database validation services: ['chn_national_id']. Send `consent=true`." + ], + "services_requiring_consent": [ + "chn_national_id" + ] + } + }, + "Country format rule violation": { + "summary": "Identifier fails the country's format rule (length/digits/regex)", + "value": { + "tax_number": [ + "Invalid tax number for BRA: Brazilian CPF (exactly 11 digits). Must be exactly 11 characters long (got 10)." + ] + } + }, + "Service-specific format violation": { + "summary": "A field fails the selected service's own format requirements", + "value": { + "error": [ + "One or more fields do not match the format required by the selected service." + ], + "invalid_fields_by_service": { + "chn_national_id": [ + "national_id_format" + ] + } + } + }, + "Input refused by every attempt": { + "summary": "Correct the input before retrying", + "value": { + "error": "The source refused to run this query with the supplied input.", + "validation_errors": [ + { + "service_id": "bra_cpf", + "code": "provider_rejected_input", + "message": "The source refused to run this query with the supplied input.", + "retryable": false + } + ] + } + } + }, + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "error": { + "description": "Request-level explanation or preflight field errors.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "validation_errors": { + "type": "array", + "description": "Present when every unanswered attempt refused the input after preflight.", + "items": { + "type": "object", + "properties": { + "service_id": { + "type": "string" + }, + "code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retryable": { + "type": "boolean", + "description": "Whether retrying later can resolve this service failure. False means correct the supplied input first." + } + } + } + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "502": { + "description": "No selected service returned a usable result. Inspect each validation_errors entry before retrying; HTTP 502 alone does not establish a transient failure. An empty_provider_response with retryable: true indicates unavailable or unexplained results, including a refused primary followed by an unavailable fallback. Legacy provider_invalid_input and provider_validation_error entries instead require corrected input and may omit retryable. Nothing is billed. When save_api_request=true, the failed attempt is recorded as a session with status Not Finished. Partial failures return 200 with database_validation.errors.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "validation_errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "service_id": { + "type": "string" + }, + "code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retryable": { + "type": "boolean", + "description": "Whether retrying later can resolve this service failure. False means correct the supplied input first." + } + } + } + } + } + }, + "examples": { + "Provider returned no result": { + "summary": "Every selected service failed", + "value": { + "error": "Database validation could not be performed. The external validation service did not return a result. Please try again later.", + "validation_errors": [ + { + "service_id": "bra_cpf", + "code": "empty_provider_response", + "message": "The provider did not return a database validation result.", + "retryable": true + } + ] + } + } + } + } + } + } + } + } + }, + "/v3/phone/send/": { + "post": { + "summary": "Send Phone Code", + "description": "Send a one-time passcode (OTP) to a phone number over WhatsApp, SMS, or another supported channel, then verify it with [`POST /v3/phone/check/`](/standalone-apis/phone-check).\n\n**How send and check pair up.** Verification state is keyed by your application plus the E.164 `phone_number` — the check call does not take `request_id`. Each new send creates a pending verification that lives for **5 minutes**: call the check with the same number and the code the user received before the window closes. Calling send again for the same number while a verification is pending re-sends a code for that same verification (`status: \"Retry\"`, same `request_id`). At most one retry is attached this way (two sends total); a further send starts a fresh verification with a new `request_id`. The 5-minute window is measured from the first send and is not extended by retries.\n\n**Delivery channels.** `options.preferred_channel` selects the channel (`whatsapp` by default; `sms`, `telegram`, `voice`, `rcs`, `viber`, and `zalo` are also supported). When the preferred channel is unavailable for the destination country or number, delivery automatically falls back to SMS — the send still reports `Success`, and the channel actually used is reported by the check response (`phone.verification_method` and the `lifecycle` events).\n\n**Branding.** SMS codes carry your application's SMS sender name, set in the Business Console, at no extra cost. Pass `options.sms_sender_name` to sign one code with another name, or `options.use_white_label_customization: true` to fall back to your white-label public name when no sender name is set. WhatsApp codes use a fixed message; connect your own WhatsApp Business number in the Business Console to send them from your business name and number, billed at the Didit fee only.\n\n**Send statuses.** `Success` — OTP sent for a new verification; `Retry` — OTP re-sent for the pending verification; `Blocked` — the anti-fraud layer refused to send (see `reason`, e.g. `repeated_attempts`, `suspicious`, `spam`). A `Blocked` send immediately finalizes the verification as `Declined` with a high-risk warning, so a follow-up check returns `Expired or Not Found`.\n\n**Billing.** The price depends on the destination country and the channel that actually delivers the message — see [Phone Verification Pricing](/getting-started/phone-verification-pricing). Only `Blocked` sends are guaranteed free: any other unbilled send attempt is charged at verification finalization or expiration, even if delivery was never confirmed or the delivery provider reported the message undeliverable. Checks are always free. As an anti-abuse measure, phone verification is disabled until your organization's first top-up (`403`).\n\n**Session persistence.** Every new verification is persisted as an API-type session: `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/`, the verification appears in the Business Console, and `status.updated` webhooks fire as it progresses.\n\n**Sandbox.** Sandbox API keys skip delivery and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Success` payload with a random `request_id`; no message is sent and nothing is persisted. Use code `123456` on the sandbox check.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limits.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints, plus an anti-abuse cap of 4 send attempts per phone number per hour; exceeding either returns `429`.", + "operationId": "post_v3phone_send", + "x-didit-sdk": { + "resource": "phone", + "method": "send_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/phone/send/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"phone_number\": \"+14155552671\",\n \"options\": {\n \"code_size\": 6,\n \"preferred_channel\": \"whatsapp\",\n \"locale\": \"en-US\"\n },\n \"vendor_data\": \"user-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.phone.send_code(\n phone_number=\"+14155552671\",\n options={\n \"locale\": \"en-US\",\n \"preferred_channel\": \"whatsapp\",\n },\n vendor_data=\"user-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.phone.sendCode({\n phone_number: \"+14155552671\",\n options: {\n locale: \"en-US\",\n preferred_channel: \"whatsapp\",\n },\n vendor_data: \"user-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Phone.SendCode(ctx, didit.PhoneSendCodeParams{\n\tPhoneNumber: \"+14155552671\",\n\tOptions: didit.Some(didit.PhoneSendCodeParamsOptions{\n\t\tLocale: didit.String(\"en-US\"),\n\t\tPreferredChannel: didit.Some(didit.PhoneSendCodeParamsOptionsPreferredChannel(\"whatsapp\")),\n\t}),\n\tVendorData: didit.String(\"user-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.PhoneSendCodeParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.phone().sendCode(PhoneSendCodeParams.builder()\n .phoneNumber(\"+14155552671\")\n .options(PhoneSendCodeParams.Options.builder()\n .locale(\"en-US\")\n .preferredChannel(PhoneSendCodeParams.Options.PreferredChannel.of(\"whatsapp\"))\n .build())\n .vendorData(\"user-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "phone->sendCode([\n 'phone_number' => '+14155552671',\n 'options' => [\n 'locale' => 'en-US',\n 'preferred_channel' => 'whatsapp',\n ],\n 'vendor_data' => 'user-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.phone.send_code(\n phone_number: \"+14155552671\",\n options: {\n locale: \"en-US\",\n preferred_channel: \"whatsapp\",\n },\n vendor_data: \"user-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Phone.SendCodeAsync(new PhoneSendCodeParams\n{\n PhoneNumber = \"+14155552671\",\n Options = new PhoneSendCodeParamsOptions\n {\n Locale = \"en-US\",\n PreferredChannel = new PhoneSendCodeParamsOptionsPreferredChannel(\"whatsapp\"),\n },\n VendorData = \"user-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "phone_number": { + "type": "string", + "maxLength": 20, + "description": "Recipient phone number in E.164 format — leading `+` and country code (e.g., `+14155552671`). Numbers are validated and normalized to E.164; unparseable or invalid numbers return `400` with a `phone_number` field error.", + "example": "+14155552671" + }, + "options": { + "type": "object", + "description": "OTP delivery options. All fields are optional.", + "properties": { + "code_size": { + "type": "integer", + "minimum": 4, + "maximum": 8, + "default": 6, + "description": "Number of digits in the OTP." + }, + "locale": { + "type": "string", + "maxLength": 5, + "pattern": "^[a-z]{2,3}(-[A-Z]{2,3})?$", + "description": "BCP-47 locale used to localize the verification message (e.g., `en-US`, `fr`, `es`). Invalid formats return `400`.", + "example": "en-US" + }, + "preferred_channel": { + "type": "string", + "enum": [ + "whatsapp", + "sms", + "telegram", + "voice", + "rcs", + "viber", + "zalo" + ], + "default": "whatsapp", + "description": "Preferred delivery channel. If the channel is unavailable for the destination country or number, the message automatically falls back to SMS — the send still reports `Success`." + }, + "use_white_label_customization": { + "type": "boolean", + "default": false, + "description": "When `true`, the send is white-labelled: if your application has no SMS sender name, the SMS names your white-label public name instead (\"123456 is your verification code for Acme Bank\"). Your SMS sender name, when set, is used with or without this flag. Applies to SMS messages, including an SMS fallback from another channel; WhatsApp codes are sent with a fixed message and are not branded. Defaults to `false`." + }, + "sms_sender_name": { + "type": "string", + "nullable": true, + "maxLength": 30, + "example": "Acme Bank", + "description": "The name this code's SMS carries (\"123456 is your verification code for Acme Bank\"), instead of your application's SMS sender name. Free. 1 to 30 characters: letters, digits, spaces and ' & - ! only, not only digits, with single spaces between words; any other value returns `400` and no code is sent. Omitted, `null` or blank, your application's SMS sender name is used. Each application can start using up to two new names per day (UTC); past that, the code is sent with your application's name. Applies to SMS messages, including an SMS fallback from another channel; WhatsApp codes are not branded.", + "pattern": "^$|^(?![\\d ]+$)[\\p{L}\\p{N}'&!-]+( [\\p{L}\\p{N}'&!-]+)*$" + } + } + }, + "signals": { + "type": "object", + "description": "Optional device and network signals about the end user, forwarded to the anti-fraud layer to improve detection of abusive or automated traffic. All fields are optional.", + "properties": { + "ip": { + "type": "string", + "description": "IP address of the end user's device. IPv4 or IPv6.", + "example": "192.0.2.1" + }, + "device_id": { + "type": "string", + "maxLength": 255, + "description": "Unique identifier of the end user's device. Android: `ANDROID_ID`; iOS: `identifierForVendor`.", + "example": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2" + }, + "device_platform": { + "type": "string", + "enum": [ + "android", + "ios", + "ipados", + "tvos", + "web" + ], + "description": "Platform of the end user's device." + }, + "device_model": { + "type": "string", + "maxLength": 255, + "description": "Model of the end user's device.", + "example": "iPhone17,2" + }, + "os_version": { + "type": "string", + "maxLength": 64, + "description": "Operating-system version of the end user's device.", + "example": "18.0.1" + }, + "app_version": { + "type": "string", + "maxLength": 64, + "description": "Version of your application.", + "example": "1.2.34" + }, + "user_agent": { + "type": "string", + "maxLength": 512, + "description": "User agent of the end user's browser or app.", + "example": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1" + } + } + }, + "vendor_data": { + "type": "string", + "description": "Optional caller-controlled identifier (your internal user id, an email, a UUID, etc.) persisted on the session and echoed back in the send response, the matching check response, webhooks, and the Business Console. Use it to correlate Didit's `request_id` with your user record." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Optional free-form JSON object persisted on the session and echoed back in the send response, the matching check response, webhooks, and the Business Console." + } + }, + "required": [ + "phone_number" + ] + }, + "examples": { + "WhatsApp (default)": { + "summary": "6-digit OTP via WhatsApp, falling back to SMS automatically", + "value": { + "phone_number": "+14155552671", + "options": { + "preferred_channel": "whatsapp", + "locale": "en-US" + }, + "vendor_data": "user-1234" + } + }, + "SMS with custom code length": { + "summary": "Force SMS delivery with a 4-digit code", + "value": { + "phone_number": "+34699999999", + "options": { + "preferred_channel": "sms", + "code_size": 4, + "locale": "es" + } + } + }, + "With fraud signals": { + "summary": "Forward device signals to strengthen anti-fraud screening", + "value": { + "phone_number": "+14155552671", + "signals": { + "ip": "192.0.2.1", + "device_id": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2", + "device_platform": "ios", + "device_model": "iPhone17,2", + "os_version": "18.0.1", + "app_version": "1.2.34" + }, + "vendor_data": "user-1234" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Send acknowledged. Inspect `status`: `Success` and `Retry` mean a code is on its way; `Blocked` means the anti-fraud layer refused and the verification is already finalized as `Declined`. `request_id` is the persisted session id (same id on a `Retry`).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PhoneVerificationSendResponse" + }, + "examples": { + "Success": { + "summary": "OTP sent for a new verification", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Success", + "reason": null, + "vendor_data": "user-1234", + "metadata": null + } + }, + "Retry": { + "summary": "Second send for the same number inside the 5-minute window — same request_id", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Retry", + "reason": null, + "vendor_data": "user-1234", + "metadata": null + } + }, + "Blocked": { + "summary": "Anti-fraud refusal — verification finalized as Declined, nothing billed", + "value": { + "request_id": "0d8af1f4-0c43-46d7-b75c-6e4e22c0a7b3", + "status": "Blocked", + "reason": "repeated_attempts", + "vendor_data": "user-1234", + "metadata": null + } + } + } + } + } + }, + "400": { + "description": "Validation error. Malformed input returns DRF's field-error envelope (one array of messages per offending field, nested under `options` for option errors). A number that parses but is rejected at send time (unreachable, or an ineligible line type such as a short code) returns a `{\"detail\": ...}` envelope instead.", + "content": { + "application/json": { + "examples": { + "Invalid phone number (field error)": { + "summary": "Number is not valid E.164", + "value": { + "phone_number": [ + "Invalid phone number provided." + ] + } + }, + "Missing phone number": { + "summary": "`phone_number` not included in the body", + "value": { + "phone_number": [ + "This field is required." + ] + } + }, + "Invalid options": { + "summary": "Out-of-range `code_size`, malformed `locale`, unknown `preferred_channel`", + "value": { + "options": { + "code_size": [ + "Ensure this value is less than or equal to 8." + ], + "locale": [ + "Ensure this field has no more than 5 characters." + ], + "preferred_channel": [ + "\"carrier_pigeon\" is not a valid choice." + ] + } + } + }, + "Number rejected at send time": { + "summary": "Valid E.164 shape but the number cannot receive an OTP", + "value": { + "detail": "Invalid phone number provided." + } + }, + "Ineligible line type": { + "summary": "Line type cannot receive an OTP (e.g., short code)", + "value": { + "detail": "Invalid phone line type provided." + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing (`{\"detail\": ...}`); a wrong, revoked or expired key returns `401`. Also returned before any send when the organization has never topped up (anti-abuse gate) or when its balance cannot cover the destination's send price (`{\"error\": ...}`).", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Top-up required": { + "summary": "Phone verification stays disabled until the organization's first top-up", + "value": { + "detail": "To protect against abuse, phone verification is disabled until your first top-up." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the send price", + "value": { + "error": "You don't have enough credits to perform this request." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). Additionally, an anti-abuse cap of 4 verification attempts per phone number per hour applies upstream. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers. Note: the `X-RateLimit-*`/`Retry-After` headers accompany only the 300/min write-limit 429; the per-number cap 429 carries no rate-limit headers.", + "content": { + "application/json": { + "examples": { + "Per-number cap": { + "summary": "More than 4 verification attempts for the same number in the last hour", + "value": { + "detail": "Maximum verification attempts reached for this phone number. Only 4 authentication attempts are allowed per hour. Try again later or use a different number." + } + }, + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "502": { + "description": "The phone verification provider is temporarily unavailable - a 5xx response, timeout, or connection failure that survived the built-in retries. Safe to retry; nothing is billed and no attempt is consumed.", + "content": { + "application/json": { + "examples": { + "Provider outage": { + "summary": "The provider did not answer the send request", + "value": { + "detail": "Phone verification is temporarily unavailable. Please try again shortly.", + "code": "phone_provider_unavailable" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "Unexpected failure while creating the verification that is not a known provider outage. Safe to retry.", + "content": { + "application/json": { + "examples": { + "Provider error": { + "summary": "OTP delivery could not be initiated", + "value": { + "detail": "Error creating phone verification" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/phone/check/": { + "post": { + "summary": "Check Phone Code", + "description": "Verify the OTP delivered by [`POST /v3/phone/send/`](/standalone-apis/phone-send) and get the final verification result plus phone intelligence: carrier name and line type, virtual (VoIP) and disposable flags, and duplicate usage of the number across your sessions.\n\n**How the check finds the verification.** Matching is by your application plus the E.164 `phone_number` — `request_id` is not an input. The most recent pending verification created within the last **5 minutes** is checked. If there is none (never sent, already finalized, or older than 5 minutes) the endpoint returns `200` with `status: \"Expired or Not Found\"`.\n\n**Attempt budget.** Each verification allows **3 code attempts**. The first two wrong codes return `status: \"Failed\"` with the attempts remaining and `phone: null`; the third wrong code finalizes the verification as `Declined` with a `VERIFICATION_CODE_ATTEMPTS_EXCEEDED` warning and returns the full phone report.\n\n**Outcomes.** `Approved` — correct code and no declining risk. `Declined` — terminal: the code was correct but a declining risk matched (a `DECLINE` action below, a blocklisted or high-risk number), or the attempt budget was exhausted. `Failed` — wrong code, attempts remaining. `Expired or Not Found` — nothing to check. On `Approved`/`Declined` the `request_id` equals the send's `request_id` (the session id) and `phone` carries the full report (`carrier`, `is_virtual`, `is_disposable`, `warnings`, `lifecycle`, `matches`); on `Failed` and `Expired or Not Found` the `request_id` is a one-off random UUID.\n\n**Risk actions.** `duplicated_phone_number_action`, `disposable_number_action`, and `voip_number_action` decide what happens when the corresponding risk is detected on a correct code: `DECLINE` flips the final status to `Declined`; `NO_ACTION` (default) records the risk in `phone.warnings` without affecting the status.\n\n**Billing.** Checks are free — delivery is billed on the send side (see [Phone Verification Pricing](/getting-started/phone-verification-pricing)).\n\n**Session persistence.** A finalized check updates the session created by the send (visible in the Business Console, queryable via `GET /v3/session/{sessionId}/decision/`) and fires a `status.updated` webhook.\n\n**Sandbox.** Sandbox API keys skip all processing: any well-formed code (4–8 digits, `123456` included) returns a static `Approved` payload with a simplified flat `phone` object (`status`, `phone_number`, `country_code`, `carrier_name`, `line_type`, `flags`); malformed input still returns `400`. Nothing is persisted.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limits.** Shared write budget of 300 requests/min per API key, plus the upstream anti-abuse cap of 4 attempts per number per hour; exceeding either returns `429`.\n\n**Social footprint.** Set `enable_social` to `true` to add a `phone_social` block: the online platforms this number is registered on, with per-category counts and any profile details available. `profiles_registered: 0` means the platforms were checked and the number was found on none of them - a valid, billable answer. The block is absent when the add-on was not requested or the check could not be completed, and it is not billed in that case. Billing adds one `phone_social` unit per request on top of this endpoint's own price.", + "operationId": "post_v3phone_check", + "x-didit-sdk": { + "resource": "phone", + "method": "check_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/phone/check/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"phone_number\": \"+14155552671\",\n \"code\": \"123456\",\n \"voip_number_action\": \"DECLINE\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.phone.check_code(\n phone_number=\"+14155552671\",\n code=\"123456\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.phone.checkCode({\n phone_number: \"+14155552671\",\n code: \"123456\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Phone.CheckCode(ctx, didit.PhoneCheckCodeParams{\n\tPhoneNumber: \"+14155552671\",\n\tCode: \"123456\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.PhoneCheckCodeParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.phone().checkCode(PhoneCheckCodeParams.builder()\n .phoneNumber(\"+14155552671\")\n .code(\"123456\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "phone->checkCode([\n 'phone_number' => '+14155552671',\n 'code' => '123456',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.phone.check_code(\n phone_number: \"+14155552671\",\n code: \"123456\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Phone.CheckCodeAsync(new PhoneCheckCodeParams\n{\n PhoneNumber = \"+14155552671\",\n Code = \"123456\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "phone_number": { + "type": "string", + "maxLength": 20, + "description": "The same phone number used in the matching [`POST /v3/phone/send/`](/standalone-apis/phone-send) call, in E.164 format. This is what links the check to the send.", + "example": "+14155552671" + }, + "code": { + "type": "string", + "minLength": 4, + "maxLength": 8, + "pattern": "^[0-9]{4,8}$", + "description": "The OTP the end user received. Must be 4–8 numeric digits — anything else returns `400` without consuming an attempt. A well-formed but wrong code returns `200` with `status: \"Failed\"` and consumes one of the 3 attempts.", + "example": "123456" + }, + "duplicated_phone_number_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the same number was already used by a different user (different `vendor_data`) of your application. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `phone.warnings` and fills `phone.matches`." + }, + "disposable_number_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the carrier lookup flags the number as a temporary/burner line (`phone.is_disposable`). `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `phone.warnings`." + }, + "voip_number_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the carrier lookup reports a virtual line type — `voip`, `isp`, or `vpn` (`phone.is_virtual`). `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `phone.warnings`." + }, + "enable_enrichment": { + "type": "boolean", + "default": false, + "description": "When `true`, also run phone intelligence on the number: a risk score, the line type, whether it was recently ported and a trust index. Billed as one `phone_intelligence_api` unit per request ($0.05 at list price) in addition to this endpoint's own price. The request returns `403` when your balance cannot cover it.", + "example": false + }, + "phone_intelligence_score_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the phone intelligence risk score is above the threshold. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "phone_type_risk_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the number is a high-risk line type. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "recent_port_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the number was ported to another carrier recently. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "low_phone_trust_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the phone intelligence trust index is at or below the threshold. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "enable_social": { + "type": "boolean", + "default": false, + "description": "When true, also look up the social footprint of the number - the messaging, social, e-commerce and professional platforms it is registered on. Billed as one `phone_social` unit per request in addition to this endpoint's own price.", + "example": true + }, + "phone_no_social_presence_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the social footprint check finds the number registered on none of the platforms it covers. Only NO_ACTION and DECLINE are accepted on this endpoint; REVIEW is available on the workflow configuration key of the same name." + } + }, + "required": [ + "phone_number", + "code" + ] + }, + "examples": { + "Default": { + "summary": "Record any risks but never auto-decline", + "value": { + "phone_number": "+14155552671", + "code": "123456" + } + }, + "Strict risk policy": { + "summary": "Auto-decline VoIP and disposable lines", + "value": { + "phone_number": "+14155552671", + "code": "123456", + "voip_number_action": "DECLINE", + "disposable_number_action": "DECLINE" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Check completed — wrong codes and missing verifications also return `200`; inspect `status`, not the HTTP code. `phone` is populated only on finalized outcomes (`Approved`/`Declined`), `null` on `Failed`, and absent on `Expired or Not Found`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PhoneVerificationCheckResponse" + }, + "examples": { + "Approved": { + "summary": "Correct code — full phone report with carrier, lifecycle, and matches", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Approved", + "message": "The verification code is correct.", + "phone": { + "status": "Approved", + "phone_number_prefix": "+1", + "phone_number": "4155552671", + "full_number": "+14155552671", + "country_code": "US", + "country_name": "United States", + "carrier": { + "name": "AT&T", + "type": "mobile" + }, + "is_disposable": false, + "is_virtual": false, + "verification_method": "whatsapp", + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [], + "lifecycle": [ + { + "type": "PHONE_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null, + "channel": "whatsapp", + "actual_channel": "whatsapp" + }, + "fee": 0.0509 + }, + { + "type": "PHONE_DELIVERY_DELIVERED", + "timestamp": "2026-06-12T01:23:43.118220+00:00", + "details": { + "channel": "whatsapp", + "status": "delivered" + }, + "fee": 0 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "PHONE_VERIFICATION_APPROVED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": null, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Failed (attempts remaining)": { + "summary": "Wrong code — the user can retry; request_id here is a one-off UUID", + "value": { + "request_id": "11111111-2222-3333-4444-555555555555", + "status": "Failed", + "message": "The verification code is incorrect. Attempts remaining: 2", + "phone": null, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:21.512340+00:00" + } + }, + "Declined (max attempts)": { + "summary": "Third wrong code — verification finalized as Declined", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Declined", + "message": "The verification code is incorrect. Maximum attempts reached.", + "phone": { + "status": "Declined", + "phone_number_prefix": "+1", + "phone_number": "4155552671", + "full_number": "+14155552671", + "country_code": "US", + "country_name": "United States", + "carrier": { + "name": "AT&T", + "type": "mobile" + }, + "is_disposable": false, + "is_virtual": false, + "verification_method": "whatsapp", + "verification_attempts": 1, + "verified_at": null, + "warnings": [ + { + "feature": "PHONE", + "risk": "VERIFICATION_CODE_ATTEMPTS_EXCEEDED", + "additional_data": null, + "log_type": "error", + "short_description": "Verification code attempts exceeded", + "long_description": "The system detected that the phone number has exceeded the maximum number of verification code attempts." + } + ], + "lifecycle": [ + { + "type": "PHONE_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null, + "channel": "whatsapp", + "actual_channel": "whatsapp" + }, + "fee": 0.0509 + }, + { + "type": "INVALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:01.118220+00:00", + "details": { + "code_tried": "111111", + "status": "Failed" + }, + "fee": 0 + }, + { + "type": "INVALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:21.512340+00:00", + "details": { + "code_tried": "222222", + "status": "Failed" + }, + "fee": 0 + }, + { + "type": "INVALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "333333", + "status": "Failed" + }, + "fee": 0 + }, + { + "type": "PHONE_VERIFICATION_DECLINED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": { + "reason": "VERIFICATION_CODE_ATTEMPTS_EXCEEDED" + }, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Declined (risk policy)": { + "summary": "Correct code, but the line is VoIP and voip_number_action=DECLINE", + "value": { + "request_id": "7f1c9d2e-44a1-4b6e-9a3e-5b8e6f1c2d3a", + "status": "Declined", + "message": "The verification code is correct.", + "phone": { + "status": "Declined", + "phone_number_prefix": "+1", + "phone_number": "5005550006", + "full_number": "+15005550006", + "country_code": "US", + "country_name": "United States", + "carrier": { + "name": "CLEC LLC", + "type": "voip" + }, + "is_disposable": false, + "is_virtual": true, + "verification_method": "sms", + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [ + { + "feature": "PHONE", + "risk": "VOIP_NUMBER_DETECTED", + "additional_data": null, + "log_type": "error", + "short_description": "VoIP number detected", + "long_description": "The system detected that the phone number is a VoIP number, which is not allowed." + } + ], + "lifecycle": [ + { + "type": "PHONE_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null, + "channel": "whatsapp", + "actual_channel": "sms" + }, + "fee": 0.0509 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "PHONE_VERIFICATION_DECLINED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": { + "reason": "VOIP_NUMBER_DETECTED" + }, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Expired or Not Found": { + "summary": "No pending verification for this number in the last 5 minutes", + "value": { + "request_id": "69cc35c6-280a-47a6-ab8c-4f32abc02ed0", + "status": "Expired or Not Found", + "message": "No pending phone verification found in the last 5 minutes.", + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-12T01:24:47.311323+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error — DRF's field-error envelope: one array of messages per offending field.", + "content": { + "application/json": { + "examples": { + "Invalid code format": { + "summary": "`code` is not 4–8 numeric digits", + "value": { + "code": [ + "Invalid code format. Enter 4 to 8 numeric digits." + ] + } + }, + "Invalid phone number": { + "summary": "`phone_number` is not valid E.164", + "value": { + "phone_number": [ + "Invalid phone number provided." + ] + } + }, + "Invalid action": { + "summary": "Risk actions accept only NO_ACTION or DECLINE", + "value": { + "voip_number_action": [ + "\"REVIEW\" is not a valid choice." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing; a wrong, revoked or expired key returns `401`. Checks are free, so there is no credit check here.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). Additionally, an anti-abuse cap of 4 verification attempts per phone number per hour applies upstream. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers. Note: the `X-RateLimit-*`/`Retry-After` headers accompany only the 300/min write-limit 429; the per-number cap 429 carries no rate-limit headers.", + "content": { + "application/json": { + "examples": { + "Per-number cap": { + "summary": "More than 4 verification attempts for the same number in the last hour", + "value": { + "detail": "Maximum verification attempts reached for this phone number. Only 4 authentication attempts are allowed per hour. Try again later or use a different number." + } + }, + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "502": { + "description": "The phone verification provider is temporarily unavailable - a 5xx response, timeout, or connection failure that survived the built-in retries. Safe to retry; the attempt budget is not consumed.", + "content": { + "application/json": { + "examples": { + "Provider outage": { + "summary": "The provider did not answer the check request", + "value": { + "detail": "Phone verification is temporarily unavailable. Please try again shortly.", + "code": "phone_provider_unavailable" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "Unexpected failure while verifying the code upstream that is not a known provider outage. Safe to retry; the attempt budget is only consumed by completed checks.", + "content": { + "application/json": { + "examples": { + "Provider error": { + "summary": "Code verification could not be completed", + "value": { + "detail": "Error checking phone verification" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/bank/account-check/": { + "post": { + "summary": "Check a bank account", + "description": "**Not available in production yet.** Bank Verification is switched off in production, so until it is switched on this endpoint answers `404` with `bank_verification_not_available`. Do not build on it yet. The rest of this description is what the endpoint does once it is available.\n\nCheck that a bank account belongs to the person or company you name, before you send money to it. No session and no sign-in: send the name and the account, read the result. It is the [account check](/core-technology/bank-verification/account-check) of the Bank Verification step, run from your backend.\n\n**What you send.** The account's `country` (ISO 3166-1 alpha-3), the `account` the way that country writes it, and the `holder` name: `full_name` for a person or `business_name` for a company. A business name checks a business account.\n\n**What you get back.** `result` is `match`, `close_match`, `no_match` or `cannot_verify`. On a `close_match`, `holder_name` is the name the bank holds the account under, when the bank returned it. `account` comes back masked to its last 4 characters.\n\n**Billing.** Each check the bank answers is billed once at the Account check price of the account's country, whatever the result: a `no_match`, an account that does not exist and a `cannot_verify` are billed too. A request answered with an error is never billed.\n\n**Every request is a new check.** There is no idempotency key and no endpoint to read a check back, so a retried request runs, and is billed, again. Store the response.\n\n**Sandbox.** Where Bank Verification is available, a sandbox key calls no bank and is charged nothing. The last 4 characters of the IBAN, else of the account number, choose the answer: `0001` a `close_match` with a `holder_name`, `0002` a `close_match` without one, `0003` and `0006` a `no_match`, `0004` `cannot_verify`, `0005` a `503` and `0009` a `400`. Any other ending is a `match`.\n\n**API keys.** A scoped API key needs write access to sessions.", + "operationId": "post_v3_bank_account_check", + "tags": [ + "Standalone APIs" + ], + "x-didit-sdk": { + "resource": "bank", + "method": "account_check", + "auth": "api_key", + "server": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckRequest" + }, + "examples": { + "Person, United Kingdom": { + "summary": "A sort code and account number, checked against a person's name", + "value": { + "country": "GBR", + "account": { + "sort_code": "20-45-67", + "account_number": "31926819" + }, + "holder": { + "full_name": "Maria Oliveira" + }, + "vendor_data": "payout_20481" + } + }, + "Company, IBAN": { + "summary": "An IBAN, checked against a company's name", + "value": { + "country": "DEU", + "account": { + "iban": "DE89 3704 0044 0532 0130 00" + }, + "holder": { + "business_name": "Northwind Trading GmbH" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The bank answered. The check is billed, whatever the result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckResponse" + }, + "examples": { + "Close match": { + "summary": "The bank holds the account under a close name", + "value": { + "request_id": "c2f4a9e0-7b1d-4c3a-9e55-1d0a6b8f2a47", + "result": "close_match", + "holder_name": "MARIA R OLIVEIRA", + "account": "****6819", + "vendor_data": "payout_20481", + "created_at": "2026-10-21T14:32:08Z" + } + }, + "Match": { + "summary": "The bank holds the account under the name sent", + "value": { + "request_id": "5b0e2d7c-41a8-4f6e-9c3b-8e7d1a2f6c90", + "result": "match", + "holder_name": null, + "account": "****3000", + "vendor_data": null, + "created_at": "2026-10-21T14:35:41Z" + } + } + } + } + } + }, + "400": { + "description": "Refused as sent, and not billed. `field` names the body field to correct and `reason` says why, with the same reasons the hosted account form shows. Only the first refused field is reported.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Missing field": { + "summary": "The country's check needs a sort code", + "value": { + "detail": "The sort code is not valid (required).", + "code": "invalid_account_input", + "field": "account.sort_code", + "reason": "required" + } + }, + "Invalid IBAN": { + "summary": "The IBAN has the wrong length for its country", + "value": { + "detail": "The iban is not valid (length).", + "code": "invalid_account_input", + "field": "account.iban", + "reason": "length" + } + }, + "Refused by the bank": { + "summary": "The bank refused the account as sent", + "value": { + "detail": "The account was refused as typed (rejected).", + "code": "invalid_account_input", + "field": "account.account_number", + "reason": "rejected" + } + }, + "No name": { + "summary": "`holder` has neither `full_name` nor `business_name`", + "value": { + "detail": "Send full_name for a person or business_name for a company.", + "code": "invalid_account_input", + "field": "holder", + "reason": "required" + } + }, + "Consent required": { + "summary": "The country's account check needs the account holder's consent", + "value": { + "detail": "Account checks in this country need the account holder's explicit consent. Ask for it, then send `consent=true`.", + "code": "invalid_account_input", + "field": "consent", + "reason": "required" + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "402": { + "description": "The check cannot be paid for, so it did not run. Top up your balance, then send it again.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Out of credit": { + "summary": "Your balance cannot cover one check", + "value": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me", + "code": "insufficient_credits" + } + }, + "Not in your contract": { + "summary": "Your contract has no price for the Account check API", + "value": { + "detail": "Your contract does not price the Account check API. Contact your account manager.", + "code": "contract_rate_missing" + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header or the bearer token is missing or invalid, the request came from an address outside the key's IP list, or a scoped key has no write access to sessions.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No valid API key or token", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "No sessions write": { + "summary": "A scoped key without write access to sessions", + "value": { + "detail": "This API key has no write access to sessions." + } + } + } + } + } + }, + "404": { + "description": "No account check serves this account yet, so nothing was checked or billed. `country_not_covered`: none serves its country, or its kind of account (person or business) there. `bank_not_covered`: none serves its bank. `bank_verification_not_available`: Bank Verification is not enabled in production yet, which is the answer for every request there until it is switched on.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Country not covered": { + "summary": "No account check serves the country", + "value": { + "detail": "No account check serves this country yet.", + "code": "country_not_covered" + } + }, + "Bank not covered": { + "summary": "No account check serves the account's bank", + "value": { + "detail": "No account check serves this account's bank yet.", + "code": "bank_not_covered" + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + }, + "503": { + "description": "The bank could not answer. Nothing was checked or billed; send the check again later.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Unavailable": { + "summary": "The bank did not answer", + "value": { + "detail": "The account check is temporarily unavailable and this request was not charged. Please try again.", + "code": "account_check_unavailable" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/bank/account-check/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"country\": \"GBR\",\n \"account\": {\"sort_code\": \"20-45-67\", \"account_number\": \"31926819\"},\n \"holder\": {\"full_name\": \"Maria Oliveira\"},\n \"vendor_data\": \"payout_20481\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.bank.account_check(\n country=\"GBR\",\n account={\n \"account_number\": \"31926819\",\n \"sort_code\": \"20-45-67\",\n },\n holder={\n \"full_name\": \"Maria Oliveira\",\n },\n vendor_data=\"payout_20481\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.bank.accountCheck({\n country: \"GBR\",\n account: {\n account_number: \"31926819\",\n sort_code: \"20-45-67\",\n },\n holder: {\n full_name: \"Maria Oliveira\",\n },\n vendor_data: \"payout_20481\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Bank.AccountCheck(ctx, didit.BankAccountCheckRequest{\n\tCountry: \"GBR\",\n\tAccount: didit.BankAccountCheckRequestAccount{\n\t\tAccountNumber: didit.String(\"31926819\"),\n\t\tSortCode: didit.String(\"20-45-67\"),\n\t},\n\tHolder: didit.BankAccountCheckHolder{\n\t\tFullName: didit.String(\"Maria Oliveira\"),\n\t},\n\tVendorData: didit.String(\"payout_20481\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BankAccountCheckHolder;\nimport me.didit.types.BankAccountCheckPersonHolder;\nimport me.didit.types.BankAccountCheckRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.bank().accountCheck(BankAccountCheckRequest.builder()\n .country(\"GBR\")\n .account(BankAccountCheckRequest.Account.builder()\n .accountNumber(\"31926819\")\n .sortCode(\"20-45-67\")\n .build())\n .holder(BankAccountCheckHolder.ofBankAccountCheckPersonHolder(BankAccountCheckPersonHolder.builder()\n .fullName(\"Maria Oliveira\")\n .build()))\n .vendorData(\"payout_20481\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "bank->accountCheck([\n 'country' => 'GBR',\n 'account' => [\n 'account_number' => '31926819',\n 'sort_code' => '20-45-67',\n ],\n 'holder' => [\n 'full_name' => 'Maria Oliveira',\n ],\n 'vendor_data' => 'payout_20481',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.bank.account_check(\n country: \"GBR\",\n account: {\n account_number: \"31926819\",\n sort_code: \"20-45-67\",\n },\n holder: {\n full_name: \"Maria Oliveira\",\n },\n vendor_data: \"payout_20481\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Bank.AccountCheckAsync(new BankAccountCheckRequest\n{\n Country = \"GBR\",\n Account = new()\n {\n AccountNumber = \"31926819\",\n SortCode = \"20-45-67\",\n },\n Holder = new()\n {\n FullName = \"Maria Oliveira\",\n },\n VendorData = \"payout_20481\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/email/send/": { + "post": { + "summary": "Send Email Code", + "description": "Send a one-time passcode (OTP) to an email address, then verify it with [`POST /v3/email/check/`](/standalone-apis/email-check).\n\n**How send and check pair up.** Verification state is keyed by your application plus the `email` address — the check call does not take `request_id`. Each new send creates a pending verification that lives for **5 minutes**: call the check with the same address and the code the user received before the window closes. Calling send again for the same address while a verification is pending generates a fresh code for that same verification (`status: \"Retry\"`, same `request_id`); the previous code stops working. At most one retry is attached this way (two sends total); a further send starts a fresh verification with a new `request_id`. The 5-minute window is measured from the first send and is not extended by retries.\n\n**Deliverability pre-check.** Before anything is sent, the address goes through syntax and DNS/MX validation. Addresses that cannot receive mail return `200` with `status: \"Undeliverable\"` and `reason: \"email_can_not_be_delivered\"` — no email is sent, nothing is billed, and the verification is immediately finalized as `Declined` (a follow-up check returns `Expired or Not Found`). A downstream send failure reports the same way.\n\n**Code format and branding.** Codes are 4–8 characters (`options.code_size`, default 6), numeric by default; set `options.alphanumeric_code: true` for uppercase letters and digits (the check comparison is case-insensitive). The email template is localized via `options.locale` (54 supported languages) and can use your application's white-label branding via `options.use_white_label_customization`.\n\n**Billing.** One Email Verification API credit per successful send (`status: \"Success\"`), charged at send time. `Retry` and `Undeliverable` sends are free, and checks are free.\n\n**Session persistence.** Every new verification is persisted as an API-type session: `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/`, the verification appears in the Business Console, and `status.updated` webhooks fire as it progresses.\n\n**Sandbox.** Sandbox API keys skip delivery and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Success` payload with a random `request_id`; no email is sent and nothing is persisted. Use code `123456` on the sandbox check.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3email_send", + "x-didit-sdk": { + "resource": "email", + "method": "send_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/email/send/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"email\": \"alice@example.com\",\n \"options\": { \"code_size\": 6, \"locale\": \"en\" },\n \"vendor_data\": \"user-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.email.send_code(\n email=\"alice@example.com\",\n options={\n \"locale\": \"en\",\n },\n vendor_data=\"user-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.email.sendCode({\n email: \"alice@example.com\",\n options: {\n locale: \"en\",\n },\n vendor_data: \"user-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Email.SendCode(ctx, didit.EmailSendCodeParams{\n\tEmail: \"alice@example.com\",\n\tOptions: didit.Some(didit.EmailSendCodeParamsOptions{\n\t\tLocale: didit.Some(didit.EmailSendCodeParamsOptionsLocale(\"en\")),\n\t}),\n\tVendorData: didit.String(\"user-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.EmailSendCodeParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.email().sendCode(EmailSendCodeParams.builder()\n .email(\"alice@example.com\")\n .options(EmailSendCodeParams.Options.builder()\n .locale(EmailSendCodeParams.Options.Locale.of(\"en\"))\n .build())\n .vendorData(\"user-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "email->sendCode([\n 'email' => 'alice@example.com',\n 'options' => [\n 'locale' => 'en',\n ],\n 'vendor_data' => 'user-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.email.send_code(\n email: \"alice@example.com\",\n options: {\n locale: \"en\",\n },\n vendor_data: \"user-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Email.SendCodeAsync(new EmailSendCodeParams\n{\n Email = \"alice@example.com\",\n Options = new EmailSendCodeParamsOptions\n {\n Locale = new EmailSendCodeParamsOptionsLocale(\"en\"),\n },\n VendorData = \"user-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "Recipient email address. Malformed addresses return `400`; syntactically valid addresses that cannot receive mail (failed DNS/MX validation) return `200` with `status: \"Undeliverable\"`.", + "example": "alice@example.com" + }, + "options": { + "type": "object", + "description": "OTP format, localization, and branding options. All fields are optional.", + "properties": { + "code_size": { + "type": "integer", + "minimum": 4, + "maximum": 8, + "default": 6, + "description": "Number of characters in the OTP (digits by default; uppercase letters and digits when `alphanumeric_code` is `true`)." + }, + "alphanumeric_code": { + "type": "boolean", + "default": false, + "description": "When `true`, the OTP is composed of uppercase letters `A–Z` and digits `0–9` instead of digits only. The `/v3/email/check/` comparison is case-insensitive." + }, + "locale": { + "type": "string", + "maxLength": 5, + "enum": [ + "en", + "ar", + "bn", + "bg", + "bs", + "ca", + "cs", + "da", + "de", + "el", + "es", + "et", + "fa", + "fi", + "fr", + "he", + "hi", + "hr", + "hu", + "hy", + "id", + "it", + "ja", + "ka", + "kk", + "ko", + "ky", + "lt", + "lv", + "cnr", + "mk", + "mn", + "ms", + "nl", + "no", + "pl", + "pt-BR", + "pt", + "ro", + "ru", + "sk", + "sl", + "so", + "sq", + "sr", + "sv", + "th", + "tr", + "uk", + "uz", + "vi", + "zh-CN", + "zh-TW", + "zh" + ], + "description": "Language of the OTP email template. Defaults to `en`. Unsupported values return `400` listing all supported locales.", + "example": "en" + }, + "use_white_label_customization": { + "type": "boolean", + "default": false, + "description": "When `true`, the OTP email uses your application's white-label customization (logo, colors, sender branding) instead of the default Didit template." + } + } + }, + "signals": { + "type": "object", + "description": "Optional device and network signals about the end user, forwarded to the anti-fraud layer to improve detection of abusive or automated traffic. All fields are optional.", + "properties": { + "ip": { + "type": "string", + "description": "IP address of the end user's device. IPv4 or IPv6.", + "example": "192.0.2.1" + }, + "device_id": { + "type": "string", + "maxLength": 255, + "description": "Unique identifier of the end user's device. Android: `ANDROID_ID`; iOS: `identifierForVendor`.", + "example": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2" + }, + "device_platform": { + "type": "string", + "enum": [ + "android", + "ios", + "ipados", + "tvos", + "web" + ], + "description": "Platform of the end user's device." + }, + "device_model": { + "type": "string", + "maxLength": 255, + "description": "Model of the end user's device.", + "example": "iPhone17,2" + }, + "os_version": { + "type": "string", + "maxLength": 64, + "description": "Operating-system version of the end user's device.", + "example": "18.0.1" + }, + "app_version": { + "type": "string", + "maxLength": 64, + "description": "Version of your application.", + "example": "1.2.34" + }, + "user_agent": { + "type": "string", + "maxLength": 512, + "description": "User agent of the end user's browser or app.", + "example": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1" + } + } + }, + "vendor_data": { + "type": "string", + "description": "Optional caller-controlled identifier (your internal user id, an email, a UUID, etc.) persisted on the session and echoed back in the send response, the matching check response, webhooks, and the Business Console. Use it to correlate Didit's `request_id` with your user record." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Optional free-form JSON object persisted on the session and echoed back in the send response, the matching check response, webhooks, and the Business Console." + } + }, + "required": [ + "email" + ] + }, + "examples": { + "Numeric 6-digit code": { + "summary": "Default — 6-digit numeric code in English", + "value": { + "email": "alice@example.com", + "options": { + "locale": "en" + }, + "vendor_data": "user-1234" + } + }, + "Alphanumeric 8-character code": { + "summary": "Uppercase letters + digits, Spanish template", + "value": { + "email": "bob@example.com", + "options": { + "code_size": 8, + "alphanumeric_code": true, + "locale": "es" + } + } + }, + "White-label branding": { + "summary": "Send the OTP email with your application's white-label customization", + "value": { + "email": "carol@example.com", + "options": { + "locale": "en", + "use_white_label_customization": true + }, + "vendor_data": "user-5678" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Send acknowledged. Inspect `status`: `Success` and `Retry` mean a code is on its way; `Undeliverable` means the address cannot receive mail and the verification is already finalized as `Declined`. `request_id` is the persisted session id (same id on a `Retry`).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailVerificationSendResponse" + }, + "examples": { + "Success": { + "summary": "OTP emailed for a new verification (billed)", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Success", + "reason": null, + "vendor_data": "user-1234", + "metadata": null + } + }, + "Retry": { + "summary": "Second send inside the 5-minute window — fresh code, same request_id, free", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Retry", + "reason": null, + "vendor_data": "user-1234", + "metadata": null + } + }, + "Undeliverable": { + "summary": "Address failed DNS/MX validation — verification finalized as Declined, nothing billed", + "value": { + "request_id": "cb1a5011-344d-444d-bdce-218904745d7f", + "status": "Undeliverable", + "reason": "email_can_not_be_delivered", + "vendor_data": "user-1234", + "metadata": null + } + } + } + } + } + }, + "400": { + "description": "Validation error — DRF's field-error envelope: one array of messages per offending field, nested under `options` for option errors.", + "content": { + "application/json": { + "examples": { + "Invalid email": { + "summary": "Malformed email address", + "value": { + "email": [ + "Enter a valid email address." + ] + } + }, + "Unsupported locale": { + "summary": "`options.locale` outside the supported set", + "value": { + "options": { + "locale": [ + "Invalid locale. Supported locales are en, ar, bn, bg, bs, ca, cs, da, de, el, es, et, fa, fi, fr, he, hi, hr, hu, hy, id, it, ja, ka, kk, ko, ky, lt, lv, cnr, mk, mn, ms, nl, no, pl, pt-BR, pt, ro, ru, sk, sl, so, sq, sr, sv, th, tr, uk, uz, vi, zh-CN, zh-TW, zh." + ] + } + } + }, + "Code size out of range": { + "summary": "`options.code_size` outside 4–8", + "value": { + "options": { + "code_size": [ + "Ensure this value is less than or equal to 8." + ] + } + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing (`{\"detail\": ...}`); a wrong, revoked or expired key returns `401`. Also returned before any send when the organization's balance cannot cover one Email Verification API credit (`{\"error\": ...}`).", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the send price", + "value": { + "error": "You don't have enough credits to perform this request." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "503": { + "description": "The email provider could not be reached (an outage or a timeout), so no code was sent and nothing was billed or counted as a send attempt. Repeat the same request.", + "content": { + "application/json": { + "examples": { + "Email delivery unavailable": { + "summary": "The email provider failed after one automatic retry", + "value": { + "detail": "Email delivery is temporarily unavailable. Please try again." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/email/check/": { + "post": { + "summary": "Check Email Code", + "description": "Verify the OTP delivered by [`POST /v3/email/send/`](/standalone-apis/email-send) and get the final verification result plus email intelligence: deliverability, breach exposure (`is_breached` and the `breaches` list from a breach-intelligence database), disposable-provider detection, and duplicate usage of the address across your sessions.\n\n**How the check finds the verification.** Matching is by your application plus the `email` address — `request_id` is not an input. The most recent pending verification created within the last **5 minutes** is checked. If there is none (never sent, already finalized, undeliverable at send time, or older than 5 minutes) the endpoint returns `200` with `status: \"Expired or Not Found\"`.\n\n**Attempt budget.** Each verification allows **3 code attempts**. The first two wrong codes return `status: \"Failed\"` with the attempts remaining and `email: null`; the third wrong code finalizes the verification as `Declined` with an `EMAIL_CODE_ATTEMPTS_EXCEEDED` warning and returns the full email report. Codes are compared case-insensitively (relevant for `alphanumeric_code` sends); only the most recently sent code is valid.\n\n**Outcomes.** `Approved` — correct code and no declining risk. `Declined` — terminal: the code was correct but a declining risk matched (a `DECLINE` action below, a blocklisted address, or the address found undeliverable at finalization), or the attempt budget was exhausted. `Failed` — wrong code, attempts remaining. `Expired or Not Found` — nothing to check. On `Approved`/`Declined` the `request_id` equals the send's `request_id` (the session id) and `email` carries the full report (`is_breached`, `breaches`, `is_disposable`, `is_undeliverable`, `warnings`, `lifecycle`, `matches`); on `Failed` and `Expired or Not Found` the `request_id` is a one-off random UUID.\n\n**Risk actions.** `duplicated_email_action`, `breached_email_action`, and `disposable_email_action` decide what happens when the corresponding risk is detected on a correct code: `DECLINE` flips the final status to `Declined`; `NO_ACTION` (default) records the risk in `email.warnings` without affecting the status.\n\n**Billing.** Checks are free — the credit is consumed by the successful send.\n\n**Session persistence.** A finalized check updates the session created by the send (visible in the Business Console, queryable via `GET /v3/session/{sessionId}/decision/`) and fires a `status.updated` webhook.\n\n**Sandbox.** Sandbox API keys skip all processing: any well-formed code (`123456` included) returns a static `Approved` payload with a simplified `email` object (`status`, `email`, `is_breached`, `is_disposable`, `is_undeliverable`); malformed input still returns `400`. Nothing is persisted.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.\n\n**Social footprint.** Set `enable_social` to `true` to add a `email_social` block: the online platforms this address is registered on, with per-category counts and any profile details available. `profiles_registered: 0` means the platforms were checked and the address was found on none of them - a valid, billable answer. The block is absent when the add-on was not requested or the check could not be completed, and it is not billed in that case. Billing adds one `email_social` unit per request on top of this endpoint's own price.", + "operationId": "post_v3email_check", + "x-didit-sdk": { + "resource": "email", + "method": "check_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/email/check/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"email\": \"alice@example.com\",\n \"code\": \"123456\",\n \"disposable_email_action\": \"DECLINE\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.email.check_code(\n email=\"alice@example.com\",\n code=\"123456\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.email.checkCode({\n email: \"alice@example.com\",\n code: \"123456\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Email.CheckCode(ctx, didit.EmailCheckCodeParams{\n\tEmail: \"alice@example.com\",\n\tCode: \"123456\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.EmailCheckCodeParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.email().checkCode(EmailCheckCodeParams.builder()\n .email(\"alice@example.com\")\n .code(\"123456\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "email->checkCode([\n 'email' => 'alice@example.com',\n 'code' => '123456',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.email.check_code(\n email: \"alice@example.com\",\n code: \"123456\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Email.CheckCodeAsync(new EmailCheckCodeParams\n{\n Email = \"alice@example.com\",\n Code = \"123456\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "The same email address used in the matching [`POST /v3/email/send/`](/standalone-apis/email-send) call. This is what links the check to the send.", + "example": "alice@example.com" + }, + "code": { + "type": "string", + "maxLength": 10, + "description": "The OTP the end user received: 4–8 digits, or 4–8 letters/digits when `alphanumeric_code: true` was used at send time. Comparison is case-insensitive.", + "example": "123456" + }, + "duplicated_email_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the same address was already used and approved by a different user (only previously Approved verifications count) (different `vendor_data`) of your application. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `email.warnings` and fills `email.matches`." + }, + "breached_email_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the address appears in known data breaches (`email.is_breached`). `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `email.warnings`." + }, + "disposable_email_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the domain belongs to a disposable/temporary-mail provider (`email.is_disposable`). `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `email.warnings`." + }, + "enable_enrichment": { + "type": "boolean", + "default": false, + "description": "When `true`, also run email intelligence on the address: a risk score and its breach history. Billed as one `email_intelligence_api` unit per request ($0.05 at list price) in addition to this endpoint's own price. The request returns `403` when your balance cannot cover it.", + "example": false + }, + "email_intelligence_score_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the email intelligence risk score is above the threshold. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "recent_email_breach_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the address appears in a recent known data breach. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "frequent_email_breach_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the address appears in many known data breaches. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "enable_social": { + "type": "boolean", + "default": false, + "description": "When true, also look up the social footprint of the address - the email, social, professional, e-commerce and entertainment platforms it is registered on. Billed as one `email_social` unit per request in addition to this endpoint's own price.", + "example": true + }, + "email_no_social_presence_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the social footprint check finds the address registered on none of the platforms it covers. Only NO_ACTION and DECLINE are accepted on this endpoint; REVIEW is available on the workflow configuration key of the same name." + } + }, + "required": [ + "email", + "code" + ] + }, + "examples": { + "Default": { + "summary": "Record any risks but never auto-decline", + "value": { + "email": "alice@example.com", + "code": "123456" + } + }, + "Strict risk policy": { + "summary": "Auto-decline disposable and breached inboxes", + "value": { + "email": "alice@example.com", + "code": "123456", + "disposable_email_action": "DECLINE", + "breached_email_action": "DECLINE" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Check completed — wrong codes and missing verifications also return `200`; inspect `status`, not the HTTP code. `email` is populated only on finalized outcomes (`Approved`/`Declined`), `null` on `Failed`, and absent on `Expired or Not Found`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailVerificationCheckResponse" + }, + "examples": { + "Approved": { + "summary": "Correct code — clean address, full email report", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Approved", + "message": "The verification code is correct.", + "email": { + "status": "Approved", + "email": "alice@example.com", + "is_breached": false, + "breaches": [], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [], + "lifecycle": [ + { + "type": "EMAIL_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null + }, + "fee": 0.02 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "EMAIL_VERIFICATION_APPROVED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": null, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Approved with breach exposure": { + "summary": "Correct code; breach exposure recorded as a warning (breached_email_action=NO_ACTION)", + "value": { + "request_id": "3a1f2b6c-9d4e-4f7a-8b2c-1e5d6f7a8b9c", + "status": "Approved", + "message": "The verification code is correct.", + "email": { + "status": "Approved", + "email": "bob@example.com", + "is_breached": true, + "breaches": [ + { + "name": "ExampleApp", + "domain": "exampleapp.com", + "breach_date": "2019-03-21", + "breach_emails_count": 763117, + "description": "In March 2019, the social platform ExampleApp suffered a data breach that exposed email addresses and passwords.", + "logo_path": "https:///logos/ExampleApp.png", + "data_classes": [ + "email_addresses", + "passwords" + ], + "is_verified": true + } + ], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [ + { + "feature": "EMAIL", + "risk": "BREACHED_EMAIL_DETECTED", + "additional_data": null, + "log_type": "information", + "short_description": "Breached email detected", + "long_description": "This email address was found in one or more known data breaches." + } + ], + "lifecycle": [ + { + "type": "EMAIL_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null + }, + "fee": 0.02 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "835126", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "EMAIL_VERIFICATION_APPROVED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": null, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-5678", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Failed (attempts remaining)": { + "summary": "Wrong code — the user can retry; request_id here is a one-off UUID", + "value": { + "request_id": "11111111-2222-3333-4444-555555555555", + "status": "Failed", + "message": "The verification code is incorrect. Attempts remaining: 2", + "email": null, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:21.512340+00:00" + } + }, + "Declined (risk policy)": { + "summary": "Correct code, but the address is disposable and disposable_email_action=DECLINE", + "value": { + "request_id": "8c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "status": "Declined", + "message": "The verification code is correct.", + "email": { + "status": "Declined", + "email": "tempuser42@mailinator.com", + "is_breached": false, + "breaches": [], + "is_disposable": true, + "is_undeliverable": false, + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [ + { + "feature": "EMAIL", + "risk": "DISPOSABLE_EMAIL_DETECTED", + "additional_data": null, + "log_type": "error", + "short_description": "Disposable email detected", + "long_description": "The system detected that the email is disposable, which is not allowed." + } + ], + "lifecycle": [ + { + "type": "EMAIL_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null + }, + "fee": 0.02 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "EMAIL_VERIFICATION_DECLINED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": { + "reason": "DISPOSABLE_EMAIL_DETECTED" + }, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Expired or Not Found": { + "summary": "No pending verification for this address in the last 5 minutes", + "value": { + "request_id": "88808a77-06e9-4cb0-85f5-9c052ddfd987", + "status": "Expired or Not Found", + "message": "No pending email verification found in the last 5 minutes.", + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-12T01:24:59.887904+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error — DRF's field-error envelope: one array of messages per offending field.", + "content": { + "application/json": { + "examples": { + "Invalid email": { + "summary": "Malformed email address", + "value": { + "email": [ + "Enter a valid email address." + ] + } + }, + "Missing code": { + "summary": "`code` not included in the body", + "value": { + "code": [ + "This field is required." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing; a wrong, revoked or expired key returns `401`. Checks are free, so there is no credit check here.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/workflows/": { + "get": { + "summary": "List workflows", + "description": "List your workflows, newest first, in a limit/offset pagination envelope (`{count, next, previous, results}`, default page size 50). Each row is one version; `workflow_id` is the stable group ID and new sessions use the latest `published` version.", + "operationId": "list_workflows", + "x-didit-sdk": { + "resource": "workflows", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50 + }, + "description": "Page size (number of workflows per page)." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0 + }, + "description": "Number of rows to skip before the first returned workflow." + } + ], + "responses": { + "200": { + "description": "One page of workflows ordered by `created_at` descending, wrapped in the pagination envelope. Each item in `results` is a `WorkflowListItem`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of workflow versions for the application." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkflowListItem" + } + } + } + }, + "examples": { + "Two workflows": { + "summary": "A default KYC workflow and a draft KYC + AML workflow", + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "uuid": "a1b2c3d4-5678-90ab-cdef-111111111111", + "workflow_id": "a1b2c3d4-5678-90ab-cdef-111111111111", + "workflow_label": "Standard KYC", + "workflow_type": "kyc", + "is_default": true, + "is_archived": false, + "is_white_label_enabled": false, + "total_price": 0.15, + "min_price": 0.15, + "max_price": 0.15, + "features": "OCR + LIVENESS + FACE_MATCH", + "is_simple_workflow": true, + "is_editable": false, + "workflow_url": "https://verify.didit.me/u/_yljPOx8RWG3UAz5ZRQGVQ", + "max_retry_attempts": 3, + "retry_window_days": 7, + "session_expiration_time": 604800, + "version": 2, + "status": "published", + "has_draft": false, + "created_by": { + "name": "Alex Mateo", + "email": "alex@example.com", + "type": "user" + }, + "updated_at": "2026-04-12T10:30:00Z" + }, + { + "uuid": "b2c3d4e5-6789-01bc-defa-222222222222", + "workflow_id": "b2c3d4e5-6789-01bc-defa-222222222222", + "workflow_label": "Full Verification + AML", + "workflow_type": "kyc", + "is_default": false, + "is_archived": false, + "is_white_label_enabled": false, + "total_price": 0.25, + "min_price": 0.25, + "max_price": 0.25, + "features": "OCR + LIVENESS + FACE_MATCH + AML", + "is_simple_workflow": true, + "is_editable": true, + "workflow_url": "https://verify.didit.me/u/Aq3kPOx8RWG3UAz5ZRQGVQ", + "max_retry_attempts": 3, + "retry_window_days": 7, + "session_expiration_time": 604800, + "version": 1, + "status": "draft", + "has_draft": true, + "created_by": null, + "updated_at": "2026-04-15T18:05:21Z" + } + ] + } + } + } + } + } + }, + "403": { + "description": "The API key is missing, malformed, expired, or belongs to a different application. Body: `{\"detail\": \"You do not have permission to perform this action.\"}`. Refresh the token via the [Auth API](/auth-api/get-credentials) and retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff and honour any `Retry-After` header." + }, + "500": { + "description": "Unexpected server error while building the list. The request can be safely retried." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/workflows/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Accept: application/json'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.workflows.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.workflows.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Workflows.ListAutoPaging(ctx, didit.WorkflowsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.workflows().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "workflows->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.workflows.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Workflows.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "summary": "Create workflow", + "description": "Create a simple workflow. Send `features` in execution order (ordering rules apply); defaults to `published` v1. The body accepts ONLY the fields listed in `SimpleWorkflowRequest` — any other key (including `workflow_type`) is rejected with a 400. Not idempotent — each call creates a new workflow. Maximum 50 workflows per application.", + "operationId": "create_workflow", + "x-didit-sdk": { + "resource": "workflows", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleWorkflowRequest" + }, + "examples": { + "Simple KYC": { + "summary": "Linear ID, liveness, and face match workflow", + "value": { + "workflow_label": "Standard KYC", + "features": [ + { + "feature": "OCR", + "config": { + "duplicated_user_action": "review" + } + }, + { + "feature": "LIVENESS", + "config": { + "face_liveness_method": "passive" + } + }, + { + "feature": "FACE_MATCH", + "config": { + "face_match_score_decline_threshold": 40, + "face_match_score_review_threshold": 60 + } + } + ] + } + }, + "KYC + AML": { + "summary": "Run AML after ID verification", + "value": { + "workflow_label": "KYC with AML", + "features": [ + { + "feature": "OCR" + }, + { + "feature": "AML", + "config": { + "aml_score_review_threshold": 70, + "aml_score_approve_threshold": 30 + } + } + ] + } + }, + "Questionnaire workflow": { + "summary": "Run a questionnaire created with POST /v3/questionnaires/", + "value": { + "workflow_label": "Source of Funds", + "features": [ + { + "feature": "QUESTIONNAIRE", + "config": { + "questionnaire_uuid": "11111111-2222-3333-4444-555555555555", + "review_questionnaire_manually": true + } + } + ] + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Workflow created. The response body is the FULL workflow configuration — the same raw `VerificationSettings` serialization returned by `GET /v3/workflows/{settings_uuid}/` (~190 fields: every feature toggle, threshold, action and the generated `workflow_graph`), not the compact list item. Store the `uuid` (also exposed as `workflow_id`) so you can reference it from later session-creation calls. Note: `features` is a `\" + \"`-joined string and prices are JSON numbers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowDetail" + }, + "examples": { + "Created (abbreviated)": { + "summary": "Key fields of the full configuration object", + "value": { + "uuid": "c3d4e5f6-7890-12cd-ef34-333333333333", + "workflow_id": "c3d4e5f6-7890-12cd-ef34-333333333333", + "workflow_label": "Standard KYC", + "workflow_type": null, + "is_default": false, + "is_archived": false, + "total_price": 0.15, + "min_price": 0.15, + "max_price": 0.15, + "features": "OCR + LIVENESS + FACE_MATCH", + "is_simple_workflow": false, + "is_editable": false, + "workflow_url": "https://verify.didit.me/u/w9TkPOx8RWG3UAz5ZRQGVQ", + "workflow_graph": { + "start_node": "ocr", + "nodes": "... one node per feature plus a final status node ..." + }, + "max_retry_attempts": 3, + "retry_window_days": 7, + "session_expiration_time": 604800, + "version": 1, + "status": "published", + "published_at": "2026-05-17T10:30:00Z", + "created_at": "2026-05-17T10:30:00Z", + "updated_at": "2026-05-17T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Common causes: a top-level key outside the accepted whitelist (e.g. `workflow_type`), an unknown `feature` value, a feature placed before its dependency (e.g., `FACE_MATCH` before `OCR`), a passive feature (`AML`, `IP_ANALYSIS`, `DATABASE_VALIDATION`, `KYB_KEY_PEOPLE`) used as the first step, a `QUESTIONNAIRE` feature whose `config.questionnaire_uuid` does not exist for this application, a `SIGNATURE` feature that would go live with no document to sign, a `SIGNATURE` feature at `signature_level: \"aes\"` while Advanced (AES) is not available (`aes_not_available`, see `signature_availability` on the workflow), or an out-of-range threshold. Body is a DRF validation error object — keys are field names, values are arrays of human-readable messages.", + "content": { + "application/json": { + "examples": { + "Advanced (AES) not available": { + "value": { + "features": { + "signature_level": [ + "Node 'signature': Advanced (AES) is not available yet (aes_not_available)." + ] + } + } + }, + "Signature step with no document": { + "value": { + "features": { + "non_field_errors": [ + "Node 'signature': Add a document to sign, or let each session bring its own." + ] + } + } + }, + "Missing feature": { + "value": { + "features": [ + "At least one feature is required." + ] + } + }, + "Unknown top-level field": { + "value": { + "workflow_type": [ + "The v3 workflow API only accepts `workflow_label`, `is_default`, `status`, `features`, `is_white_label_enabled`, `is_desktop_allowed`, `max_retry_attempts`, `retry_window_days`, `session_expiration_time`, `face_liveness_max_attempts`, `face_match_max_attempts`." + ] + } + }, + "Unknown feature": { + "value": { + "features": [ + "Invalid feature 'FOO'. Allowed values: AGE_ESTIMATION, AML, DATABASE_VALIDATION, EMAIL_VERIFICATION, FACE_MATCH, IP_ANALYSIS, KYB_DOCUMENTS, KYB_KEY_PEOPLE, KYB_REGISTRY, LIVENESS, NFC, OCR, PHONE_VERIFICATION, PROOF_OF_ADDRESS, QUESTIONNAIRE." + ] + } + }, + "Ordering": { + "value": { + "features": [ + "Feature 'FACE_MATCH' must appear after one of: OCR." + ] + } + }, + "Questionnaire not found": { + "value": { + "features": "Questionnaire '11111111-2222-3333-4444-555555555555' was not found for this application. Create the questionnaire first, then use its questionnaire_id." + } + } + } + } + } + }, + "403": { + "description": "The API key is missing, malformed, expired, or scoped to a different application. Refresh via the [Auth API](/auth-api/get-credentials) and retry.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "415": { + "description": "Unsupported media type. Set `Content-Type: application/json` and send a JSON body." + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error while creating the workflow. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/workflows/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"workflow_label\": \"Standard KYC\",\n \"features\": [\n {\"feature\": \"OCR\"},\n {\"feature\": \"LIVENESS\", \"config\": {\"face_liveness_method\": \"passive\"}},\n {\"feature\": \"FACE_MATCH\", \"config\": {\"face_match_score_decline_threshold\": 40, \"face_match_score_review_threshold\": 60}}\n ]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.workflows.create(\n workflow_label=\"Standard KYC\",\n features=[{\n \"feature\": \"OCR\",\n \"config\": {\"duplicated_user_action\": \"review\"},\n }, {\n \"feature\": \"LIVENESS\",\n \"config\": {\"face_liveness_method\": \"passive\"},\n }, {\n \"feature\": \"FACE_MATCH\",\n \"config\": {\"face_match_score_decline_threshold\": 40, \"face_match_score_review_threshold\": 60},\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.workflows.create({\n workflow_label: \"Standard KYC\",\n features: [{\n feature: \"OCR\",\n config: { duplicated_user_action: \"review\" },\n }, {\n feature: \"LIVENESS\",\n config: { face_liveness_method: \"passive\" },\n }, {\n feature: \"FACE_MATCH\",\n config: { face_match_score_decline_threshold: 40, face_match_score_review_threshold: 60 },\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Workflows.Create(ctx, didit.SimpleWorkflowRequest{\n\tWorkflowLabel: didit.String(\"Standard KYC\"),\n\tFeatures: []didit.SimpleWorkflowFeature{{\n\t\tFeature: \"OCR\",\n\t\tConfig: didit.Some(map[string]any{\"duplicated_user_action\": \"review\"}),\n\t}, {\n\t\tFeature: \"LIVENESS\",\n\t\tConfig: didit.Some(map[string]any{\"face_liveness_method\": \"passive\"}),\n\t}, {\n\t\tFeature: \"FACE_MATCH\",\n\t\tConfig: didit.Some(map[string]any{\"face_match_score_decline_threshold\": 40, \"face_match_score_review_threshold\": 60}),\n\t}},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SimpleWorkflowFeature;\nimport me.didit.types.SimpleWorkflowRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.workflows().create(SimpleWorkflowRequest.builder()\n .workflowLabel(\"Standard KYC\")\n .features(List.of(\n SimpleWorkflowFeature.builder()\n .feature(SimpleWorkflowFeature.Feature.of(\"OCR\"))\n .config(Map.of(\"duplicated_user_action\", \"review\"))\n .build(),\n SimpleWorkflowFeature.builder()\n .feature(SimpleWorkflowFeature.Feature.of(\"LIVENESS\"))\n .config(Map.of(\"face_liveness_method\", \"passive\"))\n .build(),\n SimpleWorkflowFeature.builder()\n .feature(SimpleWorkflowFeature.Feature.of(\"FACE_MATCH\"))\n .config(Map.of(\"face_match_score_decline_threshold\", 40, \"face_match_score_review_threshold\", 60))\n .build()))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "workflows->create([\n 'workflow_label' => 'Standard KYC',\n 'features' => [[\n 'feature' => 'OCR',\n 'config' => ['duplicated_user_action' => 'review'],\n ], [\n 'feature' => 'LIVENESS',\n 'config' => ['face_liveness_method' => 'passive'],\n ], [\n 'feature' => 'FACE_MATCH',\n 'config' => ['face_match_score_decline_threshold' => 40, 'face_match_score_review_threshold' => 60],\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.workflows.create(\n workflow_label: \"Standard KYC\",\n features: [{\n feature: \"OCR\",\n config: { duplicated_user_action: \"review\" },\n }, {\n feature: \"LIVENESS\",\n config: { face_liveness_method: \"passive\" },\n }, {\n feature: \"FACE_MATCH\",\n config: { face_match_score_decline_threshold: 40, face_match_score_review_threshold: 60 },\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Workflows.CreateAsync(new SimpleWorkflowRequest\n{\n WorkflowLabel = \"Standard KYC\",\n Features = new SimpleWorkflowFeature[]\n {\n new()\n {\n Feature = \"OCR\",\n Config = new Dictionary { [\"duplicated_user_action\"] = \"review\" },\n },\n new()\n {\n Feature = \"LIVENESS\",\n Config = new Dictionary { [\"face_liveness_method\"] = \"passive\" },\n },\n new()\n {\n Feature = \"FACE_MATCH\",\n Config = new Dictionary { [\"face_match_score_decline_threshold\"] = 40, [\"face_match_score_review_threshold\"] = 60 },\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/workflows/{settings_uuid}/": { + "get": { + "summary": "Get workflow", + "description": "Fetch full configuration for one workflow version by `settings_uuid`: feature toggles, thresholds, allowed documents, graph, retry policy, callbacks, and pricing.", + "operationId": "get_workflow", + "x-didit-sdk": { + "resource": "workflows", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "parameters": [ + { + "name": "settings_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the workflow (the `uuid` value returned by the list endpoint). Each version of a workflow has its own `settings_uuid`." + } + ], + "responses": { + "200": { + "description": "Full workflow configuration. The shape is the raw `VerificationSettings` serialization (~190 fields: every feature toggle, threshold, warning action, allowed-documents config, the `workflow_graph`, retry policy, callbacks and pricing). The example below is abbreviated. Notes: `features` is a `\" + \"`-joined string (e.g. `\"OCR + LIVENESS\"`), prices are JSON numbers, and `is_editable` is `true` only for `draft` versions.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowDetail" + }, + "examples": { + "KYC workflow with face match (abbreviated)": { + "value": { + "uuid": "a1b2c3d4-5678-90ab-cdef-111111111111", + "workflow_id": "a1b2c3d4-5678-90ab-cdef-111111111111", + "workflow_label": "Standard KYC", + "workflow_type": "kyc", + "is_default": true, + "is_archived": false, + "is_liveness_enabled": true, + "face_liveness_method": "passive", + "face_liveness_score_decline_threshold": 50, + "face_liveness_score_review_threshold": 70, + "is_face_match_enabled": true, + "face_match_score_decline_threshold": 40, + "face_match_score_review_threshold": 60, + "is_aml_enabled": false, + "is_phone_verification_enabled": false, + "is_email_verification_enabled": false, + "is_database_validation_enabled": false, + "is_ip_analysis_enabled": false, + "is_nfc_enabled": false, + "duplicated_user_action": "no_action", + "documents_allowed": { + "ESP": { + "P": { + "sides": 1, + "enabled": 1, + "subtypes": [ + "EPASSPORT", + "PASSPORT_GENERIC" + ], + "preferred_characters": "latin", + "expiration_check_mode": "strict" + } + } + }, + "max_retry_attempts": 3, + "retry_window_days": 7, + "session_expiration_time": 604800, + "version": 2, + "status": "published", + "published_at": "2026-04-12T10:30:00Z", + "is_simple_workflow": false, + "is_editable": false, + "workflow_url": "https://verify.didit.me/u/_yljPOx8RWG3UAz5ZRQGVQ", + "total_price": 0.15, + "min_price": 0.15, + "max_price": 0.15, + "features": "OCR + LIVENESS + FACE_MATCH", + "created_at": "2025-01-15T10:30:00Z", + "updated_at": "2026-04-12T10:30:00Z" + } + } + } + } + } + }, + "403": { + "description": "API key is missing, malformed, expired, or scoped to a different application.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No workflow with this `settings_uuid` exists for the authenticated application. Either the UUID is wrong or it belongs to another application — Didit deliberately does not distinguish the two cases to avoid leaking the existence of cross-application records.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/workflows/a1b2c3d4-5678-90ab-cdef-111111111111/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.workflows.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.workflows.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Workflows.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.workflows().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "workflows->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.workflows.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Workflows.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "patch": { + "summary": "Update workflow", + "description": "Update a workflow. Versioning depends on the `status` field you send and the current state of the version: (1) no `status` on a **published** version → the published version is updated in place (same `uuid`, same `version`); (2) no `status` on a **draft** → the draft is updated and immediately published; (3) `status: \"published\"` on a **published** version → a NEW published version is created under the same `workflow_id` (new `uuid`, `version` + 1) and returned, and any existing draft in the group is deleted; (4) `status: \"draft\"` keeps a draft as a draft (autosave) but is rejected with 400 on a published version (`\"Cannot revert a published version to draft.\"`). The body accepts the same strict field whitelist as create — unknown keys are rejected with 400.", + "operationId": "update_workflow", + "x-didit-sdk": { + "resource": "workflows", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "parameters": [ + { + "name": "settings_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the workflow to update." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleWorkflowUpdateRequest" + }, + "examples": { + "Replace features": { + "summary": "Send the full desired feature order", + "value": { + "features": [ + { + "feature": "OCR" + }, + { + "feature": "AML" + } + ] + } + }, + "Rename": { + "summary": "Rename a workflow", + "value": { + "workflow_label": "Premium KYC" + } + }, + "Set as default": { + "summary": "Make this workflow the application's default", + "value": { + "is_default": true + } + }, + "Update config": { + "summary": "Change feature configuration", + "value": { + "features": [ + { + "feature": "OCR" + }, + { + "feature": "LIVENESS", + "config": { + "face_liveness_method": "passive" + } + } + ] + } + }, + "Publish a draft": { + "summary": "Promote a draft to published", + "value": { + "status": "published" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Workflow updated. The body is the FULL configuration of the resulting version (same raw `VerificationSettings` shape as `GET /v3/workflows/{settings_uuid}/`). When the update created a new version (`status: \"published\"` sent on a published row) the returned `uuid` and `version` differ from the ones you patched — use the returned `uuid` for subsequent calls.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowDetail" + } + } + } + }, + "400": { + "description": "Validation error — a top-level key outside the accepted whitelist, an unknown feature, invalid ordering, out-of-range threshold, referencing a non-existent questionnaire, a `SIGNATURE` feature that would go live with no document to sign, a new `SIGNATURE` feature at `signature_level: \"aes\"` while Advanced (AES) is not available (`aes_not_available`, see `signature_availability` on the workflow; a step that already stores `aes` keeps it), or `status: \"draft\"` sent for a published version (`\"Cannot revert a published version to draft.\"`). The response body is a DRF validation error keyed by field name." + }, + "403": { + "description": "API key missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No workflow with this `settings_uuid` exists for the authenticated application." + }, + "415": { + "description": "Unsupported media type. Set `Content-Type: application/json`." + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X PATCH 'https://verification.didit.me/v3/workflows/a1b2c3d4-5678-90ab-cdef-111111111111/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"workflow_label\": \"Premium KYC\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.workflows.update(\n \"11111111-2222-3333-4444-555555555555\",\n features=[{\n \"feature\": \"OCR\",\n }, {\n \"feature\": \"AML\",\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.workflows.update(\"11111111-2222-3333-4444-555555555555\", {\n features: [{\n feature: \"OCR\",\n }, {\n feature: \"AML\",\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Workflows.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SimpleWorkflowUpdateRequest{\n\tFeatures: didit.Some([]didit.SimpleWorkflowFeature{{\n\t\tFeature: \"OCR\",\n\t}, {\n\t\tFeature: \"AML\",\n\t}}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.SimpleWorkflowFeature;\nimport me.didit.types.SimpleWorkflowUpdateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.workflows().update(\"11111111-2222-3333-4444-555555555555\", SimpleWorkflowUpdateRequest.builder()\n .features(List.of(\n SimpleWorkflowFeature.builder()\n .feature(SimpleWorkflowFeature.Feature.of(\"OCR\"))\n .build(),\n SimpleWorkflowFeature.builder()\n .feature(SimpleWorkflowFeature.Feature.of(\"AML\"))\n .build()))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "workflows->update('11111111-2222-3333-4444-555555555555', [\n 'features' => [[\n 'feature' => 'OCR',\n ], [\n 'feature' => 'AML',\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.workflows.update(\n \"11111111-2222-3333-4444-555555555555\",\n features: [{\n feature: \"OCR\",\n }, {\n feature: \"AML\",\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Workflows.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new SimpleWorkflowUpdateRequest\n{\n Features = new SimpleWorkflowFeature[]\n {\n new()\n {\n Feature = \"OCR\",\n },\n new()\n {\n Feature = \"AML\",\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "summary": "Delete workflow", + "description": "Permanently delete one workflow version (no undo). Existing sessions keep their verification results but their workflow reference is cleared; other versions in the same `workflow_id` group are not deleted.", + "operationId": "delete_workflow", + "x-didit-sdk": { + "resource": "workflows", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "parameters": [ + { + "name": "settings_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the workflow to delete." + } + ], + "responses": { + "204": { + "description": "Workflow deleted. The response body is empty." + }, + "403": { + "description": "API key missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No workflow with this `settings_uuid` exists for the authenticated application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X DELETE 'https://verification.didit.me/v3/workflows/a1b2c3d4-5678-90ab-cdef-111111111111/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.workflows.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.workflows.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Workflows.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.workflows().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "workflows->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.workflows.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Workflows.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ] + } + }, + "/v3/questionnaires/": { + "get": { + "summary": "List questionnaires", + "description": "List your questionnaires, newest first, in a limit/offset pagination envelope (`{count, next, previous, results}`, default page size 50). Each row is one version; `questionnaire_group_id` is the stable group ID resolved to the latest `published` version.", + "operationId": "list_questionnaires", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50 + }, + "description": "Page size (number of questionnaires per page)." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0 + }, + "description": "Number of rows to skip before the first returned questionnaire." + } + ], + "responses": { + "200": { + "description": "One page of questionnaires ordered by `created_at` descending, wrapped in the pagination envelope. Each item in `results` is a `QuestionnaireListItem`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of questionnaire versions for the application." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionnaireListItem" + } + } + } + }, + "examples": { + "One questionnaire": { + "summary": "A simple bilingual questionnaire", + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "11111111-2222-3333-4444-555555555555", + "title": "KYC Questionnaire", + "description": "Additional verification questions", + "languages": [ + "en", + "es" + ], + "default_language": "en", + "is_active": true, + "is_simple_questionnaire": true, + "created_at": "2026-04-23T09:55:00Z", + "updated_at": "2026-04-23T10:00:00Z", + "question_types": [ + "SHORT_TEXT", + "MULTIPLE_CHOICE", + "FILE_UPLOAD" + ], + "workflow_names": [], + "questionnaire_group_id": "11111111-2222-3333-4444-555555555555", + "version": 1, + "status": "published", + "published_at": "2026-04-23T10:00:00Z", + "is_editable": false + } + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error while building the list. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/questionnaires/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.questionnaires.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.questionnaires.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Questionnaires.ListAutoPaging(ctx, didit.QuestionnairesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.questionnaires().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "questionnaires->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.questionnaires.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Questionnaires.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "summary": "Create questionnaire", + "description": "Create a simple linear questionnaire. Send `form_elements` in display order; branching and repeatable groups require the Console. Defaults to `published` v1.", + "operationId": "create_questionnaire", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleQuestionnaireRequest" + }, + "examples": { + "Simple": { + "summary": "Basic questionnaire", + "value": { + "title": "Customer Onboarding", + "description": "Additional information needed for verification", + "default_language": "en", + "languages": [ + "en" + ], + "is_active": true, + "form_elements": [ + { + "id": "occupation", + "element_type": "short_text", + "label": { + "en": "What is your occupation?" + }, + "is_required": true, + "placeholder": { + "en": "Software engineer" + } + }, + { + "id": "source_of_funds", + "element_type": "multiple_choice", + "label": { + "en": "Source of funds" + }, + "is_required": true, + "options": [ + { + "value": "employment", + "label": { + "en": "Employment" + } + }, + { + "value": "business", + "label": { + "en": "Business" + } + }, + { + "value": "investments", + "label": { + "en": "Investments" + } + }, + { + "value": "other", + "label": { + "en": "Other" + } + } + ] + } + ] + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Questionnaire created. The response includes the `questionnaire_id` — store it to reference this questionnaire from workflows or future update/delete calls.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QuestionnaireDetail" + }, + "examples": { + "Created": { + "value": { + "questionnaire_id": "11111111-2222-3333-4444-555555555555", + "title": "Customer Onboarding", + "description": "Additional information needed for verification", + "languages": [ + "en" + ], + "default_language": "en", + "is_active": true, + "is_simple_questionnaire": true, + "graph": { + "start_node": "occupation", + "nodes": { + "occupation": { + "element_type": "SHORT_TEXT", + "is_required": true, + "title": { + "en": "What is your occupation?" + }, + "next": "source_of_funds" + }, + "source_of_funds": { + "element_type": "MULTIPLE_CHOICE", + "is_required": true, + "title": { + "en": "Source of funds" + }, + "choices": [ + { + "value": "employment", + "label": { + "en": "Employment" + } + }, + { + "value": "business", + "label": { + "en": "Business" + } + } + ], + "next": null + } + } + }, + "sections": [], + "questionnaire_group_id": "11111111-2222-3333-4444-555555555555", + "version": 1, + "status": "published", + "published_at": "2026-04-23T10:00:00Z", + "is_editable": false + } + } + } + } + } + }, + "400": { + "description": "Validation error. Common causes: an unknown `element_type`, a translatable field missing a translation for one of `languages`, `languages` missing `en`, `default_language` not in `languages`, an option without a `label`, a question that has `branches`/`required_if`/`repeatable_config` (not supported by this endpoint), or sending a raw `graph` instead of `form_elements`. The body is a DRF validation error keyed by field name.", + "content": { + "application/json": { + "examples": { + "Graph not accepted": { + "value": { + "graph": "The v3 questionnaire API only accepts simple `form_elements`, not `graph`." + } + }, + "Missing label": { + "value": { + "form_elements": [ + { + "label": [ + "This field is required." + ] + } + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "415": { + "description": "Unsupported media type. Set `Content-Type: application/json` and send a JSON body." + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error while creating the questionnaire. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/questionnaires/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"title\": \"Customer Onboarding\",\n \"default_language\": \"en\",\n \"languages\": [\"en\"],\n \"form_elements\": [\n {\n \"id\": \"occupation\",\n \"element_type\": \"short_text\",\n \"label\": {\"en\": \"What is your occupation?\"},\n \"is_required\": true\n }\n ]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.questionnaires.create(\n title=\"Customer Onboarding\",\n description=\"Additional information needed for verification\",\n languages=[\"en\"],\n default_language=\"en\",\n is_active=True,\n form_elements=[{\n \"id\": \"occupation\",\n \"element_type\": \"short_text\",\n \"label\": {\"en\": \"What is your occupation?\"},\n \"placeholder\": {\"en\": \"Software engineer\"},\n \"is_required\": True,\n }, {\n \"id\": \"source_of_funds\",\n \"element_type\": \"multiple_choice\",\n \"label\": {\"en\": \"Source of funds\"},\n \"is_required\": True,\n \"options\": [{\n \"value\": \"employment\",\n \"label\": {\"en\": \"Employment\"},\n }, {\n \"value\": \"business\",\n \"label\": {\"en\": \"Business\"},\n }, {\n \"value\": \"investments\",\n \"label\": {\"en\": \"Investments\"},\n }, {\n \"value\": \"other\",\n \"label\": {\"en\": \"Other\"},\n }],\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.questionnaires.create({\n title: \"Customer Onboarding\",\n description: \"Additional information needed for verification\",\n languages: [\"en\"],\n default_language: \"en\",\n is_active: true,\n form_elements: [{\n id: \"occupation\",\n element_type: \"short_text\",\n label: { en: \"What is your occupation?\" },\n placeholder: { en: \"Software engineer\" },\n is_required: true,\n }, {\n id: \"source_of_funds\",\n element_type: \"multiple_choice\",\n label: { en: \"Source of funds\" },\n is_required: true,\n options: [{\n value: \"employment\",\n label: { en: \"Employment\" },\n }, {\n value: \"business\",\n label: { en: \"Business\" },\n }, {\n value: \"investments\",\n label: { en: \"Investments\" },\n }, {\n value: \"other\",\n label: { en: \"Other\" },\n }],\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Questionnaires.Create(ctx, didit.SimpleQuestionnaireRequest{\n\tTitle: \"Customer Onboarding\",\n\tDescription: didit.String(\"Additional information needed for verification\"),\n\tLanguages: []string{\"en\"},\n\tDefaultLanguage: didit.String(\"en\"),\n\tIsActive: didit.Bool(true),\n\tFormElements: []didit.SimpleQuestionnaireElement{{\n\t\tID: didit.String(\"occupation\"),\n\t\tElementType: \"short_text\",\n\t\tLabel: map[string]string{\"en\": \"What is your occupation?\"},\n\t\tPlaceholder: didit.Some(map[string]string{\"en\": \"Software engineer\"}),\n\t\tIsRequired: didit.Bool(true),\n\t}, {\n\t\tID: didit.String(\"source_of_funds\"),\n\t\tElementType: \"multiple_choice\",\n\t\tLabel: map[string]string{\"en\": \"Source of funds\"},\n\t\tIsRequired: didit.Bool(true),\n\t\tOptions: didit.Some([]didit.SimpleQuestionnaireElementOptions{{\n\t\t\tValue: didit.String(\"employment\"),\n\t\t\tLabel: map[string]string{\"en\": \"Employment\"},\n\t\t}, {\n\t\t\tValue: didit.String(\"business\"),\n\t\t\tLabel: map[string]string{\"en\": \"Business\"},\n\t\t}, {\n\t\t\tValue: didit.String(\"investments\"),\n\t\t\tLabel: map[string]string{\"en\": \"Investments\"},\n\t\t}, {\n\t\t\tValue: didit.String(\"other\"),\n\t\t\tLabel: map[string]string{\"en\": \"Other\"},\n\t\t}}),\n\t}},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SimpleQuestionnaireElement;\nimport me.didit.types.SimpleQuestionnaireRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.questionnaires().create(SimpleQuestionnaireRequest.builder()\n .title(\"Customer Onboarding\")\n .description(\"Additional information needed for verification\")\n .languages(List.of(\"en\"))\n .defaultLanguage(\"en\")\n .isActive(true)\n .formElements(List.of(\n SimpleQuestionnaireElement.builder()\n .id(\"occupation\")\n .elementType(SimpleQuestionnaireElement.ElementType.of(\"short_text\"))\n .label(Map.of(\"en\", \"What is your occupation?\"))\n .placeholder(Map.of(\"en\", \"Software engineer\"))\n .isRequired(true)\n .build(),\n SimpleQuestionnaireElement.builder()\n .id(\"source_of_funds\")\n .elementType(SimpleQuestionnaireElement.ElementType.of(\"multiple_choice\"))\n .label(Map.of(\"en\", \"Source of funds\"))\n .isRequired(true)\n .options(List.of(\n SimpleQuestionnaireElement.Options.builder()\n .value(\"employment\")\n .label(Map.of(\"en\", \"Employment\"))\n .build(),\n SimpleQuestionnaireElement.Options.builder()\n .value(\"business\")\n .label(Map.of(\"en\", \"Business\"))\n .build(),\n SimpleQuestionnaireElement.Options.builder()\n .value(\"investments\")\n .label(Map.of(\"en\", \"Investments\"))\n .build(),\n SimpleQuestionnaireElement.Options.builder()\n .value(\"other\")\n .label(Map.of(\"en\", \"Other\"))\n .build()))\n .build()))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "questionnaires->create([\n 'title' => 'Customer Onboarding',\n 'description' => 'Additional information needed for verification',\n 'languages' => ['en'],\n 'default_language' => 'en',\n 'is_active' => true,\n 'form_elements' => [[\n 'id' => 'occupation',\n 'element_type' => 'short_text',\n 'label' => ['en' => 'What is your occupation?'],\n 'placeholder' => ['en' => 'Software engineer'],\n 'is_required' => true,\n ], [\n 'id' => 'source_of_funds',\n 'element_type' => 'multiple_choice',\n 'label' => ['en' => 'Source of funds'],\n 'is_required' => true,\n 'options' => [[\n 'value' => 'employment',\n 'label' => ['en' => 'Employment'],\n ], [\n 'value' => 'business',\n 'label' => ['en' => 'Business'],\n ], [\n 'value' => 'investments',\n 'label' => ['en' => 'Investments'],\n ], [\n 'value' => 'other',\n 'label' => ['en' => 'Other'],\n ]],\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.questionnaires.create(\n title: \"Customer Onboarding\",\n description: \"Additional information needed for verification\",\n languages: [\"en\"],\n default_language: \"en\",\n is_active: true,\n form_elements: [{\n id: \"occupation\",\n element_type: \"short_text\",\n label: { en: \"What is your occupation?\" },\n placeholder: { en: \"Software engineer\" },\n is_required: true,\n }, {\n id: \"source_of_funds\",\n element_type: \"multiple_choice\",\n label: { en: \"Source of funds\" },\n is_required: true,\n options: [{\n value: \"employment\",\n label: { en: \"Employment\" },\n }, {\n value: \"business\",\n label: { en: \"Business\" },\n }, {\n value: \"investments\",\n label: { en: \"Investments\" },\n }, {\n value: \"other\",\n label: { en: \"Other\" },\n }],\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Questionnaires.CreateAsync(new SimpleQuestionnaireRequest\n{\n Title = \"Customer Onboarding\",\n Description = \"Additional information needed for verification\",\n Languages = new string[] { \"en\" },\n DefaultLanguage = \"en\",\n IsActive = true,\n FormElements = new SimpleQuestionnaireElement[]\n {\n new()\n {\n Id = \"occupation\",\n ElementType = \"short_text\",\n Label = new Dictionary { [\"en\"] = \"What is your occupation?\" },\n Placeholder = new Dictionary { [\"en\"] = \"Software engineer\" },\n IsRequired = true,\n },\n new()\n {\n Id = \"source_of_funds\",\n ElementType = \"multiple_choice\",\n Label = new Dictionary { [\"en\"] = \"Source of funds\" },\n IsRequired = true,\n Options = new SimpleQuestionnaireElementOptions[]\n {\n new()\n {\n Value = \"employment\",\n Label = new Dictionary { [\"en\"] = \"Employment\" },\n },\n new()\n {\n Value = \"business\",\n Label = new Dictionary { [\"en\"] = \"Business\" },\n },\n new()\n {\n Value = \"investments\",\n Label = new Dictionary { [\"en\"] = \"Investments\" },\n },\n new()\n {\n Value = \"other\",\n Label = new Dictionary { [\"en\"] = \"Other\" },\n },\n },\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/questionnaires/{questionnaire_uuid}/": { + "get": { + "summary": "Get questionnaire", + "description": "Fetch full questionnaire detail for one version by `questionnaire_uuid`: title, `languages`, canonical `graph`, derived `sections`, and version metadata.", + "operationId": "get_questionnaire", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "parameters": [ + { + "name": "questionnaire_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the questionnaire (the `uuid` value returned by the list endpoint, also returned as `questionnaire_id` on detail endpoints)." + } + ], + "responses": { + "200": { + "description": "Full questionnaire detail including the canonical `graph`, the derived `sections` view, version metadata, and `is_editable`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QuestionnaireDetail" + }, + "examples": { + "Published questionnaire": { + "value": { + "questionnaire_id": "11111111-2222-3333-4444-555555555555", + "title": "Customer Onboarding", + "description": "Additional information needed for verification", + "languages": [ + "en" + ], + "default_language": "en", + "is_active": true, + "is_simple_questionnaire": true, + "graph": { + "start_node": "occupation", + "nodes": { + "occupation": { + "element_type": "SHORT_TEXT", + "is_required": true, + "title": { + "en": "What is your occupation?" + }, + "next": "source_of_funds" + }, + "source_of_funds": { + "element_type": "MULTIPLE_CHOICE", + "is_required": true, + "title": { + "en": "Source of funds" + }, + "choices": [ + { + "value": "employment", + "label": { + "en": "Employment" + } + }, + { + "value": "business", + "label": { + "en": "Business" + } + } + ], + "next": null + } + } + }, + "sections": [ + { + "title": {}, + "description": {}, + "items": [ + { + "uuid": "11111111-2222-3333-4444-666666666666", + "value": "occupation", + "element_type": "SHORT_TEXT", + "is_required": true, + "title": { + "en": "What is your occupation?" + } + } + ] + } + ], + "questionnaire_group_id": "11111111-2222-3333-4444-555555555555", + "version": 1, + "status": "published", + "published_at": "2026-04-23T10:00:00Z", + "is_editable": false + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No questionnaire with this `questionnaire_uuid` exists for the authenticated application. Either the UUID is wrong or it belongs to another application — the API deliberately does not distinguish the two cases.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/questionnaires/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.questionnaires.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.questionnaires.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Questionnaires.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.questionnaires().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "questionnaires->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.questionnaires.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Questionnaires.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "patch": { + "summary": "Update questionnaire", + "description": "Update a questionnaire. Versioning depends on the `status` field you send and the current state of the version: (1) no `status` on a **published** version → the published version is updated in place (same `questionnaire_id`, same `version`); (2) no `status` on a **draft** → the draft is updated and immediately published; (3) `status: \"published\"` on a **published** version → a NEW published version is created under the same `questionnaire_group_id` (new `questionnaire_id`, `version` + 1) and returned, and any existing draft in the group is deleted; (4) `status: \"draft\"` keeps a draft as a draft (autosave) but is rejected with 400 on a published version (`\"Cannot revert a published version to draft.\"`).", + "operationId": "update_questionnaire", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "parameters": [ + { + "name": "questionnaire_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the questionnaire to update." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleQuestionnaireUpdateRequest" + }, + "examples": { + "Rename": { + "summary": "Rename a questionnaire", + "value": { + "title": "Updated Customer Onboarding" + } + }, + "Replace questions": { + "summary": "Replace the full linear question list", + "value": { + "form_elements": [ + { + "id": "occupation", + "element_type": "short_text", + "label": { + "en": "What is your occupation?" + }, + "is_required": true + }, + { + "id": "source_of_funds", + "element_type": "multiple_choice", + "label": { + "en": "Source of funds" + }, + "options": [ + { + "value": "employment", + "label": { + "en": "Employment" + } + }, + { + "value": "business", + "label": { + "en": "Business" + } + } + ] + } + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Questionnaire updated. The body is the latest state of the resulting version — the in-place updated version, or the newly created published version when you sent `status: \"published\"` on a published row (in that case `questionnaire_id` and `version` differ from the ones you patched). `questionnaire_id` in the response is the per-version UUID; use `questionnaire_group_id` if you need the stable group identifier.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QuestionnaireDetail" + } + } + } + }, + "400": { + "description": "Validation error — unknown `element_type`, missing translation, unsupported branching/conditional fields, `languages` missing `en`, `default_language` not in `languages`, option without a label, sending a raw `graph`, or `status: \"draft\"` on a published version (`\"Cannot revert a published version to draft.\"`). Response body is a DRF validation error keyed by field name." + }, + "403": { + "description": "API key missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No questionnaire with this `questionnaire_uuid` exists for the authenticated application." + }, + "415": { + "description": "Unsupported media type. Set `Content-Type: application/json`." + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X PATCH 'https://verification.didit.me/v3/questionnaires/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"title\": \"Updated Customer Onboarding\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.questionnaires.update(\n \"11111111-2222-3333-4444-555555555555\",\n title=\"Updated Customer Onboarding\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.questionnaires.update(\"11111111-2222-3333-4444-555555555555\", {\n title: \"Updated Customer Onboarding\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Questionnaires.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SimpleQuestionnaireUpdateRequest{\n\tTitle: didit.String(\"Updated Customer Onboarding\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SimpleQuestionnaireUpdateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.questionnaires().update(\"11111111-2222-3333-4444-555555555555\", SimpleQuestionnaireUpdateRequest.builder()\n .title(\"Updated Customer Onboarding\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "questionnaires->update('11111111-2222-3333-4444-555555555555', [\n 'title' => 'Updated Customer Onboarding',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.questionnaires.update(\n \"11111111-2222-3333-4444-555555555555\",\n title: \"Updated Customer Onboarding\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Questionnaires.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new SimpleQuestionnaireUpdateRequest\n{\n Title = \"Updated Customer Onboarding\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "summary": "Delete questionnaire", + "description": "Permanently delete one questionnaire version (no undo). Stored questionnaire responses linked to this version are deleted with it, and sessions referencing it lose the link; other versions in the same group are not deleted. Workflows that referenced it keep running but the questionnaire step can no longer load it.", + "operationId": "delete_questionnaire", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "parameters": [ + { + "name": "questionnaire_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the questionnaire to delete." + } + ], + "responses": { + "204": { + "description": "Questionnaire deleted. The response body is empty." + }, + "403": { + "description": "API key missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No questionnaire with this `questionnaire_uuid` exists for the authenticated application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X DELETE 'https://verification.didit.me/v3/questionnaires/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.questionnaires.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.questionnaires.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Questionnaires.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.questionnaires().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "questionnaires->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.questionnaires.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Questionnaires.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ] + } + }, + "/v3/users/": { + "get": { + "summary": "List users", + "description": "List [users](/entities/users) Didit assembles from KYC sessions sharing the same `vendor_data` (free-form string, NOT a UUID), plus users created via the API. Paginated, ordered by `last_session_at` desc. This endpoint does not support filtering or search parameters — fetch pages and filter client-side.", + "operationId": "list_users", + "x-didit-sdk": { + "resource": "users", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "description": "Page size. Defaults to 50 when omitted. There is no enforced maximum, but keep pages small for latency." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "description": "Zero-based offset of the first record to return. Prefer following the `next` URL from the previous page over computing this by hand." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/users/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.users.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.users.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Users.ListAutoPaging(ctx, didit.UsersListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.users().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.users.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Users.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated list of users with verification status, session counts, and feature breakdown.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows — exact up to 100, capped at 100 beyond that for performance. Do not use it to detect the last page; rely on `next` being `null` instead." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserListItem" + } + } + } + }, + "examples": { + "Example": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": null, + "full_name": "John Michael Doe", + "date_of_birth": "1990-05-15", + "effective_name": "John Michael Doe", + "status": "ACTIVE", + "portrait_image_url": "https:///...", + "session_count": 3, + "approved_count": 2, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [ + "USA" + ], + "approved_emails": [ + "john@example.com" + ], + "approved_phones": [ + "+14155551234" + ], + "features": { + "ID_VERIFICATION": "Approved", + "LIVENESS": "Approved", + "FACE_MATCH": "Approved", + "AML": "Approved" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-01T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2025-06-01T08:00:00Z" + }, + { + "didit_internal_id": "6ed99d53-e8f5-4cf8-9ac1-4a506cb40f6b", + "vendor_data": null, + "display_name": "Jane S.", + "full_name": "Jane Elizabeth Smith", + "date_of_birth": "1985-11-22", + "effective_name": "Jane S.", + "status": "FLAGGED", + "portrait_image_url": null, + "session_count": 1, + "approved_count": 0, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [], + "approved_emails": [], + "approved_phones": [], + "features": { + "ID_VERIFICATION": "In Review" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "In Review" + } + ], + "last_session_at": "2025-06-20T14:00:00Z", + "last_activity_at": "2025-06-20T14:05:00Z", + "first_session_at": "2025-06-20T14:00:00Z", + "tags": [], + "created_at": "2025-06-20T14:00:00Z" + } + ] + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "The key cannot list users for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/users/{vendor_data}/": { + "get": { + "summary": "Get user", + "description": "Fetch one [user](/entities/users) by `vendor_data` (exact match). Adds `metadata`, `comments`, and `updated_at` to the list view, and switches `tags` to the detailed tag-link shape.", + "operationId": "get_user", + "x-didit-sdk": { + "resource": "users", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the user — a free-form string (NOT a UUID). This is the same value you passed as `vendor_data` when creating the session that first introduced this user, matched exactly as sent.", + "example": "user-abc-123" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/users/user-abc-123/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.users.retrieve(\n \"user-abc-123\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.users.retrieve(\"user-abc-123\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Users.Retrieve(ctx, \"user-abc-123\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.users().retrieve(\"user-abc-123\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->retrieve('user-abc-123');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.users.retrieve(\"user-abc-123\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Users.RetrieveAsync(\"user-abc-123\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Full user detail including metadata, comments/activity log, and aggregated session data.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDetailItem" + }, + "examples": { + "Active user": { + "value": { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": null, + "full_name": "John Michael Doe", + "date_of_birth": "1990-05-15", + "effective_name": "John Michael Doe", + "status": "ACTIVE", + "metadata": { + "tier": "premium", + "source": "website" + }, + "portrait_image_url": "https:///...", + "session_count": 3, + "approved_count": 2, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [ + "USA" + ], + "approved_emails": [ + "john@example.com" + ], + "approved_phones": [ + "+14155551234" + ], + "features": { + "ID_VERIFICATION": "Approved", + "LIVENESS": "Approved", + "FACE_MATCH": "Approved", + "AML": "Approved" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-01T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "tag": { + "uuid": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b", + "name": "VIP", + "color": "#2567FF", + "description": null, + "source": "custom", + "created_at": "2025-05-01T09:00:00Z", + "updated_at": "2025-05-01T09:00:00Z" + }, + "added_by_email": "analyst@acme.com", + "added_by_name": "Jane Analyst", + "created_at": "2025-06-02T11:00:00Z" + } + ], + "comments": [ + { + "uuid": "c1111111-2222-4333-8444-555555555555", + "comment_type": "STATUS_CHANGED", + "comment": null, + "actor_email": "analyst@acme.com", + "actor_name": "Jane Analyst", + "previous_status": "FLAGGED", + "new_status": "ACTIVE", + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "mentioned_emails": [], + "created_at": "2025-06-01T10:30:00Z" + } + ], + "created_at": "2025-06-01T08:00:00Z", + "updated_at": "2025-06-15T10:30:00Z" + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "Invalid or revoked API key, or the key cannot read this application's users. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No user with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update user", + "description": "Partial update of a user: `display_name`, `full_name`, `date_of_birth`, `status`, `metadata`, `approved_emails`/`approved_phones`/`issuing_states`. `metadata` fully replaces. Changing `status` here works like [Update User Status](#patch-/v3/users/-vendor_data-/update-status/): it records a `STATUS_CHANGED` activity with the optional `reason`, and `BLOCKED` adds the `vendor_data` to the system blocklist (moving away from `BLOCKED` removes that entry). Changes to `display_name`, `full_name`, `date_of_birth` or `metadata` are recorded as a `PROFILE_UPDATED` activity.", + "operationId": "update_user", + "x-didit-sdk": { + "resource": "users", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the user (free-form string, NOT a UUID).", + "example": "user-abc-123" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/users/user-abc-123/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"display_name\": \"Jane S.\", \"status\": \"FLAGGED\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.users.update(\n \"user-abc-123\",\n display_name=\"Jane S.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.users.update(\"user-abc-123\", {\n display_name: \"Jane S.\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Users.Update(ctx, \"user-abc-123\", didit.UsersUpdateParams{\n\tDisplayName: didit.String(\"Jane S.\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.UsersUpdateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.users().update(\"user-abc-123\", UsersUpdateParams.builder()\n .displayName(\"Jane S.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->update('user-abc-123', [\n 'display_name' => 'Jane S.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.users.update(\n \"user-abc-123\",\n display_name: \"Jane S.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Users.UpdateAsync(\"user-abc-123\", new UsersUpdateParams\n{\n DisplayName = \"Jane S.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "User updated. The full updated user record (same shape as Get User) is returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDetailItem" + }, + "examples": { + "Updated": { + "value": { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": "Jane S.", + "full_name": "Jane Elizabeth Smith", + "date_of_birth": "1985-11-22", + "effective_name": "Jane S.", + "status": "FLAGGED", + "metadata": { + "tier": "premium" + }, + "portrait_image_url": "https:///...", + "session_count": 3, + "approved_count": 2, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [ + "USA" + ], + "approved_emails": [ + "john@example.com" + ], + "approved_phones": [ + "+14155551234" + ], + "features": { + "ID_VERIFICATION": "Approved", + "LIVENESS": "Approved", + "FACE_MATCH": "Approved", + "AML": "Approved" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-01T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "tag": { + "uuid": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b", + "name": "VIP", + "color": "#2567FF", + "description": null, + "source": "custom", + "created_at": "2025-05-01T09:00:00Z", + "updated_at": "2025-05-01T09:00:00Z" + }, + "added_by_email": "analyst@acme.com", + "added_by_name": "Jane Analyst", + "created_at": "2025-06-02T11:00:00Z" + } + ], + "comments": [], + "created_at": "2025-06-01T08:00:00Z", + "updated_at": "2025-06-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Invalid body — typically an unknown `status` or a `date_of_birth` not in `YYYY-MM-DD`.", + "content": { + "application/json": { + "examples": { + "Bad status": { + "value": { + "status": [ + "\"INVALID\" is not a valid choice." + ] + } + }, + "Bad date_of_birth": { + "value": { + "date_of_birth": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + }, + "Reason too long": { + "value": { + "reason": [ + "Ensure this field has no more than 1000 characters." + ] + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "The key cannot update users for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No user with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "full_name": { + "type": "string", + "nullable": true, + "maxLength": 512, + "description": "Full name of the user" + }, + "display_name": { + "type": "string", + "nullable": true, + "description": "Custom display name for this user" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth in YYYY-MM-DD format" + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "User lifecycle status. A change is recorded as a `STATUS_CHANGED` activity, and `BLOCKED` adds the `vendor_data` to the system blocklist (moving away from `BLOCKED` removes that entry)." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Arbitrary JSON object you attach to the user. **Fully replaces** the existing metadata on update — merge client-side if you only want to add keys." + }, + "approved_emails": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Verified email addresses for this user, e.g. `[\"john@example.com\"]`. Fully replaces the stored list." + }, + "approved_phones": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Verified phone numbers for this user. Fully replaces the stored list." + }, + "issuing_states": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Issuing countries (ISO 3166-1 alpha-3), e.g. `[\"USA\"]`. Fully replaces the stored list." + }, + "reason": { + "type": "string", + "nullable": true, + "maxLength": 1000, + "description": "Why the status changed. Recorded on the status-change entry of the user's activity (`comments[].comment`, and **Activity** in the Business Console), only when the status actually changes. Webhooks do not carry it." + } + } + }, + "examples": { + "Rename": { + "summary": "Set display name", + "value": { + "display_name": "Jane S." + } + }, + "Override status": { + "summary": "Flag the user for review", + "value": { + "status": "FLAGGED" + } + }, + "Add metadata": { + "summary": "Replace metadata JSON", + "value": { + "metadata": { + "tier": "premium", + "risk_level": "low" + } + } + }, + "Fix extracted DOB": { + "summary": "Correct extracted profile fields", + "value": { + "full_name": "Jane Elizabeth Smith", + "date_of_birth": "1985-11-22" + } + }, + "Flag with reason": { + "summary": "Flag the user and record why", + "value": { + "status": "FLAGGED", + "reason": "Address does not match the billing country" + } + } + } + } + } + } + } + }, + "/v3/users/{vendor_data}/generate-pdf/": { + "get": { + "summary": "Download the full user history PDF report", + "description": "Render and download one compliance-ready PDF containing the full verification history of the user identified by `vendor_data`. It is the same document the Console produces from **User detail → Actions → Download PDF**, so periodic compliance exports no longer need a person clicking in the Console.\n\n**What the report contains.** A cover page with the profile summary (name, date of birth, issuing states, approved emails and phones), the latest status of every feature, the session counters and an index of the sessions included — followed by the standard report of every included session, in chronological order (oldest first). Each session report is the same document `GET /v3/session/{sessionId}/generate-pdf/` returns for that session.\n\n**Eligible sessions.** Only User Verification (KYC) sessions in `Approved`, `Declined`, `In Review` or `Kyc Expired` are included; sessions in any other status are skipped. If the user has no session in one of those statuses the endpoint returns `403` — see below.\n\n**Cap.** The report is capped at the **20 most recent** reportable sessions. When a user has more, the newest 20 are included and the cover page states how many older sessions were left out. Use `GET /v3/sessions/?vendor_data=…` for the complete list as JSON.\n\n**Branding.** If your application has white-label customization, the cover and every session report are rendered with your logo and privacy-policy URL; otherwise they carry Didit branding. Reports are rendered in English — there is no language parameter.\n\n**No caching.** Every call re-renders the PDF from the current data, so a report generated after a manual review reflects the reviewer's decision. Two calls for the same user can produce byte-different files — archive the downloaded file if you need an immutable copy.\n\n**Latency.** Rendering is synchronous and downloads every stored image of every included session, so a user with many media-heavy sessions can take tens of seconds. Use a generous client read timeout (120 s recommended) and stream the body to disk. Generation stops at a server-side time budget instead of running until your client gives up: if that budget is reached, the response is still a valid PDF containing the most recent sessions, and the cover states how many were omitted.\n\n**Scope.** The user is resolved inside the application that owns the API key. A key can never export a user of another application — such a user does not exist for it and the endpoint returns `404`.\n\n**Trailing slash.** The canonical route ends with a trailing slash (`…/generate-pdf/`). A request without it is served directly with the same response — there is no `301` redirect, so no client has to follow redirects and `curl` does not need `-L`. Use the slashed URL as in the samples.", + "operationId": "get_user_generate_pdf", + "x-didit-sdk": { + "resource": "users", + "method": "generate_pdf", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the user — a free-form string (NOT a UUID). This is the same value you passed as `vendor_data` when creating the user's sessions, matched exactly as sent. URL-encode it; a value containing `/` cannot be addressed by this route.", + "example": "user-abc-123" + } + ], + "responses": { + "200": { + "description": "The rendered PDF document, returned directly as binary `application/pdf` — there is no JSON wrapper and no download-URL indirection. The body starts with the `%PDF` magic bytes. Save it to a `.pdf` file or stream it through to your caller.", + "headers": { + "Content-Disposition": { + "description": "`attachment; filename=user_{didit_internal_id}.pdf` — the file is named after the user's stable Didit identifier (`didit_internal_id` on `GET /v3/users/{vendor_data}/`), not after `vendor_data`.", + "schema": { + "type": "string", + "example": "attachment; filename=user_f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418.pdf" + } + }, + "Content-Length": { + "description": "Total size of the PDF in bytes; the body is not chunked.", + "schema": { + "type": "integer", + "example": 148213 + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "Authorization or eligibility failure. Distinguish the two by the `detail` string:\n\n- `\"You do not have permission to perform this action.\"` — the API key is invalid or revoked, or the request carries a Console user token instead of an application API key.\n- `\"This user has no sessions in review, declined, approved or kyc expired to report.\"` — the user exists in your application but none of its sessions is in a reportable status yet (for example every session is still `In Progress`, or the user was created through `POST /v3/users/create/` and has never verified). Wait for a `status.updated` webhook before requesting the report.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "This user has no sessions in review, declined, approved or kyc expired to report." + } + } + }, + "examples": { + "Invalid credentials": { + "summary": "Revoked or invalid API key, or a Console user token", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "No reportable sessions": { + "summary": "User exists but has no Approved / Declined / In Review / Kyc Expired session", + "value": { + "detail": "This user has no sessions in review, declined, approved or kyc expired to report." + } + } + } + } + } + }, + "404": { + "description": "No user with the supplied `vendor_data` exists in the application behind the API key. Users of other applications are invisible to this key and return the same `404`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "User not found." + } + } + }, + "examples": { + "Unknown user": { + "summary": "vendor_data does not match any user of this application", + "value": { + "detail": "User not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. PDF generation has a dedicated limit of **50 requests per minute per credential** (API key, Bearer token, or source IP when unauthenticated), shared between this endpoint and `GET /v3/session/{sessionId}/generate-pdf/`, in addition to the global 600/min GET limit. Wait `Retry-After` seconds (also reflected in the `X-RateLimit-*` headers), then retry. Reports are not cached server-side, so download each report once and store the file instead of re-fetching it.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying the request.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Maximum number of PDF generations allowed in the current window (50).", + "schema": { + "type": "integer", + "example": 50 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests remaining in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix timestamp (seconds) at which the current window resets.", + "schema": { + "type": "integer", + "example": 1750000000 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable explanation of the rate-limit breach.", + "example": "Session PDF generation rate limit exceeded. You can make up to 50 requests per minute." + } + } + }, + "examples": { + "Throttled": { + "summary": "More than 50 PDF generations in one minute from the same credential", + "value": { + "detail": "Session PDF generation rate limit exceeded. You can make up to 50 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --fail \\\n 'https://verification.didit.me/v3/users/user-abc-123/generate-pdf/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n --output user-history.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.users.generate_pdf(\n \"user-abc-123\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.users.generatePdf(\"user-abc-123\");\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Users.GeneratePDF(ctx, \"user-abc-123\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.users().generatePdf(\"user-abc-123\")\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->generatePdf('user-abc-123');\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.users.generate_pdf(\"user-abc-123\")\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Users.GeneratePdfAsync(\"user-abc-123\");\nawait content.SaveToAsync(\"download.pdf\");" + } + ] + } + }, + "/v3/users/delete/": { + "post": { + "summary": "Batch delete users", + "description": "**Permanently delete** users: the user records are removed permanently. For everyday blocking prefer [Update User Status](#patch-/v3/users/-vendor_data-/update-status/) with `BLOCKED`. Select users with `vendor_data_list`, with `didit_internal_id_list`, or with both at once; or pass `delete_all: true`. This endpoint never narrows `delete_all` with filters. The Console's People filters are refused with a `400` (see the `400` response), so a narrowed intent does not turn into a full wipe. Two exceptions: `filter_has_approved_sessions` and `filter_has_declined_sessions` are not refused and not applied, so with `delete_all: true` they still delete every user; do not send them.\n\n**Biometric templates:** unless you set `retain_face_embeddings` to `true`, every image-free biometric template retained for these users after session deletion (see `/v3/biometric-templates/`) is purged before the user is deleted, so the person stops matching in duplicate detection and Face Search. With `retain_face_embeddings: true` and `face_retention_days`, you choose to keep one image-free biometric template per person for that many days, and nothing else about the person is kept. If a template cannot be purged because a biometric store is unavailable, the call returns `503`, no user in the request is deleted, and repeating the request finishes the job.\n\n**Filed regulatory reports:** if the selected data is in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "batch_delete_users", + "x-didit-sdk": { + "resource": "users", + "method": "batch_delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Provide `vendor_data_list`, `didit_internal_id_list`, or `delete_all: true`. The two lists can be combined and are OR'd. When `delete_all` is `true` it takes precedence and both lists are ignored.", + "properties": { + "vendor_data_list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Your own identifiers to delete, matched exactly as sent." + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit internal user IDs to delete - the `didit_internal_id` returned by the create and get endpoints." + }, + "delete_all": { + "type": "boolean", + "default": false, + "description": "If `true`, deletes every user in the application." + }, + "retain_face_embeddings": { + "type": "boolean", + "default": false, + "description": "Your explicit choice to keep one image-free biometric template of each deleted person for duplicate detection, for `face_retention_days`. Everything else about the person is deleted. Omit it, or send `false`, to delete the templates with the users. When `true`, `face_retention_days` is required." + }, + "face_retention_days": { + "type": "integer", + "minimum": 1, + "maximum": 3650, + "description": "Number of days the image-free biometric template is kept when `retain_face_embeddings` is `true`. Required with it, and rejected without it with a `400` on `face_retention_days`.", + "example": 365 + } + } + }, + "examples": { + "Specific users": { + "summary": "Delete users by vendor_data", + "value": { + "vendor_data_list": [ + "user-123", + "user-456" + ] + } + }, + "By Didit internal ID": { + "summary": "Delete users by didit_internal_id", + "value": { + "didit_internal_id_list": [ + "3f1c0d3e-8f0a-4c2e-9a3d-9b1f5f4a2c11" + ] + } + }, + "All users": { + "summary": "Wipe every user in this application", + "value": { + "delete_all": true + } + }, + "Keep face templates": { + "summary": "Delete users and keep anonymous face templates for 365 days", + "value": { + "vendor_data_list": [ + "user-123" + ], + "retain_face_embeddings": true, + "face_retention_days": 365 + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/users/delete/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vendor_data_list\": [\"user-123\", \"user-456\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.users.batch_delete(\n vendor_data_list=[\"user-123\", \"user-456\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.users.batchDelete({\n vendor_data_list: [\"user-123\", \"user-456\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Users.BatchDelete(ctx, didit.UsersBatchDeleteParams{\n\tVendorDataList: didit.Some([]string{\"user-123\", \"user-456\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.UsersBatchDeleteParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.users().batchDelete(UsersBatchDeleteParams.builder()\n .vendorDataList(List.of(\"user-123\", \"user-456\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->batchDelete([\n 'vendor_data_list' => ['user-123', 'user-456'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.users.batch_delete(vendor_data_list: [\"user-123\", \"user-456\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Users.BatchDeleteAsync(new UsersBatchDeleteParams\n{\n VendorDataList = new string[] { \"user-123\", \"user-456\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Users deleted. Returns the number of users actually deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deleted": { + "type": "integer", + "description": "Number of users deleted (excludes vendor_data values that didn't match anything)." + } + } + }, + "examples": { + "Deleted some": { + "value": { + "deleted": 2 + } + }, + "Nothing matched": { + "value": { + "deleted": 0 + } + } + } + } + } + }, + "400": { + "description": "Validation error; nothing is deleted. Errors are keyed by field name. With no selector (neither list and `delete_all` not `true`) the body is `{\"vendor_data_list\": [\"This field is required unless delete_all is true.\"]}`; an empty list is refused under its own key with `\"This list may not be empty.\"`. A Console-only filter (`filter_search`, `filter_status`, `filter_country`, `filter_date_from`, `filter_date_to`, `filter_tag`, `filter_latest_verification_status`, `filter_risk`, `filter_has_sessions`, `filter_has_open_alerts`, `filter_aml_monitoring`, `filter_can_sign_in`, `filter_auth_status`, `filter_auth_method` or `filter_auth_mfa_enabled`) is refused whenever it carries a value, even `false`, with an object keyed by each such field. Any other unknown `filter_*` key is refused with `\"Unknown filter.\"`.", + "content": { + "application/json": { + "examples": { + "Missing selector": { + "summary": "Neither list nor delete_all provided", + "value": { + "vendor_data_list": [ + "This field is required unless delete_all is true." + ] + } + }, + "Console-only filter": { + "value": { + "filter_has_sessions": "This filter is Console-only and is not supported by this endpoint." + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "Invalid or revoked API key, or the key cannot delete users for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + }, + "503": { + "description": "A biometric store was unavailable while retaining or purging a biometric template. Nothing is deleted; repeat the same request. Retries are idempotent and converge on a single template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Retryable": { + "value": { + "detail": "The biometric-template operation could not finish because an external store is unavailable. The operation is retryable." + } + } + } + } + } + }, + "409": { + "description": "Nothing is deleted. `code` says why: `in_filed_report` when the selection includes a person in a finalized regulatory report, `subject_deletion_in_progress` when another request is still deleting one of the selected people. Repeat the request once that deletion has finished.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/InFiledReportError" + }, + { + "$ref": "#/components/schemas/SubjectDeletionInProgressError" + } + ] + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/sessions/delete/": { + "post": { + "summary": "Bulk delete KYC sessions by session_number", + "description": "Bulk delete User Verification (KYC) sessions by their numeric `session_number`, or every KYC session in your application with `delete_all: true`.\n\n`session_number` is the human-friendly incrementing counter shown in the Console session list and returned as `session_number` by the create-session and decision endpoints; it is **not** the `session_id` UUID. To delete a session by UUID, or to delete a Business Verification (KYB) session, which this endpoint never touches, use `DELETE /v3/session/{sessionId}/delete/` instead.\n\n**Up to 20 sessions are deleted in the request, more run as a job.** The number of matching sessions decides, whether they are selected by `session_numbers` or with `delete_all`:\n- Up to 20 matching sessions: they are deleted before the response, which is `200` with one entry per session in `results[]`.\n- More than 20: the response is `202` with a deletion job, and the sessions are deleted in the background. Poll `GET /v3/sessions/delete/{job_id}/` until `status` is `COMPLETED` or `FAILED`. A job reports counts per outcome, never which sessions it deleted. A `delete_all` job deletes only the sessions created before the request; sessions started while it runs are not deleted.\n\nEach deleted session gets exactly the same treatment as the single-session delete endpoint: the session, every verification attempt it holds, its decision and extracted data, its related feature records (ID verification, face, liveness, face match, and so on) and all of its stored files are deleted, it disappears from every read endpoint, and previously issued media URLs stop resolving. Webhook deliveries still queued for a deleted session, and their retries, are dropped; webhooks already delivered are not affected. Blocklist entries are **not** removed, credits already consumed are **not** refunded, and the parent User entities are **not** deleted (use `POST /v3/users/delete/` for those); no webhook is emitted for the deletion. Deletion is permanent and irreversible.\n\n**Selection semantics:**\n- With `delete_all: true` you can narrow the selection with the filters of `GET /v3/sessions/`, each prefixed with `filter_` (for example `filter_status`, `filter_workflow_id`, `filter_date_from`). Filters that take several values in List Sessions (`status`, `workflow_id`, `country`, `region`, `document_type`, `document_name`, `session_type`, `warning`, `organization_name`) accept a comma-separated string or an array; the others take one value. An empty value is ignored. Filters apply only together with `delete_all`, and a deletion job applies the same filters.\n- An unknown `filter_*` key, or a filter value that cannot be read, rejects the whole request with `400` before anything is deleted, so a mistyped filter never widens the deletion to every session.\n- Entries in `session_numbers` that do not match a live KYC session in your application (unknown numbers, already-deleted sessions, or sessions owned by another application) are **silently skipped**: they do not appear in `results` and are not counted in a job's `total`.\n- Validation failures (a non-numeric entry, an empty list, neither `session_numbers` nor `delete_all` provided, or a template retention the application cannot honour) reject the **whole request** with `400` and delete nothing.\n- Sessions are deleted in groups. If a group cannot be deleted (for example because a storage service is unavailable), every session in that group is reported as `face_retention_outcome: failed_retryable` and stays in place; the other groups are unaffected. Repeat the request for those sessions. A job retries such a group by itself.\n- There is no documented cap on the list length; the shared write rate limit (below) is the practical bound.\n\n**Face biometric data:** the optional `retain_face_embeddings`, `face_retention_days`, `face_retention_deadline`, `deletion_instruction`, and `instruction_id` fields have the same meaning as on `DELETE /v3/session/{sessionId}/delete/` and apply to every matched session. Each result reports its own `face_retention_outcome`.\n\n**Response:** `200 OK` with `results[]` for up to 20 matching sessions, or `202 Accepted` with a deletion job for more. Every result confirms deletion except `face_retention_outcome: failed_retryable`, which reports a session that was NOT deleted and should be retried. Earlier versions of this endpoint returned `204 No Content`; update clients that assert on `204`.\n\n**Idempotency:** repeating the same request deletes nothing more: numbers that no longer match a session are skipped.\n\n**`delete_all: true` (destructive):** deletes **every** remaining KYC session in the application; `session_numbers` is ignored when set. There is no confirmation step and no undo.\n\n**Authentication:** API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`); this API never returns `401`.\n\n**Rate limit:** shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.\n\nThis endpoint accepts API keys only. Business Console user Bearer tokens are rejected with `403` (unlike the single-session delete, which accepts both).\n\n**Filed regulatory reports:** if the selected data is in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "batch_delete_sessions", + "x-didit-sdk": { + "resource": "sessions", + "method": "batch_delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_numbers": { + "type": "array", + "description": "Numeric `session_number` values of the KYC sessions to delete, as digit-only strings (JSON numbers are also accepted and coerced). Required unless `delete_all` is `true`; an empty list returns `400`. Any non-digit entry rejects the whole request with `400` (unless `delete_all` is `true`, in which case validation is skipped and the entries are ignored). Numbers that do not match a live session in your application are silently skipped.", + "items": { + "type": "string", + "pattern": "^[0-9]+$", + "example": "1001" + }, + "minItems": 1, + "example": [ + "1001", + "1002", + "1003" + ] + }, + "delete_all": { + "type": "boolean", + "description": "When `true`, every remaining KYC session in the calling application is deleted and `session_numbers` is ignored, unless you narrow the selection with `filter_*` keys. More than 20 matching sessions run as a deletion job (`202`) that covers only the sessions created before the request. Defaults to `false`. Irreversible; there is no confirmation step.", + "default": false, + "example": false + }, + "retain_face_embeddings": { + "type": "boolean", + "nullable": true, + "description": "Override the application's `face_retention_policy` for this deletion. `true` keeps one image-free face biometric template anchored to the session's User after the session is deleted; `false` deletes the face embedding with the session; omit or `null` to follow the application policy. Ignored for KYB sessions and for sessions without a face embedding.", + "example": true + }, + "face_retention_days": { + "type": "integer", + "minimum": 1, + "maximum": 3650, + "description": "Finite retention duration, in days, for a retained template. Required when a template is retained unless the application already sets `face_retention_days`. The template's `expires_at` is the earliest of this duration, the application's general data-retention window, and `face_retention_deadline`.", + "example": 365 + }, + "face_retention_deadline": { + "type": "string", + "format": "date-time", + "description": "Optional hard expiry for a retained template. Must be in the future.", + "example": "2027-08-28T00:00:00Z" + }, + "deletion_instruction": { + "type": "string", + "enum": [ + "operational_session_delete", + "privacy_erasure" + ], + "default": "operational_session_delete", + "description": "The class of instruction you are giving. `operational_session_delete` deletes the session and retains a template only when the policy or `retain_face_embeddings` says so. `privacy_erasure` purges every retained biometric template anchored to the session's User before deleting the session, cannot be overridden by the application policy, and cannot be combined with `retain_face_embeddings: true` (`400`).", + "example": "operational_session_delete" + }, + "instruction_id": { + "type": "string", + "maxLength": 255, + "description": "Your durable reference for this deletion instruction (for example an erasure-ticket id). Recorded on the audit trail and on any retained template. Generated by Didit when omitted.", + "example": "dsar-2026-0142" + }, + "filter_status": { + "description": "Narrows a `delete_all` to the sessions that match the `status` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_vendor_data": { + "description": "Narrows a `delete_all` to the sessions that match the `vendor_data` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated text, so several values are not matched as alternatives. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string" + }, + "filter_search": { + "description": "Narrows a `delete_all` to the sessions that match the `search` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated text, so several values are not matched as alternatives. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string" + }, + "filter_workflow_id": { + "description": "Narrows a `delete_all` to the sessions that match the `workflow_id` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_date_from": { + "description": "Narrows a `delete_all` to the sessions that match the `date_from` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string", + "format": "date" + }, + "filter_date_to": { + "description": "Narrows a `delete_all` to the sessions that match the `date_to` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string", + "format": "date" + }, + "filter_country": { + "description": "Narrows a `delete_all` to the sessions that match the `country` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_document_type": { + "description": "Narrows a `delete_all` to the sessions that match the `document_type` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_document_name": { + "description": "Narrows a `delete_all` to the sessions that match the `document_name` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_region": { + "description": "Narrows a `delete_all` to the sessions that match the `region` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_didit_internal_id": { + "description": "Narrows a `delete_all` to the sessions that match the `didit_internal_id` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string", + "format": "uuid" + }, + "filter_session_type": { + "description": "Narrows a `delete_all` to the sessions that match the `session_type` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_api_service": { + "description": "Narrows a `delete_all` to the sessions that match the `api_service` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string" + }, + "filter_screened_full_name": { + "description": "Narrows a `delete_all` to the sessions that match the `screened_full_name` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated text, so several values are not matched as alternatives. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string" + }, + "filter_warning": { + "description": "Narrows a `delete_all` to the sessions that match the `warning` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_status_initialized_as_in_review": { + "description": "Narrows a `delete_all` to the sessions that match the `status_initialized_as_in_review` filter of [List Sessions](/sessions-api/list-sessions), with the same values. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "boolean" + }, + "filter_is_kyc_to_be_reviewed": { + "description": "Narrows a `delete_all` to the sessions that match the `is_kyc_to_be_reviewed` filter of [List Sessions](/sessions-api/list-sessions), with the same values. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "boolean" + }, + "filter_euuid": { + "description": "Narrows a `delete_all` to the sessions that match the `euuid` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string", + "format": "uuid" + }, + "filter_organization_name": { + "description": "Narrows a `delete_all` to the sessions that match the `organization_name` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. With an API key it can only match your own organization's name, so it selects all of your sessions or none. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + } + }, + "examples": { + "Specific sessions": { + "summary": "Delete specific sessions by number", + "value": { + "session_numbers": [ + "1001", + "1002", + "1003" + ] + } + }, + "All sessions": { + "summary": "Delete every KYC session in the application", + "value": { + "delete_all": true + } + }, + "Delete and retain templates": { + "summary": "Delete sessions but keep image-free biometric templates for 365 days", + "value": { + "session_numbers": [ + "1001", + "1002" + ], + "retain_face_embeddings": true, + "face_retention_days": 365, + "instruction_id": "post-approval-cleanup-2026-08" + } + }, + "Privacy erasure": { + "summary": "Delete sessions and purge every retained template for their Users", + "value": { + "session_numbers": [ + "1001" + ], + "deletion_instruction": "privacy_erasure", + "instruction_id": "dsar-2026-0142" + } + }, + "Filtered delete_all": { + "summary": "Delete every Declined session of one workflow created in September 2026", + "value": { + "delete_all": true, + "filter_status": [ + "Declined" + ], + "filter_workflow_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "filter_date_from": "2026-09-01", + "filter_date_to": "2026-09-30" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Up to 20 sessions matched and were processed in the request. One result per matched session; numbers that matched nothing (unknown or already deleted) are skipped and do not appear. A result confirms deletion except when `face_retention_outcome` is `failed_retryable`: that session was NOT deleted and should be retried.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The deleted session." + }, + "session_number": { + "type": "integer", + "nullable": true, + "description": "The deleted session's number." + }, + "face_retention_outcome": { + "type": "string", + "enum": [ + "retained_with_user", + "deleted", + "none", + "ineligible_no_vendor_user", + "failed_retryable" + ], + "description": "What happened to the session's face biometric data. `deleted`: the face embedding was deleted with the session (default). `retained_with_user`: the session is deleted and one image-free biometric template stays anchored to the User; see `biometric_template_uuid`. `none`: the session had no face embedding (for example a document-only KYC session without a liveness selfie; KYB sessions are never selected by this endpoint). `ineligible_no_vendor_user`: retention was requested but the session has no linked User, so the embedding was deleted with the session. `failed_retryable` (batch endpoint only): the group this session was deleted with could not be deleted (a storage service, a biometric store, or a monitoring provider was unavailable); this session is still in place, repeat the request for it." + }, + "biometric_template_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Id of the retained biometric template when `face_retention_outcome` is `retained_with_user`; `null` otherwise. Manage it through `/v3/biometric-templates/{template_uuid}/`." + }, + "biometric_template_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the retained biometric template is deleted, when `face_retention_outcome` is `retained_with_user`; `null` when no template is retained." + }, + "deleted_child_verifications": { + "type": "integer", + "description": "Always `0` on this endpoint, which deletes KYC sessions only. Deleting a Business Verification (KYB) session with `DELETE /v3/session/{sessionId}/delete/` reports the verifications it created that were deleted with it." + } + } + } + } + } + }, + "examples": { + "Mixed outcomes": { + "value": { + "results": [ + { + "session_id": "4c5c7f3a-3b1d-4b7e-9c39-2b4f7e6a1d02", + "session_number": 1001, + "face_retention_outcome": "deleted", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + }, + { + "session_id": "b8e1d2c3-4f5a-4b6c-8d7e-9f0a1b2c3d4e", + "session_number": 1002, + "face_retention_outcome": "retained_with_user", + "biometric_template_uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "biometric_template_expires_at": "2027-10-09T10:15:00Z", + "deleted_child_verifications": 0 + }, + { + "session_id": "7a1e9c2d-5f4b-4c3a-8e2d-1b0a9f8e7d6c", + "session_number": 1003, + "face_retention_outcome": "ineligible_no_vendor_user", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + }, + { + "session_id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f", + "session_number": 1004, + "face_retention_outcome": "failed_retryable", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + } + ] + } + } + } + } + } + }, + "202": { + "description": "More than 20 sessions matched. They are deleted in the background; poll `GET /v3/sessions/delete/{job_id}/` until `status` is `COMPLETED` or `FAILED`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionDeletionJob" + }, + "examples": { + "Job accepted": { + "value": { + "job_id": "5b8f2c1e-7d3a-4f6b-9e2c-1a0d8f7e6c5b", + "status": "PENDING", + "total": 250, + "processed": 0, + "outcomes": { + "deleted": 0, + "none": 0, + "retained_with_user": 0, + "ineligible_no_vendor_user": 0, + "failed_retryable": 0, + "already_deleted": 0 + }, + "deleted_child_verifications": 0, + "error": null, + "created_at": "2026-10-09T10:15:00Z", + "completed_at": null + } + } + } + } + } + }, + "400": { + "description": "Validation error — nothing was deleted. Error values are arrays of message strings keyed by field name.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_numbers": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": { + "description": "Other keys: a `filter_*` key that is not a List Sessions filter (`\"Unknown filter.\"`), or a `filter_*` key whose value cannot be read (keyed by that `filter_*` key, with an array of messages)." + } + }, + "examples": { + "Missing selector": { + "summary": "Neither session_numbers nor delete_all provided", + "value": { + "session_numbers": [ + "This field is required unless delete_all is true." + ] + } + }, + "Empty list": { + "summary": "session_numbers is an empty array", + "value": { + "session_numbers": [ + "This list may not be empty." + ] + } + }, + "Non-numeric entry": { + "summary": "session_numbers contains a non-digit string", + "value": { + "session_numbers": [ + "All session_numbers must be numeric." + ] + } + }, + "Privacy erasure cannot retain": { + "summary": "deletion_instruction privacy_erasure combined with retain_face_embeddings true", + "value": { + "retain_face_embeddings": [ + "Privacy erasure cannot retain biometric templates." + ] + } + }, + "No finite retention duration": { + "summary": "Retention requested without face_retention_days on the request or the application", + "value": { + "detail": "A finite biometric-template retention duration is required." + } + }, + "Unknown filter": { + "summary": "A filter_* key that List Sessions does not have", + "value": { + "filter_reviewer": "Unknown filter." + } + }, + "Unreadable filter value": { + "summary": "A filter value that cannot be read", + "value": { + "filter_date_from": [ + "Enter a valid date." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing API key. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid API key": { + "summary": "No x-api-key header, or the key is invalid", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Shared write rate limit exceeded (300 POST/PATCH/DELETE requests per minute per API key). Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "Write budget exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + }, + "409": { + "description": "A selected session is part of a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/sessions/delete/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"session_numbers\": [\"1001\", \"1002\", \"1003\"]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.batch_delete(\n session_numbers=[\"1001\", \"1002\", \"1003\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.batchDelete({\n session_numbers: [\"1001\", \"1002\", \"1003\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.BatchDelete(ctx, didit.SessionsBatchDeleteParams{\n\tSessionNumbers: didit.Some([]string{\"1001\", \"1002\", \"1003\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.SessionsBatchDeleteParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().batchDelete(SessionsBatchDeleteParams.builder()\n .sessionNumbers(List.of(\"1001\", \"1002\", \"1003\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->batchDelete([\n 'session_numbers' => ['1001', '1002', '1003'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.batch_delete(session_numbers: [\"1001\", \"1002\", \"1003\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.BatchDeleteAsync(new SessionsBatchDeleteParams\n{\n SessionNumbers = new string[] { \"1001\", \"1002\", \"1003\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/sessions/delete/{job_id}/": { + "get": { + "summary": "Get a bulk session deletion job", + "description": "Progress of a bulk session deletion that `POST /v3/sessions/delete/` answered with `202` (more than 20 matching sessions, selected by `session_numbers` or with `delete_all`). Poll every few seconds while `status` is `PENDING` or `PROCESSING`; stop when it is `COMPLETED` or `FAILED`.\n\nThe job reports how many sessions ended in each outcome and how many it has processed, never which sessions it deleted. A `FAILED` job carries the reason in `error`; the sessions it had not reached are still in place, so repeat the deletion request to delete them.\n\n**Authentication:** API key in the `x-api-key` header. A job is only visible to the application that created it; any other `job_id` returns `404`. Missing or invalid credentials return `403`; this API never returns `401`.", + "operationId": "get_session_deletion_job", + "x-didit-sdk": { + "resource": "sessions", + "method": "retrieve_deletion_job", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The `job_id` returned with the `202` from `POST /v3/sessions/delete/`." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl 'https://verification.didit.me/v3/sessions/delete/JOB_ID/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.retrieve_deletion_job(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.retrieveDeletionJob(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.RetrieveDeletionJob(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().retrieveDeletionJob(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->retrieveDeletionJob('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.retrieve_deletion_job(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.RetrieveDeletionJobAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionDeletionJob" + }, + "examples": { + "Processing": { + "value": { + "job_id": "5b8f2c1e-7d3a-4f6b-9e2c-1a0d8f7e6c5b", + "status": "PROCESSING", + "total": 250, + "processed": 100, + "outcomes": { + "deleted": 82, + "none": 18, + "retained_with_user": 0, + "ineligible_no_vendor_user": 0, + "failed_retryable": 0, + "already_deleted": 0 + }, + "deleted_child_verifications": 0, + "error": null, + "created_at": "2026-10-09T10:15:00Z", + "completed_at": null + } + }, + "Completed": { + "value": { + "job_id": "5b8f2c1e-7d3a-4f6b-9e2c-1a0d8f7e6c5b", + "status": "COMPLETED", + "total": 250, + "processed": 250, + "outcomes": { + "deleted": 204, + "none": 44, + "retained_with_user": 0, + "ineligible_no_vendor_user": 0, + "failed_retryable": 0, + "already_deleted": 2 + }, + "deleted_child_verifications": 0, + "error": null, + "created_at": "2026-10-09T10:15:00Z", + "completed_at": "2026-10-09T10:16:40Z" + } + }, + "Failed": { + "value": { + "job_id": "5b8f2c1e-7d3a-4f6b-9e2c-1a0d8f7e6c5b", + "status": "FAILED", + "total": 250, + "processed": 100, + "outcomes": { + "deleted": 82, + "none": 18, + "retained_with_user": 0, + "ineligible_no_vendor_user": 0, + "failed_retryable": 0, + "already_deleted": 0 + }, + "deleted_child_verifications": 0, + "error": "The deletion stopped before it finished. The sessions it had not reached are still in place; repeat the request to delete them.", + "created_at": "2026-10-09T10:15:00Z", + "completed_at": "2026-10-09T10:15:52Z" + } + } + } + } + } + }, + "403": { + "description": "Missing API key. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid API key": { + "summary": "No x-api-key header, or the key is invalid", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No deletion job with this `job_id` exists for the calling application. Jobs are only visible to the application that created them." + } + } + } + }, + "/v3/sessions/{session_id}/reviews/": { + "get": { + "summary": "List session reviews and activity log", + "description": "Return the audit trail / activity feed for a User Verification (KYC) session, newest first, in a paginated envelope (`count` / `next` / `previous` / `results`, 50 entries per page by default).\n\nFeed entries are written by every actor that touches the session: manual decisions made through `PATCH /v3/session/{session_id}/update-status/` (which records `previous_status` → `new_status`), Console reviewer activity (comments with `@email` mentions, KYC/POA data edits, file uploads/removals, tag changes, detail-page views), AML ongoing-monitoring matches, and notes appended via `POST` on this same path. Reading this feed is the API-side equivalent of the **Activity** panel in the Didit Console session view.\n\nOnly User Verification (KYC) sessions are addressable here; Business Verification (KYB) session IDs return `404`.", + "operationId": "list_session_reviews", + "x-didit-sdk": { + "resource": "sessions.reviews", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session. Must belong to the same application as the API key — cross-application UUIDs, deleted sessions, and Business Verification (KYB) session IDs all return `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Page size. Defaults to `50`.", + "schema": { + "type": "integer", + "default": 50, + "example": 50 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of entries to skip from the start of the (ordered) feed. Defaults to `0`. The `next` / `previous` URLs in the response carry the right values for walking the feed.", + "schema": { + "type": "integer", + "default": 0, + "example": 0 + } + }, + { + "name": "ordering", + "in": "query", + "required": false, + "description": "Sort order. The feed defaults to newest first (`-created_at`); pass `created_at` to read it oldest first. Unknown values are silently ignored (default order is used); other serializer field names are also accepted.", + "schema": { + "type": "string", + "default": "-created_at", + "example": "created_at" + } + } + ], + "responses": { + "200": { + "description": "Paginated activity feed, newest first by default. `count` is the total number of entries; `next` / `previous` are ready-made page URLs (`null` at the ends). A `count` of `0` with empty `results` means no activity has been recorded yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of feed entries for this session (across all pages).", + "example": 3 + }, + "next": { + "type": "string", + "nullable": true, + "description": "URL of the next page, or `null` on the last page.", + "example": null + }, + "previous": { + "type": "string", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page.", + "example": null + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ReviewItem" + }, + "description": "Feed entries for this page." + } + } + }, + "examples": { + "DecisionTrail": { + "summary": "Manual approval plus API-added notes", + "value": { + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "uuid": "de0561bc-5583-4409-9171-1a31a4c8a5f4", + "activity_type": "STATUS_UPDATED", + "actor_display": "API Client", + "actor_email": null, + "actor_type": "API_KEY", + "new_status": "Approved", + "previous_status": "In Review", + "comment": "Document re-checked manually; address matches.", + "mentioned_emails": [], + "previous_value": { + "status": "In Review" + }, + "new_value": { + "status": "Approved" + }, + "changed_fields": [ + "status" + ], + "metadata": null, + "created_at": "2026-06-12T00:19:31.918735Z" + }, + { + "uuid": "35370f58-5098-44ee-8a06-8c9af80b2455", + "activity_type": "STATUS_UPDATED", + "actor_display": "Unknown", + "actor_email": null, + "actor_type": "CONSOLE_USER", + "new_status": null, + "previous_status": null, + "comment": "Note without status.", + "mentioned_emails": [], + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "created_at": "2026-06-12T00:18:44.504157Z" + }, + { + "uuid": "00338f82-f1c5-4f57-8614-c49bb258af1a", + "activity_type": "STATUS_UPDATED", + "actor_display": "Unknown", + "actor_email": null, + "actor_type": "CONSOLE_USER", + "new_status": "In Review", + "previous_status": null, + "comment": "Flagging for manual document re-check.", + "mentioned_emails": [], + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "created_at": "2026-06-12T00:18:44.466537Z" + } + ] + } + }, + "PaginatedFirstPage": { + "summary": "First page with limit=2 — next holds the follow-up URL", + "value": { + "count": 4, + "next": "https://verification.didit.me/v3/sessions/8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d/reviews/?limit=2&offset=2", + "previous": null, + "results": [ + { + "uuid": "de0561bc-5583-4409-9171-1a31a4c8a5f4", + "activity_type": "STATUS_UPDATED", + "actor_display": "API Client", + "actor_email": null, + "actor_type": "API_KEY", + "new_status": "Approved", + "previous_status": "In Review", + "comment": "Document re-checked manually; address matches.", + "mentioned_emails": [], + "previous_value": { + "status": "In Review" + }, + "new_value": { + "status": "Approved" + }, + "changed_fields": [ + "status" + ], + "metadata": null, + "created_at": "2026-06-12T00:19:31.918735Z" + }, + { + "uuid": "bd4e6227-bdd2-42b2-a726-1d12f785a457", + "activity_type": "STATUS_UPDATED", + "actor_display": "Unknown", + "actor_email": null, + "actor_type": "CONSOLE_USER", + "new_status": null, + "previous_status": null, + "comment": null, + "mentioned_emails": [], + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "created_at": "2026-06-12T00:18:44.540238Z" + } + ] + } + }, + "Empty": { + "summary": "Session with no recorded activity yet", + "value": { + "count": 0, + "next": null, + "previous": null, + "results": [] + } + } + } + } + } + }, + "403": { + "description": "Missing, malformed, expired, or revoked `x-api-key`. Authentication is checked before the session lookup, so a missing key returns `403` even when the session UUID does not exist.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You do not have permission to perform this action." + } + } + }, + "examples": { + "InvalidKey": { + "summary": "Missing or invalid x-api-key header", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No User Verification (KYC) session with this UUID is visible to your application: the UUID does not exist, the session was deleted, or it belongs to a different application. Business Verification (KYB) session IDs also return `404` here — the reviews feed is only addressable for KYC sessions.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Session not found." + } + } + }, + "examples": { + "NotFound": { + "summary": "Unknown, deleted, or cross-application session UUID", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Inspect `Retry-After` and `X-RateLimit-*` response headers. See [Rate limiting](/integration/rate-limiting)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -s 'https://verification.didit.me/v3/sessions/8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d/reviews/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.sessions.reviews.list(\n \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.sessions.reviews.list(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Sessions.Reviews.ListAutoPaging(ctx, \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\", didit.SessionsReviewsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.sessions().reviews().list(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->reviews->list('8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.reviews.list(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Sessions.Reviews.ListAsync(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "summary": "Add a review note to a session's audit trail", + "description": "Append a note to a User Verification (KYC) session's review / activity feed. **This endpoint only records an entry — it never changes the session.** The session's `status` is left untouched, no status-transition validation runs, and no webhook fires. To actually approve, decline, or re-open a session (which also writes a fully-attributed `STATUS_UPDATED` entry to this feed), use `PATCH /v3/session/{session_id}/update-status/` instead.\n\nBoth body fields are optional and an empty JSON object is accepted. `new_status` is stored purely as a label on the entry and accepts any of the ten session status values (case-sensitive). `@email` mentions inside `comment` are stored as plain text only — they trigger no notifications on this endpoint, and the entry's `mentioned_emails` stays empty.\n\nNote on attribution: entries created here carry no actor information. In the feed they show `actor_display: \"Unknown\"`, `actor_type: \"CONSOLE_USER\"`, and `activity_type: \"STATUS_UPDATED\"` (storage defaults), and the `201` body's `user` field is `\"Unknown\"`.", + "operationId": "create_session_review", + "x-didit-sdk": { + "resource": "sessions.reviews", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "Content-Type", + "in": "header", + "required": false, + "description": "Set to `application/json` when sending a JSON body.", + "schema": { + "type": "string", + "example": "application/json" + } + }, + { + "name": "session_id", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session. Must belong to the same application as the API key — cross-application UUIDs, deleted sessions, and Business Verification (KYB) session IDs all return `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "new_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Optional status label to record on the entry. Accepts any session status value, case-sensitive (`\"approved\"` is rejected with `400`). Recording a status here does **not** change the session's actual status — use the update-status endpoint for that.", + "example": "In Review" + }, + "comment": { + "type": "string", + "nullable": true, + "description": "Optional free-text note (no enforced length limit). Stored verbatim; `@email` mentions are not processed on this endpoint.", + "example": "Flagging for manual document re-check." + } + } + }, + "examples": { + "NoteWithStatusLabel": { + "summary": "Note carrying a status label (session status unchanged)", + "value": { + "new_status": "In Review", + "comment": "Flagging for manual document re-check." + } + }, + "CommentOnly": { + "summary": "Plain note without a status label", + "value": { + "comment": "Customer emailed an updated utility bill; awaiting upload." + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Entry recorded on the session's audit trail. The session itself is unchanged — its `status` keeps its previous value and no webhook is sent. The new entry also appears at the top of `GET …/reviews/` (with `actor_display: \"Unknown\"`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "user": { + "type": "string", + "description": "Display name of the entry's author. Always `\"Unknown\"` for entries created through this endpoint, because it records no actor attribution.", + "example": "Unknown" + }, + "new_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "The status label recorded on the entry, or `null` if none was sent. The session's actual status is not affected.", + "example": "In Review" + }, + "comment": { + "type": "string", + "nullable": true, + "description": "The note recorded on the entry, or `null` if none was sent.", + "example": "Flagging for manual document re-check." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the entry was written, ISO 8601 UTC.", + "example": "2026-06-12T00:18:44.466537Z" + } + } + }, + "examples": { + "NoteWithStatusLabel": { + "summary": "Note with a status label", + "value": { + "user": "Unknown", + "new_status": "In Review", + "comment": "Flagging for manual document re-check.", + "created_at": "2026-06-12T00:18:44.466537Z" + } + }, + "CommentOnly": { + "summary": "Plain note", + "value": { + "user": "Unknown", + "new_status": null, + "comment": "Note without status.", + "created_at": "2026-06-12T00:18:44.504157Z" + } + } + } + } + } + }, + "400": { + "description": "Validation error, returned as a per-field map of messages. The usual cause is a `new_status` value that is not one of the ten allowed statuses — the check is case-sensitive.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "new_status": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "\"approved\" is not a valid choice." + ] + } + } + }, + "examples": { + "InvalidStatusChoice": { + "summary": "Lowercase / unknown status value", + "value": { + "new_status": [ + "\"approved\" is not a valid choice." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, malformed, expired, or revoked `x-api-key`. Authentication is checked before the session lookup, so a missing key returns `403` even when the session UUID does not exist.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You do not have permission to perform this action." + } + } + }, + "examples": { + "InvalidKey": { + "summary": "Missing or invalid x-api-key header", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No User Verification (KYC) session with this UUID is visible to your application: the UUID does not exist, the session was deleted, or it belongs to a different application. Business Verification (KYB) session IDs also return `404` here — the reviews feed is only addressable for KYC sessions.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Session not found." + } + } + }, + "examples": { + "NotFound": { + "summary": "Unknown, deleted, or cross-application session UUID", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Inspect `Retry-After` and `X-RateLimit-*` response headers. See [Rate limiting](/integration/rate-limiting)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "# Records an audit-trail note only -- the session's status does NOT change.\ncurl -s -X POST https://verification.didit.me/v3/sessions/8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d/reviews/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"new_status\": \"In Review\",\n \"comment\": \"Flagging for manual document re-check.\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.reviews.create(\n \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\",\n new_status=\"In Review\",\n comment=\"Flagging for manual document re-check.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.reviews.create(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\", {\n new_status: \"In Review\",\n comment: \"Flagging for manual document re-check.\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Reviews.Create(ctx, \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\", didit.SessionsReviewsCreateParams{\n\tNewStatus: didit.Some(didit.SessionsReviewsCreateParamsNewStatus(\"In Review\")),\n\tComment: didit.String(\"Flagging for manual document re-check.\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsReviewsCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().reviews().create(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\", SessionsReviewsCreateParams.builder()\n .newStatus(SessionsReviewsCreateParams.NewStatus.of(\"In Review\"))\n .comment(\"Flagging for manual document re-check.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->reviews->create('8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d', [\n 'new_status' => 'In Review',\n 'comment' => 'Flagging for manual document re-check.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.reviews.create(\n \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\",\n new_status: \"In Review\",\n comment: \"Flagging for manual document re-check.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Reviews.CreateAsync(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\", new SessionsReviewsCreateParams\n{\n NewStatus = new SessionsReviewsCreateParamsNewStatus(\"In Review\"),\n Comment = \"Flagging for manual document re-check.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/billing/balance/": { + "get": { + "summary": "Get credit balance", + "description": "Get current pre-paid credit balance in USD (organization-wide) plus auto-refill config. Amounts are decimal strings — parse with a decimal library.", + "operationId": "get_billing_balance", + "x-didit-sdk": { + "resource": "billing", + "method": "retrieve_balance", + "auth": "api_key", + "server": true + }, + "tags": [ + "Billing" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/billing/balance/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.billing.retrieve_balance()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.billing.retrieveBalance();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Billing.RetrieveBalance(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.billing().retrieveBalance();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "billing->retrieveBalance();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.billing.retrieve_balance\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Billing.RetrieveBalanceAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Current credit balance plus the organization's auto-refill configuration.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "balance", + "auto_refill_enabled" + ], + "properties": { + "balance": { + "type": "string", + "description": "Current pre-paid credit balance in USD, as a fixed-point decimal string. Decreases as verifications are billed and increases when a top-up settles." + }, + "auto_refill_enabled": { + "type": "boolean", + "description": "When `true`, Didit will automatically charge the saved payment method to add credits once the balance crosses `auto_refill_threshold`. Configured from Console → Billing." + }, + "auto_refill_amount": { + "type": "string", + "nullable": true, + "description": "USD amount that will be charged and added to the balance on each auto-refill, at face value. Set from \\$50 to \\$10,000 in Console → Billing. `null` when auto-refill is disabled or unset." + }, + "auto_refill_threshold": { + "type": "string", + "nullable": true, + "description": "Trigger threshold in USD. Auto-refill fires when `balance` falls below this value. `null` when auto-refill is disabled or unset." + } + } + }, + "examples": { + "Healthy balance with auto-refill": { + "value": { + "balance": "1234.5600", + "auto_refill_enabled": true, + "auto_refill_amount": "500.00", + "auto_refill_threshold": "100.00" + } + }, + "Low balance, manual top-up only": { + "value": { + "balance": "12.0000", + "auto_refill_enabled": false, + "auto_refill_amount": null, + "auto_refill_threshold": null + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read billing data for this organization. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/billing/top-up/": { + "post": { + "summary": "Top up credits", + "description": "Add credits via a hosted Stripe Checkout session (**\\$50 to \\$10,000** per top-up). Top-ups are available on the Free, Growth and Scale plans, added at face value with no discount or bonus, and last 12 months; Enterprise annual contracts cannot top up. Redirect the customer to the returned `checkout_session_url`.", + "operationId": "billing_top_up", + "x-didit-sdk": { + "resource": "billing", + "method": "top_up", + "auth": "api_key", + "server": true + }, + "tags": [ + "Billing" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "amount_in_dollars" + ], + "properties": { + "amount_in_dollars": { + "type": "number", + "minimum": 50, + "maximum": 10000, + "description": "Amount to charge, in USD. **Must be from 50 to 10,000.** The balance receives the same amount, with no discount or bonus. Tax is added automatically at checkout based on the customer's billing address." + }, + "success_url": { + "type": "string", + "format": "uri", + "description": "URL the hosted checkout redirects the customer to after a successful payment. Currently defaults to `https://console.didit.me`. Pass a working URL explicitly; the default host is unavailable. Append the literal placeholder `{CHECKOUT_SESSION_ID}` if you want the hosted checkout to substitute the session ID." + }, + "cancel_url": { + "type": "string", + "format": "uri", + "description": "URL the hosted checkout redirects the customer to when they abort or close the Checkout window. Currently defaults to `https://console.didit.me`. Pass a working URL explicitly; the default host is unavailable." + } + } + }, + "examples": { + "Minimum top-up": { + "value": { + "amount_in_dollars": 50 + } + }, + "Custom amount with redirects": { + "value": { + "amount_in_dollars": 500, + "success_url": "https://yourapp.com/billing/top-up/success", + "cancel_url": "https://yourapp.com/billing/top-up/cancel" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/billing/top-up/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"amount_in_dollars\": 500,\n \"success_url\": \"https://yourapp.com/billing/success\",\n \"cancel_url\": \"https://yourapp.com/billing/cancel\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.billing.top_up(\n amount_in_dollars=50.0,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.billing.topUp({\n amount_in_dollars: 50.0,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Billing.TopUp(ctx, didit.BillingTopUpParams{\n\tAmountInDollars: 50.0,\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BillingTopUpParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.billing().topUp(BillingTopUpParams.builder()\n .amountInDollars(50.0)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "billing->topUp([\n 'amount_in_dollars' => 50.0,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.billing.top_up(amount_in_dollars: 50.0)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Billing.TopUpAsync(new BillingTopUpParams\n{\n AmountInDollars = 50.0,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Hosted checkout session created. Redirect the user to `checkout_session_url` to complete payment.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "checkout_session_id", + "checkout_session_url" + ], + "properties": { + "checkout_session_id": { + "type": "string", + "description": "Hosted checkout session identifier (e.g. `cs_test_a1B2c3...`). Useful for server-side reconciliation." + }, + "checkout_session_url": { + "type": "string", + "format": "uri", + "description": "Single-use hosted payment page URL. Redirect the customer here to complete payment." + } + } + }, + "examples": { + "Created": { + "value": { + "checkout_session_id": "cs_test_a1B2c3D4e5F6g7H8i9J0", + "checkout_session_url": "https://checkout.example.com/c/pay/cs_test_a1B2c3D4e5F6g7H8i9J0" + } + } + } + } + } + }, + "400": { + "description": "Invalid payload: the amount is missing, not a number, below the \\$50 minimum or above the \\$10,000 maximum, or the organization is on an Enterprise annual contract. The error body is a JSON array.", + "content": { + "application/json": { + "examples": { + "Missing amount": { + "value": [ + "amount_in_dollars is required." + ] + }, + "Not a number": { + "value": [ + "amount_in_dollars must be a number." + ] + }, + "Below minimum": { + "value": [ + "Amount must be at least $50." + ] + }, + "Above maximum": { + "value": [ + "Amount must be at most $10,000." + ] + }, + "Annual plan": { + "value": [ + "You have an annual plan. Contact your account manager to top up." + ] + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot create top-up sessions for this organization. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/": { + "get": { + "summary": "List webhook destinations", + "description": "List all webhook destinations (unpaginated array, oldest first). Each item carries the destination's configuration, its delivery metrics for the last 30 days (`metrics_window_days`) with one `daily_points` entry per day, and its health (`health_status`, `failing_streak`, `failing_since`, `last_success_at`). The list has no `summary` object and never includes the signing secret: each item shows only `secret_last4`. Fetch a single destination (`GET /v3/webhook/destinations/{destination_uuid}/`) to read `secret_shared_key`.", + "operationId": "list_webhook_destinations", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.list()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.list();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.List(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().list();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->list();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.webhooks.destinations.list.each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.ListAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "List of webhook destinations (unpaginated, oldest first).", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Stable destination identifier; use as `{destination_uuid}` for update/delete." + }, + "label": { + "type": "string", + "description": "Human-readable name shown in the Console." + }, + "url": { + "type": "string", + "format": "uri", + "description": "Destination URL that receives `POST` requests with the webhook payload." + }, + "enabled": { + "type": "boolean", + "description": "When `false`, Didit silently skips delivery to this destination." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload schema version sent to this destination. `v3` is the current/recommended format." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + }, + "subscribed_events_count": { + "type": "integer", + "description": "Cached length of `subscribed_events`, useful for UI rendering." + }, + "deliveries_count": { + "type": "integer", + "description": "Delivery attempts (retries included) made to this destination in the last 30 days." + }, + "failed_deliveries_count": { + "type": "integer", + "description": "Delivery attempts in the last 30 days that the destination answered with HTTP 400 or above, or did not answer (timeout, refused connection or blocked address)." + }, + "average_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Average response time in milliseconds over the last 30 days, rounded to an integer. `null` when there were no deliveries in that window." + }, + "error_rate_percentage": { + "type": "integer", + "description": "`round(failed_deliveries_count / deliveries_count * 100)` over the last 30 days; `0` when there are no deliveries." + }, + "last_delivery_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent delivery attempt (success or failure). `null` if never delivered." + }, + "metrics_window_days": { + "type": "integer", + "description": "Length in days of the window that `deliveries_count`, `failed_deliveries_count`, `average_response_time_ms`, `error_rate_percentage` and `daily_points` cover. Always `30`." + }, + "health_status": { + "type": "string", + "enum": [ + "healthy", + "degraded", + "failing" + ], + "description": "Delivery health. `failing`: 3 or more consecutive events failed their first delivery attempt since the destination last answered successfully (see `failing_streak`). `degraded`: not failing, but a delivery attempt failed in the last 24 hours and no later attempt has delivered that event; a retry or resend that delivers it clears this. `healthy`: neither. Independent of `enabled`. A failed attempt is one answered with HTTP 400 or above, or not answered at all." + }, + "failing_streak": { + "type": "integer", + "description": "How many consecutive events failed their first delivery attempt since the destination last answered successfully, within the last 30 days. Retries are not counted, and any successful attempt, including a retry or a resend, ends the streak. `0` when no event has failed since the last success." + }, + "failing_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the first failed attempt of the current `failing_streak` was made. `null` when `failing_streak` is `0`." + }, + "last_success_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the destination last answered a delivery with an HTTP status under 400, within the last 30 days. `null` if it has not in that window." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the destination." + }, + "daily_points": { + "type": "array", + "description": "One entry per UTC day of the last 30 days, oldest first, including days with no deliveries. Counts every attempt, retries included.", + "items": { + "type": "object", + "properties": { + "date": { + "type": "string", + "format": "date", + "description": "The UTC day." + }, + "total_deliveries": { + "type": "integer", + "description": "Delivery attempts made that day." + }, + "failed_deliveries": { + "type": "integer", + "description": "Attempts made that day that were answered with HTTP 400 or above, or not answered at all." + } + } + } + }, + "secret_last4": { + "type": "string", + "description": "Last four characters of the destination's current signing secret, so you can tell secrets apart. Not enough to verify a signature." + }, + "created_by_email": { + "type": "string", + "nullable": true, + "description": "Email of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_by_name": { + "type": "string", + "nullable": true, + "description": "Name of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + }, + "examples": { + "Two destinations": { + "value": [ + { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "subscribed_events_count": 2, + "deliveries_count": 12453, + "failed_deliveries_count": 14, + "average_response_time_ms": 187, + "error_rate_percentage": 0, + "last_delivery_at": "2026-05-17T09:21:18Z", + "metrics_window_days": 30, + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": "2026-05-17T09:21:18Z", + "environment": "live", + "daily_points": [ + { + "date": "2026-04-18", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-19", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-20", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-21", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-22", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-23", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-24", + "total_deliveries": 415, + "failed_deliveries": 9 + }, + { + "date": "2026-04-25", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-26", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-27", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-28", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-29", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-30", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-01", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-02", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-03", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-04", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-05", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-06", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-07", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-08", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-09", + "total_deliveries": 415, + "failed_deliveries": 5 + }, + { + "date": "2026-05-10", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-11", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-12", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-13", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-14", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-15", + "total_deliveries": 416, + "failed_deliveries": 0 + }, + { + "date": "2026-05-16", + "total_deliveries": 416, + "failed_deliveries": 0 + }, + { + "date": "2026-05-17", + "total_deliveries": 416, + "failed_deliveries": 0 + } + ], + "secret_last4": "0xnQ", + "created_by_email": null, + "created_by_name": null, + "created_at": "2026-01-04T08:00:00Z", + "updated_at": "2026-04-22T14:09:11Z" + }, + { + "uuid": "22222222-3333-4444-5555-666666666666", + "label": "Staging mirror", + "url": "https://staging.yourapp.com/webhooks/didit", + "enabled": false, + "webhook_version": "v3", + "subscribed_events": [ + "status.updated" + ], + "subscribed_events_count": 1, + "deliveries_count": 0, + "failed_deliveries_count": 0, + "average_response_time_ms": null, + "error_rate_percentage": 0, + "last_delivery_at": null, + "metrics_window_days": 30, + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": null, + "environment": "live", + "daily_points": [ + { + "date": "2026-04-18", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-19", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-20", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-21", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-22", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-23", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-24", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-25", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-26", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-27", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-28", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-29", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-30", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-01", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-02", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-03", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-04", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-05", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-06", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-07", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-08", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-09", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-10", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-11", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-12", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-13", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-14", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-15", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-16", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-17", + "total_deliveries": 0, + "failed_deliveries": 0 + } + ], + "secret_last4": "k9Qe", + "created_by_email": "reviewer@example.com", + "created_by_name": "Example Reviewer", + "created_at": "2026-03-12T10:15:02Z", + "updated_at": "2026-03-12T10:15:02Z" + } + ] + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create webhook destination", + "description": "Create a webhook destination. The response includes the auto-generated `secret_shared_key` HMAC signing secret (also readable later via `GET /v3/webhook/destinations/{destination_uuid}/`). Always send `subscribed_events` with at least one event (no wildcards): an explicit empty list is rejected with 400, and omitting the field creates a destination subscribed to nothing, which never receives a webhook. The `(application, url)` pair must be unique among the application's destinations.", + "operationId": "create_webhook_destination", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/webhook/destinations/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"label\": \"Production session webhooks\",\n \"url\": \"https://yourapp.com/webhooks/didit\",\n \"webhook_version\": \"v3\",\n \"subscribed_events\": [\"status.updated\", \"data.updated\"]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.create(\n label=\"Production session webhooks\",\n url=\"https://yourapp.com/webhooks/didit\",\n webhook_version=\"v3\",\n subscribed_events=[\"status.updated\", \"data.updated\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.create({\n label: \"Production session webhooks\",\n url: \"https://yourapp.com/webhooks/didit\",\n webhook_version: \"v3\",\n subscribed_events: [\"status.updated\", \"data.updated\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.Create(ctx, didit.WebhooksDestinationsCreateParams{\n\tLabel: \"Production session webhooks\",\n\tURL: \"https://yourapp.com/webhooks/didit\",\n\tWebhookVersion: didit.Some(didit.WebhooksDestinationsCreateParamsWebhookVersion(\"v3\")),\n\tSubscribedEvents: didit.Some([]didit.WebhookEvent{didit.WebhookEvent(\"status.updated\"), didit.WebhookEvent(\"data.updated\")}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.WebhookEvent;\nimport me.didit.types.WebhooksDestinationsCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().create(WebhooksDestinationsCreateParams.builder()\n .label(\"Production session webhooks\")\n .url(\"https://yourapp.com/webhooks/didit\")\n .webhookVersion(WebhooksDestinationsCreateParams.WebhookVersion.of(\"v3\"))\n .subscribedEvents(List.of(WebhookEvent.of(\"status.updated\"), WebhookEvent.of(\"data.updated\")))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->create([\n 'label' => 'Production session webhooks',\n 'url' => 'https://yourapp.com/webhooks/didit',\n 'webhook_version' => 'v3',\n 'subscribed_events' => ['status.updated', 'data.updated'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.destinations.create(\n label: \"Production session webhooks\",\n url: \"https://yourapp.com/webhooks/didit\",\n webhook_version: \"v3\",\n subscribed_events: [\"status.updated\", \"data.updated\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.CreateAsync(new WebhooksDestinationsCreateParams\n{\n Label = \"Production session webhooks\",\n Url = \"https://yourapp.com/webhooks/didit\",\n WebhookVersion = new WebhooksDestinationsCreateParamsWebhookVersion(\"v3\"),\n SubscribedEvents = new WebhookEvent[] { \"status.updated\", \"data.updated\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "label", + "url" + ], + "properties": { + "label": { + "type": "string", + "maxLength": 255, + "description": "Human-readable name displayed in Console → Webhooks. Helps distinguish destinations when you have several (e.g. \"Prod\", \"Staging mirror\")." + }, + "url": { + "type": "string", + "format": "uri", + "description": "HTTPS URL that will receive `POST` requests. Must be unique among the application's destinations. Use a stable, hardened endpoint: Didit retries failed deliveries on a backoff schedule.", + "maxLength": 2000 + }, + "enabled": { + "type": "boolean", + "default": true, + "description": "When `false`, the destination is saved but Didit will not push any webhooks to it. Useful for staging configs you want to keep parked." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload schema version Didit will send. `v3` is the current/recommended format; older versions are kept for backward compatibility with legacy integrations." + }, + "subscribed_events": { + "allOf": [ + { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + } + ], + "description": "Events to deliver. Effectively required: an explicit `[]` is rejected with 400, and omitting the field creates a destination with no subscriptions that receives nothing." + } + } + }, + "examples": { + "session_updates": { + "summary": "Session status and data updates", + "value": { + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "webhook_version": "v3", + "subscribed_events": [ + "status.updated", + "data.updated" + ] + } + }, + "transaction_updates": { + "summary": "Transaction monitoring updates", + "value": { + "label": "Transaction monitoring webhooks", + "url": "https://yourapp.com/webhooks/didit-transactions", + "webhook_version": "v3", + "subscribed_events": [ + "transaction.created", + "transaction.status.updated" + ] + } + }, + "entity_updates": { + "summary": "User and Business entity updates", + "value": { + "label": "Entity lifecycle webhooks", + "url": "https://yourapp.com/webhooks/didit-entities", + "webhook_version": "v3", + "subscribed_events": [ + "user.status.updated", + "user.data.updated", + "business.status.updated", + "business.data.updated", + "activity.created" + ] + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Destination created. `secret_shared_key` is the HMAC signing secret — store it now.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Stable destination identifier. Use as `{destination_uuid}` in subsequent calls." + }, + "label": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "enabled": { + "type": "boolean" + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ] + }, + "secret_shared_key": { + "type": "string", + "description": "Auto-generated HMAC signing secret bound to this destination (43-character URL-safe token). Use it to verify the signature headers on incoming webhooks. Always returned in full to API-key callers; Console users need the `read:webhooks` permission, otherwise they see an empty string." + }, + "secret_last4": { + "type": "string", + "description": "Last four characters of the destination's current signing secret, so you can tell secrets apart. Not enough to verify a signature." + }, + "previous_secret_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set for 24 hours after the destination's signing secret is rotated in the Business Console. Until this time, every delivery to the destination also carries `X-Signature-Previous`, `X-Signature-V2-Previous` and `X-Signature-Simple-Previous`, made with the replaced secret (see [Signature verification](/integration/webhooks#signature-verification)). `null` when no rotation happened in the last 24 hours." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + }, + "created_by_email": { + "type": "string", + "nullable": true, + "description": "Email of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_by_name": { + "type": "string", + "nullable": true, + "description": "Name of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "health_status": { + "type": "string", + "enum": [ + "healthy", + "degraded", + "failing" + ], + "description": "Delivery health. `failing`: 3 or more consecutive events failed their first delivery attempt since the destination last answered successfully (see `failing_streak`). `degraded`: not failing, but a delivery attempt failed in the last 24 hours and no later attempt has delivered that event; a retry or resend that delivers it clears this. `healthy`: neither. Independent of `enabled`. A failed attempt is one answered with HTTP 400 or above, or not answered at all." + }, + "failing_streak": { + "type": "integer", + "description": "How many consecutive events failed their first delivery attempt since the destination last answered successfully, within the last 30 days. Retries are not counted, and any successful attempt, including a retry or a resend, ends the streak. `0` when no event has failed since the last success." + }, + "failing_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the first failed attempt of the current `failing_streak` was made. `null` when `failing_streak` is `0`." + }, + "last_success_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the destination last answered a delivery with an HTTP status under 400, within the last 30 days. `null` if it has not in that window." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the destination." + }, + "summary": { + "type": "object", + "description": "Delivery metrics for the last `metrics_window_days` (30) days. `last_delivery_at` is the most recent attempt, whenever it was made.", + "properties": { + "total_deliveries": { + "type": "integer" + }, + "failed_deliveries": { + "type": "integer" + }, + "error_rate_percentage": { + "type": "integer" + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true + }, + "last_delivery_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "metrics_window_days": { + "type": "integer", + "description": "Length in days of the window the other summary metrics cover. Always `30`." + } + } + } + } + }, + "examples": { + "Created": { + "value": { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "secret_shared_key": "tno2NTeRC1ZK3sBOYlBJDyEzBVENl3pQ1AcZyAW0xnQ", + "secret_last4": "0xnQ", + "previous_secret_expires_at": null, + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_by_email": null, + "created_by_name": null, + "created_at": "2026-05-17T10:00:00Z", + "updated_at": "2026-05-17T10:00:00Z", + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": null, + "environment": "live", + "summary": { + "total_deliveries": 0, + "failed_deliveries": 0, + "error_rate_percentage": 0, + "min_response_time_ms": null, + "avg_response_time_ms": null, + "max_response_time_ms": null, + "last_delivery_at": null, + "metrics_window_days": 30 + } + } + } + } + } + } + }, + "400": { + "description": "Validation failed — usually a duplicate URL, missing/invalid `subscribed_events`, or unknown event name.", + "content": { + "application/json": { + "examples": { + "Duplicate URL": { + "value": { + "url": [ + "This webhook destination already exists for the application." + ] + } + }, + "Empty events list": { + "value": { + "subscribed_events": [ + "At least one subscribed event is required." + ] + } + }, + "Unknown event": { + "value": { + "subscribed_events": [ + "Unsupported webhook events: foo.bar" + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/": { + "get": { + "summary": "Get webhook destination", + "description": "Retrieve one webhook destination with its `secret_shared_key` signing secret, its health (`health_status`, `failing_streak`, `failing_since`, `last_success_at`) and a delivery `summary` for the last 30 days. API-key callers always receive the secret in full; Console users without `read:webhooks` see an empty string.", + "operationId": "get_webhook_destination", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination (the `uuid` returned from create / list).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.destinations.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Webhook destination details.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "enabled": { + "type": "boolean" + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ] + }, + "secret_shared_key": { + "type": "string", + "description": "HMAC signing secret (43-character URL-safe token). Always returned in full to API-key callers; Console users need `read:webhooks`, otherwise an empty string." + }, + "secret_last4": { + "type": "string", + "description": "Last four characters of the destination's current signing secret, so you can tell secrets apart. Not enough to verify a signature." + }, + "previous_secret_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set for 24 hours after the destination's signing secret is rotated in the Business Console. Until this time, every delivery to the destination also carries `X-Signature-Previous`, `X-Signature-V2-Previous` and `X-Signature-Simple-Previous`, made with the replaced secret (see [Signature verification](/integration/webhooks#signature-verification)). `null` when no rotation happened in the last 24 hours." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + }, + "created_by_email": { + "type": "string", + "nullable": true, + "description": "Email of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_by_name": { + "type": "string", + "nullable": true, + "description": "Name of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "health_status": { + "type": "string", + "enum": [ + "healthy", + "degraded", + "failing" + ], + "description": "Delivery health. `failing`: 3 or more consecutive events failed their first delivery attempt since the destination last answered successfully (see `failing_streak`). `degraded`: not failing, but a delivery attempt failed in the last 24 hours and no later attempt has delivered that event; a retry or resend that delivers it clears this. `healthy`: neither. Independent of `enabled`. A failed attempt is one answered with HTTP 400 or above, or not answered at all." + }, + "failing_streak": { + "type": "integer", + "description": "How many consecutive events failed their first delivery attempt since the destination last answered successfully, within the last 30 days. Retries are not counted, and any successful attempt, including a retry or a resend, ends the streak. `0` when no event has failed since the last success." + }, + "failing_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the first failed attempt of the current `failing_streak` was made. `null` when `failing_streak` is `0`." + }, + "last_success_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the destination last answered a delivery with an HTTP status under 400, within the last 30 days. `null` if it has not in that window." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the destination." + }, + "summary": { + "type": "object", + "description": "Delivery metrics for the last `metrics_window_days` (30) days. `last_delivery_at` is the most recent attempt, whenever it was made.", + "properties": { + "total_deliveries": { + "type": "integer" + }, + "failed_deliveries": { + "type": "integer" + }, + "error_rate_percentage": { + "type": "integer" + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true + }, + "last_delivery_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "metrics_window_days": { + "type": "integer", + "description": "Length in days of the window the other summary metrics cover. Always `30`." + } + } + } + } + }, + "examples": { + "Healthy destination": { + "value": { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "secret_shared_key": "tno2NTeRC1ZK3sBOYlBJDyEzBVENl3pQ1AcZyAW0xnQ", + "secret_last4": "0xnQ", + "previous_secret_expires_at": null, + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_by_email": null, + "created_by_name": null, + "created_at": "2026-01-04T08:00:00Z", + "updated_at": "2026-04-22T14:09:11Z", + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": "2026-05-17T09:21:18Z", + "environment": "live", + "summary": { + "total_deliveries": 12453, + "failed_deliveries": 14, + "error_rate_percentage": 0, + "min_response_time_ms": 41, + "avg_response_time_ms": 187, + "max_response_time_ms": 2103, + "last_delivery_at": "2026-05-17T09:21:18Z", + "metrics_window_days": 30 + } + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No destination with this UUID exists, or it belongs to a different application, or it has been deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update webhook destination", + "description": "Update a webhook destination. **`subscribed_events` is REPLACED wholesale, not merged**: fetch first, append, resend. Secret is not rotated. A `uuid` in the body is ignored: the path names the destination.", + "operationId": "update_webhook_destination", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination to update.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "label": { + "type": "string", + "description": "New human-readable name." + }, + "url": { + "type": "string", + "format": "uri", + "description": "New target URL. Must be unique within the application." + }, + "enabled": { + "type": "boolean", + "description": "Set to `false` to pause deliveries to this destination without deleting it." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "New payload schema version for future deliveries." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + } + } + }, + "examples": { + "Pause destination": { + "value": { + "enabled": false + } + }, + "Add transaction events": { + "value": { + "subscribed_events": [ + "status.updated", + "data.updated", + "transaction.created", + "transaction.status.updated" + ] + } + }, + "Move target URL": { + "value": { + "label": "Production session webhooks v2", + "url": "https://api.yourapp.com/webhooks/didit/v2" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"subscribed_events\": [\n \"status.updated\",\n \"data.updated\",\n \"transaction.created\"\n ]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.update(\n \"11111111-2222-3333-4444-555555555555\",\n enabled=False,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.update(\"11111111-2222-3333-4444-555555555555\", {\n enabled: false,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.WebhooksDestinationsUpdateParams{\n\tEnabled: didit.Bool(false),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.WebhooksDestinationsUpdateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().update(\"11111111-2222-3333-4444-555555555555\", WebhooksDestinationsUpdateParams.builder()\n .enabled(false)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->update('11111111-2222-3333-4444-555555555555', [\n 'enabled' => false,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.destinations.update(\n \"11111111-2222-3333-4444-555555555555\",\n enabled: false\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new WebhooksDestinationsUpdateParams\n{\n Enabled = false,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated webhook destination. Body has the same shape as `GET /v3/webhook/destinations/{destination_uuid}/`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "enabled": { + "type": "boolean" + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ] + }, + "secret_shared_key": { + "type": "string" + }, + "secret_last4": { + "type": "string", + "description": "Last four characters of the destination's current signing secret, so you can tell secrets apart. Not enough to verify a signature." + }, + "previous_secret_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set for 24 hours after the destination's signing secret is rotated in the Business Console. Until this time, every delivery to the destination also carries `X-Signature-Previous`, `X-Signature-V2-Previous` and `X-Signature-Simple-Previous`, made with the replaced secret (see [Signature verification](/integration/webhooks#signature-verification)). `null` when no rotation happened in the last 24 hours." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + }, + "created_by_email": { + "type": "string", + "nullable": true, + "description": "Email of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_by_name": { + "type": "string", + "nullable": true, + "description": "Name of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "health_status": { + "type": "string", + "enum": [ + "healthy", + "degraded", + "failing" + ], + "description": "Delivery health. `failing`: 3 or more consecutive events failed their first delivery attempt since the destination last answered successfully (see `failing_streak`). `degraded`: not failing, but a delivery attempt failed in the last 24 hours and no later attempt has delivered that event; a retry or resend that delivers it clears this. `healthy`: neither. Independent of `enabled`. A failed attempt is one answered with HTTP 400 or above, or not answered at all." + }, + "failing_streak": { + "type": "integer", + "description": "How many consecutive events failed their first delivery attempt since the destination last answered successfully, within the last 30 days. Retries are not counted, and any successful attempt, including a retry or a resend, ends the streak. `0` when no event has failed since the last success." + }, + "failing_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the first failed attempt of the current `failing_streak` was made. `null` when `failing_streak` is `0`." + }, + "last_success_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the destination last answered a delivery with an HTTP status under 400, within the last 30 days. `null` if it has not in that window." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the destination." + }, + "summary": { + "type": "object", + "properties": { + "total_deliveries": { + "type": "integer" + }, + "failed_deliveries": { + "type": "integer" + }, + "error_rate_percentage": { + "type": "integer" + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true + }, + "last_delivery_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "metrics_window_days": { + "type": "integer", + "description": "Length in days of the window the other summary metrics cover. Always `30`." + } + }, + "description": "Delivery metrics for the last `metrics_window_days` (30) days. `last_delivery_at` is the most recent attempt, whenever it was made." + } + } + }, + "examples": { + "Paused": { + "value": { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": false, + "webhook_version": "v3", + "secret_shared_key": "tno2NTeRC1ZK3sBOYlBJDyEzBVENl3pQ1AcZyAW0xnQ", + "secret_last4": "0xnQ", + "previous_secret_expires_at": null, + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_by_email": null, + "created_by_name": null, + "created_at": "2026-01-04T08:00:00Z", + "updated_at": "2026-05-17T11:02:55Z", + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": "2026-05-17T09:21:18Z", + "environment": "live", + "summary": { + "total_deliveries": 12453, + "failed_deliveries": 14, + "error_rate_percentage": 0, + "min_response_time_ms": 41, + "avg_response_time_ms": 187, + "max_response_time_ms": 2103, + "last_delivery_at": "2026-05-17T09:21:18Z", + "metrics_window_days": 30 + } + } + } + } + } + } + }, + "400": { + "description": "Validation failed (duplicate URL, empty/unknown events list, malformed body).", + "content": { + "application/json": { + "examples": { + "Duplicate URL": { + "value": { + "url": [ + "This webhook destination already exists for the application." + ] + } + }, + "Empty events": { + "value": { + "subscribed_events": [ + "At least one subscribed event is required." + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Destination with this UUID does not exist (or is deleted, or belongs to another application).", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete webhook destination", + "description": "Permanently delete a webhook destination, its signing secret and its entire delivery log. This cannot be undone. No new webhook is sent to the destination's URL; other destinations subscribed to the same events keep receiving them. Afterwards the destination no longer appears in `GET /v3/webhook/destinations/`, and every route under `/v3/webhook/destinations/{destination_uuid}/`, including its deliveries, a delivery's detail and its resend, answers 404. The application's legacy `webhook_url` is set to the URL of the oldest remaining enabled destination, or cleared when no enabled destination is left. Calling DELETE again returns 404.", + "operationId": "delete_webhook_destination", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination to delete.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.webhooks.destinations.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.webhooks.destinations.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Webhooks.Destinations.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.webhooks().destinations().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.webhooks.destinations.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Webhooks.Destinations.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ], + "responses": { + "204": { + "description": "Destination, signing secret and delivery log deleted permanently. No response body." + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No destination with this UUID exists for the application (already deleted or never existed).", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/wallet-screening/": { + "post": { + "summary": "Screen a wallet address", + "description": "Run on-demand AML screening for a single crypto wallet/address without creating a transaction. Resolves your application's configured blockchain analytics provider, screens the address synchronously, and returns the normalised risk result. LIGHTNING is available only with an Elliptic or Chainalysis BYOK connection added through Marketplace. Nothing is written to the transactions table. One AML monitoring usage is billed per successful screening (sandbox applications are not billed).", + "operationId": "screenWallet", + "x-didit-sdk": { + "resource": "wallet_screening", + "method": "screen", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "wallet_address" + ], + "properties": { + "wallet_address": { + "type": "string", + "description": "The crypto address or network identifier to screen. Must match the selected network (e.g. a `0x`-prefixed 40-hex-char address for EVM chains). Alias: `address`.", + "example": "0x94f1b9b64e2932f6a2db338f616844400cd58e8a" + }, + "blockchain": { + "type": "string", + "description": "Optional network identifier. When omitted, the network is read from the address format: `bc1`, `1` and `3` addresses are Bitcoin (BTC), `T` addresses are Tron (TRX) and base58 public keys are Solana (SOL). A `0x` address is valid on every EVM network, so omitting `blockchain` for it answers 422 with the candidate networks in `candidate_chains`. Managed screening supports BTC, ETH, LTC, XRP, BCH, DOGE, TRX, SOL, MATIC, BNB, BASE (Base), OP (Optimism), ARB (Arbitrum One), AVAX (Avalanche C-Chain), ADA (Cardano), XLM (Stellar) and HBAR (Hedera). USDT and USDC are legacy Ethereum aliases, not multi-network searches. Connected-provider coverage varies. LIGHTNING requires a supported connected provider. CELO (Celo), MODE (Mode) and PLUME (Plume) are screened through a connected TRM Labs account; managed screening answers 400 for them. Alias: `currency`.", + "enum": [ + "BTC", + "LIGHTNING", + "ETH", + "LTC", + "XRP", + "BCH", + "DOGE", + "TRX", + "SOL", + "MATIC", + "BNB", + "USDT", + "USDC", + "BASE", + "OP", + "ARB", + "AVAX", + "ADA", + "XLM", + "HBAR", + "CELO", + "MODE", + "PLUME" + ], + "example": "ETH" + }, + "direction": { + "type": "string", + "description": "Optional. Whether the address is being screened as an inbound (deposit) or outbound (withdrawal) counterparty. Accepts `inbound`, `outbound`, `deposit`, or `withdrawal`. Defaults to a neutral pre-transfer screen when omitted. This is passed to the provider as context only; it does not change the returned `risk_score`, `severity`, or the `source_of_funds`/`destination_of_funds` breakdowns, which are always both returned regardless of `direction`. Changing or omitting `direction` therefore does not require re-screening an address.", + "enum": [ + "inbound", + "outbound", + "deposit", + "withdrawal" + ] + } + } + }, + "example": { + "wallet_address": "0x94f1b9b64e2932f6a2db338f616844400cd58e8a", + "blockchain": "ETH" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/wallet-screening/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"wallet_address\": \"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\",\n \"blockchain\": \"ETH\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.wallet_screening.screen(\n wallet_address=\"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\",\n blockchain=\"ETH\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.walletScreening.screen({\n wallet_address: \"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\",\n blockchain: \"ETH\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.WalletScreening.Screen(ctx, didit.WalletScreeningScreenParams{\n\tWalletAddress: \"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\",\n\tBlockchain: didit.Some(didit.WalletScreeningScreenParamsBlockchain(\"ETH\")),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.WalletScreeningScreenParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.walletScreening().screen(WalletScreeningScreenParams.builder()\n .walletAddress(\"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\")\n .blockchain(WalletScreeningScreenParams.Blockchain.of(\"ETH\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "walletScreening->screen([\n 'wallet_address' => '0x94f1b9b64e2932f6a2db338f616844400cd58e8a',\n 'blockchain' => 'ETH',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.wallet_screening.screen(\n wallet_address: \"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\",\n blockchain: \"ETH\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.WalletScreening.ScreenAsync(new WalletScreeningScreenParams\n{\n WalletAddress = \"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\",\n Blockchain = new WalletScreeningScreenParamsBlockchain(\"ETH\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The normalised wallet screening result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletScreeningResult" + }, + "example": { + "provider": "merklescience", + "screening_type": "WALLET_SCREENING", + "risk_score": 95, + "severity": "CRITICAL", + "status": "SCREENED", + "summary": "Merkle Science risk: Critical (5/5). SANCTIONS HIT. Dominant risk: sanctioned. Driven by: the address is attributed to Sanctions / OFAC (Example Sanctions)", + "wallet_address": "0x94f1b9b64e2932f6a2db338f616844400cd58e8a", + "transaction_hash": null, + "blockchain": "ETH", + "block_timestamp": null, + "total_value_usd": null, + "fee_usd": null, + "sender_rows": [], + "receiver_rows": [], + "transaction_flows": [], + "transaction_flows_status": null, + "transaction_flows_error": null, + "source_of_funds": [ + { + "category": "sanctioned", + "percentage": 100.0, + "amount_usd": 9813695.0, + "entity_name": "Example Sanctions", + "entity_type": "Sanctions", + "entity_subtype": "OFAC", + "hops": null, + "description": "Sanctions / OFAC", + "is_direct": true, + "exposure_direction": "incoming", + "exposure_type": "direct", + "provider_entity_id": "118885", + "provider_entity_type_id": "30", + "provider_entity_subtype_id": "125", + "country": "", + "raw_amount": null, + "raw_currency": null + } + ], + "destination_of_funds": [ + { + "category": "defi", + "percentage": 84.824453, + "amount_usd": 7783414.38, + "entity_name": "Example DeFi 7", + "entity_type": "DeFi", + "entity_subtype": "Bridges", + "hops": null, + "description": "DeFi / Bridges", + "is_direct": false, + "exposure_direction": "outgoing", + "exposure_type": "indirect", + "provider_entity_id": "118459", + "provider_entity_type_id": "33", + "provider_entity_subtype_id": "143", + "country": "", + "raw_amount": null, + "raw_currency": null + }, + { + "category": "defi", + "percentage": 15.175547, + "amount_usd": 1392494.37, + "entity_name": "Example DeFi 2", + "entity_type": "DeFi", + "entity_subtype": "Yield Aggregator", + "hops": null, + "description": "DeFi / Yield Aggregator", + "is_direct": false, + "exposure_direction": "outgoing", + "exposure_type": "indirect", + "provider_entity_id": "90192", + "provider_entity_type_id": "33", + "provider_entity_subtype_id": "142", + "country": "", + "raw_amount": null, + "raw_currency": null + } + ], + "counterparty_connections": [ + { + "entity_name": "Example Sanctions", + "entity_type": "Sanctions", + "entity_subtype": "OFAC", + "risk_level": "critical", + "categories": [ + "sanctioned" + ], + "received_usd": null, + "sent_usd": null, + "received_hops": null, + "sent_hops": null, + "percentage": 0.0, + "is_direct": true, + "country": null, + "provider_entity_id": null + }, + { + "entity_name": "Example Sanctions", + "entity_type": "Sanctions", + "entity_subtype": "OFAC", + "risk_level": "critical", + "categories": [ + "sanctioned" + ], + "received_usd": null, + "sent_usd": null, + "received_hops": null, + "sent_hops": null, + "percentage": 0.0, + "is_direct": true, + "country": null, + "provider_entity_id": null + } + ], + "network_nodes": [], + "network_edges": [], + "sanctions_hit": true, + "pep_counterparty": null, + "dominant_risk_category": "sanctioned", + "risk_factors": [ + { + "source": "attribution", + "category": "sanctioned", + "label": "Sanctions / OFAC", + "entity_name": "Example Sanctions", + "direction": "attribution", + "exposure_type": "attribution", + "amount_usd": null, + "percentage": 0.0, + "is_high_risk": true, + "description": "the address is attributed to Sanctions / OFAC (Example Sanctions)" + }, + { + "source": "exposure", + "category": "sanctioned", + "label": "Sanctions / OFAC", + "entity_name": "Example Sanctions", + "direction": "incoming", + "exposure_type": "direct", + "amount_usd": 9813695.0, + "percentage": 51.679304, + "is_high_risk": true, + "description": "51.6793% direct incoming exposure to Sanctions / OFAC (Example Sanctions)" + }, + { + "source": "exposure", + "category": "defi", + "label": "DeFi / Bridges", + "entity_name": "Example DeFi 7", + "direction": "outgoing", + "exposure_type": "indirect", + "amount_usd": 7783414.38, + "percentage": 40.987766, + "is_high_risk": false, + "description": "40.9878% indirect outgoing exposure to DeFi / Bridges (Example DeFi 7)" + }, + { + "source": "exposure", + "category": "defi", + "label": "DeFi / Yield Aggregator", + "entity_name": "Example DeFi 2", + "direction": "outgoing", + "exposure_type": "indirect", + "amount_usd": 1392494.37, + "percentage": 7.33293, + "is_high_risk": false, + "description": "7.33293% indirect outgoing exposure to DeFi / Yield Aggregator (Example DeFi 2)" + } + ], + "raw_response": { + "...": "provider-specific details" + }, + "report_signature": "" + } + } + } + }, + "400": { + "description": "Validation error — malformed wallet address, missing `wallet_address`, unsupported `blockchain`, `blockchain` omitted for an address whose format names no network, or LIGHTNING without an Elliptic or Chainalysis BYOK Marketplace connection." + }, + "401": { + "description": "Missing or invalid `x-api-key`." + }, + "409": { + "description": "No AML provider is configured for the application, or the configured provider does not support wallet screening yet. Configure transaction monitoring first." + }, + "422": { + "description": "`blockchain` omitted for a `0x` address, which every EVM network shares. Nothing is screened or billed; resend with `blockchain` set to one of `candidate_chains`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "blockchain": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "A 0x address is valid on every EVM chain. Send blockchain as one of: ETH, BNB, MATIC, BASE, ARB, OP, CELO, MODE, PLUME." + ] + }, + "candidate_chains": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "ETH", + "BNB", + "MATIC", + "BASE", + "ARB", + "OP", + "CELO", + "MODE", + "PLUME" + ] + } + } + } + } + } + }, + "502": { + "description": "The AML provider could not complete the screening. Retry later." + } + } + } + }, + "/v3/wallet-screening/pdf/": { + "post": { + "summary": "Render a wallet-screening result as a PDF", + "description": "Stateless companion to `POST /v3/wallet-screening/`: repost the exact JSON body that endpoint returned — including the `report_signature` it added — and get back a Didit-branded PDF of the same result. No provider is called and nothing is billed or persisted; this only re-renders a result Didit already produced. The signature ties the PDF to an unmodified result: editing `risk_score`, `severity`, `sanctions_hit`, or any other signed field before reposting fails with `400`.", + "operationId": "screenWalletPdf", + "x-didit-sdk": { + "resource": "wallet_screening", + "method": "screen_pdf", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "description": "The unmodified JSON object returned by `POST /v3/wallet-screening/`, including `report_signature`.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/WalletScreeningResult" + } + ], + "required": [ + "report_signature" + ], + "description": "Same shape as the `POST /v3/wallet-screening/` response — pass that response body back verbatim, including `report_signature`." + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "# $RESULT is the saved JSON body from POST /v3/wallet-screening/\ncurl -X POST 'https://verification.didit.me/v3/wallet-screening/pdf/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d \"$RESULT\" \\\n -o wallet_screening_report.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.wallet_screening.screen_pdf(\n provider=\"your-provider\",\n screening_type=\"WALLET_SCREENING\",\n risk_score=1,\n severity=\"UNKNOWN\",\n status=\"SCREENED\",\n summary=\"your-summary\",\n wallet_address=\"your-wallet-address\",\n blockchain=\"your-blockchain\",\n source_of_funds=[{}],\n destination_of_funds=[{}],\n counterparty_connections=[{}],\n sanctions_hit=True,\n dominant_risk_category=\"your-dominant-risk-category\",\n report_signature=\"your-report-signature\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.walletScreening.screenPdf({\n provider: \"your-provider\",\n screening_type: \"WALLET_SCREENING\",\n risk_score: 1,\n severity: \"UNKNOWN\",\n status: \"SCREENED\",\n summary: \"your-summary\",\n wallet_address: \"your-wallet-address\",\n blockchain: \"your-blockchain\",\n source_of_funds: [{}],\n destination_of_funds: [{}],\n counterparty_connections: [{}],\n sanctions_hit: true,\n dominant_risk_category: \"your-dominant-risk-category\",\n report_signature: \"your-report-signature\",\n});\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.WalletScreening.ScreenPDF(ctx, didit.WalletScreeningResultParam{\n\tProvider: \"your-provider\",\n\tScreeningType: \"WALLET_SCREENING\",\n\tRiskScore: 1,\n\tSeverity: \"UNKNOWN\",\n\tStatus: \"SCREENED\",\n\tSummary: \"your-summary\",\n\tWalletAddress: \"your-wallet-address\",\n\tBlockchain: \"your-blockchain\",\n\tSourceOfFunds: []didit.WalletScreeningResultParamSourceOfFunds{{}},\n\tDestinationOfFunds: []didit.WalletScreeningResultParamDestinationOfFunds{{}},\n\tCounterpartyConnections: []didit.WalletScreeningResultParamCounterpartyConnections{{}},\n\tSanctionsHit: true,\n\tDominantRiskCategory: didit.String(\"your-dominant-risk-category\"),\n\tReportSignature: \"your-report-signature\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.WalletScreeningResult;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.walletScreening().screenPdf(WalletScreeningResult.builder()\n .provider(\"your-provider\")\n .screeningType(WalletScreeningResult.ScreeningType.of(\"WALLET_SCREENING\"))\n .riskScore(1L)\n .severity(WalletScreeningResult.Severity.of(\"UNKNOWN\"))\n .status(WalletScreeningResult.Status.of(\"SCREENED\"))\n .summary(\"your-summary\")\n .walletAddress(\"your-wallet-address\")\n .blockchain(\"your-blockchain\")\n .sourceOfFunds(List.of(WalletScreeningResult.SourceOfFunds.builder()\n .build()))\n .destinationOfFunds(List.of(WalletScreeningResult.DestinationOfFunds.builder()\n .build()))\n .counterpartyConnections(List.of(WalletScreeningResult.CounterpartyConnections.builder()\n .build()))\n .sanctionsHit(true)\n .dominantRiskCategory(\"your-dominant-risk-category\")\n .reportSignature(\"your-report-signature\")\n .build())\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "walletScreening->screenPdf([\n 'provider' => 'your-provider',\n 'screening_type' => 'WALLET_SCREENING',\n 'risk_score' => 1,\n 'severity' => 'UNKNOWN',\n 'status' => 'SCREENED',\n 'summary' => 'your-summary',\n 'wallet_address' => 'your-wallet-address',\n 'blockchain' => 'your-blockchain',\n 'source_of_funds' => [[]],\n 'destination_of_funds' => [[]],\n 'counterparty_connections' => [[]],\n 'sanctions_hit' => true,\n 'dominant_risk_category' => 'your-dominant-risk-category',\n 'report_signature' => 'your-report-signature',\n]);\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.wallet_screening.screen_pdf(\n provider: \"your-provider\",\n screening_type: \"WALLET_SCREENING\",\n risk_score: 1,\n severity: \"UNKNOWN\",\n status: \"SCREENED\",\n summary: \"your-summary\",\n wallet_address: \"your-wallet-address\",\n blockchain: \"your-blockchain\",\n source_of_funds: [{}],\n destination_of_funds: [{}],\n counterparty_connections: [{}],\n sanctions_hit: true,\n dominant_risk_category: \"your-dominant-risk-category\",\n report_signature: \"your-report-signature\"\n)\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.WalletScreening.ScreenPdfAsync(new WalletScreeningResult\n{\n Provider = \"your-provider\",\n ScreeningType = \"WALLET_SCREENING\",\n RiskScore = 1,\n Severity = \"UNKNOWN\",\n Status = \"SCREENED\",\n Summary = \"your-summary\",\n WalletAddress = \"your-wallet-address\",\n Blockchain = \"your-blockchain\",\n SourceOfFunds = new WalletScreeningResultSourceOfFunds[] { new() },\n DestinationOfFunds = new WalletScreeningResultDestinationOfFunds[] { new() },\n CounterpartyConnections = new WalletScreeningResultCounterpartyConnections[] { new() },\n SanctionsHit = true,\n DominantRiskCategory = \"your-dominant-risk-category\",\n ReportSignature = \"your-report-signature\",\n});\nawait content.SaveToAsync(\"download.pdf\");" + } + ], + "responses": { + "200": { + "description": "The rendered PDF document, returned directly as binary `application/pdf` — there is no JSON wrapper.", + "headers": { + "Content-Disposition": { + "description": "`attachment; filename=wallet_screening_{address}.pdf`, derived from the `wallet_address` (or `transaction_hash`) in the reposted result.", + "schema": { + "type": "string", + "example": "attachment; filename=wallet_screening_0x28c6c06298d514db0899.pdf" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "400": { + "description": "The body is not a JSON object matching the wallet-screening result shape, or `report_signature` is missing or does not match the reposted fields (edited or truncated before repost).", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "examples": { + "Not an object": { + "value": { + "non_field_errors": [ + "Expected a JSON object matching a wallet-screening result." + ] + } + }, + "Missing or invalid signature": { + "value": { + "report_signature": [ + "This wallet-screening result is missing or fails its report signature - repost the exact JSON returned by the wallet-screening endpoint, unmodified." + ] + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid `x-api-key`." + } + } + } + }, + "/v3/transactions/": { + "get": { + "summary": "List transactions", + "description": "Paginated list of monitored transactions, newest first (`txn_date` desc). `count` is capped at 100; use `total_transactions` for the true row count. This endpoint does not support filtering or search parameters.", + "operationId": "listTransactions", + "x-didit-sdk": { + "resource": "transactions", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "description": "Page size. Defaults to 50 when omitted. There is no enforced maximum, but keep pages small for latency." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "description": "Zero-based offset of the first record to return. Prefer following the `next` URL from the previous page." + }, + { + "name": "travel_rule_status", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Return only transactions whose Travel Rule transfer is in this exact status (for example `ON_HOLD` or `AWAITING_COUNTERPARTY`). `count` follows this filter; `total_transactions` still counts every transaction in the application." + }, + { + "name": "assignee", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated; a transaction matches when it meets any value. `unassigned` is what nobody holds yet, `rules` is what the rules decided on their own without a reviewer, and a member email matches the reviewer assigned to it. `me` is the signed-in Console reviewer, so an API key sending `me` gets `400`." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.transactions.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.transactions.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Transactions.ListAutoPaging(ctx, didit.TransactionsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.transactions().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Transactions.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated list of transaction summaries, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Capped count of matching transactions (exact up to 100, capped thereafter). Use `total_transactions` for the true count when paginating." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "total_transactions": { + "type": "integer", + "description": "Exact total count of (non-deleted) transactions for the application. Present on paginated responses." + }, + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Stable Didit transaction identifier. Use as `{transaction_id}` in detail/notes/related calls." + }, + "transaction_number": { + "type": "integer", + "description": "Application-scoped sequential transaction number shown in Console (e.g. `4123`)." + }, + "txn_id": { + "type": "string", + "description": "Customer-provided identifier from the create call (max 128 chars). Unique per application." + }, + "transaction_type": { + "type": "string", + "description": "Top-level category as stored: `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet`, `gamblingLimitChange`, `gamblingBonusChange`, `auditTrailEvent`. Note multi-word values are echoed back in camelCase even when submitted in snake_case." + }, + "action_type": { + "type": "string", + "description": "Sub-type within the category (e.g. `deposit`, `withdrawal`, `transfer`)." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Current monitoring verdict." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject. Stored uppercase regardless of the casing submitted." + }, + "amount": { + "type": "string", + "description": "Transaction amount as a decimal string with trailing zeros stripped (e.g. `\"1500\"`, `\"0.5\"`, `\"0.123456789012345678\"`). Up to 18 decimal places." + }, + "currency": { + "type": "string", + "description": "Currency code of `amount`. Any ISO 4217 alphabetic code is accepted (e.g. `EUR`, `USD`, `NGN`), as is a crypto asset symbol (e.g. `BTC`). Prefer a currently-circulating code. Not every accepted code has an exchange rate: when none is available for the code (for example the ISO accounting unit `XSU`, or a crypto symbol Didit does not price), automatic conversion fails and `preferred_currency_amount` is returned null unless the request supplied a pre-converted `amount_in_default_currency`." + }, + "preferred_currency_amount": { + "type": "string", + "nullable": true, + "description": "`amount` converted to the application's preferred currency, when available." + }, + "preferred_currency_code": { + "type": "string", + "nullable": true, + "description": "Preferred currency code used for `preferred_currency_amount` - the application's base currency, any currently-circulating ISO 4217 code. A code with no available exchange rate leaves `preferred_currency_amount` null unless the request supplied a pre-converted `amount_in_default_currency`." + }, + "payment_details": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference or memo from the submission." + }, + "score": { + "type": "integer", + "description": "Risk score (typically 0–100). Higher = riskier." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ], + "description": "Categorical risk severity set by rules/providers (`UNKNOWN`, `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`). `null` until something sets it." + }, + "subject_name": { + "type": "string", + "nullable": true, + "description": "Full name of the `APPLICANT` party." + }, + "subject_vendor_data": { + "type": "string", + "nullable": true, + "description": "Your internal user/business identifier for the applicant." + }, + "subject_country": { + "type": "string", + "nullable": true, + "description": "Country code of the applicant, from the submitted `address.country` (typically ISO 3166-1 alpha-3)." + }, + "counterparty_name": { + "type": "string", + "nullable": true, + "description": "Full name of the `COUNTERPARTY` party, if any." + }, + "counterparty_vendor_data": { + "type": "string", + "nullable": true, + "description": "Your internal identifier for the counterparty." + }, + "counterparty_country": { + "type": "string", + "nullable": true, + "description": "Country code of the counterparty, from the submitted `address.country`." + }, + "tags": { + "type": "array", + "description": "Tags attached to the transaction (manually or by rules).", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ], + "description": "Whether the tag is a Didit preset or a custom tag." + } + } + } + }, + "txn_date": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred (from `transaction_at` at create time)." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When Didit recorded the submission." + }, + "decision_reason_code": { + "type": "string", + "nullable": true, + "description": "Machine-readable reason for the current `status` (e.g. `WALLET_HIGH_RISK`)." + }, + "decision_reason_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label for `decision_reason_code`." + }, + "assigned_to_name": { + "type": "string", + "nullable": true, + "description": "Display name of the Console reviewer assigned to this transaction." + }, + "travel_rule_status": { + "type": "string", + "nullable": true, + "description": "Status of the transaction's Travel Rule transfer (for example `COMPLETED`), or `null` when it has none." + }, + "travel_rule": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TransactionTravelRule" + } + ], + "description": "The transaction's Travel Rule transfer, the same block as `travel_rule` on `GET /v3/transactions/{transaction_id}/`, or `null` when the transaction has none." + } + } + } + } + } + }, + "examples": { + "Single transaction": { + "value": { + "count": 1, + "next": null, + "previous": null, + "total_transactions": 1, + "results": [ + { + "uuid": "abcdef12-3456-4890-abcd-ef1234567890", + "transaction_number": 4123, + "txn_id": "your-txn-2026-05-17-001", + "transaction_type": "finance", + "action_type": "withdrawal", + "status": "IN_REVIEW", + "direction": "OUTBOUND", + "amount": "1500", + "currency": "EUR", + "preferred_currency_amount": "1620.45", + "preferred_currency_code": "USD", + "payment_details": "Withdrawal to crypto wallet", + "txn_date": "2026-05-17T08:42:00Z", + "created_at": "2026-05-17T08:42:11Z", + "score": 78, + "severity": "HIGH", + "decision_reason_code": "WALLET_HIGH_RISK", + "decision_reason_label": "Destination wallet flagged as high risk", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "Manual review", + "color": "#F59E0B", + "description": null, + "source": "custom" + } + ], + "subject_name": "Maria Garcia", + "subject_vendor_data": "user-12345", + "counterparty_name": null, + "counterparty_vendor_data": null, + "counterparty_country": null, + "subject_country": null, + "assigned_to_name": "Alex Reviewer", + "travel_rule_status": null, + "travel_rule": null + } + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot list transactions for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create transaction", + "description": "Submit a transaction for synchronous AML/risk monitoring. The transaction is created `APPROVED`, then your rules (and crypto screening, when applicable) run inline and may flip the returned `status` to `IN_REVIEW` or `DECLINED`. **`transaction_id` is your idempotency key** (max 128 chars, unique per app); reusing it returns 400. A `transaction.created` webhook is dispatched on success for live applications. Sandbox applications are not billed and do not emit `transaction.created` or `transaction.status.updated` webhooks. Sandbox Travel Rule exchanges do emit `travel_rule.status.updated` webhooks with `environment: sandbox`. A Travel Rule transfer is stored and returned before any counterparty is contacted: `travel_rule.status` comes back `AWAITING_COUNTERPARTY` (or `NOT_APPLICABLE` below your threshold, `UNCONFIRMED_OWNERSHIP` for a declared self-hosted wallet, `COMPLETED` when the other wallet is your own verified wallet) and `travel_rule.protocol` is null. Didit then chooses the rail and sends the exchange, and every later status change of the transfer, including `COUNTERPARTY_VASP_NOT_FOUND` when no rail reaches the counterparty, arrives as a `travel_rule.status.updated` webhook. Your rules run again when the rail or the status changes.", + "operationId": "createTransaction", + "tags": [ + "Transactions" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"transaction_id\": \"your-txn-2026-05-17-001\",\n \"transaction_category\": \"finance\",\n \"transaction_details\": {\n \"direction\": \"outbound\",\n \"amount\": 1500,\n \"currency\": \"EUR\",\n \"action_type\": \"withdrawal\"\n },\n \"subject\": {\n \"entity_type\": \"individual\",\n \"vendor_data\": \"user-12345\",\n \"full_name\": \"Maria Garcia\"\n }\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.create(\n transaction_id=\"your-txn-2026-05-17-001\",\n transaction_category=\"finance\",\n transaction_details={\n \"direction\": \"outbound\",\n \"amount\": 1500.0,\n \"currency\": \"EUR\",\n \"action_type\": \"withdrawal\",\n },\n subject={\n \"entity_type\": \"individual\",\n \"vendor_data\": \"user-12345\",\n \"full_name\": \"Maria Garcia\",\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.create({\n transaction_id: \"your-txn-2026-05-17-001\",\n transaction_category: \"finance\",\n transaction_details: {\n direction: \"outbound\",\n amount: 1500.0,\n currency: \"EUR\",\n action_type: \"withdrawal\",\n },\n subject: {\n entity_type: \"individual\",\n vendor_data: \"user-12345\",\n full_name: \"Maria Garcia\",\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Create(ctx, didit.TransactionsCreateParams{\n\tTransactionID: \"your-txn-2026-05-17-001\",\n\tTransactionCategory: \"finance\",\n\tTransactionDetails: didit.TransactionsCreateParamsTransactionDetails{\n\t\tDirection: \"outbound\",\n\t\tAmount: 1500.0,\n\t\tCurrency: \"EUR\",\n\t\tActionType: didit.String(\"withdrawal\"),\n\t},\n\tSubject: didit.TransactionsCreateParamsSubject{\n\t\tEntityType: didit.String(\"individual\"),\n\t\tVendorData: \"user-12345\",\n\t\tFullName: \"Maria Garcia\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TransactionsCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().create(TransactionsCreateParams.builder()\n .transactionId(\"your-txn-2026-05-17-001\")\n .transactionCategory(TransactionsCreateParams.TransactionCategory.of(\"finance\"))\n .transactionDetails(TransactionsCreateParams.TransactionDetails.builder()\n .direction(TransactionsCreateParams.TransactionDetails.Direction.of(\"outbound\"))\n .amount(1500.0)\n .currency(\"EUR\")\n .actionType(\"withdrawal\")\n .build())\n .subject(TransactionsCreateParams.Subject.builder()\n .entityType(\"individual\")\n .vendorData(\"user-12345\")\n .fullName(\"Maria Garcia\")\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->create([\n 'transaction_id' => 'your-txn-2026-05-17-001',\n 'transaction_category' => 'finance',\n 'transaction_details' => [\n 'direction' => 'outbound',\n 'amount' => 1500.0,\n 'currency' => 'EUR',\n 'action_type' => 'withdrawal',\n ],\n 'subject' => [\n 'entity_type' => 'individual',\n 'vendor_data' => 'user-12345',\n 'full_name' => 'Maria Garcia',\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.create(\n transaction_id: \"your-txn-2026-05-17-001\",\n transaction_category: \"finance\",\n transaction_details: {\n direction: \"outbound\",\n amount: 1500.0,\n currency: \"EUR\",\n action_type: \"withdrawal\",\n },\n subject: {\n entity_type: \"individual\",\n vendor_data: \"user-12345\",\n full_name: \"Maria Garcia\",\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.CreateAsync(new TransactionsCreateParams\n{\n TransactionId = \"your-txn-2026-05-17-001\",\n TransactionCategory = \"finance\",\n TransactionDetails = new()\n {\n Direction = \"outbound\",\n Amount = 1500.0,\n Currency = \"EUR\",\n ActionType = \"withdrawal\",\n },\n Subject = new()\n {\n EntityType = \"individual\",\n VendorData = \"user-12345\",\n FullName = \"Maria Garcia\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "transaction_id": { + "type": "string", + "description": "Your unique identifier for this transaction (max 128 characters).", + "example": "your-txn-2026-05-17-001" + }, + "transaction_at": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred. Defaults to now if omitted." + }, + "time_zone": { + "type": "string", + "description": "IANA time zone identifier, e.g. `Europe/Madrid`." + }, + "transaction_category": { + "type": "string", + "description": "Category of the transaction. CamelCase aliases (`travelRule`, `userPlatformEvent`, `gamblingBet`, …) are also accepted; the stored/echoed value is camelCase for multi-word categories.", + "example": "finance", + "enum": [ + "finance", + "kyc", + "travel_rule", + "user_event", + "audit_trail_event", + "gambling_bet", + "gambling_limit_change", + "gambling_bonus_change", + "travelRule" + ] + }, + "transaction_details": { + "type": "object", + "description": "Core financial details of the transaction.", + "required": [ + "direction", + "amount", + "currency" + ], + "properties": { + "direction": { + "type": "string", + "description": "Direction of the transaction relative to the subject. Case-insensitive; `in`/`out` are accepted as aliases. Echoed back uppercase (`INBOUND`/`OUTBOUND`).", + "example": "outbound", + "enum": [ + "inbound", + "outbound", + "in", + "out" + ] + }, + "amount": { + "type": "number", + "description": "Transaction amount (up to 18 decimal places). Send it as a decimal string (e.g. `\"0.123456789012345678\"`) when exact precision matters: a JSON number is parsed as a binary float first, so digits beyond about 15 significant digits are rounded before the amount is stored. Must be zero or greater: the direction carries the sign, and a negative amount returns `400`.", + "example": 1500 + }, + "currency": { + "type": "string", + "description": "Currency code. Any ISO 4217 alphabetic code is accepted (e.g. `EUR`, `USD`, `NGN`), as is a crypto asset symbol (e.g. `BTC`). Prefer a currently-circulating code. Not every accepted code has an exchange rate: when none is available for the code (for example the ISO accounting unit `XSU`, or a crypto symbol Didit does not price), automatic conversion fails and `preferred_currency_amount` is returned null unless the request supplied a pre-converted `amount_in_default_currency`.", + "example": "EUR" + }, + "currency_kind": { + "type": "string", + "description": "Whether the currency is `fiat` or `crypto`." + }, + "amount_in_default_currency": { + "type": "number", + "nullable": true, + "description": "Pre-converted amount in the default currency. If omitted, automatic FX conversion is attempted. Must be zero or greater." + }, + "default_currency": { + "type": "string", + "description": "The default currency code for the converted amount. Any currently-circulating ISO 4217 code." + }, + "payment_details": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference or memo." + }, + "payment_reference_id": { + "type": "string", + "nullable": true, + "description": "External payment system reference. For crypto AML transaction-hash screening, send the blockchain transaction hash and also provide `subject.payment_method.account_id` with the related address." + }, + "action_type": { + "type": "string", + "nullable": true, + "description": "Sub-type of the transaction, e.g. `deposit`, `withdrawal`, `transfer`." + }, + "crypto_params": { + "type": "object", + "description": "Crypto network selection, separate from currency (the asset symbol). Recording a custom token symbol does not guarantee transaction-hash coverage for its contract.", + "properties": { + "crypto_chain": { + "type": "string", + "description": "Actual network used for screening. Managed screening supports BTC, ETH, LTC, XRP, BCH, DOGE, TRX, SOL, MATIC, BNB, BASE (Base), OP (Optimism), ARB (Arbitrum One), AVAX (Avalanche C-Chain), ADA (Cardano), XLM (Stellar) and HBAR (Hedera). Common network names are accepted too, for example POLYGON or POL for MATIC, BSC for BNB and TRON for TRX. Always specify this for multi-network tokens. When it is missing for USDT, USDC or ETH sent with a 0x transaction hash, Didit looks the hash up on Ethereum, BNB Chain, Polygon, Base, Arbitrum and Optimism and screens it on the network where it is found; if no network has it, the transaction is not screened and custom_values.aml_screening_status is FAILED with the reason in custom_values.aml_screening_error. When it is missing for USDT or USDC screened by wallet (no transaction hash), a Tron address is screened on Tron, a Solana address on Solana, and a 0x address on the first of those six networks where it has sent a transaction or holds the token, or on Ethereum when it is active on none of them. Connected-provider coverage varies; LIGHTNING requires a supported connected provider.", + "example": "BASE" + } + } + } + } + }, + "subject": { + "type": "object", + "description": "The subject (applicant) of the transaction.", + "required": [ + "vendor_data", + "full_name" + ], + "properties": { + "entity_type": { + "type": "string", + "description": "Entity type: `individual` or `company`." + }, + "vendor_data": { + "type": "string", + "description": "Your internal user/business identifier.", + "example": "user-12345" + }, + "full_name": { + "type": "string", + "description": "Full name of the subject.", + "example": "Maria Garcia" + }, + "first_name": { + "type": "string", + "description": "First name of the subject." + }, + "last_name": { + "type": "string", + "description": "Last name of the subject." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth (YYYY-MM-DD)." + }, + "address": { + "type": "object", + "description": "Address object.", + "properties": { + "country": { + "type": "string", + "description": "Country code (ISO 3166-1 alpha-3 recommended). Copied to the party's `country_code`." + }, + "town": { + "type": "string" + }, + "state": { + "type": "string" + }, + "street": { + "type": "string" + }, + "post_code": { + "type": "string" + } + } + }, + "institution_details": { + "type": "object", + "description": "Institution details (for institutional subjects).", + "properties": { + "name": { + "type": "string" + }, + "code": { + "type": "string" + }, + "address": { + "type": "object" + } + } + }, + "device_context": { + "type": "object", + "description": "Device context captured at transaction time. browser_family and platform must be sent as explicit top-level keys — they are not derived from user_agent. The camelCase alias device is also accepted on the request.", + "properties": { + "fingerprint": { + "type": "string", + "description": "Unique device fingerprint." + }, + "persistent_device_id": { + "type": "string", + "description": "Stable cross-session device identifier (v2 fingerprint schema)." + }, + "user_agent": { + "type": "string", + "description": "Browser or app user agent string. CamelCase alias: userAgent." + }, + "browser_family": { + "type": "string", + "description": "Browser family (e.g. Chrome, Firefox, Safari). Must be sent explicitly — it is not derived from user_agent. CamelCase alias: browserFamily." + }, + "browser_version": { + "type": "string", + "description": "Browser version string. Must be sent explicitly. CamelCase alias: browserVersion." + }, + "platform": { + "type": "string", + "description": "Platform identifier (web, ios, android, macos, windows, linux). Must be sent explicitly — it is not derived from user_agent." + }, + "os_family": { + "type": "string", + "description": "Operating system family (e.g. iOS, Android, Windows). CamelCase alias: osFamily." + }, + "os_version": { + "type": "string", + "description": "Operating system version string. CamelCase alias: osVersion." + }, + "accept_language": { + "type": "string", + "description": "Accept-Language header value. CamelCase alias: acceptLang." + }, + "session_id": { + "type": "string", + "description": "Session identifier of the user current session. Stored in metadata. CamelCase alias: sessionId." + }, + "session_age_ms": { + "type": "number", + "description": "Age of the user session in milliseconds at transaction time. CamelCase alias: sessionAgeMs." + }, + "network_context": { + "type": "object", + "description": "Network context with IP address and geo-location. A top-level ipInfo block is also folded into this object.", + "properties": { + "ip_address": { + "type": "string", + "description": "Client IP address. CamelCase alias: ip." + }, + "ip_country_code": { + "type": "string", + "description": "Geo-resolved country of the IP (ISO 3166-1 alpha-3). CamelCase alias: countryCode3." + }, + "latitude": { + "type": "number", + "description": "Geographic latitude of the IP. CamelCase alias: lat." + }, + "longitude": { + "type": "number", + "description": "Geographic longitude of the IP. CamelCase alias: lon." + } + } + } + } + }, + "payment_method": { + "type": "object", + "description": "Payment method used by the subject.", + "properties": { + "method_type": { + "type": "string", + "description": "Type of payment method: `bank_card`, `bank_account`, `crypto_wallet`, `ewallet`, `unhosted_wallet`, `other`." + }, + "account_id": { + "type": "string", + "description": "Account identifier (e.g. IBAN, wallet address). For crypto screening, this should contain the wallet address for this participant. Inbound transaction-hash screening requires the service/customer deposit address on the subject. Outbound screening uses the destination wallet on the counterparty. On a crypto transfer on bitcoin or Lightning it may be a Lightning payment request (BOLT11 invoice, BOLT12 offer, LNURL) or a Lightning Address: Didit stores the payee it names as the account (\"\" when it names none) and what the request says on the Travel Rule transfer's `chain_details.lightning`. At most 255 characters once stored. On a crypto transfer on bitcoin or Lightning it may be a Lightning payment request (BOLT11 invoice, BOLT12 offer, LNURL) or a Lightning Address: Didit stores the payee it names as the account (\"\" when it names none) and what the request says on the Travel Rule transfer's `chain_details.lightning`. At most 255 characters once stored." + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "The destination tag or memo that is part of the wallet on a memo network (XRP, XLM, EOS, ATOM, HBAR). On one shared exchange address each memo is its own payment method. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`. Alias: `destinationTag` or `destination_tag`." + }, + "issuing_country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code of the issuer." + } + } + } + } + }, + "counterparty": { + "type": "object", + "description": "The counterparty of the transaction (optional).", + "properties": { + "entity_type": { + "type": "string", + "description": "Entity type: `individual` or `company`." + }, + "vendor_data": { + "type": "string", + "description": "Your internal identifier for the counterparty." + }, + "full_name": { + "type": "string", + "description": "Full name of the counterparty." + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "address": { + "type": "object", + "description": "Address object with `country` (ISO 3166-1 alpha-3)." + }, + "institution_details": { + "type": "object" + }, + "device_context": { + "type": "object", + "description": "Device context for the counterparty. browser_family and platform must be sent as explicit top-level keys — they are not derived from user_agent.", + "properties": { + "fingerprint": { + "type": "string", + "description": "Unique device fingerprint." + }, + "persistent_device_id": { + "type": "string", + "description": "Stable cross-session device identifier (v2 fingerprint schema)." + }, + "user_agent": { + "type": "string", + "description": "Browser or app user agent string. CamelCase alias: userAgent." + }, + "browser_family": { + "type": "string", + "description": "Browser family (e.g. Chrome, Firefox, Safari). Must be sent explicitly — it is not derived from user_agent. CamelCase alias: browserFamily." + }, + "browser_version": { + "type": "string", + "description": "Browser version string. Must be sent explicitly. CamelCase alias: browserVersion." + }, + "platform": { + "type": "string", + "description": "Platform identifier (web, ios, android, macos, windows, linux). Must be sent explicitly — it is not derived from user_agent." + }, + "os_family": { + "type": "string", + "description": "Operating system family (e.g. iOS, Android, Windows). CamelCase alias: osFamily." + }, + "os_version": { + "type": "string", + "description": "Operating system version string. CamelCase alias: osVersion." + }, + "accept_language": { + "type": "string", + "description": "Accept-Language header value. CamelCase alias: acceptLang." + }, + "session_id": { + "type": "string", + "description": "Session identifier for the counterparty session. Stored in metadata. CamelCase alias: sessionId." + }, + "session_age_ms": { + "type": "number", + "description": "Age of the user session in milliseconds at transaction time. CamelCase alias: sessionAgeMs." + }, + "network_context": { + "type": "object", + "description": "Network context with IP address and geo-location. A top-level ipInfo block is also folded into this object.", + "properties": { + "ip_address": { + "type": "string", + "description": "Client IP address. CamelCase alias: ip." + }, + "ip_country_code": { + "type": "string", + "description": "Geo-resolved country of the IP (ISO 3166-1 alpha-3). CamelCase alias: countryCode3." + }, + "latitude": { + "type": "number", + "description": "Geographic latitude of the IP. CamelCase alias: lat." + }, + "longitude": { + "type": "number", + "description": "Geographic longitude of the IP. CamelCase alias: lon." + } + } + } + } + }, + "payment_method": { + "type": "object", + "description": "Payment method used by the counterparty. On travelRule transactions, `method_type: \"unhosted_wallet\"` declares the counterparty wallet as self-hosted: the transfer skips counterparty-VASP routing, is created directly in `UNCONFIRMED_OWNERSHIP`, and (when `auto_wallet_verification` is enabled) a wallet-ownership widget session is auto-minted and returned in the transaction's `action_required` block.", + "properties": { + "method_type": { + "type": "string", + "description": "Type of payment method: `bank_card`, `bank_account`, `crypto_wallet`, `ewallet`, `unhosted_wallet`, `other`. Use `unhosted_wallet` to declare a self-hosted wallet for the Travel Rule." + }, + "account_id": { + "type": "string", + "description": "Account identifier (e.g. IBAN or wallet address). On a crypto transfer on bitcoin or Lightning it may be a Lightning payment request (BOLT11 invoice, BOLT12 offer, LNURL) or a Lightning Address: Didit stores the payee it names as the account (\"\" when it names none) and what the request says on the Travel Rule transfer's `chain_details.lightning`. At most 255 characters once stored. On a crypto transfer on bitcoin or Lightning it may be a Lightning payment request (BOLT11 invoice, BOLT12 offer, LNURL) or a Lightning Address: Didit stores the payee it names as the account (\"\" when it names none) and what the request says on the Travel Rule transfer's `chain_details.lightning`. At most 255 characters once stored." + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "The destination tag or memo that is part of the wallet on a memo network (XRP, XLM, EOS, ATOM, HBAR). On one shared exchange address each memo is its own payment method. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`. Alias: `destinationTag` or `destination_tag`." + }, + "issuing_country": { + "type": "string" + } + } + } + } + }, + "custom_properties": { + "type": "object", + "description": "Arbitrary key-value pairs attached to the transaction. Each key can be referenced in rule conditions with the field path `custom_values.`.", + "additionalProperties": true, + "example": { + "merchant_id": "test", + "bank_name": "test_2" + } + }, + "travel_rule_details": { + "type": "object", + "description": "Travel Rule data. When Travel Rule is enabled and transaction_category is travel_rule, Didit runs the exchange itself: it ignores status, protocol, required and obligations_count, and uses originator_data, beneficiary_data and metadata. Otherwise Didit stores these values as a passive Travel Rule record. While Travel Rule is on, an outbound transfer at or above your threshold must carry what its regime requires (EU TFR or FATF Recommendation 16), checked on the sides you send: a missing field answers 400 under `travel_rule_details.originator_data` or `travel_rule_details.beneficiary_data`, for example \"EU TFR requires the originator name.\"", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "description": "Required whenever you send travel_rule_details. One of the Travel Rule transfer statuses (for example AWAITING_COUNTERPARTY, COMPLETED, NOT_APPLICABLE); lowercase and camelCase spellings and the legacy values UNKNOWN, COMPLIANT, PENDING_ACTION, PENDING_COUNTERPARTY, FAILED and EXEMPT are also accepted and mapped. Any other value returns 400." + }, + "protocol": { + "type": "string" + }, + "required": { + "type": "boolean" + }, + "obligations_count": { + "type": "integer" + }, + "originator_data": { + "type": "object", + "description": "Originator party, as flat fields or one IVMS 101 person. For your user with an approved KYC verification, Didit replaces the fields the verification holds with the verified values (see `metadata.originator_provenance`). It must be an object: anything else is a `400`. On a memo network a `memo` key names the originator wallet's memo or tag." + }, + "beneficiary_data": { + "type": "object", + "description": "Beneficiary party, as flat fields or one IVMS 101 person. It must be an object: anything else is a `400`. To route the transfer, Didit reads `wallet_address` (an empty one is filled from `counterparty.payment_method.account_id`), `chain` (filled from `transaction_details.crypto_params.crypto_chain` or the `currency` when that names a supported chain), `name` (falling back to `counterparty.full_name`), `travel_address`, `counterparty_vasp_name`, `counterparty_vasp_lei` and `counterparty_vasp_email`. On a memo network `memo` names the beneficiary wallet's memo or tag; `wallet_address` and `memo` win over the counterparty's payment method." + }, + "metadata": { + "type": "object" + }, + "utxo": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleUtxo" + } + ], + "description": "Optional, UTXO chains only (BTC, LTC, BCH, DOGE). The transaction's inputs and outputs, from which Didit infers the originator wallet and the beneficiary output when you do not declare them." + }, + "originator": { + "type": "object", + "description": "An IVMS 101 originator block: `originatorPersons` (one `naturalPerson` or `legalPerson`; a legal person makes the party a company) and `accountNumber` (wallet addresses). Used for `originator_data` when that object is empty. A malformed block answers 400 keyed by its path, for example `originator.originatorPersons`." + }, + "beneficiary": { + "type": "object", + "description": "An IVMS 101 beneficiary block: `beneficiaryPersons` (one `naturalPerson` or `legalPerson`; a legal person makes the party a company) and `accountNumber` (wallet addresses). Used for `beneficiary_data` when that object is empty. A malformed block answers 400 keyed by its path, for example `beneficiary.beneficiaryPersons`." + }, + "ivms101": { + "type": "object", + "description": "The `originator` and `beneficiary` blocks wrapped in one IVMS 101 object (`IVMS101` is accepted too). Top-level blocks win over the wrapper's." + } + } + }, + "network_snapshot": { + "type": "object", + "description": "Pre-computed network graph snapshot.", + "properties": { + "nodes": { + "type": "array", + "items": { + "type": "object" + } + }, + "edges": { + "type": "array", + "items": { + "type": "object" + } + }, + "metrics": { + "type": "object" + } + } + }, + "include_crypto_screening": { + "type": "boolean", + "nullable": true, + "description": "Per-transaction override for crypto blockchain analytics screening. When `true`, crypto screening is performed regardless of the application default. When `false`, crypto screening is skipped even if enabled in the application settings. When omitted or `null`, the application-level default configured in the Console is used. Crypto screening requires `transaction_details.direction`, `currency_kind: \"crypto\"`, a chain/currency, and the wallet address selected by the direction: inbound pre-transfer screening uses `counterparty.payment_method.account_id`, inbound transaction-hash screening uses `subject.payment_method.account_id`, and outbound screening uses `counterparty.payment_method.account_id`. When `transaction_details.payment_reference_id` contains a blockchain transaction hash, Didit performs transaction screening and returns transaction summary enrichment when available." + } + }, + "required": [ + "transaction_id", + "transaction_category", + "transaction_details", + "subject" + ] + }, + "examples": { + "Minimal fiat withdrawal": { + "summary": "Smallest acceptable payload for a fiat outbound transfer", + "value": { + "transaction_id": "your-txn-2026-05-17-001", + "transaction_category": "finance", + "transaction_details": { + "direction": "outbound", + "amount": 1500, + "currency": "EUR", + "action_type": "withdrawal" + }, + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia" + } + } + }, + "Crypto outbound with screening": { + "summary": "Outbound crypto withdrawal to an unhosted wallet, opt-in screening", + "value": { + "transaction_id": "your-txn-2026-05-17-002", + "transaction_category": "finance", + "include_crypto_screening": true, + "transaction_details": { + "direction": "outbound", + "amount": 0.5, + "currency": "BTC", + "currency_kind": "crypto", + "amount_in_default_currency": 32500, + "default_currency": "USD", + "action_type": "withdrawal" + }, + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "payment_method": { + "method_type": "crypto_wallet", + "account_id": "bc1qexampleexchangewalletaddressxyz", + "issuing_country": "ESP" + } + }, + "counterparty": { + "entity_type": "individual", + "payment_method": { + "method_type": "unhosted_wallet", + "account_id": "bc1qexampleexternalwalletaddressabc" + } + } + } + }, + "Travel Rule transfer": { + "summary": "Outbound crypto transfer with Travel Rule originator/beneficiary data", + "value": { + "transaction_id": "your-txn-2026-05-17-003", + "transaction_category": "travel_rule", + "transaction_details": { + "direction": "outbound", + "amount": 25000, + "currency": "USDT", + "currency_kind": "crypto", + "action_type": "transfer" + }, + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "date_of_birth": "1992-03-14", + "address": { + "country": "ESP", + "town": "Madrid", + "street": "Calle Mayor 1", + "post_code": "28013" + } + }, + "counterparty": { + "entity_type": "individual", + "full_name": "John Beneficiary", + "institution_details": { + "name": "Beneficiary VASP, Inc.", + "code": "BVASP-US" + } + }, + "travel_rule_details": { + "status": "AWAITING_COUNTERPARTY", + "protocol": "IVMS101", + "required": true, + "originator_data": { + "name": "Maria Garcia" + }, + "beneficiary_data": { + "name": "John Beneficiary" + } + } + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Transaction submitted and monitored. The body is the full transaction detail (same shape as `GET /v3/transactions/{transaction_id}/`), reflecting any rule outcomes already applied.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionDetail" + }, + "examples": { + "Approved with no rule hits": { + "value": { + "uuid": "abcdef12-3456-4890-abcd-ef1234567890", + "environment": "live", + "transaction_number": 4123, + "txn_id": "your-txn-2026-05-17-001", + "txn_date": "2026-05-17T08:42:00Z", + "zone_id": "Europe/Madrid", + "transaction_type": "finance", + "action_type": "withdrawal", + "direction": "OUTBOUND", + "status": "APPROVED", + "source": "LIVE", + "amount": "1500", + "currency": "EUR", + "currency_type": "fiat", + "amount_in_default_currency": "1620.45", + "default_currency_code": "USD", + "preferred_currency_amount": "1620.45", + "preferred_currency_code": "USD", + "payment_details": "Withdrawal to crypto wallet", + "payment_txn_id": null, + "score": 0, + "severity": null, + "decision_reason_code": null, + "decision_reason_label": null, + "vendor_data": "user-12345", + "metadata": { + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "address": {}, + "institution_details": {}, + "device_context": {} + }, + "counterparty": {} + }, + "props": {}, + "tags": [], + "parties": [ + { + "uuid": "b1111111-2222-4333-8444-555555555555", + "role": "APPLICANT", + "entity_type": "individual", + "kind": "USER", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "first_name": null, + "last_name": null, + "country_code": null, + "dob": null, + "address": {}, + "institution_info": {}, + "device": {}, + "external_party_snapshot": null + } + ], + "payment_methods": [], + "documents": [], + "activities": [ + { + "uuid": "d4e5f6a7-8901-4bcd-9ef0-123456789abc", + "activity_type": "TRANSACTION_CREATED", + "source": "TRANSACTION", + "status": "APPROVED", + "title": "withdrawal", + "description": "Transaction recorded.", + "metadata": {}, + "occurred_at": "2026-05-17T08:42:11Z" + } + ], + "alerts": [], + "rule_runs": [], + "provider_results": [], + "travel_rule": null, + "network_snapshot": null, + "remediation": null, + "action_required": null, + "cost_breakdown": null + } + }, + "Flagged for review": { + "value": { + "uuid": "abcdef12-3456-4890-abcd-ef1234567891", + "environment": "live", + "transaction_number": 4124, + "txn_id": "your-txn-2026-05-17-002", + "txn_date": "2026-05-17T08:42:00Z", + "zone_id": "Europe/Madrid", + "transaction_type": "finance", + "action_type": "withdrawal", + "direction": "OUTBOUND", + "status": "IN_REVIEW", + "source": "LIVE", + "amount": "0.5", + "currency": "BTC", + "currency_type": "crypto", + "amount_in_default_currency": "32500", + "default_currency_code": "USD", + "preferred_currency_amount": "32500", + "preferred_currency_code": "USD", + "payment_details": "Withdrawal to crypto wallet", + "payment_txn_id": null, + "score": 78, + "severity": "HIGH", + "decision_reason_code": "WALLET_HIGH_RISK", + "decision_reason_label": "Destination wallet flagged as high risk", + "vendor_data": "user-12345", + "metadata": { + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "address": {}, + "institution_details": {}, + "device_context": {} + }, + "counterparty": {} + }, + "props": { + "order_id": "ord-9988" + }, + "tags": [], + "parties": [ + { + "uuid": "b1111111-2222-4333-8444-555555555555", + "role": "APPLICANT", + "entity_type": "individual", + "kind": "USER", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "first_name": null, + "last_name": null, + "country_code": null, + "dob": null, + "address": {}, + "institution_info": {}, + "device": {}, + "external_party_snapshot": null + } + ], + "payment_methods": [], + "documents": [], + "activities": [], + "alerts": [ + { + "uuid": "a7b8c9d0-1234-4abc-9def-567890abcdef", + "title": "Destination wallet flagged as high risk", + "description": "Provider risk score exceeded the configured threshold.", + "severity": "HIGH", + "status": "OPEN", + "source": "RULE", + "metadata": {}, + "created_at": "2026-05-17T08:42:12Z", + "due_at": null + } + ], + "rule_runs": [ + { + "uuid": "e5f6a7b8-9012-4cde-af01-23456789abcd", + "rule": "f6a7b8c9-0123-4def-b012-3456789abcde", + "rule_title": "High-risk wallet screening", + "rule_description": null, + "rule_category": "crypto", + "matched": true, + "is_test": false, + "mode": "ACTIVE", + "severity": "HIGH", + "score_delta": 78, + "status_target": "IN_REVIEW", + "action_summary": "score +78; change_status:IN_REVIEW", + "metadata": {}, + "created_at": "2026-05-17T08:42:12Z" + } + ], + "provider_results": [], + "travel_rule": null, + "network_snapshot": null, + "remediation": null, + "action_required": null, + "cost_breakdown": null + } + } + } + } + } + }, + "400": { + "description": "Validation failed (missing required fields, duplicate `transaction_id`, unknown enum value, invalid currency, etc.).", + "content": { + "application/json": { + "examples": { + "Missing required field": { + "value": { + "transaction_details": { + "amount": [ + "This field is required." + ] + } + } + }, + "Duplicate transaction_id": { + "value": { + "transaction_id": [ + "A transaction with this transaction_id already exists." + ] + } + }, + "Invalid category": { + "value": { + "non_field_errors": [ + "Unsupported transaction category." + ] + } + }, + "Missing subject identity": { + "value": { + "subject": [ + "subject.vendor_data and subject.full_name are required." + ] + } + }, + "Blocklisted participant": { + "value": { + "vendor_data": [ + "The individual 'user-12345' is in the blocklist and cannot participate in transactions." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot submit transactions for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "409": { + "description": "Another request with the same `transaction_id` created the transaction while this one was being processed, so only one of two simultaneous submissions succeeds. Read the existing transaction instead of resubmitting.", + "content": { + "application/json": { + "examples": { + "Concurrent duplicate": { + "value": { + "detail": "A transaction with this transaction_id already exists." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: transaction creates and event ingestion (`POST /v3/transactions/`, `POST /v3/events/`, `POST /v3/events/batch/`) share a budget of 600 requests per minute per API key, separate from the 300 shared by every other write request. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying, rounded up so the retry lands in the next window.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Ingestion rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-didit-sdk": { + "resource": "transactions", + "method": "create", + "auth": "api_key", + "server": true + } + } + }, + "/v3/transactions/{transaction_id}/": { + "get": { + "summary": "Get transaction", + "description": "Retrieve the full monitoring record for one transaction: parties, score/severity, rule runs, alerts, Travel Rule outcome, remediation session.", + "operationId": "getTransaction", + "x-didit-sdk": { + "resource": "transactions", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID (the `uuid` returned from create / list, **not** your `txn_id`).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/abcdef12-3456-7890-abcd-ef1234567890/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.retrieve(\n \"abcdef12-3456-7890-abcd-ef1234567890\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.retrieve(\"abcdef12-3456-7890-abcd-ef1234567890\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Retrieve(ctx, \"abcdef12-3456-7890-abcd-ef1234567890\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().retrieve(\"abcdef12-3456-7890-abcd-ef1234567890\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->retrieve('abcdef12-3456-7890-abcd-ef1234567890');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.retrieve(\"abcdef12-3456-7890-abcd-ef1234567890\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.RetrieveAsync(\"abcdef12-3456-7890-abcd-ef1234567890\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Full transaction detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionDetail" + }, + "examples": { + "In review with an alert": { + "value": { + "uuid": "abcdef12-3456-4890-abcd-ef1234567890", + "environment": "live", + "transaction_number": 4123, + "txn_id": "your-txn-2026-05-17-001", + "txn_date": "2026-05-17T08:42:00Z", + "zone_id": "Europe/Madrid", + "transaction_type": "finance", + "action_type": "withdrawal", + "direction": "OUTBOUND", + "status": "IN_REVIEW", + "source": "LIVE", + "amount": "1500", + "currency": "EUR", + "currency_type": "fiat", + "amount_in_default_currency": "1620.45", + "default_currency_code": "USD", + "preferred_currency_amount": "1620.45", + "preferred_currency_code": "USD", + "payment_details": "Withdrawal to crypto wallet", + "payment_txn_id": null, + "score": 78, + "severity": "HIGH", + "decision_reason_code": "WALLET_HIGH_RISK", + "decision_reason_label": "Destination wallet flagged as high risk", + "vendor_data": "user-12345", + "metadata": { + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "address": {}, + "institution_details": {}, + "device_context": {} + }, + "counterparty": {} + }, + "props": { + "order_id": "ord-9988" + }, + "tags": [], + "parties": [ + { + "uuid": "b1111111-2222-4333-8444-555555555555", + "role": "APPLICANT", + "entity_type": "individual", + "kind": "USER", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "first_name": null, + "last_name": null, + "country_code": null, + "dob": null, + "address": {}, + "institution_info": {}, + "device": {}, + "external_party_snapshot": null + } + ], + "payment_methods": [], + "documents": [], + "activities": [], + "alerts": [ + { + "uuid": "a7b8c9d0-1234-4abc-9def-567890abcdef", + "title": "Destination wallet flagged as high risk", + "description": "Provider risk score exceeded the configured threshold.", + "severity": "HIGH", + "status": "OPEN", + "source": "RULE", + "metadata": {}, + "created_at": "2026-05-17T08:42:12Z", + "due_at": null + } + ], + "rule_runs": [], + "provider_results": [], + "travel_rule": null, + "network_snapshot": null, + "remediation": null, + "action_required": null, + "cost_breakdown": null + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read this application's transactions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists for the application (deleted, never created, belongs to another application, or the path segment is not a valid UUID).", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/transactions/sdk-token/": { + "post": { + "summary": "Mint an SDK transaction token", + "description": "Mint a short-lived scoped token your app passes to the Didit SDKs so they can submit transactions directly from the end-user's device via `POST /v1/transactions/`. The token is bound to one `vendor_data` (your user id): every transaction submitted with it has its subject identity enforced server-side from that binding, so a tampered client cannot submit on behalf of another user. Mint tokens from your backend - never ship the API key itself in an app. camelCase aliases (`vendorData`, `ttlSeconds`, `maxUses`) are also accepted.", + "operationId": "createTransactionSdkToken", + "x-didit-sdk": { + "resource": "transactions", + "method": "create_sdk_token", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "vendor_data": { + "type": "string", + "description": "Your internal identifier for the end user this token is scoped to. Enforced as the subject identity of every transaction submitted with the token.", + "example": "user-042" + }, + "ttl_seconds": { + "type": "integer", + "default": 900, + "maximum": 86400, + "description": "Token lifetime in seconds. Defaults to 900 (15 minutes); maximum 86400 (24 hours)." + }, + "max_uses": { + "type": "integer", + "nullable": true, + "description": "Maximum number of successful submissions allowed with this token. Omit or null for unlimited uses within the TTL." + } + }, + "required": [ + "vendor_data" + ] + }, + "example": { + "vendor_data": "user-042", + "ttl_seconds": 900, + "max_uses": 3 + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/transactions/sdk-token/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"vendor_data\": \"user-042\", \"ttl_seconds\": 900, \"max_uses\": 3}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.create_sdk_token(\n vendor_data=\"user-042\",\n ttl_seconds=900,\n max_uses=3,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.createSdkToken({\n vendor_data: \"user-042\",\n ttl_seconds: 900,\n max_uses: 3,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.CreateSDKToken(ctx, didit.TransactionsCreateSDKTokenParams{\n\tVendorData: \"user-042\",\n\tTTLSeconds: didit.Int(900),\n\tMaxUses: didit.Int(3),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TransactionsCreateSdkTokenParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().createSdkToken(TransactionsCreateSdkTokenParams.builder()\n .vendorData(\"user-042\")\n .ttlSeconds(900L)\n .maxUses(3L)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->createSdkToken([\n 'vendor_data' => 'user-042',\n 'ttl_seconds' => 900,\n 'max_uses' => 3,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.create_sdk_token(\n vendor_data: \"user-042\",\n ttl_seconds: 900,\n max_uses: 3\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.CreateSdkTokenAsync(new TransactionsCreateSdkTokenParams\n{\n VendorData = \"user-042\",\n TtlSeconds = 900,\n MaxUses = 3,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The minted token. Hand `sdk_token` to your app; it authenticates the device-facing `/v1/transactions/` endpoints via the `X-Transaction-Token` header until `expires_at`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sdk_token": { + "type": "string", + "description": "The scoped transaction token to pass to the SDK." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the token stops being accepted." + } + } + }, + "example": { + "sdk_token": "q7Zt...urlsafe", + "expires_at": "2026-07-07T12:15:00Z" + } + } + } + }, + "400": { + "description": "Validation failed: missing `vendor_data`, or `ttl_seconds` outside the allowed range.", + "content": { + "application/json": { + "examples": { + "Missing vendor_data": { + "value": { + "vendor_data": [ + "This field is required." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key.", + "content": { + "application/json": { + "examples": { + "Invalid API Key": { + "value": { + "detail": "Invalid API Key." + } + } + } + } + } + } + } + } + }, + "/v1/transactions/": { + "post": { + "summary": "Submit a transaction from an SDK", + "description": "Device-facing endpoint the Didit SDKs call to submit a transaction directly from the end-user's device. Authentication is a scoped token from `POST /v3/transactions/sdk-token/` sent in the `X-Transaction-Token` header instead of an API key, and the endpoint is CORS-enabled so browsers can call it cross-origin. The payload uses the camelCase wire aliases shown below (the SDK `submitTransaction` methods build it for you). Three server-side guarantees: the application is always taken from the token; the subject identity (`externalUserId`) is always enforced from the token's `vendor_data`, so spoofed payload values are ignored; and device intelligence (the SDK device fingerprint plus server-derived IP and user agent) is attached to the subject automatically.", + "operationId": "submitTransactionFromSdk", + "x-didit-sdk": { + "resource": "transactions", + "method": "submit_from_device", + "auth": "device_token", + "server": false + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "TransactionTokenAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "txnId": { + "type": "string", + "description": "Your unique identifier for this transaction (maps to `transaction_id`, max 128 characters).", + "example": "wd-2026-07-07-0042" + }, + "txnDate": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred (maps to `transaction_at`). Defaults to now if omitted." + }, + "zoneId": { + "type": "string", + "description": "IANA time zone identifier (maps to `time_zone`), e.g. `Europe/Madrid`." + }, + "type": { + "type": "string", + "description": "Transaction category (maps to `transaction_category`), e.g. `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet`.", + "example": "travelRule" + }, + "info": { + "type": "object", + "description": "Core financial details of the transaction (maps to `transaction_details`).", + "required": [ + "direction", + "amount", + "currency" + ], + "properties": { + "direction": { + "type": "string", + "description": "Direction relative to the subject. Case-insensitive; `in`/`out` are accepted as aliases. Echoed back uppercase (`INBOUND`/`OUTBOUND`).", + "example": "out" + }, + "amount": { + "type": "number", + "description": "Transaction amount (up to 18 decimal places). A decimal string is also accepted and is the only way to preserve more than about 15 significant digits: a JSON number is parsed as a binary float first, so `0.123456789012345678` is stored as `0.12345678901234568`.", + "example": 0.25 + }, + "currency": { + "type": "string", + "description": "Currency code. Any ISO 4217 alphabetic code is accepted (e.g. `EUR`, `USD`, `NGN`), as is a crypto asset symbol (e.g. `BTC`). Prefer a currently-circulating code. Not every accepted code has an exchange rate: when none is available for the code (for example the ISO accounting unit `XSU`, or a crypto symbol Didit does not price), automatic conversion fails and `preferred_currency_amount` is returned null unless the request supplied a pre-converted `amountInDefaultCurrency`.", + "example": "ETH" + }, + "currencyType": { + "type": "string", + "description": "Whether the currency is `fiat` or `crypto` (maps to `currency_kind`)." + }, + "amountInDefaultCurrency": { + "type": "number", + "nullable": true, + "description": "Pre-converted amount in the default currency (maps to `amount_in_default_currency`). If omitted, automatic FX conversion is attempted." + }, + "defaultCurrencyCode": { + "type": "string", + "description": "The default currency code for the converted amount (maps to `default_currency`). Any currently-circulating ISO 4217 code." + }, + "paymentDetails": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference or memo (maps to `payment_details`)." + }, + "paymentTxnId": { + "type": "string", + "nullable": true, + "description": "External payment reference (maps to `payment_reference_id`); the on-chain transaction hash for crypto transactions." + }, + "type": { + "type": "string", + "nullable": true, + "description": "Sub-type of the transaction (maps to `action_type`), e.g. `deposit`, `withdrawal`, `transfer`." + }, + "cryptoParams": { + "type": "object", + "description": "Crypto-specific parameters, e.g. `{\"crypto_chain\": \"ETH\"}`." + } + } + }, + "subject": { + "type": "object", + "description": "The subject (applicant) of the transaction. `externalUserId` is ignored here: the subject identity is always enforced from the token's `vendor_data`.", + "required": [ + "fullName" + ], + "properties": { + "type": { + "type": "string", + "description": "Entity type (maps to `entity_type`): `individual` (default) or `company`." + }, + "externalUserId": { + "type": "string", + "description": "Ignored on the subject - enforced server-side from the token's `vendor_data`." + }, + "fullName": { + "type": "string", + "example": "Jane Doe" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "dob": { + "type": "string", + "format": "date", + "description": "Date of birth (YYYY-MM-DD; maps to `date_of_birth`)." + }, + "address": { + "type": "object", + "description": "Address object with `country` (ISO 3166-1 alpha-3), `town`, `state`, `street`, `post_code`." + }, + "institutionInfo": { + "type": "object", + "description": "Institution details for institutional participants (maps to `institution_details`)." + }, + "device": { + "type": "object", + "description": "Optional explicit device context. Merged with the automatically captured device intelligence; server-derived fields win on conflict." + }, + "paymentMethod": { + "type": "object", + "description": "Payment method used by the subject (maps to `payment_method`).", + "properties": { + "type": { + "type": "string", + "description": "Payment method kind (maps to `method_type`), e.g. `bank_card`, `bank_account`, `crypto_wallet`, `ewallet`, `unhosted_wallet`, `other`." + }, + "accountId": { + "type": "string", + "description": "Account identifier (maps to `account_id`): IBAN, card fingerprint, or wallet address." + }, + "issuingCountry": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code of the issuer (maps to `issuing_country`)." + } + } + } + } + }, + "counterparty": { + "type": "object", + "description": "The counterparty of the transaction (optional). Same shape as `subject`; `externalUserId` is meaningful here.", + "properties": { + "type": { + "type": "string", + "description": "Entity type (maps to `entity_type`): `individual` or `company`." + }, + "externalUserId": { + "type": "string", + "description": "Your internal identifier for the counterparty (maps to `vendor_data`)." + }, + "fullName": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "dob": { + "type": "string", + "format": "date" + }, + "address": { + "type": "object" + }, + "institutionInfo": { + "type": "object" + }, + "device": { + "type": "object" + }, + "paymentMethod": { + "type": "object", + "description": "Payment method used by the counterparty (maps to `payment_method`). On travelRule transactions, `type: \"unhosted_wallet\"` declares the counterparty wallet as self-hosted: the transfer skips counterparty-VASP routing, is created directly in `UNCONFIRMED_OWNERSHIP`, and (when `auto_wallet_verification` is enabled) a wallet-ownership widget session is auto-minted and returned in `action_required` so the SDK can launch it.", + "properties": { + "type": { + "type": "string", + "description": "Payment method kind (maps to `method_type`), e.g. `bank_card`, `bank_account`, `crypto_wallet`, `ewallet`, `unhosted_wallet`, `other`. Use `unhosted_wallet` to declare a self-hosted wallet for the Travel Rule." + }, + "accountId": { + "type": "string", + "description": "Account identifier (maps to `account_id`): IBAN, card fingerprint, or wallet address." + }, + "issuingCountry": { + "type": "string" + } + } + } + } + }, + "props": { + "type": "object", + "additionalProperties": true, + "description": "Custom key-value pairs (maps to `custom_properties`). Each key can be referenced in rule conditions as `custom_values.`." + }, + "travelRule": { + "type": "object", + "description": "Travel Rule details (maps to `travel_rule_details`). When Travel Rule is enabled for the application, Didit ignores any `status` you send and drives the exchange itself.", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string" + }, + "protocol": { + "type": "string" + }, + "required": { + "type": "boolean" + }, + "obligationsCount": { + "type": "integer" + }, + "originatorData": { + "type": "object" + }, + "beneficiaryData": { + "type": "object" + }, + "metadata": { + "type": "object" + } + } + }, + "includeCryptoScreening": { + "type": "boolean", + "nullable": true, + "description": "Per-transaction override for crypto blockchain analytics screening (maps to `include_crypto_screening`)." + }, + "fingerprint_v2": { + "type": "object", + "description": "The `didit-fp-v2` device fingerprint payload. Injected automatically by the SDKs; only relevant when calling the wire API directly." + } + }, + "required": [ + "txnId", + "type", + "info", + "subject" + ] + }, + "example": { + "txnId": "wd-2026-07-07-0042", + "type": "travelRule", + "info": { + "direction": "out", + "amount": "0.25", + "currency": "ETH", + "currencyType": "crypto", + "cryptoParams": { + "crypto_chain": "ETH" + } + }, + "subject": { + "fullName": "Jane Doe" + }, + "counterparty": { + "fullName": "Ana Diaz", + "paymentMethod": { + "type": "unhosted_wallet", + "accountId": "0xBeneficiaryWallet01" + } + }, + "travelRule": { + "status": "AWAITING_COUNTERPARTY", + "beneficiaryData": { + "wallet_address": "0xBeneficiaryWallet01", + "name": "Ana Diaz" + } + }, + "includeCryptoScreening": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v1/transactions/ \\\n -H 'X-Transaction-Token: SDK_TOKEN' -H 'Content-Type: application/json' \\\n -d '{\"txnId\": \"wd-2026-07-07-0042\", \"type\": \"travelRule\", \"info\": {\"direction\": \"out\", \"amount\": \"0.25\", \"currency\": \"ETH\", \"currencyType\": \"crypto\"}, \"subject\": {\"fullName\": \"Jane Doe\"}, \"counterparty\": {\"fullName\": \"Ana Diaz\", \"paymentMethod\": {\"type\": \"unhosted_wallet\", \"accountId\": \"0xBeneficiaryWallet01\"}}, \"travelRule\": {\"status\": \"AWAITING_COUNTERPARTY\", \"beneficiaryData\": {\"wallet_address\": \"0xBeneficiaryWallet01\", \"name\": \"Ana Diaz\"}}, \"includeCryptoScreening\": true}'" + }, + { + "lang": "javascript", + "label": "Web SDK", + "source": "import { DiditSdk } from '@didit-protocol/sdk-web';\n\nconst result = await DiditSdk.shared.submitTransaction({\n transactionToken: sdkToken,\n transaction: {\n txnId: 'wd-2026-07-07-0042',\n category: 'travelRule',\n details: { direction: 'out', amount: '0.25', currency: 'ETH', currencyType: 'crypto' },\n subject: { fullName: 'Jane Doe' },\n counterparty: { fullName: 'Ana Diaz', paymentMethod: { type: 'unhosted_wallet', accountId: '0xBeneficiaryWallet01' } },\n travelRule: { status: 'AWAITING_COUNTERPARTY', beneficiaryData: { wallet_address: '0xBeneficiaryWallet01', name: 'Ana Diaz' } },\n includeCryptoScreening: true\n },\n onActionCompleted: (refreshed) => console.log(refreshed.status)\n});" + } + ], + "responses": { + "201": { + "description": "Transaction submitted and monitored. Same detail shape as `GET /v3/transactions/{transaction_id}/`, including the `action_required` block when the end user must complete a follow-up action (the SDKs auto-launch it by default).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionDetail" + }, + "examples": { + "Wallet ownership required": { + "value": { + "uuid": "22222222-3333-4444-5555-666666666666", + "txn_id": "wd-2026-07-07-0042", + "transaction_type": "travelRule", + "direction": "OUTBOUND", + "status": "APPROVED", + "amount": "0.25", + "currency": "ETH", + "travel_rule": { + "uuid": "33333333-4444-5555-6666-777777777777", + "status": "UNCONFIRMED_OWNERSHIP", + "required": true + }, + "action_required": { + "type": "wallet_ownership", + "url": "https://verify.didit.me/wallet-ownership/3s9x...urlsafe", + "widget_session_id": "77777777-8888-9999-aaaa-bbbbbbbbbbbb", + "expires_at": "2026-07-07T13:00:00Z" + } + } + } + } + } + } + }, + "400": { + "description": "Validation failed (missing required fields, duplicate `txnId`, unknown category).", + "content": { + "application/json": { + "examples": { + "Missing fields": { + "value": { + "transaction_details": { + "currency": [ + "This field is required." + ] + } + } + }, + "Duplicate txnId": { + "value": { + "transaction_id": [ + "A transaction with this transaction_id already exists." + ] + } + } + } + } + } + }, + "401": { + "description": "The transaction token was rejected: missing, unknown, revoked, over its `max_uses`, or expired. Expired-token responses always contain the word `expired` in the detail, which is how the SDKs distinguish `expired_token` from `invalid_token` errors.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "Invalid transaction token." + } + }, + "Expired token": { + "value": { + "detail": "Transaction token expired." + } + } + } + } + } + }, + "409": { + "description": "Another request with the same `txnId` created the transaction while this one was being processed, so only one of two simultaneous submissions succeeds. Read the existing transaction instead of resubmitting.", + "content": { + "application/json": { + "examples": { + "Concurrent duplicate": { + "value": { + "detail": "A transaction with this transaction_id already exists." + } + } + } + } + } + } + } + } + }, + "/v1/transactions/{transaction_id}/": { + "get": { + "summary": "Get a transaction from an SDK", + "description": "Device-facing read endpoint, authenticated with the same `X-Transaction-Token` used to submit. The SDKs poll it after an auto-launched action flow completes or its window closes, so the final state never depends on a single notification. Returns the same transaction detail as the submit response, including the current `action_required` block (`null` once the action is resolved).", + "operationId": "getTransactionFromSdk", + "x-didit-sdk": { + "resource": "transactions", + "method": "retrieve_from_device", + "auth": "device_token", + "server": false + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID (the `uuid` from the submit response, **not** your `txnId`).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + } + ], + "security": [ + { + "TransactionTokenAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v1/transactions/22222222-3333-4444-5555-666666666666/' \\\n -H 'X-Transaction-Token: SDK_TOKEN'" + } + ], + "responses": { + "200": { + "description": "The full transaction detail, including `action_required` (`null` once the pending action is resolved).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionDetail" + } + } + } + }, + "401": { + "description": "The transaction token was rejected: missing, unknown, revoked, over its `max_uses`, or expired.", + "content": { + "application/json": { + "examples": { + "Expired token": { + "value": { + "detail": "Transaction token expired." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists for the token's application, or the path segment is not a valid UUID.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + } + } + } + }, + "/v3/businesses/": { + "get": { + "summary": "List businesses", + "description": "List [businesses](/entities/businesses) Didit assembles from KYB sessions sharing the same `vendor_data` (free-form string, NOT a UUID), plus businesses created via the API. Paginated, ordered by `last_session_at` desc. This endpoint does not support filtering or search parameters — fetch pages and filter client-side.", + "operationId": "list_businesses", + "x-didit-sdk": { + "resource": "businesses", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "description": "Page size. Defaults to 50 when omitted. There is no enforced maximum, but keep pages small for latency." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "description": "Zero-based offset of the first record to return. Prefer following the `next` URL from the previous page over computing this by hand." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/businesses/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.businesses.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.businesses.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Businesses.ListAutoPaging(ctx, didit.BusinessesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.businesses().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.businesses.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Businesses.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated list of businesses with status, session counts, and feature breakdown.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows — exact up to 100, capped at 100 beyond that for performance. Do not use it to detect the last page; rely on `next` being `null` instead." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BusinessListItem" + } + } + } + }, + "examples": { + "Example": { + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-123", + "display_name": null, + "legal_name": "Acme Trading Ltd", + "registration_number": "12345678", + "country_code": "GB", + "effective_name": "Acme Trading Ltd", + "status": "ACTIVE", + "session_count": 2, + "approved_count": 1, + "declined_count": 0, + "in_review_count": 1, + "features": { + "KYB": "Approved" + }, + "features_list": [ + { + "feature": "KYB", + "status": "Approved" + } + ], + "last_session_at": "2026-03-15T10:30:00Z", + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": "2026-01-10T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2026-01-10T08:00:00Z" + } + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot list businesses for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/customization/": { + "get": { + "summary": "Get branding customization", + "description": "Returns the white-label branding for the application resolved from the API key: colors, fonts, border radii, logo URLs and UI/UX options. Custom domain and white-label email settings are managed only in the Business Console and are not part of this response.", + "operationId": "get_customization", + "x-didit-sdk": { + "resource": "customization", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Customization" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/customization/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.customization.retrieve()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.customization.retrieve();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Customization.Retrieve(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.customization().retrieve();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "customization->retrieve();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.customization.retrieve\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Customization.RetrieveAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Current branding customization for the application.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrandingCustomization" + }, + "examples": { + "Default branding": { + "value": { + "color_primary": "#000000", + "color_secondary": "#9DA1A1", + "color_background": "#FFFFFF", + "color_panel": "#2567FF", + "color_panel_10": "#1A1A1A", + "color_on_panel_1": "#9DA1A1", + "color_on_panel_2": "#F4F4F6", + "color_on_background": "#000000", + "color_button_1": "#2567FF", + "color_button_2": "#F8F8F8", + "color_button_text_1": "#FFFFFF", + "color_button_text_2": "#000000", + "color_pill_text": "#111827", + "border_radius_panel": 10, + "border_radius_buttons": 12, + "font_family": "Inter", + "font_weight": "regular", + "font_url": "https://fonts.googleapis.com/css2?family=Inter", + "logo_square": null, + "logo_rectangular": null, + "logo_rectangular_dark": null, + "favicon": null, + "app_public_name": "Acme Verify", + "privacy_policy_url": "https://acme.com/privacy", + "skip_welcome_screen": false, + "disable_login_with_didit": false, + "hide_progress_bar": false, + "callback_seconds": 3 + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update branding customization", + "description": "Partial update of the application's white-label branding. Send `application/json` to change colors, fonts, radii and UI options, or `multipart/form-data` to upload logo images. Only the fields you include are changed. Custom domain and white-label email cannot be set through this endpoint — use the Business Console for those.", + "operationId": "update_customization", + "x-didit-sdk": { + "resource": "customization", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Customization" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrandingCustomizationUpdate" + }, + "examples": { + "Update colors and font": { + "summary": "Change branding colors and font", + "value": { + "color_primary": "#123456", + "color_button_1": "#2567FF", + "font_family": "AR One Sans", + "border_radius_buttons": 16 + } + }, + "Update UI options": { + "summary": "Change public name and flow options", + "value": { + "app_public_name": "Acme Verify", + "privacy_policy_url": "https://acme.com/privacy", + "skip_welcome_screen": true, + "hide_progress_bar": true, + "callback_seconds": 5 + } + }, + "Remove a logo": { + "summary": "Delete the current rectangular logo", + "value": { + "delete_logo_rectangular": true + } + }, + "Remove the dark-mode logo": { + "summary": "Delete the current dark-mode logo", + "value": { + "delete_logo_rectangular_dark": true + } + } + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "description": "Use multipart/form-data only to upload logo images. Branding fields above may also be sent as form fields in the same request.", + "properties": { + "image_square": { + "type": "string", + "format": "binary", + "description": "Square logo (1:1 aspect ratio). PNG, JPG or WebP, at least 512 px on the long side, at most **1 MB**." + }, + "image_rectangular": { + "type": "string", + "format": "binary", + "description": "Rectangular logo (2:1 aspect ratio). PNG, JPG or WebP, at least 512 px on the long side, at most **1 MB**." + }, + "image_rectangular_dark": { + "type": "string", + "format": "binary", + "description": "Rectangular logo for dark mode (2:1 aspect ratio), shown on a dark background. PNG, JPG or WebP, at least 512 px on the long side, at most **1 MB**. Returned as `logo_rectangular_dark`." + }, + "image_favicon": { + "type": "string", + "format": "binary", + "description": "Favicon image. Max **500 KB**." + } + } + }, + "example": { + "image_rectangular": "(binary PNG logo, 2:1)", + "image_rectangular_dark": "(binary PNG logo for dark mode, 2:1)", + "image_favicon": "(binary favicon)" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/customization/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"color_primary\": \"#123456\", \"font_family\": \"AR One Sans\"}'" + }, + { + "lang": "curl", + "label": "curl (logo upload)", + "source": "curl -X PATCH 'https://verification.didit.me/v3/customization/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'image_rectangular=@logo.png' \\\n -F 'image_rectangular_dark=@logo-dark.png' \\\n -F 'image_favicon=@favicon.ico'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.customization.update(\n image_rectangular=open(\"image_rectangular.jpg\", \"rb\"),\n image_rectangular_dark=open(\"image_rectangular_dark.jpg\", \"rb\"),\n image_favicon=open(\"image_favicon.jpg\", \"rb\"),\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.customization.update({\n image_rectangular: await fileFromPath(\"image_rectangular.jpg\"),\n image_rectangular_dark: await fileFromPath(\"image_rectangular_dark.jpg\"),\n image_favicon: await fileFromPath(\"image_favicon.jpg\"),\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Customization.Update(ctx, didit.CustomizationUpdateParams{\n\tImageRectangular: didit.Some(didit.FileFromPath(\"image_rectangular.jpg\")),\n\tImageRectangularDark: didit.Some(didit.FileFromPath(\"image_rectangular_dark.jpg\")),\n\tImageFavicon: didit.Some(didit.FileFromPath(\"image_favicon.jpg\")),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.types.CustomizationUpdateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.customization().update(CustomizationUpdateParams.builder()\n .imageRectangular(Path.of(\"image_rectangular.jpg\"))\n .imageRectangularDark(Path.of(\"image_rectangular_dark.jpg\"))\n .imageFavicon(Path.of(\"image_favicon.jpg\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "customization->update([\n 'image_rectangular' => File::fromPath('image_rectangular.jpg'),\n 'image_rectangular_dark' => File::fromPath('image_rectangular_dark.jpg'),\n 'image_favicon' => File::fromPath('image_favicon.jpg'),\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.customization.update(\n image_rectangular: Pathname(\"image_rectangular.jpg\"),\n image_rectangular_dark: Pathname(\"image_rectangular_dark.jpg\"),\n image_favicon: Pathname(\"image_favicon.jpg\")\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Customization.UpdateAsync(new CustomizationUpdateParams\n{\n ImageRectangular = FileUpload.FromPath(\"image_rectangular.jpg\"),\n ImageRectangularDark = FileUpload.FromPath(\"image_rectangular_dark.jpg\"),\n ImageFavicon = FileUpload.FromPath(\"image_favicon.jpg\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Branding updated. The FULL branding object is returned (all fields, not just the ones you changed).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrandingCustomization" + }, + "examples": { + "Updated": { + "value": { + "color_primary": "#123456", + "color_secondary": "#9DA1A1", + "color_background": "#FFFFFF", + "color_panel": "#2567FF", + "color_panel_10": "#1A1A1A", + "color_on_panel_1": "#9DA1A1", + "color_on_panel_2": "#F4F4F6", + "color_on_background": "#000000", + "color_button_1": "#2567FF", + "color_button_2": "#F8F8F8", + "color_button_text_1": "#FFFFFF", + "color_button_text_2": "#000000", + "color_pill_text": "#111827", + "border_radius_panel": 10, + "border_radius_buttons": 16, + "font_family": "AR One Sans", + "font_weight": "400", + "font_url": "https://fonts.googleapis.com/css2?family=AR+One+Sans", + "logo_square": null, + "logo_rectangular": "https://cdn.didit.me/white_label/logo_rectangular/...", + "logo_rectangular_dark": null, + "favicon": null, + "app_public_name": "Acme Verify", + "privacy_policy_url": "https://acme.com/privacy", + "skip_welcome_screen": true, + "disable_login_with_didit": false, + "hide_progress_bar": false, + "callback_seconds": 3 + } + } + } + } + } + }, + "400": { + "description": "Invalid body. For example: a malformed hex color, an unknown font family or weight, a Business-Console-only field (`domain`, `from_email`, `verify_domain`, `force_remove_domain`, `verify_email_domain`, `force_remove_email_domain`, and their read-only companions), or a logo upload that is not a PNG, JPG or WebP image, is under 512 px on its long side, is over 1 MB, or has the wrong aspect ratio. Also returned when setting `disable_login_with_didit` to `false` while a custom domain is configured.", + "content": { + "application/json": { + "examples": { + "Bad color": { + "value": { + "color_primary": [ + "Invalid color format. Use '#RRGGBB' or '#RRGGBBAA' format." + ] + } + }, + "Bad logo": { + "value": { + "image_rectangular": [ + "Image aspect ratio must be 2:1. Got 1024x1024 image dimensions" + ] + } + }, + "Unsupported logo format": { + "summary": "Not a PNG, JPG or WebP image (an SVG, for example)", + "value": { + "image_rectangular_dark": [ + "Upload a PNG, JPG or WebP image." + ] + } + }, + "Logo too small": { + "summary": "Under 512 px on the long side", + "value": { + "image_rectangular": [ + "Image must be at least 512 px on its long side. Got 400x200 image dimensions" + ] + } + }, + "Logo too large": { + "summary": "Over 1 MB", + "value": { + "image_square": [ + "File size should not exceed 1 MB" + ] + } + }, + "Console-only field": { + "value": { + "domain": [ + "Manage this field in the Business Console." + ] + } + }, + "Bad font weight": { + "value": { + "font_weight": [ + "Font weight 950 is not available for Inter. Available weights are: 100, 200, 300, 400, 500, 600, 700, 800, 900" + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/businesses/{vendor_data}/": { + "get": { + "summary": "Get business", + "description": "Fetch one [business](/entities/businesses) by `vendor_data` (exact match). Adds `metadata` and `updated_at` to the list view. Deleted businesses return 404.", + "operationId": "get_business", + "x-didit-sdk": { + "resource": "businesses", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the business — a free-form string (NOT a UUID), matched exactly as sent.", + "example": "company-123" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/businesses/company-123/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.businesses.retrieve(\n \"company-123\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.businesses.retrieve(\"company-123\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.Retrieve(ctx, \"company-123\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.businesses().retrieve(\"company-123\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->retrieve('company-123');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.businesses.retrieve(\"company-123\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.RetrieveAsync(\"company-123\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Full business detail including metadata and aggregated session data.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDetailItem" + }, + "examples": { + "Active business": { + "value": { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-123", + "display_name": null, + "legal_name": "Acme Trading Ltd", + "registration_number": "12345678", + "country_code": "GB", + "effective_name": "Acme Trading Ltd", + "status": "ACTIVE", + "session_count": 2, + "approved_count": 1, + "declined_count": 0, + "in_review_count": 1, + "features": { + "KYB": "Approved" + }, + "features_list": [ + { + "feature": "KYB", + "status": "Approved" + } + ], + "last_session_at": "2026-03-15T10:30:00Z", + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": "2026-01-10T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2026-01-10T08:00:00Z", + "metadata": { + "industry": "fintech" + }, + "updated_at": "2026-03-15T10:30:00Z" + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read this application's businesses. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No (non-deleted) business with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update business", + "description": "Partial update of a business: `display_name`, `legal_name`, `registration_number`, `country_code`, `status`, `metadata`. `metadata` fully replaces. Setting `status` to `BLOCKED` adds the `vendor_data` to the system blocklist; moving away from `BLOCKED` removes that entry. Every edit is recorded in the company's activity: a status change with the optional `reason`, and a change to `display_name`, `legal_name`, `registration_number`, `country_code` or `metadata` as a profile update.", + "operationId": "update_business", + "x-didit-sdk": { + "resource": "businesses", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the business (free-form string, NOT a UUID).", + "example": "company-123" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Partial-update body. Provide only the fields you want to change.", + "properties": { + "display_name": { + "type": "string", + "nullable": true, + "description": "Friendly display name shown in the console (takes precedence over `legal_name` for UI display)." + }, + "legal_name": { + "type": "string", + "nullable": true, + "description": "Official legal name from registry or manual entry." + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Company registration or incorporation number." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "Country of incorporation (ISO 3166-1 alpha-2, e.g. `GB`, `US`)." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "Business lifecycle status. Setting `BLOCKED` automatically adds the `vendor_data` to the system blocklist; flipping away from `BLOCKED` removes that blocklist entry." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Arbitrary JSON object you attach to the business. **Fully replaces** the existing metadata on update — merge client-side if you only want to add keys." + }, + "reason": { + "type": "string", + "nullable": true, + "maxLength": 1000, + "description": "Why the status changed. Recorded on the status-change entry of the company's **Activity** in the Business Console, only when the status actually changes. Webhooks do not carry it." + } + } + }, + "examples": { + "Update name": { + "summary": "Update display name", + "value": { + "display_name": "Acme Corp" + } + }, + "Flag for review": { + "summary": "Flip status", + "value": { + "status": "FLAGGED" + } + }, + "Add metadata": { + "summary": "Replace metadata JSON", + "value": { + "metadata": { + "industry": "fintech", + "risk_level": "low" + } + } + }, + "Fix registry data": { + "summary": "Correct extracted fields", + "value": { + "legal_name": "Acme Trading International Ltd", + "registration_number": "12345678", + "country_code": "GB" + } + }, + "Flag with reason": { + "summary": "Flag the business and record why", + "value": { + "status": "FLAGGED", + "reason": "Ownership structure under review" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/businesses/company-123/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"display_name\": \"Acme Corp\", \"status\": \"FLAGGED\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.businesses.update(\n \"company-123\",\n display_name=\"Acme Corp\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.businesses.update(\"company-123\", {\n display_name: \"Acme Corp\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.Update(ctx, \"company-123\", didit.BusinessesUpdateParams{\n\tDisplayName: didit.String(\"Acme Corp\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BusinessesUpdateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.businesses().update(\"company-123\", BusinessesUpdateParams.builder()\n .displayName(\"Acme Corp\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->update('company-123', [\n 'display_name' => 'Acme Corp',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.businesses.update(\n \"company-123\",\n display_name: \"Acme Corp\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.UpdateAsync(\"company-123\", new BusinessesUpdateParams\n{\n DisplayName = \"Acme Corp\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Business updated. The full updated business record is returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDetailItem" + }, + "examples": { + "Updated": { + "value": { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-123", + "display_name": "Acme Corp", + "legal_name": "Acme Trading Ltd", + "registration_number": "12345678", + "country_code": "GB", + "effective_name": "Acme Corp", + "status": "FLAGGED", + "session_count": 2, + "approved_count": 1, + "declined_count": 0, + "in_review_count": 1, + "features": { + "KYB": "Approved" + }, + "features_list": [ + { + "feature": "KYB", + "status": "Approved" + } + ], + "last_session_at": "2026-03-15T10:30:00Z", + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": "2026-01-10T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2026-01-10T08:00:00Z", + "metadata": { + "industry": "fintech", + "risk_level": "low" + }, + "updated_at": "2026-03-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Invalid body: typically an unknown `status`, malformed `metadata` or a `reason` over 1000 characters.", + "content": { + "application/json": { + "examples": { + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Bad metadata": { + "value": { + "metadata": [ + "Metadata must be a JSON object." + ] + } + }, + "Reason too long": { + "value": { + "reason": [ + "Ensure this field has no more than 1000 characters." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot update businesses for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No (non-deleted) business with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/businesses/{vendor_data}/update-status/": { + "patch": { + "summary": "Update business status", + "description": "Flip only the lifecycle `status` of a business: `ACTIVE`, `FLAGGED` or `BLOCKED` (not session statuses). `BLOCKED` adds the `vendor_data` to the system blocklist; moving away from `BLOCKED` removes that entry. Each change is recorded in the company's activity, with the optional `reason`. Sending the current status changes nothing and records no `reason`. The `business.status.updated` webhook does not carry `reason`.", + "operationId": "update_business_status", + "x-didit-sdk": { + "resource": "businesses", + "method": "update_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the business (free-form string, NOT a UUID).", + "example": "company-123" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/EntityStatusUpdateRequest" + }, + { + "type": "object", + "properties": { + "reason": { + "type": "string", + "nullable": true, + "maxLength": 1000, + "description": "Why the status changed. Recorded on the status-change entry of the company's **Activity** in the Business Console, only when the status actually changes. Webhooks do not carry it." + } + } + } + ] + }, + "examples": { + "Flag": { + "summary": "Flag the business for review", + "value": { + "status": "FLAGGED" + } + }, + "Block": { + "summary": "Block the business (adds to system blocklist)", + "value": { + "status": "BLOCKED" + } + }, + "Reactivate": { + "summary": "Reactivate the business", + "value": { + "status": "ACTIVE" + } + }, + "Block with reason": { + "summary": "Block the business and record why", + "value": { + "status": "BLOCKED", + "reason": "Registry shows the company dissolved" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/businesses/company-123/update-status/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"status\": \"BLOCKED\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.businesses.update_status(\n \"company-123\",\n status=\"FLAGGED\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.businesses.updateStatus(\"company-123\", {\n status: \"FLAGGED\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.UpdateStatus(ctx, \"company-123\", didit.BusinessesUpdateStatusParams{\n\tStatus: \"FLAGGED\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BusinessesUpdateStatusParams;\nimport me.didit.types.EntityStatusUpdateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.businesses().updateStatus(\"company-123\", BusinessesUpdateStatusParams.builder()\n .status(EntityStatusUpdateRequest.Status.of(\"FLAGGED\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->updateStatus('company-123', [\n 'status' => 'FLAGGED',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.businesses.update_status(\n \"company-123\",\n status: \"FLAGGED\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.UpdateStatusAsync(\"company-123\", new BusinessesUpdateStatusParams\n{\n Status = \"FLAGGED\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Business status updated. Full business record returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDetailItem" + }, + "examples": { + "Blocked": { + "value": { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-123", + "display_name": null, + "legal_name": "Acme Trading Ltd", + "registration_number": "12345678", + "country_code": "GB", + "effective_name": "Acme Trading Ltd", + "status": "BLOCKED", + "session_count": 2, + "approved_count": 1, + "declined_count": 0, + "in_review_count": 1, + "features": { + "KYB": "Approved" + }, + "features_list": [ + { + "feature": "KYB", + "status": "Approved" + } + ], + "last_session_at": "2026-03-15T10:30:00Z", + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": "2026-01-10T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2026-01-10T08:00:00Z", + "metadata": { + "industry": "fintech" + }, + "updated_at": "2026-03-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Missing or invalid `status`, or a `reason` over 1000 characters.", + "content": { + "application/json": { + "examples": { + "Missing status": { + "value": { + "status": [ + "This field is required." + ] + } + }, + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Reason too long": { + "value": { + "reason": [ + "Ensure this field has no more than 1000 characters." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot update businesses for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No (non-deleted) business with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/businesses/delete/": { + "post": { + "summary": "Batch delete businesses", + "description": "**Permanently delete** businesses — this cannot be undone. For blocking prefer [Update Business Status](#patch-/v3/businesses/-vendor_data-/update-status/) with `BLOCKED`.\n\n**Filed regulatory reports:** if the selected data is in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "batch_delete_businesses", + "x-didit-sdk": { + "resource": "businesses", + "method": "batch_delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Provide `vendor_data_list`, `didit_internal_id_list`, or `delete_all: true`. Precedence when several are sent: `delete_all` > `vendor_data_list` > `didit_internal_id_list` (only the highest-precedence selector is used).", + "properties": { + "vendor_data_list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Your own identifiers to delete, matched exactly as sent." + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit's internal UUIDs (`didit_internal_id`) to delete." + }, + "delete_all": { + "type": "boolean", + "default": false, + "description": "If `true`, deletes every business in the application. Cannot be combined with the list selectors." + } + } + }, + "examples": { + "Specific businesses": { + "summary": "Delete by vendor_data", + "value": { + "vendor_data_list": [ + "company-123", + "company-456" + ] + } + }, + "Specific internal IDs": { + "summary": "Delete by didit_internal_id", + "value": { + "didit_internal_id_list": [ + "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "b2c3d4e5-f6a7-8901-bcde-f12345678901" + ] + } + }, + "All businesses": { + "summary": "Wipe every business in this application", + "value": { + "delete_all": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/businesses/delete/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vendor_data_list\": [\"company-123\", \"company-456\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.businesses.batch_delete(\n vendor_data_list=[\"company-123\", \"company-456\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.businesses.batchDelete({\n vendor_data_list: [\"company-123\", \"company-456\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.BatchDelete(ctx, didit.BusinessesBatchDeleteParams{\n\tVendorDataList: didit.Some([]string{\"company-123\", \"company-456\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.BusinessesBatchDeleteParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.businesses().batchDelete(BusinessesBatchDeleteParams.builder()\n .vendorDataList(List.of(\"company-123\", \"company-456\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->batchDelete([\n 'vendor_data_list' => ['company-123', 'company-456'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.businesses.batch_delete(vendor_data_list: [\"company-123\", \"company-456\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.BatchDeleteAsync(new BusinessesBatchDeleteParams\n{\n VendorDataList = new string[] { \"company-123\", \"company-456\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Businesses deleted. Returns the number of rows actually removed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deleted": { + "type": "integer", + "description": "Number of businesses deleted (excludes vendor_data values that didn't match anything)." + } + } + }, + "examples": { + "Deleted some": { + "value": { + "deleted": 2 + } + }, + "Nothing matched": { + "value": { + "deleted": 0 + } + } + } + } + } + }, + "400": { + "description": "No selector supplied (all of `vendor_data_list`, `didit_internal_id_list`, and `delete_all` missing or empty). The error body is a JSON array.", + "content": { + "application/json": { + "examples": { + "Missing selector": { + "value": [ + "Provide vendor_data_list, didit_internal_id_list, or set delete_all=true." + ] + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot delete businesses for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + }, + "409": { + "description": "Nothing is deleted. `code` says why: `in_filed_report` when the selection includes a company in a finalized regulatory report, `subject_deletion_in_progress` when another request is still deleting one of the selected companies. Repeat the request once that deletion has finished.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/InFiledReportError" + }, + { + "$ref": "#/components/schemas/SubjectDeletionInProgressError" + } + ] + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/users/aml-monitoring/": { + "post": { + "summary": "Enable or disable AML ongoing monitoring for users", + "description": "Turns AML ongoing monitoring on or off for the selected users, with the same semantics as the Business Console. Exactly one monitored AML screening is kept per user, so a profile that verified several times is monitored and billed once; disabling turns monitoring off across all of the profile's screenings. Users without any AML screening are screened first (billed as a regular AML screening) when they have the full name, date of birth and country needed; otherwise they are reported as `skipped_no_identity_data`.\n\nSet `estimate_only: true` to preview counts and costs without writing anything. Selections of 20 profiles or fewer resolve inline and return `200` with the completed job; larger selections return `202` and run in the background - poll [Get AML monitoring job](#get-/v3/aml-monitoring/jobs/-job_id-/) with the returned `job_id`. Enabling is refused with `403` when the organization balance does not cover the estimate. Identifiers that match nothing are listed in `unmatched` and never fail the request: a selection that matches nothing completes as an empty job (`total` 0), the same convention as the batch delete endpoints.", + "operationId": "toggle_users_aml_monitoring", + "x-didit-sdk": { + "resource": "users", + "method": "set_aml_monitoring", + "auth": "api_key", + "server": true + }, + "tags": [ + "AML Ongoing Monitoring" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_enabled" + ], + "description": "Select users with `vendor_data_list`, `didit_internal_id_list`, or both (the two lists are OR'd). At most 1000 identifiers per request across both lists.", + "properties": { + "is_enabled": { + "type": "boolean", + "description": "true to enable monitoring, false to disable it." + }, + "estimate_only": { + "type": "boolean", + "default": false, + "description": "When true, returns the cost estimate and performs no writes - no screening, no billing, no job." + }, + "vendor_data_list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Your own identifiers of the users to toggle, matched exactly as sent." + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit internal ids of the users to toggle - the `didit_internal_id` returned by the create, get and list endpoints." + } + } + }, + "examples": { + "Estimate first": { + "summary": "Preview the cost without writing", + "value": { + "is_enabled": true, + "estimate_only": true, + "vendor_data_list": [ + "user-123", + "user-456" + ] + } + }, + "Enable": { + "summary": "Enable monitoring for two users", + "value": { + "is_enabled": true, + "vendor_data_list": [ + "user-123", + "user-456" + ] + } + }, + "Disable": { + "summary": "Disable monitoring by Didit internal id", + "value": { + "is_enabled": false, + "didit_internal_id_list": [ + "3f1c0d3e-8f0a-4c2e-9a3d-9b1f5f4a2c11" + ] + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/users/aml-monitoring/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"is_enabled\": true, \"vendor_data_list\": [\"user-123\", \"user-456\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.users.set_aml_monitoring(\n is_enabled=True,\n estimate_only=True,\n vendor_data_list=[\"user-123\", \"user-456\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.users.setAmlMonitoring({\n is_enabled: true,\n estimate_only: true,\n vendor_data_list: [\"user-123\", \"user-456\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Users.SetAMLMonitoring(ctx, didit.UsersSetAMLMonitoringParams{\n\tIsEnabled: true,\n\tEstimateOnly: didit.Bool(true),\n\tVendorDataList: didit.Some([]string{\"user-123\", \"user-456\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.UsersSetAmlMonitoringParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.users().setAmlMonitoring(UsersSetAmlMonitoringParams.builder()\n .isEnabled(true)\n .estimateOnly(true)\n .vendorDataList(List.of(\"user-123\", \"user-456\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->setAmlMonitoring([\n 'is_enabled' => true,\n 'estimate_only' => true,\n 'vendor_data_list' => ['user-123', 'user-456'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.users.set_aml_monitoring(\n is_enabled: true,\n estimate_only: true,\n vendor_data_list: [\"user-123\", \"user-456\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Users.SetAmlMonitoringAsync(new UsersSetAmlMonitoringParams\n{\n IsEnabled = true,\n EstimateOnly = true,\n VendorDataList = new string[] { \"user-123\", \"user-456\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The estimate (when `estimate_only` is true) or the completed job for a selection processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AMLMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger selections run in the background. Poll the job endpoint with `job_id`; `results` fills in as profiles are processed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AMLMonitoringJob" + } + } + } + }, + "400": { + "description": "Validation error: `is_enabled` missing, no selector, or more than 1000 identifiers across both lists." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Insufficient balance to enable monitoring for this selection. The response carries the `estimate` so you can top up the right amount.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + } + } + } + } + } + } + } + } + } + } + }, + "/v3/businesses/aml-monitoring/": { + "post": { + "summary": "Enable or disable AML ongoing monitoring for businesses", + "description": "Turns AML ongoing monitoring on or off for the selected businesses, with the same semantics as the Business Console. Exactly one monitored AML screening is kept per business, so a profile that verified several times is monitored and billed once; disabling turns monitoring off across all of the profile's screenings. Businesses without any AML screening are screened first (billed as a regular AML screening) when they have the legal or display name needed; otherwise they are reported as `skipped_no_identity_data`.\n\nSet `estimate_only: true` to preview counts and costs without writing anything. Selections of 20 profiles or fewer resolve inline and return `200` with the completed job; larger selections return `202` and run in the background - poll [Get AML monitoring job](#get-/v3/aml-monitoring/jobs/-job_id-/) with the returned `job_id`. Enabling is refused with `403` when the organization balance does not cover the estimate. Identifiers that match nothing are listed in `unmatched` and never fail the request: a selection that matches nothing completes as an empty job (`total` 0), the same convention as the batch delete endpoints.", + "operationId": "toggle_businesses_aml_monitoring", + "x-didit-sdk": { + "resource": "businesses", + "method": "set_aml_monitoring", + "auth": "api_key", + "server": true + }, + "tags": [ + "AML Ongoing Monitoring" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_enabled" + ], + "description": "Select businesses with `vendor_data_list`, `didit_internal_id_list`, or both (the two lists are OR'd). At most 1000 identifiers per request across both lists.", + "properties": { + "is_enabled": { + "type": "boolean", + "description": "true to enable monitoring, false to disable it." + }, + "estimate_only": { + "type": "boolean", + "default": false, + "description": "When true, returns the cost estimate and performs no writes - no screening, no billing, no job." + }, + "vendor_data_list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Your own identifiers of the businesses to toggle, matched exactly as sent." + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit internal ids of the businesses to toggle - the `didit_internal_id` returned by the create, get and list endpoints." + } + } + }, + "examples": { + "Estimate first": { + "summary": "Preview the cost without writing", + "value": { + "is_enabled": true, + "estimate_only": true, + "vendor_data_list": [ + "company-123", + "company-456" + ] + } + }, + "Enable": { + "summary": "Enable monitoring for two businesses", + "value": { + "is_enabled": true, + "vendor_data_list": [ + "company-123", + "company-456" + ] + } + }, + "Disable": { + "summary": "Disable monitoring by Didit internal id", + "value": { + "is_enabled": false, + "didit_internal_id_list": [ + "3f1c0d3e-8f0a-4c2e-9a3d-9b1f5f4a2c11" + ] + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/businesses/aml-monitoring/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"is_enabled\": true, \"vendor_data_list\": [\"company-123\", \"company-456\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.businesses.set_aml_monitoring(\n is_enabled=True,\n estimate_only=True,\n vendor_data_list=[\"company-123\", \"company-456\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.businesses.setAmlMonitoring({\n is_enabled: true,\n estimate_only: true,\n vendor_data_list: [\"company-123\", \"company-456\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.SetAMLMonitoring(ctx, didit.BusinessesSetAMLMonitoringParams{\n\tIsEnabled: true,\n\tEstimateOnly: didit.Bool(true),\n\tVendorDataList: didit.Some([]string{\"company-123\", \"company-456\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.BusinessesSetAmlMonitoringParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.businesses().setAmlMonitoring(BusinessesSetAmlMonitoringParams.builder()\n .isEnabled(true)\n .estimateOnly(true)\n .vendorDataList(List.of(\"company-123\", \"company-456\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->setAmlMonitoring([\n 'is_enabled' => true,\n 'estimate_only' => true,\n 'vendor_data_list' => ['company-123', 'company-456'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.businesses.set_aml_monitoring(\n is_enabled: true,\n estimate_only: true,\n vendor_data_list: [\"company-123\", \"company-456\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.SetAmlMonitoringAsync(new BusinessesSetAmlMonitoringParams\n{\n IsEnabled = true,\n EstimateOnly = true,\n VendorDataList = new string[] { \"company-123\", \"company-456\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The estimate (when `estimate_only` is true) or the completed job for a selection processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AMLMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger selections run in the background. Poll the job endpoint with `job_id`; `results` fills in as profiles are processed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AMLMonitoringJob" + } + } + } + }, + "400": { + "description": "Validation error: `is_enabled` missing, no selector, or more than 1000 identifiers across both lists." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Insufficient balance to enable monitoring for this selection. The response carries the `estimate` so you can top up the right amount.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + } + } + } + } + } + } + } + } + } + } + }, + "/v3/aml-monitoring/jobs/{job_id}/": { + "get": { + "summary": "Get AML ongoing monitoring job", + "description": "Status, outcome counters and per-record results of a monitoring toggle job started from `POST /v3/users/aml-monitoring/` or `POST /v3/businesses/aml-monitoring/`. Poll every couple of seconds while `status` is `PENDING` or `PROCESSING`; `results` is complete once it is `COMPLETED`.", + "operationId": "get_aml_monitoring_job", + "x-didit-sdk": { + "resource": "aml_monitoring", + "method": "retrieve_job", + "auth": "api_key", + "server": true + }, + "tags": [ + "AML Ongoing Monitoring" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The `job_id` returned by the toggle endpoint." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl 'https://verification.didit.me/v3/aml-monitoring/jobs/JOB_ID/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.aml_monitoring.retrieve_job(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.amlMonitoring.retrieveJob(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.AMLMonitoring.RetrieveJob(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.amlMonitoring().retrieveJob(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "amlMonitoring->retrieveJob('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.aml_monitoring.retrieve_job(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.AmlMonitoring.RetrieveJobAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ] + }, + "is_enabled": { + "type": "boolean", + "description": "true when the job enables monitoring, false when it disables it." + }, + "target_type": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Which profile family the job toggles." + }, + "total": { + "type": "integer", + "description": "Profiles the job processes." + }, + "processed": { + "type": "integer", + "description": "Profiles processed so far." + }, + "outcomes": { + "type": "object", + "description": "Per-record outcome counters. Every selected profile resolves to exactly one counter; nothing is silently skipped.", + "properties": { + "enabled": { + "type": "integer", + "description": "Monitoring enabled on an existing AML screening." + }, + "screened_and_enabled": { + "type": "integer", + "description": "A new AML screening was created (billed) and monitoring enabled on it." + }, + "already_enabled": { + "type": "integer", + "description": "Monitoring was already active; nothing changed and nothing was billed." + }, + "disabled": { + "type": "integer", + "description": "Monitoring turned off across every AML screening of the profile." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Skipped: the profile lacks the full name, date of birth and country (users) or a name (businesses) needed to screen." + }, + "skipped_blocked": { + "type": "integer", + "description": "Skipped: the user is blocked." + }, + "skipped_sandbox": { + "type": "integer", + "description": "Skipped: sandbox application, no screening is created." + }, + "failed_provider": { + "type": "integer", + "description": "The screening could not be completed; retry later." + }, + "failed_insufficient_balance": { + "type": "integer", + "description": "Balance ran out while the job was running." + } + } + }, + "results": { + "type": "array", + "description": "One entry per processed profile, in processing order. Grows while the job runs; complete once status is COMPLETED. Recorded for jobs started through the API; a job started from the Business Console reports counters only.", + "items": { + "type": "object", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit internal id of the profile." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the profile." + }, + "outcome": { + "type": "string", + "enum": [ + "enabled", + "screened_and_enabled", + "already_enabled", + "disabled", + "skipped_no_identity_data", + "skipped_blocked", + "skipped_sandbox", + "failed_provider", + "failed_insufficient_balance" + ] + } + } + } + }, + "error": { + "type": "string", + "nullable": true, + "description": "Generic failure message when status is FAILED; the detailed cause is kept on the platform side." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "404": { + "description": "No such job for this application." + } + } + } + }, + "/v3/users/{vendor_data}/update-status/": { + "patch": { + "summary": "Update user status", + "description": "Flip only the lifecycle `status` of a user: `ACTIVE`, `FLAGGED` or `BLOCKED` (not session statuses). `BLOCKED` adds the `vendor_data` to the system blocklist; moving away from `BLOCKED` removes that entry. Each change is recorded as a `STATUS_CHANGED` activity on the user, with the optional `reason` as its `comment`. Sending the current status changes nothing and records no `reason`. The `user.status.updated` webhook does not carry `reason`.", + "operationId": "update_user_status", + "x-didit-sdk": { + "resource": "users", + "method": "update_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the user." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/EntityStatusUpdateRequest" + }, + { + "type": "object", + "properties": { + "reason": { + "type": "string", + "nullable": true, + "maxLength": 1000, + "description": "Why the status changed. Recorded on the status-change entry of the user's activity (`comments[].comment`, and **Activity** in the Business Console), only when the status actually changes. Webhooks do not carry it." + } + } + } + ] + }, + "examples": { + "Activate": { + "summary": "Reactivate the user", + "value": { + "status": "ACTIVE" + } + }, + "Flag": { + "summary": "Flag the user for review", + "value": { + "status": "FLAGGED" + } + }, + "Block": { + "summary": "Block the user (adds to system blocklist)", + "value": { + "status": "BLOCKED" + } + }, + "Block with reason": { + "summary": "Block the user and record why", + "value": { + "status": "BLOCKED", + "reason": "Chargeback fraud pattern confirmed" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/users/user-abc-123/update-status/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"status\": \"BLOCKED\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.users.update_status(\n \"user-123\",\n status=\"ACTIVE\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.users.updateStatus(\"user-123\", {\n status: \"ACTIVE\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Users.UpdateStatus(ctx, \"user-123\", didit.UsersUpdateStatusParams{\n\tStatus: \"ACTIVE\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.EntityStatusUpdateRequest;\nimport me.didit.types.UsersUpdateStatusParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.users().updateStatus(\"user-123\", UsersUpdateStatusParams.builder()\n .status(EntityStatusUpdateRequest.Status.of(\"ACTIVE\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->updateStatus('user-123', [\n 'status' => 'ACTIVE',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.users.update_status(\n \"user-123\",\n status: \"ACTIVE\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Users.UpdateStatusAsync(\"user-123\", new UsersUpdateStatusParams\n{\n Status = \"ACTIVE\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "User status updated. Full user record returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDetailItem" + }, + "examples": { + "Blocked": { + "value": { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": null, + "full_name": "John Michael Doe", + "date_of_birth": "1990-05-15", + "effective_name": "John Michael Doe", + "status": "BLOCKED", + "metadata": { + "tier": "premium", + "source": "website" + }, + "portrait_image_url": "https:///...", + "session_count": 3, + "approved_count": 2, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [ + "USA" + ], + "approved_emails": [ + "john@example.com" + ], + "approved_phones": [ + "+14155551234" + ], + "features": { + "ID_VERIFICATION": "Approved", + "LIVENESS": "Approved", + "FACE_MATCH": "Approved", + "AML": "Approved" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-01T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "tag": { + "uuid": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b", + "name": "VIP", + "color": "#2567FF", + "description": null, + "source": "custom", + "created_at": "2025-05-01T09:00:00Z", + "updated_at": "2025-05-01T09:00:00Z" + }, + "added_by_email": "analyst@acme.com", + "added_by_name": "Jane Analyst", + "created_at": "2025-06-02T11:00:00Z" + } + ], + "comments": [ + { + "uuid": "c1111111-2222-4333-8444-555555555555", + "comment_type": "STATUS_CHANGED", + "comment": "Chargeback fraud pattern confirmed", + "actor_email": null, + "actor_name": null, + "previous_status": "ACTIVE", + "new_status": "BLOCKED", + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "mentioned_emails": [], + "created_at": "2025-06-15T10:30:00Z" + } + ], + "created_at": "2025-06-01T08:00:00Z", + "updated_at": "2025-06-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Missing or invalid `status`, or a `reason` over 1000 characters.", + "content": { + "application/json": { + "examples": { + "Missing status": { + "value": { + "status": [ + "This field is required." + ] + } + }, + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Reason too long": { + "value": { + "reason": [ + "Ensure this field has no more than 1000 characters." + ] + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "The key cannot update users for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No (non-deleted) user with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/businesses/create/": { + "post": { + "summary": "Create business", + "description": "Pre-create a [business](/entities/businesses) *without* a KYB session. All fields are optional, but you should always send `vendor_data` — without it the record cannot be linked to sessions or transactions. When provided, `vendor_data` must be unique among non-deleted businesses (exact match; conflicts return 400). Not idempotent.", + "operationId": "create_business", + "x-didit-sdk": { + "resource": "businesses", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your unique identifier for this business (free-form string, NOT a UUID). Optional but strongly recommended. When provided, must be unique among non-deleted businesses for the application; matched exactly as sent." + }, + "display_name": { + "type": "string", + "nullable": true, + "maxLength": 255, + "description": "Friendly display name shown in the console (takes precedence over `legal_name` for UI display)." + }, + "legal_name": { + "type": "string", + "nullable": true, + "maxLength": 255, + "description": "Official legal name of the company." + }, + "registration_number": { + "type": "string", + "nullable": true, + "maxLength": 100, + "description": "Company registration or incorporation number." + }, + "country_code": { + "type": "string", + "nullable": true, + "maxLength": 10, + "description": "Country of incorporation (ISO 3166-1 alpha-2, e.g. `GB`, `US`)." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "default": "ACTIVE", + "description": "Initial lifecycle status. Defaults to `ACTIVE`." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Arbitrary JSON object you attach to the business. Defaults to `{}`." + } + } + }, + "examples": { + "Basic": { + "summary": "Recommended minimum", + "value": { + "vendor_data": "company-456", + "legal_name": "New Corp Ltd", + "country_code": "US" + } + }, + "Full": { + "summary": "All fields", + "value": { + "vendor_data": "company-789", + "display_name": "New Corp", + "legal_name": "New Corp International Ltd", + "registration_number": "98765432", + "country_code": "DE", + "status": "ACTIVE", + "metadata": { + "industry": "fintech", + "tier": "enterprise" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/businesses/create/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vendor_data\": \"company-456\", \"legal_name\": \"New Corp Ltd\", \"country_code\": \"US\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.businesses.create(\n vendor_data=\"company-456\",\n legal_name=\"New Corp Ltd\",\n country_code=\"US\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.businesses.create({\n vendor_data: \"company-456\",\n legal_name: \"New Corp Ltd\",\n country_code: \"US\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.Create(ctx, didit.BusinessesCreateParams{\n\tVendorData: didit.String(\"company-456\"),\n\tLegalName: didit.String(\"New Corp Ltd\"),\n\tCountryCode: didit.String(\"US\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BusinessesCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.businesses().create(BusinessesCreateParams.builder()\n .vendorData(\"company-456\")\n .legalName(\"New Corp Ltd\")\n .countryCode(\"US\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->create([\n 'vendor_data' => 'company-456',\n 'legal_name' => 'New Corp Ltd',\n 'country_code' => 'US',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.businesses.create(\n vendor_data: \"company-456\",\n legal_name: \"New Corp Ltd\",\n country_code: \"US\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.CreateAsync(new BusinessesCreateParams\n{\n VendorData = \"company-456\",\n LegalName = \"New Corp Ltd\",\n CountryCode = \"US\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Business created. Full business record returned (same shape as Get Business).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDetailItem" + }, + "examples": { + "Created": { + "value": { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-456", + "display_name": null, + "legal_name": "New Corp Ltd", + "registration_number": null, + "country_code": "US", + "effective_name": "New Corp Ltd", + "status": "ACTIVE", + "session_count": 0, + "approved_count": 0, + "declined_count": 0, + "in_review_count": 0, + "features": {}, + "features_list": [], + "last_session_at": null, + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": null, + "tags": [], + "created_at": "2026-03-15T10:30:00Z", + "metadata": {}, + "updated_at": "2026-03-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Invalid body — typically a duplicate `vendor_data`, an unknown `status`, or malformed `metadata`.", + "content": { + "application/json": { + "examples": { + "Duplicate": { + "value": { + "vendor_data": [ + "A business with this vendor_data already exists." + ] + } + }, + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Bad metadata": { + "value": { + "metadata": [ + "Metadata must be a JSON object." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot create businesses for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/users/create/": { + "post": { + "summary": "Create user", + "description": "Create a [user](/entities/users) by `vendor_data`, before or without a verification session. `vendor_data` must be unique among the application's users (a duplicate returns `400`). Not idempotent.\n\n**Contact details:** `email` and `phone_number` are stored in `metadata.contact`, and `country` in `metadata.address`. When you send `first_name` or `last_name` without `full_name`, Didit joins them into `full_name`.\n\n**Verification link:** with `send_verification_link: true`, Didit also creates a verification session for the person on `workflow_id` and emails them the link at `email`. The session runs the same checks as [Create Session](/sessions-api/create-session), such as the blocklist and your credit balance. The person and the session are created together or not at all: if any check fails, nothing is written and no email is sent. The response's `verification_link` describes the session, and is `null` when you don't ask for a link.\n\n**API key access:** a scoped API key needs `users` write. With `send_verification_link: true` it also needs `sessions` write, or the call answers `403` before anything is written. Full-access keys are unaffected. See [API keys](/console/api-keys).", + "operationId": "create_user", + "x-didit-sdk": { + "resource": "users", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "vendor_data" + ], + "properties": { + "vendor_data": { + "type": "string", + "description": "Your unique identifier for this user (free-form string, NOT a UUID). Must be unique among the application's users; matched exactly as sent." + }, + "full_name": { + "type": "string", + "nullable": true, + "maxLength": 512, + "description": "Full legal name of the user." + }, + "display_name": { + "type": "string", + "nullable": true, + "description": "Friendly display name shown in the console (takes precedence over `full_name` for UI display).", + "maxLength": 255 + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth in `YYYY-MM-DD` format." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "default": "ACTIVE", + "description": "Initial lifecycle status. Defaults to `ACTIVE`." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Arbitrary JSON object you attach to the user. Defaults to `{}`." + }, + "approved_emails": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Pre-trusted email addresses for this user, e.g. `[\"john@example.com\"]`." + }, + "approved_phones": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Pre-trusted phone numbers for this user, e.g. `[\"+14155551234\"]`." + }, + "issuing_states": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Pre-recorded issuing countries (ISO 3166-1 alpha-3), e.g. `[\"USA\"]`." + }, + "first_name": { + "type": "string", + "maxLength": 255, + "description": "Given name. When `full_name` is not sent, Didit joins `first_name` and `last_name` with a space into `full_name`. Not returned as a separate field." + }, + "last_name": { + "type": "string", + "maxLength": 255, + "description": "Family name. Joined with `first_name` into `full_name` when `full_name` is not sent. Not returned as a separate field." + }, + "email": { + "type": "string", + "format": "email", + "description": "Contact email, stored in `metadata.contact.email` (it replaces an `email` you send inside `metadata.contact`). Required when `send_verification_link` is `true`: the link is sent to this address. If `metadata.contact` is not a JSON object, the call answers `400` on this field." + }, + "phone_number": { + "type": "string", + "maxLength": 32, + "description": "Contact phone number, stored in `metadata.contact.phone_number`. If `metadata.contact` is not a JSON object, the call answers `400` on this field." + }, + "country": { + "type": "string", + "maxLength": 3, + "description": "Country as an ISO 3166 code, alpha-2 or alpha-3 (`ES` or `ESP`). Stored uppercased in `metadata.address.country`. If `metadata.address` is not a JSON object, the call answers `400` on this field." + }, + "send_verification_link": { + "type": "boolean", + "default": false, + "description": "When `true`, Didit creates a verification session for the person on `workflow_id` and emails them the link at `email`. Requires `email` and `workflow_id`. A scoped API key also needs `sessions` write." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "The workflow of the verification session. Required when `send_verification_link` is `true`. It must verify a person: a Business Verification (KYB) workflow is refused. Ignored otherwise." + }, + "language": { + "type": "string", + "maxLength": 10, + "description": "Language of the verification email, as a [supported language](/integration/supported-languages) code. English when omitted. Ignored when `send_verification_link` is not `true`." + } + } + }, + "examples": { + "Basic": { + "summary": "Create with basic info", + "value": { + "vendor_data": "user-abc-123", + "full_name": "John Doe" + } + }, + "Full": { + "summary": "All fields", + "value": { + "vendor_data": "user-abc-456", + "full_name": "Jane Smith", + "display_name": "Jane S.", + "date_of_birth": "1990-05-15", + "status": "ACTIVE", + "metadata": { + "tier": "premium", + "source": "api" + }, + "approved_emails": [ + "jane@example.com" + ] + } + }, + "Contact details": { + "summary": "Name, email, phone and country", + "value": { + "vendor_data": "user-abc-789", + "first_name": "Julia", + "last_name": "Martin", + "email": "julia.martin@example.com", + "phone_number": "+34600000000", + "country": "ES" + } + }, + "With verification link": { + "summary": "Create the person and email them a verification link", + "value": { + "vendor_data": "user-abc-789", + "first_name": "Julia", + "last_name": "Martin", + "email": "julia.martin@example.com", + "send_verification_link": true, + "workflow_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "language": "es" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/users/create/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vendor_data\": \"user-abc-123\", \"full_name\": \"John Doe\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.users.create(\n vendor_data=\"user-abc-123\",\n full_name=\"John Doe\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.users.create({\n vendor_data: \"user-abc-123\",\n full_name: \"John Doe\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Users.Create(ctx, didit.UsersCreateParams{\n\tVendorData: \"user-abc-123\",\n\tFullName: didit.String(\"John Doe\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.UsersCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.users().create(UsersCreateParams.builder()\n .vendorData(\"user-abc-123\")\n .fullName(\"John Doe\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->create([\n 'vendor_data' => 'user-abc-123',\n 'full_name' => 'John Doe',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.users.create(\n vendor_data: \"user-abc-123\",\n full_name: \"John Doe\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Users.CreateAsync(new UsersCreateParams\n{\n VendorData = \"user-abc-123\",\n FullName = \"John Doe\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "User created. The full user record (same shape as Get User), plus `verification_link`: the verification session Didit emailed when `send_verification_link` was `true`, otherwise `null`.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UserDetailItem" + }, + { + "type": "object", + "required": [ + "verification_link" + ], + "properties": { + "verification_link": { + "type": "object", + "nullable": true, + "description": "The verification session Didit created and emailed to the person. Always present: `null` when `send_verification_link` was not `true`.", + "required": [ + "session_id", + "session_number", + "status", + "url", + "workflow_id", + "email" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "ID of the verification session." + }, + "session_number": { + "type": "integer", + "description": "Sequential number of the session inside your application." + }, + "status": { + "type": "string", + "description": "Status of the session, `Not Started` for a session that was just created." + }, + "url": { + "type": "string", + "format": "uri", + "description": "The verification link Didit emailed to the person." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The workflow the session runs." + }, + "email": { + "type": "string", + "format": "email", + "description": "The address the link was sent to." + } + } + } + } + } + ] + }, + "examples": { + "Created": { + "value": { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": null, + "full_name": "John Doe", + "date_of_birth": null, + "effective_name": "John Doe", + "status": "ACTIVE", + "metadata": {}, + "portrait_image_url": null, + "session_count": 0, + "approved_count": 0, + "declined_count": 0, + "in_review_count": 0, + "issuing_states": [], + "approved_emails": [], + "approved_phones": [], + "features": {}, + "features_list": [], + "last_session_at": null, + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": null, + "tags": [], + "comments": [], + "created_at": "2025-06-15T10:30:00Z", + "updated_at": "2025-06-15T10:30:00Z", + "verification_link": null + } + }, + "Created with verification link": { + "value": { + "didit_internal_id": "9b2f6c1e-3d4a-4e5f-8a7b-1c2d3e4f5a6b", + "vendor_data": "user-abc-789", + "display_name": null, + "full_name": "Julia Martin", + "date_of_birth": null, + "effective_name": "Julia Martin", + "status": "ACTIVE", + "metadata": { + "contact": { + "email": "julia.martin@example.com" + } + }, + "portrait_image_url": null, + "session_count": 1, + "approved_count": 0, + "declined_count": 0, + "in_review_count": 0, + "issuing_states": [], + "approved_emails": [], + "approved_phones": [], + "features": {}, + "features_list": [], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-15T10:30:00Z", + "tags": [], + "comments": [], + "created_at": "2025-06-15T10:30:00Z", + "updated_at": "2025-06-15T10:30:00Z", + "verification_link": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa7", + "session_number": 43762, + "status": "Not Started", + "url": "https://verify.didit.me/en/session/3FaJ9wLqX2Mz", + "workflow_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "email": "julia.martin@example.com" + } + } + } + } + } + } + }, + "400": { + "description": "Invalid body, keyed by field: typically a duplicate `vendor_data`, a missing required field or a malformed value. With `send_verification_link: true`, also a missing `email` or `workflow_id`, an unknown or Business Verification (KYB) workflow, an unsupported `language` (keyed `contact_details.email_lang`), a blocklisted `vendor_data`, or too few credits to start the session. Nothing is written.", + "content": { + "application/json": { + "examples": { + "Duplicate": { + "value": { + "vendor_data": [ + "A user with this vendor_data already exists." + ] + } + }, + "Missing vendor_data": { + "value": { + "vendor_data": [ + "This field is required." + ] + } + }, + "Bad metadata": { + "value": { + "metadata": [ + "Metadata must be a JSON object." + ] + } + }, + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Bad country": { + "value": { + "country": [ + "Use an ISO 3166 country code, like ES or ESP." + ] + } + }, + "Contact not an object": { + "value": { + "email": [ + "metadata.contact must be a JSON object to store this value." + ] + } + }, + "Address not an object": { + "value": { + "country": [ + "metadata.address must be a JSON object to store this value." + ] + } + }, + "Link without email or workflow": { + "value": { + "email": [ + "An email address is required to send a verification link." + ], + "workflow_id": [ + "Pick the workflow to send a verification link for." + ] + } + }, + "Unknown workflow": { + "value": { + "workflow_id": [ + "Invalid workflow_id." + ] + } + }, + "Business workflow": { + "value": { + "workflow_id": [ + "This workflow verifies a business. Pick a workflow that verifies a person to send them a verification link." + ] + } + }, + "Blocklisted vendor_data": { + "value": { + "vendor_data": "This user or business has been blocked and cannot create new verification sessions." + } + }, + "Insufficient credits": { + "value": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "Invalid or revoked API key, or the key cannot create users for this application. A scoped API key needs `users` write, and also `sessions` write when `send_verification_link` is `true`; nothing is written. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "No sessions write": { + "summary": "Scoped key asks for a verification link without sessions write", + "value": { + "detail": "This API key has no write access to sessions." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-users/by-id/{didit_internal_id}/faces/upload/": { + "post": { + "summary": "Upload user face", + "description": "Attach a trusted imported face image to an existing User profile. Use this after creating or retrieving a User by `vendor_data` and reading its `didit_internal_id`. The uploaded face is stored on the User profile and indexed for duplicate detection and face search. It is not added to a face blocklist.", + "operationId": "upload_user_face", + "x-didit-sdk": { + "resource": "users", + "method": "upload_face", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID that owns the application." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID that owns the User profile." + }, + { + "name": "didit_internal_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Didit's internal User UUID, returned as `didit_internal_id` by `GET /v3/users/{vendor_data}/` and `POST /v3/users/create/`." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "image" + ], + "properties": { + "image": { + "type": "string", + "format": "byte", + "description": "Raw base64-encoded JPG or PNG face image. Do not include a `data:image/...;base64,` prefix. Max decoded size: 2 MB." + }, + "comment": { + "type": "string", + "description": "Optional operator-visible note for the uploaded face.", + "default": "" + } + } + }, + "examples": { + "Imported face": { + "summary": "Attach a face imported from a previous provider", + "value": { + "image": "/9j/4AAQSkZJRgABAQEA...", + "comment": "Imported from previous KYC provider" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/vendor-users/by-id/f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418/faces/upload/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"image\": \"/9j/4AAQSkZJRgABAQEA...\", \"comment\": \"Imported from previous KYC provider\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.users.upload_face(\n \"11111111-2222-3333-4444-555555555555\",\n image=\"/9j/4AAQSkZJRgABAQEA...\",\n comment=\"Imported from previous KYC provider\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.users.uploadFace(\"11111111-2222-3333-4444-555555555555\", {\n image: \"/9j/4AAQSkZJRgABAQEA...\",\n comment: \"Imported from previous KYC provider\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Users.UploadFace(ctx, \"11111111-2222-3333-4444-555555555555\", didit.UsersUploadFaceParams{\n\tImage: \"/9j/4AAQSkZJRgABAQEA...\",\n\tComment: didit.String(\"Imported from previous KYC provider\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.UsersUploadFaceParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.users().uploadFace(\"11111111-2222-3333-4444-555555555555\", UsersUploadFaceParams.builder()\n .image(\"/9j/4AAQSkZJRgABAQEA...\")\n .comment(\"Imported from previous KYC provider\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->uploadFace('11111111-2222-3333-4444-555555555555', [\n 'image' => '/9j/4AAQSkZJRgABAQEA...',\n 'comment' => 'Imported from previous KYC provider',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.users.upload_face(\n \"11111111-2222-3333-4444-555555555555\",\n image: \"/9j/4AAQSkZJRgABAQEA...\",\n comment: \"Imported from previous KYC provider\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Users.UploadFaceAsync(\"11111111-2222-3333-4444-555555555555\", new UsersUploadFaceParams\n{\n Image = \"/9j/4AAQSkZJRgABAQEA...\",\n Comment = \"Imported from previous KYC provider\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Face uploaded and attached to the User profile.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "uuid", + "image_url", + "comment", + "uploaded_by", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the uploaded User face record." + }, + "image_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Signed URL for the uploaded face image." + }, + "comment": { + "type": "string", + "nullable": true, + "description": "Comment supplied when the face was uploaded." + }, + "uploaded_by": { + "type": "string", + "nullable": true, + "description": "Email or actor identifier of the uploader when available." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Upload timestamp." + } + } + }, + "examples": { + "Uploaded": { + "value": { + "uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "image_url": "https://signed-url.example/face.jpg", + "comment": "Imported from previous KYC provider", + "uploaded_by": "api", + "created_at": "2026-05-18T12:00:00Z" + } + } + } + } + } + }, + "400": { + "description": "Invalid request body or image. Common causes: invalid base64, invalid image, no detectable face, image over 2 MB, missing embedding, or the User already has 5 uploaded faces.", + "content": { + "application/json": { + "examples": { + "No face": { + "value": { + "image": "No face detected in the image. Please upload a clear photo with a visible face." + } + }, + "Too many faces": { + "value": { + "detail": "Maximum of 5 faces per user reached." + } + }, + "Too large": { + "value": { + "image": "Image exceeds maximum size of 2 MB." + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "examples": { + "Missing key": { + "value": { + "detail": "Authentication credentials were not provided." + } + } + } + } + } + }, + "403": { + "description": "The credential is valid but cannot write User profiles for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No User exists for that `didit_internal_id` in the application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/update-data/": { + "patch": { + "summary": "Update KYC data extracted from the ID document on a verification session", + "description": "Correct the identity fields that were extracted (OCR / NFC) from the user's ID document — misread names, dates, document numbers, and so on — after the verification has finished. Typical callers are a compliance analyst working from your own back office, or an automated reconciliation job. Every field in the body is optional: only the keys you send are written, everything else is left untouched (`PATCH` semantics).\n\nThe session must belong to your application and be in `Approved`, `Declined`, `In Review`, or `Kyc Expired` status. Sessions in any other state (e.g. still `In Progress`) return `404` with `\"No Session matches the given query.\"` — distinct from the `\"Not found.\"` returned for an unknown or deleted `sessionId`. The session must also carry an ID-verification (KYC) record: sessions whose workflow never ran an ID-verification step return `404` (`\"The session does not have a KYC.\"`). For graph workflows with multiple ID-verification steps, target a specific step with `node_id` (query parameter or body field); when omitted, the most recent KYC record is patched.\n\nTwo derived fields are recomputed on every call: `full_name` (from `first_name` + `last_name`) and `issuing_state_name` (from `issuing_state`). If the free-text `address` changes, the server may also re-geocode it and refresh `parsed_address` (skipped for sandbox sessions). `extra_fields` and `parsed_address` are merged key-by-key rather than replaced — see the per-field descriptions for the `null`-to-delete semantics.\n\nSubmitting at least one valid field fires the `data.updated` [webhook](/integration/webhooks) (trigger `manual_review`) — even when the submitted values equal the stored ones. When values actually change, a `KYC_DATA_UPDATED` activity entry recording the changed fields with previous and new values is appended to the session's `reviews`, visible in [Get Decision](#get-/v3/session/-sessionId-/decision/) and the console activity timeline. The session's status and decision are **not** re-evaluated — use [Update Status](#patch-/v3/session/-sessionId-/update-status/) to change the outcome. An empty JSON body (`{}`) is accepted and simply returns the current KYC data without firing the webhook.\n\nAccepts an API key or a console user token; either way the credential needs the `write:sessions` permission. Authentication and permission failures both return `403` — this API never responds `401`. Note that the session lookup runs before credential checks, so an unknown `sessionId` returns `404` even with missing credentials. This endpoint has a dedicated rate limit of 10 requests per minute per API key (invalid requests count too); the shared write budget of 300 requests per minute also applies.", + "operationId": "patch_v3_session_update_data", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_data", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session whose extracted document data you are correcting. Business (KYB) session IDs are not valid here — for company data use [Update KYB Company Data](#patch-/v3/session/-sessionId-/kyb/-companyUuid-/update-data/).", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node whose KYC record to patch, for graph workflows with multiple ID-verification steps (also accepted as a `node_id` body field). When omitted, the most recent KYC record on the session is patched. If no KYC exists for the given node, the call returns `404` (`\"The session does not have a KYC for node ''.\"`).", + "schema": { + "type": "string", + "example": "feature_ocr" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "All fields optional — send only what you want to change.", + "properties": { + "document_type": { + "type": "string", + "description": "Document type code. Accepts the short codes listed in the enum, plus six long-form aliases normalized server-side before validation: `Passport` → `P`, `Identity Card` → `ID`, `Driver's License` → `DL`, `Residence Permit` → `RP`, `Health Insurance Card` → `HIC`, `Tax Card` → `TC`. Any other value (including `PASSPORT` in caps) returns `400`.", + "enum": [ + "P", + "DL", + "ID", + "RP", + "SSC", + "HIC", + "WP", + "TC", + "VISA", + "PSC", + "BC", + "OTHER" + ], + "nullable": true, + "example": "P" + }, + "document_subtype": { + "type": "string", + "maxLength": 128, + "nullable": true, + "description": "Document subtype from the document registry, including the region prefix when the registry distinguishes regional variants." + }, + "document_number": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Document number as printed on the ID.", + "example": "PAB123456" + }, + "personal_number": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Personal / national identification number where the document carries one (e.g. Spanish DNI on a passport).", + "example": "99999999R" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth in `YYYY-MM-DD`. Any other format returns `400`.", + "example": "1980-01-01" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Issue date of the document in `YYYY-MM-DD`." + }, + "expiration_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Expiration date of the document in `YYYY-MM-DD`." + }, + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Issuing country as an ISO 3166-1 alpha-3 code. Case-insensitive (`esp` is stored as `ESP`); the human-readable `issuing_state_name` shown in [Get Decision](#get-/v3/session/-sessionId-/decision/) is recomputed from this value. Invalid codes return `400`.", + "example": "ESP" + }, + "first_name": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Given name(s). The stored `full_name` is recomputed as `first_name + ' ' + last_name` on every call to this endpoint.", + "example": "Carmen" + }, + "last_name": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Family name(s). Also feeds the recomputed `full_name`.", + "example": "Espanola Espanola" + }, + "gender": { + "type": "string", + "enum": [ + "M", + "F", + "U" + ], + "nullable": true, + "description": "Sex as printed on the document: `M` (male), `F` (female), or `U` (unknown). Any other value — including `X` — returns `400`." + }, + "address": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it synchronously before responding — the 200 body already reflects the refreshed `parsed_address`. The re-geocode also requires `issuing_state` to be present on the KYC record." + }, + "place_of_birth": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Place of birth as printed on the document." + }, + "nationality": { + "type": "string", + "maxLength": 32, + "nullable": true, + "description": "Nationality as printed on the document. Free text up to 32 characters — typically an ISO 3166-1 alpha-3 code (e.g. `ESP`), but not validated as one." + }, + "marital_status": { + "type": "string", + "enum": [ + "SINGLE", + "MARRIED", + "DIVORCED", + "WIDOWED", + "UNKNOWN" + ], + "description": "Marital status where the document carries it. Any other value returns `400`." + }, + "extra_fields": { + "type": "object", + "additionalProperties": true, + "nullable": true, + "description": "Per-key overrides for document-specific extra fields (e.g. `blood_group`, `dl_categories`, `mrz_string`). Keys are merged into the existing override map: send a key with a value to set it, or with `null` / `\"\"` to remove that key from the merged view. Send the whole field as `null` to clear all previously applied overrides. Must be a JSON object, otherwise `400`. The response always returns the merged view (OCR-extracted values with your overrides applied)." + }, + "parsed_address": { + "type": "object", + "nullable": true, + "description": "Structured address. Only these nested keys are accepted: `address_type`, `city`, `region`, `street_1`, `street_2`, `postal_code`, `country` — any other key returns `400`. Per-key semantics: a value sets the key, `null` removes it. `formatted_address` is recomputed server-side by joining `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country` and is returned in the response — you cannot set it directly.", + "properties": { + "address_type": { + "type": "string", + "nullable": true, + "description": "Free-form address classification (e.g. `home`)." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Madrid" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State / province / autonomous community." + }, + "street_1": { + "type": "string", + "nullable": true, + "example": "Avda de Madrid 34" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "28013" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`.", + "example": "ES" + } + } + } + } + }, + "examples": { + "Correct name and DOB": { + "summary": "Fix OCR-misread name, date of birth, and structured address", + "value": { + "first_name": "Carmen", + "last_name": "Espanola Espanola", + "date_of_birth": "1980-01-01", + "issuing_state": "ESP", + "marital_status": "MARRIED", + "extra_fields": { + "blood_group": "O+" + }, + "parsed_address": { + "street_1": "Avda de Madrid 34", + "city": "Madrid", + "postal_code": "28013", + "country": "ES" + } + } + }, + "Remove an extra field": { + "summary": "Delete one extra-field key, keep the rest", + "value": { + "extra_fields": { + "blood_group": null + } + } + }, + "Long-form document type": { + "summary": "Long-form alias is normalized to the short code (here P)", + "value": { + "document_type": "Passport" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "KYC data updated. Returns the **full current KYC record** (every updatable field with its stored value, plus the merged `extra_fields` and the current `parsed_address`) — not just the fields you submitted. The `data.updated` webhook fires when at least one valid field was in the payload.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "document_type": { + "type": "string", + "description": "Document type code. Accepts the short codes listed in the enum, plus six long-form aliases normalized server-side before validation: `Passport` → `P`, `Identity Card` → `ID`, `Driver's License` → `DL`, `Residence Permit` → `RP`, `Health Insurance Card` → `HIC`, `Tax Card` → `TC`. Any other value (including `PASSPORT` in caps) returns `400`.", + "enum": [ + "P", + "DL", + "ID", + "RP", + "SSC", + "HIC", + "WP", + "TC", + "VISA", + "PSC", + "BC", + "OTHER" + ], + "nullable": true, + "example": "P" + }, + "document_subtype": { + "type": "string", + "maxLength": 128, + "nullable": true, + "description": "Document subtype from the document registry, including the region prefix when the registry distinguishes regional variants." + }, + "document_number": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Document number as printed on the ID.", + "example": "PAB123456" + }, + "personal_number": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Personal / national identification number where the document carries one (e.g. Spanish DNI on a passport).", + "example": "99999999R" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth in `YYYY-MM-DD`. Any other format returns `400`.", + "example": "1980-01-01" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Issue date of the document in `YYYY-MM-DD`." + }, + "expiration_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Expiration date of the document in `YYYY-MM-DD`." + }, + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Issuing country as an ISO 3166-1 alpha-3 code. Case-insensitive (`esp` is stored as `ESP`); the human-readable `issuing_state_name` shown in [Get Decision](#get-/v3/session/-sessionId-/decision/) is recomputed from this value. Invalid codes return `400`.", + "example": "ESP" + }, + "first_name": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Given name(s). The stored `full_name` is recomputed as `first_name + ' ' + last_name` on every call to this endpoint.", + "example": "Carmen" + }, + "last_name": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Family name(s). Also feeds the recomputed `full_name`.", + "example": "Espanola Espanola" + }, + "gender": { + "type": "string", + "enum": [ + "M", + "F", + "U" + ], + "nullable": true, + "description": "Sex as printed on the document: `M` (male), `F` (female), or `U` (unknown). Any other value — including `X` — returns `400`." + }, + "address": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it in the background of the same request and may overwrite `parsed_address` with the geocoder result." + }, + "place_of_birth": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Place of birth as printed on the document." + }, + "nationality": { + "type": "string", + "maxLength": 32, + "nullable": true, + "description": "Nationality as printed on the document. Free text up to 32 characters — typically an ISO 3166-1 alpha-3 code (e.g. `ESP`), but not validated as one." + }, + "marital_status": { + "type": "string", + "enum": [ + "SINGLE", + "MARRIED", + "DIVORCED", + "WIDOWED", + "UNKNOWN" + ], + "description": "Marital status where the document carries it. Any other value returns `400`." + }, + "extra_fields": { + "type": "object", + "additionalProperties": true, + "nullable": true, + "description": "Merged view of the document's extra fields: the OCR-extracted values with all operator overrides applied. Keys removed via a `null` override are absent from this map." + }, + "parsed_address": { + "type": "object", + "nullable": true, + "description": "Current structured address (with internal bookkeeping keys stripped), or `null` when none exists. Includes the recomputed `formatted_address` alongside the seven editable keys.", + "properties": { + "address_type": { + "type": "string", + "nullable": true, + "description": "Free-form address classification (e.g. `home`)." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Madrid" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State / province / autonomous community." + }, + "street_1": { + "type": "string", + "nullable": true, + "example": "Avda de Madrid 34" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "28013" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`.", + "example": "ES" + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "Joined, human-readable form of the structured address. Recomputed server-side after every `parsed_address` edit.", + "example": "Avda de Madrid 34, Madrid, 28013, ES" + } + } + } + } + }, + "examples": { + "Updated KYC": { + "summary": "Full KYC record after the correction", + "value": { + "document_type": "P", + "document_subtype": null, + "document_number": "PAB123456", + "personal_number": "99999999R", + "date_of_birth": "1980-01-01", + "date_of_issue": "2021-02-09", + "expiration_date": "2031-02-08", + "issuing_state": "ESP", + "first_name": "Carmen", + "last_name": "Espanola Espanola", + "gender": "F", + "address": "AVDA DE MADRID 34, MADRID, MADRID 28013", + "place_of_birth": "MADRID", + "nationality": "ESP", + "marital_status": "MARRIED", + "extra_fields": { + "mrz_string": "P\": [\"\"]}`; errors on nested `parsed_address` / `extra_fields` keys use an object value: `{\"parsed_address\": {\"\": \"\"}}`.", + "content": { + "application/json": { + "examples": { + "Invalid gender": { + "summary": "gender must be M, F, or U", + "value": { + "gender": [ + "\"X\" is not a valid choice." + ] + } + }, + "Invalid document type": { + "summary": "Not a short code or long-form alias", + "value": { + "document_type": [ + "\"PASSPORT\" is not a valid choice." + ] + } + }, + "Invalid issuing state": { + "summary": "issuing_state is not ISO 3166-1 alpha-3", + "value": { + "issuing_state": [ + "Invalid ISO 3166-1 alpha-3 country code for issuing_state." + ] + } + }, + "Invalid marital status": { + "summary": "marital_status not in the enum", + "value": { + "marital_status": [ + "\"ENGAGED\" is not a valid choice." + ] + } + }, + "Bad date format": { + "summary": "Dates must be YYYY-MM-DD", + "value": { + "date_of_birth": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + }, + "Unsupported parsed_address key": { + "summary": "Only the seven documented keys are allowed", + "value": { + "parsed_address": { + "zipcode": "Unsupported key. Allowed keys: address_type, city, region, street_1, street_2, postal_code, country" + } + } + }, + "Invalid parsed_address country": { + "summary": "Nested country must be alpha-2, not alpha-3", + "value": { + "parsed_address": { + "country": "Invalid ISO 3166-1 alpha-2 country code." + } + } + }, + "extra_fields not an object": { + "summary": "extra_fields must be a JSON object", + "value": { + "extra_fields": [ + "This field must be a JSON object." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Credential lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown session, session in a non-editable state, or no KYC record to patch. Each case has a distinct `detail` message (see examples). The unknown-session lookup runs before credential checks.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "summary": "No session with this ID in your application (or it was deleted)", + "value": { + "detail": "Not found." + } + }, + "Wrong session state": { + "summary": "Session exists but is not Approved / Declined / In Review / Kyc Expired", + "value": { + "detail": "No Session matches the given query." + } + }, + "No KYC record": { + "summary": "The workflow never ran an ID-verification step", + "value": { + "detail": "The session does not have a KYC." + } + }, + "No KYC for node": { + "summary": "node_id given but no KYC exists for it", + "value": { + "detail": "The session does not have a KYC for node 'feature_missing'." + } + } + } + } + } + }, + "429": { + "description": "Dedicated rate limit exceeded — 10 KYC data updates per minute per API key (rejected requests count toward the budget). The shared 300/min write budget can also trigger with its own message. Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "KYC update budget exhausted", + "value": { + "detail": "Session KYC data update rate limit exceeded. You can make up to 10 requests per minute." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-data/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"first_name\": \"Carmen\",\n \"last_name\": \"Espanola Espanola\",\n \"date_of_birth\": \"1980-01-01\",\n \"issuing_state\": \"ESP\",\n \"parsed_address\": {\n \"street_1\": \"Avda de Madrid 34\",\n \"city\": \"Madrid\",\n \"postal_code\": \"28013\",\n \"country\": \"ES\"\n }\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_data(\n \"11111111-2222-3333-4444-555555555555\",\n date_of_birth=\"1980-01-01\",\n issuing_state=\"ESP\",\n first_name=\"Carmen\",\n last_name=\"Espanola Espanola\",\n marital_status=\"MARRIED\",\n extra_fields={\"blood_group\": \"O+\"},\n parsed_address={\n \"city\": \"Madrid\",\n \"street_1\": \"Avda de Madrid 34\",\n \"postal_code\": \"28013\",\n \"country\": \"ES\",\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateData(\"11111111-2222-3333-4444-555555555555\", {\n date_of_birth: \"1980-01-01\",\n issuing_state: \"ESP\",\n first_name: \"Carmen\",\n last_name: \"Espanola Espanola\",\n marital_status: \"MARRIED\",\n extra_fields: { blood_group: \"O+\" },\n parsed_address: {\n city: \"Madrid\",\n street_1: \"Avda de Madrid 34\",\n postal_code: \"28013\",\n country: \"ES\",\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateData(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsUpdateDataParams{\n\tDateOfBirth: didit.String(\"1980-01-01\"),\n\tIssuingState: didit.String(\"ESP\"),\n\tFirstName: didit.String(\"Carmen\"),\n\tLastName: didit.String(\"Espanola Espanola\"),\n\tMaritalStatus: didit.Some(didit.SessionsUpdateDataParamsMaritalStatus(\"MARRIED\")),\n\tExtraFields: didit.Some(map[string]any{\"blood_group\": \"O+\"}),\n\tParsedAddress: didit.Some(didit.SessionsUpdateDataParamsParsedAddress{\n\t\tCity: didit.String(\"Madrid\"),\n\t\tStreet1: didit.String(\"Avda de Madrid 34\"),\n\t\tPostalCode: didit.String(\"28013\"),\n\t\tCountry: didit.String(\"ES\"),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SessionsUpdateDataParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateData(\"11111111-2222-3333-4444-555555555555\", SessionsUpdateDataParams.builder()\n .dateOfBirth(LocalDate.parse(\"1980-01-01\"))\n .issuingState(\"ESP\")\n .firstName(\"Carmen\")\n .lastName(\"Espanola Espanola\")\n .maritalStatus(SessionsUpdateDataParams.MaritalStatus.of(\"MARRIED\"))\n .extraFields(Map.of(\"blood_group\", \"O+\"))\n .parsedAddress(SessionsUpdateDataParams.ParsedAddress.builder()\n .city(\"Madrid\")\n .street1(\"Avda de Madrid 34\")\n .postalCode(\"28013\")\n .country(\"ES\")\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateData('11111111-2222-3333-4444-555555555555', [\n 'date_of_birth' => '1980-01-01',\n 'issuing_state' => 'ESP',\n 'first_name' => 'Carmen',\n 'last_name' => 'Espanola Espanola',\n 'marital_status' => 'MARRIED',\n 'extra_fields' => ['blood_group' => 'O+'],\n 'parsed_address' => [\n 'city' => 'Madrid',\n 'street_1' => 'Avda de Madrid 34',\n 'postal_code' => '28013',\n 'country' => 'ES',\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_data(\n \"11111111-2222-3333-4444-555555555555\",\n date_of_birth: \"1980-01-01\",\n issuing_state: \"ESP\",\n first_name: \"Carmen\",\n last_name: \"Espanola Espanola\",\n marital_status: \"MARRIED\",\n extra_fields: { blood_group: \"O+\" },\n parsed_address: {\n city: \"Madrid\",\n street_1: \"Avda de Madrid 34\",\n postal_code: \"28013\",\n country: \"ES\",\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateDataAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsUpdateDataParams\n{\n DateOfBirth = \"1980-01-01\",\n IssuingState = \"ESP\",\n FirstName = \"Carmen\",\n LastName = \"Espanola Espanola\",\n MaritalStatus = new SessionsUpdateDataParamsMaritalStatus(\"MARRIED\"),\n ExtraFields = new Dictionary { [\"blood_group\"] = \"O+\" },\n ParsedAddress = new SessionsUpdateDataParamsParsedAddress\n {\n City = \"Madrid\",\n Street1 = \"Avda de Madrid 34\",\n PostalCode = \"28013\",\n Country = \"ES\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/features/{nodeId}/update-status/": { + "patch": { + "summary": "Update an individual feature (step) status", + "description": "Set the status of a single verification feature (step) within a session. Target the feature by its workflow graph `nodeId`. On non-graph workflows, whose steps have no node, use `default` and also pass `feature_type` to say which step you mean.\n\nWorks for both User Verification (KYC) and Business Verification (KYB) sessions and every step type: ID/OCR, Liveness, Face Match, NFC, AML, Proof of Address, Phone, Email, Database Validation, Questionnaire, and the KYB registry / documents / key-people checks. This endpoint is **not** available for standalone API sessions (`session_type = API`).\n\n`new_status` must be one of `Approved`, `Declined`, or `In Review`, and must differ from the feature's current status.\n\n**When the override applies.** Without `defer_until_flow_finishes` (or with `false`) the override applies at once, as it always did; when the person is still in the flow it is recorded as `forced`. With `defer_until_flow_finishes: true` while the person is still in the flow (the session is `Not Started`, `In Progress` or `Resubmitted` and has not finished), the override is stored as `pending_until_flow_finishes` and applies when the flow finishes, after the flow reaches its own decision. On a session whose flow already ended it applies at once either way. A waiting override is cancelled instead when the person redoes its step before finishing, when a newer override on the same check replaces it, when the session leaves the flow unfinished (abandoned, expired, or decided mid-flow), or when you cancel it with `POST /v3/session/{sessionId}/features/overrides/{overrideId}/cancel/`. List a session's overrides with `GET /v3/session/{sessionId}/features/overrides/`.\n\n**Effect on the session.** A session that already has a decision keeps it: only the check changes. When the session has no decision yet and its workflow is waiting on exactly this step, applying the override completes the step: the workflow moves on, later steps run, and a status node can set the session decision and fire `status.updated`.\n\n**Webhooks and activity.** A `data.updated` webhook fires when the override applies (at once, or when the flow finishes), never while it waits or when it is cancelled. Every override is recorded in the session's activity as `FEATURE_OVERRIDE_PENDING`, `FEATURE_OVERRIDE_APPLIED` or `FEATURE_OVERRIDE_CANCELLED`, with who made it; the request's entry carries your `comment`.\n\nAuthenticate with your API key (`x-api-key`) or a user authorization header carrying the `write:sessions` privilege; a scoped API key needs write access to decisions.", + "operationId": "patch_v3_session_update_feature_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_feature_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "in": "path", + "name": "nodeId", + "required": true, + "description": "Workflow graph node ID of the feature to update (for example `feature_ocr_1`). Use the literal value `default` for non-graph (single-step) workflows where the feature has no node ID.", + "schema": { + "type": "string", + "example": "feature_ocr_1" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "new_status" + ], + "properties": { + "new_status": { + "type": "string", + "description": "Target status for this feature. Must differ from the current value.", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "example": "Approved" + }, + "comment": { + "type": "string", + "description": "Optional free-text note recorded on the session's review trail for this feature change.", + "example": "Document manually verified by compliance." + }, + "feature_type": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "OCR", + "OCR_BACK", + "NFC", + "LIVENESS", + "FACE", + "AGE_ESTIMATION", + "AGE_VERIFICATION", + "FACE_SEARCH", + "FACE_MATCH", + "IP_ANALYSIS", + "AML", + "PHONE", + "PHONE_VERIFICATION", + "EMAIL", + "EMAIL_VERIFICATION", + "POA", + "PROOF_OF_ADDRESS", + "DATABASE_VALIDATION", + "DATA_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "BANK_VERIFICATION", + "GEOLOCATION" + ], + "description": "Which kind of step to target. Needed on non-graph workflows (`nodeId` = `default`), where every step shares no node; optional otherwise. An unknown value returns `400`.", + "example": "LIVENESS" + }, + "defer_until_flow_finishes": { + "type": "boolean", + "default": false, + "description": "While the person is still in the flow, store the override as `pending_until_flow_finishes` and apply it when the flow finishes, instead of at once. Omitted or `false`, the override applies at once. On a session whose flow already ended it applies at once either way.", + "example": false + } + } + }, + "examples": { + "Apply Now": { + "summary": "Approve the ID step at once", + "value": { + "new_status": "Approved", + "comment": "Document manually verified by compliance." + } + }, + "Wait For The Flow": { + "summary": "Approve the liveness step once the person finishes the flow", + "value": { + "new_status": "Approved", + "comment": "Liveness retried with a reviewer present.", + "defer_until_flow_finishes": true + } + }, + "Non-graph Workflow": { + "summary": "Target the liveness step of a workflow without nodes (nodeId = default)", + "value": { + "new_status": "In Review", + "feature_type": "LIVENESS" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The override was recorded. `new_status` is the check's status as stored: the requested one once the override applied, the unchanged one while it waits for the flow to finish. `override` says which happened.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid" + }, + "node_id": { + "type": "string", + "nullable": true, + "description": "The updated step's workflow node, or `null` for a step without a node (the `default` path in a non-graph workflow).", + "example": "feature_ocr_1" + }, + "feature_type": { + "type": "string", + "description": "Model name of the updated feature (e.g. `KYC`, `Face`, `FaceMatch`, `AML`, `POA`).", + "example": "KYC" + }, + "new_status": { + "type": "string", + "description": "The check's stored status: the requested one once the override applied, the unchanged one while the override waits for the flow to finish.", + "example": "Approved" + }, + "override": { + "$ref": "#/components/schemas/FeatureStatusOverride" + } + }, + "required": [ + "session_id", + "node_id", + "feature_type", + "new_status", + "override" + ] + }, + "examples": { + "Applied": { + "summary": "The override applied at once", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "node_id": "feature_ocr_1", + "feature_type": "KYC", + "new_status": "Approved", + "override": { + "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "node_id": "feature_ocr_1", + "feature_type": "", + "feature_model": "KYC", + "previous_status": "In Review", + "new_status": "Approved", + "state": "applied", + "forced": false, + "reason": "Document manually verified by compliance.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": "2026-10-13T09:15:00Z", + "cancelled_at": null, + "cancel_reason": null + } + } + }, + "Waiting For The Flow": { + "summary": "defer_until_flow_finishes while the person is still in the flow", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "node_id": "feature_liveness_1", + "feature_type": "Face", + "new_status": "Declined", + "override": { + "id": "9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3", + "node_id": "feature_liveness_1", + "feature_type": "", + "feature_model": "Face", + "previous_status": "Declined", + "new_status": "Approved", + "state": "pending_until_flow_finishes", + "forced": false, + "reason": "Liveness retried with a reviewer present.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": null, + "cancelled_at": null, + "cancel_reason": null + } + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when `new_status` is invalid or unchanged, when `feature_type` is unknown, or when the session is a standalone API session.", + "content": { + "application/json": { + "examples": { + "Same Status": { + "summary": "new_status equals the current status", + "value": { + "new_status": [ + "The new status is the same as the current status." + ] + } + }, + "API Session": { + "summary": "Feature status cannot be changed on standalone API sessions", + "value": [ + "Cannot change feature status for API sessions." + ] + }, + "Unknown feature_type": { + "summary": "feature_type is not a known step type", + "value": { + "feature_type": [ + "Unknown feature_type 'SELFIE'." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "API key lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without write access to decisions", + "value": { + "detail": "This API key has no write access to decisions." + } + } + } + } + } + }, + "404": { + "description": "The session was not found, or it has no feature with the given `nodeId`.", + "content": { + "application/json": { + "examples": { + "Session Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Session not found." + } + }, + "Feature Not Found": { + "summary": "No feature for this node_id", + "value": { + "detail": "No feature found with node_id 'feature_ocr_1' in this session." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/features/feature_ocr_1/update-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"new_status\": \"Approved\",\n \"comment\": \"Document manually verified by compliance.\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_feature_status(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_ocr_1\",\n new_status=\"Approved\",\n comment=\"Document manually verified by compliance.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateFeatureStatus(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_ocr_1\",\n {\n new_status: \"Approved\",\n comment: \"Document manually verified by compliance.\",\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateFeatureStatus(ctx, \"11111111-2222-3333-4444-555555555555\", \"feature_ocr_1\", didit.SessionsUpdateFeatureStatusParams{\n\tNewStatus: \"Approved\",\n\tComment: didit.String(\"Document manually verified by compliance.\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsUpdateFeatureStatusParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateFeatureStatus(\"11111111-2222-3333-4444-555555555555\", \"feature_ocr_1\", SessionsUpdateFeatureStatusParams.builder()\n .newStatus(SessionsUpdateFeatureStatusParams.NewStatus.of(\"Approved\"))\n .comment(\"Document manually verified by compliance.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateFeatureStatus('11111111-2222-3333-4444-555555555555', 'feature_ocr_1', [\n 'new_status' => 'Approved',\n 'comment' => 'Document manually verified by compliance.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_feature_status(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_ocr_1\",\n new_status: \"Approved\",\n comment: \"Document manually verified by compliance.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateFeatureStatusAsync(\"11111111-2222-3333-4444-555555555555\", \"feature_ocr_1\", new SessionsUpdateFeatureStatusParams\n{\n NewStatus = \"Approved\",\n Comment = \"Document manually verified by compliance.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/update-aml-hit-status/": { + "patch": { + "summary": "Update an AML hit review status", + "description": "Set the review status of a **single AML hit** (a potential sanction / PEP / watchlist / adverse-media match) returned by AML screening, without changing the overall session decision. Identify the hit by its `hit_id` (the `id` of an entry in the AML check's `hits` array — see the session decision). A hit id returned in the earlier format is still accepted; the response always names the hit by its current `id`.\n\n`review_status` is one of `Unreviewed`, `Confirmed Match`, `False Positive`, or `Inconclusive`. Reviewer decisions are preserved across ongoing-monitoring updates, so a hit you mark `False Positive` stays that way when the provider re-screens.\n\nA successful call fires a `data.updated` webhook and appends an activity entry (with actor attribution) to the session's review trail. For graph workflows with multiple AML checks, pass `node_id` to target a specific check.\n\nAuthenticate with your API key (`x-api-key`) or a user authorization header — either must carry the `write:sessions` privilege.", + "operationId": "patch_v3_session_update_aml_hit_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_aml_hit_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "in": "query", + "name": "node_id", + "required": false, + "description": "Workflow graph node ID. Required only when the session has multiple AML checks (one per node); omit it for single-AML sessions.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "hit_id", + "review_status" + ], + "properties": { + "hit_id": { + "type": "string", + "description": "The `id` of the hit in the AML check's `hits` array (`hit_...`). A hit id returned in the earlier format is still accepted.", + "example": "hit_7c1e9a40b2d35f86e0a4c912" + }, + "review_status": { + "type": "string", + "description": "The new review status for the hit.", + "enum": [ + "Unreviewed", + "Confirmed Match", + "False Positive", + "Inconclusive" + ], + "example": "False Positive" + }, + "node_id": { + "type": "string", + "description": "Optional graph node ID. Use it (here or as a query parameter) when the session has multiple AML checks.", + "example": "feature_aml_1" + } + } + }, + "example": { + "hit_id": "hit_7c1e9a40b2d35f86e0a4c912", + "review_status": "False Positive" + } + } + } + }, + "responses": { + "200": { + "description": "Hit review status updated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message", + "hit_id", + "review_status" + ], + "properties": { + "message": { + "type": "string", + "description": "Always `Hit status updated successfully`." + }, + "hit_id": { + "type": "string", + "description": "The `id` of the updated hit, as the session decision returns it, also when the request named the hit by an id in the earlier format." + }, + "review_status": { + "type": "string", + "enum": [ + "Unreviewed", + "Confirmed Match", + "False Positive", + "Inconclusive" + ], + "description": "The review status now stored on the hit." + } + } + }, + "example": { + "message": "Hit status updated successfully", + "hit_id": "hit_7c1e9a40b2d35f86e0a4c912", + "review_status": "False Positive" + } + } + } + }, + "400": { + "description": "Validation error — `hit_id` empty or `review_status` not one of the allowed values.", + "content": { + "application/json": { + "example": { + "review_status": [ + "\"Maybe\" is not a valid choice." + ] + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "API key lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "The session was not found, the session has no AML check (optionally for the given `node_id`), or no hit matches `hit_id`.", + "content": { + "application/json": { + "examples": { + "No AML Check": { + "summary": "Session has no AML check", + "value": { + "detail": "The session does not have an AML check." + } + }, + "Hit Not Found": { + "summary": "Unknown hit_id", + "value": { + "detail": "Hit with ID 'hit_9d2e7f41c0a8b3e65d1f0c47' not found in the AML check." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-aml-hit-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"hit_id\": \"abc123\",\n \"review_status\": \"False Positive\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_aml_hit_status(\n \"11111111-2222-3333-4444-555555555555\",\n hit_id=\"hit_7c1e9a40b2d35f86e0a4c912\",\n review_status=\"False Positive\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateAmlHitStatus(\"11111111-2222-3333-4444-555555555555\", {\n hit_id: \"hit_7c1e9a40b2d35f86e0a4c912\",\n review_status: \"False Positive\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateAMLHitStatus(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsUpdateAMLHitStatusParams{\n\tHitID: \"hit_7c1e9a40b2d35f86e0a4c912\",\n\tReviewStatus: \"False Positive\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsUpdateAmlHitStatusParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateAmlHitStatus(\"11111111-2222-3333-4444-555555555555\", SessionsUpdateAmlHitStatusParams.builder()\n .hitId(\"hit_7c1e9a40b2d35f86e0a4c912\")\n .reviewStatus(SessionsUpdateAmlHitStatusParams.ReviewStatus.of(\"False Positive\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateAmlHitStatus('11111111-2222-3333-4444-555555555555', [\n 'hit_id' => 'hit_7c1e9a40b2d35f86e0a4c912',\n 'review_status' => 'False Positive',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_aml_hit_status(\n \"11111111-2222-3333-4444-555555555555\",\n hit_id: \"hit_7c1e9a40b2d35f86e0a4c912\",\n review_status: \"False Positive\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateAmlHitStatusAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsUpdateAmlHitStatusParams\n{\n HitId = \"hit_7c1e9a40b2d35f86e0a4c912\",\n ReviewStatus = \"False Positive\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/bulk-update-aml-hit-status/": { + "patch": { + "summary": "Bulk update AML hit review statuses", + "description": "Update the review status of **multiple AML hits** in one call. Provide `hit_updates`, a list of `{ hit_id, review_status }` pairs. `review_status` is one of `Unreviewed`, `Confirmed Match`, `False Positive`, or `Inconclusive`.\n\nThe update is all-or-nothing: if any `hit_id` is not found in the AML check, the request returns `404` and no hit is changed. A successful call fires a single `data.updated` webhook and records one activity entry covering all changes. For graph workflows with multiple AML checks, pass `node_id` to target a specific check.\n\nAuthenticate with your API key (`x-api-key`) or a user authorization header — either must carry the `write:sessions` privilege.", + "operationId": "patch_v3_session_bulk_update_aml_hit_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "bulk_update_aml_hit_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "in": "query", + "name": "node_id", + "required": false, + "description": "Workflow graph node ID. Required only when the session has multiple AML checks (one per node); omit it for single-AML sessions.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "hit_updates" + ], + "properties": { + "hit_updates": { + "type": "array", + "description": "Non-empty list of hit updates.", + "items": { + "type": "object", + "required": [ + "hit_id", + "review_status" + ], + "properties": { + "hit_id": { + "type": "string", + "example": "hit_7c1e9a40b2d35f86e0a4c912", + "description": "The `id` of the hit in the AML check's `hits` array (`hit_...`). A hit id returned in the earlier format is still accepted." + }, + "review_status": { + "type": "string", + "enum": [ + "Unreviewed", + "Confirmed Match", + "False Positive", + "Inconclusive" + ], + "example": "Confirmed Match" + } + } + } + }, + "node_id": { + "type": "string", + "description": "Optional graph node ID. Use it (here or as a query parameter) when the session has multiple AML checks.", + "example": "feature_aml_1" + } + } + }, + "example": { + "hit_updates": [ + { + "hit_id": "hit_7c1e9a40b2d35f86e0a4c912", + "review_status": "Confirmed Match" + }, + { + "hit_id": "hit_3b8f20d6a91c4e75f0b2d6a8", + "review_status": "False Positive" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "All hits updated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message", + "updated_hits" + ], + "properties": { + "message": { + "type": "string", + "description": "`Updated {n} hit(s) successfully`, where `n` is the number of entries in `hit_updates`." + }, + "updated_hits": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The `id` of the hit each entry in `hit_updates` names, as the session decision returns it, in request order, including hits whose status did not change." + } + } + }, + "example": { + "message": "Updated 2 hit(s) successfully", + "updated_hits": [ + "hit_7c1e9a40b2d35f86e0a4c912", + "hit_3b8f20d6a91c4e75f0b2d6a8" + ] + } + } + } + }, + "400": { + "description": "Validation error — `hit_updates` empty, or an entry is missing `hit_id` / `review_status` or carries an invalid value.", + "content": { + "application/json": { + "example": { + "hit_updates": [ + "hit_updates cannot be empty" + ] + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "API key lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "The session was not found, the session has no AML check, or one or more `hit_id`s do not exist (no hit is changed in that case).", + "content": { + "application/json": { + "example": { + "detail": "Hits not found: hit_9d2e7f41c0a8b3e65d1f0c47" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/bulk-update-aml-hit-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"hit_updates\": [\n { \"hit_id\": \"abc123\", \"review_status\": \"Confirmed Match\" },\n { \"hit_id\": \"def456\", \"review_status\": \"False Positive\" }\n ]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.bulk_update_aml_hit_status(\n \"11111111-2222-3333-4444-555555555555\",\n hit_updates=[{\n \"hit_id\": \"hit_7c1e9a40b2d35f86e0a4c912\",\n \"review_status\": \"Confirmed Match\",\n }, {\n \"hit_id\": \"hit_3b8f20d6a91c4e75f0b2d6a8\",\n \"review_status\": \"False Positive\",\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.bulkUpdateAmlHitStatus(\"11111111-2222-3333-4444-555555555555\", {\n hit_updates: [{\n hit_id: \"hit_7c1e9a40b2d35f86e0a4c912\",\n review_status: \"Confirmed Match\",\n }, {\n hit_id: \"hit_3b8f20d6a91c4e75f0b2d6a8\",\n review_status: \"False Positive\",\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.BulkUpdateAMLHitStatus(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsBulkUpdateAMLHitStatusParams{\n\tHitUpdates: []didit.SessionsBulkUpdateAMLHitStatusParamsHitUpdates{{\n\t\tHitID: \"hit_7c1e9a40b2d35f86e0a4c912\",\n\t\tReviewStatus: \"Confirmed Match\",\n\t}, {\n\t\tHitID: \"hit_3b8f20d6a91c4e75f0b2d6a8\",\n\t\tReviewStatus: \"False Positive\",\n\t}},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.SessionsBulkUpdateAmlHitStatusParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().bulkUpdateAmlHitStatus(\"11111111-2222-3333-4444-555555555555\", SessionsBulkUpdateAmlHitStatusParams.builder()\n .hitUpdates(List.of(\n SessionsBulkUpdateAmlHitStatusParams.HitUpdates.builder()\n .hitId(\"hit_7c1e9a40b2d35f86e0a4c912\")\n .reviewStatus(SessionsBulkUpdateAmlHitStatusParams.HitUpdates.ReviewStatus.of(\"Confirmed Match\"))\n .build(),\n SessionsBulkUpdateAmlHitStatusParams.HitUpdates.builder()\n .hitId(\"hit_3b8f20d6a91c4e75f0b2d6a8\")\n .reviewStatus(SessionsBulkUpdateAmlHitStatusParams.HitUpdates.ReviewStatus.of(\"False Positive\"))\n .build()))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->bulkUpdateAmlHitStatus('11111111-2222-3333-4444-555555555555', [\n 'hit_updates' => [[\n 'hit_id' => 'hit_7c1e9a40b2d35f86e0a4c912',\n 'review_status' => 'Confirmed Match',\n ], [\n 'hit_id' => 'hit_3b8f20d6a91c4e75f0b2d6a8',\n 'review_status' => 'False Positive',\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.bulk_update_aml_hit_status(\n \"11111111-2222-3333-4444-555555555555\",\n hit_updates: [{\n hit_id: \"hit_7c1e9a40b2d35f86e0a4c912\",\n review_status: \"Confirmed Match\",\n }, {\n hit_id: \"hit_3b8f20d6a91c4e75f0b2d6a8\",\n review_status: \"False Positive\",\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.BulkUpdateAmlHitStatusAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsBulkUpdateAmlHitStatusParams\n{\n HitUpdates = new SessionsBulkUpdateAmlHitStatusParamsHitUpdates[]\n {\n new()\n {\n HitId = \"hit_7c1e9a40b2d35f86e0a4c912\",\n ReviewStatus = \"Confirmed Match\",\n },\n new()\n {\n HitId = \"hit_3b8f20d6a91c4e75f0b2d6a8\",\n ReviewStatus = \"False Positive\",\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/update-poa-data/": { + "patch": { + "summary": "Update Proof of Address data extracted from the POA document on a verification session", + "description": "Correct the fields extracted from the user's Proof of Address document — utility bill, bank statement, or other address evidence — after the verification has finished: issuer, dates, the printed name, the address itself, and banking details. Every field in the body is optional: only the keys you send are written, everything else is left untouched (`PATCH` semantics).\n\nThe session must belong to your application and be in `Approved`, `Declined`, `In Review`, or `Kyc Expired` status. Sessions in any other state (e.g. still `In Progress`) return `404` with `\"No Session matches the given query.\"` — distinct from the `\"Not found.\"` returned for an unknown or deleted `sessionId`. The session must also carry a POA record: sessions whose workflow never ran a Proof of Address step return `404` (`\"The session does not have a POA.\"`). For graph workflows with multiple POA steps, target a specific step with `node_id` (query parameter or body field); when omitted, the most recent POA record is patched.\n\n`extra_fields` is merged key-by-key: nine known keys (`bank_iban`, `additional_names`, …) write through to dedicated POA columns, any other key is kept as a custom extra, and `null` / `\"\"` deletes a key. `poa_parsed_address` accepts only the seven documented keys and recomputes `formatted_address` / `poa_formatted_address` after every edit. If the free-text `poa_address` changes, the server may also re-geocode it and refresh the structured address (skipped for sandbox sessions).\n\nUnlike the KYC variant of this endpoint, the `data.updated` [webhook](/integration/webhooks) (trigger `manual_review`) fires only when at least one stored value **actually changed** — re-sending identical values is a silent no-op. When values change, a `POA_DATA_UPDATED` activity entry recording the changed fields with previous and new values is appended to the session's `reviews`, visible in [Get Decision](#get-/v3/session/-sessionId-/decision/) and the console activity timeline. The session's status and decision are **not** re-evaluated — use [Update Status](#patch-/v3/session/-sessionId-/update-status/) to change the outcome.\n\nAccepts an API key or a console user token; either way the credential needs the `write:sessions` permission. Authentication and permission failures both return `403` — this API never responds `401`. Note that the session lookup runs before credential checks, so an unknown `sessionId` returns `404` even with missing credentials. This endpoint has a dedicated rate limit of 10 requests per minute per API key (invalid requests count too); the shared write budget of 300 requests per minute also applies.\n\nAn empty JSON body `{}` is a no-op: it returns `200` echoing the current POA record without firing the webhook (`has_changes` stays false).", + "operationId": "patch_v3_session_update_poa_data", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_poa_data", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session whose attached POA record you are correcting.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node whose POA record to patch, for graph workflows with multiple Proof of Address steps (also accepted as a `node_id` body field). When omitted, the most recent POA record on the session is patched. If no POA exists for the given node, the call returns `404` (`\"The session does not have a POA for node ''.\"`).", + "schema": { + "type": "string", + "example": "feature_poa" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "All fields optional — send only what you want to change.", + "properties": { + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Country that issued the POA document, ISO 3166-1 alpha-3. Case-insensitive (normalized to uppercase); invalid codes return `400`.", + "example": "ESP" + }, + "document_type": { + "type": "string", + "enum": [ + "UTILITY_BILL", + "BANK_STATEMENT", + "GOVERNMENT_ISSUED_DOCUMENT", + "OTHER_POA_DOCUMENT", + "UNKNOWN" + ], + "description": "POA document category. Values are uppercase — lowercase variants like `utility_bill` return `400`.", + "example": "UTILITY_BILL" + }, + "document_language": { + "type": "string", + "maxLength": 50, + "nullable": true, + "description": "Language of the document, typically an ISO 639-1 code (e.g. `es`). Free text — not validated as a language code.", + "example": "es" + }, + "issuer": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Organization that issued the document (utility company, bank, government agency).", + "example": "Energy Provider S.A." + }, + "issue_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Issue date of the POA document in `YYYY-MM-DD`. Any other format returns `400`.", + "example": "2026-04-12" + }, + "poa_address": { + "type": "string", + "nullable": true, + "description": "Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it in the background of the same request and may overwrite `poa_parsed_address` / `poa_formatted_address` with the geocoder result. The re-geocode also requires `issuing_state` to be present on the POA record.", + "example": "Avda de Madrid 34, 28013 Madrid" + }, + "name_on_document": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Recipient name as printed on the document.", + "example": "Carmen Espanola" + }, + "extra_fields": { + "type": "object", + "additionalProperties": true, + "nullable": true, + "description": "Document-specific extras, merged key-by-key. Nine keys map to dedicated POA columns: `bank_account_number`, `bank_iban`, `bank_sort_code`, `bank_routing_number`, `bank_swift_bic`, `bank_branch_name`, `bank_branch_address`, `document_phone_number`, and `additional_names` (must be a list of strings or `null`, otherwise `400`). Any other key is stored as a custom extra field. Per-key semantics: a value sets the key; `null` or `\"\"` deletes it. Must be a JSON object, otherwise `400`." + }, + "poa_parsed_address": { + "type": "object", + "nullable": true, + "description": "Structured address. Only these nested keys are accepted: `address_type`, `city`, `region`, `street_1`, `street_2`, `postal_code`, `country` — any other key returns `400`. Per-key semantics: a value sets the key, `null` removes it. `formatted_address` (and the top-level `poa_formatted_address`) is recomputed server-side by joining `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country` — you cannot set it directly.", + "properties": { + "address_type": { + "type": "string", + "nullable": true, + "description": "Free-form address classification (e.g. `home`)." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Madrid" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State / province / autonomous community." + }, + "street_1": { + "type": "string", + "nullable": true, + "example": "Avda de Madrid 34" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "28013" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`.", + "example": "ES" + } + } + } + } + }, + "examples": { + "Correct issuer and address": { + "summary": "Fix document classification, issuer, and the structured address", + "value": { + "issuing_state": "ESP", + "document_type": "BANK_STATEMENT", + "issuer": "Banco Example S.A.", + "issue_date": "2026-05-02", + "name_on_document": "Carmen Espanola", + "poa_address": "Avda de Madrid 34, 28013 Madrid", + "extra_fields": { + "bank_iban": "ES9121000418450200051332", + "additional_names": [ + "Carmen E. Espanola" + ] + }, + "poa_parsed_address": { + "street_1": "Avda de Madrid 34", + "city": "Madrid", + "postal_code": "28013", + "country": "ES" + } + } + }, + "Remove a banking detail": { + "summary": "Delete one extra-field key, keep the rest", + "value": { + "extra_fields": { + "bank_iban": null + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "POA data updated. Returns the **full current POA record** (every updatable field with its stored value, plus the complete `extra_fields` map, `poa_parsed_address`, and `poa_formatted_address`) — not just the fields you submitted. The `data.updated` webhook fires only when at least one value actually changed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Country that issued the POA document, ISO 3166-1 alpha-3. Case-insensitive (normalized to uppercase); invalid codes return `400`.", + "example": "ESP" + }, + "document_type": { + "type": "string", + "enum": [ + "UTILITY_BILL", + "BANK_STATEMENT", + "GOVERNMENT_ISSUED_DOCUMENT", + "OTHER_POA_DOCUMENT", + "UNKNOWN" + ], + "description": "POA document category. Values are uppercase — lowercase variants like `utility_bill` return `400`.", + "example": "UTILITY_BILL" + }, + "document_language": { + "type": "string", + "maxLength": 50, + "nullable": true, + "description": "Language of the document, typically an ISO 639-1 code (e.g. `es`). Free text — not validated as a language code.", + "example": "es" + }, + "issuer": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Organization that issued the document (utility company, bank, government agency).", + "example": "Energy Provider S.A." + }, + "issue_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Issue date of the POA document in `YYYY-MM-DD`. Any other format returns `400`.", + "example": "2026-04-12" + }, + "poa_address": { + "type": "string", + "nullable": true, + "description": "Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it in the background of the same request and may overwrite `poa_parsed_address` / `poa_formatted_address` with the geocoder result.", + "example": "Avda de Madrid 34, 28013 Madrid" + }, + "name_on_document": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Recipient name as printed on the document.", + "example": "Carmen Espanola" + }, + "extra_fields": { + "type": "object", + "additionalProperties": true, + "nullable": true, + "description": "Always present in the response: the nine known keys (with `null` for unset ones, `additional_names` defaulting to `[]`) plus any custom keys previously stored." + }, + "poa_parsed_address": { + "type": "object", + "nullable": true, + "description": "Current structured address, or `null` when none exists. Includes the recomputed `formatted_address` alongside the seven editable keys. Returned as stored — after a server-side geocode it can contain bookkeeping keys (e.g. `raw_results`) alongside the editable keys.", + "properties": { + "address_type": { + "type": "string", + "nullable": true, + "description": "Free-form address classification (e.g. `home`)." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Madrid" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State / province / autonomous community." + }, + "street_1": { + "type": "string", + "nullable": true, + "example": "Avda de Madrid 34" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "28013" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`.", + "example": "ES" + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "Joined, human-readable form of the structured address. Recomputed server-side after every `poa_parsed_address` edit.", + "example": "Avda de Madrid 34, Madrid, 28013, ES" + } + } + }, + "poa_formatted_address": { + "type": "string", + "nullable": true, + "description": "Top-level copy of the recomputed `formatted_address`, kept in sync when the structured address changes.", + "example": "Avda de Madrid 34, Madrid, 28013, ES" + } + } + }, + "examples": { + "Updated POA": { + "summary": "Full POA record after the correction", + "value": { + "issuing_state": "ESP", + "document_type": "BANK_STATEMENT", + "document_language": "es", + "issuer": "Banco Example S.A.", + "issue_date": "2026-05-02", + "poa_address": "Avda de Madrid 34, 28013 Madrid", + "name_on_document": "Carmen Espanola", + "extra_fields": { + "bank_account_number": null, + "bank_iban": "ES9121000418450200051332", + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": null, + "additional_names": [ + "Carmen E. Espanola" + ] + }, + "poa_parsed_address": { + "city": "Madrid", + "street_1": "Avda de Madrid 34", + "postal_code": "28013", + "country": "ES", + "formatted_address": "Avda de Madrid 34, Madrid, 28013, ES" + }, + "poa_formatted_address": "Avda de Madrid 34, Madrid, 28013, ES" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field errors arrive as `{\"\": [\"\"]}`; errors on nested `poa_parsed_address` / `extra_fields` keys use an object value: `{\"extra_fields\": {\"\": \"\"}}`.", + "content": { + "application/json": { + "examples": { + "Invalid document type": { + "summary": "Enum is uppercase — lowercase variants are rejected", + "value": { + "document_type": [ + "\"utility_bill\" is not a valid choice." + ] + } + }, + "Invalid issuing state": { + "summary": "issuing_state is not ISO 3166-1 alpha-3", + "value": { + "issuing_state": [ + "Invalid ISO 3166-1 alpha-3 country code for issuing_state." + ] + } + }, + "additional_names not a list": { + "summary": "additional_names accepts a list of strings or null", + "value": { + "extra_fields": { + "additional_names": "Must be a list or null." + } + } + }, + "Unsupported poa_parsed_address key": { + "summary": "Only the seven documented keys are allowed", + "value": { + "poa_parsed_address": { + "zipcode": "Unsupported key. Allowed keys: address_type, city, region, street_1, street_2, postal_code, country" + } + } + }, + "Bad date format": { + "summary": "Dates must be YYYY-MM-DD", + "value": { + "issue_date": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Credential lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown session, session in a non-editable state, or no POA record to patch. Each case has a distinct `detail` message (see examples). The unknown-session lookup runs before credential checks.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "summary": "No session with this ID in your application (or it was deleted)", + "value": { + "detail": "Not found." + } + }, + "Wrong session state": { + "summary": "Session exists but is not Approved / Declined / In Review / Kyc Expired", + "value": { + "detail": "No Session matches the given query." + } + }, + "No POA record": { + "summary": "The workflow never ran a Proof of Address step", + "value": { + "detail": "The session does not have a POA." + } + }, + "No POA for node": { + "summary": "node_id given but no POA exists for it", + "value": { + "detail": "The session does not have a POA for node 'feature_missing'." + } + } + } + } + } + }, + "429": { + "description": "Dedicated rate limit exceeded — 10 POA data updates per minute per API key (rejected requests count toward the budget). The shared 300/min write budget can also trigger with its own message. Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "POA update budget exhausted", + "value": { + "detail": "Session POA data update rate limit exceeded. You can make up to 10 requests per minute." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-poa-data/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"issuing_state\": \"ESP\",\n \"document_type\": \"BANK_STATEMENT\",\n \"issuer\": \"Banco Example S.A.\",\n \"issue_date\": \"2026-05-02\",\n \"name_on_document\": \"Carmen Espanola\",\n \"poa_address\": \"Avda de Madrid 34, 28013 Madrid\",\n \"extra_fields\": {\n \"bank_iban\": \"ES9121000418450200051332\"\n },\n \"poa_parsed_address\": {\n \"city\": \"Madrid\",\n \"postal_code\": \"28013\",\n \"country\": \"ES\"\n }\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_poa_data(\n \"11111111-2222-3333-4444-555555555555\",\n issuing_state=\"ESP\",\n document_type=\"BANK_STATEMENT\",\n issuer=\"Banco Example S.A.\",\n issue_date=\"2026-05-02\",\n poa_address=\"Avda de Madrid 34, 28013 Madrid\",\n name_on_document=\"Carmen Espanola\",\n extra_fields={\"bank_iban\": \"ES9121000418450200051332\", \"additional_names\": [\"Carmen E. Espanola\"]},\n poa_parsed_address={\n \"city\": \"Madrid\",\n \"street_1\": \"Avda de Madrid 34\",\n \"postal_code\": \"28013\",\n \"country\": \"ES\",\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updatePoaData(\"11111111-2222-3333-4444-555555555555\", {\n issuing_state: \"ESP\",\n document_type: \"BANK_STATEMENT\",\n issuer: \"Banco Example S.A.\",\n issue_date: \"2026-05-02\",\n poa_address: \"Avda de Madrid 34, 28013 Madrid\",\n name_on_document: \"Carmen Espanola\",\n extra_fields: { bank_iban: \"ES9121000418450200051332\", additional_names: [\"Carmen E. Espanola\"] },\n poa_parsed_address: {\n city: \"Madrid\",\n street_1: \"Avda de Madrid 34\",\n postal_code: \"28013\",\n country: \"ES\",\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdatePOAData(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsUpdatePOADataParams{\n\tIssuingState: didit.String(\"ESP\"),\n\tDocumentType: didit.Some(didit.SessionsUpdatePOADataParamsDocumentType(\"BANK_STATEMENT\")),\n\tIssuer: didit.String(\"Banco Example S.A.\"),\n\tIssueDate: didit.String(\"2026-05-02\"),\n\tPOAAddress: didit.String(\"Avda de Madrid 34, 28013 Madrid\"),\n\tNameOnDocument: didit.String(\"Carmen Espanola\"),\n\tExtraFields: didit.Some(map[string]any{\"bank_iban\": \"ES9121000418450200051332\", \"additional_names\": []any{\"Carmen E. Espanola\"}}),\n\tPOAParsedAddress: didit.Some(didit.SessionsUpdatePOADataParamsPOAParsedAddress{\n\t\tCity: didit.String(\"Madrid\"),\n\t\tStreet1: didit.String(\"Avda de Madrid 34\"),\n\t\tPostalCode: didit.String(\"28013\"),\n\t\tCountry: didit.String(\"ES\"),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SessionsUpdatePoaDataParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updatePoaData(\"11111111-2222-3333-4444-555555555555\", SessionsUpdatePoaDataParams.builder()\n .issuingState(\"ESP\")\n .documentType(SessionsUpdatePoaDataParams.DocumentType.of(\"BANK_STATEMENT\"))\n .issuer(\"Banco Example S.A.\")\n .issueDate(LocalDate.parse(\"2026-05-02\"))\n .poaAddress(\"Avda de Madrid 34, 28013 Madrid\")\n .nameOnDocument(\"Carmen Espanola\")\n .extraFields(Map.of(\"bank_iban\", \"ES9121000418450200051332\", \"additional_names\", List.of(\"Carmen E. Espanola\")))\n .poaParsedAddress(SessionsUpdatePoaDataParams.PoaParsedAddress.builder()\n .city(\"Madrid\")\n .street1(\"Avda de Madrid 34\")\n .postalCode(\"28013\")\n .country(\"ES\")\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updatePoaData('11111111-2222-3333-4444-555555555555', [\n 'issuing_state' => 'ESP',\n 'document_type' => 'BANK_STATEMENT',\n 'issuer' => 'Banco Example S.A.',\n 'issue_date' => '2026-05-02',\n 'poa_address' => 'Avda de Madrid 34, 28013 Madrid',\n 'name_on_document' => 'Carmen Espanola',\n 'extra_fields' => ['bank_iban' => 'ES9121000418450200051332', 'additional_names' => ['Carmen E. Espanola']],\n 'poa_parsed_address' => [\n 'city' => 'Madrid',\n 'street_1' => 'Avda de Madrid 34',\n 'postal_code' => '28013',\n 'country' => 'ES',\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_poa_data(\n \"11111111-2222-3333-4444-555555555555\",\n issuing_state: \"ESP\",\n document_type: \"BANK_STATEMENT\",\n issuer: \"Banco Example S.A.\",\n issue_date: \"2026-05-02\",\n poa_address: \"Avda de Madrid 34, 28013 Madrid\",\n name_on_document: \"Carmen Espanola\",\n extra_fields: { bank_iban: \"ES9121000418450200051332\", additional_names: [\"Carmen E. Espanola\"] },\n poa_parsed_address: {\n city: \"Madrid\",\n street_1: \"Avda de Madrid 34\",\n postal_code: \"28013\",\n country: \"ES\",\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdatePoaDataAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsUpdatePoaDataParams\n{\n IssuingState = \"ESP\",\n DocumentType = new SessionsUpdatePoaDataParamsDocumentType(\"BANK_STATEMENT\"),\n Issuer = \"Banco Example S.A.\",\n IssueDate = \"2026-05-02\",\n PoaAddress = \"Avda de Madrid 34, 28013 Madrid\",\n NameOnDocument = \"Carmen Espanola\",\n ExtraFields = new Dictionary { [\"bank_iban\"] = \"ES9121000418450200051332\", [\"additional_names\"] = new object[] { \"Carmen E. Espanola\" } },\n PoaParsedAddress = new SessionsUpdatePoaDataParamsPoaParsedAddress\n {\n City = \"Madrid\",\n Street1 = \"Avda de Madrid 34\",\n PostalCode = \"28013\",\n Country = \"ES\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/{companyUuid}/update-data/": { + "patch": { + "summary": "Update registry-extracted company data on a Business Verification (KYB) session", + "description": "Correct the registry-extracted company data on a Business Verification (KYB) session — company name, registration number, addresses, status, contact details — identified by `companyUuid`. Typical callers are a compliance analyst fixing a registry misread, or your back office syncing corrected company data. Every field in the body is optional (`PATCH` semantics); an empty JSON body (`{}`) is a no-op that simply echoes the current company record.\n\nEdits are written in two places at once: fields with a canonical company column (name, registration number, country, incorporation date, tax number, type, statuses, capital fields (`registered_capital`, `registered_capital_amount`, `registered_capital_currency`), alternative names, nature of business, website, email, phone, LEI, location, and `legal_address` → `registered_address`) update that column, and **every** submitted key is also merged into the `registry_data` snapshot (dates as ISO strings) so the \"extracted from registry\" view reflects the correction. Fields without a column — `region`, `registration_date`, `dissolution_date`, `control_scheme` — land in `registry_data` only. `user_provided_data` (what the end user confirmed in the hosted flow) is never touched, and `last_console_edit_at` is stamped on every non-empty PATCH.\n\nUnlike the KYC/POA update endpoints, there is **no session-status gate**: the company can be edited at any point in the business session's lifecycle. The company must belong to the session in the path (`404` with `\"KYB company does not belong to the given session.\"` otherwise) and to your application. Every non-empty PATCH fires the `data.updated` [webhook](/integration/webhooks) on the business session (trigger `console_registry_edit`) — even when the submitted values equal the stored ones. The session's status and decision are **not** re-evaluated, and no review/audit entry is recorded.\n\nAccepts an API key or a console user token; either way the credential needs the `write:businesses` permission. Authentication and permission failures both return `403` — this API never responds `401`. Note that the session lookup runs before credential checks, so an unknown `sessionId` returns `404` even with missing credentials. The shared write budget of 300 requests per minute per API key applies.\n\nFor User Verification sessions, the KYC counterpart is `PATCH /v3/session/{sessionId}/update-data/`; to change the session decision itself use `PATCH /v3/session/{sessionId}/update-status/`.", + "operationId": "patch_v3_session_kyb_company_update_data", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_kyb_company_data", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the parent Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "companyUuid", + "in": "path", + "required": true, + "description": "UUID of the company record to update (`registry_checks[].company.uuid` on the business session decision). Must belong to the session referenced by `sessionId`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "88888888-9999-aaaa-bbbb-cccccccccccc" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "All fields optional — send only what you want to change. Provided fields update the canonical company columns (where one exists) and are merged into the `registry_data` snapshot.", + "properties": { + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Legal name of the company. Written to the canonical column and the registry snapshot.", + "example": "Docs Probe Corp" + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Official registration number with the company registry.", + "example": "DPC-001" + }, + "country_code": { + "type": "string", + "maxLength": 2, + "description": "Country of incorporation, ISO 3166-1 alpha-2. Case-insensitive (`us` is stored as `US`). Alpha-3 codes fail the 2-character limit with `400`.", + "example": "US" + }, + "region": { + "type": "string", + "maxLength": 10, + "nullable": true, + "description": "Subdivision / region tag where the registry has one (e.g. US state). Stored in the registry snapshot only — it has no canonical column." + }, + "incorporation_date": { + "type": "string", + "format": "date", + "description": "Incorporation date in `YYYY-MM-DD`. Optional like every other field — the `PATCH` is partial. Any other date format returns `400`.", + "example": "2015-04-12" + }, + "registration_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Registration date where the registry distinguishes it from incorporation. Stored in the registry snapshot only." + }, + "dissolution_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Dissolution date for companies that are no longer active. Stored in the registry snapshot only." + }, + "tax_number": { + "type": "string", + "maxLength": 100, + "description": "Tax identification number." + }, + "legal_address": { + "type": "string", + "description": "Legal / registered address. Aliased server-side to the canonical `registered_address` column — the response returns it under `registered_address`, while the registry snapshot keeps the `legal_address` key you sent.", + "example": "1 Example Plaza, Wilmington, DE 19801" + }, + "company_type": { + "type": "string", + "maxLength": 100, + "description": "Legal entity type (e.g. `Limited Liability Company`, `PLC`, `GmbH`).", + "example": "Limited Liability Company" + }, + "registry_status": { + "type": "string", + "enum": [ + "active", + "dissolved", + "deregistered", + "see full details", + "authorised", + "appointed representative", + "unauthorised", + "inactive", + "no longer authorised", + "closed", + "struck off" + ], + "nullable": true, + "description": "Registry-reported company status. Lowercase, some values contain spaces (e.g. `struck off`, `no longer authorised`). Any other value returns `400`.", + "example": "active" + }, + "verification_status": { + "type": "string", + "enum": [ + "verified", + "failed", + "unknown" + ], + "nullable": true, + "description": "Outcome of the registry verification. Any other value returns `400`.", + "example": "verified" + }, + "alternative_names": { + "type": "string", + "description": "Trade names, former legal names, DBA names (free text)." + }, + "nature_of_business": { + "type": "string", + "description": "Short description of the company's activity.", + "example": "Software development" + }, + "registered_capital": { + "type": "string", + "maxLength": 100, + "description": "Human-readable registered capital (e.g. `USD 50000`). Use `registered_capital_amount` + `registered_capital_currency` for structured values, or this field for free-form text.", + "example": "USD 50000" + }, + "registered_capital_amount": { + "type": "number", + "format": "double", + "nullable": true, + "description": "Numeric registered-capital amount (up to 20 digits, 2 decimal places). Sets the `registered_capital_amount` column and is mirrored into `registry_data` as a decimal string; the response echoes it as a decimal string (e.g. `\"1500000.50\"`)." + }, + "registered_capital_currency": { + "type": "string", + "maxLength": 3, + "nullable": true, + "description": "ISO 4217 currency code for `registered_capital_amount`. Case-insensitive (`usd` is stored as `USD`); values that are not exactly 3 letters return `400` with the ISO 4217 message. Sets the canonical `registered_capital_currency` column and is mirrored into `registry_data`.", + "example": "USD" + }, + "website": { + "type": "string", + "maxLength": 500, + "description": "Company website. Bare domains are accepted and normalized with an `https://` prefix (`docsprobe.example.com` is stored as `https://docsprobe.example.com`); values that still fail URL validation return `400`.", + "example": "https://docsprobe.example.com" + }, + "email": { + "type": "string", + "format": "email", + "description": "Contact email on file with the registry.", + "example": "legal@docsprobe.example.com" + }, + "phone": { + "type": "string", + "maxLength": 50, + "description": "Contact phone. Must match `+`, digits, spaces, dashes, dots, or parentheses, with 7-15 digits total — otherwise `400`.", + "example": "+1 302 555 0143" + }, + "legal_entity_identifier": { + "type": "string", + "maxLength": 50, + "description": "LEI, where the company has one." + }, + "location_of_registration": { + "type": "string", + "maxLength": 255, + "description": "City / subdivision of the registry of record." + }, + "vat_number": { + "type": "string", + "maxLength": 64, + "description": "EU VAT number. Merged into the `registry_data` snapshot only — editing it here does not re-run VAT and tax ID validation, so `vat_validation_status` and the other `vat_*` result fields are unchanged." + }, + "control_scheme": { + "type": "string", + "description": "Control / governance scheme when the registry exposes it. Stored in the registry snapshot only." + } + } + }, + "examples": { + "Correct registry data": { + "summary": "Fix the company's registry-extracted fields", + "value": { + "company_name": "Docs Probe Corp", + "registration_number": "DPC-001", + "country_code": "US", + "incorporation_date": "2015-04-12", + "company_type": "Limited Liability Company", + "legal_address": "1 Example Plaza, Wilmington, DE 19801", + "registry_status": "active", + "verification_status": "verified", + "website": "docsprobe.example.com", + "email": "legal@docsprobe.example.com", + "phone": "+1 302 555 0143", + "registered_capital": "USD 50000", + "nature_of_business": "Software development" + } + }, + "Single field": { + "summary": "Only the keys you send are written", + "value": { + "registry_status": "dissolved" + } + }, + "No-op": { + "summary": "Empty body echoes the current record without firing the webhook", + "value": {} + } + } + } + } + }, + "responses": { + "200": { + "description": "Company data updated (or echoed unchanged for an empty body). Returns the full KYB company record — the same shape as `registry_checks[].company` on the business session decision. Note how `legal_address` comes back as `registered_address`. `registered_capital_amount`/`registered_capital_currency` populate their canonical columns and are mirrored into `registry_data`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Full KYB company record — the same shape as the `company` object inside `registry_checks[]` on the business session decision.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Company record identifier (the `companyUuid` path parameter)." + }, + "node_id": { + "type": "string", + "nullable": true, + "description": "Workflow graph node this company record belongs to, when the workflow is graph-based." + }, + "status": { + "type": "string", + "description": "Feature lifecycle status of the registry check (e.g. `Not Finished`, `Approved`, `Declined`, `In Review`)." + }, + "registry_status": { + "type": "string", + "nullable": true, + "description": "Registry-reported company status (e.g. `active`, `dissolved`, `struck off`)." + }, + "data_resolved": { + "type": "boolean", + "description": "`true` when the company data is complete — always `true` for manual-entry companies; for registry-sourced companies, `true` once the registry fetch resolved." + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of incorporation." + }, + "region": { + "type": "string", + "nullable": true + }, + "company_type": { + "type": "string", + "nullable": true + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "registered_address": { + "type": "string", + "nullable": true, + "description": "Canonical registered address — updated when you send `legal_address`." + }, + "tax_number": { + "type": "string", + "nullable": true + }, + "risk_level": { + "type": "string", + "nullable": true + }, + "verification_status": { + "type": "string", + "nullable": true, + "description": "`verified`, `failed`, or `unknown`." + }, + "is_from_registry": { + "type": "boolean", + "description": "`true` when the company was pre-filled from a registry search rather than entered manually." + }, + "fetch_status": { + "type": "string", + "nullable": true, + "description": "Registry fetch lifecycle for registry-sourced companies (`pending` / `resolved`); `null` for manual entries." + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "nullable": true, + "description": "Canonical column. Returned as a decimal string (e.g. `\"2750000.00\"`)." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "Canonical column. Set by the registry flow or by console/API edits via this endpoint." + }, + "website": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "phone": { + "type": "string", + "nullable": true + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true + }, + "location_of_registration": { + "type": "string", + "nullable": true + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true, + "additionalProperties": true + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Directors / officers attached to the company, including per-person KYC progress." + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Beneficial owners attached to the company, including per-person KYC progress." + }, + "addresses": { + "type": "array", + "nullable": true, + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Registry address detail, when the registry returned any." + }, + "industries": { + "type": "array", + "nullable": true, + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Registry industry classification, when available." + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "$ref": "#/components/schemas/KYBRegistryData" + }, + "user_provided_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "What the end user typed during the hosted registry-confirmation step. Never modified by this endpoint." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the end user confirmed the company data in the hosted flow." + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Stamped on every successful non-empty PATCH, so UIs can surface \"edited by operator\" context." + }, + "is_editable": { + "type": "boolean", + "description": "`true` while the end user can still edit the registry-sourced data in the hosted flow (registry-sourced and not yet confirmed)." + } + } + }, + "examples": { + "Updated company": { + "summary": "Full company record after the correction", + "value": { + "uuid": "6b9479f5-6769-46c8-baf1-df63bbbc5f8c", + "node_id": null, + "status": "Not Finished", + "registry_status": "active", + "data_resolved": true, + "company_name": "Docs Probe Corp", + "registration_number": "DPC-001", + "country_code": "US", + "region": null, + "company_type": "Limited Liability Company", + "incorporation_date": "2015-04-12", + "registered_address": "1 Example Plaza, Wilmington, DE 19801", + "tax_number": null, + "risk_level": null, + "verification_status": "verified", + "is_from_registry": false, + "fetch_status": null, + "alternative_names": null, + "nature_of_business": "Software development", + "registered_capital": "USD 50000", + "registered_capital_amount": null, + "registered_capital_currency": "USD", + "website": "https://docsprobe.example.com", + "email": "legal@docsprobe.example.com", + "phone": "+1 302 555 0143", + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": null, + "officers": [], + "beneficial_owners": [], + "addresses": null, + "industries": null, + "accounts": null, + "registry_data": { + "company_name": "Docs Probe Corp", + "registration_number": "DPC-001", + "country_code": "US", + "incorporation_date": "2015-04-12", + "legal_address": "1 Example Plaza, Wilmington, DE 19801", + "company_type": "Limited Liability Company", + "registry_status": "active", + "verification_status": "verified", + "nature_of_business": "Software development", + "registered_capital": "USD 50000", + "registered_capital_currency": "USD", + "website": "https://docsprobe.example.com", + "email": "legal@docsprobe.example.com", + "phone": "+1 302 555 0143" + }, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": "2026-06-12T00:02:34.276061Z", + "is_editable": false + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field errors arrive as `{\"\": [\"\"]}`. Max-length checks run before format checks, so `country_code: \"USA\"` reports the 2-character limit rather than the ISO message.", + "content": { + "application/json": { + "examples": { + "Alpha-3 country code": { + "summary": "country_code must be alpha-2 — length check fires first", + "value": { + "country_code": [ + "Ensure this field has no more than 2 characters." + ] + } + }, + "Non-alphabetic country code": { + "summary": "2 characters but not letters", + "value": { + "country_code": [ + "country_code must be a 2-letter ISO 3166-1 alpha-2 code." + ] + } + }, + "Invalid currency": { + "summary": "3 characters but not an ISO 4217 code", + "value": { + "registered_capital_currency": [ + "Currency must be a 3-letter ISO 4217 code (e.g. USD, EUR, GBP)." + ] + } + }, + "Invalid phone": { + "summary": "Phone fails the format / digit-count check", + "value": { + "phone": [ + "Invalid phone number format." + ] + } + }, + "Invalid registry status": { + "summary": "registry_status not in the enum", + "value": { + "registry_status": [ + "\"liquidated\" is not a valid choice." + ] + } + }, + "Invalid email": { + "summary": "Malformed email address", + "value": { + "email": [ + "Enter a valid email address." + ] + } + }, + "Invalid website": { + "summary": "Value fails URL validation even after https:// normalization", + "value": { + "website": [ + "Enter a valid URL." + ] + } + }, + "Bad date format": { + "summary": "Dates must be YYYY-MM-DD", + "value": { + "incorporation_date": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Credential lacks write:businesses", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown session, unknown company, or a company that belongs to a different session. Each case has a distinct `detail` message (see examples). The unknown-session lookup runs before credential checks.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "summary": "No session with this ID in your application (or it was deleted)", + "value": { + "detail": "Not found." + } + }, + "Unknown company": { + "summary": "No company with this UUID in your application", + "value": { + "detail": "No KYBCompany matches the given query." + } + }, + "Company / session mismatch": { + "summary": "companyUuid exists but belongs to another session", + "value": { + "detail": "KYB company does not belong to the given session." + } + } + } + } + } + }, + "429": { + "description": "Shared write rate limit exceeded (300 POST/PATCH/DELETE requests per minute per API key). Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "Write budget exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/33333333-4444-5555-6666-777777777777/kyb/88888888-9999-aaaa-bbbb-cccccccccccc/update-data/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"company_name\": \"Docs Probe Corp\",\n \"registration_number\": \"DPC-001\",\n \"country_code\": \"US\",\n \"incorporation_date\": \"2015-04-12\",\n \"company_type\": \"Limited Liability Company\",\n \"legal_address\": \"1 Example Plaza, Wilmington, DE 19801\",\n \"registry_status\": \"active\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_kyb_company_data(\n \"33333333-4444-5555-6666-777777777777\",\n \"88888888-9999-aaaa-bbbb-cccccccccccc\",\n company_name=\"Docs Probe Corp\",\n registration_number=\"DPC-001\",\n country_code=\"US\",\n incorporation_date=\"2015-04-12\",\n legal_address=\"1 Example Plaza, Wilmington, DE 19801\",\n company_type=\"Limited Liability Company\",\n registry_status=\"active\",\n verification_status=\"verified\",\n nature_of_business=\"Software development\",\n registered_capital=\"USD 50000\",\n website=\"docsprobe.example.com\",\n email=\"legal@docsprobe.example.com\",\n phone=\"+1 302 555 0143\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateKybCompanyData(\n \"33333333-4444-5555-6666-777777777777\",\n \"88888888-9999-aaaa-bbbb-cccccccccccc\",\n {\n company_name: \"Docs Probe Corp\",\n registration_number: \"DPC-001\",\n country_code: \"US\",\n incorporation_date: \"2015-04-12\",\n legal_address: \"1 Example Plaza, Wilmington, DE 19801\",\n company_type: \"Limited Liability Company\",\n registry_status: \"active\",\n verification_status: \"verified\",\n nature_of_business: \"Software development\",\n registered_capital: \"USD 50000\",\n website: \"docsprobe.example.com\",\n email: \"legal@docsprobe.example.com\",\n phone: \"+1 302 555 0143\",\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateKYBCompanyData(ctx, \"33333333-4444-5555-6666-777777777777\", \"88888888-9999-aaaa-bbbb-cccccccccccc\", didit.SessionsUpdateKYBCompanyDataParams{\n\tCompanyName: didit.String(\"Docs Probe Corp\"),\n\tRegistrationNumber: didit.String(\"DPC-001\"),\n\tCountryCode: didit.String(\"US\"),\n\tIncorporationDate: didit.String(\"2015-04-12\"),\n\tLegalAddress: didit.String(\"1 Example Plaza, Wilmington, DE 19801\"),\n\tCompanyType: didit.String(\"Limited Liability Company\"),\n\tRegistryStatus: didit.Some(didit.SessionsUpdateKYBCompanyDataParamsRegistryStatus(\"active\")),\n\tVerificationStatus: didit.Some(didit.SessionsUpdateKYBCompanyDataParamsVerificationStatus(\"verified\")),\n\tNatureOfBusiness: didit.String(\"Software development\"),\n\tRegisteredCapital: didit.String(\"USD 50000\"),\n\tWebsite: didit.String(\"docsprobe.example.com\"),\n\tEmail: didit.String(\"legal@docsprobe.example.com\"),\n\tPhone: didit.String(\"+1 302 555 0143\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport me.didit.Didit;\nimport me.didit.types.SessionsUpdateKybCompanyDataParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateKybCompanyData(\"33333333-4444-5555-6666-777777777777\", \"88888888-9999-aaaa-bbbb-cccccccccccc\", SessionsUpdateKybCompanyDataParams.builder()\n .companyName(\"Docs Probe Corp\")\n .registrationNumber(\"DPC-001\")\n .countryCode(\"US\")\n .incorporationDate(LocalDate.parse(\"2015-04-12\"))\n .legalAddress(\"1 Example Plaza, Wilmington, DE 19801\")\n .companyType(\"Limited Liability Company\")\n .registryStatus(SessionsUpdateKybCompanyDataParams.RegistryStatus.of(\"active\"))\n .verificationStatus(SessionsUpdateKybCompanyDataParams.VerificationStatus.of(\"verified\"))\n .natureOfBusiness(\"Software development\")\n .registeredCapital(\"USD 50000\")\n .website(\"docsprobe.example.com\")\n .email(\"legal@docsprobe.example.com\")\n .phone(\"+1 302 555 0143\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateKybCompanyData('33333333-4444-5555-6666-777777777777', '88888888-9999-aaaa-bbbb-cccccccccccc', [\n 'company_name' => 'Docs Probe Corp',\n 'registration_number' => 'DPC-001',\n 'country_code' => 'US',\n 'incorporation_date' => '2015-04-12',\n 'legal_address' => '1 Example Plaza, Wilmington, DE 19801',\n 'company_type' => 'Limited Liability Company',\n 'registry_status' => 'active',\n 'verification_status' => 'verified',\n 'nature_of_business' => 'Software development',\n 'registered_capital' => 'USD 50000',\n 'website' => 'docsprobe.example.com',\n 'email' => 'legal@docsprobe.example.com',\n 'phone' => '+1 302 555 0143',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_kyb_company_data(\n \"33333333-4444-5555-6666-777777777777\",\n \"88888888-9999-aaaa-bbbb-cccccccccccc\",\n company_name: \"Docs Probe Corp\",\n registration_number: \"DPC-001\",\n country_code: \"US\",\n incorporation_date: \"2015-04-12\",\n legal_address: \"1 Example Plaza, Wilmington, DE 19801\",\n company_type: \"Limited Liability Company\",\n registry_status: \"active\",\n verification_status: \"verified\",\n nature_of_business: \"Software development\",\n registered_capital: \"USD 50000\",\n website: \"docsprobe.example.com\",\n email: \"legal@docsprobe.example.com\",\n phone: \"+1 302 555 0143\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateKybCompanyDataAsync(\"33333333-4444-5555-6666-777777777777\", \"88888888-9999-aaaa-bbbb-cccccccccccc\", new SessionsUpdateKybCompanyDataParams\n{\n CompanyName = \"Docs Probe Corp\",\n RegistrationNumber = \"DPC-001\",\n CountryCode = \"US\",\n IncorporationDate = \"2015-04-12\",\n LegalAddress = \"1 Example Plaza, Wilmington, DE 19801\",\n CompanyType = \"Limited Liability Company\",\n RegistryStatus = new SessionsUpdateKybCompanyDataParamsRegistryStatus(\"active\"),\n VerificationStatus = new SessionsUpdateKybCompanyDataParamsVerificationStatus(\"verified\"),\n NatureOfBusiness = \"Software development\",\n RegisteredCapital = \"USD 50000\",\n Website = \"docsprobe.example.com\",\n Email = \"legal@docsprobe.example.com\",\n Phone = \"+1 302 555 0143\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/parties/{partyUuid}/": { + "patch": { + "summary": "Skip (or re-require) a Key People party on a Business Verification (KYB) session", + "description": "Exempt a Key People party from verification, or put it back in scope. This is the operator escape hatch for a party that can never finish its own check - most often because the individual KYC session handed to a UBO or director was deleted, which freezes that child's status and leaves the KYB parked in `Awaiting User` with nothing left that could move it.\n\nSetting `is_skipped: true` drops the party out of the Key People aggregation and immediately recomputes the `KYBKeyPeopleCheck` and the parent session status, so the business session can settle and then be approved or declined. Sending `is_skipped: false` puts the party back in scope and the check goes back to waiting on it.\n\nUnlike the applicant-facing skip in the hosted Key People screen, this endpoint is **not** gated by the role's `allow_skip` setting - that flag limits what the applicant may decide, not what your analyst may override. `requires_verification` is deliberately left untouched, so an overridden party reads as `requires_verification: true` **and** `is_skipped: true` and the record still shows what the rules asked for.\n\nThe party must belong to the session in the path (`404` with `\"Party does not belong to the given session.\"` otherwise) and to your application. A PATCH that actually changes the flag fires the `data.updated` [webhook](/integration/webhooks) on the business session with trigger `console_key_people_skip`; a PATCH that sets the flag to its current value is a no-op that echoes the party back.\n\nAccepts an API key or a console user token; either way the credential needs the `write:businesses` permission.", + "operationId": "patch_v3_session_kyb_party_update", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_kyb_party", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the parent Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "partyUuid", + "in": "path", + "required": true, + "description": "UUID of the Key People party to update (`key_people_checks[].submitted.parties[].uuid` on the business session decision). Must belong to the session referenced by `sessionId`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_skipped" + ], + "properties": { + "is_skipped": { + "type": "boolean", + "description": "`true` exempts the party from verification so the Key People check can settle without it; `false` puts it back in scope.", + "example": true + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Party updated (or echoed unchanged when the flag already had this value). Returns the party - the same shape as an entry of `key_people_checks[].submitted.parties[]` on the business session decision.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Key People party record - the same shape as an entry of `key_people_checks[].submitted.parties[]` on the business session decision.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Party identifier." + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "name": { + "type": "string", + "description": "Display name of the party." + }, + "is_skipped": { + "type": "boolean", + "description": "Whether the party is exempt from verification." + }, + "requires_verification": { + "type": "boolean", + "description": "What the role and threshold rules asked for. Left untouched by this endpoint." + }, + "kyc_session_status": { + "type": "string", + "nullable": true, + "description": "Status of the linked individual KYC session, frozen at deletion time when the session was deleted." + }, + "kyc_session_url": { + "type": "string", + "nullable": true, + "description": "Link to the linked individual KYC session. `null` when there is none or when it was deleted." + }, + "kyc_session_deleted": { + "type": "boolean", + "description": "The linked individual KYC session was deleted, so it can never settle." + }, + "kyb_sub_session_deleted": { + "type": "boolean", + "description": "Corporate-party counterpart of `kyc_session_deleted`." + } + } + } + } + } + }, + "400": { + "description": "Validation error on the request body. `is_skipped` is the only field this endpoint reads, and it is required. Field errors arrive as `{\"\": [\"\"]}`. Body validation runs *after* the party lookup, so a malformed `is_skipped` sent to a party that does not exist reports `404`, not `400`.", + "content": { + "application/json": { + "examples": { + "Missing is_skipped": { + "summary": "Empty body, or a body that omits `is_skipped`", + "value": { + "is_skipped": [ + "This field is required." + ] + } + }, + "Non-boolean is_skipped": { + "summary": "Value is not a boolean — `\"maybe\"`, `2`, `[]`. Booleans plus `\"true\"`/`\"false\"`, `1`/`0` and `\"yes\"`/`\"no\"` are all accepted", + "value": { + "is_skipped": [ + "Must be a valid boolean." + ] + } + }, + "Null is_skipped": { + "summary": "`is_skipped` is present but `null`", + "value": { + "is_skipped": [ + "This field may not be null." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Credential lacks write:businesses", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown party, or a party that belongs to a different session. Each case has a distinct `detail` message (see examples). Credential and permission checks run before the party lookup, so an unauthorised call reports `403` rather than `404`. There is no separate session lookup: an unknown `sessionId` paired with a party of yours reports the mismatch message.", + "content": { + "application/json": { + "examples": { + "Unknown party": { + "summary": "No party with this UUID in your application", + "value": { + "detail": "Not found." + } + }, + "Party / session mismatch": { + "summary": "partyUuid exists but hangs off another session", + "value": { + "detail": "Party does not belong to the given session." + } + } + } + } + } + }, + "429": { + "description": "Shared write rate limit exceeded (300 POST/PATCH/DELETE requests per minute per API key). Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "Write budget exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_kyb_party(\n \"33333333-4444-5555-6666-777777777777\",\n \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\",\n is_skipped=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateKybParty(\n \"33333333-4444-5555-6666-777777777777\",\n \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\",\n {\n is_skipped: true,\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateKYBParty(ctx, \"33333333-4444-5555-6666-777777777777\", \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\", didit.SessionsUpdateKYBPartyParams{\n\tIsSkipped: true,\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsUpdateKybPartyParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateKybParty(\"33333333-4444-5555-6666-777777777777\", \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\", SessionsUpdateKybPartyParams.builder()\n .isSkipped(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateKybParty('33333333-4444-5555-6666-777777777777', '0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41', [\n 'is_skipped' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_kyb_party(\n \"33333333-4444-5555-6666-777777777777\",\n \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\",\n is_skipped: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateKybPartyAsync(\"33333333-4444-5555-6666-777777777777\", \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\", new SessionsUpdateKybPartyParams\n{\n IsSkipped = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/travel-rule/settings/": { + "get": { + "summary": "Get Travel Rule settings", + "description": "Read the application's Travel Rule settings and VASP profile. Auto-creates a default (disabled) settings row and an empty profile on first read.", + "operationId": "getTravelRuleSettings", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_settings", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/settings/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.retrieve_settings()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.retrieveSettings();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.RetrieveSettings(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().retrieveSettings();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->retrieveSettings();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.retrieve_settings\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.RetrieveSettingsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Current Travel Rule settings and VASP profile.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleSettingsDetail" + }, + "examples": { + "Disabled (default)": { + "value": { + "is_enabled": false, + "jurisdiction": "EU", + "name_matching_strictness": "DEFAULT", + "confirmation_timeout_hours": 48, + "timeout_outcome": "HOLD", + "threshold_amount": "0.00", + "inbound_auto_accept": true, + "allow_self_declaration": true, + "allow_screenshot_proof": true, + "auto_wallet_verification": true, + "vasp_attribution_enabled": true, + "legal_name": "", + "lei": "", + "compliance_email": "", + "is_discoverable": true, + "counterparty_screening_enabled": false, + "sunrise_policy": "", + "sunrise_jurisdictions": [ + "CO", + "KH", + "VN" + ], + "non_responsive_policy": "", + "travel_address": "", + "disabled_networks": [], + "networks": { + "gtr": { + "rail": "GTR", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "trust": { + "rail": "TRUST", + "mode": "unavailable", + "state": "unavailable", + "reason": "own_membership_only", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "verifyvasp": { + "rail": "VERIFYVASP", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "sygna": { + "rail": "SYGNA", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + } + }, + "didit_network": { + "listed": false, + "missing": [ + "travel_rule_off", + "legal_name_missing", + "compliance_email_missing" + ], + "compliance_email_confirmed": false + } + } + } + } + } + } + } + } + }, + "put": { + "summary": "Update Travel Rule settings", + "description": "Upsert the application's Travel Rule settings and VASP profile. Partial update — only send the fields you want to change. Setting `is_enabled: true` requires a non-blank `legal_name` (already stored or included in the same request), otherwise returns 400.", + "operationId": "updateTravelRuleSettings", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "update_settings", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleSettingsUpdate" + }, + "example": { + "is_enabled": true, + "legal_name": "Origin CASP SL", + "jurisdiction": "EU", + "name_matching_strictness": "DEFAULT", + "confirmation_timeout_hours": 48, + "timeout_outcome": "HOLD", + "is_discoverable": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PUT https://verification.didit.me/v3/travel-rule/settings/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"is_enabled\": true, \"legal_name\": \"Origin CASP SL\", \"jurisdiction\": \"EU\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.update_settings(\n is_enabled=True,\n legal_name=\"Origin CASP SL\",\n jurisdiction=\"EU\",\n name_matching_strictness=\"DEFAULT\",\n confirmation_timeout_hours=48,\n timeout_outcome=\"HOLD\",\n is_discoverable=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.updateSettings({\n is_enabled: true,\n legal_name: \"Origin CASP SL\",\n jurisdiction: \"EU\",\n name_matching_strictness: \"DEFAULT\",\n confirmation_timeout_hours: 48,\n timeout_outcome: \"HOLD\",\n is_discoverable: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.UpdateSettings(ctx, didit.TravelRuleSettingsUpdate{\n\tIsEnabled: didit.Bool(true),\n\tLegalName: didit.String(\"Origin CASP SL\"),\n\tJurisdiction: didit.String(\"EU\"),\n\tNameMatchingStrictness: didit.Some(didit.TravelRuleSettingsUpdateNameMatchingStrictness(\"DEFAULT\")),\n\tConfirmationTimeoutHours: didit.Int(48),\n\tTimeoutOutcome: didit.Some(didit.TravelRuleSettingsUpdateTimeoutOutcome(\"HOLD\")),\n\tIsDiscoverable: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleSettingsUpdate;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().updateSettings(TravelRuleSettingsUpdate.builder()\n .isEnabled(true)\n .legalName(\"Origin CASP SL\")\n .jurisdiction(\"EU\")\n .nameMatchingStrictness(TravelRuleSettingsUpdate.NameMatchingStrictness.of(\"DEFAULT\"))\n .confirmationTimeoutHours(48L)\n .timeoutOutcome(TravelRuleSettingsUpdate.TimeoutOutcome.of(\"HOLD\"))\n .isDiscoverable(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->updateSettings([\n 'is_enabled' => true,\n 'legal_name' => 'Origin CASP SL',\n 'jurisdiction' => 'EU',\n 'name_matching_strictness' => 'DEFAULT',\n 'confirmation_timeout_hours' => 48,\n 'timeout_outcome' => 'HOLD',\n 'is_discoverable' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.update_settings(\n is_enabled: true,\n legal_name: \"Origin CASP SL\",\n jurisdiction: \"EU\",\n name_matching_strictness: \"DEFAULT\",\n confirmation_timeout_hours: 48,\n timeout_outcome: \"HOLD\",\n is_discoverable: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.UpdateSettingsAsync(new TravelRuleSettingsUpdate\n{\n IsEnabled = true,\n LegalName = \"Origin CASP SL\",\n Jurisdiction = \"EU\",\n NameMatchingStrictness = new TravelRuleSettingsUpdateNameMatchingStrictness(\"DEFAULT\"),\n ConfirmationTimeoutHours = 48,\n TimeoutOutcome = new TravelRuleSettingsUpdateTimeoutOutcome(\"HOLD\"),\n IsDiscoverable = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated settings and profile, in the same shape as GET.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleSettingsDetail" + } + } + } + }, + "400": { + "description": "Validation error, e.g. enabling without a legal_name, a sunrise_policy or non_responsive_policy other than HOLD, REJECT, PROCEED or empty, an unknown field in `matching_rules`, or a `data_sharing_fields` choice that cannot meet the originator obligations of your jurisdiction's regime (the errors are keyed by entity type under `data_sharing_fields`).", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "legal_name": [ + "legal_name is required to enable travel rule." + ] + } + } + } + } + } + } + }, + "/v3/travel-rule/settings/compliance-email/confirmation/": { + "post": { + "summary": "Send the compliance email confirmation", + "description": "Emails a single-use confirmation link to your VASP profile's compliance_email. Opening the link and pressing its confirm button confirms the address, which the Didit network needs before it lists your profile. A new request replaces the previous link; the link stops working when the compliance email changes. One email per minute per address.", + "operationId": "sendTravelRuleComplianceEmailConfirmation", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "send_compliance_email_confirmation", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/settings/compliance-email/confirmation/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.send_compliance_email_confirmation()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.sendComplianceEmailConfirmation();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.SendComplianceEmailConfirmation(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().sendComplianceEmailConfirmation();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->sendComplianceEmailConfirmation();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.send_compliance_email_confirmation\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.SendComplianceEmailConfirmationAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "202": { + "description": "The confirmation email was sent.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sent_to": { + "type": "string", + "format": "email" + } + } + }, + "example": { + "sent_to": "compliance@your-vasp.example" + } + } + } + }, + "400": { + "description": "No compliance email is set, or it is already confirmed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "No compliance email": { + "value": { + "compliance_email": [ + "Set a compliance email before confirming it." + ] + } + }, + "Already confirmed": { + "value": { + "compliance_email": [ + "This compliance email is already confirmed." + ] + } + } + } + } + } + }, + "429": { + "description": "A confirmation went to this address less than a minute ago." + }, + "503": { + "description": "The email could not be sent. Nothing changed; try again in a moment." + } + } + } + }, + "/v3/travel-rule/wallet-addresses/": { + "get": { + "summary": "List wallet address book entries", + "description": "Paginated list of your application's Travel Rule wallet address book, newest first.", + "operationId": "listTravelRuleWalletAddresses", + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/wallet-addresses/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.travel_rule.wallet_addresses.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.travelRule.walletAddresses.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.TravelRule.WalletAddresses.ListAutoPaging(ctx, didit.TravelRuleWalletAddressesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.travelRule().walletAddresses().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletAddresses->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.wallet_addresses.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.TravelRule.WalletAddresses.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated wallet address book entries.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WalletAddressEntry" + } + } + } + } + } + } + } + } + }, + "post": { + "summary": "Register a wallet address", + "description": "Add an entry to your Travel Rule wallet address book so inbound INTERNAL-rail transfers can resolve against it.", + "operationId": "createTravelRuleWalletAddress", + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressEntryCreate" + }, + "example": { + "address": "0xBeneficiaryWallet01", + "chain": "ETH", + "holder_name": "Ana Diaz", + "holder_vendor_data": "user-042", + "self_declared": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/wallet-addresses/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"address\": \"0xBeneficiaryWallet01\", \"chain\": \"ETH\", \"holder_name\": \"Ana Diaz\", \"self_declared\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.wallet_addresses.create(\n address=\"0xBeneficiaryWallet01\",\n chain=\"ETH\",\n holder_vendor_data=\"user-042\",\n holder_name=\"Ana Diaz\",\n self_declared=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.walletAddresses.create({\n address: \"0xBeneficiaryWallet01\",\n chain: \"ETH\",\n holder_vendor_data: \"user-042\",\n holder_name: \"Ana Diaz\",\n self_declared: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.WalletAddresses.Create(ctx, didit.WalletAddressEntryCreate{\n\tAddress: \"0xBeneficiaryWallet01\",\n\tChain: didit.String(\"ETH\"),\n\tHolderVendorData: didit.String(\"user-042\"),\n\tHolderName: didit.String(\"Ana Diaz\"),\n\tSelfDeclared: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.WalletAddressEntryCreate;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().walletAddresses().create(WalletAddressEntryCreate.builder()\n .address(\"0xBeneficiaryWallet01\")\n .chain(\"ETH\")\n .holderVendorData(\"user-042\")\n .holderName(\"Ana Diaz\")\n .selfDeclared(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletAddresses->create([\n 'address' => '0xBeneficiaryWallet01',\n 'chain' => 'ETH',\n 'holder_vendor_data' => 'user-042',\n 'holder_name' => 'Ana Diaz',\n 'self_declared' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.wallet_addresses.create(\n address: \"0xBeneficiaryWallet01\",\n chain: \"ETH\",\n holder_vendor_data: \"user-042\",\n holder_name: \"Ana Diaz\",\n self_declared: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.WalletAddresses.CreateAsync(new WalletAddressEntryCreate\n{\n Address = \"0xBeneficiaryWallet01\",\n Chain = \"ETH\",\n HolderVendorData = \"user-042\",\n HolderName = \"Ana Diaz\",\n SelfDeclared = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The created entry.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressEntry" + } + } + } + }, + "400": { + "description": "Duplicate wallet (address, chain and memo) for this application, a memo or X-address the chain cannot carry, a missing `address`, an invalid `travel_address`, `self_declared: true` while `allow_self_declaration` is off, or `self_declared: true` while `ownership_validation_mode` is `FULL`.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "examples": { + "Duplicate entry": { + "value": { + "address": [ + "A wallet address entry with this address and chain already exists." + ] + } + }, + "Invalid travel address": { + "value": { + "travel_address": [ + "Invalid travel address." + ] + } + }, + "Self-declaration disabled": { + "value": { + "self_declared": [ + "Self-declared ownership is disabled for this application." + ] + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/wallet-addresses/{entry_uuid}/": { + "patch": { + "summary": "Update wallet address holder metadata", + "description": "Partial update of holder_name, holder_vendor_data, entity_type, and travel_address. Setting travel_address attaches a TRP routing address to the entry; invalid values are rejected with 400. The response echoes only these four fields.", + "operationId": "updateTravelRuleWalletAddress", + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "entry_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressEntryUpdate" + }, + "example": { + "holder_name": "Ana Updated", + "travel_address": "ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH https://verification.didit.me/v3/travel-rule/wallet-addresses/{entry_uuid}/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"holder_name\": \"Ana Updated\", \"travel_address\": \"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.wallet_addresses.update(\n \"11111111-2222-3333-4444-555555555555\",\n holder_name=\"Ana Updated\",\n travel_address=\"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.walletAddresses.update(\"11111111-2222-3333-4444-555555555555\", {\n holder_name: \"Ana Updated\",\n travel_address: \"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.WalletAddresses.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.WalletAddressEntryUpdateParam{\n\tHolderName: didit.String(\"Ana Updated\"),\n\tTravelAddress: didit.String(\"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.WalletAddressEntryUpdate;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().walletAddresses().update(\"11111111-2222-3333-4444-555555555555\", WalletAddressEntryUpdate.builder()\n .holderName(\"Ana Updated\")\n .travelAddress(\"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletAddresses->update('11111111-2222-3333-4444-555555555555', [\n 'holder_name' => 'Ana Updated',\n 'travel_address' => 'ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.wallet_addresses.update(\n \"11111111-2222-3333-4444-555555555555\",\n holder_name: \"Ana Updated\",\n travel_address: \"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.WalletAddresses.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new WalletAddressEntryUpdate\n{\n HolderName = \"Ana Updated\",\n TravelAddress = \"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The updated fields.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressEntryUpdate" + } + } + } + }, + "400": { + "description": "`travel_address` is not a valid travel address.", + "content": { + "application/json": { + "examples": { + "Invalid travel address": { + "value": { + "travel_address": [ + "Invalid travel address." + ] + } + } + } + } + } + }, + "404": { + "description": "Unknown or deleted entry." + } + } + }, + "delete": { + "summary": "Delete a wallet address", + "description": "Deletes the entry together with its ownership proofs and the screenshots they stored. It stops matching new transfers immediately and the address is freed up for re-registration. Transfers that already used the wallet keep their own record of it and read the same. Deletion is irreversible.", + "operationId": "deleteTravelRuleWalletAddress", + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "entry_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE https://verification.didit.me/v3/travel-rule/wallet-addresses/{entry_uuid}/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.travel_rule.wallet_addresses.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.travelRule.walletAddresses.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.TravelRule.WalletAddresses.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.travelRule().walletAddresses().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletAddresses->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.wallet_addresses.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.TravelRule.WalletAddresses.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ], + "responses": { + "204": { + "description": "Deleted." + }, + "404": { + "description": "Unknown or already deleted entry." + }, + "503": { + "description": "`wallet_address_deletion_incomplete`: a stored file could not be deleted, so the deletion stopped before it finished. The address-book entry is still there; retry the same request to finish it.", + "content": { + "application/json": { + "example": { + "detail": "A stored file could not be deleted, so the deletion stopped before it finished. Try again.", + "code": "wallet_address_deletion_incomplete" + } + } + } + } + } + } + }, + "/v3/travel-rule/vasps/": { + "get": { + "summary": "Search the VASP directory", + "description": "Paginated, name-sorted search of the VASP directory: the shared directory entries, which Didit builds from the ESMA interim MiCA register, the public TRISA directory, the global LEI register and its own curation, plus the members of the Didit network. Each row carries the routes your application has to it today. Your own application's profile is never listed. Rows also carry the shared due-diligence assessment and your application's own decision, entity screening and questionnaires.", + "operationId": "listTravelRuleVasps", + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "search", + "in": "query", + "schema": { + "type": "string", + "maxLength": 255 + }, + "description": "A name or alias fragment (case-insensitive), an exact LEI, or a domain (example.com or a URL on it). Didit network members match on their legal name, LEI or the domain of their compliance email." + }, + { + "name": "network", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "TRISA" + ] + }, + "description": "Only directory entries a network's member directory currently lists." + }, + { + "name": "jurisdiction", + "in": "query", + "schema": { + "type": "string" + }, + "description": "ISO 3166 alpha-2 or alpha-3 country code. 400 for anything else." + }, + { + "name": "reachable", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "true: only VASPs your application has an available route to (Didit network members included). false: only directory entries it cannot reach today." + }, + { + "name": "kind", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "directory", + "didit" + ] + }, + "description": "Only directory entries, or only Didit network members." + }, + { + "name": "risk", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "description": "Only directory entries whose risk_level is this band. Didit network members, which are not scored, never match." + }, + { + "name": "responsiveness", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "RESPONSIVE", + "UNRESPONSIVE", + "UNKNOWN" + ] + }, + "description": "Only directory entries whose responsiveness.status is this value. Didit network members never match." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/travel-rule/vasps/?search=counterparty&reachable=true' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.travel_rule.list_vasps():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.travelRule.listVasps()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.TravelRule.ListVASPsAutoPaging(ctx, didit.TravelRuleListVASPsParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.travelRule().listVasps()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->listVasps() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.list_vasps.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.TravelRule.ListVaspsAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated, name-sorted VASP directory results.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VaspDirectoryEntry" + } + } + } + } + } + } + }, + "400": { + "description": "A filter is not valid: the body names each field, for example an unknown network, country code, risk band or responsiveness status.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "jurisdiction": [ + "Use an ISO 3166 alpha-2 or alpha-3 country code." + ], + "network": [ + "\"SWIFT\" is not a valid choice." + ] + } + } + } + } + }, + "x-didit-sdk": { + "resource": "travel_rule", + "method": "list_vasps", + "auth": "api_key", + "server": true + } + } + }, + "/v3/travel-rule/vasps/{vasp_id}/": { + "get": { + "summary": "Get a VASP directory entry", + "description": "One directory entry or Didit network member by the id the search returned, with the same fields and the routes your application has to it.", + "operationId": "getTravelRuleVasp", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_vasp", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.retrieve_vasp(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.retrieveVasp(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.RetrieveVASP(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().retrieveVasp(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->retrieveVasp('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.retrieve_vasp(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.RetrieveVaspAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The entry.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VaspDirectoryEntry" + } + } + } + }, + "404": { + "description": "No entry or listed Didit network member has this id.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry has this id." + } + } + } + } + } + } + }, + "/v3/travel-rule/vasps/{vasp_id}/decision/": { + "get": { + "summary": "Get your decision on a VASP", + "description": "Your application's decision on a counterparty (a directory entry or a Didit network member): approved, restricted or blocked. An entity of a brand with no decision of its own follows the brand's decision, named in inherited_from. Private to your application.", + "operationId": "getTravelRuleVaspDecision", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_vasp_decision", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/decision/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.retrieve_vasp_decision(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.retrieveVaspDecision(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.RetrieveVASPDecision(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().retrieveVaspDecision(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->retrieveVaspDecision('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.retrieve_vasp_decision(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.RetrieveVaspDecisionAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The decision that applies, or null.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecisionResponse" + }, + "examples": { + "Own decision": { + "value": { + "decision": { + "decision": "APPROVED", + "reason": "Licensed in DE, questionnaire reviewed", + "decided_by": "", + "updated_at": "2026-10-14T10:05:00Z", + "inherited_from": null + } + } + }, + "Inherited from the brand": { + "value": { + "decision": { + "decision": "BLOCKED", + "reason": "Group refused after review", + "decided_by": "", + "updated_at": "2026-10-14T10:05:00Z", + "inherited_from": { + "id": "5e4d3c2b-1a0f-4e9d-8c7b-6a5f4e3d2c1b", + "name": "Example Group" + } + } + } + }, + "No decision": { + "value": { + "decision": null + } + } + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + } + } + }, + "put": { + "summary": "Set your decision on a VASP", + "description": "Make or change your application's decision on a counterparty: APPROVED, RESTRICTED or BLOCKED, with an optional reason. It never changes the shared directory entry and acts only through your transaction rules (counterparty_dd_status). A decision on a brand also covers each of its entities that has no decision of its own.", + "operationId": "updateTravelRuleVaspDecision", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "update_vasp_decision", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecisionRequest" + }, + "example": { + "decision": "BLOCKED", + "reason": "Unlicensed in its home jurisdiction" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PUT https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/decision/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"decision\": \"BLOCKED\", \"reason\": \"Unlicensed in its home jurisdiction\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.update_vasp_decision(\n \"11111111-2222-3333-4444-555555555555\",\n decision=\"BLOCKED\",\n reason=\"Unlicensed in its home jurisdiction\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.updateVaspDecision(\"11111111-2222-3333-4444-555555555555\", {\n decision: \"BLOCKED\",\n reason: \"Unlicensed in its home jurisdiction\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.UpdateVASPDecision(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleCounterpartyDecisionRequest{\n\tDecision: \"BLOCKED\",\n\tReason: didit.String(\"Unlicensed in its home jurisdiction\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleCounterpartyDecisionRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().updateVaspDecision(\"11111111-2222-3333-4444-555555555555\", TravelRuleCounterpartyDecisionRequest.builder()\n .decision(TravelRuleCounterpartyDecisionRequest.Decision.of(\"BLOCKED\"))\n .reason(\"Unlicensed in its home jurisdiction\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->updateVaspDecision('11111111-2222-3333-4444-555555555555', [\n 'decision' => 'BLOCKED',\n 'reason' => 'Unlicensed in its home jurisdiction',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.update_vasp_decision(\n \"11111111-2222-3333-4444-555555555555\",\n decision: \"BLOCKED\",\n reason: \"Unlicensed in its home jurisdiction\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.UpdateVaspDecisionAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleCounterpartyDecisionRequest\n{\n Decision = \"BLOCKED\",\n Reason = \"Unlicensed in its home jurisdiction\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The decision as it now applies.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecisionResponse" + }, + "example": { + "decision": { + "decision": "BLOCKED", + "reason": "Unlicensed in its home jurisdiction", + "decided_by": "", + "updated_at": "2026-10-14T10:05:00Z", + "inherited_from": null + } + } + } + } + }, + "400": { + "description": "The decision is missing or not one of APPROVED, RESTRICTED or BLOCKED, or the reason is longer than 255 characters.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "decision": [ + "\"MAYBE\" is not a valid choice." + ] + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + } + } + }, + "delete": { + "summary": "Clear your decision on a VASP", + "description": "Clear your application's own decision on a counterparty. The response shows the decision that applies afterwards: null, or for an entity of a brand the brand's decision, which the entity follows again.", + "operationId": "clearTravelRuleVaspDecision", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "clear_vasp_decision", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/decision/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.clear_vasp_decision(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.clearVaspDecision(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.ClearVASPDecision(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().clearVaspDecision(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->clearVaspDecision('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.clear_vasp_decision(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.ClearVaspDecisionAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The decision cleared; the body shows the decision that applies now.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecisionResponse" + }, + "example": { + "decision": null + } + } + } + }, + "404": { + "description": "No counterparty has this id, or your application has no decision of its own on it (a decision it only inherits from its brand cannot be cleared here: clear the brand's).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Unknown id": { + "value": { + "detail": "No directory entry or Didit network member has this id." + } + }, + "No decision of its own": { + "value": { + "detail": "This application has no decision of its own on this counterparty." + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/vasps/{vasp_id}/screening/": { + "get": { + "summary": "Get a VASP's entity screening", + "description": "Whether counterparty screening is on for your application, what one screening costs your organization, how long a result stays fresh, and your latest sanctions and adverse-media screening of the counterparty. Reading it is free.", + "operationId": "getTravelRuleVaspScreening", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_vasp_screening", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/screening/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.retrieve_vasp_screening(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.retrieveVaspScreening(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.RetrieveVASPScreening(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().retrieveVaspScreening(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->retrieveVaspScreening('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.retrieve_vasp_screening(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.RetrieveVaspScreeningAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Your screening setting, its price and your latest screening.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreeningStatus" + }, + "example": { + "enabled": true, + "price": "0.20", + "fresh_days": 30, + "screening": { + "id": "7a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d", + "status": "Approved", + "total_hits": 0, + "score": null, + "screened_at": "2026-10-14T09:58:00Z", + "fresh": true, + "flagged": false + } + } + } + } + }, + "403": { + "description": "Your organization's contract has no rate configured for counterparty screening.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No contract rate is configured for this usage type." + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + } + } + }, + "post": { + "summary": "Screen a VASP", + "description": "Screen the counterparty entity against sanctions and adverse media for your application. Opt-in: counterparty_screening_enabled must be on in your Travel Rule settings. Each new screening is billed at your organization's Company AML Screening price; a screening younger than 30 days is returned instead (200, not billed) unless you send refresh: true. One screening of a counterparty runs at a time per application. The result is private to your application. Sandbox applications are never screened.", + "operationId": "screenTravelRuleVasp", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "screen_vasp", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreeningRequest" + }, + "example": { + "refresh": false + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/screening/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"refresh\": false}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.screen_vasp(\n \"11111111-2222-3333-4444-555555555555\",\n refresh=False,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.screenVasp(\"11111111-2222-3333-4444-555555555555\", {\n refresh: false,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.ScreenVASP(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleCounterpartyScreeningRequest{\n\tRefresh: didit.Bool(false),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleCounterpartyScreeningRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().screenVasp(\"11111111-2222-3333-4444-555555555555\", TravelRuleCounterpartyScreeningRequest.builder()\n .refresh(false)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->screenVasp('11111111-2222-3333-4444-555555555555', [\n 'refresh' => false,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.screen_vasp(\n \"11111111-2222-3333-4444-555555555555\",\n refresh: false\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.ScreenVaspAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleCounterpartyScreeningRequest\n{\n Refresh = false,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "A fresh screening already existed and was returned. Nothing was billed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreeningResult" + }, + "example": { + "outcome": "reused", + "screening": { + "id": "7a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d", + "status": "Approved", + "total_hits": 0, + "score": null, + "screened_at": "2026-10-14T09:58:00Z", + "fresh": true, + "flagged": false + } + } + } + } + }, + "201": { + "description": "A new screening ran and was billed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreeningResult" + }, + "example": { + "outcome": "screened", + "screening": { + "id": "7a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d", + "status": "In Review", + "total_hits": 2, + "score": 85.0, + "screened_at": "2026-10-14T09:58:00Z", + "fresh": true, + "flagged": true + } + } + } + } + }, + "400": { + "description": "The body is not valid, or the counterparty has no name to screen.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "detail": [ + "This counterparty has no name to screen." + ] + } + } + } + }, + "403": { + "description": "Your organization's balance cannot cover the screening (code insufficient_credits; nothing ran and nothing was billed), or your contract has no rate configured for it.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me", + "code": "insufficient_credits" + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + }, + "409": { + "description": "Nothing ran and nothing was billed: counterparty screening is off for your application, the application is a sandbox one, or a screening of this counterparty is already running.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Screening off": { + "value": { + "detail": "Counterparty screening is turned off. Enable it in the Travel Rule settings first." + } + }, + "Sandbox application": { + "value": { + "detail": "Counterparty screening does not run for sandbox applications." + } + }, + "Already running": { + "value": { + "detail": "A screening of this counterparty is already running. Try again in a moment." + } + } + } + } + } + }, + "502": { + "description": "The screening could not be completed. Nothing was billed; try again in a moment.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "The screening provider did not answer. Try again in a moment." + } + } + } + } + } + } + }, + "/v3/travel-rule/vasps/{vasp_id}/questionnaires/": { + "get": { + "summary": "List due-diligence questionnaires with a VASP", + "description": "Every due-diligence questionnaire your application sent the counterparty, newest first, with its status and, once answered, the answers. Private to your application.", + "operationId": "listTravelRuleVaspQuestionnaires", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "list_vasp_questionnaires", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/questionnaires/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.list_vasp_questionnaires(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.listVaspQuestionnaires(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.ListVASPQuestionnaires(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().listVaspQuestionnaires(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->listVaspQuestionnaires('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.list_vasp_questionnaires(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.ListVaspQuestionnairesAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Your questionnaires with this counterparty.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaireList" + }, + "example": { + "questionnaires": [ + { + "id": "3f9e8d7c-6b5a-4c3d-9e2f-1a0b9c8d7e6f", + "status": "SENT", + "form_version": "1", + "sent_to": "travelrule@counterparty.example", + "sent_at": "2026-10-14T10:00:00Z", + "expires_at": "2026-11-13T10:00:00Z", + "answered_at": null, + "valid_until": null, + "current": false, + "answers": {} + }, + { + "id": "8b7a6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d", + "status": "ANSWERED", + "form_version": "1", + "sent_to": "travelrule@counterparty.example", + "sent_at": "2026-09-02T08:00:00Z", + "expires_at": "2026-10-02T08:00:00Z", + "answered_at": "2026-09-04T15:20:00Z", + "valid_until": "2027-09-04T15:20:00Z", + "current": true, + "answers": { + "legal_name": "Example Counterparty VASP, incorporated in the example jurisdiction.", + "licensing": "Crypto-asset service provider authorised under MiCA by its national regulator, licence active.", + "jurisdictions": "EU and EEA. We do not serve the United States.", + "aml_program": "Written AML/CFT policy, annual independent review, last completed in March 2026.", + "compliance_officer": "Head of Compliance, reachable at compliance@counterparty.example.", + "sanctions_screening": "Customers at onboarding and daily; counterparties on every transfer.", + "transaction_monitoring": "Rule-based monitoring with analyst review; suspicious activity reports filed with the FIU.", + "travel_rule": "TRP and email; we answer requests within one business day.", + "custody": "Segregated customer wallets held with a qualified custodian, insured.", + "ownership": "No shareholder holds 25 percent or more; parent company Example Holding Group.", + "record_keeping": "Five-year record-keeping for Travel Rule and transaction data, as our regulator requires.", + "data_protection": "Encrypted at rest and in transit, stored in the EU." + } + } + ] + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + } + } + }, + "post": { + "summary": "Send a due-diligence questionnaire to a VASP", + "description": "Email Didit's standard due-diligence questionnaire to a compliance address on record for the counterparty, with a single-use answer link. The counterparty has 30 days to answer every question; the answers stay current for 365 days. A new send replaces your earlier unanswered questionnaire to the same counterparty, whose link stops working. One email per minute per address.", + "operationId": "sendTravelRuleVaspQuestionnaire", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "send_vasp_questionnaire", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaireRequest" + }, + "example": { + "sent_to": "travelrule@counterparty.example" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/questionnaires/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"sent_to\": \"travelrule@counterparty.example\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.send_vasp_questionnaire(\n \"11111111-2222-3333-4444-555555555555\",\n sent_to=\"travelrule@counterparty.example\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.sendVaspQuestionnaire(\"11111111-2222-3333-4444-555555555555\", {\n sent_to: \"travelrule@counterparty.example\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.SendVASPQuestionnaire(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleCounterpartyQuestionnaireRequest{\n\tSentTo: didit.String(\"travelrule@counterparty.example\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleCounterpartyQuestionnaireRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().sendVaspQuestionnaire(\"11111111-2222-3333-4444-555555555555\", TravelRuleCounterpartyQuestionnaireRequest.builder()\n .sentTo(\"travelrule@counterparty.example\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->sendVaspQuestionnaire('11111111-2222-3333-4444-555555555555', [\n 'sent_to' => 'travelrule@counterparty.example',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.send_vasp_questionnaire(\n \"11111111-2222-3333-4444-555555555555\",\n sent_to: \"travelrule@counterparty.example\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.SendVaspQuestionnaireAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleCounterpartyQuestionnaireRequest\n{\n SentTo = \"travelrule@counterparty.example\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "202": { + "description": "The questionnaire was emailed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaire" + }, + "example": { + "id": "3f9e8d7c-6b5a-4c3d-9e2f-1a0b9c8d7e6f", + "status": "SENT", + "form_version": "1", + "sent_to": "travelrule@counterparty.example", + "sent_at": "2026-10-14T10:00:00Z", + "expires_at": "2026-11-13T10:00:00Z", + "answered_at": null, + "valid_until": null, + "current": false, + "answers": {} + } + } + } + }, + "400": { + "description": "sent_to is not a valid email address or not a compliance address on record for the counterparty, or the counterparty has no compliance address on record.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "Not on record": { + "value": { + "sent_to": [ + "Send the questionnaire to a compliance address on record for this counterparty." + ] + } + }, + "No address": { + "value": { + "sent_to": [ + "No compliance address is on record for this counterparty. Add a contact for it first." + ] + } + } + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + }, + "429": { + "description": "A questionnaire went to this address less than a minute ago." + }, + "503": { + "description": "The email could not be sent and no questionnaire was created. Try again in a moment." + } + } + } + }, + "/v3/transactions/{transaction_id}/travel-rule/": { + "patch": { + "summary": "Finish, cancel, resend, or update the originator wallet of a Travel Rule transfer", + "description": "Provide `payment_txn_id` to report the on-chain hash and move an outbound COMPLETED transfer to FINISHED (its INTERNAL-rail counterpart also moves to FINISHED), `{\"action\": \"cancel\"}` to cancel a non-terminal transfer, or `{\"action\": \"resend\"}` to route an outbound transfer stuck in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE, or NOT_ENOUGH_COUNTERPARTY_DATA again (improve the counterparty data first, e.g. add a travel_address to the destination wallet entry). resend applies to outbound transfers only. `{\"action\": \"update_compliance_email\", \"compliance_email\": \"desk@example.com\"}` replaces the compliance email of an outbound transfer in one of those three statuses, the fix for `reason: email_undeliverable`: the status does not change, and the next `resend` sends the request to that address. A resend is queued, not run inside the request: the response shows the transfer back in AWAITING_COUNTERPARTY with a `resend queued` timeline entry and a queued `delivery`, and where it went arrives as a `travel_rule.status.updated` webhook. When the transfer went over GTR, VerifyVASP or Sygna Bridge, the network learns the on-chain hash or the cancellation after the response, with retries; `delivery` shows how that message stands. `{\"action\": \"update_originator_wallet\", \"wallet_address\": \"...\"}` records the wallet the funds actually left from on an outbound transfer, for example the address a UTXO withdrawal was broadcast from rather than the main wallet you declared. It changes the record only: nothing is sent to the counterparty again and the status stays. The originator becomes `chain_details.originator` with `source: updated`, the change is added to the `timeline` with the previous and the new wallet, and the INTERNAL-rail counterpart of the transfer is updated the same way. `{\"action\": \"beneficiary_vasp\", \"beneficiary_vasp\": \"\"}` names the counterparty yourself when discovery could not, on an outbound transfer in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE or NOT_ENOUGH_COUNTERPARTY_DATA: the transfer is routed to that VASP and sent again.", + "operationId": "patchTravelRuleTransfer", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "update_transfer", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID that owns the transfer.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTransferPatchRequest" + }, + "examples": { + "Finish": { + "value": { + "payment_txn_id": "0xchainhash" + } + }, + "Cancel": { + "value": { + "action": "cancel" + } + }, + "Resend": { + "value": { + "action": "resend" + } + }, + "Update compliance email": { + "value": { + "action": "update_compliance_email", + "compliance_email": "desk@example.com" + } + }, + "Update originator wallet": { + "value": { + "action": "update_originator_wallet", + "wallet_address": "bc1qOriginatorInputAddress01" + } + }, + "Name the counterparty": { + "value": { + "action": "beneficiary_vasp", + "beneficiary_vasp": "9f1c2b34-5d6e-4f70-8a91-b2c3d4e5f607" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH https://verification.didit.me/v3/transactions/{transaction_id}/travel-rule/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"payment_txn_id\": \"0xchainhash\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.travel_rule.update_transfer(\n \"11111111-2222-3333-4444-555555555555\",\n payment_txn_id=\"0xchainhash\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.travelRule.updateTransfer(\"11111111-2222-3333-4444-555555555555\", {\n payment_txn_id: \"0xchainhash\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.TravelRule.UpdateTransfer(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleTransferPatchRequest{\n\tPaymentTxnID: didit.String(\"0xchainhash\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleTransferPatchRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().travelRule().updateTransfer(\"11111111-2222-3333-4444-555555555555\", TravelRuleTransferPatchRequest.builder()\n .paymentTxnId(\"0xchainhash\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->travelRule->updateTransfer('11111111-2222-3333-4444-555555555555', [\n 'payment_txn_id' => '0xchainhash',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.travel_rule.update_transfer(\n \"11111111-2222-3333-4444-555555555555\",\n payment_txn_id: \"0xchainhash\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.TravelRule.UpdateTransferAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleTransferPatchRequest\n{\n PaymentTxnId = \"0xchainhash\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The updated transfer. After `update_compliance_email` its status is unchanged and it keeps `reason: email_undeliverable` until the next `resend`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTransferDetail" + } + } + } + }, + "400": { + "description": "Neither payment_txn_id nor a supported action was provided, `compliance_email` is missing or not an email address on `update_compliance_email`, or, for `update_originator_wallet`, no wallet_address, a wallet or memo the transfer's chain cannot carry, or a wallet that is not one of the transfer's UTXO inputs, or `beneficiary_vasp` is missing or not a valid directory VASP id.", + "content": { + "application/json": { + "examples": { + "No action": { + "value": { + "detail": [ + "Provide payment_txn_id, action=cancel, action=resend, action=beneficiary_vasp, action=update_originator_wallet or action=update_compliance_email." + ] + } + }, + "Invalid compliance email": { + "value": { + "compliance_email": [ + "Enter a valid email address." + ] + } + } + } + } + } + }, + "404": { + "description": "Unknown transaction, the transaction has no Travel Rule transfer, or (action `beneficiary_vasp`) no directory entry has that id." + }, + "409": { + "description": "The transfer is not in a state that allows this operation (e.g. not COMPLETED/outbound for finish, already terminal for cancel, or not an outbound transfer in a resendable status for resend). `update_compliance_email` answers `409` for an inbound transfer, and for an outbound one outside COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE and NOT_ENOUGH_COUNTERPARTY_DATA. `update_originator_wallet` answers `409` for an inbound, cancelled or expired transfer. `beneficiary_vasp` answers `409` unless the transfer is outbound and in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE or NOT_ENOUGH_COUNTERPARTY_DATA.", + "content": { + "application/json": { + "examples": { + "Cancel from a terminal status": { + "value": { + "detail": "Transfer is already in a terminal state." + } + }, + "Resend not allowed": { + "value": { + "detail": "Transfer cannot be resent from its current state." + } + }, + "Finish not allowed": { + "value": { + "detail": "Transfer must be an outbound transfer in COMPLETED status to finish." + } + }, + "Compliance email cannot change": { + "value": { + "detail": "The compliance email of this transfer cannot change from its current state." + } + } + } + } + } + } + } + } + }, + "/v3/transactions/{transaction_id}/travel-rule/ownership/": { + "post": { + "summary": "Confirm or deny wallet ownership", + "description": "Resolve a transfer that waits on wallet ownership: call it on your own side of the transfer when it is UNCONFIRMED_OWNERSHIP, or when it is an inbound transfer in ON_HOLD. Confirming marks the matching wallet address book entry as ownership-verified (for an inbound ON_HOLD transfer with no entry, Didit creates one from beneficiary_data.wallet_address) and runs the beneficiary name match on both sides of the transfer; denying declines both sides. An inbound transfer whose deposit already settled moves on to FINISHED when the match succeeds. When the counterparty wallet was declared self-hosted (counterparty.payment_method.method_type: unhosted_wallet), confirming records that the customer verified who controls that wallet and completes the transfer (COMPLETED) in both directions, or FINISHED for an inbound deposit whose on-chain hash is already known. The wallet verified is the counterparty's: the destination of an outbound withdrawal, or the source (originator's wallet) of an inbound deposit. If it is not in your wallet address book yet, Didit adds it as ownership-verified under the counterparty's name; if the book already holds it under a different holder name, the transfer moves to COUNTERPARTY_MISMATCHED_DATA. For an inbound TRP transfer in UNCONFIRMED_OWNERSHIP, ON_HOLD or COUNTERPARTY_MISMATCHED_DATA, Didit sends your decision to the originating VASP instead: approving returns the payment address (wallet_address, or the matched entry's address) and moves the transfer to COMPLETED, and denying declines it. The same call also decides an inbound transfer that a GTR or Sygna Bridge counterparty sent and that waits for your review (`UNCONFIRMED_OWNERSHIP`, `ON_HOLD` or `COUNTERPARTY_MISMATCHED_DATA`): `confirmed: true` approves it (pass `wallet_address` when the transfer names none) and `confirmed: false` declines it, and the network is answered after the response, with retries. VerifyVASP takes its answer when it asks, so a VerifyVASP transfer that would need your review is declined at once and cannot be decided later.", + "operationId": "confirmTravelRuleOwnership", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "confirm_ownership", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable UUID of the transaction your application holds for this transfer: the beneficiary side's transaction on an INTERNAL-rail transfer, or the transaction you created (outbound or inbound) when the counterparty wallet was declared self-hosted.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleOwnershipConfirmRequest" + }, + "example": { + "confirmed": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/transactions/{transaction_id}/travel-rule/ownership/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"confirmed\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.travel_rule.confirm_ownership(\n \"11111111-2222-3333-4444-555555555555\",\n confirmed=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.travelRule.confirmOwnership(\"11111111-2222-3333-4444-555555555555\", {\n confirmed: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.TravelRule.ConfirmOwnership(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleOwnershipConfirmRequest{\n\tConfirmed: true,\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleOwnershipConfirmRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().travelRule().confirmOwnership(\"11111111-2222-3333-4444-555555555555\", TravelRuleOwnershipConfirmRequest.builder()\n .confirmed(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->travelRule->confirmOwnership('11111111-2222-3333-4444-555555555555', [\n 'confirmed' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.travel_rule.confirm_ownership(\n \"11111111-2222-3333-4444-555555555555\",\n confirmed: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.TravelRule.ConfirmOwnershipAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleOwnershipConfirmRequest\n{\n Confirmed = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The updated transfer. For an inbound transfer the counterparty's originator_data/beneficiary_data stay masked (`data_masked: true`) until ownership is confirmed, then `data_unmasked` records when, why and by whom.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTransferDetail" + } + } + } + }, + "409": { + "description": "The transfer is not waiting on ownership confirmation, no wallet address book entry matches its counterparty wallet, an INTERNAL transfer from another Didit customer needs a control proof (a signed message or a test deposit) before it can be confirmed, or a network transfer was already answered over its network (a VerifyVASP transfer, or a GTR or Sygna Bridge transfer you or Didit already decided).", + "content": { + "application/json": { + "examples": { + "Not awaiting confirmation": { + "value": { + "detail": "Transfer is not awaiting ownership confirmation." + } + }, + "No matching address book entry": { + "value": { + "detail": "wallet address entry no longer available" + } + } + } + } + } + }, + "400": { + "description": "Validation error: confirmed is missing, an inbound TRP approval has no wallet_address and no address to fall back on, or `confirmed: true` on a declared self-hosted wallet transfer that names no counterparty wallet and no `wallet_address` was sent.", + "content": { + "application/json": { + "examples": { + "No payment address": { + "value": { + "wallet_address": [ + "wallet_address is required to approve." + ] + } + } + } + } + } + }, + "404": { + "description": "Unknown transaction, or the transaction has no Travel Rule transfer." + }, + "502": { + "description": "Inbound TRP transfer only: Didit could not deliver your decision to the originating VASP, so the transfer status does not change.", + "content": { + "application/json": { + "examples": { + "Callback not delivered": { + "value": { + "detail": "Counterparty inquiry-resolution callback could not be delivered." + } + } + } + } + } + } + } + } + }, + "/v3/transactions/{transaction_id}/travel-rule/messages/": { + "get": { + "summary": "List Travel Rule messages", + "description": "Messages and document requests exchanged with the counterparty VASP about this transfer.", + "operationId": "listTravelRuleMessages", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "list_messages", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID that owns the transfer.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/transactions/{transaction_id}/travel-rule/messages/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.travel_rule.list_messages(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.travelRule.listMessages(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.TravelRule.ListMessages(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().travelRule().listMessages(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->travelRule->listMessages('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.travel_rule.list_messages(\"11111111-2222-3333-4444-555555555555\").each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.TravelRule.ListMessagesAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The messages, oldest first.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleMessage" + } + } + } + } + }, + "404": { + "description": "Unknown transaction or token, or no Travel Rule transfer on the transaction." + } + } + }, + "post": { + "summary": "Send a Travel Rule message or document request", + "description": "Sends a message or a document request to the counterparty VASP. Works on INTERNAL transfers (the other Didit customer sees it on its side of the pair) and EMAIL transfers (the counterparty is emailed and answers in the pickup portal). A transfer over TRP, GTR, TRUST, VerifyVASP or Sygna carries no messages, because those protocols have none. Limits: 100 messages per transfer, 5000 characters per body, 10 requested documents, 5 files of 10 MB each.", + "operationId": "createTravelRuleMessage", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "create_message", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID that owns the transfer.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessageCreateRequest" + }, + "example": { + "kind": "DOCUMENT_REQUEST", + "body": "Please confirm the date of birth of the beneficiary.", + "requested_documents": [ + "Proof of address" + ] + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessageCreateRequest" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/transactions/{transaction_id}/travel-rule/messages/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"kind\": \"DOCUMENT_REQUEST\", \"body\": \"Please confirm the date of birth of the beneficiary.\", \"requested_documents\": [\"Proof of address\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.travel_rule.create_message(\n \"11111111-2222-3333-4444-555555555555\",\n kind=\"DOCUMENT_REQUEST\",\n body=\"Please confirm the date of birth of the beneficiary.\",\n requested_documents=[\"Proof of address\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.travelRule.createMessage(\"11111111-2222-3333-4444-555555555555\", {\n kind: \"DOCUMENT_REQUEST\",\n body: \"Please confirm the date of birth of the beneficiary.\",\n requested_documents: [\"Proof of address\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.TravelRule.CreateMessage(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleMessageCreateRequest{\n\tKind: didit.Some(didit.TravelRuleMessageCreateRequestKind(\"DOCUMENT_REQUEST\")),\n\tBody: didit.String(\"Please confirm the date of birth of the beneficiary.\"),\n\tRequestedDocuments: didit.Some([]string{\"Proof of address\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.TravelRuleMessageCreateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().travelRule().createMessage(\"11111111-2222-3333-4444-555555555555\", TravelRuleMessageCreateRequest.builder()\n .kind(TravelRuleMessageCreateRequest.Kind.of(\"DOCUMENT_REQUEST\"))\n .body(\"Please confirm the date of birth of the beneficiary.\")\n .requestedDocuments(List.of(\"Proof of address\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->travelRule->createMessage('11111111-2222-3333-4444-555555555555', [\n 'kind' => 'DOCUMENT_REQUEST',\n 'body' => 'Please confirm the date of birth of the beneficiary.',\n 'requested_documents' => ['Proof of address'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.travel_rule.create_message(\n \"11111111-2222-3333-4444-555555555555\",\n kind: \"DOCUMENT_REQUEST\",\n body: \"Please confirm the date of birth of the beneficiary.\",\n requested_documents: [\"Proof of address\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.TravelRule.CreateMessageAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleMessageCreateRequest\n{\n Kind = new TravelRuleMessageCreateRequestKind(\"DOCUMENT_REQUEST\"),\n Body = \"Please confirm the date of birth of the beneficiary.\",\n RequestedDocuments = new string[] { \"Proof of address\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The stored message.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessage" + } + } + } + }, + "400": { + "description": "Invalid body, a file type or size that is not accepted, more than 100 messages on the transfer, or a `reply_to` that is not a message of this transfer.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "requested_documents": [ + "Name at least one document the counterparty should send." + ] + } + } + } + }, + "404": { + "description": "Unknown transaction or token, or no Travel Rule transfer on the transaction." + }, + "409": { + "description": "No message can reach the counterparty. `detail` gives the reason: the transfer runs over a network rail (TRP, GTR, TRUST, VerifyVASP or Sygna), it has no counterparty yet, an INTERNAL transfer's data did not match the wallet holder so nothing reached the recipient, or an EMAIL transfer's pickup link has expired or the transfer is closed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/pickup/{token}/": { + "get": { + "summary": "Get email-rail pickup info", + "description": "Public, unauthenticated. Lets a non-Didit counterparty VASP preview the pending exchange from the emailed pickup link. The token itself is the authentication.", + "operationId": "getTravelRulePickup", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_pickup", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "contact", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "The link's `?contact=` value, the contact the link was sent to. Echoed back as `contact` unchanged; it is not validated." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/pickup/{token}/" + } + ], + "responses": { + "200": { + "description": "Pickup preview.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRulePickupInfo" + }, + "example": { + "reference": "33333333-4444-5555-6666-777777777777", + "requested_at": "2026-07-05T10:00:00Z", + "originator_vasp_name": "Origin CASP SL", + "originator_vasp_lei": "529900EXAMPLEORIG001", + "recipient_vasp_name": "Example Counterparty VASP", + "language": "es", + "amount": "0.250000000000000000", + "currency": "ETH", + "fiat_amount": "", + "fiat_currency": "", + "chain": "ETH", + "beneficiary_wallet": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb", + "status": "AWAITING_COUNTERPARTY", + "ivms_version": "ivms101.2023", + "deadline_at": "2026-07-07T10:00:00Z", + "reminders_sent": 0, + "contact": "", + "decline_reasons": [ + "data_mismatch", + "not_our_customer", + "wallet_not_hosted", + "sanctions", + "risk_appetite", + "jurisdiction", + "insufficient_data", + "other" + ], + "join_invitation": { + "eligible": false, + "url": "" + }, + "originator_data": { + "name": "Jane Doe", + "wallet_address": "0x77ccff0cc624a53cd8b8d8703ced826710211ec7" + }, + "beneficiary_data": { + "name": "Ana Diaz", + "wallet_address": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb" + }, + "ivms_request": { + "originator": { + "originatorPersons": [ + { + "naturalPerson": { + "name": { + "nameIdentifier": [ + { + "primaryIdentifier": "Doe", + "secondaryIdentifier": "Jane", + "nameIdentifierType": "LEGL" + } + ] + } + } + } + ], + "accountNumber": [ + "0x77ccff0cc624a53cd8b8d8703ced826710211ec7" + ] + }, + "beneficiary": { + "beneficiaryPersons": [ + { + "naturalPerson": { + "name": { + "nameIdentifier": [ + { + "primaryIdentifier": "Diaz", + "secondaryIdentifier": "Ana", + "nameIdentifierType": "LEGL" + } + ] + } + } + } + ], + "accountNumber": [ + "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb" + ] + } + }, + "data_masked": false + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The window has closed: the counterparty already answered, the transfer left AWAITING_COUNTERPARTY, or the deadline passed." + } + } + } + }, + "/v3/travel-rule/pickup/{token}/respond/": { + "post": { + "summary": "Respond to an email-rail pickup", + "description": "Public, unauthenticated. Accepts or declines the exchange from the emailed pickup link. On accept, beneficiary_name is checked against the originator's name_matching_strictness policy.", + "operationId": "respondTravelRulePickup", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "respond_pickup", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "contact", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "The link's `?contact=` value. Used when the body sends no `contact`." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRulePickupRespondRequest" + }, + "example": { + "decision": "accept", + "beneficiary_name": "Carlos Ruiz" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/pickup/{token}/respond/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"decision\": \"accept\", \"beneficiary_name\": \"Carlos Ruiz\"}'" + } + ], + "responses": { + "200": { + "description": "The resulting exchange status: COMPLETED or COUNTERPARTY_MISMATCHED_DATA after an accept, COUNTERPARTY_VASP_GENERAL_DECLINE after a decline, which also echoes `decline_reason`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "decline_reason": { + "type": "string", + "description": "Present after a decline." + } + } + }, + "examples": { + "accepted": { + "value": { + "status": "COMPLETED" + } + }, + "declined": { + "value": { + "status": "COUNTERPARTY_VASP_GENERAL_DECLINE", + "decline_reason": "not_our_customer" + } + } + } + } + } + }, + "400": { + "description": "A field is invalid: for example `decision` missing or not `accept` or `decline`, `decline_detail` missing on a decline with `decline_reason: other`, an `ivms_response` that is not an IVMS 101 object or is longer than 16384 characters, or a `contact` in the body that is not a UUID (`{\"contact\": [\"Must be a valid UUID.\"]}`).", + "content": { + "application/json": { + "example": { + "decline_detail": [ + "Say what the reason is when you choose another reason." + ] + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The window has closed: the counterparty already answered, the transfer left AWAITING_COUNTERPARTY, or the deadline passed." + } + } + } + }, + "/v3/travel-rule/pickup/{token}/messages/": { + "get": { + "summary": "List pickup messages", + "description": "Public, unauthenticated. The messages of an EMAIL-rail transfer as the counterparty VASP reads them: what the customer sent is `INBOUND` and is signed with the customer's VASP name. The token is the authentication.", + "operationId": "listTravelRulePickupMessages", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "list_pickup_messages", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/pickup/{token}/messages/" + } + ], + "responses": { + "200": { + "description": "The messages, oldest first, from the counterparty's side.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleMessage" + } + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The pickup link has expired or the transfer is closed." + } + } + }, + "post": { + "summary": "Reply in the pickup portal", + "description": "Public, unauthenticated. The counterparty VASP writes back on an EMAIL-rail transfer. Same body as the transaction's messages; attachments must be PDF, PNG, JPEG or WebP. The token is the authentication.", + "operationId": "createTravelRulePickupMessage", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "create_pickup_message", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessageCreateRequest" + }, + "example": { + "kind": "MESSAGE", + "body": "Attached is the proof of address of the wallet holder." + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessageCreateRequest" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/pickup/{token}/messages/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"kind\": \"MESSAGE\", \"body\": \"Attached is the proof of address of the wallet holder.\"}'" + } + ], + "responses": { + "201": { + "description": "The stored message.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessage" + } + } + } + }, + "400": { + "description": "Invalid body, a file type or size that is not accepted, more than 100 messages on the transfer, or a `reply_to` that is not a message of this transfer.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "requested_documents": [ + "Name at least one document the counterparty should send." + ] + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The pickup link has expired or the transfer is closed." + } + } + } + }, + "/v3/travel-rule/inbound/": { + "post": { + "summary": "Register an after-deposit (sunrise) inbound transfer", + "description": "Register Travel Rule data for a crypto deposit that already settled on-chain before any exchange took place. Creates an inbound travelRule transaction (or reuses an unclaimed deposit) and resolves the transfer against your wallet address book. For a newly created transaction, Didit sends transaction.created first and the travel_rule.status.updated webhooks after it.", + "operationId": "registerTravelRuleInbound", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "register_inbound", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleInboundRequest" + }, + "example": { + "chain": "ETH", + "txid": "0xdeposit-hash", + "wallet_address": "0xYourDepositWallet", + "amount": "500.00", + "currency": "USDC", + "originator_data": { + "name": "Origin CASP SL" + }, + "beneficiary_data": { + "name": "Ana Diaz" + }, + "originating_vasp": { + "name": "Origin CASP SL", + "lei": "", + "travel_address": "ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/inbound/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"chain\":\"ETH\",\"txid\":\"0xdeposit-hash\",\"wallet_address\":\"0xYourDepositWallet\",\"amount\":\"500.00\",\"currency\":\"USDC\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.register_inbound(\n chain=\"ETH\",\n txid=\"0xdeposit-hash\",\n wallet_address=\"0xYourDepositWallet\",\n amount=\"500.00\",\n currency=\"USDC\",\n originator_data={\"name\": \"Origin CASP SL\"},\n beneficiary_data={\"name\": \"Ana Diaz\"},\n originating_vasp={\n \"name\": \"Origin CASP SL\",\n \"lei\": \"\",\n \"travel_address\": \"ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC\",\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.registerInbound({\n chain: \"ETH\",\n txid: \"0xdeposit-hash\",\n wallet_address: \"0xYourDepositWallet\",\n amount: \"500.00\",\n currency: \"USDC\",\n originator_data: { name: \"Origin CASP SL\" },\n beneficiary_data: { name: \"Ana Diaz\" },\n originating_vasp: {\n name: \"Origin CASP SL\",\n lei: \"\",\n travel_address: \"ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC\",\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.RegisterInbound(ctx, didit.TravelRuleInboundRequest{\n\tChain: \"ETH\",\n\tTxid: \"0xdeposit-hash\",\n\tWalletAddress: \"0xYourDepositWallet\",\n\tAmount: \"500.00\",\n\tCurrency: \"USDC\",\n\tOriginatorData: didit.Some(map[string]any{\"name\": \"Origin CASP SL\"}),\n\tBeneficiaryData: didit.Some(map[string]any{\"name\": \"Ana Diaz\"}),\n\tOriginatingVASP: didit.Some(didit.TravelRuleInboundRequestOriginatingVASP{\n\t\tName: didit.String(\"Origin CASP SL\"),\n\t\tLei: didit.String(\"\"),\n\t\tTravelAddress: didit.String(\"ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC\"),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.TravelRuleInboundRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().registerInbound(TravelRuleInboundRequest.builder()\n .chain(\"ETH\")\n .txid(\"0xdeposit-hash\")\n .walletAddress(\"0xYourDepositWallet\")\n .amount(\"500.00\")\n .currency(\"USDC\")\n .originatorData(Map.of(\"name\", \"Origin CASP SL\"))\n .beneficiaryData(Map.of(\"name\", \"Ana Diaz\"))\n .originatingVasp(TravelRuleInboundRequest.OriginatingVasp.builder()\n .name(\"Origin CASP SL\")\n .lei(\"\")\n .travelAddress(\"ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC\")\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->registerInbound([\n 'chain' => 'ETH',\n 'txid' => '0xdeposit-hash',\n 'wallet_address' => '0xYourDepositWallet',\n 'amount' => '500.00',\n 'currency' => 'USDC',\n 'originator_data' => ['name' => 'Origin CASP SL'],\n 'beneficiary_data' => ['name' => 'Ana Diaz'],\n 'originating_vasp' => [\n 'name' => 'Origin CASP SL',\n 'lei' => '',\n 'travel_address' => 'ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC',\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.register_inbound(\n chain: \"ETH\",\n txid: \"0xdeposit-hash\",\n wallet_address: \"0xYourDepositWallet\",\n amount: \"500.00\",\n currency: \"USDC\",\n originator_data: { name: \"Origin CASP SL\" },\n beneficiary_data: { name: \"Ana Diaz\" },\n originating_vasp: {\n name: \"Origin CASP SL\",\n lei: \"\",\n travel_address: \"ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC\",\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.RegisterInboundAsync(new TravelRuleInboundRequest\n{\n Chain = \"ETH\",\n Txid = \"0xdeposit-hash\",\n WalletAddress = \"0xYourDepositWallet\",\n Amount = \"500.00\",\n Currency = \"USDC\",\n OriginatorData = new Dictionary { [\"name\"] = \"Origin CASP SL\" },\n BeneficiaryData = new Dictionary { [\"name\"] = \"Ana Diaz\" },\n OriginatingVasp = new TravelRuleInboundRequestOriginatingVasp\n {\n Name = \"Origin CASP SL\",\n Lei = \"\",\n TravelAddress = \"ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "A transfer for this txid and wallet_address was already registered; the existing one is returned unchanged (created is false).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleInboundResponse" + } + } + } + }, + "201": { + "description": "A new Travel Rule transfer was registered, on a new inbound transaction or on an existing unclaimed deposit (created is true).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleInboundResponse" + } + } + } + }, + "400": { + "description": "A required field (`chain`, `txid`, `wallet_address`, `amount`, `currency`) is missing, `originating_vasp.lei` is not a valid LEI, `originating_vasp.travel_address` is not a valid travel address, a memo, X-address or `utxo` the chain cannot carry, or `originator_data` / `beneficiary_data` that is not an object.", + "content": { + "application/json": { + "examples": { + "Missing field": { + "value": { + "txid": [ + "This field is required." + ] + } + }, + "Invalid LEI": { + "value": { + "originating_vasp": { + "lei": [ + "Invalid LEI: expected 20 alphanumeric characters (ISO 17442)." + ] + } + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/test-vasp/inbound/": { + "post": { + "summary": "Trigger an inbound transfer from the Didit Test VASP (sandbox)", + "description": "Sandbox applications only. The Didit Test VASP sends your application an inbound Travel Rule transfer, so you can exercise the receiving side of an exchange end to end before going live. The transfer is booked and decided by the same logic a real inbound transfer from a network runs against your wallet address book, so the inbox, the decision and the wallet ownership flow behave as they do live. Nothing leaves Didit. A live application receives 400. Send the same `reference` again to get the transfer the first call booked instead of a second one.", + "operationId": "triggerTravelRuleTestVaspInbound", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "trigger_test_vasp_inbound", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTestVaspInboundRequest" + }, + "example": { + "beneficiary_wallet_address": "0xYourSandboxWallet", + "scenario": "accept", + "amount": "1", + "reference": "demo-001" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/test-vasp/inbound/ \\\n -H 'x-api-key: YOUR_SANDBOX_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"beneficiary_wallet_address\":\"0xYourSandboxWallet\",\"scenario\":\"accept\",\"reference\":\"demo-001\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.travel_rule.trigger_test_vasp_inbound(\n beneficiary_wallet_address=\"0xYourSandboxWallet\",\n scenario=\"accept\",\n amount=\"1\",\n reference=\"demo-001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.travelRule.triggerTestVaspInbound({\n beneficiary_wallet_address: \"0xYourSandboxWallet\",\n scenario: \"accept\",\n amount: \"1\",\n reference: \"demo-001\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.TravelRule.TriggerTestVASPInbound(ctx, didit.TravelRuleTestVASPInboundRequest{\n\tBeneficiaryWalletAddress: \"0xYourSandboxWallet\",\n\tScenario: didit.Some(didit.TravelRuleTestVASPInboundRequestScenario(\"accept\")),\n\tAmount: didit.String(\"1\"),\n\tReference: didit.String(\"demo-001\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleTestVaspInboundRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().travelRule().triggerTestVaspInbound(TravelRuleTestVaspInboundRequest.builder()\n .beneficiaryWalletAddress(\"0xYourSandboxWallet\")\n .scenario(TravelRuleTestVaspInboundRequest.Scenario.of(\"accept\"))\n .amount(\"1\")\n .reference(\"demo-001\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->travelRule->triggerTestVaspInbound([\n 'beneficiary_wallet_address' => '0xYourSandboxWallet',\n 'scenario' => 'accept',\n 'amount' => '1',\n 'reference' => 'demo-001',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.travel_rule.trigger_test_vasp_inbound(\n beneficiary_wallet_address: \"0xYourSandboxWallet\",\n scenario: \"accept\",\n amount: \"1\",\n reference: \"demo-001\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.TravelRule.TriggerTestVaspInboundAsync(new TravelRuleTestVaspInboundRequest\n{\n BeneficiaryWalletAddress = \"0xYourSandboxWallet\",\n Scenario = new TravelRuleTestVaspInboundRequestScenario(\"accept\"),\n Amount = \"1\",\n Reference = \"demo-001\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The inbound transfer was booked and decided. A repeat with the same `reference` returns the same body.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTestVaspInboundResponse" + } + } + } + }, + "400": { + "description": "Invalid body, an unknown or unsupported `scenario`, or the application is a live application.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "409": { + "description": "The transfer could not be booked, for example because Travel Rule is not enabled for the application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/widget-session/": { + "post": { + "summary": "Mint a wallet-ownership widget session", + "description": "Mint a hosted wallet-ownership widget URL/token. Send the customer to the returned url to prove control of a wallet (message signing, Satoshi test, screenshot, or self-declaration). The widget is web-only in this release - open the url in the system browser or a custom tab, never inside a mobile SDK webview.", + "operationId": "createTravelRuleWidgetSession", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "create_widget_session", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetSessionCreate" + }, + "example": { + "wallet_address": "0xd3b62acb76fb27a7f8961ec40fa11746b113aca9", + "chain": "ETH", + "holder_name": "Jane Doe", + "vendor_data": "user-042", + "transaction_id": "99999999-aaaa-bbbb-cccc-dddddddddddd", + "expires_in_minutes": 60 + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/widget-session/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"wallet_address\":\"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\",\"chain\":\"ETH\",\"holder_name\":\"Jane Doe\",\"transaction_id\":\"99999999-aaaa-bbbb-cccc-dddddddddddd\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.create_widget_session(\n wallet_address=\"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\",\n chain=\"ETH\",\n holder_name=\"Jane Doe\",\n vendor_data=\"user-042\",\n transaction_id=\"99999999-aaaa-bbbb-cccc-dddddddddddd\",\n expires_in_minutes=60,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.createWidgetSession({\n wallet_address: \"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\",\n chain: \"ETH\",\n holder_name: \"Jane Doe\",\n vendor_data: \"user-042\",\n transaction_id: \"99999999-aaaa-bbbb-cccc-dddddddddddd\",\n expires_in_minutes: 60,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.CreateWidgetSession(ctx, didit.TravelRuleWidgetSessionCreate{\n\tWalletAddress: didit.String(\"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\"),\n\tChain: didit.String(\"ETH\"),\n\tHolderName: didit.String(\"Jane Doe\"),\n\tVendorData: didit.String(\"user-042\"),\n\tTransactionID: didit.String(\"99999999-aaaa-bbbb-cccc-dddddddddddd\"),\n\tExpiresInMinutes: didit.Int(60),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleWidgetSessionCreate;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().createWidgetSession(TravelRuleWidgetSessionCreate.builder()\n .walletAddress(\"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\")\n .chain(\"ETH\")\n .holderName(\"Jane Doe\")\n .vendorData(\"user-042\")\n .transactionId(\"99999999-aaaa-bbbb-cccc-dddddddddddd\")\n .expiresInMinutes(60L)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->createWidgetSession([\n 'wallet_address' => '0xd3b62acb76fb27a7f8961ec40fa11746b113aca9',\n 'chain' => 'ETH',\n 'holder_name' => 'Jane Doe',\n 'vendor_data' => 'user-042',\n 'transaction_id' => '99999999-aaaa-bbbb-cccc-dddddddddddd',\n 'expires_in_minutes' => 60,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.create_widget_session(\n wallet_address: \"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\",\n chain: \"ETH\",\n holder_name: \"Jane Doe\",\n vendor_data: \"user-042\",\n transaction_id: \"99999999-aaaa-bbbb-cccc-dddddddddddd\",\n expires_in_minutes: 60\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.CreateWidgetSessionAsync(new TravelRuleWidgetSessionCreate\n{\n WalletAddress = \"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\",\n Chain = \"ETH\",\n HolderName = \"Jane Doe\",\n VendorData = \"user-042\",\n TransactionId = \"99999999-aaaa-bbbb-cccc-dddddddddddd\",\n ExpiresInMinutes = 60,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The minted widget session.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetSessionResponse" + } + } + } + }, + "400": { + "description": "Invalid body: `callback_url` is not an absolute http(s) URL, `expires_in_minutes` is outside 5 to 10080, `transaction_id` is not a UUID, or a field is longer than its limit.", + "content": { + "application/json": { + "examples": { + "Invalid callback_url": { + "value": { + "callback_url": [ + "callback_url must be an absolute http(s) URL." + ] + } + } + } + } + } + }, + "404": { + "description": "`transaction_id` does not name a Travel Rule transfer in this application." + } + } + } + }, + "/v3/travel-rule/widget/{token}/": { + "get": { + "summary": "Get wallet-ownership widget context", + "description": "Public read endpoint keyed by the widget session token - no API key required. Returns the widget context plus the status of every proof, so you can poll it to detect completion; completed_at becomes non-null once a proof is verified. A SATOSHI_TEST proof can sit at status PENDING while its deposit is seen on-chain but unconfirmed - a background job automatically rechecks it and completes it once the deposit confirms, so no client action is needed while it waits. Each proof carries the fields its method needs: deposit_address, expected_amount, and its own expires_at for SATOSHI_TEST; challenge for MESSAGE_SIGNING. Prefer the travel_rule.status.updated webhook over polling when the session is linked to a transfer.", + "operationId": "getTravelRuleWidgetContext", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_widget_context", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/widget/{token}/" + } + ], + "responses": { + "200": { + "description": "The widget context and proof statuses.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetContext" + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The session expired without completing." + } + } + } + }, + "/v3/travel-rule/widget/{token}/vasps/": { + "get": { + "summary": "Search VASPs from the wallet widget", + "description": "Public read endpoint keyed by the widget session token, no API key required: the VASP picker of the end user's widget. Same search and filters as GET /v3/travel-rule/vasps/ for the widget's application, answering only who each VASP is and whether it can be reached.", + "operationId": "listTravelRuleWidgetVasps", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "list_widget_vasps", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + }, + { + "name": "search", + "in": "query", + "schema": { + "type": "string", + "maxLength": 255 + }, + "description": "A name or alias fragment (case-insensitive), an exact LEI, or a domain (example.com or a URL on it). Didit network members match on their legal name, LEI or the domain of their compliance email." + }, + { + "name": "network", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "TRISA" + ] + }, + "description": "Only directory entries a network's member directory currently lists." + }, + { + "name": "jurisdiction", + "in": "query", + "schema": { + "type": "string" + }, + "description": "ISO 3166 alpha-2 or alpha-3 country code. 400 for anything else." + }, + { + "name": "reachable", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "true: only VASPs your application has an available route to (Didit network members included). false: only directory entries it cannot reach today." + }, + { + "name": "kind", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "directory", + "didit" + ] + }, + "description": "Only directory entries, or only Didit network members." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl 'https://verification.didit.me/v3/travel-rule/widget/{token}/vasps/?search=counterparty'" + } + ], + "responses": { + "200": { + "description": "Paginated, name-sorted results.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VaspDirectoryWidgetEntry" + } + } + } + } + } + } + }, + "400": { + "description": "A filter is not valid: the body names each field, for example an unknown network or country code.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "jurisdiction": [ + "Use an ISO 3166 alpha-2 or alpha-3 country code." + ], + "network": [ + "\"SWIFT\" is not a valid choice." + ] + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The session expired." + } + } + }, + "post": { + "summary": "Pick the counterparty VASP in the wallet widget", + "description": "Public write endpoint keyed by the widget session token, no API key required: the end user chose the destination VASP that automatic discovery could not name. Only an outbound transfer in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE or NOT_ENOUGH_COUNTERPARTY_DATA accepts it. The transfer is routed to that VASP and sent again; automatic discovery never overrides the pick.", + "operationId": "selectTravelRuleWidgetVasp", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "select_widget_vasp", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "vasp" + ], + "properties": { + "vasp": { + "type": "string", + "format": "uuid", + "description": "The `id` of a VASP from `GET /v3/travel-rule/widget/{token}/vasps/`." + } + } + }, + "example": { + "vasp": "9f1c2b34-5d6e-4f70-8a91-b2c3d4e5f607" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/widget/{token}/vasps/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"vasp\": \"9f1c2b34-5d6e-4f70-8a91-b2c3d4e5f607\"}'" + } + ], + "responses": { + "200": { + "description": "The pick was recorded and the transfer is being sent again.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "transfer_status": { + "type": "string", + "description": "The transfer status after the pick." + }, + "counterparty_vasp": { + "type": "string", + "description": "The name of the VASP the end user picked." + } + } + }, + "example": { + "detail": "Counterparty VASP selected; the transfer is being resent.", + "transfer_status": "AWAITING_COUNTERPARTY", + "counterparty_vasp": "Northwind Exchange" + } + } + } + }, + "400": { + "description": "`vasp` is missing, or no live directory entry has that id.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "vasp": [ + "No directory entry has this id." + ] + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "409": { + "description": "The widget session is not linked to a Travel Rule transfer, or the transfer is not an outbound one in a status that accepts a counterparty." + }, + "410": { + "description": "The session expired." + } + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-users/toggle-ongoing-monitoring/": { + "patch": { + "summary": "Toggle AML ongoing monitoring for users", + "description": "Enables or disables AML ongoing monitoring at the user level. Exactly one monitored AML screening is kept per user; users without any AML screening (for example imported users) get one created and screened first, billed as a regular AML screening. Set estimate_only to preview counts and costs without writing. Batches of 20 or fewer resolve inline (200, COMPLETED); larger batches return 202 and run asynchronously.", + "operationId": "toggle_vendor_users_ongoing_monitoring", + "x-didit-sdk": { + "resource": "users", + "method": "set_ongoing_monitoring", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleRequest" + }, + "example": { + "is_enabled": true, + "didit_internal_id_list": [ + "5b9a0000-0000-4000-8000-00000000f00d" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Estimate (when estimate_only) or the completed job for small batches processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger batches are processed asynchronously; poll the job endpoint with the returned job_id.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "403": { + "description": "Missing write:sessions privilege, or insufficient balance to enable (the estimate is included in the error body)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.users.set_ongoing_monitoring(\n is_enabled=True,\n didit_internal_id_list=[\"5b9a0000-0000-4000-8000-00000000f00d\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.users.setOngoingMonitoring({\n is_enabled: true,\n didit_internal_id_list: [\"5b9a0000-0000-4000-8000-00000000f00d\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Users.SetOngoingMonitoring(ctx, didit.OngoingMonitoringToggleRequest{\n\tIsEnabled: true,\n\tDiditInternalIDList: didit.Some([]string{\"5b9a0000-0000-4000-8000-00000000f00d\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.OngoingMonitoringToggleRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.users().setOngoingMonitoring(OngoingMonitoringToggleRequest.builder()\n .isEnabled(true)\n .diditInternalIdList(List.of(\"5b9a0000-0000-4000-8000-00000000f00d\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->setOngoingMonitoring([\n 'is_enabled' => true,\n 'didit_internal_id_list' => ['5b9a0000-0000-4000-8000-00000000f00d'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.users.set_ongoing_monitoring(\n is_enabled: true,\n didit_internal_id_list: [\"5b9a0000-0000-4000-8000-00000000f00d\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Users.SetOngoingMonitoringAsync(new OngoingMonitoringToggleRequest\n{\n IsEnabled = true,\n DiditInternalIdList = new string[] { \"5b9a0000-0000-4000-8000-00000000f00d\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-users/toggle-ongoing-monitoring/{job_id}/": { + "get": { + "summary": "Get AML ongoing monitoring job", + "description": "Progress and per-outcome counters of a monitoring toggle job. Poll every couple of seconds while status is PENDING or PROCESSING.", + "operationId": "get_vendor_users_ongoing_monitoring_job", + "x-didit-sdk": { + "resource": "users", + "method": "retrieve_ongoing_monitoring_job", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Monitoring job UUID returned by the toggle endpoint." + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "404": { + "description": "Unknown job for this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.users.retrieve_ongoing_monitoring_job(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.users.retrieveOngoingMonitoringJob(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Users.RetrieveOngoingMonitoringJob(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.users().retrieveOngoingMonitoringJob(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->retrieveOngoingMonitoringJob('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.users.retrieve_ongoing_monitoring_job(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Users.RetrieveOngoingMonitoringJobAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-businesses/toggle-ongoing-monitoring/": { + "patch": { + "summary": "Toggle AML ongoing monitoring for businesses", + "description": "Business-level equivalent of the user toggle: one monitored AML screening per business, with businesses lacking any AML screening screened first (company entity). Same estimate_only, inline fast-path and async job contract.", + "operationId": "toggle_vendor_businesses_ongoing_monitoring", + "x-didit-sdk": { + "resource": "businesses", + "method": "set_ongoing_monitoring", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleRequest" + }, + "example": { + "is_enabled": true, + "didit_internal_id_list": [ + "b0000000-0000-4000-8000-0000000000b1" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Estimate (when estimate_only) or the completed job for small batches processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger batches are processed asynchronously; poll the job endpoint with the returned job_id.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "403": { + "description": "Missing write:sessions privilege, or insufficient balance to enable (the estimate is included in the error body)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.businesses.set_ongoing_monitoring(\n is_enabled=True,\n didit_internal_id_list=[\"b0000000-0000-4000-8000-0000000000b1\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.businesses.setOngoingMonitoring({\n is_enabled: true,\n didit_internal_id_list: [\"b0000000-0000-4000-8000-0000000000b1\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.SetOngoingMonitoring(ctx, didit.OngoingMonitoringToggleRequest{\n\tIsEnabled: true,\n\tDiditInternalIDList: didit.Some([]string{\"b0000000-0000-4000-8000-0000000000b1\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.OngoingMonitoringToggleRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.businesses().setOngoingMonitoring(OngoingMonitoringToggleRequest.builder()\n .isEnabled(true)\n .diditInternalIdList(List.of(\"b0000000-0000-4000-8000-0000000000b1\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->setOngoingMonitoring([\n 'is_enabled' => true,\n 'didit_internal_id_list' => ['b0000000-0000-4000-8000-0000000000b1'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.businesses.set_ongoing_monitoring(\n is_enabled: true,\n didit_internal_id_list: [\"b0000000-0000-4000-8000-0000000000b1\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.SetOngoingMonitoringAsync(new OngoingMonitoringToggleRequest\n{\n IsEnabled = true,\n DiditInternalIdList = new string[] { \"b0000000-0000-4000-8000-0000000000b1\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-businesses/toggle-ongoing-monitoring/{job_id}/": { + "get": { + "summary": "Get AML ongoing monitoring job", + "description": "Progress and per-outcome counters of a monitoring toggle job. Poll every couple of seconds while status is PENDING or PROCESSING.", + "operationId": "get_vendor_businesses_ongoing_monitoring_job", + "x-didit-sdk": { + "resource": "businesses", + "method": "retrieve_ongoing_monitoring_job", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Monitoring job UUID returned by the toggle endpoint." + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "404": { + "description": "Unknown job for this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.businesses.retrieve_ongoing_monitoring_job(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.businesses.retrieveOngoingMonitoringJob(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.RetrieveOngoingMonitoringJob(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.businesses().retrieveOngoingMonitoringJob(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->retrieveOngoingMonitoringJob('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.businesses.retrieve_ongoing_monitoring_job(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.RetrieveOngoingMonitoringJobAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/businesses/toggle-ongoing-monitoring/": { + "patch": { + "summary": "Toggle AML ongoing monitoring for business sessions", + "description": "Session-level toggle for KYB verifications, addressed by session number. Sessions without an AML screening get one created and attached to the session itself. Same estimate_only, inline fast-path and async job contract as the user-level endpoints. Replaces the legacy synchronous behavior that skipped sessions without an AML screening.", + "operationId": "toggle_business_sessions_ongoing_monitoring", + "x-didit-sdk": { + "resource": "businesses", + "method": "set_session_ongoing_monitoring", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_enabled" + ], + "properties": { + "is_enabled": { + "type": "boolean", + "description": "true to enable monitoring, false to disable it." + }, + "estimate_only": { + "type": "boolean", + "default": false, + "description": "When true, returns the cost estimate and performs no writes." + }, + "update_all": { + "type": "boolean", + "default": false, + "description": "Apply to every entity of the application (scoped by the filter_* params when present)." + }, + "filter_search": { + "type": "string" + }, + "filter_status": { + "type": "array", + "items": { + "type": "string" + } + }, + "filter_country": { + "type": "string" + }, + "filter_date_from": { + "type": "string", + "format": "date" + }, + "filter_date_to": { + "type": "string", + "format": "date" + }, + "session_numbers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Business session numbers to toggle. Required unless update_all (or the legacy toggle_all alias) is true." + }, + "toggle_all": { + "type": "boolean", + "default": false, + "description": "Legacy alias of update_all." + }, + "filter_workflow_id": { + "type": "string" + } + } + }, + "example": { + "is_enabled": true, + "session_numbers": [ + "1001", + "1002" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Estimate (when estimate_only) or the completed job for small batches processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger batches are processed asynchronously; poll the job endpoint with the returned job_id.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "403": { + "description": "Missing write:sessions privilege, or insufficient balance to enable (the estimate is included in the error body)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.businesses.set_session_ongoing_monitoring(\n is_enabled=True,\n session_numbers=[\"1001\", \"1002\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.businesses.setSessionOngoingMonitoring({\n is_enabled: true,\n session_numbers: [\"1001\", \"1002\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.SetSessionOngoingMonitoring(ctx, didit.BusinessesSetSessionOngoingMonitoringParams{\n\tIsEnabled: true,\n\tSessionNumbers: didit.Some([]string{\"1001\", \"1002\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.BusinessesSetSessionOngoingMonitoringParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.businesses().setSessionOngoingMonitoring(BusinessesSetSessionOngoingMonitoringParams.builder()\n .isEnabled(true)\n .sessionNumbers(List.of(\"1001\", \"1002\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->setSessionOngoingMonitoring([\n 'is_enabled' => true,\n 'session_numbers' => ['1001', '1002'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.businesses.set_session_ongoing_monitoring(\n is_enabled: true,\n session_numbers: [\"1001\", \"1002\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.SetSessionOngoingMonitoringAsync(new BusinessesSetSessionOngoingMonitoringParams\n{\n IsEnabled = true,\n SessionNumbers = new string[] { \"1001\", \"1002\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/businesses/toggle-ongoing-monitoring/{job_id}/": { + "get": { + "summary": "Get AML ongoing monitoring job", + "description": "Progress and per-outcome counters of a monitoring toggle job. Poll every couple of seconds while status is PENDING or PROCESSING.", + "operationId": "get_business_sessions_ongoing_monitoring_job", + "x-didit-sdk": { + "resource": "businesses", + "method": "retrieve_session_ongoing_monitoring_job", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Monitoring job UUID returned by the toggle endpoint." + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "404": { + "description": "Unknown job for this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.businesses.retrieve_session_ongoing_monitoring_job(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.businesses.retrieveSessionOngoingMonitoringJob(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.RetrieveSessionOngoingMonitoringJob(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.businesses().retrieveSessionOngoingMonitoringJob(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->retrieveSessionOngoingMonitoringJob('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.businesses.retrieve_session_ongoing_monitoring_job(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.RetrieveSessionOngoingMonitoringJobAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/": { + "get": { + "summary": "List cases", + "description": "List cases for the application, newest first. Supports filtering, search, and sorting; see query parameters. Paginated with `limit`/`offset`.", + "operationId": "list_cases", + "x-didit-sdk": { + "resource": "cases", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size. Defaults to 50." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset of the first record." + }, + { + "name": "status__in", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated statuses to include, e.g. OPEN,AWAITING_USER." + }, + { + "name": "priority", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated priorities to include." + }, + { + "name": "source__in", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated sources to include." + }, + { + "name": "blueprint", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter to cases on this blueprint." + }, + { + "name": "assigned_to", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter to cases assigned to this user." + }, + { + "name": "unassigned", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "true for unassigned cases, false for assigned cases." + }, + { + "name": "tag", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Filter to cases carrying this exact tag." + }, + { + "name": "search", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Matches title, case_number, tag, subject name/UUID/external ID, linked session UUID, or linked transaction UUID." + }, + { + "name": "date_from", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Created-at lower bound (inclusive)." + }, + { + "name": "date_to", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Created-at upper bound (inclusive)." + }, + { + "name": "due_before", + "in": "query", + "schema": { + "type": "string", + "format": "date-time" + }, + "description": "due_at upper bound." + }, + { + "name": "overdue", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "true to return only overdue open/awaiting-user cases." + }, + { + "name": "ordering", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "created_at", + "-created_at", + "due_at", + "-due_at" + ], + "default": "-created_at" + }, + "description": "Sort order. due_at sorts nulls last." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/?status__in=OPEN,AWAITING_USER&ordering=-due_at' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.ListAutoPaging(ctx, didit.CasesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated case list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows. Exact up to 100, capped at 100 beyond that for performance. Rely on `next` being null to detect the last page." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or null." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or null." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseListItem" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create case", + "description": "Create a case anchored to exactly one subject (a user or a business), directly or by deriving the subject from an attached session/transaction link. Source is always `MANUAL` for API-created cases.", + "operationId": "create_case", + "x-didit-sdk": { + "resource": "cases", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string", + "maxLength": 255 + }, + "blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "CaseBlueprint UUID. Omit for no blueprint." + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ], + "default": "MEDIUM" + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "subject": { + "type": "object", + "description": "Required unless links is provided; the case's exactly-one subject.", + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "business" + ] + }, + "uuid": { + "type": "string", + "format": "uuid" + } + } + }, + "links": { + "type": "array", + "description": "Sessions/business sessions/transactions to attach on creation, e.g. [{\"entity_type\": \"transaction\", \"entity_uuid\": \"...\"}]. When subject is omitted, the subject is derived from the first link's owner.", + "items": { + "type": "object" + } + }, + "assigned_to": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "User UUID to assign immediately." + }, + "review_duplicates": { + "type": "boolean", + "default": false, + "description": "Recheck existing investigations before creating the case. When true, a subject with existing cases requires a nonblank separate_incident_reason. The console always enables this protection. Omitting it preserves existing API behavior." + }, + "separate_incident_reason": { + "type": "string", + "maxLength": 1000 + }, + "source_context": { + "$ref": "#/components/schemas/CaseDuplicateCaseFraudSourceContextRequest" + } + } + }, + "example": { + "title": "Manual - Jane Doe", + "blueprint": "b1de0000-0000-4000-8000-000000000002", + "priority": "HIGH", + "subject": { + "type": "user", + "uuid": "5b9a0000-0000-4000-8000-00000000f00d" + }, + "tags": [ + "review" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"title\": \"Manual - Jane Doe\", \"blueprint\": \"b1de0000-0000-4000-8000-000000000002\", \"priority\": \"HIGH\", \"subject\": {\"type\": \"user\", \"uuid\": \"5b9a0000-0000-4000-8000-00000000f00d\"}, \"tags\": [\"review\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.create(\n title=\"Manual - Jane Doe\",\n blueprint=\"b1de0000-0000-4000-8000-000000000002\",\n priority=\"HIGH\",\n tags=[\"review\"],\n subject={\n \"type\": \"user\",\n \"uuid\": \"5b9a0000-0000-4000-8000-00000000f00d\",\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.create({\n title: \"Manual - Jane Doe\",\n blueprint: \"b1de0000-0000-4000-8000-000000000002\",\n priority: \"HIGH\",\n tags: [\"review\"],\n subject: {\n type: \"user\",\n uuid: \"5b9a0000-0000-4000-8000-00000000f00d\",\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Create(ctx, didit.CasesCreateParams{\n\tTitle: \"Manual - Jane Doe\",\n\tBlueprint: didit.String(\"b1de0000-0000-4000-8000-000000000002\"),\n\tPriority: didit.Some(didit.CasesCreateParamsPriority(\"HIGH\")),\n\tTags: didit.Some([]string{\"review\"}),\n\tSubject: didit.Some(didit.CasesCreateParamsSubject{\n\t\tType: didit.Some(didit.CasesCreateParamsSubjectType(\"user\")),\n\t\tUUID: didit.String(\"5b9a0000-0000-4000-8000-00000000f00d\"),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CasesCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().create(CasesCreateParams.builder()\n .title(\"Manual - Jane Doe\")\n .blueprint(\"b1de0000-0000-4000-8000-000000000002\")\n .priority(CasesCreateParams.Priority.of(\"HIGH\"))\n .tags(List.of(\"review\"))\n .subject(CasesCreateParams.Subject.builder()\n .type(CasesCreateParams.Subject.Type.of(\"user\"))\n .uuid(\"5b9a0000-0000-4000-8000-00000000f00d\")\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->create([\n 'title' => 'Manual - Jane Doe',\n 'blueprint' => 'b1de0000-0000-4000-8000-000000000002',\n 'priority' => 'HIGH',\n 'tags' => ['review'],\n 'subject' => [\n 'type' => 'user',\n 'uuid' => '5b9a0000-0000-4000-8000-00000000f00d',\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.create(\n title: \"Manual - Jane Doe\",\n blueprint: \"b1de0000-0000-4000-8000-000000000002\",\n priority: \"HIGH\",\n tags: [\"review\"],\n subject: {\n type: \"user\",\n uuid: \"5b9a0000-0000-4000-8000-00000000f00d\",\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.CreateAsync(new CasesCreateParams\n{\n Title = \"Manual - Jane Doe\",\n Blueprint = \"b1de0000-0000-4000-8000-000000000002\",\n Priority = new CasesCreateParamsPriority(\"HIGH\"),\n Tags = new string[] { \"review\" },\n Subject = new CasesCreateParamsSubject\n {\n Type = new CasesCreateParamsSubjectType(\"user\"),\n Uuid = \"5b9a0000-0000-4000-8000-00000000f00d\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Case created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "400": { + "description": "Neither subject nor links provided, or the subject/blueprint/assignee was not found.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "409": { + "description": "Duplicate review is enabled and an existing case was found without a separate-incident reason. No case was created." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/": { + "get": { + "summary": "Get case", + "description": "Retrieve a case with its subject, blueprint, notes, events, links, and checklist.", + "operationId": "get_case", + "x-didit-sdk": { + "resource": "cases", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.retrieve(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.retrieve(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Retrieve(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().retrieve(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->retrieve('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.retrieve(\"c1a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RetrieveAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Case detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "404": { + "description": "Case (or nested resource) not found in this application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update case", + "description": "Update the case's title, priority, due date, tags, or owning review team. Send only the fields you want to change; fields you omit keep their value. `tags` replaces the whole tag list. `team` takes the uuid of an active review team of this application (list them with `GET .../cases/teams/`), or null to clear the team. Requires a full-access API key, or `write:cases` for a console member: a key with restricted access gets 403. Logs PRIORITY_CHANGED, DUE_DATE_CHANGED, TAG_ADDED, TAG_REMOVED and TEAM_CHANGED events as applicable; a title change is saved without a timeline event. Returns the updated case.", + "operationId": "update_case", + "x-didit-sdk": { + "resource": "cases", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string", + "maxLength": 255, + "description": "New case title." + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "New deadline. Send null to clear it." + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The complete tag list. It replaces the current tags." + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "UUID of the review team that owns the case. It must be an active team of this application. Send null to clear the team." + } + } + }, + "examples": { + "Change priority and deadline": { + "value": { + "priority": "HIGH", + "due_at": "2026-11-01T00:00:00Z" + } + }, + "Move the case to a team": { + "value": { + "team": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + }, + "Clear the team": { + "value": { + "team": null + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"priority\": \"HIGH\", \"due_at\": \"2026-08-01T00:00:00Z\"}'" + }, + { + "lang": "curl", + "label": "curl (change team)", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"team\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.update(\n \"c1a5e000-0000-4000-8000-000000000001\",\n priority=\"HIGH\",\n due_at=\"2026-11-01T00:00:00Z\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.update(\"c1a5e000-0000-4000-8000-000000000001\", {\n priority: \"HIGH\",\n due_at: \"2026-11-01T00:00:00Z\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Update(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesUpdateParams{\n\tPriority: didit.Some(didit.CasesUpdateParamsPriority(\"HIGH\")),\n\tDueAt: didit.Time(time.Date(2026, 11, 1, 0, 0, 0, 0, time.UTC)),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.OffsetDateTime;\nimport me.didit.Didit;\nimport me.didit.types.CasesUpdateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().update(\"c1a5e000-0000-4000-8000-000000000001\", CasesUpdateParams.builder()\n .priority(CasesUpdateParams.Priority.of(\"HIGH\"))\n .dueAt(OffsetDateTime.parse(\"2026-11-01T00:00:00Z\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->update('c1a5e000-0000-4000-8000-000000000001', [\n 'priority' => 'HIGH',\n 'due_at' => '2026-11-01T00:00:00Z',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.update(\n \"c1a5e000-0000-4000-8000-000000000001\",\n priority: \"HIGH\",\n due_at: \"2026-11-01T00:00:00Z\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.UpdateAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesUpdateParams\n{\n Priority = new CasesUpdateParamsPriority(\"HIGH\"),\n DueAt = DateTimeOffset.Parse(\"2026-11-01T00:00:00Z\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated case.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "400": { + "description": "A field failed validation. The body is keyed by field name. `team` is rejected when it is not an active review team of this application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "Team not active in this application": { + "value": { + "team": [ + "Choose an active team of this application." + ] + } + }, + "Invalid priority": { + "value": { + "priority": [ + "\"URGENT\" is not a valid choice." + ] + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Cases endpoints need a full-access key. A console member token needs `write:cases`.", + "content": { + "application/json": { + "examples": { + "Restricted API Key": { + "summary": "API key with restricted access", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "Missing Privilege": { + "summary": "Console member without write:cases, or a key of another application", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Case not found in this application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No Case matches the given query." + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete case", + "description": "Delete a case permanently. Its draft regulatory reports are deleted with it. Linked verifications, people, companies and transactions are not deleted. There is no undelete endpoint.\n\nIf the case has a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "delete_case", + "x-didit-sdk": { + "resource": "cases", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.cases.delete(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.cases.delete(\"c1a5e000-0000-4000-8000-000000000001\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.Cases.Delete(ctx, \"c1a5e000-0000-4000-8000-000000000001\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().delete(\"c1a5e000-0000-4000-8000-000000000001\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->delete('c1a5e000-0000-4000-8000-000000000001');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.delete(\"c1a5e000-0000-4000-8000-000000000001\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Cases.DeleteAsync(\"c1a5e000-0000-4000-8000-000000000001\");" + } + ], + "responses": { + "204": { + "description": "Case deleted." + }, + "403": { + "description": "The key cannot delete cases. It requires a full-access key with `write:cases`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "The case does not exist in this application or was already deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "409": { + "description": "The case has a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/assign/": { + "post": { + "summary": "Assign or unassign case", + "description": "Assign the case to a user, or unassign it by sending assignee_user_id: null. assignee_email/assignee_name auto-provision a User row when the assignee does not exist yet.", + "operationId": "assign_case", + "x-didit-sdk": { + "resource": "cases", + "method": "assign", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "assignee_user_id" + ], + "properties": { + "assignee_user_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "null to unassign." + }, + "assignee_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "assignee_name": { + "type": "string", + "nullable": true + } + } + }, + "example": { + "assignee_user_id": "9a000000-0000-4000-8000-0000000000aa" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/assign/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"assignee_user_id\": \"9a000000-0000-4000-8000-0000000000aa\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.assign(\n \"c1a5e000-0000-4000-8000-000000000001\",\n assignee_user_id=\"9a000000-0000-4000-8000-0000000000aa\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.assign(\"c1a5e000-0000-4000-8000-000000000001\", {\n assignee_user_id: \"9a000000-0000-4000-8000-0000000000aa\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Assign(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesAssignParams{\n\tAssigneeUserID: didit.String(\"9a000000-0000-4000-8000-0000000000aa\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesAssignParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().assign(\"c1a5e000-0000-4000-8000-000000000001\", CasesAssignParams.builder()\n .assigneeUserId(\"9a000000-0000-4000-8000-0000000000aa\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->assign('c1a5e000-0000-4000-8000-000000000001', [\n 'assignee_user_id' => '9a000000-0000-4000-8000-0000000000aa',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.assign(\n \"c1a5e000-0000-4000-8000-000000000001\",\n assignee_user_id: \"9a000000-0000-4000-8000-0000000000aa\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.AssignAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesAssignParams\n{\n AssigneeUserId = \"9a000000-0000-4000-8000-0000000000aa\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Case assigned/unassigned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/resolve/": { + "post": { + "summary": "Resolve case", + "description": "Resolve the case with one of five resolutions: FALSE_POSITIVE (no concern found), VALID_THREAT (concern confirmed), INCONCLUSIVE, REQUIREMENTS_NOT_MET or REQUIREMENTS_SATISFIED. Blocked with 400 if the blueprint's checklist is not optional and items remain incomplete, or if the blueprint requires a resolution note and none is given. When the case's blueprint has a four-eyes checker step (four_eyes_checker_blueprint), the resolution is staged (case.pending_resolution) and the case moves to the checker blueprint instead of resolving immediately; a different officer must call approve or reject-approval. Submit a four-eyes resolution as a console member: a resolution an API key submits has no recorded maker, so approve and reject-approval return 403 for every caller. On a four-eyes blueprint you can name the member who must approve with `checker`: then only that member can approve or reject the resolution, and the response's `checker` names them. Without `checker`, the resolution waits on the checker blueprint's queue. Sending `checker` for a blueprint without a checker step returns 400.", + "operationId": "resolve_case", + "x-didit-sdk": { + "resource": "cases", + "method": "resolve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "resolution" + ], + "properties": { + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ] + }, + "note": { + "type": "string", + "nullable": true, + "description": "Required when the blueprint sets require_resolution_note." + }, + "checker": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Four-eyes blueprints only. The user uuid of the member who must approve this resolution (the same id as `assigned_to.uuid` on a case, or `members[].user_id` on a review team). The member must have case access to this application and cannot be the officer who submits the resolution. Omit it or send null to leave the resolution to the checker queue." + } + } + }, + "examples": { + "Resolve": { + "value": { + "resolution": "FALSE_POSITIVE", + "note": "Confirmed legitimate activity after reviewing documents." + } + }, + "Four-eyes blueprint with a named checker": { + "value": { + "resolution": "VALID_THREAT", + "note": "Document tampering confirmed.", + "checker": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/resolve/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"resolution\": \"FALSE_POSITIVE\", \"note\": \"Confirmed legitimate activity after reviewing documents.\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.resolve(\n \"c1a5e000-0000-4000-8000-000000000001\",\n resolution=\"FALSE_POSITIVE\",\n note=\"Confirmed legitimate activity after reviewing documents.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.resolve(\"c1a5e000-0000-4000-8000-000000000001\", {\n resolution: \"FALSE_POSITIVE\",\n note: \"Confirmed legitimate activity after reviewing documents.\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Resolve(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesResolveParams{\n\tResolution: \"FALSE_POSITIVE\",\n\tNote: didit.String(\"Confirmed legitimate activity after reviewing documents.\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesResolveParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().resolve(\"c1a5e000-0000-4000-8000-000000000001\", CasesResolveParams.builder()\n .resolution(CasesResolveParams.Resolution.of(\"FALSE_POSITIVE\"))\n .note(\"Confirmed legitimate activity after reviewing documents.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->resolve('c1a5e000-0000-4000-8000-000000000001', [\n 'resolution' => 'FALSE_POSITIVE',\n 'note' => 'Confirmed legitimate activity after reviewing documents.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.resolve(\n \"c1a5e000-0000-4000-8000-000000000001\",\n resolution: \"FALSE_POSITIVE\",\n note: \"Confirmed legitimate activity after reviewing documents.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ResolveAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesResolveParams\n{\n Resolution = \"FALSE_POSITIVE\",\n Note = \"Confirmed legitimate activity after reviewing documents.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Resolved, or staged for 4-eyes approval.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "400": { + "description": "The resolution was refused. Non-field errors come as a list of strings: an incomplete checklist, a missing required note, or a `checker` that could not be checked or is not an active reviewer with case access to this application. `checker` errors come keyed by the field: the named member is the officer submitting the resolution, or the case's blueprint has no checker step.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + } + ] + }, + "examples": { + "Incomplete checklist": { + "value": [ + "Case has incomplete checklist items: Verify source of funds" + ] + }, + "Resolution note required": { + "value": [ + "A resolution note is required to resolve this case." + ] + }, + "Reviewer access could not be verified": { + "value": [ + "Reviewer access could not be verified. Try again." + ] + }, + "Checker is not an active reviewer": { + "value": [ + "Choose an active reviewer with case access to this application." + ] + }, + "Checker is the submitting officer": { + "value": { + "checker": [ + "The officer who submitted the resolution cannot be its checker." + ] + } + }, + "Policy has no checker step": { + "value": { + "checker": [ + "This review policy has no checker step, so the case resolves directly." + ] + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/reopen/": { + "post": { + "summary": "Reopen case", + "description": "Move an awaiting-user or resolved case back to open, clearing resolution, resolution_notes, resolved_at, and resolved_by_email.", + "operationId": "reopen_case", + "x-didit-sdk": { + "resource": "cases", + "method": "reopen", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/reopen/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.reopen(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.reopen(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Reopen(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().reopen(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->reopen('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.reopen(\"c1a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ReopenAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Reopen case.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/request-info/": { + "post": { + "summary": "Request info (awaiting user)", + "description": "Move an open case to awaiting_user while more information is collected from the verified user. Fails with 400 if a resolution is currently pending 4-eyes approval.", + "operationId": "request_case_info", + "x-didit-sdk": { + "resource": "cases", + "method": "request_info", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/request-info/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.request_info(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.requestInfo(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RequestInfo(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().requestInfo(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requestInfo('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.request_info(\"c1a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RequestInfoAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Request info (awaiting user).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/escalate/": { + "post": { + "summary": "Escalate case", + "description": "Move the case to its blueprint's configured escalation_blueprint. Requires the current blueprint to have escalation_enabled and an escalation_blueprint set, otherwise 400.", + "operationId": "escalate_case", + "x-didit-sdk": { + "resource": "cases", + "method": "escalate", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/escalate/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.escalate(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.escalate(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Escalate(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().escalate(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->escalate('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.escalate(\"c1a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.EscalateAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Escalate case.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/transfer/": { + "post": { + "summary": "Transfer case", + "description": "Move the case to a different, active blueprint of the same application. Requires the current blueprint to have transfer_enabled, otherwise 400.", + "operationId": "transfer_case", + "x-didit-sdk": { + "resource": "cases", + "method": "transfer", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "target_blueprint" + ], + "properties": { + "target_blueprint": { + "type": "string", + "format": "uuid" + } + } + }, + "example": { + "target_blueprint": "b1de0000-0000-4000-8000-000000000002" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/transfer/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"target_blueprint\": \"b1de0000-0000-4000-8000-000000000002\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.transfer(\n \"c1a5e000-0000-4000-8000-000000000001\",\n target_blueprint=\"b1de0000-0000-4000-8000-000000000002\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.transfer(\"c1a5e000-0000-4000-8000-000000000001\", {\n target_blueprint: \"b1de0000-0000-4000-8000-000000000002\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Transfer(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesTransferParams{\n\tTargetBlueprint: \"b1de0000-0000-4000-8000-000000000002\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesTransferParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().transfer(\"c1a5e000-0000-4000-8000-000000000001\", CasesTransferParams.builder()\n .targetBlueprint(\"b1de0000-0000-4000-8000-000000000002\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->transfer('c1a5e000-0000-4000-8000-000000000001', [\n 'target_blueprint' => 'b1de0000-0000-4000-8000-000000000002',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.transfer(\n \"c1a5e000-0000-4000-8000-000000000001\",\n target_blueprint: \"b1de0000-0000-4000-8000-000000000002\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.TransferAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesTransferParams\n{\n TargetBlueprint = \"b1de0000-0000-4000-8000-000000000002\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Case transferred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/approve/": { + "post": { + "summary": "Approve staged resolution (4-eyes)", + "description": "Apply a resolution staged by resolve on a four-eyes blueprint. The caller must be a different officer than the one who submitted it, otherwise 403. When the resolution names a checker (the case's `checker`), only that member can approve it: any other caller, including an API key, gets 403.", + "operationId": "approve_case_resolution", + "x-didit-sdk": { + "resource": "cases", + "method": "approve_resolution", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/approve/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.approve_resolution(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.approveResolution(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.ApproveResolution(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().approveResolution(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->approveResolution('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.approve_resolution(\"c1a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ApproveResolutionAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Resolution applied.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "409": { + "description": "Case has no resolution pending approval.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "403": { + "description": "The caller cannot approve this resolution: they submitted it, the case has no recorded maker, or the resolution names a different checker. An API key always gets this 403 when a checker is named.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Caller submitted the resolution": { + "value": { + "detail": "The officer who submitted the resolution cannot approve it." + } + }, + "No recorded maker": { + "value": { + "detail": "Case has no recorded maker for this pending resolution; approve is not permitted." + } + }, + "Caller is not the named checker": { + "value": { + "detail": "Only the named checker can approve this resolution." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/reject-approval/": { + "post": { + "summary": "Reject staged resolution (4-eyes)", + "description": "Reject a resolution staged by resolve and return the case to its maker blueprint. The caller must be a different officer than the one who submitted it, otherwise 403. When the resolution names a checker (the case's `checker`), only that member can reject it: any other caller, including an API key, gets 403.", + "operationId": "reject_case_resolution", + "x-didit-sdk": { + "resource": "cases", + "method": "reject_resolution", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "notes": { + "type": "string", + "nullable": true + } + } + }, + "example": { + "notes": "Needs a clearer rationale before this can be approved." + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/reject-approval/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"notes\": \"Needs a clearer rationale before this can be approved.\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.reject_resolution(\n \"c1a5e000-0000-4000-8000-000000000001\",\n notes=\"Needs a clearer rationale before this can be approved.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.rejectResolution(\"c1a5e000-0000-4000-8000-000000000001\", {\n notes: \"Needs a clearer rationale before this can be approved.\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RejectResolution(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesRejectResolutionParams{\n\tNotes: didit.String(\"Needs a clearer rationale before this can be approved.\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesRejectResolutionParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().rejectResolution(\"c1a5e000-0000-4000-8000-000000000001\", CasesRejectResolutionParams.builder()\n .notes(\"Needs a clearer rationale before this can be approved.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->rejectResolution('c1a5e000-0000-4000-8000-000000000001', [\n 'notes' => 'Needs a clearer rationale before this can be approved.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.reject_resolution(\n \"c1a5e000-0000-4000-8000-000000000001\",\n notes: \"Needs a clearer rationale before this can be approved.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RejectResolutionAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesRejectResolutionParams\n{\n Notes = \"Needs a clearer rationale before this can be approved.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Resolution rejected, case returned to the maker blueprint.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "409": { + "description": "Case has no resolution pending approval.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "403": { + "description": "The caller cannot reject this resolution: they submitted it, the case has no recorded maker, or the resolution names a different checker. An API key always gets this 403 when a checker is named.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Caller submitted the resolution": { + "value": { + "detail": "The officer who submitted the resolution cannot reject it." + } + }, + "No recorded maker": { + "value": { + "detail": "Case has no recorded maker for this pending resolution; reject is not permitted." + } + }, + "Caller is not the named checker": { + "value": { + "detail": "Only the named checker can reject this resolution." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/entity-actions/": { + "post": { + "operationId": "apply_case_entity_action", + "x-didit-sdk": { + "resource": "cases", + "method": "apply_entity_action", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseEntityActionRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseEntityActionRequest" + }, + "example": { + "client_reference": "e1a00000-0000-4000-8000-000000000001", + "action": "add_tags", + "tags": [ + "source-of-funds-reviewed" + ] + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseEntityActionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseEntityActionResult" + } + } + }, + "description": "" + }, + "409": { + "description": "The reference belongs to a different action payload." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Apply one explicit action to the case subject. Supply a unique client_reference to obtain an action receipt and make retries idempotent. Reusing that reference with a different payload returns 409. With a reference, inspect receipt.status even when HTTP status is 200: FAILED means no action was applied. Repeating a failed request does not execute it again unless retry is true. Omitting client_reference preserves the legacy response.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.apply_entity_action(\n \"11111111-2222-3333-4444-555555555555\",\n client_reference=\"e1a00000-0000-4000-8000-000000000001\",\n action=\"add_tags\",\n tags=[\"source-of-funds-reviewed\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.applyEntityAction(\"11111111-2222-3333-4444-555555555555\", {\n client_reference: \"e1a00000-0000-4000-8000-000000000001\",\n action: \"add_tags\",\n tags: [\"source-of-funds-reviewed\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.ApplyEntityAction(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CaseActionCaseEntityActionRequest{\n\tClientReference: didit.String(\"e1a00000-0000-4000-8000-000000000001\"),\n\tAction: didit.CaseActionActionEnum(\"add_tags\"),\n\tTags: didit.Some([]string{\"source-of-funds-reviewed\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CaseActionActionEnum;\nimport me.didit.types.CaseActionCaseEntityActionRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().applyEntityAction(\"11111111-2222-3333-4444-555555555555\", CaseActionCaseEntityActionRequest.builder()\n .clientReference(\"e1a00000-0000-4000-8000-000000000001\")\n .action(CaseActionActionEnum.of(\"add_tags\"))\n .tags(List.of(\"source-of-funds-reviewed\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->applyEntityAction('11111111-2222-3333-4444-555555555555', [\n 'client_reference' => 'e1a00000-0000-4000-8000-000000000001',\n 'action' => 'add_tags',\n 'tags' => ['source-of-funds-reviewed'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.apply_entity_action(\n \"11111111-2222-3333-4444-555555555555\",\n client_reference: \"e1a00000-0000-4000-8000-000000000001\",\n action: \"add_tags\",\n tags: [\"source-of-funds-reviewed\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ApplyEntityActionAsync(\"11111111-2222-3333-4444-555555555555\", new CaseActionCaseEntityActionRequest\n{\n ClientReference = \"e1a00000-0000-4000-8000-000000000001\",\n Action = \"add_tags\",\n Tags = new string[] { \"source-of-funds-reviewed\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/print/": { + "get": { + "summary": "Print case (PDF)", + "description": "Render the full case (summary, subject, checklist state, notes, attached sessions/transactions, events, FIU report list) as a PDF and return a presigned download URL.", + "operationId": "print_case", + "x-didit-sdk": { + "resource": "cases", + "method": "print", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/print/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.print(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.print(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Print(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().print(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->print('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.print(\"c1a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.PrintAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Presigned PDF URL.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/notes/": { + "get": { + "operationId": "list_case_notes", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "name": "ordering", + "required": false, + "in": "query", + "description": "Which field to use when ordering the results.", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "offset", + "required": false, + "in": "query", + "description": "The initial index from which to return the results.", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationPaginatedCaseNoteList" + } + } + }, + "description": "" + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The case or note is unavailable for this caller." + } + }, + "tags": [ + "Case Management" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.notes.list(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.notes.list(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.Notes.ListAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasesNotesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().notes().list(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->notes->list('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.notes.list(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.Notes.ListAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "operationId": "create_case_note", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNoteCreateRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNoteCreateRequest" + }, + "example": { + "comment": "Proof of address matches the ID document." + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNoteCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNote" + } + } + }, + "description": "" + }, + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNote" + } + } + }, + "description": "" + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The case or note is unavailable for this caller." + }, + "409": { + "description": "This client reference was already used for another note or for a note that has been deleted." + } + }, + "tags": [ + "Case Management" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Add a case-scoped note. An optional client_reference makes an unchanged retry return the same note without duplicate activity or notifications. reply_to must identify a live note in this case. The response includes a bounded parent excerpt without copying that excerpt into the saved reply.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.notes.create(\n \"11111111-2222-3333-4444-555555555555\",\n comment=\"Proof of address matches the ID document.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.notes.create(\"11111111-2222-3333-4444-555555555555\", {\n comment: \"Proof of address matches the ID document.\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Notes.Create(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CaseCollaborationCaseNoteCreateRequest{\n\tComment: \"Proof of address matches the ID document.\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CaseCollaborationCaseNoteCreateRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().notes().create(\"11111111-2222-3333-4444-555555555555\", CaseCollaborationCaseNoteCreateRequest.builder()\n .comment(\"Proof of address matches the ID document.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->notes->create('11111111-2222-3333-4444-555555555555', [\n 'comment' => 'Proof of address matches the ID document.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.notes.create(\n \"11111111-2222-3333-4444-555555555555\",\n comment: \"Proof of address matches the ID document.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Notes.CreateAsync(\"11111111-2222-3333-4444-555555555555\", new CaseCollaborationCaseNoteCreateRequest\n{\n Comment = \"Proof of address matches the ID document.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/notes/{note_uuid}/": { + "patch": { + "operationId": "root_partial_update", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "note_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationPatchedCaseNoteUpdateRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationPatchedCaseNoteUpdateRequest" + }, + "example": { + "is_pinned": true + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationPatchedCaseNoteUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNote" + } + } + }, + "description": "" + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The case or note is unavailable for this caller." + } + }, + "tags": [ + "Case Management" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Pin or unpin an existing note. Pinned notes appear first. Pin changes are recorded in case activity and do not change evidence or source decisions.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.notes.update(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n is_pinned=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.notes.update(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n {\n is_pinned: true,\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Notes.Update(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\", didit.CaseCollaborationPatchedCaseNoteUpdateRequest{\n\tIsPinned: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CaseCollaborationPatchedCaseNoteUpdateRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().notes().update(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\", CaseCollaborationPatchedCaseNoteUpdateRequest.builder()\n .isPinned(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->notes->update('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555', [\n 'is_pinned' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.notes.update(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n is_pinned: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Notes.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\", new CaseCollaborationPatchedCaseNoteUpdateRequest\n{\n IsPinned = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "operationId": "delete_case_note", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "note_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "The note is permanently deleted. Replies remain, without the deleted parent excerpt. Attachments are deleted unless another note still uses them." + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The case or note is unavailable for this caller." + } + }, + "tags": [ + "Case Management" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.cases.notes.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.cases.notes.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.Cases.Notes.Delete(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().notes().delete(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->notes->delete('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.notes.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\"\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Cases.Notes.DeleteAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/notes/presign/": { + "post": { + "summary": "Upload note attachment", + "description": "Upload a file (JPEG, PNG, WebP, PDF, plain text, or CSV; max 5 MB) to attach to a note. Returns the s3_key to pass in the note's attachments array.", + "operationId": "upload_case_note_attachment", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "create_attachment_upload", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "file" + ], + "properties": { + "file": { + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/notes/presign/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'file=@evidence.pdf'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.notes.create_attachment_upload(\n \"c1a5e000-0000-4000-8000-000000000001\",\n file=open(\"document.pdf\", \"rb\"),\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.notes.createAttachmentUpload(\"c1a5e000-0000-4000-8000-000000000001\", {\n file: await fileFromPath(\"document.pdf\"),\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Notes.CreateAttachmentUpload(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesNotesCreateAttachmentUploadParams{\n\tFile: didit.FileFromPath(\"document.pdf\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.types.CasesNotesCreateAttachmentUploadParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().notes().createAttachmentUpload(\"c1a5e000-0000-4000-8000-000000000001\", CasesNotesCreateAttachmentUploadParams.builder()\n .file(Path.of(\"document.pdf\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->notes->createAttachmentUpload('c1a5e000-0000-4000-8000-000000000001', [\n 'file' => File::fromPath('document.pdf'),\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.notes.create_attachment_upload(\n \"c1a5e000-0000-4000-8000-000000000001\",\n file: Pathname(\"document.pdf\")\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Notes.CreateAttachmentUploadAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesNotesCreateAttachmentUploadParams\n{\n File = FileUpload.FromPath(\"document.pdf\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "File uploaded.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "s3_key": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "filename": { + "type": "string" + }, + "file_size": { + "type": "integer" + } + } + } + } + } + }, + "400": { + "description": "No file, unsupported content type, or file exceeds 5 MB.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "get": { + "summary": "Get note attachment URL", + "description": "Mint a fresh presigned download URL for an attachment already stored on one of the case's notes.", + "operationId": "get_case_note_attachment_url", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "retrieve_attachment_url", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "s3_key", + "in": "query", + "schema": { + "type": "string" + }, + "description": "The attachment's s3_key, as returned by upload or listed on the note.", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/notes/presign/?s3_key=case-notes/c1a5e000-0000-4000-8000-000000000001/evidence.pdf' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.notes.retrieve_attachment_url(\n \"c1a5e000-0000-4000-8000-000000000001\",\n s3_key=\"your-s3-key\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.notes.retrieveAttachmentUrl(\"c1a5e000-0000-4000-8000-000000000001\", {\n s3_key: \"your-s3-key\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Notes.RetrieveAttachmentURL(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesNotesRetrieveAttachmentURLParams{\n\tS3Key: \"your-s3-key\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesNotesRetrieveAttachmentUrlParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().notes().retrieveAttachmentUrl(\"c1a5e000-0000-4000-8000-000000000001\", CasesNotesRetrieveAttachmentUrlParams.builder()\n .s3Key(\"your-s3-key\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->notes->retrieveAttachmentUrl('c1a5e000-0000-4000-8000-000000000001', [\n 's3_key' => 'your-s3-key',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.notes.retrieve_attachment_url(\n \"c1a5e000-0000-4000-8000-000000000001\",\n s3_key: \"your-s3-key\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Notes.RetrieveAttachmentUrlAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesNotesRetrieveAttachmentUrlParams\n{\n S3Key = \"your-s3-key\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Presigned URL.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "s3_key": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "filename": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "s3_key missing or does not belong to this case.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/links/": { + "get": { + "summary": "List case links", + "description": "List sessions, business sessions, and transactions attached to the case.", + "operationId": "list_case_links", + "x-didit-sdk": { + "resource": "cases.links", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/links/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.links.list(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.links.list(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Links.List(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().links().list(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->links->list('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.links.list(\"c1a5e000-0000-4000-8000-000000000001\").each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Links.ListAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Case links.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseLink" + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Add case link", + "description": "Attach a session, business session, or transaction to the case. Rejected with 400 if the entity belongs to a different subject than the case.", + "operationId": "create_case_link", + "x-didit-sdk": { + "resource": "cases.links", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "entity_type", + "entity_uuid" + ], + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "session", + "business_session", + "transaction" + ] + }, + "entity_uuid": { + "type": "string", + "format": "uuid" + } + } + }, + "example": { + "entity_type": "transaction", + "entity_uuid": "77000000-0000-4000-8000-000000000077" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/links/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"entity_type\": \"transaction\", \"entity_uuid\": \"77000000-0000-4000-8000-000000000077\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.links.create(\n \"c1a5e000-0000-4000-8000-000000000001\",\n entity_type=\"transaction\",\n entity_uuid=\"77000000-0000-4000-8000-000000000077\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.links.create(\"c1a5e000-0000-4000-8000-000000000001\", {\n entity_type: \"transaction\",\n entity_uuid: \"77000000-0000-4000-8000-000000000077\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Links.Create(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesLinksCreateParams{\n\tEntityType: \"transaction\",\n\tEntityUUID: \"77000000-0000-4000-8000-000000000077\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesLinksCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().links().create(\"c1a5e000-0000-4000-8000-000000000001\", CasesLinksCreateParams.builder()\n .entityType(CasesLinksCreateParams.EntityType.of(\"transaction\"))\n .entityUuid(\"77000000-0000-4000-8000-000000000077\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->links->create('c1a5e000-0000-4000-8000-000000000001', [\n 'entity_type' => 'transaction',\n 'entity_uuid' => '77000000-0000-4000-8000-000000000077',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.links.create(\n \"c1a5e000-0000-4000-8000-000000000001\",\n entity_type: \"transaction\",\n entity_uuid: \"77000000-0000-4000-8000-000000000077\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Links.CreateAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesLinksCreateParams\n{\n EntityType = \"transaction\",\n EntityUuid = \"77000000-0000-4000-8000-000000000077\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Link created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseLink" + } + } + } + }, + "400": { + "description": "Entity belongs to a different profile than the case subject.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/links/{link_uuid}/": { + "delete": { + "summary": "Remove case link", + "description": "Detach a linked session, business session, or transaction from the case.", + "operationId": "delete_case_link", + "x-didit-sdk": { + "resource": "cases.links", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "link_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "11000000-0000-4000-8000-000000000004" + }, + "description": "Link UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/links/11000000-0000-4000-8000-000000000004/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.cases.links.delete(\n \"c1a5e000-0000-4000-8000-000000000001\",\n \"11000000-0000-4000-8000-000000000004\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.cases.links.delete(\n \"c1a5e000-0000-4000-8000-000000000001\",\n \"11000000-0000-4000-8000-000000000004\",\n);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.Cases.Links.Delete(ctx, \"c1a5e000-0000-4000-8000-000000000001\", \"11000000-0000-4000-8000-000000000004\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().links().delete(\"c1a5e000-0000-4000-8000-000000000001\", \"11000000-0000-4000-8000-000000000004\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->links->delete('c1a5e000-0000-4000-8000-000000000001', '11000000-0000-4000-8000-000000000004');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.links.delete(\n \"c1a5e000-0000-4000-8000-000000000001\",\n \"11000000-0000-4000-8000-000000000004\"\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Cases.Links.DeleteAsync(\"c1a5e000-0000-4000-8000-000000000001\", \"11000000-0000-4000-8000-000000000004\");" + } + ], + "responses": { + "204": { + "description": "Link removed." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/events/": { + "get": { + "summary": "List case events", + "description": "Read-only audit timeline of the case (created, status changes, assignment, resolution, escalation, transfers, tag/priority/due-date changes, entity actions, and more).", + "operationId": "list_case_events", + "x-didit-sdk": { + "resource": "cases", + "method": "list_events", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/events/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.list_events(\n \"c1a5e000-0000-4000-8000-000000000001\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.listEvents(\"c1a5e000-0000-4000-8000-000000000001\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.ListEventsAutoPaging(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesListEventsParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().listEvents(\"c1a5e000-0000-4000-8000-000000000001\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->listEvents('c1a5e000-0000-4000-8000-000000000001') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.list_events(\"c1a5e000-0000-4000-8000-000000000001\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.ListEventsAsync(\"c1a5e000-0000-4000-8000-000000000001\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated events, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows. Exact up to 100, capped at 100 beyond that for performance. Rely on `next` being null to detect the last page." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or null." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or null." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseEvent" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/checklist/": { + "get": { + "summary": "List case checklist", + "description": "List the case's checklist items, copied from the blueprint's content_config.checklist.items at creation, ordered.", + "operationId": "list_case_checklist", + "x-didit-sdk": { + "resource": "cases.checklist", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/checklist/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.checklist.list(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.checklist.list(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Checklist.List(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().checklist().list(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->checklist->list('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.checklist.list(\"c1a5e000-0000-4000-8000-000000000001\").each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Checklist.ListAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Checklist items.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseChecklistItem" + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/checklist/{item_uuid}/": { + "patch": { + "summary": "Toggle checklist item", + "description": "Mark a checklist item complete or incomplete. Resolution is blocked while any item is incomplete, unless the blueprint marks the checklist optional.", + "operationId": "toggle_case_checklist_item", + "x-didit-sdk": { + "resource": "cases.checklist", + "method": "toggle_item", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "item_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c4ec0000-0000-4000-8000-000000000005" + }, + "description": "Checklist item UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_completed" + ], + "properties": { + "is_completed": { + "type": "boolean" + } + } + }, + "example": { + "is_completed": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/checklist/c4ec0000-0000-4000-8000-000000000005/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"is_completed\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.checklist.toggle_item(\n \"c1a5e000-0000-4000-8000-000000000001\",\n \"c4ec0000-0000-4000-8000-000000000005\",\n is_completed=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.checklist.toggleItem(\n \"c1a5e000-0000-4000-8000-000000000001\",\n \"c4ec0000-0000-4000-8000-000000000005\",\n {\n is_completed: true,\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Checklist.ToggleItem(ctx, \"c1a5e000-0000-4000-8000-000000000001\", \"c4ec0000-0000-4000-8000-000000000005\", didit.CasesChecklistToggleItemParams{\n\tIsCompleted: true,\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesChecklistToggleItemParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().checklist().toggleItem(\"c1a5e000-0000-4000-8000-000000000001\", \"c4ec0000-0000-4000-8000-000000000005\", CasesChecklistToggleItemParams.builder()\n .isCompleted(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->checklist->toggleItem('c1a5e000-0000-4000-8000-000000000001', 'c4ec0000-0000-4000-8000-000000000005', [\n 'is_completed' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.checklist.toggle_item(\n \"c1a5e000-0000-4000-8000-000000000001\",\n \"c4ec0000-0000-4000-8000-000000000005\",\n is_completed: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Checklist.ToggleItemAsync(\"c1a5e000-0000-4000-8000-000000000001\", \"c4ec0000-0000-4000-8000-000000000005\", new CasesChecklistToggleItemParams\n{\n IsCompleted = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated checklist item.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseChecklistItem" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/aml/": { + "get": { + "summary": "List case AML hits", + "description": "List AML screening hits for the case's subject. Same shape as the hits returned by POST /v3/aml/.", + "operationId": "list_case_aml_hits", + "x-didit-sdk": { + "resource": "cases", + "method": "list_aml_hits", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/aml/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.list_aml_hits(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.listAmlHits(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.ListAMLHits(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().listAmlHits(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->listAmlHits('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.list_aml_hits(\"c1a5e000-0000-4000-8000-000000000001\").each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ListAmlHitsAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "AML hits.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AMLScreening" + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/statistics/": { + "get": { + "summary": "Case analytics", + "description": "Aggregate case analytics for the date range (defaults to the last 30 days; max 731 days). Charts, tables, totals, assignment events, and current queue counts use the same application-scoped filters. Filters use current case values. Dates apply to each metric event, so older cases assigned or resolved during the period remain included. Queue depth is independent of the date range. Invalid filters return 400; an unknown blueprint or assignee returns an empty result.", + "operationId": "get_case_analytics", + "x-didit-sdk": { + "resource": "cases", + "method": "retrieve_statistics", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "date_from", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Range start (inclusive). Defaults to 30 days ago." + }, + { + "name": "date_to", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Range end (inclusive). Defaults to today." + }, + { + "name": "blueprint", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter by the current blueprint UUID." + }, + { + "name": "assigned_to", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter by the current assigned reviewer user UUID." + }, + { + "name": "priority", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "description": "Filter by current case priority." + }, + { + "name": "source", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ] + }, + "description": "Filter by case creation source." + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ] + }, + "description": "Filter by current case status." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/statistics/?date_from=2026-06-01&date_to=2026-06-30' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.retrieve_statistics()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.retrieveStatistics();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RetrieveStatistics(ctx, didit.CasesRetrieveStatisticsParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().retrieveStatistics();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->retrieveStatistics();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.retrieve_statistics\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RetrieveStatisticsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Analytics payload.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "series": { + "type": "array", + "items": { + "type": "object", + "properties": { + "date": { + "type": "string", + "format": "date" + }, + "created": { + "type": "integer" + }, + "reviewed": { + "type": "integer" + } + } + } + }, + "tables": { + "type": "object", + "properties": { + "officer": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Dimension value: officer email, \"blueprint_name:uuid\", source, or status. \"unassigned\"/\"none\" for missing dimensions." + }, + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + } + }, + "blueprint": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Dimension value: officer email, \"blueprint_name:uuid\", source, or status. \"unassigned\"/\"none\" for missing dimensions." + }, + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + } + }, + "source": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Dimension value: officer email, \"blueprint_name:uuid\", source, or status. \"unassigned\"/\"none\" for missing dimensions." + }, + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + } + }, + "status": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Dimension value: officer email, \"blueprint_name:uuid\", source, or status. \"unassigned\"/\"none\" for missing dimensions." + }, + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + } + } + } + }, + "totals": { + "type": "object", + "properties": { + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + }, + "queue": { + "type": "object", + "description": "Live snapshot, not scoped to the date range.", + "properties": { + "open": { + "type": "integer" + }, + "overdue": { + "type": "integer" + } + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/bulk/status/": { + "post": { + "summary": "Bulk update case status", + "description": "Apply the same status transition to several cases in one call: OPEN reopens, AWAITING_USER requests info, RESOLVED resolves (resolution required). Each case is evaluated independently; gate failures (incomplete checklist, missing required note) are reported per case without aborting the batch.", + "operationId": "bulk_update_case_status", + "x-didit-sdk": { + "resource": "cases", + "method": "bulk_update_status", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "case_uuids", + "status" + ], + "properties": { + "case_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1 + }, + "status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ] + }, + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ], + "description": "Required when status is RESOLVED." + }, + "note": { + "type": "string", + "nullable": true + } + } + }, + "example": { + "case_uuids": [ + "c1a5e000-0000-4000-8000-000000000001" + ], + "status": "RESOLVED", + "resolution": "FALSE_POSITIVE" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/bulk/status/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"case_uuids\": [\"c1a5e000-0000-4000-8000-000000000001\"], \"status\": \"RESOLVED\", \"resolution\": \"FALSE_POSITIVE\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.bulk_update_status(\n case_uuids=[\"c1a5e000-0000-4000-8000-000000000001\"],\n status=\"RESOLVED\",\n resolution=\"FALSE_POSITIVE\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.bulkUpdateStatus({\n case_uuids: [\"c1a5e000-0000-4000-8000-000000000001\"],\n status: \"RESOLVED\",\n resolution: \"FALSE_POSITIVE\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.BulkUpdateStatus(ctx, didit.CasesBulkUpdateStatusParams{\n\tCaseUUIDs: []string{\"c1a5e000-0000-4000-8000-000000000001\"},\n\tStatus: \"RESOLVED\",\n\tResolution: didit.Some(didit.CasesBulkUpdateStatusParamsResolution(\"FALSE_POSITIVE\")),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CasesBulkUpdateStatusParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().bulkUpdateStatus(CasesBulkUpdateStatusParams.builder()\n .caseUuids(List.of(\"c1a5e000-0000-4000-8000-000000000001\"))\n .status(CasesBulkUpdateStatusParams.Status.of(\"RESOLVED\"))\n .resolution(CasesBulkUpdateStatusParams.Resolution.of(\"FALSE_POSITIVE\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->bulkUpdateStatus([\n 'case_uuids' => ['c1a5e000-0000-4000-8000-000000000001'],\n 'status' => 'RESOLVED',\n 'resolution' => 'FALSE_POSITIVE',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.bulk_update_status(\n case_uuids: [\"c1a5e000-0000-4000-8000-000000000001\"],\n status: \"RESOLVED\",\n resolution: \"FALSE_POSITIVE\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.BulkUpdateStatusAsync(new CasesBulkUpdateStatusParams\n{\n CaseUuids = new string[] { \"c1a5e000-0000-4000-8000-000000000001\" },\n Status = \"RESOLVED\",\n Resolution = new CasesBulkUpdateStatusParamsResolution(\"FALSE_POSITIVE\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Per-case results.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBulkUpdateResult" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/bulk/assign/": { + "post": { + "summary": "Bulk assign cases", + "description": "Assign (or unassign, with assignee_user_id: null) several cases at once. All cases must share the same blueprint; mismatches are reported per case without aborting the batch.", + "operationId": "bulk_assign_cases", + "x-didit-sdk": { + "resource": "cases", + "method": "bulk_assign", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "case_uuids", + "assignee_user_id" + ], + "properties": { + "case_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1 + }, + "assignee_user_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "assignee_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "assignee_name": { + "type": "string", + "nullable": true + } + } + }, + "example": { + "case_uuids": [ + "c1a5e000-0000-4000-8000-000000000001" + ], + "assignee_user_id": "9a000000-0000-4000-8000-0000000000aa" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/bulk/assign/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"case_uuids\": [\"c1a5e000-0000-4000-8000-000000000001\"], \"assignee_user_id\": \"9a000000-0000-4000-8000-0000000000aa\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.bulk_assign(\n case_uuids=[\"c1a5e000-0000-4000-8000-000000000001\"],\n assignee_user_id=\"9a000000-0000-4000-8000-0000000000aa\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.bulkAssign({\n case_uuids: [\"c1a5e000-0000-4000-8000-000000000001\"],\n assignee_user_id: \"9a000000-0000-4000-8000-0000000000aa\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.BulkAssign(ctx, didit.CasesBulkAssignParams{\n\tCaseUUIDs: []string{\"c1a5e000-0000-4000-8000-000000000001\"},\n\tAssigneeUserID: didit.String(\"9a000000-0000-4000-8000-0000000000aa\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CasesBulkAssignParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().bulkAssign(CasesBulkAssignParams.builder()\n .caseUuids(List.of(\"c1a5e000-0000-4000-8000-000000000001\"))\n .assigneeUserId(\"9a000000-0000-4000-8000-0000000000aa\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->bulkAssign([\n 'case_uuids' => ['c1a5e000-0000-4000-8000-000000000001'],\n 'assignee_user_id' => '9a000000-0000-4000-8000-0000000000aa',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.bulk_assign(\n case_uuids: [\"c1a5e000-0000-4000-8000-000000000001\"],\n assignee_user_id: \"9a000000-0000-4000-8000-0000000000aa\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.BulkAssignAsync(new CasesBulkAssignParams\n{\n CaseUuids = new string[] { \"c1a5e000-0000-4000-8000-000000000001\" },\n AssigneeUserId = \"9a000000-0000-4000-8000-0000000000aa\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Per-case results.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBulkUpdateResult" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/": { + "get": { + "summary": "List blueprints", + "description": "List case blueprints for the application.", + "operationId": "list_case_blueprints", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.blueprints.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.blueprints.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.Blueprints.ListAutoPaging(ctx, didit.CasesBlueprintsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().blueprints().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.blueprints.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.Blueprints.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated blueprint list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows, capped at 100 for performance." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseBlueprintListItem" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create blueprint", + "description": "Create a case blueprint: general settings (assignment, escalation, transfer, 4-eyes pairing) plus content_config, the six sections that drive the case page. Set publish=false to create an unpublished draft; Publish creates its first live version. Discarding a never-published draft deletes it.", + "operationId": "create_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "assignee_pool" + ], + "properties": { + "name": { + "type": "string", + "maxLength": 24, + "description": "Max 24 characters, e.g. \"AML investigation\"." + }, + "description": { + "type": "string", + "nullable": true + }, + "assignee_pool": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1, + "description": "List of User UUIDs. Empty arrays are rejected with \"Pick at least one assignee\"." + }, + "assignment_method": { + "type": "string", + "enum": [ + "MANUAL", + "AUTOMATIC" + ], + "default": "MANUAL" + }, + "max_active_cases": { + "type": "integer", + "nullable": true, + "minimum": 1, + "description": "Automatic-mode threshold: \"fewer than N cases of this blueprint assigned\"." + }, + "escalation_enabled": { + "type": "boolean", + "default": false + }, + "escalation_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Target blueprint UUID. Not enforced at save time, but the escalate action fails with 400 until both escalation_enabled and escalation_blueprint are set." + }, + "transfer_enabled": { + "type": "boolean", + "default": false + }, + "four_eyes_checker_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Setting this enables 4-eyes review: resolutions on this blueprint stage on the checker blueprint's queue." + }, + "require_resolution_note": { + "type": "boolean", + "default": false + }, + "content_config": { + "$ref": "#/components/schemas/CaseContentConfig" + }, + "is_active": { + "type": "boolean", + "default": true + }, + "default_deadline_hours": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 8760, + "description": "Default deadline for newly opened cases. Existing case deadlines are unchanged." + }, + "publish": { + "type": "boolean", + "writeOnly": true, + "default": true, + "description": "Set false to save a never-published draft. The record remains inactive at version 0 until the draft is explicitly published. Omitting this field preserves immediate creation for existing integrations." + } + } + }, + "example": { + "name": "AML investigation", + "assignee_pool": [ + "9a000000-0000-4000-8000-0000000000aa" + ], + "assignment_method": "AUTOMATIC", + "max_active_cases": 15, + "require_resolution_note": true, + "content_config": { + "aml_control": { + "enabled": true, + "financial": true, + "identity": false + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"name\": \"AML investigation\", \"assignee_pool\": [\"9a000000-0000-4000-8000-0000000000aa\"], \"assignment_method\": \"AUTOMATIC\", \"max_active_cases\": 15, \"require_resolution_note\": true, \"content_config\": {\"aml_control\": {\"enabled\": true, \"financial\": true, \"identity\": false}}}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.create(\n name=\"AML investigation\",\n assignee_pool=[\"9a000000-0000-4000-8000-0000000000aa\"],\n assignment_method=\"AUTOMATIC\",\n max_active_cases=15,\n require_resolution_note=True,\n content_config={\n \"aml_control\": {\n \"enabled\": True,\n \"financial\": True,\n \"identity\": False,\n },\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.create({\n name: \"AML investigation\",\n assignee_pool: [\"9a000000-0000-4000-8000-0000000000aa\"],\n assignment_method: \"AUTOMATIC\",\n max_active_cases: 15,\n require_resolution_note: true,\n content_config: {\n aml_control: {\n enabled: true,\n financial: true,\n identity: false,\n },\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.Create(ctx, didit.CasesBlueprintsCreateParams{\n\tName: \"AML investigation\",\n\tAssigneePool: []string{\"9a000000-0000-4000-8000-0000000000aa\"},\n\tAssignmentMethod: didit.Some(didit.CasesBlueprintsCreateParamsAssignmentMethod(\"AUTOMATIC\")),\n\tMaxActiveCases: didit.Int(15),\n\tRequireResolutionNote: didit.Bool(true),\n\tContentConfig: didit.Some(didit.CaseContentConfigParam{\n\t\tAMLControl: didit.Some(didit.CaseContentConfigParamAMLControl{\n\t\t\tEnabled: didit.Bool(true),\n\t\t\tFinancial: didit.Bool(true),\n\t\t\tIdentity: didit.Bool(false),\n\t\t}),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CaseContentConfig;\nimport me.didit.types.CasesBlueprintsCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().create(CasesBlueprintsCreateParams.builder()\n .name(\"AML investigation\")\n .assigneePool(List.of(\"9a000000-0000-4000-8000-0000000000aa\"))\n .assignmentMethod(CasesBlueprintsCreateParams.AssignmentMethod.of(\"AUTOMATIC\"))\n .maxActiveCases(15L)\n .requireResolutionNote(true)\n .contentConfig(CaseContentConfig.builder()\n .amlControl(CaseContentConfig.AmlControl.builder()\n .enabled(true)\n .financial(true)\n .identity(false)\n .build())\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->create([\n 'name' => 'AML investigation',\n 'assignee_pool' => ['9a000000-0000-4000-8000-0000000000aa'],\n 'assignment_method' => 'AUTOMATIC',\n 'max_active_cases' => 15,\n 'require_resolution_note' => true,\n 'content_config' => [\n 'aml_control' => [\n 'enabled' => true,\n 'financial' => true,\n 'identity' => false,\n ],\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.create(\n name: \"AML investigation\",\n assignee_pool: [\"9a000000-0000-4000-8000-0000000000aa\"],\n assignment_method: \"AUTOMATIC\",\n max_active_cases: 15,\n require_resolution_note: true,\n content_config: {\n aml_control: {\n enabled: true,\n financial: true,\n identity: false,\n },\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.CreateAsync(new CasesBlueprintsCreateParams\n{\n Name = \"AML investigation\",\n AssigneePool = new string[] { \"9a000000-0000-4000-8000-0000000000aa\" },\n AssignmentMethod = new CasesBlueprintsCreateParamsAssignmentMethod(\"AUTOMATIC\"),\n MaxActiveCases = 15,\n RequireResolutionNote = true,\n ContentConfig = new CaseContentConfig\n {\n AmlControl = new CaseContentConfigAmlControl\n {\n Enabled = true,\n Financial = true,\n Identity = false,\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Blueprint created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintDetail" + } + } + } + }, + "400": { + "description": "Name over 24 chars, empty assignee_pool, or a cross-referenced blueprint (escalation/checker) not found.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/{blueprint_uuid}/": { + "get": { + "summary": "Get blueprint", + "description": "Retrieve a blueprint, including its connected creation sources (AML config, transaction rules, workflow nodes) and which blueprints escalate to it.", + "operationId": "get_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "blueprint_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "b1de0000-0000-4000-8000-000000000002" + }, + "description": "Blueprint UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/b1de0000-0000-4000-8000-000000000002/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.retrieve(\n \"b1de0000-0000-4000-8000-000000000002\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.retrieve(\"b1de0000-0000-4000-8000-000000000002\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.Retrieve(ctx, \"b1de0000-0000-4000-8000-000000000002\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().retrieve(\"b1de0000-0000-4000-8000-000000000002\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->retrieve('b1de0000-0000-4000-8000-000000000002');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.retrieve(\"b1de0000-0000-4000-8000-000000000002\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.RetrieveAsync(\"b1de0000-0000-4000-8000-000000000002\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Blueprint detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update blueprint", + "description": "Partially update a blueprint's general settings or content_config.", + "operationId": "update_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "blueprint_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "b1de0000-0000-4000-8000-000000000002" + }, + "description": "Blueprint UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 24, + "description": "Max 24 characters, e.g. \"AML investigation\"." + }, + "description": { + "type": "string", + "nullable": true + }, + "assignee_pool": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1, + "description": "List of User UUIDs. Empty arrays are rejected with \"Pick at least one assignee\"." + }, + "assignment_method": { + "type": "string", + "enum": [ + "MANUAL", + "AUTOMATIC" + ] + }, + "max_active_cases": { + "type": "integer", + "nullable": true, + "minimum": 1, + "description": "Automatic-mode threshold: \"fewer than N cases of this blueprint assigned\"." + }, + "escalation_enabled": { + "type": "boolean" + }, + "escalation_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Target blueprint UUID. Not enforced at save time, but the escalate action fails with 400 until both escalation_enabled and escalation_blueprint are set." + }, + "transfer_enabled": { + "type": "boolean" + }, + "four_eyes_checker_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Setting this enables 4-eyes review: resolutions on this blueprint stage on the checker blueprint's queue." + }, + "require_resolution_note": { + "type": "boolean" + }, + "content_config": { + "$ref": "#/components/schemas/CaseContentConfig" + }, + "is_active": { + "type": "boolean" + }, + "default_deadline_hours": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 8760, + "description": "Default deadline for newly opened cases. Existing case deadlines are unchanged." + }, + "expected_version": { + "type": "integer", + "minimum": 1, + "description": "The version opened by the editor. A stale version returns 409 without changing the review policy." + } + } + }, + "example": { + "transfer_enabled": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/b1de0000-0000-4000-8000-000000000002/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"transfer_enabled\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.update(\n \"b1de0000-0000-4000-8000-000000000002\",\n transfer_enabled=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.update(\"b1de0000-0000-4000-8000-000000000002\", {\n transfer_enabled: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.Update(ctx, \"b1de0000-0000-4000-8000-000000000002\", didit.CasesBlueprintsUpdateParams{\n\tTransferEnabled: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesBlueprintsUpdateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().update(\"b1de0000-0000-4000-8000-000000000002\", CasesBlueprintsUpdateParams.builder()\n .transferEnabled(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->update('b1de0000-0000-4000-8000-000000000002', [\n 'transfer_enabled' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.update(\n \"b1de0000-0000-4000-8000-000000000002\",\n transfer_enabled: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.UpdateAsync(\"b1de0000-0000-4000-8000-000000000002\", new CasesBlueprintsUpdateParams\n{\n TransferEnabled = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated blueprint.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintDetail" + } + } + } + }, + "409": { + "description": "The review policy changed since expected_version. Review the latest version before retrying." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete blueprint", + "description": "Delete a blueprint. Deletion always succeeds and does not touch the blueprint's cases: existing cases keep their blueprint reference and its settings continue to apply to them. A deleted blueprint no longer appears in blueprint lists and can no longer be chosen as a transfer target; if a maker blueprint is deleted while a resolution is pending 4-eyes approval, a rejected resolution leaves the case on the checker blueprint.", + "operationId": "delete_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "blueprint_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "b1de0000-0000-4000-8000-000000000002" + }, + "description": "Blueprint UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/b1de0000-0000-4000-8000-000000000002/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.cases.blueprints.delete(\n \"b1de0000-0000-4000-8000-000000000002\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.cases.blueprints.delete(\"b1de0000-0000-4000-8000-000000000002\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.Cases.Blueprints.Delete(ctx, \"b1de0000-0000-4000-8000-000000000002\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().blueprints().delete(\"b1de0000-0000-4000-8000-000000000002\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->delete('b1de0000-0000-4000-8000-000000000002');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.blueprints.delete(\"b1de0000-0000-4000-8000-000000000002\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Cases.Blueprints.DeleteAsync(\"b1de0000-0000-4000-8000-000000000002\");" + } + ], + "responses": { + "204": { + "description": "Blueprint deleted." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/presets/": { + "get": { + "summary": "List blueprint presets", + "description": "List the four built-in presets (Screening hit, AML investigation, Suspicious activity, KYC review) with their content_config and whether each is already installed on this application.", + "operationId": "list_case_blueprint_presets", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "list_presets", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/presets/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.list_presets()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.listPresets();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.ListPresets(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().listPresets();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->listPresets();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.list_presets\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.ListPresetsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Preset catalogue.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "preset_key": { + "type": "string", + "enum": [ + "screening_hit", + "aml_investigation", + "suspicious_activity", + "kyc" + ] + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "content_config": { + "$ref": "#/components/schemas/CaseContentConfig" + }, + "installed": { + "type": "boolean" + } + } + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/presets/install/": { + "post": { + "summary": "Install blueprint presets", + "description": "Copy one or more presets into the application as editable blueprints. Idempotent per preset_key; omit preset_keys to install every preset not yet installed.", + "operationId": "install_case_blueprint_presets", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "install_presets", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "preset_keys": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Subset of screening_hit, aml_investigation, suspicious_activity, kyc. Omit to install all." + } + } + }, + "example": { + "preset_keys": [ + "aml_investigation", + "kyc" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/presets/install/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"preset_keys\": [\"aml_investigation\", \"kyc\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.install_presets(\n preset_keys=[\"aml_investigation\", \"kyc\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.installPresets({\n preset_keys: [\"aml_investigation\", \"kyc\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.InstallPresets(ctx, didit.CasesBlueprintsInstallPresetsParams{\n\tPresetKeys: didit.Some([]string{\"aml_investigation\", \"kyc\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CasesBlueprintsInstallPresetsParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().installPresets(CasesBlueprintsInstallPresetsParams.builder()\n .presetKeys(List.of(\"aml_investigation\", \"kyc\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->installPresets([\n 'preset_keys' => ['aml_investigation', 'kyc'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.install_presets(preset_keys: [\"aml_investigation\", \"kyc\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.InstallPresetsAsync(new CasesBlueprintsInstallPresetsParams\n{\n PresetKeys = new string[] { \"aml_investigation\", \"kyc\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "One or more presets installed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintPresetInstallResult" + } + } + } + }, + "200": { + "description": "No new presets to install (all requested presets already exist).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintPresetInstallResult" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/report-templates/": { + "get": { + "summary": "List report templates", + "description": "List FIU report templates for the application.", + "operationId": "list_report_templates", + "x-didit-sdk": { + "resource": "report_templates", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.report_templates.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.reportTemplates.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.ReportTemplates.ListAutoPaging(ctx, didit.ReportTemplatesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.reportTemplates().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "reportTemplates->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.report_templates.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.ReportTemplates.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated templates.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows, capped at 100 for performance." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ReportTemplateDetail" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create report template", + "description": "Create a reusable per-jurisdiction, per-report-type goAML header template. Rejected with 400 if any of the template-level obligatory fields (rentity_id, submission_code, currency_code_local) is missing from field_config; an invalid template cannot be saved or used to create a report. Per-filing header fields (action, submission_date, transactions) are enforced at report validate/finalize time instead.", + "operationId": "create_report_template", + "x-didit-sdk": { + "resource": "report_templates", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "jurisdiction", + "report_type" + ], + "properties": { + "name": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code, upper-cased on save." + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "field_config": { + "type": "object", + "description": "Reusable goAML header defaults. Template-level obligatory fields: rentity_id, submission_code, and currency_code_local (3-letter). Optional: enabled_fields (which optional report blocks to include) plus reporting_person, location, reason, and action defaults. Per-filing header fields (action, submission_date, transactions, jurisdiction) are enforced when the report is validated or finalized, not on the template." + } + } + }, + "example": { + "name": "US SAR default", + "jurisdiction": "US", + "report_type": "SAR", + "field_config": { + "rentity_id": "FIU-US-00123", + "submission_code": "E", + "currency_code_local": "USD" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"name\": \"US SAR default\", \"jurisdiction\": \"US\", \"report_type\": \"SAR\", \"field_config\": {\"rentity_id\": \"FIU-US-00123\", \"submission_code\": \"E\", \"currency_code_local\": \"USD\"}}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.report_templates.create(\n name=\"US SAR default\",\n jurisdiction=\"US\",\n report_type=\"SAR\",\n field_config={\"rentity_id\": \"FIU-US-00123\", \"submission_code\": \"E\", \"currency_code_local\": \"USD\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.reportTemplates.create({\n name: \"US SAR default\",\n jurisdiction: \"US\",\n report_type: \"SAR\",\n field_config: { rentity_id: \"FIU-US-00123\", submission_code: \"E\", currency_code_local: \"USD\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.ReportTemplates.Create(ctx, didit.ReportTemplatesCreateParams{\n\tName: \"US SAR default\",\n\tJurisdiction: \"US\",\n\tReportType: \"SAR\",\n\tFieldConfig: didit.Some(map[string]any{\"rentity_id\": \"FIU-US-00123\", \"submission_code\": \"E\", \"currency_code_local\": \"USD\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.ReportTemplatesCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.reportTemplates().create(ReportTemplatesCreateParams.builder()\n .name(\"US SAR default\")\n .jurisdiction(\"US\")\n .reportType(ReportTemplatesCreateParams.ReportType.of(\"SAR\"))\n .fieldConfig(Map.of(\"rentity_id\", \"FIU-US-00123\", \"submission_code\", \"E\", \"currency_code_local\", \"USD\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "reportTemplates->create([\n 'name' => 'US SAR default',\n 'jurisdiction' => 'US',\n 'report_type' => 'SAR',\n 'field_config' => ['rentity_id' => 'FIU-US-00123', 'submission_code' => 'E', 'currency_code_local' => 'USD'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.report_templates.create(\n name: \"US SAR default\",\n jurisdiction: \"US\",\n report_type: \"SAR\",\n field_config: { rentity_id: \"FIU-US-00123\", submission_code: \"E\", currency_code_local: \"USD\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.ReportTemplates.CreateAsync(new ReportTemplatesCreateParams\n{\n Name = \"US SAR default\",\n Jurisdiction = \"US\",\n ReportType = \"SAR\",\n FieldConfig = new Dictionary { [\"rentity_id\"] = \"FIU-US-00123\", [\"submission_code\"] = \"E\", [\"currency_code_local\"] = \"USD\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Template created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReportTemplateDetail" + } + } + } + }, + "400": { + "description": "Unrecognized jurisdiction, or field_config fails obligatory-field validation.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/report-templates/{template_uuid}/": { + "get": { + "summary": "Get report template", + "description": "Retrieve a report template.", + "operationId": "get_report_template", + "x-didit-sdk": { + "resource": "report_templates", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "template_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "7ec00000-0000-4000-8000-000000000006" + }, + "description": "Report template UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/7ec00000-0000-4000-8000-000000000006/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.report_templates.retrieve(\n \"7ec00000-0000-4000-8000-000000000006\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.reportTemplates.retrieve(\"7ec00000-0000-4000-8000-000000000006\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.ReportTemplates.Retrieve(ctx, \"7ec00000-0000-4000-8000-000000000006\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.reportTemplates().retrieve(\"7ec00000-0000-4000-8000-000000000006\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "reportTemplates->retrieve('7ec00000-0000-4000-8000-000000000006');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.report_templates.retrieve(\"7ec00000-0000-4000-8000-000000000006\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.ReportTemplates.RetrieveAsync(\"7ec00000-0000-4000-8000-000000000006\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Template detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReportTemplateDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update report template", + "description": "Partially update a report template. Re-validates obligatory goAML fields on save.", + "operationId": "update_report_template", + "x-didit-sdk": { + "resource": "report_templates", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "template_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "7ec00000-0000-4000-8000-000000000006" + }, + "description": "Report template UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code, upper-cased on save." + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "field_config": { + "type": "object", + "description": "Reusable goAML header defaults. Template-level obligatory fields: rentity_id, submission_code, and currency_code_local (3-letter). Optional: enabled_fields (which optional report blocks to include) plus reporting_person, location, reason, and action defaults. Per-filing header fields (action, submission_date, transactions, jurisdiction) are enforced when the report is validated or finalized, not on the template." + } + } + }, + "example": { + "field_config": { + "rentity_id": "FIU-US-00124" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/7ec00000-0000-4000-8000-000000000006/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"field_config\": {\"rentity_id\": \"FIU-US-00124\"}}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.report_templates.update(\n \"7ec00000-0000-4000-8000-000000000006\",\n field_config={\"rentity_id\": \"FIU-US-00124\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.reportTemplates.update(\"7ec00000-0000-4000-8000-000000000006\", {\n field_config: { rentity_id: \"FIU-US-00124\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.ReportTemplates.Update(ctx, \"7ec00000-0000-4000-8000-000000000006\", didit.ReportTemplatesUpdateParams{\n\tFieldConfig: didit.Some(map[string]any{\"rentity_id\": \"FIU-US-00124\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.ReportTemplatesUpdateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.reportTemplates().update(\"7ec00000-0000-4000-8000-000000000006\", ReportTemplatesUpdateParams.builder()\n .fieldConfig(Map.of(\"rentity_id\", \"FIU-US-00124\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "reportTemplates->update('7ec00000-0000-4000-8000-000000000006', [\n 'field_config' => ['rentity_id' => 'FIU-US-00124'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.report_templates.update(\n \"7ec00000-0000-4000-8000-000000000006\",\n field_config: { rentity_id: \"FIU-US-00124\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.ReportTemplates.UpdateAsync(\"7ec00000-0000-4000-8000-000000000006\", new ReportTemplatesUpdateParams\n{\n FieldConfig = new Dictionary { [\"rentity_id\"] = \"FIU-US-00124\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated template.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReportTemplateDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete report template", + "description": "Delete a report template. Reports already created from it are unaffected.", + "operationId": "delete_report_template", + "x-didit-sdk": { + "resource": "report_templates", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "template_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "7ec00000-0000-4000-8000-000000000006" + }, + "description": "Report template UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/7ec00000-0000-4000-8000-000000000006/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.report_templates.delete(\n \"7ec00000-0000-4000-8000-000000000006\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.reportTemplates.delete(\"7ec00000-0000-4000-8000-000000000006\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.ReportTemplates.Delete(ctx, \"7ec00000-0000-4000-8000-000000000006\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.reportTemplates().delete(\"7ec00000-0000-4000-8000-000000000006\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "reportTemplates->delete('7ec00000-0000-4000-8000-000000000006');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.report_templates.delete(\"7ec00000-0000-4000-8000-000000000006\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.ReportTemplates.DeleteAsync(\"7ec00000-0000-4000-8000-000000000006\");" + } + ], + "responses": { + "204": { + "description": "Template deleted." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/regulatory-reports/": { + "get": { + "summary": "List reports for case", + "description": "List FIU reports generated for the case.", + "operationId": "list_case_regulatory_reports", + "x-didit-sdk": { + "resource": "cases.regulatory_reports", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/regulatory-reports/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.regulatory_reports.list(\n \"c1a5e000-0000-4000-8000-000000000001\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.regulatoryReports.list(\"c1a5e000-0000-4000-8000-000000000001\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.RegulatoryReports.ListAutoPaging(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesRegulatoryReportsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().regulatoryReports().list(\"c1a5e000-0000-4000-8000-000000000001\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->regulatoryReports->list('c1a5e000-0000-4000-8000-000000000001') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.regulatory_reports.list(\"c1a5e000-0000-4000-8000-000000000001\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.RegulatoryReports.ListAsync(\"c1a5e000-0000-4000-8000-000000000001\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated reports for the case.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportList" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create report from case", + "description": "Create a draft FIU report for the case: DRAFT status, auto-populated from the optional template plus the case's subject, linked transactions, and narrative. Validate then finalize to produce the goAML XML and PDF.", + "operationId": "create_case_regulatory_report", + "x-didit-sdk": { + "resource": "cases.regulatory_reports", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ], + "description": "Required unless a template is given; the template's report_type is used otherwise." + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "Required unless a template is given." + }, + "template": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Report template UUID; its field_config seeds the draft before case-derived data is merged in." + }, + "transactions": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "nullable": true, + "description": "Transactions to attach; defaults to the case's linked transactions." + }, + "rentity_id": { + "type": "string", + "nullable": true + }, + "rentity_branch": { + "type": "string", + "nullable": true + }, + "submission_code": { + "type": "string", + "enum": [ + "E", + "M" + ], + "default": "E" + }, + "entity_reference": { + "type": "string", + "nullable": true + }, + "fiu_ref_number": { + "type": "string", + "nullable": true + }, + "currency_code_local": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "nullable": true + }, + "submission_date": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reporting_person": { + "type": "object" + }, + "location": { + "type": "object" + }, + "reason": { + "type": "string", + "nullable": true + }, + "action": { + "type": "string", + "nullable": true + }, + "activity": { + "type": "string", + "nullable": true + }, + "narrative": { + "type": "object", + "properties": { + "who": { + "type": "string" + }, + "what": { + "type": "string" + }, + "when": { + "type": "string" + }, + "where": { + "type": "string" + }, + "why": { + "type": "string" + }, + "how": { + "type": "string" + } + } + }, + "report_indicators": { + "type": "array", + "items": { + "type": "string" + } + }, + "included_fields": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "example": { + "template": "7ec00000-0000-4000-8000-000000000006" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/regulatory-reports/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"template\": \"7ec00000-0000-4000-8000-000000000006\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.regulatory_reports.create(\n \"c1a5e000-0000-4000-8000-000000000001\",\n template=\"7ec00000-0000-4000-8000-000000000006\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.regulatoryReports.create(\"c1a5e000-0000-4000-8000-000000000001\", {\n template: \"7ec00000-0000-4000-8000-000000000006\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RegulatoryReports.Create(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesRegulatoryReportsCreateParams{\n\tTemplate: didit.String(\"7ec00000-0000-4000-8000-000000000006\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesRegulatoryReportsCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().regulatoryReports().create(\"c1a5e000-0000-4000-8000-000000000001\", CasesRegulatoryReportsCreateParams.builder()\n .template(\"7ec00000-0000-4000-8000-000000000006\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->regulatoryReports->create('c1a5e000-0000-4000-8000-000000000001', [\n 'template' => '7ec00000-0000-4000-8000-000000000006',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.regulatory_reports.create(\n \"c1a5e000-0000-4000-8000-000000000001\",\n template: \"7ec00000-0000-4000-8000-000000000006\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RegulatoryReports.CreateAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesRegulatoryReportsCreateParams\n{\n Template = \"7ec00000-0000-4000-8000-000000000006\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Draft report created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "400": { + "description": "The referenced template was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/": { + "get": { + "summary": "List reports", + "description": "Application-wide FIU report list, optionally filtered by status, report_type, or case.", + "operationId": "list_regulatory_reports", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + }, + { + "name": "status", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "DRAFT", + "VALIDATED", + "FINALIZED" + ] + }, + "description": "Filter by status." + }, + { + "name": "report_type", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "description": "Filter by report type." + }, + { + "name": "case", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter to reports on this case." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.regulatory_reports.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.regulatoryReports.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.RegulatoryReports.ListAutoPaging(ctx, didit.RegulatoryReportsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.regulatoryReports().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.regulatory_reports.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.RegulatoryReports.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated reports across the application.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportList" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create standalone report", + "description": "Create a draft FIU report from the standalone Reports page: both case and template are required (unlike the case-scoped create, which can omit template).", + "operationId": "create_standalone_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "case", + "template" + ], + "properties": { + "case": { + "type": "string", + "format": "uuid" + }, + "template": { + "type": "string", + "format": "uuid" + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ], + "description": "Required unless a template is given; the template's report_type is used otherwise." + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "Required unless a template is given." + }, + "transactions": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "nullable": true, + "description": "Transactions to attach; defaults to the case's linked transactions." + }, + "rentity_id": { + "type": "string", + "nullable": true + }, + "rentity_branch": { + "type": "string", + "nullable": true + }, + "submission_code": { + "type": "string", + "enum": [ + "E", + "M" + ], + "default": "E" + }, + "entity_reference": { + "type": "string", + "nullable": true + }, + "fiu_ref_number": { + "type": "string", + "nullable": true + }, + "currency_code_local": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "nullable": true + }, + "submission_date": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reporting_person": { + "type": "object" + }, + "location": { + "type": "object" + }, + "reason": { + "type": "string", + "nullable": true + }, + "action": { + "type": "string", + "nullable": true + }, + "activity": { + "type": "string", + "nullable": true + }, + "narrative": { + "type": "object", + "properties": { + "who": { + "type": "string" + }, + "what": { + "type": "string" + }, + "when": { + "type": "string" + }, + "where": { + "type": "string" + }, + "why": { + "type": "string" + }, + "how": { + "type": "string" + } + } + }, + "report_indicators": { + "type": "array", + "items": { + "type": "string" + } + }, + "included_fields": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "example": { + "case": "c1a5e000-0000-4000-8000-000000000001", + "template": "7ec00000-0000-4000-8000-000000000006" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"case\": \"c1a5e000-0000-4000-8000-000000000001\", \"template\": \"7ec00000-0000-4000-8000-000000000006\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.regulatory_reports.create(\n case=\"c1a5e000-0000-4000-8000-000000000001\",\n template=\"7ec00000-0000-4000-8000-000000000006\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.regulatoryReports.create({\n case: \"c1a5e000-0000-4000-8000-000000000001\",\n template: \"7ec00000-0000-4000-8000-000000000006\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.RegulatoryReports.Create(ctx, didit.RegulatoryReportsCreateParams{\n\tCase: \"c1a5e000-0000-4000-8000-000000000001\",\n\tTemplate: \"7ec00000-0000-4000-8000-000000000006\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.RegulatoryReportsCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.regulatoryReports().create(RegulatoryReportsCreateParams.builder()\n .case_(\"c1a5e000-0000-4000-8000-000000000001\")\n .template(\"7ec00000-0000-4000-8000-000000000006\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->create([\n 'case' => 'c1a5e000-0000-4000-8000-000000000001',\n 'template' => '7ec00000-0000-4000-8000-000000000006',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.regulatory_reports.create(\n case: \"c1a5e000-0000-4000-8000-000000000001\",\n template: \"7ec00000-0000-4000-8000-000000000006\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.RegulatoryReports.CreateAsync(new RegulatoryReportsCreateParams\n{\n Case = \"c1a5e000-0000-4000-8000-000000000001\",\n Template = \"7ec00000-0000-4000-8000-000000000006\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Draft report created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "400": { + "description": "The referenced template was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/{report_uuid}/": { + "get": { + "summary": "Get report", + "description": "Retrieve a regulatory report.", + "operationId": "get_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.regulatory_reports.retrieve(\n \"5ec00000-0000-4000-8000-000000000007\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.regulatoryReports.retrieve(\"5ec00000-0000-4000-8000-000000000007\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.RegulatoryReports.Retrieve(ctx, \"5ec00000-0000-4000-8000-000000000007\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.regulatoryReports().retrieve(\"5ec00000-0000-4000-8000-000000000007\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->retrieve('5ec00000-0000-4000-8000-000000000007');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.regulatory_reports.retrieve(\"5ec00000-0000-4000-8000-000000000007\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.RegulatoryReports.RetrieveAsync(\"5ec00000-0000-4000-8000-000000000007\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Report detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update report", + "description": "Edit a draft or validated report's fields. Finalized reports cannot be edited (409).", + "operationId": "update_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2 + }, + "rentity_id": { + "type": "string", + "nullable": true + }, + "rentity_branch": { + "type": "string", + "nullable": true + }, + "submission_code": { + "type": "string", + "enum": [ + "E", + "M" + ], + "default": "E" + }, + "entity_reference": { + "type": "string", + "nullable": true + }, + "fiu_ref_number": { + "type": "string", + "nullable": true + }, + "currency_code_local": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "nullable": true + }, + "submission_date": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reporting_person": { + "type": "object" + }, + "location": { + "type": "object" + }, + "reason": { + "type": "string", + "nullable": true + }, + "action": { + "type": "string", + "nullable": true + }, + "activity": { + "type": "string", + "nullable": true + }, + "narrative": { + "type": "object", + "properties": { + "who": { + "type": "string" + }, + "what": { + "type": "string" + }, + "when": { + "type": "string" + }, + "where": { + "type": "string" + }, + "why": { + "type": "string" + }, + "how": { + "type": "string" + } + } + }, + "report_indicators": { + "type": "array", + "items": { + "type": "string" + } + }, + "included_fields": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "example": { + "rentity_id": "FIU-US-00123", + "currency_code_local": "USD" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"rentity_id\": \"FIU-US-00123\", \"currency_code_local\": \"USD\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.regulatory_reports.update(\n \"5ec00000-0000-4000-8000-000000000007\",\n rentity_id=\"FIU-US-00123\",\n currency_code_local=\"USD\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.regulatoryReports.update(\"5ec00000-0000-4000-8000-000000000007\", {\n rentity_id: \"FIU-US-00123\",\n currency_code_local: \"USD\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.RegulatoryReports.Update(ctx, \"5ec00000-0000-4000-8000-000000000007\", didit.RegulatoryReportsUpdateParams{\n\tRentityID: didit.String(\"FIU-US-00123\"),\n\tCurrencyCodeLocal: didit.String(\"USD\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.RegulatoryReportsUpdateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.regulatoryReports().update(\"5ec00000-0000-4000-8000-000000000007\", RegulatoryReportsUpdateParams.builder()\n .rentityId(\"FIU-US-00123\")\n .currencyCodeLocal(\"USD\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->update('5ec00000-0000-4000-8000-000000000007', [\n 'rentity_id' => 'FIU-US-00123',\n 'currency_code_local' => 'USD',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.regulatory_reports.update(\n \"5ec00000-0000-4000-8000-000000000007\",\n rentity_id: \"FIU-US-00123\",\n currency_code_local: \"USD\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.RegulatoryReports.UpdateAsync(\"5ec00000-0000-4000-8000-000000000007\", new RegulatoryReportsUpdateParams\n{\n RentityId = \"FIU-US-00123\",\n CurrencyCodeLocal = \"USD\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated report.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "409": { + "description": "Report is already finalized.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete report", + "description": "Delete a regulatory report permanently. The report, its copied narrative and reasons, its transaction links, and its generated goAML XML and PDF are erased, including every stored version of those files. The report disappears from lists and download URLs stop working. Other records, including the case, transactions, people and companies, are not deleted. There is no undo.\n\nA draft or validated report can be deleted without a request body. Deleting a finalized report requires `confirm_delete: true` in the request body. If the report is missing, the request returns `404`; if a finalized report is sent without confirmation, it returns `400` and nothing is deleted.", + "operationId": "delete_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl (draft report)", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "curl", + "label": "curl (finalized report)", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"confirm_delete\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.regulatory_reports.delete(\n \"5ec00000-0000-4000-8000-000000000007\",\n confirm_delete=True,\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.regulatoryReports.delete(\"5ec00000-0000-4000-8000-000000000007\", {\n confirm_delete: true,\n});" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.RegulatoryReportsDeleteParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.regulatoryReports().delete(\"5ec00000-0000-4000-8000-000000000007\", RegulatoryReportsDeleteParams.builder()\n .confirmDelete(true)\n .build());" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->delete('5ec00000-0000-4000-8000-000000000007', [\n 'confirm_delete' => true,\n]);" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.regulatory_reports.delete(\n \"5ec00000-0000-4000-8000-000000000007\",\n confirm_delete: true\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.RegulatoryReports.DeleteAsync(\"5ec00000-0000-4000-8000-000000000007\", new RegulatoryReportsDeleteParams\n{\n ConfirmDelete = true,\n});" + } + ], + "responses": { + "204": { + "description": "Report deleted, including its generated files." + }, + "400": { + "description": "A finalized report was sent without `confirm_delete: true`. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "confirm_delete": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "example": { + "confirm_delete": [ + "This field is required to delete a finalized report." + ] + } + } + } + }, + "403": { + "description": "The API key cannot delete reports. The endpoint requires `write:cases`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Report not found in this application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "409": { + "description": "The report is already being deleted. Repeat the request to finish erasing its files.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "This report is being deleted. Retry deleting it to finish erasing its files.", + "code": "report_deletion_pending" + } + } + } + }, + "503": { + "description": "The report files could not be erased right now. The report is still in place. Repeat the request.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "The report files could not be erased. Please retry deleting the report.", + "code": "report_storage_unavailable" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": false, + "description": "Required only for a finalized report.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "confirm_delete": { + "type": "boolean", + "description": "Set to true to confirm that a finalized report and its generated files are deleted permanently." + } + }, + "example": { + "confirm_delete": true + } + }, + "example": { + "confirm_delete": true + } + } + } + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/{report_uuid}/validate/": { + "post": { + "summary": "Validate report", + "description": "Check the report against the obligatory goAML header fields (reporting entity ID, submission code, local currency, action taken, submission date), plus at least one attached transaction and a 2-letter jurisdiction, without generating any files. Also stores the result on the report: validation_errors and validated_at are updated, and a non-finalized report's status moves to VALIDATED (no errors) or back to DRAFT (errors).", + "operationId": "validate_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "validate", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/validate/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.regulatory_reports.validate(\n \"5ec00000-0000-4000-8000-000000000007\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.regulatoryReports.validate(\"5ec00000-0000-4000-8000-000000000007\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.RegulatoryReports.Validate(ctx, \"5ec00000-0000-4000-8000-000000000007\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.regulatoryReports().validate(\"5ec00000-0000-4000-8000-000000000007\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->validate('5ec00000-0000-4000-8000-000000000007');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.regulatory_reports.validate(\"5ec00000-0000-4000-8000-000000000007\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.RegulatoryReports.ValidateAsync(\"5ec00000-0000-4000-8000-000000000007\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Validation result.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "DRAFT", + "VALIDATED", + "FINALIZED" + ] + }, + "is_valid": { + "type": "boolean" + }, + "errors": { + "type": "array", + "description": "Empty when valid.", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/{report_uuid}/finalize/": { + "post": { + "summary": "Finalize report", + "description": "Re-validate, then render the goAML XML and PDF and upload both to S3. On SAR/STR/TFR reports, this also moves the case's transaction alerts from Pending SAR to SAR filed.", + "operationId": "finalize_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "finalize", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/finalize/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.regulatory_reports.finalize(\n \"5ec00000-0000-4000-8000-000000000007\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.regulatoryReports.finalize(\"5ec00000-0000-4000-8000-000000000007\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.RegulatoryReports.Finalize(ctx, \"5ec00000-0000-4000-8000-000000000007\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.regulatoryReports().finalize_(\"5ec00000-0000-4000-8000-000000000007\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->finalize('5ec00000-0000-4000-8000-000000000007');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.regulatory_reports.finalize(\"5ec00000-0000-4000-8000-000000000007\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.RegulatoryReports.FinalizeAsync(\"5ec00000-0000-4000-8000-000000000007\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Report finalized; xml_s3_key and pdf_s3_key are set.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "400": { + "description": "Report failed validation.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "409": { + "description": "Report is not in a finalizable state.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/{report_uuid}/download-url/": { + "get": { + "summary": "Get report download URL", + "description": "Mint a presigned URL for a finalized report's goAML XML or PDF.", + "operationId": "get_regulatory_report_download_url", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "retrieve_download_url", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + }, + { + "name": "file", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "pdf", + "xml" + ], + "default": "pdf" + }, + "description": "Which file to download." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/download-url/?file=xml' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.regulatory_reports.retrieve_download_url(\n \"5ec00000-0000-4000-8000-000000000007\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.regulatoryReports.retrieveDownloadUrl(\"5ec00000-0000-4000-8000-000000000007\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.RegulatoryReports.RetrieveDownloadURL(ctx, \"5ec00000-0000-4000-8000-000000000007\", didit.RegulatoryReportsRetrieveDownloadURLParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.regulatoryReports().retrieveDownloadUrl(\"5ec00000-0000-4000-8000-000000000007\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->retrieveDownloadUrl('5ec00000-0000-4000-8000-000000000007');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.regulatory_reports.retrieve_download_url(\"5ec00000-0000-4000-8000-000000000007\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.RegulatoryReports.RetrieveDownloadUrlAsync(\"5ec00000-0000-4000-8000-000000000007\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Presigned download URL.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "download_url": { + "type": "string", + "format": "uri" + }, + "file": { + "type": "string", + "enum": [ + "pdf", + "xml" + ] + } + } + } + } + } + }, + "409": { + "description": "Report is not finalized yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "status": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/transactions/rules/": { + "get": { + "summary": "List rules", + "description": "List the application's transaction-monitoring rules: your custom rules plus any library presets you have installed. Filter by source, category, mode, bundle or free-text search; order with `ordering` (run_count, approved_pct, reviewed_pct, declined_pct, latest_triggered_at, title, created_at, source, each with an optional `-` prefix).", + "operationId": "listTransactionRules", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "source", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "PRESET", + "CUSTOM" + ] + }, + "description": "PRESET or CUSTOM" + }, + { + "name": "category", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Filter by rule category" + }, + { + "name": "mode", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ] + }, + "description": "ACTIVE, DISABLED or TEST" + }, + { + "name": "bundle", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Filter presets by bundle" + }, + { + "name": "search", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Case-insensitive match against title and description" + }, + { + "name": "ordering", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated ordering fields, default source,-run_count" + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "description": "Page size" + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "description": "Page offset" + } + ], + "responses": { + "200": { + "description": "Paginated rules.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleDetail" + } + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/rules/?source=CUSTOM&ordering=-run_count' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.transactions.rules.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.transactions.rules.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Transactions.Rules.ListAutoPaging(ctx, didit.TransactionsRulesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.transactions().rules().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.rules.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Transactions.Rules.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "summary": "Create rule", + "description": "Create a CUSTOM transaction-monitoring rule. Titles are unique per application (duplicate -> 400). A `change_status` action targeting `AWAITING_USER` must carry `workflow_id`. Rules created in ACTIVE mode apply to new transactions immediately - the recommended workflow is create in TEST mode, backtest, then switch mode to ACTIVE.", + "operationId": "createTransactionRule", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleCreateRequest" + }, + "example": { + "title": "Structuring - inbound (custom)", + "description": "20+ inbound EUR transfers, all under 10,000 EUR, 30 days", + "category": "finance", + "mode": "TEST", + "evaluation_mode": "ALL", + "scope": { + "transaction_types": [ + "finance" + ], + "directions": [ + "INBOUND" + ] + }, + "conditions": [ + { + "field": "currency", + "operator": "eq", + "value": "EUR" + }, + { + "field": "amount", + "operator": "lt", + "value": 10000 + } + ], + "aggregation": [ + { + "metric": "count", + "operator": "gte", + "value": 20, + "window": "30d", + "filters": { + "direction": "INBOUND", + "transaction_type": "finance", + "subject_vendor_data": "__current__", + "currency": "EUR" + } + }, + { + "metric": "max", + "field": "amount", + "operator": "lt", + "value": 10000, + "window": "30d", + "filters": { + "direction": "INBOUND", + "transaction_type": "finance", + "subject_vendor_data": "__current__", + "currency": "EUR" + } + } + ], + "actions": [ + { + "type": "add_score", + "value": 35 + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "The created rule.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleDetail" + } + } + } + }, + "400": { + "description": "Validation error - duplicate title, unknown operator/metric, malformed window, invalid scope direction, or AWAITING_USER without workflow_id.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "title": [ + "A rule with this name already exists." + ] + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/rules/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"title\": \"Structuring - inbound (custom)\", \"description\": \"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\", \"category\": \"finance\", \"mode\": \"TEST\", \"evaluation_mode\": \"ALL\", \"scope\": {\"transaction_types\": [\"finance\"], \"directions\": [\"INBOUND\"]}, \"conditions\": [{\"field\": \"currency\", \"operator\": \"eq\", \"value\": \"EUR\"}, {\"field\": \"amount\", \"operator\": \"lt\", \"value\": 10000}], \"aggregation\": [{\"metric\": \"count\", \"operator\": \"gte\", \"value\": 20, \"window\": \"30d\", \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}}, {\"metric\": \"max\", \"field\": \"amount\", \"operator\": \"lt\", \"value\": 10000, \"window\": \"30d\", \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}}], \"actions\": [{\"type\": \"add_score\", \"value\": 35}]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.rules.create(\n title=\"Structuring - inbound (custom)\",\n description=\"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\",\n category=\"finance\",\n mode=\"TEST\",\n evaluation_mode=\"ALL\",\n scope={\n \"transaction_types\": [\"finance\"],\n \"directions\": [\"INBOUND\"],\n },\n conditions=[{\n \"field\": \"currency\",\n \"operator\": \"eq\",\n \"value\": \"EUR\",\n }, {\n \"field\": \"amount\",\n \"operator\": \"lt\",\n \"value\": 10000,\n }],\n aggregation=[{\n \"metric\": \"count\",\n \"operator\": \"gte\",\n \"value\": 20.0,\n \"window\": \"30d\",\n \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"},\n }, {\n \"metric\": \"max\",\n \"field\": \"amount\",\n \"operator\": \"lt\",\n \"value\": 10000.0,\n \"window\": \"30d\",\n \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"},\n }],\n actions=[{\n \"type\": \"add_score\",\n \"value\": 35,\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.rules.create({\n title: \"Structuring - inbound (custom)\",\n description: \"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\",\n category: \"finance\",\n mode: \"TEST\",\n evaluation_mode: \"ALL\",\n scope: {\n transaction_types: [\"finance\"],\n directions: [\"INBOUND\"],\n },\n conditions: [{\n field: \"currency\",\n operator: \"eq\",\n value: \"EUR\",\n }, {\n field: \"amount\",\n operator: \"lt\",\n value: 10000,\n }],\n aggregation: [{\n metric: \"count\",\n operator: \"gte\",\n value: 20.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }, {\n metric: \"max\",\n field: \"amount\",\n operator: \"lt\",\n value: 10000.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }],\n actions: [{\n type: \"add_score\",\n value: 35,\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Rules.Create(ctx, didit.TransactionRuleCreateRequest{\n\tTitle: \"Structuring - inbound (custom)\",\n\tDescription: didit.String(\"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\"),\n\tCategory: \"finance\",\n\tMode: \"TEST\",\n\tEvaluationMode: didit.Some(didit.TransactionRuleCreateRequestEvaluationMode(\"ALL\")),\n\tScope: didit.Some(didit.TransactionRuleScopeParam{\n\t\tTransactionTypes: didit.Some([]string{\"finance\"}),\n\t\tDirections: didit.Some([]didit.TransactionRuleScopeDirections{\"INBOUND\"}),\n\t}),\n\tConditions: didit.Some([]didit.TransactionRuleConditionParam{{\n\t\tField: \"currency\",\n\t\tOperator: \"eq\",\n\t\tValue: didit.Some[any](\"EUR\"),\n\t}, {\n\t\tField: \"amount\",\n\t\tOperator: \"lt\",\n\t\tValue: didit.Some[any](10000),\n\t}}),\n\tAggregation: didit.Some([]didit.TransactionRuleAggregationParam{{\n\t\tMetric: didit.Some(didit.TransactionRuleAggregationMetric(\"count\")),\n\t\tOperator: didit.Some(didit.TransactionRuleAggregationOperator(\"gte\")),\n\t\tValue: 20.0,\n\t\tWindow: didit.String(\"30d\"),\n\t\tFilters: didit.Some(map[string]any{\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}),\n\t}, {\n\t\tMetric: didit.Some(didit.TransactionRuleAggregationMetric(\"max\")),\n\t\tField: didit.String(\"amount\"),\n\t\tOperator: didit.Some(didit.TransactionRuleAggregationOperator(\"lt\")),\n\t\tValue: 10000.0,\n\t\tWindow: didit.String(\"30d\"),\n\t\tFilters: didit.Some(map[string]any{\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}),\n\t}}),\n\tActions: didit.Some([]didit.TransactionRuleActionParam{{\n\t\tType: \"add_score\",\n\t\tValue: didit.Some[any](35),\n\t}}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.TransactionRuleAction;\nimport me.didit.types.TransactionRuleActionAddScore;\nimport me.didit.types.TransactionRuleAggregation;\nimport me.didit.types.TransactionRuleCondition;\nimport me.didit.types.TransactionRuleCreateRequest;\nimport me.didit.types.TransactionRuleScope;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().rules().create(TransactionRuleCreateRequest.builder()\n .title(\"Structuring - inbound (custom)\")\n .description(\"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\")\n .category(\"finance\")\n .mode(TransactionRuleCreateRequest.Mode.of(\"TEST\"))\n .evaluationMode(TransactionRuleCreateRequest.EvaluationMode.of(\"ALL\"))\n .scope(TransactionRuleScope.builder()\n .transactionTypes(List.of(\"finance\"))\n .directions(List.of(TransactionRuleScope.Directions.of(\"INBOUND\")))\n .build())\n .conditions(List.of(\n TransactionRuleCondition.builder()\n .field(\"currency\")\n .operator(TransactionRuleCondition.Operator.of(\"eq\"))\n .value(\"EUR\")\n .build(),\n TransactionRuleCondition.builder()\n .field(\"amount\")\n .operator(TransactionRuleCondition.Operator.of(\"lt\"))\n .value(10000)\n .build()))\n .aggregation(List.of(\n TransactionRuleAggregation.builder()\n .metric(TransactionRuleAggregation.Metric.of(\"count\"))\n .operator(TransactionRuleAggregation.Operator.of(\"gte\"))\n .value(20.0)\n .window(\"30d\")\n .filters(Map.of(\"direction\", \"INBOUND\", \"transaction_type\", \"finance\", \"subject_vendor_data\", \"__current__\", \"currency\", \"EUR\"))\n .build(),\n TransactionRuleAggregation.builder()\n .metric(TransactionRuleAggregation.Metric.of(\"max\"))\n .field(\"amount\")\n .operator(TransactionRuleAggregation.Operator.of(\"lt\"))\n .value(10000.0)\n .window(\"30d\")\n .filters(Map.of(\"direction\", \"INBOUND\", \"transaction_type\", \"finance\", \"subject_vendor_data\", \"__current__\", \"currency\", \"EUR\"))\n .build()))\n .actions(List.of(TransactionRuleAction.ofAddScore(TransactionRuleActionAddScore.builder()\n .type(TransactionRuleActionAddScore.Type.of(\"add_score\"))\n .value(35L)\n .build())))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->create([\n 'title' => 'Structuring - inbound (custom)',\n 'description' => '20+ inbound EUR transfers, all under 10,000 EUR, 30 days',\n 'category' => 'finance',\n 'mode' => 'TEST',\n 'evaluation_mode' => 'ALL',\n 'scope' => [\n 'transaction_types' => ['finance'],\n 'directions' => ['INBOUND'],\n ],\n 'conditions' => [[\n 'field' => 'currency',\n 'operator' => 'eq',\n 'value' => 'EUR',\n ], [\n 'field' => 'amount',\n 'operator' => 'lt',\n 'value' => 10000,\n ]],\n 'aggregation' => [[\n 'metric' => 'count',\n 'operator' => 'gte',\n 'value' => 20.0,\n 'window' => '30d',\n 'filters' => ['direction' => 'INBOUND', 'transaction_type' => 'finance', 'subject_vendor_data' => '__current__', 'currency' => 'EUR'],\n ], [\n 'metric' => 'max',\n 'field' => 'amount',\n 'operator' => 'lt',\n 'value' => 10000.0,\n 'window' => '30d',\n 'filters' => ['direction' => 'INBOUND', 'transaction_type' => 'finance', 'subject_vendor_data' => '__current__', 'currency' => 'EUR'],\n ]],\n 'actions' => [[\n 'type' => 'add_score',\n 'value' => 35,\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.rules.create(\n title: \"Structuring - inbound (custom)\",\n description: \"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\",\n category: \"finance\",\n mode: \"TEST\",\n evaluation_mode: \"ALL\",\n scope: {\n transaction_types: [\"finance\"],\n directions: [\"INBOUND\"],\n },\n conditions: [{\n field: \"currency\",\n operator: \"eq\",\n value: \"EUR\",\n }, {\n field: \"amount\",\n operator: \"lt\",\n value: 10000,\n }],\n aggregation: [{\n metric: \"count\",\n operator: \"gte\",\n value: 20.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }, {\n metric: \"max\",\n field: \"amount\",\n operator: \"lt\",\n value: 10000.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }],\n actions: [{\n type: \"add_score\",\n value: 35,\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Rules.CreateAsync(new TransactionRuleCreateRequest\n{\n Title = \"Structuring - inbound (custom)\",\n Description = \"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\",\n Category = \"finance\",\n Mode = \"TEST\",\n EvaluationMode = new TransactionRuleCreateRequestEvaluationMode(\"ALL\"),\n Scope = new TransactionRuleScope\n {\n TransactionTypes = new string[] { \"finance\" },\n Directions = new TransactionRuleScopeDirections[] { \"INBOUND\" },\n },\n Conditions = new TransactionRuleCondition[]\n {\n new()\n {\n Field = \"currency\",\n Operator = \"eq\",\n Value = \"EUR\",\n },\n new()\n {\n Field = \"amount\",\n Operator = \"lt\",\n Value = 10000,\n },\n },\n Aggregation = new TransactionRuleAggregation[]\n {\n new()\n {\n Metric = new TransactionRuleAggregationMetric(\"count\"),\n Operator = new TransactionRuleAggregationOperator(\"gte\"),\n Value = 20.0,\n Window = \"30d\",\n Filters = new Dictionary { [\"direction\"] = \"INBOUND\", [\"transaction_type\"] = \"finance\", [\"subject_vendor_data\"] = \"__current__\", [\"currency\"] = \"EUR\" },\n },\n new()\n {\n Metric = new TransactionRuleAggregationMetric(\"max\"),\n Field = \"amount\",\n Operator = new TransactionRuleAggregationOperator(\"lt\"),\n Value = 10000.0,\n Window = \"30d\",\n Filters = new Dictionary { [\"direction\"] = \"INBOUND\", [\"transaction_type\"] = \"finance\", [\"subject_vendor_data\"] = \"__current__\", [\"currency\"] = \"EUR\" },\n },\n },\n Actions = new TransactionRuleAction[]\n {\n new()\n {\n Type = new TransactionRuleActionType(\"add_score\"),\n Value = 35,\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/transactions/rules/{rule_uuid}/": { + "parameters": [ + { + "name": "rule_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The rule's uuid from list or create." + } + ], + "get": { + "summary": "Get rule", + "description": "Retrieve one rule with its full configuration and execution counters.", + "operationId": "getTransactionRule", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "The rule.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleDetail" + } + } + } + }, + "404": { + "description": "No such rule on this application.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/rules/RULE_UUID/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.rules.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.rules.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Rules.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().rules().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.rules.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Rules.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "patch": { + "summary": "Update rule", + "description": "Partially update a rule. Custom rules accept every configuration field. Library (PRESET) rules accept only `mode` - use it to activate, disable, or switch a preset to TEST; any other field returns 400. An installed copy of a withheld library rule is switched off: setting its `mode` to anything but `DISABLED` answers 400 (\"This library rule cannot run yet: its conditions read data the rules engine cannot evaluate.\").", + "operationId": "updateTransactionRule", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleUpdateRequest" + }, + "example": { + "mode": "ACTIVE" + } + } + } + }, + "responses": { + "200": { + "description": "The updated rule.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleDetail" + } + } + } + }, + "400": { + "description": "Validation error, a non-mode field on a library rule, or a mode other than DISABLED on a withheld library rule.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/transactions/rules/RULE_UUID/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"mode\": \"ACTIVE\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.rules.update(\n \"11111111-2222-3333-4444-555555555555\",\n mode=\"ACTIVE\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.rules.update(\"11111111-2222-3333-4444-555555555555\", {\n mode: \"ACTIVE\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Rules.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TransactionRuleUpdateRequest{\n\tMode: didit.Some(didit.TransactionRuleUpdateRequestMode(\"ACTIVE\")),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TransactionRuleUpdateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().rules().update(\"11111111-2222-3333-4444-555555555555\", TransactionRuleUpdateRequest.builder()\n .mode(TransactionRuleUpdateRequest.Mode.of(\"ACTIVE\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->update('11111111-2222-3333-4444-555555555555', [\n 'mode' => 'ACTIVE',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.rules.update(\n \"11111111-2222-3333-4444-555555555555\",\n mode: \"ACTIVE\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Rules.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new TransactionRuleUpdateRequest\n{\n Mode = new TransactionRuleUpdateRequestMode(\"ACTIVE\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "summary": "Delete rule", + "description": "Delete a CUSTOM rule. Deletion is immediate and permanent: the rule stops evaluating for new transactions and disappears from listings, while past rule runs on already-evaluated transactions keep their recorded results. Library rules cannot be deleted - uninstall them via the install endpoint instead (they can always be reinstalled from the library).", + "operationId": "deleteTransactionRule", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "204": { + "description": "Deleted." + }, + "400": { + "description": "The rule is a library preset - uninstall instead.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "detail": "Library rules cannot be deleted. Uninstall them via the rules install endpoint instead." + } + } + } + }, + "404": { + "description": "No such rule on this application.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/transactions/rules/RULE_UUID/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.transactions.rules.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.transactions.rules.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Transactions.Rules.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.transactions().rules().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.rules.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Transactions.Rules.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ] + } + }, + "/v3/transactions/rules/backtest/": { + "post": { + "summary": "Backtest rule configuration", + "description": "Evaluate a hypothetical rule configuration against your recent transactions (most recent first, up to the backtest cap) without creating a rule or touching any transaction. Use it before activating a rule to see how often it would have matched and how many subjects it would have affected.", + "operationId": "backtestTransactionRules", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "backtest", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleBacktestRequest" + }, + "example": { + "conditions": [ + { + "field": "currency", + "operator": "eq", + "value": "EUR" + }, + { + "field": "amount", + "operator": "lt", + "value": 10000 + } + ], + "aggregation": [ + { + "metric": "count", + "operator": "gte", + "value": 20, + "window": "30d", + "filters": { + "direction": "INBOUND", + "transaction_type": "finance", + "subject_vendor_data": "__current__", + "currency": "EUR" + } + }, + { + "metric": "max", + "field": "amount", + "operator": "lt", + "value": 10000, + "window": "30d", + "filters": { + "direction": "INBOUND", + "transaction_type": "finance", + "subject_vendor_data": "__current__", + "currency": "EUR" + } + } + ], + "evaluation_mode": "ALL", + "scope": { + "transaction_types": [ + "finance" + ], + "directions": [ + "INBOUND" + ] + }, + "period_days": 90 + } + } + } + }, + "responses": { + "200": { + "description": "Backtest statistics.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleBacktestResult" + }, + "example": { + "evaluated": 4820, + "matched": 12, + "affected_entities": 4, + "period_days": 90 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/rules/backtest/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"conditions\": [{\"field\": \"currency\", \"operator\": \"eq\", \"value\": \"EUR\"}, {\"field\": \"amount\", \"operator\": \"lt\", \"value\": 10000}], \"aggregation\": [{\"metric\": \"count\", \"operator\": \"gte\", \"value\": 20, \"window\": \"30d\", \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}}, {\"metric\": \"max\", \"field\": \"amount\", \"operator\": \"lt\", \"value\": 10000, \"window\": \"30d\", \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}}], \"evaluation_mode\": \"ALL\", \"scope\": {\"transaction_types\": [\"finance\"], \"directions\": [\"INBOUND\"]}, \"period_days\": 90}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.rules.backtest(\n conditions=[{\n \"field\": \"currency\",\n \"operator\": \"eq\",\n \"value\": \"EUR\",\n }, {\n \"field\": \"amount\",\n \"operator\": \"lt\",\n \"value\": 10000,\n }],\n aggregation=[{\n \"metric\": \"count\",\n \"operator\": \"gte\",\n \"value\": 20.0,\n \"window\": \"30d\",\n \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"},\n }, {\n \"metric\": \"max\",\n \"field\": \"amount\",\n \"operator\": \"lt\",\n \"value\": 10000.0,\n \"window\": \"30d\",\n \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"},\n }],\n evaluation_mode=\"ALL\",\n scope={\n \"transaction_types\": [\"finance\"],\n \"directions\": [\"INBOUND\"],\n },\n period_days=90,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.rules.backtest({\n conditions: [{\n field: \"currency\",\n operator: \"eq\",\n value: \"EUR\",\n }, {\n field: \"amount\",\n operator: \"lt\",\n value: 10000,\n }],\n aggregation: [{\n metric: \"count\",\n operator: \"gte\",\n value: 20.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }, {\n metric: \"max\",\n field: \"amount\",\n operator: \"lt\",\n value: 10000.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }],\n evaluation_mode: \"ALL\",\n scope: {\n transaction_types: [\"finance\"],\n directions: [\"INBOUND\"],\n },\n period_days: 90,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Rules.Backtest(ctx, didit.TransactionRuleBacktestRequest{\n\tConditions: didit.Some([]didit.TransactionRuleConditionParam{{\n\t\tField: \"currency\",\n\t\tOperator: \"eq\",\n\t\tValue: didit.Some[any](\"EUR\"),\n\t}, {\n\t\tField: \"amount\",\n\t\tOperator: \"lt\",\n\t\tValue: didit.Some[any](10000),\n\t}}),\n\tAggregation: didit.Some([]didit.TransactionRuleAggregationParam{{\n\t\tMetric: didit.Some(didit.TransactionRuleAggregationMetric(\"count\")),\n\t\tOperator: didit.Some(didit.TransactionRuleAggregationOperator(\"gte\")),\n\t\tValue: 20.0,\n\t\tWindow: didit.String(\"30d\"),\n\t\tFilters: didit.Some(map[string]any{\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}),\n\t}, {\n\t\tMetric: didit.Some(didit.TransactionRuleAggregationMetric(\"max\")),\n\t\tField: didit.String(\"amount\"),\n\t\tOperator: didit.Some(didit.TransactionRuleAggregationOperator(\"lt\")),\n\t\tValue: 10000.0,\n\t\tWindow: didit.String(\"30d\"),\n\t\tFilters: didit.Some(map[string]any{\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}),\n\t}}),\n\tEvaluationMode: didit.Some(didit.TransactionRuleBacktestRequestEvaluationMode(\"ALL\")),\n\tScope: didit.Some(didit.TransactionRuleScopeParam{\n\t\tTransactionTypes: didit.Some([]string{\"finance\"}),\n\t\tDirections: didit.Some([]didit.TransactionRuleScopeDirections{\"INBOUND\"}),\n\t}),\n\tPeriodDays: didit.Int(90),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.TransactionRuleAggregation;\nimport me.didit.types.TransactionRuleBacktestRequest;\nimport me.didit.types.TransactionRuleCondition;\nimport me.didit.types.TransactionRuleScope;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().rules().backtest(TransactionRuleBacktestRequest.builder()\n .conditions(List.of(\n TransactionRuleCondition.builder()\n .field(\"currency\")\n .operator(TransactionRuleCondition.Operator.of(\"eq\"))\n .value(\"EUR\")\n .build(),\n TransactionRuleCondition.builder()\n .field(\"amount\")\n .operator(TransactionRuleCondition.Operator.of(\"lt\"))\n .value(10000)\n .build()))\n .aggregation(List.of(\n TransactionRuleAggregation.builder()\n .metric(TransactionRuleAggregation.Metric.of(\"count\"))\n .operator(TransactionRuleAggregation.Operator.of(\"gte\"))\n .value(20.0)\n .window(\"30d\")\n .filters(Map.of(\"direction\", \"INBOUND\", \"transaction_type\", \"finance\", \"subject_vendor_data\", \"__current__\", \"currency\", \"EUR\"))\n .build(),\n TransactionRuleAggregation.builder()\n .metric(TransactionRuleAggregation.Metric.of(\"max\"))\n .field(\"amount\")\n .operator(TransactionRuleAggregation.Operator.of(\"lt\"))\n .value(10000.0)\n .window(\"30d\")\n .filters(Map.of(\"direction\", \"INBOUND\", \"transaction_type\", \"finance\", \"subject_vendor_data\", \"__current__\", \"currency\", \"EUR\"))\n .build()))\n .evaluationMode(TransactionRuleBacktestRequest.EvaluationMode.of(\"ALL\"))\n .scope(TransactionRuleScope.builder()\n .transactionTypes(List.of(\"finance\"))\n .directions(List.of(TransactionRuleScope.Directions.of(\"INBOUND\")))\n .build())\n .periodDays(90L)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->backtest([\n 'conditions' => [[\n 'field' => 'currency',\n 'operator' => 'eq',\n 'value' => 'EUR',\n ], [\n 'field' => 'amount',\n 'operator' => 'lt',\n 'value' => 10000,\n ]],\n 'aggregation' => [[\n 'metric' => 'count',\n 'operator' => 'gte',\n 'value' => 20.0,\n 'window' => '30d',\n 'filters' => ['direction' => 'INBOUND', 'transaction_type' => 'finance', 'subject_vendor_data' => '__current__', 'currency' => 'EUR'],\n ], [\n 'metric' => 'max',\n 'field' => 'amount',\n 'operator' => 'lt',\n 'value' => 10000.0,\n 'window' => '30d',\n 'filters' => ['direction' => 'INBOUND', 'transaction_type' => 'finance', 'subject_vendor_data' => '__current__', 'currency' => 'EUR'],\n ]],\n 'evaluation_mode' => 'ALL',\n 'scope' => [\n 'transaction_types' => ['finance'],\n 'directions' => ['INBOUND'],\n ],\n 'period_days' => 90,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.rules.backtest(\n conditions: [{\n field: \"currency\",\n operator: \"eq\",\n value: \"EUR\",\n }, {\n field: \"amount\",\n operator: \"lt\",\n value: 10000,\n }],\n aggregation: [{\n metric: \"count\",\n operator: \"gte\",\n value: 20.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }, {\n metric: \"max\",\n field: \"amount\",\n operator: \"lt\",\n value: 10000.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }],\n evaluation_mode: \"ALL\",\n scope: {\n transaction_types: [\"finance\"],\n directions: [\"INBOUND\"],\n },\n period_days: 90\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Rules.BacktestAsync(new TransactionRuleBacktestRequest\n{\n Conditions = new TransactionRuleCondition[]\n {\n new()\n {\n Field = \"currency\",\n Operator = \"eq\",\n Value = \"EUR\",\n },\n new()\n {\n Field = \"amount\",\n Operator = \"lt\",\n Value = 10000,\n },\n },\n Aggregation = new TransactionRuleAggregation[]\n {\n new()\n {\n Metric = new TransactionRuleAggregationMetric(\"count\"),\n Operator = new TransactionRuleAggregationOperator(\"gte\"),\n Value = 20.0,\n Window = \"30d\",\n Filters = new Dictionary { [\"direction\"] = \"INBOUND\", [\"transaction_type\"] = \"finance\", [\"subject_vendor_data\"] = \"__current__\", [\"currency\"] = \"EUR\" },\n },\n new()\n {\n Metric = new TransactionRuleAggregationMetric(\"max\"),\n Field = \"amount\",\n Operator = new TransactionRuleAggregationOperator(\"lt\"),\n Value = 10000.0,\n Window = \"30d\",\n Filters = new Dictionary { [\"direction\"] = \"INBOUND\", [\"transaction_type\"] = \"finance\", [\"subject_vendor_data\"] = \"__current__\", [\"currency\"] = \"EUR\" },\n },\n },\n EvaluationMode = new TransactionRuleBacktestRequestEvaluationMode(\"ALL\"),\n Scope = new TransactionRuleScope\n {\n TransactionTypes = new string[] { \"finance\" },\n Directions = new TransactionRuleScopeDirections[] { \"INBOUND\" },\n },\n PeriodDays = 90,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/transactions/rules/library/": { + "get": { + "summary": "List rule library", + "description": "Browse the full preset rule library (150+ rules) with each entry's complete configuration and an `is_installed` flag for this application. Filter by bundle, category, or free-text search. A library rule whose conditions read data the rules engine cannot evaluate yet is withheld until it can run. Withheld rules are not listed.", + "operationId": "listTransactionRuleLibrary", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "list_library", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "bundle", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Filter by bundle, e.g. finance, aml_ctf, crypto_monitoring, travel_rule" + }, + { + "name": "category", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Filter by category" + }, + { + "name": "search", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Case-insensitive match against title and description" + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "description": "Page size" + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "description": "Page offset" + } + ], + "responses": { + "200": { + "description": "Paginated library entries.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleLibraryEntry" + } + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/rules/library/?bundle=finance' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.transactions.rules.list_library():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.transactions.rules.listLibrary()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Transactions.Rules.ListLibraryAutoPaging(ctx, didit.TransactionsRulesListLibraryParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.transactions().rules().listLibrary()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->listLibrary() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.rules.list_library.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Transactions.Rules.ListLibraryAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/transactions/rules/install/": { + "post": { + "summary": "Install library rules", + "description": "Install one or more presets onto the application, by `library_keys` or a whole `bundle`. An installed preset is active at once while transaction monitoring is on, and disabled otherwise; already-installed keys are skipped. A library rule whose conditions read data the rules engine cannot evaluate yet is withheld until it can run. A request that names one in `library_keys` answers 400 and installs nothing (\"These library rules cannot run yet: their conditions read data the rules engine cannot evaluate.\" followed by the keys), and a `bundle` install skips it. A withheld rule comes back on its own once its conditions can be evaluated. A `library_keys` entry that is not in the library is skipped, not refused.", + "operationId": "installTransactionRuleLibrary", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "install_library", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleInstallRequest" + }, + "example": { + "library_keys": [ + "structuring-inbound", + "structuring-outbound" + ] + } + } + } + }, + "responses": { + "200": { + "description": "What was installed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleInstallResult" + }, + "example": { + "installed_library_keys": [ + "structuring-inbound", + "structuring-outbound" + ], + "installed_count": 2 + } + } + } + }, + "400": { + "description": "Neither library_keys nor bundle provided, `library_keys` is not a list, or a `library_keys` entry is withheld. An unknown entry is skipped and answers 200 without it.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/rules/install/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"library_keys\": [\"structuring-inbound\", \"structuring-outbound\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.rules.install_library(\n library_keys=[\"structuring-inbound\", \"structuring-outbound\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.rules.installLibrary({\n library_keys: [\"structuring-inbound\", \"structuring-outbound\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Rules.InstallLibrary(ctx, didit.TransactionRuleInstallRequest{\n\tLibraryKeys: didit.Some([]string{\"structuring-inbound\", \"structuring-outbound\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.TransactionRuleInstallRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().rules().installLibrary(TransactionRuleInstallRequest.builder()\n .libraryKeys(List.of(\"structuring-inbound\", \"structuring-outbound\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->installLibrary([\n 'library_keys' => ['structuring-inbound', 'structuring-outbound'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.rules.install_library(library_keys: [\"structuring-inbound\", \"structuring-outbound\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Rules.InstallLibraryAsync(new TransactionRuleInstallRequest\n{\n LibraryKeys = new string[] { \"structuring-inbound\", \"structuring-outbound\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "summary": "Uninstall library rules", + "description": "Remove installed presets from the application, by `library_keys` or a whole `bundle`. Uninstalled presets stop evaluating immediately and can be reinstalled from the library at any time.", + "operationId": "uninstallTransactionRuleLibrary", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "uninstall_library", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleInstallRequest" + }, + "example": { + "bundle": "responsible_gaming" + } + } + } + }, + "responses": { + "200": { + "description": "How many presets were removed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleInstallResult" + }, + "example": { + "uninstalled_count": 5 + } + } + } + }, + "400": { + "description": "Neither library_keys nor bundle provided.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/transactions/rules/install/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"bundle\": \"responsible_gaming\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.rules.uninstall_library(\n bundle=\"responsible_gaming\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.rules.uninstallLibrary({\n bundle: \"responsible_gaming\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Rules.UninstallLibrary(ctx, didit.TransactionRuleInstallRequest{\n\tBundle: didit.String(\"responsible_gaming\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TransactionRuleInstallRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().rules().uninstallLibrary(TransactionRuleInstallRequest.builder()\n .bundle(\"responsible_gaming\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->uninstallLibrary([\n 'bundle' => 'responsible_gaming',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.rules.uninstall_library(bundle: \"responsible_gaming\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Rules.UninstallLibraryAsync(new TransactionRuleInstallRequest\n{\n Bundle = \"responsible_gaming\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/": { + "get": { + "summary": "List networks", + "description": "List fraud networks detected within this application's organization, highest risk first by default. By default dismissed networks are excluded; pass status to include them. Sandbox applications never have networks: clustering excludes sandbox data entirely. Every response carries the organization's `sharing_mode` and whether cross-organization signals apply to it (`cross_org_available`, `cross_org_unavailable_reason`). `kpis` aggregate the networks that match your filters and `counts` aggregate every network the application can see; pass `summary=false` to skip both when you only need the page. Requires a full-access API key, or `list:networks` for a console member.", + "operationId": "list_networks", + "x-didit-sdk": { + "resource": "networks", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "status", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated statuses to include: active,in_review,resolved,dismissed. Omit to exclude dismissed." + }, + { + "name": "scope", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "identity", + "transactions" + ] + }, + "description": "`transactions` keeps networks whose members are all transaction parties; `identity` keeps every other network. Omit for both. It also narrows `counts.views` and `counts.facets`. Any other value returns 400." + }, + { + "name": "signal_type", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated signal types: ip_address,device,face,document_number,phone,email,address." + }, + { + "name": "pattern_type", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated pattern types, e.g. exact_same_device,same_email." + }, + { + "name": "risk_band", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated bands: low,medium,high." + }, + { + "name": "min_size", + "in": "query", + "schema": { + "type": "integer" + }, + "description": "Minimum member count." + }, + { + "name": "max_size", + "in": "query", + "schema": { + "type": "integer" + }, + "description": "Maximum member count." + }, + { + "name": "assignee", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated assignees: `unassigned`, member emails (matched case-insensitively), or `me` for the calling console member. A network matches any value. `me` needs a console member token: an API key gets 400 `{\"assignee\": [\"\\\"me\\\" needs a user token; an API key has no assignee identity.\"]}`." + }, + { + "name": "priority", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated priorities: `low`, `medium`, `high`, or `none` for networks without a priority. Any other value returns 400 `{\"priority\": \"Unknown priority: .\"}`." + }, + { + "name": "risk_min", + "in": "query", + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "description": "Minimum risk score, a whole number from 0 to 100. A value that is not a whole number returns 400 `{\"risk_min\": \"Use a whole number from 0 to 100.\"}`." + }, + { + "name": "has_case", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "`true` keeps the networks your application opened a case for, `false` the others. Any other value is ignored." + }, + { + "name": "date_from", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Lower bound on last_activity_at (ISO 8601 date or datetime)." + }, + { + "name": "date_to", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Upper bound on first_activity_at (ISO 8601 date or datetime)." + }, + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Free-text search across network name, member names, the network_id (N-0001), and exact pattern_type tokens." + }, + { + "name": "ordering", + "in": "query", + "schema": { + "type": "string" + }, + "description": "One of risk_score, -risk_score, last_activity_at, -last_activity_at, first_activity_at, -first_activity_at, network_number, -network_number. Defaults to -risk_score,-last_activity_at." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 100, + "minimum": 1, + "maximum": 500 + }, + "description": "Page size, 1-500. Defaults to 100." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset of the first record." + }, + { + "name": "summary", + "in": "query", + "schema": { + "type": "boolean", + "default": true + }, + "description": "`false` returns the page and its `count` only, without `kpis` and `counts`. Defaults to `true`. Any value other than `true` or `false` returns 400." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/?status=active,in_review&ordering=-risk_score' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.networks.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.networks.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Networks.ListAutoPaging(ctx, didit.NetworksListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.networks().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.networks.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Networks.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated network list, with the organization's sharing mode and cross-organization availability, plus `kpis` and `counts` unless `summary=false`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "integer", + "nullable": true, + "description": "Offset of the next page, or null on the last page." + }, + "previous": { + "type": "integer", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkListItem" + } + }, + "kpis": { + "type": "object", + "description": "Each key is {value, delta} over the results matching the current filters. Omitted when `summary=false`.", + "properties": { + "active": { + "type": "object" + }, + "in_review": { + "type": "object" + }, + "applicants_involved": { + "type": "object" + }, + "detected_this_period": { + "type": "object" + }, + "false_positive_rate": { + "type": "object" + } + } + }, + "counts": { + "$ref": "#/components/schemas/NetworkListCounts" + }, + "sharing_mode": { + "type": "string", + "enum": [ + "off", + "private", + "global" + ], + "description": "The organization's cross-organization sharing mode. Only `global` compares networks across organizations; `private` and `off` keep them to the organization's own history.", + "example": "global" + }, + "cross_org_available": { + "type": "boolean", + "description": "Whether cross-organization signals apply to this organization. They need both the Enterprise plan and the `global` sharing mode.", + "example": true + }, + "cross_org_unavailable_reason": { + "type": "string", + "enum": [ + "plan", + "sharing_mode" + ], + "nullable": true, + "description": "Why cross-organization signals do not apply, checked in this order. `plan`: the organization's plan does not include the cross-organization fraud network (it needs the Enterprise plan), whatever the sharing mode. `sharing_mode`: the plan includes it, but the sharing mode is `private` or `off`. Null when `cross_org_available` is `true`.", + "example": null + } + }, + "required": [ + "count", + "next", + "previous", + "results", + "sharing_mode", + "cross_org_available", + "cross_org_unavailable_reason" + ] + } + } + } + }, + "400": { + "description": "An invalid filter, ordering, pagination or `summary` value.", + "content": { + "application/json": { + "examples": { + "Invalid summary": { + "summary": "Invalid summary", + "value": { + "summary": [ + "Must be \"true\" or \"false\"." + ] + } + }, + "Invalid scope": { + "summary": "Invalid scope", + "value": { + "scope": [ + "Use identity or transactions." + ] + } + }, + "Unsupported ordering": { + "summary": "Unsupported ordering", + "value": [ + "Unsupported ordering." + ] + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `list:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/": { + "get": { + "summary": "Get network", + "description": "Retrieve a single network's full detail: stats, fraud tags and risk signals, plus the organization's `sharing_mode` and cross-organization availability, your application's `tags` on the network, member `outcomes` and pattern scores (`patterns`). Requires a full-access API key, or `read:networks` for a console member.", + "operationId": "get_network", + "x-didit-sdk": { + "resource": "networks", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.retrieve(\n \"n3a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.retrieve(\"n3a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.Retrieve(ctx, \"n3a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().retrieve(\"n3a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->retrieve('n3a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.retrieve(\"n3a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.RetrieveAsync(\"n3a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Network detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkDetailResponse" + }, + "example": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "network_id": "N-0042", + "name": null, + "status": "in_review", + "risk": { + "score": 82, + "band": "high", + "bar": 0.82 + }, + "pattern_types": [ + "exact_same_device", + "same_email" + ], + "signal_types": [ + "device", + "email" + ], + "member_counts": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signal_coverage": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + }, + "first_activity_at": "2026-10-01T09:12:44Z", + "last_activity_at": "2026-10-12T16:03:10Z", + "stats": { + "members": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signals": { + "total": 2, + "by_type": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + } + } + }, + "fraud_tags": [], + "risk_signals": { + "residential_proxy_reuse": { + "detected": false, + "signal_count": 0, + "max_subject_count": 0 + }, + "email_intelligence": { + "enriched_signal_count": 1, + "high_risk_count": 0, + "social_profile_count": 0 + }, + "phone_intelligence": { + "enriched_signal_count": 0, + "high_risk_count": 0 + } + }, + "reviewed_at": "2026-10-12T16:30:00Z", + "reviewed_by": "reviewer@example.com", + "review_reason": "Shared device across three new accounts.", + "notes_count": 0, + "sharing_mode": "global", + "cross_org_available": true, + "cross_org_unavailable_reason": null, + "tags": [ + { + "uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "name": "SUSPICIOUS", + "color": "#6366F1", + "source": "custom", + "added_by": null, + "added_at": "2026-10-13T08:15:00Z" + } + ], + "outcomes": { + "approved": 1, + "declined": 1, + "in_review": 0, + "total": 3 + }, + "patterns": [ + { + "type": "background", + "score": 0.8125, + "members": 3, + "reason": null, + "computed_at": "2026-10-13T08:00:00Z" + } + ] + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `read:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/graph/": { + "get": { + "summary": "Get network graph", + "description": "Retrieve the network as a graph of subject nodes (vendor users, vendor businesses, transaction parties) and signal nodes (the shared device/IP/face/document/phone/email/address values linking them), with edges representing memberships.", + "operationId": "get_network_graph", + "x-didit-sdk": { + "resource": "networks", + "method": "retrieve_graph", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + }, + { + "name": "depth", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "1", + "2", + "3", + "all" + ] + }, + "description": "Hops from the focus subject to include. Defaults to all (unbounded)." + }, + { + "name": "focus_kind", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "vendor_user", + "vendor_business", + "transaction_party" + ] + }, + "description": "Subject type to center the graph on. Must be supplied together with focus_id; defaults to an arbitrary member when depth is bounded and neither is supplied." + }, + { + "name": "focus_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Subject UUID to center the graph on." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/graph/?depth=2' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.retrieve_graph(\n \"n3a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.retrieveGraph(\"n3a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.RetrieveGraph(ctx, \"n3a5e000-0000-4000-8000-000000000001\", didit.NetworksRetrieveGraphParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().retrieveGraph(\"n3a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->retrieveGraph('n3a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.retrieve_graph(\"n3a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.RetrieveGraphAsync(\"n3a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Network graph.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkGraph" + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/members/": { + "get": { + "summary": "List network members", + "description": "List the subjects (vendor users, vendor businesses, or transaction parties) that belong to this network.", + "operationId": "list_network_members", + "x-didit-sdk": { + "resource": "networks", + "method": "list_members", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/members/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.list_members(\n \"n3a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.listMembers(\"n3a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.ListMembers(ctx, \"n3a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().listMembers(\"n3a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->listMembers('n3a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.list_members(\"n3a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.ListMembersAsync(\"n3a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Network members. Not paginated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkMember" + } + } + } + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/signals/": { + "get": { + "summary": "List network signals", + "description": "List the signals (shared device, IP, face, document number, phone, email, or address values) that link this network's members.", + "operationId": "list_network_signals", + "x-didit-sdk": { + "resource": "networks", + "method": "list_signals", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/signals/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.list_signals(\n \"n3a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.listSignals(\"n3a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.ListSignals(ctx, \"n3a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().listSignals(\"n3a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->listSignals('n3a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.list_signals(\"n3a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.ListSignalsAsync(\"n3a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Network signals. Not paginated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkSignal" + } + } + } + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/timeline/": { + "get": { + "summary": "Get network timeline", + "description": "Without `contract`: the network's own events (detection, each signal's latest observation and its review), oldest first and not paginated. `limit`, `cursor` and `event_type` are ignored. With `contract=2`: the typed timeline, newest first and paginated by cursor. It adds what the member accounts did (verification attempts, transactions, sign-ins you post through the events API and the live rules they hit) and the cases opened for the network. `note_added` events are returned to console members only, never to an API key. Requires a full-access API key, or `read:networks` for a console member.", + "operationId": "get_network_timeline", + "x-didit-sdk": { + "resource": "networks", + "method": "retrieve_timeline", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + }, + { + "name": "contract", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "2" + ] + }, + "description": "`2` returns the typed timeline. Omit it for the default timeline. Any other value returns 400." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 500, + "default": 100 + }, + "description": "Typed timeline only. Events per page, 1 to 500. Defaults to 100." + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Typed timeline only. The previous page's `next_cursor`. An invalid or empty cursor returns 400." + }, + { + "name": "event_type", + "in": "query", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "array", + "maxItems": 20, + "items": { + "type": "string", + "enum": [ + "network_detected", + "signal_observed", + "network_active", + "network_in_review", + "network_resolved", + "network_dismissed", + "verification_attempt", + "transaction", + "login", + "rule_hit", + "case_linked", + "note_added" + ] + } + }, + "description": "Typed timeline only. Event types to keep, repeated or comma-separated, at most 20 values. Defaults to every type." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/timeline/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "curl", + "label": "curl (typed timeline)", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/3fa85f64-5717-4562-b3fc-2c963f66afa6/timeline/?contract=2&limit=50&event_type=verification_attempt,login' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.retrieve_timeline(\n \"n3a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.retrieveTimeline(\"n3a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.RetrieveTimeline(ctx, \"n3a5e000-0000-4000-8000-000000000001\", didit.NetworksRetrieveTimelineParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().retrieveTimeline(\"n3a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->retrieveTimeline('n3a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.retrieve_timeline(\"n3a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.RetrieveTimelineAsync(\"n3a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Without `contract`: `{results}`, oldest first, not paginated. With `contract=2`: `{results, next_cursor}`, newest first.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/NetworkTimeline" + }, + { + "$ref": "#/components/schemas/NetworkTimelinePage" + } + ] + }, + "examples": { + "Default timeline": { + "summary": "Without contract", + "value": { + "results": [ + { + "event_type": "network_detected", + "occurred_at": "2026-10-01T09:12:44Z", + "label": "Network detected", + "metadata": { + "network_id": "N-0042" + } + }, + { + "event_type": "signal_observed", + "occurred_at": "2026-10-12T16:03:10Z", + "label": "device observed", + "metadata": { + "signal_type": "device", + "subject_count": 3, + "is_hub": false + } + }, + { + "event_type": "network_in_review", + "occurred_at": "2026-10-12T16:30:00Z", + "label": "Network in_review", + "metadata": { + "reason": "Shared device across three new accounts." + } + } + ] + } + }, + "Typed timeline": { + "summary": "With contract=2", + "value": { + "results": [ + { + "id": "monitoring_event:9b2f1c3e-4d5a-4b6c-8d7e-0f1a2b3c4d5e", + "event_type": "login", + "occurred_at": "2026-10-12T18:41:02Z", + "ingested_at": "2026-10-12T18:41:03Z", + "label": "Login", + "metadata": {}, + "account": { + "node_id": "vendor_user:5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "node_type": "account", + "uuid": "5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "vendor_data": "user-1042", + "display_name": "Alex Example", + "ownership": "owned", + "route": { + "kind": "user", + "id": "5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "application_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + } + }, + "source_ref": { + "kind": "monitoring_event", + "id": "9b2f1c3e-4d5a-4b6c-8d7e-0f1a2b3c4d5e", + "ownership": "owned" + }, + "verification_attempt": null, + "transaction": null, + "login": { + "action": "auth.login", + "succeeded": true, + "status": "APPROVED", + "ip": { + "address": "203.0.113.24", + "country_code": "ES", + "city": "Madrid", + "connection_type": null, + "is_vpn_or_tor": false, + "is_data_center": false + }, + "device": { + "fingerprint": "fp_0a1b2c3d", + "label": "Chrome · Android" + }, + "is_first_login": false, + "is_new_device": true + }, + "rule_hit": null, + "case_linked": null, + "note_added": null + }, + { + "id": "session:1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "event_type": "verification_attempt", + "occurred_at": "2026-10-12T16:03:10Z", + "ingested_at": "2026-10-12T16:03:10Z", + "label": "Verification attempt 2 of 2", + "metadata": {}, + "account": { + "node_id": "vendor_user:5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "node_type": "account", + "uuid": "5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "vendor_data": "user-1042", + "display_name": "Alex Example", + "ownership": "owned", + "route": { + "kind": "user", + "id": "5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "application_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + } + }, + "source_ref": { + "kind": "session", + "id": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "ownership": "owned", + "route": { + "kind": "session", + "id": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "application_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + } + }, + "verification_attempt": { + "session_id": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "session_number": 1287, + "attempt": 2, + "attempt_count": 2, + "status": "Approved", + "status_changed_at": "2026-10-12T16:05:41Z", + "decline_reasons": [] + }, + "transaction": null, + "login": null, + "rule_hit": null, + "case_linked": null, + "note_added": null + }, + { + "id": "signal_observed:8d7c6b5a-4e3f-4a2b-9c1d-0e9f8a7b6c5d", + "event_type": "signal_observed", + "occurred_at": "2026-10-12T16:03:10Z", + "ingested_at": "2026-10-01T09:12:44Z", + "label": "device observed", + "metadata": { + "signal_type": "device", + "subject_count": 3, + "is_hub": false + }, + "account": null, + "source_ref": null, + "verification_attempt": null, + "transaction": null, + "login": null, + "rule_hit": null, + "case_linked": null, + "note_added": null + } + ], + "next_cursor": "eyJ0IjoiMjAyNi0xMC0xMlQxNjowMzoxMCswMDowMCIsImMiOiIyMDI2LTEwLTAxVDA5OjEyOjQ0KzAwOjAwIiwiciI6MSwidSI6IjhkN2M2YjVhLTRlM2YtNGEyYi05YzFkLTBlOWY4YTdiNmM1ZCJ9" + } + } + } + } + } + }, + "400": { + "description": "With `contract` present: an invalid `contract`, `limit`, `cursor` or `event_type` value.", + "content": { + "application/json": { + "examples": { + "Unsupported contract": { + "summary": "Unsupported contract", + "value": { + "contract": [ + "\"3\" is not a valid choice." + ] + } + }, + "Invalid cursor": { + "summary": "Invalid cursor", + "value": { + "cursor": [ + "This cursor is not valid. Reload the feed." + ] + } + }, + "Limit too large": { + "summary": "Limit too large", + "value": { + "limit": [ + "Ensure this value is less than or equal to 500." + ] + } + }, + "Unknown event type": { + "summary": "Unknown event type", + "value": { + "event_type": { + "0": [ + "\"logout\" is not a valid choice." + ] + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `read:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/map/": { + "get": { + "summary": "Get network map", + "description": "Geographic points where this network's members' addresses/locations converge, sourced from the sessions, business sessions, and transactions backing its memberships.", + "operationId": "get_network_map", + "x-didit-sdk": { + "resource": "networks", + "method": "retrieve_map", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/map/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.retrieve_map(\n \"n3a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.retrieveMap(\"n3a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.RetrieveMap(ctx, \"n3a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().retrieveMap(\"n3a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->retrieveMap('n3a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.retrieve_map(\"n3a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.RetrieveMapAsync(\"n3a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Network map.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkMap" + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/sessions/{session_id}/networks/": { + "get": { + "summary": "List networks for a session", + "description": "List the networks the session's vendor user belongs to. Resolves through the session's vendor_data-linked user.", + "operationId": "list_session_network_memberships", + "x-didit-sdk": { + "resource": "sessions", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Session UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/sessions/aaaaaaaa-0000-4000-8000-000000000001/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.sessions.list_networks(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.sessions.listNetworks(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.ListNetworks(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.sessions().listNetworks(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->listNetworks('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.sessions.list_networks(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ListNetworksAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/business-sessions/{business_session_id}/networks/": { + "get": { + "summary": "List networks for a business session", + "description": "List the networks the business session's vendor business belongs to.", + "operationId": "list_business_session_network_memberships", + "x-didit-sdk": { + "resource": "business_sessions", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "business_session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Business session UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/business-sessions/aaaaaaaa-0000-4000-8000-000000000002/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.business_sessions.list_networks(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.businessSessions.listNetworks(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.BusinessSessions.ListNetworks(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.businessSessions().listNetworks(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businessSessions->listNetworks('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.business_sessions.list_networks(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.BusinessSessions.ListNetworksAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/users/{vendor_user_uuid}/networks/": { + "get": { + "summary": "List networks for a user", + "description": "List the networks a vendor user belongs to.", + "operationId": "list_user_network_memberships", + "x-didit-sdk": { + "resource": "users", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "vendor_user_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Vendor user UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/users/aaaaaaaa-0000-4000-8000-000000000003/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.users.list_networks(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.users.listNetworks(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Users.ListNetworks(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.users().listNetworks(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->listNetworks('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.users.list_networks(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Users.ListNetworksAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/businesses/{vendor_business_uuid}/networks/": { + "get": { + "summary": "List networks for a business", + "description": "List the networks a vendor business belongs to.", + "operationId": "list_business_network_memberships", + "x-didit-sdk": { + "resource": "businesses", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "vendor_business_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Vendor business UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/businesses/aaaaaaaa-0000-4000-8000-000000000004/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.businesses.list_networks(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.businesses.listNetworks(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.ListNetworks(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.businesses().listNetworks(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->listNetworks('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.businesses.list_networks(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.ListNetworksAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/transactions/{transaction_uuid}/networks/": { + "get": { + "summary": "List networks for a transaction", + "description": "List the networks the transaction's backing KYC/KYB subject belongs to (resolved through the transaction's session or business session).", + "operationId": "list_transaction_network_memberships", + "x-didit-sdk": { + "resource": "transactions", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "transaction_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Transaction UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/transactions/aaaaaaaa-0000-4000-8000-000000000005/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.transactions.list_networks(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.transactions.listNetworks(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.ListNetworks(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.transactions().listNetworks(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->listNetworks('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.transactions.list_networks(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.ListNetworksAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/transactions/{transaction_uuid}/parties/{transaction_party_uuid}/networks/": { + "get": { + "summary": "List networks for a transaction party", + "description": "List the networks a transaction party (remitter, beneficiary, or counterparty) belongs to, resolved through the party itself rather than through the transaction's backing session.", + "operationId": "list_transaction_party_network_memberships", + "x-didit-sdk": { + "resource": "transactions", + "method": "list_party_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "transaction_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Transaction UUID." + }, + { + "name": "transaction_party_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Transaction party UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/transactions/aaaaaaaa-0000-4000-8000-000000000005/parties/bbbbbbbb-0000-4000-8000-000000000006/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.transactions.list_party_networks(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.transactions.listPartyNetworks(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.ListPartyNetworks(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.transactions().listPartyNetworks(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->listPartyNetworks('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.transactions.list_party_networks(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.ListPartyNetworksAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/biometric-templates/": { + "get": { + "summary": "List retained biometric templates", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nReturns a paginated list of the templates the application currently holds. Purged templates are not listed.", + "operationId": "list_biometric_templates", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "query", + "name": "vendor_data", + "required": false, + "description": "Case-insensitive substring match on the owning User's current `vendor_data`.", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "vendor_user_uuid", + "required": false, + "description": "Exact match on the owning User.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "query", + "name": "status", + "required": false, + "description": "Lifecycle status. Purged templates are never listed.", + "schema": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed" + ] + } + }, + { + "in": "query", + "name": "source_type", + "required": false, + "description": "How the template was created.", + "schema": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ] + } + }, + { + "in": "query", + "name": "retained_from", + "required": false, + "description": "Templates retained at or after this time.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "retained_to", + "required": false, + "description": "Templates retained at or before this time. Must not be earlier than `retained_from` (`400`).", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "ordering", + "required": false, + "description": "Sort order.", + "schema": { + "type": "string", + "enum": [ + "retained_at", + "-retained_at", + "expires_at", + "-expires_at" + ], + "default": "-retained_at" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "description": "Page size.", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "required": false, + "description": "Pagination offset.", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Paginated templates.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Template id. Randomly generated; not derived from the deleted session or its face." + }, + "vendor_user_uuid": { + "type": "string", + "format": "uuid", + "description": "The owning User." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "The User's current `vendor_data`, resolved when you read the template (never copied)." + }, + "source_type": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ], + "description": "How the template was created: a session deletion (API or Console), the application's automatic data-retention window, or a Didit-operated backfill you instructed." + }, + "provenance_reference": { + "type": "string", + "format": "uuid", + "description": "Opaque reference tying the template to its audit trail. It is not the deleted session's id and cannot be resolved to it." + }, + "retention_policy": { + "type": "string", + "enum": [ + "delete_with_session", + "retain_until_user_deleted" + ], + "description": "The application policy at retention time." + }, + "retention_override": { + "type": "boolean", + "nullable": true, + "description": "The `retain_face_embeddings` value sent on the deletion, or `null` when the policy applied." + }, + "retained_at": { + "type": "string", + "format": "date-time" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the template expires and is purged automatically. Always set." + }, + "retained_by": { + "type": "string", + "nullable": true, + "description": "Principal that gave the instruction: the application id for API keys, the Console member id for Console actions, or a `service:` identifier for automatic retention. Never an email address." + }, + "retained_by_type": { + "type": "string", + "enum": [ + "API_KEY", + "CONSOLE_USER", + "SYSTEM" + ] + }, + "instruction_class": { + "type": "string", + "enum": [ + "operational_session_delete", + "privacy_erasure" + ] + }, + "instruction_source": { + "type": "string", + "enum": [ + "api", + "console", + "automatic_retention", + "backfill", + "privacy_erasure", + "explicit_template_purge", + "vendor_user_delete", + "expiry", + "reconciliation", + "migration", + "system" + ] + }, + "instruction_id": { + "type": "string", + "description": "The `instruction_id` sent on the deletion, or a Didit-generated id." + }, + "held_out_of_use": { + "type": "boolean", + "description": "`true` only while a legal hold applies. A held template is excluded from all matching." + }, + "status": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed", + "purged" + ], + "description": "Only `active` templates that are not held, not past `expires_at`, and whose User still exists take part in matching." + }, + "purged_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + } + } + } + }, + "examples": { + "One active template": { + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "vendor_user_uuid": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f", + "vendor_data": "user-42", + "source_type": "session_delete", + "provenance_reference": "6b1e8d2f-4a3c-4f9e-8c7d-1a2b3c4d5e6f", + "retention_policy": "retain_until_user_deleted", + "retention_override": null, + "retained_at": "2026-08-28T10:15:42.318402Z", + "expires_at": "2027-08-28T10:15:42.318402Z", + "retained_by": "3c9e2b1f-7d4a-4e8b-9c1d-5f6a7b8c9d0e", + "retained_by_type": "API_KEY", + "instruction_class": "operational_session_delete", + "instruction_source": "api", + "instruction_id": "post-approval-cleanup-2026-08", + "held_out_of_use": false, + "status": "active", + "purged_at": null + } + ] + } + } + } + } + } + }, + "400": { + "description": "Invalid filter value, or `retained_to` earlier than `retained_from`. Field-keyed validation errors.", + "content": { + "application/json": { + "examples": { + "Bad range": { + "value": { + "retained_to": [ + "Must be on or after retained_from." + ] + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/biometric-templates/?vendor_data=user-42&status=active' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.biometric_templates.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.biometricTemplates.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.BiometricTemplates.ListAutoPaging(ctx, didit.BiometricTemplatesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.biometricTemplates().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "biometricTemplates->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.biometric_templates.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.BiometricTemplates.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/biometric-templates/count/": { + "get": { + "summary": "Count retained biometric templates", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nReturns the number of templates matching the same filters as the list endpoint. Use it to check what a filtered bulk purge with `delete_all: true` would touch.", + "operationId": "count_biometric_templates", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "count", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "query", + "name": "vendor_data", + "required": false, + "description": "Case-insensitive substring match on the owning User's current `vendor_data`.", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "vendor_user_uuid", + "required": false, + "description": "Exact match on the owning User.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "query", + "name": "status", + "required": false, + "description": "Lifecycle status. Purged templates are never listed.", + "schema": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed" + ] + } + }, + { + "in": "query", + "name": "source_type", + "required": false, + "description": "How the template was created.", + "schema": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ] + } + }, + { + "in": "query", + "name": "retained_from", + "required": false, + "description": "Templates retained at or after this time.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "retained_to", + "required": false, + "description": "Templates retained at or before this time. Must not be earlier than `retained_from` (`400`).", + "schema": { + "type": "string", + "format": "date-time" + } + } + ], + "responses": { + "200": { + "description": "Count.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "minimum": 0 + } + } + }, + "examples": { + "Count": { + "value": { + "count": 128 + } + } + } + } + } + }, + "400": { + "description": "Invalid filter value, or `retained_to` earlier than `retained_from`. Field-keyed validation errors.", + "content": { + "application/json": { + "examples": { + "Bad range": { + "value": { + "retained_to": [ + "Must be on or after retained_from." + ] + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/biometric-templates/count/?status=active' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.biometric_templates.count()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.biometricTemplates.count();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.BiometricTemplates.Count(ctx, didit.BiometricTemplatesCountParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.biometricTemplates().count();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "biometricTemplates->count();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.biometric_templates.count\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.BiometricTemplates.CountAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/biometric-templates/{template_uuid}/": { + "get": { + "summary": "Get a retained biometric template", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nReturns one template. The response never includes an image, a vector, the deleted session's id, or identity data.", + "operationId": "get_biometric_template", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "template_uuid", + "required": true, + "description": "Template id, as returned in `biometric_template_uuid` by the session deletion endpoints, `biometric_template_id` on a Face Search match, or the list endpoint.", + "schema": { + "type": "string", + "format": "uuid", + "example": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b" + } + } + ], + "responses": { + "200": { + "description": "The template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Template id. Randomly generated; not derived from the deleted session or its face." + }, + "vendor_user_uuid": { + "type": "string", + "format": "uuid", + "description": "The owning User." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "The User's current `vendor_data`, resolved when you read the template (never copied)." + }, + "source_type": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ], + "description": "How the template was created: a session deletion (API or Console), the application's automatic data-retention window, or a Didit-operated backfill you instructed." + }, + "provenance_reference": { + "type": "string", + "format": "uuid", + "description": "Opaque reference tying the template to its audit trail. It is not the deleted session's id and cannot be resolved to it." + }, + "retention_policy": { + "type": "string", + "enum": [ + "delete_with_session", + "retain_until_user_deleted" + ], + "description": "The application policy at retention time." + }, + "retention_override": { + "type": "boolean", + "nullable": true, + "description": "The `retain_face_embeddings` value sent on the deletion, or `null` when the policy applied." + }, + "retained_at": { + "type": "string", + "format": "date-time" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the template expires and is purged automatically. Always set." + }, + "retained_by": { + "type": "string", + "nullable": true, + "description": "Principal that gave the instruction: the application id for API keys, the Console member id for Console actions, or a `service:` identifier for automatic retention. Never an email address." + }, + "retained_by_type": { + "type": "string", + "enum": [ + "API_KEY", + "CONSOLE_USER", + "SYSTEM" + ] + }, + "instruction_class": { + "type": "string", + "enum": [ + "operational_session_delete", + "privacy_erasure" + ] + }, + "instruction_source": { + "type": "string", + "enum": [ + "api", + "console", + "automatic_retention", + "backfill", + "privacy_erasure", + "explicit_template_purge", + "vendor_user_delete", + "expiry", + "reconciliation", + "migration", + "system" + ] + }, + "instruction_id": { + "type": "string", + "description": "The `instruction_id` sent on the deletion, or a Didit-generated id." + }, + "held_out_of_use": { + "type": "boolean", + "description": "`true` only while a legal hold applies. A held template is excluded from all matching." + }, + "status": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed", + "purged" + ], + "description": "Only `active` templates that are not held, not past `expires_at`, and whose User still exists take part in matching." + }, + "purged_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "examples": { + "Active template": { + "value": { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "vendor_user_uuid": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f", + "vendor_data": "user-42", + "source_type": "session_delete", + "provenance_reference": "6b1e8d2f-4a3c-4f9e-8c7d-1a2b3c4d5e6f", + "retention_policy": "retain_until_user_deleted", + "retention_override": null, + "retained_at": "2026-08-28T10:15:42.318402Z", + "expires_at": "2027-08-28T10:15:42.318402Z", + "retained_by": "3c9e2b1f-7d4a-4e8b-9c1d-5f6a7b8c9d0e", + "retained_by_type": "API_KEY", + "instruction_class": "operational_session_delete", + "instruction_source": "api", + "instruction_id": "post-approval-cleanup-2026-08", + "held_out_of_use": false, + "status": "active", + "purged_at": null + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown template, a template of another application, or (on `GET`) a purged template.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Biometric template not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/biometric-templates/9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.biometric_templates.retrieve(\n \"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.biometricTemplates.retrieve(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.BiometricTemplates.Retrieve(ctx, \"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.biometricTemplates().retrieve(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "biometricTemplates->retrieve('9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.biometric_templates.retrieve(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.BiometricTemplates.RetrieveAsync(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "summary": "Purge a retained biometric template", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nPurges one template: it is removed from every kind of matching and the stored template is deleted. Irreversible and recorded on the audit trail with the acting principal.\n\n**Retry semantics:** Didit deletes the stored template first and only then marks the template purged. If a biometric store is unavailable the call returns `503`, the template moves to `status: purge_failed` and is already excluded from matching; repeat the same call to finish. Repeating a purge of an already purged template returns `already_purged`.\n\nTemplates are also purged by `POST /v3/users/delete/` for the owning User, by any deletion of that User's sessions with `deletion_instruction: privacy_erasure`, and automatically at `expires_at`.", + "operationId": "purge_biometric_template", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "template_uuid", + "required": true, + "description": "Template id, as returned in `biometric_template_uuid` by the session deletion endpoints, `biometric_template_id` on a Face Search match, or the list endpoint.", + "schema": { + "type": "string", + "format": "uuid", + "example": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b" + } + } + ], + "responses": { + "200": { + "description": "Purge outcome.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "outcome": { + "type": "string", + "enum": [ + "purged", + "already_purged" + ] + } + } + }, + "examples": { + "Purged": { + "value": { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "outcome": "purged" + } + }, + "Already purged": { + "value": { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "outcome": "already_purged" + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown template, a template of another application, or (on `GET`) a purged template.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Biometric template not found." + } + } + } + } + } + }, + "503": { + "description": "A biometric store was unavailable while retaining or purging a biometric template. Nothing is deleted; repeat the same request. Retries are idempotent and converge on a single template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Retryable": { + "value": { + "detail": "The biometric-template operation could not finish because an external store is unavailable. The operation is retryable." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE https://verification.didit.me/v3/biometric-templates/9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.biometric_templates.delete(\n \"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.biometricTemplates.delete(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.BiometricTemplates.Delete(ctx, \"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.biometricTemplates().delete(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "biometricTemplates->delete('9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.biometric_templates.delete(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.BiometricTemplates.DeleteAsync(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/biometric-templates/delete/": { + "post": { + "summary": "Bulk purge retained biometric templates", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nPurges many templates in one call, by explicit id list or with `delete_all: true` optionally narrowed by the same filters as the list endpoint. Templates are purged one by one and each result reports `purged` or `already_purged`. If a biometric store is unavailable the call returns `503` at the first failing template: templates purged earlier in the request stay purged, the failing one moves to `purge_failed` (already excluded from matching), and repeating the request finishes the job.\n\n**`delete_all: true` without filters purges every biometric template in the application and cannot be undone.** Run the count endpoint with the same filters first.", + "operationId": "bulk_purge_biometric_templates", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "batch_delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "template_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1, + "description": "Templates to purge. Required unless `delete_all` is `true`. Already purged ids are reported as `already_purged`." + }, + "delete_all": { + "type": "boolean", + "default": false, + "description": "When `true`, purges every template matching the filters below (all of the application's templates when no filter is given). `template_uuids` is ignored. Irreversible." + }, + "vendor_data": { + "type": "string", + "description": "Only with `delete_all`: case-insensitive substring match on the owning User's `vendor_data`." + }, + "vendor_user_uuid": { + "type": "string", + "format": "uuid", + "description": "Only with `delete_all`: exact match on the owning User." + }, + "status": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed" + ], + "description": "Only with `delete_all`." + }, + "source_type": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ], + "description": "Only with `delete_all`." + }, + "retained_from": { + "type": "string", + "format": "date-time", + "description": "Only with `delete_all`." + }, + "retained_to": { + "type": "string", + "format": "date-time", + "description": "Only with `delete_all`. Must not be earlier than `retained_from`." + } + } + }, + "examples": { + "By id": { + "value": { + "template_uuids": [ + "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" + ] + } + }, + "Every template of one User": { + "value": { + "delete_all": true, + "vendor_user_uuid": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f" + } + }, + "Every template in the application": { + "value": { + "delete_all": true + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Per-template purge outcomes.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "outcome": { + "type": "string", + "enum": [ + "purged", + "already_purged" + ] + } + } + } + } + } + }, + "examples": { + "Mixed": { + "value": { + "results": [ + { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "outcome": "purged" + }, + { + "uuid": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", + "outcome": "already_purged" + } + ] + } + } + } + } + } + }, + "400": { + "description": "Validation error — nothing is purged.", + "content": { + "application/json": { + "examples": { + "Missing selector": { + "value": { + "template_uuids": [ + "This field is required unless delete_all is true." + ] + } + }, + "Bad range": { + "value": { + "retained_to": [ + "Must be on or after retained_from." + ] + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "503": { + "description": "A biometric store was unavailable while retaining or purging a biometric template. Nothing is deleted; repeat the same request. Retries are idempotent and converge on a single template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Retryable": { + "value": { + "detail": "The biometric-template operation could not finish because an external store is unavailable. The operation is retryable." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/biometric-templates/delete/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"delete_all\": true, \"vendor_user_uuid\": \"2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.biometric_templates.batch_delete(\n template_uuids=[\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\", \"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.biometricTemplates.batchDelete({\n template_uuids: [\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\", \"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.BiometricTemplates.BatchDelete(ctx, didit.BiometricTemplatesBatchDeleteParams{\n\tTemplateUUIDs: didit.Some([]string{\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\", \"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.BiometricTemplatesBatchDeleteParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.biometricTemplates().batchDelete(BiometricTemplatesBatchDeleteParams.builder()\n .templateUuids(List.of(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\", \"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "biometricTemplates->batchDelete([\n 'template_uuids' => ['9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b', '1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.biometric_templates.batch_delete(template_uuids: [\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\", \"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.BiometricTemplates.BatchDeleteAsync(new BiometricTemplatesBatchDeleteParams\n{\n TemplateUuids = new string[] { \"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\", \"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/kyb/shareholders/": { + "post": { + "summary": "KYB registry company with shareholders (standalone)", + "operationId": "post_v3_kyb_shareholders", + "x-didit-sdk": { + "resource": "kyb", + "method": "list_shareholders", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Retrieve the Lite profile **plus the direct shareholders** (natural persons and corporate entities, with their ownership bands) of one candidate returned by `POST /v3/kyb/search/`.\n\n**Pairing with search.** Pass the candidate's `kyb_response_id` exactly as returned by a search of the same application. Search at the tier you intend to buy (`tier` on `POST /v3/kyb/search/`), because the search runs on the registry chain that serves that tier and an empty Lite search is not proof that the registry does not hold the company. When the country's ownership data lives at another registry, Didit re-finds the company there by registration number, at no extra cost.\n\n**Billing.** Billed at the country's Shareholders price - see [KYB registry pricing](/getting-started/kyb-registry-pricing). When the registry holds no shareholder record for the company (or the country offers no ownership data at all) the Lite profile is delivered and billed instead: read `registry_tier_delivered` and `registry_ownership_status`. The balance is checked against the Shareholders price before any registry call (`403` when short).\n\n**Availability.** v3 only. Every call creates a business session (its id is the `request_id`).\n\n**Pending reports.** When a report is still being prepared, or the ownership section has not been retrieved from the registry yet, the endpoint returns 202 Accepted, kyb_registry.fetch_status=pending, kyb_registry.registry_ownership_status=pending, and registry_tier_delivered=null. Keep request_id and retrieve GET /v3/session/{request_id}/decision/ until registry_checks[0].company.fetch_status is resolved. Didit completes the report automatically and charges the delivered tier once it is ready. Do not repeat the purchase while it is pending; every new POST creates a separate request.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBRegistryCandidateRequest" + }, + "example": { + "kyb_response_id": "69aeeb95febb0f1704042259", + "vendor_data": "company-1234" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/kyb/shareholders/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"kyb_response_id\": \"69aeeb95febb0f1704042259\",\n \"vendor_data\": \"company-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.kyb.list_shareholders(\n kyb_response_id=\"69aeeb95febb0f1704042259\",\n vendor_data=\"company-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.kyb.listShareholders({\n kyb_response_id: \"69aeeb95febb0f1704042259\",\n vendor_data: \"company-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.KYB.ListShareholders(ctx, didit.KYBRegistryCandidateRequest{\n\tKYBResponseID: \"69aeeb95febb0f1704042259\",\n\tVendorData: didit.String(\"company-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.KYBRegistryCandidateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.kyb().listShareholders(KYBRegistryCandidateRequest.builder()\n .kybResponseId(\"69aeeb95febb0f1704042259\")\n .vendorData(\"company-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "kyb->listShareholders([\n 'kyb_response_id' => '69aeeb95febb0f1704042259',\n 'vendor_data' => 'company-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.kyb.list_shareholders(\n kyb_response_id: \"69aeeb95febb0f1704042259\",\n vendor_data: \"company-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Kyb.ListShareholdersAsync(new KYBRegistryCandidateRequest\n{\n KybResponseId = \"69aeeb95febb0f1704042259\",\n VendorData = \"company-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Company report returned and billed at the delivered tier. Ownership reports still being prepared can return 202; keep request_id to retrieve their saved decision.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the business session created for this retrieval. The stored record (visible in the console) keeps updating if the registry resolves after the response." + }, + "registry_tier_requested": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "The tier this endpoint sells: `basic` (Lite) for select, `shareholders`, `ubo`." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "The tier actually delivered and billed. Lower than requested when the country's registries hold no ownership data, or the registry has no ownership record for this company.", + "nullable": true + }, + "kyb_registry": { + "type": "object", + "description": "Full registry company profile.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit's id for this company record." + }, + "node_id": { + "type": "string", + "example": "feature_kyb_registry" + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review" + ], + "description": "Didit's assessment of the registry check (e.g. `In Review` when the company is not active in the registry or ownership data could not be derived)." + }, + "registry_status": { + "type": "string", + "nullable": true, + "enum": [ + "active", + "dissolved", + "deregistered", + "see full details", + "authorised", + "appointed representative", + "unauthorised", + "inactive", + "no longer authorised", + "closed", + "struck off", + null + ], + "description": "Normalized company status as reported by the registry." + }, + "data_resolved": { + "type": "boolean", + "description": "`true` once the registry returned the full profile. `false` when the registry was still processing after polling — the stored record completes automatically once the registry resolves." + }, + "company_name": { + "type": "string", + "example": "Tesco PLC" + }, + "registration_number": { + "type": "string", + "nullable": true, + "example": "00445790" + }, + "country_code": { + "type": "string", + "description": "ISO 3166-1 alpha-2.", + "example": "GB" + }, + "region": { + "type": "string", + "nullable": true, + "description": "Subdivision code when the registry is regional (e.g. `CA` for `US-CA`); empty otherwise." + }, + "company_type": { + "type": "string", + "nullable": true, + "example": "Public Limited Company" + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true, + "example": "1947-11-27" + }, + "registered_address": { + "type": "string", + "nullable": true, + "example": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA" + }, + "tax_number": { + "type": "string", + "nullable": true, + "example": "GB412 8925 93" + }, + "risk_level": { + "type": "string", + "nullable": true, + "description": "Registry-reported risk level, when available." + }, + "verification_status": { + "type": "string", + "nullable": true, + "description": "Registry verification status, when available." + }, + "is_from_registry": { + "type": "boolean", + "description": "Always `true` for this endpoint — the profile was retrieved from an official registry." + }, + "fetch_status": { + "type": "string", + "enum": [ + "pending", + "resolved" + ], + "description": "Registry fetch lifecycle. Mirrored by `data_resolved`." + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true, + "description": "Activity description or classification code, when reported." + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "nullable": true, + "description": "Decimal amount as a string, when reported." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 code, when reported." + }, + "website": { + "type": "string", + "nullable": true, + "example": "https://www.tesco.com/" + }, + "email": { + "type": "string", + "nullable": true, + "example": "customer.service@tesco.co.uk" + }, + "phone": { + "type": "string", + "nullable": true, + "example": "03301231688" + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true, + "description": "LEI, when reported." + }, + "location_of_registration": { + "type": "string", + "nullable": true, + "description": "Registry/jurisdiction the company is registered with, when reported." + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards. At select time this is typically not yet set — VAT and tax ID validation runs when the end user submits the registry step in the hosted flow." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true, + "description": "Registry-reported share/capital details (currency, total shares, share classes), when available." + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "example": "MELISSA BETHELL" + }, + "designation": { + "type": "string", + "nullable": true, + "description": "Raw registry designation (e.g. `Director`, `Secretary`)." + }, + "role": { + "type": "string", + "description": "Normalized role derived from the designation. Full 17-value model set; registry parsing on this endpoint typically emits director, non_executive_director, secretary, chairman, or company_officer.", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "company_officer", + "settlor", + "protector", + "investor", + "other" + ] + }, + "nationality": { + "type": "string", + "nullable": true + }, + "is_active": { + "type": "boolean", + "description": "`false` when the registry marks the appointment as resigned." + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending", + null + ], + "description": "Status of an associated KYC session, when one was started for this officer. `null` otherwise." + }, + "kyc_session_url": { + "type": "string", + "nullable": true, + "description": "Verification URL of the associated KYC session, when one exists." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + }, + "description": "Directors, secretaries, and other officers extracted from the registry." + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the owner is a natural person or a corporate entity." + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Normalized roles (e.g. `ubo`, `shareholder`)." + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership band reported by the registry (e.g. `25%`)." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership band reported by the registry." + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending", + null + ] + }, + "kyc_session_url": { + "type": "string", + "nullable": true + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true, + "description": "Parsed ownership percentage (max bound, falling back to min bound)." + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + }, + "shareholding": { + "$ref": "#/components/schemas/KYBRegistryShareholding" + } + } + }, + "description": "Beneficial owners / persons with significant control extracted from the registry. Can be empty when the registry does not expose ownership." + }, + "addresses": { + "type": "array", + "nullable": true, + "description": "Every address the registry states, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryAddress" + } + }, + "industries": { + "type": "array", + "nullable": true, + "description": "The activities the registry classifies the company under, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryIndustry" + } + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "$ref": "#/components/schemas/KYBRegistryData" + }, + "user_provided_data": { + "type": "object", + "nullable": true, + "description": "Always `null` on this endpoint (used by the hosted KYB flow when end users edit registry data)." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set when a console reviewer edits the record later." + }, + "is_editable": { + "type": "boolean", + "description": "Whether the registry-sourced data can still be edited in the hosted flow/console." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "Same as the top-level `registry_tier_delivered`.", + "nullable": true + }, + "registry_ownership_status": { + "type": "string", + "nullable": true, + "enum": [ + "not_requested", + "delivered", + "not_held", + "unavailable", + "pending", + null + ], + "description": "`delivered` when the ownership data came back, `not_held` when the ownership registry answered and does not list this company (see `registry_data.ownership_error`), `unavailable` when an ownership tier was requested but the registry had none for this company or could not be asked (see `registry_data.ownership_error`), `pending` when the ownership section has not been retrieved from the registry yet and Didit is still completing it, `not_requested` on the Lite tier." + }, + "ownership": { + "$ref": "#/components/schemas/KYBRegistryOwnership" + }, + "registry_tier": { + "type": "string", + "nullable": true, + "description": "The registry tier the workflow ordered: `basic`, `shareholders` or `ubo`. Also in `ownership.tier_requested`." + }, + "registry_monitoring": { + "type": "object", + "nullable": true, + "description": "The latest registry monitor for the company, or `null` when none exists.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string" + }, + "unavailable_reason": { + "type": "string", + "nullable": true + }, + "enrolled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "next_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "last_event_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "events_count": { + "type": "integer" + }, + "watched_fields": { + "description": "The company fields the monitor watches." + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Resolved company": { + "summary": "Full registry profile (officer list truncated for brevity)", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "f1e2d3c4-b5a6-4789-9c0d-1e2f3a4b5c6d", + "node_id": "feature_kyb_registry", + "status": "In Review", + "registry_status": "active", + "data_resolved": true, + "company_name": "Tesco PLC", + "registration_number": "00445790", + "country_code": "GB", + "region": "", + "company_type": "Public Limited Company", + "incorporation_date": "1947-11-27", + "registered_address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "tax_number": "GB412 8925 93", + "risk_level": "low risk", + "verification_status": "verified", + "is_from_registry": true, + "fetch_status": "resolved", + "alternative_names": "TESCO STORES (HOLDINGS) PUBLIC LIMITED COMPANY", + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": "https://www.tesco.com/", + "email": "customer.service@tesco.co.uk", + "phone": "03301231688", + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": { + "currency": "Pound (£)", + "total_number_of_shares": "8174378721", + "meta_detail": { + "Class_Of_Share": "Ordinary" + } + }, + "officers": [ + { + "uuid": "a1b2c3d4-e5f6-4789-9a0b-1c2d3e4f5a6b", + "name": "CHRISTOPHER JON TAYLOR", + "designation": "Secretary", + "role": "secretary", + "nationality": null, + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + }, + { + "uuid": "b2c3d4e5-f6a7-4890-8b1c-2d3e4f5a6b7c", + "name": "MELISSA BETHELL", + "designation": "Director", + "role": "director", + "nationality": "British", + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + } + ], + "beneficial_owners": [], + "addresses": [ + { + "address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "type": "legal_entity_registry_address", + "description": "registered office address" + } + ], + "industries": null, + "accounts": { + "last_account": "26 February 2025", + "next_account": "26 February 2026", + "due_by_date": "26 August 2026" + }, + "registry_data": {}, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems use DRF's standard envelope.", + "content": { + "application/json": { + "examples": { + "Missing kyb_response_id": { + "summary": "`kyb_response_id` is required", + "value": { + "kyb_response_id": [ + "This field is required." + ] + } + }, + "save_api_request=false rejected": { + "summary": "Selects are always persisted", + "value": { + "save_api_request": [ + "KYB registry select must be saved so billing and retrieval can be tracked." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "202": { + "description": "Report accepted and still processing. When a report is still being prepared, the endpoint returns 202 Accepted, kyb_registry.fetch_status=pending, and registry_tier_delivered=null. Keep request_id and retrieve GET /v3/session/{request_id}/decision/ until registry_checks[0].company.fetch_status is resolved. Didit completes the report automatically and charges the delivered tier once it is ready. Do not repeat the purchase while it is pending; every new POST creates a separate request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/paths/~1v3~1kyb~1shareholders~1/post/responses/200/content/application~1json/schema" + }, + "examples": { + "Pending (registry still resolving)": { + "summary": "Ownership report is still processing; retrieve the saved decision", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "7c0a4f7e-2b1d-4f6a-9c3e-8d5b2a1f0e9d", + "node_id": "feature_kyb_registry", + "status": "Not Finished", + "registry_status": null, + "data_resolved": false, + "company_name": "Example Holdings Limited", + "registration_number": "12345678", + "country_code": "HK", + "region": null, + "company_type": null, + "incorporation_date": null, + "registered_address": null, + "tax_number": null, + "risk_level": null, + "verification_status": null, + "is_from_registry": true, + "fetch_status": "pending", + "alternative_names": null, + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": null, + "email": null, + "phone": null, + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": {}, + "officers": [], + "beneficial_owners": [], + "addresses": null, + "industries": null, + "accounts": null, + "registry_data": {}, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true, + "registry_tier_delivered": null, + "registry_ownership_status": "pending" + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00", + "registry_tier_delivered": null, + "registry_tier_requested": "shareholders" + } + } + } + } + } + } + } + } + }, + "/v3/kyb/ubo/": { + "post": { + "summary": "KYB registry company with ultimate beneficial owners (standalone)", + "operationId": "post_v3_kyb_ubo", + "x-didit-sdk": { + "resource": "kyb", + "method": "list_ubos", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Retrieve the Lite profile, the direct shareholders **and the ultimate beneficial owners** resolved through the ownership chain (`registry_data.ubo_resolution` carries the corporate parent chain) of one candidate returned by `POST /v3/kyb/search/`.\n\n**Pairing with search.** Pass the candidate's `kyb_response_id` exactly as returned by a search of the same application. Search at the tier you intend to buy (`tier` on `POST /v3/kyb/search/`), because the search runs on the registry chain that serves that tier and an empty Lite search is not proof that the registry does not hold the company. When the country's ownership data lives at another registry, Didit re-finds the company there by registration number, at no extra cost.\n\n**Billing.** Billed at the country's UBO price - see [KYB registry pricing](/getting-started/kyb-registry-pricing). The tier delivered is the highest the country and the registry can serve: Shareholders when the resolved report names no natural person as a beneficial owner, Lite when there is no ownership record at all; `registry_tier_delivered` names what was billed. The balance is checked against the UBO price before any registry call (`403` when short).\n\n**Availability.** v3 only. Every call creates a business session (its id is the `request_id`).\n\n**Pending reports.** When a report is still being prepared, or the ownership section has not been retrieved from the registry yet, the endpoint returns 202 Accepted, kyb_registry.fetch_status=pending, kyb_registry.registry_ownership_status=pending, and registry_tier_delivered=null. Keep request_id and retrieve GET /v3/session/{request_id}/decision/ until registry_checks[0].company.fetch_status is resolved. Didit completes the report automatically and charges the delivered tier once it is ready. Do not repeat the purchase while it is pending; every new POST creates a separate request.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBRegistryCandidateRequest" + }, + "example": { + "kyb_response_id": "69aeeb95febb0f1704042259", + "vendor_data": "company-1234" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/kyb/ubo/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"kyb_response_id\": \"69aeeb95febb0f1704042259\",\n \"vendor_data\": \"company-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.kyb.list_ubos(\n kyb_response_id=\"69aeeb95febb0f1704042259\",\n vendor_data=\"company-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.kyb.listUbos({\n kyb_response_id: \"69aeeb95febb0f1704042259\",\n vendor_data: \"company-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.KYB.ListUBOs(ctx, didit.KYBRegistryCandidateRequest{\n\tKYBResponseID: \"69aeeb95febb0f1704042259\",\n\tVendorData: didit.String(\"company-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.KYBRegistryCandidateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.kyb().listUbos(KYBRegistryCandidateRequest.builder()\n .kybResponseId(\"69aeeb95febb0f1704042259\")\n .vendorData(\"company-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "kyb->listUbos([\n 'kyb_response_id' => '69aeeb95febb0f1704042259',\n 'vendor_data' => 'company-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.kyb.list_ubos(\n kyb_response_id: \"69aeeb95febb0f1704042259\",\n vendor_data: \"company-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Kyb.ListUbosAsync(new KYBRegistryCandidateRequest\n{\n KybResponseId = \"69aeeb95febb0f1704042259\",\n VendorData = \"company-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Company report returned and billed at the delivered tier. Ownership reports still being prepared can return 202; keep request_id to retrieve their saved decision.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the business session created for this retrieval. The stored record (visible in the console) keeps updating if the registry resolves after the response." + }, + "registry_tier_requested": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "The tier this endpoint sells: `basic` (Lite) for select, `shareholders`, `ubo`." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "The tier actually delivered and billed. Lower than requested when the country's registries hold no ownership data, when the registry has no ownership record for this company, or when the resolved report of a `ubo` request names no natural person as a beneficial owner.", + "nullable": true + }, + "kyb_registry": { + "type": "object", + "description": "Full registry company profile.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit's id for this company record." + }, + "node_id": { + "type": "string", + "example": "feature_kyb_registry" + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review" + ], + "description": "Didit's assessment of the registry check (e.g. `In Review` when the company is not active in the registry or ownership data could not be derived)." + }, + "registry_status": { + "type": "string", + "nullable": true, + "enum": [ + "active", + "dissolved", + "deregistered", + "see full details", + "authorised", + "appointed representative", + "unauthorised", + "inactive", + "no longer authorised", + "closed", + "struck off", + null + ], + "description": "Normalized company status as reported by the registry." + }, + "data_resolved": { + "type": "boolean", + "description": "`true` once the registry returned the full profile. `false` when the registry was still processing after polling — the stored record completes automatically once the registry resolves." + }, + "company_name": { + "type": "string", + "example": "Tesco PLC" + }, + "registration_number": { + "type": "string", + "nullable": true, + "example": "00445790" + }, + "country_code": { + "type": "string", + "description": "ISO 3166-1 alpha-2.", + "example": "GB" + }, + "region": { + "type": "string", + "nullable": true, + "description": "Subdivision code when the registry is regional (e.g. `CA` for `US-CA`); empty otherwise." + }, + "company_type": { + "type": "string", + "nullable": true, + "example": "Public Limited Company" + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true, + "example": "1947-11-27" + }, + "registered_address": { + "type": "string", + "nullable": true, + "example": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA" + }, + "tax_number": { + "type": "string", + "nullable": true, + "example": "GB412 8925 93" + }, + "risk_level": { + "type": "string", + "nullable": true, + "description": "Registry-reported risk level, when available." + }, + "verification_status": { + "type": "string", + "nullable": true, + "description": "Registry verification status, when available." + }, + "is_from_registry": { + "type": "boolean", + "description": "Always `true` for this endpoint — the profile was retrieved from an official registry." + }, + "fetch_status": { + "type": "string", + "enum": [ + "pending", + "resolved" + ], + "description": "Registry fetch lifecycle. Mirrored by `data_resolved`." + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true, + "description": "Activity description or classification code, when reported." + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "nullable": true, + "description": "Decimal amount as a string, when reported." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 code, when reported." + }, + "website": { + "type": "string", + "nullable": true, + "example": "https://www.tesco.com/" + }, + "email": { + "type": "string", + "nullable": true, + "example": "customer.service@tesco.co.uk" + }, + "phone": { + "type": "string", + "nullable": true, + "example": "03301231688" + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true, + "description": "LEI, when reported." + }, + "location_of_registration": { + "type": "string", + "nullable": true, + "description": "Registry/jurisdiction the company is registered with, when reported." + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards. At select time this is typically not yet set — VAT and tax ID validation runs when the end user submits the registry step in the hosted flow." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true, + "description": "Registry-reported share/capital details (currency, total shares, share classes), when available." + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "example": "MELISSA BETHELL" + }, + "designation": { + "type": "string", + "nullable": true, + "description": "Raw registry designation (e.g. `Director`, `Secretary`)." + }, + "role": { + "type": "string", + "description": "Normalized role derived from the designation. Full 17-value model set; registry parsing on this endpoint typically emits director, non_executive_director, secretary, chairman, or company_officer.", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "company_officer", + "settlor", + "protector", + "investor", + "other" + ] + }, + "nationality": { + "type": "string", + "nullable": true + }, + "is_active": { + "type": "boolean", + "description": "`false` when the registry marks the appointment as resigned." + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending", + null + ], + "description": "Status of an associated KYC session, when one was started for this officer. `null` otherwise." + }, + "kyc_session_url": { + "type": "string", + "nullable": true, + "description": "Verification URL of the associated KYC session, when one exists." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + }, + "description": "Directors, secretaries, and other officers extracted from the registry." + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the owner is a natural person or a corporate entity." + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Normalized roles (e.g. `ubo`, `shareholder`)." + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership band reported by the registry (e.g. `25%`)." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership band reported by the registry." + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending", + null + ] + }, + "kyc_session_url": { + "type": "string", + "nullable": true + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true, + "description": "Parsed ownership percentage (max bound, falling back to min bound)." + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + }, + "shareholding": { + "$ref": "#/components/schemas/KYBRegistryShareholding" + } + } + }, + "description": "Beneficial owners / persons with significant control extracted from the registry. Can be empty when the registry does not expose ownership." + }, + "addresses": { + "type": "array", + "nullable": true, + "description": "Every address the registry states, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryAddress" + } + }, + "industries": { + "type": "array", + "nullable": true, + "description": "The activities the registry classifies the company under, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryIndustry" + } + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "$ref": "#/components/schemas/KYBRegistryData" + }, + "user_provided_data": { + "type": "object", + "nullable": true, + "description": "Always `null` on this endpoint (used by the hosted KYB flow when end users edit registry data)." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set when a console reviewer edits the record later." + }, + "is_editable": { + "type": "boolean", + "description": "Whether the registry-sourced data can still be edited in the hosted flow/console." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "Same as the top-level `registry_tier_delivered`.", + "nullable": true + }, + "registry_ownership_status": { + "type": "string", + "nullable": true, + "enum": [ + "not_requested", + "delivered", + "not_held", + "unavailable", + "pending", + null + ], + "description": "`delivered` when the ownership data came back, `not_held` when the ownership registry answered and does not list this company (see `registry_data.ownership_error`), `unavailable` when an ownership tier was requested but the registry had none for this company or could not be asked (see `registry_data.ownership_error`), `pending` when the ownership section has not been retrieved from the registry yet and Didit is still completing it, `not_requested` on the Lite tier." + }, + "ownership": { + "$ref": "#/components/schemas/KYBRegistryOwnership" + }, + "registry_tier": { + "type": "string", + "nullable": true, + "description": "The registry tier the workflow ordered: `basic`, `shareholders` or `ubo`. Also in `ownership.tier_requested`." + }, + "registry_monitoring": { + "type": "object", + "nullable": true, + "description": "The latest registry monitor for the company, or `null` when none exists.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string" + }, + "unavailable_reason": { + "type": "string", + "nullable": true + }, + "enrolled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "next_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "last_event_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "events_count": { + "type": "integer" + }, + "watched_fields": { + "description": "The company fields the monitor watches." + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Resolved company": { + "summary": "Full registry profile (officer list truncated for brevity)", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "f1e2d3c4-b5a6-4789-9c0d-1e2f3a4b5c6d", + "node_id": "feature_kyb_registry", + "status": "In Review", + "registry_status": "active", + "data_resolved": true, + "company_name": "Tesco PLC", + "registration_number": "00445790", + "country_code": "GB", + "region": "", + "company_type": "Public Limited Company", + "incorporation_date": "1947-11-27", + "registered_address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "tax_number": "GB412 8925 93", + "risk_level": "low risk", + "verification_status": "verified", + "is_from_registry": true, + "fetch_status": "resolved", + "alternative_names": "TESCO STORES (HOLDINGS) PUBLIC LIMITED COMPANY", + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": "https://www.tesco.com/", + "email": "customer.service@tesco.co.uk", + "phone": "03301231688", + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": { + "currency": "Pound (£)", + "total_number_of_shares": "8174378721", + "meta_detail": { + "Class_Of_Share": "Ordinary" + } + }, + "officers": [ + { + "uuid": "a1b2c3d4-e5f6-4789-9a0b-1c2d3e4f5a6b", + "name": "CHRISTOPHER JON TAYLOR", + "designation": "Secretary", + "role": "secretary", + "nationality": null, + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + }, + { + "uuid": "b2c3d4e5-f6a7-4890-8b1c-2d3e4f5a6b7c", + "name": "MELISSA BETHELL", + "designation": "Director", + "role": "director", + "nationality": "British", + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + } + ], + "beneficial_owners": [], + "addresses": [ + { + "address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "type": "legal_entity_registry_address", + "description": "registered office address" + } + ], + "industries": null, + "accounts": { + "last_account": "26 February 2025", + "next_account": "26 February 2026", + "due_by_date": "26 August 2026" + }, + "registry_data": { + "ubo_resolution": { + "parent_chain": [], + "subsidiaries": [], + "unresolved": [] + } + }, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems use DRF's standard envelope.", + "content": { + "application/json": { + "examples": { + "Missing kyb_response_id": { + "summary": "`kyb_response_id` is required", + "value": { + "kyb_response_id": [ + "This field is required." + ] + } + }, + "save_api_request=false rejected": { + "summary": "Selects are always persisted", + "value": { + "save_api_request": [ + "KYB registry select must be saved so billing and retrieval can be tracked." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "202": { + "description": "Report accepted and still processing. When a report is still being prepared, the endpoint returns 202 Accepted, kyb_registry.fetch_status=pending, and registry_tier_delivered=null. Keep request_id and retrieve GET /v3/session/{request_id}/decision/ until registry_checks[0].company.fetch_status is resolved. Didit completes the report automatically and charges the delivered tier once it is ready. Do not repeat the purchase while it is pending; every new POST creates a separate request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/paths/~1v3~1kyb~1ubo~1/post/responses/200/content/application~1json/schema" + }, + "examples": { + "Pending (registry still resolving)": { + "summary": "Ownership report is still processing; retrieve the saved decision", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "7c0a4f7e-2b1d-4f6a-9c3e-8d5b2a1f0e9d", + "node_id": "feature_kyb_registry", + "status": "Not Finished", + "registry_status": null, + "data_resolved": false, + "company_name": "Example Holdings Limited", + "registration_number": "12345678", + "country_code": "HK", + "region": null, + "company_type": null, + "incorporation_date": null, + "registered_address": null, + "tax_number": null, + "risk_level": null, + "verification_status": null, + "is_from_registry": true, + "fetch_status": "pending", + "alternative_names": null, + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": null, + "email": null, + "phone": null, + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": {}, + "officers": [], + "beneficial_owners": [], + "addresses": null, + "industries": null, + "accounts": null, + "registry_data": {}, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true, + "registry_tier_delivered": null, + "registry_ownership_status": "pending" + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00", + "registry_tier_delivered": null, + "registry_tier_requested": "ubo" + } + } + } + } + } + } + } + } + }, + "/v3/id-verification/wallets/": { + "get": { + "operationId": "list_wallets", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "list", + "auth": "api_key", + "server": true + }, + "description": "Every Digital ID wallet with its start endpoint, countries, required fields and whether this application can start it in this environment now. Listed does not mean live.", + "summary": "List Digital ID wallets", + "tags": [ + "Digital ID wallets" + ], + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletCatalogResponse" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "Missing credentials (x-api-key or Authorization: Bearer)." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request GET \\\n --url https://verification.didit.me/v3/id-verification/wallets/ \\\n --header 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.list()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.list();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.List(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().list();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->list();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.list\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.ListAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/id-verification/wallets/verifications/{request_id}/": { + "get": { + "operationId": "get_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "retrieve_verification", + "auth": "api_key", + "server": true + }, + "description": "Status and result of a wallet verification: the normalized identity and the wallet's own attributes once verified, the error code once declined, the next action while pending. A verification past expires_at is decided timeout.", + "summary": "Get a wallet verification", + "parameters": [ + { + "in": "path", + "name": "request_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The request_id the start endpoint returned.", + "required": true + } + ], + "tags": [ + "Digital ID wallets" + ], + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "ftn", + "name": "Finnish Trust Network", + "issuing_authority": "Finnish banks and mobile operators (FTN)" + }, + "country": "FIN", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "Missing credentials (x-api-key or Authorization: Bearer)." + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error not_found: no wallet verification of this application has this request_id." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request GET \\\n --url https://verification.didit.me/v3/id-verification/wallets/verifications/REQUEST_ID/ \\\n --header 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.retrieve_verification(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.retrieveVerification(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.RetrieveVerification(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().retrieveVerification(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->retrieveVerification('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.retrieve_verification(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.RetrieveVerificationAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/id-verification/wallets/verifications/{request_id}/poll/": { + "post": { + "operationId": "poll_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "poll_verification", + "auth": "api_key", + "server": true + }, + "description": "Smart-ID and Mobile-ID: ask the provider whether the user confirmed, refused or let the prompt lapse, and verify the signed identity when they confirmed. Redirect wallets complete through the user's browser, so for them this only reads the verification back. Answers the verification as it is afterwards.", + "summary": "Poll a wallet verification", + "parameters": [ + { + "in": "path", + "name": "request_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The request_id the start endpoint returned.", + "required": true + } + ], + "tags": [ + "Digital ID wallets" + ], + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "smart_id", + "name": "Smart-ID", + "issuing_authority": "SK ID Solutions" + }, + "country": "EST", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "in_app", + "simulated": true, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "confirm_in_app", + "verification_code": "1234", + "poll_interval_seconds": 2 + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "Missing credentials (x-api-key or Authorization: Bearer)." + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error not_found: no wallet verification of this application has this request_id." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/verifications/REQUEST_ID/poll/ \\\n --header 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.poll_verification(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.pollVerification(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.PollVerification(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().pollVerification(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->pollVerification('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.poll_verification(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.PollVerificationAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/id-verification/wallets/verifications/{request_id}/cancel/": { + "post": { + "operationId": "cancel_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "cancel_verification", + "auth": "api_key", + "server": true + }, + "description": "Withdraw a pending verification: it is declined as cancelled and never billed. A decided one is returned unchanged. Smart-ID and Mobile-ID: a cancel that arrives while a poll is checking the sign-in waits a few seconds for that poll, then declines it, or returns it verified if the user had already confirmed. A verification the user is completing right now cannot be cancelled (409 verification_in_progress with Retry-After): retry after that many seconds to read its outcome.", + "summary": "Cancel a wallet verification", + "parameters": [ + { + "in": "path", + "name": "request_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The request_id the start endpoint returned.", + "required": true + } + ], + "tags": [ + "Digital ID wallets" + ], + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "ftn", + "name": "Finnish Trust Network", + "issuing_authority": "Finnish banks and mobile operators (FTN)" + }, + "country": "FIN", + "status": "Declined", + "outcome": "cancelled", + "error": "cancelled_by_customer", + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": null, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "Missing credentials (x-api-key or Authorization: Bearer)." + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error not_found: no wallet verification of this application has this request_id." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error verification_in_progress: the user is completing this sign-in right now. The Retry-After header says when to retry; the retry returns the decided verification." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/verifications/REQUEST_ID/cancel/ \\\n --header 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.cancel_verification(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.cancelVerification(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.CancelVerification(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().cancelVerification(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->cancelVerification('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.cancel_verification(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.CancelVerificationAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/id-verification/wallets/mitid/": { + "post": { + "operationId": "start_mitid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_mitid", + "auth": "api_key", + "server": true + }, + "description": "Start a MitID sign-in (Danish Agency for Digital Government; DNK) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start MitID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMitidWalletVerificationStartRequest" + }, + "example": { + "country": "DNK", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMitidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMitidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "mitid", + "name": "MitID", + "issuing_authority": "Danish Agency for Digital Government" + }, + "country": "DNK", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/mitid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"DNK\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_mitid(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"DNK\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startMitid({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"DNK\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartMitid(ctx, didit.IDWalletsStartMitidParams{\n\tBody: didit.DigitalIDWalletAPIMitidWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIMitidWalletVerificationStartCountryEnum(\"DNK\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiMitidWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiMitidWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startMitid(DigitalIdWalletApiMitidWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiMitidWalletVerificationStartCountryEnum.of(\"DNK\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startMitid([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'DNK',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_mitid(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"DNK\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartMitidAsync(new DigitalIdWalletApiMitidWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiMitidWalletVerificationStartCountryEnum(\"DNK\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "mitid" + } + }, + "/v3/id-verification/wallets/bankid-se/": { + "post": { + "operationId": "start_bankid_se_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_bankid_se", + "auth": "api_key", + "server": true + }, + "description": "Start a BankID (SWE) sign-in (Finansiell ID-Teknik / bank consortium; SWE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start BankID (SWE) verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidSeWalletVerificationStartRequest" + }, + "example": { + "country": "SWE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidSeWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidSeWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "bankid_se", + "name": "BankID", + "issuing_authority": "Finansiell ID-Teknik / bank consortium" + }, + "country": "SWE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/bankid-se/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"SWE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_bankid_se(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"SWE\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startBankidSe({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"SWE\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartBankidSe(ctx, didit.IDWalletsStartBankidSeParams{\n\tBody: didit.DigitalIDWalletAPIBankidSeWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryEe9enum(\"SWE\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiBankidSeWalletVerificationStartRequest;\nimport me.didit.types.DigitalIdWalletApiCountryEe9Enum;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startBankidSe(DigitalIdWalletApiBankidSeWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryEe9Enum.of(\"SWE\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startBankidSe([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'SWE',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_bankid_se(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"SWE\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartBankidSeAsync(new DigitalIdWalletApiBankidSeWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryEe9Enum(\"SWE\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "bankid_se" + } + }, + "/v3/id-verification/wallets/bankid-no/": { + "post": { + "operationId": "start_bankid_no_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_bankid_no", + "auth": "api_key", + "server": true + }, + "description": "Start a BankID (NOR) sign-in (BankID BankAxept AS; NOR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start BankID (NOR) verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidNoWalletVerificationStartRequest" + }, + "example": { + "country": "NOR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidNoWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidNoWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "bankid_no", + "name": "BankID", + "issuing_authority": "BankID BankAxept AS" + }, + "country": "NOR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/bankid-no/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"NOR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_bankid_no(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"NOR\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startBankidNo({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NOR\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartBankidNo(ctx, didit.IDWalletsStartBankidNoParams{\n\tBody: didit.DigitalIDWalletAPIBankidNoWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryAfcEnum(\"NOR\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiBankidNoWalletVerificationStartRequest;\nimport me.didit.types.DigitalIdWalletApiCountryAfcEnum;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startBankidNo(DigitalIdWalletApiBankidNoWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryAfcEnum.of(\"NOR\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startBankidNo([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'NOR',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_bankid_no(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NOR\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartBankidNoAsync(new DigitalIdWalletApiBankidNoWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryAfcEnum(\"NOR\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "bankid_no" + } + }, + "/v3/id-verification/wallets/vipps/": { + "post": { + "operationId": "start_vipps_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_vipps", + "auth": "api_key", + "server": true + }, + "description": "Start a Vipps sign-in (Vipps MobilePay / BankID NO; NOR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Vipps verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiVippsWalletVerificationStartRequest" + }, + "example": { + "country": "NOR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiVippsWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiVippsWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "vipps", + "name": "Vipps", + "issuing_authority": "Vipps MobilePay / BankID NO" + }, + "country": "NOR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/vipps/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"NOR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_vipps(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"NOR\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startVipps({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NOR\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartVipps(ctx, didit.IDWalletsStartVippsParams{\n\tBody: didit.DigitalIDWalletAPIVippsWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryAfcEnum(\"NOR\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiCountryAfcEnum;\nimport me.didit.types.DigitalIdWalletApiVippsWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startVipps(DigitalIdWalletApiVippsWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryAfcEnum.of(\"NOR\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startVipps([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'NOR',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_vipps(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NOR\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartVippsAsync(new DigitalIdWalletApiVippsWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryAfcEnum(\"NOR\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "vipps" + } + }, + "/v3/id-verification/wallets/buypass/": { + "post": { + "operationId": "start_buypass_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_buypass", + "auth": "api_key", + "server": true + }, + "description": "Start a Buypass ID sign-in (Buypass AS; NOR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Buypass ID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBuypassWalletVerificationStartRequest" + }, + "example": { + "country": "NOR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBuypassWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBuypassWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "buypass", + "name": "Buypass ID", + "issuing_authority": "Buypass AS" + }, + "country": "NOR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/buypass/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"NOR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_buypass(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"NOR\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startBuypass({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NOR\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartBuypass(ctx, didit.IDWalletsStartBuypassParams{\n\tBody: didit.DigitalIDWalletAPIBuypassWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryAfcEnum(\"NOR\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiBuypassWalletVerificationStartRequest;\nimport me.didit.types.DigitalIdWalletApiCountryAfcEnum;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startBuypass(DigitalIdWalletApiBuypassWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryAfcEnum.of(\"NOR\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startBuypass([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'NOR',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_buypass(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NOR\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartBuypassAsync(new DigitalIdWalletApiBuypassWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryAfcEnum(\"NOR\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "buypass" + } + }, + "/v3/id-verification/wallets/itsme/": { + "post": { + "operationId": "start_itsme_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_itsme", + "auth": "api_key", + "server": true + }, + "description": "Start a itsme sign-in (Belgian Mobile ID; BEL, LUX, NLD) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start itsme verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiItsmeWalletVerificationStartRequest" + }, + "example": { + "country": "BEL", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiItsmeWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiItsmeWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "itsme", + "name": "itsme", + "issuing_authority": "Belgian Mobile ID" + }, + "country": "BEL", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/itsme/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"BEL\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_itsme(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"BEL\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startItsme({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"BEL\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartItsme(ctx, didit.IDWalletsStartItsmeParams{\n\tBody: didit.DigitalIDWalletAPIItsmeWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.DigitalIDWalletAPIItsmeWalletVerificationStartCountryEnum(\"BEL\"),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiItsmeWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiItsmeWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startItsme(DigitalIdWalletApiItsmeWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiItsmeWalletVerificationStartCountryEnum.of(\"BEL\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startItsme([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'BEL',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_itsme(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"BEL\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartItsmeAsync(new DigitalIdWalletApiItsmeWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = \"BEL\",\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "itsme" + } + }, + "/v3/id-verification/wallets/idin/": { + "post": { + "operationId": "start_idin_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_idin", + "auth": "api_key", + "server": true + }, + "description": "Start a iDIN sign-in (Dutch banks (Currence iDIN); NLD) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start iDIN verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdinWalletVerificationStartRequest" + }, + "example": { + "country": "NLD", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdinWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdinWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "idin", + "name": "iDIN", + "issuing_authority": "Dutch banks (Currence iDIN)" + }, + "country": "NLD", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/idin/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"NLD\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_idin(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"NLD\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startIdin({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NLD\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartIdin(ctx, didit.IDWalletsStartIdinParams{\n\tBody: didit.DigitalIDWalletAPIIdinWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIIdinWalletVerificationStartCountryEnum(\"NLD\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiIdinWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiIdinWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startIdin(DigitalIdWalletApiIdinWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiIdinWalletVerificationStartCountryEnum.of(\"NLD\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startIdin([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'NLD',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_idin(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NLD\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartIdinAsync(new DigitalIdWalletApiIdinWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiIdinWalletVerificationStartCountryEnum(\"NLD\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "idin" + } + }, + "/v3/id-verification/wallets/ftn/": { + "post": { + "operationId": "start_ftn_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_ftn", + "auth": "api_key", + "server": true + }, + "description": "Start a Finnish Trust Network sign-in (Finnish banks and mobile operators (FTN); FIN) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Finnish Trust Network verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFtnWalletVerificationStartRequest" + }, + "example": { + "country": "FIN", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFtnWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFtnWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "ftn", + "name": "Finnish Trust Network", + "issuing_authority": "Finnish banks and mobile operators (FTN)" + }, + "country": "FIN", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/ftn/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"FIN\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_ftn(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"FIN\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startFtn({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"FIN\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartFtn(ctx, didit.IDWalletsStartFtnParams{\n\tBody: didit.DigitalIDWalletAPIFtnWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIFtnWalletVerificationStartCountryEnum(\"FIN\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiFtnWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiFtnWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startFtn(DigitalIdWalletApiFtnWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiFtnWalletVerificationStartCountryEnum.of(\"FIN\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startFtn([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'FIN',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_ftn(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"FIN\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartFtnAsync(new DigitalIdWalletApiFtnWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiFtnWalletVerificationStartCountryEnum(\"FIN\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "ftn" + } + }, + "/v3/id-verification/wallets/personalausweis/": { + "post": { + "operationId": "start_personalausweis_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_personalausweis", + "auth": "api_key", + "server": true + }, + "description": "Start a Personalausweis sign-in (Bundesministerium des Innern (eID); DEU) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Personalausweis verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest" + }, + "example": { + "country": "DEU", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "personalausweis", + "name": "Personalausweis", + "issuing_authority": "Bundesministerium des Innern (eID)" + }, + "country": "DEU", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/personalausweis/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"DEU\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_personalausweis(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"DEU\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startPersonalausweis({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"DEU\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartPersonalausweis(ctx, didit.IDWalletsStartPersonalausweisParams{\n\tBody: didit.DigitalIDWalletAPIPersonalausweisWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIPersonalausweisWalletVerificationStartCountryEnum(\"DEU\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiPersonalausweisWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startPersonalausweis(DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiPersonalausweisWalletVerificationStartCountryEnum.of(\"DEU\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startPersonalausweis([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'DEU',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_personalausweis(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"DEU\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartPersonalausweisAsync(new DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiPersonalausweisWalletVerificationStartCountryEnum(\"DEU\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "personalausweis" + } + }, + "/v3/id-verification/wallets/frejaid/": { + "post": { + "operationId": "start_frejaid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_frejaid", + "auth": "api_key", + "server": true + }, + "description": "Start a Freja eID sign-in (Freja eID Group; SWE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Freja eID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFrejaidWalletVerificationStartRequest" + }, + "example": { + "country": "SWE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFrejaidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFrejaidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "frejaid", + "name": "Freja eID", + "issuing_authority": "Freja eID Group" + }, + "country": "SWE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/frejaid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"SWE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_frejaid(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"SWE\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startFrejaid({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"SWE\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartFrejaid(ctx, didit.IDWalletsStartFrejaidParams{\n\tBody: didit.DigitalIDWalletAPIFrejaidWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryEe9enum(\"SWE\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiCountryEe9Enum;\nimport me.didit.types.DigitalIdWalletApiFrejaidWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startFrejaid(DigitalIdWalletApiFrejaidWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryEe9Enum.of(\"SWE\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startFrejaid([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'SWE',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_frejaid(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"SWE\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartFrejaidAsync(new DigitalIdWalletApiFrejaidWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryEe9Enum(\"SWE\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "frejaid" + } + }, + "/v3/id-verification/wallets/uae-pass/": { + "post": { + "operationId": "start_uae_pass_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_uae_pass", + "auth": "api_key", + "server": true + }, + "description": "Start a UAE PASS sign-in (UAE Digital Government Authority; ARE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start UAE PASS verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiUaePassWalletVerificationStartRequest" + }, + "example": { + "country": "ARE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiUaePassWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiUaePassWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "uae_pass", + "name": "UAE PASS", + "issuing_authority": "UAE Digital Government Authority" + }, + "country": "ARE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/uae-pass/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"ARE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_uae_pass(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"ARE\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startUaePass({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"ARE\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartUaePass(ctx, didit.IDWalletsStartUaePassParams{\n\tBody: didit.DigitalIDWalletAPIUaePassWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIUaePassWalletVerificationStartCountryEnum(\"ARE\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiUaePassWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiUaePassWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startUaePass(DigitalIdWalletApiUaePassWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiUaePassWalletVerificationStartCountryEnum.of(\"ARE\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startUaePass([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'ARE',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_uae_pass(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"ARE\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartUaePassAsync(new DigitalIdWalletApiUaePassWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiUaePassWalletVerificationStartCountryEnum(\"ARE\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "uae_pass" + } + }, + "/v3/id-verification/wallets/govbr/": { + "post": { + "operationId": "start_govbr_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_govbr", + "auth": "api_key", + "server": true + }, + "description": "Start a gov.br sign-in (Governo Federal do Brasil; BRA) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start gov.br verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovbrWalletVerificationStartRequest" + }, + "example": { + "country": "BRA", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovbrWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovbrWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "govbr", + "name": "gov.br", + "issuing_authority": "Governo Federal do Brasil" + }, + "country": "BRA", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/govbr/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"BRA\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_govbr(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"BRA\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startGovbr({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"BRA\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartGovbr(ctx, didit.IDWalletsStartGovbrParams{\n\tBody: didit.DigitalIDWalletAPIGovbrWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIGovbrWalletVerificationStartCountryEnum(\"BRA\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiGovbrWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiGovbrWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startGovbr(DigitalIdWalletApiGovbrWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiGovbrWalletVerificationStartCountryEnum.of(\"BRA\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startGovbr([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'BRA',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_govbr(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"BRA\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartGovbrAsync(new DigitalIdWalletApiGovbrWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiGovbrWalletVerificationStartCountryEnum(\"BRA\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "govbr" + } + }, + "/v3/id-verification/wallets/oneid/": { + "post": { + "operationId": "start_oneid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_oneid", + "auth": "api_key", + "server": true + }, + "description": "Start a OneID sign-in (OneID (UK bank-verified identity); GBR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start OneID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiOneidWalletVerificationStartRequest" + }, + "example": { + "country": "GBR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiOneidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiOneidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "oneid", + "name": "OneID", + "issuing_authority": "OneID (UK bank-verified identity)" + }, + "country": "GBR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/oneid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"GBR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_oneid(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"GBR\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startOneid({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"GBR\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartOneid(ctx, didit.IDWalletsStartOneidParams{\n\tBody: didit.DigitalIDWalletAPIOneidWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountry520enum(\"GBR\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiCountry520Enum;\nimport me.didit.types.DigitalIdWalletApiOneidWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startOneid(DigitalIdWalletApiOneidWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountry520Enum.of(\"GBR\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startOneid([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'GBR',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_oneid(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"GBR\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartOneidAsync(new DigitalIdWalletApiOneidWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountry520Enum(\"GBR\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "oneid" + } + }, + "/v3/id-verification/wallets/govuk-wallet/": { + "post": { + "operationId": "start_govuk_wallet_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_govuk_wallet", + "auth": "api_key", + "server": true + }, + "description": "Start a GOV.UK Wallet sign-in (UK Government Digital Service; GBR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start GOV.UK Wallet verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovukWalletWalletVerificationStartRequest" + }, + "example": { + "country": "GBR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovukWalletWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovukWalletWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "govuk_wallet", + "name": "GOV.UK Wallet", + "issuing_authority": "UK Government Digital Service" + }, + "country": "GBR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/govuk-wallet/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"GBR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_govuk_wallet(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"GBR\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startGovukWallet({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"GBR\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartGovukWallet(ctx, didit.IDWalletsStartGovukWalletParams{\n\tBody: didit.DigitalIDWalletAPIGovukWalletWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountry520enum(\"GBR\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiCountry520Enum;\nimport me.didit.types.DigitalIdWalletApiGovukWalletWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startGovukWallet(DigitalIdWalletApiGovukWalletWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountry520Enum.of(\"GBR\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startGovukWallet([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'GBR',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_govuk_wallet(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"GBR\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartGovukWalletAsync(new DigitalIdWalletApiGovukWalletWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountry520Enum(\"GBR\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "govuk_wallet" + } + }, + "/v3/id-verification/wallets/smart-id/": { + "post": { + "operationId": "start_smart_id_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_smart_id", + "auth": "api_key", + "server": true + }, + "description": "Start a Smart-ID sign-in (SK ID Solutions; EST, LVA, LTU, BEL) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=confirm_in_app with the comparison code to show the user; the user confirms it and enters their PIN in the app (never collect the PIN). Poll POST /v3/id-verification/wallets/verifications/{request_id}/poll/ every poll_interval_seconds, or rely on the webhook: Didit polls in the background too. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Smart-ID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiSmartIdWalletVerificationStartRequest" + }, + "example": { + "country": "EST", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "personal_code": "00000000000" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiSmartIdWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiSmartIdWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "smart_id", + "name": "Smart-ID", + "issuing_authority": "SK ID Solutions" + }, + "country": "EST", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "in_app", + "simulated": true, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "confirm_in_app", + "verification_code": "1234", + "poll_interval_seconds": 2 + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/smart-id/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"EST\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"personal_code\": \"00000000000\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_smart_id(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"EST\",\n personal_code=\"00000000000\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startSmartId({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"EST\",\n personal_code: \"00000000000\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartSmartID(ctx, didit.IDWalletsStartSmartIDParams{\n\tBody: didit.DigitalIDWalletAPISmartIDWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.DigitalIDWalletAPISmartIDWalletVerificationStartCountryEnum(\"EST\"),\n\t\tPersonalCode: \"00000000000\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiSmartIdWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiSmartIdWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startSmartId(DigitalIdWalletApiSmartIdWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiSmartIdWalletVerificationStartCountryEnum.of(\"EST\"))\n .personalCode(\"00000000000\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startSmartId([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'EST',\n 'personal_code' => '00000000000',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_smart_id(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"EST\",\n personal_code: \"00000000000\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartSmartIdAsync(new DigitalIdWalletApiSmartIdWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = \"EST\",\n PersonalCode = \"00000000000\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "smart_id" + } + }, + "/v3/id-verification/wallets/mobile-id/": { + "post": { + "operationId": "start_mobile_id_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_mobile_id", + "auth": "api_key", + "server": true + }, + "description": "Start a Mobile-ID sign-in (SK ID Solutions with the national mobile operators; EST, LTU) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=confirm_in_app with the comparison code to show the user; the user confirms it and enters their PIN in the app (never collect the PIN). Poll POST /v3/id-verification/wallets/verifications/{request_id}/poll/ every poll_interval_seconds, or rely on the webhook: Didit polls in the background too. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Mobile-ID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMobileIdWalletVerificationStartRequest" + }, + "example": { + "country": "EST", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "personal_code": "00000000000", + "phone_number": "+37200000000" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMobileIdWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMobileIdWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "mobile_id", + "name": "Mobile-ID", + "issuing_authority": "SK ID Solutions with the national mobile operators" + }, + "country": "EST", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "in_app", + "simulated": true, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "confirm_in_app", + "verification_code": "1234", + "poll_interval_seconds": 2 + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/mobile-id/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"EST\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"personal_code\": \"00000000000\",\n \"phone_number\": \"+37200000000\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_mobile_id(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"EST\",\n personal_code=\"00000000000\",\n phone_number=\"+37200000000\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startMobileId({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"EST\",\n personal_code: \"00000000000\",\n phone_number: \"+37200000000\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartMobileID(ctx, didit.IDWalletsStartMobileIDParams{\n\tBody: didit.DigitalIDWalletAPIMobileIDWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.DigitalIDWalletAPIMobileIDWalletVerificationStartCountryEnum(\"EST\"),\n\t\tPersonalCode: \"00000000000\",\n\t\tPhoneNumber: \"+37200000000\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiMobileIdWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiMobileIdWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startMobileId(DigitalIdWalletApiMobileIdWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiMobileIdWalletVerificationStartCountryEnum.of(\"EST\"))\n .personalCode(\"00000000000\")\n .phoneNumber(\"+37200000000\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startMobileId([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'EST',\n 'personal_code' => '00000000000',\n 'phone_number' => '+37200000000',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_mobile_id(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"EST\",\n personal_code: \"00000000000\",\n phone_number: \"+37200000000\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartMobileIdAsync(new DigitalIdWalletApiMobileIdWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = \"EST\",\n PersonalCode = \"00000000000\",\n PhoneNumber = \"+37200000000\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "mobile_id" + } + }, + "/v3/id-verification/wallets/idcard-ee/": { + "post": { + "operationId": "start_idcard_ee_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_idcard_ee", + "auth": "api_key", + "server": true + }, + "description": "Start a Estonian ID-card sign-in (Police and Border Guard Board (Estonia); EST) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Estonian ID-card verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdcardEeWalletVerificationStartRequest" + }, + "example": { + "country": "EST", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdcardEeWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdcardEeWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "idcard_ee", + "name": "Estonian ID-card", + "issuing_authority": "Police and Border Guard Board (Estonia)" + }, + "country": "EST", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/idcard-ee/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"EST\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_idcard_ee(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"EST\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startIdcardEe({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"EST\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartIdcardEe(ctx, didit.IDWalletsStartIdcardEeParams{\n\tBody: didit.DigitalIDWalletAPIIdcardEeWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIIdcardEeWalletVerificationStartCountryEnum(\"EST\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiIdcardEeWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiIdcardEeWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startIdcardEe(DigitalIdWalletApiIdcardEeWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiIdcardEeWalletVerificationStartCountryEnum.of(\"EST\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startIdcardEe([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'EST',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_idcard_ee(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"EST\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartIdcardEeAsync(new DigitalIdWalletApiIdcardEeWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiIdcardEeWalletVerificationStartCountryEnum(\"EST\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "idcard_ee" + } + }, + "/v3/id-verification/wallets/eparaksts-mobile/": { + "post": { + "operationId": "start_eparaksts_mobile_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_eparaksts_mobile", + "auth": "api_key", + "server": true + }, + "description": "Start a eParaksts mobile sign-in (LVRTC (Latvian State Radio and Television Centre); LVA) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start eParaksts mobile verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest" + }, + "example": { + "country": "LVA", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "eparaksts_mobile", + "name": "eParaksts mobile", + "issuing_authority": "LVRTC (Latvian State Radio and Television Centre)" + }, + "country": "LVA", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/eparaksts-mobile/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"LVA\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_eparaksts_mobile(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"LVA\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startEparakstsMobile({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"LVA\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartEparakstsMobile(ctx, didit.IDWalletsStartEparakstsMobileParams{\n\tBody: didit.DigitalIDWalletAPIEparakstsMobileWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIEparakstsMobileWalletVerificationStartCountryEnum(\"LVA\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiEparakstsMobileWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startEparakstsMobile(DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiEparakstsMobileWalletVerificationStartCountryEnum.of(\"LVA\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startEparakstsMobile([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'LVA',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_eparaksts_mobile(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"LVA\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartEparakstsMobileAsync(new DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiEparakstsMobileWalletVerificationStartCountryEnum(\"LVA\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "eparaksts_mobile" + } + }, + "/v3/id-verification/wallets/bankid-cz/": { + "post": { + "operationId": "start_bankid_cz_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_bankid_cz", + "auth": "api_key", + "server": true + }, + "description": "Start a Bank iD sign-in (Bankovní identita, a.s.; CZE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Bank iD verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidCzWalletVerificationStartRequest" + }, + "example": { + "country": "CZE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidCzWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidCzWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "bankid_cz", + "name": "Bank iD", + "issuing_authority": "Bankovní identita, a.s." + }, + "country": "CZE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/bankid-cz/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"CZE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_bankid_cz(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"CZE\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startBankidCz({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"CZE\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartBankidCz(ctx, didit.IDWalletsStartBankidCzParams{\n\tBody: didit.DigitalIDWalletAPIBankidCzWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryE2dEnum(\"CZE\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiBankidCzWalletVerificationStartRequest;\nimport me.didit.types.DigitalIdWalletApiCountryE2dEnum;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startBankidCz(DigitalIdWalletApiBankidCzWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryE2dEnum.of(\"CZE\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startBankidCz([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'CZE',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_bankid_cz(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"CZE\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartBankidCzAsync(new DigitalIdWalletApiBankidCzWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryE2dEnum(\"CZE\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "bankid_cz" + } + }, + "/v3/id-verification/wallets/mojeid/": { + "post": { + "operationId": "start_mojeid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_mojeid", + "auth": "api_key", + "server": true + }, + "description": "Start a MojeID sign-in (CZ.NIC; CZE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start MojeID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMojeidWalletVerificationStartRequest" + }, + "example": { + "country": "CZE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMojeidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMojeidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "mojeid", + "name": "MojeID", + "issuing_authority": "CZ.NIC" + }, + "country": "CZE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/mojeid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"CZE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_mojeid(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"CZE\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startMojeid({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"CZE\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartMojeid(ctx, didit.IDWalletsStartMojeidParams{\n\tBody: didit.DigitalIDWalletAPIMojeidWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryE2dEnum(\"CZE\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiCountryE2dEnum;\nimport me.didit.types.DigitalIdWalletApiMojeidWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startMojeid(DigitalIdWalletApiMojeidWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryE2dEnum.of(\"CZE\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startMojeid([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'CZE',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_mojeid(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"CZE\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartMojeidAsync(new DigitalIdWalletApiMojeidWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryE2dEnum(\"CZE\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "mojeid" + } + }, + "/v3/id-verification/wallets/diia/": { + "post": { + "operationId": "start_diia_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_diia", + "auth": "api_key", + "server": true + }, + "description": "Start a Diia sign-in (Ministry of Digital Transformation of Ukraine; UKR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Diia verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiDiiaWalletVerificationStartRequest" + }, + "example": { + "country": "UKR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiDiiaWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiDiiaWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "diia", + "name": "Diia", + "issuing_authority": "Ministry of Digital Transformation of Ukraine" + }, + "country": "UKR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/diia/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"UKR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_diia(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"UKR\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startDiia({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"UKR\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartDiia(ctx, didit.IDWalletsStartDiiaParams{\n\tBody: didit.DigitalIDWalletAPIDiiaWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIDiiaWalletVerificationStartCountryEnum(\"UKR\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiDiiaWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiDiiaWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startDiia(DigitalIdWalletApiDiiaWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiDiiaWalletVerificationStartCountryEnum.of(\"UKR\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startDiia([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'UKR',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_diia(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"UKR\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartDiiaAsync(new DigitalIdWalletApiDiiaWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiDiiaWalletVerificationStartCountryEnum(\"UKR\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "diia" + } + }, + "/v3/id-verification/wallets/franceconnect/": { + "post": { + "operationId": "start_franceconnect_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_franceconnect", + "auth": "api_key", + "server": true + }, + "description": "Start a FranceConnect sign-in (DINUM (French state); FRA) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start FranceConnect verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFranceconnectWalletVerificationStartRequest" + }, + "example": { + "country": "FRA", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFranceconnectWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFranceconnectWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "franceconnect", + "name": "FranceConnect", + "issuing_authority": "DINUM (French state)" + }, + "country": "FRA", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/franceconnect/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"FRA\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_franceconnect(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"FRA\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startFranceconnect({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"FRA\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartFranceconnect(ctx, didit.IDWalletsStartFranceconnectParams{\n\tBody: didit.DigitalIDWalletAPIFranceconnectWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIFranceconnectWalletVerificationStartCountryEnum(\"FRA\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiFranceconnectWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiFranceconnectWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startFranceconnect(DigitalIdWalletApiFranceconnectWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiFranceconnectWalletVerificationStartCountryEnum.of(\"FRA\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startFranceconnect([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'FRA',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_franceconnect(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"FRA\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartFranceconnectAsync(new DigitalIdWalletApiFranceconnectWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiFranceconnectWalletVerificationStartCountryEnum(\"FRA\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "franceconnect" + } + }, + "/v3/id-verification/wallets/audkenni/": { + "post": { + "operationId": "start_audkenni_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_audkenni", + "auth": "api_key", + "server": true + }, + "description": "Start a Auðkenni sign-in (Auðkenni (Icelandic electronic ID); ISL) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Auðkenni verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiAudkenniWalletVerificationStartRequest" + }, + "example": { + "country": "ISL", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiAudkenniWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiAudkenniWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "audkenni", + "name": "Auðkenni", + "issuing_authority": "Auðkenni (Icelandic electronic ID)" + }, + "country": "ISL", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/audkenni/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"ISL\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_audkenni(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"ISL\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startAudkenni({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"ISL\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartAudkenni(ctx, didit.IDWalletsStartAudkenniParams{\n\tBody: didit.DigitalIDWalletAPIAudkenniWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIAudkenniWalletVerificationStartCountryEnum(\"ISL\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiAudkenniWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiAudkenniWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startAudkenni(DigitalIdWalletApiAudkenniWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiAudkenniWalletVerificationStartCountryEnum.of(\"ISL\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startAudkenni([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'ISL',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_audkenni(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"ISL\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartAudkenniAsync(new DigitalIdWalletApiAudkenniWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiAudkenniWalletVerificationStartCountryEnum(\"ISL\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "audkenni" + } + }, + "/v3/id-verification/wallets/connectid/": { + "post": { + "operationId": "start_connectid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_connectid", + "auth": "api_key", + "server": true + }, + "description": "Start a ConnectID sign-in (Australian Payments Plus; AUS) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start ConnectID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiConnectidWalletVerificationStartRequest" + }, + "example": { + "country": "AUS", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiConnectidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiConnectidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "connectid", + "name": "ConnectID", + "issuing_authority": "Australian Payments Plus" + }, + "country": "AUS", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/connectid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"AUS\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_connectid(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"AUS\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startConnectid({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"AUS\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartConnectid(ctx, didit.IDWalletsStartConnectidParams{\n\tBody: didit.DigitalIDWalletAPIConnectidWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIConnectidWalletVerificationStartCountryEnum(\"AUS\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiConnectidWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiConnectidWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startConnectid(DigitalIdWalletApiConnectidWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiConnectidWalletVerificationStartCountryEnum.of(\"AUS\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startConnectid([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'AUS',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_connectid(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"AUS\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartConnectidAsync(new DigitalIdWalletApiConnectidWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiConnectidWalletVerificationStartCountryEnum(\"AUS\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "connectid" + } + }, + "/v3/id-verification/wallets/eudi/": { + "post": { + "operationId": "start_eudi_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_eudi", + "auth": "api_key", + "server": true + }, + "description": "Start a EUDI Wallet sign-in (The user's own member state; the issuer differs per country; AUT, BEL, BGR, HRV, CYP, CZE, DNK, EST, FIN, FRA, DEU, GRC, HUN, IRL, ITA, LVA, LTU, LUX, MLT, NLD, POL, PRT, ROU, SVK, SVN, ESP, SWE, ISL, LIE, NOR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start EUDI Wallet verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEudiWalletVerificationStartRequest" + }, + "example": { + "country": "AUT", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEudiWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEudiWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "eudi", + "name": "EUDI Wallet", + "issuing_authority": "The user's own member state; the issuer differs per country" + }, + "country": "AUT", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/eudi/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"AUT\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_eudi(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"AUT\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startEudi({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"AUT\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartEudi(ctx, didit.IDWalletsStartEudiParams{\n\tBody: didit.DigitalIDWalletAPIEudiWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.DigitalIDWalletAPIEudiWalletVerificationStartCountryEnum(\"AUT\"),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiEudiWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiEudiWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startEudi(DigitalIdWalletApiEudiWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiEudiWalletVerificationStartCountryEnum.of(\"AUT\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startEudi([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'AUT',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_eudi(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"AUT\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartEudiAsync(new DigitalIdWalletApiEudiWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = \"AUT\",\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "eudi" + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/teams/": { + "get": { + "summary": "List review teams", + "description": "Requires `list:cases` for the requested application.", + "operationId": "list_case_teams", + "x-didit-sdk": { + "resource": "cases.teams", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000000, + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseReviewTeam" + } + }, + "count": { + "type": "integer", + "description": "Matching count, capped at 100. Follow next to continue beyond the cap." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.teams.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.teams.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.Teams.ListAutoPaging(ctx, didit.CasesTeamsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().teams().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->teams->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.teams.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.Teams.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "summary": "Create review team", + "description": "Requires `write:cases` for the requested application.", + "operationId": "create_case_teams", + "x-didit-sdk": { + "resource": "cases.teams", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "responses": { + "201": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeam" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeamCreate" + }, + "example": { + "name": "AML analysts", + "members": [ + { + "user_id": "9a000000-0000-4000-8000-0000000000aa", + "capacity": 10 + } + ] + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.teams.create(\n name=\"AML analysts\",\n members=[{\n \"user_id\": \"9a000000-0000-4000-8000-0000000000aa\",\n \"capacity\": 10,\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.teams.create({\n name: \"AML analysts\",\n members: [{\n user_id: \"9a000000-0000-4000-8000-0000000000aa\",\n capacity: 10,\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Teams.Create(ctx, didit.CaseReviewTeamCreate{\n\tName: \"AML analysts\",\n\tMembers: []didit.CaseReviewTeamMemberParam{{\n\t\tUserID: \"9a000000-0000-4000-8000-0000000000aa\",\n\t\tCapacity: didit.Int(10),\n\t}},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CaseReviewTeamCreate;\nimport me.didit.types.CaseReviewTeamMember;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().teams().create(CaseReviewTeamCreate.builder()\n .name(\"AML analysts\")\n .members(List.of(CaseReviewTeamMember.builder()\n .userId(\"9a000000-0000-4000-8000-0000000000aa\")\n .capacity(10L)\n .build()))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->teams->create([\n 'name' => 'AML analysts',\n 'members' => [[\n 'user_id' => '9a000000-0000-4000-8000-0000000000aa',\n 'capacity' => 10,\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.teams.create(\n name: \"AML analysts\",\n members: [{\n user_id: \"9a000000-0000-4000-8000-0000000000aa\",\n capacity: 10,\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Teams.CreateAsync(new CaseReviewTeamCreate\n{\n Name = \"AML analysts\",\n Members = new CaseReviewTeamMember[]\n {\n new()\n {\n UserId = \"9a000000-0000-4000-8000-0000000000aa\",\n Capacity = 10,\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/fraud/checks/{check_id}/": { + "get": { + "operationId": "root_retrieve", + "x-didit-sdk": { + "resource": "fraud_checks", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "description": "Fetch a previously created check by its `check_id`.", + "summary": "Retrieve a Fraud Check", + "parameters": [ + { + "in": "path", + "name": "check_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckResponse" + } + } + }, + "description": "" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudClientCredentialsErrorMessage" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "tags": [ + "Fraud Check" + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.fraud_checks.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.fraudChecks.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.FraudChecks.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.fraudChecks().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "fraudChecks->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.fraud_checks.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.FraudChecks.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/": { + "get": { + "operationId": "organization_application_cases_requests_list", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "name": "ordering", + "required": false, + "in": "query", + "description": "Which field to use when ordering the results.", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "offset", + "required": false, + "in": "query", + "description": "The initial index from which to return the results.", + "schema": { + "type": "integer" + } + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingPaginatedCaseRequestList" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.requests.list(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.requests.list(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.Requests.ListAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasesRequestsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().requests().list(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->list('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.requests.list(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.Requests.ListAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "operationId": "organization_application_cases_requests_create", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestConfirmRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestConfirmRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestConfirmRequest" + } + } + }, + "required": true + }, + "responses": { + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "400": { + "description": "Invalid inputs, unsupported purpose, unavailable verified contact or an existing immutable response." + }, + "409": { + "description": "The preview changed or expired. Preview again before confirming." + }, + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequest" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Confirm the exact signed preview within 15 minutes. Only fixed templates and an OTP-verified contact belonging to the case subject are accepted. The request ID makes retries idempotent. Delivery is queued; SENT, SIMULATED and UNKNOWN are distinct outcomes. An actionable request sets the case to Awaiting user. A review update leaves its status unchanged.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.requests.create(\n \"11111111-2222-3333-4444-555555555555\",\n contact_id=\"11111111-2222-3333-4444-555555555555\",\n purpose=\"identity_document\",\n request_id=\"11111111-2222-3333-4444-555555555555\",\n expires_at=\"2026-01-15T10:00:00Z\",\n preview_token=\"your-preview-token\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.requests.create(\"11111111-2222-3333-4444-555555555555\", {\n contact_id: \"11111111-2222-3333-4444-555555555555\",\n purpose: \"identity_document\",\n request_id: \"11111111-2222-3333-4444-555555555555\",\n expires_at: \"2026-01-15T10:00:00Z\",\n preview_token: \"your-preview-token\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Requests.Create(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CustomerMessagingCaseRequestConfirmRequest{\n\tContactID: \"11111111-2222-3333-4444-555555555555\",\n\tPurpose: didit.CustomerMessagingPurposeEnum(\"identity_document\"),\n\tRequestID: \"11111111-2222-3333-4444-555555555555\",\n\tExpiresAt: time.Date(2026, 1, 15, 10, 0, 0, 0, time.UTC),\n\tPreviewToken: \"your-preview-token\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.OffsetDateTime;\nimport me.didit.Didit;\nimport me.didit.types.CustomerMessagingCaseRequestConfirmRequest;\nimport me.didit.types.CustomerMessagingPurposeEnum;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().requests().create(\"11111111-2222-3333-4444-555555555555\", CustomerMessagingCaseRequestConfirmRequest.builder()\n .contactId(\"11111111-2222-3333-4444-555555555555\")\n .purpose(CustomerMessagingPurposeEnum.of(\"identity_document\"))\n .requestId(\"11111111-2222-3333-4444-555555555555\")\n .expiresAt(OffsetDateTime.parse(\"2026-01-15T10:00:00Z\"))\n .previewToken(\"your-preview-token\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->create('11111111-2222-3333-4444-555555555555', [\n 'contact_id' => '11111111-2222-3333-4444-555555555555',\n 'purpose' => 'identity_document',\n 'request_id' => '11111111-2222-3333-4444-555555555555',\n 'expires_at' => '2026-01-15T10:00:00Z',\n 'preview_token' => 'your-preview-token',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.requests.create(\n \"11111111-2222-3333-4444-555555555555\",\n contact_id: \"11111111-2222-3333-4444-555555555555\",\n purpose: \"identity_document\",\n request_id: \"11111111-2222-3333-4444-555555555555\",\n expires_at: \"2026-01-15T10:00:00Z\",\n preview_token: \"your-preview-token\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Requests.CreateAsync(\"11111111-2222-3333-4444-555555555555\", new CustomerMessagingCaseRequestConfirmRequest\n{\n ContactId = \"11111111-2222-3333-4444-555555555555\",\n Purpose = \"identity_document\",\n RequestId = \"11111111-2222-3333-4444-555555555555\",\n ExpiresAt = DateTimeOffset.Parse(\"2026-01-15T10:00:00Z\"),\n PreviewToken = \"your-preview-token\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/fraud/checks/": { + "post": { + "operationId": "create_fraud_check", + "x-didit-sdk": { + "resource": "fraud_checks", + "method": "create", + "auth": "api_key", + "server": true + }, + "description": "Submit one or more typed identifiers and receive normalization, fresh enrichment, authoritative validation where the country and identifier type are covered, independent risk dimensions and a recommendation in one response.\n\nThese profiles support identifier checks. Selfie-based identity binding (`biometric_identity`) requires profile version `2026-09-14.2` and a Brazilian CPF; it runs only with the user's consent and when your organization is entitled to it. Earlier versions report it as `not_available_in_profile_version`. Modules that cannot run are reported as skipped with a deterministic reason; a source that could not answer is never reported as an identity mismatch.", + "summary": "Run a Fraud Check", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + }, + "example": { + "client_reference": "review-attempt-001", + "subject": { + "identifiers": [ + { + "type": "email", + "value": "applicant@example.com" + } + ], + "attributes": { + "first_name": "Alex", + "last_name": "Example" + } + }, + "checks": { + "include": [ + "networks_same_org", + "networks_cross_org", + "email_risk" + ] + } + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckResponse" + } + } + }, + "description": "" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudClientCredentialsErrorMessage" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "" + }, + "409": { + "description": "Conflict, returned without a charge: the `client_reference` was already used with a different request body; a check with the same `client_reference` is still running (retry after `Retry-After` seconds with the same reference); or the verification this check records into was deleted before the check completed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + }, + "examples": { + "different_body": { + "summary": "Reference reused with a different body", + "value": { + "error": "client_reference has already been used with a different request body." + } + }, + "still_running": { + "summary": "A check with this reference is still running", + "value": { + "error": "A check with this client_reference is already running. Retry with the same reference later." + } + }, + "verification_deleted": { + "summary": "The verification was deleted before the check completed", + "value": { + "error": "This verification was deleted before the check completed." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying, sent when a check with the same `client_reference` is still running.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "tags": [ + "Fraud Check" + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.fraud_checks.create(\n client_reference=\"review-attempt-001\",\n subject={\n \"identifiers\": [{\n \"type\": \"email\",\n \"value\": \"applicant@example.com\",\n }],\n \"attributes\": {\n \"first_name\": \"Alex\",\n \"last_name\": \"Example\",\n },\n },\n checks={\n \"include\": [\"networks_same_org\", \"networks_cross_org\", \"email_risk\"],\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.fraudChecks.create({\n client_reference: \"review-attempt-001\",\n subject: {\n identifiers: [{\n type: \"email\",\n value: \"applicant@example.com\",\n }],\n attributes: {\n first_name: \"Alex\",\n last_name: \"Example\",\n },\n },\n checks: {\n include: [\"networks_same_org\", \"networks_cross_org\", \"email_risk\"],\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.FraudChecks.Create(ctx, didit.FraudCheckRequestRequest{\n\tClientReference: didit.String(\"review-attempt-001\"),\n\tSubject: didit.FraudCheckSubjectRequest{\n\t\tIdentifiers: []didit.FraudCheckIdentifierRequest{{\n\t\t\tType: didit.FraudTypeEnum(\"email\"),\n\t\t\tValue: \"applicant@example.com\",\n\t\t}},\n\t\tAttributes: didit.Some(didit.FraudCheckAttributesRequest{\n\t\t\tFirstName: didit.String(\"Alex\"),\n\t\t\tLastName: didit.String(\"Example\"),\n\t\t}),\n\t},\n\tChecks: didit.Some(didit.FraudCheckModuleSelectionRequest{\n\t\tInclude: didit.Some([]didit.FraudExcludeEnum{didit.FraudExcludeEnum(\"networks_same_org\"), didit.FraudExcludeEnum(\"networks_cross_org\"), didit.FraudExcludeEnum(\"email_risk\")}),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.FraudCheckAttributesRequest;\nimport me.didit.types.FraudCheckIdentifierRequest;\nimport me.didit.types.FraudCheckModuleSelectionRequest;\nimport me.didit.types.FraudCheckRequestRequest;\nimport me.didit.types.FraudCheckSubjectRequest;\nimport me.didit.types.FraudExcludeEnum;\nimport me.didit.types.FraudTypeEnum;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.fraudChecks().create(FraudCheckRequestRequest.builder()\n .clientReference(\"review-attempt-001\")\n .subject(FraudCheckSubjectRequest.builder()\n .identifiers(List.of(FraudCheckIdentifierRequest.builder()\n .type(FraudTypeEnum.of(\"email\"))\n .value(\"applicant@example.com\")\n .build()))\n .attributes(FraudCheckAttributesRequest.builder()\n .firstName(\"Alex\")\n .lastName(\"Example\")\n .build())\n .build())\n .checks(FraudCheckModuleSelectionRequest.builder()\n .include(List.of(FraudExcludeEnum.of(\"networks_same_org\"), FraudExcludeEnum.of(\"networks_cross_org\"), FraudExcludeEnum.of(\"email_risk\")))\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "fraudChecks->create([\n 'client_reference' => 'review-attempt-001',\n 'subject' => [\n 'identifiers' => [[\n 'type' => 'email',\n 'value' => 'applicant@example.com',\n ]],\n 'attributes' => [\n 'first_name' => 'Alex',\n 'last_name' => 'Example',\n ],\n ],\n 'checks' => [\n 'include' => ['networks_same_org', 'networks_cross_org', 'email_risk'],\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.fraud_checks.create(\n client_reference: \"review-attempt-001\",\n subject: {\n identifiers: [{\n type: \"email\",\n value: \"applicant@example.com\",\n }],\n attributes: {\n first_name: \"Alex\",\n last_name: \"Example\",\n },\n },\n checks: {\n include: [\"networks_same_org\", \"networks_cross_org\", \"email_risk\"],\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.FraudChecks.CreateAsync(new FraudCheckRequestRequest\n{\n ClientReference = \"review-attempt-001\",\n Subject = new()\n {\n Identifiers = new FraudCheckIdentifierRequest[]\n {\n new()\n {\n Type = \"email\",\n Value = \"applicant@example.com\",\n },\n },\n Attributes = new FraudCheckAttributesRequest\n {\n FirstName = \"Alex\",\n LastName = \"Example\",\n },\n },\n Checks = new FraudCheckModuleSelectionRequest\n {\n Include = new FraudExcludeEnum[] { \"networks_same_org\", \"networks_cross_org\", \"email_risk\" },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/{request_uuid}/cancel/": { + "post": { + "operationId": "organization_application_cases_requests_cancel_create", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "cancel", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequest" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "400": { + "description": "Invalid inputs, unsupported purpose, unavailable verified contact or an existing immutable response." + }, + "409": { + "description": "The preview changed or expired. Preview again before confirming." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.requests.cancel(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.requests.cancel(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Requests.Cancel(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().requests().cancel(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->cancel('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.requests.cancel(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Requests.CancelAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/options/": { + "get": { + "operationId": "organization_application_cases_requests_options_retrieve", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "retrieve_options", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestOptions" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.requests.retrieve_options(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.requests.retrieveOptions(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Requests.RetrieveOptions(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().requests().retrieveOptions(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->retrieveOptions('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.requests.retrieve_options(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Requests.RetrieveOptionsAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/{request_uuid}/attachment/": { + "get": { + "operationId": "organization_application_cases_requests_attachment_retrieve", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "retrieve_attachment", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "image/jpeg": { + "schema": { + "type": "string", + "format": "binary" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\ncontent = client.cases.requests.retrieve_attachment(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nwith open(\"download.png\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst content = await didit.cases.requests.retrieveAttachment(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nawait writeFile(\"download.png\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresp, err := client.Cases.Requests.RetrieveAttachment(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.png\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().requests().retrieveAttachment(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\")\n .writeTo(Path.of(\"download.png\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->retrieveAttachment('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555');\n$content->saveTo('download.png');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ncontent = didit.cases.requests.retrieve_attachment(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nFile.binwrite(\"download.png\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar content = await client.Cases.Requests.RetrieveAttachmentAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nawait content.SaveToAsync(\"download.png\");" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/{record_uuid}/history/": { + "get": { + "operationId": "list_case_routing_rule_history", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "list_history", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "name": "ordering", + "required": false, + "in": "query", + "description": "Which field to use when ordering the results.", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "offset", + "required": false, + "in": "query", + "description": "The initial index from which to return the results.", + "schema": { + "type": "integer" + } + } + ], + "tags": [ + "Case Assignment" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPaginatedPolicyRevisionList" + } + } + }, + "description": "" + }, + "403": { + "description": "Case access is required." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.routing_rules.list_history(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.routingRules.listHistory(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.RoutingRules.ListHistoryAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasesRoutingRulesListHistoryParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().routingRules().listHistory(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->listHistory('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.routing_rules.list_history(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.RoutingRules.ListHistoryAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/session/{sessionId}/resubmission-preview/": { + "post": { + "summary": "Preview a resubmission email", + "description": "Render the email that would accompany a KYC or KYB resubmission without changing the session, resetting checks, extending its expiry, running checks or sending mail. Requires the `write:sessions` privilege (a scoped API key needs write access to decisions) and a session in `Approved`, `Declined`, `In Review`, `Kyc Expired` or `Abandoned` status. The server validates the selected workflow steps and renders its fixed translated template with the applicable verified white-label sender and branding. The request cannot set a subject, message body or action URL.\n\n`email_address` is optional. Without it, the email goes to the person's own address: the one they verified in an email verification step of the flow, else the one given in `contact_details` when the session was created. An address you pass wins (`recipient_source: request`). When no address is known and you pass none, `recipient` and `preview_token` are empty strings and no email can be sent.\n\nThe response includes the recipient and where it comes from, the sender, subject, HTML, language, resolved steps, whether confirming sends the email (`will_be_sent`, `not_sent_reason`) and a signed `preview_token`. `requires_user_action` is `false` when every selected step runs without the person: the resubmission then finishes on its own and no email goes out (`not_sent_reason: no_user_action`).\n\nThis preview does not reserve session state or authorize delivery. After reviewing it, call [Update Session Status](/sessions-api/update-status) with `new_status: Resubmitted`, the same `nodes_to_resubmit` and `email_language` you previewed, `send_email: true`, `email_address` set to `recipient` and `email_preview_token` set to `preview_token`. The receipt expires after 15 minutes. When a receipt is supplied, changed inputs, message content, sender or session state return `409` before any mutation or send. Existing integrations may omit the receipt. Regenerate the preview after a conflict. Treat the response as sensitive verification content; responses use `Cache-Control: no-store`. Render HTML in a sandboxed frame, never directly in your application DOM.", + "operationId": "post_v3_session_resubmission_preview", + "x-didit-sdk": { + "resource": "sessions", + "method": "preview_resubmission", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session to update. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "nodes_to_resubmit": { + "type": "array", + "description": "Steps to preview, using the same validation, selection and workflow ordering as a resubmission. Omit to auto-select recorded attempts needing resubmission.", + "items": { + "type": "object", + "required": [ + "node_id", + "feature" + ], + "properties": { + "node_id": { + "type": "string", + "description": "Node identifier as it appears in the session's workflow definition (for example `feature_ocr`, `feature_liveness`).", + "example": "feature_ocr" + }, + "feature": { + "type": "string", + "description": "Feature type of the node. The aliases `ID_VERIFICATION`, `POA`, `PHONE`, and `EMAIL` are normalized server-side to `OCR`, `PROOF_OF_ADDRESS`, `PHONE_VERIFICATION`, and `EMAIL_VERIFICATION`. `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and `KYB` pass schema validation but are always rejected with the business-rule `400` shown in the examples.", + "enum": [ + "OCR", + "OCR_BACK", + "NFC", + "AML", + "FACE", + "LIVENESS", + "FACE_MATCH", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "PROOF_OF_ADDRESS", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "FACE_SEARCH", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_DOCUMENTS", + "ID_VERIFICATION", + "POA", + "PHONE", + "EMAIL", + "KYB_REGISTRY", + "KYB_KEY_PEOPLE", + "KYB", + "GEOLOCATION" + ], + "example": "OCR" + } + } + } + }, + "email_address": { + "type": "string", + "format": "email", + "description": "Optional. Send the email to this address instead of the person's own. Without it, the recipient is the address the person verified in the flow, else the one given in `contact_details`. No email is sent by this call.", + "example": "user@example.com" + }, + "email_language": { + "type": "string", + "description": "Language for the notification email. Accepts any string at schema level; unsupported codes silently fall back to English (`en`).", + "default": "en", + "example": "en" + } + } + }, + "examples": { + "Person's Own Address": { + "summary": "Preview a proof-of-address resubmission for the person's own address", + "value": { + "nodes_to_resubmit": [ + { + "node_id": "feature_poa_1", + "feature": "POA" + } + ], + "email_language": "en" + } + }, + "Named Recipient": { + "summary": "Preview the email for an address you name", + "value": { + "nodes_to_resubmit": [ + { + "node_id": "feature_ocr", + "feature": "OCR" + } + ], + "email_address": "person@example.com", + "email_language": "en" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Preview only. The session is unchanged and no email is sent.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "recipient", + "recipient_source", + "sender", + "sender_name", + "sender_email", + "sender_kind", + "subject", + "html", + "text", + "language", + "will_be_sent", + "not_sent_reason", + "preview_token", + "nodes_to_resubmit", + "requires_user_action" + ], + "properties": { + "recipient": { + "type": "string", + "description": "The address the email goes to. An empty string when no address is known for the person: then no email can be sent and `preview_token` is empty too. Pass it as `email_address` when you confirm.", + "example": "person@example.com" + }, + "recipient_source": { + "type": "string", + "enum": [ + "email_verification", + "contact_details", + "request", + "" + ], + "description": "Where `recipient` comes from. `request`: the `email_address` you passed. `email_verification`: the address the person verified in an email verification step of the flow. `contact_details`: the address given in `contact_details` when the session was created. An empty string when there is no address.", + "example": "email_verification" + }, + "sender": { + "type": "string", + "description": "The `From` header the email goes out with.", + "example": "Example App " + }, + "sender_name": { + "type": "string", + "description": "The display name part of `sender`.", + "example": "Example App" + }, + "sender_email": { + "type": "string", + "format": "email", + "description": "The address part of `sender`.", + "example": "no-reply@mail.example.com" + }, + "sender_kind": { + "type": "string", + "enum": [ + "customer", + "didit" + ], + "description": "`customer`: the email goes out from your application's verified sender domain (white label). `didit`: it goes out from Didit's own sender.", + "example": "customer" + }, + "subject": { + "type": "string", + "description": "The subject line exactly as it is sent.", + "example": "Action Required: Complete your verification in Example App" + }, + "html": { + "type": "string", + "description": "The email's HTML exactly as it is sent, with its logo embedded as a data URL. Render it in a sandboxed frame, never directly in your application DOM.", + "example": "..." + }, + "text": { + "type": "string", + "description": "The email's words as plain text, as the person's Messages thread keeps them, for a compact preview (the HTML stays the email the person receives).", + "example": "Almost there\n\nSome steps need to be completed again.\nHere’s what to fix:\n\nhttps://verify.didit.me/session/abc123" + }, + "language": { + "type": "string", + "description": "The language the email is written in. English (`en`) when the `email_language` you asked for is not offered.", + "example": "en" + }, + "will_be_sent": { + "type": "boolean", + "description": "Whether confirming the change with `send_email: true` sends this email. `false` when `not_sent_reason` is set.", + "example": true + }, + "not_sent_reason": { + "type": "string", + "nullable": true, + "enum": [ + "no_user_action", + "no_recipient", + "sandbox", + null + ], + "description": "Why confirming would send no email, or `null` when it will be sent. `no_user_action`: every selected step runs without the person, so the resubmission finishes on its own and no email goes out. `no_recipient`: no address is known for the person and you passed none. `sandbox`: a sandbox session whose recipient has not opted into sandbox email delivery, so the send is only simulated.", + "example": null + }, + "nodes_to_resubmit": { + "type": "array", + "items": { + "type": "object", + "required": [ + "node_id", + "feature" + ], + "properties": { + "node_id": { + "type": "string", + "description": "Node identifier as it appears in the session's workflow definition (for example `feature_ocr`, `feature_liveness`).", + "example": "feature_ocr" + }, + "feature": { + "type": "string", + "description": "Feature type of the node. The aliases `ID_VERIFICATION`, `POA`, `PHONE`, and `EMAIL` are normalized server-side to `OCR`, `PROOF_OF_ADDRESS`, `PHONE_VERIFICATION`, and `EMAIL_VERIFICATION`. `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and `KYB` pass schema validation but are always rejected with the business-rule `400` shown in the examples.", + "enum": [ + "OCR", + "OCR_BACK", + "NFC", + "AML", + "FACE", + "LIVENESS", + "FACE_MATCH", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "PROOF_OF_ADDRESS", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "FACE_SEARCH", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_DOCUMENTS", + "ID_VERIFICATION", + "POA", + "PHONE", + "EMAIL", + "KYB_REGISTRY", + "KYB_KEY_PEOPLE", + "KYB", + "GEOLOCATION" + ], + "example": "OCR" + } + } + } + }, + "requires_user_action": { + "type": "boolean", + "description": "Whether at least one selected check requires user interaction. This does not predict the final workflow outcome." + }, + "preview_token": { + "type": "string", + "description": "Signed receipt for this exact email, valid for 15 minutes. Pass it as `email_preview_token` to [Update Session Status](/sessions-api/update-status). An empty string when there is no recipient.", + "example": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + } + }, + "examples": { + "Will Be Sent": { + "summary": "Default recipient: the address verified in the flow", + "value": { + "recipient": "person@example.com", + "recipient_source": "email_verification", + "sender": "Example App ", + "sender_name": "Example App", + "sender_email": "no-reply@mail.example.com", + "sender_kind": "customer", + "subject": "Action Required: Complete your verification in Example App", + "html": "...", + "language": "en", + "will_be_sent": true, + "not_sent_reason": null, + "nodes_to_resubmit": [ + { + "node_id": "feature_poa_1", + "feature": "PROOF_OF_ADDRESS" + } + ], + "requires_user_action": true, + "preview_token": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + }, + "No User Action": { + "summary": "Only backend steps are selected, so no email goes out", + "value": { + "recipient": "person@example.com", + "recipient_source": "contact_details", + "sender": "Example App ", + "sender_name": "Example App", + "sender_email": "no-reply@mail.example.com", + "sender_kind": "customer", + "subject": "Action Required: Complete your verification in Example App", + "html": "...", + "language": "en", + "will_be_sent": false, + "not_sent_reason": "no_user_action", + "nodes_to_resubmit": [ + { + "node_id": "feature_aml", + "feature": "AML" + } + ], + "requires_user_action": false, + "preview_token": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + } + } + } + }, + "headers": { + "Cache-Control": { + "description": "Always `no-store`: the preview carries verification content and must not be cached.", + "schema": { + "type": "string", + "example": "no-store" + } + } + } + }, + "400": { + "description": "The session is not in a status this endpoint accepts, it is already `Resubmitted`, the selected steps cannot be resubmitted, or a field is invalid.", + "content": { + "application/json": { + "examples": { + "Wrong Current Status": { + "summary": "Session is not in a status this endpoint accepts", + "value": { + "detail": "Only sessions with status Approved, Declined, In Review, Kyc Expired, Abandoned, Resubmitted can be updated. Current status: In Progress" + } + }, + "Already Resubmitted": { + "summary": "The session is already Resubmitted", + "value": [ + "The new status is the same as the current status." + ] + }, + "Nothing To Resubmit": { + "summary": "Auto-select found no step that needs resubmission", + "value": { + "nodes_to_resubmit": "No features found that need resubmission. All features are already approved." + } + }, + "Invalid Email": { + "summary": "email_address is not an email address", + "value": { + "email_address": [ + "Enter a valid email address." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials, a caller without the `write:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no write access to decisions." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "The steps to resubmit are all backend-only (AML, DATABASE_VALIDATION, IP_ANALYSIS), and the session was already resubmitted for backend-only steps within the last 30 seconds, so Update Session Status would refuse the resubmission. Retry after the interval in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Backend-only resubmit throttled": { + "value": { + "detail": "This session was already resubmitted for backend-only features within the last 30 seconds; repeating it cannot change the outcome. Wait before resubmitting again." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.preview_resubmission(\n \"11111111-2222-3333-4444-555555555555\",\n nodes_to_resubmit=[{\n \"node_id\": \"feature_poa_1\",\n \"feature\": \"POA\",\n }],\n email_language=\"en\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.previewResubmission(\"11111111-2222-3333-4444-555555555555\", {\n nodes_to_resubmit: [{\n node_id: \"feature_poa_1\",\n feature: \"POA\",\n }],\n email_language: \"en\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.PreviewResubmission(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsPreviewResubmissionParams{\n\tNodesToResubmit: didit.Some([]didit.SessionsPreviewResubmissionParamsNodesToResubmit{{\n\t\tNodeID: \"feature_poa_1\",\n\t\tFeature: \"POA\",\n\t}}),\n\tEmailLanguage: didit.String(\"en\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.SessionsPreviewResubmissionParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().previewResubmission(\"11111111-2222-3333-4444-555555555555\", SessionsPreviewResubmissionParams.builder()\n .nodesToResubmit(List.of(SessionsPreviewResubmissionParams.NodesToResubmit.builder()\n .nodeId(\"feature_poa_1\")\n .feature(SessionsPreviewResubmissionParams.NodesToResubmit.Feature.of(\"POA\"))\n .build()))\n .emailLanguage(\"en\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->previewResubmission('11111111-2222-3333-4444-555555555555', [\n 'nodes_to_resubmit' => [[\n 'node_id' => 'feature_poa_1',\n 'feature' => 'POA',\n ]],\n 'email_language' => 'en',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.preview_resubmission(\n \"11111111-2222-3333-4444-555555555555\",\n nodes_to_resubmit: [{\n node_id: \"feature_poa_1\",\n feature: \"POA\",\n }],\n email_language: \"en\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.PreviewResubmissionAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsPreviewResubmissionParams\n{\n NodesToResubmit = new SessionsPreviewResubmissionParamsNodesToResubmit[]\n {\n new()\n {\n NodeId = \"feature_poa_1\",\n Feature = \"POA\",\n },\n },\n EmailLanguage = \"en\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/{record_uuid}/": { + "get": { + "summary": "Retrieve routing rule", + "description": "Requires `read:cases` for the requested application.", + "operationId": "retrieve_case_routing_rules", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "record_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRule" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "404": { + "description": "Record not found in this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "patch": { + "summary": "Update routing rule", + "description": "Requires `write:cases` for the requested application.", + "operationId": "update_case_routing_rules", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "record_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRule" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "404": { + "description": "Record not found in this application." + }, + "409": { + "description": "The routing rule changed since expected_version. Review the latest rule before retrying." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRuleUpdate" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.update(\n \"11111111-2222-3333-4444-555555555555\",\n name=\"your-name\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.update(\"11111111-2222-3333-4444-555555555555\", {\n name: \"your-name\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CaseRoutingRuleUpdate{\n\tName: didit.String(\"your-name\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CaseRoutingRuleUpdate;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().update(\"11111111-2222-3333-4444-555555555555\", CaseRoutingRuleUpdate.builder()\n .name(\"your-name\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->update('11111111-2222-3333-4444-555555555555', [\n 'name' => 'your-name',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.update(\n \"11111111-2222-3333-4444-555555555555\",\n name: \"your-name\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new CaseRoutingRuleUpdate\n{\n Name = \"your-name\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/case-requests/{request_uuid}/": { + "get": { + "operationId": "case_requests_retrieve", + "x-didit-sdk": { + "resource": "case_requests", + "method": "retrieve", + "auth": "device_token", + "server": false + }, + "parameters": [ + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPublic" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + } + }, + "security": [ + { + "CaseRequestToken": [] + } + ], + "description": "Access one customer request using its request-specific token. No private case findings, original identity data or recipient address are returned. Actionable requests expire after seven days. Cancellation or case resolution invalidates the link. Responses are immutable and remain evidence for reviewer assessment; they never approve or update a source verification." + }, + "post": { + "operationId": "case_requests_create", + "x-didit-sdk": { + "resource": "case_requests", + "method": "respond", + "auth": "device_token", + "server": false + }, + "parameters": [ + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseCustomerResponseRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseCustomerResponseRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseCustomerResponseResult" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "400": { + "description": "Invalid inputs, unsupported purpose, unavailable verified contact or an existing immutable response." + }, + "409": { + "description": "The preview changed or expired. Preview again before confirming." + } + }, + "security": [ + { + "CaseRequestToken": [] + } + ], + "description": "Access one customer request using its request-specific token. No private case findings, original identity data or recipient address are returned. Actionable requests expire after seven days. Cancellation or case resolution invalidates the link. Responses are immutable and remain evidence for reviewer assessment; they never approve or update a source verification." + } + }, + "/v3/fraud/checks/plan/": { + "post": { + "operationId": "plan_create", + "x-didit-sdk": { + "resource": "fraud_checks", + "method": "plan", + "auth": "api_key", + "server": true + }, + "description": "Validate selected checks and estimate their price without executing, creating a session or billing.", + "summary": "Plan a Fraud Check", + "tags": [ + "Fraud Check" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + }, + "example": { + "client_reference": "review-attempt-001", + "subject": { + "identifiers": [ + { + "type": "email", + "value": "applicant@example.com" + } + ], + "attributes": { + "first_name": "Alex", + "last_name": "Example" + } + }, + "checks": { + "include": [ + "networks_same_org", + "networks_cross_org", + "email_risk" + ] + } + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckPlan" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudBadRequestErrorMessage" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.fraud_checks.plan(\n client_reference=\"review-attempt-001\",\n subject={\n \"identifiers\": [{\n \"type\": \"email\",\n \"value\": \"applicant@example.com\",\n }],\n \"attributes\": {\n \"first_name\": \"Alex\",\n \"last_name\": \"Example\",\n },\n },\n checks={\n \"include\": [\"networks_same_org\", \"networks_cross_org\", \"email_risk\"],\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.fraudChecks.plan({\n client_reference: \"review-attempt-001\",\n subject: {\n identifiers: [{\n type: \"email\",\n value: \"applicant@example.com\",\n }],\n attributes: {\n first_name: \"Alex\",\n last_name: \"Example\",\n },\n },\n checks: {\n include: [\"networks_same_org\", \"networks_cross_org\", \"email_risk\"],\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.FraudChecks.Plan(ctx, didit.FraudCheckRequestRequest{\n\tClientReference: didit.String(\"review-attempt-001\"),\n\tSubject: didit.FraudCheckSubjectRequest{\n\t\tIdentifiers: []didit.FraudCheckIdentifierRequest{{\n\t\t\tType: didit.FraudTypeEnum(\"email\"),\n\t\t\tValue: \"applicant@example.com\",\n\t\t}},\n\t\tAttributes: didit.Some(didit.FraudCheckAttributesRequest{\n\t\t\tFirstName: didit.String(\"Alex\"),\n\t\t\tLastName: didit.String(\"Example\"),\n\t\t}),\n\t},\n\tChecks: didit.Some(didit.FraudCheckModuleSelectionRequest{\n\t\tInclude: didit.Some([]didit.FraudExcludeEnum{didit.FraudExcludeEnum(\"networks_same_org\"), didit.FraudExcludeEnum(\"networks_cross_org\"), didit.FraudExcludeEnum(\"email_risk\")}),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.FraudCheckAttributesRequest;\nimport me.didit.types.FraudCheckIdentifierRequest;\nimport me.didit.types.FraudCheckModuleSelectionRequest;\nimport me.didit.types.FraudCheckRequestRequest;\nimport me.didit.types.FraudCheckSubjectRequest;\nimport me.didit.types.FraudExcludeEnum;\nimport me.didit.types.FraudTypeEnum;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.fraudChecks().plan(FraudCheckRequestRequest.builder()\n .clientReference(\"review-attempt-001\")\n .subject(FraudCheckSubjectRequest.builder()\n .identifiers(List.of(FraudCheckIdentifierRequest.builder()\n .type(FraudTypeEnum.of(\"email\"))\n .value(\"applicant@example.com\")\n .build()))\n .attributes(FraudCheckAttributesRequest.builder()\n .firstName(\"Alex\")\n .lastName(\"Example\")\n .build())\n .build())\n .checks(FraudCheckModuleSelectionRequest.builder()\n .include(List.of(FraudExcludeEnum.of(\"networks_same_org\"), FraudExcludeEnum.of(\"networks_cross_org\"), FraudExcludeEnum.of(\"email_risk\")))\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "fraudChecks->plan([\n 'client_reference' => 'review-attempt-001',\n 'subject' => [\n 'identifiers' => [[\n 'type' => 'email',\n 'value' => 'applicant@example.com',\n ]],\n 'attributes' => [\n 'first_name' => 'Alex',\n 'last_name' => 'Example',\n ],\n ],\n 'checks' => [\n 'include' => ['networks_same_org', 'networks_cross_org', 'email_risk'],\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.fraud_checks.plan(\n client_reference: \"review-attempt-001\",\n subject: {\n identifiers: [{\n type: \"email\",\n value: \"applicant@example.com\",\n }],\n attributes: {\n first_name: \"Alex\",\n last_name: \"Example\",\n },\n },\n checks: {\n include: [\"networks_same_org\", \"networks_cross_org\", \"email_risk\"],\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.FraudChecks.PlanAsync(new FraudCheckRequestRequest\n{\n ClientReference = \"review-attempt-001\",\n Subject = new()\n {\n Identifiers = new FraudCheckIdentifierRequest[]\n {\n new()\n {\n Type = \"email\",\n Value = \"applicant@example.com\",\n },\n },\n Attributes = new FraudCheckAttributesRequest\n {\n FirstName = \"Alex\",\n LastName = \"Example\",\n },\n },\n Checks = new FraudCheckModuleSelectionRequest\n {\n Include = new FraudExcludeEnum[] { \"networks_same_org\", \"networks_cross_org\", \"email_risk\" },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/": { + "get": { + "summary": "List routing rules", + "description": "Requires `list:cases` for the requested application.", + "operationId": "list_case_routing_rules", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000000, + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseRoutingRule" + } + }, + "count": { + "type": "integer", + "description": "Matching count, capped at 100. Follow next to continue beyond the cap." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.routing_rules.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.routingRules.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.RoutingRules.ListAutoPaging(ctx, didit.CasesRoutingRulesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().routingRules().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.routing_rules.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.RoutingRules.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "summary": "Create routing rule", + "description": "Requires `write:cases` for the requested application. Set publish=false to create an unpublished draft; Publish creates its first live version. Discarding a never-published draft deletes it.", + "operationId": "create_case_routing_rules", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "responses": { + "201": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRule" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRuleCreate" + }, + "example": { + "name": "AML screening alerts", + "conditions": [ + { + "field": "source", + "operator": "equals", + "value": "AML_SCREENING" + } + ], + "blueprint": "b1de0000-0000-4000-8000-000000000002" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.create(\n name=\"AML screening alerts\",\n conditions=[{\n \"field\": \"source\",\n \"operator\": \"equals\",\n \"value\": \"AML_SCREENING\",\n }],\n blueprint=\"b1de0000-0000-4000-8000-000000000002\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.create({\n name: \"AML screening alerts\",\n conditions: [{\n field: \"source\",\n operator: \"equals\",\n value: \"AML_SCREENING\",\n }],\n blueprint: \"b1de0000-0000-4000-8000-000000000002\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.Create(ctx, didit.CaseRoutingRuleCreate{\n\tName: \"AML screening alerts\",\n\tConditions: []didit.CaseRoutingConditionParam{{\n\t\tField: \"source\",\n\t\tOperator: \"equals\",\n\t\tValue: \"AML_SCREENING\",\n\t}},\n\tBlueprint: \"b1de0000-0000-4000-8000-000000000002\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CaseRoutingCondition;\nimport me.didit.types.CaseRoutingRuleCreate;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().create(CaseRoutingRuleCreate.builder()\n .name(\"AML screening alerts\")\n .conditions(List.of(CaseRoutingCondition.builder()\n .field(CaseRoutingCondition.Field.of(\"source\"))\n .operator(CaseRoutingCondition.Operator.of(\"equals\"))\n .value(CaseRoutingCondition.Value.ofString(\"AML_SCREENING\"))\n .build()))\n .blueprint(\"b1de0000-0000-4000-8000-000000000002\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->create([\n 'name' => 'AML screening alerts',\n 'conditions' => [[\n 'field' => 'source',\n 'operator' => 'equals',\n 'value' => 'AML_SCREENING',\n ]],\n 'blueprint' => 'b1de0000-0000-4000-8000-000000000002',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.create(\n name: \"AML screening alerts\",\n conditions: [{\n field: \"source\",\n operator: \"equals\",\n value: \"AML_SCREENING\",\n }],\n blueprint: \"b1de0000-0000-4000-8000-000000000002\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.CreateAsync(new CaseRoutingRuleCreate\n{\n Name = \"AML screening alerts\",\n Conditions = new CaseRoutingCondition[]\n {\n new()\n {\n Field = \"source\",\n Operator = \"equals\",\n Value = \"AML_SCREENING\",\n },\n },\n Blueprint = \"b1de0000-0000-4000-8000-000000000002\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/{record_uuid}/publish/": { + "post": { + "operationId": "publish_case_routing_rule", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "publish", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Assignment" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftPreconditionRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftPreconditionRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftPreconditionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationCaseRoutingRule" + } + } + }, + "description": "" + }, + "403": { + "description": "Case access is required." + }, + "409": { + "description": "The published version or draft revision has changed. Reload before retrying." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.publish(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version=0,\n expected_revision=\"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.publish(\"11111111-2222-3333-4444-555555555555\", {\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.Publish(ctx, \"11111111-2222-3333-4444-555555555555\", didit.RoutingPublicationPolicyDraftPreconditionRequest{\n\tExpectedVersion: 0,\n\tExpectedRevision: didit.String(\"11111111-2222-3333-4444-555555555555\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.RoutingPublicationPolicyDraftPreconditionRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().publish(\"11111111-2222-3333-4444-555555555555\", RoutingPublicationPolicyDraftPreconditionRequest.builder()\n .expectedVersion(0L)\n .expectedRevision(\"11111111-2222-3333-4444-555555555555\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->publish('11111111-2222-3333-4444-555555555555', [\n 'expected_version' => 0,\n 'expected_revision' => '11111111-2222-3333-4444-555555555555',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.publish(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.PublishAsync(\"11111111-2222-3333-4444-555555555555\", new RoutingPublicationPolicyDraftPreconditionRequest\n{\n ExpectedVersion = 0,\n ExpectedRevision = \"11111111-2222-3333-4444-555555555555\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/{blueprint_uuid}/publish/": { + "post": { + "operationId": "publish_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "publish", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftPreconditionRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftPreconditionRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftPreconditionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationCaseBlueprint" + } + } + }, + "description": "" + }, + "409": { + "description": "Published version or saved draft revision changed. Reload before retrying." + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.publish(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version=0,\n expected_revision=\"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.publish(\"11111111-2222-3333-4444-555555555555\", {\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.Publish(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasePublicationPolicyDraftPreconditionRequest{\n\tExpectedVersion: 0,\n\tExpectedRevision: didit.String(\"11111111-2222-3333-4444-555555555555\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasePublicationPolicyDraftPreconditionRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().publish(\"11111111-2222-3333-4444-555555555555\", CasePublicationPolicyDraftPreconditionRequest.builder()\n .expectedVersion(0L)\n .expectedRevision(\"11111111-2222-3333-4444-555555555555\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->publish('11111111-2222-3333-4444-555555555555', [\n 'expected_version' => 0,\n 'expected_revision' => '11111111-2222-3333-4444-555555555555',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.publish(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.PublishAsync(\"11111111-2222-3333-4444-555555555555\", new CasePublicationPolicyDraftPreconditionRequest\n{\n ExpectedVersion = 0,\n ExpectedRevision = \"11111111-2222-3333-4444-555555555555\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/preview/": { + "post": { + "operationId": "organization_application_cases_requests_preview_create", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "preview", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPreviewRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPreviewRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPreviewRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPreviewResponse" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "400": { + "description": "Invalid inputs, unsupported purpose, unavailable verified contact or an existing immutable response." + }, + "409": { + "description": "The preview changed or expired. Preview again before confirming." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Render the production email without sending or changing the case. Returns the exact recipient, sender, subject, body and a signed preview_token. Send the same contact_id, purpose, language, request_id, expires_at and preview_token to confirm within 15 minutes.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.requests.preview(\n \"11111111-2222-3333-4444-555555555555\",\n contact_id=\"11111111-2222-3333-4444-555555555555\",\n purpose=\"identity_document\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.requests.preview(\"11111111-2222-3333-4444-555555555555\", {\n contact_id: \"11111111-2222-3333-4444-555555555555\",\n purpose: \"identity_document\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Requests.Preview(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CustomerMessagingCaseRequestPreviewRequest{\n\tContactID: \"11111111-2222-3333-4444-555555555555\",\n\tPurpose: didit.CustomerMessagingPurposeEnum(\"identity_document\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CustomerMessagingCaseRequestPreviewRequest;\nimport me.didit.types.CustomerMessagingPurposeEnum;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().requests().preview(\"11111111-2222-3333-4444-555555555555\", CustomerMessagingCaseRequestPreviewRequest.builder()\n .contactId(\"11111111-2222-3333-4444-555555555555\")\n .purpose(CustomerMessagingPurposeEnum.of(\"identity_document\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->preview('11111111-2222-3333-4444-555555555555', [\n 'contact_id' => '11111111-2222-3333-4444-555555555555',\n 'purpose' => 'identity_document',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.requests.preview(\n \"11111111-2222-3333-4444-555555555555\",\n contact_id: \"11111111-2222-3333-4444-555555555555\",\n purpose: \"identity_document\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Requests.PreviewAsync(\"11111111-2222-3333-4444-555555555555\", new CustomerMessagingCaseRequestPreviewRequest\n{\n ContactId = \"11111111-2222-3333-4444-555555555555\",\n Purpose = \"identity_document\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/action-receipts/": { + "get": { + "operationId": "action_receipts_retrieve", + "x-didit-sdk": { + "resource": "cases", + "method": "list_action_receipts", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "query", + "name": "offset", + "schema": { + "type": "integer", + "maximum": 1000000, + "minimum": 0, + "default": 0 + } + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseActionReceiptPage" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "List subject-action receipts, newest first, with at most 20 results per page. Use next_offset for the next page. Input note text is not exposed in the history.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.list_action_receipts(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.listActionReceipts(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.ListActionReceipts(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasesListActionReceiptsParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().listActionReceipts(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->listActionReceipts('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.list_action_receipts(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ListActionReceiptsAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/notes/{note_uuid}/": { + "delete": { + "operationId": "delete_network_note", + "x-didit-sdk": { + "resource": "networks.notes", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "summary": "Delete your network note", + "description": "Requires write:networks and the authenticated author of the note. Deletion is irreversible. Connected account and verification data are unaffected.", + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Network UUID." + }, + { + "name": "note_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Console user access token in the form Bearer ." + } + ], + "responses": { + "204": { + "description": "Note deleted." + }, + "401": { + "description": "Authentication is required." + }, + "403": { + "description": "Missing permission or Networks access." + }, + "404": { + "description": "Network or note is not visible to this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\nclient = Didit()\n\nclient.networks.notes.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n extra_headers={\"Authorization\": \"Bearer YOUR_CONSOLE_USER_TOKEN\"},\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nconst didit = new Didit({\n fetch: (url, init) => {\n const headers = new Headers(init?.headers);\n headers.set(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\");\n return fetch(url, { ...init, headers });\n },\n});\n\nawait didit.networks.notes.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nclient := didit.NewClient()\n\nif err := client.Networks.Notes.Delete(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\", didit.NetworksNotesDeleteParams{\n\tAuthorization: \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n}); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.core.RequestOptions;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nDidit client = new Didit();\n\nclient.networks().notes().delete(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\", RequestOptions.builder()\n .header(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\")\n .build());" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->notes->delete('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555', [\n 'authorization' => 'Bearer YOUR_CONSOLE_USER_TOKEN',\n]);" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\ndidit = Didit::Client.new\n\ndidit.networks.notes.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n request_options: { extra_headers: { \"Authorization\" => \"Bearer YOUR_CONSOLE_USER_TOKEN\" } }\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nusing var client = new DiditClient();\n\nawait client.Networks.Notes.DeleteAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\", requestOptions: new RequestOptions\n{\n Headers = new Dictionary\n {\n [\"Authorization\"] = \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n },\n});" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/action-receipts/{receipt_uuid}/retry/": { + "post": { + "operationId": "action_receipts_retry_create", + "x-didit-sdk": { + "resource": "cases", + "method": "retry_action_receipt", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "receipt_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseActionReceipt" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Retry only this failed subject action using its saved inputs. Current write permission is checked. Applied actions return their receipt without executing again. Case closure is independent.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.retry_action_receipt(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.retryActionReceipt(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RetryActionReceipt(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().retryActionReceipt(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->retryActionReceipt('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.retry_action_receipt(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RetryActionReceiptAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/{blueprint_uuid}/draft/": { + "get": { + "operationId": "get_case_blueprint_draft", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "retrieve_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftResponse" + } + } + }, + "description": "" + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.retrieve_draft(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.retrieveDraft(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.RetrieveDraft(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().retrieveDraft(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->retrieveDraft('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.retrieve_draft(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.RetrieveDraftAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "put": { + "operationId": "save_case_blueprint_draft", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "save_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftWriteRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftWriteRequest" + }, + "example": { + "expected_version": 1, + "expected_revision": "4e5f0000-0000-4000-8000-000000000003", + "changes": { + "max_active_cases": 20 + } + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftWriteRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraft" + } + } + }, + "description": "" + }, + "409": { + "description": "Published version or saved draft revision changed. Reload before retrying." + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.save_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version=1,\n expected_revision=\"4e5f0000-0000-4000-8000-000000000003\",\n changes={\"max_active_cases\": 20},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.saveDraft(\"11111111-2222-3333-4444-555555555555\", {\n expected_version: 1,\n expected_revision: \"4e5f0000-0000-4000-8000-000000000003\",\n changes: { max_active_cases: 20 },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.SaveDraft(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasePublicationPolicyDraftWriteRequest{\n\tExpectedVersion: 1,\n\tExpectedRevision: didit.String(\"4e5f0000-0000-4000-8000-000000000003\"),\n\tChanges: map[string]any{\"max_active_cases\": 20},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.CasePublicationPolicyDraftWriteRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().saveDraft(\"11111111-2222-3333-4444-555555555555\", CasePublicationPolicyDraftWriteRequest.builder()\n .expectedVersion(1L)\n .expectedRevision(\"4e5f0000-0000-4000-8000-000000000003\")\n .changes(Map.of(\"max_active_cases\", 20))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->saveDraft('11111111-2222-3333-4444-555555555555', [\n 'expected_version' => 1,\n 'expected_revision' => '4e5f0000-0000-4000-8000-000000000003',\n 'changes' => ['max_active_cases' => 20],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.save_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version: 1,\n expected_revision: \"4e5f0000-0000-4000-8000-000000000003\",\n changes: { max_active_cases: 20 }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.SaveDraftAsync(\"11111111-2222-3333-4444-555555555555\", new CasePublicationPolicyDraftWriteRequest\n{\n ExpectedVersion = 1,\n ExpectedRevision = \"4e5f0000-0000-4000-8000-000000000003\",\n Changes = new Dictionary { [\"max_active_cases\"] = 20 },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "operationId": "discard_case_blueprint_draft", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "discard_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "204": { + "description": "No response body" + }, + "409": { + "description": "Published version or saved draft revision changed. Reload before retrying." + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftPreconditionRequest" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.cases.blueprints.discard_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version=0,\n expected_revision=\"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.cases.blueprints.discardDraft(\"11111111-2222-3333-4444-555555555555\", {\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\",\n});" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.Cases.Blueprints.DiscardDraft(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasePublicationPolicyDraftPreconditionRequest{\n\tExpectedVersion: 0,\n\tExpectedRevision: didit.String(\"11111111-2222-3333-4444-555555555555\"),\n}); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasePublicationPolicyDraftPreconditionRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().blueprints().discardDraft(\"11111111-2222-3333-4444-555555555555\", CasePublicationPolicyDraftPreconditionRequest.builder()\n .expectedVersion(0L)\n .expectedRevision(\"11111111-2222-3333-4444-555555555555\")\n .build());" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->discardDraft('11111111-2222-3333-4444-555555555555', [\n 'expected_version' => 0,\n 'expected_revision' => '11111111-2222-3333-4444-555555555555',\n]);" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.blueprints.discard_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\"\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Cases.Blueprints.DiscardDraftAsync(\"11111111-2222-3333-4444-555555555555\", new CasePublicationPolicyDraftPreconditionRequest\n{\n ExpectedVersion = 0,\n ExpectedRevision = \"11111111-2222-3333-4444-555555555555\",\n});" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/teams/{record_uuid}/": { + "get": { + "summary": "Retrieve review team", + "description": "Requires `read:cases` for the requested application.", + "operationId": "retrieve_case_teams", + "x-didit-sdk": { + "resource": "cases.teams", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "record_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeam" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "404": { + "description": "Record not found in this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.teams.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.teams.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Teams.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().teams().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->teams->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.teams.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Teams.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "patch": { + "summary": "Update review team", + "description": "Requires `write:cases` for the requested application.", + "operationId": "update_case_teams", + "x-didit-sdk": { + "resource": "cases.teams", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "record_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeam" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "404": { + "description": "Record not found in this application." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeamUpdate" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.teams.update(\n \"11111111-2222-3333-4444-555555555555\",\n name=\"your-name\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.teams.update(\"11111111-2222-3333-4444-555555555555\", {\n name: \"your-name\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Teams.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CaseReviewTeamUpdate{\n\tName: didit.String(\"your-name\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CaseReviewTeamUpdate;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().teams().update(\"11111111-2222-3333-4444-555555555555\", CaseReviewTeamUpdate.builder()\n .name(\"your-name\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->teams->update('11111111-2222-3333-4444-555555555555', [\n 'name' => 'your-name',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.teams.update(\n \"11111111-2222-3333-4444-555555555555\",\n name: \"your-name\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Teams.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new CaseReviewTeamUpdate\n{\n Name = \"your-name\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/report/": { + "get": { + "operationId": "download_network_report", + "x-didit-sdk": { + "resource": "networks", + "method": "download_report", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "summary": "Download a network evidence report", + "description": "Download a PDF of the current organization network, its observed account connections, signal references and review status. Requires read:networks. Global intelligence and private analyst notes are not included. Each account and signal list is capped at 500 entries and the report states when it is incomplete. The request does not change network status. Errors are returned as JSON.", + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Network UUID." + }, + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Console user access token in the form Bearer ." + } + ], + "responses": { + "200": { + "description": "Network evidence report. Cache-Control: no-store.", + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "description": "Authentication is required." + }, + "403": { + "description": "Missing permission or Networks access." + }, + "404": { + "description": "Network or note is not visible to this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\nclient = Didit()\n\ncontent = client.networks.download_report(\n \"11111111-2222-3333-4444-555555555555\",\n extra_headers={\"Authorization\": \"Bearer YOUR_CONSOLE_USER_TOKEN\"},\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nconst didit = new Didit({\n fetch: (url, init) => {\n const headers = new Headers(init?.headers);\n headers.set(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\");\n return fetch(url, { ...init, headers });\n },\n});\n\nconst content = await didit.networks.downloadReport(\"11111111-2222-3333-4444-555555555555\");\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nclient := didit.NewClient()\n\nresp, err := client.Networks.DownloadReport(ctx, \"11111111-2222-3333-4444-555555555555\", didit.NetworksDownloadReportParams{\n\tAuthorization: \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.core.RequestOptions;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nDidit client = new Didit();\n\nclient.networks().downloadReport(\"11111111-2222-3333-4444-555555555555\", RequestOptions.builder()\n .header(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\")\n .build())\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->downloadReport('11111111-2222-3333-4444-555555555555', [\n 'authorization' => 'Bearer YOUR_CONSOLE_USER_TOKEN',\n]);\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\ndidit = Didit::Client.new\n\ncontent = didit.networks.download_report(\n \"11111111-2222-3333-4444-555555555555\",\n request_options: { extra_headers: { \"Authorization\" => \"Bearer YOUR_CONSOLE_USER_TOKEN\" } }\n)\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nusing var client = new DiditClient();\n\nvar content = await client.Networks.DownloadReportAsync(\"11111111-2222-3333-4444-555555555555\", requestOptions: new RequestOptions\n{\n Headers = new Dictionary\n {\n [\"Authorization\"] = \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n },\n});\nawait content.SaveToAsync(\"download.pdf\");" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/notes/": { + "get": { + "operationId": "list_network_notes", + "x-didit-sdk": { + "resource": "networks.notes", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "summary": "List private network notes", + "description": "Console users with read:networks can read notes on a network visible to the application. Notes stay within the organization and are not contributed to shared intelligence. Newest notes appear first.", + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Network UUID." + }, + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Console user access token in the form Bearer ." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 30 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000000, + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "A page of notes. Count is capped at 100; use next to continue.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkNote" + } + } + } + } + } + } + }, + "401": { + "description": "Authentication is required." + }, + "403": { + "description": "Missing permission or Networks access." + }, + "404": { + "description": "Network or note is not visible to this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\nclient = Didit()\n\nfor item in client.networks.notes.list(\n \"11111111-2222-3333-4444-555555555555\",\n extra_headers={\"Authorization\": \"Bearer YOUR_CONSOLE_USER_TOKEN\"},\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nconst didit = new Didit({\n fetch: (url, init) => {\n const headers = new Headers(init?.headers);\n headers.set(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\");\n return fetch(url, { ...init, headers });\n },\n});\n\nfor await (const item of didit.networks.notes.list(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nclient := didit.NewClient()\n\niter := client.Networks.Notes.ListAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.NetworksNotesListParams{\n\tAuthorization: \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.core.RequestOptions;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nDidit client = new Didit();\n\nfor (var item : client.networks().notes().list(\"11111111-2222-3333-4444-555555555555\", RequestOptions.builder()\n .header(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\")\n .build())) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->notes->list('11111111-2222-3333-4444-555555555555', [\n 'authorization' => 'Bearer YOUR_CONSOLE_USER_TOKEN',\n]) as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\ndidit = Didit::Client.new\n\ndidit.networks.notes.list(\n \"11111111-2222-3333-4444-555555555555\",\n request_options: { extra_headers: { \"Authorization\" => \"Bearer YOUR_CONSOLE_USER_TOKEN\" } }\n).auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Networks.Notes.ListAsync(\"11111111-2222-3333-4444-555555555555\", requestOptions: new RequestOptions\n{\n Headers = new Dictionary\n {\n [\"Authorization\"] = \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n },\n}))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "operationId": "create_network_note", + "x-didit-sdk": { + "resource": "networks.notes", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "summary": "Add a private network note", + "description": "Requires a console user with write:networks. The author is assigned from the authenticated user. Supply a new random UUID client_reference for each note; retry the same body and reference after an uncertain response to avoid duplicates. Reusing a reference for another body, author or network returns 409. Notes follow the surviving investigation when networks merge.", + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Network UUID." + }, + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Console user access token in the form Bearer ." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "comment", + "client_reference" + ], + "properties": { + "comment": { + "type": "string", + "minLength": 1, + "maxLength": 10000 + }, + "client_reference": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Saved private note.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkNote" + } + } + } + }, + "200": { + "description": "Saved private note.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkNote" + } + } + } + }, + "400": { + "description": "Invalid or blank note." + }, + "409": { + "description": "The reference was already used for another note." + }, + "401": { + "description": "Authentication is required." + }, + "403": { + "description": "Missing permission or Networks access." + }, + "404": { + "description": "Network or note is not visible to this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\nclient = Didit()\n\nresult = client.networks.notes.create(\n \"11111111-2222-3333-4444-555555555555\",\n comment=\"your-comment\",\n client_reference=\"11111111-2222-3333-4444-555555555555\",\n extra_headers={\"Authorization\": \"Bearer YOUR_CONSOLE_USER_TOKEN\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nconst didit = new Didit({\n fetch: (url, init) => {\n const headers = new Headers(init?.headers);\n headers.set(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\");\n return fetch(url, { ...init, headers });\n },\n});\n\nconst result = await didit.networks.notes.create(\"11111111-2222-3333-4444-555555555555\", {\n comment: \"your-comment\",\n client_reference: \"11111111-2222-3333-4444-555555555555\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nclient := didit.NewClient()\n\nresult, err := client.Networks.Notes.Create(ctx, \"11111111-2222-3333-4444-555555555555\", didit.NetworksNotesCreateParams{\n\tComment: \"your-comment\",\n\tClientReference: \"11111111-2222-3333-4444-555555555555\",\n\tAuthorization: \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.core.RequestOptions;\nimport me.didit.types.NetworksNotesCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nDidit client = new Didit();\n\nvar result = client.networks().notes().create(\"11111111-2222-3333-4444-555555555555\", NetworksNotesCreateParams.builder()\n .comment(\"your-comment\")\n .clientReference(\"11111111-2222-3333-4444-555555555555\")\n .build(), RequestOptions.builder()\n .header(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->notes->create('11111111-2222-3333-4444-555555555555', [\n 'comment' => 'your-comment',\n 'client_reference' => '11111111-2222-3333-4444-555555555555',\n 'authorization' => 'Bearer YOUR_CONSOLE_USER_TOKEN',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\ndidit = Didit::Client.new\n\nresult = didit.networks.notes.create(\n \"11111111-2222-3333-4444-555555555555\",\n comment: \"your-comment\",\n client_reference: \"11111111-2222-3333-4444-555555555555\",\n request_options: { extra_headers: { \"Authorization\" => \"Bearer YOUR_CONSOLE_USER_TOKEN\" } }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.Notes.CreateAsync(\"11111111-2222-3333-4444-555555555555\", new NetworksNotesCreateParams\n{\n Comment = \"your-comment\",\n ClientReference = \"11111111-2222-3333-4444-555555555555\",\n}, requestOptions: new RequestOptions\n{\n Headers = new Dictionary\n {\n [\"Authorization\"] = \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/{record_uuid}/draft/": { + "get": { + "operationId": "get_case_routing_rule_draft", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "retrieve_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Assignment" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftResponse" + } + } + }, + "description": "" + }, + "403": { + "description": "Case access is required." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.retrieve_draft(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.retrieveDraft(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.RetrieveDraft(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().retrieveDraft(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->retrieveDraft('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.retrieve_draft(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.RetrieveDraftAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "put": { + "operationId": "save_case_routing_rule_draft", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "save_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Assignment" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftWriteRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftWriteRequest" + }, + "example": { + "expected_version": 1, + "expected_revision": "4e5f0000-0000-4000-8000-000000000003", + "changes": { + "priority": 10 + } + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftWriteRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraft" + } + } + }, + "description": "" + }, + "403": { + "description": "Case access is required." + }, + "409": { + "description": "The published version or draft revision has changed. Reload before retrying." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.save_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version=1,\n expected_revision=\"4e5f0000-0000-4000-8000-000000000003\",\n changes={\"priority\": 10},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.saveDraft(\"11111111-2222-3333-4444-555555555555\", {\n expected_version: 1,\n expected_revision: \"4e5f0000-0000-4000-8000-000000000003\",\n changes: { priority: 10 },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.SaveDraft(ctx, \"11111111-2222-3333-4444-555555555555\", didit.RoutingPublicationPolicyDraftWriteRequest{\n\tExpectedVersion: 1,\n\tExpectedRevision: didit.String(\"4e5f0000-0000-4000-8000-000000000003\"),\n\tChanges: map[string]any{\"priority\": 10},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.RoutingPublicationPolicyDraftWriteRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().saveDraft(\"11111111-2222-3333-4444-555555555555\", RoutingPublicationPolicyDraftWriteRequest.builder()\n .expectedVersion(1L)\n .expectedRevision(\"4e5f0000-0000-4000-8000-000000000003\")\n .changes(Map.of(\"priority\", 10))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->saveDraft('11111111-2222-3333-4444-555555555555', [\n 'expected_version' => 1,\n 'expected_revision' => '4e5f0000-0000-4000-8000-000000000003',\n 'changes' => ['priority' => 10],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.save_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version: 1,\n expected_revision: \"4e5f0000-0000-4000-8000-000000000003\",\n changes: { priority: 10 }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.SaveDraftAsync(\"11111111-2222-3333-4444-555555555555\", new RoutingPublicationPolicyDraftWriteRequest\n{\n ExpectedVersion = 1,\n ExpectedRevision = \"4e5f0000-0000-4000-8000-000000000003\",\n Changes = new Dictionary { [\"priority\"] = 10 },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "operationId": "discard_case_routing_rule_draft", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "discard_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Assignment" + ], + "responses": { + "204": { + "description": "No response body" + }, + "403": { + "description": "Case access is required." + }, + "409": { + "description": "The published version or draft revision has changed. Reload before retrying." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftPreconditionRequest" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.cases.routing_rules.discard_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version=0,\n expected_revision=\"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.cases.routingRules.discardDraft(\"11111111-2222-3333-4444-555555555555\", {\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\",\n});" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.Cases.RoutingRules.DiscardDraft(ctx, \"11111111-2222-3333-4444-555555555555\", didit.RoutingPublicationPolicyDraftPreconditionRequest{\n\tExpectedVersion: 0,\n\tExpectedRevision: didit.String(\"11111111-2222-3333-4444-555555555555\"),\n}); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.RoutingPublicationPolicyDraftPreconditionRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().routingRules().discardDraft(\"11111111-2222-3333-4444-555555555555\", RoutingPublicationPolicyDraftPreconditionRequest.builder()\n .expectedVersion(0L)\n .expectedRevision(\"11111111-2222-3333-4444-555555555555\")\n .build());" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->discardDraft('11111111-2222-3333-4444-555555555555', [\n 'expected_version' => 0,\n 'expected_revision' => '11111111-2222-3333-4444-555555555555',\n]);" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.routing_rules.discard_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\"\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Cases.RoutingRules.DiscardDraftAsync(\"11111111-2222-3333-4444-555555555555\", new RoutingPublicationPolicyDraftPreconditionRequest\n{\n ExpectedVersion = 0,\n ExpectedRevision = \"11111111-2222-3333-4444-555555555555\",\n});" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/duplicates/": { + "post": { + "operationId": "duplicates_create", + "x-didit-sdk": { + "resource": "cases", + "method": "find_duplicates", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicateQueryRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicateQueryRequest" + }, + "example": { + "subject": { + "type": "user", + "uuid": "5b9a0000-0000-4000-8000-00000000f00d" + } + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicateQueryRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicatePage" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Review existing cases for a subject or owned source record before manual case creation. Both open and resolved investigations are returned. Each page contains at most 20 cases; use next_offset to request another page.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.find_duplicates(\n subject={\n \"type\": \"user\",\n \"uuid\": \"5b9a0000-0000-4000-8000-00000000f00d\",\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.findDuplicates({\n subject: {\n type: \"user\",\n uuid: \"5b9a0000-0000-4000-8000-00000000f00d\",\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.FindDuplicates(ctx, didit.CaseDuplicateCaseDuplicateQueryRequest{\n\tSubject: didit.Some(didit.CaseDuplicateCaseSubjectInputRequest{\n\t\tType: didit.CaseDuplicateTypeEnum(\"user\"),\n\t\tUUID: \"5b9a0000-0000-4000-8000-00000000f00d\",\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CaseDuplicateCaseDuplicateQueryRequest;\nimport me.didit.types.CaseDuplicateCaseSubjectInputRequest;\nimport me.didit.types.CaseDuplicateTypeEnum;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().findDuplicates(CaseDuplicateCaseDuplicateQueryRequest.builder()\n .subject(CaseDuplicateCaseSubjectInputRequest.builder()\n .type(CaseDuplicateTypeEnum.of(\"user\"))\n .uuid(\"5b9a0000-0000-4000-8000-00000000f00d\")\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->findDuplicates([\n 'subject' => [\n 'type' => 'user',\n 'uuid' => '5b9a0000-0000-4000-8000-00000000f00d',\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.find_duplicates(\n subject: {\n type: \"user\",\n uuid: \"5b9a0000-0000-4000-8000-00000000f00d\",\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.FindDuplicatesAsync(new CaseDuplicateCaseDuplicateQueryRequest\n{\n Subject = new CaseDuplicateCaseSubjectInputRequest\n {\n Type = \"user\",\n Uuid = \"5b9a0000-0000-4000-8000-00000000f00d\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/preview/": { + "post": { + "summary": "Test case routing", + "description": "Requires `read:cases` for the requested application. Evaluates current rules or a supplied draft against current reviewer access, availability, skills and capacity. It does not save the draft or reserve reviewer capacity.", + "operationId": "preview_case_routing", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "preview", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingPreview" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingPreviewRequest" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.preview(\n source=\"MANUAL\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.preview({\n source: \"MANUAL\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.Preview(ctx, didit.CaseRoutingPreviewRequest{\n\tSource: \"MANUAL\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CaseRoutingPreviewRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().preview(CaseRoutingPreviewRequest.builder()\n .source(CaseRoutingPreviewRequest.Source.of(\"MANUAL\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->preview([\n 'source' => 'MANUAL',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.preview(source: \"MANUAL\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.PreviewAsync(new CaseRoutingPreviewRequest\n{\n Source = \"MANUAL\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/reviewers/": { + "get": { + "summary": "List eligible case reviewers", + "description": "Requires `list:cases` for the requested application.", + "operationId": "list_case_reviewers", + "x-didit-sdk": { + "resource": "cases", + "method": "list_reviewers", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "format": "uuid" + }, + "email": { + "type": "string", + "format": "email" + }, + "name": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "503": { + "description": "Reviewer access could not be verified. Retry later." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.list_reviewers()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.listReviewers();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.ListReviewers(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().listReviewers();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->listReviewers();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.list_reviewers\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ListReviewersAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/{blueprint_uuid}/history/": { + "get": { + "operationId": "list_case_blueprint_history", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "list_history", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "name": "ordering", + "required": false, + "in": "query", + "description": "Which field to use when ordering the results.", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "offset", + "required": false, + "in": "query", + "description": "The initial index from which to return the results.", + "schema": { + "type": "integer" + } + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPaginatedPolicyRevisionList" + } + } + }, + "description": "" + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.blueprints.list_history(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.blueprints.listHistory(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.Blueprints.ListHistoryAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasesBlueprintsListHistoryParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().blueprints().listHistory(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->listHistory('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.blueprints.list_history(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.Blueprints.ListHistoryAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/session/{sessionId}/signatures/{nodeId}/documents/{documentId}/file/": { + "get": { + "summary": "Download a signed document", + "description": "Download the signed PDF of one document of an E-signature step. Use the `signed_file` path of `signatures[].documents[]` in the decision. The PDF holds the document the signer was shown with their field values stamped and the evidence page (unless `signature_evidence_page` is off). An Advanced (AES) PDF also carries the PAdES signature with its timestamp; a Simple PDF has no digital signature, seal or timestamp. Didit checks the file against `sha256_signed` before returning it.\n\nWorks for User Verification (KYC) and Business Verification (KYB) sessions. Needs `read:sessions`; your API key works. See the [E-signature report](/core-technology/electronic-signature/report-electronic-signature#download-the-signed-documents).", + "operationId": "get_v3_session_signature_document_file", + "x-didit-sdk": { + "resource": "sessions.signatures", + "method": "download_document", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "The User Verification (KYC) or Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "nodeId", + "in": "path", + "required": true, + "description": "The E-signature step: `node_id` of the item in `signatures[]`.", + "schema": { + "type": "string", + "example": "feature_signature" + } + }, + { + "name": "documentId", + "in": "path", + "required": true, + "description": "`id` of the document in `signatures[].documents[]`.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "The signed PDF.", + "headers": { + "Content-Disposition": { + "description": "Attachment with the file name.", + "schema": { + "type": "string", + "example": "attachment; filename=\"agreement-signed.pdf\"" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "403": { + "description": "Missing credentials, or a credential that can't read this session. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "You do not have permission to perform this action." + } + } + } + }, + "404": { + "description": "No signed document matches: the session, step or document doesn't exist, was deleted, or isn't signed yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not signed yet": { + "summary": "The signer hasn't signed", + "value": { + "code": "signature_not_signed", + "detail": "signature_not_signed" + } + }, + "Unknown document": { + "summary": "No such document in this step", + "value": { + "code": "signature_not_found", + "detail": "signature_not_found" + } + } + } + } + } + }, + "409": { + "description": "The stored file no longer matches its recorded SHA-256. Nothing is returned.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Changed file": { + "summary": "Stored file doesn't match its hash", + "value": { + "code": "signature_document_changed", + "detail": "signature_document_changed" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.sessions.signatures.download_document(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.sessions.signatures.downloadDocument(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Sessions.Signatures.DownloadDocument(ctx, \"11111111-2222-3333-4444-555555555555\", \"feature_signature\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().signatures().downloadDocument(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\", \"11111111-2222-3333-4444-555555555555\")\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->signatures->downloadDocument('11111111-2222-3333-4444-555555555555', 'feature_signature', '11111111-2222-3333-4444-555555555555');\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.sessions.signatures.download_document(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Sessions.Signatures.DownloadDocumentAsync(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\", \"11111111-2222-3333-4444-555555555555\");\nawait content.SaveToAsync(\"download.pdf\");" + } + ] + } + }, + "/v3/session/{sessionId}/signatures/{nodeId}/evidence/": { + "get": { + "summary": "Download the signature evidence", + "description": "Download a signer's evidence PDF: the level and format, the signer, the IP address, each document with its original SHA-256 and field values and the SHA-256 of every signed file, plus, for an Advanced (AES) signature, the verified identity session, the code confirmation, the certificate and the timestamp. Use the `evidence_file` path of the signer's item in `signatures[]`.\n\nAlways pass `signature_id` when a Business Verification step has several signers: without it, the route picks one of the step's signers. Needs `read:sessions`; your API key works.", + "operationId": "get_v3_session_signature_evidence", + "x-didit-sdk": { + "resource": "sessions.signatures", + "method": "download_evidence", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "The User Verification (KYC) or Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "nodeId", + "in": "path", + "required": true, + "description": "The E-signature step: `node_id` of the item in `signatures[]`.", + "schema": { + "type": "string", + "example": "feature_signature" + } + }, + { + "name": "signature_id", + "in": "query", + "required": false, + "description": "`id` of the signer's item in `signatures[]`.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "The evidence PDF.", + "headers": { + "Content-Disposition": { + "description": "Attachment with the file name.", + "schema": { + "type": "string", + "example": "attachment; filename=\"signature-evidence.pdf\"" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "403": { + "description": "Missing credentials, or a credential that can't read this session. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "You do not have permission to perform this action." + } + } + } + }, + "404": { + "description": "No evidence matches: the session, step or signer doesn't exist, was deleted, or hasn't signed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not signed yet": { + "summary": "The signer hasn't signed", + "value": { + "code": "signature_not_signed", + "detail": "signature_not_signed" + } + }, + "Unknown signer": { + "summary": "No such signature in this step", + "value": { + "code": "signature_not_found", + "detail": "signature_not_found" + } + } + } + } + } + }, + "409": { + "description": "The stored file no longer matches its recorded SHA-256. Nothing is returned.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Changed file": { + "summary": "Stored file doesn't match its hash", + "value": { + "code": "signature_document_changed", + "detail": "signature_document_changed" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.sessions.signatures.download_evidence(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.sessions.signatures.downloadEvidence(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n);\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Sessions.Signatures.DownloadEvidence(ctx, \"11111111-2222-3333-4444-555555555555\", \"feature_signature\", didit.SessionsSignaturesDownloadEvidenceParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().signatures().downloadEvidence(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\")\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->signatures->downloadEvidence('11111111-2222-3333-4444-555555555555', 'feature_signature');\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.sessions.signatures.download_evidence(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\"\n)\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Sessions.Signatures.DownloadEvidenceAsync(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\");\nawait content.SaveToAsync(\"download.pdf\");" + } + ] + } + }, + "/v3/session/{sessionId}/signatures/{nodeId}/fields/{fieldId}/image/": { + "get": { + "summary": "Download a signature image", + "description": "Download the signature or initials a signer applied to a field, as PNG. Use the `image_url` of the field in `signatures[].documents[].fields[]`. Needs `read:sessions`; your API key works.", + "operationId": "get_v3_session_signature_field_image", + "x-didit-sdk": { + "resource": "sessions.signatures", + "method": "download_field_image", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "The User Verification (KYC) or Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "nodeId", + "in": "path", + "required": true, + "description": "The E-signature step: `node_id` of the item in `signatures[]`.", + "schema": { + "type": "string", + "example": "feature_signature" + } + }, + { + "name": "fieldId", + "in": "path", + "required": true, + "description": "The field on this signer's copy, as in the field's `image_url`.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "The image.", + "content": { + "image/png": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "400": { + "description": "The stored image no longer matches its recorded SHA-256.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "example": { + "code": "signature_document_changed", + "detail": "signature_document_changed" + } + } + } + }, + "403": { + "description": "Missing credentials, or a credential that can't read this session. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "You do not have permission to perform this action." + } + } + } + }, + "404": { + "description": "No image matches: the session, step or field doesn't exist, was deleted, or has no applied signature or initials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Unknown field": { + "summary": "No such field", + "value": { + "code": "signature_field_not_found", + "detail": "signature_field_not_found" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.sessions.signatures.download_field_image(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nwith open(\"download.png\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.sessions.signatures.downloadFieldImage(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nawait writeFile(\"download.png\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Sessions.Signatures.DownloadFieldImage(ctx, \"11111111-2222-3333-4444-555555555555\", \"feature_signature\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.png\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().signatures().downloadFieldImage(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\", \"11111111-2222-3333-4444-555555555555\")\n .writeTo(Path.of(\"download.png\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->signatures->downloadFieldImage('11111111-2222-3333-4444-555555555555', 'feature_signature', '11111111-2222-3333-4444-555555555555');\n$content->saveTo('download.png');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.sessions.signatures.download_field_image(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nFile.binwrite(\"download.png\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Sessions.Signatures.DownloadFieldImageAsync(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\", \"11111111-2222-3333-4444-555555555555\");\nawait content.SaveToAsync(\"download.png\");" + } + ] + } + }, + "/v3/session/{sessionId}/signatures/{nodeId}/validate/": { + "post": { + "summary": "Validate signatures again", + "description": "Re-check the signed documents of an E-signature step. Didit re-reads every signed PDF, checks its SHA-256 and, for an Advanced (AES) signature, validates the PAdES signature, the certificate, the revocation data and the timestamp (a Simple signature has no digital signature, so only the file integrity is checked), then saves the result in each signer's `validation` and returns the updated items. Nothing is signed again. Send `signature_id` to check one signer; without it, every signer of the step is checked, and the call fails if any of them hasn't signed.\n\nNeeds `write:sessions`; your API key works. In the console it's the **Validate again** button.", + "operationId": "post_v3_session_signature_validate", + "x-didit-sdk": { + "resource": "sessions.signatures", + "method": "validate", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "The User Verification (KYC) or Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "nodeId", + "in": "path", + "required": true, + "description": "The E-signature step: `node_id` of the item in `signatures[]`.", + "schema": { + "type": "string", + "example": "feature_signature" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "signature_id": { + "type": "string", + "format": "uuid", + "description": "`id` of one signer's item in `signatures[]`." + } + } + }, + "example": { + "signature_id": "0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10" + } + } + } + }, + "responses": { + "200": { + "description": "The checked signers, with their updated `validation`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "signatures": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SignatureResult" + } + } + } + } + } + } + }, + "400": { + "description": "A stored signed PDF no longer matches the SHA-256 recorded when it was signed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Document changed": { + "summary": "A signed file no longer matches its hash", + "value": { + "code": "signature_document_changed", + "detail": "signature_document_changed" + } + } + } + } + } + }, + "403": { + "description": "Missing credentials, or a credential that can't read this session. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "You do not have permission to perform this action." + } + } + } + }, + "404": { + "description": "No signature matches: the session, step or signer doesn't exist or was deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Unknown signer": { + "summary": "No such signature in this step", + "value": { + "code": "signature_not_found", + "detail": "signature_not_found" + } + } + } + } + } + }, + "409": { + "description": "A signer of the step hasn't signed, so there's nothing to validate.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not signed": { + "summary": "A signer hasn't signed yet", + "value": { + "code": "signature_not_signed", + "detail": "signature_not_signed" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.signatures.validate(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n signature_id=\"0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.signatures.validate(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n {\n signature_id: \"0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10\",\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Signatures.Validate(ctx, \"11111111-2222-3333-4444-555555555555\", \"feature_signature\", didit.SessionsSignaturesValidateParams{\n\tSignatureID: didit.String(\"0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsSignaturesValidateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().signatures().validate(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\", SessionsSignaturesValidateParams.builder()\n .signatureId(\"0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->signatures->validate('11111111-2222-3333-4444-555555555555', 'feature_signature', [\n 'signature_id' => '0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.signatures.validate(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n signature_id: \"0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Signatures.ValidateAsync(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\", new SessionsSignaturesValidateParams\n{\n SignatureId = \"0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/document-ai/{sessionId}/documents/{documentUuid}/": { + "patch": { + "summary": "Update a Document AI document", + "description": "Correct the fields Document AI extracted from one uploaded document, set its review status, or both, on a KYC or KYB session. `extracted_data` must be a JSON object and is merged key by key: a key you send replaces the stored value (a key sent as `null` is stored as `null`) and keys you leave out keep their values. The document status feeds the Document AI result of its workflow node in the session decision; the session status itself is not recomputed. Every successful call, including one that changes nothing, sends a `data.updated` [webhook](https://docs.didit.me/integration/webhooks) with trigger `manual_review`. A scoped API key needs `decisions: write`; see [Document AI](https://docs.didit.me/core-technology/document-ai/overview).", + "operationId": "patch_v3_document_ai_document", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_document_ai_data", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "documentUuid", + "in": "path", + "required": true, + "description": "UUID of the Document AI document (`document_ai_documents[].items[].uuid` in `GET /v3/session/{sessionId}/decision/`). It must belong to the session in the path.", + "schema": { + "type": "string", + "format": "uuid", + "example": "44444444-5555-6666-7777-888888888888" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "extracted_data": { + "type": "object", + "additionalProperties": true, + "description": "Field keys (the keys you configured for the document) and their corrected values. Must be a JSON object; merged key by key into the stored data. Leave it out rather than sending `null`, which returns `400`.", + "example": { + "invoice_number": "INV-2026-0042" + } + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Not Finished" + ], + "description": "New review status of the document.", + "example": "Approved" + } + } + }, + "example": { + "extracted_data": { + "invoice_number": "INV-2026-0042" + }, + "status": "Approved" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/document-ai/11111111-2222-3333-4444-555555555555/documents/44444444-5555-6666-7777-888888888888/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"extracted_data\": {\"invoice_number\": \"INV-2026-0042\"}, \"status\": \"Approved\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_document_ai_data(\n \"11111111-2222-3333-4444-555555555555\",\n \"44444444-5555-6666-7777-888888888888\",\n extracted_data={\"invoice_number\": \"INV-2026-0042\"},\n status=\"Approved\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateDocumentAiData(\n \"11111111-2222-3333-4444-555555555555\",\n \"44444444-5555-6666-7777-888888888888\",\n {\n extracted_data: { invoice_number: \"INV-2026-0042\" },\n status: \"Approved\",\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateDocumentAiData(ctx, \"11111111-2222-3333-4444-555555555555\", \"44444444-5555-6666-7777-888888888888\", didit.SessionsUpdateDocumentAiDataParams{\n\tExtractedData: didit.Some(map[string]any{\"invoice_number\": \"INV-2026-0042\"}),\n\tStatus: didit.Some(didit.SessionsUpdateDocumentAiDataParamsStatus(\"Approved\")),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SessionsUpdateDocumentAiDataParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateDocumentAiData(\"11111111-2222-3333-4444-555555555555\", \"44444444-5555-6666-7777-888888888888\", SessionsUpdateDocumentAiDataParams.builder()\n .extractedData(Map.of(\"invoice_number\", \"INV-2026-0042\"))\n .status(SessionsUpdateDocumentAiDataParams.Status.of(\"Approved\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateDocumentAiData('11111111-2222-3333-4444-555555555555', '44444444-5555-6666-7777-888888888888', [\n 'extracted_data' => ['invoice_number' => 'INV-2026-0042'],\n 'status' => 'Approved',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_document_ai_data(\n \"11111111-2222-3333-4444-555555555555\",\n \"44444444-5555-6666-7777-888888888888\",\n extracted_data: { invoice_number: \"INV-2026-0042\" },\n status: \"Approved\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateDocumentAiDataAsync(\"11111111-2222-3333-4444-555555555555\", \"44444444-5555-6666-7777-888888888888\", new SessionsUpdateDocumentAiDataParams\n{\n ExtractedData = new Dictionary { [\"invoice_number\"] = \"INV-2026-0042\" },\n Status = new SessionsUpdateDocumentAiDataParamsStatus(\"Approved\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Document updated. Returns the document after the merge.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "uuid", + "document_key", + "original_filename", + "file_url", + "status", + "extracted_data" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Document identifier." + }, + "document_key": { + "type": "string", + "description": "The configured document this upload corresponds to." + }, + "original_filename": { + "type": "string", + "nullable": true, + "description": "File name the document was uploaded with." + }, + "file_url": { + "type": "string", + "nullable": true, + "description": "Storage path of the uploaded file. It is not a download link: read the presigned `file_url` from the session decision to download the file." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Review status of the document." + }, + "extracted_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "All extracted fields after the merge, keyed by field key." + } + } + }, + "example": { + "uuid": "44444444-5555-6666-7777-888888888888", + "document_key": "invoice", + "original_filename": "invoice-2026-09.pdf", + "file_url": "document_ai/11111111-2222-3333-4444-555555555555-66666666-7777-8888-9999-000000000000.pdf", + "status": "Approved", + "extracted_data": { + "invoice_number": "INV-2026-0042", + "total_amount": 1250.0, + "issue_date": "2026-09-01" + } + } + } + } + }, + "400": { + "description": "Validation error, checked before the document is looked up: `status` not one of the allowed values, or `extracted_data` sent as `null`.", + "content": { + "application/json": { + "examples": { + "Invalid status": { + "summary": "status not in the enum", + "value": { + "status": [ + "\"Rejected\" is not a valid choice." + ] + } + }, + "Null extracted_data": { + "summary": "extracted_data is null", + "value": { + "extracted_data": [ + "This field may not be null." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits, or no current document with this UUID belongs to it. The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's limits (also returned for a session deleted moments ago)", + "value": { + "detail": "Session not found." + } + }, + "Unknown document": { + "summary": "documentUuid does not belong to the session", + "value": { + "detail": "Document AI document not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/aml/dataset-freshness/": { + "get": { + "summary": "Get AML dataset freshness", + "description": "Report how current the datasets behind AML screening are, per source family (`sanctions`, `pep`, `warnings`, `adverse_media`), as audit evidence for [ongoing AML monitoring](https://docs.didit.me/core-technology/aml-screening/continuous-monitoring-aml-screening#proving-that-monitoring-ran). The answer is platform-wide: no session in the path, no customer data in the body, the same answer for every application.\n\nAlways `200` for an accepted key. Check `availability` first: when it is not `available`, `dataset_freshness` is `null`. Dates are never estimated: a family whose datasets reported no date is absent from `sources`, and a family where only some datasets reported one has `oldest_updated_at: null`. Readings are taken while ongoing monitoring runs, not on each call; `captured_at` says when.\n\nA scoped API key needs `sessions: read`; a key limited to some workflows or to approved sessions cannot call this endpoint. Nothing is billed.", + "operationId": "get_aml_dataset_freshness", + "x-didit-sdk": { + "resource": "aml", + "method": "retrieve_dataset_freshness", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/aml/dataset-freshness/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.aml.retrieve_dataset_freshness()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.aml.retrieveDatasetFreshness();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.AML.RetrieveDatasetFreshness(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.aml().retrieveDatasetFreshness();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "aml->retrieveDatasetFreshness();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.aml.retrieve_dataset_freshness\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Aml.RetrieveDatasetFreshnessAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The latest dataset freshness reading, or `null` with the reason.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "dataset_freshness", + "availability" + ], + "properties": { + "dataset_freshness": { + "allOf": [ + { + "$ref": "#/components/schemas/AMLDatasetFreshness" + } + ], + "nullable": true, + "description": "The latest reading, or `null` when `availability` is not `available`." + }, + "availability": { + "type": "string", + "enum": [ + "available", + "not_configured", + "not_yet_collected" + ], + "description": "`available` when a reading exists; `not_configured` when dataset dates are not collected in this environment; `not_yet_collected` when collection is set up but no reading exists yet." + } + } + }, + "examples": { + "Available": { + "value": { + "dataset_freshness": { + "captured_at": "2026-10-17T06:00:12.345678+00:00", + "provider": "aml_data_source", + "catalog_version": "20261017054500-abc", + "sources": { + "sanctions": { + "last_updated_at": "2026-10-17T04:10:00+00:00", + "oldest_updated_at": "2026-10-15T22:30:00+00:00", + "dataset_count": 48, + "dated_dataset_count": 48 + }, + "pep": { + "last_updated_at": "2026-10-16T23:05:00+00:00", + "oldest_updated_at": null, + "dataset_count": 12, + "dated_dataset_count": 11 + } + } + }, + "availability": "available" + } + }, + "Not collected yet": { + "value": { + "dataset_freshness": null, + "availability": "not_yet_collected" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing key gets the generic message; a wrong, revoked or expired key gets `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions read": { + "value": { + "detail": "This API key has no read access to sessions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/": { + "get": { + "summary": "List lists", + "description": "Returns the application's lists, newest first: the system lists Didit provisions (one blocklist per entry type, the **High Risk Countries** custom list, and any system allowlist) and the allowlists and custom lists you created. Filter with `list_type` and `entry_type`; deleted lists never appear. Results are paginated with `limit` and `offset`. A scoped API key needs `workflows: read`. See [Lists](https://docs.didit.me/management-api/lists/overview) for what each list type does.", + "operationId": "list_lists", + "x-didit-sdk": { + "resource": "lists", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_type", + "in": "query", + "required": false, + "description": "Only lists of this type.", + "schema": { + "type": "string", + "enum": [ + "blocklist", + "allowlist", + "custom" + ] + } + }, + { + "name": "entry_type", + "in": "query", + "required": false, + "description": "Only lists of this entry type.", + "schema": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email", + "ip_address", + "device_fingerprint", + "wallet_address", + "bank_account", + "country", + "user", + "business", + "key" + ] + } + }, + { + "name": "ordering", + "in": "query", + "required": false, + "description": "Sort order: `-created_at` (newest first, the default) or `created_at` (oldest first).", + "schema": { + "type": "string", + "enum": [ + "-created_at", + "created_at" + ], + "default": "-created_at" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Page size. Defaults to 50. A value above 1000000 returns `400`; zero, a negative or a non-numeric value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Zero-based index of the first item to return. Prefer following `next`. A value above 1000000 returns `400`; a negative or non-numeric value falls back to 0.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/lists/?list_type=blocklist' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.lists.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.lists.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Lists.ListAutoPaging(ctx, didit.ListsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.lists().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.lists.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Lists.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "A page of lists.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "description": "Total number of lists matching the filters." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ComplianceList" + } + } + } + }, + "examples": { + "Lists": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "uuid": "22222222-3333-4444-5555-666666666666", + "name": "Trusted office IPs", + "description": "Office egress ranges", + "list_type": "allowlist", + "entry_type": "ip_address", + "is_system": false, + "entry_count": 3, + "last_entry_added_at": "2026-10-02T08:15:27.501334Z", + "created_at": "2026-09-30T13:20:45.889120Z", + "updated_at": "2026-09-30T13:20:45.889120Z" + }, + { + "uuid": "11111111-2222-3333-4444-555555555555", + "name": "Face Blocklist", + "description": "Blocked biometric faces. When a face matching an entry in this list is detected during liveness verification, the session will be automatically declined.", + "list_type": "blocklist", + "entry_type": "face", + "is_system": true, + "entry_count": 12, + "last_entry_added_at": "2026-10-09T16:42:03.118204Z", + "created_at": "2026-01-14T10:02:11.402917Z", + "updated_at": "2026-01-14T10:02:11.402917Z" + } + ] + } + } + } + } + } + }, + "400": { + "description": "A query parameter is invalid.", + "content": { + "application/json": { + "examples": { + "Unknown list type": { + "value": { + "list_type": [ + "\"denylist\" is not a valid choice." + ] + } + }, + "Unsupported ordering": { + "value": { + "ordering": [ + "\"name\" is not a valid choice." + ] + } + }, + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: read` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no read access to workflows." + } + } + } + } + } + }, + "429": { + "description": "The shared read limit of 600 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create a list", + "description": "Creates an allowlist or a custom list in the application the API key belongs to. Blocklists cannot be created: Didit provisions one system blocklist per entry type, and you add to it with [Create a list entry](https://docs.didit.me/management-api/lists/create-entry). `entry_type` cannot change after creation and decides how entry values are validated and matched. Allowlist entries mark matching items as trusted (for example, a face allowlist skips the duplicated-face action) and never override a blocklist match; reference any list from a workflow or transaction rule condition with `value_type: \"list\"`. A scoped API key needs `workflows: write`.", + "operationId": "create_list", + "x-didit-sdk": { + "resource": "lists", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "list_type", + "entry_type" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "List name. Must differ from the name of every other list of the application, system lists included." + }, + "description": { + "type": "string", + "default": "", + "description": "Optional free-text description." + }, + "list_type": { + "type": "string", + "enum": [ + "allowlist", + "custom" + ], + "description": "`allowlist` or `custom`. `blocklist` is refused with `400`." + }, + "entry_type": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email", + "ip_address", + "device_fingerprint", + "wallet_address", + "bank_account", + "country", + "user", + "business", + "key" + ], + "description": "What the list holds: `face`, `document` (document number), `phone`, `email`, `ip_address` (IP address or CIDR range), `device_fingerprint`, `wallet_address`, `bank_account`, `country` (ISO 3166-1 alpha-3), `user` or `business` (your `vendor_data`), or `key` (any string). It decides how entry values are validated and matched. Cannot be changed later." + } + } + }, + "examples": { + "IP allowlist": { + "value": { + "name": "Trusted office IPs", + "description": "Office egress ranges", + "list_type": "allowlist", + "entry_type": "ip_address" + } + }, + "Custom country list": { + "value": { + "name": "Enhanced due diligence countries", + "list_type": "custom", + "entry_type": "country" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/lists/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"name\": \"Trusted office IPs\",\n \"description\": \"Office egress ranges\",\n \"list_type\": \"allowlist\",\n \"entry_type\": \"ip_address\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.lists.create(\n name=\"Trusted office IPs\",\n description=\"Office egress ranges\",\n list_type=\"allowlist\",\n entry_type=\"ip_address\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.lists.create({\n name: \"Trusted office IPs\",\n description: \"Office egress ranges\",\n list_type: \"allowlist\",\n entry_type: \"ip_address\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Lists.Create(ctx, didit.ListsCreateParams{\n\tName: \"Trusted office IPs\",\n\tDescription: didit.String(\"Office egress ranges\"),\n\tListType: \"allowlist\",\n\tEntryType: \"ip_address\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.ListsCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.lists().create(ListsCreateParams.builder()\n .name(\"Trusted office IPs\")\n .description(\"Office egress ranges\")\n .listType(ListsCreateParams.ListType.of(\"allowlist\"))\n .entryType(ListsCreateParams.EntryType.of(\"ip_address\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->create([\n 'name' => 'Trusted office IPs',\n 'description' => 'Office egress ranges',\n 'list_type' => 'allowlist',\n 'entry_type' => 'ip_address',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.lists.create(\n name: \"Trusted office IPs\",\n description: \"Office egress ranges\",\n list_type: \"allowlist\",\n entry_type: \"ip_address\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Lists.CreateAsync(new ListsCreateParams\n{\n Name = \"Trusted office IPs\",\n Description = \"Office egress ranges\",\n ListType = \"allowlist\",\n EntryType = \"ip_address\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The list was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ComplianceList" + }, + "examples": { + "New allowlist": { + "value": { + "uuid": "22222222-3333-4444-5555-666666666666", + "name": "Trusted office IPs", + "description": "Office egress ranges", + "list_type": "allowlist", + "entry_type": "ip_address", + "is_system": false, + "entry_count": 0, + "last_entry_added_at": null, + "created_at": "2026-10-12T09:15:44.031876Z", + "updated_at": "2026-10-12T09:15:44.031876Z" + } + } + } + } + } + }, + "400": { + "description": "The body is invalid.", + "content": { + "application/json": { + "examples": { + "Blocklist refused": { + "value": { + "list_type": [ + "Cannot create additional blocklist lists. System blocklists are auto-created for each application." + ] + } + }, + "Duplicate name": { + "value": { + "name": [ + "A list with this name already exists for this application." + ] + } + }, + "Missing fields": { + "value": { + "name": [ + "This field is required." + ], + "list_type": [ + "This field is required." + ], + "entry_type": [ + "This field is required." + ] + } + }, + "Unknown entry type": { + "value": { + "entry_type": [ + "\"iban\" is not a valid choice." + ] + } + }, + "Name too long": { + "value": { + "name": [ + "Ensure this field has no more than 255 characters." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/{list_uuid}/": { + "get": { + "summary": "Get a list", + "description": "Returns one list of the application with its entry count and when its newest entry was added. A list of another application, or one that was deleted, returns `404`. This returns the list only: read its values with [List list entries](https://docs.didit.me/management-api/lists/list-entries). A scoped API key needs `workflows: read`.", + "operationId": "get_list", + "x-didit-sdk": { + "resource": "lists", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list, as returned by List lists or Create a list.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.lists.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.lists.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Lists.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.lists().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.lists.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Lists.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The list.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ComplianceList" + }, + "examples": { + "System face blocklist": { + "value": { + "uuid": "11111111-2222-3333-4444-555555555555", + "name": "Face Blocklist", + "description": "Blocked biometric faces. When a face matching an entry in this list is detected during liveness verification, the session will be automatically declined.", + "list_type": "blocklist", + "entry_type": "face", + "is_system": true, + "entry_count": 12, + "last_entry_added_at": "2026-10-09T16:42:03.118204Z", + "created_at": "2026-01-14T10:02:11.402917Z", + "updated_at": "2026-01-14T10:02:11.402917Z" + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: read` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no read access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No List matches the given query." + } + } + } + } + } + }, + "429": { + "description": "The shared read limit of 600 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update a list", + "description": "Renames a list or changes its description. Only `name` and `description` are read; any other field is ignored, so `list_type` and `entry_type` keep the values they were created with. System lists cannot be modified and return `400`. The response contains only `name` and `description`: call [Get a list](https://docs.didit.me/management-api/lists/get) for the full list. A scoped API key needs `workflows: write`.", + "operationId": "update_list", + "x-didit-sdk": { + "resource": "lists", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list, as returned by List lists or Create a list.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "New name. Must differ from the name of every other list of the application." + }, + "description": { + "type": "string", + "description": "New description. Send an empty string to clear it." + } + } + }, + "examples": { + "Rename": { + "value": { + "name": "Trusted office and VPN IPs" + } + }, + "Rename and describe": { + "value": { + "name": "Trusted office and VPN IPs", + "description": "Office egress ranges and the corporate VPN" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"name\": \"Trusted office and VPN IPs\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.lists.update(\n \"11111111-2222-3333-4444-555555555555\",\n name=\"Trusted office and VPN IPs\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.lists.update(\"11111111-2222-3333-4444-555555555555\", {\n name: \"Trusted office and VPN IPs\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Lists.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.ListsUpdateParams{\n\tName: didit.String(\"Trusted office and VPN IPs\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.ListsUpdateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.lists().update(\"11111111-2222-3333-4444-555555555555\", ListsUpdateParams.builder()\n .name(\"Trusted office and VPN IPs\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->update('11111111-2222-3333-4444-555555555555', [\n 'name' => 'Trusted office and VPN IPs',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.lists.update(\n \"11111111-2222-3333-4444-555555555555\",\n name: \"Trusted office and VPN IPs\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Lists.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new ListsUpdateParams\n{\n Name = \"Trusted office and VPN IPs\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The list was updated. Only the editable fields are returned.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "description" + ], + "properties": { + "name": { + "type": "string", + "description": "The list's name after the update." + }, + "description": { + "type": "string", + "description": "The list's description after the update." + } + } + }, + "examples": { + "Renamed": { + "value": { + "name": "Trusted office and VPN IPs", + "description": "Office egress ranges" + } + } + } + } + } + }, + "400": { + "description": "The list is a system list, or the body is invalid.", + "content": { + "application/json": { + "examples": { + "System list": { + "value": { + "non_field_errors": [ + "System lists cannot be modified." + ] + } + }, + "Duplicate name": { + "value": { + "name": [ + "A list with this name already exists for this application." + ] + } + }, + "Blank name": { + "value": { + "name": [ + "This field may not be blank." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No List matches the given query." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete a list", + "description": "Deletes an allowlist or custom list together with its entries. Deletion is irreversible: the list and its entries no longer appear in any read, and workflow and transaction rule conditions that reference the list evaluate it as an empty list from then on. Sessions and transactions already evaluated keep their results, and the name can be reused for a new list. System lists cannot be deleted and return `400`; to empty one, [delete its entries](https://docs.didit.me/management-api/lists/delete-entry). A scoped API key needs `workflows: write`.", + "operationId": "delete_list", + "x-didit-sdk": { + "resource": "lists", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list, as returned by List lists or Create a list.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.lists.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.lists.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Lists.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.lists().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.lists.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Lists.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ], + "responses": { + "204": { + "description": "The list and its entries were deleted. No body." + }, + "400": { + "description": "The list is a system list.", + "content": { + "application/json": { + "examples": { + "System list": { + "value": { + "detail": "System lists cannot be deleted." + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was already deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No List matches the given query." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/{list_uuid}/entries/": { + "get": { + "summary": "List list entries", + "description": "Returns the current entries of one list, newest first. `search` matches part of an entry's `value` or `display_label` without regard to case, and `reference_session_id` narrows the results to the entries taken from one person verification session. Image URLs in `metadata` are short-lived signed URLs issued again on every call; a scoped API key without `media: read` receives `null` in their place. A scoped API key needs `workflows: read`.", + "operationId": "list_list_entries", + "x-didit-sdk": { + "resource": "lists.entries", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list, as returned by List lists or Create a list.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "search", + "in": "query", + "required": false, + "description": "Case-insensitive text that the entry's `value` or `display_label` must contain.", + "schema": { + "type": "string" + } + }, + { + "name": "reference_session_id", + "in": "query", + "required": false, + "description": "Only the entries taken from this person verification session, for example to see which of its faces are already listed.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Page size. Defaults to 50. A value above 1000000 returns `400`; zero, a negative or a non-numeric value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Zero-based index of the first item to return. Prefer following `next`. A value above 1000000 returns `400`; a negative or non-numeric value falls back to 0.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/entries/?search=example.com' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.lists.entries.list(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.lists.entries.list(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Lists.Entries.ListAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.ListsEntriesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.lists().entries().list(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->entries->list('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.lists.entries.list(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Lists.Entries.ListAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "A page of entries, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "description": "Total number of entries matching the filters." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ComplianceListEntry" + } + } + } + }, + "examples": { + "Entries": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "uuid": "bbbbbbbb-cccc-dddd-eeee-ffffffffffff", + "value": "X1234567", + "display_label": "Jane Doe - X1234567", + "comment": "", + "added_by": "api", + "reference_session_id": "33333333-4444-5555-6666-777777777777", + "reference_session_number": 1042, + "reference_session_type": "kyc", + "reference_object_uuid": "66666666-7777-8888-9999-000000000000", + "metadata": { + "full_name": "Jane Doe", + "issuing_state": "ESP", + "document_type": "ID", + "region": "", + "document_number": "X1234567" + }, + "created_at": "2026-10-12T10:06:12.774915Z" + }, + { + "uuid": "eeeeeeee-ffff-0000-1111-222222222222", + "value": "y7654321", + "display_label": "", + "comment": "Reported by a partner", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": null, + "metadata": null, + "created_at": "2026-10-03T07:48:30.552810Z" + } + ] + } + } + } + } + } + }, + "400": { + "description": "A query parameter is invalid.", + "content": { + "application/json": { + "examples": { + "Invalid session id": { + "value": { + "reference_session_id": [ + "Must be a valid UUID." + ] + } + }, + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: read` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no read access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "List not found." + } + } + } + } + } + }, + "429": { + "description": "The shared read limit of 600 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create a list entry", + "description": "Adds one entry to a list, in one of three ways: by `value`; from a verification session with `reference_session_id`, where Didit reads the value from the session's most recent record of the list's entry type and links the entry to it; or by `value` with a `reference_object_uuid` and `metadata.reference_type` pointing back to a transaction, user or business. On the system face, document, phone and email blocklists, the record read from the session is also flagged as blocklisted, so new matching sessions are declined; on the system user and business blocklists, matching users or businesses are set to `BLOCKED`. Sessions and transactions that already finished are not changed. A scoped API key needs `workflows: write`. See [Create a list entry](https://docs.didit.me/management-api/lists/create-entry) for each mode.", + "operationId": "create_list_entry", + "x-didit-sdk": { + "resource": "lists.entries", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list to add to. For a face you only have as an image, use Upload a face to a list instead.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Send `value`, `reference_session_id`, or both. Which fields apply depends on the list's `entry_type`.", + "properties": { + "value": { + "type": "string", + "description": "The value to list, validated against the list's `entry_type`: `email` a valid email address; `phone` a valid international number, stored in E.164; `ip_address` an IP address or CIDR range (for example `10.0.0.0/8`); `device_fingerprint` at least 8 characters among letters, digits, `-`, `_`, `:` and `.` (a blocklist refuses a fingerprint that several distinct devices have reported); `country` an ISO 3166-1 alpha-2 or alpha-3 code, stored as alpha-3; `user` and `business` the `vendor_data`; any other type a non-empty string. Stored in lower case except phone numbers and country codes, and a list holds each value once (case-insensitive). Required unless `reference_session_id` is sent; must be left empty with a document-capture `face_image_source`." + }, + "reference_session_id": { + "type": "string", + "format": "uuid", + "description": "A person or business verification session of your application to take the value from. Didit reads the session's most recent record of the list's entry type (`face`, `document`, `phone`, `email`, `ip_address` or `device_fingerprint`), links the entry to the session and to that record, and fills `display_label` and `metadata` for documents. For a `device_fingerprint` blocklist, a session that only has a device-class fingerprint, or was completed on more than one device, is refused: send the exact fingerprint as `value`. For other entry types, or when the session has no such record, send `value` too; your `value` is then stored and the entry is still linked to the session." + }, + "reference_object_uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the record the entry stands for when it does not come from a session: a transaction UUID, or the `didit_internal_id` of a user or business. Pair it with `metadata.reference_type`. Replaced by the session's record when `reference_session_id` provides one." + }, + "face_image_source": { + "type": "string", + "enum": [ + "liveness", + "front_image_camera_front", + "back_image_camera_front" + ], + "default": "liveness", + "description": "Face lists with `reference_session_id`: which of the session's images the face is taken from. `liveness` (default) is the liveness selfie. `front_image_camera_front` and `back_image_camera_front` are the selfies the front camera took while the user photographed the front or back of their document: accepted only on the system face blocklist, with `reference_session_id` and an empty `value`. Each selfie can be added once and must show one clear face." + }, + "reference_node_id": { + "type": "string", + "nullable": true, + "maxLength": 100, + "description": "With a document-capture `face_image_source`: the ID verification node whose selfie to use, for workflows with more than one. Defaults to the most recent ID verification that has that image. Refused with `liveness`." + }, + "display_label": { + "type": "string", + "maxLength": 512, + "description": "Human-readable label. When empty, Didit derives one from the session where it can (for example the holder's name and document number)." + }, + "comment": { + "type": "string", + "description": "Free-text note, such as the reason for listing." + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Free-form JSON stored with the entry and returned as stored. Use `reference_type` and `reference_id` so the console links back to the source. `reference_image_url` and `front_image_url` are set by Didit only and are dropped if you send them. For an entry read from a session, Didit's keys are merged with yours.", + "properties": { + "reference_type": { + "type": "string", + "enum": [ + "transaction", + "vendor_user", + "vendor_business" + ], + "description": "What `reference_object_uuid` points to." + }, + "reference_id": { + "type": "string", + "description": "Identifier of the source record." + } + } + } + } + }, + "examples": { + "By value": { + "value": { + "value": "+14155552671", + "comment": "Reported fraud" + } + }, + "From a session": { + "value": { + "reference_session_id": "33333333-4444-5555-6666-777777777777" + } + }, + "Face from a document-capture selfie": { + "value": { + "reference_session_id": "33333333-4444-5555-6666-777777777777", + "face_image_source": "front_image_camera_front" + } + }, + "From a transaction": { + "value": { + "value": "0x52908400098527886E0F7030069857D2E4169EE7", + "reference_object_uuid": "55555555-6666-7777-8888-999999999999", + "comment": "Linked to a chargeback", + "metadata": { + "reference_type": "transaction", + "reference_id": "55555555-6666-7777-8888-999999999999" + } + } + }, + "Country": { + "value": { + "value": "PRK", + "comment": "Sanctioned jurisdiction" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/entries/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"value\": \"+14155552671\", \"comment\": \"Reported fraud\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.lists.entries.create(\n \"11111111-2222-3333-4444-555555555555\",\n value=\"+14155552671\",\n comment=\"Reported fraud\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.lists.entries.create(\"11111111-2222-3333-4444-555555555555\", {\n value: \"+14155552671\",\n comment: \"Reported fraud\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Lists.Entries.Create(ctx, \"11111111-2222-3333-4444-555555555555\", didit.ListsEntriesCreateParams{\n\tValue: didit.String(\"+14155552671\"),\n\tComment: didit.String(\"Reported fraud\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.ListsEntriesCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.lists().entries().create(\"11111111-2222-3333-4444-555555555555\", ListsEntriesCreateParams.builder()\n .value(\"+14155552671\")\n .comment(\"Reported fraud\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->entries->create('11111111-2222-3333-4444-555555555555', [\n 'value' => '+14155552671',\n 'comment' => 'Reported fraud',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.lists.entries.create(\n \"11111111-2222-3333-4444-555555555555\",\n value: \"+14155552671\",\n comment: \"Reported fraud\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Lists.Entries.CreateAsync(\"11111111-2222-3333-4444-555555555555\", new ListsEntriesCreateParams\n{\n Value = \"+14155552671\",\n Comment = \"Reported fraud\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The entry was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ComplianceListEntry" + }, + "examples": { + "By value": { + "value": { + "uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "value": "+14155552671", + "display_label": "", + "comment": "Reported fraud", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": null, + "metadata": null, + "created_at": "2026-10-12T10:04:51.220391Z" + } + }, + "Document from a session": { + "value": { + "uuid": "bbbbbbbb-cccc-dddd-eeee-ffffffffffff", + "value": "X1234567", + "display_label": "Jane Doe - X1234567", + "comment": "", + "added_by": "api", + "reference_session_id": "33333333-4444-5555-6666-777777777777", + "reference_session_number": 1042, + "reference_session_type": "kyc", + "reference_object_uuid": "66666666-7777-8888-9999-000000000000", + "metadata": { + "full_name": "Jane Doe", + "issuing_state": "ESP", + "document_type": "ID", + "region": "", + "document_number": "X1234567" + }, + "created_at": "2026-10-12T10:06:12.774915Z" + } + }, + "From a transaction": { + "value": { + "uuid": "dddddddd-eeee-ffff-0000-111111111111", + "value": "0x52908400098527886e0f7030069857d2e4169ee7", + "display_label": "", + "comment": "Linked to a chargeback", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": "55555555-6666-7777-8888-999999999999", + "metadata": { + "reference_type": "transaction", + "reference_id": "55555555-6666-7777-8888-999999999999" + }, + "created_at": "2026-10-12T10:20:40.117062Z" + } + } + } + } + } + }, + "400": { + "description": "The body is invalid, the value is already in the list, or the session cannot provide the value.", + "content": { + "application/json": { + "examples": { + "Nothing to list": { + "value": { + "value": [ + "Either 'value' or 'reference_session_id' must be provided." + ] + } + }, + "Duplicate value": { + "value": { + "value": [ + "An entry with this value already exists in this list." + ] + } + }, + "Invalid phone": { + "value": { + "value": [ + "Invalid phone number. Provide a valid international number (e.g. +1234567890)." + ] + } + }, + "Invalid IP": { + "value": { + "value": [ + "Invalid IP address or CIDR range (e.g. 192.168.1.1 or 10.0.0.0/24)." + ] + } + }, + "Invalid country": { + "value": { + "value": [ + "Invalid country code: XX. Provide a valid ISO 3166-1 alpha-2 or alpha-3 country code." + ] + } + }, + "Device-class fingerprint": { + "value": { + "value": [ + "This value is a device-class fingerprint: several distinct devices have reported it, so blocklisting it would decline unrelated people. Block the user by document, face, email or phone instead." + ] + } + }, + "Unknown session": { + "value": { + "reference_session_id": [ + "Session not found or does not belong to this application." + ] + } + }, + "No data in session": { + "value": { + "value": [ + "Either provide a 'value' or ensure the session contains data for this entry type." + ] + } + }, + "Selfie on another list": { + "value": { + "face_image_source": [ + "A document-capture selfie can only be added to the face blocklist." + ] + } + }, + "No clear face in selfie": { + "value": { + "face_image_source": [ + "No clear face was found in this selfie, so it cannot be blocklisted." + ] + } + }, + "Selfie already listed": { + "value": { + "face_image_source": [ + "This selfie is already in the blocklist." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "List not found." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + }, + "503": { + "description": "Face analysis of a document-capture selfie is temporarily unavailable. Nothing was added; retry later.", + "content": { + "application/json": { + "examples": { + "Unavailable": { + "value": { + "detail": "Service temporarily unavailable. Please try again later." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/{list_uuid}/entries/{entry_uuid}/": { + "delete": { + "summary": "Delete a list entry", + "description": "Deletes one entry from a list, system lists included. The entry stops matching at once, and deletion is irreversible: to list the value again, create a new entry. On a system face, document, phone or email blocklist, the record the entry stands for is no longer flagged as blocklisted; on the system user or business blocklist, users or businesses of the application whose `vendor_data` equals the entry's value (or whose `didit_internal_id` equals its `reference_object_uuid`) and that are `BLOCKED` return to `ACTIVE`. Sessions and transactions that already finished keep their status. A scoped API key needs `workflows: write`.", + "operationId": "delete_list_entry", + "x-didit-sdk": { + "resource": "lists.entries", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list that holds the entry.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "entry_uuid", + "in": "path", + "required": true, + "description": "UUID of the entry, as returned by List list entries or Create a list entry.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/entries/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.lists.entries.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.lists.entries.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\n);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Lists.Entries.Delete(ctx, \"11111111-2222-3333-4444-555555555555\", \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.lists().entries().delete(\"11111111-2222-3333-4444-555555555555\", \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->entries->delete('11111111-2222-3333-4444-555555555555', 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.lists.entries.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\"\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Lists.Entries.DeleteAsync(\"11111111-2222-3333-4444-555555555555\", \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\");" + } + ], + "responses": { + "204": { + "description": "The entry was deleted. No body." + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No entry with this UUID exists in this list, the list is not in the API key's application or was deleted, or the entry was already deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No ListEntry matches the given query." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/{list_uuid}/entries/face-upload/": { + "post": { + "summary": "Upload a face to a list", + "description": "Adds a face to a face blocklist or face allowlist from a base64-encoded JPEG or PNG image, for when you have no verification session to reference. Didit detects the faces in the image, uses the largest one, and creates an entry whose `value` and `reference_object_uuid` are the UUID of the new face. A blocklisted face declines new sessions whose face matches; an allowlisted face skips the duplicated-face action and never overrides a blocklist match. In a sandbox application the image is not analyzed and the entry matches nothing. A scoped API key needs `workflows: write`. To list a face from an existing session, use [Create a list entry](https://docs.didit.me/management-api/lists/create-entry) with `reference_session_id` instead.", + "operationId": "upload_list_entry_face", + "x-didit-sdk": { + "resource": "lists.entries", + "method": "upload_face", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of a list with `entry_type: face` and `list_type: blocklist` or `allowlist`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "image" + ], + "properties": { + "image": { + "type": "string", + "format": "byte", + "description": "Base64-encoded JPEG or PNG, at most 2 MB once decoded, showing a clearly visible face. Send the bare base64 string, without a `data:image/...;base64,` prefix." + }, + "comment": { + "type": "string", + "default": "", + "description": "Optional free-text note, such as the reason for listing." + } + } + }, + "examples": { + "Face image": { + "value": { + "image": "/9j/4AAQSkZJRgABAQAAAQABAAD...", + "comment": "Known fraud attempt" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/entries/face-upload/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d \"{\\\"image\\\": \\\"$(base64 < face.jpg | tr -d '\\n')\\\", \\\"comment\\\": \\\"Known fraud attempt\\\"}\"" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.lists.entries.upload_face(\n \"11111111-2222-3333-4444-555555555555\",\n image=\"/9j/4AAQSkZJRgABAQAAAQABAAD...\",\n comment=\"Known fraud attempt\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.lists.entries.uploadFace(\"11111111-2222-3333-4444-555555555555\", {\n image: \"/9j/4AAQSkZJRgABAQAAAQABAAD...\",\n comment: \"Known fraud attempt\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Lists.Entries.UploadFace(ctx, \"11111111-2222-3333-4444-555555555555\", didit.ListsEntriesUploadFaceParams{\n\tImage: \"/9j/4AAQSkZJRgABAQAAAQABAAD...\",\n\tComment: didit.String(\"Known fraud attempt\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.ListsEntriesUploadFaceParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.lists().entries().uploadFace(\"11111111-2222-3333-4444-555555555555\", ListsEntriesUploadFaceParams.builder()\n .image(\"/9j/4AAQSkZJRgABAQAAAQABAAD...\")\n .comment(\"Known fraud attempt\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->entries->uploadFace('11111111-2222-3333-4444-555555555555', [\n 'image' => '/9j/4AAQSkZJRgABAQAAAQABAAD...',\n 'comment' => 'Known fraud attempt',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.lists.entries.upload_face(\n \"11111111-2222-3333-4444-555555555555\",\n image: \"/9j/4AAQSkZJRgABAQAAAQABAAD...\",\n comment: \"Known fraud attempt\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Lists.Entries.UploadFaceAsync(\"11111111-2222-3333-4444-555555555555\", new ListsEntriesUploadFaceParams\n{\n Image = \"/9j/4AAQSkZJRgABAQAAAQABAAD...\",\n Comment = \"Known fraud attempt\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The face entry was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ComplianceListEntry" + }, + "examples": { + "Uploaded face": { + "value": { + "uuid": "cccccccc-dddd-eeee-ffff-000000000000", + "value": "44444444-5555-6666-7777-888888888888", + "display_label": "Manually uploaded face", + "comment": "Known fraud attempt", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": "44444444-5555-6666-7777-888888888888", + "metadata": { + "reference_image_url": "https://media.example.com/faces/reference.jpg?expires=1760263938&signature=3f9a1c" + }, + "created_at": "2026-10-12T10:12:18.904467Z" + } + }, + "Sandbox application": { + "value": { + "uuid": "cccccccc-dddd-eeee-ffff-000000000000", + "value": "44444444-5555-6666-7777-888888888888", + "display_label": "Sandbox face entry", + "comment": "Known fraud attempt", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": "44444444-5555-6666-7777-888888888888", + "metadata": { + "sandbox": true + }, + "created_at": "2026-10-12T10:12:18.904467Z" + } + } + } + } + } + }, + "400": { + "description": "The list does not take face uploads, or the image is missing, invalid, too large or shows no usable face. No entry was created.", + "content": { + "application/json": { + "examples": { + "Not a face list": { + "value": { + "detail": "Face upload is only available for face-type lists." + } + }, + "Custom face list": { + "value": { + "detail": "Face upload is only available for face blocklists and allowlists." + } + }, + "Missing image": { + "value": { + "image": [ + "This field is required." + ] + } + }, + "Invalid base64": { + "value": { + "image": [ + "Invalid base64 encoding." + ] + } + }, + "Too large": { + "value": { + "image": [ + "Image exceeds maximum size of 2 MB." + ] + } + }, + "Unreadable image": { + "value": { + "image": "Invalid image. Provide a valid base64-encoded JPG or PNG." + } + }, + "No face": { + "value": { + "image": "No face detected in the image. Please upload a clear photo with a visible face." + } + }, + "No usable face": { + "value": { + "image": "Could not extract face embedding. Try a different image." + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "List not found." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + }, + "503": { + "description": "Face analysis is temporarily unavailable. No entry was created; retry later.", + "content": { + "application/json": { + "examples": { + "Unavailable": { + "value": { + "detail": "Service temporarily unavailable. Please try again later." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/blocklist/": { + "get": { + "summary": "List blocklisted items", + "description": "Returns one paginated feed of the faces, identity documents, phone numbers and email addresses of the application's verification sessions that are currently blocklisted, newest `blocklisted_at` first; filter with `item_type`. Items of deleted sessions are not included. The feed covers records blocklisted from a session (with [Add session items to the blocklist](https://docs.didit.me/management-api/blocklist/add), from the console, or by a list entry created from a session); values added to a list by hand, uploaded faces, faces from a document-capture selfie and the other entry types are read with [List list entries](https://docs.didit.me/management-api/lists/list-entries). `face_image` and `front_image` are short-lived signed URLs, and a scoped API key without `media: read` receives `null`. A scoped API key needs `workflows: read`.", + "operationId": "list_blocklist_items", + "x-didit-sdk": { + "resource": "blocklist", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Blocklist" + ], + "parameters": [ + { + "name": "item_type", + "in": "query", + "required": false, + "description": "Only items of this type. When omitted, every type is returned.", + "schema": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email" + ] + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Page size. Defaults to 50. A value above 1000000 returns `400`; zero, a negative or a non-numeric value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Zero-based index of the first item to return. Prefer following `next`. A value above 1000000 returns `400`; a negative or non-numeric value falls back to 0.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/blocklist/?item_type=document' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.blocklist.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.blocklist.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Blocklist.ListAutoPaging(ctx, didit.BlocklistListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.blocklist().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "blocklist->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.blocklist.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Blocklist.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "A page of blocklisted items, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "description": "Total number of blocklisted items matching the filters." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BlocklistedItem" + } + } + } + }, + "examples": { + "Mixed feed": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "type": "document", + "session_id": "33333333-4444-5555-6666-777777777777", + "session_number": 1042, + "blocklisted_at": "2026-10-12T10:06:12.701553Z", + "face_image": null, + "document_type": "ID", + "document_number": "X1234567", + "front_image": "https://media.example.com/documents/front.jpg?expires=1760263938&signature=8b2e47", + "phone_number": null, + "country_code": null, + "email": null + }, + { + "type": "phone", + "session_id": "77777777-8888-9999-0000-111111111111", + "session_number": 1017, + "blocklisted_at": "2026-10-08T15:31:09.400127Z", + "face_image": null, + "document_type": null, + "document_number": null, + "front_image": null, + "phone_number": "+14155552671", + "country_code": "US", + "email": null + } + ] + } + } + } + } + } + }, + "400": { + "description": "A query parameter is invalid.", + "content": { + "application/json": { + "examples": { + "Unknown item type": { + "value": { + "item_type": [ + "\"wallet\" is not a valid choice." + ] + } + }, + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: read` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no read access to workflows." + } + } + } + } + } + }, + "429": { + "description": "The shared read limit of 600 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/blocklist/add/": { + "post": { + "summary": "Add session items to the blocklist", + "description": "Blocklists the face, identity document, phone number or email address of one person verification session of the application. For each selected type, Didit takes the session's most recent record, flags it as blocklisted and adds an entry linked to the session to the matching system blocklist (`added_by: \"api\"`), so new sessions with a matching face, document number, phone number or email address are declined. Every selected item is checked first, so if one is missing nothing is blocklisted; repeating the call for an item already listed keeps its single entry and refreshes `blocklisted_at`. The session itself and sessions that already finished are not changed. A scoped API key needs `workflows: write`.", + "operationId": "add_to_blocklist", + "x-didit-sdk": { + "resource": "blocklist", + "method": "add", + "auth": "api_key", + "server": true + }, + "tags": [ + "Blocklist" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "session_id" + ], + "description": "At least one `blocklist_*` flag must be `true`.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "A person verification session of your application. Deleted sessions are refused." + }, + "blocklist_face": { + "type": "boolean", + "default": false, + "description": "Blocklist the session's face." + }, + "blocklist_document": { + "type": "boolean", + "default": false, + "description": "Blocklist the session's identity document. The document needs its issuing state, document type, document number and full name." + }, + "blocklist_phone": { + "type": "boolean", + "default": false, + "description": "Blocklist the session's phone number." + }, + "blocklist_email": { + "type": "boolean", + "default": false, + "description": "Blocklist the session's email address." + } + } + }, + "examples": { + "Face and document": { + "value": { + "session_id": "33333333-4444-5555-6666-777777777777", + "blocklist_face": true, + "blocklist_document": true + } + }, + "Phone only": { + "value": { + "session_id": "33333333-4444-5555-6666-777777777777", + "blocklist_phone": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/blocklist/add/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"session_id\": \"33333333-4444-5555-6666-777777777777\",\n \"blocklist_face\": true,\n \"blocklist_document\": true\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.blocklist.add(\n session_id=\"33333333-4444-5555-6666-777777777777\",\n blocklist_face=True,\n blocklist_document=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.blocklist.add({\n session_id: \"33333333-4444-5555-6666-777777777777\",\n blocklist_face: true,\n blocklist_document: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Blocklist.Add(ctx, didit.BlocklistAddParams{\n\tSessionID: \"33333333-4444-5555-6666-777777777777\",\n\tBlocklistFace: didit.Bool(true),\n\tBlocklistDocument: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BlocklistAddParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.blocklist().add(BlocklistAddParams.builder()\n .sessionId(\"33333333-4444-5555-6666-777777777777\")\n .blocklistFace(true)\n .blocklistDocument(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "blocklist->add([\n 'session_id' => '33333333-4444-5555-6666-777777777777',\n 'blocklist_face' => true,\n 'blocklist_document' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.blocklist.add(\n session_id: \"33333333-4444-5555-6666-777777777777\",\n blocklist_face: true,\n blocklist_document: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Blocklist.AddAsync(new BlocklistAddParams\n{\n SessionId = \"33333333-4444-5555-6666-777777777777\",\n BlocklistFace = true,\n BlocklistDocument = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The selected items were blocklisted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string", + "description": "Always `Successfully updated blocklist.`" + } + } + }, + "examples": { + "Blocklisted": { + "value": { + "message": "Successfully updated blocklist." + } + } + } + } + } + }, + "400": { + "description": "The body is invalid, the session is not a verification session of this application, or it has no record of a selected type. Nothing was blocklisted.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "value": { + "non_field_errors": [ + "Session not found" + ] + } + }, + "No item selected": { + "value": { + "non_field_errors": [ + "Must specify at least one item to blocklist" + ] + } + }, + "No face record": { + "value": { + "non_field_errors": [ + "Session does not have a face record" + ] + } + }, + "Incomplete document": { + "value": { + "non_field_errors": [ + "To blocklist a document, the document must have an issuing state, document type, document number and full name" + ] + } + }, + "Invalid session id": { + "value": { + "session_id": [ + "Must be a valid UUID." + ] + } + }, + "Missing session id": { + "value": { + "session_id": [ + "This field is required." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/blocklist/remove/": { + "post": { + "summary": "Remove session items from the blocklist", + "description": "Lifts the block on the face, identity document, phone number or email address of one person verification session of the application. For each selected type, Didit takes the session's most recent record, clears its blocklisted flag and deletes the system blocklist entries created from that record, so it no longer declines new sessions. Entries added to a list by value are not touched: find them with [List list entries](https://docs.didit.me/management-api/lists/list-entries) and delete them with [Delete a list entry](https://docs.didit.me/management-api/lists/delete-entry). Unblocking a record that is not blocklisted succeeds and changes nothing, and sessions that already finished are not changed. A scoped API key needs `workflows: write`.", + "operationId": "remove_from_blocklist", + "x-didit-sdk": { + "resource": "blocklist", + "method": "remove", + "auth": "api_key", + "server": true + }, + "tags": [ + "Blocklist" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "session_id" + ], + "description": "At least one `unblock_*` flag must be `true`.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "A person verification session of your application. Deleted sessions are refused." + }, + "unblock_face": { + "type": "boolean", + "default": false, + "description": "Lift the block on the session's face." + }, + "unblock_document": { + "type": "boolean", + "default": false, + "description": "Lift the block on the session's identity document." + }, + "unblock_phone": { + "type": "boolean", + "default": false, + "description": "Lift the block on the session's phone number." + }, + "unblock_email": { + "type": "boolean", + "default": false, + "description": "Lift the block on the session's email address." + } + } + }, + "examples": { + "Face and document": { + "value": { + "session_id": "33333333-4444-5555-6666-777777777777", + "unblock_face": true, + "unblock_document": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/blocklist/remove/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"session_id\": \"33333333-4444-5555-6666-777777777777\",\n \"unblock_face\": true,\n \"unblock_document\": true\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.blocklist.remove(\n session_id=\"33333333-4444-5555-6666-777777777777\",\n unblock_face=True,\n unblock_document=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.blocklist.remove({\n session_id: \"33333333-4444-5555-6666-777777777777\",\n unblock_face: true,\n unblock_document: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Blocklist.Remove(ctx, didit.BlocklistRemoveParams{\n\tSessionID: \"33333333-4444-5555-6666-777777777777\",\n\tUnblockFace: didit.Bool(true),\n\tUnblockDocument: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BlocklistRemoveParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.blocklist().remove(BlocklistRemoveParams.builder()\n .sessionId(\"33333333-4444-5555-6666-777777777777\")\n .unblockFace(true)\n .unblockDocument(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "blocklist->remove([\n 'session_id' => '33333333-4444-5555-6666-777777777777',\n 'unblock_face' => true,\n 'unblock_document' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.blocklist.remove(\n session_id: \"33333333-4444-5555-6666-777777777777\",\n unblock_face: true,\n unblock_document: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Blocklist.RemoveAsync(new BlocklistRemoveParams\n{\n SessionId = \"33333333-4444-5555-6666-777777777777\",\n UnblockFace = true,\n UnblockDocument = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The block on the selected items was lifted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string", + "description": "Always `Successfully removed from blocklist.`" + } + } + }, + "examples": { + "Unblocked": { + "value": { + "message": "Successfully removed from blocklist." + } + } + } + } + } + }, + "400": { + "description": "The body is invalid, the session is not a verification session of this application, or it has no record of a selected type. Nothing was changed.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "value": { + "non_field_errors": [ + "Session not found" + ] + } + }, + "No item selected": { + "value": { + "non_field_errors": [ + "Must specify at least one item to unblock" + ] + } + }, + "No email record": { + "value": { + "non_field_errors": [ + "Session does not have an email record" + ] + } + }, + "Invalid session id": { + "value": { + "session_id": [ + "Must be a valid UUID." + ] + } + }, + "Missing session id": { + "value": { + "session_id": [ + "This field is required." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/": { + "get": { + "summary": "Get webhook configuration", + "description": "Return the application's webhook settings in one call: every [webhook destination](https://docs.didit.me/management-api/webhook/list-destinations) with its signing secret, the event types a destination can subscribe to, the application-level (legacy) webhook URL, version and secret, the data and biometric-template retention policy, and the Digital ID wallet return hosts. Didit delivers webhooks to destinations, each signed with its own `secret_shared_key`; the top-level `webhook_url`, `webhook_version` and `secret_shared_key` are used only while no destination is enabled, and then only for `status.updated` and `data.updated`. The response contains signing secrets, so keep it out of logs and client-side code. A scoped API key needs `webhooks: read`.", + "operationId": "get_webhook_config", + "x-didit-sdk": { + "resource": "webhooks", + "method": "retrieve_config", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.retrieve_config()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.retrieveConfig();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.RetrieveConfig(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().retrieveConfig();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->retrieveConfig();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.retrieve_config\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.RetrieveConfigAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The webhook configuration.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookConfig" + }, + "examples": { + "One destination": { + "value": { + "webhook_url": "https://yourapp.com/webhooks/didit", + "webhook_version": "v3", + "secret_shared_key": "dGhpcy1pcy10aGUtYXBwLWxldmVsLXNlY3JldC0wMDA", + "capture_method": "MOBILE", + "data_retention_months": 24, + "face_retention_policy": "delete_with_session", + "face_retention_days": null, + "biometric_consent_mode": "auto", + "available_events": [ + { + "value": "status.updated", + "label": "Session status updated" + }, + { + "value": "data.updated", + "label": "Session data updated" + }, + { + "value": "user.status.updated", + "label": "User status updated" + }, + { + "value": "user.data.updated", + "label": "User data updated" + }, + { + "value": "business.status.updated", + "label": "Business status updated" + }, + { + "value": "business.data.updated", + "label": "Business data updated" + }, + { + "value": "activity.created", + "label": "Activity created" + }, + { + "value": "transaction.created", + "label": "Transaction created" + }, + { + "value": "transaction.status.updated", + "label": "Transaction status updated" + }, + { + "value": "travel_rule.status.updated", + "label": "Travel Rule Status Updated" + }, + { + "value": "workflow.compliance_changed", + "label": "Workflow Compliance Changed" + } + ], + "allowed_return_hosts": [ + "shop.example.com" + ], + "endpoints": [ + { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "secret_shared_key": "dGhpcy1pcy1hbi1leGFtcGxlLXNlY3JldC0wMDAwMDA", + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_at": "2026-01-04T08:00:00.000000Z", + "updated_at": "2026-04-22T14:09:11.523671Z" + } + ] + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks read": { + "value": { + "detail": "This API key has no read access to webhooks." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update webhook configuration", + "description": "Partially update application-level settings: the legacy webhook URL, version and secret, the data and biometric-template retention policy, and the Digital ID wallet return hosts. Send only the fields to change; the response is the full configuration, the same shape as `GET /v3/webhook/`. Destinations are not managed here: create, update and delete them with the `/v3/webhook/destinations/` endpoints, and do not send an `endpoints` field to this endpoint. A `data_retention_months` change also applies to existing sessions, and sessions it deletes cannot be restored; read [Data retention](https://docs.didit.me/console/data-retention) first. A scoped API key needs `webhooks: write`.", + "operationId": "update_webhook_config", + "x-didit-sdk": { + "resource": "webhooks", + "method": "update_config", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Send only the fields to change. Destinations are not managed here.", + "properties": { + "webhook_url": { + "type": "string", + "format": "uri", + "nullable": true, + "maxLength": 2000, + "description": "Legacy application-level webhook URL. Didit uses it only while the application has no enabled destination, and then only for `status.updated` and `data.updated`. It does not create a destination, and the next create, update or delete of a destination replaces it. To receive webhooks, create a destination with `POST /v3/webhook/destinations/` instead." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload version of the legacy `webhook_url`." + }, + "rotate_secret_key": { + "type": "boolean", + "default": false, + "description": "`true` replaces the application-level `secret_shared_key` with a new random value at once; the new value is in the response. Destination secrets are not changed. Every call with `true` generates a new secret, so it is not idempotent." + }, + "capture_method": { + "type": "string", + "enum": [ + "MOBILE", + "MOBILE_AND_DESKTOP" + ], + "description": "Legacy application setting. The verification flow does not read it." + }, + "data_retention_months": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 120, + "description": "Automatic deletion window in months (a month counts as 30 days), or `null` for no automatic deletion. A change applies to existing sessions too: every user verification session older than the new window is deleted automatically, and that deletion is irreversible." + }, + "face_retention_policy": { + "type": "string", + "enum": [ + "delete_with_session", + "retain_until_user_deleted" + ], + "description": "What happens to a session's face embedding when the session is deleted. `retain_until_user_deleted` needs `face_retention_days` in the same request or already set. A change applies to session deletions from then on; it creates or purges no existing biometric template." + }, + "face_retention_days": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 3650, + "description": "Retention period, in days, of biometric templates under `retain_until_user_deleted`." + }, + "biometric_consent_mode": { + "type": "string", + "enum": [ + "auto", + "always", + "never" + ], + "description": "When the hosted flow and the native SDKs ask the person for explicit biometric consent before a face capture. `auto` (default) asks only where a trigger applies: a declared state, the person's IP, or the identity document's issuer is in Illinois, Texas or Colorado, or the person looks to be in the US without a known state. `always` asks every person whose workflow captures a face. `never` never shows the step, and you collect consent yourself before the selfie. Each change is recorded in the audit log. See [Biometric consent](https://docs.didit.me/getting-started/biometric-consent)." + }, + "allowed_return_hosts": { + "type": "array", + "maxItems": 20, + "description": "Replaces the whole list of Digital ID wallet return hosts. Bare hostnames only, such as `shop.example.com`: no scheme, port, path or wildcard. Values are lowercased and duplicates are dropped. Send `[]` to clear the list.", + "items": { + "type": "string", + "maxLength": 253 + } + }, + "allowed_domains": { + "type": "array", + "maxItems": 50, + "items": { + "type": "string", + "maxLength": 300 + }, + "description": "Replaces the whole list of allowed origins. Origins (for example `https://app.example.com`; `http` only for `localhost`) the hosted verification flow may be embedded in and may send the user back to with a session callback. Server-to-server calls are not affected. An empty list means no restriction. Didit stores each origin in lower case without a default port. An invalid entry fails with `400` on `allowed_domains`. Send `[]` to remove the restriction.", + "example": [ + "https://app.example.com" + ] + } + } + }, + "examples": { + "Enable biometric template retention": { + "value": { + "face_retention_policy": "retain_until_user_deleted", + "face_retention_days": 365, + "biometric_consent_mode": "auto" + } + }, + "Rotate the legacy secret": { + "value": { + "rotate_secret_key": true + } + }, + "Set data retention": { + "value": { + "data_retention_months": 24 + } + }, + "Register return hosts": { + "value": { + "allowed_return_hosts": [ + "shop.example.com", + "app.example.com" + ] + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/webhook/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"face_retention_policy\": \"retain_until_user_deleted\", \"face_retention_days\": 365}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.update_config(\n face_retention_policy=\"retain_until_user_deleted\",\n face_retention_days=365,\n biometric_consent_mode=\"auto\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.updateConfig({\n face_retention_policy: \"retain_until_user_deleted\",\n face_retention_days: 365,\n biometric_consent_mode: \"auto\",\n});\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.WebhooksUpdateConfigParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().updateConfig(WebhooksUpdateConfigParams.builder()\n .faceRetentionPolicy(WebhooksUpdateConfigParams.FaceRetentionPolicy.of(\"retain_until_user_deleted\"))\n .faceRetentionDays(365L)\n .biometricConsentMode(WebhooksUpdateConfigParams.BiometricConsentMode.of(\"auto\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->updateConfig([\n 'face_retention_policy' => 'retain_until_user_deleted',\n 'face_retention_days' => 365,\n 'biometric_consent_mode' => 'auto',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.update_config(\n face_retention_policy: \"retain_until_user_deleted\",\n face_retention_days: 365,\n biometric_consent_mode: \"auto\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.UpdateConfigAsync(new WebhooksUpdateConfigParams\n{\n FaceRetentionPolicy = new WebhooksUpdateConfigParamsFaceRetentionPolicy(\"retain_until_user_deleted\"),\n FaceRetentionDays = 365,\n BiometricConsentMode = new WebhooksUpdateConfigParamsBiometricConsentMode(\"auto\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The full configuration after the update, the same shape as `GET /v3/webhook/`. When you rotated the secret, store the new `secret_shared_key` before you discard the response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookConfig" + }, + "examples": { + "Retention enabled": { + "value": { + "webhook_url": "https://yourapp.com/webhooks/didit", + "webhook_version": "v3", + "secret_shared_key": "dGhpcy1pcy10aGUtYXBwLWxldmVsLXNlY3JldC0wMDA", + "capture_method": "MOBILE", + "data_retention_months": 24, + "face_retention_policy": "retain_until_user_deleted", + "face_retention_days": 365, + "biometric_consent_mode": "auto", + "available_events": [ + { + "value": "status.updated", + "label": "Session status updated" + }, + { + "value": "data.updated", + "label": "Session data updated" + }, + { + "value": "user.status.updated", + "label": "User status updated" + }, + { + "value": "user.data.updated", + "label": "User data updated" + }, + { + "value": "business.status.updated", + "label": "Business status updated" + }, + { + "value": "business.data.updated", + "label": "Business data updated" + }, + { + "value": "activity.created", + "label": "Activity created" + }, + { + "value": "transaction.created", + "label": "Transaction created" + }, + { + "value": "transaction.status.updated", + "label": "Transaction status updated" + }, + { + "value": "travel_rule.status.updated", + "label": "Travel Rule Status Updated" + }, + { + "value": "workflow.compliance_changed", + "label": "Workflow Compliance Changed" + } + ], + "allowed_return_hosts": [ + "shop.example.com" + ], + "endpoints": [ + { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "secret_shared_key": "dGhpcy1pcy1hbi1leGFtcGxlLXNlY3JldC0wMDAwMDA", + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_at": "2026-01-04T08:00:00.000000Z", + "updated_at": "2026-04-22T14:09:11.523671Z" + } + ] + } + } + } + } + } + }, + "400": { + "description": "Validation failed: each invalid field is mapped to a list of messages, and nothing is changed. A body that is not valid JSON returns `{\"detail\": \"JSON parse error - ...\"}`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Each invalid field mapped to a list of messages.", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "Retention days missing": { + "value": { + "face_retention_days": [ + "A finite retention duration is required when face retention is enabled." + ], + "biometric_consent_mode": "auto" + } + }, + "Retention window out of range": { + "value": { + "data_retention_months": [ + "Ensure this value is less than or equal to 120." + ] + } + }, + "Invalid return host": { + "value": { + "allowed_return_hosts": [ + "'https://shop.example.com' is not a hostname. Register the bare host (e.g. shop.example.com): no scheme, port, path or wildcard." + ] + } + }, + "Invalid choice": { + "value": { + "face_retention_policy": [ + "\"forever\" is not a valid choice." + ] + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks write": { + "value": { + "detail": "This API key has no write access to webhooks." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 300 write requests (POST, PATCH, PUT, DELETE) in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (300).", + "schema": { + "type": "integer", + "example": 300 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-write" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/analytics/": { + "get": { + "summary": "Get webhook destination analytics", + "description": "Delivery totals, failure rate and response times for one webhook destination, over the whole range (`summary`) and per UTC day (`points`). Every attempt counts as a delivery: the first send of an event, each automatic [retry](https://docs.didit.me/integration/webhooks#retry-policy) and each resend. A delivery is failed when its recorded HTTP status is 400 or above, so timeouts (recorded as `504`) and connection failures (`503`) count as failed; a delivery still queued counts in `total_deliveries` only. Delivery records are available for about 30 days. A scoped API key needs `webhooks: read`.", + "operationId": "get_webhook_destination_analytics", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "retrieve_analytics", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination (the `uuid` returned by `GET /v3/webhook/destinations/`).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "date_from", + "in": "query", + "required": false, + "description": "First UTC day to include, as `YYYY-MM-DD` (inclusive). Omit to start from the oldest delivery available. `dateFrom` is accepted as an alias.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-10-01" + }, + { + "name": "date_to", + "in": "query", + "required": false, + "description": "Last UTC day to include, as `YYYY-MM-DD` (inclusive). Must not be earlier than `date_from`. `dateTo` is accepted as an alias.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-10-16" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/analytics/?date_from=2026-10-01&date_to=2026-10-16' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.retrieve_analytics(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.retrieveAnalytics(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.RetrieveAnalytics(ctx, \"11111111-2222-3333-4444-555555555555\", didit.WebhooksDestinationsRetrieveAnalyticsParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().retrieveAnalytics(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->retrieveAnalytics('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.destinations.retrieve_analytics(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.RetrieveAnalyticsAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Delivery statistics for the destination.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "summary", + "points" + ], + "properties": { + "summary": { + "type": "object", + "description": "Totals over the requested range.", + "required": [ + "total_deliveries", + "failed_deliveries", + "min_response_time_ms", + "avg_response_time_ms", + "max_response_time_ms", + "error_rate_percentage" + ], + "properties": { + "total_deliveries": { + "type": "integer", + "minimum": 0, + "description": "Delivery attempts, including queued ones." + }, + "failed_deliveries": { + "type": "integer", + "minimum": 0, + "description": "Attempts whose recorded HTTP status is 400 or above." + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Fastest attempt in milliseconds, or `null` when no attempt has a recorded time." + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Mean attempt time in milliseconds, rounded, or `null` when no attempt has a recorded time." + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Slowest attempt in milliseconds, or `null` when no attempt has a recorded time." + }, + "error_rate_percentage": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "`failed_deliveries` / `total_deliveries` x 100, rounded to a whole number. `0` when there are no deliveries." + } + } + }, + "points": { + "type": "array", + "description": "One entry per UTC day that had deliveries, oldest first. Days without deliveries are omitted, so fill the gaps with zeros when you chart them.", + "items": { + "type": "object", + "required": [ + "date", + "total_deliveries", + "failed_deliveries", + "min_response_time_ms", + "avg_response_time_ms", + "max_response_time_ms" + ], + "properties": { + "date": { + "type": "string", + "format": "date", + "description": "UTC day, `YYYY-MM-DD`." + }, + "total_deliveries": { + "type": "integer", + "minimum": 0, + "description": "Delivery attempts, including queued ones." + }, + "failed_deliveries": { + "type": "integer", + "minimum": 0, + "description": "Attempts whose recorded HTTP status is 400 or above." + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Fastest attempt in milliseconds, or `null` when no attempt has a recorded time." + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Mean attempt time in milliseconds, rounded, or `null` when no attempt has a recorded time." + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Slowest attempt in milliseconds, or `null` when no attempt has a recorded time." + } + } + } + } + } + }, + "examples": { + "Three days of deliveries": { + "value": { + "summary": { + "total_deliveries": 1250, + "failed_deliveries": 12, + "min_response_time_ms": 38, + "avg_response_time_ms": 164, + "max_response_time_ms": 5004, + "error_rate_percentage": 1 + }, + "points": [ + { + "date": "2026-10-14", + "total_deliveries": 410, + "failed_deliveries": 0, + "min_response_time_ms": 38, + "avg_response_time_ms": 152, + "max_response_time_ms": 911 + }, + { + "date": "2026-10-15", + "total_deliveries": 395, + "failed_deliveries": 12, + "min_response_time_ms": 41, + "avg_response_time_ms": 190, + "max_response_time_ms": 5004 + }, + { + "date": "2026-10-16", + "total_deliveries": 445, + "failed_deliveries": 0, + "min_response_time_ms": 40, + "avg_response_time_ms": 151, + "max_response_time_ms": 1220 + } + ] + } + }, + "No deliveries in range": { + "value": { + "summary": { + "total_deliveries": 0, + "failed_deliveries": 0, + "min_response_time_ms": null, + "avg_response_time_ms": null, + "max_response_time_ms": null, + "error_rate_percentage": 0 + }, + "points": [] + } + } + } + } + } + }, + "400": { + "description": "`date_from` or `date_to` is not a `YYYY-MM-DD` date, or `date_to` is earlier than `date_from`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Each invalid field mapped to a list of messages.", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "Malformed date": { + "value": { + "date_from": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + }, + "Reversed range": { + "value": { + "date_to": [ + "date_to must be greater than or equal to date_from." + ] + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks read": { + "value": { + "detail": "This API key has no read access to webhooks." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No destination with this UUID exists for the application, or it has been deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Destination not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/deliveries/": { + "get": { + "summary": "List webhook destination deliveries", + "description": "Page through every delivery attempt to one webhook destination, newest first. The first send of each event, each automatic [retry](https://docs.didit.me/integration/webhooks#retry-policy) and each resend are separate entries. Entries carry the HTTP status and response time but not the payload: fetch one delivery to read its headers and body. `count` is exact up to 100 and reads 100 above that, so follow `next` until it is `null`. Delivery records are available for about 30 days. A scoped API key needs `webhooks: read` and `decisions: read`; a key limited to some workflows or to approved sessions cannot use this endpoint.", + "operationId": "list_webhook_destination_deliveries", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "list_deliveries", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination (the `uuid` returned by `GET /v3/webhook/destinations/`).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of deliveries per page. Defaults to 50. A value that is not a positive integer falls back to 50; a value above 1000000 returns `400`.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "example": 50 + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of deliveries to skip. Defaults to 0. Prefer following the `next` and `previous` URLs from the response.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "example": 0 + }, + { + "name": "date_from", + "in": "query", + "required": false, + "description": "First UTC day to include, as `YYYY-MM-DD` (inclusive). Omit to start from the oldest delivery available. `dateFrom` is accepted as an alias.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-10-01" + }, + { + "name": "date_to", + "in": "query", + "required": false, + "description": "Last UTC day to include, as `YYYY-MM-DD` (inclusive). Must not be earlier than `date_from`. `dateTo` is accepted as an alias.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-10-16" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/deliveries/?limit=50&date_from=2026-10-01' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.webhooks.destinations.list_deliveries(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.webhooks.destinations.listDeliveries(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Webhooks.Destinations.ListDeliveriesAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.WebhooksDestinationsListDeliveriesParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.webhooks().destinations().listDeliveries(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->listDeliveries('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.webhooks.destinations.list_deliveries(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Webhooks.Destinations.ListDeliveriesAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "One page of deliveries, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "description": "Matching deliveries, exact up to 100 and capped at 100 above that. Use `next` to know whether more pages exist." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebhookDestinationDelivery" + } + } + } + }, + "examples": { + "Failed delivery, its retry and a queued event": { + "value": { + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "uuid": "66666666-7777-8888-9999-000000000003", + "session_id": null, + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": null, + "response_time_ms": null, + "status": "APPROVED", + "webhook_type": "transaction.created", + "created_at": "2026-10-15T09:23:02.871190Z" + }, + { + "uuid": "66666666-7777-8888-9999-000000000002", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 200, + "response_time_ms": 143, + "status": "In Progress", + "webhook_type": "status.updated", + "created_at": "2026-10-15T09:22:19.104233Z" + }, + { + "uuid": "66666666-7777-8888-9999-000000000001", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 503, + "response_time_ms": 412, + "status": "In Progress", + "webhook_type": "status.updated", + "created_at": "2026-10-15T09:21:18.412345Z" + } + ] + } + } + } + } + } + }, + "400": { + "description": "`date_from` or `date_to` is not a `YYYY-MM-DD` date, `date_to` is earlier than `date_from`, or `limit` or `offset` is above 1000000 (that error is a JSON array of messages).", + "content": { + "application/json": { + "examples": { + "Malformed date": { + "value": { + "date_from": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + }, + "Reversed range": { + "value": { + "date_to": [ + "date_to must be greater than or equal to date_from." + ] + } + }, + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` A key limited to some workflows or to approved sessions gets `This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks read": { + "value": { + "detail": "This API key has no read access to webhooks." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No destination with this UUID exists for the application, or it has been deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Destination not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/deliveries/{delivery_uuid}/": { + "get": { + "summary": "Get webhook destination delivery", + "description": "Retrieve one delivery attempt to a webhook destination with the exact request headers and JSON body Didit sent, the HTTP status your endpoint returned and the response time. `request_header` and `request_body` are JSON text: parse them to read the signature headers and the payload, for example to check your [signature verification](https://docs.didit.me/integration/webhooks#signature-verification) against the destination's `secret_shared_key`. A delivery never contains the signing secret, and Didit does not record your endpoint's response body. A scoped API key needs `webhooks: read` and `decisions: read`; a key limited to some workflows or to approved sessions cannot use this endpoint.", + "operationId": "get_webhook_destination_delivery", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "retrieve_delivery", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination the delivery was sent to.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "delivery_uuid", + "in": "path", + "required": true, + "description": "UUID of the delivery: the `uuid` of an entry returned by `GET /v3/webhook/destinations/{destination_uuid}/deliveries/`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "66666666-7777-8888-9999-000000000001" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/deliveries/66666666-7777-8888-9999-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.retrieve_delivery(\n \"11111111-2222-3333-4444-555555555555\",\n \"66666666-7777-8888-9999-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.retrieveDelivery(\n \"11111111-2222-3333-4444-555555555555\",\n \"66666666-7777-8888-9999-000000000001\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.RetrieveDelivery(ctx, \"11111111-2222-3333-4444-555555555555\", \"66666666-7777-8888-9999-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().retrieveDelivery(\"11111111-2222-3333-4444-555555555555\", \"66666666-7777-8888-9999-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->retrieveDelivery('11111111-2222-3333-4444-555555555555', '66666666-7777-8888-9999-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.destinations.retrieve_delivery(\n \"11111111-2222-3333-4444-555555555555\",\n \"66666666-7777-8888-9999-000000000001\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.RetrieveDeliveryAsync(\"11111111-2222-3333-4444-555555555555\", \"66666666-7777-8888-9999-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The delivery, with its request headers and body.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookDestinationDeliveryDetail" + }, + "examples": { + "Connection failure": { + "value": { + "uuid": "66666666-7777-8888-9999-000000000001", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 503, + "response_time_ms": 412, + "status": "In Progress", + "webhook_type": "status.updated", + "created_at": "2026-10-15T09:21:18.412345Z", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"a5e02a03404e1337a0198e6be15d34abbd053c658a0e51e5d0e78554213ef29b\", \"X-Signature-V2\": \"927e7cb770c94ce745939bff73f04aa200015f3bc64795ba46c2aaf78a6cedc5\", \"X-Signature-Simple\": \"e37b0d4c716c72bb5ae7127b9cc455d28ab4686e9e22312b4ec2327409a73302\", \"X-Timestamp\": \"1792056078\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792056078,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` A key limited to some workflows or to approved sessions gets `This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks read": { + "value": { + "detail": "This API key has no read access to webhooks." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The destination does not exist for the application or has been deleted, or the delivery does not exist, belongs to another destination, or is no longer available.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Destination not found": { + "value": { + "detail": "Webhook destination not found." + } + }, + "Delivery not found": { + "value": { + "detail": "Webhook delivery not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/deliveries/{delivery_uuid}/resend/": { + "post": { + "summary": "Resend webhook destination delivery", + "description": "Send a past delivery again, for example after your endpoint was down. Didit takes the stored payload, sets a fresh `timestamp`, signs it again with the destination's current `secret_shared_key` (`X-Signature`, `X-Signature-V2`, `X-Signature-Simple` and `X-Timestamp`) and posts it to the delivery's `request_url`, even if the destination's URL has changed since or the destination is disabled. `event_id` and the rest of the payload are unchanged, so deduplicate on `event_id`. The call waits up to 5 seconds for your endpoint and returns the new delivery with `200` whatever your endpoint answered: read `response_status`. A resend is not retried automatically, every call sends again and creates a new delivery, and the request has no body. A scoped API key needs `webhooks: write` and `decisions: read`; a key limited to some workflows or to approved sessions cannot use this endpoint.", + "operationId": "resend_webhook_destination_delivery", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "resend_delivery", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination the delivery was sent to.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "delivery_uuid", + "in": "path", + "required": true, + "description": "UUID of the delivery to resend: the `uuid` of an entry returned by `GET /v3/webhook/destinations/{destination_uuid}/deliveries/`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "66666666-7777-8888-9999-000000000001" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/deliveries/66666666-7777-8888-9999-000000000001/resend/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.resend_delivery(\n \"11111111-2222-3333-4444-555555555555\",\n \"66666666-7777-8888-9999-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.resendDelivery(\n \"11111111-2222-3333-4444-555555555555\",\n \"66666666-7777-8888-9999-000000000001\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.ResendDelivery(ctx, \"11111111-2222-3333-4444-555555555555\", \"66666666-7777-8888-9999-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().resendDelivery(\"11111111-2222-3333-4444-555555555555\", \"66666666-7777-8888-9999-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->resendDelivery('11111111-2222-3333-4444-555555555555', '66666666-7777-8888-9999-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.destinations.resend_delivery(\n \"11111111-2222-3333-4444-555555555555\",\n \"66666666-7777-8888-9999-000000000001\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.ResendDeliveryAsync(\"11111111-2222-3333-4444-555555555555\", \"66666666-7777-8888-9999-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The new delivery, already attempted. `response_status` is what your endpoint answered this time (`504` timeout, `503` connection failure). The original delivery is unchanged.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookDestinationDeliveryDetail" + }, + "examples": { + "Accepted on resend": { + "value": { + "uuid": "66666666-7777-8888-9999-000000000004", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 200, + "response_time_ms": 151, + "status": "In Progress", + "webhook_type": "status.updated", + "created_at": "2026-10-16T14:05:42.330981Z", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"b8eb1e376517ee0cad8ba98ff7f8fd35fc72eca231000963a96a335f82328835\", \"X-Signature-V2\": \"9efd2ce9ed1303e1d05273ef9608a59363ed9789a05253db4748b3e8e50cb109\", \"X-Signature-Simple\": \"bf3a9dc04b2c20ab539203b0cd5cca2a10f630bf62d8feb6d68d82af80f64774\", \"X-Timestamp\": \"1792159542\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792159542,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}" + } + } + } + } + } + }, + "400": { + "description": "The stored delivery could not be signed again. Nothing is sent.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string", + "description": "Stable machine-readable error code. Branch on this, not on `detail`." + } + } + }, + "examples": { + "Unsignable delivery": { + "value": { + "detail": "Invalid webhook data format", + "code": "invalid_webhook_payload" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` A key limited to some workflows or to approved sessions gets `This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks write": { + "value": { + "detail": "This API key has no write access to webhooks." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The destination does not exist for the application or has been deleted, or the delivery does not exist, belongs to another destination, or is no longer available.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Destination not found": { + "value": { + "detail": "Webhook destination not found." + } + }, + "Delivery not found": { + "value": { + "detail": "Webhook delivery not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 300 write requests (POST, PATCH, PUT, DELETE) in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (300).", + "schema": { + "type": "integer", + "example": 300 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-write" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/": { + "get": { + "summary": "List monitoring events", + "description": "List the monitoring events stored for your application, newest `occurred_at` first, with optional filters that combine with AND. Multi-value filters (`event_category`, `action_type`, `status`, `retention_class`) repeat the parameter and match any of the values; the other filters are exact matches, and unknown values simply return no events. Each result is a summary: call `GET /v3/events/{event_uuid}/` for the payload, device context, IP enrichment and rule evidence, and prefer `GET /v3/events/subjects/{vendor_data}/` for one subject's history. Reads work whether or not event ingestion is switched on. A scoped API key needs `transactions: read`. See [Event ingestion](https://docs.didit.me/transaction-monitoring/events).", + "operationId": "list_monitoring_events", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of events per page. Defaults to 50. Values above 1000000 return `400`; a non-numeric, zero or negative value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "example": 50 + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of events to skip. Defaults to 0. Prefer following the `next` URL from the response.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "example": 0 + }, + { + "name": "event_category", + "in": "query", + "required": false, + "description": "Only events in these categories. Repeat the parameter for several values (`?event_category=payment&event_category=dispute`); an event matching any of them is returned.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ] + } + }, + "example": [ + "payment", + "dispute" + ] + }, + { + "name": "action_type", + "in": "query", + "required": false, + "description": "Only events with these action types (exact, case-sensitive). Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "example": [ + "deposit_completed", + "chargeback_opened" + ] + }, + { + "name": "status", + "in": "query", + "required": false, + "description": "Only events with these statuses. Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ] + } + }, + "example": [ + "IN_REVIEW", + "DECLINED" + ] + }, + { + "name": "retention_class", + "in": "query", + "required": false, + "description": "Only events in these retention classes. Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ] + } + }, + "example": [ + "risk_relevant" + ] + }, + { + "name": "subject_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the subject's `vendor_data`. The same id can name an individual and a company; add `subject_entity_type` to pick one, or use `GET /v3/events/subjects/{vendor_data}/`.", + "schema": { + "type": "string" + }, + "example": "user-42" + }, + { + "name": "subject_entity_type", + "in": "query", + "required": false, + "description": "`company` returns only events of company subjects; any other non-empty value (for example `individual`) returns only events of individuals. Omit it to return both.", + "schema": { + "type": "string", + "enum": [ + "individual", + "company" + ] + }, + "example": "individual" + }, + { + "name": "business_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the `business.vendor_data` sent with the event.", + "schema": { + "type": "string" + }, + "example": "merchant-7" + }, + { + "name": "session_id", + "in": "query", + "required": false, + "description": "Exact match on `session.session_id`.", + "schema": { + "type": "string" + }, + "example": "web-session-abc" + }, + { + "name": "device_fingerprint", + "in": "query", + "required": false, + "description": "Exact match on `device_context.fingerprint`.", + "schema": { + "type": "string" + }, + "example": "fp-a8c1" + }, + { + "name": "ip_address", + "in": "query", + "required": false, + "description": "Exact match on the event's IP address.", + "schema": { + "type": "string" + }, + "example": "203.0.113.42" + }, + { + "name": "game_id", + "in": "query", + "required": false, + "description": "Exact match on the promoted `game_id`.", + "schema": { + "type": "string" + }, + "example": "poker-holdem-01" + }, + { + "name": "round_id", + "in": "query", + "required": false, + "description": "Exact match on the promoted `round_id`.", + "schema": { + "type": "string" + }, + "example": "round-88231" + }, + { + "name": "affiliate_code", + "in": "query", + "required": false, + "description": "Exact match on the promoted `affiliate_code`.", + "schema": { + "type": "string" + }, + "example": "aff-summer" + }, + { + "name": "payment_reference_id", + "in": "query", + "required": false, + "description": "Exact match on the payment reference (from `transaction_context.payment_reference_id` or `payload.payment_reference_id`).", + "schema": { + "type": "string" + }, + "example": "dep_735" + }, + { + "name": "counterparty_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the promoted `counterparty_vendor_data`.", + "schema": { + "type": "string" + }, + "example": "user-77" + }, + { + "name": "matched_only", + "in": "query", + "required": false, + "description": "`true` (also `1` or `yes`, case-insensitive) returns only events at least one rule matched (`rules_matched_count` above 0). Any other value is ignored.", + "schema": { + "type": "boolean", + "default": false + }, + "example": true + }, + { + "name": "occurred_from", + "in": "query", + "required": false, + "description": "Only events with `occurred_at` at or after this instant (inclusive). Send an ISO 8601 value: `2026-10-01T00:00:00Z`, an explicit offset with the `+` percent-encoded (`2026-10-01T02:00:00%2B02:00`; an unencoded `+` is decoded as a space and the value no longer parses), or a date alone (`2026-10-01`), which means 00:00:00 UTC of that day. A value without an offset is read as UTC. Values in any other format are not accepted.", + "schema": { + "type": "string", + "format": "date-time" + }, + "example": "2026-10-01T00:00:00Z" + }, + { + "name": "occurred_to", + "in": "query", + "required": false, + "description": "Only events with `occurred_at` at or before this instant (inclusive). A date alone means the start of that day, so `occurred_to=2026-10-15` excludes the rest of 15 October; send `2026-10-15T23:59:59Z` to include it. Send an ISO 8601 value: `2026-10-01T00:00:00Z`, an explicit offset with the `+` percent-encoded (`2026-10-01T02:00:00%2B02:00`; an unencoded `+` is decoded as a space and the value no longer parses), or a date alone (`2026-10-01`), which means 00:00:00 UTC of that day. A value without an offset is read as UTC. Values in any other format are not accepted.", + "schema": { + "type": "string", + "format": "date-time" + }, + "example": "2026-10-17T23:59:59Z" + }, + { + "name": "search", + "in": "query", + "required": false, + "description": "Case-insensitive substring match on `event_id`, `action_type`, `subject_vendor_data` or `counterparty_vendor_data`.", + "schema": { + "type": "string" + }, + "example": "chargeback" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/events/?event_category=payment&event_category=dispute&matched_only=true&occurred_from=2026-10-01T00:00:00Z&limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.monitoring_events.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.monitoringEvents.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.MonitoringEvents.ListAutoPaging(ctx, didit.MonitoringEventsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.monitoringEvents().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.monitoring_events.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.MonitoringEvents.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "One page of event summaries.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventPage" + }, + "examples": { + "Payment and dispute events": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "event_uuid": "11111111-2222-3333-4444-555555555555", + "event_id": "chargeback-2026-10-16-000318", + "event_category": "dispute", + "action_type": "chargeback_opened", + "occurred_at": "2026-10-16T14:02:11Z", + "received_at": "2026-10-16T14:02:12.418230Z", + "status": "IN_REVIEW", + "score": 60, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": null, + "device_fingerprint": null, + "ip_address": null, + "ip_country": null, + "amount": "250.000000000000000000", + "currency": "EUR", + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": "dep_735", + "retention_class": "monetary_or_compliance", + "rules_matched_count": 1, + "correlation_pending": false, + "is_droppable": false + }, + { + "event_uuid": "22222222-3333-4444-5555-666666666666", + "event_id": "login-2026-10-15-000912", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-15T08:30:00.120000Z", + "received_at": "2026-10-15T08:30:00.734512Z", + "status": "ACCEPTED", + "score": 0, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": "web-session-abc", + "device_fingerprint": "fp-a8c1", + "ip_address": "203.0.113.42", + "ip_country": "ES", + "amount": null, + "currency": null, + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": null, + "retention_class": "standard", + "rules_matched_count": 0, + "correlation_pending": false, + "is_droppable": true + } + ] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is larger than 1000000. The body is a JSON array with one message. A non-numeric or non-positive `limit` falls back to the default of 50 instead of failing.", + "content": { + "application/json": { + "examples": { + "limit too large": { + "summary": "limit above 1000000", + "value": [ + "limit must not exceed 1000000." + ] + }, + "offset too large": { + "summary": "offset above 1000000", + "value": [ + "offset must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: read`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: `GET` requests share a budget of 600 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create monitoring event", + "description": "Send one non-monetary event (login, session, gameplay, bonus, affiliate, payment, dispute, security, responsible gambling or custom) from your backend. The event is validated against its category contract, stored, IP-enriched once per unique public address, and evaluated by your real-time event rules before the response returns; cross-subject and long-window rules run asynchronously (`correlation_pending: true`), and in a sandbox application no rules run. `event_id` is the idempotency key: the same `event_id` with the same `event_category`, `action_type`, `occurred_at`, `subject.vendor_data` and `payload` returns `200` with `idempotent_replay: true` and the stored outcome (other envelope fields are not compared), while different values return `409`. Event ingestion is opt-in per application: until it is switched on this endpoint returns `403` with `code: event_monitoring_disabled` and stores nothing. A scoped API key needs `transactions: write`. See [Event ingestion](https://docs.didit.me/transaction-monitoring/events) and [Rules on events](https://docs.didit.me/transaction-monitoring/event-rules); to send events from a browser or app, use [SDK event submission](https://docs.didit.me/transaction-monitoring/sdk-event-submission).", + "operationId": "create_monitoring_event", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventCreateRequest" + }, + "examples": { + "Login": { + "value": { + "event_id": "login-2026-10-17-000001", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-17T09:15:02.481Z", + "time_zone": "Europe/Madrid", + "subject": { + "vendor_data": "user-42", + "entity_type": "individual" + }, + "session": { + "session_id": "web-session-abc", + "sequence": 142 + }, + "device_context": { + "fingerprint": "fp-a8c1", + "network_context": { + "ip_address": "203.0.113.42" + } + }, + "payload": { + "method": "password" + }, + "custom_values": { + "vip_tier": "gold" + } + } + }, + "Deposit completed": { + "value": { + "event_id": "deposit-2026-10-17-000002", + "event_category": "payment", + "action_type": "deposit_completed", + "occurred_at": "2026-10-17T09:16:40Z", + "subject": { + "vendor_data": "user-42" + }, + "transaction_context": { + "amount": "100.00", + "currency": "EUR", + "direction": "inbound", + "payment_reference_id": "dep_736" + }, + "payload": { + "payment_method": "card", + "status": "completed" + } + } + }, + "Chargeback opened": { + "value": { + "event_id": "chargeback-2026-10-16-000318", + "event_category": "dispute", + "action_type": "chargeback_opened", + "occurred_at": "2026-10-16T14:02:11Z", + "subject": { + "vendor_data": "user-42" + }, + "payload": { + "payment_reference_id": "dep_735", + "chargeback_id": "cb_5521", + "amount": "250.00", + "currency": "EUR", + "reason_code": "fraud" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/events/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"event_id\": \"login-2026-10-17-000001\",\n \"event_category\": \"auth\",\n \"action_type\": \"login\",\n \"occurred_at\": \"2026-10-17T09:15:02.481Z\",\n \"subject\": {\"vendor_data\": \"user-42\", \"entity_type\": \"individual\"},\n \"session\": {\"session_id\": \"web-session-abc\", \"sequence\": 142},\n \"device_context\": {\n \"fingerprint\": \"fp-a8c1\",\n \"network_context\": {\"ip_address\": \"203.0.113.42\"}\n },\n \"payload\": {\"method\": \"password\"}\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.monitoring_events.create(\n event_id=\"login-2026-10-17-000001\",\n event_category=\"auth\",\n action_type=\"login\",\n occurred_at=\"2026-10-17T09:15:02.481Z\",\n time_zone=\"Europe/Madrid\",\n subject={\n \"vendor_data\": \"user-42\",\n \"entity_type\": \"individual\",\n },\n session={\n \"session_id\": \"web-session-abc\",\n \"sequence\": 142,\n },\n device_context={\n \"fingerprint\": \"fp-a8c1\",\n \"network_context\": {\n \"ip_address\": \"203.0.113.42\",\n },\n },\n payload={\"method\": \"password\"},\n custom_values={\"vip_tier\": \"gold\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.monitoringEvents.create({\n event_id: \"login-2026-10-17-000001\",\n event_category: \"auth\",\n action_type: \"login\",\n occurred_at: \"2026-10-17T09:15:02.481Z\",\n time_zone: \"Europe/Madrid\",\n subject: {\n vendor_data: \"user-42\",\n entity_type: \"individual\",\n },\n session: {\n session_id: \"web-session-abc\",\n sequence: 142,\n },\n device_context: {\n fingerprint: \"fp-a8c1\",\n network_context: {\n ip_address: \"203.0.113.42\",\n },\n },\n payload: { method: \"password\" },\n custom_values: { vip_tier: \"gold\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.MonitoringEvents.Create(ctx, didit.MonitoringEventCreateRequest{\n\tEventID: \"login-2026-10-17-000001\",\n\tEventCategory: \"auth\",\n\tActionType: \"login\",\n\tOccurredAt: time.Date(2026, 10, 17, 9, 15, 2, 0, time.UTC),\n\tTimeZone: didit.String(\"Europe/Madrid\"),\n\tSubject: didit.MonitoringEventCreateRequestSubject{\n\t\tVendorData: \"user-42\",\n\t\tEntityType: didit.String(\"individual\"),\n\t},\n\tSession: didit.Some(didit.MonitoringEventCreateRequestSession{\n\t\tSessionID: didit.String(\"web-session-abc\"),\n\t\tSequence: didit.Int(142),\n\t}),\n\tDeviceContext: didit.Some(didit.MonitoringEventCreateRequestDeviceContext{\n\t\tFingerprint: didit.String(\"fp-a8c1\"),\n\t\tNetworkContext: didit.Some(didit.MonitoringEventCreateRequestDeviceContextNetworkContext{\n\t\t\tIPAddress: didit.String(\"203.0.113.42\"),\n\t\t}),\n\t}),\n\tPayload: didit.Some(map[string]any{\"method\": \"password\"}),\n\tCustomValues: didit.Some(map[string]any{\"vip_tier\": \"gold\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.OffsetDateTime;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.MonitoringEventCreateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.monitoringEvents().create(MonitoringEventCreateRequest.builder()\n .eventId(\"login-2026-10-17-000001\")\n .eventCategory(MonitoringEventCreateRequest.EventCategory.of(\"auth\"))\n .actionType(\"login\")\n .occurredAt(OffsetDateTime.parse(\"2026-10-17T09:15:02.481Z\"))\n .timeZone(\"Europe/Madrid\")\n .subject(MonitoringEventCreateRequest.Subject.builder()\n .vendorData(\"user-42\")\n .entityType(\"individual\")\n .build())\n .session(MonitoringEventCreateRequest.Session.builder()\n .sessionId(\"web-session-abc\")\n .sequence(142L)\n .build())\n .deviceContext(MonitoringEventCreateRequest.DeviceContext.builder()\n .fingerprint(\"fp-a8c1\")\n .networkContext(MonitoringEventCreateRequest.DeviceContext.NetworkContext.builder()\n .ipAddress(\"203.0.113.42\")\n .build())\n .build())\n .payload(Map.of(\"method\", \"password\"))\n .customValues(Map.of(\"vip_tier\", MonitoringEventCreateRequest.CustomValuesValue.ofString(\"gold\")))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->create([\n 'event_id' => 'login-2026-10-17-000001',\n 'event_category' => 'auth',\n 'action_type' => 'login',\n 'occurred_at' => '2026-10-17T09:15:02.481Z',\n 'time_zone' => 'Europe/Madrid',\n 'subject' => [\n 'vendor_data' => 'user-42',\n 'entity_type' => 'individual',\n ],\n 'session' => [\n 'session_id' => 'web-session-abc',\n 'sequence' => 142,\n ],\n 'device_context' => [\n 'fingerprint' => 'fp-a8c1',\n 'network_context' => [\n 'ip_address' => '203.0.113.42',\n ],\n ],\n 'payload' => ['method' => 'password'],\n 'custom_values' => ['vip_tier' => 'gold'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.monitoring_events.create(\n event_id: \"login-2026-10-17-000001\",\n event_category: \"auth\",\n action_type: \"login\",\n occurred_at: \"2026-10-17T09:15:02.481Z\",\n time_zone: \"Europe/Madrid\",\n subject: {\n vendor_data: \"user-42\",\n entity_type: \"individual\",\n },\n session: {\n session_id: \"web-session-abc\",\n sequence: 142,\n },\n device_context: {\n fingerprint: \"fp-a8c1\",\n network_context: {\n ip_address: \"203.0.113.42\",\n },\n },\n payload: { method: \"password\" },\n custom_values: { vip_tier: \"gold\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.MonitoringEvents.CreateAsync(new MonitoringEventCreateRequest\n{\n EventId = \"login-2026-10-17-000001\",\n EventCategory = \"auth\",\n ActionType = \"login\",\n OccurredAt = DateTimeOffset.Parse(\"2026-10-17T09:15:02.481Z\"),\n TimeZone = \"Europe/Madrid\",\n Subject = new()\n {\n VendorData = \"user-42\",\n EntityType = \"individual\",\n },\n Session = new MonitoringEventCreateRequestSession\n {\n SessionId = \"web-session-abc\",\n Sequence = 142,\n },\n DeviceContext = new MonitoringEventCreateRequestDeviceContext\n {\n Fingerprint = \"fp-a8c1\",\n NetworkContext = new MonitoringEventCreateRequestDeviceContextNetworkContext\n {\n IpAddress = \"203.0.113.42\",\n },\n },\n Payload = new Dictionary { [\"method\"] = \"password\" },\n CustomValues = new Dictionary { [\"vip_tier\"] = \"gold\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The event was stored and evaluated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventIngestResult" + }, + "examples": { + "Stored, no rule matched": { + "value": { + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "standard", + "expires_at": "2026-11-16T09:15:02.481000Z", + "idempotent_replay": false, + "rule_matches": [] + } + }, + "Rule matched, async rule pending": { + "value": { + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "IN_REVIEW", + "score": 40, + "rules_evaluated_count": 6, + "rules_matched_count": 1, + "correlation_pending": true, + "retention_class": "risk_relevant", + "expires_at": "2027-01-15T09:15:02.481000Z", + "idempotent_replay": false, + "rule_matches": [ + { + "rule_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "library_key": null, + "mode": "ACTIVE", + "is_async": false + } + ] + } + } + } + } + } + }, + "200": { + "description": "Idempotent replay: an event with this `event_id` and the same content is already stored. Nothing was stored or evaluated again; the body describes the stored event.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventIngestResult" + }, + "examples": { + "Replay": { + "value": { + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "standard", + "expires_at": "2026-11-16T09:15:02.481000Z", + "idempotent_replay": true, + "rule_matches": [] + } + } + } + } + } + }, + "400": { + "description": "Validation failed. The body is keyed by field; nested fields are nested objects. Nothing is stored.", + "content": { + "application/json": { + "examples": { + "Missing fields": { + "value": { + "event_id": [ + "This field is required." + ], + "subject": [ + "This field is required." + ] + } + }, + "Unknown category": { + "value": { + "event_category": [ + "\"login\" is not a valid choice." + ] + } + }, + "occurred_at too old": { + "value": { + "occurred_at": [ + "occurred_at is more than 7 days in the past. Use the batch import lane for backfills." + ] + } + }, + "occurred_at in the future": { + "value": { + "occurred_at": [ + "occurred_at is more than 5 minutes in the future." + ] + } + }, + "Missing subject vendor_data": { + "value": { + "subject": { + "vendor_data": [ + "This field is required." + ] + } + } + }, + "Missing required payload keys": { + "value": { + "payload": [ + "payment.deposit_completed requires: payment_method, status." + ] + } + }, + "Missing one-of payload key": { + "value": { + "payload": [ + "dispute.chargeback_opened requires one of: dispute_id, chargeback_id." + ] + } + }, + "Security change without changed_field": { + "value": { + "payload": [ + "security.*_changed requires 'changed_field'." + ] + } + }, + "Payload too large": { + "value": { + "payload": [ + "payload exceeds 16384 bytes. Summarise telemetry client-side." + ] + } + }, + "Too many custom_values": { + "value": { + "custom_values": [ + "custom_values accepts at most 100 keys." + ] + } + }, + "Nested custom value": { + "value": { + "custom_values": [ + "custom_values['limits'] must be a scalar - rule conditions cannot address nested values." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: write`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist. Also returned with `code: event_monitoring_disabled` while event ingestion is switched off for the application; nothing is stored in that case, and the check runs before the body is validated.", + "content": { + "application/json": { + "examples": { + "Event monitoring disabled": { + "value": { + "detail": "Event monitoring is not enabled for this application. Turn on event ingestion in the transaction monitoring settings before submitting events.", + "code": "event_monitoring_disabled" + } + }, + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "409": { + "description": "`event_id` was already used for an event with different content (category, action type, `occurred_at`, subject `vendor_data` or payload). Send the event under a new `event_id`.", + "content": { + "application/json": { + "examples": { + "event_id reused": { + "value": { + "event_id": "event_id 'login-2026-10-17-000001' already exists with a different payload." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: transaction creates and event ingestion (`POST /v3/transactions/`, `POST /v3/events/`, `POST /v3/events/batch/`) share a budget of 600 requests per minute per API key, separate from the 300 shared by every other write request. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying, rounded up so the retry lands in the next window.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Ingestion rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/batch/": { + "post": { + "summary": "Create monitoring events in batch", + "description": "Submit 1 to 500 events in one request, each in the same envelope as `POST /v3/events/`. Every item is validated, stored and evaluated on its own and gets its own outcome (`accepted`, `replayed` or `rejected`), so one malformed event never rolls back the others: the response is `201` when no item was rejected and `207` when at least one was, and both mean the request was processed. Send `{\"events\": [...]}`; a bare JSON array of events is also accepted. Event ingestion must be switched on for the application, otherwise the whole request returns `403` with `code: event_monitoring_disabled` and nothing is stored. A scoped API key needs `transactions: write`. See [Batches](https://docs.didit.me/transaction-monitoring/events#batches).", + "operationId": "create_monitoring_events_batch", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "create_batch", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "events" + ], + "properties": { + "events": { + "type": "array", + "minItems": 1, + "maxItems": 500, + "description": "1 to 500 events. Each item follows `MonitoringEventCreateRequest`, including the 7-day past and 5-minute future window on `occurred_at`.", + "items": { + "$ref": "#/components/schemas/MonitoringEventCreateRequest" + } + } + } + }, + "example": { + "events": [ + { + "event_id": "login-2026-10-17-000001", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-17T09:15:02.481Z", + "subject": { + "vendor_data": "user-42" + } + }, + { + "event_id": "deposit-2026-10-17-000002", + "event_category": "payment", + "action_type": "deposit_completed", + "occurred_at": "2026-10-17T09:16:40Z", + "subject": { + "vendor_data": "user-42" + }, + "transaction_context": { + "amount": "100.00", + "currency": "EUR", + "payment_reference_id": "dep_736" + }, + "payload": { + "payment_method": "card", + "status": "completed" + } + } + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/events/batch/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"events\": [\n {\"event_id\": \"login-2026-10-17-000001\", \"event_category\": \"auth\", \"action_type\": \"login\",\n \"occurred_at\": \"2026-10-17T09:15:02.481Z\", \"subject\": {\"vendor_data\": \"user-42\"}},\n {\"event_id\": \"deposit-2026-10-17-000002\", \"event_category\": \"payment\", \"action_type\": \"deposit_completed\",\n \"occurred_at\": \"2026-10-17T09:16:40.000Z\", \"subject\": {\"vendor_data\": \"user-42\"},\n \"transaction_context\": {\"amount\": \"100.00\", \"currency\": \"EUR\", \"payment_reference_id\": \"dep_736\"},\n \"payload\": {\"payment_method\": \"card\", \"status\": \"completed\"}}\n ]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.monitoring_events.create_batch(\n events=[{\n \"event_id\": \"login-2026-10-17-000001\",\n \"event_category\": \"auth\",\n \"action_type\": \"login\",\n \"occurred_at\": \"2026-10-17T09:15:02.481Z\",\n \"subject\": {\n \"vendor_data\": \"user-42\",\n },\n }, {\n \"event_id\": \"deposit-2026-10-17-000002\",\n \"event_category\": \"payment\",\n \"action_type\": \"deposit_completed\",\n \"occurred_at\": \"2026-10-17T09:16:40Z\",\n \"subject\": {\n \"vendor_data\": \"user-42\",\n },\n \"transaction_context\": {\n \"amount\": \"100.00\",\n \"currency\": \"EUR\",\n \"payment_reference_id\": \"dep_736\",\n },\n \"payload\": {\"payment_method\": \"card\", \"status\": \"completed\"},\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.monitoringEvents.createBatch({\n events: [{\n event_id: \"login-2026-10-17-000001\",\n event_category: \"auth\",\n action_type: \"login\",\n occurred_at: \"2026-10-17T09:15:02.481Z\",\n subject: {\n vendor_data: \"user-42\",\n },\n }, {\n event_id: \"deposit-2026-10-17-000002\",\n event_category: \"payment\",\n action_type: \"deposit_completed\",\n occurred_at: \"2026-10-17T09:16:40Z\",\n subject: {\n vendor_data: \"user-42\",\n },\n transaction_context: {\n amount: \"100.00\",\n currency: \"EUR\",\n payment_reference_id: \"dep_736\",\n },\n payload: { payment_method: \"card\", status: \"completed\" },\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.MonitoringEvents.CreateBatch(ctx, didit.MonitoringEventsCreateBatchParams{\n\tEvents: []didit.MonitoringEventCreateRequest{{\n\t\tEventID: \"login-2026-10-17-000001\",\n\t\tEventCategory: \"auth\",\n\t\tActionType: \"login\",\n\t\tOccurredAt: time.Date(2026, 10, 17, 9, 15, 2, 0, time.UTC),\n\t\tSubject: didit.MonitoringEventCreateRequestSubject{\n\t\t\tVendorData: \"user-42\",\n\t\t},\n\t}, {\n\t\tEventID: \"deposit-2026-10-17-000002\",\n\t\tEventCategory: \"payment\",\n\t\tActionType: \"deposit_completed\",\n\t\tOccurredAt: time.Date(2026, 10, 17, 9, 16, 40, 0, time.UTC),\n\t\tSubject: didit.MonitoringEventCreateRequestSubject{\n\t\t\tVendorData: \"user-42\",\n\t\t},\n\t\tTransactionContext: didit.Some(didit.MonitoringEventCreateRequestTransactionContext{\n\t\t\tAmount: didit.String(\"100.00\"),\n\t\t\tCurrency: didit.String(\"EUR\"),\n\t\t\tPaymentReferenceID: didit.String(\"dep_736\"),\n\t\t}),\n\t\tPayload: didit.Some(map[string]any{\"payment_method\": \"card\", \"status\": \"completed\"}),\n\t}},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.OffsetDateTime;\nimport java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.MonitoringEventCreateRequest;\nimport me.didit.types.MonitoringEventsCreateBatchParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.monitoringEvents().createBatch(MonitoringEventsCreateBatchParams.builder()\n .events(List.of(\n MonitoringEventCreateRequest.builder()\n .eventId(\"login-2026-10-17-000001\")\n .eventCategory(MonitoringEventCreateRequest.EventCategory.of(\"auth\"))\n .actionType(\"login\")\n .occurredAt(OffsetDateTime.parse(\"2026-10-17T09:15:02.481Z\"))\n .subject(MonitoringEventCreateRequest.Subject.builder()\n .vendorData(\"user-42\")\n .build())\n .build(),\n MonitoringEventCreateRequest.builder()\n .eventId(\"deposit-2026-10-17-000002\")\n .eventCategory(MonitoringEventCreateRequest.EventCategory.of(\"payment\"))\n .actionType(\"deposit_completed\")\n .occurredAt(OffsetDateTime.parse(\"2026-10-17T09:16:40Z\"))\n .subject(MonitoringEventCreateRequest.Subject.builder()\n .vendorData(\"user-42\")\n .build())\n .transactionContext(MonitoringEventCreateRequest.TransactionContext.builder()\n .amount(\"100.00\")\n .currency(\"EUR\")\n .paymentReferenceId(\"dep_736\")\n .build())\n .payload(Map.of(\"payment_method\", \"card\", \"status\", \"completed\"))\n .build()))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->createBatch([\n 'events' => [[\n 'event_id' => 'login-2026-10-17-000001',\n 'event_category' => 'auth',\n 'action_type' => 'login',\n 'occurred_at' => '2026-10-17T09:15:02.481Z',\n 'subject' => [\n 'vendor_data' => 'user-42',\n ],\n ], [\n 'event_id' => 'deposit-2026-10-17-000002',\n 'event_category' => 'payment',\n 'action_type' => 'deposit_completed',\n 'occurred_at' => '2026-10-17T09:16:40Z',\n 'subject' => [\n 'vendor_data' => 'user-42',\n ],\n 'transaction_context' => [\n 'amount' => '100.00',\n 'currency' => 'EUR',\n 'payment_reference_id' => 'dep_736',\n ],\n 'payload' => ['payment_method' => 'card', 'status' => 'completed'],\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.monitoring_events.create_batch(\n events: [{\n event_id: \"login-2026-10-17-000001\",\n event_category: \"auth\",\n action_type: \"login\",\n occurred_at: \"2026-10-17T09:15:02.481Z\",\n subject: {\n vendor_data: \"user-42\",\n },\n }, {\n event_id: \"deposit-2026-10-17-000002\",\n event_category: \"payment\",\n action_type: \"deposit_completed\",\n occurred_at: \"2026-10-17T09:16:40Z\",\n subject: {\n vendor_data: \"user-42\",\n },\n transaction_context: {\n amount: \"100.00\",\n currency: \"EUR\",\n payment_reference_id: \"dep_736\",\n },\n payload: { payment_method: \"card\", status: \"completed\" },\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.MonitoringEvents.CreateBatchAsync(new MonitoringEventsCreateBatchParams\n{\n Events = new MonitoringEventCreateRequest[]\n {\n new()\n {\n EventId = \"login-2026-10-17-000001\",\n EventCategory = \"auth\",\n ActionType = \"login\",\n OccurredAt = DateTimeOffset.Parse(\"2026-10-17T09:15:02.481Z\"),\n Subject = new()\n {\n VendorData = \"user-42\",\n },\n },\n new()\n {\n EventId = \"deposit-2026-10-17-000002\",\n EventCategory = \"payment\",\n ActionType = \"deposit_completed\",\n OccurredAt = DateTimeOffset.Parse(\"2026-10-17T09:16:40Z\"),\n Subject = new()\n {\n VendorData = \"user-42\",\n },\n TransactionContext = new MonitoringEventCreateRequestTransactionContext\n {\n Amount = \"100.00\",\n Currency = \"EUR\",\n PaymentReferenceId = \"dep_736\",\n },\n Payload = new Dictionary { [\"payment_method\"] = \"card\", [\"status\"] = \"completed\" },\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "No item was rejected: every item is `accepted` or `replayed`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "accepted_count", + "replayed_count", + "rejected_count", + "results" + ], + "properties": { + "accepted_count": { + "type": "integer", + "minimum": 0, + "description": "Items stored and evaluated." + }, + "replayed_count": { + "type": "integer", + "minimum": 0, + "description": "Items already stored with the same content." + }, + "rejected_count": { + "type": "integer", + "minimum": 0, + "description": "Items not stored." + }, + "results": { + "type": "array", + "description": "One entry per item, in request order.", + "items": { + "type": "object", + "description": "Outcome of one item. Accepted and replayed items also carry every field of `MonitoringEventIngestResult`; rejected items carry `event_id` and `errors` instead.", + "required": [ + "index", + "outcome" + ], + "properties": { + "index": { + "type": "integer", + "minimum": 0, + "description": "Position of the item in your `events` array." + }, + "outcome": { + "type": "string", + "enum": [ + "accepted", + "replayed", + "rejected" + ], + "description": "`accepted`: stored and evaluated. `replayed`: already stored with the same content. `rejected`: not stored; see `errors`." + }, + "event_id": { + "type": "string", + "nullable": true, + "description": "Your `event_id`. On items rejected by validation it is read from the item's snake_case `event_id` key, so it is null when the item omitted it or sent the `eventId` alias." + }, + "errors": { + "type": "object", + "additionalProperties": true, + "description": "Rejected items only. Validation errors keyed by field, in the same shape as the `400` of `POST /v3/events/`; a reused `event_id` with different content is reported as a string under `errors.event_id`." + }, + "event_uuid": { + "type": "string", + "format": "uuid", + "description": "Accepted and replayed items only." + }, + "status": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ], + "description": "Accepted and replayed items only." + }, + "score": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "rules_evaluated_count": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "rules_matched_count": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "correlation_pending": { + "type": "boolean", + "description": "Accepted and replayed items only." + }, + "retention_class": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ], + "description": "Accepted and replayed items only." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Accepted and replayed items only." + }, + "idempotent_replay": { + "type": "boolean", + "description": "Accepted and replayed items only." + }, + "rule_matches": { + "type": "array", + "description": "Accepted and replayed items only.", + "items": { + "type": "object", + "required": [ + "rule_id", + "library_key", + "mode", + "is_async" + ], + "properties": { + "rule_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the rule." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Library key when the rule was installed from the rules library, otherwise null." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "TEST" + ], + "description": "`TEST` rules record the match but apply no actions." + }, + "is_async": { + "type": "boolean", + "description": "`true` when the match came from the asynchronous pass." + } + } + } + } + } + } + } + } + }, + "examples": { + "All landed": { + "value": { + "accepted_count": 1, + "replayed_count": 1, + "rejected_count": 0, + "results": [ + { + "index": 0, + "outcome": "accepted", + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "standard", + "expires_at": "2026-11-16T09:15:02.481000Z", + "idempotent_replay": false, + "rule_matches": [] + }, + { + "index": 1, + "outcome": "replayed", + "event_uuid": "11111111-2222-3333-4444-555555555555", + "event_id": "deposit-2026-10-17-000002", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "monetary_or_compliance", + "expires_at": null, + "idempotent_replay": true, + "rule_matches": [] + } + ] + } + } + } + } + } + }, + "207": { + "description": "At least one item was rejected. The other items were stored; fix and resend only the rejected ones (resending an accepted item is safe, it comes back `replayed`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "accepted_count", + "replayed_count", + "rejected_count", + "results" + ], + "properties": { + "accepted_count": { + "type": "integer", + "minimum": 0, + "description": "Items stored and evaluated." + }, + "replayed_count": { + "type": "integer", + "minimum": 0, + "description": "Items already stored with the same content." + }, + "rejected_count": { + "type": "integer", + "minimum": 0, + "description": "Items not stored." + }, + "results": { + "type": "array", + "description": "One entry per item, in request order.", + "items": { + "type": "object", + "description": "Outcome of one item. Accepted and replayed items also carry every field of `MonitoringEventIngestResult`; rejected items carry `event_id` and `errors` instead.", + "required": [ + "index", + "outcome" + ], + "properties": { + "index": { + "type": "integer", + "minimum": 0, + "description": "Position of the item in your `events` array." + }, + "outcome": { + "type": "string", + "enum": [ + "accepted", + "replayed", + "rejected" + ], + "description": "`accepted`: stored and evaluated. `replayed`: already stored with the same content. `rejected`: not stored; see `errors`." + }, + "event_id": { + "type": "string", + "nullable": true, + "description": "Your `event_id`. On items rejected by validation it is read from the item's snake_case `event_id` key, so it is null when the item omitted it or sent the `eventId` alias." + }, + "errors": { + "type": "object", + "additionalProperties": true, + "description": "Rejected items only. Validation errors keyed by field, in the same shape as the `400` of `POST /v3/events/`; a reused `event_id` with different content is reported as a string under `errors.event_id`." + }, + "event_uuid": { + "type": "string", + "format": "uuid", + "description": "Accepted and replayed items only." + }, + "status": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ], + "description": "Accepted and replayed items only." + }, + "score": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "rules_evaluated_count": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "rules_matched_count": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "correlation_pending": { + "type": "boolean", + "description": "Accepted and replayed items only." + }, + "retention_class": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ], + "description": "Accepted and replayed items only." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Accepted and replayed items only." + }, + "idempotent_replay": { + "type": "boolean", + "description": "Accepted and replayed items only." + }, + "rule_matches": { + "type": "array", + "description": "Accepted and replayed items only.", + "items": { + "type": "object", + "required": [ + "rule_id", + "library_key", + "mode", + "is_async" + ], + "properties": { + "rule_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the rule." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Library key when the rule was installed from the rules library, otherwise null." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "TEST" + ], + "description": "`TEST` rules record the match but apply no actions." + }, + "is_async": { + "type": "boolean", + "description": "`true` when the match came from the asynchronous pass." + } + } + } + } + } + } + } + } + }, + "examples": { + "Partial success": { + "value": { + "accepted_count": 1, + "replayed_count": 0, + "rejected_count": 2, + "results": [ + { + "index": 0, + "outcome": "accepted", + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "standard", + "expires_at": "2026-11-16T09:15:02.481000Z", + "idempotent_replay": false, + "rule_matches": [] + }, + { + "index": 1, + "event_id": "deposit-2026-10-17-000002", + "outcome": "rejected", + "errors": { + "payload": [ + "payment.deposit_completed requires: payment_method, status." + ] + } + }, + { + "index": 2, + "event_id": "login-2026-10-16-000777", + "outcome": "rejected", + "errors": { + "event_id": "event_id 'login-2026-10-16-000777' already exists with a different payload." + } + } + ] + } + } + } + } + } + }, + "400": { + "description": "The batch itself is malformed: `events` is missing or not an array, is empty, or has more than 500 items. Nothing is stored; split large batches.", + "content": { + "application/json": { + "examples": { + "Not a list": { + "value": { + "events": "Expected a list of events." + } + }, + "Empty": { + "value": { + "events": "At least one event is required." + } + }, + "Too many": { + "value": { + "events": "A batch accepts at most 500 events; received 612." + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: write`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist. Also returned with `code: event_monitoring_disabled` while event ingestion is switched off for the application; nothing is stored in that case, and the check runs before the body is validated.", + "content": { + "application/json": { + "examples": { + "Event monitoring disabled": { + "value": { + "detail": "Event monitoring is not enabled for this application. Turn on event ingestion in the transaction monitoring settings before submitting events.", + "code": "event_monitoring_disabled" + } + }, + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: transaction creates and event ingestion (`POST /v3/transactions/`, `POST /v3/events/`, `POST /v3/events/batch/`) share a budget of 600 requests per minute per API key, separate from the 300 shared by every other write request. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying, rounded up so the retry lands in the next window.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Ingestion rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/sdk-token/": { + "post": { + "summary": "Mint monitoring SDK token", + "description": "Mint a short-lived device credential bound to one subject, so your web or mobile app can send that user's events straight to Didit without an API key. The app sends the returned `monitoring_token` in the `X-Monitoring-Token` header to the device lane (`POST /v1/events/` and `POST /v1/events/batch/`), where the subject is always overwritten with this `vendor_data`, events outside `allowed_event_categories` are rejected, each submitted event spends one use, and a request whose `Origin` does not equal `bind_to_origin` is refused. Call it from your backend only; minting creates the credential and nothing else (no user is created). Event ingestion must be switched on for the application, otherwise this endpoint returns `403` with `code: event_monitoring_disabled` instead of minting a credential that could never write. A scoped API key needs `transactions: write`. See [SDK event submission](https://docs.didit.me/transaction-monitoring/sdk-event-submission).", + "operationId": "create_monitoring_event_sdk_token", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "create_sdk_token", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "camelCase aliases are accepted: `vendorData`, `entityType`, `allowedEventCategories`, `ttlSeconds`, `maxUses`, `bindToOrigin`.", + "required": [ + "vendor_data" + ], + "properties": { + "vendor_data": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "Your id for the subject every event submitted with this credential belongs to." + }, + "entity_type": { + "type": "string", + "maxLength": 32, + "default": "individual", + "description": "`company` binds a company subject; any other value (default `individual`) binds an individual." + }, + "allowed_event_categories": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ] + }, + "description": "Categories the credential may submit. Omit it or send `[]` to allow every category." + }, + "ttl_seconds": { + "type": "integer", + "minimum": 60, + "maximum": 86400, + "default": 900, + "description": "Lifetime of the credential in seconds." + }, + "max_uses": { + "type": "integer", + "minimum": 1, + "nullable": true, + "default": 1000, + "description": "Number of events the credential may submit (a batch spends one use per event). Send `null` for no cap within the lifetime." + }, + "bind_to_origin": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "When set, the browser `Origin` header of every device request must equal this value exactly; a request without `Origin` is refused." + } + } + }, + "example": { + "vendor_data": "user-42", + "allowed_event_categories": [ + "auth", + "session", + "gameplay" + ], + "ttl_seconds": 900, + "max_uses": 1000, + "bind_to_origin": "https://app.example.com" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/events/sdk-token/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"vendor_data\": \"user-42\",\n \"allowed_event_categories\": [\"auth\", \"session\", \"gameplay\"],\n \"ttl_seconds\": 900,\n \"max_uses\": 1000,\n \"bind_to_origin\": \"https://app.example.com\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.monitoring_events.create_sdk_token(\n vendor_data=\"user-42\",\n allowed_event_categories=[\"auth\", \"session\", \"gameplay\"],\n ttl_seconds=900,\n max_uses=1000,\n bind_to_origin=\"https://app.example.com\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.monitoringEvents.createSdkToken({\n vendor_data: \"user-42\",\n allowed_event_categories: [\"auth\", \"session\", \"gameplay\"],\n ttl_seconds: 900,\n max_uses: 1000,\n bind_to_origin: \"https://app.example.com\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.MonitoringEvents.CreateSDKToken(ctx, didit.MonitoringEventsCreateSDKTokenParams{\n\tVendorData: \"user-42\",\n\tAllowedEventCategories: didit.Some([]didit.MonitoringEventsCreateSDKTokenParamsAllowedEventCategories{\"auth\", \"session\", \"gameplay\"}),\n\tTTLSeconds: didit.Int(900),\n\tMaxUses: didit.Int(1000),\n\tBindToOrigin: didit.String(\"https://app.example.com\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.MonitoringEventsCreateSdkTokenParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.monitoringEvents().createSdkToken(MonitoringEventsCreateSdkTokenParams.builder()\n .vendorData(\"user-42\")\n .allowedEventCategories(List.of(MonitoringEventsCreateSdkTokenParams.AllowedEventCategories.of(\"auth\"), MonitoringEventsCreateSdkTokenParams.AllowedEventCategories.of(\"session\"), MonitoringEventsCreateSdkTokenParams.AllowedEventCategories.of(\"gameplay\")))\n .ttlSeconds(900L)\n .maxUses(1000L)\n .bindToOrigin(\"https://app.example.com\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->createSdkToken([\n 'vendor_data' => 'user-42',\n 'allowed_event_categories' => ['auth', 'session', 'gameplay'],\n 'ttl_seconds' => 900,\n 'max_uses' => 1000,\n 'bind_to_origin' => 'https://app.example.com',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.monitoring_events.create_sdk_token(\n vendor_data: \"user-42\",\n allowed_event_categories: [\"auth\", \"session\", \"gameplay\"],\n ttl_seconds: 900,\n max_uses: 1000,\n bind_to_origin: \"https://app.example.com\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.MonitoringEvents.CreateSdkTokenAsync(new MonitoringEventsCreateSdkTokenParams\n{\n VendorData = \"user-42\",\n AllowedEventCategories = new MonitoringEventsCreateSdkTokenParamsAllowedEventCategories[] { \"auth\", \"session\", \"gameplay\" },\n TtlSeconds = 900,\n MaxUses = 1000,\n BindToOrigin = \"https://app.example.com\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The credential was minted. Hand `monitoring_token` to the app and treat it as a secret until `expires_at`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "monitoring_token", + "expires_at", + "allowed_event_categories", + "max_uses", + "bind_to_origin" + ], + "properties": { + "monitoring_token": { + "type": "string", + "description": "The device credential (a 43-character URL-safe token). Send it in the `X-Monitoring-Token` header." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the credential stops being accepted." + }, + "allowed_event_categories": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ] + }, + "description": "Categories the credential may submit; `[]` means every category." + }, + "max_uses": { + "type": "integer", + "nullable": true, + "description": "Event budget of the credential; null means no cap." + }, + "bind_to_origin": { + "type": "string", + "nullable": true, + "description": "Origin the credential is bound to, or null." + } + } + }, + "example": { + "monitoring_token": "q7ZtR2xKp9VbN4mWc8LdH1sYfJ3gT6uEaQ0oPiXzBkC", + "expires_at": "2026-10-17T09:30:02.481000Z", + "allowed_event_categories": [ + "auth", + "session", + "gameplay" + ], + "max_uses": 1000, + "bind_to_origin": "https://app.example.com" + } + } + } + }, + "400": { + "description": "Validation failed: missing `vendor_data`, `ttl_seconds` outside 60 to 86400, `max_uses` below 1, or an unknown category (keyed by its position in the array).", + "content": { + "application/json": { + "examples": { + "Missing vendor_data": { + "value": { + "vendor_data": [ + "This field is required." + ] + } + }, + "ttl_seconds too short": { + "value": { + "ttl_seconds": [ + "Ensure this value is greater than or equal to 60." + ] + } + }, + "Unknown category": { + "value": { + "allowed_event_categories": { + "1": [ + "\"login\" is not a valid choice." + ] + } + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: write`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist. Also returned with `code: event_monitoring_disabled` while event ingestion is switched off for the application; nothing is stored in that case, and the check runs before the body is validated.", + "content": { + "application/json": { + "examples": { + "Event monitoring disabled": { + "value": { + "detail": "Event monitoring is not enabled for this application. Turn on event ingestion in the transaction monitoring settings before submitting events.", + "code": "event_monitoring_disabled" + } + }, + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: write requests (`POST`, `PATCH`, `DELETE`) share a budget of 300 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/{event_uuid}/": { + "get": { + "summary": "Get monitoring event", + "description": "Retrieve one monitoring event with everything the list returns plus the payload, custom values, device context, field provenance (`field_sources`), the promoted domain fields, `expires_at`, every matching rule run with its evidence, the IP enrichment and the tags rules attached. While `correlation_pending` is `true` an asynchronous rule still has to run; read the event again a few minutes later for the final `status`, `score` and `rule_runs`. Reads work whether or not event ingestion is switched on. A scoped API key needs `transactions: read`. See [Evidence](https://docs.didit.me/transaction-monitoring/event-rules#evidence) and [IP enrichment](https://docs.didit.me/transaction-monitoring/ip-enrichment).", + "operationId": "get_monitoring_event", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "event_uuid", + "in": "path", + "required": true, + "description": "Didit identifier of the event (`event_uuid` from the create, batch, list or timeline response), not your own `event_id`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/events/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.monitoring_events.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.monitoringEvents.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.MonitoringEvents.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.monitoringEvents().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.monitoring_events.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.MonitoringEvents.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The event with its evidence.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventDetail" + }, + "examples": { + "Login from a VPN, in review": { + "value": { + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-17T09:15:02.481000Z", + "received_at": "2026-10-17T09:15:02.903114Z", + "status": "IN_REVIEW", + "score": 40, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": "web-session-abc", + "device_fingerprint": "fp-a8c1", + "ip_address": "203.0.113.42", + "ip_country": "ES", + "amount": null, + "currency": null, + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": null, + "retention_class": "risk_relevant", + "rules_matched_count": 1, + "correlation_pending": false, + "is_droppable": true, + "payload": { + "method": "password" + }, + "custom_values": { + "vip_tier": "gold", + "vpn_or_proxy_detected": true + }, + "device_context": { + "fingerprint": "fp-a8c1", + "network_context": { + "ip_address": "203.0.113.42", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_city": "Madrid", + "asn_number": 64500, + "asn_organization": "Example Networks", + "is_vpn_or_tor": true, + "is_data_center": false + } + }, + "field_sources": { + "subject.vendor_data": "customer", + "event_category": "customer", + "action_type": "customer", + "occurred_at": "customer", + "received_at": "server", + "payload.method": "customer", + "session.session_id": "customer", + "session.sequence": "customer", + "device_context.fingerprint": "customer", + "device_context.network_context.ip_address": "customer", + "device_context.network_context.ip_country": "server", + "device_context.network_context.ip_country_code": "server", + "device_context.network_context.ip_city": "server", + "device_context.network_context.asn_number": "server", + "device_context.network_context.asn_organization": "server", + "device_context.network_context.is_vpn_or_tor": "server", + "device_context.network_context.is_data_center": "server" + }, + "session_sequence": 142, + "table_id": null, + "hand_id": null, + "match_id": null, + "market_id": null, + "side": null, + "role": null, + "net_result_amount": null, + "payment_fingerprint": null, + "instrument_reference_hash": null, + "withdrawal_address_hash": null, + "dispute_id": null, + "chargeback_id": null, + "changed_field": null, + "identity_match_ref": null, + "schema_version": 1, + "expires_at": "2027-01-15T09:15:02.481000Z", + "rules_evaluated_count": 6, + "rule_runs": [ + { + "rule_uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "rule_title": "Login from VPN or Tor", + "library_key": null, + "matched": true, + "mode": "ACTIVE", + "is_async": false, + "severity": "MEDIUM", + "score_delta": 40, + "action_summary": "score +40; add_tag:VPN_LOGIN", + "matched_payload": [ + { + "field": "event.ip.is_vpn_or_tor", + "operator": "eq", + "expected_value": true, + "current_value": true, + "matched": true, + "group_index": null, + "group_logic": "AND", + "field_source": "server" + } + ], + "created_at": "2026-10-17T09:15:02.951207Z" + } + ], + "ip_enrichment": { + "ip_country_code": "ES", + "ip_city": "Madrid", + "asn_number": 64500, + "asn_organization": "Example Networks", + "is_vpn_or_tor": true, + "is_data_center": false, + "proxy_type": null, + "last_enriched_at": "2026-10-17T09:15:02.910442Z" + }, + "subject_profile": null, + "tags": [ + "VPN_LOGIN" + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: read`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No event with this `event_uuid` exists in the application: it was never stored, it was deleted, or it belongs to another application. A path segment that is not a UUID does not match the route and also returns `404`.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No MonitoringEvent matches the given query." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: `GET` requests share a budget of 600 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/subjects/{vendor_data}/": { + "get": { + "summary": "Get subject event timeline", + "description": "List every monitoring event of one subject, newest `occurred_at` first. A `vendor_data` is unique only within an entity type, so the same id can name an individual and a company: `entity_type` picks which timeline you get, and it defaults to `individual`, so a company's events need `entity_type=company`. The filters of `GET /v3/events/` apply on top, and a `vendor_data` with no events returns an empty page rather than `404`. A scoped API key needs `transactions: read`. See [Reading events back](https://docs.didit.me/transaction-monitoring/events#reading-events-back).", + "operationId": "get_monitoring_event_subject_timeline", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "retrieve_subject_timeline", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "description": "Your id for the subject (`subject.vendor_data` of its events). URL-encode it; a value containing `/` cannot be addressed by this endpoint.", + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "example": "user-42" + }, + { + "name": "entity_type", + "in": "query", + "required": false, + "description": "`company` returns the company with this `vendor_data`; any other value, or none, returns the individual. `subject_entity_type` is accepted as an alias when `entity_type` is absent.", + "schema": { + "type": "string", + "enum": [ + "individual", + "company" + ], + "default": "individual" + }, + "example": "individual" + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of events per page. Defaults to 50. Values above 1000000 return `400`; a non-numeric, zero or negative value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "example": 50 + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of events to skip. Defaults to 0. Prefer following the `next` URL from the response.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "example": 0 + }, + { + "name": "event_category", + "in": "query", + "required": false, + "description": "Only events in these categories. Repeat the parameter for several values (`?event_category=payment&event_category=dispute`); an event matching any of them is returned.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ] + } + }, + "example": [ + "payment", + "dispute" + ] + }, + { + "name": "action_type", + "in": "query", + "required": false, + "description": "Only events with these action types (exact, case-sensitive). Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "example": [ + "deposit_completed", + "chargeback_opened" + ] + }, + { + "name": "status", + "in": "query", + "required": false, + "description": "Only events with these statuses. Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ] + } + }, + "example": [ + "IN_REVIEW", + "DECLINED" + ] + }, + { + "name": "retention_class", + "in": "query", + "required": false, + "description": "Only events in these retention classes. Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ] + } + }, + "example": [ + "risk_relevant" + ] + }, + { + "name": "business_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the `business.vendor_data` sent with the event.", + "schema": { + "type": "string" + }, + "example": "merchant-7" + }, + { + "name": "session_id", + "in": "query", + "required": false, + "description": "Exact match on `session.session_id`.", + "schema": { + "type": "string" + }, + "example": "web-session-abc" + }, + { + "name": "device_fingerprint", + "in": "query", + "required": false, + "description": "Exact match on `device_context.fingerprint`.", + "schema": { + "type": "string" + }, + "example": "fp-a8c1" + }, + { + "name": "ip_address", + "in": "query", + "required": false, + "description": "Exact match on the event's IP address.", + "schema": { + "type": "string" + }, + "example": "203.0.113.42" + }, + { + "name": "game_id", + "in": "query", + "required": false, + "description": "Exact match on the promoted `game_id`.", + "schema": { + "type": "string" + }, + "example": "poker-holdem-01" + }, + { + "name": "round_id", + "in": "query", + "required": false, + "description": "Exact match on the promoted `round_id`.", + "schema": { + "type": "string" + }, + "example": "round-88231" + }, + { + "name": "affiliate_code", + "in": "query", + "required": false, + "description": "Exact match on the promoted `affiliate_code`.", + "schema": { + "type": "string" + }, + "example": "aff-summer" + }, + { + "name": "payment_reference_id", + "in": "query", + "required": false, + "description": "Exact match on the payment reference (from `transaction_context.payment_reference_id` or `payload.payment_reference_id`).", + "schema": { + "type": "string" + }, + "example": "dep_735" + }, + { + "name": "counterparty_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the promoted `counterparty_vendor_data`.", + "schema": { + "type": "string" + }, + "example": "user-77" + }, + { + "name": "matched_only", + "in": "query", + "required": false, + "description": "`true` (also `1` or `yes`, case-insensitive) returns only events at least one rule matched (`rules_matched_count` above 0). Any other value is ignored.", + "schema": { + "type": "boolean", + "default": false + }, + "example": true + }, + { + "name": "occurred_from", + "in": "query", + "required": false, + "description": "Only events with `occurred_at` at or after this instant (inclusive). Send an ISO 8601 value: `2026-10-01T00:00:00Z`, an explicit offset with the `+` percent-encoded (`2026-10-01T02:00:00%2B02:00`; an unencoded `+` is decoded as a space and the value no longer parses), or a date alone (`2026-10-01`), which means 00:00:00 UTC of that day. A value without an offset is read as UTC. Values in any other format are not accepted.", + "schema": { + "type": "string", + "format": "date-time" + }, + "example": "2026-10-01T00:00:00Z" + }, + { + "name": "occurred_to", + "in": "query", + "required": false, + "description": "Only events with `occurred_at` at or before this instant (inclusive). A date alone means the start of that day, so `occurred_to=2026-10-15` excludes the rest of 15 October; send `2026-10-15T23:59:59Z` to include it. Send an ISO 8601 value: `2026-10-01T00:00:00Z`, an explicit offset with the `+` percent-encoded (`2026-10-01T02:00:00%2B02:00`; an unencoded `+` is decoded as a space and the value no longer parses), or a date alone (`2026-10-01`), which means 00:00:00 UTC of that day. A value without an offset is read as UTC. Values in any other format are not accepted.", + "schema": { + "type": "string", + "format": "date-time" + }, + "example": "2026-10-17T23:59:59Z" + }, + { + "name": "search", + "in": "query", + "required": false, + "description": "Case-insensitive substring match on `event_id`, `action_type`, `subject_vendor_data` or `counterparty_vendor_data`.", + "schema": { + "type": "string" + }, + "example": "chargeback" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/events/subjects/user-42/?entity_type=individual&limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.monitoring_events.retrieve_subject_timeline(\n \"user-42\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.monitoringEvents.retrieveSubjectTimeline(\"user-42\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.MonitoringEvents.RetrieveSubjectTimelineAutoPaging(ctx, \"user-42\", didit.MonitoringEventsRetrieveSubjectTimelineParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.monitoringEvents().retrieveSubjectTimeline(\"user-42\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->retrieveSubjectTimeline('user-42') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.monitoring_events.retrieve_subject_timeline(\"user-42\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.MonitoringEvents.RetrieveSubjectTimelineAsync(\"user-42\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "One page of the subject's events.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventPage" + }, + "examples": { + "Individual timeline": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "event_uuid": "11111111-2222-3333-4444-555555555555", + "event_id": "chargeback-2026-10-16-000318", + "event_category": "dispute", + "action_type": "chargeback_opened", + "occurred_at": "2026-10-16T14:02:11Z", + "received_at": "2026-10-16T14:02:12.418230Z", + "status": "IN_REVIEW", + "score": 60, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": null, + "device_fingerprint": null, + "ip_address": null, + "ip_country": null, + "amount": "250.000000000000000000", + "currency": "EUR", + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": "dep_735", + "retention_class": "monetary_or_compliance", + "rules_matched_count": 1, + "correlation_pending": false, + "is_droppable": false + }, + { + "event_uuid": "22222222-3333-4444-5555-666666666666", + "event_id": "login-2026-10-15-000912", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-15T08:30:00.120000Z", + "received_at": "2026-10-15T08:30:00.734512Z", + "status": "ACCEPTED", + "score": 0, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": "web-session-abc", + "device_fingerprint": "fp-a8c1", + "ip_address": "203.0.113.42", + "ip_country": "ES", + "amount": null, + "currency": null, + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": null, + "retention_class": "standard", + "rules_matched_count": 0, + "correlation_pending": false, + "is_droppable": true + } + ] + } + }, + "No events": { + "value": { + "count": 0, + "next": null, + "previous": null, + "results": [] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is larger than 1000000. The body is a JSON array with one message. A non-numeric or non-positive `limit` falls back to the default of 50 instead of failing.", + "content": { + "application/json": { + "examples": { + "limit too large": { + "summary": "limit above 1000000", + "value": [ + "limit must not exceed 1000000." + ] + }, + "offset too large": { + "summary": "offset above 1000000", + "value": [ + "offset must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: read`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: `GET` requests share a budget of 600 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/enrichment-metrics/": { + "get": { + "summary": "Get event enrichment metrics", + "description": "Return the event ingestion and IP-enrichment counters of your application for a period, summed over every lane (server, batch and device), so you can watch event volume and confirm that enrichment is resolved once per unique address rather than once per event. The period covers today plus the previous `days` days, counted by UTC calendar date. The three ratios are null when their denominator is zero. A scoped API key needs `transactions: read`. See [IP enrichment](https://docs.didit.me/transaction-monitoring/events#ip-enrichment).", + "operationId": "get_monitoring_event_enrichment_metrics", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "retrieve_enrichment_metrics", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "days", + "in": "query", + "required": false, + "description": "Length of the period in days. Defaults to 30; values are clamped to 1 to 365, and a non-integer value falls back to 30. The value applied is returned as `period_days`.", + "schema": { + "type": "integer", + "default": 30, + "minimum": 1, + "maximum": 365 + }, + "example": 30 + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/events/enrichment-metrics/?days=30' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.monitoring_events.retrieve_enrichment_metrics()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.monitoringEvents.retrieveEnrichmentMetrics();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.MonitoringEvents.RetrieveEnrichmentMetrics(ctx, didit.MonitoringEventsRetrieveEnrichmentMetricsParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.monitoringEvents().retrieveEnrichmentMetrics();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->retrieveEnrichmentMetrics();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.monitoring_events.retrieve_enrichment_metrics\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.MonitoringEvents.RetrieveEnrichmentMetricsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Counters and ratios for the period.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "events_ingested", + "events_rejected", + "enrichment_lookups", + "enrichment_cache_hits", + "enrichment_provider_calls", + "enrichment_skipped_low_value", + "unique_ips_seen", + "period_days", + "cache_hit_rate", + "unique_ip_ratio", + "provider_calls_per_1k_events" + ], + "properties": { + "events_ingested": { + "type": "integer", + "description": "New events stored. Idempotent replays are not counted." + }, + "events_rejected": { + "type": "integer", + "description": "Items rejected inside batch requests (server and device lanes). A rejected single-event request is not counted." + }, + "enrichment_lookups": { + "type": "integer", + "description": "Events whose public IP address was resolved, from the cache or freshly." + }, + "enrichment_cache_hits": { + "type": "integer", + "description": "Lookups answered from the cache." + }, + "enrichment_provider_calls": { + "type": "integer", + "description": "Lookups that needed a fresh resolution of the address." + }, + "enrichment_skipped_low_value": { + "type": "integer", + "description": "Low-value telemetry events whose address was not already known. They never trigger a fresh resolution of their own." + }, + "unique_ips_seen": { + "type": "integer", + "description": "Addresses that needed a fresh resolution." + }, + "period_days": { + "type": "integer", + "description": "The `days` value applied after clamping." + }, + "cache_hit_rate": { + "type": "number", + "nullable": true, + "description": "`enrichment_cache_hits / enrichment_lookups`, rounded to 4 decimals. Null when there were no lookups." + }, + "unique_ip_ratio": { + "type": "number", + "nullable": true, + "description": "`unique_ips_seen / events_ingested`, rounded to 4 decimals. Null when no events were ingested." + }, + "provider_calls_per_1k_events": { + "type": "number", + "nullable": true, + "description": "Fresh resolutions per 1000 ingested events, rounded to 2 decimals. Null when no events were ingested." + } + } + }, + "examples": { + "Last 30 days": { + "value": { + "events_ingested": 128450, + "events_rejected": 214, + "enrichment_lookups": 41230, + "enrichment_cache_hits": 40118, + "enrichment_provider_calls": 1112, + "enrichment_skipped_low_value": 74320, + "unique_ips_seen": 1112, + "period_days": 30, + "cache_hit_rate": 0.973, + "unique_ip_ratio": 0.0087, + "provider_calls_per_1k_events": 8.66 + } + }, + "No events yet": { + "value": { + "events_ingested": 0, + "events_rejected": 0, + "enrichment_lookups": 0, + "enrichment_cache_hits": 0, + "enrichment_provider_calls": 0, + "enrichment_skipped_low_value": 0, + "unique_ips_seen": 0, + "period_days": 30, + "cache_hit_rate": null, + "unique_ip_ratio": null, + "provider_calls_per_1k_events": null + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: read`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: `GET` requests share a budget of 600 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/{event_uuid}/delete/": { + "delete": { + "summary": "Delete monitoring event", + "description": "Permanently delete one monitoring event together with its rule runs, notes and tag links, for example an event sent by mistake. After the `204` the event is gone from every read (retrieve returns `404`, and it no longer appears in the list or the subject timeline), the deletion is irreversible, and its `event_id` is free to be used for a new event. Not affected: the user or company the event resolved to, cases a rule opened, list entries a rule added, and the scores and statuses already applied to other events. To erase everything about a person or a company, delete the user or the company instead, which also deletes their events. A scoped API key needs `transactions: write`.", + "operationId": "delete_monitoring_event", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "event_uuid", + "in": "path", + "required": true, + "description": "Didit identifier of the event (`event_uuid` from the create, batch, list or timeline response), not your own `event_id`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/events/11111111-2222-3333-4444-555555555555/delete/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.monitoring_events.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.monitoringEvents.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.MonitoringEvents.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.monitoringEvents().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.monitoring_events.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.MonitoringEvents.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ], + "responses": { + "204": { + "description": "The event was deleted. No body." + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: write`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No event with this `event_uuid` exists in the application: it was never stored, it was deleted, or it belongs to another application. A path segment that is not a UUID does not match the route and also returns `404`.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No MonitoringEvent matches the given query." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: write requests (`POST`, `PATCH`, `DELETE`) share a budget of 300 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/kyb/parties/{partyUuid}/regenerate/": { + "post": { + "summary": "Regenerate a Key People party's KYC session", + "description": "Give a Key People party of a Business Verification (KYB) session a new individual KYC session after its previous one was deleted or expired before the person started it, so the Key People check can finish again; send no request body. It works when the party's KYC session was deleted (`kyc_session_deleted: true`) or expired before the person started it (`Expired`, never started). A session still in progress or one that ended in any other status (`Approved`, `Declined`, `In Review`, `Abandoned`, ...) returns `409` unless it was deleted. A party that never had one or a company party always returns `409`. Didit creates a new hosted KYC session with the workflow the party was verified with (its latest published version) or, when that is no longer known, the workflow your Key People settings assign to the party's role, so its link lasts that KYC workflow's session expiration time; it emails the link to the party when an email is on file, clears `is_skipped`, and recomputes the Key People check and the business session status. It then sends a `data.updated` [webhook](https://docs.didit.me/integration/webhooks) for the business session with trigger `console_key_people_regenerate`; to exempt the party instead, use `PATCH /v3/session/{sessionId}/kyb/parties/{partyUuid}/`. A scoped API key needs `decisions: write`, and without `sessions: write` it receives `kyc_session_url` as `null`. Deleted sessions qualify even if the person had already started or finished verification. Imported sessions do not qualify through expiry; they must have been deleted.", + "operationId": "post_v3_session_kyb_party_regenerate", + "x-didit-sdk": { + "resource": "sessions", + "method": "regenerate_kyb_party", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the parent Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "partyUuid", + "in": "path", + "required": true, + "description": "UUID of the Key People party (`key_people_checks[].submitted.parties[].uuid` on the business session decision). Must belong to the session referenced by `sessionId`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/session/33333333-4444-5555-6666-777777777777/kyb/parties/0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41/regenerate/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.regenerate_kyb_party(\n \"33333333-4444-5555-6666-777777777777\",\n \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.regenerateKybParty(\n \"33333333-4444-5555-6666-777777777777\",\n \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.RegenerateKYBParty(ctx, \"33333333-4444-5555-6666-777777777777\", \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().regenerateKybParty(\"33333333-4444-5555-6666-777777777777\", \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->regenerateKybParty('33333333-4444-5555-6666-777777777777', '0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.regenerate_kyb_party(\n \"33333333-4444-5555-6666-777777777777\",\n \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.RegenerateKybPartyAsync(\"33333333-4444-5555-6666-777777777777\", \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "New KYC session created. Returns the updated party with its new child session, which expires after its KYC workflow's session expiration time.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBKeyPeopleParty" + }, + "example": { + "uuid": "0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41", + "entity_type": "person", + "source": "USER", + "name": "Jane Smith", + "first_name": "Jane", + "last_name": "Smith", + "company_name": null, + "registration_number": null, + "email": "user@example.com", + "nationality": "GB", + "phone_number": null, + "date_of_birth": "1980-05-04", + "position": "Director", + "custom_fields": {}, + "is_active": true, + "is_skipped": false, + "requires_verification": true, + "roles": [ + { + "role": "director", + "ownership_percent": null, + "voting_percent": null + } + ], + "kyc_session_id": "55555555-6666-7777-8888-999999999999", + "kyc_session_status": "Not Started", + "kyc_session_url": "https://verify.didit.me/session/abc123def456", + "kyc_session_deleted": false, + "kyb_sub_session_id": null, + "kyb_sub_session_status": null, + "kyb_sub_session_url": null, + "kyb_sub_session_deleted": false, + "verification_workflow_type": "kyc", + "verification_workflow_id": "12121212-3434-5656-7878-909090909090", + "verification_workflow_label": "Key People KYC", + "verification_workflow_features": [ + { + "feature": "ID_VERIFICATION", + "status": "Not Finished" + }, + { + "feature": "LIVENESS", + "status": "Not Finished" + }, + { + "feature": "AML", + "status": "Not Finished" + } + ], + "didit_internal_id": null + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits; no party with this UUID exists in your application; or the party belongs to another session. Each case has its own `detail`. The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's workflow or approved-only limits", + "value": { + "detail": "Session not found." + } + }, + "Unknown party": { + "summary": "No party with this UUID in your application", + "value": { + "detail": "No BusinessParty matches the given query." + } + }, + "Party of another session": { + "summary": "partyUuid exists but belongs to a different session", + "value": { + "detail": "Party does not belong to the given session." + } + } + } + } + } + }, + "409": { + "description": "The party has no eligible KYC session to replace. Unless deleted, a session still in progress or one that ended in another status (`Approved`, `Declined`, `In Review`, `Abandoned`, ...) cannot be regenerated. A party that never had one or a company party also returns `409`. An eligible session was deleted or expired before the person started it. Deleted sessions qualify even if the person had already started or finished verification. Imported sessions do not qualify through expiry; they must have been deleted. Branch on `code`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "detail", + "code" + ], + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string", + "enum": [ + "kyb_party_kyc_session_not_deleted" + ], + "description": "Stable machine-readable error code." + } + } + }, + "example": { + "detail": "This party's KYC session is still live (it has neither been deleted nor expired without starting), so it cannot be regenerated.", + "code": "kyb_party_kyc_session_not_deleted" + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/transactions/{transaction_id}/notes/": { + "get": { + "summary": "List transaction notes", + "description": "List the notes on one monitored transaction, newest first (`created_at` descending): notes you add through the API, notes and status changes reviewers make in the Console, and notes Didit adds automatically (a rule's `add_note` action, or a problem creating the user-action verification session), which carry `actor_name` `Rule Engine`. `metadata` tells the note types apart.\n\nPaginated with `limit` and `offset`. `count` is exact up to 100 and capped at 100 above that, so follow `next` until it is `null` instead of computing pages from `count`. This is a read with no side effects.\n\nA scoped API key needs `transactions: read`; a key limited to some workflows or to approved sessions cannot call this endpoint. Reviewers see the same list under **Notes** on the [transaction detail](https://docs.didit.me/transaction-monitoring/console#transaction-detail).", + "operationId": "list_transaction_notes", + "x-didit-sdk": { + "resource": "transactions.notes", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit transaction UUID: the `uuid` returned by `POST /v3/transactions/` and `GET /v3/transactions/`. Your own `txn_id` is not accepted.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "description": "Number of notes per page. Defaults to 50. A value that is not a positive integer falls back to 50; a value above 1000000 returns `400`." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "description": "Zero-based position of the first note to return. Prefer following `next`. A value that is not a whole number falls back to 0; a value above 1000000 returns `400`." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/abcdef12-3456-7890-abcd-ef1234567890/notes/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.transactions.notes.list(\n \"abcdef12-3456-7890-abcd-ef1234567890\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.transactions.notes.list(\"abcdef12-3456-7890-abcd-ef1234567890\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Transactions.Notes.ListAutoPaging(ctx, \"abcdef12-3456-7890-abcd-ef1234567890\", didit.TransactionsNotesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.transactions().notes().list(\"abcdef12-3456-7890-abcd-ef1234567890\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->notes->list('abcdef12-3456-7890-abcd-ef1234567890') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.notes.list(\"abcdef12-3456-7890-abcd-ef1234567890\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Transactions.Notes.ListAsync(\"abcdef12-3456-7890-abcd-ef1234567890\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "A page of notes, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Number of notes on the transaction: exact up to 100, capped at 100 above that. Follow `next` instead of computing pages from it." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionNote" + } + } + } + }, + "examples": { + "API, Console and rule notes": { + "value": { + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "uuid": "c1111111-2222-4333-8444-555555555555", + "note": "Customer confirmed the withdrawal by phone.", + "actor_email": null, + "actor_name": null, + "metadata": { + "mentioned_emails": [ + "analyst@example.com" + ] + }, + "created_at": "2026-10-16T10:05:12.481203Z" + }, + { + "uuid": "c2222222-3333-4444-8555-666666666666", + "note": "Status changed from IN_REVIEW to APPROVED. Reason: Source of funds verified", + "actor_email": "reviewer@example.com", + "actor_name": "Alex Reviewer", + "metadata": { + "type": "status_change", + "previous_status": "IN_REVIEW", + "new_status": "APPROVED", + "reason_code": "SOF_VERIFIED", + "reason_label": "Source of funds verified" + }, + "created_at": "2026-10-15T17:22:40.103911Z" + }, + { + "uuid": "c3333333-4444-4555-8666-777777777777", + "note": "Large withdrawal to a new wallet: check the source of funds.", + "actor_email": null, + "actor_name": "Rule Engine", + "metadata": { + "rule_uuid": "d1111111-2222-4333-8444-555555555555", + "source": "rule_action" + }, + "created_at": "2026-10-15T08:42:12.004518Z" + } + ] + } + }, + "No notes yet": { + "value": { + "count": 0, + "next": null, + "previous": null, + "results": [] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is above 1000000. The body is a JSON array of messages, not an object.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "examples": { + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + }, + "Offset too large": { + "value": [ + "offset must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing key gets the generic message; a wrong, revoked or expired key gets `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without transactions read": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists in the application (never created, deleted, or owned by another application). A `transaction_id` that is not a UUID does not reach the API: it returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "No Transaction matches the given query." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create transaction note", + "description": "Add a plain-text note to a monitored transaction's review timeline, where Console reviewers see it. A note is context, not a decision: the transaction's `status`, `score` and alerts do not change, no webhook is sent, and nothing is billed. The transaction's `activities` gain a `TRANSACTION_NOTE_ADDED` entry.\n\nNotes created with an API key have no author: `actor_email` and `actor_name` are `null`, so put the author in the text if you need it. Every address in `mentioned_emails` must belong to a member of your organization, otherwise the whole request fails with `400`; mentions are stored lower-cased and de-duplicated, and this endpoint does not notify the mentioned members.\n\nEach call creates a new note, so retrying a timed-out request can add it twice: list the notes before retrying. There is no API endpoint to edit or remove a note. A scoped API key needs `transactions: write`; a key limited to some workflows or to approved sessions cannot call this endpoint.", + "operationId": "create_transaction_note", + "x-didit-sdk": { + "resource": "transactions.notes", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit transaction UUID: the `uuid` returned by `POST /v3/transactions/` and `GET /v3/transactions/`. Your own `txn_id` is not accepted.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "note" + ], + "properties": { + "note": { + "type": "string", + "minLength": 1, + "description": "Plain-text note. Leading and trailing whitespace is trimmed and a blank note is rejected. No length limit." + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + }, + "default": [], + "description": "Emails of members of your organization to mention. Each must be a valid email of an organization member." + } + } + }, + "examples": { + "Note with a mention": { + "value": { + "note": "Customer confirmed the withdrawal by phone.", + "mentioned_emails": [ + "analyst@example.com" + ] + } + }, + "Plain note": { + "value": { + "note": "Source of funds document requested by email." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/abcdef12-3456-7890-abcd-ef1234567890/notes/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"note\": \"Customer confirmed the withdrawal by phone.\", \"mentioned_emails\": [\"analyst@example.com\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.notes.create(\n \"abcdef12-3456-7890-abcd-ef1234567890\",\n note=\"Customer confirmed the withdrawal by phone.\",\n mentioned_emails=[\"analyst@example.com\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.notes.create(\"abcdef12-3456-7890-abcd-ef1234567890\", {\n note: \"Customer confirmed the withdrawal by phone.\",\n mentioned_emails: [\"analyst@example.com\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Notes.Create(ctx, \"abcdef12-3456-7890-abcd-ef1234567890\", didit.TransactionsNotesCreateParams{\n\tNote: \"Customer confirmed the withdrawal by phone.\",\n\tMentionedEmails: didit.Some([]string{\"analyst@example.com\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.TransactionsNotesCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().notes().create(\"abcdef12-3456-7890-abcd-ef1234567890\", TransactionsNotesCreateParams.builder()\n .note(\"Customer confirmed the withdrawal by phone.\")\n .mentionedEmails(List.of(\"analyst@example.com\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->notes->create('abcdef12-3456-7890-abcd-ef1234567890', [\n 'note' => 'Customer confirmed the withdrawal by phone.',\n 'mentioned_emails' => ['analyst@example.com'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.notes.create(\n \"abcdef12-3456-7890-abcd-ef1234567890\",\n note: \"Customer confirmed the withdrawal by phone.\",\n mentioned_emails: [\"analyst@example.com\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Notes.CreateAsync(\"abcdef12-3456-7890-abcd-ef1234567890\", new TransactionsNotesCreateParams\n{\n Note = \"Customer confirmed the withdrawal by phone.\",\n MentionedEmails = new string[] { \"analyst@example.com\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The note was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionNote" + }, + "examples": { + "Created": { + "value": { + "uuid": "c1111111-2222-4333-8444-555555555555", + "note": "Customer confirmed the withdrawal by phone.", + "actor_email": null, + "actor_name": null, + "metadata": { + "mentioned_emails": [ + "analyst@example.com" + ] + }, + "created_at": "2026-10-16T10:05:12.481203Z" + } + } + } + } + } + }, + "400": { + "description": "The body is invalid: `note` missing, null or blank, an invalid address in `mentioned_emails` (errors keyed by list position), or a mentioned address that is not a member of your organization. Nothing is created.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Field errors keyed by field name.", + "additionalProperties": {} + }, + "examples": { + "Missing note": { + "value": { + "note": [ + "This field is required." + ] + } + }, + "Blank note": { + "value": { + "note": [ + "This field may not be blank." + ] + } + }, + "Invalid email": { + "value": { + "mentioned_emails": { + "0": [ + "Enter a valid email address." + ] + } + } + }, + "Not an organization member": { + "value": { + "mentioned_emails": [ + "Mentioned users must belong to this organization and have access to this application." + ] + } + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing key gets the generic message; a wrong, revoked or expired key gets `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without transactions write": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists in the application (never created, deleted, or owned by another application). A `transaction_id` that is not a UUID does not reach the API: it returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "No Transaction matches the given query." + } + } + } + } + } + }, + "429": { + "description": "More than 300 write requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/transactions/{transaction_id}/related/": { + "get": { + "summary": "List related transactions", + "description": "List the other transactions in your application that involve the same people or companies as this one. A transaction is related when any of its parties, in any role, has the same `vendor_data` as one of this transaction's parties. The transaction itself is never returned, and parties without `vendor_data` are ignored: when none of this transaction's parties has one, the result is always empty.\n\nResults use the summary shape of `GET /v3/transactions/`, newest first (`txn_date`, then `created_at`). Paginated with `limit` and `offset`; `count` is exact up to 100 and capped at 100 above that, so follow `next` until it is `null`.\n\nA scoped API key needs `transactions: read`; a key limited to some workflows or to approved sessions cannot call this endpoint. Send the same `vendor_data` for the same person or company on every [transaction](https://docs.didit.me/transaction-monitoring/transactions#subject-and-counterparty) to make this list complete.", + "operationId": "list_related_transactions", + "x-didit-sdk": { + "resource": "transactions", + "method": "list_related", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit transaction UUID: the `uuid` returned by `POST /v3/transactions/` and `GET /v3/transactions/`. Your own `txn_id` is not accepted.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "description": "Number of transactions per page. Defaults to 50. A value that is not a positive integer falls back to 50; a value above 1000000 returns `400`." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "description": "Zero-based position of the first transaction to return. Prefer following `next`. A value that is not a whole number falls back to 0; a value above 1000000 returns `400`." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/abcdef12-3456-7890-abcd-ef1234567890/related/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.transactions.list_related(\n \"abcdef12-3456-7890-abcd-ef1234567890\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.transactions.listRelated(\"abcdef12-3456-7890-abcd-ef1234567890\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Transactions.ListRelatedAutoPaging(ctx, \"abcdef12-3456-7890-abcd-ef1234567890\", didit.TransactionsListRelatedParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.transactions().listRelated(\"abcdef12-3456-7890-abcd-ef1234567890\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->listRelated('abcdef12-3456-7890-abcd-ef1234567890') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.list_related(\"abcdef12-3456-7890-abcd-ef1234567890\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Transactions.ListRelatedAsync(\"abcdef12-3456-7890-abcd-ef1234567890\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "A page of related transactions, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Number of related transactions: exact up to 100, capped at 100 above that. Follow `next` instead of computing pages from it." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionSummary" + } + } + } + }, + "examples": { + "One related transaction": { + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "abcdef99-3456-4890-abcd-ef1234567890", + "transaction_number": 4087, + "txn_id": "your-txn-2026-10-02-017", + "transaction_type": "finance", + "action_type": "deposit", + "status": "APPROVED", + "source": "LIVE", + "direction": "INBOUND", + "amount": "250", + "currency": "EUR", + "currency_type": "fiat", + "preferred_currency_amount": "270.08", + "preferred_currency_code": "USD", + "payment_details": "Card top-up", + "txn_date": "2026-10-02T14:10:00Z", + "created_at": "2026-10-02T14:10:03.512244Z", + "score": 12, + "severity": "LOW", + "decision_reason_code": null, + "decision_reason_label": null, + "tags": [], + "subject_name": "Maria Garcia", + "subject_vendor_data": "user-12345", + "subject_type": "user", + "subject_uuid": "e1111111-2222-4333-8444-555555555555", + "counterparty_name": null, + "counterparty_vendor_data": null, + "counterparty_country": null, + "subject_country": "ESP", + "assigned_to_name": null, + "travel_rule_status": null, + "application_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "application_name": "Production app", + "organization_id": "11111111-2222-3333-4444-555555555555", + "organization_name": "Example Org" + } + ] + } + }, + "No party has vendor_data": { + "value": { + "count": 0, + "next": null, + "previous": null, + "results": [] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is above 1000000. The body is a JSON array of messages, not an object.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "examples": { + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + }, + "Offset too large": { + "value": [ + "offset must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing key gets the generic message; a wrong, revoked or expired key gets `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without transactions read": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists in the application (never created, deleted, or owned by another application). A `transaction_id` that is not a UUID does not reach the API: it returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "No Transaction matches the given query." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/businesses/{business_session_id}/pdf/": { + "get": { + "summary": "Download a business session PDF report", + "description": "Render the PDF report of one Business Verification (KYB) session: company registry data, key people and beneficial owners, documents, AML results and the other workflow features, plus the session's tags and review activity. With white-label customization on, the report carries your logo and privacy policy URL. The body is the PDF itself (`application/pdf`), not JSON.\n\nThe session must be `Approved`, `Declined` or `In Review`; any other status returns `403`, so wait for the `status.updated` [webhook](https://docs.didit.me/integration/webhooks) first. The report is rendered on every call and nothing is billed, so store the file instead of fetching it repeatedly. `GET /v3/session/{sessionId}/generate-pdf/` renders the same kind of report by session ID alone.\n\nA PDF report counts as media: a scoped API key needs `businesses: read` and `media: read`, and a key limited to some workflows or to approved sessions cannot call it.", + "operationId": "get_business_session_pdf", + "x-didit-sdk": { + "resource": "businesses", + "method": "generate_session_pdf", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID.", + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID. Must be the application the API key belongs to: a key used on any other application gets `403`.", + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + }, + { + "name": "business_session_id", + "in": "path", + "required": true, + "description": "`session_id` of the Business Verification (KYB) session, as returned when the session was created. A session of another application returns `404`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl --fail -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/businesses/22222222-3333-4444-5555-666666666666/pdf/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n --output business_report.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\ncontent = client.businesses.generate_session_pdf(\n \"22222222-3333-4444-5555-666666666666\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst content = await didit.businesses.generateSessionPdf(\"22222222-3333-4444-5555-666666666666\");\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresp, err := client.Businesses.GenerateSessionPDF(ctx, \"22222222-3333-4444-5555-666666666666\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.businesses().generateSessionPdf(\"22222222-3333-4444-5555-666666666666\")\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->generateSessionPdf('22222222-3333-4444-5555-666666666666');\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ncontent = didit.businesses.generate_session_pdf(\"22222222-3333-4444-5555-666666666666\")\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar content = await client.Businesses.GenerateSessionPdfAsync(\"22222222-3333-4444-5555-666666666666\");\nawait content.SaveToAsync(\"download.pdf\");" + } + ], + "responses": { + "200": { + "description": "The rendered PDF, returned directly as binary `application/pdf`.", + "headers": { + "Content-Disposition": { + "description": "Always `attachment; filename=business_{business_session_id}.pdf`.", + "schema": { + "type": "string", + "example": "attachment; filename=business_22222222-3333-4444-5555-666666666666.pdf" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed or the session is not finished. Read `detail`: a session that is not `Approved`, `Declined` or `In Review`, a missing API key (a wrong, revoked or expired key gets `401`), an `application_id` that is not the key's application, a scoped key without `media: read` or `businesses: read`, a key limited to workflows or approved sessions, or a request from outside the key's IP list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Session not finished": { + "value": { + "detail": "You can only generate a PDF for sessions in review, declined or approved." + } + }, + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without media read": { + "value": { + "detail": "This API key has no read access to media." + } + }, + "Scoped key without businesses read": { + "value": { + "detail": "This API key has no read access to companies." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No business session with this ID exists in the application (never created, deleted, or owned by another application). A value that is not a UUID returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "Business session not found." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/transactions/{transaction_uuid}/pdf/": { + "get": { + "summary": "Download a transaction PDF report", + "description": "Render one monitored transaction as a PDF report: overview and verdict, parties and payment methods, custom properties, alerts and rule runs, screening and Travel Rule results, the network summary, the cost breakdown, up to 50 related transactions and the 20 most recent activity entries. The body is the PDF itself (`application/pdf`), not JSON.\n\nThe report is rendered from the transaction's current data on every call, so request it again after a status change. Nothing is stored and nothing is billed. Rendering can take a few seconds, so use a generous client timeout.\n\nThis endpoint lives on an application-scoped path: put your organization ID and the ID of the application your API key belongs to in the URL ([find your organization ID](https://docs.didit.me/getting-started/api-authentication#find-your-organization-id)). A PDF report counts as media: a scoped API key needs `media: read` and `transactions: read`, and a key limited to some workflows or to approved sessions cannot call it.", + "operationId": "get_transaction_pdf", + "x-didit-sdk": { + "resource": "transactions", + "method": "generate_pdf", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID.", + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID. Must be the application the API key belongs to: a key used on any other application gets `403`.", + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + }, + { + "name": "transaction_uuid", + "in": "path", + "required": true, + "description": "Didit transaction UUID: the `uuid` returned by `POST /v3/transactions/` and `GET /v3/transactions/`. Your own `txn_id` is not accepted.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl --fail -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/transactions/abcdef12-3456-7890-abcd-ef1234567890/pdf/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n --output transaction_report.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\ncontent = client.transactions.generate_pdf(\n \"abcdef12-3456-7890-abcd-ef1234567890\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst content = await didit.transactions.generatePdf(\"abcdef12-3456-7890-abcd-ef1234567890\");\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresp, err := client.Transactions.GeneratePDF(ctx, \"abcdef12-3456-7890-abcd-ef1234567890\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.transactions().generatePdf(\"abcdef12-3456-7890-abcd-ef1234567890\")\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->generatePdf('abcdef12-3456-7890-abcd-ef1234567890');\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ncontent = didit.transactions.generate_pdf(\"abcdef12-3456-7890-abcd-ef1234567890\")\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar content = await client.Transactions.GeneratePdfAsync(\"abcdef12-3456-7890-abcd-ef1234567890\");\nawait content.SaveToAsync(\"download.pdf\");" + } + ], + "responses": { + "200": { + "description": "The rendered PDF, returned directly as binary `application/pdf`.", + "headers": { + "Content-Disposition": { + "description": "Always `attachment; filename=transaction_{transaction_uuid}.pdf`.", + "schema": { + "type": "string", + "example": "attachment; filename=transaction_abcdef12-3456-7890-abcd-ef1234567890.pdf" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. Read `detail`: a missing API key (a wrong, revoked or expired key gets `401`), an `application_id` that is not the key's application, a scoped key without `media: read` or `transactions: read`, a key limited to workflows or approved sessions, or a request from outside the key's IP list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without media read": { + "value": { + "detail": "This API key has no read access to media." + } + }, + "Scoped key without transactions read": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists in the application (never created, deleted, or owned by another application). A `transaction_uuid` that is not a UUID does not reach the API: it returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "No Transaction matches the given query." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/analytics/": { + "get": { + "summary": "Get application analytics", + "description": "Read the numbers behind the Business Console [analytics dashboard](https://docs.didit.me/console/analytics) for a date range. Without `scope` and `widget_keys` the response is the user verification (KYC) summary (`ApplicationAnalyticsSummary`): daily session counts, status totals, conversion rate, the same figures for the preceding period of equal length, locations, demographics, devices, workflow funnels, active verification time, resubmissions, warnings and time series. With `scope` or `widget_keys` it is the widgets of one dashboard tab (`ApplicationAnalyticsWidgetResponse`): `kyc`, `kyb`, `transactions` or `home`.\n\nSession counts are by session creation date (UTC) and reflect each session's current status. They come from daily rollups refreshed in the background, so the newest activity can take a short while to appear. Invalid workflow IDs and versions are ignored rather than rejected. Nothing is billed.\n\nOnly a full-access API key can call this endpoint: every scoped key gets `403`, whatever access it holds, and so does a key of another application. See [API keys](https://docs.didit.me/console/api-keys).", + "operationId": "get_application_analytics", + "x-didit-sdk": { + "resource": "analytics", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Analytics" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID.", + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID. Must be the application the API key belongs to: a key used on any other application gets `403`.", + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + }, + { + "name": "start_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date", + "example": "2026-10-01" + }, + "description": "First day of the range, `YYYY-MM-DD`, inclusive (UTC). Send it together with `end_date`: when either is missing, both are ignored and the range runs from 30 days before today through today." + }, + { + "name": "end_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date", + "example": "2026-10-15" + }, + "description": "Last day of the range, `YYYY-MM-DD`, inclusive (UTC). A future date counts as today. The range may not exceed 730 days." + }, + { + "name": "scope", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "home", + "kyc", + "kyb", + "transactions" + ] + }, + "description": "Dashboard tab to return widgets for (case-insensitive). Omit it, together with `widget_keys`, for the KYC summary. Sending only `widget_keys` implies `kyc`. `home` accepts any KYC, KYB or transaction widget and defaults to the application's saved home layout." + }, + { + "name": "widget_keys", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "tx_count_total,tx_count_daily" + }, + "description": "Comma-separated widget keys, answered in this order. Keys the scope does not accept are dropped; when none is left you get the scope's defaults. KYC: `kyc_sessions_volume`, `kyc_warning_insights`, `kyc_id_locations`, `kyc_ip_locations`, `kyc_workflow_funnel`, `kyc_average_verification_time`, `kyc_conversion_rate`, `kyc_conversion_by_country`, `kyc_resubmission_insights`, `kyc_original_status_distribution`, `kyc_database_validation_coverage`, `kyc_database_validation_latency`, `kyc_demographics`, `kyc_device_details`. KYB: `kyb_sessions_volume`, `kyb_workflow_funnel`, `kyb_average_verification_time`, `kyb_ip_locations`, `kyb_business_country_distribution`, `kyb_warning_insights`, `kyb_conversion_rate`, `kyb_conversion_by_country`, `kyb_industry_breakdown`, `kyb_resubmission_insights`, `kyb_original_status_distribution`, `kyb_registry_coverage`, `kyb_registry_data_completeness`, `kyb_device_details`. Transactions: `tx_count_total`, `tx_count_daily`, `tx_volume_total_preferred_currency`, `tx_volume_daily_preferred_currency`, `tx_average_amount`, `tx_average_amount_daily`, `tx_transaction_category_breakdown`, `tx_action_type_breakdown`, `tx_status_volume_trend`, `tx_user_ip_or_country_distribution`, `tx_tag_breakdown`, `tx_top_rules_triggered`, `tx_team_member_performance`, `tx_score_histogram`." + }, + { + "name": "workflow_ids", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "33333333-4444-5555-6666-777777777777" + }, + "description": "Comma-separated workflow IDs. Limits the KYC summary and the KYC and KYB widgets to those workflows. Values that are not UUIDs are ignored; when none is valid the filter is dropped. The summary also accepts `workflow_id`." + }, + { + "name": "versions", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "3,4" + }, + "description": "Comma-separated workflow version numbers. Applies only together with `workflow_ids`; values that are not whole numbers are ignored. `version` is accepted as an alias." + }, + { + "name": "branch_path_ids", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "default" + }, + "description": "Comma-separated branch path IDs. Applies only together with `workflow_ids`, and only to KYC figures. Figures are currently recorded only for `default` (all paths combined), so any other value returns empty figures. `branch_path_id` is accepted as an alias." + }, + { + "name": "tag", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Transaction widgets only: comma-separated tag UUIDs or tag names." + }, + { + "name": "transaction_type", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "finance" + }, + "description": "Transaction widgets only: comma-separated transaction categories." + }, + { + "name": "action_type", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "withdrawal" + }, + "description": "Transaction widgets only: comma-separated action types (case-insensitive)." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/analytics/?start_date=2026-10-01&end_date=2026-10-15' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.analytics.retrieve()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.analytics.retrieve();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Analytics.Retrieve(ctx, didit.AnalyticsRetrieveParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.analytics().retrieve();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "analytics->retrieve();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.analytics.retrieve\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Analytics.RetrieveAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The KYC summary, or the requested widgets when `scope` or `widget_keys` is sent.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/ApplicationAnalyticsSummary" + }, + { + "$ref": "#/components/schemas/ApplicationAnalyticsWidgetResponse" + } + ] + }, + "examples": { + "KYC summary": { + "value": { + "grouping_mode": "day", + "data_points": [ + { + "rounded_date": "2026-10-14", + "total": 12, + "approved": 8, + "declined": 1, + "canceled": 1, + "pending": 2, + "in_review": 1, + "expired": 1, + "abandoned": 0, + "in_progress": 1, + "not_started": 0, + "manually_approved": 1, + "manually_declined": 0 + }, + { + "rounded_date": "2026-10-15", + "total": 10, + "approved": 6, + "declined": 2, + "canceled": 0, + "pending": 2, + "in_review": 2, + "expired": 0, + "abandoned": 0, + "in_progress": 0, + "not_started": 0, + "manually_approved": 0, + "manually_declined": 1 + } + ], + "request_breakdown": { + "total_approved": 14, + "total_in_review": 3, + "total_declined": 3, + "total_canceled": 1, + "total_pending": 4, + "total_expired": 1, + "total_abandoned": 0, + "total_in_progress": 1, + "total_not_started": 0, + "total_manually_approved": 1, + "total_manually_declined": 1 + }, + "conversion_rate": 70.0, + "location_stats": { + "countries": [ + { + "session_count": 15, + "country": "Spain", + "country_code": "ES" + }, + { + "session_count": 7, + "country": "Portugal", + "country_code": "PT" + } + ], + "ip_countries": [ + { + "session_count": 15, + "country": "Spain", + "country_code": "ES" + }, + { + "session_count": 7, + "country": "Portugal", + "country_code": "PT" + } + ], + "id_countries": [ + { + "country": "Spain", + "country_code": "ES", + "session_count": 14 + }, + { + "country": "Portugal", + "country_code": "PT", + "session_count": 6 + } + ], + "conversion_by_country": [ + { + "country": "Spain", + "country_code": "ES", + "finished": 13, + "approved": 10 + }, + { + "country": "Portugal", + "country_code": "PT", + "finished": 6, + "approved": 4 + } + ], + "cities": [ + { + "session_count": 9, + "location": "Madrid, Spain", + "latitude": 40.4168, + "longitude": -3.7038 + } + ] + }, + "demographic_stats": { + "age_distribution": { + "under_18": 0, + "18_24": 5, + "25_34": 8, + "35_44": 4, + "45_64": 2, + "65_plus": 1 + }, + "gender_distribution": { + "M": 11, + "F": 9 + } + }, + "device_details": { + "device_models": [ + { + "device": "Apple", + "count": 12 + }, + { + "device": "Android", + "count": 10 + } + ], + "browsers": [ + { + "browser": "Safari", + "count": 12 + }, + { + "browser": "Chrome", + "count": 10 + } + ], + "operating_systems": [ + { + "os": "iOS", + "count": 12 + }, + { + "os": "Android", + "count": 10 + } + ] + }, + "workflow_funnel_stats": { + "33333333-4444-5555-6666-777777777777": { + "total": 22, + "steps": { + "OCR": { + "reached": 21 + }, + "LIVENESS": { + "reached": 19 + }, + "FACE_MATCH": { + "reached": 19 + }, + "AML": { + "reached": 18 + } + } + } + }, + "verification_time_stats": { + "overall_avg_seconds": 64.0, + "overall_count": 18, + "per_feature": { + "OCR": { + "avg_seconds": 41.2, + "total_seconds": 741.6, + "count": 18 + }, + "LIVENESS": { + "avg_seconds": 22.8, + "total_seconds": 410.4, + "count": 18 + } + }, + "per_sub_step": { + "OCR": { + "front_id_photo": { + "avg_seconds": 18.3, + "total_seconds": 329.4, + "count": 18 + } + } + } + }, + "resubmission_stats": { + "first_pass": 17, + "second": 2, + "third_plus": 1, + "abandoned": 0 + }, + "warning_stats": { + "ID_VERIFICATION": { + "total": 21, + "risks": { + "DOCUMENT_EXPIRED": { + "error": 1, + "warning": 0, + "information": 0 + } + } + }, + "LIVENESS": { + "total": 19, + "risks": { + "LOW_LIVENESS_SCORE": { + "error": 0, + "warning": 2, + "information": 0 + } + } + } + }, + "original_status_stats": { + "approved": 13, + "in_review": 5, + "declined": 2 + }, + "demographic_time_series": [ + { + "date": "2026-10-14", + "age_distribution": { + "under_18": 0, + "18_24": 3, + "25_34": 4, + "35_44": 2, + "45_64": 1, + "65_plus": 0 + }, + "gender_distribution": { + "M": 6, + "F": 4 + } + }, + { + "date": "2026-10-15", + "age_distribution": { + "under_18": 0, + "18_24": 2, + "25_34": 4, + "35_44": 2, + "45_64": 1, + "65_plus": 1 + }, + "gender_distribution": { + "M": 5, + "F": 5 + } + } + ], + "device_time_series": [ + { + "date": "2026-10-14", + "devices": { + "Apple": 7, + "Android": 5 + }, + "browsers": { + "Safari": 7, + "Chrome": 5 + }, + "operating_systems": { + "iOS": 7, + "Android": 5 + } + }, + { + "date": "2026-10-15", + "devices": { + "Apple": 5, + "Android": 5 + }, + "browsers": { + "Safari": 5, + "Chrome": 5 + }, + "operating_systems": { + "iOS": 5, + "Android": 5 + } + } + ], + "verification_time_time_series": [ + { + "date": "2026-10-14", + "overall_avg": 66.1, + "per_feature": { + "OCR": 42.0, + "LIVENESS": 24.1 + } + }, + { + "date": "2026-10-15", + "overall_avg": 61.5, + "per_feature": { + "OCR": 40.2, + "LIVENESS": 21.3 + } + } + ], + "resubmission_time_series": [ + { + "date": "2026-10-14", + "first_pass": 9, + "second": 1, + "third_plus": 0, + "abandoned": 0 + }, + { + "date": "2026-10-15", + "first_pass": 8, + "second": 1, + "third_plus": 1, + "abandoned": 0 + } + ], + "original_status_time_series": [ + { + "date": "2026-10-14", + "approved": 7, + "in_review": 3, + "declined": 1 + }, + { + "date": "2026-10-15", + "approved": 6, + "in_review": 2, + "declined": 1 + } + ], + "previous_data_points": [ + { + "rounded_date": "2026-10-12", + "total": 9, + "approved": 6, + "declined": 1, + "canceled": 1, + "pending": 1 + }, + { + "rounded_date": "2026-10-13", + "total": 11, + "approved": 7, + "declined": 2, + "canceled": 0, + "pending": 2 + } + ], + "previous_request_breakdown": { + "total_approved": 13, + "total_in_review": 2, + "total_declined": 3, + "total_canceled": 1, + "total_pending": 3 + } + } + }, + "Transaction widgets": { + "value": { + "scope": "transactions", + "available_widget_keys": [ + "tx_count_total", + "tx_count_daily", + "tx_volume_total_preferred_currency", + "tx_volume_daily_preferred_currency", + "tx_average_amount", + "tx_average_amount_daily", + "tx_transaction_category_breakdown", + "tx_action_type_breakdown", + "tx_status_volume_trend", + "tx_user_ip_or_country_distribution", + "tx_tag_breakdown", + "tx_top_rules_triggered", + "tx_team_member_performance", + "tx_score_histogram" + ], + "requested_widget_keys": [ + "tx_count_total", + "tx_count_daily" + ], + "widgets": { + "tx_count_total": { + "kind": "metric", + "title": "Transaction count", + "value": 42 + }, + "tx_count_daily": { + "kind": "time_series", + "title": "Transaction count", + "items": [ + { + "date": "2026-10-14", + "value": 20 + }, + { + "date": "2026-10-15", + "value": 22 + } + ], + "previous_items": [ + { + "date": "2026-10-12", + "value": 18 + }, + { + "date": "2026-10-13", + "value": 15 + } + ] + } + } + } + } + } + } + } + }, + "400": { + "description": "Invalid date range or scope. The body is an object keyed by the offending parameter, with a string message.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "examples": { + "Bad date format": { + "value": { + "start_date": "Invalid date format. Use YYYY-MM-DD (e.g., 2024-01-15)." + } + }, + "Start after end": { + "value": { + "start_date": "Start date must be before or equal to end date." + } + }, + "Range too long": { + "value": { + "date_range": "Date range cannot exceed 730 days (2 years)." + } + }, + "Unknown scope": { + "value": { + "scope": "Unsupported analytics scope." + } + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. Read `detail`: a missing API key (a wrong, revoked or expired key gets `401`), a key of another application, any scoped key, or a request from outside the key's IP list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key": { + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/analytics/today/": { + "get": { + "summary": "Get today's verifications by hour", + "operationId": "get_application_analytics_today", + "x-didit-sdk": { + "resource": "analytics", + "method": "retrieve_today", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Analytics" + ], + "description": "Today's user verification (KYC) sessions, from local midnight in `tz`, counted per local hour they were created in, next to the same hours of the same weekday one week before, and split by the outcome each one holds now: approved, in review or declined. This is the volume card of the Business Console's Home.\n\n`total` is today so far. `previous_total_so_far` counts the comparison day only up to the same local time, so a change between them compares the same part of both days. The hours still to come today are `0`. A day on which the clocks change has 23 or 25 hours: the missing hour stays `0` and the repeated hour holds both.", + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID.", + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID. Must be the application the API key belongs to: a key used on any other application gets `403`.", + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + }, + { + "name": "tz", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "UTC", + "example": "Europe/Madrid" + }, + "description": "IANA time zone whose midnight starts today, for example `Europe/Madrid` or `America/Mexico_City`. Omit it for UTC." + } + ], + "responses": { + "200": { + "description": "Today's verifications by local hour, the comparison day's, and today's outcomes.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApplicationAnalyticsToday" + }, + "examples": { + "Madrid, 14:00": { + "value": { + "time_zone": "Europe/Madrid", + "date": "2026-10-30", + "current_hour": 14, + "previous_date": "2026-10-23", + "hours": [ + 3, + 1, + 0, + 0, + 1, + 2, + 4, + 9, + 14, + 18, + 21, + 19, + 22, + 24, + 27, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "previous_hours": [ + 2, + 1, + 1, + 0, + 1, + 3, + 5, + 8, + 12, + 16, + 19, + 18, + 20, + 22, + 24, + 26, + 23, + 19, + 15, + 12, + 9, + 7, + 5, + 3 + ], + "total": 165, + "previous_total_so_far": 151, + "outcomes": { + "approved": 139, + "in_review": 6, + "declined": 18 + } + } + } + } + } + } + }, + "400": { + "description": "Unknown time zone. The body is keyed by the parameter, with a list of messages.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "Unknown time zone": { + "value": { + "tz": [ + "Unknown time zone: Europe/Atlantis." + ] + } + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. Read `detail`: a missing or invalid API key (never `401`), a key of another application, any scoped key, or a request from outside the key's IP list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key": { + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/analytics/today/?tz=Europe%2FMadrid' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/webhooks/": { + "get": { + "summary": "List session webhook deliveries", + "description": "List every webhook delivery Didit recorded for one verification session, user (KYC) or business (KYB), newest first, as a plain JSON array (not paginated). Each entry is one attempt to one destination: the first send, each automatic [retry](https://docs.didit.me/integration/webhooks#retry-policy) and each resend are separate entries with their own `uuid`, and retries and resends keep the payload's `event_id`. `request_header` and `request_body` are JSON text; parse them to read the signatures and the payload. Delivery records are available for about 30 days, so an empty array means no webhook has been sent for the session yet or every delivery is older than that. A scoped API key needs `sessions: read` and `decisions: read`.", + "operationId": "get_v3_session_webhooks", + "x-didit-sdk": { + "resource": "sessions.webhooks", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session, user (KYC) or business (KYB): the `session_id` returned by `POST /v3/session/`. The server resolves it against both session types.\n\n**Must be a valid UUID.** The route only matches well-formed UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/22222222-3333-4444-5555-666666666666/webhooks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.webhooks.list(\n \"22222222-3333-4444-5555-666666666666\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.webhooks.list(\"22222222-3333-4444-5555-666666666666\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Webhooks.List(ctx, \"22222222-3333-4444-5555-666666666666\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().webhooks().list(\"22222222-3333-4444-5555-666666666666\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->webhooks->list('22222222-3333-4444-5555-666666666666');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.webhooks.list(\"22222222-3333-4444-5555-666666666666\").each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Webhooks.ListAsync(\"22222222-3333-4444-5555-666666666666\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Every delivery recorded for the session, newest first.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionWebhookDelivery" + } + }, + "examples": { + "Failed delivery and its successful retry": { + "value": [ + { + "uuid": "66666666-7777-8888-9999-000000000002", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 200, + "status": "In Progress", + "webhook_type": "status.updated", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"9188851d160b5b3aacfc946e4dde1f061b7e568662518942c927ddaf507f533d\", \"X-Signature-V2\": \"381236af514d3fb538579365ac5a24f2464ce1b8224c6e770c60d8201c43ecc5\", \"X-Signature-Simple\": \"4740131dc38f29e78a8508371979be74967d5307464494bd3148a03b4d6c8716\", \"X-Timestamp\": \"1792056139\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792056139,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}", + "created_at": "2026-10-15T09:22:19.104233Z" + }, + { + "uuid": "66666666-7777-8888-9999-000000000001", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 503, + "status": "In Progress", + "webhook_type": "status.updated", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"a5e02a03404e1337a0198e6be15d34abbd053c658a0e51e5d0e78554213ef29b\", \"X-Signature-V2\": \"927e7cb770c94ce745939bff73f04aa200015f3bc64795ba46c2aaf78a6cedc5\", \"X-Signature-Simple\": \"e37b0d4c716c72bb5ae7127b9cc455d28ab4686e9e22312b4ec2327409a73302\", \"X-Timestamp\": \"1792056078\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792056078,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}", + "created_at": "2026-10-15T09:21:18.412345Z" + } + ] + }, + "No webhook sent yet": { + "value": [] + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. No `x-api-key`, or an invalid or revoked key, gets `Authentication credentials were not provided or are invalid.` A key of another application gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` An unknown `sessionId` returns `404` before the credential is checked.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Key of another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions read": { + "value": { + "detail": "This API key has no read access to sessions." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist or has been deleted (`Not found.`, returned before the credential is checked), or it is outside the workflow or status limits of a scoped key (`Session not found.`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "value": { + "detail": "Not found." + } + }, + "Outside the key's limits": { + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/webhook/{webhook_uuid}": { + "get": { + "summary": "Get session webhook delivery", + "description": "Retrieve one webhook delivery of a verification session: the URL it went to, the HTTP status your endpoint returned, and the exact headers and JSON body Didit sent. `request_header` and `request_body` are JSON text; to check your [signature verification](https://docs.didit.me/integration/webhooks#signature-verification), recompute `X-Signature-V2` from the parsed body with the secret of the destination it was sent to (or the application-level secret for a delivery to the legacy webhook URL). The path has no trailing slash: with one it returns `404`. A scoped API key needs `sessions: read` and `decisions: read`.", + "operationId": "get_v3_session_webhook", + "x-didit-sdk": { + "resource": "sessions.webhooks", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session, user (KYC) or business (KYB): the `session_id` returned by `POST /v3/session/`. The server resolves it against both session types.\n\n**Must be a valid UUID.** The route only matches well-formed UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + }, + { + "name": "webhook_uuid", + "in": "path", + "required": true, + "description": "UUID of the delivery: the `uuid` of an entry returned by `GET /v3/session/{sessionId}/webhooks/`. It must belong to the session in the path.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "66666666-7777-8888-9999-000000000001" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/22222222-3333-4444-5555-666666666666/webhook/66666666-7777-8888-9999-000000000001' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.webhooks.retrieve(\n \"22222222-3333-4444-5555-666666666666\",\n \"66666666-7777-8888-9999-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.webhooks.retrieve(\n \"22222222-3333-4444-5555-666666666666\",\n \"66666666-7777-8888-9999-000000000001\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Webhooks.Retrieve(ctx, \"22222222-3333-4444-5555-666666666666\", \"66666666-7777-8888-9999-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().webhooks().retrieve(\"22222222-3333-4444-5555-666666666666\", \"66666666-7777-8888-9999-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->webhooks->retrieve('22222222-3333-4444-5555-666666666666', '66666666-7777-8888-9999-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.webhooks.retrieve(\n \"22222222-3333-4444-5555-666666666666\",\n \"66666666-7777-8888-9999-000000000001\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Webhooks.RetrieveAsync(\"22222222-3333-4444-5555-666666666666\", \"66666666-7777-8888-9999-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The delivery, with its request headers and body.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionWebhookDelivery" + }, + "examples": { + "Connection failure": { + "value": { + "uuid": "66666666-7777-8888-9999-000000000001", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 503, + "status": "In Progress", + "webhook_type": "status.updated", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"a5e02a03404e1337a0198e6be15d34abbd053c658a0e51e5d0e78554213ef29b\", \"X-Signature-V2\": \"927e7cb770c94ce745939bff73f04aa200015f3bc64795ba46c2aaf78a6cedc5\", \"X-Signature-Simple\": \"e37b0d4c716c72bb5ae7127b9cc455d28ab4686e9e22312b4ec2327409a73302\", \"X-Timestamp\": \"1792056078\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792056078,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}", + "created_at": "2026-10-15T09:21:18.412345Z" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. No `x-api-key`, or an invalid or revoked key, gets `Authentication credentials were not provided or are invalid.` A key of another application gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` An unknown `sessionId` returns `404` before the credential is checked.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Key of another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions read": { + "value": { + "detail": "This API key has no read access to sessions." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist or has been deleted (`Not found.`, returned before the credential is checked), or it is outside the workflow or status limits of a scoped key (`Session not found.`). A delivery that does not exist, belongs to another session or is no longer available returns `No WebhookLog matches the given query.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "value": { + "detail": "Not found." + } + }, + "Outside the key's limits": { + "value": { + "detail": "Session not found." + } + }, + "Delivery not in this session": { + "value": { + "detail": "No WebhookLog matches the given query." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/webhook/{webhook_uuid}/resend": { + "post": { + "summary": "Resend session webhook delivery", + "description": "Send a recorded session webhook again to the URL it first went to, with the same payload and `event_id` and a fresh `timestamp`, `X-Timestamp` header and signatures. Didit signs it with the current secret of the destination the delivery belongs to (or the application-level secret for a delivery to the legacy webhook URL), waits up to 5 seconds for your endpoint, records the attempt as a new delivery and returns it with `200` whatever your endpoint answered: read `response_status`. A resend is not retried automatically, and every call sends again, so make your endpoint idempotent on `event_id`. The path has no trailing slash and the request has no body. A scoped API key needs `webhooks: write` and `decisions: read`.", + "operationId": "post_v3_session_webhook_resend", + "x-didit-sdk": { + "resource": "sessions.webhooks", + "method": "resend", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session, user (KYC) or business (KYB): the `session_id` returned by `POST /v3/session/`. The server resolves it against both session types.\n\n**Must be a valid UUID.** The route only matches well-formed UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + }, + { + "name": "webhook_uuid", + "in": "path", + "required": true, + "description": "UUID of the delivery to resend: the `uuid` of an entry returned by `GET /v3/session/{sessionId}/webhooks/`. It must belong to the session in the path.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "66666666-7777-8888-9999-000000000001" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/session/22222222-3333-4444-5555-666666666666/webhook/66666666-7777-8888-9999-000000000001/resend' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.webhooks.resend(\n \"22222222-3333-4444-5555-666666666666\",\n \"66666666-7777-8888-9999-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.webhooks.resend(\n \"22222222-3333-4444-5555-666666666666\",\n \"66666666-7777-8888-9999-000000000001\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Webhooks.Resend(ctx, \"22222222-3333-4444-5555-666666666666\", \"66666666-7777-8888-9999-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().webhooks().resend(\"22222222-3333-4444-5555-666666666666\", \"66666666-7777-8888-9999-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->webhooks->resend('22222222-3333-4444-5555-666666666666', '66666666-7777-8888-9999-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.webhooks.resend(\n \"22222222-3333-4444-5555-666666666666\",\n \"66666666-7777-8888-9999-000000000001\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Webhooks.ResendAsync(\"22222222-3333-4444-5555-666666666666\", \"66666666-7777-8888-9999-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The new delivery, already attempted. `response_status` is what your endpoint answered this time. The original delivery is unchanged.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionWebhookDelivery" + }, + "examples": { + "Accepted on resend": { + "value": { + "uuid": "66666666-7777-8888-9999-000000000004", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 200, + "status": "In Progress", + "webhook_type": "status.updated", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"b8eb1e376517ee0cad8ba98ff7f8fd35fc72eca231000963a96a335f82328835\", \"X-Signature-V2\": \"9efd2ce9ed1303e1d05273ef9608a59363ed9789a05253db4748b3e8e50cb109\", \"X-Signature-Simple\": \"bf3a9dc04b2c20ab539203b0cd5cca2a10f630bf62d8feb6d68d82af80f64774\", \"X-Timestamp\": \"1792159542\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792159542,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}", + "created_at": "2026-10-16T14:05:42.330981Z" + } + } + } + } + } + }, + "400": { + "description": "The delivery could not be resent. Branch on `code`: `webhook_url_missing` means the delivery has no recorded URL and nothing is sent; `webhook_resend_error` means the stored delivery could not be signed again or the attempt could not be completed. After `webhook_resend_error`, `detail` starts with `Error processing webhook resend:` followed by a diagnostic text that can change.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string", + "description": "Stable machine-readable error code. Branch on this, not on `detail`." + } + } + }, + "examples": { + "No recorded URL": { + "value": { + "detail": "The webhook delivery does not have a request URL configured", + "code": "webhook_url_missing" + } + }, + "Resend failed": { + "value": { + "detail": "Error processing webhook resend: Invalid webhook data format", + "code": "webhook_resend_error" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. No `x-api-key`, or an invalid or revoked key, gets `Authentication credentials were not provided or are invalid.` A key of another application gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` An unknown `sessionId` returns `404` before the credential is checked.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Key of another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks write": { + "value": { + "detail": "This API key has no write access to webhooks." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist or has been deleted (`Not found.`, returned before the credential is checked), or it is outside the workflow or status limits of a scoped key (`Session not found.`). A delivery that does not exist, belongs to another session or is no longer available returns `Webhook log not found`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "value": { + "detail": "Not found." + } + }, + "Outside the key's limits": { + "value": { + "detail": "Session not found." + } + }, + "Delivery not in this session": { + "value": { + "detail": "Webhook log not found" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 300 write requests (POST, PATCH, PUT, DELETE) in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (300).", + "schema": { + "type": "integer", + "example": 300 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-write" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/events/": { + "get": { + "summary": "List session events", + "description": "Read the event timeline of one verification session, newest first by `sent_at`: the screens the user saw, buttons and uploads, failed attempts, status and feature-step changes, and the workflow routing trace. Works for User Verification (KYC) and Business Verification (KYB) session IDs. Events are stored asynchronously, so a new event can take a few seconds to appear. Results are paginated with `limit` and `offset`; follow `next` until it is `null`. A scoped API key needs `sessions: read` and `decisions: read`, and without `media: read` it receives `file_url` as `null`. The routing events are described in [Retrieve session](https://docs.didit.me/sessions-api/retrieve-session).", + "operationId": "get_v3_session_events", + "x-didit-sdk": { + "resource": "sessions", + "method": "list_events", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of events per page. Defaults to 50. A value below 1 or a non-numeric value falls back to 50; a value above 1,000,000 returns `400`.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000, + "example": 50 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of results to skip before the first returned one. Defaults to 0; a negative or non-numeric value falls back to 0, a value above 1,000,000 returns `400`. Prefer following the `next` and `previous` URLs.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000, + "example": 0 + } + }, + { + "name": "ordering", + "in": "query", + "required": false, + "description": "Optional sort override. The default order is `-sent_at`. Pass a result field name (for example `sent_at`, `created_at`, `event_type`, `feature_step` or `step`) to sort ascending, prefix it with `-` to sort descending, and separate several fields with commas. Unknown fields are ignored.", + "schema": { + "type": "string", + "example": "sent_at" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/events/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.sessions.list_events(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.sessions.listEvents(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Sessions.ListEventsAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsListEventsParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.sessions().listEvents(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->listEvents('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.list_events(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Sessions.ListEventsAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "One page of the session's events, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Total number of events of the session." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionEvent" + } + } + } + }, + "examples": { + "Completed session": { + "value": { + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "uuid": "aaaaaaaa-1111-2222-3333-444444444444", + "event_type": "SESSION_STATUS_CHANGED", + "created_at": "2026-09-14T10:02:13.904512Z", + "component": null, + "feature_step": null, + "step": null, + "details": { + "old_status": "In Progress", + "current_status": "Approved" + }, + "ip_address": null, + "user_agent": null, + "device_info": null, + "file_url": null, + "sent_at": 1789380131482 + }, + { + "uuid": "bbbbbbbb-1111-2222-3333-444444444444", + "event_type": "FEATURE_STEP_CHANGED", + "created_at": "2026-09-14T10:01:49.210377Z", + "component": null, + "feature_step": null, + "step": null, + "details": { + "old_feature_step": "OCR", + "current_feature_step": "LIVENESS" + }, + "ip_address": null, + "user_agent": null, + "device_info": null, + "file_url": null, + "sent_at": 1789380107103 + }, + { + "uuid": "cccccccc-1111-2222-3333-444444444444", + "event_type": "STEP_RENDERED", + "created_at": "2026-09-14T10:01:00.581930Z", + "component": "DocumentCapture", + "feature_step": "OCR", + "step": "DOCUMENT_CAPTURE_FRONT", + "details": {}, + "ip_address": "203.0.113.24", + "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", + "device_info": { + "device_model": "iPhone" + }, + "file_url": null, + "sent_at": 1789380058776 + } + ] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is above 1,000,000. The body is a JSON array with one message.", + "content": { + "application/json": { + "examples": { + "Limit too large": { + "summary": "limit=2000000", + "value": [ + "limit must not exceed 1000000." + ] + }, + "Offset too large": { + "summary": "offset=2000000", + "value": [ + "offset must not exceed 1000000." + ] + } + }, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions read": { + "summary": "Scoped key lacks sessions: read", + "value": { + "detail": "This API key has no read access to sessions." + } + }, + "Scoped key without decisions read": { + "summary": "Scoped key lacks decisions: read", + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted, or is outside a scoped key's workflow or approved-only limits. The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's workflow or approved-only limits", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 600 GET requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/aml-screening-history/": { + "get": { + "summary": "List AML screening history", + "description": "List every completed ongoing-monitoring re-screen of a session's AML check, newest first by `completed_at`, including the runs where nothing changed: alerts, activity entries and webhooks fire only when a result moves, so this history is the evidence that a clean subject was still being screened. Works for KYC and KYB session IDs; pass `node_id` when the session ran more than one AML step. Only ongoing-monitoring re-screens are listed (the first screening is part of the session decision) and runs from before this history existed are not reconstructed, so a session without ongoing monitoring returns an empty list. `count` is exact up to 100 and capped at 100 above that, so follow `next` until it is `null`. Pass `month` to read one calendar month at a time. A scoped API key needs `decisions: read`; see [Continuous AML monitoring](https://docs.didit.me/core-technology/aml-screening/continuous-monitoring-aml-screening).", + "operationId": "get_v3_session_aml_screening_history", + "x-didit-sdk": { + "resource": "sessions", + "method": "list_aml_screening_history", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node ID of the AML check. Needed only when the session ran more than one AML step; without it the most recent AML check is used. An unknown node returns `404`.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + }, + { + "name": "month", + "in": "query", + "required": false, + "description": "A UTC calendar month as `YYYY-MM`. Only screenings completed in that month are returned; without it every month is returned. A malformed month returns `400`.", + "schema": { + "type": "string", + "pattern": "^\\d{4}-(0[1-9]|1[0-2])$", + "example": "2026-09" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of screening runs per page. Defaults to 50. A value below 1 or a non-numeric value falls back to 50; a value above 1,000,000 returns `400`.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000, + "example": 50 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of results to skip before the first returned one. Defaults to 0; a negative or non-numeric value falls back to 0, a value above 1,000,000 returns `400`. Prefer following the `next` and `previous` URLs.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000, + "example": 0 + } + }, + { + "name": "ordering", + "in": "query", + "required": false, + "description": "Optional sort override. The default order is `-completed_at`. Pass a result field name (for example `completed_at`, `started_at`, `outcome` or `score`) to sort ascending, prefix it with `-` to sort descending, and separate several fields with commas. Unknown fields are ignored.", + "schema": { + "type": "string", + "example": "completed_at" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/aml-screening-history/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.sessions.list_aml_screening_history(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.sessions.listAmlScreeningHistory(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Sessions.ListAMLScreeningHistoryAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsListAMLScreeningHistoryParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.sessions().listAmlScreeningHistory(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->listAmlScreeningHistory('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.list_aml_screening_history(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Sessions.ListAmlScreeningHistoryAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "One page of screening runs, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "maximum": 100, + "description": "Number of runs, exact up to 100 and capped at 100 above that." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AMLScreeningRun" + } + } + } + }, + "examples": { + "Monitored subject": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "uuid": "dddddddd-1111-2222-3333-444444444444", + "run_id": "aaaaaaaa-1111-2222-3333-444444444444", + "completed_at": "2026-09-14T04:12:33.481207Z", + "started_at": "2026-09-14T04:00:01.002311Z", + "outcome": "NO_CHANGE", + "trigger": "ONGOING_MONITORING", + "provider": "aml_data_source", + "status": "Approved", + "score": 0.0, + "total_hits": 0, + "new_hits": 0, + "entities_screened": 1, + "dataset_freshness": { + "captured_at": "2026-09-14T03:58:02.114820+00:00", + "provider": "aml_data_source", + "catalog_version": "2026-09-13", + "sources": { + "sanctions": { + "last_updated_at": "2026-09-13T21:10:00+00:00", + "oldest_updated_at": "2026-09-11T06:00:00+00:00", + "dataset_count": 120, + "dated_dataset_count": 120 + }, + "pep": { + "last_updated_at": "2026-09-13T08:00:00+00:00", + "oldest_updated_at": null, + "dataset_count": 45, + "dated_dataset_count": 44 + }, + "warnings": { + "last_updated_at": "2026-09-12T18:45:00+00:00", + "oldest_updated_at": "2026-09-01T00:00:00+00:00", + "dataset_count": 30, + "dated_dataset_count": 30 + } + } + } + }, + { + "uuid": "eeeeeeee-1111-2222-3333-444444444444", + "run_id": "bbbbbbbb-1111-2222-3333-444444444444", + "completed_at": "2026-09-13T04:11:58.020914Z", + "started_at": "2026-09-13T04:00:00.871022Z", + "outcome": "FAILED", + "trigger": "ONGOING_MONITORING", + "provider": "aml_data_source", + "status": "Approved", + "score": 0.0, + "total_hits": 0, + "new_hits": null, + "entities_screened": 0, + "dataset_freshness": null + } + ] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is above 1,000,000 (a JSON array with one message), or `month` is not a `YYYY-MM` calendar month (an object keyed by `month`).", + "content": { + "application/json": { + "examples": { + "Limit too large": { + "summary": "limit=2000000", + "value": [ + "limit must not exceed 1000000." + ] + }, + "Offset too large": { + "summary": "offset=2000000", + "value": [ + "offset must not exceed 1000000." + ] + }, + "Malformed month": { + "summary": "month=2026-13", + "value": { + "month": [ + "Not a calendar month. Use YYYY-MM." + ] + } + } + }, + "schema": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "object", + "properties": { + "month": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + ] + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions read": { + "summary": "Scoped key lacks decisions: read", + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits, or it has no AML check (for the given `node_id`). The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's limits (also returned for a session deleted moments ago)", + "value": { + "detail": "Session not found." + } + }, + "No AML check": { + "summary": "The session never ran an AML step", + "value": { + "detail": "The session does not have an AML check." + } + }, + "No AML check for node": { + "summary": "node_id does not match an AML check of the session", + "value": { + "detail": "The session does not have an AML check for node 'feature_aml_2'." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 600 GET requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/aml-screening-history/export/": { + "get": { + "summary": "Export AML screening history as CSV", + "description": "Download the same ongoing-monitoring history as `GET /v3/session/{sessionId}/aml-screening-history/` as a CSV file, one line per completed re-screen, newest first, ready to hand to an auditor. The file is streamed whole (no pagination) and starts with a header line; an empty cell means unknown, dataset dates are never estimated, and a value that would start a spreadsheet formula is prefixed with an apostrophe. Works for KYC and KYB session IDs; pass `node_id` when the session ran more than one AML step. Pass `month` to export one calendar month. Errors are returned as JSON, not CSV. A scoped API key needs `decisions: read` and `media: read`: the response is a file, so a key without `media: read` gets `403`.", + "operationId": "get_v3_session_aml_screening_history_export", + "x-didit-sdk": { + "resource": "sessions", + "method": "export_aml_screening_history", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node ID of the AML check. Needed only when the session ran more than one AML step; without it the most recent AML check is used. An unknown node returns `404`.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + }, + { + "name": "month", + "in": "query", + "required": false, + "description": "A UTC calendar month as `YYYY-MM`. Only screenings completed in that month are returned; without it every month is returned. A malformed month returns `400`.", + "schema": { + "type": "string", + "pattern": "^\\d{4}-(0[1-9]|1[0-2])$", + "example": "2026-09" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/aml-screening-history/export/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -o aml-screening-history.csv" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.sessions.export_aml_screening_history(\n \"11111111-2222-3333-4444-555555555555\",\n)\nwith open(\"download.csv\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.sessions.exportAmlScreeningHistory(\"11111111-2222-3333-4444-555555555555\");\nawait writeFile(\"download.csv\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Sessions.ExportAMLScreeningHistory(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsExportAMLScreeningHistoryParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.csv\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().exportAmlScreeningHistory(\"11111111-2222-3333-4444-555555555555\")\n .writeTo(Path.of(\"download.csv\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->exportAmlScreeningHistory('11111111-2222-3333-4444-555555555555');\n$content->saveTo('download.csv');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.sessions.export_aml_screening_history(\"11111111-2222-3333-4444-555555555555\")\nFile.binwrite(\"download.csv\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Sessions.ExportAmlScreeningHistoryAsync(\"11111111-2222-3333-4444-555555555555\");\nawait content.SaveToAsync(\"download.csv\");" + } + ], + "responses": { + "200": { + "description": "The CSV file, streamed as `text/csv` with CRLF line endings. Columns, in order: `screened_at` (when the re-screen finished), `outcome` (`NO_CHANGE`, `CHANGED` or `FAILED`), `provider` (opaque identifier of the AML data source; do not branch on it), `aml_status`, `aml_score`, `total_hits`, `datasets_captured_at`, `datasets_sanctions_last_updated_at`, `datasets_pep_last_updated_at`, `datasets_warnings_last_updated_at`, `datasets_adverse_media_last_updated_at`, `datasets_catalog_version`, `run_id` (the monitoring run), `entities_screened` (`1`, or `0` for a failed run) and `new_hits` (empty when unknown). Dates are ISO 8601 with a UTC offset. A session with no re-screens yet returns the header line only.", + "headers": { + "Content-Disposition": { + "description": "`attachment; filename=\"aml-screening-history-{sessionId}.csv\"`, or `aml-screening-history-{sessionId}-{month}.csv` when `month` is passed.", + "schema": { + "type": "string", + "example": "attachment; filename=\"aml-screening-history-11111111-2222-3333-4444-555555555555.csv\"" + } + } + }, + "content": { + "text/csv": { + "schema": { + "type": "string", + "format": "binary" + }, + "example": "screened_at,outcome,provider,aml_status,aml_score,total_hits,datasets_captured_at,datasets_sanctions_last_updated_at,datasets_pep_last_updated_at,datasets_warnings_last_updated_at,datasets_adverse_media_last_updated_at,datasets_catalog_version,run_id,entities_screened,new_hits\r\n2026-09-14T04:12:33.481207+00:00,NO_CHANGE,aml_data_source,Approved,0.0,0,2026-09-14T03:58:02.114820+00:00,2026-09-13T21:10:00+00:00,2026-09-13T08:00:00+00:00,2026-09-12T18:45:00+00:00,,2026-09-13,aaaaaaaa-1111-2222-3333-444444444444,1,0\r\n2026-09-13T04:11:58.020914+00:00,FAILED,aml_data_source,Approved,0.0,0,,,,,,,bbbbbbbb-1111-2222-3333-444444444444,0,\r\n" + } + } + }, + "400": { + "description": "`month` is not a `YYYY-MM` calendar month.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "month": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "examples": { + "Malformed month": { + "summary": "month=2026-13", + "value": { + "month": [ + "Not a calendar month. Use YYYY-MM." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions read": { + "summary": "Scoped key lacks decisions: read", + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Scoped key without media read": { + "summary": "Scoped key lacks media: read (the response is a file)", + "value": { + "detail": "This API key has no read access to media." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits, or it has no AML check (for the given `node_id`). The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's limits (also returned for a session deleted moments ago)", + "value": { + "detail": "Session not found." + } + }, + "No AML check": { + "summary": "The session never ran an AML step", + "value": { + "detail": "The session does not have an AML check." + } + }, + "No AML check for node": { + "summary": "node_id does not match an AML check of the session", + "value": { + "detail": "The session does not have an AML check for node 'feature_aml_2'." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 600 GET requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/add-images/": { + "patch": { + "summary": "Add an image to a session", + "description": "Attach one extra image or PDF to the ID verification (`model_type=kyc`) or Proof of Address (`model_type=poa`) result of a finished User Verification (KYC) session, for example evidence collected outside the flow. The session must be `Approved`, `Declined`, `In Review` or `Kyc Expired` and carry the result named by `model_type`; Business Verification (KYB) sessions are not supported. Each call appends one file (to the most recent result when the step ran more than once), keeps the existing files and records a `FILE_ADDED` entry in the session activity log; the upload sends no webhook and does not change the session status. The file is compressed on upload and is returned in `extra_files` of the session decision. This endpoint is limited to 10 requests per minute per API key. A scoped API key needs `sessions: write`, and without `media: read` it receives the `extra_files` links as `null`.", + "operationId": "patch_v3_session_add_images", + "x-didit-sdk": { + "resource": "sessions", + "method": "add_images", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session to add the file to (the `session_id` returned by `POST /v3/session/`). A Business Verification (KYB) session ID returns `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "file", + "model_type" + ], + "properties": { + "file": { + "type": "string", + "format": "binary", + "description": "The image or PDF. Allowed extensions: `jpg`, `jpeg`, `png`, `webp`, `tiff`, `pdf`. At most 15 MB as uploaded; the file is then compressed and rejected with `400` if it is still larger than 2 MB (2,097,152 bytes). Empty files are rejected." + }, + "model_type": { + "type": "string", + "enum": [ + "kyc", + "poa" + ], + "description": "`kyc` adds the file to the ID verification result, `poa` to the Proof of Address result.", + "example": "kyc" + }, + "file_password": { + "type": "string", + "description": "Password of an encrypted PDF. Without it, or with a wrong one, an encrypted PDF returns `400`." + } + } + }, + "example": { + "file": "(binary)", + "model_type": "kyc" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/add-images/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'file=@./back-of-id.jpg' \\\n -F 'model_type=kyc'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.add_images(\n \"11111111-2222-3333-4444-555555555555\",\n file=open(\"image.jpg\", \"rb\"),\n model_type=\"kyc\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.addImages(\"11111111-2222-3333-4444-555555555555\", {\n file: await fileFromPath(\"image.jpg\"),\n model_type: \"kyc\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.AddImages(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsAddImagesParams{\n\tFile: didit.FileFromPath(\"image.jpg\"),\n\tModelType: \"kyc\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.types.SessionsAddImagesParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().addImages(\"11111111-2222-3333-4444-555555555555\", SessionsAddImagesParams.builder()\n .file(Path.of(\"image.jpg\"))\n .modelType(SessionsAddImagesParams.ModelType.of(\"kyc\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->addImages('11111111-2222-3333-4444-555555555555', [\n 'file' => File::fromPath('image.jpg'),\n 'model_type' => 'kyc',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.add_images(\n \"11111111-2222-3333-4444-555555555555\",\n file: Pathname(\"image.jpg\"),\n model_type: \"kyc\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.AddImagesAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsAddImagesParams\n{\n File = FileUpload.FromPath(\"image.jpg\"),\n ModelType = \"kyc\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "File added. Returns every extra file of that result, including the new one.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "extra_files" + ], + "properties": { + "extra_files": { + "type": "array", + "description": "Short-lived presigned links to all extra files of the result named by `model_type`, oldest first. Each link is `null` for a scoped API key without `media: read`. Read fresh links from the session decision later.", + "items": { + "type": "string", + "format": "uri", + "nullable": true + } + } + } + }, + "example": { + "extra_files": [ + "https://media.example.com/session/11111111-2222-3333-4444-555555555555/kyc-extra-files/66666666-7777-8888-9999-000000000000.jpg?expires=1789383731&signature=abc123" + ] + } + } + } + }, + "400": { + "description": "Validation error. Field errors arrive as `{\"\": [\"\"]}`; encrypted-PDF errors as `{\"detail\": \"...\"}`.", + "content": { + "application/json": { + "examples": { + "Missing file": { + "summary": "No file part in the form", + "value": { + "file": [ + "No file was submitted." + ] + } + }, + "Missing model_type": { + "summary": "model_type omitted", + "value": { + "model_type": [ + "This field is required." + ] + } + }, + "Invalid model_type": { + "summary": "model_type is not kyc or poa", + "value": { + "model_type": [ + "\"passport\" is not a valid choice." + ] + } + }, + "File too large": { + "summary": "Upload over 15 MB", + "value": { + "file": [ + "File size should not exceed 15 MB" + ] + } + }, + "Too large after compression": { + "summary": "Still over 2 MB after compression", + "value": { + "file": [ + "File size is too big. Max allowed size is 2097152 bytes." + ] + } + }, + "Encrypted PDF": { + "summary": "Encrypted PDF without file_password", + "value": { + "detail": "The PDF is encrypted. Please upload a decrypted PDF or a photo instead." + } + }, + "Wrong PDF password": { + "summary": "file_password does not open the PDF", + "value": { + "detail": "The PDF password is incorrect. Please provide the correct password." + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions write": { + "summary": "Scoped key lacks sessions: write", + "value": { + "detail": "This API key has no write access to sessions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits; it is a KYB session or not in `Approved`, `Declined`, `In Review` or `Kyc Expired`; or it has no result of the requested `model_type` (checked before the file is validated). The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's workflow or approved-only limits", + "value": { + "detail": "Session not found." + } + }, + "Not eligible": { + "summary": "Session not finished yet, or a KYB session ID", + "value": { + "detail": "No Session matches the given query." + } + }, + "No ID verification": { + "summary": "model_type=kyc on a session without ID verification", + "value": { + "detail": "The session does not have KYC data." + } + }, + "No Proof of Address": { + "summary": "model_type=poa on a session without Proof of Address", + "value": { + "detail": "The session does not have POA data." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 10 add-images requests per minute per API key (rejected requests count too). The shared budget of 300 write requests per minute also applies. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Session add-images rate limit exceeded. You can make up to 10 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/update-aml-status/": { + "patch": { + "summary": "Update an AML check status", + "description": "Set the status of a session's AML screening check as a whole (the `status` of an `aml_screenings[]` entry in the session decision), for example `Approved` after your team cleared every hit. Works for KYC and KYB session IDs; pass `node_id` (query parameter or body field) when the session ran more than one AML step, otherwise the most recent AML check is updated. When the status actually changes, an `AML_STATUS_UPDATED` activity entry is recorded and a `data.updated` [webhook](https://docs.didit.me/integration/webhooks) is sent; sending the current status returns `200` and changes nothing. The session status is not recomputed: use `PATCH /v3/session/{sessionId}/update-status/` for the session decision and `PATCH /v3/session/{sessionId}/update-aml-hit-status/` for single hits. A scoped API key needs `decisions: write`.", + "operationId": "patch_v3_session_update_aml_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_aml_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node ID of the AML check to update. Needed only when the session ran more than one AML step. Takes precedence over `node_id` in the body.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "New status of the AML check.", + "example": "Approved" + }, + "node_id": { + "type": "string", + "description": "Workflow graph node ID of the AML check, as an alternative to the query parameter.", + "example": "feature_aml_1" + } + } + }, + "example": { + "status": "Approved" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-aml-status/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"status\": \"Approved\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_aml_status(\n \"11111111-2222-3333-4444-555555555555\",\n status=\"Approved\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateAmlStatus(\"11111111-2222-3333-4444-555555555555\", {\n status: \"Approved\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateAMLStatus(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsUpdateAMLStatusParams{\n\tStatus: \"Approved\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsUpdateAmlStatusParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateAmlStatus(\"11111111-2222-3333-4444-555555555555\", SessionsUpdateAmlStatusParams.builder()\n .status(SessionsUpdateAmlStatusParams.Status.of(\"Approved\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateAmlStatus('11111111-2222-3333-4444-555555555555', [\n 'status' => 'Approved',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_aml_status(\n \"11111111-2222-3333-4444-555555555555\",\n status: \"Approved\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateAmlStatusAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsUpdateAmlStatusParams\n{\n Status = \"Approved\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "AML check status set (or unchanged when it already had this value).", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message", + "status", + "previous_status" + ], + "properties": { + "message": { + "type": "string", + "description": "Always `AML status updated successfully`." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Status of the AML check now." + }, + "previous_status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Status before the call. Equal to `status` when nothing changed." + } + } + }, + "example": { + "message": "AML status updated successfully", + "status": "Approved", + "previous_status": "In Review" + } + } + } + }, + "400": { + "description": "Validation error: `status` missing or not one of the allowed values. The AML check lookup runs first, so a session without an AML check returns `404` even with an invalid body.", + "content": { + "application/json": { + "examples": { + "Missing status": { + "summary": "Empty body or no status", + "value": { + "status": [ + "This field is required." + ] + } + }, + "Invalid status": { + "summary": "status not in the enum", + "value": { + "status": [ + "\"Cleared\" is not a valid choice." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits, or it has no AML check (for the given `node_id`). The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's limits (also returned for a session deleted moments ago)", + "value": { + "detail": "Session not found." + } + }, + "No AML check": { + "summary": "The session never ran an AML step", + "value": { + "detail": "The session does not have an AML check." + } + }, + "No AML check for node": { + "summary": "node_id does not match an AML check of the session", + "value": { + "detail": "The session does not have an AML check for node 'feature_aml_2'." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/status/": { + "get": { + "summary": "Get service status (public, unauthenticated)", + "description": "Returns the state of each Didit service (`operational`, `degraded` or `down`), the overall state (the worst of them), and the incidents that are ongoing or were resolved in the last 7 days. Use it to power a status widget or to check Didit's health from your integration.\n\nPublic: send no auth header. The response contains no organization data and is the same for every caller. Responses are cached for 30 seconds, so poll no more than once every 30 seconds.", + "operationId": "get_v3_status", + "tags": [ + "Status" + ], + "security": [ + {} + ], + "parameters": [], + "responses": { + "200": { + "description": "Current service status.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServiceStatusFeed" + }, + "examples": { + "One service degraded": { + "summary": "An ongoing incident and one resolved this week", + "value": { + "status": "degraded", + "updated_at": "2026-10-13T09:41:07.512345Z", + "services": [ + { + "key": "service_id_verification", + "name": "ID verification", + "status": "operational" + }, + { + "key": "service_liveness_face_match", + "name": "Liveness and face match", + "status": "operational" + }, + { + "key": "service_aml_screening", + "name": "AML screening", + "status": "operational" + }, + { + "key": "service_kyb_registries", + "name": "KYB registries", + "status": "operational" + }, + { + "key": "service_database_validation", + "name": "Database validation", + "status": "degraded" + }, + { + "key": "service_phone_delivery", + "name": "Phone verification delivery", + "status": "operational" + }, + { + "key": "service_email_delivery", + "name": "Email verification delivery", + "status": "operational" + }, + { + "key": "service_transaction_monitoring", + "name": "Transaction monitoring", + "status": "operational" + } + ], + "incidents": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "service": "service_database_validation", + "service_name": "Database validation", + "state": "degraded", + "status": "ongoing", + "title": "Database validation is degraded in Brazil.", + "body": "Checks against Brazil (CPF) may be slow or fail since 08:55 UTC.", + "affected": "Brazil (CPF)", + "country_name": "Brazil", + "started_at": "2026-10-13T08:55:00Z", + "resolved_at": null, + "duration_minutes": null + }, + { + "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "service": "service_aml_screening", + "service_name": "AML screening", + "state": "down", + "status": "resolved", + "title": "AML screening is back to normal.", + "body": "Recovered at 14:32 UTC after 17 minutes.", + "affected": "", + "country_name": "", + "started_at": "2026-10-11T14:15:00Z", + "resolved_at": "2026-10-11T14:32:00Z", + "duration_minutes": 17 + } + ] + } + }, + "All operational": { + "summary": "No incident in the last 7 days", + "value": { + "status": "operational", + "updated_at": "2026-10-13T09:41:07.512345Z", + "services": [ + { + "key": "service_id_verification", + "name": "ID verification", + "status": "operational" + }, + { + "key": "service_liveness_face_match", + "name": "Liveness and face match", + "status": "operational" + }, + { + "key": "service_aml_screening", + "name": "AML screening", + "status": "operational" + }, + { + "key": "service_kyb_registries", + "name": "KYB registries", + "status": "operational" + }, + { + "key": "service_database_validation", + "name": "Database validation", + "status": "operational" + }, + { + "key": "service_phone_delivery", + "name": "Phone verification delivery", + "status": "operational" + }, + { + "key": "service_email_delivery", + "name": "Email verification delivery", + "status": "operational" + }, + { + "key": "service_transaction_monitoring", + "name": "Transaction monitoring", + "status": "operational" + } + ], + "incidents": [] + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests in a minute from the same IP address (or the same API key, if you send one). Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "detail" + ], + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -fsS https://verification.didit.me/v3/status/" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.get('https://verification.didit.me/v3/status/', timeout=5)\nresp.raise_for_status()\nfeed = resp.json()\nprint(feed['status'])\nfor service in feed['services']:\n if service['status'] != 'operational':\n print(service['key'], service['status'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const feed = await fetch('https://verification.didit.me/v3/status/').then((r) => r.json());\nconsole.log(feed.status);\nconst affected = feed.services.filter((s) => s.status !== 'operational');\nconsole.log(affected.map((s) => `${s.key}: ${s.status}`));" + } + ], + "x-didit-sdk": { + "resource": "system", + "method": "retrieve_status", + "auth": "public", + "server": false + } + } + }, + "/v3/organization/bank-verification-pricing/": { + "get": { + "summary": "Get bank verification prices (public, unauthenticated)", + "description": "Returns the list price of each Bank Verification method in every country: bank sign-in with its balance and transactions add-ons, and the account check. Bank Verification is not enabled in production yet, so a bank step is refused on production workflows until it is; treat these prices as a list price, not as an offer you can buy today. Use it to quote a bank step before you build a workflow, or to show bank prices in your own product.\n\nPublic: send no auth header. The response contains no organization data and is the same for every caller. It is cached publicly for one hour and carries an `ETag`, so send `If-None-Match` to get a `304 Not Modified` when nothing changed.\n\nA method is `available` only where Didit serves it in production today, and its `price_usd` is `null` everywhere else, so the feed never shows a price you cannot buy. `available: false` covers a country with no approved price, a method the country does not support, and a route that is not switched on yet.\n\nThese are list prices before your plan discount. Your plan discount applies to the whole bank price, and a negotiated or contract price replaces the list price for your organization.", + "operationId": "get_v3_bank_verification_pricing", + "tags": [ + "Pricing" + ], + "security": [ + {} + ], + "parameters": [], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -fsS https://verification.didit.me/v3/organization/bank-verification-pricing/" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# This call needs no API key.\nclient = Didit()\n\nresult = client.pricing.bank_verification()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// This call needs no API key.\nconst didit = new Didit();\n\nconst result = await didit.pricing.bankVerification();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// This call needs no API key.\nclient := didit.NewClient()\n\nresult, err := client.Pricing.BankVerification(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// This call needs no API key.\nDidit client = new Didit();\n\nvar result = client.pricing().bankVerification();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "pricing->bankVerification();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# This call needs no API key.\ndidit = Didit::Client.new\n\nresult = didit.pricing.bank_verification\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// This call needs no API key.\nusing var client = new DiditClient();\n\nvar result = await client.Pricing.BankVerificationAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-sdk": { + "resource": "pricing", + "method": "bank_verification", + "auth": "none", + "server": true + }, + "responses": { + "200": { + "description": "Bank Verification list prices by country.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankVerificationPricing" + }, + "examples": { + "Two countries": { + "summary": "One country with both methods, one with neither", + "value": { + "currency": "USD", + "statement_upload": { + "price_usd": "0.0000", + "included_after_charged_bank_attempt": true, + "standalone_available": false + }, + "own_provider_fee_usd": "0.0200", + "countries": { + "GBR": { + "name": "United Kingdom", + "methods": { + "open_banking": { + "available": true, + "market": "UK and EEA", + "price_usd": "0.7900", + "add_ons": { + "balance": "0.3500", + "transactions": "1.3000" + } + }, + "account_lookup": { + "available": true, + "price_usd": "0.4000" + } + } + }, + "CAN": { + "name": "Canada", + "methods": { + "open_banking": { + "available": false, + "market": null, + "price_usd": null, + "add_ons": {} + }, + "account_lookup": { + "available": false, + "price_usd": null + } + } + } + } + } + } + } + } + } + }, + "304": { + "description": "Not modified: the `ETag` you sent in `If-None-Match` still matches. No body." + } + } + } + }, + "/v3/session/{sessionId}/simulate/": { + "post": { + "summary": "Simulate a sandbox session's status", + "description": "Force a terminal status on a sandbox session after the fact, from your backend, without running the flow. Sandbox applications only: a live session returns `404`. Works for User Verification (KYC) and Business Verification (KYB) sessions.\n\nSet `new_status`, or let `scenario` or `risk` supply it. `risk` (or the first risk the scenario's magic values carry) adds that warning to the session. The change is recorded in the session's activity.\n\n`send_webhooks` (default `true`) sends exactly the webhooks a real status change sends. With `false` the status still changes and the console still updates live, but no webhook is logged or sent, the missed-webhook recovery never re-sends that decision, and the next status change sends webhooks as usual. The response echoes the value applied.\n\nRequires the `write:sessions` privilege; a scoped API key needs write access to sessions. An unknown `session_id` returns `404` and another application's session returns `403`, both before the body is read; a live session returns `404` only after the body is validated. To simulate from the hosted flow with the session's own token, use `POST /v3/session/{sessionId}/sandbox/simulate/`. See [Sandbox & Test Data](/integration/sandbox-testing) for the scenario catalog.", + "operationId": "post_v3_session_simulate", + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the sandbox verification session. Accepts both user (KYC) and business (KYB) session IDs.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSimulateRequest" + }, + "examples": { + "Scenario Without Webhooks": { + "summary": "Decline through a scenario without sending webhooks", + "value": { + "scenario": "decline_aml_hit", + "send_webhooks": false + } + }, + "Status Only": { + "summary": "Approve and send the usual webhooks", + "value": { + "new_status": "Approved" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The status changed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSimulateResponse" + }, + "examples": { + "Simulated": { + "summary": "Declined through a scenario, webhooks not sent", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "previous_status": "In Progress", + "status": "Declined", + "scenario": "decline_aml_hit", + "risk": "POSSIBLE_MATCH_FOUND", + "send_webhooks": false + } + } + } + } + } + }, + "400": { + "description": "Validation error: an unknown scenario or risk, no `new_status` when neither `scenario` nor `risk` supplies one, a status this endpoint does not set, or the session already has the requested status (a bare JSON array).", + "content": { + "application/json": { + "examples": { + "Unknown Scenario": { + "summary": "scenario is not in the catalog", + "value": { + "scenario": [ + "Unknown sandbox scenario: 'not-a-scenario'." + ] + } + }, + "Unknown Risk": { + "summary": "risk is not in the catalog", + "value": { + "risk": [ + "Unknown sandbox risk case: 'NOT_A_RISK'." + ] + } + }, + "Missing new_status": { + "summary": "No new_status, scenario or risk", + "value": { + "new_status": [ + "This field is required." + ] + } + }, + "Invalid new_status": { + "summary": "A status the simulation does not set", + "value": { + "new_status": [ + "\"Resubmitted\" is not a valid choice." + ] + } + }, + "Same Status": { + "summary": "The session already has this status", + "value": [ + "The new status is the same as the current status." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials, a caller without the `write:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no write access to sessions." + } + } + } + } + } + }, + "404": { + "description": "No session with this `session_id` exists, or it is a live (non-sandbox) session.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown or live session", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/simulate/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"scenario\": \"decline_aml_hit\", \"send_webhooks\": false}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.simulate(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n scenario=\"decline_aml_hit\",\n send_webhooks=False,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.simulate(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", {\n scenario: \"decline_aml_hit\",\n send_webhooks: false,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Simulate(ctx, \"3fa85f64-5717-4562-b3fc-2c963f66afa6\", didit.SandboxSimulateRequest{\n\tScenario: didit.String(\"decline_aml_hit\"),\n\tSendWebhooks: didit.Bool(false),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SandboxSimulateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().simulate(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", SandboxSimulateRequest.builder()\n .scenario(\"decline_aml_hit\")\n .sendWebhooks(false)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->simulate('3fa85f64-5717-4562-b3fc-2c963f66afa6', [\n 'scenario' => 'decline_aml_hit',\n 'send_webhooks' => false,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.simulate(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n scenario: \"decline_aml_hit\",\n send_webhooks: false\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.SimulateAsync(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", new SandboxSimulateRequest\n{\n Scenario = \"decline_aml_hit\",\n SendWebhooks = false,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "simulate", + "auth": "api_key", + "server": true + } + } + }, + "/v3/session/{sessionId}/sandbox/simulate/": { + "post": { + "summary": "Simulate a sandbox session's status from the client", + "description": "The same simulation as `POST /v3/session/{sessionId}/simulate/`, authenticated with the session's own `Session-Token` (returned in `session_token` by [Create Session](/sessions-api/create-session)) instead of your API key, so a test harness or the hosted flow can call it from the client. The body, the response and `send_webhooks` behave exactly as on that endpoint.\n\nA `Session-Token` of a live (non-sandbox) application, or one that does not match the `sessionId` in the path, returns `404`. See [Sandbox & Test Data](/integration/sandbox-testing).", + "operationId": "post_v3_session_sandbox_simulate", + "tags": [ + "Sessions" + ], + "security": [ + { + "SessionTokenAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the sandbox verification session. Must match the session encoded in the Session-Token used to authenticate.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSimulateRequest" + }, + "examples": { + "Scenario Without Webhooks": { + "summary": "Decline through a scenario without sending webhooks", + "value": { + "scenario": "decline_aml_hit", + "send_webhooks": false + } + }, + "Status Only": { + "summary": "Approve and send the usual webhooks", + "value": { + "new_status": "Approved" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The status changed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSimulateResponse" + }, + "examples": { + "Simulated": { + "summary": "Declined through a scenario, webhooks not sent", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "previous_status": "In Progress", + "status": "Declined", + "scenario": "decline_aml_hit", + "risk": "POSSIBLE_MATCH_FOUND", + "send_webhooks": false + } + } + } + } + } + }, + "400": { + "description": "Validation error: an unknown scenario or risk, no `new_status` when neither `scenario` nor `risk` supplies one, a status this endpoint does not set, or the session already has the requested status (a bare JSON array).", + "content": { + "application/json": { + "examples": { + "Unknown Scenario": { + "summary": "scenario is not in the catalog", + "value": { + "scenario": [ + "Unknown sandbox scenario: 'not-a-scenario'." + ] + } + }, + "Unknown Risk": { + "summary": "risk is not in the catalog", + "value": { + "risk": [ + "Unknown sandbox risk case: 'NOT_A_RISK'." + ] + } + }, + "Missing new_status": { + "summary": "No new_status, scenario or risk", + "value": { + "new_status": [ + "This field is required." + ] + } + }, + "Invalid new_status": { + "summary": "A status the simulation does not set", + "value": { + "new_status": [ + "\"Resubmitted\" is not a valid choice." + ] + } + }, + "Same Status": { + "summary": "The session already has this status", + "value": [ + "The new status is the same as the current status." + ] + } + } + } + } + }, + "403": { + "description": "The `Session-Token` header is missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid Session Token": { + "summary": "Missing or invalid Session-Token", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "The `Session-Token` belongs to a live (non-sandbox) application, or does not match the `sessionId` in the path.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Live session, or session_id / token mismatch", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/sandbox/simulate/ \\\n -H 'Session-Token: YOUR_SESSION_TOKEN' \\\n -H 'Content-Type: application/json' \\\n -d '{\"new_status\": \"Approved\", \"send_webhooks\": false}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/sandbox/simulate/\",\n headers={\"Session-Token\": \"YOUR_SESSION_TOKEN\"},\n json={\"new_status\": \"Approved\", \"send_webhooks\": False},\n)\nresponse.raise_for_status()\nprint(response.json())" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "simulate_sandbox", + "auth": "device_token", + "server": false + } + } + }, + "/v3/session/{sessionId}/features/overrides/": { + "get": { + "summary": "List a session's check overrides", + "description": "Every override of an individual feature (step) status on a User Verification (KYC) or Business Verification (KYB) session, newest first and not paginated: applied ones, ones waiting for the person to finish the flow (`pending_until_flow_finishes`) and cancelled ones, each with who asked, their reason, and whether it applied while the person was still in the flow (`forced`).\n\n`person_in_flow` tells you whether a new override with `defer_until_flow_finishes: true` would wait. `flow_finished_at` is when the person last finished the flow; it is `null` while they are still in it (a resubmission included) and when the session closed before they finished.\n\nRequires the `read:sessions` privilege; a scoped API key needs read access to decisions.", + "operationId": "get_v3_session_feature_overrides", + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + ], + "responses": { + "200": { + "description": "The session's check overrides, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "session_id", + "person_in_flow", + "flow_finished_at", + "overrides" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "person_in_flow": { + "type": "boolean", + "description": "Whether the person is still in the flow, so a new override with `defer_until_flow_finishes: true` would wait.", + "example": true + }, + "flow_finished_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the person last finished the flow. `null` while they are still in it (a resubmission included) and when the session closed before they finished (abandoned, expired or decided mid-flow).", + "example": null + }, + "overrides": { + "type": "array", + "description": "Every override of the session, newest first.", + "items": { + "$ref": "#/components/schemas/FeatureStatusOverride" + } + } + } + }, + "examples": { + "Overrides": { + "summary": "One override waiting, one applied", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "person_in_flow": true, + "flow_finished_at": null, + "overrides": [ + { + "id": "9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3", + "node_id": "feature_liveness_1", + "feature_type": "", + "feature_model": "Face", + "previous_status": "Declined", + "new_status": "Approved", + "state": "pending_until_flow_finishes", + "forced": false, + "reason": "Liveness retried with a reviewer present.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": null, + "cancelled_at": null, + "cancel_reason": null + }, + { + "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "node_id": "feature_ocr_1", + "feature_type": "", + "feature_model": "KYC", + "previous_status": "In Review", + "new_status": "Approved", + "state": "applied", + "forced": true, + "reason": "Document manually verified by compliance.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": "2026-10-13T09:15:00Z", + "cancelled_at": null, + "cancel_reason": null + } + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials, a caller without the `read:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks read:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no read access to decisions." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/features/overrides/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.list_feature_overrides(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.listFeatureOverrides(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.ListFeatureOverrides(ctx, \"3fa85f64-5717-4562-b3fc-2c963f66afa6\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().listFeatureOverrides(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->listFeatureOverrides('3fa85f64-5717-4562-b3fc-2c963f66afa6');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.list_feature_overrides(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ListFeatureOverridesAsync(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "list_feature_overrides", + "auth": "api_key", + "server": true + } + } + }, + "/v3/session/{sessionId}/features/overrides/{overrideId}/cancel/": { + "post": { + "summary": "Cancel a check override that waits for the flow to finish", + "description": "Cancel an override of a feature (step) status that is still `pending_until_flow_finishes`, so it never applies. The check keeps its status, the cancellation is recorded in the session's activity as `FEATURE_OVERRIDE_CANCELLED`, and no webhook is sent. The response is the override with `state: cancelled` and `cancel_reason: cancelled_by_reviewer`.\n\nAn override that already applied or was cancelled returns `409` with the code `feature_override_not_pending`. Find override IDs with `GET /v3/session/{sessionId}/features/overrides/` or in the `override` returned by the feature status update.\n\nRequires the `write:sessions` privilege; a scoped API key needs write access to decisions.", + "operationId": "post_v3_session_feature_override_cancel", + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + }, + { + "in": "path", + "name": "overrideId", + "required": true, + "description": "The `id` of the override to cancel.", + "schema": { + "type": "string", + "format": "uuid", + "example": "9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3" + } + } + ], + "responses": { + "200": { + "description": "The override is cancelled and will never apply.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FeatureStatusOverride" + }, + "examples": { + "Cancelled": { + "summary": "The cancelled override", + "value": { + "id": "9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3", + "node_id": "feature_liveness_1", + "feature_type": "", + "feature_model": "Face", + "previous_status": "Declined", + "new_status": "Approved", + "state": "cancelled", + "forced": false, + "reason": "Liveness retried with a reviewer present.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": null, + "cancelled_at": "2026-10-13T09:20:00Z", + "cancel_reason": "cancelled_by_reviewer" + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials, a caller without the `write:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no write access to decisions." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists, or the session has no override with this `overrideId`.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + }, + "Override Not Found": { + "summary": "No override with this id on the session", + "value": { + "detail": "Override not found." + } + } + } + } + } + }, + "409": { + "description": "The override already applied or was cancelled.", + "content": { + "application/json": { + "examples": { + "Not Pending": { + "summary": "Only a waiting override can be cancelled", + "value": { + "detail": "This override is no longer pending: it already applied or was cancelled.", + "code": "feature_override_not_pending" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/features/overrides/9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3/cancel/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.cancel_feature_override(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n \"9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.cancelFeatureOverride(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n \"9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.CancelFeatureOverride(ctx, \"3fa85f64-5717-4562-b3fc-2c963f66afa6\", \"9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().cancelFeatureOverride(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", \"9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->cancelFeatureOverride('3fa85f64-5717-4562-b3fc-2c963f66afa6', '9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.cancel_feature_override(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n \"9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.CancelFeatureOverrideAsync(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", \"9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "cancel_feature_override", + "auth": "api_key", + "server": true + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/tags/add/": { + "post": { + "summary": "Add a tag to a network", + "description": "Put one of your application's tags on the network. Returns the network detail, whose `tags` lists your application's tags on the network, with `201` when the tag was added and `200` when it was already on the network. A tag that belongs to another application, or does not exist, returns 404 `Tag not found.`. Requires a full-access API key, or `write:networks` for a console member.", + "operationId": "add_network_tag", + "tags": [ + "Networks" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "description": "Network UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkTagRequest" + }, + "example": { + "tag_uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/3fa85f64-5717-4562-b3fc-2c963f66afa6/tags/add/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"tag_uuid\": \"7c9e6679-7425-40de-944b-e07fc1f90ae7\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.add_tag(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n tag_uuid=\"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.addTag(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", {\n tag_uuid: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.AddTag(ctx, \"3fa85f64-5717-4562-b3fc-2c963f66afa6\", didit.NetworkTagRequest{\n\tTagUUID: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.NetworkTagRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().addTag(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", NetworkTagRequest.builder()\n .tagUuid(\"7c9e6679-7425-40de-944b-e07fc1f90ae7\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->addTag('3fa85f64-5717-4562-b3fc-2c963f66afa6', [\n 'tag_uuid' => '7c9e6679-7425-40de-944b-e07fc1f90ae7',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.add_tag(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n tag_uuid: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.AddTagAsync(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", new NetworkTagRequest\n{\n TagUuid = \"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The tag was added. Returns the network detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkDetailResponse" + }, + "example": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "network_id": "N-0042", + "name": null, + "status": "in_review", + "risk": { + "score": 82, + "band": "high", + "bar": 0.82 + }, + "pattern_types": [ + "exact_same_device", + "same_email" + ], + "signal_types": [ + "device", + "email" + ], + "member_counts": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signal_coverage": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + }, + "first_activity_at": "2026-10-01T09:12:44Z", + "last_activity_at": "2026-10-12T16:03:10Z", + "stats": { + "members": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signals": { + "total": 2, + "by_type": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + } + } + }, + "fraud_tags": [], + "risk_signals": { + "residential_proxy_reuse": { + "detected": false, + "signal_count": 0, + "max_subject_count": 0 + }, + "email_intelligence": { + "enriched_signal_count": 1, + "high_risk_count": 0, + "social_profile_count": 0 + }, + "phone_intelligence": { + "enriched_signal_count": 0, + "high_risk_count": 0 + } + }, + "reviewed_at": "2026-10-12T16:30:00Z", + "reviewed_by": "reviewer@example.com", + "review_reason": "Shared device across three new accounts.", + "notes_count": 0, + "sharing_mode": "global", + "cross_org_available": true, + "cross_org_unavailable_reason": null, + "tags": [ + { + "uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "name": "SUSPICIOUS", + "color": "#6366F1", + "source": "custom", + "added_by": null, + "added_at": "2026-10-13T08:15:00Z" + } + ], + "outcomes": { + "approved": 1, + "declined": 1, + "in_review": 0, + "total": 3 + }, + "patterns": [ + { + "type": "background", + "score": 0.8125, + "members": 3, + "reason": null, + "computed_at": "2026-10-13T08:00:00Z" + } + ] + } + } + } + }, + "200": { + "description": "The tag was already on the network and is left as it is. Returns the network detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkDetailResponse" + }, + "example": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "network_id": "N-0042", + "name": null, + "status": "in_review", + "risk": { + "score": 82, + "band": "high", + "bar": 0.82 + }, + "pattern_types": [ + "exact_same_device", + "same_email" + ], + "signal_types": [ + "device", + "email" + ], + "member_counts": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signal_coverage": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + }, + "first_activity_at": "2026-10-01T09:12:44Z", + "last_activity_at": "2026-10-12T16:03:10Z", + "stats": { + "members": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signals": { + "total": 2, + "by_type": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + } + } + }, + "fraud_tags": [], + "risk_signals": { + "residential_proxy_reuse": { + "detected": false, + "signal_count": 0, + "max_subject_count": 0 + }, + "email_intelligence": { + "enriched_signal_count": 1, + "high_risk_count": 0, + "social_profile_count": 0 + }, + "phone_intelligence": { + "enriched_signal_count": 0, + "high_risk_count": 0 + } + }, + "reviewed_at": "2026-10-12T16:30:00Z", + "reviewed_by": "reviewer@example.com", + "review_reason": "Shared device across three new accounts.", + "notes_count": 0, + "sharing_mode": "global", + "cross_org_available": true, + "cross_org_unavailable_reason": null, + "tags": [ + { + "uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "name": "SUSPICIOUS", + "color": "#6366F1", + "source": "custom", + "added_by": null, + "added_at": "2026-10-13T08:15:00Z" + } + ], + "outcomes": { + "approved": 1, + "declined": 1, + "in_review": 0, + "total": 3 + }, + "patterns": [ + { + "type": "background", + "score": 0.8125, + "members": 3, + "reason": null, + "computed_at": "2026-10-13T08:00:00Z" + } + ] + } + } + } + }, + "400": { + "description": "Invalid body: `tag_uuid` is missing or is not a UUID.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tag_uuid": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "examples": { + "Missing tag_uuid": { + "summary": "Missing tag_uuid", + "value": { + "tag_uuid": [ + "This field is required." + ] + } + }, + "Invalid tag_uuid": { + "summary": "Invalid tag_uuid", + "value": { + "tag_uuid": [ + "Must be a valid UUID." + ] + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `write:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Network not found": { + "summary": "Network not found", + "value": { + "detail": "Network not found." + } + }, + "Tag not found": { + "summary": "Tag not found", + "value": { + "detail": "Tag not found." + } + } + } + } + } + } + }, + "x-didit-sdk": { + "resource": "networks", + "method": "add_tag", + "auth": "api_key", + "server": true, + "scope": "application" + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/tags/remove/": { + "post": { + "summary": "Remove a tag from a network", + "description": "Remove one of your application's tags from the network. Returns the network detail. 404 when the tag is not on the network, or does not belong to your application. Requires a full-access API key, or `write:networks` for a console member.", + "operationId": "remove_network_tag", + "tags": [ + "Networks" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "description": "Network UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkTagRequest" + }, + "example": { + "tag_uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/3fa85f64-5717-4562-b3fc-2c963f66afa6/tags/remove/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"tag_uuid\": \"7c9e6679-7425-40de-944b-e07fc1f90ae7\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.remove_tag(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n tag_uuid=\"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.removeTag(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", {\n tag_uuid: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.RemoveTag(ctx, \"3fa85f64-5717-4562-b3fc-2c963f66afa6\", didit.NetworkTagRequest{\n\tTagUUID: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.NetworkTagRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().removeTag(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", NetworkTagRequest.builder()\n .tagUuid(\"7c9e6679-7425-40de-944b-e07fc1f90ae7\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->removeTag('3fa85f64-5717-4562-b3fc-2c963f66afa6', [\n 'tag_uuid' => '7c9e6679-7425-40de-944b-e07fc1f90ae7',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.remove_tag(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n tag_uuid: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.RemoveTagAsync(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", new NetworkTagRequest\n{\n TagUuid = \"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The tag was removed from the network. Returns the network detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkDetailResponse" + }, + "example": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "network_id": "N-0042", + "name": null, + "status": "in_review", + "risk": { + "score": 82, + "band": "high", + "bar": 0.82 + }, + "pattern_types": [ + "exact_same_device", + "same_email" + ], + "signal_types": [ + "device", + "email" + ], + "member_counts": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signal_coverage": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + }, + "first_activity_at": "2026-10-01T09:12:44Z", + "last_activity_at": "2026-10-12T16:03:10Z", + "stats": { + "members": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signals": { + "total": 2, + "by_type": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + } + } + }, + "fraud_tags": [], + "risk_signals": { + "residential_proxy_reuse": { + "detected": false, + "signal_count": 0, + "max_subject_count": 0 + }, + "email_intelligence": { + "enriched_signal_count": 1, + "high_risk_count": 0, + "social_profile_count": 0 + }, + "phone_intelligence": { + "enriched_signal_count": 0, + "high_risk_count": 0 + } + }, + "reviewed_at": "2026-10-12T16:30:00Z", + "reviewed_by": "reviewer@example.com", + "review_reason": "Shared device across three new accounts.", + "notes_count": 0, + "sharing_mode": "global", + "cross_org_available": true, + "cross_org_unavailable_reason": null, + "tags": [], + "outcomes": { + "approved": 1, + "declined": 1, + "in_review": 0, + "total": 3 + }, + "patterns": [ + { + "type": "background", + "score": 0.8125, + "members": 3, + "reason": null, + "computed_at": "2026-10-13T08:00:00Z" + } + ] + } + } + } + }, + "400": { + "description": "Invalid body: `tag_uuid` is missing or is not a UUID.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tag_uuid": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "examples": { + "Missing tag_uuid": { + "summary": "Missing tag_uuid", + "value": { + "tag_uuid": [ + "This field is required." + ] + } + }, + "Invalid tag_uuid": { + "summary": "Invalid tag_uuid", + "value": { + "tag_uuid": [ + "Must be a valid UUID." + ] + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `write:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Network not found": { + "summary": "Network not found", + "value": { + "detail": "Network not found." + } + }, + "Tag not found": { + "summary": "Tag not found", + "value": { + "detail": "Tag not found." + } + }, + "Tag not on the network": { + "summary": "Tag not on the network", + "value": { + "detail": "This tag is not on the network." + } + } + } + } + } + } + }, + "x-didit-sdk": { + "resource": "networks", + "method": "remove_tag", + "auth": "api_key", + "server": true, + "scope": "application" + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/{request_uuid}/remind/": { + "post": { + "summary": "Remind the customer of a request", + "description": "Send a waiting customer request's email again, to the same verified recipient, with the same content. You can remind a request at most once every 24 hours. A reminder changes nothing else: the request keeps its deadline and the case keeps its status. Each reminder is logged on the case as a CUSTOMER_REQUEST event with action `reminded`. The response's `delivery_status` is how this reminder's delivery ended: SENT, SIMULATED (the email was not sent), or UNKNOWN (the email might have been accepted; it is not sent again automatically). `next_reminder_at` is when you can remind this request again. Returns 409 with a `reason` when the customer no longer has the request to answer, and 429 with `retry_at` and a `Retry-After` header when the request was reminded in the last 24 hours. Requires `write:cases`.", + "operationId": "organization_application_cases_requests_remind_create", + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "description": "Organization UUID." + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "description": "Application UUID." + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "description": "Case UUID." + }, + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "description": "Customer request UUID, from the case's request list." + } + ], + "tags": [ + "Case Management" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/requests/3fa85f64-5717-4562-b3fc-2c963f66afa6/remind/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.requests.remind(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.requests.remind(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Requests.Remind(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().requests().remind(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->remind('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.requests.remind(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Requests.RemindAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The reminder was recorded and its email sent.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "request", + "reminded_at", + "delivery_status", + "next_reminder_at" + ], + "properties": { + "request": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequest" + }, + "reminded_at": { + "type": "string", + "format": "date-time", + "description": "When this reminder was recorded." + }, + "delivery_status": { + "type": "string", + "enum": [ + "SENT", + "SIMULATED", + "UNKNOWN" + ], + "description": "How this reminder's delivery ended. SIMULATED: the email was not sent. UNKNOWN: the email might have been accepted; it is not sent again automatically." + }, + "next_reminder_at": { + "type": "string", + "format": "date-time", + "description": "When you can remind this request again: `reminded_at` plus 24 hours." + } + } + }, + "example": { + "request": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "purpose": "confirm_details", + "language": "en", + "recipient": "customer@example.com", + "expires_at": "2026-10-20T09:00:00Z", + "actor_email": "reviewer@example.com", + "delivery_status": "SENT", + "delivered_at": "2026-10-13T09:00:04Z", + "cancelled_at": null, + "responded_at": null, + "response": null, + "has_attachment": false, + "created_at": "2026-10-13T09:00:00Z" + }, + "reminded_at": "2026-10-14T10:30:00Z", + "delivery_status": "SENT", + "next_reminder_at": "2026-10-15T10:30:00Z" + } + } + } + }, + "400": { + "description": "The case has a resolution pending approval. The body is a list of strings.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "example": [ + "Customer messages require an active case without a pending resolution." + ] + } + } + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "409": { + "description": "The request no longer waits on the customer, so there is nothing to remind them of. `reason` says why: `answered` (the customer responded), `cancelled`, `expired`, `closed` (the case was resolved after the request went out, even if it was reopened since), `not_actionable` (a review-in-progress update asks nothing), `not_delivered` (the first email has not gone out), or `contact_unavailable` (the verified email is no longer available).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "case_request_not_waiting" + ] + }, + "reason": { + "type": "string", + "enum": [ + "answered", + "cancelled", + "expired", + "closed", + "not_actionable", + "not_delivered", + "contact_unavailable" + ] + } + } + }, + "example": { + "detail": "This request no longer waits on the customer, so there is nothing to remind them of.", + "code": "case_request_not_waiting", + "reason": "answered" + } + } + } + }, + "429": { + "description": "The request was already reminded in the last 24 hours. `retry_at` and the `Retry-After` header say when you can remind it again.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before you can remind this request again.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 3600 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "case_request_reminder_too_soon" + ] + }, + "retry_at": { + "type": "string", + "format": "date-time" + } + } + }, + "example": { + "detail": "This request was already reminded in the last 24 hours.", + "code": "case_request_reminder_too_soon", + "retry_at": "2026-10-15T10:30:00Z" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-didit-sdk": { + "resource": "cases.requests", + "method": "remind", + "auth": "api_key", + "server": true, + "scope": "application" + } + } + }, + "/v3/session/{sessionId}/decision-email-preview/": { + "post": { + "summary": "Preview a session's decision email", + "description": "Render the email the person receives when you set a session to `Approved` or `Declined` with `send_email: true`, without changing the session or sending anything. The response shows the subject and HTML exactly as they are sent, the recipient, the sender, the language, and whether confirming the change sends the email at all (`will_be_sent`, `not_sent_reason`).\n\nThe recipient is the person's own address: the one they verified in an email verification step of the flow, else the one given in `contact_details` when the session was created. The sender is your application's verified sender domain when white label applies (`sender_kind: customer`), else Didit's sender (`sender_kind: didit`).\n\nTo send exactly what you previewed, call [Update Session Status](/sessions-api/update-status) with the same `new_status`, `send_email: true`, `email_address` set to `recipient`, `email_language` set to `language` and `email_preview_token` set to `preview_token`. The receipt expires after 15 minutes. When the recipient, language, session status, sender or email changed since the preview, the update returns `409` before anything changes; preview again and confirm with the new receipt.\n\nWorks for User Verification (KYC) and Business Verification (KYB) sessions in `Approved`, `Declined`, `In Review`, `Kyc Expired`, `Abandoned` or `Resubmitted` status. The status check runs before body validation. Requires the `write:sessions` privilege; a scoped API key needs write access to decisions. Responses use `Cache-Control: no-store`: treat them as sensitive verification content.", + "operationId": "post_v3_session_decision_email_preview", + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "new_status" + ], + "properties": { + "new_status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "The decision whose email you want to preview.", + "example": "Approved" + }, + "email_language": { + "type": "string", + "default": "en", + "description": "Language of the email, the same default as Update Session Status. A language that is not offered falls back to English; `language` in the response says which one is used.", + "example": "en" + } + } + }, + "examples": { + "Approved": { + "summary": "Preview the Approved email in English", + "value": { + "new_status": "Approved" + } + }, + "Declined in Spanish": { + "summary": "Preview the Declined email in Spanish", + "value": { + "new_status": "Declined", + "email_language": "es" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Preview only. The session is unchanged and no email is sent.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: the preview carries verification content and must not be cached.", + "schema": { + "type": "string", + "example": "no-store" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "recipient", + "recipient_source", + "sender", + "sender_name", + "sender_email", + "sender_kind", + "subject", + "html", + "text", + "language", + "will_be_sent", + "not_sent_reason", + "preview_token" + ], + "properties": { + "recipient": { + "type": "string", + "description": "The address the email goes to. An empty string when no address is known for the person: then no email can be sent and `preview_token` is empty too. Pass it as `email_address` when you confirm.", + "example": "person@example.com" + }, + "recipient_source": { + "type": "string", + "enum": [ + "email_verification", + "contact_details", + "" + ], + "description": "Where `recipient` comes from. `email_verification`: the address the person verified in an email verification step of the flow. `contact_details`: the address given in `contact_details` when the session was created. An empty string when there is no address.", + "example": "email_verification" + }, + "sender": { + "type": "string", + "description": "The `From` header the email goes out with.", + "example": "Example App " + }, + "sender_name": { + "type": "string", + "description": "The display name part of `sender`.", + "example": "Example App" + }, + "sender_email": { + "type": "string", + "format": "email", + "description": "The address part of `sender`.", + "example": "no-reply@mail.example.com" + }, + "sender_kind": { + "type": "string", + "enum": [ + "customer", + "didit" + ], + "description": "`customer`: the email goes out from your application's verified sender domain (white label). `didit`: it goes out from Didit's own sender.", + "example": "customer" + }, + "subject": { + "type": "string", + "description": "The subject line exactly as it is sent.", + "example": "Your verification has been approved in Example App" + }, + "html": { + "type": "string", + "description": "The email's HTML exactly as it is sent, with its logo embedded as a data URL. Render it in a sandboxed frame, never directly in your application DOM.", + "example": "..." + }, + "text": { + "type": "string", + "description": "The email's words as plain text, as the person's Messages thread keeps them, for a compact preview (the HTML stays the email the person receives).", + "example": "Verification Approved\n\nYour identity has been successfully verified with Acme Pay. No further action is needed on your part." + }, + "language": { + "type": "string", + "description": "The language the email is written in. English (`en`) when the `email_language` you asked for is not offered.", + "example": "en" + }, + "will_be_sent": { + "type": "boolean", + "description": "Whether confirming the change with `send_email: true` sends this email. `false` when `not_sent_reason` is set.", + "example": true + }, + "not_sent_reason": { + "type": "string", + "nullable": true, + "enum": [ + "status_unchanged", + "notification_emails_disabled", + "no_recipient", + "sandbox", + null + ], + "description": "Why confirming would send no email, or `null` when it will be sent. `status_unchanged`: the session already has this status, so the update changes nothing and sends nothing. `notification_emails_disabled`: the session was created without `contact_details.send_notification_emails: true`, so no decision email goes to the address given in `contact_details`. `no_recipient`: no address is known for the person. `sandbox`: a sandbox session whose recipient has not opted into sandbox email delivery, so the send is only simulated.", + "example": null + }, + "preview_token": { + "type": "string", + "description": "Signed receipt for this exact email, valid for 15 minutes. Pass it as `email_preview_token` to [Update Session Status](/sessions-api/update-status). An empty string when there is no recipient.", + "example": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + } + }, + "examples": { + "Will Be Sent": { + "summary": "The Approved email goes to the address verified in the flow", + "value": { + "recipient": "person@example.com", + "recipient_source": "email_verification", + "sender": "Example App ", + "sender_name": "Example App", + "sender_email": "no-reply@mail.example.com", + "sender_kind": "customer", + "subject": "Your verification has been approved in Example App", + "html": "...", + "language": "en", + "will_be_sent": true, + "not_sent_reason": null, + "preview_token": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + }, + "No Recipient": { + "summary": "No address is known for the person", + "value": { + "recipient": "", + "recipient_source": "", + "sender": "Example App ", + "sender_name": "Example App", + "sender_email": "no-reply@mail.example.com", + "sender_kind": "customer", + "subject": "Your verification has been declined in Example App", + "html": "...", + "language": "en", + "will_be_sent": false, + "not_sent_reason": "no_recipient", + "preview_token": "" + } + } + } + } + } + }, + "400": { + "description": "The session is not in a status this endpoint accepts (`{\"detail\": ...}`), or the body is invalid (`{\"\": [\"\"]}`).", + "content": { + "application/json": { + "examples": { + "Wrong Current Status": { + "summary": "Session is not in a status this endpoint accepts", + "value": { + "detail": "Only sessions with status Approved, Declined, In Review, Kyc Expired, Abandoned, Resubmitted can be updated. Current status: In Progress" + } + }, + "Invalid new_status": { + "summary": "new_status is not Approved or Declined", + "value": { + "new_status": [ + "\"Resubmitted\" is not a valid choice." + ] + } + }, + "Missing new_status": { + "summary": "Required field omitted", + "value": { + "new_status": [ + "This field is required." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials, a caller without the `write:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no write access to decisions." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/decision-email-preview/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"new_status\": \"Approved\", \"email_language\": \"en\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.preview_decision_email(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n new_status=\"Approved\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.previewDecisionEmail(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", {\n new_status: \"Approved\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.PreviewDecisionEmail(ctx, \"3fa85f64-5717-4562-b3fc-2c963f66afa6\", didit.SessionsPreviewDecisionEmailParams{\n\tNewStatus: \"Approved\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsPreviewDecisionEmailParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().previewDecisionEmail(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", SessionsPreviewDecisionEmailParams.builder()\n .newStatus(SessionsPreviewDecisionEmailParams.NewStatus.of(\"Approved\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->previewDecisionEmail('3fa85f64-5717-4562-b3fc-2c963f66afa6', [\n 'new_status' => 'Approved',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.preview_decision_email(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n new_status: \"Approved\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.PreviewDecisionEmailAsync(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", new SessionsPreviewDecisionEmailParams\n{\n NewStatus = \"Approved\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "preview_decision_email", + "auth": "api_key", + "server": true + } + } + }, + "/v3/travel-rule/join/{token}/": { + "get": { + "summary": "Get a free-join invitation", + "description": "Public, unauthenticated. Shows the invitation a join link is for while it is open: who asked, the address it went to, and the directory values that will be prefilled. The token is the authentication.", + "operationId": "getTravelRuleJoinInvitation", + "x-didit-sdk": { + "resource": "travel_rule.join", + "method": "get_invitation", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The invitation token from the join link." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/join/{token}/" + } + ], + "responses": { + "200": { + "description": "The invitation.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleJoinInvitation" + } + } + } + }, + "404": { + "description": "No invitation has this link." + }, + "410": { + "description": "The invitation is no longer open: expired, already used, or withdrawn." + } + } + }, + "post": { + "summary": "Register a free Travel Rule account", + "description": "Public, unauthenticated. Creates a free Travel Rule account for a counterparty VASP that received a request. The email must be the address the invitation went to. A 6-character code is emailed to it; send it to the verify endpoint to finish. Registration attempts are rate limited per address and per IP.", + "operationId": "registerTravelRuleJoin", + "x-didit-sdk": { + "resource": "travel_rule.join", + "method": "register", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The invitation token from the join link." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleJoinRegisterRequest" + }, + "example": { + "email": "compliance@example.com", + "password": "a-long-passphrase" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/join/{token}/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"email\": \"compliance@example.com\", \"password\": \"a-long-passphrase\"}'" + } + ], + "responses": { + "202": { + "description": "The account was created and a code was emailed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Tells the counterparty to check their email for the verification code." + }, + "email": { + "type": "string", + "format": "email", + "description": "The address the code was sent to." + } + } + } + } + } + }, + "400": { + "description": "The email is not the invited address, or the password or address was refused." + }, + "404": { + "description": "No invitation has this link." + }, + "410": { + "description": "The invitation is no longer open: expired, already used, or withdrawn." + }, + "429": { + "description": "Too many attempts to join. The `X-RateLimit-*` headers name the budget that was spent." + } + } + } + }, + "/v3/travel-rule/join/{token}/verify/": { + "post": { + "summary": "Verify a free Travel Rule account", + "description": "Public, unauthenticated. Spends the emailed code, creates the free organization and answers with the credentials its application uses on the workspace endpoints. Receiving Travel Rule data is free.", + "operationId": "verifyTravelRuleJoin", + "x-didit-sdk": { + "resource": "travel_rule.join", + "method": "verify", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The invitation token from the join link." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleJoinVerifyRequest" + }, + "example": { + "email": "compliance@example.com", + "code": "123456" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/join/{token}/verify/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"email\": \"compliance@example.com\", \"code\": \"123456\"}'" + } + ], + "responses": { + "201": { + "description": "The organization and application credentials.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleJoinCredentials" + } + } + } + }, + "400": { + "description": "The code or email was refused." + }, + "404": { + "description": "No invitation has this link." + }, + "410": { + "description": "The invitation is no longer open: expired, already used, or withdrawn." + }, + "429": { + "description": "Too many attempts to join. The `X-RateLimit-*` headers name the budget that was spent." + } + } + } + }, + "/v3/travel-rule/workspace/": { + "get": { + "summary": "Get the free Travel Rule workspace", + "description": "Reads the free workspace of an application that joined free: directory claim, domain proof, billing and Didit network listing.", + "operationId": "getTravelRuleWorkspace", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "get", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/workspace/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.workspace.get()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.workspace.get();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.Workspace.Get(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().workspace().get();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->workspace->get();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.workspace.get\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Workspace.GetAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/claim/": { + "post": { + "summary": "Claim a directory entry", + "description": "Claims the directory entry this workspace is. When the work email is at a domain the entry lists, the claim is proved and applies at once: the VASP profile and Travel Rule settings are filled from the entry and Travel Rule is turned on, so the next transfer to it routes over the Didit network. Otherwise the claim waits for review (`pending_review`).", + "operationId": "claimTravelRuleWorkspace", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "claim", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspaceClaimRequest" + }, + "example": { + "vasp_id": "0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/claim/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vasp_id\": \"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.workspace.claim(\n vasp_id=\"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.workspace.claim({\n vasp_id: \"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.Workspace.Claim(ctx, didit.TravelRuleWorkspaceClaimRequest{\n\tVASPID: \"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleWorkspaceClaimRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().workspace().claim(TravelRuleWorkspaceClaimRequest.builder()\n .vaspId(\"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->workspace->claim([\n 'vasp_id' => '0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.workspace.claim(vasp_id: \"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Workspace.ClaimAsync(new TravelRuleWorkspaceClaimRequest\n{\n VaspId = \"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The workspace with its claim status.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "400": { + "description": "No directory entry has this id." + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/domain/code/": { + "post": { + "summary": "Email a domain verification code", + "description": "Emails a single-use code to the workspace's work address to prove it controls the email domain. Throttled, and a limited number of wrong attempts is accepted per code. Also returns the TXT record for the DNS alternative.", + "operationId": "sendTravelRuleWorkspaceDomainCode", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "send_domain_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/domain/code/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.workspace.send_domain_code()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.workspace.sendDomainCode();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.Workspace.SendDomainCode(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().workspace().sendDomainCode();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->workspace->sendDomainCode();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.workspace.send_domain_code\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Workspace.SendDomainCodeAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The domain, its status and the TXT record.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspaceDomainCodeResponse" + } + } + } + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/domain/confirm/": { + "post": { + "summary": "Confirm the domain verification code", + "description": "Spends the emailed code and marks the email domain verified.", + "operationId": "confirmTravelRuleWorkspaceDomain", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "confirm_domain", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspaceDomainConfirmRequest" + }, + "example": { + "code": "123456" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/domain/confirm/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"code\": \"123456\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.workspace.confirm_domain(\n code=\"123456\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.workspace.confirmDomain({\n code: \"123456\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.Workspace.ConfirmDomain(ctx, didit.TravelRuleWorkspaceDomainConfirmRequest{\n\tCode: \"123456\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleWorkspaceDomainConfirmRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().workspace().confirmDomain(TravelRuleWorkspaceDomainConfirmRequest.builder()\n .code(\"123456\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->workspace->confirmDomain([\n 'code' => '123456',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.workspace.confirm_domain(code: \"123456\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Workspace.ConfirmDomainAsync(new TravelRuleWorkspaceDomainConfirmRequest\n{\n Code = \"123456\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "400": { + "description": "The code is wrong, expired or already used." + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/domain/dns/": { + "post": { + "summary": "Verify the domain from its TXT record", + "description": "Checks for the `didit-tr-verify.` TXT record returned by the code endpoint and marks the domain verified when it matches. One of the two proofs, the emailed code or the TXT record, is enough.", + "operationId": "verifyTravelRuleWorkspaceDomainDns", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "verify_domain_dns", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/domain/dns/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.workspace.verify_domain_dns()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.workspace.verifyDomainDns();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.Workspace.VerifyDomainDNS(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().workspace().verifyDomainDns();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->workspace->verifyDomainDns();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.workspace.verify_domain_dns\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Workspace.VerifyDomainDnsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "400": { + "description": "The TXT record was not found or does not match." + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/billing/": { + "post": { + "summary": "Enable outbound billing", + "description": "Turns outbound Travel Rule billing on for the workspace. Receiving stays free. This cannot be turned back off through the API: contact Didit support.", + "operationId": "enableTravelRuleWorkspaceBilling", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "enable_billing", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspaceBillingRequest" + }, + "example": { + "enabled": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/billing/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"enabled\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.workspace.enable_billing(\n enabled=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.workspace.enableBilling({\n enabled: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.Workspace.EnableBilling(ctx, didit.TravelRuleWorkspaceBillingRequest{\n\tEnabled: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleWorkspaceBillingRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().workspace().enableBilling(TravelRuleWorkspaceBillingRequest.builder()\n .enabled(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->workspace->enableBilling([\n 'enabled' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.workspace.enable_billing(enabled: true)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Workspace.EnableBillingAsync(new TravelRuleWorkspaceBillingRequest\n{\n Enabled = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The workspace with `billing_enabled` true.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "400": { + "description": "`enabled` was false." + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/investigator/summary/": { + "get": { + "operationId": "investigator_summary", + "summary": "Investigator summary", + "description": "Stage, queue counts, reports prepared and decided today, 30-day agreement, prices and the autonomy level of each queue. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "stage": { + "type": "string" + }, + "last_report_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "prepared_today": { + "type": "integer" + }, + "ready_to_accept": { + "type": "integer" + }, + "open": { + "type": "integer" + }, + "decided_today": { + "type": "integer" + }, + "automation_rate_month": { + "type": "number", + "nullable": true + }, + "preparing": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "timezone", + "schema": { + "type": "string" + }, + "description": "IANA time zone (for example `Europe/Madrid`) that \"today\" counts from. UTC by default." + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_summary", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.retrieve_summary()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.retrieveSummary();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.RetrieveSummary(ctx, didit.InvestigatorRetrieveSummaryParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().retrieveSummary();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->retrieveSummary();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.retrieve_summary\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.RetrieveSummaryAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/queue/": { + "get": { + "operationId": "investigator_queue", + "summary": "List the Investigator queue", + "description": "Open reports, **Ready to accept** first, then by deadline, risk and wait. `counts` covers every kind in scope. `count` is capped at 100. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "counts": { + "type": "object", + "additionalProperties": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/InvestigatorQueueRow" + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "kind", + "schema": { + "type": "string" + }, + "description": "`session`, `business_session`, `transaction` or `case`, or several separated by commas." + }, + { + "in": "query", + "name": "queue", + "schema": { + "type": "string", + "enum": [ + "kyc", + "kyb", + "aml", + "transactions", + "cases" + ] + } + }, + { + "in": "query", + "name": "recommendation", + "schema": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ] + } + }, + { + "in": "query", + "name": "confidence", + "schema": { + "type": "string", + "enum": [ + "high", + "moderate", + "low" + ] + } + }, + { + "in": "query", + "name": "ready", + "schema": { + "type": "boolean" + } + }, + { + "in": "query", + "name": "search", + "schema": { + "type": "string" + }, + "description": "Name, company, case title, number, transaction ID or item ID." + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "list_queue", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.investigator.list_queue():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.investigator.listQueue()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Investigator.ListQueueAutoPaging(ctx, didit.InvestigatorListQueueParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.investigator().listQueue()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->listQueue() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.investigator.list_queue.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Investigator.ListQueueAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/investigator/runs/{run_id}/": { + "get": { + "operationId": "investigator_run_retrieve", + "summary": "Get an Investigator report", + "description": "The full report of one run. 404 when the run has no completed report, or its item was deleted. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorRunDetail" + } + } + } + }, + "404": { + "description": "Not found, or no report." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "path", + "name": "run_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_run", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.retrieve_run(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.retrieveRun(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.RetrieveRun(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().retrieveRun(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->retrieveRun('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.retrieve_run(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.RetrieveRunAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/runs/{run_id}/accept/": { + "post": { + "operationId": "investigator_run_accept", + "summary": "Accept a recommendation", + "description": "Applies the recommended action through the item's normal path, so your usual webhooks fire. A `narrative` makes it edited and accepted. Restricted keys need the `decisions` scope (write); transaction alerts also need `transactions` (write). Escalating a verification or transaction opens a case.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorDecision" + } + } + } + }, + "409": { + "description": "Already decided, report not ready, or a guardrail holds the item." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "path", + "name": "run_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorAcceptRequest" + }, + "example": { + "send_email": false + } + } + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "accept_run", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.accept_run(\n \"11111111-2222-3333-4444-555555555555\",\n send_email=False,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.acceptRun(\"11111111-2222-3333-4444-555555555555\", {\n send_email: false,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.AcceptRun(ctx, \"11111111-2222-3333-4444-555555555555\", didit.InvestigatorAcceptRequest{\n\tSendEmail: didit.Bool(false),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.InvestigatorAcceptRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().acceptRun(\"11111111-2222-3333-4444-555555555555\", InvestigatorAcceptRequest.builder()\n .sendEmail(false)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->acceptRun('11111111-2222-3333-4444-555555555555', [\n 'send_email' => false,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.accept_run(\n \"11111111-2222-3333-4444-555555555555\",\n send_email: false\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.AcceptRunAsync(\"11111111-2222-3333-4444-555555555555\", new InvestigatorAcceptRequest\n{\n SendEmail = false,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/runs/{run_id}/override/": { + "post": { + "operationId": "investigator_run_override", + "summary": "Override a recommendation", + "description": "Takes another action with a category and a reason. Also overrides a decision the Investigator took on its own while it still stands. Restricted keys need the `decisions` scope (write); transaction alerts also need `transactions` (write). Escalating a verification or transaction opens a case.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorDecision" + } + } + } + }, + "409": { + "description": "Already decided, or a guardrail holds the item." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "path", + "name": "run_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorOverrideRequest" + }, + "example": { + "action": "decline", + "category": "evidence_misread", + "reason": "The proof of address is older than three months." + } + } + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "override_run", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.override_run(\n \"11111111-2222-3333-4444-555555555555\",\n action=\"decline\",\n category=\"evidence_misread\",\n reason=\"The proof of address is older than three months.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.overrideRun(\"11111111-2222-3333-4444-555555555555\", {\n action: \"decline\",\n category: \"evidence_misread\",\n reason: \"The proof of address is older than three months.\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.OverrideRun(ctx, \"11111111-2222-3333-4444-555555555555\", didit.InvestigatorOverrideRequest{\n\tAction: \"decline\",\n\tCategory: \"evidence_misread\",\n\tReason: \"The proof of address is older than three months.\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.InvestigatorOverrideRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().overrideRun(\"11111111-2222-3333-4444-555555555555\", InvestigatorOverrideRequest.builder()\n .action(InvestigatorOverrideRequest.Action.of(\"decline\"))\n .category(InvestigatorOverrideRequest.Category.of(\"evidence_misread\"))\n .reason(\"The proof of address is older than three months.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->overrideRun('11111111-2222-3333-4444-555555555555', [\n 'action' => 'decline',\n 'category' => 'evidence_misread',\n 'reason' => 'The proof of address is older than three months.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.override_run(\n \"11111111-2222-3333-4444-555555555555\",\n action: \"decline\",\n category: \"evidence_misread\",\n reason: \"The proof of address is older than three months.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.OverrideRunAsync(\"11111111-2222-3333-4444-555555555555\", new InvestigatorOverrideRequest\n{\n Action = \"decline\",\n Category = \"evidence_misread\",\n Reason = \"The proof of address is older than three months.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/runs/batch-accept/": { + "post": { + "operationId": "investigator_batch_accept", + "summary": "Batch accept recommendations", + "description": "Accepts up to 50 reports marked Ready to accept. Each is its own decision: one that cannot be accepted is skipped with its reason. Restricted keys need the `decisions` scope (write); transaction alerts also need `transactions` (write). Escalating a verification or transaction opens a case.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorBatchAcceptResponse" + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorBatchAcceptRequest" + } + } + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "batch_accept_runs", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.batch_accept_runs(\n run_ids=[\"11111111-2222-3333-4444-555555555555\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.batchAcceptRuns({\n run_ids: [\"11111111-2222-3333-4444-555555555555\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.BatchAcceptRuns(ctx, didit.InvestigatorBatchAcceptRequest{\n\tRunIDs: []string{\"11111111-2222-3333-4444-555555555555\"},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.InvestigatorBatchAcceptRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().batchAcceptRuns(InvestigatorBatchAcceptRequest.builder()\n .runIds(List.of(\"11111111-2222-3333-4444-555555555555\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->batchAcceptRuns([\n 'run_ids' => ['11111111-2222-3333-4444-555555555555'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.batch_accept_runs(run_ids: [\"11111111-2222-3333-4444-555555555555\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.BatchAcceptRunsAsync(new InvestigatorBatchAcceptRequest\n{\n RunIds = new string[] { \"11111111-2222-3333-4444-555555555555\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/transactions/{transaction_id}/": { + "get": { + "operationId": "investigator_transaction_retrieve", + "summary": "Get a transaction's Investigator review", + "description": "The transaction's latest review, or `investigator: null` when it never had one. Restricted keys need the `decisions` and `transactions` scopes (read).", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "transaction_id": { + "type": "string", + "format": "uuid" + }, + "investigator": { + "$ref": "#/components/schemas/InvestigatorBlock" + } + } + } + } + } + }, + "404": { + "description": "Not this application's transaction." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "path", + "name": "transaction_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_transaction", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.retrieve_transaction(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.retrieveTransaction(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.RetrieveTransaction(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().retrieveTransaction(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->retrieveTransaction('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.retrieve_transaction(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.RetrieveTransactionAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/guardrails/": { + "get": { + "operationId": "investigator_guardrails", + "summary": "List the locked guardrails", + "description": "The five guardrails that apply to every queue. They cannot be switched off. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "enum": [ + "sanctions_to_person", + "no_tipping_off", + "no_protected_characteristics", + "never_files_reports", + "every_decision_logged" + ] + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "locked": { + "type": "boolean" + } + } + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "list_guardrails", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.list_guardrails()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.listGuardrails();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.ListGuardrails(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().listGuardrails();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->listGuardrails();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.list_guardrails\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.ListGuardrailsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/dashboard/": { + "get": { + "operationId": "investigator_dashboard", + "summary": "Investigator dashboard", + "description": "The last 30 days: reviews, decisions, automation rate, hours saved, spend, overrides, quality samples since go-live, per-queue and weekly figures. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "stage": { + "type": "string" + }, + "window_days": { + "type": "integer" + }, + "reviews": { + "type": "integer" + }, + "decided": { + "type": "integer" + }, + "automated": { + "type": "integer" + }, + "automation_rate": { + "type": "number", + "nullable": true + }, + "hours_saved": { + "type": "number", + "nullable": true + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_dashboard", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.retrieve_dashboard()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.retrieveDashboard();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.RetrieveDashboard(ctx, didit.InvestigatorRetrieveDashboardParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().retrieveDashboard();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->retrieveDashboard();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.retrieve_dashboard\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.RetrieveDashboardAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/performance/": { + "get": { + "operationId": "investigator_performance", + "summary": "Investigator performance", + "description": "Agreement with your team, volume, time saved, confidence calibration, per-queue results, override reasons and disagreements for a period. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "period": { + "type": "integer" + }, + "start": { + "type": "string", + "format": "date" + }, + "end": { + "type": "string", + "format": "date" + }, + "investigated": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "period", + "schema": { + "type": "string", + "enum": [ + "7", + "30", + "90" + ], + "default": "30" + }, + "description": "Days, ending today." + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_performance", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.retrieve_performance()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.retrievePerformance();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.RetrievePerformance(ctx, didit.InvestigatorRetrievePerformanceParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().retrievePerformance();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->retrievePerformance();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.retrieve_performance\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.RetrievePerformanceAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/activity/": { + "get": { + "operationId": "investigator_activity", + "summary": "List Investigator activity", + "description": "The append-only audit trail, newest first. A batch accept is one entry. Adds `counts` per group and `actors`. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "counts": { + "type": "object", + "additionalProperties": true + }, + "actors": { + "type": "array", + "items": {} + }, + "results": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true, + "description": "`id`, `at`, `kind`, `group`, `queue`, `run_id`, `item`, `actor`, `agent_version`, `data`, `report_version`, `steps`, `sources`, `batch`." + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "group", + "schema": { + "type": "string", + "enum": [ + "agent", + "decisions", + "overrides", + "autonomy" + ] + } + }, + { + "in": "query", + "name": "actor", + "schema": { + "type": "string" + }, + "description": "`agent` for the Investigator, or a person's email." + }, + { + "in": "query", + "name": "since", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "until", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "list_activity", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.investigator.list_activity():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.investigator.listActivity()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Investigator.ListActivityAutoPaging(ctx, didit.InvestigatorListActivityParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.investigator().listActivity()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->listActivity() as $item) {\n print_r($item);\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.investigator.list_activity.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Investigator.ListActivityAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/investigator/activity/export/": { + "get": { + "operationId": "investigator_activity_export", + "summary": "Export Investigator activity", + "description": "The same entries as CSV, one row per decision, newest first, up to 10,000 entries. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "text/csv": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "group", + "schema": { + "type": "string", + "enum": [ + "agent", + "decisions", + "overrides", + "autonomy" + ] + } + }, + { + "in": "query", + "name": "actor", + "schema": { + "type": "string" + }, + "description": "`agent` for the Investigator, or a person's email." + }, + { + "in": "query", + "name": "since", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "until", + "schema": { + "type": "string", + "format": "date-time" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "export_activity", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.investigator.export_activity()\nwith open(\"download.csv\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.investigator.exportActivity();\nawait writeFile(\"download.csv\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Investigator.ExportActivity(ctx, didit.InvestigatorExportActivityParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.csv\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.investigator().exportActivity()\n .writeTo(Path.of(\"download.csv\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->exportActivity();\n$content->saveTo('download.csv');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.investigator.export_activity\nFile.binwrite(\"download.csv\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Investigator.ExportActivityAsync();\nawait content.SaveToAsync(\"download.csv\");" + } + ] + } + }, + "/v3/investigator/settings/": { + "get": { + "operationId": "investigator_settings_retrieve", + "summary": "Get Investigator settings", + "description": "The stage (`none`, `shadow` or `live`) and the monthly spend limit with this month's spend. Restricted keys need the `applications` scope (read).", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorSettings" + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_settings", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.retrieve_settings()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.retrieveSettings();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.RetrieveSettings(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().retrieveSettings();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->retrieveSettings();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.retrieve_settings\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.RetrieveSettingsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "patch": { + "operationId": "investigator_settings_update", + "summary": "Update Investigator settings", + "description": "Change the stage, the monthly spend limit, or both. Moving past `none` needs a plan that includes the Investigator (Growth or higher); `live` also needs a contract that prices it, if you have one. Restricted keys need the `applications` scope (write).", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorSettings" + } + } + } + }, + "400": { + "description": "Neither field sent, or an invalid value." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorSettings" + }, + "example": { + "stage": "shadow", + "monthly_spend_limit": "500.00" + } + } + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "update_settings", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.update_settings(\n stage=\"shadow\",\n monthly_spend_limit=\"500.00\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.updateSettings({\n stage: \"shadow\",\n monthly_spend_limit: \"500.00\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.UpdateSettings(ctx, didit.InvestigatorSettingsParam{\n\tStage: didit.Some(didit.InvestigatorSettingsStage(\"shadow\")),\n\tMonthlySpendLimit: didit.String(\"500.00\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.InvestigatorSettings;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().updateSettings(InvestigatorSettings.builder()\n .stage(InvestigatorSettings.Stage.of(\"shadow\"))\n .monthlySpendLimit(\"500.00\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->updateSettings([\n 'stage' => 'shadow',\n 'monthly_spend_limit' => '500.00',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.update_settings(\n stage: \"shadow\",\n monthly_spend_limit: \"500.00\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.UpdateSettingsAsync(new InvestigatorSettings\n{\n Stage = new InvestigatorSettingsStage(\"shadow\"),\n MonthlySpendLimit = \"500.00\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/travel-rule/wallet-addresses/import/": { + "post": { + "summary": "Import wallet addresses in bulk", + "description": "Add up to 10,000 wallets to your Travel Rule address book in one call, as JSON or as a CSV file. A wallet can be sent by its address, by the SHA-256 of its lowercased address (`address_hash`) to keep the address private, or both. The import is idempotent: a wallet already in the book is reported as `existing` and left untouched, so the same file can be sent again. Each row is validated on its own, so one bad row never fails the others. A wallet imported by its hash only matches transfers to that address and can be proven with the wallet-ownership widget like any other entry.", + "operationId": "importTravelRuleWalletAddresses", + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "bulk_import", + "auth": "api_key", + "server": true + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "required": [ + "entries" + ], + "properties": { + "entries": { + "type": "array", + "minItems": 1, + "maxItems": 10000, + "items": { + "$ref": "#/components/schemas/WalletAddressImportRow" + } + } + } + }, + { + "type": "array", + "minItems": 1, + "maxItems": 10000, + "items": { + "$ref": "#/components/schemas/WalletAddressImportRow" + } + } + ] + }, + "example": { + "entries": [ + { + "address": "0xBeneficiaryWallet01", + "chain": "ETH", + "holder_name": "Ana Diaz", + "holder_vendor_data": "user-042" + }, + { + "address_hash": "4f7d2e1c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e", + "chain": "bitcoin", + "holder_vendor_data": "user-043" + }, + { + "address": "rExchangeSharedAddress01", + "chain": "XRP", + "memo": "104729", + "holder_name": "Ana Diaz" + } + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "file" + ], + "properties": { + "file": { + "type": "string", + "format": "binary", + "description": "A UTF-8 CSV file of at most 5 MB with a header row naming its columns (the row fields or their aliases). Empty cells count as not sent; columns the import does not read are ignored." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl (JSON)", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/wallet-addresses/import/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"entries\": [{\"address\": \"0xBeneficiaryWallet01\", \"chain\": \"ETH\", \"holder_name\": \"Ana Diaz\"}, {\"address\": \"rExchangeSharedAddress01\", \"chain\": \"XRP\", \"memo\": \"104729\"}]}'" + }, + { + "lang": "curl", + "label": "curl (CSV)", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/wallet-addresses/import/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'file=@wallets.csv;type=text/csv'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.wallet_addresses.bulk_import(\n body={\n \"entries\": [{\n \"address\": \"0xBeneficiaryWallet01\",\n \"chain\": \"ETH\",\n \"holder_name\": \"Ana Diaz\",\n \"holder_vendor_data\": \"user-042\",\n }, {\n \"address_hash\": \"4f7d2e1c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e\",\n \"chain\": \"bitcoin\",\n \"holder_vendor_data\": \"user-043\",\n }, {\n \"address\": \"rExchangeSharedAddress01\",\n \"chain\": \"XRP\",\n \"memo\": \"104729\",\n \"holder_name\": \"Ana Diaz\",\n }],\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.walletAddresses.bulkImport({\n file: await fileFromPath(\"wallet-addresses.csv\"),\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.WalletAddresses.BulkImport(ctx, didit.TravelRuleWalletAddressesBulkImportParams{\n\tFile: didit.Some(didit.FileFromPath(\"wallet-addresses.csv\")),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.types.TravelRuleWalletAddressesBulkImportParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().walletAddresses().bulkImport(TravelRuleWalletAddressesBulkImportParams.builder()\n .file(Path.of(\"wallet-addresses.csv\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletAddresses->bulkImport([\n 'file' => File::fromPath('wallet-addresses.csv'),\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.wallet_addresses.bulk_import(\n body: {\n entries: [{\n address: \"0xBeneficiaryWallet01\",\n chain: \"ETH\",\n holder_name: \"Ana Diaz\",\n holder_vendor_data: \"user-042\",\n }, {\n address_hash: \"4f7d2e1c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e\",\n chain: \"bitcoin\",\n holder_vendor_data: \"user-043\",\n }, {\n address: \"rExchangeSharedAddress01\",\n chain: \"XRP\",\n memo: \"104729\",\n holder_name: \"Ana Diaz\",\n }],\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.WalletAddresses.BulkImportAsync(new TravelRuleWalletAddressesBulkImportParams\n{\n File = FileUpload.FromPath(\"wallet-addresses.csv\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Every row was created, already in the book, or a repeat of an earlier row.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressImportReport" + }, + "example": { + "total": 3, + "created": 2, + "existing": 1, + "duplicate": 0, + "failed": 0, + "rows": [ + { + "row": 1, + "status": "existing", + "entry_id": "11111111-2222-3333-4444-555555555555", + "errors": {}, + "duplicate_of_row": null + }, + { + "row": 2, + "status": "created", + "entry_id": "22222222-3333-4444-5555-666666666666", + "errors": {}, + "duplicate_of_row": null + }, + { + "row": 3, + "status": "created", + "entry_id": "33333333-4444-5555-6666-777777777777", + "errors": {}, + "duplicate_of_row": null + } + ] + } + } + } + }, + "207": { + "description": "At least one row failed; the others were imported. Each failed row names its errors.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressImportReport" + }, + "example": { + "total": 2, + "created": 1, + "existing": 0, + "duplicate": 0, + "failed": 1, + "rows": [ + { + "row": 1, + "status": "created", + "entry_id": "44444444-5555-6666-7777-888888888888", + "errors": {}, + "duplicate_of_row": null + }, + { + "row": 2, + "status": "failed", + "entry_id": null, + "errors": { + "address": [ + "Send address, address_hash or both." + ] + }, + "duplicate_of_row": null + } + ] + } + } + } + }, + "400": { + "description": "The request itself cannot be read: both `entries` and a `file`, or neither; more than 10,000 rows; a file over 5 MB, not UTF-8 CSV, without a header row or without rows.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "file": [ + "At most 10000 rows per import." + ] + } + } + } + } + } + } + }, + "/v3/travel-rule/counterparty-contacts/": { + "get": { + "summary": "List counterparty contacts", + "description": "The compliance addresses the EMAIL rail may write to for your application: Didit's platform contacts plus your application's own private ones, bounced ones included, newest first. Paginated with `limit` (default 50) and `offset`; `count` stops at 100, so 100 means 100 or more.", + "operationId": "listTravelRuleCounterpartyContacts", + "x-didit-sdk": { + "resource": "travel_rule.counterparty_contacts", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "counterparty", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Only the contacts of this directory entry. Must be a UUID; an empty value means no filter." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1 + }, + "description": "Page size, default 50." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + }, + "description": "Rows to skip." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/travel-rule/counterparty-contacts/?counterparty={vasp_id}' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.travel_rule.counterparty_contacts.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.travelRule.counterpartyContacts.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.TravelRule.CounterpartyContacts.ListAutoPaging(ctx, didit.TravelRuleCounterpartyContactsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.travelRule().counterpartyContacts().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->counterpartyContacts->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.counterparty_contacts.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.TravelRule.CounterpartyContacts.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "The contacts.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleCounterpartyContact" + } + } + } + }, + "example": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "6c1f9b8e-2d4a-4f7e-8b3c-5a9d0e1f2a3b", + "counterparty": "0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91", + "counterparty_name": "Example Counterparty VASP", + "email": "compliance@counterparty.example", + "name": "Compliance desk", + "source": "application", + "scope": "application", + "created_by": "", + "verified_at": null, + "is_invalid": false, + "invalid_reason": "", + "invalid_detail": "", + "invalid_at": null, + "created_at": "2026-10-19T09:00:00Z" + } + ] + } + } + } + }, + "400": { + "description": "`counterparty` is not a UUID.", + "content": { + "application/json": { + "example": { + "counterparty": [ + "Must be a valid UUID." + ] + } + } + } + } + } + }, + "post": { + "summary": "Add a counterparty contact", + "description": "Adds a private compliance address for one directory entry. Only your application sees and uses it, and the EMAIL rail writes to it before any platform contact. Adding an address the entry already has among your own contacts returns that contact.", + "operationId": "createTravelRuleCounterpartyContact", + "x-didit-sdk": { + "resource": "travel_rule.counterparty_contacts", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyContactCreate" + }, + "example": { + "counterparty": "0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91", + "email": "compliance@counterparty.example", + "name": "Compliance desk" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/counterparty-contacts/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"counterparty\": \"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\", \"email\": \"compliance@counterparty.example\", \"name\": \"Compliance desk\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.counterparty_contacts.create(\n counterparty=\"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\",\n email=\"compliance@counterparty.example\",\n name=\"Compliance desk\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.counterpartyContacts.create({\n counterparty: \"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\",\n email: \"compliance@counterparty.example\",\n name: \"Compliance desk\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.CounterpartyContacts.Create(ctx, didit.TravelRuleCounterpartyContactCreate{\n\tCounterparty: \"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\",\n\tEmail: \"compliance@counterparty.example\",\n\tName: didit.String(\"Compliance desk\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleCounterpartyContactCreate;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().counterpartyContacts().create(TravelRuleCounterpartyContactCreate.builder()\n .counterparty(\"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\")\n .email(\"compliance@counterparty.example\")\n .name(\"Compliance desk\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->counterpartyContacts->create([\n 'counterparty' => '0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91',\n 'email' => 'compliance@counterparty.example',\n 'name' => 'Compliance desk',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.counterparty_contacts.create(\n counterparty: \"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\",\n email: \"compliance@counterparty.example\",\n name: \"Compliance desk\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.CounterpartyContacts.CreateAsync(new TravelRuleCounterpartyContactCreate\n{\n Counterparty = \"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\",\n Email = \"compliance@counterparty.example\",\n Name = \"Compliance desk\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The contact was added.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyContact" + }, + "example": { + "uuid": "6c1f9b8e-2d4a-4f7e-8b3c-5a9d0e1f2a3b", + "counterparty": "0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91", + "counterparty_name": "Example Counterparty VASP", + "email": "compliance@counterparty.example", + "name": "Compliance desk", + "source": "application", + "scope": "application", + "created_by": "", + "verified_at": null, + "is_invalid": false, + "invalid_reason": "", + "invalid_detail": "", + "invalid_at": null, + "created_at": "2026-10-19T09:00:00Z" + } + } + } + }, + "400": { + "description": "A field is missing or invalid, or no directory entry has this id.", + "content": { + "application/json": { + "example": { + "counterparty": [ + "No directory entry with this id." + ] + } + } + } + } + } + } + }, + "/v3/travel-rule/counterparty-contacts/{contact_uuid}/": { + "delete": { + "summary": "Delete a counterparty contact", + "description": "Deletes one of your application's own contacts. The EMAIL rail stops writing to it at once. Platform contacts cannot be deleted.", + "operationId": "deleteTravelRuleCounterpartyContact", + "x-didit-sdk": { + "resource": "travel_rule.counterparty_contacts", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "contact_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE https://verification.didit.me/v3/travel-rule/counterparty-contacts/{contact_uuid}/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.travel_rule.counterparty_contacts.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.travelRule.counterpartyContacts.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.TravelRule.CounterpartyContacts.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.travelRule().counterpartyContacts().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->counterpartyContacts->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.counterparty_contacts.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.TravelRule.CounterpartyContacts.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ], + "responses": { + "204": { + "description": "Deleted." + }, + "404": { + "description": "No contact of your application has this id. A platform contact answers 404 too." + } + } + } + }, + "/v3/travel-rule/transfers/": { + "get": { + "summary": "List Travel Rule transfers", + "description": "Your application's Travel Rule transfers, newest first, paginated with `limit` (default 50) and `offset`. Rows carry VASP names, wallets and memos only, never person data. Comma-separated values select several. An unknown value answers 400 naming the parameter, for example `{\"direction\": [\"Unknown value(s): inbound.\"]}`.", + "operationId": "listTravelRuleTransfers", + "x-didit-sdk": { + "resource": "travel_rule.transfers", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "direction", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "INBOUND, OUTBOUND, or both comma-separated. Case-sensitive." + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "One or more transfer statuses, comma-separated." + }, + { + "name": "rail", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "One or more of INTERNAL, TRP, GTR, TRUST, VERIFYVASP, SYGNA, EMAIL, TEST_VASP, comma-separated." + }, + { + "name": "kind", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "withdrawal, deposit or message, comma-separated." + }, + { + "name": "currency", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "The transaction's currency, case-insensitive." + }, + { + "name": "start_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date" + }, + "description": "Transfers created on or after this day (UTC), YYYY-MM-DD." + }, + { + "name": "end_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date" + }, + "description": "Transfers created on or before this day (UTC), YYYY-MM-DD." + }, + { + "name": "counterparty", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The `id` of a directory entry or Didit network member from GET /v3/travel-rule/vasps/: your transfers with that counterparty. A value that is not a valid id answers 400." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1 + }, + "description": "Page size, default 50." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + }, + "description": "Rows to skip." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/travel-rule/transfers/?direction=OUTBOUND&status=AWAITING_COUNTERPARTY' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.travel_rule.transfers.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.travelRule.transfers.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.TravelRule.Transfers.ListAutoPaging(ctx, didit.TravelRuleTransfersListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.travelRule().transfers().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->transfers->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.transfers.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.TravelRule.Transfers.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "A page of transfers.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleTransferRow" + } + } + } + }, + "example": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "id": "5b0f6c2e-2a4d-4f0e-9a41-0c3f9a7e1d22", + "transaction_id": "a3c1e8f4-7b62-4d1a-8e0b-2f9d6c4b7a10", + "txn_id": "wd-2026-10-19-0042", + "txn_date": "2026-10-19T08:41:12Z", + "kind": "withdrawal", + "direction": "OUTBOUND", + "status": "AWAITING_COUNTERPARTY", + "rail": "TRP", + "asset": "ETH", + "amount": "1.25", + "originator_vasp": "Origin CASP SL", + "beneficiary_vasp": "Example Counterparty VASP", + "originator_wallet": "0x77ccff0cc624a53cd8b8d8703ced826710211ec7", + "beneficiary_wallet": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb", + "originator_memo": "", + "beneficiary_memo": "", + "originator_wallet_type": "vasp", + "beneficiary_wallet_type": "vasp", + "matched_transaction_id": null, + "deadline_at": "2026-10-21T08:41:13Z", + "created_at": "2026-10-19T08:41:13Z" + } + ] + } + } + } + }, + "400": { + "description": "A filter value is not valid.", + "content": { + "application/json": { + "example": { + "start_date": [ + "Use the YYYY-MM-DD format." + ] + } + } + } + } + } + } + }, + "/v3/session/imports/providers/": { + "get": { + "summary": "List migration providers", + "description": "The 15 providers you can migrate verifications from: what each needs to connect, whether its API can list records, the permissions its key needs and its default status mapping.", + "operationId": "get_v3_session_imports_providers_list", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "list_providers", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "200": { + "description": "Migration providers.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "providers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImportProvider" + } + }, + "mapping_targets": { + "type": "array", + "items": { + "type": "string" + } + }, + "skipped_statuses": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.list_providers()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.listProviders();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.ListProviders(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().listProviders();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->listProviders();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.list_providers\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.ListProvidersAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/providers/check/": { + "post": { + "summary": "Check a provider key", + "description": "Read the provider with your key before creating a migration. Nothing is stored.", + "operationId": "post_v3_session_imports_providers_check", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "check_provider", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "provider": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ], + "description": "Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`." + }, + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "options": { + "type": "object", + "properties": { + "region": { + "type": "string", + "description": "The provider region, one of its `regions` (Onfido: `eu`, `us`, `ca`)." + } + } + }, + "record_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "For `api_ids` providers: the first record ID of your export." + } + }, + "required": [ + "provider", + "credentials" + ] + }, + "example": { + "provider": "sumsub", + "credentials": { + "app_token": "YOUR_PROVIDER_APP_TOKEN", + "secret_key": "YOUR_PROVIDER_SECRET_KEY" + } + } + } + } + }, + "responses": { + "200": { + "description": "The key can read records.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean" + }, + "provider": { + "type": "string" + }, + "credential_hint": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "400": { + "description": "The provider refused the key: `code`, `detail`, `missing_permissions`, `suggested_region`." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.check_provider(\n provider=\"sumsub\",\n credentials={\"app_token\": \"YOUR_PROVIDER_APP_TOKEN\", \"secret_key\": \"YOUR_PROVIDER_SECRET_KEY\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.checkProvider({\n provider: \"sumsub\",\n credentials: { app_token: \"YOUR_PROVIDER_APP_TOKEN\", secret_key: \"YOUR_PROVIDER_SECRET_KEY\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.CheckProvider(ctx, didit.SessionsImportsCheckProviderParams{\n\tProvider: \"sumsub\",\n\tCredentials: map[string]string{\"app_token\": \"YOUR_PROVIDER_APP_TOKEN\", \"secret_key\": \"YOUR_PROVIDER_SECRET_KEY\"},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SessionsImportsCheckProviderParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().checkProvider(SessionsImportsCheckProviderParams.builder()\n .provider(SessionsImportsCheckProviderParams.Provider.of(\"sumsub\"))\n .credentials(Map.of(\"app_token\", \"YOUR_PROVIDER_APP_TOKEN\", \"secret_key\", \"YOUR_PROVIDER_SECRET_KEY\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->checkProvider([\n 'provider' => 'sumsub',\n 'credentials' => ['app_token' => 'YOUR_PROVIDER_APP_TOKEN', 'secret_key' => 'YOUR_PROVIDER_SECRET_KEY'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.check_provider(\n provider: \"sumsub\",\n credentials: { app_token: \"YOUR_PROVIDER_APP_TOKEN\", secret_key: \"YOUR_PROVIDER_SECRET_KEY\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.CheckProviderAsync(new SessionsImportsCheckProviderParams\n{\n Provider = \"sumsub\",\n Credentials = new Dictionary { [\"app_token\"] = \"YOUR_PROVIDER_APP_TOKEN\", [\"secret_key\"] = \"YOUR_PROVIDER_SECRET_KEY\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/{importId}/start/": { + "post": { + "summary": "Start a dry run", + "description": "Start a migration whose dry run finished (`READY`): the same job imports for real, with the status mapping and face templates choice as sent now.", + "operationId": "post_v3_session_imports_start", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "start", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + }, + "description": "Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported." + }, + "face_templates": { + "type": "boolean", + "default": false, + "description": "Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`." + }, + "lawful_basis_confirmed": { + "type": "boolean", + "default": false, + "description": "Confirms you have a lawful basis to share these selfies with Didit as processor." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.start(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.start(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Start(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsImportsStartParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().start(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->start('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.start(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.StartAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/{importId}/pause/": { + "post": { + "summary": "Pause an import job", + "description": "Pause a queued or running import.", + "operationId": "post_v3_session_imports_pause", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "pause", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.pause(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.pause(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Pause(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().pause(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->pause('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.pause(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.PauseAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/{importId}/resume/": { + "post": { + "summary": "Resume an import job", + "description": "Resume an import you paused. A job paused by the provider's rate limit resumes by itself.", + "operationId": "post_v3_session_imports_resume", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "resume", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.resume(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.resume(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Resume(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().resume(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->resume('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.resume(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.ResumeAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/{importId}/cancel/": { + "post": { + "summary": "Cancel an import job", + "description": "Cancel an import. What was imported stays; the uploaded file and the key are erased.", + "operationId": "post_v3_session_imports_cancel", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "cancel", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.cancel(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.cancel(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Cancel(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().cancel(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->cancel('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.cancel(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.CancelAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/{importId}/retry/": { + "post": { + "summary": "Retry a migration", + "description": "Run a migration that ended again as a new job, with a new key (the old one was erased). It keeps the provider, workflow, region, status mapping and face templates choice; everything already in Didit is skipped.", + "operationId": "post_v3_session_imports_retry", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "retry", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": false, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "file": { + "type": "string", + "format": "binary", + "description": "The record IDs or export file again, for `api_ids` and `export_file` providers." + } + } + }, + "example": { + "credentials": { + "app_token": "YOUR_PROVIDER_APP_TOKEN", + "secret_key": "YOUR_PROVIDER_SECRET_KEY" + } + } + } + } + }, + "responses": { + "201": { + "description": "The new import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "Not a migration, not ended, or the provider refused the key." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.retry(\n \"11111111-2222-3333-4444-555555555555\",\n credentials={\"app_token\": \"YOUR_PROVIDER_APP_TOKEN\", \"secret_key\": \"YOUR_PROVIDER_SECRET_KEY\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.retry(\"11111111-2222-3333-4444-555555555555\", {\n credentials: { app_token: \"YOUR_PROVIDER_APP_TOKEN\", secret_key: \"YOUR_PROVIDER_SECRET_KEY\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Retry(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsImportsRetryParams{\n\tCredentials: didit.Some(map[string]string{\"app_token\": \"YOUR_PROVIDER_APP_TOKEN\", \"secret_key\": \"YOUR_PROVIDER_SECRET_KEY\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SessionsImportsRetryParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().retry(\"11111111-2222-3333-4444-555555555555\", SessionsImportsRetryParams.builder()\n .credentials(Map.of(\"app_token\", \"YOUR_PROVIDER_APP_TOKEN\", \"secret_key\", \"YOUR_PROVIDER_SECRET_KEY\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->retry('11111111-2222-3333-4444-555555555555', [\n 'credentials' => ['app_token' => 'YOUR_PROVIDER_APP_TOKEN', 'secret_key' => 'YOUR_PROVIDER_SECRET_KEY'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.retry(\n \"11111111-2222-3333-4444-555555555555\",\n credentials: { app_token: \"YOUR_PROVIDER_APP_TOKEN\", secret_key: \"YOUR_PROVIDER_SECRET_KEY\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.RetryAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsImportsRetryParams\n{\n Credentials = new Dictionary { [\"app_token\"] = \"YOUR_PROVIDER_APP_TOKEN\", [\"secret_key\"] = \"YOUR_PROVIDER_SECRET_KEY\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/import-jobs/": { + "get": { + "summary": "List import jobs", + "description": "Every import of the application in one shape: verification imports and migrations, people and businesses.", + "operationId": "get_v3_import_jobs_list", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "list_jobs", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "scope", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "all", + "sessions", + "users", + "businesses" + ], + "default": "all" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + } + } + ], + "responses": { + "200": { + "description": "Import jobs, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImportJobSummary" + } + }, + "counts": { + "type": "object", + "properties": { + "all": { + "type": "integer" + }, + "running_and_waiting": { + "type": "integer" + }, + "needs_attention": { + "type": "integer" + }, + "done": { + "type": "integer" + } + } + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.list_jobs()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.listJobs();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.ListJobs(ctx, didit.SessionsImportsListJobsParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().listJobs();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->listJobs();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.list_jobs\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.ListJobsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/import-jobs/active/": { + "get": { + "summary": "List active import jobs", + "description": "What is running or waiting, plus what just ended, for a progress indicator.", + "operationId": "get_v3_import_jobs_active", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "list_active_jobs", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "200": { + "description": "Active import jobs.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImportJobSummary" + } + }, + "running": { + "type": "integer" + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.list_active_jobs()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.listActiveJobs();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.ListActiveJobs(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().listActiveJobs();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->listActiveJobs();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.list_active_jobs\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.ListActiveJobsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/transactions/delete/": { + "delete": { + "summary": "Batch delete transactions", + "description": "Delete one or more transactions permanently. Select them with `transaction_uuids`, or delete every transaction in the application with `delete_all: true`.\n\nA deleted transaction goes with the records that are only its own: its parties, tags, notes, payment method links, documents and every stored version of their files, rule runs, alerts, provider results, network snapshot and Travel Rule data. Cases, people and companies stay; their link to the deleted transaction is cleared. Draft regulatory reports stop listing it and are rebuilt without it. Deletion is permanent and irreversible; there is no undo endpoint.\n\nIf any selected transaction is listed in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each transaction and the reports that list it. Delete those reports, then repeat the request.", + "operationId": "batch_delete_transactions", + "x-didit-sdk": { + "resource": "transactions", + "method": "batch_delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "oneOf": [ + { + "title": "Selected transactions", + "required": [ + "transaction_uuids" + ], + "properties": { + "transaction_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit transaction UUIDs to delete. Required unless `delete_all` is true.", + "minItems": 1 + }, + "delete_all": { + "type": "boolean", + "enum": [ + false + ], + "default": false, + "description": "Leave out or send `false` when you select transactions with `transaction_uuids`." + } + } + }, + { + "title": "All transactions", + "required": [ + "delete_all" + ], + "properties": { + "delete_all": { + "type": "boolean", + "enum": [ + true + ], + "description": "Delete every transaction in the application. `transaction_uuids` is ignored." + }, + "transaction_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Ignored when `delete_all` is true." + } + } + } + ] + }, + "examples": { + "Selected transactions": { + "value": { + "transaction_uuids": [ + "abcdef12-3456-7890-abcd-ef1234567890", + "12345678-90ab-cdef-1234-567890abcdef" + ] + } + }, + "All transactions": { + "value": { + "delete_all": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl (selected transactions)", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/transactions/delete/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"transaction_uuids\": [\"abcdef12-3456-7890-abcd-ef1234567890\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.transactions.batch_delete(\n body={\n \"transaction_uuids\": [\"abcdef12-3456-7890-abcd-ef1234567890\", \"12345678-90ab-cdef-1234-567890abcdef\"],\n },\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.transactions.batchDelete({\n transaction_uuids: [\"abcdef12-3456-7890-abcd-ef1234567890\", \"12345678-90ab-cdef-1234-567890abcdef\"],\n});" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.TransactionsBatchDeleteParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.transactions().batchDelete(TransactionsBatchDeleteParams.builder()\n .putAdditionalProperty(\"transaction_uuids\", List.of(\"abcdef12-3456-7890-abcd-ef1234567890\", \"12345678-90ab-cdef-1234-567890abcdef\"))\n .build());" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->batchDelete([\n 'transaction_uuids' => ['abcdef12-3456-7890-abcd-ef1234567890', '12345678-90ab-cdef-1234-567890abcdef'],\n]);" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.transactions.batch_delete(\n body: {\n transaction_uuids: [\"abcdef12-3456-7890-abcd-ef1234567890\", \"12345678-90ab-cdef-1234-567890abcdef\"],\n }\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Transactions.BatchDeleteAsync(new TransactionsBatchDeleteParams\n{\n TransactionUuids = new string[] { \"abcdef12-3456-7890-abcd-ef1234567890\", \"12345678-90ab-cdef-1234-567890abcdef\" },\n});" + } + ], + "responses": { + "204": { + "description": "Selected transactions deleted." + }, + "400": { + "description": "No transaction selector was supplied.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "transaction_uuids": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "example": { + "transaction_uuids": [ + "This field is required unless delete_all is true." + ] + } + } + } + }, + "403": { + "description": "The API key cannot delete transactions. The endpoint requires `write:transactions`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "409": { + "description": "At least one selected transaction is listed in a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/travel-rule/networks/": { + "get": { + "summary": "Get Travel Rule networks", + "description": "Read, for this application, every network's true state: whether it sends through your own membership or through Didit's, and why it cannot yet. Also returns the due-diligence questionnaire and your participants. Reading creates nothing.", + "operationId": "getTravelRuleNetworks", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/networks/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.networks.retrieve()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.networks.retrieve();\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().networks().retrieve();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->networks->retrieve();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.networks.retrieve\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Networks.RetrieveAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Network states, questionnaire and participants.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworksOverview" + }, + "examples": { + "Before the questionnaire": { + "value": { + "platform_networks_enabled": false, + "questionnaire": { + "status": "draft", + "answers": {}, + "submitted_at": null, + "reviewed_at": null, + "review_note": "", + "history": [], + "updated_at": null + }, + "participants": [], + "networks": { + "gtr": { + "rail": "GTR", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "trust": { + "rail": "TRUST", + "mode": "unavailable", + "state": "unavailable", + "reason": "own_membership_only", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "verifyvasp": { + "rail": "VERIFYVASP", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "sygna": { + "rail": "SYGNA", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + } + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + } + } + } + }, + "/v3/travel-rule/networks/questionnaire/": { + "get": { + "summary": "Get the network questionnaire", + "description": "Read Didit's network due-diligence questionnaire for this application, its status and Didit's review note. An application that never saved one reads an empty draft.", + "operationId": "getTravelRuleNetworkQuestionnaire", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "retrieve_questionnaire", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/networks/questionnaire/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.networks.retrieve_questionnaire()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.networks.retrieveQuestionnaire();\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().networks().retrieveQuestionnaire();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->networks->retrieveQuestionnaire();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.networks.retrieve_questionnaire\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Networks.RetrieveQuestionnaireAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The questionnaire.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaire" + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + } + } + }, + "put": { + "summary": "Save network questionnaire answers", + "description": "Save answers to the questionnaire while it is draft or changes_requested. Saves are partial: answers you do not send are kept.", + "operationId": "updateTravelRuleNetworkQuestionnaire", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "update_questionnaire", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PUT https://verification.didit.me/v3/travel-rule/networks/questionnaire/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"legal_name\": \"Example VASP Ltd\", \"incorporation_country\": \"ES\", \"aml_ctf_policy_attested\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.networks.update_questionnaire(\n legal_name=\"Example VASP Ltd\",\n incorporation_country=\"ES\",\n aml_ctf_policy_attested=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.networks.updateQuestionnaire({\n legal_name: \"Example VASP Ltd\",\n incorporation_country: \"ES\",\n aml_ctf_policy_attested: true,\n});\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleNetworkQuestionnaireAnswers;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().networks().updateQuestionnaire(TravelRuleNetworkQuestionnaireAnswers.builder()\n .legalName(\"Example VASP Ltd\")\n .incorporationCountry(\"ES\")\n .amlCtfPolicyAttested(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->networks->updateQuestionnaire([\n 'legal_name' => 'Example VASP Ltd',\n 'incorporation_country' => 'ES',\n 'aml_ctf_policy_attested' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.networks.update_questionnaire(\n legal_name: \"Example VASP Ltd\",\n incorporation_country: \"ES\",\n aml_ctf_policy_attested: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Networks.UpdateQuestionnaireAsync(new TravelRuleNetworkQuestionnaireAnswers\n{\n LegalName = \"Example VASP Ltd\",\n IncorporationCountry = \"ES\",\n AmlCtfPolicyAttested = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaireAnswers" + }, + "example": { + "legal_name": "Example VASP Ltd", + "incorporation_country": "ES", + "aml_ctf_policy_attested": true + } + } + } + }, + "responses": { + "200": { + "description": "The questionnaire with the merged answers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaire" + } + } + } + }, + "400": { + "description": "Validation error, keyed by field." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Network access through Didit's membership is not open yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_platform_networks_disabled" + ] + } + } + } + } + } + }, + "409": { + "description": "The questionnaire is with Didit or decided: it can only change while draft or changes_requested.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_questionnaire_not_editable" + ] + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/networks/questionnaire/submit/": { + "post": { + "summary": "Submit the network questionnaire", + "description": "Send the saved questionnaire to Didit's review. The saved answers must be complete: every field except lei and data_protection_contact_email, with the three attestations true. Didit approves it, asks for changes (edit and submit again) or rejects it. Networks you requested while it was pending are handed to their onboarding when it is approved.", + "operationId": "submitTravelRuleNetworkQuestionnaire", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "submit_questionnaire", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/networks/questionnaire/submit/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.networks.submit_questionnaire()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.networks.submitQuestionnaire();\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().networks().submitQuestionnaire();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->networks->submitQuestionnaire();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.networks.submit_questionnaire\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Networks.SubmitQuestionnaireAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The submitted questionnaire.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaire" + } + } + } + }, + "400": { + "description": "The saved answers are incomplete or invalid, keyed by field." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Network access through Didit's membership is not open yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_platform_networks_disabled" + ] + } + } + } + } + } + }, + "409": { + "description": "The questionnaire is with Didit or decided: it can only change while draft or changes_requested.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_questionnaire_not_editable" + ] + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/networks/{network}/request/": { + "post": { + "summary": "Request a Travel Rule network", + "description": "Ask for this application's own participant on a network Didit's membership carries (GTR or Sygna). Without an approved questionnaire the participant waits for it (questionnaire_pending); with one it goes to the network's onboarding (submitted_to_network) and becomes active once the network gives it a code. Idempotent: a pending, onboarding or live participant is returned unchanged. If the network refused the registration (last_error is set), requesting again retries. TRUST and VerifyVASP are reachable only through your own membership: TRUST admits members per VASP, and a VerifyVASP Enclave serves one VASP id.", + "operationId": "requestTravelRuleNetworkAccess", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "request_access", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "network", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "gtr", + "sygna" + ] + }, + "description": "The network to join through Didit's membership." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/networks/gtr/request/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.networks.request_access(\n \"your-network\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.networks.requestAccess(\"your-network\");\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().networks().requestAccess(\"your-network\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->networks->requestAccess('your-network');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.networks.request_access(\"your-network\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Networks.RequestAccessAsync(\"your-network\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Your participant on the network.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkParticipant" + } + } + } + }, + "400": { + "description": "Unknown network, a network Didit does not carry (connect your own membership), or a sandbox application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_network_own_membership_only", + "travel_rule_network_sandbox_application" + ] + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Network access through Didit's membership is not open yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_platform_networks_disabled" + ] + } + } + } + } + } + } + } + } + }, + "/v3/address-validation/": { + "post": { + "summary": "Validate an address", + "description": "Confirm that an address exists and get it back normalised: its delivery lines, components and coordinates. It confirms the address only; it never links the address to a person.\n\n**Request.** Send the address on one line (`address`) or as structured fields (`address_line_1` and, optionally, `address_line_2`, `city`, `region`, `postal_code`), never both, and always its `country` (ISO 3166-1 alpha-2 or alpha-3).\n\n**Result.** `address_validation.status` is `verified` (confirmed to the building), `partially_verified` (confirmed only to the street or above), `not_verified` (only the region or city matched, or nothing did) or `unavailable` (the validation could not run). HTTP is `200` for every completed request, `unavailable` included; retry an `unavailable` result later.\n\n**Billing.** A live call costs $0.10 per validated address. An `unavailable` result is not charged. Nothing is stored apart from the usage record: no session is created and the result is returned only in this response.\n\n**Sandbox.** Sandbox keys validate the request and always return a deterministic `verified` result that echoes the address sent, without consuming credits.\n\n**Authentication.** Send your application's API key in `x-api-key`. Missing or invalid credentials return `403` - this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests per minute per API key across POST/PATCH/DELETE endpoints (transaction creates and event ingestion have a separate budget of 600).", + "operationId": "post_v3_address_validation", + "x-didit-sdk": { + "resource": "address_validation", + "method": "validate", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/address-validation/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"address_line_1\": \"1600 Amphitheatre Pkwy\",\n \"city\": \"Mountain View\",\n \"region\": \"CA\",\n \"postal_code\": \"94043\",\n \"country\": \"US\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.address_validation.validate(\n address_line_1=\"1600 Amphitheatre Pkwy\",\n city=\"Mountain View\",\n region=\"CA\",\n postal_code=\"94043\",\n country=\"US\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.addressValidation.validate({\n address_line_1: \"1600 Amphitheatre Pkwy\",\n city: \"Mountain View\",\n region: \"CA\",\n postal_code: \"94043\",\n country: \"US\",\n});\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.AddressValidationRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.addressValidation().validate(AddressValidationRequest.builder()\n .addressLine1(\"1600 Amphitheatre Pkwy\")\n .city(\"Mountain View\")\n .region(\"CA\")\n .postalCode(\"94043\")\n .country(\"US\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "addressValidation->validate([\n 'address_line_1' => '1600 Amphitheatre Pkwy',\n 'city' => 'Mountain View',\n 'region' => 'CA',\n 'postal_code' => '94043',\n 'country' => 'US',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.address_validation.validate(\n address_line_1: \"1600 Amphitheatre Pkwy\",\n city: \"Mountain View\",\n region: \"CA\",\n postal_code: \"94043\",\n country: \"US\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.AddressValidation.ValidateAsync(new AddressValidationRequest\n{\n AddressLine1 = \"1600 Amphitheatre Pkwy\",\n City = \"Mountain View\",\n Region = \"CA\",\n PostalCode = \"94043\",\n Country = \"US\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddressValidationRequest" + }, + "examples": { + "Structured": { + "summary": "Structured address fields", + "value": { + "address_line_1": "1600 Amphitheatre Pkwy", + "city": "Mountain View", + "region": "CA", + "postal_code": "94043", + "country": "US" + } + }, + "One line": { + "summary": "The whole address on one line", + "value": { + "address": "Paseo de la Castellana 259, 28046 Madrid", + "country": "ES" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The validation completed. Read `address_validation.status`; HTTP is always `200` for a completed request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddressValidationResponse" + }, + "examples": { + "Verified": { + "summary": "A real address, confirmed to the building", + "value": { + "request_id": "6f7d2c1e-1c5a-4a1e-9b8f-2a1f0f4b9c3d", + "address_validation": { + "status": "verified", + "verification_level": "premise", + "address": { + "address_line_1": "1600 Amphitheatre Pkwy", + "address_line_2": null, + "city": "Mountain View", + "region": "CA", + "postal_code": "94043-1351", + "country": "US" + }, + "formatted_address": "1600 Amphitheatre Pkwy, Mountain View CA 94043-1351", + "latitude": 37.422022, + "longitude": -122.08429 + }, + "created_at": "2026-10-27T11:20:00.000000+00:00" + } + }, + "Not verified": { + "summary": "An invented street: only the city matched", + "value": { + "request_id": "0b9e3f4a-6c2d-4e8b-a1f7-5d3c2b1a0e9f", + "address_validation": { + "status": "not_verified", + "verification_level": "locality", + "address": { + "address_line_1": "Calle Inventada Que No Existe 99999 28046", + "address_line_2": null, + "city": "Madrid", + "region": "Madrid", + "postal_code": null, + "country": "ES" + }, + "formatted_address": "Calle Inventada Que No Existe 99999 28046, Madrid, MADRID, SPAIN", + "latitude": 40.42603054, + "longitude": -3.68645331 + }, + "created_at": "2026-10-27T11:21:00.000000+00:00" + } + }, + "Unavailable": { + "summary": "The validation could not run; not charged", + "value": { + "request_id": "3c1b2a0f-9e8d-4c7b-b6a5-1f0e9d8c7b6a", + "address_validation": { + "status": "unavailable", + "verification_level": null, + "address": null, + "formatted_address": null, + "latitude": null, + "longitude": null + }, + "created_at": "2026-10-27T11:22:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/AddressValidationValidationError" + }, + "403": { + "$ref": "#/components/responses/AddressValidationPermissionDenied" + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + } + } + }, + "/v3/travel-rule/wallet-proofs/": { + "get": { + "summary": "List screenshot wallet proofs to review", + "description": "Your review queue for screenshot wallet-ownership proofs. A screenshot is the one proof Didit cannot check itself: once the end user uploads it from the widget it waits PENDING until you approve or reject it with POST /v3/travel-rule/wallet-proofs/{proof_uuid}/decision/. Oldest first, so the queue is worked from the proof that has waited longest. Only uploaded screenshots appear, never a proof whose end user has not uploaded a file yet, and never one on a wallet you deleted from your address book (Didit closes those as OWNERSHIP_CHANGED).", + "operationId": "listTravelRuleWalletProofs", + "x-didit-sdk": { + "resource": "travel_rule.wallet_proofs", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Which proofs to list. Defaults to PENDING, the ones still to decide.", + "schema": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ], + "default": "PENDING" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/wallet-proofs/?status=PENDING \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.travel_rule.wallet_proofs.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.travelRule.walletProofs.list()) {\n console.log(item);\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.travelRule().walletProofs().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletProofs->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.wallet_proofs.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.TravelRule.WalletProofs.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated screenshot proofs. count is exact up to 100 and capped at 100 above it.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleWalletProofReview" + } + } + } + } + } + } + }, + "400": { + "description": "Unknown status." + }, + "401": { + "description": "Missing or invalid x-api-key." + }, + "403": { + "description": "The caller lacks the transactions permission." + } + } + } + }, + "/v3/travel-rule/wallet-proofs/{proof_uuid}/decision/": { + "post": { + "summary": "Approve or reject a screenshot wallet proof", + "description": "Decide a screenshot proof from your review queue. approve verifies the proof and the address book entry and applies the ownership to the transfer the widget session was minted for, exactly as a verified signature does. A screenshot never confirms a transfer that another Didit customer sent over the INTERNAL rail: that takes a signed message or a test deposit. reject closes the proof as REJECTED with failure_reason REVIEW_REJECTED; the end user can start a new proof from the widget. The note is kept with the proof.", + "operationId": "decideTravelRuleWalletProof", + "x-didit-sdk": { + "resource": "travel_rule.wallet_proofs", + "method": "decide", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "proof_uuid", + "in": "path", + "required": true, + "description": "proof_id from the review queue.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWalletProofDecision" + }, + "example": { + "decision": "approve", + "note": "Address matches the wallet app." + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/wallet-proofs/{proof_uuid}/decision/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"decision\": \"approve\", \"note\": \"Address matches the wallet app.\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.wallet_proofs.decide(\n \"11111111-2222-3333-4444-555555555555\",\n decision=\"approve\",\n note=\"Address matches the wallet app.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.walletProofs.decide(\"11111111-2222-3333-4444-555555555555\", {\n decision: \"approve\",\n note: \"Address matches the wallet app.\",\n});\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleWalletProofDecision;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().walletProofs().decide(\"11111111-2222-3333-4444-555555555555\", TravelRuleWalletProofDecision.builder()\n .decision(TravelRuleWalletProofDecision.Decision.of(\"approve\"))\n .note(\"Address matches the wallet app.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletProofs->decide('11111111-2222-3333-4444-555555555555', [\n 'decision' => 'approve',\n 'note' => 'Address matches the wallet app.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.wallet_proofs.decide(\n \"11111111-2222-3333-4444-555555555555\",\n decision: \"approve\",\n note: \"Address matches the wallet app.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.WalletProofs.DecideAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleWalletProofDecision\n{\n Decision = \"approve\",\n Note = \"Address matches the wallet app.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The decided proof.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWalletProofReview" + } + } + } + }, + "400": { + "description": "Invalid decision, or a note longer than 512 characters." + }, + "401": { + "description": "Missing or invalid x-api-key." + }, + "403": { + "description": "The caller lacks the transactions permission." + }, + "404": { + "description": "No submitted screenshot proof with this id in your application." + }, + "409": { + "description": "The proof is already decided (the body carries its status), or code OWNERSHIP_CHANGED: the wallet's holder changed or the wallet was deleted after the widget session was minted, so the proof is closed and the current holder needs a new one.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + } + } + } + }, + "/v3/travel-rule/widget/{token}/proofs/": { + "post": { + "summary": "Start a wallet-ownership proof", + "description": "Public endpoint keyed by the widget session token, no API key required: the hosted widget calls it when the end user picks a proof method. A proof exists, and counts in your proof funnel, only from this call. Picking the same method again returns the proof already open (200, same challenge or deposit amount) instead of starting a second one; picking another method closes the unfinished one as METHOD_SWITCHED, except a submitted screenshot or a test deposit still being checked. MESSAGE_SIGNING returns the challenge to sign; SATOSHI_TEST the deposit address, exact amount and window; SELF_DECLARATION is verified at once (status VERIFIED). Signature formats verified per chain family: EVM chains take a personal_sign (EIP-191) signature, hex encoded; Bitcoin takes a BIP-137 signmessage signature (P2PKH, P2SH-P2WPKH and P2WPKH addresses) or a BIP-322 simple signature (P2WPKH and P2TR addresses), base64 encoded; Solana takes an Ed25519 signature over the challenge, base58 encoded; Tron takes a signMessageV2 (TIP-191) signature, hex encoded. A BIP-322 full or proof-of-funds signature, or a multisig address, fails with SIGNATURE_UNSUPPORTED_SCHEME.", + "operationId": "createTravelRuleWidgetProof", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "create_widget_proof", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofCreate" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/widget/{token}/proofs/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"method\": \"MESSAGE_SIGNING\"}'" + } + ], + "responses": { + "201": { + "description": "A new PENDING proof (VERIFIED for SELF_DECLARATION).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofSummary" + } + } + } + }, + "200": { + "description": "The proof already open for this method, resumed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofSummary" + } + } + } + }, + "400": { + "description": "Invalid body, a wallet that does not match the transfer, SCREENSHOT not allowed, or code CHAIN_UNSUPPORTED when the method is not available on the chain.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + }, + "403": { + "description": "SELF_DECLARATION is not allowed for this application." + }, + "404": { + "description": "Unknown token." + }, + "409": { + "description": "code OWNERSHIP_CHANGED: the wallet's holder changed or the wallet was deleted after the session was minted; mint a new session for the current holder. Also returned for SATOSHI_TEST when the session has no satoshi_deposit_address, and when the session has no attempts left (no code; the body carries max_attempts, attempts_used and attempts_remaining 0): mint a new session.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + }, + "410": { + "description": "The session expired." + } + } + } + }, + "/v3/travel-rule/widget/{token}/proofs/{proof_id}/verify/": { + "post": { + "summary": "Submit wallet-ownership proof evidence", + "description": "Public endpoint keyed by the widget session token, no API key required. Send the evidence the proof's method needs: signature for MESSAGE_SIGNING, txid for SATOSHI_TEST (JSON), or a file for SCREENSHOT (multipart). Every submission counts as an attempt. A signature that does not verify, or a deposit that does not match, answers 400 with a code and leaves the proof PENDING so the end user can try again. A deposit seen but not confirmed yet answers 200 with status PENDING and is rechecked automatically until the window closes. A screenshot answers 200 with status PENDING and waits in your review queue (GET /v3/travel-rule/wallet-proofs/). An EXPIRED or REJECTED proof never verifies later: start a new one. Signature formats verified per chain family: EVM chains take a personal_sign (EIP-191) signature, hex encoded; Bitcoin takes a BIP-137 signmessage signature (P2PKH, P2SH-P2WPKH and P2WPKH addresses) or a BIP-322 simple signature (P2WPKH and P2TR addresses), base64 encoded; Solana takes an Ed25519 signature over the challenge, base58 encoded; Tron takes a signMessageV2 (TIP-191) signature, hex encoded. A BIP-322 full or proof-of-funds signature, or a multisig address, fails with SIGNATURE_UNSUPPORTED_SCHEME.", + "operationId": "verifyTravelRuleWidgetProof", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "verify_widget_proof", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + }, + { + "name": "proof_id", + "in": "path", + "required": true, + "description": "proof_id returned when the proof was started.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "signature": { + "type": "string", + "description": "MESSAGE_SIGNING: the wallet's signature over the proof's challenge." + }, + "txid": { + "type": "string", + "description": "SATOSHI_TEST: the transaction id of the test deposit." + } + } + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "file" + ], + "properties": { + "file": { + "type": "string", + "format": "binary", + "description": "SCREENSHOT: an image showing the wallet in the end user's wallet app." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/widget/{token}/proofs/{proof_id}/verify/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"signature\": \"0x...\"}'" + } + ], + "responses": { + "200": { + "description": "VERIFIED, or still PENDING (a screenshot awaiting review, a deposit not indexed or not confirmed yet, with code DEPOSIT_NOT_FOUND or DEPOSIT_UNCONFIRMED).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofVerifyResult" + } + } + } + }, + "400": { + "description": "Missing evidence; a failed attempt that keeps the proof PENDING so the end user can try again (code SIGNATURE_INVALID, SIGNATURE_UNSUPPORTED_SCHEME or DEPOSIT_LOOKUP_FAILED); or a test deposit that rejects the proof (code DEPOSIT_MISMATCH, DEPOSIT_STALE or TXID_REUSED, status REJECTED, with the session's attempt counts): picking a method again starts a new proof while attempts_remaining is above 0.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + }, + "404": { + "description": "Unknown token, or no such proof on this session." + }, + "409": { + "description": "code OWNERSHIP_CHANGED (the proof is closed with it); a closed proof (REJECTED or EXPIRED), with its status, the code it closed with and the session's attempt counts: pick a method again to start a new proof; or a SELF_DECLARATION proof, which is verified when started.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + }, + "410": { + "description": "The session expired, or code WINDOW_EXPIRED when the satoshi test window closed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + } + } + } + }, + "/v3/id-verification/wallets/verifications/{request_id}/response/": { + "post": { + "operationId": "submit_wallet_verification_response", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "submit_verification_response", + "auth": "api_key", + "server": true + }, + "description": "Digital Credentials API wallets (Google Wallet): send what the wallet returned to your page or app for a verification whose next_action is request_digital_credential. Didit decrypts it, checks the issuer's signature against the trusted issuer roots, every returned value against its signed digest, and the device signature over your origin and this request, then decides the verification: verified (billed once) or declined with the error code. Single use: a second submit returns the decided verification.", + "summary": "Submit a wallet response", + "parameters": [ + { + "in": "path", + "name": "request_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The request_id the start endpoint returned.", + "required": true + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletDigitalCredentialResponseRequest" + }, + "example": { + "response": { + "protocol": "openid4vp-v1-signed", + "data": { + "response": "" + } + } + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletDigitalCredentialResponseRequest" + }, + "example": { + "response": { + "protocol": "openid4vp-v1-signed", + "data": { + "response": "" + } + } + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletDigitalCredentialResponseRequest" + }, + "example": { + "response": { + "protocol": "openid4vp-v1-signed", + "data": { + "response": "" + } + } + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "google_wallet", + "name": "Google Wallet", + "issuing_authority": "Google Wallet (ID pass) and US state issuers (mobile driving licence)" + }, + "country": "USA", + "status": "Approved", + "outcome": "verified", + "error": null, + "interaction": "digital_credentials", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": null, + "identity": { + "full_name": "Alex Example", + "first_name": "Alex", + "last_name": "Example", + "date_of_birth": "1990-01-31", + "nationality": "USA", + "issuing_state": "USA" + }, + "attributes": { + "document_number": "X0000000", + "document_expiry_date": "2030-01-31" + }, + "level_of_assurance": "substantial", + "verified_at": "2026-01-01T10:01:00Z", + "signature_valid": true, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "Missing credentials (x-api-key or Authorization: Bearer)." + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error not_found: no wallet verification of this application has this request_id." + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]})." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error not_a_digital_credentials_wallet: this verification does not take a wallet response." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot verify the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/verifications/REQUEST_ID/response/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"response\": {\n \"protocol\": \"openid4vp-v1-signed\",\n \"data\": {\n \"response\": \"\"\n }\n }\n}'" + } + ] + } + }, + "/v3/id-verification/wallets/google-wallet/": { + "post": { + "operationId": "start_google_wallet_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_google_wallet", + "auth": "api_key", + "server": true + }, + "description": "Start a Google Wallet sign-in (Google Wallet (ID pass) and US state issuers (mobile driving licence); USA, GBR, BRA, SGP, TWN) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=request_digital_credential with a request Didit signed for your application, usable only from origin. In a user gesture there, pass next_action.digital_credentials.request to navigator.credentials.get({digital: ...}) (or to Android Credential Manager as GetDigitalCredentialOption requestJson), then POST what the wallet returns to /v3/id-verification/wallets/verifications/{request_id}/response/. The browser offers its own QR code when the wallet is on another device. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Google Wallet verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGoogleWalletWalletVerificationStartRequest" + }, + "example": { + "country": "USA", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "origin": "https://app.example.com" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGoogleWalletWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGoogleWalletWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "google_wallet", + "name": "Google Wallet", + "issuing_authority": "Google Wallet (ID pass) and US state issuers (mobile driving licence)" + }, + "country": "USA", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "digital_credentials", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "request_digital_credential", + "digital_credentials": { + "protocol": "openid4vp-v1-signed", + "request": { + "requests": [ + { + "protocol": "openid4vp-v1-signed", + "data": { + "request": "" + } + } + ] + } + } + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, origin_invalid, origin_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/google-wallet/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"USA\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"origin\": \"https://app.example.com\"\n}'" + } + ], + "x-didit-wallet-id": "google_wallet" + } + }, + "/v3/session/{sessionId}/kyb/documents/": { + "post": { + "summary": "Upload KYB documents to a session", + "description": "Upload one or more corporate documents (articles of incorporation, shareholder register, certificate of incorporation, ...) to the KYB Documents step of a Business Verification session, from your own server instead of the hosted page. Didit runs the hosted pipeline: compression, OCR, tampering and cross-document checks, document grouping, and advances the session when the required document groups are satisfied. The step costs one flat fee however many files you send. The session must be open and on the KYB Documents step, and the company chosen at the registry step must exist. Read the people the documents name with `GET /v3/session/{sessionId}/kyb/extracted-parties/`. Requires `write:businesses`.", + "operationId": "post_v3_session_kyb_documents", + "x-didit-sdk": { + "resource": "sessions", + "method": "upload_kyb_documents", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "documents" + ], + "properties": { + "documents": { + "type": "array", + "items": { + "type": "string", + "format": "binary" + }, + "description": "Files to upload: JPG, JPEG, PNG, TIFF, WEBP, PDF or ZIP archives of them. PDFs up to 50 MB, photos up to 6 MB before compression." + }, + "document_passwords": { + "type": "array", + "items": { + "type": "string" + }, + "description": "One entry per file in `documents`, in the same order: the password of a protected PDF, empty for any other file." + }, + "document_group": { + "type": "string", + "enum": [ + "LEGAL_PRESENCE", + "COMPANY_DETAILS", + "OWNERSHIP_STRUCTURE", + "REPRESENTATIVES_AUTHORIZATION" + ], + "description": "Required document group this upload answers." + }, + "locale": { + "type": "string", + "maxLength": 10, + "description": "Locale used to translate the response of the next step." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Documents processed. When every required group is satisfied the session advances and `next_step` names the next step; otherwise it stays on the Documents step and `document_groups` and `document_answer` say what is still missing.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "valid": { + "type": "boolean" + }, + "next_step": { + "type": "string", + "nullable": true, + "description": "Step the session is on after the upload." + }, + "kyb_status": { + "type": "string", + "description": "Status of the company check." + }, + "document_answer": { + "type": "object", + "additionalProperties": true, + "description": "Result per uploaded document and the uploads left." + } + }, + "additionalProperties": true + }, + "example": { + "valid": true, + "next_step": "KYB_KEY_PEOPLE", + "kyb_status": "In Progress" + } + } + } + }, + "400": { + "description": "A document was rejected (tampering, unreadable or unsupported) or the session has no KYB company yet. Retry with another file while attempts remain.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "message": "No KYB company found. Submit company data first." + } + } + } + }, + "402": { + "description": "The organization has no credit left for the documents step.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Insufficient credits." + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted, belongs to another application or is outside a scoped key's limits; no detail distinguishes them.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + }, + "409": { + "description": "The session is finished (`kyb_session_not_writable`) or is not on the KYB Documents step (`kyb_step_mismatch`, with `next_step` naming the step it is on).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string" + }, + "next_step": { + "type": "string" + } + } + }, + "example": { + "detail": "The session is not on the step this operation completes.", + "code": "kyb_step_mismatch", + "next_step": "KYB_REGISTRY" + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.upload_kyb_documents(\n \"33333333-4444-5555-6666-777777777777\",\n documents=[open(\"document.pdf\", \"rb\")],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.uploadKybDocuments(\"33333333-4444-5555-6666-777777777777\", {\n documents: [await fileFromPath(\"document.pdf\")],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UploadKYBDocuments(ctx, \"33333333-4444-5555-6666-777777777777\", didit.SessionsUploadKYBDocumentsParams{\n\tDocuments: []didit.File{didit.FileFromPath(\"document.pdf\")},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.SessionsUploadKybDocumentsParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().uploadKybDocuments(\"33333333-4444-5555-6666-777777777777\", SessionsUploadKybDocumentsParams.builder()\n .documents(List.of(Path.of(\"document.pdf\")))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->uploadKybDocuments('33333333-4444-5555-6666-777777777777', [\n 'documents' => [File::fromPath('document.pdf')],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.upload_kyb_documents(\n \"33333333-4444-5555-6666-777777777777\",\n documents: [Pathname(\"document.pdf\")]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UploadKybDocumentsAsync(\"33333333-4444-5555-6666-777777777777\", new SessionsUploadKybDocumentsParams\n{\n Documents = new FileUpload[] { FileUpload.FromPath(\"document.pdf\") },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/extracted-parties/": { + "get": { + "summary": "List the parties named by the uploaded documents", + "description": "Return the shareholders and directors that the documents uploaded to this KYB session name, merged per person and attributed to the documents that named them. Show them to your applicant to confirm, then declare them with the party draft endpoints. A range stays a range: `ownership_percent.text` keeps the verbatim figure and `min` / `max` the numbers read from it. Nobody is labelled a UBO here; that is decided when the parties are submitted, from the role settings and ownership thresholds of the workflow. The same list is in the session decision as `document_verifications[].extracted_parties`. Requires `read:businesses` or `read:sessions`.", + "operationId": "get_v3_session_kyb_extracted_parties", + "x-didit-sdk": { + "resource": "sessions", + "method": "list_kyb_extracted_parties", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Parties named by the current documents.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "parties", + "document_count" + ], + "properties": { + "parties": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ] + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "appointment_date": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "roles": { + "type": "array", + "items": { + "type": "object", + "properties": { + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "ownership_percent": { + "type": "object", + "properties": { + "text": { + "type": "string", + "nullable": true, + "description": "The ownership figure exactly as the document states it, such as `25-50%`, `>50%` or `NO EXHIBE`." + }, + "min": { + "type": "number", + "nullable": true, + "description": "Lower number read out of `text`; null when it states none." + }, + "max": { + "type": "number", + "nullable": true, + "description": "Upper number read out of `text`; null when it states none." + } + } + } + } + } + }, + "documents": { + "type": "array", + "description": "The uploaded documents that named this party.", + "items": { + "type": "object", + "properties": { + "document_uuid": { + "type": "string", + "format": "uuid" + }, + "document_type": { + "type": "string", + "nullable": true + }, + "document_subtype": { + "type": "string", + "nullable": true + }, + "original_filename": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "description": "The shareholders and directors the uploaded documents name, merged per person. Nobody is labelled a UBO here: that is decided when the parties are submitted." + }, + "document_count": { + "type": "integer", + "description": "Number of uploaded documents the list was read from." + }, + "node_id": { + "type": "string", + "nullable": true + } + } + }, + "example": { + "parties": [ + { + "name": "Ana Lopez", + "entity_type": "person", + "first_name": "Ana", + "last_name": "Lopez", + "appointment_date": null, + "designation": "Administrador unico", + "roles": [ + { + "role": "shareholder", + "ownership_percent": { + "text": "60%", + "min": 60, + "max": 60 + } + } + ], + "documents": [ + { + "document_uuid": "0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41", + "document_type": "OWNERSHIP_STRUCTURE", + "document_subtype": "ARTICLES_OF_INCORPORATION", + "original_filename": "acta_constitutiva.pdf", + "status": "Approved" + } + ] + } + ], + "document_count": 1, + "node_id": "node_kyb_documents" + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted, belongs to another application or is outside a scoped key's limits; no detail distinguishes them.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.list_kyb_extracted_parties(\n \"33333333-4444-5555-6666-777777777777\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.listKybExtractedParties(\"33333333-4444-5555-6666-777777777777\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.ListKYBExtractedParties(ctx, \"33333333-4444-5555-6666-777777777777\", didit.SessionsListKYBExtractedPartiesParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().listKybExtractedParties(\"33333333-4444-5555-6666-777777777777\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->listKybExtractedParties('33333333-4444-5555-6666-777777777777');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.list_kyb_extracted_parties(\"33333333-4444-5555-6666-777777777777\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ListKybExtractedPartiesAsync(\"33333333-4444-5555-6666-777777777777\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/parties/drafts/": { + "get": { + "summary": "List the KYB party drafts", + "description": "List the parties you are preparing for this KYB session, oldest first. A draft is not a declared party: it does not appear in the decision or the console and creates no verification session until you submit it with `POST /v3/session/{sessionId}/kyb/parties/submit/`. Requires `write:businesses`.", + "operationId": "get_v3_session_kyb_party_drafts", + "x-didit-sdk": { + "resource": "sessions", + "method": "list_kyb_party_drafts", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The stored drafts.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "parties": { + "type": "array", + "items": { + "type": "object", + "required": [ + "draft_id", + "created_at", + "updated_at", + "party" + ], + "properties": { + "draft_id": { + "type": "string", + "format": "uuid" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "party": { + "type": "object", + "required": [ + "entity_type", + "nationality" + ], + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "full_name": { + "type": "string", + "maxLength": 255, + "description": "Full name of a person. Send it, or `first_name` and `last_name`." + }, + "first_name": { + "type": "string", + "maxLength": 128 + }, + "last_name": { + "type": "string", + "maxLength": 128 + }, + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Role the party holds. Interchangeable with `roles`; when both are sent they are merged." + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "description": "Every role the party holds. A person needs at least one role (here or in `role`)." + }, + "position": { + "type": "string", + "maxLength": 128, + "description": "Job title of a person." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Name of a company party. Required when `entity_type` is `company`." + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Registration number of a company party." + }, + "email": { + "type": "string", + "format": "email", + "description": "Where the verification link is sent. Required when the party has to complete verification, which depends on the role settings and ownership thresholds of the workflow." + }, + "ownership_percent": { + "type": "number", + "nullable": true, + "description": "Share of the company the party owns." + }, + "nationality": { + "type": "string", + "maxLength": 100, + "description": "Nationality of a person, or country of incorporation of a company. Required for every party." + }, + "phone_number": { + "type": "string", + "maxLength": 32 + }, + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined extra fields." + }, + "skip_verification": { + "type": "boolean", + "default": false, + "description": "Ask to skip this party's verification. Honoured only when the party's role allows skipping in the workflow's Key People settings." + } + } + } + } + } + }, + "node_id": { + "type": "string", + "nullable": true + } + } + }, + "example": { + "parties": [ + { + "draft_id": "5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d", + "created_at": "2026-10-31T10:00:00Z", + "updated_at": "2026-10-31T10:00:00Z", + "party": { + "entity_type": "person", + "first_name": "Ana", + "last_name": "Lopez", + "roles": [ + "ubo", + "director" + ], + "ownership_percent": 60, + "nationality": "MEX", + "email": "ana.lopez@example.com", + "date_of_birth": "1984-06-12" + } + } + ], + "node_id": "node_kyb_key_people" + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted, belongs to another application or is outside a scoped key's limits; no detail distinguishes them.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.list_kyb_party_drafts(\n \"33333333-4444-5555-6666-777777777777\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.listKybPartyDrafts(\"33333333-4444-5555-6666-777777777777\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.ListKYBPartyDrafts(ctx, \"33333333-4444-5555-6666-777777777777\", didit.SessionsListKYBPartyDraftsParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().listKybPartyDrafts(\"33333333-4444-5555-6666-777777777777\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->listKybPartyDrafts('33333333-4444-5555-6666-777777777777');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.list_kyb_party_drafts(\"33333333-4444-5555-6666-777777777777\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ListKybPartyDraftsAsync(\"33333333-4444-5555-6666-777777777777\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "post": { + "summary": "Create a KYB party draft", + "description": "Add a party (a person or a company) to the list you are preparing for this KYB session. The same fields and rules as the hosted Key People screen apply: a person needs `full_name` or `first_name` and `last_name`, at least one role and a nationality; a company needs `company_name` and the country of incorporation in `nationality`. Nothing is declared or billed until `POST /v3/session/{sessionId}/kyb/parties/submit/`. Requires `write:businesses`.", + "operationId": "post_v3_session_kyb_party_drafts", + "x-didit-sdk": { + "resource": "sessions", + "method": "create_kyb_party_draft", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "entity_type", + "nationality" + ], + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "full_name": { + "type": "string", + "maxLength": 255, + "description": "Full name of a person. Send it, or `first_name` and `last_name`." + }, + "first_name": { + "type": "string", + "maxLength": 128 + }, + "last_name": { + "type": "string", + "maxLength": 128 + }, + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Role the party holds. Interchangeable with `roles`; when both are sent they are merged." + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "description": "Every role the party holds. A person needs at least one role (here or in `role`)." + }, + "position": { + "type": "string", + "maxLength": 128, + "description": "Job title of a person." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Name of a company party. Required when `entity_type` is `company`." + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Registration number of a company party." + }, + "email": { + "type": "string", + "format": "email", + "description": "Where the verification link is sent. Required when the party has to complete verification, which depends on the role settings and ownership thresholds of the workflow." + }, + "ownership_percent": { + "type": "number", + "nullable": true, + "description": "Share of the company the party owns." + }, + "nationality": { + "type": "string", + "maxLength": 100, + "description": "Nationality of a person, or country of incorporation of a company. Required for every party." + }, + "phone_number": { + "type": "string", + "maxLength": 32 + }, + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined extra fields." + }, + "skip_verification": { + "type": "boolean", + "default": false, + "description": "Ask to skip this party's verification. Honoured only when the party's role allows skipping in the workflow's Key People settings." + } + } + }, + "example": { + "entity_type": "person", + "first_name": "Ana", + "last_name": "Lopez", + "roles": [ + "ubo", + "director" + ], + "ownership_percent": 60, + "nationality": "MEX", + "email": "ana.lopez@example.com", + "date_of_birth": "1984-06-12" + } + } + } + }, + "responses": { + "201": { + "description": "Draft created.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "draft_id", + "created_at", + "updated_at", + "party" + ], + "properties": { + "draft_id": { + "type": "string", + "format": "uuid" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "party": { + "type": "object", + "required": [ + "entity_type", + "nationality" + ], + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "full_name": { + "type": "string", + "maxLength": 255, + "description": "Full name of a person. Send it, or `first_name` and `last_name`." + }, + "first_name": { + "type": "string", + "maxLength": 128 + }, + "last_name": { + "type": "string", + "maxLength": 128 + }, + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Role the party holds. Interchangeable with `roles`; when both are sent they are merged." + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "description": "Every role the party holds. A person needs at least one role (here or in `role`)." + }, + "position": { + "type": "string", + "maxLength": 128, + "description": "Job title of a person." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Name of a company party. Required when `entity_type` is `company`." + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Registration number of a company party." + }, + "email": { + "type": "string", + "format": "email", + "description": "Where the verification link is sent. Required when the party has to complete verification, which depends on the role settings and ownership thresholds of the workflow." + }, + "ownership_percent": { + "type": "number", + "nullable": true, + "description": "Share of the company the party owns." + }, + "nationality": { + "type": "string", + "maxLength": 100, + "description": "Nationality of a person, or country of incorporation of a company. Required for every party." + }, + "phone_number": { + "type": "string", + "maxLength": 32 + }, + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined extra fields." + }, + "skip_verification": { + "type": "boolean", + "default": false, + "description": "Ask to skip this party's verification. Honoured only when the party's role allows skipping in the workflow's Key People settings." + } + } + } + } + }, + "example": { + "draft_id": "5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d", + "created_at": "2026-10-31T10:00:00Z", + "updated_at": "2026-10-31T10:00:00Z", + "party": { + "entity_type": "person", + "first_name": "Ana", + "last_name": "Lopez", + "roles": [ + "ubo", + "director" + ], + "ownership_percent": 60, + "nationality": "MEX", + "email": "ana.lopez@example.com", + "date_of_birth": "1984-06-12" + } + } + } + } + }, + "400": { + "description": "A party field is invalid or a required one is missing.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "non_field_errors": [ + "Person parties require at least one role." + ] + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted, belongs to another application or is outside a scoped key's limits; no detail distinguishes them.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + }, + "409": { + "description": "The session is finished (`kyb_session_not_writable`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "This verification session is no longer accepting changes.", + "code": "kyb_session_not_writable" + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.create_kyb_party_draft(\n \"33333333-4444-5555-6666-777777777777\",\n entity_type=\"person\",\n first_name=\"Ana\",\n last_name=\"Lopez\",\n roles=[\"ubo\", \"director\"],\n date_of_birth=\"1984-06-12\",\n email=\"ana.lopez@example.com\",\n ownership_percent=60.0,\n nationality=\"MEX\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.createKybPartyDraft(\"33333333-4444-5555-6666-777777777777\", {\n entity_type: \"person\",\n first_name: \"Ana\",\n last_name: \"Lopez\",\n roles: [\"ubo\", \"director\"],\n date_of_birth: \"1984-06-12\",\n email: \"ana.lopez@example.com\",\n ownership_percent: 60.0,\n nationality: \"MEX\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.CreateKYBPartyDraft(ctx, \"33333333-4444-5555-6666-777777777777\", didit.SessionsCreateKYBPartyDraftParams{\n\tEntityType: \"person\",\n\tFirstName: didit.String(\"Ana\"),\n\tLastName: didit.String(\"Lopez\"),\n\tRoles: didit.Some([]didit.SessionsCreateKYBPartyDraftParamsRoles{\"ubo\", \"director\"}),\n\tDateOfBirth: didit.String(\"1984-06-12\"),\n\tEmail: didit.String(\"ana.lopez@example.com\"),\n\tOwnershipPercent: didit.Float(60.0),\n\tNationality: \"MEX\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.SessionsCreateKybPartyDraftParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().createKybPartyDraft(\"33333333-4444-5555-6666-777777777777\", SessionsCreateKybPartyDraftParams.builder()\n .entityType(SessionsCreateKybPartyDraftParams.EntityType.of(\"person\"))\n .firstName(\"Ana\")\n .lastName(\"Lopez\")\n .roles(List.of(SessionsCreateKybPartyDraftParams.Roles.of(\"ubo\"), SessionsCreateKybPartyDraftParams.Roles.of(\"director\")))\n .dateOfBirth(LocalDate.parse(\"1984-06-12\"))\n .email(\"ana.lopez@example.com\")\n .ownershipPercent(60.0)\n .nationality(\"MEX\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->createKybPartyDraft('33333333-4444-5555-6666-777777777777', [\n 'entity_type' => 'person',\n 'first_name' => 'Ana',\n 'last_name' => 'Lopez',\n 'roles' => ['ubo', 'director'],\n 'date_of_birth' => '1984-06-12',\n 'email' => 'ana.lopez@example.com',\n 'ownership_percent' => 60.0,\n 'nationality' => 'MEX',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.create_kyb_party_draft(\n \"33333333-4444-5555-6666-777777777777\",\n entity_type: \"person\",\n first_name: \"Ana\",\n last_name: \"Lopez\",\n roles: [\"ubo\", \"director\"],\n date_of_birth: \"1984-06-12\",\n email: \"ana.lopez@example.com\",\n ownership_percent: 60.0,\n nationality: \"MEX\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.CreateKybPartyDraftAsync(\"33333333-4444-5555-6666-777777777777\", new SessionsCreateKybPartyDraftParams\n{\n EntityType = \"person\",\n FirstName = \"Ana\",\n LastName = \"Lopez\",\n Roles = new SessionsCreateKybPartyDraftParamsRoles[] { \"ubo\", \"director\" },\n DateOfBirth = \"1984-06-12\",\n Email = \"ana.lopez@example.com\",\n OwnershipPercent = 60.0,\n Nationality = \"MEX\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/parties/drafts/{draftId}/": { + "patch": { + "summary": "Edit a KYB party draft", + "description": "Change fields of one party draft. The edit is merged into the stored draft and the merged party is validated as a whole, so an edit that would leave the party incomplete is rejected. Changing `first_name` or `last_name` replaces the stored full name. Requires `write:businesses`.", + "operationId": "patch_v3_session_kyb_party_draft", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_kyb_party_draft", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "draftId", + "in": "path", + "required": true, + "description": "UUID of the party draft (`draft_id` returned when it was created).", + "schema": { + "type": "string", + "format": "uuid", + "example": "5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Any subset of the party fields. The edit is merged into the stored draft and the merged party is validated as a whole, so a draft that could be submitted before an edit can still be submitted after it.", + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "full_name": { + "type": "string", + "maxLength": 255, + "description": "Full name of a person. Send it, or `first_name` and `last_name`." + }, + "first_name": { + "type": "string", + "maxLength": 128 + }, + "last_name": { + "type": "string", + "maxLength": 128 + }, + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Role the party holds. Interchangeable with `roles`; when both are sent they are merged." + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "description": "Every role the party holds. A person needs at least one role (here or in `role`)." + }, + "position": { + "type": "string", + "maxLength": 128, + "description": "Job title of a person." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Name of a company party. Required when `entity_type` is `company`." + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Registration number of a company party." + }, + "email": { + "type": "string", + "format": "email", + "description": "Where the verification link is sent. Required when the party has to complete verification, which depends on the role settings and ownership thresholds of the workflow." + }, + "ownership_percent": { + "type": "number", + "nullable": true, + "description": "Share of the company the party owns." + }, + "nationality": { + "type": "string", + "maxLength": 100, + "description": "Nationality of a person, or country of incorporation of a company. Required for every party." + }, + "phone_number": { + "type": "string", + "maxLength": 32 + }, + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined extra fields." + }, + "skip_verification": { + "type": "boolean", + "default": false, + "description": "Ask to skip this party's verification. Honoured only when the party's role allows skipping in the workflow's Key People settings." + } + } + }, + "example": { + "ownership_percent": 55 + } + } + } + }, + "responses": { + "200": { + "description": "Draft updated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "draft_id", + "created_at", + "updated_at", + "party" + ], + "properties": { + "draft_id": { + "type": "string", + "format": "uuid" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "party": { + "type": "object", + "required": [ + "entity_type", + "nationality" + ], + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "full_name": { + "type": "string", + "maxLength": 255, + "description": "Full name of a person. Send it, or `first_name` and `last_name`." + }, + "first_name": { + "type": "string", + "maxLength": 128 + }, + "last_name": { + "type": "string", + "maxLength": 128 + }, + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Role the party holds. Interchangeable with `roles`; when both are sent they are merged." + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "description": "Every role the party holds. A person needs at least one role (here or in `role`)." + }, + "position": { + "type": "string", + "maxLength": 128, + "description": "Job title of a person." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Name of a company party. Required when `entity_type` is `company`." + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Registration number of a company party." + }, + "email": { + "type": "string", + "format": "email", + "description": "Where the verification link is sent. Required when the party has to complete verification, which depends on the role settings and ownership thresholds of the workflow." + }, + "ownership_percent": { + "type": "number", + "nullable": true, + "description": "Share of the company the party owns." + }, + "nationality": { + "type": "string", + "maxLength": 100, + "description": "Nationality of a person, or country of incorporation of a company. Required for every party." + }, + "phone_number": { + "type": "string", + "maxLength": 32 + }, + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined extra fields." + }, + "skip_verification": { + "type": "boolean", + "default": false, + "description": "Ask to skip this party's verification. Honoured only when the party's role allows skipping in the workflow's Key People settings." + } + } + } + } + }, + "example": { + "draft_id": "5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d", + "created_at": "2026-10-31T10:00:00Z", + "updated_at": "2026-10-31T10:00:00Z", + "party": { + "entity_type": "person", + "first_name": "Ana", + "last_name": "Lopez", + "roles": [ + "ubo", + "director" + ], + "ownership_percent": 55, + "nationality": "MEX", + "email": "ana.lopez@example.com", + "date_of_birth": "1984-06-12" + } + } + } + } + }, + "400": { + "description": "The merged party is invalid.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "non_field_errors": [ + "Person parties require at least one role." + ] + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session or the draft does not exist, or the session is outside the key's application or a scoped key's limits.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + }, + "409": { + "description": "The session is finished (`kyb_session_not_writable`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "This verification session is no longer accepting changes.", + "code": "kyb_session_not_writable" + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_kyb_party_draft(\n \"33333333-4444-5555-6666-777777777777\",\n \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\",\n ownership_percent=55.0,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateKybPartyDraft(\n \"33333333-4444-5555-6666-777777777777\",\n \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\",\n {\n ownership_percent: 55.0,\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateKYBPartyDraft(ctx, \"33333333-4444-5555-6666-777777777777\", \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\", didit.SessionsUpdateKYBPartyDraftParams{\n\tOwnershipPercent: didit.Float(55.0),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsUpdateKybPartyDraftParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateKybPartyDraft(\"33333333-4444-5555-6666-777777777777\", \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\", SessionsUpdateKybPartyDraftParams.builder()\n .ownershipPercent(55.0)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateKybPartyDraft('33333333-4444-5555-6666-777777777777', '5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d', [\n 'ownership_percent' => 55.0,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_kyb_party_draft(\n \"33333333-4444-5555-6666-777777777777\",\n \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\",\n ownership_percent: 55.0\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateKybPartyDraftAsync(\"33333333-4444-5555-6666-777777777777\", \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\", new SessionsUpdateKybPartyDraftParams\n{\n OwnershipPercent = 55.0,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "summary": "Delete a KYB party draft", + "description": "Drop one party draft. Declared parties are not affected. Requires `write:businesses`.", + "operationId": "delete_v3_session_kyb_party_draft", + "x-didit-sdk": { + "resource": "sessions", + "method": "delete_kyb_party_draft", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "draftId", + "in": "path", + "required": true, + "description": "UUID of the party draft (`draft_id` returned when it was created).", + "schema": { + "type": "string", + "format": "uuid", + "example": "5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Draft deleted. No body." + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session or the draft does not exist, or the session is outside the key's application or a scoped key's limits.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + }, + "409": { + "description": "The session is finished (`kyb_session_not_writable`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "This verification session is no longer accepting changes.", + "code": "kyb_session_not_writable" + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.sessions.delete_kyb_party_draft(\n \"33333333-4444-5555-6666-777777777777\",\n \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.sessions.deleteKybPartyDraft(\n \"33333333-4444-5555-6666-777777777777\",\n \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\",\n);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Sessions.DeleteKYBPartyDraft(ctx, \"33333333-4444-5555-6666-777777777777\", \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\", didit.SessionsDeleteKYBPartyDraftParams{}); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().deleteKybPartyDraft(\"33333333-4444-5555-6666-777777777777\", \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->deleteKybPartyDraft('33333333-4444-5555-6666-777777777777', '5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.delete_kyb_party_draft(\n \"33333333-4444-5555-6666-777777777777\",\n \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\"\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Sessions.DeleteKybPartyDraftAsync(\"33333333-4444-5555-6666-777777777777\", \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\");" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/parties/submit/": { + "post": { + "summary": "Submit the KYB party drafts", + "description": "Declare the stored drafts as the company's key people. This is the hosted Key People step under your API key: Didit decides from the role settings and ownership thresholds of the workflow which parties must be verified, creates one individual KYC session (or a KYB sub-session for a corporate owner) for each of them, sends the emails the workflow asks for, and advances the session. Each child session is billed like in the hosted flow. The drafts are consumed. Repeating the call after a successful submit changes nothing and answers `200` with `already_submitted: true` and the parties that already exist. The session must be on the Key People step. Send each person's `kyc_session_url` to them, or embed it. Requires `write:businesses`.", + "operationId": "post_v3_session_kyb_parties_submit", + "x-didit-sdk": { + "resource": "sessions", + "method": "submit_kyb_parties", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "locale": { + "type": "string", + "maxLength": 10, + "description": "Locale used to translate the response of the step the session advances to." + } + } + }, + "example": { + "locale": "es" + } + } + } + }, + "responses": { + "200": { + "description": "Parties declared (or already declared).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "valid": { + "type": "boolean" + }, + "next_step": { + "type": "string", + "nullable": true, + "description": "Step the session is on." + }, + "status": { + "type": "string", + "description": "Status of the business session." + }, + "advanced": { + "type": "boolean", + "description": "`true` when this call declared the parties and advanced the session." + }, + "already_submitted": { + "type": "boolean", + "description": "`true` when the parties had been declared by an earlier call." + }, + "kyb_key_people_status": { + "type": "string" + }, + "parties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/KYBKeyPeopleParty" + } + } + } + }, + "example": { + "valid": true, + "next_step": "FINISHED", + "status": "In Progress", + "advanced": true, + "already_submitted": false, + "kyb_key_people_status": "In Progress", + "parties": [] + } + } + } + }, + "402": { + "description": "The organization has no credit left for the child verification sessions.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Insufficient credits." + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted, belongs to another application or is outside a scoped key's limits; no detail distinguishes them.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + }, + "409": { + "description": "`kyb_no_party_drafts`: there is nothing to submit. `kyb_parties_already_submitted`: drafts were added after the parties were declared. `kyb_step_mismatch`: the session is not on the Key People step (`next_step` names its step). `kyb_session_not_writable`: the session is finished.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string" + }, + "next_step": { + "type": "string" + } + } + }, + "example": { + "detail": "There are no draft parties to submit.", + "code": "kyb_no_party_drafts" + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.submit_kyb_parties(\n \"33333333-4444-5555-6666-777777777777\",\n locale=\"es\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.submitKybParties(\"33333333-4444-5555-6666-777777777777\", {\n locale: \"es\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.SubmitKYBParties(ctx, \"33333333-4444-5555-6666-777777777777\", didit.SessionsSubmitKYBPartiesParams{\n\tLocale: didit.String(\"es\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsSubmitKybPartiesParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().submitKybParties(\"33333333-4444-5555-6666-777777777777\", SessionsSubmitKybPartiesParams.builder()\n .locale(\"es\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->submitKybParties('33333333-4444-5555-6666-777777777777', [\n 'locale' => 'es',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.submit_kyb_parties(\n \"33333333-4444-5555-6666-777777777777\",\n locale: \"es\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.SubmitKybPartiesAsync(\"33333333-4444-5555-6666-777777777777\", new SessionsSubmitKybPartiesParams\n{\n Locale = \"es\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/files-export/": { + "post": { + "summary": "Export every file of a session", + "description": "Start preparing one ZIP with every image, video and document of a User Verification (KYC) or Business Verification (KYB) session, in original quality: the files are stored in the ZIP as they were captured, never recompressed. Answers `202` with the export; read it with `GET /v3/session/{sessionId}/files-export/{exportId}/` until `status` is `READY`, then download `download_url`. While an export of the same session is still being prepared, that export is returned instead of a new one.\n\nThe ZIP is deleted 24 hours after it is ready, and at once when the session is deleted. A deleted session cannot be exported. Every export and every download link handed out is written to the session's audit log. A scoped API key needs `media: read` and `decisions: read`; a console user needs the privilege to read sessions.", + "operationId": "post_v3_session_files_export", + "x-didit-sdk": { + "resource": "sessions", + "method": "export_files", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs. A deleted session returns `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/files-export/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.export_files(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.exportFiles(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.ExportFiles(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().exportFiles(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->exportFiles('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.export_files(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ExportFilesAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "202": { + "description": "The export, being prepared.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionFilesExport" + }, + "example": { + "export_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "session_id": "11111111-2222-3333-4444-555555555555", + "status": "PENDING", + "file_count": null, + "size_bytes": null, + "download_url": null, + "expires_at": null, + "error": null, + "created_at": "2026-10-29T12:00:03Z", + "completed_at": null + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/files-export/{exportId}/": { + "get": { + "summary": "Get a session files export", + "description": "Progress of an export started with `POST /v3/session/{sessionId}/files-export/`. Once `status` is `READY`, `download_url` is a signed URL of the ZIP that stays valid for a short time; read the export again for a new one until `expires_at`. Each read that hands out a download link is written to the session's audit log. An export of a deleted session returns `404`.", + "operationId": "get_v3_session_files_export", + "x-didit-sdk": { + "resource": "sessions", + "method": "get_files_export", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs. A deleted session returns `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "exportId", + "in": "path", + "required": true, + "description": "The `export_id` returned by `POST /v3/session/{sessionId}/files-export/`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/files-export/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.get_files_export(\n \"11111111-2222-3333-4444-555555555555\",\n \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.getFilesExport(\n \"11111111-2222-3333-4444-555555555555\",\n \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.GetFilesExport(ctx, \"11111111-2222-3333-4444-555555555555\", \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().getFilesExport(\"11111111-2222-3333-4444-555555555555\", \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->getFilesExport('11111111-2222-3333-4444-555555555555', 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.get_files_export(\n \"11111111-2222-3333-4444-555555555555\",\n \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.GetFilesExportAsync(\"11111111-2222-3333-4444-555555555555\", \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The export.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionFilesExport" + }, + "example": { + "export_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "session_id": "11111111-2222-3333-4444-555555555555", + "status": "READY", + "file_count": 9, + "size_bytes": 18734120, + "download_url": "https://storage.example.com/session/11111111-2222-3333-4444-555555555555/files-export/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/session-42-files.zip?X-Amz-Signature=...", + "expires_at": "2026-10-30T12:00:41Z", + "error": null, + "created_at": "2026-10-29T12:00:03Z", + "completed_at": "2026-10-29T12:00:41Z" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/travel-rule/unmatched/": { + "get": { + "summary": "List unmatched Travel Rule deposits", + "description": "Inbound deposits that are still waiting for the originating VASP's Travel Rule data: their transfer is `AWAITING_COUNTERPARTY`, `NOT_ENOUGH_COUNTERPARTY_DATA` or `COUNTERPARTY_VASP_NOT_FOUND`, no received message is paired with them, and nobody matched them by hand. The rows and the filters are the same as in `GET /v3/travel-rule/transfers/`, newest first, paginated with `limit` (default 50) and `offset`. Pair a deposit with the message the originating VASP sent with `POST /v3/travel-rule/unmatched/{transaction_uuid}/match/`.", + "operationId": "listUnmatchedTravelRuleDeposits", + "x-didit-sdk": { + "resource": "travel_rule.unmatched_deposits", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "direction", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "INBOUND, OUTBOUND, or both comma-separated. Case-sensitive." + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "One or more transfer statuses, comma-separated." + }, + { + "name": "rail", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "One or more of INTERNAL, TRP, GTR, TRUST, VERIFYVASP, SYGNA, EMAIL, TEST_VASP, comma-separated." + }, + { + "name": "kind", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "withdrawal, deposit or message, comma-separated." + }, + { + "name": "currency", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "The transaction's currency, case-insensitive." + }, + { + "name": "start_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date" + }, + "description": "Transfers created on or after this day (UTC), YYYY-MM-DD." + }, + { + "name": "end_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date" + }, + "description": "Transfers created on or before this day (UTC), YYYY-MM-DD." + }, + { + "name": "counterparty", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The `id` of a directory entry or Didit network member from GET /v3/travel-rule/vasps/: your transfers with that counterparty. A value that is not a valid id answers 400." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 1000000 + }, + "description": "Page size, default 50." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000000 + }, + "description": "Rows to skip." + } + ], + "responses": { + "200": { + "description": "A page of unmatched deposits.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleTransferRow" + } + } + } + }, + "example": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "id": "7d3a1c55-0b9e-4d52-8a3c-1f6e2b9d4a08", + "transaction_id": "c41f9b2e-5a8d-4e0c-9f17-3b6d8a2e5c91", + "txn_id": "dep-2026-10-20-0007", + "txn_date": "2026-10-19T08:41:12Z", + "kind": "deposit", + "direction": "INBOUND", + "status": "AWAITING_COUNTERPARTY", + "rail": "TRP", + "asset": "ETH", + "amount": "1.25", + "originator_vasp": "", + "beneficiary_vasp": "Example Counterparty VASP", + "originator_wallet": "0x77ccff0cc624a53cd8b8d8703ced826710211ec7", + "beneficiary_wallet": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb", + "originator_memo": "", + "beneficiary_memo": "", + "originator_wallet_type": "vasp", + "beneficiary_wallet_type": "vasp", + "matched_transaction_id": null, + "deadline_at": null, + "created_at": "2026-10-19T08:41:13Z" + } + ] + } + } + } + }, + "400": { + "description": "A filter value is not valid.", + "content": { + "application/json": { + "example": { + "start_date": [ + "Use the YYYY-MM-DD format." + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.travel_rule.unmatched_deposits.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.travelRule.unmatchedDeposits.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.TravelRule.UnmatchedDeposits.ListAutoPaging(ctx, didit.TravelRuleUnmatchedDepositsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.travelRule().unmatchedDeposits().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->unmatchedDeposits->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.unmatched_deposits.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.TravelRule.UnmatchedDeposits.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/travel-rule/unmatched/{transaction_uuid}/match/": { + "post": { + "summary": "Match a Travel Rule deposit to a received message", + "operationId": "matchUnmatchedTravelRuleDeposit", + "x-didit-sdk": { + "resource": "travel_rule.unmatched_deposits", + "method": "match", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Pair an inbound deposit that is still waiting for originator data with the Travel Rule message the originating VASP sent for it. The deposit takes the message's originator data, counterparty VASP and rail, and both transfers record the match on their timelines. `transaction_uuid` is the deposit's `transaction_id` from `GET /v3/travel-rule/unmatched/`. `transaction_id` in the body is the received message's `transaction_id`, from a row of `GET /v3/travel-rule/transfers/?kind=message`; the transfer's own `id` is not found. The message must be for the same asset as the deposit. A deposit or message deleted from the Console answers `404`; one that has already moved on (matched, or no longer waiting) answers `409`.", + "parameters": [ + { + "name": "transaction_uuid", + "in": "path", + "required": true, + "description": "The `transaction_id` of the unmatched deposit.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleManualMatchRequest" + }, + "example": { + "transaction_id": "e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24" + } + } + } + }, + "responses": { + "200": { + "description": "Both transfers after the match.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleManualMatchResponse" + }, + "example": { + "deposit": { + "id": "7d3a1c55-0b9e-4d52-8a3c-1f6e2b9d4a08", + "transaction_id": "c41f9b2e-5a8d-4e0c-9f17-3b6d8a2e5c91", + "txn_id": "dep-2026-10-20-0007", + "txn_date": "2026-10-19T08:41:12Z", + "kind": "deposit", + "direction": "INBOUND", + "status": "AWAITING_COUNTERPARTY", + "rail": "TRP", + "asset": "ETH", + "amount": "1.25", + "originator_vasp": "Example Counterparty VASP", + "beneficiary_vasp": "Example Counterparty VASP", + "originator_wallet": "0x77ccff0cc624a53cd8b8d8703ced826710211ec7", + "beneficiary_wallet": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb", + "originator_memo": "", + "beneficiary_memo": "", + "originator_wallet_type": "vasp", + "beneficiary_wallet_type": "vasp", + "matched_transaction_id": "e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24", + "deadline_at": null, + "created_at": "2026-10-19T08:41:13Z" + }, + "message": { + "id": "2b8e6f10-9c3a-47d1-b5e2-0a4c7d9f3e66", + "transaction_id": "e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24", + "txn_id": "msg-2026-10-20-0003", + "txn_date": "2026-10-19T08:41:12Z", + "kind": "message", + "direction": "INBOUND", + "status": "COMPLETED", + "rail": "TRP", + "asset": "ETH", + "amount": "1.25", + "originator_vasp": "Origin CASP SL", + "beneficiary_vasp": "Example Counterparty VASP", + "originator_wallet": "0x77ccff0cc624a53cd8b8d8703ced826710211ec7", + "beneficiary_wallet": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb", + "originator_memo": "", + "beneficiary_memo": "", + "originator_wallet_type": "vasp", + "beneficiary_wallet_type": "vasp", + "matched_transaction_id": null, + "deadline_at": "2026-10-21T08:41:13Z", + "created_at": "2026-10-19T08:41:13Z" + } + } + } + } + }, + "400": { + "description": "Validation error: `transaction_id` is missing, is the deposit itself, is not a received Travel Rule message, or is for a different asset than the deposit; or the transaction in the path is not an inbound deposit.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Error": { + "value": { + "transaction_id": [ + "The travel rule message is for a different asset than the deposit." + ] + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "description": "Unknown deposit or message, or one of them was deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No TravelRuleTransfer matches the given query." + } + } + } + }, + "409": { + "description": "The deposit is no longer waiting for originator data, or the message is already matched to a deposit.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Error": { + "value": { + "detail": "This deposit is no longer waiting for originator data." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.unmatched_deposits.match(\n \"11111111-2222-3333-4444-555555555555\",\n transaction_id=\"e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.unmatchedDeposits.match(\"11111111-2222-3333-4444-555555555555\", {\n transaction_id: \"e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.UnmatchedDeposits.Match(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleManualMatchRequest{\n\tTransactionID: \"e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleManualMatchRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().unmatchedDeposits().match(\"11111111-2222-3333-4444-555555555555\", TravelRuleManualMatchRequest.builder()\n .transactionId(\"e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->unmatchedDeposits->match('11111111-2222-3333-4444-555555555555', [\n 'transaction_id' => 'e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.unmatched_deposits.match(\n \"11111111-2222-3333-4444-555555555555\",\n transaction_id: \"e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.UnmatchedDeposits.MatchAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleManualMatchRequest\n{\n TransactionId = \"e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + } + }, + "components": { + "schemas": { + "DocumentAIDetectedCode": { + "type": "object", + "required": [ + "type", + "page_number", + "position", + "x", + "y", + "width", + "height", + "page_width", + "page_height", + "raw_payload", + "parsed_payload" + ], + "properties": { + "type": { + "type": "string", + "description": "Detected symbology, for example `QR_CODE`, `CODE_128`, `EAN_13`, `DATA_MATRIX`, or `PDF_417`." + }, + "page_number": { + "type": "integer", + "minimum": 1, + "description": "1-based page number." + }, + "position": { + "type": "array", + "nullable": true, + "description": "Detected polygon in rendered-page pixel coordinates. `null` when no reliable location is available.", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "type": "integer" + } + } + }, + "x": { + "type": "integer", + "nullable": true, + "description": "Left edge of the bounding box in rendered-page pixels." + }, + "y": { + "type": "integer", + "nullable": true, + "description": "Top edge of the bounding box in rendered-page pixels." + }, + "width": { + "type": "integer", + "nullable": true, + "description": "Bounding-box width in rendered-page pixels." + }, + "height": { + "type": "integer", + "nullable": true, + "description": "Bounding-box height in rendered-page pixels." + }, + "page_width": { + "type": "integer", + "description": "Rendered page width in pixels." + }, + "page_height": { + "type": "integer", + "description": "Rendered page height in pixels." + }, + "raw_payload": { + "type": "string", + "description": "Raw decoder payload as a base64 string when available; otherwise an empty string." + }, + "parsed_payload": { + "nullable": true, + "description": "Decoded string or structured value. `null` when the code was located but could not be decoded." + } + } + }, + "SessionImportJob": { + "type": "object", + "required": [ + "uuid", + "status", + "import_type", + "source_format", + "source_filename", + "total_rows", + "processed_rows", + "created_count", + "updated_count", + "skipped_count", + "failed_count" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Import job id. Use this value to poll progress and fetch row errors." + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED", + "PAUSED", + "CANCELLED", + "READY" + ] + }, + "import_type": { + "type": "string", + "enum": [ + "user_verification", + "business_verification", + "status_rules", + "transactions" + ] + }, + "source_format": { + "type": "string", + "enum": [ + "csv", + "ndjson" + ] + }, + "source_filename": { + "type": "string" + }, + "total_rows": { + "type": "integer", + "format": "int64" + }, + "processed_rows": { + "type": "integer", + "format": "int64" + }, + "checkpoint_row": { + "type": "integer", + "format": "int64" + }, + "created_count": { + "type": "integer", + "format": "int64" + }, + "updated_count": { + "type": "integer", + "format": "int64" + }, + "skipped_count": { + "type": "integer", + "format": "int64" + }, + "failed_count": { + "type": "integer", + "format": "int64" + }, + "total_media_bytes": { + "type": "integer", + "format": "int64" + }, + "last_error": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "started_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "state": { + "type": "string", + "enum": [ + "ready", + "queued", + "running", + "paused", + "completed", + "errors", + "failed", + "cancelled" + ], + "description": "The job's state as the console shows it; `errors` is a completed job with rejected rows." + }, + "source": { + "type": "string", + "enum": [ + "file", + "api", + "provider" + ] + }, + "provider": { + "type": "string", + "description": "Migration source; empty for a file or API import." + }, + "provider_options": { + "type": "object" + }, + "credential_hint": { + "type": "string", + "description": "The first characters of the key the migration holds. The key itself is never returned." + }, + "credentials_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resumes_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When a job paused by the provider's rate limit resumes by itself." + }, + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "is_dry_run": { + "type": "boolean" + }, + "dry_run_summary": { + "type": "object", + "description": "What a dry run found: `total`, `by_status`, `already_in_didit`, `same_person`, `not_finished`, `failed`, `unmapped_statuses`." + }, + "face_templates_enabled": { + "type": "boolean" + }, + "lawful_basis_confirmed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "not_finished_count": { + "type": "integer", + "format": "int64" + }, + "same_person_count": { + "type": "integer", + "format": "int64" + }, + "face_templates_count": { + "type": "integer", + "format": "int64" + }, + "face_duplicates_count": { + "type": "integer", + "format": "int64" + }, + "face_blocklist_hits_count": { + "type": "integer", + "format": "int64" + }, + "created_by_name": { + "type": "string", + "nullable": true + }, + "created_by_email": { + "type": "string", + "nullable": true + } + } + }, + "SessionImportError": { + "type": "object", + "required": [ + "uuid", + "row_number", + "error", + "raw_row" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "row_number": { + "type": "integer", + "format": "int64" + }, + "external_id": { + "type": "string", + "nullable": true + }, + "error": { + "type": "string" + }, + "raw_row": { + "type": "object", + "additionalProperties": true + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BrandingCustomizationUpdate": { + "type": "object", + "description": "Partial branding update body. Provide only the fields you want to change. Hex colors accept `#RRGGBB` or `#RRGGBBAA`. Custom domain and white-label email are not accepted here.", + "properties": { + "color_primary": { + "type": "string", + "description": "Headers / primary text color." + }, + "color_secondary": { + "type": "string", + "description": "Paragraph / secondary text color." + }, + "color_background": { + "type": "string", + "description": "Page background color." + }, + "color_panel": { + "type": "string", + "description": "Panel / accent color." + }, + "color_panel_10": { + "type": "string", + "description": "Panel color at 10% opacity." + }, + "color_on_panel_1": { + "type": "string", + "description": "Support text color on panels." + }, + "color_on_panel_2": { + "type": "string", + "description": "Pill / badge color on panels." + }, + "color_on_background": { + "type": "string", + "description": "Text color on background." + }, + "color_button_1": { + "type": "string", + "description": "Primary button color." + }, + "color_button_2": { + "type": "string", + "description": "Secondary button color." + }, + "color_button_text_1": { + "type": "string", + "description": "Primary button text color." + }, + "color_button_text_2": { + "type": "string", + "description": "Secondary button text color." + }, + "color_pill_text": { + "type": "string", + "description": "Text color for pills, inputs and selectors." + }, + "border_radius_panel": { + "type": "integer", + "minimum": 0, + "description": "Panel corner radius in pixels." + }, + "border_radius_buttons": { + "type": "integer", + "minimum": 0, + "description": "Button corner radius in pixels." + }, + "font_family": { + "type": "string", + "description": "Google Font family name (e.g. `Inter`, `AR One Sans`)." + }, + "font_weight": { + "type": "string", + "description": "Font weight available for the selected family (e.g. `regular`, `500`, `700`)." + }, + "app_public_name": { + "type": "string", + "nullable": true, + "description": "Public name shown in the verification flow." + }, + "privacy_policy_url": { + "type": "string", + "nullable": true, + "description": "Privacy policy URL shown to the end user." + }, + "skip_welcome_screen": { + "type": "boolean", + "description": "Skip the initial welcome screen." + }, + "disable_login_with_didit": { + "type": "boolean", + "description": "Continue as guest instead of showing the login-with-Didit screen. Cannot be set back to `false` while a custom domain is configured (400)." + }, + "hide_progress_bar": { + "type": "boolean", + "description": "Hide the progress bar during the flow." + }, + "callback_seconds": { + "type": "integer", + "minimum": 0, + "description": "Delay (seconds) before redirecting to the callback URL." + }, + "delete_logo_square": { + "type": "boolean", + "description": "When `true`, removes the current square logo." + }, + "delete_logo_rectangular": { + "type": "boolean", + "description": "When `true`, removes the current rectangular logo." + }, + "delete_logo_rectangular_dark": { + "type": "boolean", + "description": "When `true`, removes the current dark-mode logo, and `logo_rectangular_dark` then reads `null`. If the same multipart request also uploads `image_rectangular_dark`, the upload wins." + }, + "delete_favicon": { + "type": "boolean", + "description": "When `true`, removes the current favicon." + } + } + }, + "BrandingCustomization": { + "type": "object", + "description": "White-label branding for an application: colors, fonts, radii, logo URLs and UI/UX options. Logo URLs are read-only (set them by uploading images). Custom domain and white-label email are not included.", + "properties": { + "color_primary": { + "type": "string" + }, + "color_secondary": { + "type": "string" + }, + "color_background": { + "type": "string" + }, + "color_panel": { + "type": "string" + }, + "color_panel_10": { + "type": "string" + }, + "color_on_panel_1": { + "type": "string" + }, + "color_on_panel_2": { + "type": "string" + }, + "color_on_background": { + "type": "string" + }, + "color_button_1": { + "type": "string" + }, + "color_button_2": { + "type": "string" + }, + "color_button_text_1": { + "type": "string" + }, + "color_button_text_2": { + "type": "string" + }, + "color_pill_text": { + "type": "string" + }, + "border_radius_panel": { + "type": "integer" + }, + "border_radius_buttons": { + "type": "integer" + }, + "font_family": { + "type": "string" + }, + "font_weight": { + "type": "string" + }, + "font_url": { + "type": "string", + "nullable": true, + "description": "Read-only. Resolved Google Fonts stylesheet URL for the selected family." + }, + "logo_square": { + "type": "string", + "nullable": true, + "description": "Read-only URL of the square logo. Upload via `image_square`." + }, + "logo_rectangular": { + "type": "string", + "nullable": true, + "description": "Read-only URL of the rectangular logo. Upload via `image_rectangular`." + }, + "logo_rectangular_dark": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Read-only. Temporary signed URL of the rectangular logo for dark mode, shown on a dark background. `null` when no dark logo is set or rectangular logos are not shown for your account. Upload it with `image_rectangular_dark`." + }, + "favicon": { + "type": "string", + "nullable": true, + "description": "Read-only URL of the favicon. Upload via `image_favicon`." + }, + "app_public_name": { + "type": "string", + "nullable": true + }, + "privacy_policy_url": { + "type": "string", + "nullable": true + }, + "skip_welcome_screen": { + "type": "boolean" + }, + "disable_login_with_didit": { + "type": "boolean" + }, + "hide_progress_bar": { + "type": "boolean" + }, + "callback_seconds": { + "type": "integer" + } + } + }, + "WebhookEvent": { + "type": "string", + "description": "Supported webhook event name. Use these exact strings in `subscribed_events`; unsupported values are rejected.", + "enum": [ + "status.updated", + "data.updated", + "user.status.updated", + "user.data.updated", + "business.status.updated", + "business.data.updated", + "activity.created", + "transaction.created", + "transaction.status.updated", + "travel_rule.status.updated", + "workflow.compliance_changed", + "transaction_rule_suggestions.created", + "auth.signup", + "auth.login", + "auth.login_failed", + "auth.password_reset_requested", + "auth.password_reset_completed", + "auth.mfa_changed", + "auth.new_device", + "auth.account_locked", + "investigator.report.ready", + "investigator.decision.automated", + "investigator.decision.overridden", + "investigator.decision.contested", + "investigator.question.raised", + "investigator.automation.paused", + "investigator.rfi.answered", + "investigator.rfi.overdue", + "investigator.drift.detected", + "investigator.sla.breached", + "investigator.qa.failed", + "investigator.external.report.ready" + ] + }, + "WebhookSubscribedEvents": { + "type": "array", + "description": "Event filter for this webhook destination. Didit delivers only webhooks whose event type exactly matches one of these values — there is no wildcard subscription. When sent, the list must contain at least one valid event (an explicit `[]` is rejected with 400); a destination whose list is empty (field omitted at create) receives nothing. On update the list is replaced wholesale, never merged.", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "example": [ + "status.updated", + "data.updated" + ] + }, + "PhoneVerificationSendResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the verification. A `Retry` send returns the same `request_id` as the original send. This id appears in the Business Console, is returned again by a finalized `POST /v3/phone/check/`, and can be passed to `GET /v3/session/{sessionId}/decision/`." + }, + "status": { + "type": "string", + "enum": [ + "Success", + "Retry", + "Blocked" + ], + "description": "`Success` — OTP sent to a new verification. `Retry` — OTP re-sent for the pending verification created by a previous send. `Blocked` — the anti-fraud layer refused to send; the verification is immediately finalized as `Declined` and nothing is billed." + }, + "reason": { + "type": "string", + "nullable": true, + "description": "Why a send was `Blocked` (e.g., `repeated_attempts`, `suspicious`, `spam`). `null` on `Success` and `Retry`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` stored on the session (from the first send)." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` stored on the session (from the first send)." + } + } + }, + "PhoneVerificationCheckResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "On `Approved`/`Declined`: the session id of the matched verification — identical to the `request_id` returned by `POST /v3/phone/send/`. On `Failed` and `Expired or Not Found`: a random one-off UUID that cannot be looked up later." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "Failed", + "Expired or Not Found" + ], + "description": "`Approved` — correct code, no declining risk. `Declined` — terminal: a declining risk matched or the attempt budget (3) was exhausted. `Failed` — wrong code, attempts remaining. `Expired or Not Found` — no pending verification for this number in the last 5 minutes." + }, + "message": { + "type": "string", + "description": "Human-readable explanation of the outcome, including the number of attempts remaining after a wrong code." + }, + "phone": { + "type": "object", + "nullable": true, + "description": "Full phone report. Present (non-null) only on finalized outcomes (`Approved`/`Declined`); `null` on `Failed` and absent on `Expired or Not Found`.", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Not Finished", + "Expired" + ], + "description": "Final status of the phone verification. Matches the top-level `status` for finalized checks." + }, + "phone_number_prefix": { + "type": "string", + "description": "Country calling code with leading `+` (e.g., `+1`).", + "example": "+1" + }, + "phone_number": { + "type": "string", + "description": "National number without the country calling code.", + "example": "4155552671" + }, + "full_number": { + "type": "string", + "description": "Complete number in E.164 format.", + "example": "+14155552671" + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of the number.", + "example": "US" + }, + "country_name": { + "type": "string", + "description": "Full country name derived from `country_code`.", + "example": "United States" + }, + "carrier": { + "type": "object", + "description": "Current network serving the number, from the carrier lookup run at finalization.", + "properties": { + "name": { + "type": "string", + "description": "Carrier name. Empty string when the lookup could not identify it." + }, + "type": { + "type": "string", + "description": "Line type reported by the lookup: `mobile`, `fixed_line`, `voip`, `isp`, `vpn`, `toll_free`, `premium_rate`, `pager`, `payphone`, `satellite`, `service`, `shared_cost`, `short_codes_commercial`, `calling_cards`, `local_rate`, `universal_access`, `voice_mail`, `other`, or `unknown`." + } + } + }, + "is_disposable": { + "type": "boolean", + "description": "`true` when the number belongs to a temporary/burner number service." + }, + "is_virtual": { + "type": "boolean", + "description": "`true` when the line type is virtual (`voip`, `isp`, or `vpn`)." + }, + "verification_method": { + "type": "string", + "description": "Channel that actually delivered the OTP once delivery is confirmed (e.g., `whatsapp`, `sms`); until then, the requested channel." + }, + "verification_attempts": { + "type": "integer", + "description": "Number of OTP sends for this verification (1, or 2 after a retry)." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the correct code was entered. `null` if the verification was never approved." + }, + "warnings": { + "type": "array", + "description": "Risk warnings recorded during the verification (duplicate, disposable, VoIP, attempts exceeded, blocklist, etc.). Empty array when there are none.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature that produced the warning. Always `PHONE` here." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk code." + }, + "additional_data": { + "type": "object", + "nullable": true, + "description": "Risk-specific context (e.g., the duplicated session id for duplicate risks). `null` when the risk carries no extra data." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` risks decline the verification, `warning` flags it for review, `information` is recorded without affecting the status." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "lifecycle": { + "type": "array", + "description": "Chronological audit trail of the verification. Event `type` is one of `PHONE_VERIFICATION_MESSAGE_SENT`, `PHONE_VERIFICATION_RETRY_MESSAGE_SENT`, `PHONE_VERIFICATION_BLOCKED`, `PHONE_DELIVERY_DELIVERED`, `PHONE_DELIVERY_UNDELIVERABLE`, `VALID_CODE_ENTERED`, `INVALID_CODE_ENTERED`, `PHONE_VERIFICATION_APPROVED`, `PHONE_VERIFICATION_DECLINED`, `PHONE_VERIFICATION_IN_REVIEW`, `PHONE_VERIFICATION_EXPIRED`.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Event type." + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the event happened." + }, + "details": { + "type": "object", + "nullable": true, + "description": "Event-specific payload. Send events: `{status, reason, channel, actual_channel}` (`actual_channel` is filled once delivery is confirmed). Delivery events: `{channel, status}`. Code-entry events: `{code_tried, status}`. Final-status events: `null`, or `{reason}` when declined or in review." + }, + "fee": { + "type": "number", + "description": "USD amount billed for this event. Non-zero only on send events (the delivery price); 0 for code entries, delivery confirmations, and status events." + } + } + } + }, + "matches": { + "type": "array", + "description": "Other sessions of your application (KYC, KYB, or API) where the same phone number was used by a different user (different `vendor_data`). Up to 5 entries, a synthetic list-entry blocklist match first when present (session-derived matches keep query order). Empty array when there are no matches.", + "items": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the other verification that used this phone number." + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of that session." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of that session." + }, + "verification_date": { + "type": "string", + "description": "When that session was created (`YYYY-MM-DDTHH:MM:SSZ`)." + }, + "phone_number": { + "type": "string", + "description": "The matched phone number." + }, + "status": { + "type": "string", + "description": "Status of that session (e.g., `Approved`, `Declined`, `In Progress`)." + }, + "is_blocklisted": { + "type": "boolean", + "description": "Whether the phone number is on your application's blocklist." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "For API-type sessions, the standalone API that created it (e.g., `PHONE_VERIFICATION`); `null` for regular verification sessions." + }, + "source": { + "type": "string", + "description": "Where the match was found. Usually `session`; a blocklist hit via an organization list entry prepends a synthetic match with `source: \"list_entry\"` and null `session_id`/`session_number`." + } + } + } + }, + "phone_social": { + "$ref": "#/components/schemas/PhoneSocialFootprint" + }, + "phone_intelligence": { + "allOf": [ + { + "$ref": "#/components/schemas/PhoneIntelligence" + } + ], + "description": "Phone intelligence signals. Present only when `enable_enrichment` was `true` on the check." + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of the matched verification's session. `null` on `Expired or Not Found`." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "`metadata` of the matched verification's session. `null` on `Expired or Not Found`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this check response." + } + } + }, + "EmailVerificationSendResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the verification. A `Retry` send returns the same `request_id` as the original send. This id appears in the Business Console, is returned again by a finalized `POST /v3/email/check/`, and can be passed to `GET /v3/session/{sessionId}/decision/`." + }, + "status": { + "type": "string", + "enum": [ + "Success", + "Retry", + "Undeliverable" + ], + "description": "`Success` — OTP emailed to a new verification (billed). `Retry` — fresh OTP emailed for the pending verification created by a previous send (free). `Undeliverable` — the address failed deliverability validation or the message could not be sent; the verification is immediately finalized as `Declined` and nothing is billed." + }, + "reason": { + "type": "string", + "nullable": true, + "enum": [ + "email_can_not_be_delivered", + null + ], + "description": "`email_can_not_be_delivered` when `status` is `Undeliverable`; `null` otherwise." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` stored on the session (from the first send)." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` stored on the session (from the first send)." + } + } + }, + "EmailVerificationCheckResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "On `Approved`/`Declined`: the session id of the matched verification — identical to the `request_id` returned by `POST /v3/email/send/`. On `Failed` and `Expired or Not Found`: a random one-off UUID that cannot be looked up later." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "Failed", + "Expired or Not Found" + ], + "description": "`Approved` — correct code, no declining risk. `Declined` — terminal: a declining risk matched or the attempt budget (3) was exhausted. `Failed` — wrong code, attempts remaining. `Expired or Not Found` — no pending verification for this address in the last 5 minutes." + }, + "message": { + "type": "string", + "description": "Human-readable explanation of the outcome, including the number of attempts remaining after a wrong code." + }, + "email": { + "type": "object", + "nullable": true, + "description": "Full email report. Present (non-null) only on finalized outcomes (`Approved`/`Declined`); `null` on `Failed` and absent on `Expired or Not Found`.", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Not Finished", + "Expired" + ], + "description": "Final status of the email verification. Matches the top-level `status` for finalized checks." + }, + "email": { + "type": "string", + "format": "email", + "description": "The verified email address.", + "example": "alice@example.com" + }, + "is_breached": { + "type": "boolean", + "description": "`true` when the address appears in known data breaches (breach intelligence lookup run at finalization)." + }, + "breaches": { + "type": "array", + "description": "Up to 5 most recent known breaches containing this address. Empty array (or omitted) when `is_breached` is `false`.", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Breach identifier." + }, + "domain": { + "type": "string", + "description": "Domain of the breached service." + }, + "breach_date": { + "type": "string", + "format": "date", + "description": "Date the breach occurred." + }, + "breach_emails_count": { + "type": "integer", + "description": "Total number of accounts exposed in the breach." + }, + "description": { + "type": "string", + "description": "Human-readable description of the breach (may contain HTML markup)." + }, + "logo_path": { + "type": "string", + "description": "URL of the breached service's logo." + }, + "data_classes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Categories of data exposed, in snake_case (e.g., `email_addresses`, `passwords`)." + }, + "is_verified": { + "type": "boolean", + "description": "Whether the breach has been verified as legitimate." + } + } + } + }, + "is_disposable": { + "type": "boolean", + "description": "`true` when the domain belongs to a known disposable/temporary-mail provider." + }, + "is_undeliverable": { + "type": "boolean", + "description": "`true` when the address cannot receive mail (failed syntax or DNS/MX validation, or the message bounced)." + }, + "verification_attempts": { + "type": "integer", + "description": "Number of OTP sends for this verification (1, or 2 after a retry)." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the correct code was entered. `null` if the verification was never approved." + }, + "warnings": { + "type": "array", + "description": "Risk warnings recorded during the verification (duplicate, breached, disposable, undeliverable, attempts exceeded, blocklist, etc.). Empty array when there are none.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature that produced the warning. Always `EMAIL` here." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk code." + }, + "additional_data": { + "type": "object", + "nullable": true, + "description": "Risk-specific context (e.g., the duplicated session id for duplicate risks). `null` when the risk carries no extra data." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` risks decline the verification, `warning` flags it for review, `information` is recorded without affecting the status." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "lifecycle": { + "type": "array", + "description": "Chronological audit trail of the verification. Event `type` is one of `EMAIL_VERIFICATION_MESSAGE_SENT`, `EMAIL_VERIFICATION_RETRY_MESSAGE_SENT`, `VALID_CODE_ENTERED`, `INVALID_CODE_ENTERED`, `EMAIL_VERIFICATION_APPROVED`, `EMAIL_VERIFICATION_DECLINED`, `EMAIL_VERIFICATION_IN_REVIEW`, `EMAIL_VERIFICATION_EXPIRED`.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Event type." + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the event happened." + }, + "details": { + "type": "object", + "nullable": true, + "description": "Event-specific payload. Send events: `{status, reason}`. Code-entry events: `{code_tried, status}`. Final-status events: `null`, or `{reason}` when declined or in review." + }, + "fee": { + "type": "number", + "description": "USD amount billed for this event. Non-zero only on the first successful send; 0 for retries, code entries, and status events." + } + } + } + }, + "matches": { + "type": "array", + "description": "Other sessions of your application (KYC, KYB, or API) where the same email address was used by a different user (different `vendor_data`). Up to 5 entries; a synthetic list-entry blocklist match is prepended when present (and only when no session-derived blocklisted email exists) — session-derived matches keep created_at order. Empty array when there are no matches.", + "items": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the other verification that used this email address." + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of that session." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of that session." + }, + "verification_date": { + "type": "string", + "description": "When that session was created (`YYYY-MM-DDTHH:MM:SSZ`)." + }, + "email": { + "type": "string", + "description": "The matched email address." + }, + "status": { + "type": "string", + "description": "Status of that session (e.g., `Approved`, `Declined`, `In Progress`)." + }, + "is_blocklisted": { + "type": "boolean", + "description": "Whether the email address is on your application's blocklist." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "For API-type sessions, the standalone API that created it (e.g., `PHONE_VERIFICATION`); `null` for regular verification sessions." + }, + "source": { + "type": "string", + "description": "Where the match was found. Usually `session`; a blocklist hit via an organization list entry prepends a synthetic match with `source: \"list_entry\"` and null `session_id`/`session_number`." + } + } + } + }, + "email_social": { + "$ref": "#/components/schemas/EmailSocialFootprint" + }, + "email_intelligence": { + "allOf": [ + { + "$ref": "#/components/schemas/EmailIntelligence" + } + ], + "description": "Email intelligence signals. Present only when `enable_enrichment` was `true` on the check." + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of the matched verification's session. `null` on `Expired or Not Found`." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "`metadata` of the matched verification's session. `null` on `Expired or Not Found`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this check response." + } + } + }, + "WorkflowListItem": { + "type": "object", + "description": "One workflow version in the list view.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Per-version UUID. Use as `{settings_uuid}` for get/update/delete, and as `workflow_id` when creating sessions." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable group ID shared by every version of the same workflow. Equals `uuid` for version 1." + }, + "workflow_label": { + "type": "string", + "description": "Display name for the workflow." + }, + "workflow_type": { + "type": "string", + "nullable": true, + "description": "Base type for simple workflows: `kyc`, `kyb`, `adaptive_age_verification`, `biometric_authentication`, etc. `null` for graph-based workflows without a KYB feature." + }, + "is_default": { + "type": "boolean", + "description": "Whether this is the default workflow for new sessions." + }, + "is_archived": { + "type": "boolean" + }, + "is_white_label_enabled": { + "type": "boolean", + "description": "Whether white-label customization applies to sessions created with this workflow." + }, + "total_price": { + "type": "number", + "description": "Fixed price per verification in USD (JSON number, excludes variable-priced features such as DATABASE_VALIDATION and PHONE_VERIFICATION)." + }, + "min_price": { + "type": "number", + "description": "Cheapest fixed price across workflow-graph paths, in USD." + }, + "max_price": { + "type": "number", + "description": "Most expensive fixed price across workflow-graph paths, in USD." + }, + "features": { + "type": "string", + "description": "Enabled features as a single `\" + \"`-joined string, e.g. `\"OCR + LIVENESS + FACE_MATCH\"`. Split on `\" + \"` to get a list." + }, + "is_simple_workflow": { + "type": "boolean", + "description": "True when the workflow has no custom workflow graph." + }, + "is_editable": { + "type": "boolean", + "description": "True when this version is a `draft` (drafts are editable in place; published versions are immutable in the Console)." + }, + "workflow_url": { + "type": "string", + "format": "uri", + "description": "Public shareable verification URL for this workflow (`https://verify.didit.me/u/`)." + }, + "max_retry_attempts": { + "type": "integer", + "description": "Maximum retry attempts allowed after a declined verification (0-10)." + }, + "retry_window_days": { + "type": "integer", + "nullable": true, + "description": "Rolling window in days used to count retries (1-365). `null` = all-time limit." + }, + "session_expiration_time": { + "type": "integer", + "description": "Seconds before an unfinished session expires (3600-2419200)." + }, + "version": { + "type": "integer", + "description": "Version number within the `workflow_id` group, starting at 1." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Version status. New sessions use the latest `published` version of a group." + }, + "has_draft": { + "type": "boolean", + "description": "True when any version in this `workflow_id` group is currently a draft." + }, + "created_by": { + "type": "object", + "nullable": true, + "description": "Who created this version: `{name, email, type}` where `type` is `user` (Console user, `email` set) or `api_key` (`name` is the client id, `email` is null). `null` when no creator was recorded.", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string", + "nullable": true + }, + "type": { + "type": "string", + "enum": [ + "user", + "api_key" + ] + } + } + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "SimpleWorkflowRequest": { + "type": "object", + "description": "Create a simple linear workflow. The API converts this feature list into a node-based workflow graph internally. STRICT field whitelist: any key not listed here (including `workflow_type`) is rejected with a 400.", + "required": [ + "features" + ], + "properties": { + "workflow_label": { + "type": "string", + "maxLength": 50, + "default": "ID Verification", + "description": "Display name for the workflow (max 50 characters). Defaults to `\"ID Verification\"` when omitted — always set it so workflows stay distinguishable.", + "example": "Standard KYC" + }, + "is_default": { + "type": "boolean", + "description": "Set this workflow as the default for new sessions." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Omit this field to create a published workflow ready for sessions. Use `draft` only when you want to save without publishing." + }, + "features": { + "type": "array", + "minItems": 1, + "description": "Verification features in execution order. The API links each item to the next one and adds one final status node automatically.", + "items": { + "$ref": "#/components/schemas/SimpleWorkflowFeature" + } + }, + "is_white_label_enabled": { + "type": "boolean", + "description": "Enable white-label customization for sessions created with this workflow.", + "default": false + }, + "is_desktop_allowed": { + "type": "boolean", + "description": "Allow the verification flow to run on desktop browsers.", + "default": false + }, + "max_retry_attempts": { + "type": "integer", + "minimum": 0, + "maximum": 10, + "default": 3, + "description": "Maximum retry attempts allowed after a declined verification. `0` blocks the user after the first decline." + }, + "retry_window_days": { + "type": "integer", + "minimum": 1, + "maximum": 365, + "nullable": true, + "default": 7, + "description": "Rolling window in days used to count retries. `null` enforces an all-time limit." + }, + "face_liveness_max_attempts": { + "type": "integer", + "minimum": 1, + "maximum": 3, + "default": 3, + "description": "Maximum liveness submissions per session. Default 3: one initial attempt plus two retries. This value is also copied into generated liveness and age-estimation workflow nodes unless the feature-level config overrides it." + }, + "face_match_max_attempts": { + "type": "integer", + "minimum": 1, + "maximum": 3, + "default": 3, + "description": "Maximum face-match submissions per session. Default 3: one initial attempt plus two retries. This value is also copied into generated face-match workflow nodes unless the feature-level config overrides it." + }, + "session_expiration_time": { + "type": "integer", + "minimum": 3600, + "maximum": 2419200, + "default": 604800, + "description": "Seconds before an unfinished session expires. Minimum 1 hour (3600), maximum 4 weeks (2419200)." + } + } + }, + "SimpleWorkflowUpdateRequest": { + "type": "object", + "description": "Update workflow metadata or replace the full linear feature list. STRICT field whitelist: any key not listed here is rejected with a 400.", + "properties": { + "workflow_label": { + "type": "string", + "maxLength": 50, + "description": "Display name for the workflow (max 50 characters)." + }, + "is_default": { + "type": "boolean", + "description": "Set this workflow as the default for new sessions." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Controls versioning. Omit to update the current version in place (drafts auto-publish). `published` on an already-published version creates and returns a NEW version. `draft` is only valid while the version is still a draft." + }, + "features": { + "type": "array", + "minItems": 1, + "description": "If provided, this replaces the full feature order and configuration.", + "items": { + "$ref": "#/components/schemas/SimpleWorkflowFeature" + } + }, + "is_white_label_enabled": { + "type": "boolean", + "description": "Enable white-label customization for sessions created with this workflow." + }, + "is_desktop_allowed": { + "type": "boolean", + "description": "Allow the verification flow to run on desktop browsers." + }, + "max_retry_attempts": { + "type": "integer", + "minimum": 0, + "maximum": 10, + "description": "Maximum retry attempts allowed after a declined verification. `0` blocks the user after the first decline." + }, + "retry_window_days": { + "type": "integer", + "minimum": 1, + "maximum": 365, + "nullable": true, + "description": "Rolling window in days used to count retries. `null` enforces an all-time limit." + }, + "face_liveness_max_attempts": { + "type": "integer", + "minimum": 1, + "maximum": 3, + "description": "Maximum liveness submissions per session. This value is also copied into generated liveness and age-estimation workflow nodes unless the feature-level config overrides it." + }, + "face_match_max_attempts": { + "type": "integer", + "minimum": 1, + "maximum": 3, + "description": "Maximum face-match submissions per session. This value is also copied into generated face-match workflow nodes unless the feature-level config overrides it." + }, + "session_expiration_time": { + "type": "integer", + "minimum": 3600, + "maximum": 2419200, + "description": "Seconds before an unfinished session expires. Minimum 1 hour (3600), maximum 4 weeks (2419200)." + } + } + }, + "SimpleWorkflowFeature": { + "type": "object", + "required": [ + "feature" + ], + "properties": { + "feature": { + "type": "string", + "description": "Feature to run. Use the uppercase values shown here.", + "enum": [ + "OCR", + "NFC", + "LIVENESS", + "FACE_MATCH", + "PROOF_OF_ADDRESS", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "DATABASE_VALIDATION", + "AML", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "SIGNATURE" + ] + }, + "config": { + "type": "object", + "description": "Feature-specific settings. For `QUESTIONNAIRE`, include `questionnaire_uuid` with the `questionnaire_id` returned by the questionnaire create endpoint. For `SIGNATURE`, a workflow that goes live needs a document to sign: set `signature_documents_source` to `session` or `both` so each session can bring its own, or send `status: \"draft\"` and add the documents before you publish. `signature_level: \"aes\"` is refused with `aes_not_available` while `signature_availability.aes` is `false` on the workflow. For `OCR`, `methods` selects which ID Verification methods each country may use (document capture, non-doc lookup against a government or other authoritative source, and digital identity wallets); omit it and every country is document capture only. For `SIGNATURE`, see [E-signature settings](/management-api/workflows/feature-configs#e-signature): `signature_level` accepts `simple` (the default, no ID verification, email or phone needed) and `aes` for now, and workflow documents and their fields are managed in the console, so through the API send documents and fields per session with `signature_documents_source: \"session\"`.", + "additionalProperties": true + }, + "label": { + "type": "string", + "description": "Optional internal label for this workflow node." + } + } + }, + "ReviewItem": { + "type": "object", + "description": "One entry in a session's review / activity feed. Status changes carry `previous_status`, `new_status`, `previous_value`, `new_value`, and `changed_fields`; comments carry `comment` and (for Console comments) `mentioned_emails`; the remaining fields are `null` or empty when they do not apply to the activity type.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of this feed entry.", + "example": "de0561bc-5583-4409-9171-1a31a4c8a5f4" + }, + "activity_type": { + "type": "string", + "enum": [ + "COMMENT", + "STATUS_UPDATED", + "KYC_DATA_UPDATED", + "POA_DATA_UPDATED", + "FILE_ADDED", + "FILE_REMOVED", + "AML_ONGOING_MATCH", + "AML_STATUS_UPDATED", + "AML_HIT_STATUS_UPDATED", + "KYB_DATA_UPDATED", + "KYB_DOCUMENT_UPLOADED", + "TAG_ADDED", + "TAG_REMOVED", + "FEATURE_OVERRIDE_PENDING", + "FEATURE_OVERRIDE_APPLIED", + "FEATURE_OVERRIDE_CANCELLED" + ], + "description": "What kind of activity this entry records. `STATUS_UPDATED` rows come from manual decisions (`PATCH /v3/session/{session_id}/update-status/`) and from entries added via `POST …/reviews/` (which default to this type even when no status is recorded). `COMMENT` rows come from Console reviewers; Console detail-page views are also stored as `COMMENT` rows flagged with `metadata.system_event = \"DETAIL_VIEWED\"`. The remaining types track data edits, file changes, tags, and AML monitoring activity. STATUS_UPDATED rows are also written by system actors (e.g. AML ongoing monitoring with actor_type SYSTEM, and automatic feature-status updates), not only by the update-status endpoint and manual review posts. `FEATURE_OVERRIDE_PENDING`, `FEATURE_OVERRIDE_APPLIED` and `FEATURE_OVERRIDE_CANCELLED` record an override of one check's status (`PATCH /v3/session/{session_id}/features/{node_id}/update-status/`): requested to wait for the flow to finish, applied, or cancelled. They carry the check's status in `previous_value` / `new_value` (`{\"status\": ...}`), leave `previous_status` and `new_status` `null`, and name the override in `metadata` (`feature_override_id`, `node_id`, `feature_model`, `forced`, `state`, `cancel_reason`). The entry for the request carries the reviewer's `comment`; an override applied when the flow finishes is recorded with `actor_type` `SYSTEM`.", + "example": "STATUS_UPDATED" + }, + "actor_display": { + "type": "string", + "description": "Human-readable author label, resolved in order: actor's full name → Console account identifier → local part of `actor_email` → `\"API Client\"` (for `API_KEY` actors) → `\"System\"` (for `SYSTEM` actors) → `\"Unknown\"`. Entries created via `POST …/reviews/` always display `\"Unknown\"` because that endpoint records no actor attribution.", + "example": "API Client" + }, + "actor_email": { + "type": "string", + "nullable": true, + "description": "Email of the Console user who performed the action. `null` for API-key and system actors.", + "example": null + }, + "actor_type": { + "type": "string", + "enum": [ + "API_KEY", + "CONSOLE_USER", + "SYSTEM" + ], + "description": "Who performed the action: an API key, a Console user, or Didit itself (`SYSTEM`, e.g. AML ongoing monitoring). Entries created via `POST …/reviews/` carry the default `CONSOLE_USER` regardless of the caller.", + "example": "API_KEY" + }, + "new_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Session status recorded by this entry (for status updates). `null` for comments and data-change entries.", + "example": "Approved" + }, + "previous_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Status the session had before the change. Populated on `STATUS_UPDATED` entries written by the update-status endpoint; `null` on entries added via `POST …/reviews/`. Also populated on system-written STATUS_UPDATED rows.", + "example": "In Review" + }, + "comment": { + "type": "string", + "nullable": true, + "description": "Free-text note attached to the entry, or `null`.", + "example": "Document re-checked manually; address matches." + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + }, + "description": "Console users `@email`-mentioned in the comment. Always empty for entries created via this API — mention processing is a Console-only feature.", + "example": [] + }, + "previous_value": { + "type": "object", + "nullable": true, + "description": "Snapshot of the changed data before the action, e.g. `{\"status\": \"In Review\"}` on a status change. `null` when nothing was changed.", + "example": { + "status": "In Review" + } + }, + "new_value": { + "type": "object", + "nullable": true, + "description": "Snapshot of the changed data after the action, e.g. `{\"status\": \"Approved\"}`. `null` when nothing was changed.", + "example": { + "status": "Approved" + } + }, + "changed_fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the fields modified by this activity (e.g. `[\"status\"]`). Empty for comments and for entries added via `POST …/reviews/`.", + "example": [ + "status" + ] + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Extra context for system-generated entries. Console detail-page views carry `{\"system_event\": \"DETAIL_VIEWED\", \"detail_type\": \"verification\"}` so they can be filtered out of notification logic. `null` for ordinary entries.", + "example": null + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the entry was written, ISO 8601 UTC.", + "example": "2026-06-12T00:19:31.918735Z" + } + } + }, + "QuestionnaireListItem": { + "type": "object", + "description": "One questionnaire version in the list view.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Per-version UUID. Use as `{questionnaire_uuid}` for get/update/delete and as `questionnaire_uuid` in a workflow's QUESTIONNAIRE feature config." + }, + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, + "default_language": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "is_simple_questionnaire": { + "type": "boolean", + "description": "True for linear questionnaires created through this API; false for graph-based questionnaires built in the Console." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "question_types": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Uppercase element types of the answerable questions, in creation order (e.g. `SHORT_TEXT`, `MULTIPLE_CHOICE`, `FILE_UPLOAD`). Structural `SECTION_HEADER` and `SEPARATOR` elements are excluded. One entry per question, so values can repeat." + }, + "workflow_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of workflows using this questionnaire. Currently always `[]` on this endpoint (populated only in the Console listing)." + }, + "questionnaire_group_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier that groups all versions of the same questionnaire." + }, + "version": { + "type": "integer", + "description": "Version number within the group, starting at 1." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ] + }, + "published_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this version was published. `null` for drafts." + }, + "is_editable": { + "type": "boolean", + "description": "True when this version is a `draft`." + } + } + }, + "SimpleQuestionnaireRequest": { + "type": "object", + "description": "Create a simple linear questionnaire. The API converts `form_elements` into the internal questionnaire graph.", + "required": [ + "title", + "languages", + "form_elements" + ], + "properties": { + "title": { + "type": "string", + "description": "Internal name for the questionnaire." + }, + "description": { + "type": "string", + "nullable": true + }, + "languages": { + "type": "array", + "description": "Languages included in every label. Must include `en`.", + "items": { + "type": "string" + }, + "example": [ + "en" + ] + }, + "default_language": { + "type": "string", + "description": "Default language. Must be included in `languages`.", + "example": "en" + }, + "is_active": { + "type": "boolean", + "default": true + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Omit this field to create a published questionnaire ready for workflows." + }, + "form_elements": { + "type": "array", + "minItems": 1, + "description": "Questions in the exact order users should see them. Branching and conditional fields are not supported.", + "items": { + "$ref": "#/components/schemas/SimpleQuestionnaireElement" + } + } + } + }, + "SimpleQuestionnaireUpdateRequest": { + "type": "object", + "description": "Update questionnaire metadata or replace the full linear `form_elements` list.", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, + "default_language": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ] + }, + "form_elements": { + "type": "array", + "minItems": 1, + "description": "If provided, this replaces the full linear question list.", + "items": { + "$ref": "#/components/schemas/SimpleQuestionnaireElement" + } + } + } + }, + "SimpleQuestionnaireElement": { + "type": "object", + "required": [ + "element_type", + "label" + ], + "properties": { + "id": { + "type": "string", + "description": "Optional stable question id. If omitted, the API generates `q1`, `q2`, etc. Use lowercase letters, numbers, and underscores for readability.", + "example": "source_of_funds" + }, + "element_type": { + "type": "string", + "description": "Question type. The API accepts these lowercase values and their uppercase equivalents.", + "enum": [ + "short_text", + "long_text", + "dropdown", + "single_choice", + "multiple_choice", + "number", + "image", + "file_upload", + "time", + "email", + "address", + "phone", + "country", + "date_picker", + "consent", + "section_header", + "separator", + "heading", + "paragraph" + ] + }, + "label": { + "type": "object", + "description": "Question label by language code, for example `{ \"en\": \"Source of funds\" }`.", + "additionalProperties": { + "type": "string" + } + }, + "description": { + "type": "object", + "nullable": true, + "additionalProperties": { + "type": "string" + } + }, + "placeholder": { + "type": "object", + "nullable": true, + "additionalProperties": { + "type": "string" + } + }, + "is_required": { + "type": "boolean", + "default": false + }, + "options": { + "type": "array", + "description": "Use for `dropdown`, `single_choice`, and `multiple_choice`. If `value` is omitted, the API generates one from the option label.", + "items": { + "type": "object", + "required": [ + "label" + ], + "properties": { + "value": { + "type": "string" + }, + "label": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "max_files": { + "type": "integer", + "minimum": 1, + "maximum": 5, + "description": "Use with `file_upload` or `image`." + }, + "validation": { + "type": "object", + "nullable": true, + "description": "Optional format validation for `short_text`, `long_text`, or `number`. Enforced while the user fills the field (they cannot proceed until it passes) and re-checked by the API on submission.", + "properties": { + "pattern": { + "type": "string", + "description": "Regular expression (ECMA-262 subset) the value must fully match. For `short_text` and `long_text`. Do not add ^/$ anchors; the whole value is matched." + }, + "min_length": { + "type": "integer", + "minimum": 0, + "description": "Minimum number of characters. For `short_text` and `long_text`." + }, + "max_length": { + "type": "integer", + "minimum": 0, + "description": "Maximum number of characters. For `short_text` and `long_text`." + }, + "min": { + "type": "number", + "description": "Minimum numeric value. For `number`." + }, + "max": { + "type": "number", + "description": "Maximum numeric value. For `number`." + }, + "error_message": { + "type": "object", + "description": "Translated message shown when validation fails, keyed by language code.", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "QuestionnaireDetail": { + "type": "object", + "description": "Full questionnaire detail returned from GET, POST (create) and PATCH (update) endpoints.", + "properties": { + "questionnaire_id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of the questionnaire. Use this id when referencing the questionnaire from a workflow or in subsequent update/delete requests." + }, + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, + "default_language": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "is_simple_questionnaire": { + "type": "boolean" + }, + "graph": { + "type": "object", + "description": "Graph structure with start_node and nodes map." + }, + "sections": { + "type": "array", + "description": "Questionnaire content grouped into sections (derived from the graph).", + "items": { + "type": "object" + } + }, + "questionnaire_group_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier that groups all versions of the same questionnaire." + }, + "version": { + "type": "integer" + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ] + }, + "published_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "is_editable": { + "type": "boolean", + "description": "True when the questionnaire can still be edited in place (draft versions)." + } + } + }, + "UserListItem": { + "type": "object", + "description": "A verified user.", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit's stable internal UUID for this user." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your unique identifier for this user (passed when creating sessions). This can be null when no vendor identifier was supplied." + }, + "display_name": { + "type": "string", + "nullable": true, + "description": "Custom display name set by you" + }, + "full_name": { + "type": "string", + "nullable": true, + "description": "Full name extracted from verified documents" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "effective_name": { + "type": "string", + "nullable": true, + "description": "Best available name: display_name if set, otherwise full_name" + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "Lifecycle status of the user record (NOT a session status). `ACTIVE` is the default, `FLAGGED` marks the user for manual attention, `BLOCKED` prevents new sessions for this `vendor_data`." + }, + "portrait_image_url": { + "type": "string", + "nullable": true, + "description": "Presigned URL of the user's portrait photo (expires after a few hours)" + }, + "session_count": { + "type": "integer", + "description": "Total number of verification sessions for this user" + }, + "approved_count": { + "type": "integer", + "description": "Number of approved sessions" + }, + "declined_count": { + "type": "integer", + "description": "Number of declined sessions" + }, + "in_review_count": { + "type": "integer", + "description": "Number of sessions in review" + }, + "issuing_states": { + "type": "array", + "items": { + "type": "string" + }, + "description": "ISO 3166-1 alpha-3 codes of issuing countries seen on this user's approved ID documents, e.g. `[\"USA\", \"ESP\"]`. Empty array when none." + }, + "approved_emails": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Verified email addresses collected from this user's approved sessions, e.g. `[\"john@example.com\"]`." + }, + "approved_phones": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Verified phone numbers collected from this user's approved sessions, e.g. `[\"+14155551234\"]`." + }, + "features": { + "type": "object", + "description": "Aggregated per-feature status across all of this user's sessions. Possible keys: `ID_VERIFICATION`, `NFC`, `LIVENESS`, `FACE_MATCH`, `POA`, `QUESTIONNAIRE`, `EMAIL_VERIFICATION`, `PHONE`, `AML`, `IP_ANALYSIS`, `AGE_ESTIMATION`, `DATABASE_VALIDATION`. Possible values: `Approved`, `Declined`, `In Review`, `Not Finished`, `Resub Requested`." + }, + "features_list": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "description": "Same data as `features`, as an ordered array of `{feature, status}` objects." + }, + "last_session_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent session" + }, + "first_session_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the first session" + }, + "last_activity_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent activity on this user (session, transaction, status change, data edit, etc.)." + }, + "tags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + } + } + }, + "description": "Tags assigned to this user" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BlocklistItem": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email" + ] + }, + "session_id": { + "type": "string", + "format": "uuid" + }, + "session_number": { + "type": "integer" + }, + "blocklisted_at": { + "type": "string", + "format": "date-time" + }, + "face_image": { + "type": "string", + "nullable": true + }, + "document_type": { + "type": "string", + "nullable": true + }, + "document_number": { + "type": "string", + "nullable": true + }, + "phone_number": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + } + } + }, + "UserDetailItem": { + "type": "object", + "description": "Full user detail. Extends UserListItem with metadata, comments, and updated_at.", + "allOf": [ + { + "$ref": "#/components/schemas/UserListItem" + }, + { + "type": "object", + "properties": { + "metadata": { + "type": "object", + "description": "Custom metadata JSON you attached to this user. Defaults to `{}`." + }, + "tags": { + "type": "array", + "description": "Tag assignments. NOTE: on detail responses each entry is a tag *link* object (`{uuid, tag: {...}, added_by_email, added_by_name, created_at}`), unlike the flat `{uuid, name, color}` shape used on list responses.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the tag assignment (not the tag itself)." + }, + "tag": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ] + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "added_by_email": { + "type": "string", + "nullable": true + }, + "added_by_name": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + } + }, + "comments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "comment_type": { + "type": "string", + "enum": [ + "COMMENT", + "STATUS_CHANGED", + "METADATA_UPDATED", + "TAG_ADDED", + "TAG_REMOVED", + "PROFILE_UPDATED" + ] + }, + "comment": { + "type": "string", + "nullable": true + }, + "actor_email": { + "type": "string", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "previous_status": { + "type": "string", + "nullable": true, + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED", + null + ] + }, + "new_status": { + "type": "string", + "nullable": true, + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED", + null + ] + }, + "previous_value": { + "type": "object", + "nullable": true, + "description": "Previous field values for PROFILE_UPDATED entries." + }, + "new_value": { + "type": "object", + "nullable": true, + "description": "New field values for PROFILE_UPDATED entries." + }, + "changed_fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the fields changed by a PROFILE_UPDATED entry." + }, + "metadata": { + "type": "object", + "nullable": true + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string" + } + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "description": "Activity log and comments for this user (status changes, profile edits, manual notes)." + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + ] + }, + "BusinessListItem": { + "type": "object", + "description": "A verified business.", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit's stable internal UUID for this business." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your unique identifier for this business (passed when creating sessions). This can be null when no vendor identifier was supplied." + }, + "display_name": { + "type": "string", + "nullable": true, + "description": "Custom display name set by you" + }, + "legal_name": { + "type": "string", + "nullable": true, + "description": "Official legal name from registry or manual entry" + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Company registration or incorporation number" + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "Country of incorporation (ISO 3166-1 alpha-2, e.g. `GB`, `US`)." + }, + "effective_name": { + "type": "string", + "nullable": true, + "description": "Best available name: display_name if set, otherwise legal_name" + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "Lifecycle status of the business record (NOT a session status). `ACTIVE` is the default, `FLAGGED` marks it for manual attention, `BLOCKED` prevents new sessions for this `vendor_data`." + }, + "session_count": { + "type": "integer", + "description": "Total number of verification sessions for this business" + }, + "approved_count": { + "type": "integer", + "description": "Number of approved sessions" + }, + "declined_count": { + "type": "integer", + "description": "Number of declined sessions" + }, + "in_review_count": { + "type": "integer", + "description": "Number of sessions in review" + }, + "features": { + "type": "object", + "description": "Aggregated per-feature status across all of this business's KYB sessions. Currently the only key is `KYB` (the registry company check status). Possible values: `Approved`, `Declined`, `In Review`, `Not Finished`, `Resub Requested`." + }, + "features_list": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "description": "Same data as `features`, as an ordered array of `{feature, status}` objects." + }, + "last_session_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent session" + }, + "first_session_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the first session" + }, + "last_activity_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent activity (status change, session update, etc.)" + }, + "tags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + } + } + }, + "description": "Tags assigned to this business" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BusinessDetailItem": { + "type": "object", + "description": "Full business detail. Extends BusinessListItem with metadata and updated_at.", + "allOf": [ + { + "$ref": "#/components/schemas/BusinessListItem" + }, + { + "type": "object", + "properties": { + "metadata": { + "type": "object", + "description": "Custom metadata JSON you attached to this business. Defaults to `{}`." + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + ] + }, + "TransactionDetail": { + "type": "object", + "description": "Full monitoring record for one transaction, as returned by `GET /v3/transactions/{transaction_id}/` and `POST /v3/transactions/`.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit-stable transaction identifier. Use as `{transaction_id}` for follow-up calls." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the transaction. A `sandbox` transaction is stored but never evaluated, screened, notified or billed, so its empty `rule_runs` means \"not evaluated\"." + }, + "transaction_number": { + "type": "integer", + "description": "Application-scoped sequential transaction number shown in Console (e.g. `4123`)." + }, + "txn_id": { + "type": "string", + "description": "The `transaction_id` you supplied at create time (max 128 chars). Unique per application." + }, + "txn_date": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred (from `transaction_at`; defaults to submission time)." + }, + "zone_id": { + "type": "string", + "nullable": true, + "description": "IANA time zone identifier provided at create time." + }, + "transaction_type": { + "type": "string", + "description": "Top-level category as stored: `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet`, `gamblingLimitChange`, `gamblingBonusChange`, `auditTrailEvent`. Note multi-word values are echoed back in camelCase even when submitted in snake_case." + }, + "action_type": { + "type": "string", + "description": "Sub-type within the category (e.g. `deposit`, `withdrawal`, `transfer`). Defaults to the category when not supplied." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject. Stored uppercase regardless of the casing submitted (`in`/`out`/`inbound`/`outbound` are accepted on input)." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Current monitoring verdict. Transactions are created `APPROVED`; rules may flip them to `IN_REVIEW`/`DECLINED` synchronously." + }, + "source": { + "type": "string", + "enum": [ + "LIVE", + "IMPORTED" + ], + "description": "How the record originated: `LIVE` for transactions submitted through the API or SDK, `IMPORTED` for transactions loaded through the console import." + }, + "amount": { + "type": "string", + "description": "Transaction amount as a decimal string with trailing zeros stripped (e.g. `\"1500\"`, `\"0.5\"`, `\"0.123456789012345678\"`). Up to 18 decimal places." + }, + "currency": { + "type": "string", + "description": "Currency code of `amount`. Any ISO 4217 alphabetic code is accepted (e.g. `EUR`, `USD`, `NGN`), as is a crypto asset symbol (e.g. `BTC`). Prefer a currently-circulating code. Not every accepted code has an exchange rate: when none is available for the code (for example the ISO accounting unit `XSU`, or a crypto symbol Didit does not price), automatic conversion fails and `preferred_currency_amount` is returned null unless the request supplied a pre-converted `amount_in_default_currency`." + }, + "currency_type": { + "type": "string", + "nullable": true, + "description": "`fiat` or `crypto`, as submitted in `currency_kind`." + }, + "amount_in_default_currency": { + "type": "string", + "nullable": true, + "description": "Pre-converted amount you supplied, as a decimal string with trailing zeros stripped." + }, + "default_currency_code": { + "type": "string", + "nullable": true + }, + "preferred_currency_amount": { + "type": "string", + "nullable": true, + "description": "`amount` converted to the application's preferred currency, when available." + }, + "preferred_currency_code": { + "type": "string", + "nullable": true + }, + "payment_details": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference or memo from the submission." + }, + "payment_txn_id": { + "type": "string", + "nullable": true, + "description": "External payment system reference (e.g. blockchain transaction hash) from `payment_reference_id`." + }, + "score": { + "type": "integer", + "description": "Risk score accumulated by rules (typically 0–100). Higher = riskier." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ], + "description": "Categorical risk severity set by rules/providers (`UNKNOWN`, `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`). `null` until something sets it." + }, + "decision_reason_code": { + "type": "string", + "nullable": true, + "description": "Machine-readable reason for the current `status`." + }, + "decision_reason_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label for `decision_reason_code`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Convenience copy of the applicant's `vendor_data`." + }, + "metadata": { + "type": "object", + "description": "Snapshot of the `subject` and `counterparty` payloads as submitted at create time." + }, + "props": { + "type": "object", + "description": "The `custom_properties` you supplied at create time. Each key is addressable in rule conditions as `custom_values.`." + }, + "tags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ], + "description": "Whether the tag is a Didit preset or a custom tag." + } + } + }, + "description": "Tags attached to the transaction (manually or by rules)." + }, + "parties": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "role": { + "type": "string", + "enum": [ + "APPLICANT", + "REMITTER", + "BENEFICIARY", + "COUNTERPARTY" + ], + "description": "Party role. The create endpoint assigns `APPLICANT` (the subject) and `COUNTERPARTY`." + }, + "entity_type": { + "type": "string", + "enum": [ + "individual", + "company", + "external", + "unhostedWallet" + ] + }, + "kind": { + "type": "string", + "enum": [ + "USER", + "BUSINESS", + "EXTERNAL" + ], + "description": "`USER`/`BUSINESS` parties link back to a Didit-owned entity via `vendor_data`. `EXTERNAL` parties have no Didit entity behind them." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your internal user/business identifier." + }, + "full_name": { + "type": "string", + "nullable": true + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "Country code from the submitted `address.country` (typically ISO 3166-1 alpha-3)." + }, + "dob": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth from the submitted `date_of_birth`." + }, + "address": { + "type": "object", + "description": "Address object as submitted. `{}` when omitted." + }, + "institution_info": { + "type": "object", + "description": "Institution details as submitted. `{}` when omitted." + }, + "device": { + "type": "object", + "description": "Normalized device context, including server-side IP enrichment under network_context (geolocation, VPN/data-center flags) when an IP was provided.", + "properties": { + "fingerprint": { + "type": "string", + "description": "Unique device fingerprint." + }, + "persistent_device_id": { + "type": "string", + "description": "Stable cross-session device identifier (v2 fingerprint schema)." + }, + "user_agent": { + "type": "string", + "description": "Browser or app user agent string." + }, + "browser_family": { + "type": "string", + "description": "Browser family (e.g. Chrome, Firefox, Safari)." + }, + "browser_version": { + "type": "string", + "description": "Browser version string." + }, + "platform": { + "type": "string", + "description": "Platform identifier (web, ios, android, macos, windows, linux)." + }, + "os_family": { + "type": "string", + "description": "Operating system family (e.g. iOS, Android, Windows)." + }, + "os_version": { + "type": "string", + "description": "Operating system version string." + }, + "accept_language": { + "type": "string", + "description": "Accept-Language header value." + }, + "session_id": { + "type": "string", + "description": "Session identifier (stored in metadata)." + }, + "session_age_ms": { + "type": "number", + "description": "Session age in milliseconds." + }, + "network_context": { + "type": "object", + "description": "Network context with IP address and geo-location.", + "properties": { + "ip_address": { + "type": "string", + "description": "Client IP address." + }, + "ip_country_code": { + "type": "string", + "description": "Geo-resolved country of the IP (ISO 3166-1 alpha-3)." + }, + "latitude": { + "type": "number", + "description": "Geographic latitude of the IP." + }, + "longitude": { + "type": "number", + "description": "Geographic longitude of the IP." + } + } + } + } + }, + "external_party_snapshot": { + "type": "object", + "nullable": true, + "description": "Frozen copy of the party data for `EXTERNAL` parties. `null` while the party is linked to a Didit user or business." + } + } + }, + "description": "Transaction parties (applicant, counterparty)." + }, + "payment_methods": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "payment_method_type": { + "type": "string", + "enum": [ + "bank_card", + "bank_account", + "e_wallet", + "crypto_wallet", + "unhosted_wallet", + "other" + ] + }, + "label": { + "type": "string", + "nullable": true + }, + "fingerprint": { + "type": "string", + "nullable": true, + "description": "SHA-256 over `type:account_id:issuing_country` — stable identifier to correlate reuse of the same instrument." + }, + "account_id": { + "type": "string", + "nullable": true, + "description": "Account identifier (IBAN, wallet address, card token, …)." + }, + "issuing_country": { + "type": "string", + "nullable": true + }, + "owner_kind": { + "type": "string", + "enum": [ + "USER", + "BUSINESS", + "EXTERNAL" + ] + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your identifier for the owning user/business." + }, + "owner_name": { + "type": "string", + "nullable": true + }, + "institution_info": { + "type": "object" + }, + "details": { + "type": "object", + "description": "Raw `payment_method` object as submitted." + }, + "external_owner_snapshot": { + "type": "object", + "nullable": true + }, + "role": { + "type": "string", + "enum": [ + "SOURCE", + "DESTINATION", + "FUNDING", + "BENEFICIARY" + ], + "description": "How this method was used in the transaction. The create endpoint assigns `SOURCE`/`DESTINATION` from the direction (outbound: subject = SOURCE; inbound: subject = DESTINATION)." + } + } + }, + "description": "Payment methods linked to the transaction." + }, + "documents": { + "type": "array", + "description": "Files attached to the transaction from the console. `[]` when none.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string", + "nullable": true + }, + "note": { + "type": "string", + "nullable": true + }, + "uploaded_by": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "files": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "file_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived signed download URL; fetch it when needed rather than storing it." + }, + "original_filename": { + "type": "string", + "nullable": true + }, + "file_size": { + "type": "integer", + "nullable": true + } + } + } + } + } + } + }, + "activities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "activity_type": { + "type": "string", + "description": "E.g. `TRANSACTION_CREATED`, `TRANSACTION_NOTE_ADDED`, `TRANSACTION_STATUS_CHANGED`." + }, + "source": { + "type": "string" + }, + "status": { + "type": "string", + "nullable": true + }, + "title": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object" + }, + "occurred_at": { + "type": "string", + "format": "date-time" + } + } + }, + "description": "Activity log entries (creation, notes, manual reviews, status changes)." + }, + "alerts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "severity": { + "type": "string", + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ] + }, + "status": { + "type": "string", + "enum": [ + "OPEN", + "INVESTIGATING", + "AWAITING_USER", + "PENDING_SAR", + "SAR_FILED", + "RESOLVED", + "DISMISSED" + ] + }, + "source": { + "type": "string", + "enum": [ + "RULE", + "PROVIDER", + "MANUAL" + ] + }, + "metadata": { + "type": "object" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "description": "Always empty today. An open alert is a transaction with status `IN_REVIEW` or `AWAITING_USER`." + }, + "rule_runs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "rule": { + "type": "string", + "format": "uuid", + "description": "UUID of the rule that ran." + }, + "rule_title": { + "type": "string" + }, + "rule_description": { + "type": "string", + "nullable": true + }, + "rule_category": { + "type": "string" + }, + "matched": { + "type": "boolean", + "description": "Whether the rule's conditions matched this transaction. A run is recorded only for matched rules, so live evaluations always carry `true`." + }, + "is_test": { + "type": "boolean", + "description": "True when the rule ran in TEST mode (no effect on status/score)." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ] + }, + "severity": { + "type": "string", + "nullable": true + }, + "score_delta": { + "type": "integer", + "description": "Score added by this rule's `add_score` actions." + }, + "status_target": { + "type": "string", + "nullable": true, + "description": "Status set by a `change_status` action, if any." + }, + "action_summary": { + "type": "string", + "nullable": true, + "description": "Summary of the actions the rule executed, joined with `; ` (e.g. `score +30; change_status:IN_REVIEW; add_tag:high_risk_country`). Parts: `score +N`, `change_status:`, `add_tag:`, `add_note`, `add_to_list`, `open_case`. `null` when the rule executed no action." + }, + "metadata": { + "type": "object", + "description": "Evaluation detail: `scope_match`, `conditions[]` and `aggregations[]`. Each condition entry carries `field`, `operator`, `expected_value`, the `current_value` read from the transaction, `matched`, `group_index` and `group_logic`. Each aggregation entry (velocity / count / sum) carries `metric`, `field`, `operator`, `expected_value`, the `computed_value` the aggregate evaluated to, `window`, `filters`, `matched` and `sample_references`; deferred async aggregations have `computed_value: null` and `deferred: true`. A nested condition group is one `conditions[]` entry with `operator` `any_of` or `all_of`, `field` null, and its members' results under its own `conditions`." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "description": "Per-rule execution results — which rule fired, with what score impact." + }, + "provider_results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "provider": { + "type": "string" + }, + "result_type": { + "type": "string", + "enum": [ + "FIAT_MONITORING", + "WALLET_SCREENING", + "TRANSACTION_SCREENING", + "TRAVEL_RULE", + "NETWORK_GRAPH", + "CUSTOM" + ] + }, + "status": { + "type": "string" + }, + "severity": { + "type": "string", + "nullable": true + }, + "score": { + "type": "integer" + }, + "summary": { + "type": "string", + "nullable": true + }, + "payload": { + "type": "object", + "description": "Raw provider response payload." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "description": "Raw provider payloads (AML screening, blockchain analytics, etc.)." + }, + "travel_rule": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TransactionTravelRule" + } + ], + "description": "Travel Rule transfer or check. Populated when `travel_rule_details` was submitted, and also when Travel Rule is enabled and the transaction was created with `transaction_category: travelRule` (Didit initiates the exchange itself, even without `travel_rule_details`). `null` when no transfer or check exists. On an inbound transfer, the counterparty's `originator_data` and `beneficiary_data` stay masked (`data_masked: true`) until wallet ownership is confirmed." + }, + "network_snapshot": { + "type": "object", + "nullable": true, + "description": "Graph snapshot of related transactions/parties used by the rules engine. Either the `network_snapshot` submitted with the transaction or the graph Didit builds from a successful AML wallet screening (which replaces a submitted one). `null` when neither exists.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "nodes": { + "type": "array", + "items": { + "type": "object" + } + }, + "edges": { + "type": "array", + "items": { + "type": "object" + } + }, + "metrics": { + "type": "object" + } + } + }, + "remediation": { + "type": "object", + "nullable": true, + "description": "Remediation session offered to the user when re-verification is required. Superseded by `action_required`, which is the canonical block; kept for backward compatibility.", + "properties": { + "session_id": { + "type": "string" + }, + "session_token": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "status": { + "type": "string" + } + } + }, + "action_required": { + "type": "object", + "nullable": true, + "description": "Pending end-user action required to complete this transaction, or null when there is none. The Didit SDKs auto-launch the action by default and refresh the transaction when it finishes. Two variants, discriminated by `type`: `verification_session` (a hosted verification session created by a rule action) and `wallet_ownership` (a wallet-ownership widget session auto-minted for a Travel Rule transfer that needs proof of wallet control, gated by the `auto_wallet_verification` Travel Rule setting). When both could apply, `wallet_ownership` takes precedence because it blocks the Travel Rule exchange.", + "oneOf": [ + { + "$ref": "#/components/schemas/TransactionVerificationSessionAction" + }, + { + "$ref": "#/components/schemas/TransactionWalletOwnershipAction" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "verification_session": "#/components/schemas/TransactionVerificationSessionAction", + "wallet_ownership": "#/components/schemas/TransactionWalletOwnershipAction" + } + } + }, + "cost_breakdown": { + "type": "object", + "nullable": true, + "description": "Cost of this transaction: `total_price` and `items`, one per charge, each with `usage_type`, `price`, `details` and `created_at`. A re-screen of the same transaction is a new item." + } + } + }, + "TravelRuleSettingsDetail": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean", + "description": "Whether the managed Travel Rule negotiation flow is active for outbound `travelRule` transactions on this application." + }, + "jurisdiction": { + "type": "string", + "maxLength": 8, + "description": "Your VASP's operating jurisdiction code. Default `\"EU\"`. Didit maps it to the regime it stamps on your Travel Rule transactions as `travel_rule_regime` (for example `EU` or an EU member code becomes `eu`), which transaction rules can key on. Other Didit customers see it as your jurisdiction in the VASP directory once the Didit network lists your profile." + }, + "name_matching_strictness": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "How strictly beneficiary names must match before a transfer can reach COMPLETED. `CONTAINS` accepts one name whose words all appear in the other." + }, + "confirmation_timeout_hours": { + "type": "integer", + "minimum": 1, + "description": "Hours an AWAITING_COUNTERPARTY transfer may remain unconfirmed before the periodic sweeper expires it. Default 48." + }, + "timeout_outcome": { + "type": "string", + "enum": [ + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "What happens to the underlying transaction when a transfer expires. Default HOLD. sunrise_policy and non_responsive_policy take precedence when they apply and are set." + }, + "threshold_amount": { + "type": "string", + "description": "Decimal string, in your application's default currency. Minimum transfer amount that triggers a managed exchange; transfers below it resolve straight to NOT_APPLICABLE. Didit compares the transaction amount converted to your default currency, or the `amount_in_default_currency` you send; when no converted amount is available, the threshold does not apply. Defaults to \"0.00\" (EU TFR applies no minimum amount)." + }, + "ownership_validation_mode": { + "type": "string", + "enum": [ + "FULL", + "SIMPLIFIED", + "AUTOMATED" + ], + "description": "How much proof a wallet's ownership takes. `FULL` always needs a proof (signed message, test deposit or reviewed screenshot), never the end user's word. `SIMPLIFIED` accepts a self-declaration below `simplified_validation_threshold` and a proof above it. `AUTOMATED` lets the address book and earlier proofs answer on their own, self-declarations included while `allow_self_declaration` is true. Default `AUTOMATED`." + }, + "simplified_validation_threshold": { + "type": "string", + "description": "Decimal string, default \"0.00\". In `SIMPLIFIED` mode a transfer below this amount (in the threshold currency) accepts a self-declaration. A transfer with no amount in that currency counts as above it." + }, + "data_sharing_fields": { + "$ref": "#/components/schemas/TravelRuleDataSharingFields" + }, + "matching_rules": { + "$ref": "#/components/schemas/TravelRuleMatchingRules" + }, + "legal_name": { + "type": "string", + "description": "Your VASP's legal name. Required (non-blank) to set is_enabled to true." + }, + "lei": { + "type": "string", + "description": "Your Legal Entity Identifier, surfaced in the VASP directory." + }, + "compliance_email": { + "type": "string", + "format": "email", + "description": "Compliance contact email for your VASP profile. The Didit network lists your profile only once this address is confirmed through an emailed link (POST /v3/travel-rule/settings/compliance-email/confirmation/); changing it needs a new confirmation." + }, + "is_discoverable": { + "type": "boolean", + "description": "Whether your VASP profile can be listed in the Didit network: found in GET /v3/travel-rule/vasps/ by other Didit customers and reached on the INTERNAL rail. Listing also needs a live application, Travel Rule on, a legal name and a confirmed compliance email; didit_network says which are missing. Default true." + }, + "inbound_auto_accept": { + "type": "boolean", + "description": "When true, an inbound TRP inquiry whose beneficiary wallet is a verified address-book entry with a matching name is approved automatically. When false, that inquiry waits in ON_HOLD for you to approve or deny it with the ownership endpoint. INTERNAL-rail and after-deposit transfers are not affected. Default true." + }, + "allow_self_declaration": { + "type": "boolean", + "description": "When true, the wallet-ownership widget offers self-declaration as a fallback proof method. Default true." + }, + "allow_screenshot_proof": { + "type": "boolean", + "description": "When true, the wallet-ownership widget offers screenshot upload as an ownership evidence method. Default true." + }, + "auto_wallet_verification": { + "type": "boolean", + "description": "When true, submitting an outbound Travel Rule transfer that needs end-user proof of wallet control auto-mints a wallet-ownership widget session bound to the transaction and exposes it in the transaction's action_required block. Default true." + }, + "vasp_attribution_enabled": { + "type": "boolean", + "description": "When true, an outbound Travel Rule transfer that no rail can route is attributed through blockchain analytics: the destination wallet is resolved to its owning VASP, the entity is recorded on the transfer and in the VASP directory, and the rails are re-run once with the attributed name. Default true." + }, + "counterparty_screening_enabled": { + "type": "boolean", + "description": "Opt-in sanctions and adverse-media screening of counterparty VASPs, billed per screening at the Company AML Screening price. When on, Didit screens the counterparty an outbound exchange reaches (reusing a screening younger than 30 days) and you can screen one with POST /v3/travel-rule/vasps/{vasp_id}/screening/. Default false: nothing is screened or billed." + }, + "sunrise_policy": { + "type": "string", + "enum": [ + "", + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "What an expired transfer does to its transaction when the counterparty is a directory entry in a jurisdiction on sunrise_jurisdictions, which does not enforce the Travel Rule yet: HOLD moves it to review, REJECT declines it, PROCEED leaves its status untouched. Empty (the default) follows timeout_outcome. Applies before non_responsive_policy." + }, + "sunrise_jurisdictions": { + "type": "array", + "items": { + "type": "string" + }, + "readOnly": true, + "description": "Read-only. The countries (ISO 3166 alpha-2) Didit treats as in a Travel Rule sunrise period, where sunrise_policy applies. Currently CO, KH and VN. Any other jurisdiction, and an empty or unknown one, follows timeout_outcome." + }, + "non_responsive_policy": { + "type": "string", + "enum": [ + "", + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "What an expired transfer does to its transaction when the counterparty is a directory entry whose responsiveness is UNRESPONSIVE: HOLD, REJECT or PROCEED, as for timeout_outcome. Empty (the default) follows timeout_outcome. sunrise_policy applies first when both fit." + }, + "travel_address": { + "type": "string", + "description": "Read-only. Your VASP's own travel address - the encoded inbound TRP inquiry endpoint other VASPs use to reach you. Empty while `is_enabled` is false." + }, + "disabled_networks": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA" + ] + }, + "description": "Networks opted out of platform membership. Does not disable your connected own memberships or provision unavailable networks.", + "default": [] + }, + "networks": { + "type": "object", + "readOnly": true, + "description": "Per network, how this application would send and why it cannot. No network, including GTR, is included by default: a network is reachable through your own connected membership, or through Didit's membership once Didit's membership of that network is live and your own participant on it is active. Not a live health or delivery check.", + "properties": { + "gtr": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "trust": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "verifyvasp": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "sygna": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + } + } + }, + "didit_network": { + "type": "object", + "readOnly": true, + "description": "Whether the Didit network lists your VASP profile, so other Didit customers find it in the directory and reach it on the INTERNAL rail, and each condition it still misses.", + "properties": { + "listed": { + "type": "boolean" + }, + "missing": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "travel_rule_off", + "sandbox_application", + "not_discoverable", + "legal_name_missing", + "compliance_email_missing", + "compliance_email_unconfirmed", + "excluded_by_staff", + "internal_organization" + ] + }, + "description": "Empty when listed. compliance_email_unconfirmed: send the confirmation with POST /v3/travel-rule/settings/compliance-email/confirmation/ and open the emailed link. excluded_by_staff: Didit marked the application as a test, demo or internal one." + }, + "compliance_email_confirmed": { + "type": "boolean", + "description": "Whether the current compliance_email was confirmed through the emailed link." + } + } + } + } + }, + "TravelRuleSettingsUpdate": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean" + }, + "legal_name": { + "type": "string", + "maxLength": 255 + }, + "lei": { + "type": "string", + "maxLength": 20 + }, + "jurisdiction": { + "type": "string", + "maxLength": 8 + }, + "name_matching_strictness": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "`CONTAINS` accepts one name whose words all appear in the other." + }, + "confirmation_timeout_hours": { + "type": "integer", + "minimum": 1 + }, + "timeout_outcome": { + "type": "string", + "enum": [ + "HOLD", + "REJECT", + "PROCEED" + ] + }, + "compliance_email": { + "type": "string", + "format": "email", + "description": "Compliance contact email for your VASP profile. The Didit network lists your profile only once this address is confirmed through an emailed link (POST /v3/travel-rule/settings/compliance-email/confirmation/); changing it needs a new confirmation." + }, + "is_discoverable": { + "type": "boolean", + "description": "Whether your VASP profile can be listed in the Didit network: found in GET /v3/travel-rule/vasps/ by other Didit customers and reached on the INTERNAL rail. Listing also needs a live application, Travel Rule on, a legal name and a confirmed compliance email; didit_network says which are missing. Default true." + }, + "threshold_amount": { + "type": "string", + "description": "Decimal string, zero or greater, in your application's default currency. Minimum transfer amount that triggers a managed exchange." + }, + "ownership_validation_mode": { + "type": "string", + "enum": [ + "FULL", + "SIMPLIFIED", + "AUTOMATED" + ], + "description": "See the response field of the same name." + }, + "simplified_validation_threshold": { + "type": "string", + "description": "Decimal string, 0 or more." + }, + "data_sharing_fields": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleDataSharingFields" + } + ], + "description": "Merged per entity type: an entity type you leave out keeps its current choice. A choice that cannot meet the originator obligations of your `jurisdiction` regime (EU TFR or FATF Recommendation 16), an empty list included, is refused with `400`." + }, + "matching_rules": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleMatchingRules" + } + ], + "description": "Merged per entity type, and per field inside it. A field that entity type does not compare (for example `date_of_birth` for `company`) is refused with `400`." + }, + "inbound_auto_accept": { + "type": "boolean" + }, + "allow_self_declaration": { + "type": "boolean" + }, + "allow_screenshot_proof": { + "type": "boolean", + "description": "When true, the wallet-ownership widget offers screenshot upload as an ownership evidence method. Default true." + }, + "auto_wallet_verification": { + "type": "boolean" + }, + "vasp_attribution_enabled": { + "type": "boolean" + }, + "counterparty_screening_enabled": { + "type": "boolean", + "description": "Opt-in sanctions and adverse-media screening of counterparty VASPs, billed per screening at the Company AML Screening price. When on, Didit screens the counterparty an outbound exchange reaches (reusing a screening younger than 30 days) and you can screen one with POST /v3/travel-rule/vasps/{vasp_id}/screening/. Default false: nothing is screened or billed." + }, + "sunrise_policy": { + "type": "string", + "enum": [ + "", + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "HOLD, REJECT, PROCEED, or empty to follow timeout_outcome. See the response field of the same name." + }, + "non_responsive_policy": { + "type": "string", + "enum": [ + "", + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "HOLD, REJECT, PROCEED, or empty to follow timeout_outcome. See the response field of the same name." + }, + "disabled_networks": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA" + ] + }, + "description": "Networks opted out of platform membership. Does not disable your connected own memberships or provision unavailable networks.", + "default": [] + } + }, + "description": "All fields optional; PUT applies a partial update." + }, + "WalletAddressEntry": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "address": { + "type": "string", + "description": "The wallet address; `\"\"` for an entry imported by its hash only. Unique per application, chain and memo, comparing the address without regard to case. After you delete an entry, you can register the same address again." + }, + "chain": { + "type": "string", + "description": "Chain token for the address, for example `ETH` or `BTC`. When Didit routes or registers a transfer, it matches address book entries on the wallet address and, when the transfer carries a chain, on this value too, ignoring case. A transfer's chain is `beneficiary_data.chain`, filled from the transaction's crypto chain or currency symbol (for example `ETH`) when you do not send it, or the `chain` of an after-deposit registration, so use the same token everywhere. An entry with an empty chain matches any chain. A Lightning payee (a node id or a Lightning Address) registered on bitcoin is stored lowercased on `LIGHTNING`, where Lightning transfers are matched. Known chains are stored under one canonical identifier whatever spelling you send: \"ethereum\", \"Ethereum\" and \"ERC20\" are all stored as ETH, \"BNB Smart Chain\" and \"BEP20\" as BNB, \"tron\" as TRX. An unknown chain name is kept as given." + }, + "holder_vendor_data": { + "type": "string", + "description": "Your internal identifier for the holder." + }, + "holder_name": { + "type": "string", + "description": "Name of the wallet holder, used for the beneficiary name match." + }, + "entity_type": { + "type": "string", + "description": "Free text, defaults to \"individual\"." + }, + "is_ownership_verified": { + "type": "boolean", + "description": "Read-only. Whether ownership of this address has been proven." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "travel_address": { + "type": "string", + "description": "The counterparty VASP's travel address for this wallet, if known. Lets Didit route this beneficiary's transfers over the TRP rail, for the assets TRP carries (BTC, ETH, and USDC on a named network)." + }, + "address_hash": { + "type": "string", + "description": "Read-only. SHA-256 of the lowercased address, in lowercase hex. An entry imported by its hash only is matched on it." + }, + "memo": { + "type": "string", + "description": "The destination tag or memo that is part of the wallet on a memo network (XRP, XLM, EOS, ATOM, HBAR); `\"\"` for the whole address. An entry with a memo matches only transfers naming that memo; one without matches the whole address." + }, + "receiving_travel_address": { + "type": "string", + "readOnly": true, + "description": "Your per-wallet travel address for this wallet: give it to the sending VASP so its TRP inquiry names this wallet. Empty for a wallet recorded as another VASP's, while Travel Rule is off, and before your VASP profile is saved." + } + } + }, + "WalletAddressEntryCreate": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string", + "maxLength": 128, + "description": "The wallet address. An XRP X-address is stored as its classic address and its tag, so it matches transfers that name either form." + }, + "chain": { + "type": "string", + "maxLength": 32, + "description": "Chain token for the address, for example `ETH` or `BTC`. When Didit routes or registers a transfer, it matches address book entries on the wallet address and, when the transfer carries a chain, on this value too, ignoring case. A transfer's chain is `beneficiary_data.chain`, filled from the transaction's crypto chain or currency symbol (for example `ETH`) when you do not send it, or the `chain` of an after-deposit registration, so use the same token everywhere. An entry with an empty chain matches any chain. Known chains are stored under one canonical identifier whatever spelling you send: \"ethereum\", \"Ethereum\" and \"ERC20\" are all stored as ETH, \"BNB Smart Chain\" and \"BEP20\" as BNB, \"tron\" as TRX. An unknown chain name is kept as given." + }, + "holder_vendor_data": { + "type": "string", + "maxLength": 255 + }, + "holder_name": { + "type": "string", + "maxLength": 255 + }, + "entity_type": { + "type": "string", + "default": "individual", + "maxLength": 16 + }, + "self_declared": { + "type": "boolean", + "default": false, + "description": "Write-only. When true, the entry is created already ownership-verified via a SELF_DECLARATION proof. Returns 400 when `allow_self_declaration` is off in your Travel Rule settings." + }, + "travel_address": { + "type": "string", + "description": "Optional. The counterparty VASP's travel address for this wallet, enabling TRP routing for the assets TRP carries (BTC, ETH, and USDC on a named network). An invalid travel address returns 400.", + "maxLength": 512 + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "Optional. The destination tag or memo that is part of the wallet. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`. Alias: `destinationTag` or `destination_tag`." + } + } + }, + "WalletAddressEntryUpdate": { + "type": "object", + "properties": { + "holder_name": { + "type": "string", + "maxLength": 255 + }, + "holder_vendor_data": { + "type": "string", + "maxLength": 255 + }, + "entity_type": { + "type": "string", + "maxLength": 16 + }, + "travel_address": { + "type": "string", + "maxLength": 512 + } + }, + "description": "PATCH is a partial update; the response echoes only holder_name, holder_vendor_data, entity_type, travel_address, receiving_travel_address, label, and owner_type." + }, + "TravelRuleTransferDetail": { + "type": "object", + "description": "Full transfer object returned by the ownership-confirmation and finish/cancel endpoints. While an inbound transfer's wallet ownership is unconfirmed, the counterparty's `originator_data` and `beneficiary_data` come back masked (the first letter of each word is kept; the wallet, chain and VASP routing stay readable) and `data_masked` is true.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "AWAITING_COUNTERPARTY", + "ON_HOLD", + "COMPLETED", + "FINISHED", + "CANCELLED", + "EXPIRED", + "COUNTERPARTY_MISMATCHED_DATA", + "COUNTERPARTY_VASP_NOT_FOUND", + "COUNTERPARTY_VASP_NOT_REACHABLE", + "UNCONFIRMED_OWNERSHIP", + "COUNTERPARTY_VASP_GENERAL_DECLINE", + "NOT_ENOUGH_COUNTERPARTY_DATA", + "NOT_APPLICABLE" + ] + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ] + }, + "rail": { + "type": "string", + "nullable": true, + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL", + "TEST_VASP", + null + ], + "description": "The rail the transfer was routed over, or null when no rail was selected (for example below the threshold, a self-hosted counterparty wallet, an after-deposit registration, or no reachable counterparty). INTERNAL means both sides are Didit applications; EMAIL means the counterparty was reached by an emailed pickup link. TEST_VASP means the Didit Test VASP answered the sandbox transfer. Over GTR, VERIFYVASP and SYGNA, Didit reads the counterparty's answer from the network's response or by polling it every 5 minutes. TRUST only sends, so a TRUST transfer stays AWAITING_COUNTERPARTY until it expires. A network rail is used only when you have connected your own membership for that network. It is chosen after the transaction create returns, so it is null in that response." + }, + "protocol": { + "type": "string", + "nullable": true + }, + "ivms_version": { + "type": "string", + "example": "ivms101.2023" + }, + "required": { + "type": "boolean" + }, + "ownership_confirmed": { + "type": "boolean", + "nullable": true + }, + "deadline_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "timeline": { + "type": "array", + "items": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "at": { + "type": "string", + "format": "date-time" + }, + "detail": { + "type": "string" + }, + "name_match": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleNameMatch" + } + ], + "nullable": true, + "description": "The name comparison this entry decided, next to `detail`. Absent on entries that compared no name." + }, + "reason": { + "type": "string", + "description": "Present only on the entry of a transition a failed delivery caused: `email_undeliverable` or `delivery_failed`, the value the transfer's `reason` reports." + } + } + } + }, + "counterparty_vasp": { + "type": "object", + "nullable": true, + "properties": { + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "dd_score": { + "type": "integer", + "description": "Due-diligence score from 0 to 100, where higher is better. Same scale as in the VASP directory." + }, + "dd_score_breakdown": { + "type": "object", + "description": "Per-component contributions to dd_score, one object per component as in the VASP directory (jurisdiction, verification, protocol, lei, history, essentials, responsiveness), each {points, max, reason, detail}. {} until the counterparty has been assessed.", + "additionalProperties": { + "type": "object", + "properties": { + "points": { + "type": "number", + "description": "Points awarded for this component." + }, + "max": { + "type": "integer", + "description": "Maximum points this component can award." + }, + "reason": { + "type": "string", + "description": "Machine-readable code of the component's verdict." + }, + "detail": { + "type": "string", + "description": "Human-readable reason for the awarded points." + } + } + } + }, + "dd_assessed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the due-diligence assessment last ran. Null until the counterparty has been assessed." + } + } + }, + "originator_data": { + "type": "object", + "description": "Originator party. For an inbound transfer the counterparty's values are masked until ownership is confirmed." + }, + "beneficiary_data": { + "type": "object", + "description": "Beneficiary party. For an inbound transfer the counterparty's values are masked until ownership is confirmed." + }, + "name_match": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleNameMatch" + } + ], + "nullable": true, + "description": "The name comparison that decided the transfer, or null until one ran. While the parties are masked, and on the other customer's copy of an INTERNAL exchange, `transfer_name`, `compared_name`, `matched_names` and `mismatched_names` are hidden. `score` and `matched` can disagree: `matched` follows the strictness, `score` is the similarity of the two full names." + }, + "data_match": { + "$ref": "#/components/schemas/TravelRuleDataMatch" + }, + "mismatched_fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Shortcut for `data_match.mismatched_fields`. Empty when nothing differs." + }, + "data_unmasked": { + "$ref": "#/components/schemas/TravelRuleDataUnmasked" + }, + "data_masked": { + "type": "boolean", + "description": "True while the counterparty's personal data is masked in this response." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "delivery": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleDelivery" + } + ], + "description": "Where the latest message this transfer owes the other side stands. Null when the transfer never owed one (for example below `threshold_amount`, a declared self-hosted wallet, or a sunrise registration)." + }, + "discovery": { + "type": "array", + "description": "How Didit looked for the counterparty, one entry per step that ran. Empty when no discovery ran, for example on an inbound transfer.", + "items": { + "$ref": "#/components/schemas/TravelRuleDiscoveryStep" + } + }, + "reason": { + "type": "string", + "description": "Why a `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE` transfer is stuck; `\"\"` for any other status. A failed delivery decides it first, on `COUNTERPARTY_VASP_NOT_REACHABLE` only: `email_undeliverable` (the mail provider refused the compliance email and no address is left to try: when it was sent, on a reminder, or as a permanent bounce reported later) or `delivery_failed` (the rail that was delivering failed for good: a network rail refused the message or ran out of retries, a `TRP` identity host did not answer, or the counterparty's `TRP` host did not accept the inquiry). Otherwise the `discovery` trace decides it: `awaiting_end_user` (no step named a counterparty Didit can reach), `directory_match_no_route` (a step named a directory VASP that has no route), `picked_member_without_proven_wallet` (the end user picked a Didit customer that has not proven control of the wallet), or `no_directory_match` when the trace gives no other reason. A resend moves the transfer on, so the reason clears. See [Why a transfer is stuck](/transaction-monitoring/travel-rule#why-a-transfer-is-stuck)." + }, + "next_actions": { + "type": "array", + "description": "What moves the transfer on; `[]` for any status but `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE`. `email_undeliverable` offers `update_compliance_email`, then `resend`. `delivery_failed` offers `resend`. A discovery reason offers `select_counterparty_vasp`, `declare_self_hosted`, `add_vasp_hint` and `resend`.", + "items": { + "$ref": "#/components/schemas/TravelRuleNextAction" + } + }, + "chain_details": { + "$ref": "#/components/schemas/TravelRuleChainDetails" + } + } + }, + "TravelRuleOwnershipConfirmRequest": { + "type": "object", + "required": [ + "confirmed" + ], + "properties": { + "confirmed": { + "type": "boolean", + "description": "true to confirm ownership, false to deny it." + }, + "wallet_address": { + "type": "string", + "default": "", + "description": "Inbound TRP transfers: the payment address Didit returns to the originating VASP when you approve. Defaults to the matched address book entry's address, then beneficiary_data.wallet_address. On a declared self-hosted wallet transfer that names no counterparty wallet, the wallet whose ownership you verified; without it the request is rejected with 400. Ignored for other transfers." + } + } + }, + "TravelRuleTransferPatchRequest": { + "type": "object", + "description": "Provide exactly one of: payment_txn_id (finish), action=cancel, action=resend, action=update_compliance_email with compliance_email, action=update_originator_wallet with wallet_address, or action=beneficiary_vasp with beneficiary_vasp.", + "properties": { + "payment_txn_id": { + "type": "string", + "description": "The on-chain transaction hash. Requires the transfer to be an outbound transfer in COMPLETED status." + }, + "action": { + "type": "string", + "enum": [ + "cancel", + "resend", + "update_compliance_email", + "update_originator_wallet", + "beneficiary_vasp" + ], + "description": "\"cancel\" cancels a non-terminal transfer. \"resend\" re-runs routing for an outbound transfer stuck in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE, or NOT_ENOUGH_COUNTERPARTY_DATA. \"update_compliance_email\" replaces the compliance email the request goes to, on an outbound transfer in one of those three statuses; send \"resend\" afterwards. \"update_originator_wallet\" records the wallet the funds actually left from (`wallet_address`, `memo`). \"beneficiary_vasp\" names the counterparty VASP yourself (requires `beneficiary_vasp`) for an outbound transfer in one of those statuses: it is routed to that VASP and sent again." + }, + "compliance_email": { + "type": "string", + "format": "email", + "maxLength": 254, + "description": "Required with `action: update_compliance_email`. Kept as this transfer's `beneficiary_data.counterparty_vasp_email`: it belongs to this transfer only and joins no directory entry and no contact list." + }, + "wallet_address": { + "type": "string", + "maxLength": 128, + "description": "With `action: update_originator_wallet`: the wallet the funds left from. When the transfer's UTXO inputs are known it must be one of them." + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "With `action: update_originator_wallet`: the wallet's memo or tag on a memo network. Optional." + }, + "beneficiary_vasp": { + "type": "string", + "format": "uuid", + "description": "Required with action=beneficiary_vasp: the `id` of a directory VASP, from `GET /v3/travel-rule/vasps/`. Your choice outranks every automatic discovery step, which never overrides it." + } + } + }, + "VaspDirectoryEntry": { + "type": "object", + "description": "A VASP of the directory or a member of the Didit network, with the routes your application has to it. A customer is a member of the Didit network only while its application is live, its Travel Rule is on, its profile is discoverable, has a legal name and a confirmed compliance email, and Didit has not marked the application as a test, demo or internal one. Each row also carries the shared due-diligence assessment (dd_score, risk_level, responsiveness, essential_checks) and your application's own decision, entity screening and questionnaires, which no other customer sees.", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The entry's id, for GET /v3/travel-rule/vasps/{vasp_id}/." + }, + "kind": { + "type": "string", + "enum": [ + "directory", + "didit" + ], + "description": "directory for an entry of the shared directory; didit for a member of the Didit network." + }, + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "description": "ISO 3166 country code, as the source gives it." + }, + "domains": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The VASP's web domains. Always empty for Didit network members." + }, + "reachable": { + "type": "boolean", + "description": "Whether your application has an available route to this VASP, or to one of its entities for a brand. Always true for a Didit network member, except on a sandbox application, where every row is false." + }, + "aliases": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "trading_name", + "other_legal_name", + "network_name", + "attribution", + "merged", + "staff" + ], + "description": "trading_name, other_legal_name (from the global LEI register), network_name (the name on a Travel Rule network), attribution (the entity name blockchain analytics gives the VASP's wallets), merged (the name of a duplicate entry merged into this one) or staff." + } + } + }, + "description": "Other names the VASP is found by." + }, + "networks": { + "type": "array", + "items": { + "type": "object", + "description": "A current listing of this VASP in a Travel Rule network's member directory. A TRISA listing says who the VASP is on TRISA; Didit has no TRISA rail, so it is never a route.", + "properties": { + "network": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "TRISA" + ] + }, + "code": { + "type": "string", + "description": "The VASP's identifier on that network." + }, + "listed_name": { + "type": "string", + "description": "The name the network lists the VASP under." + }, + "country": { + "type": "string", + "description": "The country the network lists, when it gives one." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When Didit staff verified the listing. Null for listings read from a public directory." + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the source last showed the listing. A listing the source has not shown for three weeks is removed." + }, + "source": { + "type": "string", + "enum": [ + "trisa_directory", + "staff" + ], + "description": "Where the listing comes from: the public TRISA directory, or Didit staff." + } + } + } + }, + "routes": { + "type": "array", + "items": { + "type": "object", + "description": "One way your application can deliver Travel Rule data to this VASP, in the order dispatch tries them. A network route needs a membership on that network: your own (connected in the Marketplace) or Didit's once Didit holds one. Without it the route is listed as unavailable with the reason, never as a way to reach the VASP.", + "properties": { + "rail": { + "type": "string", + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL" + ] + }, + "status": { + "type": "string", + "enum": [ + "available", + "unavailable" + ] + }, + "reason": { + "type": "string", + "enum": [ + "", + "no_membership", + "network_switched_off", + "sandbox_application" + ], + "description": "Why an unavailable route cannot be used: no_membership (neither you nor Didit holds a membership on that network), network_switched_off (you turned that network off in your settings) or sandbox_application (a sandbox application sends only to the Didit Test VASP, so no route is available to it). Empty when the route is available." + }, + "via": { + "type": "string", + "enum": [ + "", + "own_membership", + "didit_membership", + "directory", + "contact", + "didit_network" + ], + "description": "How an available route is reached: own_membership or didit_membership for a network rail, directory for a TRP endpoint the directory verified, contact for the EMAIL rail, didit_network for a Didit network member. Empty when the route is unavailable." + } + } + } + }, + "umbrella": { + "type": "object", + "nullable": true, + "description": "The brand this entry is a regulated entity of. Null when it is not part of one.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + } + } + }, + "entities": { + "type": "array", + "items": { + "type": "object", + "description": "A regulated entity of a brand: the legal entity that receives a transfer.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "jurisdiction": { + "type": "string" + }, + "reachable": { + "type": "boolean", + "description": "Whether your application has an available route to it." + } + } + }, + "description": "For a brand, the regulated entities that receive its transfers. A transfer naming the brand goes to the reachable entity in the jurisdiction it states (beneficiary_data.counterparty_vasp_jurisdiction), else to the entity blockchain analytics attribution linked to the destination wallet, else to the only reachable entity when no jurisdiction is stated. Two entities that fit equally are never guessed between." + }, + "contacts": { + "type": "array", + "items": { + "type": "object", + "description": "A compliance contact the EMAIL rail can write to.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "email": { + "type": "string", + "format": "email" + }, + "name": { + "type": "string" + }, + "source": { + "type": "string", + "enum": [ + "staff", + "application", + "learned" + ], + "description": "staff: curated by Didit; application: added by your application; learned: an address that answered a Travel Rule request through its response link." + }, + "scope": { + "type": "string", + "enum": [ + "platform", + "application" + ], + "description": "platform contacts are used for every Didit customer's transfers; application contacts are private to your application, which sees only its own." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When someone at this address answered a request. Null when it has not yet." + } + } + }, + "description": "The platform's contacts for this VASP and your application's own." + }, + "dd_status": { + "type": "string", + "enum": [ + "NOT_STARTED", + "IN_PROGRESS", + "COMPLETED", + "REJECTED" + ], + "description": "Didit's verification status of the entry, scored by the verification component of dd_score. Always COMPLETED for a Didit network member." + }, + "dd_score": { + "type": "integer", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "The shared due-diligence score of a directory entry, 0 to 100, where higher is safer (see dd_score_semantic): the sum of the points in dd_score_breakdown. Null for a Didit network member, which is not scored. An entry not assessed yet (dd_assessed_at null) shows 0 with an empty risk_level, so read risk_level rather than the raw score." + }, + "reachable_rails": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL" + ] + }, + "description": "Rails recorded on the entry. Read routes for what your application can use today." + }, + "is_didit_customer": { + "type": "boolean", + "description": "true for a member of the Didit network (dd_status is always COMPLETED, dd_score null and risk_level empty for these) and for a directory entry linked to a Didit customer." + }, + "dd_score_breakdown": { + "type": "object", + "description": "One object per component of dd_score. Components, their weight (max) and their reason codes: jurisdiction, 25 (jurisdiction_not_high_risk, jurisdiction_high_risk, jurisdiction_unknown); verification, 15 (dd_status_completed, dd_status_in_progress, dd_status_not_started, dd_status_rejected); protocol, 15 (native_rail, email_only, no_rail); lei, 10 (lei_valid, lei_missing); history, 10 (history, no_history); essentials, 15 (essential_checks, no_essential_checks); responsiveness, 10 (responsive, unresponsive, responsiveness_unknown). Empty for a Didit network member.", + "additionalProperties": { + "type": "object", + "properties": { + "points": { + "type": "number", + "description": "Points the component contributes, rounded to one decimal." + }, + "max": { + "type": "integer", + "description": "The component's weight: the points it contributes at best." + }, + "reason": { + "type": "string", + "description": "Machine-readable code of the component's verdict." + }, + "detail": { + "type": "string", + "description": "One-line English explanation." + } + } + } + }, + "dd_assessed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When dd_score, risk_level and responsiveness were last computed: after an exchange with the entry completes, finishes, expires, mismatches or is declined, when Didit refreshes or curates the entry, and in the weekly directory refresh. Null for a Didit network member." + }, + "dd_score_semantic": { + "type": "string", + "enum": [ + "higher_is_safer" + ], + "description": "Which way dd_score points: always higher_is_safer." + }, + "risk_level": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH", + "" + ], + "description": "The band dd_score falls in: LOW (70 or more), MEDIUM (40 to 69) or HIGH (below 40). Empty for a Didit network member and for an entry not assessed yet. Filter on it with risk. Your own flagged screening never changes this shared value: it makes counterparty_risk_level HIGH in your rules instead." + }, + "responsiveness": { + "type": "object", + "description": "Whether the VASP answers Travel Rule requests: the share of the requests Didit customers started towards it in the last 90 days that it answered. Only requests sent over a network or protocol rail, or by email to a shared address (the entry's compliance address or a platform contact), count; a request sent to an address a customer typed, or to a customer's private contact, never does. An expired request is unanswered; a completed exchange, a decline or a data mismatch is an answer; a cancelled request or an unreachable rail is neither. Always UNKNOWN with zero counts for a Didit network member.", + "properties": { + "status": { + "type": "string", + "enum": [ + "RESPONSIVE", + "UNRESPONSIVE", + "UNKNOWN" + ], + "description": "UNKNOWN with fewer than 5 decided requests in the window; UNRESPONSIVE when less than 10 percent of them were answered; RESPONSIVE otherwise." + }, + "answered": { + "type": "integer", + "description": "Decided requests in the window that the VASP answered." + }, + "total": { + "type": "integer", + "description": "Decided requests in the window: answered plus expired." + }, + "computed_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "essential_checks": { + "type": "object", + "description": "The essential-checks report of a directory entry, built only from the platform's verified sources: registration (an official register lists the VASP: the EU's interim MiCA register, the public TRISA directory or the global LEI register), licence (a MiCA authorisation is on record and current), domain (the entry's domain matches its TRP endpoint or the website its register gives) and lei (its LEI record is active, issued and consistent with the entry's names and country). The essentials component of dd_score is the share of decided checks that pass. Empty until the weekly directory refresh first checks the entry, and always empty for a Didit network member.", + "properties": { + "level": { + "type": "string", + "enum": [ + "essential" + ] + }, + "checked_at": { + "type": "string", + "format": "date-time" + }, + "checks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "enum": [ + "registration", + "licence", + "domain", + "lei" + ] + }, + "status": { + "type": "string", + "enum": [ + "pass", + "fail", + "unknown" + ], + "description": "unknown when no source on record can decide the check." + }, + "reason": { + "type": "string", + "description": "Machine-readable code of the verdict." + }, + "detail": { + "type": "string", + "description": "One-line English explanation." + } + } + } + }, + "passed": { + "type": "integer" + }, + "failed": { + "type": "integer" + }, + "unknown": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "decision": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecision" + } + ], + "nullable": true, + "description": "Your application's decision on this VASP, or for an entity with no decision of its own its brand's (inherited_from names the brand). Null when there is none. Private to your application." + }, + "entity_screening": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreening" + } + ], + "nullable": true, + "description": "Your application's latest sanctions and adverse-media screening of this VASP. Null when you never screened it. Private to your application." + }, + "questionnaires": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaire" + }, + "description": "Your application's due-diligence questionnaires with this VASP, newest first. Private to your application." + } + }, + "example": { + "id": "0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91", + "kind": "directory", + "name": "Example Counterparty VASP", + "lei": "529900EXAMPLECPTY001", + "jurisdiction": "DE", + "domains": [ + "counterparty.example" + ], + "reachable": true, + "aliases": [ + { + "name": "Example Counterparty", + "kind": "trading_name" + } + ], + "networks": [ + { + "network": "GTR", + "code": "EXAMPLE01", + "listed_name": "Example Counterparty VASP", + "country": "DE", + "verified_at": "2026-10-12T09:30:00Z", + "last_seen_at": "2026-10-12T09:30:00Z", + "source": "staff" + } + ], + "routes": [ + { + "rail": "GTR", + "status": "unavailable", + "reason": "no_membership", + "via": "" + }, + { + "rail": "EMAIL", + "status": "available", + "reason": "", + "via": "contact" + } + ], + "umbrella": null, + "entities": [], + "contacts": [ + { + "id": "6c1f9b8e-2d4a-4f7e-8b3c-5a9d0e1f2a3b", + "email": "travelrule@counterparty.example", + "name": "Travel Rule desk", + "source": "staff", + "scope": "platform", + "verified_at": null + } + ], + "dd_status": "NOT_STARTED", + "dd_score": 62, + "dd_score_breakdown": { + "jurisdiction": { + "points": 25.0, + "max": 25, + "reason": "jurisdiction_not_high_risk", + "detail": "DEU is not on the high-risk jurisdiction list" + }, + "verification": { + "points": 3.8, + "max": 15, + "reason": "dd_status_not_started", + "detail": "due diligence status NOT_STARTED" + }, + "protocol": { + "points": 6.0, + "max": 15, + "reason": "email_only", + "detail": "reachable over the email fallback only" + }, + "lei": { + "points": 10.0, + "max": 10, + "reason": "lei_valid", + "detail": "valid LEI on record" + }, + "history": { + "points": 5.0, + "max": 10, + "reason": "no_history", + "detail": "no exchange history" + }, + "essentials": { + "points": 7.5, + "max": 15, + "reason": "no_essential_checks", + "detail": "no decided essential check yet" + }, + "responsiveness": { + "points": 5.0, + "max": 10, + "reason": "responsiveness_unknown", + "detail": "0 decided recent requests, too few to judge" + } + }, + "dd_assessed_at": "2026-10-12T09:30:00Z", + "dd_score_semantic": "higher_is_safer", + "risk_level": "MEDIUM", + "responsiveness": { + "status": "UNKNOWN", + "answered": 0, + "total": 0, + "computed_at": "2026-10-12T09:30:00Z" + }, + "essential_checks": {}, + "reachable_rails": [ + "GTR", + "EMAIL" + ], + "is_didit_customer": false, + "decision": { + "decision": "APPROVED", + "reason": "Licensed in DE, questionnaire reviewed", + "decided_by": "", + "updated_at": "2026-10-14T10:05:00Z", + "inherited_from": null + }, + "entity_screening": { + "id": "7a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d", + "status": "Approved", + "total_hits": 0, + "score": null, + "screened_at": "2026-10-14T09:58:00Z", + "fresh": true, + "flagged": false + }, + "questionnaires": [ + { + "id": "8b7a6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d", + "status": "ANSWERED", + "form_version": "1", + "sent_to": "travelrule@counterparty.example", + "sent_at": "2026-09-02T08:00:00Z", + "expires_at": "2026-10-02T08:00:00Z", + "answered_at": "2026-09-04T15:20:00Z", + "valid_until": "2027-09-04T15:20:00Z", + "current": true, + "answers": { + "legal_name": "Example Counterparty VASP, incorporated in the example jurisdiction.", + "licensing": "Crypto-asset service provider authorised under MiCA by its national regulator, licence active.", + "jurisdictions": "EU and EEA. We do not serve the United States.", + "aml_program": "Written AML/CFT policy, annual independent review, last completed in March 2026.", + "compliance_officer": "Head of Compliance, reachable at compliance@counterparty.example.", + "sanctions_screening": "Customers at onboarding and daily; counterparties on every transfer.", + "transaction_monitoring": "Rule-based monitoring with analyst review; suspicious activity reports filed with the FIU.", + "travel_rule": "TRP and email; we answer requests within one business day.", + "custody": "Segregated customer wallets held with a qualified custodian, insured.", + "ownership": "No shareholder holds 25 percent or more; parent company Example Holding Group.", + "record_keeping": "Five-year record-keeping for Travel Rule and transaction data, as our regulator requires.", + "data_protection": "Encrypted at rest and in transit, stored in the EU." + } + } + ] + } + }, + "TravelRuleCounterpartyDecision": { + "type": "object", + "description": "Your application's decision on a counterparty VASP. Private to your application: it never changes the shared directory entry and another customer never sees it. It acts only through your transaction rules (the counterparty_dd_status rule field and the travel-rule-counterparty-blocked and travel-rule-counterparty-restricted presets); it never changes how a transfer is routed.", + "properties": { + "decision": { + "type": "string", + "enum": [ + "APPROVED", + "RESTRICTED", + "BLOCKED" + ], + "description": "APPROVED: your team cleared the counterparty. RESTRICTED: transfers to it may go ahead after review. BLOCKED: your team refused it." + }, + "reason": { + "type": "string", + "maxLength": 255, + "description": "Free-text reason you gave, empty when none." + }, + "decided_by": { + "type": "string", + "description": "The console user who made or last changed the decision. Empty when an API key made it." + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "inherited_from": { + "type": "object", + "nullable": true, + "description": "The brand whose decision this entity follows because you made none on the entity itself. Null when the decision is the counterparty's own.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + } + } + } + } + }, + "TravelRuleCounterpartyDecisionResponse": { + "type": "object", + "properties": { + "decision": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecision" + } + ], + "nullable": true, + "description": "The decision that applies to the counterparty for your application: its own, else (for an entity of a brand) the brand's. Null when there is none." + } + } + }, + "TravelRuleCounterpartyDecisionRequest": { + "type": "object", + "required": [ + "decision" + ], + "properties": { + "decision": { + "type": "string", + "enum": [ + "APPROVED", + "RESTRICTED", + "BLOCKED" + ] + }, + "reason": { + "type": "string", + "maxLength": 255, + "description": "Optional. Replaces the previous reason; omit it to clear it." + } + } + }, + "TravelRuleCounterpartyScreening": { + "type": "object", + "description": "One sanctions and adverse-media screening of the counterparty entity, run for and billed to your application. Private to your application: another customer never sees it.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Approved: no possible match needs attention. In Review or Declined: a possible sanctions or adverse-media match." + }, + "total_hits": { + "type": "integer", + "description": "How many possible matches the screening returned." + }, + "score": { + "type": "number", + "nullable": true, + "description": "The highest risk score among the possible matches. Null when nothing matched." + }, + "screened_at": { + "type": "string", + "format": "date-time" + }, + "fresh": { + "type": "boolean", + "description": "Whether the screening is younger than fresh_days (30 days). A fresh screening is reused instead of billed again." + }, + "flagged": { + "type": "boolean", + "description": "true while the screening is fresh and its status is In Review or Declined. While your latest screening is flagged and counterparty screening stays on, counterparty_risk_level is HIGH in your rules." + } + } + }, + "TravelRuleCounterpartyScreeningStatus": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Your counterparty_screening_enabled Travel Rule setting. Off by default; nothing is screened or billed while it is off." + }, + "price": { + "type": "string", + "description": "Decimal string, in USD: what one screening costs your organization, the Company AML Screening price." + }, + "fresh_days": { + "type": "integer", + "description": "How many days a screening stays fresh and is reused." + }, + "screening": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreening" + } + ], + "nullable": true, + "description": "Your latest screening of the counterparty. Null when you never screened it." + } + } + }, + "TravelRuleCounterpartyScreeningRequest": { + "type": "object", + "properties": { + "refresh": { + "type": "boolean", + "default": false, + "description": "true screens again (and bills again) even when a fresh screening exists." + } + } + }, + "TravelRuleCounterpartyScreeningResult": { + "type": "object", + "properties": { + "outcome": { + "type": "string", + "enum": [ + "screened", + "reused" + ], + "description": "screened: a new screening ran and was billed (201). reused: a fresh screening was returned and nothing was billed (200)." + }, + "screening": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreening" + } + } + }, + "TravelRuleCounterpartyQuestionnaire": { + "type": "object", + "description": "One due-diligence questionnaire your application sent the counterparty: Didit's standard questionnaire, emailed to a compliance address on record with a single-use answer link. Private to your application.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "SENT", + "ANSWERED", + "EXPIRED" + ], + "description": "SENT: waiting for answers. ANSWERED: the counterparty answered every question. EXPIRED: the answer link closed unanswered (its deadline passed, or a newer questionnaire replaced it), or answers passed valid_until." + }, + "form_version": { + "type": "string", + "description": "Version of Didit's standard questionnaire the answers belong to. Currently 1." + }, + "sent_to": { + "type": "string", + "format": "email" + }, + "sent_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "The answer-by deadline, 30 days after sending. The link stops working after it." + }, + "answered_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "valid_until": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "How long the answers stay current: 365 days after answering. Null until answered." + }, + "current": { + "type": "boolean", + "description": "true while the questionnaire is ANSWERED and valid_until has not passed." + }, + "answers": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The answers keyed by question id: legal_name, licensing, jurisdictions, aml_program, compliance_officer, sanctions_screening, transaction_monitoring, travel_rule, custody, ownership, record_keeping, data_protection. Each answer is up to 2,000 characters. Empty until answered." + } + } + }, + "TravelRuleCounterpartyQuestionnaireRequest": { + "type": "object", + "properties": { + "sent_to": { + "type": "string", + "format": "email", + "description": "Optional. A compliance address on record for the counterparty: for a directory entry its compliance address or a contact your application sees, for a Didit network member its confirmed compliance email. Default: the address a Travel Rule email to the counterparty goes to (your own contact first, then a platform contact, then the entry's compliance address)." + } + } + }, + "TravelRuleCounterpartyQuestionnaireList": { + "type": "object", + "properties": { + "questionnaires": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaire" + }, + "description": "Newest first." + } + } + }, + "TravelRulePickupInfo": { + "type": "object", + "description": "The pending exchange behind an emailed pickup link, as the counterparty's response page shows it.", + "properties": { + "reference": { + "type": "string", + "format": "uuid", + "description": "The transfer's uuid, as the sending VASP's console shows it." + }, + "requested_at": { + "type": "string", + "format": "date-time" + }, + "originator_vasp_name": { + "type": "string" + }, + "originator_vasp_lei": { + "type": "string" + }, + "recipient_vasp_name": { + "type": "string", + "description": "The counterparty VASP the request is addressed to." + }, + "language": { + "type": "string", + "description": "The language of the request email and the response page, from the counterparty's jurisdiction." + }, + "amount": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "fiat_amount": { + "type": "string", + "description": "The converted value the sender gave, or empty." + }, + "fiat_currency": { + "type": "string" + }, + "chain": { + "type": "string" + }, + "beneficiary_wallet": { + "type": "string" + }, + "status": { + "type": "string" + }, + "ivms_version": { + "type": "string" + }, + "deadline_at": { + "type": "string", + "format": "date-time", + "description": "When the link stops answering." + }, + "reminders_sent": { + "type": "integer" + }, + "contact": { + "type": "string", + "description": "The `?contact=` value of the link, echoed back; empty when the link has none." + }, + "decline_reasons": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "data_mismatch", + "not_our_customer", + "wallet_not_hosted", + "sanctions", + "risk_appetite", + "jurisdiction", + "insufficient_data", + "other" + ] + }, + "description": "The values `decline_reason` accepts." + }, + "join_invitation": { + "type": "object", + "description": "Whether the counterparty holds a live invitation to join Didit for free. The invitation link travels only in the email to the invited address, so `url` is empty here.", + "properties": { + "eligible": { + "type": "boolean" + }, + "url": { + "type": "string" + } + } + }, + "originator_data": { + "type": "object", + "description": "The originator fields the sending VASP shares (`data_sharing_fields`). The name and the wallet are always included." + }, + "beneficiary_data": { + "type": "object" + }, + "ivms_request": { + "type": "object", + "description": "The same request as an IVMS 101 object. Left out while `data_masked` is true." + }, + "data_masked": { + "type": "boolean", + "description": "Always false on this endpoint: an email request is outbound, and the sender's own user is never masked." + } + } + }, + "TravelRulePickupRespondRequest": { + "type": "object", + "required": [ + "decision" + ], + "properties": { + "decision": { + "type": "string", + "enum": [ + "accept", + "decline" + ] + }, + "beneficiary_name": { + "type": "string", + "description": "The beneficiary's name as your records hold it, run through the sending VASP's name rule. `ivms_response` takes precedence when it names the beneficiary.", + "maxLength": 255 + }, + "decline_reason": { + "type": "string", + "enum": [ + "", + "data_mismatch", + "not_our_customer", + "wallet_not_hosted", + "sanctions", + "risk_appetite", + "jurisdiction", + "insufficient_data", + "other" + ], + "description": "Why you decline. Recorded on the sending VASP's transfer timeline. Empty (the default) when you give no reason." + }, + "decline_detail": { + "type": "string", + "maxLength": 512, + "description": "Free text; required when `decision` is `decline` and `decline_reason` is `other`." + }, + "ivms_response": { + "type": "object", + "description": "Your full IVMS 101 response. Its beneficiary name wins over `beneficiary_name`." + }, + "contact": { + "type": "string", + "format": "uuid", + "description": "The contact the link was sent to, from the link's `?contact=` value. When the body leaves it out, the `contact` query parameter is used." + } + } + }, + "TravelRuleInboundRequest": { + "type": "object", + "required": [ + "chain", + "txid", + "wallet_address", + "amount", + "currency" + ], + "properties": { + "chain": { + "type": "string", + "description": "Chain the deposit settled on, for example `ETH`. Stored lowercase and compared case-insensitively with your wallet address book entries, so use the same chain token there.", + "maxLength": 32 + }, + "txid": { + "type": "string", + "description": "On-chain transaction hash. Deduped together with wallet_address.", + "maxLength": 128 + }, + "wallet_address": { + "type": "string", + "description": "The destination wallet you received the deposit on. An XRP X-address is split into its classic address and tag.", + "maxLength": 128 + }, + "amount": { + "type": "string", + "description": "Deposit amount as a decimal string, zero or greater, with up to 18 decimal places." + }, + "currency": { + "type": "string", + "description": "Deposit asset, e.g. \"USDC\".", + "maxLength": 16 + }, + "originator_data": { + "type": "object", + "description": "Originator details from the sending VASP, if known, as a flat object such as `{\"name\": \"...\"}`. Didit reads name (or full_name), first_name, last_name and vendor_data from it.", + "additionalProperties": true + }, + "beneficiary_data": { + "type": "object", + "description": "Beneficiary details (your customer), as a flat object such as `{\"name\": \"...\"}`. Didit matches name against the holder_name of the matched address book entry, and sets wallet_address and chain from the request.", + "additionalProperties": true + }, + "originating_vasp": { + "type": "object", + "description": "Optional description of the sending VASP. It stays on this transfer only (`metadata.originating_vasp`, and the transfer's originator VASP in GET /v3/travel-rule/transfers/); it never creates, edits or links a shared VASP directory entry.", + "properties": { + "name": { + "type": "string", + "maxLength": 255 + }, + "lei": { + "type": "string", + "maxLength": 20, + "description": "Optional. A valid 20-character ISO 17442 LEI, or empty." + }, + "travel_address": { + "type": "string", + "description": "The sender's travel address, recorded on this transfer.", + "maxLength": 512 + } + } + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "Optional. The deposit's destination tag or memo on a memo network: on a shared deposit address it is what tells your customers apart, and the address-book entry for that memo is matched. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`. Alias: `destinationTag` or `destination_tag`." + }, + "utxo": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleUtxo" + } + ], + "description": "Optional, UTXO chains only. The deposit transaction's inputs and outputs: the sender's wallet is the declared `originator_data.wallet_address`, else the largest input." + } + } + }, + "TravelRuleInboundResponse": { + "type": "object", + "properties": { + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Didit transaction UUID for the inbound deposit." + }, + "txn_id": { + "type": "string", + "description": "txn_id of the inbound transaction: Didit-generated when Didit created the transaction, or your own when an unclaimed deposit was reused." + }, + "created": { + "type": "boolean", + "description": "true when this call registered the transfer (HTTP 201); false when a transfer for the same txid and wallet_address already existed (HTTP 200)." + }, + "travel_rule": { + "$ref": "#/components/schemas/TravelRuleTransferDetail" + } + } + }, + "TravelRuleTestVaspInboundRequest": { + "type": "object", + "required": [ + "beneficiary_wallet_address" + ], + "properties": { + "beneficiary_wallet_address": { + "type": "string", + "description": "The destination wallet address. The Didit Test VASP sends to it, matched against your wallet address book on the chain of the asset. A wallet with no matching entry is accepted and the inbound transaction is booked ON_HOLD." + }, + "scenario": { + "type": "string", + "enum": [ + "accept", + "name_mismatch" + ], + "default": "accept", + "description": "What the Didit Test VASP declares the beneficiary to be. `accept` declares the holder's own name; `name_mismatch` declares a name no holder agrees with. The decision itself is always yours." + }, + "chain": { + "type": "string", + "default": "ETH", + "description": "Chain of the asset." + }, + "amount": { + "type": "string", + "default": "1", + "description": "Amount as a decimal string." + }, + "currency": { + "type": "string", + "default": "ETH", + "description": "Asset code." + }, + "reference": { + "type": "string", + "description": "Optional idempotency key. A repeat with the same value returns the transfer the first call booked." + } + } + }, + "TravelRuleTestVaspInboundResponse": { + "type": "object", + "properties": { + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Didit transaction UUID of the inbound transfer." + }, + "txn_id": { + "type": "string", + "description": "Didit-generated txn_id." + }, + "scenario": { + "type": "string", + "description": "The scenario that ran." + }, + "travel_rule_status": { + "type": "string", + "description": "The status your application's own decision resolved to." + }, + "detail": { + "type": "string", + "description": "Why the transfer resolved to that status." + }, + "test_vasp": { + "type": "boolean", + "description": "Always true: the sender was the Didit Test VASP." + }, + "travel_rule": { + "$ref": "#/components/schemas/TravelRuleTransferDetail" + } + } + }, + "TravelRuleWidgetSessionCreate": { + "type": "object", + "properties": { + "wallet_address": { + "type": "string", + "description": "The wallet address to verify. Optional; when empty, the address must be supplied when a proof is started.", + "maxLength": 128 + }, + "chain": { + "type": "string", + "description": "Chain token for the address, for example `ETH`, `BTC` or `SOL`. Use the same token as in your wallet address book. Message signing and the Satoshi test are offered only for a supported chain. The address book entry the proof verifies stores it under its canonical identifier (ETH, BTC, SOL, TRX).", + "maxLength": 32 + }, + "holder_name": { + "type": "string", + "description": "Name of the wallet holder, used for the beneficiary name match.", + "maxLength": 255 + }, + "vendor_data": { + "type": "string", + "description": "Your internal identifier for the holder, carried onto the created address-book entry.", + "maxLength": 255 + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Optional. Didit transaction UUID of a Travel Rule transfer to advance when the proof verifies.", + "nullable": true + }, + "satoshi_deposit_address": { + "type": "string", + "description": "Optional. A deposit address you control; supplying it enables the SATOSHI_TEST proof method.", + "maxLength": 128 + }, + "callback_url": { + "type": "string", + "description": "Optional. Absolute http(s) URL where the widget returns the customer after completion.", + "maxLength": 512 + }, + "expires_in_minutes": { + "type": "integer", + "description": "Optional. Lifetime of the minted link, in minutes. Automatically extended to cover the Satoshi test window when SATOSHI_TEST is offered.", + "default": 60, + "minimum": 5, + "maximum": 10080 + } + } + }, + "TravelRuleWidgetSessionResponse": { + "type": "object", + "properties": { + "widget_session_id": { + "type": "string", + "format": "uuid" + }, + "token": { + "type": "string", + "description": "Opaque session token embedded in the widget URL." + }, + "url": { + "type": "string", + "description": "The hosted wallet-ownership widget URL. Open it verbatim in the system browser (never inside a mobile SDK webview)." + }, + "expires_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleWidgetContext": { + "type": "object", + "properties": { + "vasp_name": { + "type": "string", + "description": "Legal name from your VASP profile, shown to the customer in the widget. Empty when no profile exists." + }, + "wallet_address": { + "type": "string" + }, + "chain": { + "type": "string", + "description": "The chain the session was minted with, as you sent it." + }, + "chain_family": { + "type": "string", + "nullable": true, + "enum": [ + "EVM", + "BTC", + "SOL", + "TRON", + null + ], + "description": "The key family behind chain: EVM (Ethereum, BNB Chain, Polygon, Arbitrum, Base, Optimism, Avalanche, Linea and the other EVM chains), BTC, SOL or TRON. One EVM key controls the same address on every EVM chain. Null when no signature can be verified on the chain." + }, + "holder_name": { + "type": "string" + }, + "allowed_methods": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SCREENSHOT", + "SATOSHI_TEST", + "SELF_DECLARATION" + ] + }, + "description": "Proof methods available for this session. MESSAGE_SIGNING requires a chain with a chain_family, SATOSHI_TEST requires a satoshi_deposit_address on the session and a chain whose deposits Didit can read back, SCREENSHOT requires the allow_screenshot_proof setting, and SELF_DECLARATION requires the allow_self_declaration setting." + }, + "transfer_status": { + "type": "string", + "nullable": true, + "description": "Status of the linked Travel Rule transfer. Null when the session was minted without a transaction_id." + }, + "callback_url": { + "type": "string", + "description": "Where the widget returns the customer after completion. Empty when not set." + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Non-null once a proof has been verified." + }, + "proofs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleWidgetProofSummary" + } + }, + "max_attempts": { + "type": "integer", + "description": "Rejected wallet proofs this session allows." + }, + "attempts_used": { + "type": "integer", + "description": "Rejected proofs so far." + }, + "attempts_remaining": { + "type": "integer", + "description": "When 0, starting another proof answers 409 and you mint a new widget session." + }, + "customization": { + "type": "object", + "nullable": true, + "description": "The application's whitelabel customization (colors, logo, font family) when white label is enabled - the widget renders with the same branding as the verification flow. Null otherwise." + } + } + }, + "TravelRuleWidgetProofSummary": { + "type": "object", + "properties": { + "proof_id": { + "type": "string", + "format": "uuid" + }, + "method": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SATOSHI_TEST", + "SCREENSHOT", + "SELF_DECLARATION" + ] + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ], + "description": "PENDING: the end user picked this method and the proof is not decided yet. A failed signature or a test deposit that is not readable or confirmed yet keeps the proof PENDING with a failure_reason, so the end user can try again; a submitted SATOSHI_TEST deposit is rechecked automatically until its window closes, and a submitted SCREENSHOT waits for your review. VERIFIED: ownership is proven. REJECTED: the test deposit is genuinely wrong (another sender, not the expected amount to the deposit address, older than the challenge, or a transaction that already proved another wallet) or your reviewer rejected the screenshot; failure_reason says which. A rejected proof is final and uses one of the session's attempts. EXPIRED: the proof can no longer be decided (its window closed, the end user switched method, the widget session lapsed, or the wallet's holder changed); failure_reason says which." + }, + "wallet_address": { + "type": "string" + }, + "chain": { + "type": "string", + "description": "Canonical chain identifier of the address book entry the proof is for, e.g. ETH, BTC, SOL, TRX." + }, + "deposit_address": { + "type": "string", + "nullable": true, + "description": "Deposit address the customer must send the Satoshi test amount to. Present only on a SATOSHI_TEST proof." + }, + "expected_amount": { + "type": "string", + "nullable": true, + "description": "Exact on-chain amount, in the deposit's native currency, the Satoshi test deposit must match. Present only on a SATOSHI_TEST proof." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Deadline for this proof's own Satoshi test window. Present only on a SATOSHI_TEST proof; the widget session's own expires_at is extended to cover it when SATOSHI_TEST is offered." + }, + "challenge": { + "type": "string", + "nullable": true, + "description": "Didit-issued message the customer signs with their wallet. Present only on a MESSAGE_SIGNING proof." + }, + "rejection_reason": { + "type": "string", + "description": "Why the proof was rejected, as an English sentence for the end user. Present only when `status` is REJECTED; empty when your reviewer rejected a screenshot (your note stays with you)." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "attempts": { + "type": "integer", + "description": "How many times evidence was submitted for this proof (signatures, transaction ids, uploads)." + }, + "failure_reason": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleWalletProofFailureReason" + } + ], + "description": "Why the last attempt fell short, or why the proof closed. Empty when nothing failed." + } + } + }, + "CaseSubject": { + "type": "object", + "nullable": true, + "description": "The single user or business the case is anchored to.", + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "business" + ] + }, + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "external_id": { + "type": "string", + "nullable": true, + "description": "The subject's vendor_data." + }, + "country": { + "type": "string", + "nullable": true, + "description": "Three-letter country code of the subject (ISO 3166-1 alpha-3), or null when it is not known. For a business, its country of incorporation. For a person, the issuing country of an identity document from their verifications; a person with documents from several countries shows one of them, and ICAO document codes such as `XXA` can appear.", + "example": "ESP" + }, + "portrait": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived signed URL of the person's portrait image. Always null for a business, and null for a person without a portrait. The URL expires, so download the image when you read the case instead of storing the URL." + } + } + }, + "CaseAssignee": { + "type": "object", + "nullable": true, + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "full_name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + } + } + }, + "CaseBlueprintSummary": { + "type": "object", + "nullable": true, + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "maxLength": 24 + } + } + }, + "CaseContentConfig": { + "type": "object", + "description": "The six togglable sections that drive which tabs and fields appear on the case page. Unknown sections or keys are rejected.", + "properties": { + "checklist": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "optional": { + "type": "boolean", + "description": "When true, a case can be resolved with incomplete checklist items." + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "maxLength": 255 + }, + "order": { + "type": "integer" + } + } + } + } + } + }, + "aml_control": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "financial": { + "type": "boolean" + }, + "identity": { + "type": "boolean" + } + } + }, + "identity": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "personal_information": { + "type": "boolean" + }, + "applicant_tags": { + "type": "boolean" + }, + "contact_information": { + "type": "boolean" + }, + "risk_overview": { + "type": "boolean" + }, + "poi_documents": { + "type": "boolean" + }, + "poa_documents": { + "type": "boolean" + } + } + }, + "verifications": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "summary": { + "type": "boolean" + } + } + }, + "reporting": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "fiu_reports": { + "type": "boolean" + } + } + }, + "financial": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "payment_methods": { + "type": "boolean" + }, + "transactions": { + "type": "boolean" + } + } + } + } + }, + "CaseNote": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "comment": { + "type": "string" + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + } + }, + "is_internal": { + "type": "boolean" + }, + "attachments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "s3_key": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "file_size": { + "type": "integer" + }, + "url": { + "type": "string", + "format": "uri" + } + } + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "CaseEvent": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "event_type": { + "type": "string", + "enum": [ + "CREATED", + "STATUS_CHANGED", + "ASSIGNED", + "UNASSIGNED", + "RESOLVED", + "REOPENED", + "LINK_ADDED", + "LINK_REMOVED", + "NOTE_ADDED", + "NOTE_PINNED", + "NOTE_UNPINNED", + "PRIORITY_CHANGED", + "DUE_DATE_CHANGED", + "TAG_ADDED", + "TAG_REMOVED", + "ESCALATED", + "TRANSFERRED", + "CHECKLIST_COMPLETED", + "REPORT_CREATED", + "REPORT_FINALIZED", + "SUBMITTED_FOR_APPROVAL", + "APPROVAL_GRANTED", + "APPROVAL_REJECTED", + "ENTITY_ACTION", + "APPROVAL_INVALIDATED", + "CUSTOMER_REQUEST", + "STAGE_CHANGED", + "TEAM_CHANGED", + "CHECKER_ASSIGNED", + "CHECKER_UNASSIGNED" + ] + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "previous_status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ], + "nullable": true + }, + "new_status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ], + "nullable": true + }, + "metadata": { + "type": "object" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "CaseLink": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "linked_entity_type": { + "type": "string", + "enum": [ + "session", + "business_session", + "transaction" + ], + "nullable": true + }, + "linked_entity_uuid": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "linked_entity_summary": { + "type": "object", + "nullable": true, + "description": "session_id/session_number/status for session and business_session links; uuid/txn_id/status for transaction links." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "CaseChecklistItem": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string", + "maxLength": 255 + }, + "is_completed": { + "type": "boolean" + }, + "completed_by": { + "type": "string", + "format": "email", + "nullable": true + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "order": { + "type": "integer" + } + } + }, + "CaseBlueprintListItem": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "maxLength": 24 + }, + "description": { + "type": "string", + "nullable": true + }, + "assignment_method": { + "type": "string", + "enum": [ + "MANUAL", + "AUTOMATIC" + ] + }, + "assignee_pool": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "max_active_cases": { + "type": "integer", + "nullable": true + }, + "escalation_enabled": { + "type": "boolean" + }, + "transfer_enabled": { + "type": "boolean" + }, + "require_resolution_note": { + "type": "boolean" + }, + "is_active": { + "type": "boolean" + }, + "preset_key": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "version": { + "type": "integer", + "readOnly": true + }, + "default_deadline_hours": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 8760 + } + } + }, + "CaseBlueprintDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "maxLength": 24 + }, + "description": { + "type": "string", + "nullable": true + }, + "assignment_method": { + "type": "string", + "enum": [ + "MANUAL", + "AUTOMATIC" + ] + }, + "assignee_pool": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "max_active_cases": { + "type": "integer", + "nullable": true + }, + "escalation_enabled": { + "type": "boolean" + }, + "transfer_enabled": { + "type": "boolean" + }, + "require_resolution_note": { + "type": "boolean" + }, + "is_active": { + "type": "boolean" + }, + "preset_key": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "escalation_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "escalation_blueprint_name": { + "type": "string", + "nullable": true + }, + "four_eyes_checker_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "four_eyes_checker_blueprint_name": { + "type": "string", + "nullable": true + }, + "content_config": { + "type": "object", + "description": "The six togglable sections that drive which tabs and fields appear on the case page. Unknown sections or keys are rejected.", + "properties": { + "checklist": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "optional": { + "type": "boolean", + "description": "When true, a case can be resolved with incomplete checklist items." + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "maxLength": 255 + }, + "order": { + "type": "integer" + } + } + } + } + } + }, + "aml_control": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "financial": { + "type": "boolean" + }, + "identity": { + "type": "boolean" + } + } + }, + "identity": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "personal_information": { + "type": "boolean" + }, + "applicant_tags": { + "type": "boolean" + }, + "contact_information": { + "type": "boolean" + }, + "risk_overview": { + "type": "boolean" + }, + "poi_documents": { + "type": "boolean" + }, + "poa_documents": { + "type": "boolean" + } + } + }, + "verifications": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "summary": { + "type": "boolean" + } + } + }, + "reporting": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "fiu_reports": { + "type": "boolean" + } + } + }, + "financial": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "payment_methods": { + "type": "boolean" + }, + "transactions": { + "type": "boolean" + } + } + } + } + }, + "creation_sources": { + "type": "array", + "description": "Connectors currently routing new cases into this blueprint.", + "items": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "aml", + "rule", + "workflow" + ] + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + }, + "receives_escalation_from": { + "type": "array", + "description": "Other blueprints whose Escalation target points at this one.", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "version": { + "type": "integer", + "readOnly": true + }, + "default_deadline_hours": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 8760 + } + } + }, + "CaseListItem": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "case_number": { + "type": "string", + "nullable": true, + "description": "Auto-assigned, e.g. CS-0001." + }, + "title": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ] + }, + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ], + "nullable": true + }, + "pending_resolution": { + "type": "object", + "nullable": true, + "description": "Set only while a 4-eyes resolution is staged on the checker blueprint, awaiting approve or reject-approval.", + "properties": { + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ] + }, + "resolution_notes": { + "type": "string", + "nullable": true + }, + "maker_email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email of the officer who submitted the resolution. Null when an API key submitted it: that resolution has no recorded maker, so approve and reject-approval return 403 for every caller." + }, + "maker_name": { + "type": "string", + "nullable": true + }, + "maker_user_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Member user uuid of the officer who submitted the resolution. Null when an API key submitted it. That member cannot be named as `checker`." + }, + "maker_blueprint_id": { + "type": "string", + "format": "uuid" + }, + "staged_at": { + "type": "string", + "format": "date-time" + } + } + }, + "checker": { + "$ref": "#/components/schemas/CaseChecker" + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "source": { + "type": "string", + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ] + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "subject": { + "$ref": "#/components/schemas/CaseSubject" + }, + "assigned_to": { + "$ref": "#/components/schemas/CaseAssignee" + }, + "assigned_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "team": { + "$ref": "#/components/schemas/CaseTeamSummary" + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "created_by_email": { + "type": "string", + "nullable": true + }, + "created_by_name": { + "type": "string", + "nullable": true + }, + "blueprint": { + "$ref": "#/components/schemas/CaseBlueprintSummary" + }, + "links_count": { + "type": "integer" + }, + "notes_count": { + "type": "integer" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "application_id": { + "type": "string", + "format": "uuid" + }, + "application_name": { + "type": "string" + }, + "organization_id": { + "type": "string", + "format": "uuid" + }, + "organization_name": { + "type": "string" + } + } + }, + "CaseDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "case_number": { + "type": "string", + "nullable": true + }, + "title": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ] + }, + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ], + "nullable": true + }, + "resolution_notes": { + "type": "string", + "nullable": true + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "source": { + "type": "string", + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ] + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "subject": { + "$ref": "#/components/schemas/CaseSubject" + }, + "assigned_to": { + "$ref": "#/components/schemas/CaseAssignee" + }, + "assigned_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "team": { + "$ref": "#/components/schemas/CaseTeamSummary" + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resolved_by_email": { + "type": "string", + "nullable": true + }, + "pending_resolution": { + "type": "object", + "nullable": true, + "description": "Set only while a 4-eyes resolution is staged on the checker blueprint, awaiting approve or reject-approval.", + "properties": { + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ] + }, + "resolution_notes": { + "type": "string", + "nullable": true + }, + "maker_email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email of the officer who submitted the resolution. Null when an API key submitted it: that resolution has no recorded maker, so approve and reject-approval return 403 for every caller." + }, + "maker_name": { + "type": "string", + "nullable": true + }, + "maker_user_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Member user uuid of the officer who submitted the resolution. Null when an API key submitted it. That member cannot be named as `checker`." + }, + "maker_blueprint_id": { + "type": "string", + "format": "uuid" + }, + "staged_at": { + "type": "string", + "format": "date-time" + } + } + }, + "checker": { + "$ref": "#/components/schemas/CaseChecker" + }, + "metadata": { + "type": "object" + }, + "blueprint": { + "$ref": "#/components/schemas/CaseBlueprintDetail" + }, + "notes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseNote" + }, + "description": "Newest 50 notes." + }, + "events": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseEvent" + }, + "description": "Newest 100 events." + }, + "links": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseLink" + } + }, + "links_count": { + "type": "integer" + }, + "notes_count": { + "type": "integer" + }, + "checklist_items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseChecklistItem" + } + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "ReportTemplateDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code." + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "report_type_display": { + "type": "string" + }, + "field_config": { + "type": "object", + "description": "Reusable goAML header defaults. Template-level obligatory fields: rentity_id, submission_code, and currency_code_local (3-letter). Optional: enabled_fields (which optional report blocks to include) plus reporting_person, location, reason, and action defaults. Per-filing header fields (action, submission_date, transactions, jurisdiction) are enforced when the report is validated or finalized, not on the template." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "RegulatoryReportDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "case_uuid": { + "type": "string", + "format": "uuid" + }, + "case_number": { + "type": "string", + "nullable": true + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "report_type_display": { + "type": "string" + }, + "report_code": { + "type": "string", + "description": "goAML report code derived from report_type, e.g. STR, CTR, BCR." + }, + "report_number": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "enum": [ + "DRAFT", + "VALIDATED", + "FINALIZED" + ] + }, + "status_display": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2 + }, + "rentity_id": { + "type": "string", + "nullable": true, + "description": "Reporting entity ID issued by the FIU." + }, + "rentity_branch": { + "type": "string", + "nullable": true + }, + "submission_code": { + "type": "string", + "enum": [ + "E", + "M" + ] + }, + "entity_reference": { + "type": "string", + "nullable": true + }, + "fiu_ref_number": { + "type": "string", + "nullable": true + }, + "currency_code_local": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "nullable": true + }, + "submission_date": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reporting_person": { + "type": "object" + }, + "location": { + "type": "object" + }, + "reason": { + "type": "string", + "nullable": true + }, + "action": { + "type": "string", + "nullable": true + }, + "activity": { + "type": "string", + "nullable": true + }, + "narrative": { + "type": "object", + "description": "Five Ws narrative sections.", + "properties": { + "who": { + "type": "string" + }, + "what": { + "type": "string" + }, + "when": { + "type": "string" + }, + "where": { + "type": "string" + }, + "why": { + "type": "string" + }, + "how": { + "type": "string" + } + } + }, + "report_indicators": { + "type": "array", + "items": { + "type": "string" + } + }, + "included_fields": { + "type": "array", + "items": { + "type": "string" + } + }, + "transactions": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "validation_errors": { + "type": "array", + "description": "Result of the most recent validation; each entry is a {field, message} object.", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + }, + "validated_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "finalized_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "created_by_email": { + "type": "string", + "nullable": true + }, + "created_by_name": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TransactionRuleCondition": { + "type": "object", + "required": [ + "field", + "operator" + ], + "properties": { + "field": { + "type": "string", + "description": "Transaction field path to evaluate. Core fields: amount, currency, direction, action_type, score, severity, transaction_type, payment_details, preferred_currency_amount (falls back to amount_in_default_currency), default_currency_code, subject_country, counterparty_country, subject_vendor_data, counterparty_vendor_data, subject_device_fingerprint, counterparty_device_fingerprint, subject_browser_family, subject_browser_version, subject_platform, subject_accept_language, subject_session_age_ms, subject_ip_country, counterparty_ip_country, subject_ip_address, counterparty_ip_address, subject_payment_method_type, subject_payment_method_country, subject_payment_method_fingerprint, subject_payment_method_account_id (and the counterparty_payment_method_* equivalents), travel_rule_status, travel_rule_required, travel_rule_obligations_count, counterparty_dd_status (your decision on the Travel Rule counterparty: APPROVED, RESTRICTED or BLOCKED, an entity following its brand's), counterparty_risk_level (LOW, MEDIUM or HIGH; HIGH while your own counterparty screening is flagged), counterparty_jurisdiction (the counterparty VASP's country), counterparty_responsiveness (RESPONSIVE, UNRESPONSIVE or UNKNOWN), subject_days_since_previous_transaction, tags, and custom_values. for any key submitted in custom_properties on transaction create. Country-code fields accept ISO-2 or ISO-3; both sides are normalized to ISO-3 before comparison." + }, + "operator": { + "type": "string", + "enum": [ + "eq", + "ne", + "gt", + "gte", + "lt", + "lte", + "in", + "not_in", + "contains", + "not_contains", + "contains_any", + "regex", + "fuzzy_match", + "exists", + "is_not_empty", + "is_not_null", + "not_exists", + "is_empty", + "is_null" + ], + "description": "Comparison operator. in/not_in test membership (value may be a list or a single scalar). contains/not_contains are case-insensitive substring checks against a string value. contains_any takes a list of strings and matches when any is a case-insensitive substring of the field value. regex matches the value pattern against the field. fuzzy_match compares against the value string with the required `score` threshold (0-100). gt/gte/lt/lte never match when either side is missing. exists/is_not_empty/is_not_null (aliases) and not_exists/is_empty/is_null (aliases) are presence checks that ignore `value`." + }, + "value": { + "description": "Comparison value. Its shape depends on the operator and value_type; omit it for the presence-check operators. Every other operator needs a value that is not `null` or an empty list, otherwise the rule is refused with `400`. `contains_any` needs a list of strings, and `contains`, `not_contains` and `regex` need a single string." + }, + "value_type": { + "type": "string", + "enum": [ + "list", + "field", + "relative_date" + ], + "description": "Optional value interpretation. `list`: value is a List UUID and the list's entries become the comparison value. `field`: value is another field path, compared field-to-field. `relative_date`: value is a relative-date object, e.g. {\"direction\": \"past\", \"unit\": \"days\", \"amount\": 30}." + }, + "score": { + "type": "number", + "minimum": 0, + "maximum": 100, + "description": "Required when operator is fuzzy_match: the 0-100 similarity threshold." + }, + "group_index": { + "type": "integer", + "description": "Optional grouped logic. When any condition carries group_index, conditions sharing an index form a group and grouped evaluation replaces evaluation_mode." + }, + "group_logic": { + "type": "string", + "enum": [ + "AND", + "OR" + ], + "default": "AND", + "description": "How conditions combine WITHIN a group." + }, + "groups_logic": { + "type": "string", + "enum": [ + "AND", + "OR" + ], + "default": "OR", + "description": "How group outcomes combine ACROSS groups. Read from the first condition." + } + }, + "description": "A condition reads only the transaction being evaluated - it never filters the aggregation window." + }, + "TransactionRuleAggregation": { + "type": "object", + "required": [ + "value" + ], + "properties": { + "metric": { + "type": "string", + "enum": [ + "count", + "sum", + "max", + "min", + "avg", + "distinct_count" + ], + "default": "count", + "description": "How the matching historical transactions are aggregated. unique_count is accepted as an alias of distinct_count." + }, + "field": { + "type": "string", + "default": "amount", + "description": "Field aggregated by sum/max/min/avg/distinct_count. Ignored for count." + }, + "operator": { + "type": "string", + "enum": [ + "eq", + "ne", + "gt", + "gte", + "lt", + "lte" + ], + "default": "eq", + "description": "Comparison applied to the computed metric." + }, + "value": { + "type": "number", + "description": "Threshold the computed metric is compared against." + }, + "window": { + "type": "string", + "pattern": "^\\d+(m|h|d)$", + "default": "1d", + "description": "Look-back window ending at the transaction's txn_date: m (MINUTES, not months), h (hours) or d (days), e.g. 30m, 24h, 7d, 30d." + }, + "filters": { + "type": "object", + "additionalProperties": true, + "description": "Narrows which historical transactions are aggregated. Keys are field paths from the condition field catalog; a value of \"__current__\" resolves to the same field's value on the transaction being evaluated (e.g. {\"subject_vendor_data\": \"__current__\"} scopes the window to the same subject), and a list value means membership. Records without the grouped value are never grouped together: when the transaction being evaluated has no value for a \"__current__\" field (no counterparty, no device fingerprint), the aggregation does not match, whatever its operator. Filters compare by equality or membership ONLY - there is no lt/gte filter, so a range such as \"historical transactions under 10,000\" cannot be expressed here. Put the range on the metric instead (metric max with operator lt keeps every transaction in the window below the threshold)." + } + }, + "description": "Velocity check over the application's historical transactions inside the window. Every aggregation entry must match for the rule to match, regardless of evaluation_mode. An aggregation constrains only the window; the transaction being evaluated is constrained by scope and conditions, so repeat direction, currency or transaction_type on both halves when the rule promises them." + }, + "TransactionRuleScope": { + "type": "object", + "properties": { + "transaction_types": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Restrict to transaction types, e.g. finance, kyc, travel_rule, user_event. Empty or missing = no restriction." + }, + "directions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ] + }, + "description": "Restrict to transaction directions (case-insensitive on write)." + }, + "action_types": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Restrict to your action_type values, e.g. withdrawal, deposit." + } + }, + "description": "Which transactions the rule applies to. Only these three keys are accepted." + }, + "TransactionRuleAction": { + "oneOf": [ + { + "$ref": "#/components/schemas/TransactionRuleActionAddScore" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionChangeStatus" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionAddTags" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionAddNote" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionAddToList" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionOpenCase" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "add_score": "#/components/schemas/TransactionRuleActionAddScore", + "change_status": "#/components/schemas/TransactionRuleActionChangeStatus", + "add_tags": "#/components/schemas/TransactionRuleActionAddTags", + "add_note": "#/components/schemas/TransactionRuleActionAddNote", + "add_to_list": "#/components/schemas/TransactionRuleActionAddToList", + "open_case": "#/components/schemas/TransactionRuleActionOpenCase" + } + }, + "description": "One rule action. A rule can carry several; all are applied together when it matches. Rules in TEST mode record what they would have done without applying any action." + }, + "TransactionRuleCreateRequest": { + "type": "object", + "required": [ + "title", + "category", + "mode" + ], + "properties": { + "title": { + "type": "string", + "description": "Rule name. Unique per application among non-deleted rules; duplicates return 400." + }, + "description": { + "type": "string", + "nullable": true + }, + "category": { + "type": "string", + "description": "Risk theme used for grouping and reporting, e.g. finance, aml_ctf, anomaly_detection, fatf, device_intelligence, crypto_monitoring, travel_rule, responsible_gaming, e_commerce." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ], + "description": "ACTIVE evaluates live. TEST evaluates and records rule runs but never touches score or status - the recommended starting mode. DISABLED is skipped." + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ], + "default": "ALL", + "description": "Whether every condition must match (ALL) or at least one (ANY). Ignored when conditions use group_index." + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + } + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + } + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAction" + } + }, + "metadata": { + "type": "object", + "description": "Free-form metadata stored on the rule." + } + }, + "description": "Creates a CUSTOM rule. Rules in ACTIVE mode take effect immediately for new transactions - create in TEST mode and backtest first." + }, + "TransactionRuleUpdateRequest": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Rule name. Unique per application among non-deleted rules; duplicates return 400." + }, + "description": { + "type": "string", + "nullable": true + }, + "category": { + "type": "string", + "description": "Risk theme used for grouping and reporting, e.g. finance, aml_ctf, anomaly_detection, fatf, device_intelligence, crypto_monitoring, travel_rule, responsible_gaming, e_commerce." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ], + "description": "ACTIVE evaluates live. TEST evaluates and records rule runs but never touches score or status - the recommended starting mode. DISABLED is skipped." + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ], + "default": "ALL", + "description": "Whether every condition must match (ALL) or at least one (ANY). Ignored when conditions use group_index." + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + } + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + } + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAction" + } + }, + "metadata": { + "type": "object", + "description": "Free-form metadata stored on the rule." + } + }, + "description": "Partial update. Custom rules accept every field; library (PRESET) rules accept only mode - anything else returns 400." + }, + "TransactionRuleDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "title": { + "type": "string", + "description": "Rule name. Unique per application among non-deleted rules; duplicates return 400." + }, + "description": { + "type": "string", + "nullable": true + }, + "category": { + "type": "string", + "description": "Risk theme used for grouping and reporting, e.g. finance, aml_ctf, anomaly_detection, fatf, device_intelligence, crypto_monitoring, travel_rule, responsible_gaming, e_commerce." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ], + "description": "ACTIVE evaluates live. TEST evaluates and records rule runs but never touches score or status - the recommended starting mode. DISABLED is skipped." + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ], + "default": "ALL", + "description": "Whether every condition must match (ALL) or at least one (ANY). Ignored when conditions use group_index." + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + } + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + } + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAction" + } + }, + "metadata": { + "type": "object", + "description": "Free-form metadata stored on the rule." + }, + "severity": { + "type": "string", + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ], + "nullable": true, + "description": "Informational severity carried by library rules; not settable on custom rules." + }, + "source": { + "type": "string", + "enum": [ + "PRESET", + "CUSTOM" + ], + "description": "PRESET rules come from the library (install/uninstall, mode-only edits); CUSTOM rules are fully yours." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Stable library identifier; null for custom rules." + }, + "latest_triggered_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "run_count": { + "type": "integer", + "description": "How many transactions matched this rule." + }, + "approved_count": { + "type": "integer" + }, + "reviewed_count": { + "type": "integer" + }, + "declined_count": { + "type": "integer" + }, + "awaiting_user_count": { + "type": "integer" + }, + "approved_pct": { + "type": "number" + }, + "reviewed_pct": { + "type": "number" + }, + "declined_pct": { + "type": "number" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TransactionRuleBacktestRequest": { + "type": "object", + "properties": { + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + }, + "default": [] + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + }, + "default": [] + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ], + "default": "ALL" + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "period_days": { + "type": "integer", + "minimum": 1, + "maximum": 365, + "default": 90 + } + }, + "description": "Hypothetical rule configuration to evaluate against recent transactions. Read-only and free: nothing is created or modified. Send the same conditions, aggregation, evaluation_mode AND scope as the rule you intend to activate. Omitted conditions, aggregation and scope add no constraints of their own; dropping scope can include directions or transaction types the saved rule skips. Omitted evaluation_mode defaults to ALL (which can reduce matches for an ANY rule), and omitted period_days defaults to 90 days, not unlimited history." + }, + "TransactionRuleBacktestResult": { + "type": "object", + "properties": { + "evaluated": { + "type": "integer", + "description": "Transactions evaluated (most recent first, capped)." + }, + "matched": { + "type": "integer", + "description": "Transactions the configuration would have matched." + }, + "affected_entities": { + "type": "integer", + "description": "Distinct subjects (by vendor_data) among the matches." + }, + "period_days": { + "type": "integer" + } + } + }, + "TransactionRuleLibraryEntry": { + "type": "object", + "properties": { + "library_key": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "category": { + "type": "string" + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ] + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + } + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + } + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAction" + } + }, + "metadata": { + "type": "object" + }, + "bundle": { + "type": "string", + "nullable": true, + "description": "Functional family, e.g. finance, aml_ctf, crypto_monitoring, travel_rule, responsible_gaming." + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "industries": { + "type": "array", + "items": { + "type": "string" + } + }, + "source": { + "type": "string" + }, + "is_installed": { + "type": "boolean", + "description": "Whether this preset is currently installed on the application." + } + } + }, + "TransactionRuleInstallRequest": { + "type": "object", + "properties": { + "library_keys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Specific presets to install or uninstall." + }, + "bundle": { + "type": "string", + "description": "Install or uninstall every preset in this bundle instead." + } + }, + "description": "Provide library_keys OR bundle." + }, + "TransactionRuleInstallResult": { + "type": "object", + "properties": { + "installed_library_keys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "POST (install) only: presets installed by this call." + }, + "installed_count": { + "type": "integer", + "description": "POST (install) only." + }, + "uninstalled_count": { + "type": "integer", + "description": "DELETE (uninstall) only: presets removed by this call." + } + } + }, + "WalletScreeningResult": { + "type": "object", + "properties": { + "provider": { + "type": "string", + "description": "Provider that performed the screening (e.g. `merklescience`, `crystal`)." + }, + "screening_type": { + "type": "string", + "enum": [ + "WALLET_SCREENING" + ], + "description": "Always `WALLET_SCREENING` for this endpoint." + }, + "risk_score": { + "type": "integer", + "description": "Normalised 0-100 risk score. Higher means greater exposure to risky entities. Merkle Science grades an address 0-5, which maps to 0, 15, 35, 55, 75 or 95." + }, + "severity": { + "type": "string", + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ], + "description": "Risk bucket derived from `risk_score`: `0-9` UNKNOWN, `10-39` LOW, `40-69` MEDIUM, `70-89` HIGH, `90-100` CRITICAL. `UNKNOWN` is the lowest band, not a separate no-data state: `risk_score` 0 (the common clean-address case) means no adverse assessment, while a non-zero score in the 1-9 range is a real but sub-LOW signal - read the `risk_score`, not just the band. Never treat `UNKNOWN` as an affirmative low-risk or clear rating; do not display it as a pass." + }, + "status": { + "type": "string", + "enum": [ + "SCREENED", + "PENDING", + "ERROR" + ], + "description": "Screening outcome status." + }, + "summary": { + "type": "string", + "description": "Human-readable summary of the screening result." + }, + "wallet_address": { + "type": "string", + "description": "The screened address, echoed back." + }, + "transaction_hash": { + "type": "string", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "blockchain": { + "type": "string", + "description": "The blockchain that was screened." + }, + "block_timestamp": { + "type": "string", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "total_value_usd": { + "type": "number", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "fee_usd": { + "type": "number", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "sender_rows": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Transaction screening field, shared with the result shape. Always empty on a wallet screening result." + }, + "receiver_rows": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Transaction screening field, shared with the result shape. Always empty on a wallet screening result." + }, + "transaction_flows": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Transaction screening field, shared with the result shape. Always empty on a wallet screening result." + }, + "transaction_flows_status": { + "type": "string", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "transaction_flows_error": { + "type": "string", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "source_of_funds": { + "type": "array", + "description": "Where the address received funds from, attributed by entity. Each entry is an exposure breakdown.", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "description": "Normalised risk category, e.g. `exchange_licensed`, `dex`, `defi`, `payment_processor`, `mixer`, `darknet_market`, `sanctioned`, `stolen_funds`, `high_risk_exchange`." + }, + "percentage": { + "type": "number", + "description": "Share of the direction's total exposure, 0-100." + }, + "amount_usd": { + "type": "number", + "nullable": true, + "description": "USD value attributed to this entity." + }, + "entity_name": { + "type": "string", + "nullable": true, + "description": "Name of the attributed entity, for example an exchange, a mixer or a sanctioned group." + }, + "entity_type": { + "type": "string", + "nullable": true, + "description": "Provider entity type (e.g. `Exchange`, `DeFi`, `Sanctions`, `Theft`)." + }, + "entity_subtype": { + "type": "string", + "nullable": true, + "description": "More granular subtype (e.g. `Mandatory KYC and AML`, `OFAC`, `Yield Aggregator`)." + }, + "hops": { + "type": "integer", + "nullable": true, + "description": "Number of hops to the entity (1 for a direct counterparty), when the provider reports one. Always `null` for Merkle Science, which reports only whether exposure is direct or indirect: use `exposure_type`." + }, + "description": { + "type": "string", + "nullable": true, + "description": "`entity_type / entity_subtype`, as the provider labels the entity." + }, + "is_direct": { + "type": "boolean", + "description": "True when `exposure_type` is `direct`." + }, + "exposure_direction": { + "type": "string", + "enum": [ + "incoming", + "outgoing", + "connected" + ], + "description": "Whether the funds came from (`incoming`) or went to (`outgoing`) this entity." + }, + "exposure_type": { + "type": "string", + "enum": [ + "direct", + "indirect" + ], + "description": "`direct` (1 hop) or `indirect` (multi-hop) exposure." + }, + "provider_entity_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for the entity." + }, + "provider_entity_type_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for `entity_type`." + }, + "provider_entity_subtype_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for `entity_subtype`." + }, + "country": { + "type": "string", + "nullable": true, + "description": "Primary ISO 3166-1 alpha-2 country of the entity. Empty or `null` when the provider does not know it." + }, + "raw_amount": { + "type": "number", + "nullable": true, + "description": "Amount in the asset's own unit, when the provider reports it." + }, + "raw_currency": { + "type": "string", + "nullable": true, + "description": "Unit of `raw_amount`, when the provider reports it." + } + } + } + }, + "destination_of_funds": { + "type": "array", + "description": "Where the address sent funds to, attributed by entity. Same item shape as `source_of_funds` with `exposure_direction` = `outgoing`.", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "description": "Normalised risk category, e.g. `exchange_licensed`, `dex`, `defi`, `payment_processor`, `mixer`, `darknet_market`, `sanctioned`, `stolen_funds`, `high_risk_exchange`." + }, + "percentage": { + "type": "number", + "description": "Share of the direction's total exposure, 0-100." + }, + "amount_usd": { + "type": "number", + "nullable": true, + "description": "USD value attributed to this entity." + }, + "entity_name": { + "type": "string", + "nullable": true, + "description": "Name of the attributed entity, for example an exchange, a mixer or a sanctioned group." + }, + "entity_type": { + "type": "string", + "nullable": true, + "description": "Provider entity type (e.g. `Exchange`, `DeFi`, `Sanctions`, `Theft`)." + }, + "entity_subtype": { + "type": "string", + "nullable": true, + "description": "More granular subtype (e.g. `Mandatory KYC and AML`, `OFAC`, `Yield Aggregator`)." + }, + "hops": { + "type": "integer", + "nullable": true, + "description": "Number of hops to the entity (1 for a direct counterparty), when the provider reports one. Always `null` for Merkle Science, which reports only whether exposure is direct or indirect: use `exposure_type`." + }, + "description": { + "type": "string", + "nullable": true, + "description": "`entity_type / entity_subtype`, as the provider labels the entity." + }, + "is_direct": { + "type": "boolean", + "description": "True when `exposure_type` is `direct`." + }, + "exposure_direction": { + "type": "string", + "enum": [ + "incoming", + "outgoing", + "connected" + ], + "description": "Whether the funds came from (`incoming`) or went to (`outgoing`) this entity." + }, + "exposure_type": { + "type": "string", + "enum": [ + "direct", + "indirect" + ], + "description": "`direct` (1 hop) or `indirect` (multi-hop) exposure." + }, + "provider_entity_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for the entity." + }, + "provider_entity_type_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for `entity_type`." + }, + "provider_entity_subtype_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for `entity_subtype`." + }, + "country": { + "type": "string", + "nullable": true, + "description": "Primary ISO 3166-1 alpha-2 country of the entity. Empty or `null` when the provider does not know it." + }, + "raw_amount": { + "type": "number", + "nullable": true, + "description": "Amount in the asset's own unit, when the provider reports it." + }, + "raw_currency": { + "type": "string", + "nullable": true, + "description": "Unit of `raw_amount`, when the provider reports it." + } + } + } + }, + "counterparty_connections": { + "type": "array", + "description": "Entities the provider attributes the screened address to. For Merkle Science these come from the address's own owner and user tags, so the same entity can appear twice, and they carry no fund flows: `received_usd`, `sent_usd`, `received_hops` and `sent_hops` are `null` and `percentage` is 0. Do not route on `risk_level` or `is_direct` here: use `sanctions_hit`, `dominant_risk_category` and `risk_factors[].is_high_risk` for the verdict, and `exposure_type` in `source_of_funds`/`destination_of_funds` for directness.", + "items": { + "type": "object", + "properties": { + "entity_name": { + "type": "string", + "nullable": true, + "description": "Attributed entity name." + }, + "entity_type": { + "type": "string", + "nullable": true, + "description": "Provider entity type." + }, + "entity_subtype": { + "type": "string", + "nullable": true, + "description": "Provider entity subtype." + }, + "risk_level": { + "type": "string", + "description": "Lowercase and provider-dependent. For Merkle Science it is the address's own 0-5 risk grade (`no_risk`, `low`, `medium_low`, `medium`, `high`, `critical`), raised to `high` when the entity's category is high risk; it is not a separate grade of the entity." + }, + "categories": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Normalised risk categories of the entity." + }, + "received_usd": { + "type": "number", + "nullable": true, + "description": "USD received from the entity, when the provider reports it." + }, + "sent_usd": { + "type": "number", + "nullable": true, + "description": "USD sent to the entity, when the provider reports it." + }, + "received_hops": { + "type": "integer", + "nullable": true, + "description": "Hops to the entity on the receiving side, when the provider reports them." + }, + "sent_hops": { + "type": "integer", + "nullable": true, + "description": "Hops to the entity on the sending side, when the provider reports them." + }, + "percentage": { + "type": "number", + "description": "Share of the address's exposure, when the provider reports it; otherwise 0." + }, + "is_direct": { + "type": "boolean", + "description": "Always `true` for Merkle Science, whose connections are attributions rather than counterparties." + }, + "country": { + "type": "string", + "nullable": true, + "description": "Country of the entity, when known." + }, + "provider_entity_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for the entity." + } + } + } + }, + "network_nodes": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Exposure graph nodes for transaction screening. Empty on a wallet screening result." + }, + "network_edges": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Exposure graph edges for transaction screening. Empty on a wallet screening result." + }, + "sanctions_hit": { + "type": "boolean", + "description": "True if the address has direct or indirect sanctions exposure." + }, + "pep_counterparty": { + "type": "boolean", + "description": "Politically exposed person exposure. `null` when no PEP finding is available, which is every result today because no provider reports one; `false` only for an explicit negative screening result. Do not read `null` as a completed PEP check.", + "nullable": true + }, + "dominant_risk_category": { + "type": "string", + "nullable": true, + "description": "Highest-weighted high-risk category, or `null` when none is dominant (e.g. `sanctioned`, `mixer`, `stolen_funds`)." + }, + "risk_factors": { + "type": "array", + "description": "Ranked explanation of `risk_score`: what produced this verdict. Providers grade an address on their own model and return only a verdict (Merkle Science returns an integer 0-5), so the score alone is not actionable. Ordered high-risk first, then by the value that moved through each driver, capped at the five strongest; `source_of_funds` and `destination_of_funds` always carry the complete breakdown. A high `risk_score` with no `is_high_risk` entry means the provider reported no sanctions or attributed-entity match and graded the address purely on its own exposure model - `summary` states this explicitly.", + "items": { + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": [ + "attribution", + "exposure" + ], + "description": "`attribution` is what the provider says the address itself is (its own tag or cluster); `exposure` is where its funds actually moved." + }, + "category": { + "type": "string", + "description": "Normalised risk category, same vocabulary as the exposure breakdown." + }, + "label": { + "type": "string", + "description": "The provider's own wording for the driver (e.g. `Gambling / Crypto only`), which is what you would also see in the provider's console." + }, + "entity_name": { + "type": "string", + "nullable": true, + "description": "Attributed entity behind the driver, for example a gambling site or an exchange." + }, + "direction": { + "type": "string", + "enum": [ + "incoming", + "outgoing", + "connected", + "unknown", + "attribution" + ], + "description": "Direction of the fund flow, `attribution` when the driver is the address's own tag, or `unknown` when the provider did not report a direction." + }, + "exposure_type": { + "type": "string", + "enum": [ + "direct", + "indirect", + "unknown", + "attribution" + ], + "description": "`direct` (1 hop) or `indirect` (multi-hop), `attribution` for the address's own tag, or `unknown` when the provider did not state how many hops away the counterparty is. Treat `unknown` as unclassified, not as `direct`." + }, + "amount_usd": { + "type": "number", + "format": "float", + "nullable": true, + "description": "USD value attributed to this driver." + }, + "percentage": { + "type": "number", + "format": "float", + "description": "Share of the address's TOTAL screened volume, both directions combined - unlike `percentage` inside `source_of_funds`/`destination_of_funds`, which is normalised per direction. Comparable and summable across the whole list." + }, + "is_high_risk": { + "type": "boolean", + "description": "Whether the category is one Didit treats as high risk (`sanctioned`, `mixer`, `stolen_funds`, `ransomware`, `scam`, `darknet_market`, `terrorist_financing`, `child_exploitation`, `hacked_exchange`, `high_risk_exchange`, `seized_funds`)." + }, + "description": { + "type": "string", + "description": "One-line rendering of the driver, e.g. `9.01% indirect outgoing exposure to Gambling / Crypto only (Example Gambling)`." + } + } + } + }, + "raw_response": { + "type": "object", + "additionalProperties": true, + "description": "The provider's own payload. Its shape is provider-specific and can change without notice: read the normalised fields instead of parsing it." + }, + "report_signature": { + "type": "string", + "description": "Opaque HMAC-SHA256 signature over this exact result, scoped to your application. Not meaningful on its own — repost the full response body, unmodified and including this field, to `POST /v3/wallet-screening/pdf/` to render it as a PDF. The PDF endpoint rejects the request with `400` if `report_signature` is missing or if any signed field (e.g. `risk_score`, `severity`, `sanctions_hit`) was edited before repost." + } + }, + "required": [ + "provider", + "screening_type", + "risk_score", + "severity", + "status", + "summary", + "wallet_address", + "blockchain", + "sanctions_hit", + "dominant_risk_category", + "source_of_funds", + "destination_of_funds", + "counterparty_connections", + "report_signature" + ] + }, + "NetworkRiskDisplay": { + "type": "object", + "properties": { + "score": { + "type": "integer", + "description": "Risk score, 0-100." + }, + "band": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ], + "description": "Band the score renders as. high >= 70, medium >= 40, otherwise low." + }, + "bar": { + "type": "number", + "format": "float", + "description": "Normalized 0-1 value for rendering a risk bar." + } + } + }, + "NetworkNodeTag": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string", + "nullable": true, + "description": "Null for guard tags, whose color token the renderer owns." + }, + "source": { + "type": "string", + "enum": [ + "subject_tag", + "session_tag", + "transaction_tag", + "signal_guard" + ], + "description": "Where the tag came from: a tag on the user/business, a tag on a session in this network, a tag on a transaction a party belongs to, or a signal guard condition (e.g. hub, datacenter IP)." + }, + "category": { + "type": "string", + "enum": [ + "customer", + "quality" + ], + "nullable": true, + "description": "customer for customer-defined tags, quality for signal-quality guard tags. Null when not applicable." + } + } + }, + "NetworkListItem": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "network_id": { + "type": "string", + "description": "Human-readable per-organization sequential id, e.g. N-0001." + }, + "name": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "enum": [ + "active", + "in_review", + "resolved", + "dismissed" + ], + "description": "Network lifecycle status. Distinct from the per-applicant outcome_status inside a network (approved/in_review/declined/not_completed)." + }, + "risk": { + "$ref": "#/components/schemas/NetworkRiskDisplay" + }, + "pattern_types": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "exact_same_device", + "similar_device", + "same_ip_address", + "same_address", + "similar_selfie_backgrounds", + "similar_poa_documents", + "same_document_number", + "same_phone_number", + "same_email" + ] + }, + "description": "The clustering patterns detected in this network." + }, + "signal_types": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ip_address", + "device", + "face", + "document_number", + "phone", + "email", + "address" + ] + }, + "description": "The distinct signal types present in this network." + }, + "member_counts": { + "type": "object", + "description": "Counts of members by category (e.g. total, per outcome_status)." + }, + "member_status": { + "type": "object", + "description": "Breakdown of members by outcome_status." + }, + "signal_coverage": { + "type": "object", + "description": "Per signal_type coverage of this network's members." + }, + "first_activity_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_activity_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "NetworkDetail": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkListItem" + }, + { + "type": "object", + "properties": { + "stats": { + "type": "object", + "description": "Aggregate stats for the network (e.g. member totals)." + }, + "fraud_tags": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Fraud-pattern tags surfaced on the network." + }, + "risk_signals": { + "type": "object", + "description": "Risk-signal summaries: residential_proxy_reuse, email_intelligence, phone_intelligence." + }, + "reviewed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reviewed_by": { + "type": "string", + "nullable": true + }, + "review_reason": { + "type": "string", + "nullable": true + } + } + } + ], + "properties": { + "notes_count": { + "type": "integer", + "minimum": 0, + "readOnly": true, + "description": "Number of private analyst notes in this investigation." + } + } + }, + "NetworkGraphNode": { + "type": "object", + "description": "A subject node or a signal node, distinguished by kind. Subject nodes (kind: vendor_user | vendor_business | transaction_party) carry id, kind, subject, node_status (approved | in_review | declined | not_completed), and tags. Signal nodes (kind: signal) carry id, kind, uuid, signal_type, subject_count, coverage, is_hub, is_datacenter_ip, is_high_collision_device, node_status (signal_active | signal_guarded), tags, risk_evidence, first_seen_at, and last_seen_at.", + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "vendor_user", + "vendor_business", + "transaction_party", + "signal" + ] + } + } + }, + "NetworkGraphEdge": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "source": { + "type": "string", + "description": "Subject node id." + }, + "target": { + "type": "string", + "description": "Signal node id." + }, + "subject_kind": { + "type": "string", + "enum": [ + "vendor_user", + "vendor_business", + "transaction_party" + ] + }, + "signal_type": { + "type": "string", + "enum": [ + "ip_address", + "device", + "face", + "document_number", + "phone", + "email", + "address" + ] + }, + "confidence": { + "type": "number", + "format": "float", + "description": "1.0 for an exact match." + }, + "observation_count": { + "type": "integer" + }, + "first_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "NetworkGraph": { + "type": "object", + "properties": { + "network": { + "$ref": "#/components/schemas/NetworkDetail" + }, + "depth": { + "type": "integer", + "nullable": true, + "description": "Effective depth applied, or null when unbounded (depth=all)." + }, + "truncated": { + "type": "boolean", + "description": "True if the neighborhood was cut off by depth." + }, + "nodes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkGraphNode" + } + }, + "edges": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkGraphEdge" + } + }, + "metrics": { + "type": "object", + "properties": { + "node_count": { + "type": "integer" + }, + "edge_count": { + "type": "integer" + }, + "hub_signal_count": { + "type": "integer" + } + } + } + } + }, + "NetworkMember": { + "type": "object", + "properties": { + "subject": { + "type": "object", + "description": "The vendor_user, vendor_business, or transaction_party this member represents." + }, + "outcome_status": { + "type": "string", + "description": "The subject's verification/decision outcome." + }, + "node_status": { + "type": "string", + "enum": [ + "approved", + "in_review", + "declined", + "not_completed", + "signal_active", + "signal_guarded" + ], + "description": "For a member row this is always one of the subject-outcome values: approved, in_review, declined, or not_completed." + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkNodeTag" + } + }, + "risk": { + "$ref": "#/components/schemas/NetworkRiskDisplay" + }, + "signal_coverage": { + "type": "object" + }, + "shared_infrastructure": { + "type": "object" + }, + "joined_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "provenance": { + "type": "object", + "properties": { + "session_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "business_session_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "transaction_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "NetworkSignal": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "signal_type": { + "type": "string", + "enum": [ + "ip_address", + "device", + "face", + "document_number", + "phone", + "email", + "address" + ] + }, + "subject_count": { + "type": "integer" + }, + "coverage": { + "type": "number", + "format": "float" + }, + "is_hub": { + "type": "boolean", + "description": "True when this signal links an unusually large number of subjects." + }, + "is_datacenter_ip": { + "type": "boolean" + }, + "is_high_collision_device": { + "type": "boolean" + }, + "node_status": { + "type": "string", + "enum": [ + "signal_active", + "signal_guarded" + ] + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkNodeTag" + } + }, + "risk_evidence": { + "type": "object" + }, + "first_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "NetworkTimelineEvent": { + "type": "object", + "properties": { + "event_type": { + "type": "string", + "description": "e.g. network_detected, signal_observed, network_in_review, network_resolved, network_dismissed." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "label": { + "type": "string" + }, + "metadata": { + "type": "object" + } + } + }, + "NetworkMap": { + "type": "object", + "properties": { + "network": { + "$ref": "#/components/schemas/NetworkDetail" + }, + "convergence_count": { + "type": "integer", + "description": "Number of distinct address/location points members converge on." + }, + "countries": { + "type": "array", + "items": { + "type": "string" + } + }, + "points": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "country": { + "type": "string", + "nullable": true + }, + "country_code": { + "type": "string", + "nullable": true + }, + "city": { + "type": "string", + "nullable": true + }, + "latitude": { + "type": "number", + "format": "float", + "nullable": true + }, + "longitude": { + "type": "number", + "format": "float", + "nullable": true + }, + "observed_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + } + } + } + }, + "EntityNetworkMembership": { + "type": "object", + "properties": { + "network": { + "$ref": "#/components/schemas/NetworkListItem" + }, + "subject": { + "type": "object" + }, + "signal_types": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ip_address", + "device", + "face", + "document_number", + "phone", + "email", + "address" + ] + } + }, + "signal_count": { + "type": "integer" + }, + "joined_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "RiskSignals": { + "type": "object", + "description": "Optional device and network context observed by your application. All fields are optional. Passing them feeds cross-session match detection in Networks.", + "properties": { + "ip": { + "type": "string", + "format": "ip", + "description": "IPv4 or IPv6 address observed for the end user.", + "example": "192.0.2.1" + }, + "device_id": { + "type": "string", + "maxLength": 255, + "description": "Stable device identifier collected by your application (Android `ANDROID_ID`, iOS `identifierForVendor`)." + }, + "device_platform": { + "type": "string", + "enum": [ + "android", + "ios", + "ipados", + "tvos", + "web" + ], + "description": "Platform of the end user's device." + }, + "device_model": { + "type": "string", + "maxLength": 255, + "description": "Device model reported by your application." + }, + "os_version": { + "type": "string", + "maxLength": 64, + "description": "Operating-system version." + }, + "app_version": { + "type": "string", + "maxLength": 64, + "description": "Version of your application." + }, + "user_agent": { + "type": "string", + "maxLength": 512, + "description": "Browser or app user agent." + } + } + }, + "PhoneRiskRequest": { + "type": "object", + "required": [ + "phone_number" + ], + "properties": { + "phone_number": { + "type": "string", + "maxLength": 20, + "description": "Phone number to score. It is validated and normalized to E.164.", + "example": "+14155552671" + }, + "country_code": { + "type": "string", + "maxLength": 3, + "description": "Optional ISO 3166-1 alpha-2 or alpha-3 country hint for intelligence coverage.", + "example": "US" + }, + "signals": { + "$ref": "#/components/schemas/RiskSignals" + }, + "vendor_data": { + "type": "string", + "description": "Your stable reference for this request. Stored on the API session and echoed in the response." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Your non-sensitive JSON metadata. Stored on the API session and echoed in the response." + }, + "enable_social": { + "type": "boolean", + "default": false, + "description": "When true, also look up the social footprint of the number - the messaging, social, e-commerce and professional platforms it is registered on. Billed as one `phone_social` unit per request in addition to this endpoint's own price.", + "example": true + } + } + }, + "EmailRiskRequest": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "Email address to score.", + "example": "alice@example.com" + }, + "country_code": { + "type": "string", + "maxLength": 3, + "description": "Optional ISO 3166-1 alpha-2 or alpha-3 country hint for intelligence coverage.", + "example": "US" + }, + "signals": { + "$ref": "#/components/schemas/RiskSignals" + }, + "vendor_data": { + "type": "string", + "description": "Your stable reference for this request. Stored on the API session and echoed in the response." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Your non-sensitive JSON metadata. Stored on the API session and echoed in the response." + }, + "enable_social": { + "type": "boolean", + "default": false, + "description": "When true, also look up the social footprint of the address - the email, social, professional, e-commerce and entertainment platforms it is registered on. Billed as one `email_social` unit per request in addition to this endpoint's own price.", + "example": true + } + } + }, + "IPRiskRequest": { + "type": "object", + "required": [ + "ip_address" + ], + "properties": { + "ip_address": { + "type": "string", + "format": "ip", + "description": "IPv4 or IPv6 address to score.", + "example": "8.8.8.8" + }, + "claimed_country": { + "type": "string", + "maxLength": 3, + "description": "Optional ISO 3166-1 alpha-2 or alpha-3 country claimed by the user or session. Used to calculate `country_mismatch`.", + "example": "ESP" + }, + "user_agent": { + "type": "string", + "maxLength": 512, + "description": "User agent observed by your backend. Stored as request context; it does not provide device fingerprinting." + }, + "vendor_data": { + "type": "string", + "description": "Your stable reference for this request. Stored on the API session and echoed in the response." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Your non-sensitive JSON metadata. Stored on the API session and echoed in the response." + } + } + }, + "RiskWarning": { + "type": "object", + "required": [ + "feature", + "risk", + "log_type", + "short_description", + "long_description" + ], + "properties": { + "feature": { + "type": "string", + "description": "Feature that produced the warning." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk code. New values can be added over time." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Risk-specific context, or `null`." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity used by the decision rules." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation." + } + } + }, + "RiskLifecycleEvent": { + "type": "object", + "required": [ + "type", + "timestamp", + "details", + "fee" + ], + "properties": { + "type": { + "type": "string", + "description": "Machine-readable event type. New values can be added over time." + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the event happened." + }, + "details": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Event-specific payload, or `null`." + }, + "fee": { + "type": "number", + "description": "USD amount billed for this event." + } + } + }, + "PhoneMatchItem": { + "type": "object", + "description": "Another session of your application that used the same phone number with a different `vendor_data`.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the other verification that used this phone number." + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of that session." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of that session." + }, + "verification_date": { + "type": "string", + "description": "When that session was created (`YYYY-MM-DDTHH:MM:SSZ`)." + }, + "phone_number": { + "type": "string", + "description": "The matched phone number." + }, + "status": { + "type": "string", + "description": "Status of that session (e.g., `Approved`, `Declined`, `In Progress`)." + }, + "is_blocklisted": { + "type": "boolean", + "description": "Whether the phone number is on your application's blocklist." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "For API-type sessions, the standalone API that created it; `null` for regular verification sessions." + }, + "source": { + "type": "string", + "description": "Where the match was found. Usually `session`; a blocklist hit via an organization list entry uses `list_entry`." + } + } + }, + "EmailMatchItem": { + "type": "object", + "description": "Another session of your application that used the same email address with a different `vendor_data`.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the other verification that used this email address." + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of that session." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of that session." + }, + "verification_date": { + "type": "string", + "description": "When that session was created (`YYYY-MM-DDTHH:MM:SSZ`)." + }, + "email": { + "type": "string", + "description": "The matched email address." + }, + "status": { + "type": "string", + "description": "Status of that session (e.g., `Approved`, `Declined`, `In Progress`)." + }, + "is_blocklisted": { + "type": "boolean", + "description": "Whether the email address is on your application's blocklist." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "For API-type sessions, the standalone API that created it; `null` for regular verification sessions." + }, + "source": { + "type": "string", + "description": "Where the match was found. Usually `session`; a blocklist hit via an organization list entry uses `list_entry`." + } + } + }, + "PhoneIntelligence": { + "type": "object", + "description": "Normalized phone-intelligence block. Present only when the intelligence add-on ran for this lookup; when present it is also exposed under the `enrichment` alias with identical values.", + "properties": { + "score": { + "type": "number", + "nullable": true, + "description": "Normalized 0-100 risk score. Higher is riskier." + }, + "phone_type_risk": { + "type": "string", + "nullable": true, + "description": "`Low`, `Medium` or `High`, derived from the score." + }, + "phone_trust_index": { + "type": "number", + "nullable": true, + "description": "Inverse of the score, for dashboards that prefer higher is better." + }, + "network_stability_index": { + "type": "number", + "nullable": true, + "description": "Stability of the line's network." + }, + "is_disposable": { + "type": "boolean", + "nullable": true, + "description": "`true` when the number looks disposable or virtual." + }, + "is_ported": { + "type": "boolean", + "nullable": true, + "description": "`true` when the number has moved carrier." + }, + "is_recently_ported": { + "type": "boolean", + "nullable": true, + "description": "`true` when the port happened recently." + }, + "carrier_type": { + "type": "string", + "nullable": true, + "description": "Line classification reported by the lookup (e.g., `mobile`)." + }, + "reason_codes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Stable Didit identifier of the signal, for example `IR-5AC67A32`. Opaque: group and filter on it, do not parse it." + }, + "value": { + "nullable": true, + "description": "For a risk flag (for example `voip`, `leaked`, `disposable`, `spam_trap`), its value: a boolean, a level or a date. Null for a scored reason code." + }, + "direction": { + "type": "string", + "enum": [ + "risk", + "trust" + ], + "description": "Whether the signal raised the risk or added trust." + }, + "message": { + "type": "string", + "description": "Human-readable explanation of the signal, in English." + }, + "impact_level": { + "type": "string", + "nullable": true, + "description": "How strongly the signal moved the score, for example `low`, `medium` or `high`." + } + } + }, + "description": "Signals that explain the score, each with a stable Didit code, a direction, a message and an impact level. New codes can appear over time, so do not reject unknown codes." + } + } + }, + "EmailIntelligence": { + "type": "object", + "description": "Normalized email-intelligence block. Present only when the intelligence add-on ran for this email; when present it is also exposed as the `enrichment` alias with identical values.", + "properties": { + "score": { + "type": "number", + "nullable": true, + "description": "Normalized 0-100 risk score. Higher is riskier." + }, + "deliverable": { + "type": "string", + "nullable": true, + "description": "Deliverability band (e.g., `high`)." + }, + "breach_risk_level": { + "type": "string", + "nullable": true, + "description": "`Low`, `Medium` or `High`." + }, + "breach_risk_index": { + "type": "number", + "nullable": true, + "description": "Numeric breach-risk index." + }, + "is_recent_breach": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address appears in a recent breach." + }, + "is_frequent_breached": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address appears in many breaches." + }, + "is_disposable": { + "type": "boolean", + "nullable": true, + "description": "`true` when the domain is a throwaway provider." + }, + "is_free_domain": { + "type": "boolean", + "nullable": true, + "description": "`true` when the domain is a free mail provider." + }, + "is_breached": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address appears in known breaches." + }, + "no_of_breaches": { + "type": "number", + "nullable": true, + "description": "Number of known breaches containing the address." + }, + "reason_codes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Stable Didit identifier of the signal, for example `IR-5AC67A32`. Opaque: group and filter on it, do not parse it." + }, + "value": { + "nullable": true, + "description": "For a risk flag (for example `voip`, `leaked`, `disposable`, `spam_trap`), its value: a boolean, a level or a date. Null for a scored reason code." + }, + "direction": { + "type": "string", + "enum": [ + "risk", + "trust" + ], + "description": "Whether the signal raised the risk or added trust." + }, + "message": { + "type": "string", + "description": "Human-readable explanation of the signal, in English." + }, + "impact_level": { + "type": "string", + "nullable": true, + "description": "How strongly the signal moved the score, for example `low`, `medium` or `high`." + } + } + }, + "description": "Signals that explain the score, each with a stable Didit code, a direction, a message and an impact level. New codes can appear over time, so do not reject unknown codes." + } + } + }, + "IPRiskResult": { + "type": "object", + "description": "IP risk result. Individual enrichment fields can be `null` when the lookup returned no usable data.", + "required": [ + "status", + "threat" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review" + ], + "description": "`In Review` when the resolved country conflicts with `claimed_country`, or when the address is associated with VPN, Tor, or data-center infrastructure. Otherwise `Approved`." + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "The scored address. `null` when no enrichment source returned usable data." + }, + "ip_country": { + "type": "string", + "nullable": true, + "description": "Resolved country name." + }, + "ip_country_code": { + "type": "string", + "nullable": true, + "description": "Resolved country code. Compare countries through `country_mismatch` instead of comparing raw country strings yourself." + }, + "ip_state": { + "type": "string", + "nullable": true, + "description": "Resolved state or region." + }, + "ip_city": { + "type": "string", + "nullable": true, + "description": "Resolved city." + }, + "latitude": { + "type": "number", + "nullable": true, + "description": "Approximate network geolocation, not a precise device location." + }, + "longitude": { + "type": "number", + "nullable": true, + "description": "Approximate network geolocation, not a precise device location." + }, + "isp": { + "type": "string", + "nullable": true, + "description": "Internet service provider serving the address." + }, + "organization": { + "type": "string", + "nullable": true, + "description": "Organization owning the address block." + }, + "is_vpn_or_tor": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address is associated with VPN or Tor infrastructure." + }, + "is_data_center": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address belongs to a hosting or data-center network." + }, + "time_zone": { + "type": "string", + "nullable": true, + "description": "Time zone of the resolved location." + }, + "time_zone_offset": { + "type": "string", + "nullable": true, + "description": "UTC offset of the resolved location." + }, + "asn_number": { + "type": "number", + "nullable": true, + "description": "Autonomous system number." + }, + "asn_organization": { + "type": "string", + "nullable": true, + "description": "Organization operating the autonomous system." + }, + "connection_type": { + "type": "string", + "nullable": true, + "description": "Connection classification (e.g., `cellular`, `hosting`)." + }, + "carrier": { + "type": "string", + "nullable": true, + "description": "Mobile carrier when the address is on a mobile network." + }, + "proxy_type": { + "type": "string", + "nullable": true, + "description": "Proxy classification when the address is behind a proxy." + }, + "claimed_country": { + "type": "string", + "nullable": true, + "description": "Echo of the `claimed_country` you sent." + }, + "country_mismatch": { + "type": "boolean", + "nullable": true, + "description": "`true` when the resolved country differs from `claimed_country`. `null` when no enrichment data or no claimed country is available." + }, + "threat": { + "type": "object", + "additionalProperties": true, + "description": "Free-form network-threat evidence from the enrichment source (e.g., `is_proxy`, `is_tor`). Empty object when no enrichment data is available. Treat as diagnostic detail, not a stable contract." + } + } + }, + "PhoneRiskResponse": { + "type": "object", + "required": [ + "request_id", + "status", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the API session created for this request. Store it with `vendor_data` if you need to correlate the result with later webhooks or console activity." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Final decision for this API session. Normally `Approved`; the risk evaluation can produce `In Review` or `Declined`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, `null` when you did not send one." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` you sent, `null` when you did not send one." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this response." + }, + "phone": { + "type": "object", + "description": "Normalized phone report, the same block used by Phone Verification responses.", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Decision for this phone number. Mirrors the top-level `status`." + }, + "phone_number_prefix": { + "type": "string", + "description": "Country calling code with leading `+` (e.g., `+1`).", + "example": "+1" + }, + "phone_number": { + "type": "string", + "description": "National number without the country calling code.", + "example": "4155552671" + }, + "full_number": { + "type": "string", + "description": "Complete number in E.164 format.", + "example": "+14155552671" + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of the number.", + "example": "US" + }, + "country_name": { + "type": "string", + "description": "Full country name derived from `country_code`.", + "example": "United States" + }, + "carrier": { + "type": "object", + "nullable": true, + "description": "Current network serving the number. `null` when the lookup could not resolve a carrier.", + "properties": { + "name": { + "type": "string", + "description": "Carrier name. Empty string when the lookup could not identify it." + }, + "type": { + "type": "string", + "description": "Line type reported by the lookup: `mobile`, `fixed_line`, `voip`, `isp`, `vpn`, `toll_free`, `premium_rate`, or similar." + } + } + }, + "is_disposable": { + "type": "boolean", + "description": "`true` when the number belongs to a temporary or burner number service." + }, + "is_virtual": { + "type": "boolean", + "description": "`true` when the line type is virtual (`voip`, `isp`, or `vpn`)." + }, + "verification_method": { + "type": "string", + "nullable": true, + "description": "OTP channel of the underlying verification. `null` for risk lookups, which never send a code." + }, + "verification_attempts": { + "type": "integer", + "description": "OTP sends for this number. `0` for risk lookups." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the number was last verified by an OTP. `null` for risk lookups." + }, + "warnings": { + "type": "array", + "description": "Risk warnings recorded for this request. Empty array when there are none.", + "items": { + "$ref": "#/components/schemas/RiskWarning" + } + }, + "lifecycle": { + "type": "array", + "description": "Chronological audit trail of the request. Empty array for risk lookups without send events.", + "items": { + "$ref": "#/components/schemas/RiskLifecycleEvent" + } + }, + "matches": { + "type": "array", + "description": "Other sessions of your application where the same phone number was used by a different user (different `vendor_data`). Up to 5 entries. Empty array when there are no matches.", + "items": { + "$ref": "#/components/schemas/PhoneMatchItem" + } + }, + "phone_intelligence": { + "$ref": "#/components/schemas/PhoneIntelligence" + }, + "enrichment": { + "$ref": "#/components/schemas/PhoneIntelligence" + }, + "phone_social": { + "$ref": "#/components/schemas/PhoneSocialFootprint" + } + } + } + } + }, + "EmailRiskResponse": { + "type": "object", + "required": [ + "request_id", + "status", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the API session created for this request. Store it with `vendor_data` if you need to correlate the result with later webhooks or console activity." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Final decision for this API session. Normally `Approved`; the risk evaluation can produce `In Review` or `Declined`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, `null` when you did not send one." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` you sent, `null` when you did not send one." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this response." + }, + "email": { + "type": "object", + "description": "Normalized email report, the same block used by Email Verification responses.", + "required": [ + "status", + "email", + "is_breached", + "breaches", + "is_disposable", + "is_undeliverable" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Decision for this email address. Mirrors the top-level `status`." + }, + "email": { + "type": "string", + "description": "The scored address.", + "example": "alice@example.com" + }, + "is_breached": { + "type": "boolean", + "description": "Whether the address appears in a known credential breach." + }, + "breaches": { + "type": "array", + "description": "Up to 5 most recent known breaches containing this address. Empty array when `is_breached` is `false`.", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Breach identifier." + }, + "domain": { + "type": "string", + "description": "Domain of the breached service." + }, + "breach_date": { + "type": "string", + "format": "date", + "description": "Date the breach occurred." + }, + "breach_emails_count": { + "type": "integer", + "description": "Total number of accounts exposed in the breach." + }, + "description": { + "type": "string", + "description": "Human-readable description of the breach (may contain HTML markup)." + }, + "logo_path": { + "type": "string", + "description": "URL of the breached service's logo." + }, + "data_classes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Categories of data exposed, in snake_case (e.g., `email_addresses`, `passwords`)." + }, + "is_verified": { + "type": "boolean", + "description": "Whether the breach has been verified as legitimate." + } + } + } + }, + "is_disposable": { + "type": "boolean", + "description": "Whether the domain belongs to a throwaway provider." + }, + "is_undeliverable": { + "type": "boolean", + "description": "Whether the address was found undeliverable." + }, + "verification_attempts": { + "type": "integer", + "description": "OTP sends for this address. `0` for risk lookups." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the address was last verified by an OTP. `null` for risk lookups." + }, + "warnings": { + "type": "array", + "description": "Risk warnings recorded for this request. Empty array when there are none.", + "items": { + "$ref": "#/components/schemas/RiskWarning" + } + }, + "lifecycle": { + "type": "array", + "description": "Chronological audit trail of the request. Empty array for risk lookups without sends.", + "items": { + "$ref": "#/components/schemas/RiskLifecycleEvent" + } + }, + "matches": { + "type": "array", + "description": "Other sessions of your application where the same email address was used by a different user (different `vendor_data`). Up to 5 entries. Empty array when there are no matches.", + "items": { + "$ref": "#/components/schemas/EmailMatchItem" + } + }, + "email_intelligence": { + "$ref": "#/components/schemas/EmailIntelligence" + }, + "enrichment": { + "$ref": "#/components/schemas/EmailIntelligence" + }, + "email_social": { + "$ref": "#/components/schemas/EmailSocialFootprint" + } + } + } + } + }, + "IPRiskResponse": { + "type": "object", + "required": [ + "request_id", + "status", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the API session created for this request. Store it with `vendor_data` if you need to correlate the result with later webhooks or console activity." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review" + ], + "description": "Decision for this API session: `In Review` on network or country-mismatch risk, otherwise `Approved`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, `null` when you did not send one." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` you sent, `null` when you did not send one." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this response." + }, + "ip": { + "type": "object", + "description": "IP risk result. Individual enrichment fields can be `null` when the lookup returned no usable data.", + "required": [ + "status", + "threat" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review" + ], + "description": "`In Review` when the resolved country conflicts with `claimed_country`, or when the address is associated with VPN, Tor, or data-center infrastructure. Otherwise `Approved`." + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "The scored address. `null` when no enrichment source returned usable data." + }, + "ip_country": { + "type": "string", + "nullable": true, + "description": "Resolved country name." + }, + "ip_country_code": { + "type": "string", + "nullable": true, + "description": "Resolved country code. Compare countries through `country_mismatch` instead of comparing raw country strings yourself." + }, + "ip_state": { + "type": "string", + "nullable": true, + "description": "Resolved state or region." + }, + "ip_city": { + "type": "string", + "nullable": true, + "description": "Resolved city." + }, + "latitude": { + "type": "number", + "nullable": true, + "description": "Approximate network geolocation, not a precise device location." + }, + "longitude": { + "type": "number", + "nullable": true, + "description": "Approximate network geolocation, not a precise device location." + }, + "isp": { + "type": "string", + "nullable": true, + "description": "Internet service provider serving the address." + }, + "organization": { + "type": "string", + "nullable": true, + "description": "Organization owning the address block." + }, + "is_vpn_or_tor": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address is associated with VPN or Tor infrastructure." + }, + "is_data_center": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address belongs to a hosting or data-center network." + }, + "time_zone": { + "type": "string", + "nullable": true, + "description": "Time zone of the resolved location." + }, + "time_zone_offset": { + "type": "string", + "nullable": true, + "description": "UTC offset of the resolved location." + }, + "asn_number": { + "type": "number", + "nullable": true, + "description": "Autonomous system number." + }, + "asn_organization": { + "type": "string", + "nullable": true, + "description": "Organization operating the autonomous system." + }, + "connection_type": { + "type": "string", + "nullable": true, + "description": "Connection classification (e.g., `cellular`, `hosting`)." + }, + "carrier": { + "type": "string", + "nullable": true, + "description": "Mobile carrier when the address is on a mobile network." + }, + "proxy_type": { + "type": "string", + "nullable": true, + "description": "Proxy classification when the address is behind a proxy." + }, + "claimed_country": { + "type": "string", + "nullable": true, + "description": "Echo of the `claimed_country` you sent." + }, + "country_mismatch": { + "type": "boolean", + "nullable": true, + "description": "`true` when the resolved country differs from `claimed_country`. `null` when no enrichment data or no claimed country is available." + }, + "threat": { + "type": "object", + "additionalProperties": true, + "description": "Free-form network-threat evidence from the enrichment source (e.g., `is_proxy`, `is_tor`). Empty object when no enrichment data is available. Treat as diagnostic detail, not a stable contract." + } + } + } + } + }, + "SocialFootprintPlatform": { + "type": "object", + "properties": { + "platform": { + "type": "string", + "description": "Platform identifier.", + "example": "whatsapp" + }, + "category": { + "type": "string", + "description": "Platform category, e.g. MESSAGING, SOCIAL_MEDIA, ECOMMERCE, PROFESSIONAL, EMAIL_PROVIDER.", + "example": "MESSAGING" + }, + "scope": { + "type": "string", + "enum": [ + "global", + "local" + ], + "description": "`global` for worldwide platforms, `local` for region-specific ones.", + "example": "global" + } + } + }, + "SocialFootprintCategorySummary": { + "type": "object", + "properties": { + "registered": { + "type": "integer", + "nullable": true, + "description": "Platforms in this category where registration is confirmed.", + "example": 2 + }, + "ratio": { + "type": "number", + "format": "float", + "nullable": true, + "description": "Share of the category's checked platforms with a registration, 0.0-1.0.", + "example": 0.6667 + } + } + }, + "SocialFootprintProfile": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "description": "Profile name, when the platform exposes one." + }, + "photo": { + "type": "string", + "nullable": true, + "description": "Profile photo URL, when the platform exposes one." + }, + "about": { + "type": "string", + "nullable": true, + "description": "Profile about/bio text, when the platform exposes one." + }, + "privacy_status": { + "type": "string", + "nullable": true, + "enum": [ + "PUBLIC", + "PRIVATE", + null + ], + "description": "Profile visibility, when the platform exposes it." + }, + "business_account": { + "type": "boolean", + "nullable": true, + "description": "Whether the profile is a business account, when the platform exposes it." + } + } + }, + "PhoneSocialFootprint": { + "type": "object", + "nullable": true, + "description": "Social footprint of the phone number: the online platforms it is registered on. Present only when `enable_social` was set and the check completed. Every count may be null when no data was returned for it. `profiles_registered: 0` means the platforms were checked and the phone number was found on none of them - a valid, billable answer and a common synthetic-identity signal.", + "properties": { + "profiles_registered": { + "type": "integer", + "nullable": true, + "description": "Platforms where the phone number is confirmed registered.", + "example": 5 + }, + "profiles_checked": { + "type": "integer", + "nullable": true, + "description": "Platforms evaluated for registration.", + "example": 14 + }, + "names_found": { + "type": "integer", + "nullable": true, + "description": "Platforms that returned an associated name.", + "example": 0 + }, + "photos_found": { + "type": "integer", + "nullable": true, + "description": "Platforms that returned a profile photo.", + "example": 0 + }, + "communication_breadth": { + "type": "integer", + "nullable": true, + "description": "Communication platforms with detected presence.", + "example": 2 + }, + "digital_footprint_diversity": { + "type": "integer", + "nullable": true, + "description": "Distinct platform categories with detected presence.", + "example": 5 + }, + "digital_sophistication_index": { + "type": "integer", + "nullable": true, + "description": "Composite digital-maturity indicator, 0-10.", + "example": 5 + }, + "engagement_depth_index": { + "type": "integer", + "nullable": true, + "description": "Profile-richness indicator, 0-10.", + "example": 3 + }, + "subscription_affordability_index": { + "type": "integer", + "nullable": true, + "description": "Free vs paid platform mix, 0-10.", + "example": 2 + }, + "free_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on free platforms.", + "example": 4 + }, + "full_paid_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on fully paid platforms.", + "example": 0 + }, + "partial_paid_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on freemium platforms.", + "example": 1 + }, + "whatsapp_profile_completeness": { + "type": "string", + "nullable": true, + "enum": [ + "Basic", + "Partial", + "Complete", + "Not Present", + null + ], + "description": "Completeness of the WhatsApp profile. Phone only; null for email.", + "example": "Partial" + }, + "registered_platforms": { + "type": "array", + "description": "Platforms where registration is confirmed.", + "items": { + "$ref": "#/components/schemas/SocialFootprintPlatform" + } + }, + "not_registered_platforms": { + "type": "array", + "description": "Platforms checked with a negative result.", + "items": { + "$ref": "#/components/schemas/SocialFootprintPlatform" + } + }, + "categories": { + "type": "object", + "description": "Registered count and ratio per platform category.", + "additionalProperties": { + "$ref": "#/components/schemas/SocialFootprintCategorySummary" + } + }, + "profiles": { + "type": "object", + "description": "Per-platform profile details, keyed by platform, when the platform exposes them.", + "additionalProperties": { + "$ref": "#/components/schemas/SocialFootprintProfile" + } + } + } + }, + "EmailSocialFootprint": { + "type": "object", + "nullable": true, + "description": "Social footprint of the email address: the online platforms it is registered on. Present only when `enable_social` was set and the check completed. Every count may be null when no data was returned for it. `profiles_registered: 0` means the platforms were checked and the email address was found on none of them - a valid, billable answer and a common synthetic-identity signal.", + "properties": { + "profiles_registered": { + "type": "integer", + "nullable": true, + "description": "Platforms where the email address is confirmed registered.", + "example": 5 + }, + "profiles_checked": { + "type": "integer", + "nullable": true, + "description": "Platforms evaluated for registration.", + "example": 14 + }, + "names_found": { + "type": "integer", + "nullable": true, + "description": "Platforms that returned an associated name.", + "example": 0 + }, + "photos_found": { + "type": "integer", + "nullable": true, + "description": "Platforms that returned a profile photo.", + "example": 0 + }, + "communication_breadth": { + "type": "integer", + "nullable": true, + "description": "Communication platforms with detected presence.", + "example": 2 + }, + "digital_footprint_diversity": { + "type": "integer", + "nullable": true, + "description": "Distinct platform categories with detected presence.", + "example": 5 + }, + "digital_sophistication_index": { + "type": "integer", + "nullable": true, + "description": "Composite digital-maturity indicator, 0-10.", + "example": 5 + }, + "engagement_depth_index": { + "type": "integer", + "nullable": true, + "description": "Profile-richness indicator, 0-10.", + "example": 3 + }, + "subscription_affordability_index": { + "type": "integer", + "nullable": true, + "description": "Free vs paid platform mix, 0-10.", + "example": 2 + }, + "free_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on free platforms.", + "example": 4 + }, + "full_paid_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on fully paid platforms.", + "example": 0 + }, + "partial_paid_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on freemium platforms.", + "example": 1 + }, + "whatsapp_profile_completeness": { + "type": "string", + "nullable": true, + "enum": [ + "Basic", + "Partial", + "Complete", + "Not Present", + null + ], + "description": "Completeness of the WhatsApp profile. Phone only; null for email.", + "example": "Partial" + }, + "registered_platforms": { + "type": "array", + "description": "Platforms where registration is confirmed.", + "items": { + "$ref": "#/components/schemas/SocialFootprintPlatform" + } + }, + "not_registered_platforms": { + "type": "array", + "description": "Platforms checked with a negative result.", + "items": { + "$ref": "#/components/schemas/SocialFootprintPlatform" + } + }, + "categories": { + "type": "object", + "description": "Registered count and ratio per platform category.", + "additionalProperties": { + "$ref": "#/components/schemas/SocialFootprintCategorySummary" + } + }, + "profiles": { + "type": "object", + "description": "Per-platform profile details, keyed by platform, when the platform exposes them.", + "additionalProperties": { + "$ref": "#/components/schemas/SocialFootprintProfile" + } + } + } + }, + "KYBRegistryAddress": { + "type": "object", + "description": "An address the registry states, in the same shape for every country and registry source. Every key is always present; a part the registry does not publish is null.", + "properties": { + "type": { + "type": "string", + "nullable": true, + "description": "Address type in lower case: `registered` for the registered office; other types as the registry names them (for example `postal`, `office`). `null` when the registry does not say what the address is." + }, + "address": { + "type": "string", + "description": "The full address as one line. Always present on every address." + }, + "street": { + "type": "string", + "nullable": true, + "description": "Street and number." + }, + "city": { + "type": "string", + "nullable": true, + "description": "City or locality." + }, + "postal_code": { + "type": "string", + "nullable": true, + "description": "Postal code." + }, + "region": { + "type": "string", + "nullable": true, + "description": "Region, state or province." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code." + }, + "description": { + "type": "string", + "nullable": true, + "description": "The registry's own label for the address, when it gives one." + } + }, + "required": [ + "type", + "address", + "street", + "city", + "postal_code", + "region", + "country_code", + "description" + ] + }, + "KYBRegistryIndustry": { + "type": "object", + "description": "An activity the registry classifies the company under, in the same shape for every country and registry source.", + "properties": { + "code": { + "type": "string", + "nullable": true, + "description": "Activity code as the registry prints it (for example `73110`)." + }, + "description": { + "type": "string", + "nullable": true, + "description": "Activity label, when the registry publishes one." + }, + "scheme": { + "type": "string", + "nullable": true, + "description": "Classification system, one name per system whichever source reported it: `UK SIC 2007`, `US SIC`, `NACE Rev. 2`, `NACE Rev. 2.1`, `NAF Rev. 2`, `CNAE`, `SBI`, `NOGA 2008`, `CAE Rev. 4`, `Canada SIC 1977`; other national systems keep the registry's own name. Null when the registry does not name the system." + }, + "is_primary": { + "type": "boolean", + "description": "True for the company's main activity. Exactly one entry is primary when the list is not empty." + } + }, + "required": [ + "code", + "description", + "scheme", + "is_primary" + ] + }, + "KYBRegistryShareholding": { + "type": "object", + "nullable": true, + "description": "What an owner holds, in the same shape for every country and registry source. Null when the registry states no holding. A band (for example a UK PSC's 25-50%) is not a stake: `percentage` is null and the band stays in `ownership_min_shares` / `ownership_max_shares`.", + "properties": { + "share_class": { + "type": "string", + "nullable": true, + "description": "Share class or type as the registry names it." + }, + "number_of_shares": { + "type": "number", + "nullable": true, + "description": "Number of shares held." + }, + "currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 currency of the values below." + }, + "nominal_value": { + "type": "number", + "nullable": true, + "description": "Nominal value per share." + }, + "total_value": { + "type": "number", + "nullable": true, + "description": "Total value of the holding." + }, + "percentage": { + "type": "number", + "nullable": true, + "description": "Exact stake in percent, to four decimal places." + } + }, + "required": [ + "share_class", + "number_of_shares", + "currency", + "nominal_value", + "total_value", + "percentage" + ] + }, + "KYBRegistryOwnership": { + "type": "object", + "description": "The outcome of the ownership (Shareholders / UBO) tier, in the same shape for every country and registry source.", + "properties": { + "status": { + "type": "string", + "nullable": true, + "enum": [ + "not_requested", + "pending", + "delivered", + "not_held", + "unavailable", + null + ], + "description": "`delivered` when the registry published ownership; `not_held` when the ownership registry answered and does not list the company at all (see `unavailable_reason`); `unavailable` when it did not serve the ownership data for any other reason (see `unavailable_reason`); `pending` while the registry is still fetching it; `not_requested` for the Basic tier." + }, + "tier_requested": { + "type": "string", + "nullable": true, + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "The tier the workflow ordered." + }, + "tier_delivered": { + "type": "string", + "nullable": true, + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "The tier the registry delivered (and that is billed). A UBO request answered with direct shareholders only is `shareholders`." + }, + "unavailable_reason": { + "type": "string", + "nullable": true, + "enum": [ + "no_owners_on_record", + "no_natural_person_owner", + "ownership_section_unavailable", + "company_not_found", + "ambiguous_company_match", + "missing_registration_number", + "entity_type_not_covered", + "provider_unavailable", + "other", + null + ], + "description": "Why a requested ownership tier was not delivered. Null unless `status` is `unavailable` or `pending`." + }, + "direct_ownership_complete": { + "type": "boolean", + "nullable": true, + "description": "True when the direct shareholders' stated stakes add up to 100% (within one percentage point of rounding); false when they do not; null when ownership was not delivered or a stake is not stated." + }, + "unresolved": { + "type": "array", + "description": "Parts of the ownership chain the registry could not resolve, for example no group structure on record above the direct owners.", + "items": { + "type": "object", + "properties": { + "reason": { + "type": "string" + }, + "section": { + "type": "string", + "nullable": true + } + } + } + } + }, + "required": [ + "status", + "tier_requested", + "tier_delivered", + "unavailable_reason", + "direct_ownership_complete", + "unresolved" + ] + }, + "KYBRegistryData": { + "type": "object", + "nullable": true, + "description": "Didit's normalized registry detail for the company: the ownership outcome (`ownership_error`, `section_errors`, `ubo_resolution`, `lite_enrichment`), the corrections made with update-data, and registry facts that have no field of their own. It never carries the registry source's own record. Every key is optional and present only when the company has a value for it; new keys may be added. Read the canonical fields (`addresses`, `industries`, `ownership`, `beneficial_owners[].shareholding`) for anything they cover.", + "properties": { + "company_name": { + "type": "string", + "description": "Company name as corrected in the console." + }, + "registration_number": { + "type": "string", + "description": "Registration number as the registry or a console correction states it." + }, + "country_code": { + "type": "string", + "description": "Country of the registration as the registry or a console correction states it." + }, + "region": { + "type": "string", + "description": "Region or state of the registration." + }, + "incorporation_date": { + "type": "string", + "description": "Incorporation date (ISO 8601)." + }, + "registration_date": { + "type": "string", + "description": "Registration date (ISO 8601) when the registry states one apart from the incorporation date." + }, + "dissolution_date": { + "type": "string", + "description": "Dissolution date (ISO 8601) when the registry states one." + }, + "tax_number": { + "type": "string", + "description": "Tax identification number." + }, + "vat_number": { + "type": "string", + "description": "VAT number." + }, + "legal_address": { + "type": "string", + "description": "Legal address as corrected in the console." + }, + "registered_address": { + "type": "string", + "description": "Registered address as the registry states it." + }, + "company_type": { + "type": "string", + "description": "Legal form." + }, + "registry_status": { + "type": "string", + "description": "Company status at the registry." + }, + "verification_status": { + "type": "string", + "description": "Verification status." + }, + "alternative_names": { + "type": "string", + "description": "Other names the company trades or traded under." + }, + "nature_of_business": { + "type": "string", + "description": "Activity description." + }, + "registered_capital": { + "type": "string", + "description": "Registered capital as printed." + }, + "registered_capital_amount": { + "type": "string", + "description": "Registered capital amount, as a decimal string." + }, + "registered_capital_currency": { + "type": "string", + "description": "Registered capital currency (ISO 4217)." + }, + "website": { + "type": "string", + "description": "Website." + }, + "email": { + "type": "string", + "description": "Contact email." + }, + "phone": { + "type": "string", + "description": "Contact phone." + }, + "legal_entity_identifier": { + "type": "string", + "description": "Legal Entity Identifier (LEI)." + }, + "location_of_registration": { + "type": "string", + "description": "Place of registration." + }, + "control_scheme": { + "type": "string", + "description": "Governance scheme when the registry exposes it." + }, + "ownership_error": { + "type": "string", + "enum": [ + "no_owners_on_record", + "no_natural_person_owner", + "ownership_section_unavailable", + "company_not_found", + "ambiguous_company_match", + "missing_registration_number", + "entity_type_not_covered", + "provider_unavailable", + "other" + ], + "description": "Why a requested ownership tier was not delivered, in the same codes as `ownership.unavailable_reason`. `no_owners_on_record` (the registry answered and names no owner) and `ownership_section_unavailable` (the registry did not return the ownership section) are different findings." + }, + "section_errors": { + "type": "object", + "description": "Sections the registry did not return, keyed by section (`officers`, `shareholders`, `group_structure`). A section listed here was never delivered: an empty list next to it says nothing about the company.", + "additionalProperties": { + "$ref": "#/components/schemas/KYBRegistrySectionError" + } + }, + "ubo_resolution": { + "$ref": "#/components/schemas/KYBRegistryUboResolution" + }, + "lite_enrichment": { + "$ref": "#/components/schemas/KYBRegistryLiteEnrichment" + }, + "registry_metadata": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ] + }, + "description": "The register's own fields for the company, keyed as the register names them, as plain values (legacy records). Only fields reviewed as registry facts are included." + }, + "registry_sources": { + "type": "array", + "description": "The official registers the record was read from (legacy records). Other sources are not named.", + "items": { + "$ref": "#/components/schemas/KYBRegistrySource" + } + }, + "previous_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names the company was registered under before (legacy records)." + }, + "parent_companies": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Parent companies the registry names (legacy records)." + }, + "documents_available": { + "type": "integer", + "description": "How many filings the registry offers for the company (legacy records)." + } + } + }, + "KYBRegistryGroupCompany": { + "type": "object", + "description": "A company of the corporate group, by its registry identity.", + "properties": { + "name": { + "type": "string", + "nullable": true, + "description": "Company name." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "Country of registration." + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Registration number." + } + }, + "required": [ + "name", + "country_code", + "registration_number" + ] + }, + "KYBRegistryUboResolution": { + "type": "object", + "description": "How the UBO tier walked the ownership chain above the company.", + "properties": { + "parent_chain": { + "type": "array", + "description": "Corporate parents from the ultimate parent down to the company.", + "items": { + "$ref": "#/components/schemas/KYBRegistryGroupCompany" + } + }, + "subsidiaries": { + "type": "array", + "description": "Companies directly below the company in its corporate group.", + "items": { + "$ref": "#/components/schemas/KYBRegistryGroupCompany" + } + }, + "unresolved": { + "type": "array", + "description": "Parts of the chain the registry could not resolve (the same entries as `ownership.unresolved`).", + "items": { + "type": "object", + "properties": { + "reason": { + "type": "string", + "description": "Why, for example `no_group_structure_on_record`, `selected_company_not_in_group_structure` or `section_error`." + }, + "section": { + "type": "string", + "nullable": true, + "description": "The section concerned, for example `group_structure`." + } + }, + "required": [ + "reason", + "section" + ] + } + } + }, + "required": [ + "parent_chain", + "subsidiaries", + "unresolved" + ] + }, + "KYBRegistrySectionError": { + "type": "object", + "description": "A section the registry did not return.", + "properties": { + "status_code": { + "type": "integer", + "nullable": true, + "description": "The status the registry answered for the section." + } + }, + "required": [ + "status_code" + ] + }, + "KYBRegistryLiteEnrichment": { + "type": "object", + "description": "Blank Lite profile fields another of the country's registry sources filled. The source is not named.", + "properties": { + "fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The company fields that were filled." + }, + "people_added": { + "type": "integer", + "description": "How many officers or owners were added." + } + }, + "required": [ + "fields", + "people_added" + ] + }, + "KYBRegistrySource": { + "type": "object", + "description": "An official register the record was read from. Only official government registers, regulators and the official identifier system are named; any other source is left out.", + "properties": { + "name": { + "type": "string", + "description": "Register name, for example `UK Companies House`." + }, + "source_type": { + "type": "string", + "enum": [ + "official" + ], + "description": "Always `official`." + } + }, + "required": [ + "name", + "source_type" + ] + }, + "KYBOwnershipStructure": { + "type": "object", + "nullable": true, + "description": "The cap table the registry proved, as it stood when the ownership tier was delivered: direct `shareholders` and `beneficial_owners`. Null when the registry proved no ownership. It never carries the registry source's own rows; read `company.beneficial_owners` and `company.ownership` for the current record.", + "properties": { + "shareholders": { + "type": "array", + "items": { + "$ref": "#/components/schemas/KYBOwnershipStructureOwner" + } + }, + "beneficial_owners": { + "type": "array", + "items": { + "$ref": "#/components/schemas/KYBOwnershipStructureOwner" + } + } + }, + "required": [ + "shareholders", + "beneficial_owners" + ] + }, + "KYBOwnershipStructureOwner": { + "type": "object", + "description": "One owner of the ownership snapshot: the same registry identity and stake as `company.beneficial_owners[]`, and its canonical `shareholding`.", + "properties": { + "name": { + "type": "string", + "nullable": true, + "description": "Owner name as the registry states it." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name, for a natural person, when the registry states it." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name, for a natural person, when the registry states it." + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Date of birth (ISO 8601) when the registry publishes it." + }, + "country_of_birth": { + "type": "string", + "nullable": true, + "description": "Country of birth when the registry publishes it." + }, + "company_name": { + "type": "string", + "nullable": true, + "description": "Company name, for a corporate owner." + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Registration number, for a corporate owner." + }, + "entity_type": { + "type": "string", + "nullable": true, + "enum": [ + "person", + "company", + null + ], + "description": "Whether the owner is a natural person or a company." + }, + "nationality": { + "type": "string", + "nullable": true, + "description": "Nationality when the registry states it." + }, + "designation": { + "type": "string", + "nullable": true, + "description": "The owner's title or role as the registry prints it." + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "`shareholder` for a direct holder, `ubo` for a beneficial owner." + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the stake, in percent (equal to the upper bound for an exact stake)." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the stake, in percent." + }, + "voting_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the voting rights, in percent." + }, + "voting_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the voting rights, in percent." + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the holding or appointment started (ISO 8601)." + }, + "is_active": { + "type": "boolean", + "nullable": true, + "description": "False for a former owner." + }, + "shareholding": { + "$ref": "#/components/schemas/KYBRegistryShareholding" + } + }, + "required": [ + "name", + "first_name", + "last_name", + "date_of_birth", + "country_of_birth", + "company_name", + "registration_number", + "entity_type", + "nationality", + "designation", + "roles", + "ownership_min_shares", + "ownership_max_shares", + "voting_min_shares", + "voting_max_shares", + "appointment_date", + "is_active", + "shareholding" + ] + }, + "DigitalIdWalletApiWalletVerificationError": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Stable machine-readable code, e.g. wallet_unavailable." + }, + "detail": { + "type": "string" + } + }, + "required": [ + "detail", + "error" + ] + }, + "DigitalIdWalletApiWalletVerificationStartForbidden": { + "oneOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + }, + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + ] + }, + "DigitalIdWalletApiClientCredentialsForbiddenErrorMessage": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "default": "Forbidden - User doesn't have permission" + } + } + }, + "DigitalIdWalletApiWalletVerificationStartBadRequest": { + "oneOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + }, + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationFieldErrors" + } + ] + }, + "DigitalIdWalletApiWalletVerificationFieldErrors": { + "type": "object", + "description": "Request validation errors: each rejected field with its messages.", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "DigitalIdWalletApiWalletVerificationResponse": { + "type": "object", + "description": "One standalone wallet verification, as every wallet endpoint answers it.", + "properties": { + "request_id": { + "type": "string", + "format": "uuid" + }, + "wallet_verification": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResult" + }, + "vendor_data": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "The metadata object sent at start, or null." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "created_at", + "metadata", + "request_id", + "vendor_data", + "wallet_verification" + ] + }, + "DigitalIdWalletApiWalletVerificationResult": { + "type": "object", + "properties": { + "wallet": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWallet" + } + ], + "readOnly": true + }, + "country": { + "type": "string" + }, + "status": { + "type": "string", + "description": "Not Finished while pending, then Approved (verified) or Declined." + }, + "outcome": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResultOutcomeEnum" + }, + "error": { + "type": "string", + "nullable": true, + "readOnly": true + }, + "interaction": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiInteractionEnum" + } + ], + "readOnly": true + }, + "simulated": { + "type": "boolean", + "readOnly": true + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "next_action": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletNextAction" + } + ], + "nullable": true, + "readOnly": true + }, + "identity": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletIdentity" + } + ], + "nullable": true, + "readOnly": true + }, + "attributes": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "readOnly": true + }, + "level_of_assurance": { + "type": "string", + "nullable": true + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "signature_valid": { + "type": "boolean", + "nullable": true, + "description": "True once the credential's signature was verified; null for a simulated one." + }, + "credential_type": { + "type": "string", + "nullable": true + } + }, + "required": [ + "attributes", + "country", + "credential_type", + "error", + "expires_at", + "identity", + "interaction", + "level_of_assurance", + "next_action", + "outcome", + "signature_valid", + "simulated", + "status", + "verified_at", + "wallet" + ] + }, + "DigitalIdWalletApiWalletIdentity": { + "type": "object", + "properties": { + "full_name": { + "type": "string", + "nullable": true + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true, + "description": "ISO3 nationality, only when the wallet asserts one (never inferred)." + }, + "issuing_state": { + "type": "string", + "description": "ISO3 country whose scheme issued the identity." + } + }, + "required": [ + "date_of_birth", + "first_name", + "full_name", + "issuing_state", + "last_name", + "nationality" + ] + }, + "DigitalIdWalletApiWalletNextAction": { + "type": "object", + "properties": { + "type": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletNextActionTypeEnum" + } + ], + "description": "redirect: send the user's browser to url. confirm_in_app: show verification_code to the user, who checks it matches the code in their wallet app and confirms there; then poll. request_digital_credential: in a user gesture on the origin you started with, ask the wallet with digital_credentials.request, then send what it returns to POST .../verifications/{request_id}/response/.\n\n* `redirect` - redirect\n* `confirm_in_app` - confirm_in_app\n* `request_digital_credential` - request_digital_credential" + }, + "url": { + "type": "string", + "format": "uri", + "description": "redirect only: where the user signs in with the wallet." + }, + "digital_credentials": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletDigitalCredentialsRequest" + } + ], + "description": "request_digital_credential only: the request to hand to the wallet." + }, + "verification_code": { + "type": "string", + "description": "confirm_in_app only: the 4-digit comparison code the wallet app shows too." + }, + "poll_interval_seconds": { + "type": "integer", + "description": "confirm_in_app only: how often to call POST .../verifications/{request_id}/poll/ for prompt feedback. Didit also polls pending sign-ins in the background, so the webhook reports the outcome even when you do not poll." + } + }, + "required": [ + "type" + ] + }, + "DigitalIdWalletApiWalletNextActionTypeEnum": { + "enum": [ + "redirect", + "confirm_in_app", + "request_digital_credential" + ], + "type": "string", + "description": "* `redirect` - redirect\n* `confirm_in_app` - confirm_in_app\n* `request_digital_credential` - request_digital_credential" + }, + "DigitalIdWalletApiInteractionEnum": { + "enum": [ + "redirect", + "in_app", + "digital_credentials" + ], + "type": "string", + "description": "* `redirect` - redirect\n* `in_app` - in_app\n* `digital_credentials` - digital_credentials" + }, + "DigitalIdWalletApiWalletVerificationResultOutcomeEnum": { + "enum": [ + "pending", + "verified", + "cancelled", + "timeout", + "failed" + ], + "type": "string", + "description": "* `pending` - Pending\n* `verified` - Verified\n* `cancelled` - Cancelled by the user\n* `timeout` - Timed out\n* `failed` - Failed" + }, + "DigitalIdWalletApiWallet": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Catalog id of the wallet, e.g. bankid_se." + }, + "name": { + "type": "string" + }, + "issuing_authority": { + "type": "string", + "nullable": true + } + }, + "required": [ + "id", + "issuing_authority", + "name" + ] + }, + "DigitalIdWalletApiEudiWalletVerificationStartRequest": { + "type": "object", + "description": "Start a EUDI Wallet verification (AUT, BEL, BGR, HRV, CYP, CZE, DNK, EST, FIN, FRA, DEU, GRC, HUN, IRL, ITA, LVA, LTU, LUX, MLT, NLD, POL, PRT, ROU, SVK, SVN, ESP, SWE, ISL, LIE, NOR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiEudiWalletVerificationStartCountryEnum" + } + ], + "description": "ISO 3166-1 alpha-3 country of the EUDI Wallet identity.\n\n* `AUT` - AUT\n* `BEL` - BEL\n* `BGR` - BGR\n* `HRV` - HRV\n* `CYP` - CYP\n* `CZE` - CZE\n* `DNK` - DNK\n* `EST` - EST\n* `FIN` - FIN\n* `FRA` - FRA\n* `DEU` - DEU\n* `GRC` - GRC\n* `HUN` - HUN\n* `IRL` - IRL\n* `ITA` - ITA\n* `LVA` - LVA\n* `LTU` - LTU\n* `LUX` - LUX\n* `MLT` - MLT\n* `NLD` - NLD\n* `POL` - POL\n* `PRT` - PRT\n* `ROU` - ROU\n* `SVK` - SVK\n* `SVN` - SVN\n* `ESP` - ESP\n* `SWE` - SWE\n* `ISL` - ISL\n* `LIE` - LIE\n* `NOR` - NOR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "country", + "return_url" + ] + }, + "DigitalIdWalletApiDeviceEnum": { + "enum": [ + "same_device", + "cross_device" + ], + "type": "string", + "description": "* `same_device` - same_device\n* `cross_device` - cross_device" + }, + "DigitalIdWalletApiEudiWalletVerificationStartCountryEnum": { + "enum": [ + "AUT", + "BEL", + "BGR", + "HRV", + "CYP", + "CZE", + "DNK", + "EST", + "FIN", + "FRA", + "DEU", + "GRC", + "HUN", + "IRL", + "ITA", + "LVA", + "LTU", + "LUX", + "MLT", + "NLD", + "POL", + "PRT", + "ROU", + "SVK", + "SVN", + "ESP", + "SWE", + "ISL", + "LIE", + "NOR" + ], + "type": "string", + "description": "* `AUT` - AUT\n* `BEL` - BEL\n* `BGR` - BGR\n* `HRV` - HRV\n* `CYP` - CYP\n* `CZE` - CZE\n* `DNK` - DNK\n* `EST` - EST\n* `FIN` - FIN\n* `FRA` - FRA\n* `DEU` - DEU\n* `GRC` - GRC\n* `HUN` - HUN\n* `IRL` - IRL\n* `ITA` - ITA\n* `LVA` - LVA\n* `LTU` - LTU\n* `LUX` - LUX\n* `MLT` - MLT\n* `NLD` - NLD\n* `POL` - POL\n* `PRT` - PRT\n* `ROU` - ROU\n* `SVK` - SVK\n* `SVN` - SVN\n* `ESP` - ESP\n* `SWE` - SWE\n* `ISL` - ISL\n* `LIE` - LIE\n* `NOR` - NOR" + }, + "DigitalIdWalletApiConnectidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a ConnectID verification (AUS).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiConnectidWalletVerificationStartCountryEnum" + } + ], + "default": "AUS", + "description": "ISO 3166-1 alpha-3 country of the ConnectID identity. Defaults to AUS, the only country it serves.\n\n* `AUS` - AUS" + }, + "authorisation_server_id": { + "type": "string", + "minLength": 1, + "description": "Authorisation server ID selected with ConnectID's bank selector. Sandbox defaults to Mock Bank A.", + "maxLength": 36 + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiConnectidWalletVerificationStartCountryEnum": { + "enum": [ + "AUS" + ], + "type": "string", + "description": "* `AUS` - AUS" + }, + "DigitalIdWalletApiAudkenniWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Auðkenni verification (ISL).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiAudkenniWalletVerificationStartCountryEnum" + } + ], + "default": "ISL", + "description": "ISO 3166-1 alpha-3 country of the Auðkenni identity. Defaults to ISL, the only country it serves.\n\n* `ISL` - ISL" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiAudkenniWalletVerificationStartCountryEnum": { + "enum": [ + "ISL" + ], + "type": "string", + "description": "* `ISL` - ISL" + }, + "DigitalIdWalletApiFranceconnectWalletVerificationStartRequest": { + "type": "object", + "description": "Start a FranceConnect verification (FRA).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiFranceconnectWalletVerificationStartCountryEnum" + } + ], + "default": "FRA", + "description": "ISO 3166-1 alpha-3 country of the FranceConnect identity. Defaults to FRA, the only country it serves.\n\n* `FRA` - FRA" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiFranceconnectWalletVerificationStartCountryEnum": { + "enum": [ + "FRA" + ], + "type": "string", + "description": "* `FRA` - FRA" + }, + "DigitalIdWalletApiDiiaWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Diia verification (UKR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDiiaWalletVerificationStartCountryEnum" + } + ], + "default": "UKR", + "description": "ISO 3166-1 alpha-3 country of the Diia identity. Defaults to UKR, the only country it serves.\n\n* `UKR` - UKR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiDiiaWalletVerificationStartCountryEnum": { + "enum": [ + "UKR" + ], + "type": "string", + "description": "* `UKR` - UKR" + }, + "DigitalIdWalletApiMojeidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a MojeID verification (CZE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryE2dEnum" + } + ], + "default": "CZE", + "description": "ISO 3166-1 alpha-3 country of the MojeID identity. Defaults to CZE, the only country it serves.\n\n* `CZE` - CZE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiCountryE2dEnum": { + "enum": [ + "CZE" + ], + "type": "string", + "description": "* `CZE` - CZE" + }, + "DigitalIdWalletApiBankidCzWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Bank iD verification (CZE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryE2dEnum" + } + ], + "default": "CZE", + "description": "ISO 3166-1 alpha-3 country of the Bank iD identity. Defaults to CZE, the only country it serves.\n\n* `CZE` - CZE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest": { + "type": "object", + "description": "Start a eParaksts mobile verification (LVA).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiEparakstsMobileWalletVerificationStartCountryEnum" + } + ], + "default": "LVA", + "description": "ISO 3166-1 alpha-3 country of the eParaksts mobile identity. Defaults to LVA, the only country it serves.\n\n* `LVA` - LVA" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiEparakstsMobileWalletVerificationStartCountryEnum": { + "enum": [ + "LVA" + ], + "type": "string", + "description": "* `LVA` - LVA" + }, + "DigitalIdWalletApiIdcardEeWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Estonian ID-card verification (EST).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiIdcardEeWalletVerificationStartCountryEnum" + } + ], + "default": "EST", + "description": "ISO 3166-1 alpha-3 country of the Estonian ID-card identity. Defaults to EST, the only country it serves.\n\n* `EST` - EST" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiIdcardEeWalletVerificationStartCountryEnum": { + "enum": [ + "EST" + ], + "type": "string", + "description": "* `EST` - EST" + }, + "DigitalIdWalletApiMobileIdWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Mobile-ID verification (EST, LTU).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiMobileIdWalletVerificationStartCountryEnum" + } + ], + "description": "ISO 3166-1 alpha-3 country of the Mobile-ID identity.\n\n* `EST` - EST\n* `LTU` - LTU" + }, + "personal_code": { + "type": "string", + "minLength": 1, + "description": "The user's national personal identification code. It addresses the user's Mobile-ID app; the user confirms the comparison code and enters their PIN in the app. Never collect the PIN.", + "pattern": "^[0-9-]{6,20}$" + }, + "phone_number": { + "type": "string", + "minLength": 1, + "description": "The Mobile-ID phone number in E.164 format (+372... or +370...).", + "maxLength": 16 + } + }, + "required": [ + "country", + "personal_code", + "phone_number" + ] + }, + "DigitalIdWalletApiMobileIdWalletVerificationStartCountryEnum": { + "enum": [ + "EST", + "LTU" + ], + "type": "string", + "description": "* `EST` - EST\n* `LTU` - LTU" + }, + "DigitalIdWalletApiSmartIdWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Smart-ID verification (EST, LVA, LTU, BEL).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiSmartIdWalletVerificationStartCountryEnum" + } + ], + "description": "ISO 3166-1 alpha-3 country of the Smart-ID identity.\n\n* `EST` - EST\n* `LVA` - LVA\n* `LTU` - LTU\n* `BEL` - BEL" + }, + "personal_code": { + "type": "string", + "minLength": 1, + "description": "The user's national personal identification code. It addresses the user's Smart-ID app; the user confirms the comparison code and enters their PIN in the app. Never collect the PIN.", + "pattern": "^[0-9-]{6,20}$" + } + }, + "required": [ + "country", + "personal_code" + ] + }, + "DigitalIdWalletApiSmartIdWalletVerificationStartCountryEnum": { + "enum": [ + "EST", + "LVA", + "LTU", + "BEL" + ], + "type": "string", + "description": "* `EST` - EST\n* `LVA` - LVA\n* `LTU` - LTU\n* `BEL` - BEL" + }, + "DigitalIdWalletApiGovukWalletWalletVerificationStartRequest": { + "type": "object", + "description": "Start a GOV.UK Wallet verification (GBR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountry520Enum" + } + ], + "default": "GBR", + "description": "ISO 3166-1 alpha-3 country of the GOV.UK Wallet identity. Defaults to GBR, the only country it serves.\n\n* `GBR` - GBR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiCountry520Enum": { + "enum": [ + "GBR" + ], + "type": "string", + "description": "* `GBR` - GBR" + }, + "DigitalIdWalletApiOneidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a OneID verification (GBR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountry520Enum" + } + ], + "default": "GBR", + "description": "ISO 3166-1 alpha-3 country of the OneID identity. Defaults to GBR, the only country it serves.\n\n* `GBR` - GBR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiGovbrWalletVerificationStartRequest": { + "type": "object", + "description": "Start a gov.br verification (BRA).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiGovbrWalletVerificationStartCountryEnum" + } + ], + "default": "BRA", + "description": "ISO 3166-1 alpha-3 country of the gov.br identity. Defaults to BRA, the only country it serves.\n\n* `BRA` - BRA" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiGovbrWalletVerificationStartCountryEnum": { + "enum": [ + "BRA" + ], + "type": "string", + "description": "* `BRA` - BRA" + }, + "DigitalIdWalletApiUaePassWalletVerificationStartRequest": { + "type": "object", + "description": "Start a UAE PASS verification (ARE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiUaePassWalletVerificationStartCountryEnum" + } + ], + "default": "ARE", + "description": "ISO 3166-1 alpha-3 country of the UAE PASS identity. Defaults to ARE, the only country it serves.\n\n* `ARE` - ARE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiUaePassWalletVerificationStartCountryEnum": { + "enum": [ + "ARE" + ], + "type": "string", + "description": "* `ARE` - ARE" + }, + "DigitalIdWalletApiFrejaidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Freja eID verification (SWE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryEe9Enum" + } + ], + "default": "SWE", + "description": "ISO 3166-1 alpha-3 country of the Freja eID identity. Defaults to SWE, the only country it serves.\n\n* `SWE` - SWE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiCountryEe9Enum": { + "enum": [ + "SWE" + ], + "type": "string", + "description": "* `SWE` - SWE" + }, + "DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Personalausweis verification (DEU).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiPersonalausweisWalletVerificationStartCountryEnum" + } + ], + "default": "DEU", + "description": "ISO 3166-1 alpha-3 country of the Personalausweis identity. Defaults to DEU, the only country it serves.\n\n* `DEU` - DEU" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiPersonalausweisWalletVerificationStartCountryEnum": { + "enum": [ + "DEU" + ], + "type": "string", + "description": "* `DEU` - DEU" + }, + "DigitalIdWalletApiFtnWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Finnish Trust Network verification (FIN).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiFtnWalletVerificationStartCountryEnum" + } + ], + "default": "FIN", + "description": "ISO 3166-1 alpha-3 country of the Finnish Trust Network identity. Defaults to FIN, the only country it serves.\n\n* `FIN` - FIN" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiFtnWalletVerificationStartCountryEnum": { + "enum": [ + "FIN" + ], + "type": "string", + "description": "* `FIN` - FIN" + }, + "DigitalIdWalletApiIdinWalletVerificationStartRequest": { + "type": "object", + "description": "Start a iDIN verification (NLD).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiIdinWalletVerificationStartCountryEnum" + } + ], + "default": "NLD", + "description": "ISO 3166-1 alpha-3 country of the iDIN identity. Defaults to NLD, the only country it serves.\n\n* `NLD` - NLD" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiIdinWalletVerificationStartCountryEnum": { + "enum": [ + "NLD" + ], + "type": "string", + "description": "* `NLD` - NLD" + }, + "DigitalIdWalletApiItsmeWalletVerificationStartRequest": { + "type": "object", + "description": "Start a itsme verification (BEL, LUX, NLD).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiItsmeWalletVerificationStartCountryEnum" + } + ], + "description": "ISO 3166-1 alpha-3 country of the itsme identity.\n\n* `BEL` - BEL\n* `LUX` - LUX\n* `NLD` - NLD" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "country", + "return_url" + ] + }, + "DigitalIdWalletApiItsmeWalletVerificationStartCountryEnum": { + "enum": [ + "BEL", + "LUX", + "NLD" + ], + "type": "string", + "description": "* `BEL` - BEL\n* `LUX` - LUX\n* `NLD` - NLD" + }, + "DigitalIdWalletApiBuypassWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Buypass ID verification (NOR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryAfcEnum" + } + ], + "default": "NOR", + "description": "ISO 3166-1 alpha-3 country of the Buypass ID identity. Defaults to NOR, the only country it serves.\n\n* `NOR` - NOR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiCountryAfcEnum": { + "enum": [ + "NOR" + ], + "type": "string", + "description": "* `NOR` - NOR" + }, + "DigitalIdWalletApiVippsWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Vipps verification (NOR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryAfcEnum" + } + ], + "default": "NOR", + "description": "ISO 3166-1 alpha-3 country of the Vipps identity. Defaults to NOR, the only country it serves.\n\n* `NOR` - NOR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiBankidNoWalletVerificationStartRequest": { + "type": "object", + "description": "Start a BankID verification (NOR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryAfcEnum" + } + ], + "default": "NOR", + "description": "ISO 3166-1 alpha-3 country of the BankID identity. Defaults to NOR, the only country it serves.\n\n* `NOR` - NOR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiBankidSeWalletVerificationStartRequest": { + "type": "object", + "description": "Start a BankID verification (SWE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryEe9Enum" + } + ], + "default": "SWE", + "description": "ISO 3166-1 alpha-3 country of the BankID identity. Defaults to SWE, the only country it serves.\n\n* `SWE` - SWE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiMitidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a MitID verification (DNK).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiMitidWalletVerificationStartCountryEnum" + } + ], + "default": "DNK", + "description": "ISO 3166-1 alpha-3 country of the MitID identity. Defaults to DNK, the only country it serves.\n\n* `DNK` - DNK" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiMitidWalletVerificationStartCountryEnum": { + "enum": [ + "DNK" + ], + "type": "string", + "description": "* `DNK` - DNK" + }, + "DigitalIdWalletApiWalletCatalogResponse": { + "type": "object", + "properties": { + "wallets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletCatalogEntry" + } + } + }, + "required": [ + "wallets" + ] + }, + "DigitalIdWalletApiWalletCatalogEntry": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string", + "description": "The wallet's own start endpoint." + }, + "countries": { + "type": "array", + "items": { + "type": "string" + } + }, + "issuing_authority": { + "type": "string" + }, + "level_of_assurance": { + "type": "string", + "nullable": true + }, + "interaction": { + "$ref": "#/components/schemas/DigitalIdWalletApiInteractionEnum" + }, + "required_fields": { + "type": "array", + "items": { + "type": "string" + } + }, + "available": { + "type": "boolean", + "description": "Whether this application can start it in this environment now. Listed does not mean live." + } + }, + "required": [ + "available", + "countries", + "id", + "interaction", + "issuing_authority", + "level_of_assurance", + "name", + "path", + "required_fields" + ] + }, + "CaseDuplicateCaseDuplicate": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "case_number": { + "type": "string", + "nullable": true, + "maxLength": 32 + }, + "title": { + "type": "string", + "maxLength": 255 + }, + "status": { + "$ref": "#/components/schemas/CaseDuplicateStatusEnum" + }, + "priority": { + "$ref": "#/components/schemas/CaseDuplicatePriorityEnum" + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "created_at", + "title", + "uuid" + ] + }, + "CaseDuplicateCaseDuplicatePage": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next_offset": { + "type": "integer", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicate" + } + } + }, + "required": [ + "count", + "next_offset", + "results" + ] + }, + "RoutingPublicationPolicyDraftResponse": { + "type": "object", + "properties": { + "draft": { + "allOf": [ + { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraft" + } + ], + "nullable": true + } + }, + "required": [ + "draft" + ] + }, + "RoutingPublicationPolicyDraftWriteRequest": { + "type": "object", + "properties": { + "expected_version": { + "type": "integer", + "minimum": 0 + }, + "expected_revision": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "changes": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "changes", + "expected_revision", + "expected_version" + ] + }, + "CasePublicationPolicyDraft": { + "type": "object", + "properties": { + "revision": { + "type": "string", + "format": "uuid" + }, + "base_version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "changes": {}, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "base_version", + "updated_at" + ] + }, + "CasePublicationPolicyDraftPreconditionRequest": { + "type": "object", + "properties": { + "expected_version": { + "type": "integer", + "minimum": 0 + }, + "expected_revision": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "required": [ + "expected_revision", + "expected_version" + ] + }, + "CaseCollaborationCaseNote": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "comment": { + "type": "string" + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 255 + }, + "actor_name": { + "type": "string", + "nullable": true, + "maxLength": 255 + }, + "mentioned_emails": {}, + "is_internal": { + "type": "boolean" + }, + "attachments": {}, + "tags": {}, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "reply_to": { + "allOf": [ + { + "$ref": "#/components/schemas/CaseCollaborationCaseNoteReply" + } + ], + "nullable": true, + "readOnly": true + }, + "is_pinned": { + "type": "boolean" + }, + "client_reference": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "required": [ + "comment", + "created_at", + "reply_to", + "uuid" + ] + }, + "CustomerMessagingPaginatedCaseRequestList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=400&limit=100" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=200&limit=100" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequest" + } + } + } + }, + "FraudCheckResponse": { + "type": "object", + "properties": { + "check_id": { + "type": "string", + "format": "uuid" + }, + "request_id": { + "type": "string", + "format": "uuid", + "description": "Native verification session created for this check." + }, + "client_reference": { + "type": "string", + "nullable": true + }, + "processing_status": { + "$ref": "#/components/schemas/FraudProcessingStatusEnum" + }, + "profile_id": { + "type": "string" + }, + "profile_version": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "inputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckInput" + } + }, + "modules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckModuleReport" + } + }, + "attribute_evidence": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "compound_evidence": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "risk": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckRisk" + } + }, + "network": { + "type": "object", + "additionalProperties": {} + }, + "enrichment": { + "type": "object", + "additionalProperties": {} + }, + "identity_relationship": { + "allOf": [ + { + "$ref": "#/components/schemas/FraudCheckIdentityRelationship" + } + ], + "nullable": true + }, + "recommendation": { + "$ref": "#/components/schemas/FraudRecommendationEnum" + }, + "next_action": { + "$ref": "#/components/schemas/FraudNextActionEnum" + }, + "decision_reasons": { + "type": "array", + "items": { + "type": "string" + } + }, + "coverage": { + "type": "object", + "additionalProperties": {} + }, + "usage": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "vendor_data": { + "type": "string", + "nullable": true + }, + "metadata": { + "nullable": true + } + }, + "required": [ + "attribute_evidence", + "check_id", + "client_reference", + "completed_at", + "compound_evidence", + "coverage", + "created_at", + "decision_reasons", + "inputs", + "modules", + "network", + "next_action", + "processing_status", + "profile_id", + "profile_version", + "recommendation", + "risk", + "usage" + ] + }, + "CaseReviewTeam": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseReviewTeamMember" + }, + "maxItems": 250 + }, + "is_active": { + "type": "boolean" + }, + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + } + }, + "CaseActionCaseEntityActionRequest": { + "type": "object", + "properties": { + "client_reference": { + "type": "string", + "format": "uuid" + }, + "retry": { + "type": "boolean", + "default": false + }, + "action": { + "$ref": "#/components/schemas/CaseActionActionEnum" + }, + "status": { + "type": "string", + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "note": { + "type": "string", + "nullable": true + } + }, + "required": [ + "action" + ] + }, + "RoutingPublicationPaginatedPolicyRevisionList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=400&limit=100" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=200&limit=100" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RoutingPublicationPolicyRevision" + } + } + } + }, + "FraudRecommendationEnum": { + "enum": [ + "approve", + "review", + "reject" + ], + "type": "string", + "description": "* `approve` - Approve\n* `review` - Review\n* `reject` - Reject" + }, + "CustomerMessagingCaseRequestPreviewResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid" + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "recipient": { + "type": "string", + "format": "email" + }, + "sender": { + "type": "string" + }, + "subject": { + "type": "string" + }, + "html": { + "type": "string" + }, + "requires_user_action": { + "type": "boolean" + }, + "preview_token": { + "type": "string" + } + }, + "required": [ + "expires_at", + "html", + "preview_token", + "recipient", + "request_id", + "requires_user_action", + "sender", + "subject" + ] + }, + "CustomerMessagingCaseCustomerResponseRequest": { + "type": "object", + "properties": { + "full_name": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "file": { + "type": "string", + "format": "binary", + "writeOnly": true + } + } + }, + "CaseCollaborationCaseNoteReply": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "comment": { + "type": "string", + "maxLength": 300 + } + }, + "required": [ + "actor_email", + "actor_name", + "comment", + "uuid" + ] + }, + "FraudCheckModuleReport": { + "type": "object", + "properties": { + "module": { + "$ref": "#/components/schemas/FraudExcludeEnum" + }, + "status": { + "$ref": "#/components/schemas/FraudStatusEnum" + }, + "reason": { + "type": "string", + "nullable": true + }, + "latency_ms": { + "type": "integer" + }, + "freshness": { + "type": "string", + "nullable": true + }, + "billable_units": { + "type": "integer" + } + }, + "required": [ + "billable_units", + "freshness", + "latency_ms", + "module", + "reason", + "status" + ] + }, + "CaseDuplicateEntityTypeEnum": { + "enum": [ + "session", + "business_session", + "transaction" + ], + "type": "string", + "description": "* `session` - Session\n* `business_session` - Business Session\n* `transaction` - Transaction" + }, + "FraudClientCredentialsErrorMessage": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "default": "Authentication failed: Invalid client access token." + } + } + }, + "FraudAvailabilityEnum": { + "enum": [ + "available", + "not_requested", + "no_reference", + "insufficient_history", + "unsupported_region", + "source_unavailable" + ], + "type": "string", + "description": "* `available` - Available\n* `not_requested` - Not Requested\n* `no_reference` - No Reference\n* `insufficient_history` - Insufficient History\n* `unsupported_region` - Unsupported Region\n* `source_unavailable` - Source Unavailable" + }, + "CasePublicationPolicyDraftResponse": { + "type": "object", + "properties": { + "draft": { + "allOf": [ + { + "$ref": "#/components/schemas/CasePublicationPolicyDraft" + } + ], + "nullable": true + } + }, + "required": [ + "draft" + ] + }, + "FraudCheckAttributesRequest": { + "type": "object", + "description": "Corroborating attributes. All optional; they widen module coverage.", + "properties": { + "first_name": { + "type": "string", + "maxLength": 128 + }, + "last_name": { + "type": "string", + "maxLength": 128 + }, + "full_name": { + "type": "string", + "maxLength": 256 + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "nationality": { + "type": "string", + "maxLength": 3 + }, + "gender": { + "type": "string", + "maxLength": 16 + }, + "address": { + "type": "string", + "maxLength": 512 + }, + "city": { + "type": "string", + "maxLength": 128 + }, + "state": { + "type": "string", + "maxLength": 128 + }, + "postal_code": { + "type": "string", + "maxLength": 32 + } + } + }, + "NetworkNote": { + "type": "object", + "required": [ + "uuid", + "actor_email", + "comment", + "client_reference", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "actor_email": { + "type": "string", + "format": "email", + "readOnly": true, + "description": "Authenticated author, assigned by the server." + }, + "comment": { + "type": "string", + "minLength": 1, + "maxLength": 10000 + }, + "client_reference": { + "type": "string", + "format": "uuid" + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + } + }, + "CaseActionEntityTypeEnum": { + "enum": [ + "user", + "business" + ], + "type": "string", + "description": "* `user` - user\n* `business` - business" + }, + "CaseRoutingCondition": { + "type": "object", + "properties": { + "field": { + "type": "string", + "enum": [ + "source", + "priority", + "tags" + ] + }, + "operator": { + "type": "string", + "enum": [ + "equals", + "in", + "contains" + ] + }, + "value": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": [ + "field", + "operator", + "value" + ], + "description": "Source and priority support equals or in with supported values. Tags support contains with one non-empty tag." + }, + "RoutingPublicationPolicyRevision": { + "type": "object", + "properties": { + "version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "snapshot": {}, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "published_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "published_at", + "snapshot", + "version" + ] + }, + "CasePublicationPolicyRevision": { + "type": "object", + "properties": { + "version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "snapshot": {}, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "published_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "published_at", + "snapshot", + "version" + ] + }, + "FraudCheckIdentityRelationship": { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/FraudResultEnum" + }, + "reference_type": { + "type": "string" + }, + "country": { + "type": "string" + }, + "availability": { + "$ref": "#/components/schemas/FraudAvailabilityEnum" + }, + "reason_codes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "availability", + "country", + "reason_codes", + "reference_type", + "result" + ] + }, + "CasePublicationPaginatedPolicyRevisionList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=400&limit=100" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=200&limit=100" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CasePublicationPolicyRevision" + } + } + } + }, + "CaseDuplicateBlankEnum": { + "enum": [ + "" + ] + }, + "FraudCheckPlan": { + "type": "object", + "properties": { + "profile_id": { + "type": "string" + }, + "profile_version": { + "type": "string" + }, + "modules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckPlannedModule" + } + }, + "estimated_price": { + "type": "string", + "nullable": true + }, + "currency": { + "type": "string", + "nullable": true + }, + "is_sandbox": { + "type": "boolean" + }, + "can_afford": { + "type": "boolean" + } + }, + "required": [ + "can_afford", + "currency", + "estimated_price", + "is_sandbox", + "modules", + "profile_id", + "profile_version" + ] + }, + "CaseReviewTeamUpdate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseReviewTeamMember" + }, + "maxItems": 250 + }, + "is_active": { + "type": "boolean" + } + } + }, + "RoutingPublicationPolicyDraft": { + "type": "object", + "properties": { + "revision": { + "type": "string", + "format": "uuid" + }, + "base_version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "changes": {}, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "base_version", + "updated_at" + ] + }, + "FraudCheckRequestRequest": { + "type": "object", + "properties": { + "client_reference": { + "type": "string", + "minLength": 1, + "description": "Your own reference for this check. Doubles as the idempotency key: reusing it with the same body returns the original check without running or billing the modules again; reusing it with a different body returns 409.", + "maxLength": 255 + }, + "profile": { + "type": "string", + "minLength": 1, + "default": "standard", + "description": "Orchestration profile id. Defaults to the Didit `standard` profile.", + "maxLength": 64 + }, + "profile_version": { + "type": "string", + "minLength": 1, + "description": "Pin a frozen profile version. Defaults to the current version of the profile.", + "maxLength": 32 + }, + "subject": { + "$ref": "#/components/schemas/FraudCheckSubjectRequest" + }, + "context": { + "$ref": "#/components/schemas/FraudCheckContextRequest" + }, + "checks": { + "$ref": "#/components/schemas/FraudCheckModuleSelectionRequest" + }, + "vendor_data": { + "type": "string", + "maxLength": 1000 + }, + "metadata": {} + }, + "required": [ + "subject" + ] + }, + "CaseDuplicateCaseSubjectInputRequest": { + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/CaseDuplicateTypeEnum" + }, + "uuid": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "uuid" + ] + }, + "CaseReviewTeamMember": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "format": "uuid" + }, + "availability": { + "type": "string", + "enum": [ + "available", + "unavailable" + ], + "default": "available" + }, + "capacity": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 10 + }, + "skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "available_from": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "available_until": { + "type": "string", + "format": "date-time", + "nullable": true + } + }, + "required": [ + "user_id" + ] + }, + "CaseRoutingPreviewRequest": { + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ] + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ], + "default": "MEDIUM" + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 50 + }, + "rule_uuid": { + "type": "string", + "format": "uuid" + }, + "draft": { + "$ref": "#/components/schemas/CaseRoutingRuleCreate" + } + }, + "required": [ + "source" + ] + }, + "FraudExcludeEnum": { + "enum": [ + "normalization", + "email_risk", + "email_social", + "phone_social", + "phone_risk", + "ip_risk", + "database_validation", + "networks_same_org", + "networks_cross_org", + "biometric_identity" + ], + "type": "string", + "description": "* `normalization` - Normalization\n* `email_risk` - Email Risk\n* `email_social` - Email Social\n* `phone_social` - Phone Social\n* `phone_risk` - Phone Risk\n* `ip_risk` - Ip Risk\n* `database_validation` - Database Validation\n* `networks_same_org` - Networks Same Org\n* `networks_cross_org` - Networks Cross Org\n* `biometric_identity` - Biometric Identity" + }, + "CaseDuplicateCaseLinkCreateRequest": { + "type": "object", + "properties": { + "entity_type": { + "$ref": "#/components/schemas/CaseDuplicateEntityTypeEnum" + }, + "entity_uuid": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "entity_type", + "entity_uuid" + ] + }, + "CaseDuplicateStatusEnum": { + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ], + "type": "string", + "description": "* `OPEN` - Open\n* `AWAITING_USER` - Awaiting user\n* `RESOLVED` - Resolved" + }, + "FraudBandEnum": { + "enum": [ + "low", + "medium", + "high", + "unknown" + ], + "type": "string", + "description": "* `low` - Low\n* `medium` - Medium\n* `high` - High\n* `unknown` - Unknown" + }, + "CaseDuplicatePriorityEnum": { + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ], + "type": "string", + "description": "* `LOW` - Low\n* `MEDIUM` - Medium\n* `HIGH` - High" + }, + "FraudStatusEnum": { + "enum": [ + "planned", + "completed", + "skipped", + "failed" + ], + "type": "string", + "description": "* `planned` - Planned\n* `completed` - Completed\n* `skipped` - Skipped\n* `failed` - Failed" + }, + "FraudCheckInput": { + "type": "object", + "properties": { + "reference": { + "type": "string", + "description": "Redacted handle for the submitted identifier." + }, + "type": { + "$ref": "#/components/schemas/FraudTypeEnum" + }, + "namespace": { + "type": "object", + "additionalProperties": {} + }, + "quality": { + "type": "string" + }, + "normalization": { + "type": "object", + "additionalProperties": {} + }, + "availability": { + "$ref": "#/components/schemas/FraudAvailabilityEnum" + } + }, + "required": [ + "availability", + "namespace", + "normalization", + "quality", + "reference", + "type" + ] + }, + "CustomerMessagingPurposesEnum": { + "enum": [ + "identity_document", + "proof_of_address", + "business_document", + "confirm_details", + "review_in_progress" + ], + "type": "string", + "description": "* `identity_document` - identity_document\n* `proof_of_address` - proof_of_address\n* `business_document` - business_document\n* `confirm_details` - confirm_details\n* `review_in_progress` - review_in_progress" + }, + "FraudTypeEnum": { + "enum": [ + "email", + "phone", + "national_id", + "tax_number", + "passport_number", + "driving_licence_number", + "residence_permit_number", + "voter_id", + "external_id" + ], + "type": "string", + "description": "* `email` - Email\n* `phone` - Phone\n* `national_id` - National Id\n* `tax_number` - Tax Number\n* `passport_number` - Passport Number\n* `driving_licence_number` - Driving Licence Number\n* `residence_permit_number` - Residence Permit Number\n* `voter_id` - Voter Id\n* `external_id` - External Id" + }, + "CaseDuplicateCaseSubjectInput": { + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/CaseDuplicateTypeEnum" + }, + "uuid": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "uuid" + ] + }, + "CaseReviewTeamCreate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseReviewTeamMember" + }, + "maxItems": 250 + }, + "is_active": { + "type": "boolean" + } + }, + "required": [ + "name", + "members" + ] + }, + "CaseDuplicateCaseDuplicateQueryRequest": { + "type": "object", + "properties": { + "subject": { + "$ref": "#/components/schemas/CaseDuplicateCaseSubjectInputRequest" + }, + "links": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseDuplicateCaseLinkCreateRequest" + } + }, + "offset": { + "type": "integer", + "maximum": 1000000, + "minimum": 0, + "default": 0 + } + } + }, + "CaseDuplicateResolutionEnum": { + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT" + ], + "type": "string", + "description": "* `FALSE_POSITIVE` - False positive\n* `VALID_THREAT` - Valid threat" + }, + "CasePublicationPolicyDraftWriteRequest": { + "type": "object", + "properties": { + "expected_version": { + "type": "integer", + "minimum": 0 + }, + "expected_revision": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "changes": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "changes", + "expected_revision", + "expected_version" + ] + }, + "FraudCheckSubjectRequest": { + "type": "object", + "properties": { + "selfie": { + "type": "string", + "minLength": 1, + "description": "Base64 JPEG, PNG or WebP selfie, up to 6 MB. No reference image or URL.", + "maxLength": 8388672 + }, + "identifiers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckIdentifierRequest" + }, + "description": "One or more typed identifiers. One is enough.", + "maxItems": 10, + "minItems": 1 + }, + "attributes": { + "$ref": "#/components/schemas/FraudCheckAttributesRequest" + } + }, + "required": [ + "identifiers" + ] + }, + "RoutingPublicationRoutingCondition": { + "type": "object", + "properties": { + "field": { + "$ref": "#/components/schemas/RoutingPublicationFieldEnum" + }, + "operator": { + "$ref": "#/components/schemas/RoutingPublicationOperatorEnum" + }, + "value": {} + }, + "required": [ + "field", + "operator", + "value" + ] + }, + "CaseActionCaseActionReceipt": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "client_reference": { + "type": "string", + "format": "uuid" + }, + "action": { + "type": "string", + "maxLength": 32 + }, + "status": { + "$ref": "#/components/schemas/CaseActionStatusEnum" + }, + "result": {}, + "error": { + "type": "string", + "maxLength": 255 + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "attempts": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "action", + "client_reference", + "created_at", + "status", + "updated_at", + "uuid" + ] + }, + "FraudCheckContextRequest": { + "type": "object", + "properties": { + "ip_address": { + "type": "string", + "minLength": 1 + }, + "user_agent": { + "type": "string", + "maxLength": 512 + }, + "country": { + "type": "string", + "description": "Country the subject claims to be in. Contextual only - never proof of identity ownership.", + "maxLength": 3 + }, + "purpose": { + "type": "string", + "maxLength": 128 + }, + "consent_obtained": { + "type": "boolean" + } + } + }, + "CustomerMessagingCaseCustomerResponseResult": { + "type": "object", + "properties": { + "responded_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "responded_at" + ] + }, + "CustomerMessagingCaseRequestContact": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "email": { + "type": "string", + "format": "email" + }, + "verified_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "email", + "uuid", + "verified_at" + ] + }, + "FraudClientCredentialsForbiddenErrorMessage": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "default": "Forbidden - User doesn't have permission" + } + } + }, + "CasePublicationAssignmentMethodEnum": { + "enum": [ + "MANUAL", + "AUTOMATIC" + ], + "type": "string", + "description": "* `MANUAL` - Manual\n* `AUTOMATIC` - Automatic" + }, + "CaseDuplicateNullEnum": { + "enum": [ + null + ] + }, + "CustomerMessagingLanguageEnum": { + "enum": [ + "en", + "es", + "pt" + ], + "type": "string", + "description": "* `en` - en\n* `es` - es\n* `pt` - pt" + }, + "CaseDuplicateCaseFraudSourceContextRequest": { + "type": "object", + "properties": { + "check_id": { + "type": "string", + "format": "uuid" + }, + "request_id": { + "type": "string", + "format": "uuid" + }, + "source_section": { + "type": "string", + "minLength": 1, + "pattern": "^[A-Z][A-Z0-9_:-]{0,63}$" + } + }, + "required": [ + "check_id", + "request_id", + "source_section" + ] + }, + "FraudCheckPlannedModule": { + "type": "object", + "properties": { + "module": { + "$ref": "#/components/schemas/FraudExcludeEnum" + }, + "available": { + "type": "boolean" + }, + "availability_reason": { + "type": "string", + "nullable": true + }, + "will_run": { + "type": "boolean" + }, + "selection_reason": { + "type": "string", + "nullable": true + }, + "estimated_price": { + "type": "string", + "nullable": true + } + }, + "required": [ + "availability_reason", + "available", + "estimated_price", + "module", + "selection_reason", + "will_run" + ] + }, + "CustomerMessagingCaseRequestOptions": { + "type": "object", + "properties": { + "contacts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestContact" + } + }, + "purposes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerMessagingPurposesEnum" + } + }, + "languages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerMessagingLanguagesEnum" + } + } + }, + "required": [ + "contacts", + "languages", + "purposes" + ] + }, + "CaseCollaborationPaginatedCaseNoteList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=400&limit=100" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=200&limit=100" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseCollaborationCaseNote" + } + } + } + }, + "FraudProcessingStatusEnum": { + "enum": [ + "pending", + "running", + "completed", + "failed" + ], + "type": "string", + "description": "* `pending` - Pending\n* `running` - Running\n* `completed` - Completed\n* `failed` - Failed" + }, + "CasePublicationCaseBlueprint": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "name": { + "type": "string", + "maxLength": 24 + }, + "description": { + "type": "string", + "nullable": true + }, + "assignment_method": { + "$ref": "#/components/schemas/CasePublicationAssignmentMethodEnum" + }, + "assignee_pool": {}, + "max_active_cases": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true + }, + "escalation_enabled": { + "type": "boolean" + }, + "escalation_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "escalation_blueprint_name": { + "type": "string", + "readOnly": true + }, + "transfer_enabled": { + "type": "boolean" + }, + "four_eyes_checker_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "four_eyes_checker_blueprint_name": { + "type": "string", + "readOnly": true + }, + "require_resolution_note": { + "type": "boolean" + }, + "content_config": {}, + "version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "default_deadline_hours": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "preset_key": { + "type": "string", + "nullable": true, + "maxLength": 64 + }, + "creation_sources": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + }, + "readOnly": true + }, + "receives_escalation_from": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + }, + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "created_at", + "creation_sources", + "escalation_blueprint_name", + "four_eyes_checker_blueprint_name", + "name", + "receives_escalation_from", + "updated_at", + "uuid" + ] + }, + "CaseRoutingRuleUpdate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "priority": { + "type": "integer", + "minimum": 0 + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseRoutingCondition" + }, + "maxItems": 20 + }, + "required_skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "blueprint": { + "type": "string", + "format": "uuid" + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "expected_version": { + "type": "integer", + "minimum": 1, + "writeOnly": true, + "description": "Version opened by the editor. When supplied, stale writes return 409 without changing the rule." + } + } + }, + "CustomerMessagingCaseRequestPreviewRequest": { + "type": "object", + "properties": { + "contact_id": { + "type": "string", + "format": "uuid" + }, + "purpose": { + "$ref": "#/components/schemas/CustomerMessagingPurposeEnum" + }, + "language": { + "allOf": [ + { + "$ref": "#/components/schemas/CustomerMessagingLanguageEnum" + } + ], + "default": "en" + } + }, + "required": [ + "contact_id", + "purpose" + ] + }, + "CaseDuplicateCaseFraudSourceContext": { + "type": "object", + "properties": { + "check_id": { + "type": "string", + "format": "uuid" + }, + "request_id": { + "type": "string", + "format": "uuid" + }, + "source_section": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_:-]{0,63}$" + } + }, + "required": [ + "check_id", + "request_id", + "source_section" + ] + }, + "RoutingPublicationOperatorEnum": { + "enum": [ + "equals", + "in", + "contains" + ], + "type": "string", + "description": "* `equals` - equals\n* `in` - in\n* `contains` - contains" + }, + "CaseRoutingPreview": { + "type": "object", + "properties": { + "rule_uuid": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "rule_name": { + "type": "string", + "nullable": true + }, + "rule_version": { + "type": "integer", + "nullable": true + }, + "blueprint_uuid": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "team_uuid": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "assignee_user_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "reason": { + "type": "string" + }, + "candidates": { + "type": "array", + "items": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "format": "uuid" + }, + "eligible": { + "type": "boolean" + }, + "active_cases": { + "type": "integer", + "nullable": true + }, + "reason": { + "type": "string", + "nullable": true + } + } + } + }, + "matched_rules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "priority": { + "type": "integer" + } + } + } + } + } + }, + "FraudDimensionEnum": { + "enum": [ + "identity", + "contact", + "device_network", + "behavioral", + "fraud_network" + ], + "type": "string", + "description": "* `identity` - Identity\n* `contact` - Contact\n* `device_network` - Device Network\n* `behavioral` - Behavioral\n* `fraud_network` - Fraud Network" + }, + "FraudBadRequestErrorMessage": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "default": "Bad Request - Invalid request data" + } + } + }, + "FraudNextActionEnum": { + "enum": [ + "none", + "manual_review", + "step_up", + "resubmit_capture" + ], + "type": "string", + "description": "* `none` - None\n* `manual_review` - Manual Review\n* `step_up` - Step Up\n* `resubmit_capture` - Resubmit Capture" + }, + "RoutingPublicationPolicyDraftPreconditionRequest": { + "type": "object", + "properties": { + "expected_version": { + "type": "integer", + "minimum": 0 + }, + "expected_revision": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "required": [ + "expected_revision", + "expected_version" + ] + }, + "FraudCheckRisk": { + "type": "object", + "properties": { + "dimension": { + "$ref": "#/components/schemas/FraudDimensionEnum" + }, + "score": { + "type": "integer", + "nullable": true + }, + "band": { + "$ref": "#/components/schemas/FraudBandEnum" + }, + "availability": { + "$ref": "#/components/schemas/FraudAvailabilityEnum" + }, + "requested_but_unavailable": { + "type": "boolean", + "description": "True when the request supplied everything this dimension needed and the platform still could not answer it. Such a check is never recommended for approval on the strength of the dimensions that did answer." + }, + "reason_codes": { + "type": "array", + "items": { + "type": "string" + } + }, + "evidence_indexes": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "availability", + "band", + "dimension", + "evidence_indexes", + "reason_codes", + "requested_but_unavailable", + "score" + ] + }, + "CaseDuplicateSourceEnum": { + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ], + "type": "string", + "description": "* `MANUAL` - Manual\n* `AML_SCREENING` - AML screening\n* `TRANSACTION_RULE` - Transaction rule\n* `WORKFLOW_BUILDER` - Workflow builder" + }, + "CaseCollaborationPatchedCaseNoteUpdateRequest": { + "type": "object", + "properties": { + "is_pinned": { + "type": "boolean" + } + } + }, + "CustomerMessagingPurposeEnum": { + "enum": [ + "identity_document", + "proof_of_address", + "business_document", + "confirm_details", + "review_in_progress" + ], + "type": "string", + "description": "* `identity_document` - identity_document\n* `proof_of_address` - proof_of_address\n* `business_document` - business_document\n* `confirm_details` - confirm_details\n* `review_in_progress` - review_in_progress" + }, + "CaseDuplicateTypeEnum": { + "enum": [ + "user", + "business" + ], + "type": "string", + "description": "* `user` - user\n* `business` - business" + }, + "CaseCollaborationCaseNoteCreateRequest": { + "type": "object", + "properties": { + "comment": { + "type": "string", + "minLength": 1, + "maxLength": 10000 + }, + "reply_to": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "client_reference": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email", + "minLength": 1 + } + }, + "is_internal": { + "type": "boolean", + "default": false + }, + "attachments": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "required": [ + "comment" + ] + }, + "CustomerMessagingLanguagesEnum": { + "enum": [ + "en", + "es", + "pt" + ], + "type": "string", + "description": "* `en` - en\n* `es` - es\n* `pt` - pt" + }, + "CustomerMessagingCaseRequestConfirmRequest": { + "type": "object", + "properties": { + "contact_id": { + "type": "string", + "format": "uuid" + }, + "purpose": { + "$ref": "#/components/schemas/CustomerMessagingPurposeEnum" + }, + "language": { + "allOf": [ + { + "$ref": "#/components/schemas/CustomerMessagingLanguageEnum" + } + ], + "default": "en" + }, + "request_id": { + "type": "string", + "format": "uuid" + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "preview_token": { + "type": "string", + "minLength": 1, + "maxLength": 512 + } + }, + "required": [ + "contact_id", + "expires_at", + "preview_token", + "purpose", + "request_id" + ] + }, + "CaseRoutingRule": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "priority": { + "type": "integer", + "minimum": 0 + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseRoutingCondition" + }, + "maxItems": 20 + }, + "required_skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "blueprint": { + "type": "string", + "format": "uuid" + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "version": { + "type": "integer", + "readOnly": true + }, + "blueprint_name": { + "type": "string", + "readOnly": true + }, + "team_name": { + "type": "string", + "nullable": true, + "readOnly": true + } + } + }, + "FraudResultEnum": { + "enum": [ + "match", + "mismatch", + "inconclusive", + "no_reference" + ], + "type": "string", + "description": "* `match` - Match\n* `mismatch` - Mismatch\n* `inconclusive` - Inconclusive\n* `no_reference` - No Reference" + }, + "CaseRoutingRuleCreate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "priority": { + "type": "integer", + "minimum": 0 + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseRoutingCondition" + }, + "maxItems": 20 + }, + "required_skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "blueprint": { + "type": "string", + "format": "uuid" + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "publish": { + "type": "boolean", + "writeOnly": true, + "description": "Set false to save a never-published draft. The record remains inactive at version 0 until the draft is explicitly published. Omitting this field preserves immediate creation for existing integrations." + } + }, + "required": [ + "name", + "conditions", + "blueprint" + ] + }, + "FraudCheckIdentifierRequest": { + "type": "object", + "properties": { + "type": { + "allOf": [ + { + "$ref": "#/components/schemas/FraudTypeEnum" + } + ], + "description": "Identifier type. Determines which namespace fields are required.\n\n* `email` - Email\n* `phone` - Phone\n* `national_id` - National Id\n* `tax_number` - Tax Number\n* `passport_number` - Passport Number\n* `driving_licence_number` - Driving Licence Number\n* `residence_permit_number` - Residence Permit Number\n* `voter_id` - Voter Id\n* `external_id` - External Id" + }, + "value": { + "type": "string", + "minLength": 1, + "description": "The identifier value, in any customary format.", + "maxLength": 255 + }, + "country": { + "type": "string", + "description": "ISO-3166 alpha-2 or alpha-3 issuing country. Required for government identifier types: the same digits mean different people in different countries.", + "maxLength": 3 + }, + "issuer": { + "type": "string", + "description": "Optional issuing authority or sub-national issuer, when the country alone is not unique.", + "maxLength": 128 + } + }, + "required": [ + "type", + "value" + ] + }, + "CaseActionActionEnum": { + "enum": [ + "set_status", + "add_tags", + "add_note", + "add_to_blocklist" + ], + "type": "string", + "description": "* `set_status` - set_status\n* `add_tags` - add_tags\n* `add_note` - add_note\n* `add_to_blocklist` - add_to_blocklist" + }, + "CaseActionCaseActionReceiptPage": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next_offset": { + "type": "integer", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseActionCaseActionReceipt" + } + } + }, + "required": [ + "count", + "next_offset", + "results" + ] + }, + "CaseActionCaseEntityActionResult": { + "type": "object", + "properties": { + "receipt": { + "$ref": "#/components/schemas/CaseActionCaseActionReceipt" + }, + "entity_type": { + "$ref": "#/components/schemas/CaseActionEntityTypeEnum" + }, + "entity_uuid": { + "type": "string", + "format": "uuid" + }, + "action": { + "type": "string" + }, + "previous_status": { + "type": "string" + }, + "new_status": { + "type": "string" + }, + "tags_added": { + "type": "array", + "items": { + "type": "string" + } + }, + "comment_uuid": { + "type": "string", + "format": "uuid" + }, + "blocklisted": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "CustomerMessagingCaseRequest": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "purpose": { + "type": "string", + "readOnly": true + }, + "language": { + "type": "string", + "readOnly": true + }, + "recipient": { + "type": "string", + "format": "email", + "readOnly": true + }, + "expires_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "actor_email": { + "type": "string", + "format": "email", + "readOnly": true, + "nullable": true + }, + "delivery_status": { + "type": "string", + "readOnly": true + }, + "delivered_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "nullable": true + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "nullable": true + }, + "responded_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "nullable": true + }, + "response": { + "readOnly": true, + "nullable": true + }, + "has_attachment": { + "type": "boolean", + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "actor_email", + "cancelled_at", + "created_at", + "delivered_at", + "delivery_status", + "expires_at", + "has_attachment", + "language", + "purpose", + "recipient", + "responded_at", + "response", + "uuid" + ] + }, + "CustomerMessagingCaseRequestPublic": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid" + }, + "purpose": { + "$ref": "#/components/schemas/CustomerMessagingPurposeEnum" + }, + "language": { + "$ref": "#/components/schemas/CustomerMessagingLanguageEnum" + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "responded_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "copy": { + "type": "object", + "additionalProperties": {} + }, + "organization_name": { + "type": "string" + }, + "is_white_label": { + "type": "boolean" + }, + "customization": { + "type": "object", + "additionalProperties": {}, + "nullable": true + } + }, + "required": [ + "copy", + "customization", + "expires_at", + "is_white_label", + "language", + "organization_name", + "purpose", + "request_id", + "responded_at" + ] + }, + "RoutingPublicationFieldEnum": { + "enum": [ + "source", + "priority", + "tags" + ], + "type": "string", + "description": "* `source` - source\n* `priority` - priority\n* `tags` - tags" + }, + "RoutingPublicationCaseRoutingRule": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "priority": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RoutingPublicationRoutingCondition" + } + }, + "required_skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "blueprint": { + "type": "string", + "format": "uuid" + }, + "blueprint_name": { + "type": "string", + "readOnly": true + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "team_name": { + "type": "string", + "readOnly": true + }, + "is_active": { + "type": "boolean" + }, + "version": { + "type": "integer", + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "blueprint", + "blueprint_name", + "conditions", + "created_at", + "name", + "team_name", + "updated_at", + "uuid", + "version" + ] + }, + "CaseActionStatusEnum": { + "enum": [ + "APPLIED", + "FAILED" + ], + "type": "string", + "description": "* `APPLIED` - Applied\n* `FAILED` - Failed" + }, + "FraudCheckModuleSelectionRequest": { + "type": "object", + "properties": { + "include": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudExcludeEnum" + } + }, + "exclude": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudExcludeEnum" + } + } + } + }, + "SignatureField": { + "type": "object", + "description": "A field to place on a session document: which signer fills it, how, and where. Give either a position (`page`, `x`, `y`, `width` and `height`) or an `anchor`. Positions are fractions of the page as it's displayed, from its top-left corner, rounded to 4 decimals, and the field must fit inside the page. A document takes up to 200 fields and one signer up to 500, and every role with fields on a document needs a `signature` or `initials` field there. See [Fields on the document](/core-technology/electronic-signature/overview#fields-on-the-document).", + "required": [ + "key", + "type" + ], + "properties": { + "key": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]{0,63}$", + "description": "Names the field in the results and in `sign.field_values`. Unique within the document." + }, + "type": { + "type": "string", + "enum": [ + "signature", + "initials", + "full_name", + "date_signed", + "text", + "checkbox", + "email", + "data" + ], + "description": "What the field holds. The signer applies their adopted `signature` and `initials`, types `text`, ticks a `checkbox`, and types an `email` when the session has no verified email. Didit fills `full_name` (the verified name), `date_signed` (the date of signing, in the signer's time zone), `data` (from `prefill`) and a verified `email`, and shows them read-only. There is no date picker type: use `text`." + }, + "role": { + "type": "string", + "enum": [ + "verified_person", + "legal_representative", + "key_people" + ], + "description": "Who fills the field, with the `signature_signer` values. A step fills and stamps only the fields of its own signer. Defaults to the step's signer." + }, + "page": { + "type": "integer", + "minimum": 1, + "description": "Page number, from 1 to the document's page count. For a Word document, a page of the converted PDF." + }, + "x": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Left edge, as a fraction of the page width." + }, + "y": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Top edge, as a fraction of the page height." + }, + "width": { + "type": "number", + "minimum": 0.02, + "maximum": 1, + "description": "Width, as a fraction of the page width. A signature is at least `0.12` wide." + }, + "height": { + "type": "number", + "minimum": 0.012, + "maximum": 1, + "description": "Height, as a fraction of the page height. A signature is at least `0.035` high." + }, + "anchor": { + "$ref": "#/components/schemas/SignatureFieldAnchor" + }, + "required": { + "type": "boolean", + "description": "`signature` and `initials` are required unless you set `false`. `full_name`, `date_signed` and `data` are always required. The signer can't finish a document until its required fields have a value, and a required checkbox must be ticked." + }, + "label": { + "type": "string", + "minLength": 1, + "maxLength": 80, + "description": "Shown to the signer and in the results. Defaults from the type." + }, + "placeholder": { + "type": "string", + "nullable": true, + "maxLength": 80, + "description": "`text` and `email` only." + }, + "validation": { + "$ref": "#/components/schemas/SignatureFieldValidation" + }, + "prefill": { + "type": "string", + "nullable": true, + "description": "Where the value comes from: `identity.full_name`, `identity.first_name`, `identity.last_name`, `identity.date_of_birth` (`DD/MM/YYYY`), `identity.nationality`, `identity.document_number`, `identity.document_type`, `identity.issuing_country`, `identity.address`, `contact.email`, `contact.phone`, `company.name`, `company.registration_number`, `company.address`, `company.country` (KYB), `session.vendor_data`, `metadata.` (a key of the session's `metadata`) or `api.` (a key of `sign.field_values`). Required for `data`. `full_name` always uses `identity.full_name` and `email` uses `contact.email`. The values of `data`, `full_name` and verified `email` fields are fixed when the signer starts signing." + }, + "editable_when_prefilled": { + "type": "boolean", + "default": false, + "description": "`text` and `email` only: the signer can change a pre-filled value." + }, + "checked_by_default": { + "type": "boolean", + "default": false, + "description": "`checkbox` only: the box starts ticked." + }, + "date_format": { + "type": "string", + "enum": [ + "DD/MM/YYYY", + "MM/DD/YYYY", + "YYYY-MM-DD", + "D MMMM YYYY" + ], + "default": "DD/MM/YYYY", + "description": "`date_signed` only." + } + } + }, + "SessionSignDocument": { + "type": "object", + "required": [ + "file_name", + "content_base64" + ], + "properties": { + "title": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "Title shown to the signer. Defaults to `file_name`." + }, + "file_name": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "File name, ending in `.pdf` or `.docx`." + }, + "content_base64": { + "type": "string", + "format": "byte", + "description": "The file, base64-encoded. A PDF can have up to 20 MB and 100 pages, without a password, scripts or attached files. A Word file (`.docx`) can have up to 20 MB and is converted to PDF while the request runs, in at most 25 seconds; the PDF then has the same limits. Up to 5 Word files per session. A Word file with macros, tracked changes, links to external content or embedded objects is refused with `signature_document_not_supported`, and one that can't be converted returns `signature_document_conversion_failed`, each with a `reason`." + }, + "fields": { + "type": "array", + "description": "Optional fields to place on this document, up to 200. Pages and positions refer to the PDF, or to the converted PDF for a Word file. Without fields, the signer reads the document and agrees to it.", + "items": { + "$ref": "#/components/schemas/SignatureField" + }, + "maxItems": 200 + } + } + }, + "SignatureResult": { + "type": "object", + "description": "One signer's result for one E-signature step. See the [E-signature report](/core-technology/electronic-signature/report-electronic-signature).", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Pass it as `signature_id` to download this signer's evidence or validate their signature." + }, + "node_id": { + "type": "string", + "description": "The workflow step that produced it." + }, + "status": { + "type": "string", + "enum": [ + "intro", + "review", + "code", + "signing", + "signed", + "declined" + ], + "description": "Where the signer is in the step. Only `signed` means the documents are signed." + }, + "verdict": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "In Review", + "Declined" + ], + "description": "The step's outcome. A warning sets it through its rule; custom status rules apply after." + }, + "level": { + "type": "string", + "enum": [ + "simple", + "aes", + "qes" + ], + "description": "`simple` (default) or `aes`. `qes` (Qualified Electronic Signature, QES) is an add-on that is not available yet." + }, + "format": { + "type": "string", + "enum": [ + "pdf", + "pades" + ], + "description": "`pdf` for a Simple signature, `pades` for Advanced (AES)." + }, + "profile": { + "type": "string", + "nullable": true, + "description": "`PAdES-B-LT` once an Advanced (AES) signature is signed, otherwise null." + }, + "signer": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "description": "The signer's name. Verified (from the approved ID verification or the session) except at the Simple level without a verified session name, where it is the name the signer typed and is not verified." + }, + "capacity": { + "type": "string", + "enum": [ + "self", + "company" + ] + }, + "company": { + "type": "string", + "nullable": true + }, + "role": { + "type": "string", + "nullable": true, + "description": "The key person's designation, as declared." + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The key person's structured roles, as declared for the company." + }, + "identity_session_id": { + "type": "string", + "format": "uuid", + "description": "The KYC session that verified the signer." + } + } + }, + "identity_used": { + "type": "object", + "description": "The verified identity the signature relies on. Empty for a Simple signature with no verified identity.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid" + }, + "kyc_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "verified": { + "type": "boolean" + }, + "expected_name": { + "type": "string", + "nullable": true, + "description": "The name the signer had to match." + } + } + }, + "certificate": { + "type": "object", + "nullable": true, + "description": "The certificate the documents were signed with. `null` until signed, and always `null` for a Simple signature.", + "properties": { + "issuer": { + "type": "string" + }, + "issued_to": { + "type": "string" + }, + "serial": { + "type": "string", + "description": "Serial number, hexadecimal." + }, + "valid_from": { + "type": "string", + "format": "date-time" + }, + "valid_until": { + "type": "string", + "format": "date-time" + } + } + }, + "confirmation": { + "type": "object", + "properties": { + "channel": { + "type": "string", + "nullable": true, + "enum": [ + "email", + "sms" + ], + "description": "`null` for a Simple signature, which has no code." + }, + "destination": { + "type": "string", + "nullable": true, + "description": "The masked contact the code went to." + }, + "confirmed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "attempts": { + "type": "integer", + "description": "Codes entered, the right one included." + } + } + }, + "timestamp": { + "type": "object", + "nullable": true, + "description": "The RFC 3161 timestamp in the signature, issued by Didit. `null` until signed, and always `null` for a Simple signature.", + "properties": { + "time": { + "type": "string", + "format": "date-time" + }, + "qualified": { + "type": "boolean", + "description": "Always `false` for AES." + }, + "valid": { + "type": "boolean" + } + } + }, + "documents": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The document on this signer's copy. Use it to download the signed file." + }, + "uuid": { + "type": "string", + "format": "uuid", + "description": "Same as `id`." + }, + "title": { + "type": "string" + }, + "file_name": { + "type": "string" + }, + "pages": { + "type": "integer", + "description": "Pages of the document signed. For Word, of the converted PDF." + }, + "size_bytes": { + "type": "integer" + }, + "sha256_original": { + "type": "string", + "description": "SHA-256 of the document as the signer was shown it." + }, + "sha256_signed": { + "type": "string", + "nullable": true, + "description": "SHA-256 of the signed PDF, once signed." + }, + "status": { + "type": "string", + "enum": [ + "signed", + "declined", + "not_signed", + "not_reached" + ], + "description": "`not_signed`: opened but not signed. `not_reached`: never opened." + }, + "opened_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "read_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the signer reached the end of a document without fields, when the step requires it." + }, + "read_to_end": { + "type": "boolean" + }, + "consented_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the signer agreed to a document without fields, or finished the fields of a document with fields." + }, + "consent": { + "type": "boolean" + }, + "signed_file": { + "type": "string", + "nullable": true, + "description": "Path of the signed PDF, once signed. See [Download a signed document](/sessions-api/electronic-signature/download-signed-document)." + }, + "mode": { + "type": "string", + "enum": [ + "fields", + "read_and_agree" + ], + "description": "`fields` for a document with fields, `read_and_agree` for a document without." + }, + "source_format": { + "type": "string", + "enum": [ + "pdf", + "docx" + ], + "description": "`pdf`, or `docx` for a Word document Didit converted to PDF." + }, + "conversion": { + "type": "object", + "nullable": true, + "description": "`null` for a PDF. For a Word document, the original file and the conversion.", + "properties": { + "from": { + "type": "string", + "enum": [ + "docx" + ] + }, + "file_name": { + "type": "string", + "description": "Your original Word file name." + }, + "sha256": { + "type": "string", + "description": "SHA-256 of your original Word file." + }, + "size_bytes": { + "type": "integer", + "description": "Size of your original Word file." + }, + "converter": { + "type": "string", + "description": "Version of the converter that produced the PDF." + } + } + }, + "fields": { + "type": "array", + "description": "Fields on the document, with their values. Empty for a document without fields.", + "items": { + "$ref": "#/components/schemas/SignatureFieldResult" + } + } + } + } + }, + "adopted_signature": { + "type": "object", + "nullable": true, + "description": "The signature and initials the signer adopted for signature and initials fields. `null` until the signer adopts one.", + "properties": { + "method": { + "type": "string", + "enum": [ + "typed", + "drawn" + ], + "description": "`typed`: the verified name in a handwriting style. `drawn`: drawn with a finger or a mouse." + }, + "font": { + "type": "string", + "nullable": true, + "description": "The handwriting style of a typed signature. `null` when drawn." + }, + "adopted_at": { + "type": "string", + "format": "date-time" + } + } + }, + "electronic_signature_consent_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the signer agreed, on the review screen, to sign the documents electronically. Set when any document has fields." + }, + "evidence_file": { + "type": "string", + "nullable": true, + "description": "Path of the evidence PDF, once signed. See [Download the signature evidence](/sessions-api/electronic-signature/download-evidence)." + }, + "warnings": { + "type": "array", + "description": "Warnings raised for this signer. See [E-signature warnings](/core-technology/electronic-signature/warnings-electronic-signature).", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": [ + "SIGNATURE_DECLINED", + "SIGNATURE_OTP_FAILED", + "SIGNATURE_NAME_MISMATCH", + "SIGNATURE_IDENTITY_NOT_VERIFIED", + "SIGNATURE_DOCUMENT_CHANGED", + "SIGNATURE_FIELD_DATA_MISSING" + ] + }, + "node_id": { + "type": "string" + } + } + } + }, + "validation": { + "type": "object", + "nullable": true, + "description": "The last cryptographic check of the signed documents. `null` until the documents are signed. If Didit can't confirm the outcome of signing, it's `{\"valid\": null, \"reason\": \"signature_provider_outcome_unknown\", \"checked_at\": ...}` and `status` stays `signing`: treat it as not signed.", + "properties": { + "valid": { + "type": "boolean", + "nullable": true, + "description": "`true` when every document is valid. `null` when the outcome of signing couldn't be confirmed." + }, + "reason": { + "type": "string", + "description": "Set when `valid` is `null`: `signature_provider_outcome_unknown`." + }, + "checked_at": { + "type": "string", + "format": "date-time" + }, + "documents": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "checked_at": { + "type": "string", + "format": "date-time" + }, + "valid": { + "type": "boolean", + "description": "`true` when the signature and the timestamp are both valid and trusted." + }, + "integrity": { + "type": "boolean", + "description": "`true` when the document is unchanged since signing." + }, + "trusted": { + "type": "boolean", + "description": "`true` when the signing certificate chains to a trusted root." + }, + "revoked": { + "type": "boolean" + }, + "trusted_list": { + "type": "object", + "description": "EU Trusted List check. For AES: `{\"status\": \"not_applicable\", \"reason\": \"aes_not_qualified\"}`.", + "properties": { + "status": { + "type": "string" + }, + "reason": { + "type": "string" + } + } + }, + "certificate": { + "type": "object", + "nullable": true, + "description": "The certificate the documents were signed with. `null` until signed, and always `null` for a Simple signature.", + "properties": { + "issuer": { + "type": "string" + }, + "issued_to": { + "type": "string" + }, + "serial": { + "type": "string", + "description": "Serial number, hexadecimal." + }, + "valid_from": { + "type": "string", + "format": "date-time" + }, + "valid_until": { + "type": "string", + "format": "date-time" + } + } + }, + "timestamp": { + "type": "object", + "nullable": true, + "description": "The RFC 3161 timestamp in the signature, issued by Didit. `null` until signed, and always `null` for a Simple signature.", + "properties": { + "time": { + "type": "string", + "format": "date-time" + }, + "qualified": { + "type": "boolean", + "description": "Always `false` for AES." + }, + "valid": { + "type": "boolean" + } + } + } + } + } + } + } + }, + "declined_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "declined_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ] + }, + "signed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the signing link stops working." + }, + "email_sent_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "email_unavailable_reason": { + "type": "string", + "nullable": true + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "The signer's IP address when they asked for their code." + }, + "place": { + "type": "string", + "nullable": true, + "description": "Not collected: always `null`." + }, + "place_unavailable_reason": { + "type": "string", + "nullable": true, + "enum": [ + "not_collected" + ] + } + } + }, + "SignatureFieldValidation": { + "type": "object", + "nullable": true, + "description": "Rules for a `text` field's value. The console preview, the signing flow and the API apply the same rules.", + "properties": { + "kind": { + "type": "string", + "enum": [ + "any", + "letters", + "number", + "email", + "pattern" + ], + "description": "`letters`: letters in any script, spaces, hyphens and apostrophes. `number`: digits with an optional decimal separator. `pattern`: the value must match `pattern`." + }, + "min_length": { + "type": "integer", + "minimum": 0, + "maximum": 500, + "description": "From `0` up to `max_length`." + }, + "max_length": { + "type": "integer", + "minimum": 1, + "maximum": 500, + "default": 120 + }, + "pattern": { + "type": "string", + "nullable": true, + "minLength": 1, + "maxLength": 200, + "description": "Required with `kind: \"pattern\"`. A regular expression the whole value must match. Lookarounds and backreferences aren't supported." + }, + "message": { + "type": "string", + "nullable": true, + "maxLength": 120, + "description": "Shown to the signer instead of the default error." + } + } + }, + "SignatureFieldAnchor": { + "type": "object", + "description": "Places a field by a piece of text in the document instead of a position. Didit doesn't remove the text from the PDF, so write it in the same color as the page background. When the text isn't found, the call returns `400` with `field_anchor_not_found`. See [Place fields by anchor text](/sessions-api/create-session#place-fields-by-anchor-text).", + "required": [ + "text" + ], + "properties": { + "text": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "description": "Text searched in the document. For a Word file, in the converted PDF." + }, + "occurrence": { + "type": "string", + "enum": [ + "first", + "all" + ], + "default": "first", + "description": "`all` places one field per match; the keys after the first get `_2`, `_3` and so on." + }, + "offset_x": { + "type": "number", + "description": "Added to the left edge of the text, as a fraction of the page width." + }, + "offset_y": { + "type": "number", + "description": "Added to the top edge of the text, as a fraction of the page height." + }, + "width": { + "type": "number", + "minimum": 0.02, + "maximum": 1, + "description": "Width of the field, as a fraction of the page width." + }, + "height": { + "type": "number", + "minimum": 0.012, + "maximum": 1, + "description": "Height of the field, as a fraction of the page height." + } + } + }, + "SignatureFieldResult": { + "type": "object", + "description": "A field of a signed document, with its value on this signer's copy. See [Fields](/core-technology/electronic-signature/report-electronic-signature#fields).", + "properties": { + "key": { + "type": "string", + "description": "The field's key, as used in `sign.field_values`." + }, + "type": { + "type": "string", + "enum": [ + "signature", + "initials", + "full_name", + "date_signed", + "text", + "checkbox", + "email", + "data" + ] + }, + "role": { + "type": "string", + "enum": [ + "verified_person", + "legal_representative", + "key_people" + ] + }, + "label": { + "type": "string" + }, + "page": { + "type": "integer", + "description": "Page number, from 1. For a Word document, a page of the converted PDF." + }, + "x": { + "type": "number", + "description": "Left edge, as a fraction of the page width." + }, + "y": { + "type": "number", + "description": "Top edge, as a fraction of the page height." + }, + "width": { + "type": "number", + "description": "Width, as a fraction of the page width." + }, + "height": { + "type": "number", + "description": "Height, as a fraction of the page height." + }, + "required": { + "type": "boolean" + }, + "value": { + "nullable": true, + "description": "A string for `text`, `email`, `full_name`, `date_signed` and `data`, a boolean for a `checkbox`, and `true` for an applied signature or initials. `null` when empty." + }, + "display_value": { + "type": "string", + "nullable": true, + "description": "The value as stamped on the page: dates in the field's `date_format`, `Yes` or `No` for a checkbox, `Signed` for a signature or initials." + }, + "value_source": { + "type": "string", + "nullable": true, + "enum": [ + "signer", + "identity", + "contact", + "company", + "session", + "metadata", + "api", + "signing_time", + null + ], + "description": "Where the value came from: `signer` (typed, ticked or applied by the signer), `identity` (the verified ID), `contact` (the verified email or phone), `company` (the verified company), `session` (`vendor_data`), `metadata`, `api` (`sign.field_values`) or `signing_time` (the date signed)." + }, + "filled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "image_url": { + "type": "string", + "nullable": true, + "description": "Signature and initials only: the path of the applied image. See [Download a signature image](/sessions-api/electronic-signature/download-field-image)." + }, + "value_unavailable_reason": { + "type": "string", + "nullable": true, + "enum": [ + "source_empty", + null + ], + "description": "`source_empty` when a pre-filled field had no value in the session and `signature_field_data_missing_action` let the signer sign anyway." + } + } + }, + "VaspDirectoryWidgetEntry": { + "type": "object", + "description": "What the end user's VASP picker shows: who the VASP is and whether it can be reached.", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The entry's id, for GET /v3/travel-rule/vasps/{vasp_id}/." + }, + "kind": { + "type": "string", + "enum": [ + "directory", + "didit" + ], + "description": "directory for an entry of the shared directory; didit for a member of the Didit network." + }, + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "description": "ISO 3166 country code, as the source gives it." + }, + "domains": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The VASP's web domains. Always empty for Didit network members." + }, + "reachable": { + "type": "boolean", + "description": "Whether your application has an available route to this VASP, or to one of its entities for a brand. Always true for a Didit network member." + } + } + }, + "TravelRuleDataSharingFields": { + "type": "object", + "description": "The fields Didit sends to the counterparty VASP about your own users, per entity type. The `name` and the `wallet_address` are always sent, whatever the list says. An entity type you never chose shares every field. An empty list is a choice too: it shares the name and the wallet only.", + "properties": { + "individual": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "name", + "wallet_address", + "address", + "national_id", + "customer_identification", + "date_of_birth", + "place_of_birth" + ] + }, + "description": "Fields shared about a natural person." + }, + "company": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "name", + "wallet_address", + "address", + "national_id", + "customer_identification" + ] + }, + "description": "Fields shared about a legal person." + } + } + }, + "TravelRuleMatchingRules": { + "type": "object", + "description": "How strictly each field of the counterparty's data is compared with what you hold, per entity type. A field you leave out keeps its default. `NONE` skips the comparison, `STRICT` needs exactly the same words, `CONTAINS` accepts one value whose words all appear in the other, `DEFAULT` is the standard subset match, and `FUZZY` accepts close spellings (and, for dates of birth, a swapped day and month).", + "properties": { + "individual": { + "type": "object", + "description": "Rules for a natural person.", + "properties": { + "name": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default: follows `name_matching_strictness`." + }, + "date_of_birth": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default `NONE`." + }, + "address": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default `NONE`." + }, + "account": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "The wallet the exchange was routed by. Default `STRICT`." + } + } + }, + "company": { + "type": "object", + "description": "Rules for a legal person.", + "properties": { + "name": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default: follows `name_matching_strictness`." + }, + "address": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default `NONE`." + }, + "account": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "The wallet the exchange was routed by. Default `STRICT`." + } + } + } + } + }, + "TravelRuleDataMatch": { + "type": "object", + "nullable": true, + "description": "Field-by-field comparison of the transfer's party data with what the other side knows about the wallet holder. It names fields and results, never values, so it stays readable while the counterparty's data is masked. Null until a comparison ran.", + "properties": { + "matched": { + "type": "boolean", + "description": "True when no compared field differs." + }, + "mismatched_fields": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "name", + "date_of_birth", + "address", + "account" + ] + }, + "description": "The fields that differ." + }, + "fields": { + "type": "object", + "description": "One entry per field compared for the party's entity type (`name`, `date_of_birth`, `address`, `account`; a company has no `date_of_birth`).", + "additionalProperties": { + "type": "object", + "properties": { + "result": { + "type": "string", + "enum": [ + "matched", + "mismatched", + "not_compared" + ], + "description": "`not_compared` when the rule is `NONE` or either side has no value for the field." + }, + "strictness": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ] + } + } + } + } + } + }, + "TravelRuleDataUnmasked": { + "type": "object", + "nullable": true, + "description": "Audit record of the moment the counterparty's data was unmasked. Null while the data is still masked, and for outbound transfers.", + "properties": { + "at": { + "type": "string", + "format": "date-time" + }, + "reason": { + "type": "string", + "enum": [ + "ownership_confirmed", + "wallet_proof", + "address_book_match", + "own_verified_wallet", + "inquiry_approved", + "known_payment_method", + "travel_address" + ], + "description": "What confirmed ownership: ownership_confirmed (your confirmation through the console or the API), wallet_proof (a wallet proof in the widget, or an INTERNAL inbound transfer, set when it is created), address_book_match (a verified address-book wallet), own_verified_wallet (your user's own verified wallet), inquiry_approved (your approval of an inbound TRP inquiry or a GTR or Sygna message), known_payment_method (a wallet your user already paid with) or travel_address (the per-wallet travel address named the wallet)." + }, + "actor": { + "type": "string", + "description": "Who confirmed it: the console user's email or the API key's label. Blank for an automatic decision." + } + } + }, + "TravelRuleMessageAttachment": { + "type": "object", + "nullable": true, + "description": "The document attached to a message, stored like any other transaction document.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string", + "nullable": true + }, + "note": { + "type": "string", + "nullable": true + }, + "uploaded_by": { + "type": "string", + "nullable": true, + "description": "Who attached it. In the pickup portal, a file you sent shows your VASP name, never the person or API key behind it." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "files": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "file_url": { + "type": "string", + "nullable": true, + "description": "Time-limited download URL." + }, + "original_filename": { + "type": "string" + }, + "file_size": { + "type": "integer" + } + } + } + } + } + }, + "TravelRuleMessage": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "kind": { + "type": "string", + "enum": [ + "MESSAGE", + "DOCUMENT_REQUEST" + ] + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "`OUTBOUND` is what the reader's own side sent. In the pickup portal the direction is from the counterparty's side." + }, + "author": { + "type": "string", + "description": "Who wrote it on its own side: a console user's email or an API key label, or the other VASP's name." + }, + "body": { + "type": "string" + }, + "requested_documents": { + "type": "array", + "items": { + "type": "string" + }, + "description": "What a DOCUMENT_REQUEST asks for, in the requester's words." + }, + "reply_to": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "fulfilled_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set on a DOCUMENT_REQUEST once a reply to it carries a document." + }, + "attachment": { + "$ref": "#/components/schemas/TravelRuleMessageAttachment" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleMessageCreateRequest": { + "type": "object", + "description": "Send as JSON, or as multipart/form-data with the attachments in `files`.", + "properties": { + "kind": { + "type": "string", + "enum": [ + "MESSAGE", + "DOCUMENT_REQUEST" + ], + "default": "MESSAGE" + }, + "body": { + "type": "string", + "maxLength": 5000, + "description": "Required for a MESSAGE unless a file is attached." + }, + "requested_documents": { + "type": "array", + "items": { + "type": "string", + "maxLength": 255 + }, + "maxItems": 10, + "description": "Required (at least one) for a DOCUMENT_REQUEST, and only allowed there." + }, + "reply_to": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "A message of the same transfer. A reply that carries a document fulfils the document request it answers." + }, + "files": { + "type": "array", + "items": { + "type": "string", + "format": "binary" + }, + "maxItems": 5, + "description": "Multipart only. Up to 5 files of 10 MB each." + } + } + }, + "SessionDeletionJob": { + "type": "object", + "description": "A bulk session deletion running in the background: progress and counts per outcome, never which sessions it deleted.", + "properties": { + "job_id": { + "type": "string", + "format": "uuid", + "description": "Poll `GET /v3/sessions/delete/{job_id}/` with this id." + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ], + "description": "`PENDING`: accepted, not started yet. `PROCESSING`: deleting; `processed` and `outcomes` are updating. `COMPLETED`: every selected session is accounted for. `FAILED`: the job stopped early; see `error`." + }, + "total": { + "type": "integer", + "description": "Sessions selected when the deletion was requested." + }, + "processed": { + "type": "integer", + "description": "Sessions the job has finished with so far." + }, + "outcomes": { + "type": "object", + "description": "How many sessions ended in each outcome. Every key is always present.", + "properties": { + "deleted": { + "type": "integer", + "description": "Sessions deleted together with their face embedding." + }, + "none": { + "type": "integer", + "description": "Sessions deleted that had no face embedding." + }, + "retained_with_user": { + "type": "integer", + "description": "Sessions deleted while one image-free biometric template was retained for their User, as the request or the application policy asked." + }, + "ineligible_no_vendor_user": { + "type": "integer", + "description": "Sessions deleted, with their face embedding, because retention was requested but they had no linked User." + }, + "failed_retryable": { + "type": "integer", + "description": "Sessions reported as not deleted. A job retries a group it could not delete by itself instead of counting it here." + }, + "already_deleted": { + "type": "integer", + "description": "Selected sessions that were already deleted, for example by another request, when the job reached them." + } + } + }, + "deleted_child_verifications": { + "type": "integer", + "description": "Verifications that the deleted business verifications created, deleted with them. Always `0` for a job started by `POST /v3/sessions/delete/`, which deletes KYC sessions only." + }, + "error": { + "type": "string", + "nullable": true, + "description": "`null` unless `status` is `FAILED`; then why the deletion stopped. The sessions the job had not reached are still in place: repeat the request to delete them." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the deletion was requested." + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the job finished; `null` while it runs." + } + } + }, + "EntityStatusUpdateRequest": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "New lifecycle status. `BLOCKED` also adds the `vendor_data` to the system blocklist." + } + } + }, + "AMLMonitoringToggleResult": { + "oneOf": [ + { + "type": "object", + "required": [ + "users_total", + "already_enabled", + "with_existing_aml", + "needing_screening", + "skipped_no_identity_data", + "skipped_blocked", + "estimated_screening_cost_usd", + "estimated_annual_monitoring_cost_usd", + "balance_sufficient", + "unmatched" + ], + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + }, + "unmatched": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Identifiers of the request that matched no profile in this application, exactly as sent." + } + } + }, + { + "type": "object", + "required": [ + "job_id", + "status", + "is_enabled", + "target_type", + "total", + "processed", + "outcomes", + "results", + "error", + "created_at", + "completed_at", + "estimate", + "unmatched" + ], + "properties": { + "job_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ] + }, + "is_enabled": { + "type": "boolean", + "description": "true when the job enables monitoring, false when it disables it." + }, + "target_type": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Which profile family the job toggles." + }, + "total": { + "type": "integer", + "description": "Profiles the job processes." + }, + "processed": { + "type": "integer", + "description": "Profiles processed so far." + }, + "outcomes": { + "type": "object", + "description": "Per-record outcome counters. Every selected profile resolves to exactly one counter; nothing is silently skipped.", + "properties": { + "enabled": { + "type": "integer", + "description": "Monitoring enabled on an existing AML screening." + }, + "screened_and_enabled": { + "type": "integer", + "description": "A new AML screening was created (billed) and monitoring enabled on it." + }, + "already_enabled": { + "type": "integer", + "description": "Monitoring was already active; nothing changed and nothing was billed." + }, + "disabled": { + "type": "integer", + "description": "Monitoring turned off across every AML screening of the profile." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Skipped: the profile lacks the full name, date of birth and country (users) or a name (businesses) needed to screen." + }, + "skipped_blocked": { + "type": "integer", + "description": "Skipped: the user is blocked." + }, + "skipped_sandbox": { + "type": "integer", + "description": "Skipped: sandbox application, no screening is created." + }, + "failed_provider": { + "type": "integer", + "description": "The screening could not be completed; retry later." + }, + "failed_insufficient_balance": { + "type": "integer", + "description": "Balance ran out while the job was running." + } + } + }, + "results": { + "type": "array", + "description": "One entry per processed profile, in processing order. Grows while the job runs; complete once status is COMPLETED. Recorded for jobs started through the API.", + "items": { + "type": "object", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit internal id of the profile." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the profile." + }, + "outcome": { + "type": "string", + "enum": [ + "enabled", + "screened_and_enabled", + "already_enabled", + "disabled", + "skipped_no_identity_data", + "skipped_blocked", + "skipped_sandbox", + "failed_provider", + "failed_insufficient_balance" + ] + } + } + } + }, + "error": { + "type": "string", + "nullable": true, + "description": "Generic failure message when status is FAILED; the detailed cause is kept on the platform side." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + } + } + }, + "unmatched": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Identifiers of the request that matched no profile in this application, exactly as sent. They are not part of the job." + } + } + } + ] + }, + "AMLMonitoringJob": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ] + }, + "is_enabled": { + "type": "boolean", + "description": "true when the job enables monitoring, false when it disables it." + }, + "target_type": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Which profile family the job toggles." + }, + "total": { + "type": "integer", + "description": "Profiles the job processes." + }, + "processed": { + "type": "integer", + "description": "Profiles processed so far." + }, + "outcomes": { + "type": "object", + "description": "Per-record outcome counters. Every selected profile resolves to exactly one counter; nothing is silently skipped.", + "properties": { + "enabled": { + "type": "integer", + "description": "Monitoring enabled on an existing AML screening." + }, + "screened_and_enabled": { + "type": "integer", + "description": "A new AML screening was created (billed) and monitoring enabled on it." + }, + "already_enabled": { + "type": "integer", + "description": "Monitoring was already active; nothing changed and nothing was billed." + }, + "disabled": { + "type": "integer", + "description": "Monitoring turned off across every AML screening of the profile." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Skipped: the profile lacks the full name, date of birth and country (users) or a name (businesses) needed to screen." + }, + "skipped_blocked": { + "type": "integer", + "description": "Skipped: the user is blocked." + }, + "skipped_sandbox": { + "type": "integer", + "description": "Skipped: sandbox application, no screening is created." + }, + "failed_provider": { + "type": "integer", + "description": "The screening could not be completed; retry later." + }, + "failed_insufficient_balance": { + "type": "integer", + "description": "Balance ran out while the job was running." + } + } + }, + "results": { + "type": "array", + "description": "One entry per processed profile, in processing order. Grows while the job runs; complete once status is COMPLETED. Recorded for jobs started through the API.", + "items": { + "type": "object", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit internal id of the profile." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the profile." + }, + "outcome": { + "type": "string", + "enum": [ + "enabled", + "screened_and_enabled", + "already_enabled", + "disabled", + "skipped_no_identity_data", + "skipped_blocked", + "skipped_sandbox", + "failed_provider", + "failed_insufficient_balance" + ] + } + } + } + }, + "error": { + "type": "string", + "nullable": true, + "description": "Generic failure message when status is FAILED; the detailed cause is kept on the platform side." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + } + } + }, + "unmatched": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Identifiers of the request that matched no profile in this application, exactly as sent. They are not part of the job." + } + } + }, + "OngoingMonitoringToggleRequest": { + "type": "object", + "required": [ + "is_enabled" + ], + "properties": { + "is_enabled": { + "type": "boolean", + "description": "true to enable monitoring, false to disable it." + }, + "estimate_only": { + "type": "boolean", + "default": false, + "description": "When true, returns the cost estimate and performs no writes." + }, + "update_all": { + "type": "boolean", + "default": false, + "description": "Apply to every entity of the application (scoped by the filter_* params when present)." + }, + "filter_search": { + "type": "string" + }, + "filter_status": { + "type": "array", + "items": { + "type": "string" + } + }, + "filter_country": { + "type": "string" + }, + "filter_date_from": { + "type": "string", + "format": "date" + }, + "filter_date_to": { + "type": "string", + "format": "date" + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Entities to toggle. Required unless update_all is true." + } + } + }, + "OngoingMonitoringJob": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ] + }, + "is_enabled": { + "type": "boolean" + }, + "total": { + "type": "integer" + }, + "processed": { + "type": "integer" + }, + "outcomes": { + "type": "object", + "description": "Per-entity outcome counters. Nothing is silently skipped: every requested entity resolves to exactly one counter.", + "properties": { + "enabled": { + "type": "integer", + "description": "Monitoring enabled on an existing AML screening." + }, + "screened_and_enabled": { + "type": "integer", + "description": "A new AML screening was created (billed) and monitoring enabled." + }, + "already_enabled": { + "type": "integer", + "description": "Monitoring was already active (legacy duplicate rows are consolidated)." + }, + "disabled": { + "type": "integer", + "description": "Monitoring disabled on every AML row of the entity." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Skipped: no full name + date of birth + country available to screen." + }, + "skipped_blocked": { + "type": "integer", + "description": "Skipped: the user is blocked." + }, + "skipped_sandbox": { + "type": "integer", + "description": "Skipped: sandbox application." + }, + "failed_provider": { + "type": "integer", + "description": "The screening provider call failed." + }, + "failed_insufficient_balance": { + "type": "integer", + "description": "Balance ran out mid-job." + } + } + }, + "error": { + "type": "string", + "nullable": true + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (no writes are performed).", + "properties": { + "users_total": { + "type": "integer", + "description": "Entities in the selection." + }, + "already_enabled": { + "type": "integer" + }, + "with_existing_aml": { + "type": "integer", + "description": "Entities whose existing AML screening will be switched to monitored." + }, + "needing_screening": { + "type": "integer", + "description": "Entities without any AML screening that will be screened first (billed)." + }, + "skipped_no_identity_data": { + "type": "integer" + }, + "skipped_blocked": { + "type": "integer" + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2 + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07 + }, + "balance_sufficient": { + "type": "boolean" + } + } + } + } + }, + "OngoingMonitoringToggleResult": { + "oneOf": [ + { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (no writes are performed).", + "properties": { + "users_total": { + "type": "integer", + "description": "Entities in the selection." + }, + "already_enabled": { + "type": "integer" + }, + "with_existing_aml": { + "type": "integer", + "description": "Entities whose existing AML screening will be switched to monitored." + }, + "needing_screening": { + "type": "integer", + "description": "Entities without any AML screening that will be screened first (billed)." + }, + "skipped_no_identity_data": { + "type": "integer" + }, + "skipped_blocked": { + "type": "integer" + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2 + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07 + }, + "balance_sufficient": { + "type": "boolean" + } + } + }, + { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + ] + }, + "CaseBlueprintPresetInstallResult": { + "type": "object", + "properties": { + "created": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "CaseBulkUpdateResult": { + "type": "object", + "properties": { + "updated": { + "type": "integer" + }, + "failures": { + "type": "array", + "items": { + "type": "object", + "properties": { + "case_uuid": { + "type": "string", + "format": "uuid" + }, + "detail": { + "type": "string" + } + } + } + } + } + }, + "RegulatoryReportList": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows, capped at 100 for performance." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "EntityNetworkMembershipList": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EntityNetworkMembership" + } + } + } + }, + "SessionDecision": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The unique identifier of the session." + }, + "session_kind": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Discriminator indicating whether this is a User Verification (KYC) or Business Verification (KYB) session. When `user`, the KYC feature arrays (`id_verifications`, `nfc_verifications`, `liveness_checks`, `face_matches`, `poa_verifications`, `document_ai_documents`, `database_validations`, ...) apply and the business arrays are absent. When `business`, the payload instead carries `registry_checks`, `document_verifications`, `key_people_checks` plus the shared arrays (`aml_screenings`, `phone_verifications`, `email_verifications`, `ip_analyses`, `questionnaire_responses`). **Omitted when `include=events` is passed** — the events shape only exists for user sessions, so the discriminator is dropped from that variant." + }, + "shared_from_session": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "For a session cloned with `POST /v3/session/import-shared/`: the `session_id` of the original session in the source application. `null` for every other session. Omitted when `include=events` is passed." + }, + "shared_from_session_number": { + "type": "integer", + "nullable": true, + "description": "`session_number` of the source session when this session was imported from another application of your organization. `null` otherwise." + }, + "session_number": { + "type": "integer", + "description": "The number of the session.", + "nullable": true + }, + "session_url": { + "type": "string", + "format": "uri", + "description": "The URL of the session.", + "nullable": true + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Awaiting User", + "In Review", + "Approved", + "Declined", + "Resubmitted", + "Expired", + "Kyc Expired", + "Abandoned" + ], + "description": "Overall lifecycle status of the session — the rolled-up workflow result. Terminal values are `Approved`, `Declined`, `In Review`, `Expired`, `Kyc Expired`, and `Abandoned`; `Not Started`, `In Progress`, `Awaiting User`, and `Resubmitted` indicate the session is still moving through the workflow." + }, + "status_override": { + "type": "object", + "nullable": true, + "description": "Present when a reviewer set `status` by hand. `null` when the status is the automatic one. Use it to tell a manual approval from an automatic one.", + "properties": { + "overridden_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "automatic_status": { + "type": "string", + "nullable": true, + "description": "The status the platform's own checks conclude." + } + } + }, + "decision_trace": { + "type": "object", + "description": "How the status was reached: who set it and which warnings did.", + "properties": { + "status": { + "type": "string", + "description": "The session status." + }, + "decided_by": { + "type": "string", + "nullable": true, + "enum": [ + "manual", + "automatic", + "investigator", + null + ], + "description": "Who set the status. `null` while the session is not decided." + }, + "decided_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "automatic_status": { + "type": "string", + "nullable": true, + "description": "What the platform's own checks conclude, which differs from `status` after a manual decision." + }, + "terminal_node_id": { + "type": "string", + "nullable": true, + "description": "The workflow status node that ended the session, when one did." + }, + "actor": { + "type": "object", + "nullable": true, + "description": "The reviewer behind a manual decision.", + "properties": { + "user": { + "type": "string", + "nullable": true + }, + "actor_type": { + "type": "string", + "nullable": true + } + } + }, + "warning_counts": { + "type": "object", + "description": "Warnings per `log_type` (`error`, `warning`, `information`) and `total`.", + "additionalProperties": { + "type": "integer" + } + }, + "status_warnings": { + "type": "array", + "description": "The warnings that set a `Declined` or `In Review` status.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string" + }, + "feature": { + "type": "string", + "nullable": true + }, + "node_id": { + "type": "string", + "nullable": true + }, + "log_type": { + "type": "string", + "nullable": true + }, + "check_status": { + "type": "string", + "nullable": true + }, + "source": { + "type": "string", + "description": "The payload list the warning came from, for example `id_verifications`." + }, + "short_description": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "linked_records": { + "type": "object", + "nullable": true, + "description": "A summary of other records linked to the session. `null` when there is nothing to show, and for workflows that declare an age assurance standard.", + "properties": { + "same_face": { + "type": "object", + "nullable": true, + "description": "Other people whose face matched one of this session's face searches.", + "properties": { + "count": { + "type": "integer" + }, + "best_match": { + "type": "object", + "nullable": true, + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "session_number": { + "type": "integer", + "nullable": true + }, + "vendor_user_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "similarity_percentage": { + "type": "number", + "nullable": true + } + } + } + } + }, + "earlier_verifications": { + "type": "object", + "nullable": true, + "properties": { + "count": { + "type": "integer" + }, + "vendor_user_id": { + "type": "string", + "format": "uuid", + "nullable": true + } + } + }, + "networks": { + "type": "object", + "nullable": true, + "properties": { + "count": { + "type": "integer" + }, + "open": { + "type": "integer" + }, + "dismissed": { + "type": "integer" + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "network_id": { + "type": "string", + "format": "uuid" + }, + "display_number": { + "type": "integer", + "nullable": true + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "risk_score": { + "type": "number", + "nullable": true + } + } + } + } + } + } + } + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier of the workflow group that produced this session. Workflows are versioned — this value identifies the workflow itself, not the specific version.", + "nullable": true + }, + "features": { + "type": "array", + "items": { + "type": "string", + "description": "The feature of the session." + }, + "description": "List of verification features configured for this session, as display names: `ID_VERIFICATION`, `NFC`, `LIVENESS`, `FACE_MATCH`, `POA`, `PHONE`, `EMAIL_VERIFICATION`, `AML`, `IP_ANALYSIS`, `DATABASE_VALIDATION`, `QUESTIONNAIRE`, `AGE_ESTIMATION`, `FACE_SEARCH`, `GEOLOCATION` (Location verification) (Business Verification sessions use the KYB feature set: `KYB_REGISTRY`, `KYB_DOCUMENTS`, `KYB_KEY_PEOPLE`, `AML`, `PHONE`, `EMAIL_VERIFICATION`, `IP_ANALYSIS`, `QUESTIONNAIRE`). With `include=events` each entry becomes an object `{feature, node_id}` instead of a plain string, and the email feature is renamed: it appears as `EMAIL` in events mode instead of the `EMAIL_VERIFICATION` used here." + }, + "vendor_data": { + "type": "string", + "description": "The vendor data of the session.", + "nullable": true + }, + "metadata": { + "type": "object", + "description": "The metadata of the session.", + "nullable": true + }, + "expected_details": { + "type": "object", + "description": "Expected details supplied at session creation — used for cross-validation against extracted data (KYC) or to pre-fill the company registry search (KYB). Echoes back exactly the keys that were provided; `null` when none were supplied. KYC sessions carry the person fields; Business (KYB) sessions carry `company_name`, `registry_country` and `registration_number`.", + "properties": { + "first_name": { + "type": "string", + "description": "User's first name. For example, `John`." + }, + "last_name": { + "type": "string", + "description": "User's last name. For example, `Doe`." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "User's date of birth with format: YYYY-MM-DD. For example, `1990-05-15`." + }, + "gender": { + "type": "string", + "nullable": true, + "enum": [ + "M", + "F" + ], + "default": null, + "description": "User's gender. Must be either 'M', 'F', or null." + }, + "nationality": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the applicant's country of origin. For example, `USA`." + }, + "country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the country of the Proof of Address document, or the country of the applicant's ID document, which may differ from nationality. For example, `GBR`." + }, + "address": { + "type": "string", + "description": "The address in a human readable format, including as much information as possible. For example, `123 Main St, San Francisco, CA 94105, USA`." + }, + "identification_number": { + "type": "string", + "description": "The user's document number, personal number, or tax number. For example, `123456789`." + }, + "ip_address": { + "type": "string", + "format": "ipv4", + "description": "Expected IP address for the session. If the actual IP address differs from this value, a warning will be logged. For example, `192.168.1.100`." + }, + "expected_document_types": { + "type": "array", + "description": "Document types the ID verification step was restricted to at session creation. Empty/unset means no restriction. Codes: `P` (passport), `ID` (national ID), `DL` (driver's license), `RP` (residence permit), `HIC` (health insurance card), `TC` (tax card), `SSC` (social security card).", + "items": { + "type": "string", + "enum": [ + "P", + "ID", + "DL", + "RP", + "HIC", + "TC", + "SSC" + ] + } + }, + "id_country": { + "type": "string", + "nullable": true, + "description": "Expected ID-document issuing country (ISO 3166-1 alpha-3), when restricted at creation time.", + "example": "ESP" + }, + "poa_country": { + "type": "string", + "nullable": true, + "description": "Expected Proof of Address document country (ISO 3166-1 alpha-3), when restricted at creation time.", + "example": "ESP" + }, + "company_name": { + "type": "string", + "description": "Expected legal name of the business. Business (KYB) sessions only." + }, + "registry_country": { + "type": "string", + "description": "Expected registry country as `XX` or `XX-YY` (ISO 3166-1 alpha-2, optionally with an ISO 3166-2 subdivision for state-level registries, e.g. `US-CA`). Business (KYB) sessions only." + }, + "registration_number": { + "type": "string", + "description": "Expected company registration number. Business (KYB) sessions only." + } + }, + "nullable": true + }, + "contact_details": { + "type": "object", + "description": "Contact details supplied when the session was created (used for notification emails, prefilled phone/email verification, and communication language). `null` when neither an email nor a phone number was provided.", + "properties": { + "email": { + "type": "string", + "description": "Email address of the user (e.g., \"john.doe@example.com\") that will be used during the Email Verification step. If not provided, the user must provide it during the verification flow." + }, + "email_lang": { + "type": "string", + "description": "Language code (ISO 639-1) for email notifications. Controls the language of all email communications (e.g., \"en\", \"es\", \"fr\")." + }, + "send_notification_emails": { + "type": "boolean", + "default": false, + "description": "If true and an email is provided, Didit sends the initial \"Verify your identity\" email asynchronously when the User Verification (KYC) or Business Verification (KYB) session is created. Didit also sends verification status notifications for sessions requiring manual review to the provided email address." + }, + "phone": { + "type": "string", + "nullable": true, + "description": "Phone number of the user in E.164 format if available (e.g., +14155552671). `null` when only an email was supplied at session creation. Note: This field returns the original phone number provided during session creation. If the number was invalid, it may not be in E.164 format." + } + }, + "nullable": true + }, + "callback": { + "type": "string", + "format": "uri", + "description": "The callback URL of the session.", + "nullable": true + }, + "id_verifications": { + "type": "array", + "nullable": true, + "description": "**Array of ID verification (OCR) reports** produced by ID-document steps in the workflow graph. Always a JSON array — read `id_verifications[0]` and iterate if your workflow can run more than one ID check (for example a primary check plus a step-up). `null` until at least one ID step has produced data. Each item is the full report, including `node_id`, feature-level `status`, extracted document fields, image URLs, quality scores, MRZ, address parsing, cross-session matches, and `warnings[]`.", + "items": { + "type": "object", + "description": "One ID verification report - the full report for a single ID Verification step. `verification_method` says which of the three methods produced it (document capture, non-doc lookup or wallet sign-in) and the method-specific evidence block is populated accordingly.", + "properties": { + "node_id": { + "type": "string", + "description": "Workflow graph node id of the OCR step that produced this report." + }, + "verification_method": { + "type": "string", + "enum": [ + "document", + "id_lookup", + "wallet" + ], + "description": "Which ID Verification method produced this report. `document` - the user photographed a physical ID (the default, and the value on every session created before the other methods existed). `id_lookup` - the user typed their national ID number and it was checked against a government register or another authoritative source for that country (`id_lookup.source` names which). `wallet` - the user signed in with a government or bank digital identity." + }, + "assurance": { + "type": "string", + "enum": [ + "documentary", + "data_match", + "cryptographic" + ], + "description": "Assurance tier derived from `verification_method`: `documentary` for a document that was captured and checked, `data_match` for details that matched a record held by the source queried, `cryptographic` for attributes an identity provider authenticated and signed." + }, + "wallet_provider": { + "type": "string", + "nullable": true, + "description": "Catalog wallet id when `verification_method` is `wallet` (`mitid`, `bankid_se`, `itsme`, ...). `null` otherwise." + }, + "fallback_from": { + "type": "object", + "nullable": true, + "description": "Why a non-document method ended the ID step. Set on the declined report when a lookup or a wallet sign-in failed and the country's fallback switch was `decline`. `null` otherwise.", + "properties": { + "method": { + "type": "string", + "enum": [ + "id_lookup", + "wallet" + ], + "description": "The method that failed." + }, + "reason": { + "type": "string", + "description": "The outcome that ended it, e.g. `no_match`, `provider_error`, `cancelled`, `timeout`." + }, + "action": { + "type": "string", + "description": "The configured action that was taken, e.g. `decline`." + } + } + }, + "id_lookup": { + "type": "object", + "nullable": true, + "description": "Registry evidence. Present only when `verification_method` is `id_lookup`; `null` otherwise.", + "properties": { + "source": { + "type": "string", + "description": "Customer-facing name of the source queried, e.g. `Department of Home Affairs` (a government register) or `UK credit bureau and financial services records` (not a government register). This is the only statement of provenance for the lookup; do not assume a government source." + }, + "checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the register answered." + }, + "attempts": { + "type": "integer", + "description": "1-based attempt number this report came from." + }, + "max_attempts": { + "type": "integer", + "description": "Attempt budget configured for the country (1 to 5, default 1)." + }, + "outcome": { + "type": "string", + "enum": [ + "match", + "partial_match", + "no_match", + "provider_error", + "format_rejected" + ], + "nullable": true, + "description": "What the register answered. Only `match`, `partial_match` and `no_match` count against the attempt budget and bill." + }, + "comparison": { + "type": "array", + "description": "One row per comparable field the source answered on, plus one row per source error. Comparison rows use the normalised field names below rather than the country's request field keys, because several request fields (first and last name, or any of the identifier aliases) collapse into a single comparison. A source that failed appends a row with `field: \"provider\"` and `result: \"error\"`, so read `result` before treating a row as a field verdict.", + "items": { + "type": "object", + "required": [ + "field", + "label", + "provided", + "record", + "result" + ], + "properties": { + "field": { + "type": "string", + "enum": [ + "identification_number", + "full_name", + "date_of_birth", + "address", + "provider" + ], + "description": "Normalised comparison field. `provider` is not a user field: it marks a source error row." + }, + "label": { + "type": "string", + "description": "Display label for the row, e.g. `ID number`, `Full name`, `Provider`." + }, + "provided": { + "type": "string", + "nullable": true, + "description": "What the user typed. `null` on a `provider` error row." + }, + "record": { + "type": "string", + "nullable": true, + "description": "What the source held. On a `provider` error row this carries the error message instead." + }, + "result": { + "type": "string", + "enum": [ + "match", + "partial", + "no_match", + "error" + ], + "description": "Per-field comparison result. `error` appears only on a `provider` row and means that source did not answer; treat it as evidence about the query, never as a field mismatch." + } + } + } + }, + "registry_portrait": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived presigned URL to the portrait the source returned. Only the four government registers that return one (South Africa, Nigeria, Argentina, Panama)." + }, + "face_match_score": { + "type": "number", + "nullable": true, + "description": "Selfie against the registry portrait, 0-100. Only where the register returns a portrait." + } + } + }, + "wallet_verification": { + "type": "object", + "nullable": true, + "description": "Credential evidence. Present only when `verification_method` is `wallet`; `null` otherwise.", + "properties": { + "provider": { + "type": "string", + "description": "Catalog wallet id, e.g. `mitid`." + }, + "provider_name": { + "type": "string", + "nullable": true, + "description": "Wallet name as the end user knows it, e.g. `MitID`." + }, + "issuing_authority": { + "type": "string", + "nullable": true, + "description": "The authority behind the wallet, e.g. `Danish Agency for Digital Government`." + }, + "issuing_country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country the wallet was offered in." + }, + "credential_type": { + "type": "string", + "nullable": true, + "description": "Credential the wallet presented, e.g. `person_identification`." + }, + "level_of_assurance": { + "type": "string", + "enum": [ + "low", + "substantial", + "high" + ], + "nullable": true, + "description": "The level of assurance the wallet asserted. A sign-in that asserts less than the level requested fails instead of downgrading." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "The wallet's authentication instant." + }, + "signature_valid": { + "type": "boolean", + "nullable": true, + "description": "Whether the signed assertion the wallet returned validated." + }, + "attributes": { + "type": "object", + "additionalProperties": true, + "description": "The attributes the wallet shared, keyed as the wallet's catalog entry lists them." + }, + "portrait": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived presigned URL to the portrait the wallet shared, where it shares one." + }, + "face_match_score": { + "type": "number", + "nullable": true, + "description": "Selfie against the wallet portrait, 0-100, where the wallet shares one." + } + } + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Expired" + ], + "description": "Feature-level status of this ID verification step. Independent of the top-level session `status` — a session can reach its overall decision while an individual feature stays `In Review`." + }, + "document_type": { + "type": "string", + "description": "Type of identity document" + }, + "document_subtype": { + "type": "string", + "nullable": true, + "description": "Accepted document subtype code from the workflow document settings, including the document region when applicable. Examples: `ID_CARD_GENERIC`, `QUEENSLAND_DRIVER_LICENSE_GENERIC`." + }, + "document_number": { + "type": "string", + "description": "Document identification number" + }, + "personal_number": { + "type": "string", + "description": "Personal identification number" + }, + "portrait_image": { + "type": "string", + "format": "uri", + "description": "URL of the portrait image" + }, + "front_image": { + "type": "string", + "format": "uri", + "description": "URL of the front document image" + }, + "front_video": { + "type": "string", + "format": "uri", + "description": "URL of the front document video" + }, + "back_image": { + "type": "string", + "format": "uri", + "description": "URL of the back document image" + }, + "back_video": { + "type": "string", + "format": "uri", + "description": "URL of the back document video" + }, + "full_front_image": { + "type": "string", + "format": "uri", + "description": "URL of the full front document image" + }, + "full_back_image": { + "type": "string", + "format": "uri", + "description": "URL of the full back document image" + }, + "front_image_camera_front": { + "type": "string", + "format": "uri", + "description": "URL of the front document image captured from the front-facing camera" + }, + "back_image_camera_front": { + "type": "string", + "format": "uri", + "description": "URL of the back document image captured from the front-facing camera" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth from document" + }, + "age": { + "type": "integer", + "description": "Calculated age from date of birth" + }, + "expiration_date": { + "type": "string", + "format": "date", + "description": "Document expiration date" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "description": "Document issue date" + }, + "issuing_state": { + "type": "string", + "description": "ISO code of issuing state" + }, + "issuing_state_name": { + "type": "string", + "description": "Full name of issuing state" + }, + "first_name": { + "type": "string", + "description": "First name from document" + }, + "last_name": { + "type": "string", + "description": "Last name from document" + }, + "full_name": { + "type": "string", + "description": "Full name from document" + }, + "gender": { + "type": "string", + "description": "Gender from document" + }, + "address": { + "type": "string", + "description": "Raw address from document" + }, + "formatted_address": { + "type": "string", + "description": "Formatted full address" + }, + "place_of_birth": { + "type": "string", + "description": "Place of birth from document" + }, + "marital_status": { + "type": "string", + "description": "Marital status from document" + }, + "nationality": { + "type": "string", + "description": "ISO nationality code" + }, + "extra_fields": { + "type": "object", + "description": "Additional document fields. Includes document-specific attributes (e.g. `dl_categories`, `blood_group`, `sponsor`, `employer`, `dl_class_code_b_from`, `dl_class_code_b_to`). Driver license class validity dates are returned as `dl_class_code__from` and `dl_class_code__to`, and per-class restriction/information codes (e.g. column 12 of UK licences) as `dl_class_code__notes`. When the document carries names, address, or place of birth in multiple scripts, this object also includes alternate-script versions of those fields. The workflow setting `preferred_characters` (`latin` or `non_latin`) controls which script populates the top-level fields (`first_name`, `last_name`, `full_name`, `address`, `place_of_birth`); the opposite script lands here as `first_name_non_latin` / `last_name_non_latin` / `full_name_non_latin` / `middle_name_non_latin` / `address_non_latin` / `place_of_birth_non_latin` (when `preferred_characters` is `latin`) or with a `_latin` suffix (when `preferred_characters` is `non_latin`). Keys appear only when the OCR actually extracts a real alternate-script value — a field whose alternate value is in the same script as the preferred set (for example, a Cyrillic-labelled field whose OCR value came back in Latin homoglyphs) is skipped. When `preferred_characters` is `latin` but the document only carries non-Latin text, Didit transliterates the extracted values and exposes `first_name_latin` / `last_name_latin` / etc. as a fallback.", + "additionalProperties": true, + "properties": { + "dl_categories": { + "type": "array", + "items": { + "type": "string" + } + }, + "blood_group": { + "type": "string", + "nullable": true + }, + "first_name_non_latin": { + "type": "string", + "description": "First name in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document (e.g. `Айна` for a Kyrgyz ID)." + }, + "last_name_non_latin": { + "type": "string", + "description": "Surname in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document." + }, + "middle_name_non_latin": { + "type": "string", + "description": "Middle name / patronymic in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document." + }, + "full_name_non_latin": { + "type": "string", + "description": "Composed full name in the non-Latin script. Reconstructed from the individual components when both first and last names are available." + }, + "address_non_latin": { + "type": "string", + "description": "Address in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document." + }, + "place_of_birth_non_latin": { + "type": "string", + "description": "Place of birth in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document." + }, + "first_name_latin": { + "type": "string", + "description": "First name in Latin script. Populated when `preferred_characters=non_latin` and a Latin version is present on the document, or — as a transliteration fallback — when `preferred_characters=latin` but the document only carries non-Latin text." + }, + "last_name_latin": { + "type": "string", + "description": "Surname in Latin script. Same rules as `first_name_latin`." + }, + "middle_name_latin": { + "type": "string", + "description": "Middle name / patronymic in Latin script. Same rules as `first_name_latin`." + }, + "full_name_latin": { + "type": "string", + "description": "Composed full name in Latin script. Always reconstructed from the individual components when both first and last names are available." + }, + "address_latin": { + "type": "string", + "description": "Address in Latin script. Same rules as `first_name_latin`." + }, + "place_of_birth_latin": { + "type": "string", + "description": "Place of birth in Latin script. Same rules as `first_name_latin`." + } + } + }, + "mrz": { + "type": "object", + "nullable": true, + "description": "Parsed Machine-Readable Zone (MRZ) fields extracted from the document. `null` if no MRZ was detected.", + "properties": { + "surname": { + "type": "string", + "example": "MARTINEZ_families` / `very_high_similarity_plus__families`. Present only when `match_source` is `recovered_high`." + } + } + } + }, + "browser_version": { + "type": "string", + "nullable": true + }, + "os_version": { + "type": "string", + "nullable": true + }, + "user_agent": { + "type": "string", + "nullable": true, + "description": "The browser's user agent string." + }, + "accept_language": { + "type": "string", + "nullable": true, + "description": "The `Accept-Language` header the browser sent." + }, + "session_identifier": { + "type": "string", + "nullable": true, + "description": "Identifier of the browsing session the verification ran in." + }, + "session_age_ms": { + "type": "integer", + "nullable": true, + "description": "Age of that browsing session in milliseconds when the step ran." + }, + "coords_accuracy": { + "type": "number", + "nullable": true, + "description": "Accuracy radius, in metres, of the coordinates the device reported." + }, + "raw_device_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Raw device signals collected for the step." + } + } + } + }, + "geolocation_checks": { + "type": "array", + "nullable": true, + "description": "**Array of Location verification results**, one per Location verification step: that step's accepted attempt. `null` until a Location verification step has produced data. Earlier attempts of the same step are in `geolocation_attempts`.", + "items": { + "$ref": "#/components/schemas/GeolocationCheck" + } + }, + "geolocation_attempts": { + "type": "array", + "nullable": true, + "description": "Every attempt of every Location verification step of this session, oldest first. Attempts that a resubmission replaced are left out. A workflow that withholds `geolocation_checks` through its response attributes withholds this list too.", + "items": { + "$ref": "#/components/schemas/GeolocationAttempt" + } + }, + "database_validations": { + "type": "array", + "nullable": true, + "description": "**Array of Database Validation reports** — always a JSON array, never a singular `database_validation` object. `null` until at least one DB validation step has produced data. Each item is the full report, including `node_id`, feature-level `status`, `screened_data`, the per-service `validations[]` (each with its registry `source_data`), and `warnings[]`. The `screened_data` and `source_data` fields vary by country:\n\n**ARG (Argentina)**: screened_data: {document_number, selfie, gender, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, date_of_death, tax_id, tax_id_type, face_match_score, sit_1_since, banks, last_position, highest_position, rejected_checks}. Uses RENAPER biometric face-match validation. Gender is auto-inferred from face analysis or given name when not present in the document (e.g., driver licenses)\n\n**BOL (Bolivia)**: screened_data: {document_number, date_of_birth, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, gender}\n\n**BRA (Brazil)**: screened_data: {tax_number, first_name, last_name, date_of_birth}. source_data always includes {identification_number, first_name, last_name, date_of_birth, lgpd_minor, minor_under_18, minor_under_16} for successful CPF matches. Successful adult lookups set those flags to false. The Receita Federal Consulta CPF service may return HTTP 422 (minor under 18) or 451 (under 16) with LGPD-withheld data; in those cases source_data sets the minor flags to true as appropriate, includes the upstream HTTP status, and field-level validation is no_match. See the Brazil section of the Database Validation Outcome Codes reference for the full code list.\n\n**CHL (Chile)**: screened_data: {personal_number, first_name, last_name, date_of_birth}. source_data: {identification_number, first_name, last_name, date_of_birth, gender}\n\n**COL (Colombia)**: screened_data: {document_number, document_type, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, document_type}\n\n**CRI (Costa Rica)**: screened_data: {personal_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**DOM (Dominican Republic)**: screened_data: {personal_number}. source_data: {identification_number} (returns valid/invalid only)\n\n**ECU (Ecuador)**: screened_data: {personal_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, street, formatted_address, gender, nationality, education, marital_status, spouse, mother_name, father_name, profession}\n\n**ESP (Spain)**: screened_data: {personal_number, document_type, expiration_date, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, document_type, expiration_date}\n\n**GTM (Guatemala)**: screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**HND (Honduras)**: screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**MEX (Mexico)**: screened_data: {personal_number (CURP), first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, gender, nationality, curp_status, state_of_birth, doc_probatorio, registration_year, registration_state, registration_municipality, num_acta, crip, issuing_state_code, foreign_registry_number, folio_carta, folio_certificado}. Uses the RENAPO civil registry. A separate INE validity-check service is also available for MEX as an additional one-by-one validation.\n\n**PAN (Panama)**: screened_data: {personal_number, selfie}. source_data: {identification_number}. Uses SIB biometric validation\n\n**PER (Peru)**: screened_data: {personal_number (DNI), first_name, last_name}. source_data: {identification_number, first_name, last_name, paternal_name, maternal_name, verification_number, verification_letter}\n\n**PRY (Paraguay)**: screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**SLV (El Salvador)**: screened_data: {document_number, date_of_birth, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**URY (Uruguay)**: screened_data: {personal_number, date_of_birth, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**VEN (Venezuela)**: screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, gender, date_of_birth, document_type}", + "items": { + "type": "object", + "properties": { + "node_id": { + "type": "string", + "description": "Unique identifier for this database validation step in the workflow graph." + }, + "issuing_state": { + "type": "string", + "description": "ISO 3166-1 alpha-3 code of the country whose government/registry sources were queried (e.g. `BRA`, `MEX`, `ESP`, `NGA`, `IDN`). Database Validation covers 60+ countries across the Americas, Europe, Africa, the Middle East and Asia-Pacific, plus global multi-country sources — coverage expands regularly, so treat the value as an open set rather than a fixed enum." + }, + "validation_type": { + "type": "string", + "enum": [ + "one_by_one", + "two_by_two", + "not_enabled" + ], + "description": "How the result was corroborated: `one_by_one` (single-service outcome), `two_by_two` (two or more distinct services independently full-matched the identification number), or `not_enabled` (persisted when no service produced a usable match). The value is never `null`. Dedicated biometric 2x2 validation modes are currently available for `ECU` and `PER`." + }, + "screened_data": { + "type": "object", + "description": "The input data used for validation. Fields vary by country — see the Database Validation docs (`/core-technology/database-validation/overview`) for country-specific formats.", + "properties": { + "last_name": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "tax_number": { + "type": "string", + "description": "Brazil: CPF (11 digits)" + }, + "personal_number": { + "type": "string", + "description": "Chile: RUT, DOM: Cédula (11 digits), ECU: Cédula (10 digits), ESP: DNI/NIE, MEX: CURP (18 chars), PER: DNI (8 digits), URY: CI" + }, + "document_number": { + "type": "string", + "description": "ARG: DNI, BOL: CI, COL: Cédula, GTM: DPI, HND: DNI, PAN: Cédula, PRY: CI, SLV: DUI, VEN: Cédula" + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "document_type": { + "type": "string", + "description": "COL: CC/CE/TI/NIT, ESP: ID/PASSPORT" + }, + "expiration_date": { + "type": "string", + "format": "date", + "description": "Required for ESP" + } + } + }, + "validations": { + "type": "array", + "description": "One entry per registry service that returned a validation payload. Each entry identifies the service (`service_id`, `service_name`) and carries the per-field `validation` comparison, the service `outcome_code` / `outcome_detail` when reported, and the normalised registry record in `source_data`.", + "items": { + "type": "object", + "properties": { + "service_id": { + "type": "string", + "description": "Identifier of the registry service that produced this validation result.", + "example": "bra_cpf" + }, + "service_name": { + "type": "string", + "description": "Human-readable name of the registry service.", + "example": "Brazil - CPF status check" + }, + "validation": { + "type": "object", + "description": "The results of the field validations.", + "properties": { + "full_name": { + "type": "string", + "description": "Match result for full name (e.g., 'full_match', 'partial_match', 'no_match')." + }, + "date_of_birth": { + "type": "string", + "description": "Match result for date of birth." + }, + "identification_number": { + "type": "string", + "description": "Match result for identification number." + } + } + }, + "outcome_code": { + "type": "string", + "nullable": true, + "description": "Machine-readable outcome of the registry lookup (e.g. `MATCH`, `NO_MATCH`, `PARTIAL_MATCH`, `INCONCLUSIVE`, source-specific codes). Present when the service reports one." + }, + "outcome_detail": { + "type": "string", + "nullable": true, + "description": "Human-readable explanation of `outcome_code`, when provided by the service." + }, + "source_data": { + "type": "object", + "description": "The data returned from the government database. Common fields: identification_number, first_name, last_name, full_name, date_of_birth, gender. Additional fields by country: BRA always includes lgpd_minor, minor_under_18, and minor_under_16 on successful CPF matches; it also includes the upstream HTTP status when CPF data is withheld under LGPD (HTTP 422/451). ECU adds street, formatted_address, nationality, education, marital_status, spouse, mother_name, father_name, profession. PER adds paternal_name, maternal_name, verification_number, verification_letter. ESP adds document_type, expiration_date. VEN/COL add document_type.", + "additionalProperties": true + } + } + } + }, + "errors": { + "type": "array", + "description": "Errors returned by registry services while validating, each tagged with the `service_id` that produced it. **This key is omitted entirely when there are no errors.**", + "items": { + "type": "object", + "description": "The upstream service's error object spread as-is — its keys vary by service (commonly a message/code and any provider-specific details) — plus `service_id` identifying the service that errored.", + "properties": { + "service_id": { + "type": "string", + "nullable": true, + "description": "Identifier of the registry service that errored." + } + }, + "additionalProperties": true + } + }, + "match_type": { + "type": "string", + "enum": [ + "no_match", + "partial_match", + "full_match" + ], + "nullable": true, + "description": "The aggregate match outcome across the queried services. `null` until the validation has been evaluated." + }, + "status": { + "type": "string", + "description": "Feature-level status of this database validation. Independent of the top-level session `status` — a session can reach its overall decision while an individual feature stays `In Review`.", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ] + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "NFC", + "LIVENESS", + "FACEMATCH", + "PROOF_OF_ADDRESS", + "PHONE", + "EMAIL", + "AML", + "LOCATION", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "GEOLOCATION", + "SIGNATURE" + ], + "description": "The feature that generated this warning. The value identifies the feature block the warning belongs to. Each module's warnings array typically uses the value matching its module (e.g. `PHONE` inside `phone_verifications[].warnings`), but the enum above is authoritative across all feature warnings on the decision payload." + }, + "risk": { + "type": "string" + }, + "additional_data": { + "type": "object", + "nullable": true + }, + "log_type": { + "type": "string" + }, + "short_description": { + "type": "string" + }, + "long_description": { + "type": "string" + }, + "node_id": { + "type": "string", + "description": "The workflow graph node ID that generated this warning" + } + } + }, + "description": "Warnings related to the database validation." + } + } + } + }, + "database_validation_not_performed_reason": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Why a configured database validation produced no result, as `{code, message}` plus context fields. Present only when `database_validations` is `null` for a workflow that has the step, so you can tell \"the registry agreed\" from \"nothing ran\". `null` otherwise.", + "properties": { + "code": { + "type": "string", + "description": "Stable machine-readable reason." + }, + "message": { + "type": "string", + "description": "Human-readable explanation." + } + } + }, + "reviews": { + "type": "array", + "items": { + "type": "object", + "properties": { + "user": { + "type": "string", + "description": "Who performed the review. Usually the console reviewer's identifier (their email), but the value is `\"API Client\"` for status updates made with an API key and `\"Unknown\"` when no actor could be resolved — so do not parse this field as an email address." + }, + "new_status": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + } + }, + "assignee": { + "type": "object", + "nullable": true, + "description": "The Console reviewer the session is assigned to, or `null` when nobody holds it.", + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "assigned_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "waiting_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the session started waiting for review. `null` unless the session is `In Review` and your application sets a review deadline." + }, + "sla_due_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the review is due. `null` unless the session is `In Review` and your application sets a review deadline." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp at which the session was created (ISO 8601, UTC)." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp at which this session is scheduled to expire. Set on User Verification (KYC) sessions when an expiration was configured at creation time (or once `Approved` status triggers KYC expiry computation). `null` for sessions without a configured expiration, and for kinds that do not use expiry (e.g. Business Verification (KYB) sessions that never received an `expires_at`)." + }, + "registry_checks": { + "type": "array", + "nullable": true, + "description": "Company registry checks. Present only when `session_kind = business`. Each item wraps the full company-registry payload.", + "items": { + "type": "object", + "description": "One company registry check — the full company-registry payload enriched with ownership structure and warnings. Present only when `session_kind = business`.", + "properties": { + "status": { + "type": "string", + "description": "Feature lifecycle status." + }, + "node_id": { + "type": "string", + "nullable": true + }, + "data_resolved": { + "type": "boolean" + }, + "company": { + "type": "object", + "description": "Full registry payload for the company (registry record merged with any user-provided or console-edited data).", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "node_id": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string" + }, + "registry_status": { + "type": "string", + "nullable": true + }, + "data_resolved": { + "type": "boolean" + }, + "company_name": { + "type": "string" + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_code": { + "type": "string", + "description": "Country of incorporation (ISO 3166-1 alpha-2, e.g. `GB`, `DE`, `US`)." + }, + "region": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision code when applicable (e.g. `CA`, `NY` for US states)." + }, + "company_type": { + "type": "string", + "nullable": true + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "registered_address": { + "type": "string", + "nullable": true + }, + "tax_number": { + "type": "string", + "nullable": true + }, + "risk_level": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ], + "nullable": true + }, + "verification_status": { + "type": "string", + "nullable": true + }, + "is_from_registry": { + "type": "boolean" + }, + "fetch_status": { + "type": "string", + "nullable": true + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "format": "decimal", + "nullable": true, + "description": "Registered/share capital amount as a decimal string (e.g. `\"100.00\"`), parsed from the registry filing. `null` when not available." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 currency code of `registered_capital_amount` (e.g. `\"GBP\"`). `null` when not available." + }, + "website": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "format": "email", + "nullable": true + }, + "phone": { + "type": "string", + "nullable": true + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true + }, + "location_of_registration": { + "type": "string", + "nullable": true + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "designation": { + "type": "string", + "nullable": true + }, + "role": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "Pending" + ], + "nullable": true + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + } + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ] + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership percentage as reported by the registry. Free-form string (e.g. `\"25.00\"`, `\"50\"`); use `effective_ownership_percent` for a parsed numeric value." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership percentage as reported by the registry. Free-form string; use `effective_ownership_percent` for a parsed numeric value." + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "Pending" + ], + "nullable": true + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + }, + "shareholding": { + "$ref": "#/components/schemas/KYBRegistryShareholding" + } + } + } + }, + "addresses": { + "type": "array", + "nullable": true, + "description": "Every address the registry states, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryAddress" + } + }, + "industries": { + "type": "array", + "nullable": true, + "description": "The activities the registry classifies the company under, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryIndustry" + } + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "$ref": "#/components/schemas/KYBRegistryData" + }, + "user_provided_data": { + "type": "object", + "description": "Data confirmed or edited by the end user." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "is_editable": { + "type": "boolean" + }, + "ownership": { + "$ref": "#/components/schemas/KYBRegistryOwnership" + }, + "registry_tier": { + "type": "string", + "nullable": true, + "description": "The registry tier the workflow ordered: `basic`, `shareholders` or `ubo`. Also in `ownership.tier_requested`." + }, + "registry_tier_delivered": { + "type": "string", + "nullable": true, + "description": "The tier the registry delivered and that is billed. Also in `ownership.tier_delivered`." + }, + "registry_ownership_status": { + "type": "string", + "nullable": true, + "description": "`not_requested`, `pending`, `delivered`, `not_held` or `unavailable`. Also in `ownership.status`." + }, + "registry_monitoring": { + "type": "object", + "nullable": true, + "description": "The latest registry monitor for the company, or `null` when none exists.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string" + }, + "unavailable_reason": { + "type": "string", + "nullable": true + }, + "enrolled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "next_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "last_event_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "events_count": { + "type": "integer" + }, + "watched_fields": { + "description": "The company fields the monitor watches." + } + } + } + } + }, + "ownership_structure": { + "$ref": "#/components/schemas/KYBOwnershipStructure" + }, + "warnings": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + }, + "key_people_checks": { + "type": "array", + "nullable": true, + "description": "Aggregate key-people checks. Present only when `session_kind = business`. Each item uses the two-bucket shape (`registry`, `submitted`, `ubo_kyc_summary`).", + "items": { + "type": "object", + "description": "Aggregate check over the company's officers + UBOs. Uses the `registry` / `submitted` shape. Present only when session_kind = business.", + "properties": { + "status": { + "type": "string" + }, + "node_id": { + "type": "string", + "nullable": true + }, + "registry": { + "type": "object", + "description": "Parties discovered via the registry check.", + "properties": { + "officers": { + "type": "array", + "description": "Officers discovered via the registry check, each enriched with the status of any child KYC session and AML screening spawned for them.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "description": "Officer's full name as recorded in the registry." + }, + "designation": { + "type": "string", + "nullable": true, + "description": "Free-text designation from the registry (e.g. `Director`, `Company Secretary`)." + }, + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Normalized officer role." + }, + "nationality": { + "type": "string", + "nullable": true + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "is_active": { + "type": "boolean" + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "aml_status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "nullable": true, + "description": "Status of the AML screening run for this officer. `null` when no AML screening was run." + }, + "kyc_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "nullable": true, + "description": "Raw lifecycle status of the child KYC session spawned for this officer. This is the full session status enum (not a reduced Approved/Declined/Pending set). `null` when no child KYC session exists." + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Shareable URL of the child KYC session. `null` when no child session exists." + }, + "kyc_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Session id of the child KYC session. Pass it to this same decision endpoint to fetch the person's full KYC report. `null` when no child session exists." + }, + "verification_workflow_type": { + "type": "string", + "nullable": true, + "description": "Base workflow type of the child session's workflow (e.g. `kyc`). `null` for graph-based workflows or when no child session exists." + }, + "verification_workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Workflow id used by the child session. `null` when no child session exists." + }, + "verification_workflow_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label of the child session's workflow. `null` when unset." + }, + "verification_workflow_features": { + "type": "array", + "nullable": true, + "description": "Per-feature progress of the child verification session, e.g. `[{\"feature\": \"ID_VERIFICATION\", \"status\": \"Approved\"}]`. `null` when the child session has no resolvable feature list.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature display name (e.g. `ID_VERIFICATION`, `LIVENESS`, `AML`)." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Feature-level status inside the child session." + } + } + } + }, + "didit_internal_id": { + "type": "string", + "nullable": true, + "description": "Stable internal identifier of the verified individual behind the child session — useful for cross-session linking. `null` when no child session exists or the user has not been resolved yet." + }, + "kyc_document_type": { + "type": "string", + "nullable": true, + "description": "Document type presented in the child KYC session (e.g. `P`, `ID`, `DL`). `null` until the child session has an ID verification result." + }, + "kyc_nationality": { + "type": "string", + "nullable": true, + "description": "Nationality extracted from the document in the child KYC session. `null` until available." + }, + "kyc_liveness_passed": { + "type": "boolean", + "nullable": true, + "description": "Whether the child KYC session's liveness check passed. `null` when no liveness check has run." + }, + "kyc_verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the child KYC session reached `Approved`. `null` while the child session has any other status." + }, + "kyc_aml_categories": { + "type": "object", + "nullable": true, + "description": "Per-category summary derived from the person's AML hits. `null` when there are no hits.", + "properties": { + "sanctions": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "pep": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "adverse_media": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "warnings": { + "type": "string", + "enum": [ + "match", + "clear" + ] + } + } + }, + "kyc_notification_sent_at": { + "type": "string", + "nullable": true, + "description": "ISO 8601 timestamp of the last verification-invite notification sent for this person's child session. `null` when none was sent." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + } + }, + "beneficial_owners": { + "type": "array", + "description": "Beneficial owners discovered via the registry check, each enriched with the status of any child KYC session and AML screening spawned for them.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "description": "Beneficial owner's full name (or company name for corporate UBOs)." + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ] + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "roles": { + "type": "array", + "description": "Role names held by this owner, e.g. `[\"ubo\", \"shareholder\"]`.", + "items": { + "type": "string" + } + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership percentage as reported by the registry. Free-form string (e.g. `\"25\"`, `\"25-50%\"`); use `effective_ownership_percent` for a parsed numeric value." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership percentage as reported by the registry. Free-form string; use `effective_ownership_percent` for a parsed numeric value." + }, + "is_active": { + "type": "boolean" + }, + "aml_status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "nullable": true, + "description": "Status of the AML screening run for this beneficial owner. `null` when no AML screening was run." + }, + "kyc_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "nullable": true, + "description": "Raw lifecycle status of the child KYC session spawned for this beneficial owner. This is the full session status enum (not a reduced Approved/Declined/Pending set). `null` when no child KYC session exists." + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Shareable URL of the child KYC session. `null` when no child session exists." + }, + "kyc_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Session id of the child KYC session. Pass it to this same decision endpoint to fetch the person's full KYC report. `null` when no child session exists." + }, + "verification_workflow_type": { + "type": "string", + "nullable": true, + "description": "Base workflow type of the child session's workflow (e.g. `kyc`). `null` for graph-based workflows or when no child session exists." + }, + "verification_workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Workflow id used by the child session. `null` when no child session exists." + }, + "verification_workflow_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label of the child session's workflow. `null` when unset." + }, + "verification_workflow_features": { + "type": "array", + "nullable": true, + "description": "Per-feature progress of the child verification session, e.g. `[{\"feature\": \"ID_VERIFICATION\", \"status\": \"Approved\"}]`. `null` when the child session has no resolvable feature list.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature display name (e.g. `ID_VERIFICATION`, `LIVENESS`, `AML`)." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Feature-level status inside the child session." + } + } + } + }, + "didit_internal_id": { + "type": "string", + "nullable": true, + "description": "Stable internal identifier of the verified individual behind the child session — useful for cross-session linking. `null` when no child session exists or the user has not been resolved yet." + }, + "kyc_document_type": { + "type": "string", + "nullable": true, + "description": "Document type presented in the child KYC session (e.g. `P`, `ID`, `DL`). `null` until the child session has an ID verification result." + }, + "kyc_nationality": { + "type": "string", + "nullable": true, + "description": "Nationality extracted from the document in the child KYC session. `null` until available." + }, + "kyc_liveness_passed": { + "type": "boolean", + "nullable": true, + "description": "Whether the child KYC session's liveness check passed. `null` when no liveness check has run." + }, + "kyc_verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the child KYC session reached `Approved`. `null` while the child session has any other status." + }, + "kyc_aml_categories": { + "type": "object", + "nullable": true, + "description": "Per-category summary derived from the person's AML hits. `null` when there are no hits.", + "properties": { + "sanctions": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "pep": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "adverse_media": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "warnings": { + "type": "string", + "enum": [ + "match", + "clear" + ] + } + } + }, + "kyc_notification_sent_at": { + "type": "string", + "nullable": true, + "description": "ISO 8601 timestamp of the last verification-invite notification sent for this person's child session. `null` when none was sent." + }, + "child_beneficial_owners": { + "type": "array", + "description": "For corporate UBOs: the owning entity's own beneficial owners, as raw objects extracted from the registry. Empty array when not applicable.", + "items": { + "type": "object" + } + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true, + "description": "Parsed numeric ownership percentage — the max bound when available, otherwise the min bound. `null` when neither bound could be parsed." + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + } + } + } + } + } + }, + "submitted": { + "type": "object", + "description": "Parties submitted by the business admin during the Key People flow (BusinessParty rows where source=USER).", + "properties": { + "parties": { + "type": "array", + "items": { + "type": "object", + "description": "Party submitted by the business admin via the Key People flow (source=USER).", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ] + }, + "source": { + "type": "string", + "enum": [ + "USER" + ], + "description": "Always `USER` here — this bucket only contains parties entered or confirmed by the end user in the Key People flow (registry-discovered people appear under `registry`)." + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "format": "email", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true, + "description": "Nationality for persons; country of incorporation for company parties." + }, + "phone_number": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "position": { + "type": "string", + "nullable": true, + "description": "Free-text job title / position (persons only)." + }, + "custom_fields": { + "type": "object", + "description": "Workflow-defined custom fields collected for this party; keys are defined by the workflow's key-people field descriptors." + }, + "is_active": { + "type": "boolean" + }, + "is_skipped": { + "type": "boolean", + "description": "True when the end user skipped verification for this party (only possible when the role allows skipping)." + }, + "requires_verification": { + "type": "boolean", + "description": "True when a child KYC / KYB sub-session is required for this party based on role settings and ownership thresholds." + }, + "roles": { + "type": "array", + "description": "All roles held by this party, each with its own ownership and voting percentages. There is no scalar `role`/`ownership_percent` on the party itself.", + "items": { + "type": "object", + "properties": { + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "ownership_percent": { + "type": "number", + "nullable": true + }, + "voting_percent": { + "type": "number", + "nullable": true + } + } + } + }, + "kyc_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Child KYC session id for person parties requiring verification. `null` otherwise." + }, + "kyc_session_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "nullable": true, + "description": "Lifecycle status of the child KYC session. `null` when no child KYC session exists." + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true + }, + "kyb_sub_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Child KYB sub-session id for corporate parties requiring verification. `null` otherwise." + }, + "kyb_sub_session_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "nullable": true, + "description": "Lifecycle status of the child KYB sub-session. `null` when no sub-session exists." + }, + "kyb_sub_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Shareable URL of the child KYB sub-session. `null` when no sub-session exists." + }, + "verification_workflow_type": { + "type": "string", + "nullable": true, + "description": "Base workflow type of the child session's workflow (KYC session preferred, falling back to the KYB sub-session). `null` for graph-based workflows or when no child session exists." + }, + "verification_workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Workflow id used by the child session. `null` when no child session exists." + }, + "verification_workflow_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label of the child session's workflow. `null` when unset." + }, + "verification_workflow_features": { + "type": "array", + "nullable": true, + "description": "Per-feature progress of the child verification session, e.g. `[{\"feature\": \"ID_VERIFICATION\", \"status\": \"Approved\"}]`. `null` when the child session has no resolvable feature list.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature display name (e.g. `ID_VERIFICATION`, `LIVENESS`, `AML`)." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Feature-level status inside the child session." + } + } + } + }, + "didit_internal_id": { + "type": "string", + "nullable": true, + "description": "Stable internal identifier of the verified individual behind the child session. `null` when not resolved." + } + } + } + } + } + }, + "ubo_kyc_summary": { + "type": "object", + "nullable": true, + "description": "Aggregate UBO KYC progress, or null when no UBOs have linked KYC sessions.", + "properties": { + "total": { + "type": "integer" + }, + "approved": { + "type": "integer" + }, + "flagged": { + "type": "integer", + "description": "UBOs whose KYC is Declined or In Review." + }, + "pending": { + "type": "integer" + } + } + }, + "warnings": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + }, + "document_verifications": { + "type": "array", + "nullable": true, + "description": "Document verification checks grouped by workflow node. Present only when `session_kind = business`.", + "items": { + "type": "object", + "description": "Documents grouped by workflow node. Present only when session_kind = business.", + "properties": { + "status": { + "type": "string" + }, + "node_id": { + "type": "string", + "nullable": true + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "document_type": { + "type": "string", + "enum": [ + "LEGAL_PRESENCE", + "COMPANY_DETAILS", + "OWNERSHIP_STRUCTURE", + "REPRESENTATIVES_AUTHORIZATION", + "OTHER" + ], + "nullable": true, + "description": "High-level KYB document group the file was classified into. `null` while unclassified." + }, + "document_subtype": { + "type": "string", + "nullable": true, + "description": "Specific OCR-detected subtype (e.g. `CERTIFICATE_OF_INCORPORATION`, `SHAREHOLDER_REGISTRY`); `UNKNOWN` when the OCR could not confidently classify the document." + }, + "document_group": { + "type": "string", + "enum": [ + "LEGAL_PRESENCE", + "COMPANY_DETAILS", + "OWNERSHIP_STRUCTURE", + "REPRESENTATIVES_AUTHORIZATION", + "OTHER" + ], + "nullable": true, + "description": "Alias of `document_type` — the group name used by `groups` and `required_groups`." + }, + "file_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived presigned link to the uploaded file. `null` for analyst-created document request placeholders (`is_requested = true`) that have not been fulfilled yet." + }, + "original_filename": { + "type": "string", + "nullable": true, + "description": "Filename as uploaded by the user or analyst." + }, + "file_size": { + "type": "integer", + "nullable": true, + "description": "File size in bytes." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Per-document verification status." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "cross_check_result": { + "type": "object", + "nullable": true, + "description": "Cross-reference of the OCR-extracted document fields against the registry company profile. An object with `critical`, `non_critical` and `people` sections, each mapping a field name (e.g. `company_name`, `registration_number`, `incorporation_date`, `registered_address`) to a comparison record like `{\"document\": \"...\", \"registry\": \"...\", \"match\": true, \"score\": 0.97}`. `null` when the document had no OCR data or there was no registry company to compare against." + }, + "document_metadata": { + "type": "object", + "nullable": true, + "description": "Metadata extracted from the uploaded corporate document. When metadata extraction succeeded, all 16 keys are always present (individual values are `null` when not applicable to the file type); the whole object is `null` when nothing could be extracted. For PDFs, `overlay_manipulation` carries forensic overlay-text analysis.", + "properties": { + "file_size": { + "type": "integer", + "nullable": true, + "description": "Size of the document." + }, + "content_type": { + "type": "string", + "nullable": true, + "description": "Content type of the document." + }, + "creation_date": { + "type": "string", + "nullable": true, + "description": "Date when the document was created." + }, + "modified_date": { + "type": "string", + "nullable": true, + "description": "Date when the document was modified." + }, + "creator": { + "type": "string", + "nullable": true, + "description": "Authoring tool/user recorded in the file metadata (PDF `Creator`)." + }, + "producer": { + "type": "string", + "nullable": true, + "description": "Software that produced the file (PDF `Producer`)." + }, + "software": { + "type": "string", + "nullable": true, + "description": "Editing software recorded in the file metadata." + }, + "encryption": { + "type": "string", + "nullable": true, + "description": "Encryption scheme of the file, when encrypted." + }, + "is_signed": { + "type": "boolean", + "nullable": true, + "description": "Whether the document carries a digital signature." + }, + "is_tampered": { + "type": "boolean", + "nullable": true, + "description": "Whether the digital signature failed validation (post-signing modification)." + }, + "signature_info": { + "type": "object", + "nullable": true, + "description": "Details of the digital signature(s), when present." + }, + "exif_original_date": { + "type": "string", + "nullable": true, + "description": "EXIF original capture date, for image uploads." + }, + "exif_digitized_date": { + "type": "string", + "nullable": true, + "description": "EXIF digitization date, for image uploads." + }, + "processed_by_known_editor": { + "type": "boolean", + "nullable": true, + "description": "Whether metadata indicates the file passed through a known image/PDF editor." + }, + "has_different_creation_mod_date": { + "type": "boolean", + "nullable": true, + "description": "Whether creation and modification dates differ — a possible (but not conclusive) editing signal." + }, + "overlay_manipulation": { + "type": "object", + "nullable": true, + "description": "PDF forensic analysis for suspected overlay-text manipulation. Null when no overlay manipulation evidence was found or the document was not a PDF.", + "properties": { + "detected": { + "type": "boolean", + "description": "Whether overlay-text manipulation evidence was detected." + }, + "analyzed": { + "type": "boolean", + "description": "Whether the PDF could be analyzed." + }, + "signals": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Forensic signals that fired, such as duplicate_font_subset or glyph_fragmentation." + }, + "manipulated_regions": { + "type": "array", + "description": "Suspected manipulated areas in PDF page coordinates.", + "items": { + "type": "object", + "properties": { + "page": { + "type": "integer" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + }, + "width": { + "type": "number" + }, + "height": { + "type": "number" + }, + "page_width": { + "type": "number" + }, + "page_height": { + "type": "number" + } + } + } + } + } + } + } + }, + "description": { + "type": "string", + "nullable": true, + "description": "Analyst-provided label or reason for a document request. `null` for regular uploads." + }, + "is_requested": { + "type": "boolean", + "description": "True for analyst-created document request placeholders awaiting an upload." + }, + "ocr_data": { + "type": "object", + "nullable": true + }, + "is_console_upload": { + "type": "boolean", + "description": "True for documents uploaded from the console rather than the end-user flow. Console uploads never change the feature-level aggregate status." + }, + "is_confirmed": { + "type": "boolean", + "description": "Console uploads start `false` and flip `true` once the admin confirms them; end-user uploads are always `true`." + } + } + } + }, + "groups": { + "type": "object", + "description": "Per-group progress counters keyed by group name (e.g. legal_presence, ownership_structure).", + "additionalProperties": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "approved": { + "type": "integer" + }, + "pending": { + "type": "integer" + }, + "declined": { + "type": "integer" + }, + "in_review": { + "type": "integer" + }, + "other": { + "type": "integer" + }, + "missing": { + "type": "integer", + "description": "Requested placeholders with no file uploaded yet." + } + } + } + }, + "required_groups": { + "type": "array", + "items": { + "type": "string" + } + }, + "warnings": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Whether the session was created by a live application (`live`) or a sandbox application (`sandbox`). Sandbox sessions are free, fully simulated, and never bill credits." + }, + "sandbox_scenario": { + "type": "string", + "nullable": true, + "description": "The sandbox scenario the session was created with. `null` for live sessions and for sandbox sessions created without one." + }, + "resubmissions": { + "type": "array", + "description": "Returned only with `include=resubmissions`, for user and business sessions. One entry per resubmission request, oldest first. Each entry carries the results the resubmitted steps returned before the request, so earlier submissions stay readable. Deleting the session deletes every submission.", + "items": { + "type": "object", + "properties": { + "number": { + "type": "integer", + "description": "1 for the first resubmission of the session, 2 for the second, and so on." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the resubmission was requested." + }, + "previous_status": { + "type": "string", + "description": "The session status just before the request." + }, + "nodes": { + "type": "array", + "description": "The workflow steps sent back to the user.", + "items": { + "type": "object", + "properties": { + "node_id": { + "type": "string" + }, + "feature": { + "type": "string" + } + } + } + }, + "reasons": { + "type": "object", + "nullable": true, + "additionalProperties": { + "type": "string" + }, + "description": "The reason given for each step, keyed by `node_id`." + }, + "requested_by": { + "type": "object", + "nullable": true, + "description": "Who requested the resubmission.", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "results": { + "type": "object", + "additionalProperties": true, + "description": "What the resubmitted steps returned before the request, under the same keys and in the same shape as the current results of this response (`id_verifications`, `nfc_verifications`, `liveness_checks`, `face_matches`, `poa_verifications`, `phone_verifications`, `email_verifications`, `aml_screenings`, `ip_analyses`, `database_validations`, `questionnaire_responses`, `document_ai_documents`, `geolocation_checks`; for business sessions also `document_verifications`). Only the keys of the steps that were sent back appear." + } + } + } + }, + "biometric_consent": { + "type": "object", + "nullable": true, + "description": "Whether the session required explicit biometric consent before a face capture, why, and the evidence of the answer. User Verification (KYC) sessions only; the same object is in the `decision` of session webhooks. `null` when the session was never evaluated, for example a workflow without a face capture. Fields with no recorded value are `null`. A native SDK below 4.10.0, or one that does not report its version, cannot show the consent step: its face captures run as before, and a session that required consent then reports `status: \"pending\"` with `reason: \"BIOMETRIC_CONSENT_UNSUPPORTED_SDK\"`. See [Biometric consent](/getting-started/biometric-consent).", + "properties": { + "required": { + "type": "boolean", + "description": "Whether the session must collect explicit biometric consent. Never goes back to `false`." + }, + "trigger": { + "type": "string", + "nullable": true, + "enum": [ + "declared_state", + "ip_state", + "ip_unknown", + "document_issuer", + "forced", + "customer_collected" + ], + "description": "Why consent is required, or how it was satisfied. `declared_state`: you declared a covered state (`user_state`, or a US address in `expected_details`). `ip_state`: the person's IP is in a covered state (Illinois, Texas, Colorado). `ip_unknown`: the evidence places the person in the US but the state cannot be told (the IP is in the US with no state, or cannot be located while your details or the document place the person in the US). `document_issuer`: the identity document was issued by a covered state. `forced`: the application's mode is Always. `customer_collected`: you sent `biometric_consent` when creating the session. `null` while not required." + }, + "status": { + "type": "string", + "enum": [ + "not_required", + "pending", + "given", + "declined" + ], + "description": "`not_required`; `pending` (required and not given: the person has not answered yet, or `reason` says why it was not collected); `given`; `declined` (a decline was recorded and the session ended as `Declined`; no Didit screen offers a decline today)." + }, + "given_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When consent was given. For `customer_collected`, the `given_at` you sent." + }, + "declined_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When a decline was recorded." + }, + "text_version": { + "type": "string", + "nullable": true, + "description": "Version id of the consent text the person answered, or the one you sent." + }, + "text_hash": { + "type": "string", + "nullable": true, + "description": "SHA-256 (hex) of the exact consent text shown, in the language it was shown in." + }, + "locale": { + "type": "string", + "nullable": true, + "description": "Language the consent text was shown in." + }, + "policy_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The biometric data policy the consent step linked to." + }, + "step": { + "type": "string", + "nullable": true, + "description": "Workflow step the session was on when the person answered, for example `LIVENESS`." + }, + "node_id": { + "type": "string", + "nullable": true, + "description": "Workflow graph node the session was on when the person answered." + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "IP the answer came from." + }, + "ip_country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of the answer's IP." + }, + "ip_state": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision of the answer's IP, for example `US-IL`." + }, + "ip_city": { + "type": "string", + "nullable": true, + "description": "City of the answer's IP." + }, + "user_agent": { + "type": "string", + "nullable": true, + "description": "User agent of the answer's request." + }, + "detected_ip_address": { + "type": "string", + "nullable": true, + "description": "The person's IP the requirement was last evaluated from. Can differ from `ip_address` when the person answered on another device." + }, + "detected_country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of the detected IP." + }, + "detected_state": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision of the detected IP." + }, + "detected_city": { + "type": "string", + "nullable": true, + "description": "City of the detected IP." + }, + "declared_state": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision you declared: `user_state`, or the state of a US address in `expected_details`." + }, + "document_state": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision that issued the identity document, read at ID capture." + }, + "jurisdictions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "US states (USPS codes, for example `IL`) whose biometric law covers the person. `US` when the evidence places the person in the US but no state is known. Empty when none applies." + }, + "retention_months": { + "type": "integer", + "nullable": true, + "description": "The shortest biometric retention limit, in months, among `jurisdictions`. `null` when none applies." + }, + "reason": { + "type": "string", + "nullable": true, + "enum": [ + "BIOMETRIC_CONSENT_UNSUPPORTED_SDK", + "BIOMETRIC_CONSENT_DECLINED" + ], + "description": "`BIOMETRIC_CONSENT_UNSUPPORTED_SDK`: consent was required and a native SDK below 4.10.0, or one that did not report its version, captured a face without the consent step; `status` stays `pending`, or `given` if the person later agreed in the hosted flow. `BIOMETRIC_CONSENT_DECLINED`: a decline was recorded (it replaces the SDK reason). `null` otherwise, and always `null` when the session did not require consent." + }, + "decline_reason": { + "type": "string", + "nullable": true, + "enum": [ + "BIOMETRIC_CONSENT_DECLINED" + ], + "description": "`BIOMETRIC_CONSENT_DECLINED` when a recorded decline ended the session, otherwise `null`." + }, + "face_data_deleted_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the decline deleted the session's face images, videos and face templates. `null` unless a decline was recorded." + } + } + }, + "investigator": { + "$ref": "#/components/schemas/InvestigatorBlock" + }, + "biometric_retention": { + "$ref": "#/components/schemas/BiometricRetention" + }, + "fraud_check": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "The result of a Fraud Check API call. `null` for every other session." + }, + "full_session_video": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Expiring URL of the full-session recording. Returned only when your organization records full-session video." + }, + "full_session_video_recorded_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the full-session recording was made. Returned only when your organization records full-session video." + } + } + }, + "WorkflowDetail": { + "type": "object", + "description": "The fields below are typed; the object also carries every other column of the workflow configuration (feature toggles, thresholds, warning actions, allowed documents, callbacks) as additional properties. Fields that do not apply to the `workflow_type` are `null`.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Per-version identifier (`settings_uuid`). Every version of a workflow has its own." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier shared by every version of the workflow. Sessions created with it use the latest published version." + }, + "workflow_label": { + "type": "string", + "description": "Display name of the workflow." + }, + "workflow_type": { + "type": "string", + "nullable": true, + "enum": [ + "kyc", + "kyb", + "adaptive_age_verification", + "biometric_authentication", + "address_verification", + "questionnaire_verification", + "email_verification", + "phone_verification", + null + ], + "description": "Workflow template. `null` for graph workflows created without a template." + }, + "is_default": { + "type": "boolean", + "description": "Whether this is the application's default workflow." + }, + "is_archived": { + "type": "boolean", + "description": "Archived workflows cannot start new sessions." + }, + "version": { + "type": "integer", + "description": "Version number within the workflow." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Draft versions are editable; published versions are immutable and used for new sessions." + }, + "published_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this version was published. `null` for drafts." + }, + "is_simple_workflow": { + "type": "boolean", + "description": "`true` when the workflow has no custom `workflow_graph`." + }, + "is_editable": { + "type": "boolean", + "description": "`true` only for `draft` versions." + }, + "workflow_url": { + "type": "string", + "format": "uri", + "description": "Public URL that starts a session with this workflow." + }, + "workflow_graph": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Graph structure of the workflow (nodes and edges). `null` for simple workflows." + }, + "features": { + "type": "string", + "description": "Enabled features joined with `\" + \"`, for example `\"OCR + LIVENESS\"`." + }, + "total_price": { + "type": "number", + "nullable": true, + "description": "Price per session; for graph workflows the most expensive path." + }, + "min_price": { + "type": "number", + "nullable": true, + "description": "Price of the cheapest path through the workflow." + }, + "max_price": { + "type": "number", + "nullable": true, + "description": "Price of the most expensive path through the workflow." + }, + "special_add_on_prices": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Organization-specific add-on prices, or `null` when none are set." + }, + "max_retry_attempts": { + "type": "integer", + "description": "Retries allowed after a declined verification." + }, + "retry_window_days": { + "type": "integer", + "nullable": true, + "description": "Days during which the retries are allowed." + }, + "session_expiration_time": { + "type": "integer", + "description": "Seconds before an unfinished session expires." + }, + "questionnaire": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "The questionnaire attached to the workflow, or `null`." + }, + "kyb_role_settings": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Per-role verification settings for KYB key people." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "signature_availability": { + "type": "object", + "description": "Which E-signature levels can sign right now. Check it before you set `signature_level`. Returned on `kyc`, `kyb` and graph workflows.", + "properties": { + "simple": { + "type": "boolean", + "description": "Simple is always available." + }, + "aes": { + "type": "boolean", + "description": "Whether Advanced (AES) can sign now. While it is `false`, saving a new step at `signature_level: \"aes\"` is refused with `aes_not_available`." + }, + "qes": { + "type": "boolean", + "description": "Whether Qualified (QES) can sign now. It is `false` until a qualified provider is connected." + } + } + }, + "is_bank_verification_enabled": { + "type": "boolean", + "description": "Whether a simple KYC or KYB workflow runs Bank verification. It runs after the contact steps (and Location verification) and before database validation, AML and IP analysis. Bank verification is not generally available yet: turning it on is refused wherever it is not enabled." + }, + "bank_verification_config": { + "type": "object", + "additionalProperties": true, + "description": "Bank verification settings of a simple workflow, with the same keys a `BANK_VERIFICATION` node takes (for example `bank_countries` and `bank_method_order`). Unknown keys are dropped. Every key is optional and an absent key keeps its default, so `{}` means all defaults. See [Bank verification settings](/management-api/workflows/feature-configs#bank-verification).", + "example": {} + }, + "is_geolocation_enabled": { + "type": "boolean", + "description": "Whether a simple KYC workflow runs Location verification. It runs after the contact steps and before Bank verification and database validation. Business (KYB) workflows cannot run it." + }, + "geolocation_config": { + "type": "object", + "additionalProperties": true, + "description": "Location verification settings of a simple workflow, with the same keys a `GEOLOCATION` node takes (for example `is_geolocation_geofencing_enabled` and `geolocation_max_attempts`). Unknown keys are dropped. Every key is optional and an absent key keeps its default, so `{}` means all defaults.", + "example": {} + } + }, + "additionalProperties": true + }, + "UserSessionListItem": { + "title": "KYC session row (session_kind: user)", + "type": "object", + "description": "Summary row for a User Verification (KYC) session. Identity fields (`full_name`, `country`, `document_type`, `portrait_image`, contact fields) are populated progressively as the user completes verification steps, and are `null` until the relevant step finishes. Fields restricted by the workflow's response-attributes configuration are also returned as `null`.", + "required": [ + "session_id", + "session_kind", + "session_number", + "session_url", + "portrait_image", + "document_type", + "full_name", + "country", + "status", + "vendor_data", + "didit_internal_id", + "created_at", + "features", + "phone_number", + "email_address", + "additional_details" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of the verification session. Use it with `GET /v3/session/{sessionId}/decision/` to fetch the full decision payload.", + "example": "5b3720ed-d429-42ef-b67f-37ea805f48ee" + }, + "session_kind": { + "type": "string", + "enum": [ + "user" + ], + "description": "Row discriminator. Always `\"user\"` for KYC rows; Business Verification (KYB) rows have `\"business\"` and a different shape.", + "example": "user" + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of the session within your application. Also matched by the `search` parameter.", + "example": 720 + }, + "session_url": { + "type": "string", + "nullable": true, + "description": "Hosted verification URL the end user opens to complete the flow. `null` when the session has no hosted URL.", + "example": "https://verify.didit.me/session/CuXoBYCYVO5Y" + }, + "portrait_image": { + "type": "string", + "nullable": true, + "description": "Expiring (presigned) URL of the best available portrait: the NFC chip photo when available, otherwise the ID document portrait, otherwise the liveness reference image. `null` until one of those exists. Download promptly; the link expires.", + "example": "https:///portraits/5b3720ed.jpg?X-Amz-Expires=3600&X-Amz-Signature=..." + }, + "document_type": { + "type": "string", + "nullable": true, + "enum": [ + "Passport", + "Identity Card", + "Driver's License", + "Residence Permit", + "Health Insurance Card", + "Tax Card" + ], + "description": "Human-readable type of the verified ID document. `null` before document verification completes, and also `null` after completion when the verified code has no display mapping (`SSC`, `WP`, `VISA`, `PSC`, `BC`, `OTHER`). Note: the `document_type` query parameter filters by short codes (`P`, `ID`, `DL`, `RP`, ...), not by these display values.", + "example": "Passport" + }, + "full_name": { + "type": "string", + "nullable": true, + "description": "Best available full name for the session, resolved in order: ID document OCR, AML screened data, database-validation screened data, proof-of-address document, then the expected details provided at session creation. `null` when none is available yet.", + "example": "Jane Marie Doe" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-3 country associated with the session, resolved in order: ID document issuing state, database-validation issuing state, proof-of-address issuing state, expected details, then the verified phone's country. May differ from nationality.", + "example": "ESP" + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Overall session status. See the [Verification Statuses](/integration/verification-statuses) page for the lifecycle.", + "example": "Approved" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the end user, echoed back exactly as sent at session creation.", + "example": "your-user-id-123" + }, + "didit_internal_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Didit's internal identifier for the verified user profile behind this session. Stable across multiple sessions of the same user; filterable via the `didit_internal_id` query parameter.", + "example": "d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "UTC timestamp when the session was created.", + "example": "2026-06-09T12:26:54.328364Z" + }, + "features": { + "type": "array", + "description": "Per-feature status for every verification step configured in the session's workflow, in workflow order.", + "items": { + "type": "object", + "required": [ + "feature", + "status" + ], + "properties": { + "feature": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "NFC", + "LIVENESS", + "FACE_MATCH", + "POA", + "QUESTIONNAIRE", + "EMAIL_VERIFICATION", + "PHONE", + "AML", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "DATABASE_VALIDATION", + "GEOLOCATION" + ], + "description": "Verification feature identifier.", + "example": "ID_VERIFICATION" + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Status of this feature. `Not Finished` until the step completes.", + "example": "Approved" + } + } + } + }, + "phone_number": { + "type": "object", + "nullable": true, + "description": "Phone associated with the session: the verified phone when phone verification ran, otherwise the contact phone provided at session creation. `null` when neither exists.", + "required": [ + "number", + "is_verified" + ], + "properties": { + "number": { + "type": "string", + "description": "Phone number in E.164 format.", + "example": "+14155552671" + }, + "is_verified": { + "type": "boolean", + "description": "`true` only when the phone-verification feature approved this number.", + "example": true + } + } + }, + "email_address": { + "type": "object", + "nullable": true, + "description": "Email associated with the session: the verified email when email verification ran, otherwise the contact email provided at session creation. `null` when neither exists.", + "required": [ + "email", + "is_verified" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "example": "jane@example.com" + }, + "is_verified": { + "type": "boolean", + "description": "`true` only when the email-verification feature approved this address.", + "example": true + } + } + }, + "additional_details": { + "type": "object", + "nullable": true, + "description": "Compact per-feature extras. Only features that produced data appear as keys; `null` when no feature produced extras. Fields excluded by the workflow's response-attributes configuration are omitted.", + "properties": { + "aml": { + "type": "object", + "description": "Present when AML screening ran.", + "properties": { + "total_hits": { + "type": "integer", + "description": "Number of AML screening hits.", + "example": 0 + }, + "entity_type": { + "type": "string", + "description": "Screened entity type.", + "example": "person" + }, + "is_adverse_media_fetched": { + "type": "boolean", + "example": false + }, + "screened_data": { + "type": "object", + "description": "Subset of the data that was screened. Only non-empty keys are included.", + "properties": { + "nationality": { + "type": "string", + "example": "ESP" + }, + "document_number": { + "type": "string", + "example": "XX1234567" + }, + "date_of_birth": { + "type": "string", + "example": "1990-05-21" + } + } + } + } + }, + "database_validation": { + "type": "object", + "description": "Present when database validation ran.", + "properties": { + "validation_type": { + "type": "string", + "example": "DOCUMENT_VALIDATION" + }, + "match_type": { + "type": "string", + "example": "FULL_MATCH" + }, + "identification_number": { + "type": "string", + "description": "Tax, personal, or document number that was validated (first available).", + "example": "12345678Z" + } + } + }, + "poa": { + "type": "object", + "description": "Present when proof of address ran.", + "properties": { + "document_type": { + "type": "string", + "example": "UTILITY_BILL" + }, + "issuer": { + "type": "string", + "example": "Energy Co" + }, + "address": { + "type": "string", + "example": "Calle Mayor 1, 28013 Madrid" + }, + "name_on_document": { + "type": "string", + "example": "Jane Marie Doe" + } + } + }, + "liveness": { + "type": "object", + "description": "Present when a liveness check ran.", + "properties": { + "method": { + "type": "string", + "example": "PASSIVE" + }, + "score": { + "type": "number", + "description": "Liveness confidence score, rounded to 2 decimals.", + "example": 0.98 + } + } + }, + "face_match": { + "type": "object", + "description": "Present when face match ran.", + "properties": { + "score": { + "type": "number", + "description": "Face-match similarity score, rounded to 2 decimals.", + "example": 0.95 + }, + "source_image": { + "type": "string", + "description": "Expiring (presigned) URL of the source face image.", + "example": "https:///faces/source.jpg?X-Amz-Expires=3600&..." + }, + "target_image": { + "type": "string", + "description": "Expiring (presigned) URL of the target face image.", + "example": "https:///faces/target.jpg?X-Amz-Expires=3600&..." + } + } + }, + "phone_verification": { + "type": "object", + "description": "Present when phone verification ran.", + "properties": { + "country_code": { + "type": "string", + "description": "ISO 3166-1 alpha-2 country of the phone number.", + "example": "US" + }, + "is_temporary": { + "type": "boolean", + "example": false + }, + "is_ported": { + "type": "boolean", + "example": false + }, + "is_blocked": { + "type": "boolean", + "example": false + } + } + }, + "email_verification": { + "type": "object", + "description": "Present when email verification ran.", + "properties": { + "is_breached": { + "type": "boolean", + "example": false + }, + "is_disposable": { + "type": "boolean", + "example": false + }, + "is_undeliverable": { + "type": "boolean", + "example": false + }, + "is_blocklisted": { + "type": "boolean", + "example": false + } + } + } + } + }, + "assignee": { + "type": "object", + "nullable": true, + "description": "The Console reviewer the session is assigned to, or `null` when nobody holds it.", + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "assigned_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "waiting_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the session started waiting for review. `null` unless the session is `In Review`." + }, + "sla_due_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the review is due, from the review deadline set for your application in the Console. `null` unless the session is `In Review` and a deadline is set." + }, + "top_warnings": { + "type": "array", + "description": "Up to a few of the session's most important warnings, for a quick triage view. The full list is in the decision.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "description": "Warning code, for example `FACE_MATCH_LOW_SIMILARITY`." + }, + "feature": { + "type": "string", + "description": "The feature that raised it." + }, + "log_type": { + "type": "string", + "nullable": true, + "description": "`error` or `warning`; `null` for a legacy entry." + }, + "short_description": { + "type": "string" + } + } + } + }, + "warnings_count": { + "type": "integer", + "description": "Number of warnings on the session." + } + } + }, + "BusinessSessionListItem": { + "title": "KYB session row (session_kind: business)", + "type": "object", + "description": "Summary row for a Business Verification (KYB) session. Returned only when `session_kind` is `business` or `all`.", + "required": [ + "session_id", + "session_kind", + "session_number", + "status", + "vendor_data", + "created_at", + "workflow_id", + "workflow_type", + "workflow_label", + "company_name", + "registration_number", + "country" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of the business verification session.", + "example": "8a1f63c2-1b9e-4f3a-9c41-2f6a1f0b7d52" + }, + "session_kind": { + "type": "string", + "enum": [ + "business" + ], + "description": "Row discriminator. Always `\"business\"` for KYB rows.", + "example": "business" + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of the business session within your application. Numbered independently from KYC sessions.", + "example": 42 + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Overall session status. See the [Verification Statuses](/integration/verification-statuses) page.", + "example": "Approved" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the business, echoed back exactly as sent at session creation.", + "example": "your-company-id-77" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "UTC timestamp when the session was created.", + "example": "2026-06-08T09:14:02.118450Z" + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Identifier of the workflow this session was created with.", + "example": "9f9b1234-aaaa-bbbb-cccc-1234567890ab" + }, + "workflow_type": { + "type": "string", + "nullable": true, + "description": "Workflow type. `kyb` for business workflows.", + "example": "kyb" + }, + "workflow_label": { + "type": "string", + "nullable": true, + "description": "Display name of the workflow.", + "example": "Standard KYB" + }, + "company_name": { + "type": "string", + "nullable": true, + "description": "Name of the company being verified, from the registry company linked to the session. `null` until a company is selected.", + "example": "Acme Holdings S.L." + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Company registration number from the registry company linked to the session.", + "example": "B12345678" + }, + "country": { + "type": "string", + "nullable": true, + "description": "Country code of the registry company linked to the session.", + "example": "ES" + } + } + }, + "TransactionVerificationSessionAction": { + "title": "Verification session", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "verification_session" + ] + }, + "url": { + "type": "string", + "format": "uri", + "description": "Hosted verification URL to open for the end user." + }, + "session_id": { + "type": "string", + "format": "uuid", + "description": "Verification session id." + }, + "session_token": { + "type": "string", + "description": "Session token, used by the mobile SDKs to launch the flow natively." + }, + "status": { + "type": "string", + "description": "Verification session status." + } + }, + "required": [ + "type", + "url" + ] + }, + "TransactionWalletOwnershipAction": { + "title": "Wallet ownership", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "wallet_ownership" + ] + }, + "url": { + "type": "string", + "format": "uri", + "description": "Hosted wallet-ownership widget URL. Open it in a real browser surface - on mobile use SFSafariViewController or a Chrome Custom Tab, never an embedded webview." + }, + "widget_session_id": { + "type": "string", + "format": "uuid", + "description": "Wallet-ownership widget session id." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the widget session expires." + } + }, + "required": [ + "type", + "url" + ] + }, + "TransactionRuleActionAddScore": { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "add_score" + ] + }, + "value": { + "type": "integer", + "description": "Points added to the transaction's risk score. May be negative." + } + }, + "description": "Add points to the transaction's cumulative risk score; the total decides the status via the application's review/decline thresholds." + }, + "TransactionRuleActionChangeStatus": { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "change_status" + ] + }, + "value": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Status to set immediately, regardless of score. Alias key: status." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "REQUIRED when value is AWAITING_USER: the verification workflow the subject is asked to complete. Alias key: remediation_workflow_id. Didit creates a hosted remediation session for the subject when the rule fires." + } + }, + "description": "Directly set the transaction status. DECLINED always wins across matched rules." + }, + "TransactionRuleActionAddTags": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "add_tags" + ] + }, + "tag_uuid": { + "type": "string", + "format": "uuid", + "description": "Existing application tag to attach." + }, + "tag_name": { + "type": "string", + "description": "Tag name to attach; created on first use (stored uppercased). Provide tag_uuid or tag_name." + }, + "tag_color": { + "type": "string", + "description": "Hex color used when tag_name creates a new tag, e.g. #007AFE." + } + }, + "description": "Attach an application tag to the matched transaction." + }, + "TransactionRuleActionAddNote": { + "type": "object", + "required": [ + "type", + "note" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "add_note" + ] + }, + "note": { + "type": "string", + "description": "Note text attached to the transaction (author: Rule Engine). Alias key: value." + } + }, + "description": "Attach an automated analyst note to the matched transaction." + }, + "TransactionRuleActionAddToList": { + "type": "object", + "required": [ + "type", + "list_id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "add_to_list" + ] + }, + "list_id": { + "type": "string", + "format": "uuid", + "description": "List to add the subject's vendor_data to." + } + }, + "description": "Add the subject's vendor_data to a blocklist, allowlist or custom list." + }, + "TransactionRuleActionOpenCase": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "open_case" + ] + }, + "blueprint": { + "type": "string", + "format": "uuid", + "description": "Case blueprint UUID; defaults to the application's default blueprint." + }, + "grouping": { + "type": "string", + "enum": [ + "by_applicant", + "by_rule_and_applicant" + ], + "description": "Whether repeat matches join one case per applicant or one per rule+applicant." + }, + "attach_matched_transaction": { + "type": "boolean", + "description": "Attach the matched transaction to the case." + } + }, + "description": "Open (or add to) an investigation case. Only the first open_case action on a rule is applied." + }, + "KYBRegistrySearchResults": { + "type": "object", + "properties": { + "companies": { + "type": "array", + "items": { + "type": "object", + "properties": { + "kyb_response_id": { + "type": "string", + "nullable": true, + "description": "Handle for this candidate — pass it to `POST /v3/kyb/select/` to retrieve the full profile. Ephemeral and scoped to this search: do not store it long-term; re-search to get a fresh one.", + "example": "69aeeb95febb0f1704042259" + }, + "name": { + "type": "string", + "nullable": true, + "description": "Registered company name.", + "example": "Tesco PLC" + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Registry registration number.", + "example": "00445790" + }, + "status": { + "type": "string", + "nullable": true, + "description": "Normalized registry status of the company (e.g. `active`, `dissolved`, `inactive`, `struck off`).", + "example": "active" + }, + "type": { + "type": "string", + "nullable": true, + "description": "Legal form as reported by the registry.", + "example": "Public Limited Company" + }, + "registered_address": { + "type": "string", + "nullable": true, + "description": "Registered address of the candidate, when the registry returns one. Use it, with the registration number, to tell same-named companies apart: in the United States a candidate can be listed under the state where the company operates rather than the state you searched.", + "example": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, AL7 1GA" + }, + "risk_level": { + "type": "string", + "nullable": true, + "description": "Registry-reported risk level, when available. Usually `null` at search time." + }, + "fetch_status": { + "type": "string", + "nullable": true, + "description": "Always `pending` for search candidates — the full company profile is only fetched (and billed) when you select the candidate.", + "example": "pending" + } + } + }, + "description": "Deduplicated candidate list. Empty while `search_resolved=false` (webhook mode) or when nothing matched." + }, + "pagination": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Number of candidates returned." + }, + "page": { + "type": "integer" + }, + "per_page": { + "type": "integer", + "example": 25 + } + } + }, + "search_status": { + "type": "string", + "nullable": true, + "enum": [ + "pending", + "resolved" + ], + "description": "Registry search lifecycle status." + }, + "search_resolved": { + "type": "boolean", + "description": "`true` once the candidate list is final." + } + } + }, + "KYBRegistryCandidateRequest": { + "type": "object", + "required": [ + "kyb_response_id" + ], + "properties": { + "kyb_response_id": { + "type": "string", + "maxLength": 1024, + "description": "Candidate handle from `POST /v3/kyb/search/` (`kyb_registry.companies[].kyb_response_id`). Ephemeral: use it shortly after the search that produced it. Handles can be long, so store and send the value exactly as returned, without trimming or assuming a length.", + "example": "69aeeb95febb0f1704042259" + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for this company; echoed back and stored with the business session." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON stored with the business session and echoed back." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "Must be `true` (the default). KYB registry selects are always persisted so billing and retrieval can be tracked; `false` returns `400`." + } + } + }, + "WebhookEventEnvelope": { + "type": "object", + "description": "Fields Didit adds to every signed webhook delivery, whatever the event type. Read `webhook_type` to know which payload you received.", + "required": [ + "event_id", + "webhook_type", + "timestamp", + "created_at", + "application_id", + "environment" + ], + "properties": { + "event_id": { + "type": "string", + "format": "uuid", + "description": "Identifier of the event. Every destination receives the same value and a redelivery keeps it, so use it to deduplicate." + }, + "webhook_type": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "timestamp": { + "type": "integer", + "description": "Unix time in seconds at which this delivery was signed. Equal to the `X-Timestamp` header; a redelivery carries a new value." + }, + "created_at": { + "type": "integer", + "description": "Unix time in seconds of the change that produced the event." + }, + "application_id": { + "type": "string", + "format": "uuid", + "description": "The application the event belongs to." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "`sandbox` for sandbox sessions and sandbox applications, otherwise `live`." + } + } + }, + "SessionWebhookEventData": { + "type": "object", + "description": "Session fields of `status.updated` and `data.updated`. Optional fields are omitted, never sent as `null`, when they do not apply. Business (KYB) sessions add `business_session_id`, `session_kind` and `vendor_business_id`.", + "required": [ + "session_id", + "status", + "sandbox_scenario" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The verification session." + }, + "business_session_id": { + "type": "string", + "format": "uuid", + "description": "Business (KYB) sessions only: the same value as `session_id`." + }, + "session_kind": { + "type": "string", + "enum": [ + "business" + ], + "description": "Business (KYB) sessions only. User (KYC) session events carry no top-level `session_kind`; read `decision.session_kind` instead." + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Session status after the change." + }, + "trigger": { + "type": "string", + "description": "Present when the event was not produced by the verification flow itself, for example `manual_review`, `manual_step_update`, `ongoing_monitoring`, `webhook_reconciliation` (a `status.updated` resent because the first terminal webhook was never created, so you may receive a second event for a decision you already got: deduplicate on `session_id` and `status`), `manual_webhook_recovery`, `kyb_registry_change`, `biometric_retention` (a `data.updated` sent when the session's live biometric data is deleted on schedule) or a `console_*` edit such as `console_registry_edit`, `console_key_people_skip` or `console_key_people_regenerate`." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "The session's workflow. Omitted when the session has none." + }, + "workflow_version": { + "type": "integer", + "description": "Version of the workflow the session ran. Sent together with `workflow_id`." + }, + "vendor_data": { + "type": "string", + "description": "Your `vendor_data` for the session. Omitted when empty." + }, + "vendor_business_id": { + "type": "string", + "description": "Business (KYB) sessions only: the business the session belongs to. Omitted when none." + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Your session `metadata`. Omitted when empty." + }, + "decision": { + "$ref": "#/components/schemas/SessionDecision" + }, + "resubmit_info": { + "type": "object", + "description": "Only when `status` is `Resubmitted`: the steps the user must redo and why.", + "required": [ + "nodes_to_resubmit", + "reasons" + ], + "properties": { + "nodes_to_resubmit": { + "type": "array", + "items": { + "type": "object", + "required": [ + "node_id", + "feature" + ], + "properties": { + "node_id": { + "type": "string", + "description": "Node identifier as it appears in the session's workflow definition (for example `feature_ocr`, `feature_liveness`).", + "example": "feature_poa" + }, + "feature": { + "type": "string", + "description": "Feature type of the node, for example `OCR`, `LIVENESS` or `PROOF_OF_ADDRESS`.", + "example": "PROOF_OF_ADDRESS" + } + } + }, + "description": "Workflow steps the user must redo. The same `{node_id, feature}` entries as `nodes_to_resubmit` in [Update Status](#patch-/v3/session/-sessionId-/update-status/)." + }, + "reasons": { + "type": "object", + "additionalProperties": true, + "description": "Reason per `node_id` in `nodes_to_resubmit`." + } + } + }, + "sandbox_scenario": { + "type": "string", + "nullable": true, + "description": "The simulated outcome armed on a sandbox session, or `null`. Always `null` for live sessions." + } + } + }, + "SessionStatusUpdatedEvent": { + "description": "A verification session changed status. `decision` is sent only for `Approved`, `Declined`, `In Review` and `Abandoned`; it has the shape of `GET /v3/session/{sessionId}/decision/` for destinations on webhook version 3 (the default), and the older reduced shape for version 1 or 2 destinations. Business sessions always receive the version 3 business decision.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "$ref": "#/components/schemas/SessionWebhookEventData" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "status.updated" + ], + "description": "Always `status.updated`." + } + }, + "required": [ + "webhook_type" + ] + } + ] + }, + "SessionDataUpdatedEvent": { + "description": "Data of a verification session changed without a status change, for example after a manual review edit, a step override or an ongoing-monitoring rescreen. `decision` is sent only for `Approved`, `Declined`, `In Review` and `Abandoned`; it has the shape of `GET /v3/session/{sessionId}/decision/` for destinations on webhook version 3 (the default), and the older reduced shape for version 1 or 2 destinations. Business sessions always receive the version 3 business decision.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "$ref": "#/components/schemas/SessionWebhookEventData" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "data.updated" + ], + "description": "Always `data.updated`." + } + }, + "required": [ + "webhook_type" + ] + } + ] + }, + "UserStatusUpdatedEvent": { + "description": "A user changed status. Not sent when the user is created.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "user.status.updated" + ], + "description": "Always `user.status.updated`." + }, + "vendor_user_id": { + "type": "string", + "format": "uuid", + "description": "The user the event is about." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the user, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "User status after the change." + }, + "previous_status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "Status before the change." + } + }, + "required": [ + "webhook_type", + "vendor_user_id", + "vendor_data", + "status", + "previous_status" + ] + } + ] + }, + "UserDataUpdatedEvent": { + "description": "Fields of a user changed. Not sent when the user is created. One save can produce both a status and a data event.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "user.data.updated" + ], + "description": "Always `user.data.updated`." + }, + "vendor_user_id": { + "type": "string", + "format": "uuid", + "description": "The user the event is about." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the user, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "User status after the change." + }, + "changed_fields": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "vendor_data", + "display_name", + "full_name", + "date_of_birth", + "metadata", + "portrait_image", + "issuing_states", + "approved_emails", + "approved_phones", + "features", + "session_count", + "approved_count", + "declined_count", + "in_review_count", + "first_session_at", + "last_session_at" + ] + }, + "description": "Names of the fields that changed." + }, + "changes": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "previous": { + "nullable": true, + "description": "Value before the change. Dates are ISO 8601 strings." + }, + "current": { + "nullable": true, + "description": "Value after the change. Dates are ISO 8601 strings." + } + } + }, + "description": "Previous and current value of each changed field, keyed by field name. Values can be personal data (names, dates of birth, portrait links, emails, phone numbers); handle them accordingly." + } + }, + "required": [ + "webhook_type", + "vendor_user_id", + "vendor_data", + "status", + "changed_fields", + "changes" + ] + } + ] + }, + "BusinessStatusUpdatedEvent": { + "description": "A business changed status. Not sent when the business is created.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "business.status.updated" + ], + "description": "Always `business.status.updated`." + }, + "vendor_business_id": { + "type": "string", + "format": "uuid", + "description": "The business the event is about." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the business, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "Business status after the change." + }, + "previous_status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "Status before the change." + } + }, + "required": [ + "webhook_type", + "vendor_business_id", + "vendor_data", + "status", + "previous_status" + ] + } + ] + }, + "BusinessDataUpdatedEvent": { + "description": "Fields of a business changed. Not sent when the business is created. One save can produce both a status and a data event.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "business.data.updated" + ], + "description": "Always `business.data.updated`." + }, + "vendor_business_id": { + "type": "string", + "format": "uuid", + "description": "The business the event is about." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the business, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "Business status after the change." + }, + "changed_fields": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "vendor_data", + "display_name", + "metadata", + "legal_name", + "registration_number", + "country_code", + "session_count", + "approved_count", + "declined_count", + "in_review_count", + "features", + "first_session_at", + "last_session_at" + ] + }, + "description": "Names of the fields that changed." + }, + "changes": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "previous": { + "nullable": true, + "description": "Value before the change. Dates are ISO 8601 strings." + }, + "current": { + "nullable": true, + "description": "Value after the change. Dates are ISO 8601 strings." + } + } + }, + "description": "Previous and current value of each changed field, keyed by field name." + } + }, + "required": [ + "webhook_type", + "vendor_business_id", + "vendor_data", + "status", + "changed_fields", + "changes" + ] + } + ] + }, + "TransactionCreatedEvent": { + "description": "A transaction was created and screened. Sent for live applications only.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "transaction.created" + ], + "description": "Always `transaction.created`." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Didit's identifier of the transaction." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "The `transaction_id` you supplied at create time, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Monitoring status after the change." + }, + "score": { + "type": "integer", + "description": "Risk score accumulated by rules." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL", + null + ], + "description": "Risk severity, or `null` until a rule or provider sets one." + }, + "amount": { + "type": "string", + "description": "Transaction amount as a decimal string. Unlike the API it is not normalized, so it can carry trailing zeros (up to 18 decimal places)." + }, + "currency": { + "type": "string", + "description": "Currency code or crypto asset symbol of `amount`." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject." + }, + "action_required": { + "type": "object", + "nullable": true, + "oneOf": [ + { + "$ref": "#/components/schemas/TransactionVerificationSessionAction" + }, + { + "$ref": "#/components/schemas/TransactionWalletOwnershipAction" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "verification_session": "#/components/schemas/TransactionVerificationSessionAction", + "wallet_ownership": "#/components/schemas/TransactionWalletOwnershipAction" + } + }, + "description": "Pending end-user action, or `null`. Same object as `action_required` on the transaction; `session_token` is a credential, so never log it." + } + }, + "required": [ + "webhook_type", + "transaction_id", + "txn_id", + "status", + "score", + "severity", + "amount", + "currency", + "direction", + "action_required" + ] + } + ] + }, + "TransactionStatusUpdatedEvent": { + "description": "A transaction changed status, from a rule, a review or an end-user action. Sent for live applications only.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "transaction.status.updated" + ], + "description": "Always `transaction.status.updated`." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Didit's identifier of the transaction." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "The `transaction_id` you supplied at create time, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Monitoring status after the change." + }, + "score": { + "type": "integer", + "description": "Risk score accumulated by rules." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL", + null + ], + "description": "Risk severity, or `null` until a rule or provider sets one." + }, + "amount": { + "type": "string", + "description": "Transaction amount as a decimal string. Unlike the API it is not normalized, so it can carry trailing zeros (up to 18 decimal places)." + }, + "currency": { + "type": "string", + "description": "Currency code or crypto asset symbol of `amount`." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject." + }, + "action_required": { + "type": "object", + "nullable": true, + "oneOf": [ + { + "$ref": "#/components/schemas/TransactionVerificationSessionAction" + }, + { + "$ref": "#/components/schemas/TransactionWalletOwnershipAction" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "verification_session": "#/components/schemas/TransactionVerificationSessionAction", + "wallet_ownership": "#/components/schemas/TransactionWalletOwnershipAction" + } + }, + "description": "Pending end-user action, or `null`. Same object as `action_required` on the transaction; `session_token` is a credential, so never log it." + } + }, + "required": [ + "webhook_type", + "transaction_id", + "txn_id", + "status", + "score", + "severity", + "amount", + "currency", + "direction", + "action_required" + ] + } + ] + }, + "TravelRuleStatusUpdatedEvent": { + "description": "A Travel Rule exchange changed status. Sent on every transition up to and including `FINISHED`, `CANCELLED` or `EXPIRED`, never after. Sent for live applications and, for Travel Rule exchanges, for sandbox applications too (`environment: sandbox`). There is no top-level `status`.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "travel_rule.status.updated" + ], + "description": "Always `travel_rule.status.updated`." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "The transaction the exchange belongs to." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "The `transaction_id` you supplied at create time, or `null`." + }, + "travel_rule_status": { + "type": "string", + "enum": [ + "AWAITING_COUNTERPARTY", + "ON_HOLD", + "COMPLETED", + "FINISHED", + "CANCELLED", + "EXPIRED", + "COUNTERPARTY_MISMATCHED_DATA", + "COUNTERPARTY_VASP_NOT_FOUND", + "COUNTERPARTY_VASP_NOT_REACHABLE", + "UNCONFIRMED_OWNERSHIP", + "COUNTERPARTY_VASP_GENERAL_DECLINE", + "NOT_ENOUGH_COUNTERPARTY_DATA", + "NOT_APPLICABLE" + ], + "description": "Exchange status after the change." + }, + "rail": { + "type": "string", + "enum": [ + "", + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL", + "TEST_VASP" + ], + "description": "Network the exchange runs on, or an empty string when none could be resolved. `TEST_VASP` is a sandbox transfer the Didit Test VASP answered." + }, + "test_vasp": { + "type": "boolean", + "description": "Present and `true` only on a sandbox transfer the Didit Test VASP answered." + }, + "test_vasp_scenario": { + "type": "string", + "description": "Present only with `test_vasp`: the sandbox scenario that decided the outcome, or an empty string." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction of the transfer." + } + }, + "required": [ + "webhook_type", + "transaction_id", + "txn_id", + "travel_rule_status", + "rail", + "direction" + ] + } + ] + }, + "WorkflowComplianceChangedEvent": { + "description": "The compliance status of a published workflow changed after a re-evaluation. One event per workflow. There is no top-level `status`.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "workflow.compliance_changed" + ], + "description": "Always `workflow.compliance_changed`." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "The workflow that changed." + }, + "workflow_version": { + "type": "integer", + "description": "Version that was evaluated." + }, + "workflow_label": { + "type": "string", + "nullable": true, + "description": "Display name of the workflow." + }, + "transition": { + "type": "object", + "required": [ + "from", + "to" + ], + "properties": { + "from": { + "type": "string", + "enum": [ + "compliant", + "gaps_detected", + "pending_change", + "unknown_coverage", + "not_evaluated" + ], + "description": "Status before the re-evaluation." + }, + "to": { + "type": "string", + "enum": [ + "compliant", + "gaps_detected", + "pending_change", + "unknown_coverage", + "not_evaluated" + ], + "description": "Status after the re-evaluation." + } + } + }, + "severity": { + "type": "string", + "enum": [ + "actionable", + "indicative", + "informative" + ], + "description": "`actionable` when gaps were detected, `indicative` for a pending change, `informative` when gaps were resolved." + }, + "kb_version": { + "type": "string", + "description": "Version of the regulatory knowledge base used." + } + }, + "required": [ + "webhook_type", + "workflow_id", + "workflow_version", + "workflow_label", + "transition", + "severity", + "kb_version" + ] + } + ] + }, + "WebhookEventPayload": { + "description": "Body of a signed webhook delivery, discriminated by `webhook_type`. `activity.created` can be subscribed to but is not sent today. Treat any other `webhook_type` as a new event type and keep the raw body.", + "oneOf": [ + { + "$ref": "#/components/schemas/SessionStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/SessionDataUpdatedEvent" + }, + { + "$ref": "#/components/schemas/UserStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/UserDataUpdatedEvent" + }, + { + "$ref": "#/components/schemas/BusinessStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/BusinessDataUpdatedEvent" + }, + { + "$ref": "#/components/schemas/TransactionCreatedEvent" + }, + { + "$ref": "#/components/schemas/TransactionStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/TravelRuleStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/WorkflowComplianceChangedEvent" + }, + { + "$ref": "#/components/schemas/TransactionRuleSuggestionsCreatedEvent" + } + ], + "discriminator": { + "propertyName": "webhook_type", + "mapping": { + "status.updated": "#/components/schemas/SessionStatusUpdatedEvent", + "data.updated": "#/components/schemas/SessionDataUpdatedEvent", + "user.status.updated": "#/components/schemas/UserStatusUpdatedEvent", + "user.data.updated": "#/components/schemas/UserDataUpdatedEvent", + "business.status.updated": "#/components/schemas/BusinessStatusUpdatedEvent", + "business.data.updated": "#/components/schemas/BusinessDataUpdatedEvent", + "transaction.created": "#/components/schemas/TransactionCreatedEvent", + "transaction.status.updated": "#/components/schemas/TransactionStatusUpdatedEvent", + "travel_rule.status.updated": "#/components/schemas/TravelRuleStatusUpdatedEvent", + "workflow.compliance_changed": "#/components/schemas/WorkflowComplianceChangedEvent", + "transaction_rule_suggestions.created": "#/components/schemas/TransactionRuleSuggestionsCreatedEvent" + } + } + }, + "KYBRegistrySearchResolvedCallback": { + "type": "object", + "description": "Body of the callback sent to the `webhook_url` of `POST /v3/kyb/search/` once the registry search resolves. It is not signed: the request carries `X-Didit-Unsigned-Callback: true` and none of the signature headers, so confirm it by matching `request_id` against a search you started. Uses `event_type`, not `webhook_type`.", + "required": [ + "event_id", + "event_type", + "request_id", + "vendor_data", + "metadata", + "search_status", + "search_resolved", + "kyb_registry", + "timestamp", + "created_at" + ], + "properties": { + "event_id": { + "type": "string", + "format": "uuid", + "description": "Identifier of the callback." + }, + "event_type": { + "type": "string", + "enum": [ + "kyb.registry_search.resolved" + ], + "description": "Always `kyb.registry_search.resolved`." + }, + "request_id": { + "type": "string", + "format": "uuid", + "description": "The `request_id` returned by `POST /v3/kyb/search/`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` you sent." + }, + "search_status": { + "type": "string", + "description": "Always `resolved` today." + }, + "search_resolved": { + "type": "boolean", + "description": "`true` when `search_status` is `resolved`." + }, + "kyb_registry": { + "$ref": "#/components/schemas/KYBRegistrySearchResults" + }, + "timestamp": { + "type": "integer", + "description": "Unix time in seconds at which the callback was queued." + }, + "created_at": { + "type": "integer", + "description": "Unix time in seconds at which the callback was queued." + } + } + }, + "AMLScreening": { + "type": "object", + "properties": { + "node_id": { + "type": "string", + "description": "Unique identifier for this AML step in the workflow graph." + }, + "status": { + "type": "string", + "description": "Feature-level status of this AML screening. Independent of the top-level session `status` — a session can reach its overall decision while an individual feature stays `In Review`.", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ] + }, + "total_hits": { + "type": "integer", + "description": "Total number of matches found" + }, + "entity_type": { + "type": "string", + "description": "Type of entity screened: person or company" + }, + "hits": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Didit's stable identifier for the hit: `hit_` followed by 24 hexadecimal characters. The same matched entity keeps the same `id` when the subject is screened again. Pass it as `hit_id` to the AML hit review endpoints." + }, + "url": { + "type": "string", + "format": "uri", + "description": "Source URL for the match" + }, + "match": { + "type": "boolean", + "description": "Whether this is a confirmed match" + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 1, + "description": "Deprecated weighted match score in the 0-1 range (e.g. `0.73`). Use `match_score` (0-100) instead.", + "deprecated": true + }, + "target": { + "type": "boolean", + "nullable": true, + "description": "Whether the matched entity is the direct target of the listing (as opposed to a related party). `null` when the source does not specify it." + }, + "caption": { + "type": "string", + "description": "Caption or title of the match" + }, + "datasets": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "PEP", + "PEP Level 1", + "PEP Level 2", + "PEP Level 3", + "PEP Level 4", + "Sanctions", + "Adverse Media", + "Warnings and Regulatory Enforcement", + "Fitness and Probity", + "SIP", + "SIE", + "Insolvency", + "Other Lists" + ] + }, + "description": "List of dataset categories where the match was found. Indicates whether the entity appeared in PEP lists (with level), sanctions databases, adverse media, warnings/regulatory enforcement, or other compliance datasets. `Other Lists` when the entity's source states none of these categories." + }, + "features": { + "type": "object", + "nullable": true, + "description": "Additional features of the match" + }, + "rca_name": { + "type": "string", + "description": "RCA name if applicable" + }, + "last_seen": { + "type": "string", + "format": "date-time", + "description": "Date when match was last seen" + }, + "risk_view": { + "type": "object", + "properties": { + "crimes": { + "type": "object", + "properties": { + "score": { + "type": "integer" + }, + "weightage": { + "type": "integer" + }, + "risk_level": { + "type": "string" + }, + "risk_scores": { + "type": "object" + } + } + }, + "countries": { + "type": "object", + "properties": { + "score": { + "type": "integer" + }, + "weightage": { + "type": "integer" + }, + "risk_level": { + "type": "string" + }, + "risk_scores": { + "type": "object" + } + } + }, + "categories": { + "type": "object", + "properties": { + "score": { + "type": "integer" + }, + "weightage": { + "type": "integer" + }, + "risk_level": { + "type": "string" + }, + "risk_scores": { + "type": "object" + } + } + }, + "custom_list": { + "type": "object" + } + } + }, + "first_seen": { + "type": "string", + "format": "date-time", + "description": "Date when match was first seen" + }, + "properties": { + "type": "object", + "properties": { + "name": { + "type": "array", + "items": { + "type": "string" + } + }, + "alias": { + "type": "array", + "items": { + "type": "string" + } + }, + "notes": { + "type": "array", + "items": { + "type": "string" + } + }, + "title": { + "type": "string", + "nullable": true + }, + "gender": { + "type": "array", + "items": { + "type": "string" + } + }, + "height": { + "type": "string", + "nullable": true + }, + "topics": { + "type": "string", + "nullable": true + }, + "weight": { + "type": "string", + "nullable": true + }, + "address": { + "type": "string", + "nullable": true + }, + "country": { + "type": "string", + "nullable": true + }, + "website": { + "type": "string", + "nullable": true + }, + "eyeColor": { + "type": "string", + "nullable": true + }, + "keywords": { + "type": "string", + "nullable": true + }, + "lastName": { + "type": "array", + "items": { + "type": "string" + } + }, + "position": { + "type": "string", + "nullable": true + }, + "religion": { + "type": "string", + "nullable": true + }, + "birthDate": { + "type": "array", + "items": { + "type": "string" + } + }, + "education": { + "type": "array", + "items": { + "type": "string" + } + }, + "ethnicity": { + "type": "string", + "nullable": true + }, + "firstName": { + "type": "array", + "items": { + "type": "string" + } + }, + "hairColor": { + "type": "string", + "nullable": true + }, + "weakAlias": { + "type": "string", + "nullable": true + }, + "birthPlace": { + "type": "array", + "items": { + "type": "string" + } + }, + "modifiedAt": { + "type": "string", + "nullable": true + }, + "wikidataId": { + "type": "string", + "nullable": true + }, + "citizenship": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + } + } + }, + "match_score": { + "type": "number", + "description": "Weighted identity confidence score (0-100) calculated from name similarity, date of birth, and country matching. Used to determine if hit is a False Positive or Possible Match.", + "format": "float", + "nullable": true + }, + "risk_score": { + "type": "number", + "description": "Entity risk score (0-100). Used to determine final AML status (Approved/In Review/Declined) based on the highest risk_score among non-False-Positive hits." + }, + "review_status": { + "type": "string", + "description": "Review status for this hit. Can be 'Unreviewed', 'Confirmed Match', 'False Positive', or 'Inconclusive'.", + "enum": [ + "Unreviewed", + "Confirmed Match", + "False Positive", + "Inconclusive" + ] + }, + "score_breakdown": { + "type": "object", + "description": "Detailed breakdown of how the match_score was calculated.", + "properties": { + "name_score": { + "type": "integer", + "description": "Raw name similarity percentage (0-100)." + }, + "name_weight": { + "type": "integer", + "description": "Original weight for name from config (0-100)." + }, + "name_weight_normalized": { + "type": "number", + "description": "Normalized weight for name after re-weighting for missing data (0-100)." + }, + "name_contribution": { + "type": "number", + "description": "Points contributed by name to the total score." + }, + "dob_score": { + "type": "integer", + "description": "DOB match score (-100 to 100)." + }, + "dob_weight": { + "type": "integer", + "description": "Original weight for DOB from config (0-100)." + }, + "dob_weight_normalized": { + "type": "number", + "description": "Normalized weight for DOB after re-weighting for missing data (0-100)." + }, + "dob_contribution": { + "type": "number", + "description": "Points contributed by DOB to the total score." + }, + "country_score": { + "type": "integer", + "description": "Country match score (-100 to 100)." + }, + "country_weight": { + "type": "integer", + "description": "Original weight for country from config (0-100)." + }, + "country_weight_normalized": { + "type": "number", + "description": "Normalized weight for country after re-weighting for missing data (0-100)." + }, + "country_contribution": { + "type": "number", + "description": "Points contributed by country to the total score." + }, + "document_number_match_type": { + "type": "string", + "description": "Result of document number 'Golden Key' evaluation.", + "enum": [ + "MATCH", + "NEUTRAL", + "HARD_MISMATCH" + ] + }, + "document_number_effect": { + "type": "string", + "description": "Human-readable description of the document number effect on the score." + }, + "total_score": { + "type": "integer", + "description": "Final weighted match score (0-100)." + } + } + }, + "pep_matches": { + "type": "array", + "items": { + "type": "object", + "properties": { + "aliases": { + "type": "array", + "items": { + "type": "string" + } + }, + "education": { + "type": "array", + "items": { + "type": "string" + } + }, + "list_name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "source_url": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + }, + "matched_name": { + "type": "string" + }, + "pep_position": { + "type": "string" + }, + "date_of_birth": { + "type": "string" + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + } + }, + "place_of_birth": { + "type": "string" + } + } + } + }, + "linked_entities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "array", + "items": { + "type": "string" + } + }, + "active": { + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "type": "array", + "items": { + "type": "string" + } + }, + "details": { + "type": "array", + "items": { + "type": "string" + } + }, + "relation": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "warning_matches": { + "type": "array", + "description": "List of warning and regulatory enforcement matches. Includes entries from categories: Warning, SIP, Warnings and Regulatory Enforcement, Fitness and Probity, SIE, and Insolvency.", + "items": { + "type": "object", + "properties": { + "list_name": { + "type": "string", + "description": "Name of the warning list publisher" + }, + "matched_name": { + "type": "string", + "description": "Name matched in the warning list" + }, + "description": { + "type": "string", + "description": "Description of the warning entry" + }, + "publisher": { + "type": "string", + "description": "Publisher or source of the warning" + }, + "source_url": { + "type": "string", + "format": "uri", + "description": "URL to the source of the warning" + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Additional source URLs" + }, + "countries": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Countries associated with the warning" + }, + "created_at": { + "type": "string", + "nullable": true, + "description": "Date the warning entry was created" + }, + "updated_at": { + "type": "string", + "nullable": true, + "description": "Date the warning entry was last updated" + }, + "additional_data": { + "type": "object", + "description": "Additional data from the warning source. May contain a `data` object with `case_details` array.", + "properties": { + "data": { + "type": "object", + "properties": { + "case_details": { + "type": "array", + "description": "Details of warning cases associated with this match", + "items": { + "type": "object", + "properties": { + "case_type": { + "type": "string", + "description": "Type of the warning case", + "enum": [ + "Accusation", + "Arrest", + "Bribery", + "Criminal", + "Fraud", + "Illegal Fundraising", + "Non-conviction Terror", + "Proclaimed Offender", + "Regulatory Enforcement", + "Terrorism", + "Terrorism-related", + "Terrorism-related listing" + ] + } + } + } + } + } + } + } + } + } + } + }, + "sanction_matches": { + "type": "array", + "description": "List of sanction list matches found for this entity.", + "items": { + "type": "object", + "properties": { + "list_name": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Name(s) of the sanction list(s)" + }, + "matched_name": { + "type": "string", + "description": "Name matched in the sanction list" + }, + "description": { + "type": "string", + "description": "Description of the sanction entry" + }, + "reason": { + "type": "string", + "nullable": true, + "description": "Free-text reason why the entity was sanctioned. Joined from the sanction list's reason entries. Examples: 'Executive Order 13224 (Terrorism)', 'Support for actions undermining Ukraine's sovereignty', 'Listed due to national security concerns.'" + }, + "source_url": { + "type": "string", + "format": "uri", + "description": "URL to the source of the sanction" + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Additional source URLs" + }, + "details": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Additional details about the sanction" + }, + "legal_basis": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Legal basis for the sanction" + }, + "listed_on": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Date(s) when the entity was listed" + }, + "sanction_list": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Sanction list identifier(s)" + }, + "sanction_program": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Sanction program(s) the entity is listed under" + }, + "sanctioning_authority": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Authority/authorities that imposed the sanction" + }, + "updated_on": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Date(s) when the sanction entry was last updated" + }, + "additional_data": { + "type": "object", + "description": "Additional data from the sanction source not captured in the main fields" + } + } + } + }, + "adverse_media_details": { + "type": "object", + "properties": { + "sentiment": { + "type": "string" + }, + "entity_type": { + "type": "string" + }, + "sentiment_score": { + "type": "integer" + }, + "adverse_keywords": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + } + }, + "adverse_media_matches": { + "type": "array", + "items": { + "type": "object", + "properties": { + "country": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "headline": { + "type": "string" + }, + "sentiment": { + "type": "string" + }, + "thumbnail": { + "type": "string", + "format": "uri" + }, + "source_url": { + "type": "string", + "format": "uri" + }, + "author_name": { + "type": "string", + "nullable": true + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + } + }, + "adverse_keywords": { + "type": "array", + "items": { + "type": "string" + } + }, + "sentiment_score": { + "type": "integer" + }, + "publication_date": { + "type": "string", + "format": "date-time" + } + } + } + }, + "additional_information": { + "type": "object" + } + } + } + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Overall AML screening score — the highest risk score across all hits (float, 0-100). `null` until computed." + }, + "screened_data": { + "type": "object", + "properties": { + "full_name": { + "type": "string" + }, + "nationality": { + "type": "string" + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "document_number": { + "type": "string", + "nullable": true + } + } + }, + "is_ongoing_monitoring_enabled": { + "type": "boolean", + "description": "Whether AML ongoing monitoring is enabled for this screening. When `true`, the entity is re-screened periodically and any new hits update the report." + }, + "next_ongoing_monitoring_bill_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date (YYYY-MM-DD) when this ongoing-monitoring subscription will next be billed. Computed as the date of the last ongoing-monitoring billing plus 365 days when `is_ongoing_monitoring_enabled = true`; otherwise `null`. This is a billing date, not a screening date - use `last_ongoing_screening_at` for that." + }, + "last_ongoing_screening_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When ongoing monitoring last completed a re-screening of this record, whether or not the result changed. `null` when the record has never been re-screened; it is never back-filled from the enablement or billing date." + }, + "last_ongoing_screening_datasets_updated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "The most recent AML dataset update date the provider reported at the time of that screening. `null` when the provider reported no dataset dates; never estimated." + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "NFC", + "LIVENESS", + "FACEMATCH", + "PROOF_OF_ADDRESS", + "PHONE", + "EMAIL", + "AML", + "LOCATION", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "GEOLOCATION", + "SIGNATURE" + ], + "description": "The feature that generated this warning. The value identifies the feature block the warning belongs to. Within an AML screening this is typically `AML`, but the enum above is authoritative across all feature warnings on the decision payload." + }, + "risk": { + "type": "string" + }, + "additional_data": { + "type": "object", + "nullable": true + }, + "log_type": { + "type": "string" + }, + "short_description": { + "type": "string" + }, + "long_description": { + "type": "string" + }, + "node_id": { + "type": "string", + "description": "The workflow graph node ID that generated this warning" + } + } + } + } + } + }, + "MonitoringEvent": { + "type": "object", + "description": "One stored monitoring event as returned by the list and subject-timeline endpoints. Restricted evidence (payload, device context, rule evidence) is only returned by `GET /v3/events/{event_uuid}/`.", + "required": [ + "event_uuid", + "event_id", + "event_category", + "action_type", + "occurred_at", + "received_at", + "status", + "score", + "subject_vendor_data", + "subject_entity_type", + "subject_uuid", + "counterparty_vendor_data", + "session_id", + "device_fingerprint", + "ip_address", + "ip_country", + "amount", + "currency", + "game_id", + "round_id", + "bonus_id", + "affiliate_code", + "payment_reference_id", + "retention_class", + "rules_matched_count", + "correlation_pending", + "is_droppable" + ], + "properties": { + "event_uuid": { + "type": "string", + "format": "uuid", + "description": "Didit identifier of the event. Use it as `{event_uuid}` in the retrieve and delete calls." + }, + "event_id": { + "type": "string", + "maxLength": 128, + "description": "Your own identifier, the idempotency key you sent." + }, + "event_category": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ], + "description": "Event category." + }, + "action_type": { + "type": "string", + "maxLength": 128, + "description": "What happened within the category, as you sent it." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "description": "When the event happened on your side." + }, + "received_at": { + "type": "string", + "format": "date-time", + "description": "When Didit accepted the event." + }, + "status": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ], + "description": "`ACCEPTED`: stored, no matched rule changed its standing (the normal answer for most events, and always the answer in a sandbox application, where no rules run). `APPROVED`: a rule matched and the score stayed under your review threshold. `IN_REVIEW`: a rule pushed the score over the review threshold or set the status. `DECLINED`: a rule declined it or the score crossed your decline threshold." + }, + "score": { + "type": "integer", + "description": "Accumulated rule score. Starts at 0; rule `add_score` actions move it (negative deltas are allowed)." + }, + "subject_vendor_data": { + "type": "string", + "maxLength": 255, + "description": "`subject.vendor_data` of the event: your id for the user or company." + }, + "subject_entity_type": { + "type": "string", + "description": "`individual` or `company`: which kind of subject `subject_vendor_data` names.", + "example": "individual" + }, + "subject_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "UUID of the user (individual) or company the event resolved to, or null when that record no longer exists." + }, + "counterparty_vendor_data": { + "type": "string", + "nullable": true, + "description": "The other player of a P2P event (promoted from `payload.counterparty_vendor_data`).", + "maxLength": 255 + }, + "session_id": { + "type": "string", + "nullable": true, + "description": "`session.session_id` sent with the event.", + "maxLength": 255 + }, + "device_fingerprint": { + "type": "string", + "nullable": true, + "description": "`device_context.fingerprint` sent with the event.", + "maxLength": 255 + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "IP address of the event. On the server lane this is `device_context.network_context.ip_address` as you sent it; on the device lane it is the IP of the request.", + "maxLength": 64 + }, + "ip_country": { + "type": "string", + "nullable": true, + "description": "Country code of `ip_address` from IP enrichment, or null when the address was not enriched (missing, private, or low-value telemetry whose address was not already known).", + "example": "ES" + }, + "amount": { + "type": "string", + "nullable": true, + "description": "Amount as a decimal string with 18 decimal places, from `transaction_context.amount`, `payload.amount` or `payload.stake_amount`, in that order.", + "example": "250.000000000000000000" + }, + "currency": { + "type": "string", + "nullable": true, + "description": "Currency from `transaction_context.currency` or `payload.currency`.", + "maxLength": 10 + }, + "game_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.game_id`.", + "maxLength": 128 + }, + "round_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.round_id`.", + "maxLength": 128 + }, + "bonus_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.bonus_id`.", + "maxLength": 128 + }, + "affiliate_code": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.affiliate_code`.", + "maxLength": 128 + }, + "payment_reference_id": { + "type": "string", + "nullable": true, + "description": "Payment reference from `transaction_context.payment_reference_id` or `payload.payment_reference_id`.", + "maxLength": 255 + }, + "retention_class": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ], + "description": "Retention class. Payment and dispute events are `monetary_or_compliance`; security and responsible-gambling events are `risk_relevant`; `session.page_view`, `session.sdk_heartbeat`, `session.sdk_loaded`, `session.device_seen`, `session.bot_signal_observed`, `bonus.promotion_viewed` and `affiliate.referral_clicked` are `low_value`; everything else is `standard`. An event an active rule matched is promoted to `risk_relevant` (never demoted)." + }, + "rules_matched_count": { + "type": "integer", + "minimum": 0, + "description": "Number of rules that matched the event, including rules in `TEST` mode (which record evidence but apply no actions)." + }, + "correlation_pending": { + "type": "boolean", + "description": "`true` while an asynchronous rule (cross-subject correlation, or a sequence longer than 24 hours) still has to run for this event. It resolves within minutes and `status`, `score` and `rules_matched_count` update in place." + }, + "is_droppable": { + "type": "boolean", + "description": "Whether an SDK under backpressure may drop events of this kind. `false` for every `security`, `payment`, `dispute` and `responsible_gambling` event and for `gameplay.bet_placed`, `gameplay.bet_settled` and `gameplay.p2p_settlement`." + } + } + }, + "MonitoringEventDetail": { + "description": "One monitoring event with its full envelope, enrichment and rule evidence. Every field of `MonitoringEvent` plus the fields below.", + "allOf": [ + { + "$ref": "#/components/schemas/MonitoringEvent" + }, + { + "type": "object", + "required": [ + "payload", + "custom_values", + "device_context", + "field_sources", + "session_sequence", + "table_id", + "hand_id", + "match_id", + "market_id", + "side", + "role", + "net_result_amount", + "payment_fingerprint", + "instrument_reference_hash", + "withdrawal_address_hash", + "dispute_id", + "chargeback_id", + "changed_field", + "identity_match_ref", + "schema_version", + "expires_at", + "rules_evaluated_count", + "rule_runs", + "ip_enrichment", + "subject_profile", + "tags" + ], + "properties": { + "payload": { + "type": "object", + "additionalProperties": true, + "description": "The category fields you sent in `payload`." + }, + "custom_values": { + "type": "object", + "additionalProperties": true, + "description": "The scalar `custom_values` you sent. Didit adds `vpn_or_proxy_detected: true` or `is_data_center: true` when IP enrichment flags the address and you did not set them." + }, + "device_context": { + "type": "object", + "additionalProperties": true, + "description": "The device context as stored: your `device_context` (including `fingerprint`, `persistent_device_id` and `bot_score` when sent) with `network_context` completed by IP enrichment (`ip_country`, `ip_country_code`, `ip_city`, `asn_number`, `asn_organization`, `is_vpn_or_tor`, `is_data_center`, `proxy_type`, `connection_type`) for keys you left empty." + }, + "field_sources": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "customer", + "sdk", + "server" + ] + }, + "description": "Field path to provenance: `customer` (sent by your backend), `sdk` (sent through the device lane) or `server` (derived by Didit, such as `received_at` and enriched network fields). Check it before acting on a match." + }, + "session_sequence": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "`session.sequence` sent with the event." + }, + "table_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.table_id`.", + "maxLength": 128 + }, + "hand_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.hand_id`.", + "maxLength": 128 + }, + "match_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.match_id`.", + "maxLength": 128 + }, + "market_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.market_id`.", + "maxLength": 128 + }, + "side": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.side`.", + "maxLength": 64 + }, + "role": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.role`.", + "maxLength": 64 + }, + "net_result_amount": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.net_result_amount`, as a decimal string with 18 decimal places.", + "example": "-120.000000000000000000" + }, + "payment_fingerprint": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.payment_fingerprint`.", + "maxLength": 255 + }, + "instrument_reference_hash": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.instrument_reference_hash`.", + "maxLength": 255 + }, + "withdrawal_address_hash": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.withdrawal_address_hash` (or `payload.destination_reference_hash`).", + "maxLength": 255 + }, + "dispute_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.dispute_id`.", + "maxLength": 255 + }, + "chargeback_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.chargeback_id`.", + "maxLength": 255 + }, + "changed_field": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.changed_field` (security change events).", + "maxLength": 128 + }, + "identity_match_ref": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.identity_match_ref` (responsible-gambling registry matches).", + "maxLength": 255 + }, + "schema_version": { + "type": "integer", + "description": "Version of the event envelope schema. Currently `1`.", + "example": 1 + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the event expires under its retention class and is deleted: `occurred_at` plus the class period (14, 30 or 90 days by default, adjustable per application). Null for `monetary_or_compliance` events, which follow transaction retention." + }, + "rules_evaluated_count": { + "type": "integer", + "minimum": 0, + "description": "Number of event rules evaluated for this event." + }, + "rule_runs": { + "type": "array", + "description": "Every rule that matched the event, newest first.", + "items": { + "$ref": "#/components/schemas/MonitoringEventRuleRun" + } + }, + "ip_enrichment": { + "type": "object", + "nullable": true, + "description": "Enrichment of the event's IP address, or null when the address was not enriched. See [IP enrichment](https://docs.didit.me/transaction-monitoring/ip-enrichment).", + "properties": { + "ip_country_code": { + "type": "string", + "nullable": true, + "description": "Country code of the address." + }, + "ip_city": { + "type": "string", + "nullable": true, + "description": "City of the address." + }, + "asn_number": { + "type": "integer", + "nullable": true, + "description": "Autonomous system number." + }, + "asn_organization": { + "type": "string", + "nullable": true, + "description": "Organization that operates the autonomous system." + }, + "is_vpn_or_tor": { + "type": "boolean", + "description": "Whether the address is a known VPN or Tor exit." + }, + "is_data_center": { + "type": "boolean", + "description": "Whether the address belongs to a hosting or data-centre network." + }, + "proxy_type": { + "type": "string", + "nullable": true, + "description": "Proxy classification of the address as an opaque string, or null." + }, + "last_enriched_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the address was last enriched." + } + }, + "required": [ + "ip_country_code", + "ip_city", + "asn_number", + "asn_organization", + "is_vpn_or_tor", + "is_data_center", + "proxy_type", + "last_enriched_at" + ] + }, + "subject_profile": { + "type": "object", + "nullable": true, + "description": "Cached email and phone risk signals of the subject, or null when no profile exists for the subject.", + "properties": { + "email_risk_score": { + "type": "integer", + "nullable": true, + "description": "Email risk score." + }, + "email_deliverability": { + "type": "string", + "nullable": true, + "description": "Email deliverability verdict." + }, + "email_is_disposable": { + "type": "boolean", + "nullable": true, + "description": "Whether the email address is disposable." + }, + "email_is_breached": { + "type": "boolean", + "nullable": true, + "description": "Whether the email address appears in known data breaches." + }, + "email_social_count": { + "type": "integer", + "nullable": true, + "description": "Number of online accounts linked to the email address." + }, + "email_social_providers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the online services where an account linked to the email address was found." + }, + "phone_risk_score": { + "type": "integer", + "nullable": true, + "description": "Phone risk score." + }, + "phone_line_type": { + "type": "string", + "nullable": true, + "description": "Phone line type." + }, + "phone_carrier": { + "type": "string", + "nullable": true, + "description": "Phone carrier name." + }, + "phone_social_count": { + "type": "integer", + "nullable": true, + "description": "Number of online accounts linked to the phone number." + }, + "phone_social_providers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the online services where an account linked to the phone number was found." + }, + "source_provider": { + "type": "string", + "nullable": true, + "description": "Opaque identifier of the source of the profile." + }, + "last_enriched_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the profile was last refreshed." + } + }, + "required": [ + "email_risk_score", + "email_deliverability", + "email_is_disposable", + "email_is_breached", + "email_social_count", + "email_social_providers", + "phone_risk_score", + "phone_line_type", + "phone_carrier", + "phone_social_count", + "phone_social_providers", + "source_provider", + "last_enriched_at" + ] + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the tags attached to the event (rule `add_tags` actions)." + } + } + } + ] + }, + "MonitoringEventRuleRun": { + "type": "object", + "properties": { + "rule_uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the transaction monitoring rule." + }, + "rule_title": { + "type": "string", + "description": "Title of the rule." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Library key when the rule was installed from the rules library, otherwise null." + }, + "matched": { + "type": "boolean", + "description": "Always `true`: only matching runs are recorded." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "TEST" + ], + "description": "Mode of the rule when it ran. A `TEST` run records evidence but applied no actions." + }, + "is_async": { + "type": "boolean", + "description": "`true` when the run came from the asynchronous correlation pass rather than the ingest request." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL", + null + ], + "description": "Severity of the rule. Null for `TEST` runs and for rules without a severity." + }, + "score_delta": { + "type": "integer", + "description": "How much this run changed the event's score (0 for `TEST` runs)." + }, + "action_summary": { + "type": "string", + "nullable": true, + "description": "Short summary of the actions applied, for example `score +40; add_tag:VPN_LOGIN`. Null when nothing was applied." + }, + "matched_payload": { + "type": "array", + "description": "The conditions and aggregations that matched. Condition entries carry `field`, `operator`, `expected_value`, `current_value`, `matched`, `group_index`, `group_logic` and `field_source` (`server`, `sdk` or `customer`); aggregation entries carry `metric`, `field`, `operator`, `expected_value`, `computed_value`, `window`, `filters`, `matched` and `sample_references`.", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the rule run was recorded." + } + }, + "required": [ + "rule_uuid", + "rule_title", + "library_key", + "matched", + "mode", + "is_async", + "severity", + "score_delta", + "action_summary", + "matched_payload", + "created_at" + ] + }, + "MonitoringEventPage": { + "type": "object", + "description": "One page of monitoring events, newest `occurred_at` first.", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Number of matching events, exact up to 100 and capped at 100 above that. Do not compute page counts from it; follow `next` until it is null." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or null on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or null on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MonitoringEvent" + } + } + } + }, + "MonitoringEventCreateRequest": { + "type": "object", + "description": "The event envelope, the same for every category. camelCase aliases are accepted: `eventId`, `eventCategory` (or `category`), `actionType`, `occurredAt`, `timeZone` (or `zoneId`), `deviceContext` (or `device`), `transactionContext`, `customValues` (or `props`); inside `subject`: `vendorData`, `entityType`, `fullName`; inside `session`: `sessionId`, `pageUrl`; inside `transaction_context`: `paymentReferenceId`.", + "required": [ + "event_id", + "event_category", + "action_type", + "occurred_at", + "subject" + ], + "properties": { + "event_id": { + "type": "string", + "maxLength": 128, + "description": "Your identifier for the event, unique per application. It is the idempotency key: resending the same `event_category`, `action_type`, `occurred_at`, `subject.vendor_data` and `payload` under it is a replay, anything different is a conflict.", + "example": "login-2026-10-17-000001" + }, + "event_category": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ], + "description": "Event category.", + "example": "auth" + }, + "action_type": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "What happened within the category. Open vocabulary; the managed actions listed in [Event ingestion](https://docs.didit.me/transaction-monitoring/events) (for example `login`, `deposit_completed`, `chargeback_opened`) unlock typed validation and managed rule fields. Leading and trailing whitespace is trimmed.", + "example": "login" + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "description": "When the event happened on your side, ISO 8601 (a value without an offset is read as UTC). Must be no more than 7 days in the past and no more than 5 minutes in the future.", + "example": "2026-10-17T09:15:02.481Z" + }, + "time_zone": { + "type": "string", + "nullable": true, + "maxLength": 64, + "description": "Time zone of the occurrence on your side, for example `Europe/Madrid`. Stored with the event; not returned by the read endpoints." + }, + "subject": { + "type": "object", + "description": "The user or company the event belongs to. It resolves to the user (or company) with this `vendor_data` in your application; one is created when none exists.", + "required": [ + "vendor_data" + ], + "properties": { + "vendor_data": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "Your id for the subject.", + "example": "user-42" + }, + "entity_type": { + "type": "string", + "maxLength": 32, + "default": "individual", + "description": "`company` resolves the subject to a company; any other value (default `individual`) resolves it to an individual. The same `vendor_data` can name one of each.", + "example": "individual" + }, + "full_name": { + "type": "string", + "nullable": true, + "maxLength": 512, + "description": "Display name, used only when this event creates the user or company." + }, + "email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Validated as an email address; not stored on the event." + }, + "phone": { + "type": "string", + "nullable": true, + "maxLength": 32, + "description": "Validated; not stored on the event." + }, + "country": { + "type": "string", + "nullable": true, + "maxLength": 3, + "description": "Validated; not stored on the event." + } + } + }, + "business": { + "type": "object", + "description": "The business the event belongs to, when it differs from the subject. Only `vendor_data` is read.", + "additionalProperties": true, + "properties": { + "vendor_data": { + "type": "string", + "maxLength": 255, + "description": "Your id for the business. Filterable as `business_vendor_data`.", + "example": "merchant-7" + } + } + }, + "session": { + "type": "object", + "description": "Your session context for the event.", + "properties": { + "session_id": { + "type": "string", + "nullable": true, + "maxLength": 255, + "description": "Your session identifier. Filterable and returned as `session_id`." + }, + "sequence": { + "type": "integer", + "nullable": true, + "description": "Position of the event within the session. Returned as `session_sequence`." + }, + "page_url": { + "type": "string", + "nullable": true, + "maxLength": 2048, + "description": "Validated; not stored on the event." + }, + "referrer": { + "type": "string", + "nullable": true, + "maxLength": 2048, + "description": "Validated; not stored on the event." + } + } + }, + "device_context": { + "type": "object", + "additionalProperties": true, + "description": "Device and network context, stored as sent and returned by `GET /v3/events/{event_uuid}/`. On this server lane Didit does not see the end user's connection, so the IP enriched and used by rules is `network_context.ip_address` as you send it.", + "properties": { + "fingerprint": { + "type": "string", + "maxLength": 255, + "description": "Device fingerprint. Filterable as `device_fingerprint`." + }, + "persistent_device_id": { + "type": "string", + "maxLength": 255, + "description": "Your persistent device identifier." + }, + "bot_score": { + "type": "integer", + "description": "Bot score from your own detection, as an integer." + }, + "network_context": { + "type": "object", + "additionalProperties": true, + "properties": { + "ip_address": { + "type": "string", + "maxLength": 64, + "description": "End user's IP address. Public addresses are enriched once per unique address.", + "example": "203.0.113.42" + } + } + } + } + }, + "transaction_context": { + "type": "object", + "description": "Money context. Non-empty values here also satisfy the required payload keys of the action.", + "properties": { + "amount": { + "type": "string", + "nullable": true, + "description": "Amount as a decimal string (a number is also accepted).", + "example": "1000.00" + }, + "currency": { + "type": "string", + "nullable": true, + "maxLength": 10, + "description": "Currency code." + }, + "direction": { + "type": "string", + "nullable": true, + "maxLength": 16, + "description": "Direction of the money movement, for example `inbound` or `outbound`." + }, + "payment_reference_id": { + "type": "string", + "nullable": true, + "maxLength": 255, + "description": "Your payment reference. Filterable as `payment_reference_id`." + } + } + }, + "payload": { + "type": "object", + "additionalProperties": true, + "description": "The category's domain fields, at most 16384 bytes once JSON-encoded. Some actions require keys (in `payload` or `transaction_context`): `gameplay.p2p_settlement` needs `game_id`, `counterparty_vendor_data`, `stake_amount`, `net_result_amount`, `role` and one of `hand_id`, `round_id`, `match_id`; `gameplay.bet_placed` and `gameplay.bet_settled` need `game_id`; `payment.deposit_initiated`, `payment.deposit_completed`, `payment.withdrawal_requested` and `payment.withdrawal_completed` need `payment_reference_id`, `amount`, `currency`, `payment_method`, `status`; `payment.withdrawal_address_changed` needs `withdrawal_address_hash`; `dispute.chargeback_opened`, `dispute.chargeback_won` and `dispute.chargeback_lost` need `payment_reference_id` and one of `dispute_id`, `chargeback_id`; `session.bot_signal_observed` needs `telemetry_schema_version` and one of `bot_score`, `actions_per_minute`, `timing_variance_ms`, `pointer_entropy`, `keystroke_entropy`, `reaction_time_ms_p50`; `responsible_gambling.self_exclusion_match` needs `registry_source`, `exclusion_status`, `identity_match_ref`. Every `security` action ending in `_changed` needs `changed_field`, plus `previous_reference_hash` or `new_reference_hash` when the field is `password`, `mfa_method`, `mfa`, `email`, `phone`, `withdrawal_address`, `payout_method` or `api_key`. Send hashes and references, never a card number, a full withdrawal address, a password hash or an MFA secret." + }, + "custom_values": { + "type": "object", + "description": "Up to 100 keys of your own, addressable from rules as `custom_values.`. Values must be scalars (string, number, boolean or null); nested objects and arrays are rejected.", + "additionalProperties": { + "nullable": true, + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ] + } + } + }, + "example": { + "event_id": "login-2026-10-17-000001", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-17T09:15:02.481Z", + "time_zone": "Europe/Madrid", + "subject": { + "vendor_data": "user-42", + "entity_type": "individual" + }, + "session": { + "session_id": "web-session-abc", + "sequence": 142 + }, + "device_context": { + "fingerprint": "fp-a8c1", + "network_context": { + "ip_address": "203.0.113.42" + } + }, + "payload": { + "method": "password" + }, + "custom_values": { + "vip_tier": "gold" + } + } + }, + "MonitoringEventIngestResult": { + "type": "object", + "description": "Outcome of ingesting one event. The same shape is returned for a new event and for an idempotent replay.", + "required": [ + "event_uuid", + "event_id", + "status", + "score", + "rules_evaluated_count", + "rules_matched_count", + "correlation_pending", + "retention_class", + "expires_at", + "idempotent_replay", + "rule_matches" + ], + "properties": { + "event_uuid": { + "type": "string", + "format": "uuid", + "description": "Didit identifier of the stored event." + }, + "event_id": { + "type": "string", + "description": "Your `event_id`." + }, + "status": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ], + "description": "Status after the real-time rules ran. See `MonitoringEvent.status`." + }, + "score": { + "type": "integer", + "description": "Score after the real-time rules ran." + }, + "rules_evaluated_count": { + "type": "integer", + "minimum": 0, + "description": "Number of event rules evaluated in the request. 0 in a sandbox application, where no rules run." + }, + "rules_matched_count": { + "type": "integer", + "minimum": 0, + "description": "Number of rules that matched, including `TEST` mode rules." + }, + "correlation_pending": { + "type": "boolean", + "description": "`true` when an asynchronous rule still has to run. Read `GET /v3/events/{event_uuid}/` a few minutes later for the final `status` and `score`." + }, + "retention_class": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ], + "description": "Retention class, after any promotion by a matched rule." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the event expires under its retention class and is deleted. Null for `monetary_or_compliance` (payment and dispute) events." + }, + "idempotent_replay": { + "type": "boolean", + "description": "`true` when an event with this `event_id` and the same content was already stored; the values are those of the stored event and no rule ran again." + }, + "rule_matches": { + "type": "array", + "description": "Rules that matched the event.", + "items": { + "type": "object", + "required": [ + "rule_id", + "library_key", + "mode", + "is_async" + ], + "properties": { + "rule_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the rule." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Library key when the rule was installed from the rules library, otherwise null." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "TEST" + ], + "description": "`TEST` rules record the match but apply no actions." + }, + "is_async": { + "type": "boolean", + "description": "`true` when the match came from the asynchronous pass." + } + } + } + } + } + }, + "ComplianceList": { + "type": "object", + "description": "A blocklist, allowlist or custom list of one application.", + "required": [ + "uuid", + "name", + "description", + "list_type", + "entry_type", + "is_system", + "entry_count", + "last_entry_added_at", + "created_at", + "updated_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "List identifier. Use it as `list_uuid` in the list and entry endpoints, and as the value of a workflow or transaction rule condition with `value_type: \"list\"`." + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "List name, unique among the application's lists." + }, + "description": { + "type": "string", + "description": "Free-text description. Empty string when not set." + }, + "list_type": { + "type": "string", + "enum": [ + "blocklist", + "allowlist", + "custom" + ], + "description": "`blocklist`: a match declines the session or transaction (system lists only, one per entry type). `allowlist`: matching items are trusted, for example a face allowlist skips the duplicated-face action; never overrides a blocklist match. `custom`: used in workflow and transaction rule conditions (`value_type: \"list\"`)." + }, + "entry_type": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email", + "ip_address", + "device_fingerprint", + "wallet_address", + "bank_account", + "country", + "user", + "business", + "key" + ], + "description": "What the list holds: `face`, `document` (document number), `phone`, `email`, `ip_address` (IP address or CIDR range), `device_fingerprint`, `wallet_address`, `bank_account`, `country` (ISO 3166-1 alpha-3), `user` or `business` (your `vendor_data`), or `key` (any string). It decides how entry values are validated and matched." + }, + "is_system": { + "type": "boolean", + "description": "`true` for the lists Didit provisions: one blocklist per entry type (`face`, `document`, `phone`, `email`, `ip_address`, `device_fingerprint`, `wallet_address`, `bank_account`, `user`, `business`), the **High Risk Countries** custom list, and system allowlists. System lists cannot be renamed, edited or deleted; you can add and delete their entries." + }, + "entry_count": { + "type": "integer", + "minimum": 0, + "description": "Number of entries currently in the list." + }, + "last_entry_added_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the newest current entry was added. `null` for an empty list." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the list was created." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When the list was last updated." + } + } + }, + "ComplianceListEntry": { + "type": "object", + "description": "One value in a list.", + "required": [ + "uuid", + "value", + "display_label", + "comment", + "added_by", + "reference_session_id", + "reference_session_number", + "reference_session_type", + "reference_object_uuid", + "metadata", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Entry identifier. Use it as `entry_uuid` in Delete a list entry." + }, + "value": { + "type": "string", + "description": "The listed value: a phone number in E.164, an email address, a document number, an IP address or CIDR range, a device fingerprint, a wallet address, a bank account, an ISO 3166-1 alpha-3 country code, a `vendor_data`, any string on a `key` list, or the UUID of the stored face on a face list. Values you send are stored in lower case, except phone numbers and country codes. Matching is case-insensitive." + }, + "display_label": { + "type": "string", + "nullable": true, + "maxLength": 512, + "description": "Human-readable label. Didit fills it for entries taken from a session (for example the holder's name and document number) when you send none." + }, + "comment": { + "type": "string", + "description": "Free-text note. Empty string when not set." + }, + "added_by": { + "type": "string", + "nullable": true, + "description": "Who added the entry: `api` (an API key), a console user's email address or `console`, `system` or `migration` (entries Didit created), or `rule` (a transaction or monitoring rule action)." + }, + "reference_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Verification session the entry was taken from. `null` when the entry was added by value." + }, + "reference_session_number": { + "type": "integer", + "nullable": true, + "description": "Application-scoped number of that session, as shown in the console." + }, + "reference_session_type": { + "type": "string", + "nullable": true, + "enum": [ + "kyc", + "kyb", + null + ], + "description": "`kyc` for a person verification session, `kyb` for a business verification session, `null` when there is no session." + }, + "reference_object_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The record the entry stands for: the face, document, phone or email record read from a session, the face created by Upload a face to a list, or the UUID you sent (for example a transaction UUID or a user's `didit_internal_id`)." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Extra context. Holds the keys Didit sets (listed here) merged with any `metadata` you sent when creating the entry, returned as stored. `null` when there is none.", + "properties": { + "reference_image_url": { + "type": "string", + "description": "Face entries: short-lived signed URL, issued again on every call; `null` for a scoped API key without `media: read`. Absent when no image belongs to the entry." + }, + "front_image_url": { + "type": "string", + "description": "Document entries that carry a document front image: short-lived signed URL, issued again on every call; `null` for a scoped API key without `media: read`." + }, + "full_name": { + "type": "string", + "description": "Document entries taken from a session: holder's full name." + }, + "issuing_state": { + "type": "string", + "description": "Document entries taken from a session: ISO 3166-1 alpha-3 issuing country." + }, + "document_type": { + "type": "string", + "description": "Document entries taken from a session: document type code, for example `P`, `ID` or `DL`." + }, + "region": { + "type": "string", + "description": "Document entries taken from a session: issuing state or province of a regionally issued document, empty when not applicable. Not set by Add session items to the blocklist." + }, + "document_number": { + "type": "string", + "description": "Document entries taken from a session: the document number." + }, + "face_image_source": { + "type": "string", + "enum": [ + "front_image_camera_front", + "back_image_camera_front" + ], + "description": "Face entries taken from a document-capture selfie: which selfie. Absent for liveness faces." + }, + "reference_node_id": { + "type": "string", + "nullable": true, + "description": "Face entries taken from a document-capture selfie: the ID verification node the selfie came from." + }, + "fingerprint_tier": { + "type": "string", + "nullable": true, + "enum": [ + "persistent_id", + "composite_hash", + "legacy_fp", + "unknown", + null + ], + "description": "Device fingerprint entries taken from a session: how the fingerprint was derived. `persistent_id` identifies one device; `composite_hash` and `legacy_fp` can be shared by devices of the same model." + }, + "sandbox": { + "type": "boolean", + "description": "`true` on a face entry uploaded in a sandbox application; such an entry matches nothing." + }, + "reference_type": { + "type": "string", + "enum": [ + "transaction", + "vendor_user", + "vendor_business" + ], + "description": "Set by you: what `reference_object_uuid` points to, so the console links back to it." + }, + "reference_id": { + "type": "string", + "description": "Set by you: identifier of the source record." + } + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the entry was added." + } + } + }, + "BlocklistedItem": { + "type": "object", + "description": "One blocklisted face, identity document, phone number or email address of a verification session. Fields that do not apply to the item's `type` are `null`.", + "required": [ + "type", + "session_id", + "session_number", + "blocklisted_at", + "face_image", + "document_type", + "document_number", + "front_image", + "phone_number", + "country_code", + "email" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email" + ], + "description": "What was blocklisted." + }, + "session_id": { + "type": "string", + "format": "uuid", + "description": "Verification session the item belongs to." + }, + "session_number": { + "type": "integer", + "nullable": true, + "description": "Application-scoped number of that session, as shown in the console." + }, + "blocklisted_at": { + "type": "string", + "format": "date-time", + "description": "When the item was last blocklisted." + }, + "face_image": { + "type": "string", + "nullable": true, + "description": "Face items: short-lived signed URL, issued again on every call; `null` for a scoped API key without `media: read`. Also `null` when no reference image is stored." + }, + "document_type": { + "type": "string", + "nullable": true, + "description": "Document items: document type code, for example `P` (passport), `ID` (national ID), `DL` (driver's license) or `RP` (residence permit)." + }, + "document_number": { + "type": "string", + "nullable": true, + "description": "Document items: the document number, or the personal number when the document has none." + }, + "front_image": { + "type": "string", + "nullable": true, + "description": "Document items: front image of the document, short-lived signed URL, issued again on every call; `null` for a scoped API key without `media: read`." + }, + "phone_number": { + "type": "string", + "nullable": true, + "description": "Phone items: the phone number in E.164." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "Phone items: ISO 3166-1 alpha-2 country of the number." + }, + "email": { + "type": "string", + "nullable": true, + "description": "Email items: the email address." + } + } + }, + "TransactionNote": { + "type": "object", + "description": "One note on a monitored transaction's review timeline.", + "required": [ + "uuid", + "note", + "actor_email", + "actor_name", + "metadata", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Note identifier." + }, + "note": { + "type": "string", + "description": "Plain-text note body." + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email of the Console user who wrote the note or changed the status. `null` for notes created with an API key and for notes Didit adds automatically." + }, + "actor_name": { + "type": "string", + "nullable": true, + "description": "Display name of the Console user (their email when they have no name). `Rule Engine` on notes Didit adds automatically. `null` for notes created with an API key." + }, + "metadata": { + "type": "object", + "description": "Tells the note types apart. Notes written through the API or the Console carry `mentioned_emails`. A status change made in the Console carries `type: status_change` with `previous_status`, `new_status`, `reason_code` and `reason_label`. Notes Didit adds carry `source`: `rule_action` (with `rule_uuid`) for a rule's `add_note` action, or `remediation_session` for a problem creating the user-action verification session.", + "properties": { + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + }, + "description": "Mentioned organization members, lower-cased and de-duplicated. Empty when nobody was mentioned." + }, + "type": { + "type": "string", + "enum": [ + "status_change" + ], + "description": "Present on notes recording a status change made in the Console." + }, + "previous_status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Status before the change." + }, + "new_status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Status after the change." + }, + "reason_code": { + "type": "string", + "nullable": true, + "description": "Reason code given with the change." + }, + "reason_label": { + "type": "string", + "nullable": true, + "description": "Reason label given with the change." + }, + "source": { + "type": "string", + "enum": [ + "rule_action", + "remediation_session" + ], + "description": "Present on notes Didit adds automatically." + }, + "rule_uuid": { + "type": "string", + "format": "uuid", + "description": "Rule whose `add_note` action wrote the note (with `source: rule_action`)." + } + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the note was stored." + } + } + }, + "TransactionSummary": { + "type": "object", + "description": "Summary of a monitored transaction, the shape `GET /v3/transactions/` lists. Call `GET /v3/transactions/{transaction_id}/` with `uuid` for parties, alerts and rule runs.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit transaction UUID. Use it as `{transaction_id}` on the transaction endpoints." + }, + "transaction_number": { + "type": "integer", + "nullable": true, + "description": "Application-scoped sequential number shown in the Console." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "Your own identifier from the create call. Unique per application." + }, + "transaction_type": { + "type": "string", + "description": "Category as stored: `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet`, `gamblingLimitChange`, `gamblingBonusChange` or `auditTrailEvent`." + }, + "action_type": { + "type": "string", + "description": "Sub-type within the category, for example `withdrawal`." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Current monitoring verdict." + }, + "source": { + "type": "string", + "enum": [ + "LIVE", + "IMPORTED" + ], + "description": "`LIVE` for transactions submitted for monitoring, `IMPORTED` for historical imports." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject." + }, + "amount": { + "type": "string", + "description": "Amount as a decimal string with trailing zeros removed (up to 18 decimal places)." + }, + "currency": { + "type": "string", + "description": "Currency code or crypto asset symbol of `amount`." + }, + "currency_type": { + "type": "string", + "nullable": true, + "description": "`fiat` or `crypto` when known." + }, + "preferred_currency_amount": { + "type": "string", + "nullable": true, + "description": "`amount` converted to the application's preferred currency, when a rate was available." + }, + "preferred_currency_code": { + "type": "string", + "nullable": true, + "description": "Currency of `preferred_currency_amount`." + }, + "payment_details": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference." + }, + "txn_date": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When Didit recorded it." + }, + "score": { + "type": "integer", + "description": "Risk score. Higher is riskier." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL", + null + ], + "description": "Risk severity, `null` until something sets it." + }, + "decision_reason_code": { + "type": "string", + "nullable": true, + "description": "Machine-readable reason for the current `status`." + }, + "decision_reason_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label for `decision_reason_code`." + }, + "tags": { + "type": "array", + "description": "Tags on the transaction.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string", + "description": "Hex color, for example `#F59E0B`." + }, + "description": { + "type": "string", + "nullable": true + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ], + "description": "Didit preset or custom tag." + } + } + } + }, + "subject_name": { + "type": "string", + "nullable": true, + "description": "Full name of the `APPLICANT` party." + }, + "subject_vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the applicant." + }, + "subject_type": { + "type": "string", + "nullable": true, + "enum": [ + "user", + "business", + null + ], + "description": "Whether the applicant is linked to a Didit user or business; `null` when it is linked to neither." + }, + "subject_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "UUID of the Didit user or business the applicant is linked to." + }, + "subject_country": { + "type": "string", + "nullable": true, + "description": "Country code of the applicant." + }, + "counterparty_name": { + "type": "string", + "nullable": true, + "description": "Full name of the `COUNTERPARTY` party, if any." + }, + "counterparty_vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the counterparty." + }, + "counterparty_country": { + "type": "string", + "nullable": true, + "description": "Country code of the counterparty." + }, + "assigned_to_name": { + "type": "string", + "nullable": true, + "description": "Name of the Console reviewer assigned to the transaction." + }, + "travel_rule_status": { + "type": "string", + "nullable": true, + "description": "Status of the transaction's Travel Rule transfer, or `null` when it has none." + }, + "application_id": { + "type": "string", + "format": "uuid", + "description": "Application that owns the transaction." + }, + "application_name": { + "type": "string", + "nullable": true, + "description": "Name of that application." + }, + "organization_id": { + "type": "string", + "format": "uuid", + "description": "Organization that owns the application." + }, + "organization_name": { + "type": "string", + "nullable": true, + "description": "Name of that organization." + } + } + }, + "ApplicationAnalyticsDataPoint": { + "type": "object", + "description": "Session counts for one day, by creation date (UTC). Each count reflects the session's current status. Points of the previous period carry at least the required fields.", + "required": [ + "rounded_date", + "total", + "approved", + "declined", + "canceled", + "pending" + ], + "properties": { + "rounded_date": { + "type": "string", + "format": "date", + "description": "The day, `YYYY-MM-DD`." + }, + "total": { + "type": "integer", + "description": "Sessions created that day." + }, + "approved": { + "type": "integer", + "description": "Of those, sessions `Approved` or `Kyc Expired`." + }, + "declined": { + "type": "integer", + "description": "Sessions `Declined`." + }, + "canceled": { + "type": "integer", + "description": "Sessions `Expired` or `Abandoned`." + }, + "pending": { + "type": "integer", + "description": "Sessions `Not Started`, `In Progress` or `In Review`." + }, + "in_review": { + "type": "integer", + "description": "Sessions `In Review`." + }, + "expired": { + "type": "integer", + "description": "Sessions `Expired`." + }, + "abandoned": { + "type": "integer", + "description": "Sessions `Abandoned`." + }, + "in_progress": { + "type": "integer", + "description": "Sessions `In Progress`." + }, + "not_started": { + "type": "integer", + "description": "Sessions `Not Started`." + }, + "manually_approved": { + "type": "integer", + "description": "Sessions a reviewer approved after the workflow gave another verdict." + }, + "manually_declined": { + "type": "integer", + "description": "Sessions a reviewer declined after the workflow gave another verdict." + } + } + }, + "ApplicationAnalyticsRequestBreakdown": { + "type": "object", + "description": "Status totals for the range. The previous-period breakdown carries at least the required fields.", + "required": [ + "total_approved", + "total_in_review", + "total_declined", + "total_canceled", + "total_pending" + ], + "properties": { + "total_approved": { + "type": "integer", + "description": "Sessions `Approved` or `Kyc Expired`." + }, + "total_in_review": { + "type": "integer", + "description": "Sessions `In Review`." + }, + "total_declined": { + "type": "integer", + "description": "Sessions `Declined`." + }, + "total_canceled": { + "type": "integer", + "description": "Sessions `Expired` or `Abandoned`." + }, + "total_pending": { + "type": "integer", + "description": "Sessions `Not Started`, `In Progress` or `In Review` (includes `total_in_review`)." + }, + "total_expired": { + "type": "integer", + "description": "Sessions `Expired`." + }, + "total_abandoned": { + "type": "integer", + "description": "Sessions `Abandoned`." + }, + "total_in_progress": { + "type": "integer", + "description": "Sessions `In Progress`." + }, + "total_not_started": { + "type": "integer", + "description": "Sessions `Not Started`." + }, + "total_manually_approved": { + "type": "integer", + "description": "Approved by a reviewer." + }, + "total_manually_declined": { + "type": "integer", + "description": "Declined by a reviewer." + } + } + }, + "ApplicationAnalyticsCountryCount": { + "type": "object", + "properties": { + "country": { + "type": "string", + "description": "Country name." + }, + "country_code": { + "type": "string", + "description": "Two-letter country code." + }, + "session_count": { + "type": "integer", + "description": "Sessions from that country." + } + } + }, + "ApplicationAnalyticsLocationStats": { + "type": "object", + "properties": { + "ip_countries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsCountryCount" + }, + "description": "Countries of the users' IP addresses, most sessions first." + }, + "countries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsCountryCount" + }, + "description": "Same list as `ip_countries`." + }, + "id_countries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsCountryCount" + }, + "description": "Issuing countries of the verified ID documents, most sessions first." + }, + "conversion_by_country": { + "type": "array", + "description": "Per document issuing country: finished sessions (approved, declined or in review) and how many were approved. Most finished first.", + "items": { + "type": "object", + "properties": { + "country": { + "type": "string" + }, + "country_code": { + "type": "string" + }, + "finished": { + "type": "integer" + }, + "approved": { + "type": "integer" + } + } + } + }, + "cities": { + "type": "array", + "description": "Up to 100 IP cities, most sessions first.", + "items": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "`City, Country`." + }, + "session_count": { + "type": "integer" + }, + "latitude": { + "type": "number", + "nullable": true + }, + "longitude": { + "type": "number", + "nullable": true + } + } + } + } + } + }, + "ApplicationAnalyticsDemographicStats": { + "type": "object", + "properties": { + "age_distribution": { + "type": "object", + "description": "Sessions per age band from the ID document. Empty when there is no data.", + "properties": { + "under_18": { + "type": "integer" + }, + "18_24": { + "type": "integer" + }, + "25_34": { + "type": "integer" + }, + "35_44": { + "type": "integer" + }, + "45_64": { + "type": "integer" + }, + "65_plus": { + "type": "integer" + } + }, + "additionalProperties": { + "type": "integer" + } + }, + "gender_distribution": { + "type": "object", + "description": "Sessions per document gender code: `M`, `F` or `U` (unknown).", + "additionalProperties": { + "type": "integer" + } + } + } + }, + "ApplicationAnalyticsDeviceDetails": { + "type": "object", + "description": "Top five of each, most sessions first.", + "properties": { + "device_models": { + "type": "array", + "description": "Device brands: `Apple`, `Android`, `LG`, everything else grouped as `Other`.", + "items": { + "type": "object", + "properties": { + "device": { + "type": "string" + }, + "count": { + "type": "integer" + } + } + } + }, + "browsers": { + "type": "array", + "description": "Browsers: `Chrome`, `Safari`, `Didit App`, `Firefox`, everything else grouped as `Other`.", + "items": { + "type": "object", + "properties": { + "browser": { + "type": "string" + }, + "count": { + "type": "integer" + } + } + } + }, + "operating_systems": { + "type": "array", + "description": "Operating systems.", + "items": { + "type": "object", + "properties": { + "os": { + "type": "string" + }, + "count": { + "type": "integer" + } + } + } + } + } + }, + "ApplicationAnalyticsWorkflowFunnel": { + "type": "object", + "description": "Keyed by workflow ID. `total` is the sessions started on that workflow; `steps` counts the sessions that reached each step (`OCR`, `LIVENESS`, `FACE_MATCH`, `AML`, `POA`, `PHONE`, `EMAIL`, `NFC`, `DATABASE_VALIDATION`, `QUESTIONNAIRE`, `IP_ANALYSIS`, `AGE_ESTIMATION`, `KYB_REGISTRY`, `KYB_DOCUMENTS`, `KYB_KEY_PEOPLE`). Reaching a step is not passing it.", + "additionalProperties": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "steps": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "reached": { + "type": "integer" + } + } + } + } + } + } + }, + "ApplicationAnalyticsVerificationTimeStats": { + "type": "object", + "description": "Active time users spent verifying, ignoring idle gaps. An empty object when there is no data.", + "properties": { + "overall_avg_seconds": { + "type": "number", + "description": "Average per session, one decimal." + }, + "overall_count": { + "type": "integer", + "description": "Sessions measured." + }, + "per_feature": { + "type": "object", + "description": "Keyed by feature, for example `OCR` or `LIVENESS`.", + "additionalProperties": { + "type": "object", + "properties": { + "avg_seconds": { + "type": "number" + }, + "total_seconds": { + "type": "number" + }, + "count": { + "type": "integer" + } + } + } + }, + "per_sub_step": { + "type": "object", + "description": "Keyed by feature, then by sub-step, for example `front_id_photo`.", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "avg_seconds": { + "type": "number" + }, + "total_seconds": { + "type": "number" + }, + "count": { + "type": "integer" + } + } + } + } + } + } + }, + "ApplicationAnalyticsResubmissionStats": { + "type": "object", + "properties": { + "first_pass": { + "type": "integer", + "description": "Finished sessions with no resubmission." + }, + "second": { + "type": "integer", + "description": "Finished sessions resubmitted once." + }, + "third_plus": { + "type": "integer", + "description": "Finished sessions resubmitted two or more times." + }, + "abandoned": { + "type": "integer", + "description": "Abandoned sessions." + } + } + }, + "ApplicationAnalyticsOriginalStatusStats": { + "type": "object", + "description": "The workflow's automatic verdict, before any reviewer changed it.", + "properties": { + "approved": { + "type": "integer" + }, + "in_review": { + "type": "integer" + }, + "declined": { + "type": "integer" + } + } + }, + "ApplicationAnalyticsWarningStats": { + "type": "object", + "description": "Keyed by feature (`ID_VERIFICATION`, `LIVENESS`, `FACEMATCH`, `AML`, `PROOF_OF_ADDRESS`, `PHONE`, `EMAIL`, `NFC`, `LOCATION`, `DATABASE_VALIDATION`, `QUESTIONNAIRE` and the KYB features). `total` is the sessions that reached the feature; `risks` counts sessions per warning code and level.", + "additionalProperties": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "risks": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "error": { + "type": "integer" + }, + "warning": { + "type": "integer" + }, + "information": { + "type": "integer" + } + } + } + } + } + } + }, + "ApplicationAnalyticsDemographicPoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "age_distribution": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + }, + "gender_distribution": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + } + }, + "ApplicationAnalyticsDevicePoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "devices": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + }, + "browsers": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + }, + "operating_systems": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + } + }, + "ApplicationAnalyticsVerificationTimePoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "overall_avg": { + "type": "number", + "description": "Average active seconds per session." + }, + "per_feature": { + "type": "object", + "description": "Average active seconds per feature.", + "additionalProperties": { + "type": "number" + } + } + } + }, + "ApplicationAnalyticsResubmissionPoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "first_pass": { + "type": "integer" + }, + "second": { + "type": "integer" + }, + "third_plus": { + "type": "integer" + }, + "abandoned": { + "type": "integer" + } + } + }, + "ApplicationAnalyticsOriginalStatusPoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "approved": { + "type": "integer" + }, + "in_review": { + "type": "integer" + }, + "declined": { + "type": "integer" + } + } + }, + "ApplicationAnalyticsSummary": { + "type": "object", + "description": "User verification (KYC) summary, returned when neither `scope` nor `widget_keys` is sent.", + "required": [ + "data_points", + "request_breakdown", + "previous_data_points", + "previous_request_breakdown", + "conversion_rate", + "location_stats", + "demographic_stats", + "device_details", + "workflow_funnel_stats", + "verification_time_stats", + "resubmission_stats", + "warning_stats", + "original_status_stats", + "grouping_mode", + "demographic_time_series", + "device_time_series", + "verification_time_time_series", + "resubmission_time_series", + "original_status_time_series" + ], + "properties": { + "data_points": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDataPoint" + }, + "description": "One point per day, from the first day with sessions in the range to `end_date`. Always daily, whatever `grouping_mode` says." + }, + "request_breakdown": { + "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" + }, + "previous_data_points": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDataPoint" + }, + "description": "Daily points for the period of the same length right before `start_date`." + }, + "previous_request_breakdown": { + "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" + }, + "conversion_rate": { + "type": "number", + "nullable": true, + "description": "`total_approved / (total_approved + total_declined + total_in_review) x 100`, one decimal. `null` when nothing finished." + }, + "location_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsLocationStats" + }, + "demographic_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsDemographicStats" + }, + "device_details": { + "$ref": "#/components/schemas/ApplicationAnalyticsDeviceDetails" + }, + "workflow_funnel_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsWorkflowFunnel" + }, + "verification_time_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimeStats" + }, + "resubmission_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionStats" + }, + "warning_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsWarningStats" + }, + "original_status_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusStats" + }, + "grouping_mode": { + "type": "string", + "enum": [ + "day", + "week", + "month" + ], + "description": "How the `*_time_series` lists are grouped: `day` for up to 14 days, `week` up to 90, `month` beyond. Measured from the first day with sessions in the range." + }, + "demographic_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDemographicPoint" + } + }, + "device_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDevicePoint" + } + }, + "verification_time_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimePoint" + } + }, + "resubmission_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionPoint" + } + }, + "original_status_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusPoint" + } + } + } + }, + "ApplicationAnalyticsWidget": { + "type": "object", + "description": "One dashboard widget. Every widget has `kind` and `title`; the other fields depend on `kind`. The session widgets reuse the summary objects (`data_points`, `request_breakdown`, `workflow_funnel_stats` and so on); list widgets put their entries in `items`; transaction metrics use `value`.", + "required": [ + "kind", + "title" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "sessions_volume", + "workflow_funnel", + "average_verification_time", + "country_distribution", + "warning_insights", + "conversion_rate", + "conversion_by_country", + "resubmission_insights", + "original_status_distribution", + "database_validation_coverage", + "database_validation_latency", + "demographics", + "device_details", + "horizontal_bar", + "kyb_registry_coverage", + "kyb_registry_data_completeness", + "metric", + "time_series", + "breakdown", + "status_time_series", + "leaderboard", + "histogram" + ], + "description": "Widget type." + }, + "title": { + "type": "string", + "description": "Display title, for example `Verification volume`." + }, + "grouping_mode": { + "type": "string", + "enum": [ + "day", + "week", + "month" + ], + "description": "How the `*_time_series` lists are grouped: `day` for up to 14 days, `week` up to 90, `month` beyond. Measured from the first day with sessions in the range." + }, + "data_points": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDataPoint" + } + }, + "request_breakdown": { + "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" + }, + "previous_data_points": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDataPoint" + } + }, + "previous_request_breakdown": { + "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" + }, + "conversion_rate": { + "type": "number", + "nullable": true + }, + "workflow_funnel_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsWorkflowFunnel" + }, + "verification_time_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimeStats" + }, + "verification_time_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimePoint" + } + }, + "warning_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsWarningStats" + }, + "resubmission_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionStats" + }, + "resubmission_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionPoint" + } + }, + "original_status_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusStats" + }, + "original_status_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusPoint" + } + }, + "demographic_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsDemographicStats" + }, + "demographic_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDemographicPoint" + } + }, + "device_details": { + "$ref": "#/components/schemas/ApplicationAnalyticsDeviceDetails" + }, + "device_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDevicePoint" + } + }, + "items": { + "type": "array", + "description": "Entries of a list widget. `country_distribution` (KYC): `country`, `country_code`, `session_count`. `conversion_by_country`: `country`, `country_code`, `finished`, `approved`. `time_series`: `date`, `value`. `status_time_series`: `date`, `approved`, `declined`, `flagged`. `breakdown`: `transaction_type` or `action_type` with `count` and `volume`, or `tag_id`, `tag_name`, `count`. `leaderboard`: `rule_title`, `trigger_count` or `assignee`, `handled_count`, `average_processing_seconds`, each with `approved_percentage`, `declined_percentage`, `flagged_percentage`. `histogram`: `score`, `count`.", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "previous_items": { + "type": "array", + "description": "Transaction time series for the period of the same length right before `start_date`.", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "value": { + "type": "number", + "description": "`metric` widgets: the figure for the range." + }, + "currency": { + "type": "string", + "nullable": true, + "description": "Transaction volume and amount widgets: the application's default transaction currency." + }, + "thresholds": { + "type": "object", + "description": "`histogram` widget: the application's review and decline score thresholds.", + "properties": { + "review": { + "type": "integer" + }, + "decline": { + "type": "integer" + } + } + }, + "search": { + "type": "object", + "description": "`kyb_registry_coverage` widget: registry search totals.", + "properties": { + "searches_total": { + "type": "integer" + }, + "searches_with_no_candidates": { + "type": "integer" + } + } + } + } + }, + "ApplicationAnalyticsWidgetResponse": { + "type": "object", + "description": "Dashboard widgets, returned when `scope` or `widget_keys` is sent.", + "required": [ + "scope", + "available_widget_keys", + "requested_widget_keys", + "widgets" + ], + "properties": { + "scope": { + "type": "string", + "enum": [ + "home", + "kyc", + "kyb", + "transactions" + ], + "description": "Dashboard tab." + }, + "available_widget_keys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Every widget key this scope accepts." + }, + "requested_widget_keys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The widget keys answered, in order: your valid `widget_keys`, or the scope's defaults." + }, + "widgets": { + "type": "object", + "description": "Keyed by widget key, in the order of `requested_widget_keys`.", + "additionalProperties": { + "$ref": "#/components/schemas/ApplicationAnalyticsWidget" + } + } + } + }, + "ApplicationAnalyticsToday": { + "type": "object", + "description": "Today's verifications by local hour against the same weekday last week.", + "required": [ + "time_zone", + "date", + "current_hour", + "previous_date", + "hours", + "previous_hours", + "total", + "previous_total_so_far", + "outcomes" + ], + "properties": { + "time_zone": { + "type": "string", + "description": "The IANA time zone the day and its hours are in.", + "example": "Europe/Madrid" + }, + "date": { + "type": "string", + "format": "date", + "description": "Today in that time zone.", + "example": "2026-10-30" + }, + "current_hour": { + "type": "integer", + "minimum": 0, + "maximum": 23, + "description": "The local hour it is now.", + "example": 14 + }, + "previous_date": { + "type": "string", + "format": "date", + "description": "The comparison day: the same weekday one week before.", + "example": "2026-10-23" + }, + "hours": { + "type": "array", + "items": { + "type": "integer", + "minimum": 0 + }, + "minItems": 24, + "maxItems": 24, + "description": "Verifications created today, per local hour from 00:00. The hours still to come are 0." + }, + "previous_hours": { + "type": "array", + "items": { + "type": "integer", + "minimum": 0 + }, + "minItems": 24, + "maxItems": 24, + "description": "Verifications created on the comparison day, per local hour from 00:00." + }, + "total": { + "type": "integer", + "minimum": 0, + "description": "Verifications created today so far.", + "example": 165 + }, + "previous_total_so_far": { + "type": "integer", + "minimum": 0, + "description": "Verifications created on the comparison day up to the same local time.", + "example": 151 + }, + "outcomes": { + "type": "object", + "description": "Of today's verifications, how many are approved, waiting in review and declined now. The rest are not finished yet.", + "required": [ + "approved", + "in_review", + "declined" + ], + "properties": { + "approved": { + "type": "integer", + "minimum": 0 + }, + "in_review": { + "type": "integer", + "minimum": 0 + }, + "declined": { + "type": "integer", + "minimum": 0 + } + } + } + } + }, + "WebhookConfigDestination": { + "type": "object", + "description": "A webhook destination as listed in the webhook configuration, without delivery metrics. `GET /v3/webhook/destinations/{destination_uuid}/` adds its delivery summary.", + "required": [ + "uuid", + "label", + "url", + "enabled", + "webhook_version", + "secret_shared_key", + "subscribed_events", + "created_at", + "updated_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Destination ID." + }, + "label": { + "type": "string", + "maxLength": 255, + "description": "Human-readable name." + }, + "url": { + "type": "string", + "format": "uri", + "maxLength": 2000, + "description": "HTTPS endpoint Didit posts events to." + }, + "enabled": { + "type": "boolean", + "description": "`false` pauses deliveries to this destination." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload version sent to this destination." + }, + "secret_shared_key": { + "type": "string", + "description": "This destination's signing secret (43-character URL-safe token). Verify each webhook with the secret of the destination it was sent to. API-key callers always receive it in full; Console users without `read:webhooks` receive an empty string." + }, + "subscribed_events": { + "type": "array", + "uniqueItems": true, + "description": "Event types delivered to this destination. Empty when the destination was created without events, in which case it receives nothing.", + "items": { + "$ref": "#/components/schemas/WebhookEvent" + } + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "WebhookConfig": { + "type": "object", + "description": "Application-level webhook configuration, retention policy and Digital ID wallet return hosts, with every webhook destination.", + "required": [ + "webhook_url", + "webhook_version", + "secret_shared_key", + "capture_method", + "data_retention_months", + "face_retention_policy", + "face_retention_days", + "available_events", + "allowed_return_hosts", + "endpoints" + ], + "properties": { + "webhook_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Application-level (legacy) webhook URL. Didit sends to it only while the application has no enabled destination, and then only `status.updated` and `data.updated`. Every create, update or delete of a destination sets it to the URL of the oldest enabled destination, or `null` when none is enabled." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload version of the legacy `webhook_url`. Each destination has its own `webhook_version`." + }, + "secret_shared_key": { + "type": "string", + "description": "Application-level signing secret (43-character URL-safe token). It signs only deliveries to the legacy `webhook_url`; each destination signs with its own `secret_shared_key`. API-key callers always receive it in full; Console users without `read:webhooks` receive an empty string." + }, + "capture_method": { + "type": "string", + "enum": [ + "MOBILE", + "MOBILE_AND_DESKTOP" + ], + "description": "Legacy application setting. The verification flow does not read it: capture options are configured per workflow." + }, + "data_retention_months": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 120, + "description": "Automatic deletion window. User verification sessions created more than this many months ago (a month counts as 30 days) are deleted automatically, and that deletion is irreversible. `null` means sessions are never deleted automatically. See [Data retention](https://docs.didit.me/console/data-retention)." + }, + "face_retention_policy": { + "type": "string", + "enum": [ + "delete_with_session", + "retain_until_user_deleted" + ], + "description": "What happens to a session's face embedding when the session is deleted. `delete_with_session` (default) deletes it with the session. `retain_until_user_deleted` is the opt-in [biometric template retention](https://docs.didit.me/console/data-retention#biometric-template-retention): an image-free biometric template linked to the User is managed separately until its retention period ends, the User is deleted, or you purge it." + }, + "face_retention_days": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 3650, + "description": "Retention period, in days, of biometric templates under `retain_until_user_deleted`. Required when that policy is set." + }, + "biometric_consent_mode": { + "type": "string", + "enum": [ + "auto", + "always", + "never" + ], + "description": "When the hosted flow and the native SDKs ask the person for explicit biometric consent before a face capture. `auto` (default) asks only where a trigger applies: a declared state, the person's IP, or the identity document's issuer is in Illinois, Texas or Colorado, or the person looks to be in the US without a known state. `always` asks every person whose workflow captures a face. `never` never shows the step, and you collect consent yourself before the selfie. Each change is recorded in the audit log. See [Biometric consent](https://docs.didit.me/getting-started/biometric-consent)." + }, + "available_events": { + "type": "array", + "description": "Every event type a destination can subscribe to, with a display label.", + "items": { + "type": "object", + "required": [ + "value", + "label" + ], + "properties": { + "value": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "label": { + "type": "string", + "description": "Display label, for example `Session status updated`." + } + } + } + }, + "allowed_return_hosts": { + "type": "array", + "maxItems": 20, + "description": "Hostnames (exact match, HTTPS only) a standalone Digital ID wallet verification may send the user back to with its `return_url`. Your white-label domain is always allowed as well. See [Register your return destination](https://docs.didit.me/standalone-apis/digital-id-wallets#register-your-return-destination).", + "items": { + "type": "string", + "maxLength": 253 + } + }, + "endpoints": { + "type": "array", + "description": "Every webhook destination of the application, oldest first, each with its signing secret. Read only here: manage destinations with the `/v3/webhook/destinations/` endpoints.", + "items": { + "$ref": "#/components/schemas/WebhookConfigDestination" + } + }, + "allowed_domains": { + "type": "array", + "maxItems": 50, + "items": { + "type": "string", + "maxLength": 300 + }, + "description": "Origins (for example `https://app.example.com`; `http` only for `localhost`) the hosted verification flow may be embedded in and may send the user back to with a session callback. Server-to-server calls are not affected. An empty list means no restriction. Didit stores each origin in lower case without a default port. An invalid entry fails with `400` on `allowed_domains`.", + "example": [ + "https://app.example.com" + ] + } + } + }, + "WebhookDestinationDelivery": { + "type": "object", + "description": "One delivery attempt to a webhook destination. The first send of an event, each automatic retry and each resend are separate deliveries.", + "required": [ + "uuid", + "session_id", + "request_url", + "response_status", + "response_time_ms", + "status", + "webhook_type", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Delivery ID, for the detail and resend calls." + }, + "session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "User verification session the event is about. `null` for business session, user, business, transaction, Travel Rule and workflow events." + }, + "request_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL the delivery was sent to: the destination's URL when the event was generated." + }, + "response_status": { + "type": "integer", + "nullable": true, + "description": "HTTP status your endpoint returned. `null` while the delivery is queued and not yet attempted. When no HTTP answer was received Didit records a status of its own: `504` when your endpoint did not answer within 5 seconds, `503` for a connection failure, `400` when the URL was refused as unsafe (for example a private or internal address), and `404` for any other request error. `400` or above counts as a failed delivery." + }, + "response_time_ms": { + "type": "integer", + "nullable": true, + "minimum": 0, + "description": "Time the attempt took, in milliseconds. `null` while the delivery is queued." + }, + "status": { + "type": "string", + "nullable": true, + "description": "Status the event reported when it was generated: the session status for session events (for example `Approved` or `In Review`), the person's or company's status for user and business events, and the transaction status for transaction events. `null` for events that carry no status." + }, + "webhook_type": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the delivery was created." + } + } + }, + "WebhookDestinationDeliveryDetail": { + "type": "object", + "description": "One delivery attempt to a webhook destination, with the exact request headers and body Didit sent.", + "required": [ + "uuid", + "session_id", + "request_url", + "response_status", + "response_time_ms", + "status", + "webhook_type", + "created_at", + "request_header", + "request_body" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Delivery ID, for the detail and resend calls." + }, + "session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "User verification session the event is about. `null` for business session, user, business, transaction, Travel Rule and workflow events." + }, + "request_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL the delivery was sent to: the destination's URL when the event was generated." + }, + "response_status": { + "type": "integer", + "nullable": true, + "description": "HTTP status your endpoint returned. `null` while the delivery is queued and not yet attempted. When no HTTP answer was received Didit records a status of its own: `504` when your endpoint did not answer within 5 seconds, `503` for a connection failure, `400` when the URL was refused as unsafe (for example a private or internal address), and `404` for any other request error. `400` or above counts as a failed delivery." + }, + "response_time_ms": { + "type": "integer", + "nullable": true, + "minimum": 0, + "description": "Time the attempt took, in milliseconds. `null` while the delivery is queued." + }, + "status": { + "type": "string", + "nullable": true, + "description": "Status the event reported when it was generated: the session status for session events (for example `Approved` or `In Review`), the person's or company's status for user and business events, and the transaction status for transaction events. `null` for events that carry no status." + }, + "webhook_type": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the delivery was created." + }, + "request_header": { + "type": "string", + "description": "JSON text (a string) of the request headers Didit sent: `Content-Type`, `X-Signature`, `X-Signature-V2`, `X-Signature-Simple`, `X-Timestamp` and `User-Agent`. Parse it with a JSON parser. It holds signatures, never the signing secret." + }, + "request_body": { + "type": "string", + "description": "JSON text (a string) of the exact payload Didit sent, keys sorted, including `event_id` and `timestamp`. Parse it with a JSON parser; see [Payload shape](https://docs.didit.me/integration/webhooks#payload-shape). For a scoped key without `media: read`, media URLs inside it are `null`; without `sessions: write`, session tokens and hosted verification links are `null`. A body changed that way no longer matches its signatures. Didit does not record the response body your endpoint returned." + } + } + }, + "SessionWebhookDelivery": { + "type": "object", + "description": "One webhook delivery attempt recorded for a verification session, with the exact request headers and body Didit sent.", + "required": [ + "uuid", + "session_id", + "request_url", + "response_status", + "status", + "webhook_type", + "request_header", + "request_body", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Delivery ID. Use it as `{webhook_uuid}` to get or resend this delivery." + }, + "session_id": { + "type": "string", + "format": "uuid", + "description": "The session the delivery belongs to (the session in the path)." + }, + "request_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL the delivery was sent to." + }, + "response_status": { + "type": "integer", + "nullable": true, + "description": "HTTP status your endpoint returned. `null` while the delivery is queued and not yet attempted. When no HTTP answer was received Didit records a status of its own: `504` when your endpoint did not answer within 5 seconds, `503` for a connection failure, `400` when the URL was refused as unsafe (for example a private or internal address), and `404` for any other request error. `400` or above counts as a failed delivery." + }, + "status": { + "type": "string", + "nullable": true, + "description": "Session status when the event was generated, for example `In Progress`, `Approved` or `Declined`." + }, + "webhook_type": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "request_header": { + "type": "string", + "description": "JSON text (a string) of the request headers Didit sent: `Content-Type`, `X-Signature`, `X-Signature-V2`, `X-Signature-Simple`, `X-Timestamp` and `User-Agent`. Parse it with a JSON parser. It holds signatures, never the signing secret." + }, + "request_body": { + "type": "string", + "description": "JSON text (a string) of the exact payload Didit sent, keys sorted, including `event_id` and `timestamp`. Parse it with a JSON parser; see [Payload shape](https://docs.didit.me/integration/webhooks#payload-shape). For a scoped key without `media: read`, media URLs inside it are `null`; without `sessions: write`, session tokens and hosted verification links are `null`. A body changed that way no longer matches its signatures. Didit does not record the response body your endpoint returned." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the delivery was created." + } + } + }, + "SessionEvent": { + "type": "object", + "description": "One entry of a verification session's event timeline.", + "required": [ + "uuid", + "event_type", + "created_at", + "component", + "feature_step", + "step", + "details", + "ip_address", + "user_agent", + "device_info", + "file_url", + "sent_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Event identifier." + }, + "event_type": { + "type": "string", + "enum": [ + "SESSION_CREATED", + "SESSION_COMPLETED", + "SESSION_STATUS_CHANGED", + "LINK_OPENED", + "FORM_SUBMITTED", + "BUTTON_CLICKED", + "FILE_UPLOADED", + "ATTEMPT_FAILED", + "FEATURE_STEP_CHANGED", + "STEP_RENDERED", + "NOTE_ADDED", + "MANUAL_REVIEW_STARTED", + "WORKFLOW_BRANCH_EVALUATED", + "WORKFLOW_STATUS_NODE_REACHED", + "LOG" + ], + "description": "What happened. Reported by the device during the hosted flow or SDK: `LINK_OPENED`, `STEP_RENDERED`, `BUTTON_CLICKED`, `FORM_SUBMITTED`, `FILE_UPLOADED`, `ATTEMPT_FAILED` and `LOG` (debugging). Recorded by the server: `SESSION_CREATED`, `SESSION_STATUS_CHANGED` (`details.old_status`, `details.current_status`), `FEATURE_STEP_CHANGED` (`details.old_feature_step`, `details.current_feature_step`), `WORKFLOW_BRANCH_EVALUATED` and `WORKFLOW_STATUS_NODE_REACHED` (the workflow routing trace). `SESSION_COMPLETED`, `NOTE_ADDED` and `MANUAL_REVIEW_STARTED` can appear on older sessions." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When Didit stored the event. Events are stored asynchronously, a few seconds after `sent_at`." + }, + "component": { + "type": "string", + "nullable": true, + "description": "UI component that reported the event (device events only)." + }, + "feature_step": { + "type": "string", + "nullable": true, + "enum": [ + "OCR", + "OCR_BACK", + "NFC", + "AML", + "FACE", + "LIVENESS", + "FACE_MATCH", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "PROOF_OF_ADDRESS", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "FACE_SEARCH", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "DOCUMENT_AI", + "GEOLOCATION", + null + ], + "description": "Verification feature the session was on when the event happened." + }, + "step": { + "type": "string", + "nullable": true, + "description": "Screen within the feature, for example `DOCUMENT_CAPTURE_FRONT` or `FACE_PASSIVE_LIVENESS`." + }, + "details": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Event-specific data. The keys depend on `event_type`." + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "IP address the device reported the event from. `null` for server-recorded events." + }, + "user_agent": { + "type": "string", + "nullable": true, + "description": "User agent of the device. `null` for server-recorded events." + }, + "device_info": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Device details the SDK or browser reported." + }, + "file_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived presigned link to a file attached to the event (for example a captured image). `null` when there is none, and always `null` for a scoped API key without `media: read`." + }, + "sent_at": { + "type": "integer", + "format": "int64", + "description": "When the event happened, as a Unix timestamp in milliseconds. Results are ordered by it." + } + } + }, + "AMLDatasetSourceFreshness": { + "type": "object", + "description": "Freshness of one family of AML datasets.", + "required": [ + "last_updated_at", + "oldest_updated_at", + "dataset_count", + "dated_dataset_count" + ], + "properties": { + "last_updated_at": { + "type": "string", + "format": "date-time", + "description": "Most recent update of any dataset in the family." + }, + "oldest_updated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Staleness bound: every dataset in the family is at least this current. `null` when at least one dataset in the family reported no update date, because the bound is then unknown." + }, + "dataset_count": { + "type": "integer", + "description": "Number of datasets in the family." + }, + "dated_dataset_count": { + "type": "integer", + "description": "Number of those datasets that reported an update date." + } + } + }, + "AMLDatasetFreshness": { + "type": "object", + "description": "How current the AML datasets were when a screening ran. Dates are never estimated.", + "required": [ + "captured_at", + "provider", + "catalog_version", + "sources" + ], + "properties": { + "captured_at": { + "type": "string", + "format": "date-time", + "description": "When Didit read the dataset catalog this freshness comes from." + }, + "provider": { + "type": "string", + "description": "Opaque identifier of the AML data source. Treat it as an opaque string; do not branch on it.", + "example": "aml_data_source" + }, + "catalog_version": { + "type": "string", + "nullable": true, + "description": "Version identifier of the dataset catalog, when the source reports one." + }, + "sources": { + "type": "object", + "description": "Freshness per dataset family. A family whose datasets reported no update date is absent rather than filled with `null`.", + "properties": { + "sanctions": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" + }, + "pep": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" + }, + "warnings": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" + }, + "adverse_media": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" + } + } + } + } + }, + "AMLScreeningRun": { + "type": "object", + "description": "One completed ongoing-monitoring re-screen of an AML check.", + "required": [ + "uuid", + "run_id", + "completed_at", + "started_at", + "outcome", + "trigger", + "provider", + "status", + "score", + "total_hits", + "new_hits", + "entities_screened", + "dataset_freshness" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Identifier of this screening run." + }, + "run_id": { + "type": "string", + "format": "uuid", + "description": "The monitoring run this screening belongs to. Every verification screened in the same run shares it." + }, + "completed_at": { + "type": "string", + "format": "date-time", + "description": "When this subject's re-screen finished. Results are ordered by it, newest first." + }, + "started_at": { + "type": "string", + "format": "date-time", + "description": "When the monitoring batch this re-screen belongs to started." + }, + "outcome": { + "type": "string", + "enum": [ + "NO_CHANGE", + "CHANGED", + "FAILED" + ], + "description": "`NO_CHANGE`: completed and the result did not move. `CHANGED`: completed and the score, status, hit count or hits changed. `FAILED`: no usable answer for this subject, so the check was left as it was." + }, + "trigger": { + "type": "string", + "enum": [ + "ONGOING_MONITORING" + ], + "description": "What started the screening. Always `ONGOING_MONITORING` today." + }, + "provider": { + "type": "string", + "description": "Opaque identifier of the AML data source that answered. Treat it as an opaque string; do not branch on it.", + "example": "aml_data_source" + }, + "status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested", + null + ], + "description": "AML check status this run left the check in." + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "AML score this run left the check in." + }, + "total_hits": { + "type": "integer", + "nullable": true, + "description": "Number of hits this run left the check in." + }, + "new_hits": { + "type": "integer", + "nullable": true, + "description": "Number of hits this run found that the check did not have before it. `0` when nothing new was found; `null` when the run failed or was recorded before this count existed." + }, + "entities_screened": { + "type": "integer", + "minimum": 0, + "maximum": 1, + "description": "Number of entities this run screened for the verification: `1` when the subject was screened, `0` when the run failed." + }, + "dataset_freshness": { + "allOf": [ + { + "$ref": "#/components/schemas/AMLDatasetFreshness" + } + ], + "nullable": true, + "description": "Dataset freshness behind this run, or `null` when no dates were available." + } + } + }, + "KYBKeyPeopleParty": { + "type": "object", + "description": "A Key People party of a Business Verification (KYB) session, with its child verification. The same party appears in `key_people_checks[].submitted.parties[]` of the business session decision.", + "required": [ + "uuid", + "entity_type", + "source", + "name", + "is_active", + "is_skipped", + "requires_verification", + "roles", + "kyc_session_deleted", + "kyb_sub_session_deleted" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Party identifier." + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "source": { + "type": "string", + "enum": [ + "REGISTRY", + "USER" + ], + "description": "`REGISTRY`: taken from the company registry. `USER`: entered in the Key People screen." + }, + "name": { + "type": "string", + "description": "Display name of the party." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "First name (persons)." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Last name (persons)." + }, + "company_name": { + "type": "string", + "nullable": true, + "description": "Company name (company parties)." + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Company registration number (company parties)." + }, + "email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email the verification link is sent to." + }, + "nationality": { + "type": "string", + "nullable": true, + "description": "Nationality for persons, country of incorporation for companies." + }, + "phone_number": { + "type": "string", + "nullable": true, + "description": "Phone number collected for the party." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth (persons)." + }, + "position": { + "type": "string", + "nullable": true, + "description": "Free-text job title (persons)." + }, + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined custom fields collected for the party, keyed by field key." + }, + "is_active": { + "type": "boolean", + "description": "Whether the party is active." + }, + "is_skipped": { + "type": "boolean", + "description": "Whether the party is exempt from verification. Regenerating clears it." + }, + "requires_verification": { + "type": "boolean", + "description": "Whether the role and ownership rules require a verification for this party." + }, + "roles": { + "type": "array", + "description": "Roles the party holds in the company.", + "items": { + "type": "object", + "required": [ + "role", + "ownership_percent", + "voting_percent" + ], + "properties": { + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Role code." + }, + "ownership_percent": { + "type": "number", + "nullable": true, + "description": "Ownership percentage for this role, when known." + }, + "voting_percent": { + "type": "number", + "nullable": true, + "description": "Voting-rights percentage for this role, when known." + } + } + } + }, + "kyc_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Child individual KYC session (persons)." + }, + "kyc_session_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User", + null + ], + "description": "Status of the child KYC session. A deleted session keeps the status it had when it was deleted." + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Hosted link of the child KYC session. `null` when there is none or it was deleted, and for a scoped API key without `sessions: write`." + }, + "kyc_session_deleted": { + "type": "boolean", + "description": "The child KYC session was deleted, so it can never settle." + }, + "kyb_sub_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Child KYB session (company parties)." + }, + "kyb_sub_session_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User", + null + ], + "description": "Status of the child KYB session." + }, + "kyb_sub_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Hosted link of the child KYB session, with the same `null` rules as `kyc_session_url`." + }, + "kyb_sub_session_deleted": { + "type": "boolean", + "description": "Company-party counterpart of `kyc_session_deleted`." + }, + "verification_workflow_type": { + "type": "string", + "nullable": true, + "enum": [ + "kyc", + "kyb", + "adaptive_age_verification", + "biometric_authentication", + "address_verification", + "questionnaire_verification", + "email_verification", + "phone_verification", + null + ], + "description": "Type of the workflow the child session runs." + }, + "verification_workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Workflow the child session runs." + }, + "verification_workflow_label": { + "type": "string", + "nullable": true, + "description": "Label of that workflow." + }, + "verification_workflow_features": { + "type": "array", + "nullable": true, + "description": "Features of the child session's workflow with their current status.", + "items": { + "type": "object", + "required": [ + "feature", + "status" + ], + "properties": { + "feature": { + "type": "string", + "description": "Feature code, for example `ID_VERIFICATION` or `LIVENESS`." + }, + "status": { + "type": "string", + "description": "Feature status, `Not Finished` until it runs." + } + } + } + }, + "didit_internal_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Didit's identifier of the verified person, once known." + } + } + }, + "TravelRuleDelivery": { + "type": "object", + "description": "Where the latest message the transfer owes the other side stands. Didit contacts the counterparty after the request that created or changed the transfer has returned, so this object says whether that message got through: queued, being sent, waiting to be retried after an error, delivered, or failed. A failure the network may still recover from (no answer in time, a failed connection, HTTP `408`, `429` or `5xx`) is tried again after 30 seconds, 2 minutes, 10 minutes, 30 minutes and 1 hour. When a first message (`kind: SEND`) fails for good, because the network refused it or the sixth attempt failed too, the transfer moves to `COUNTERPARTY_VASP_NOT_REACHABLE` with `last_error` in its `timeline`; any other message that fails for good is noted in the `timeline`. The transfer's `status` stays the source of truth for the exchange itself.", + "required": [ + "kind", + "status", + "rail", + "attempts", + "last_error", + "next_attempt_at", + "delivered_at" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "SEND", + "ANSWER", + "TXID", + "CANCEL", + "POLL" + ], + "description": "What the message carries. `SEND`: the transfer's first message to the counterparty (outbound; the rail is chosen when it is sent). `ANSWER`: your accept or decline, owed to the network that asked (inbound). `TXID`: the on-chain transaction hash, after you finish the transfer. `CANCEL`: the cancellation, after you cancel it. `POLL`: a status query to a network that offers one, repeated every 5 minutes while the transfer waits on the counterparty." + }, + "status": { + "type": "string", + "enum": [ + "QUEUED", + "SENDING", + "RETRYING", + "DELIVERED", + "FAILED" + ], + "description": "`QUEUED`: waiting to be sent. `SENDING`: an attempt is running. `RETRYING`: an attempt failed and the next one runs at `next_attempt_at`. `DELIVERED`: the other side took the message. `FAILED`: the other side refused it, or the last attempt failed." + }, + "rail": { + "type": "string", + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL", + "" + ], + "description": "The rail that carries the message. Empty until Didit has chosen one for a first message." + }, + "attempts": { + "type": "integer", + "minimum": 0, + "description": "Attempts made so far for this message. A message that is not a status poll is attempted at most 6 times." + }, + "last_error": { + "type": "string", + "description": "The kind of failure the last attempt hit, `\"\"` when none did: for example `email undeliverable`, `trp identity endpoint unreachable` or `trp transfer inquiry delivery failed`. It never carries the counterparty's own answer, which stays on the transfer's `timeline`.\nWhen GTR answers with an error, its own status code and message are included. Response bodies are never included.", + "example": "email undeliverable" + }, + "next_attempt_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the next attempt runs while the message is `QUEUED` or `RETRYING`." + }, + "delivered_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the other side took the message." + } + } + }, + "TravelRuleDiscoveryStep": { + "type": "object", + "description": "One step of the counterparty discovery Didit ran for an outbound transfer, in the order it ran them. The first step that names a VASP with a route ends the chain.", + "properties": { + "step": { + "type": "string", + "enum": [ + "internal_address_book", + "wallet_attribution", + "network_lookup", + "customer_hint", + "end_user" + ], + "description": "`internal_address_book`: another Didit customer that proved control of the destination wallet. `wallet_attribution`: the wallet's blockchain analytics attribution. `network_lookup`: a network that can say which VASP holds the address. `customer_hint`: the VASP name, LEI or jurisdiction you sent in `beneficiary_data`. `end_user`: the counterparty the end user (or you) picked, or the request to pick one." + }, + "outcome": { + "type": "string", + "enum": [ + "match", + "miss", + "skipped" + ], + "description": "`match`: the step named a counterparty. `miss`: it ran and found none. `skipped`: it could not run, and `reason` says why." + }, + "reason": { + "type": "string", + "description": "Machine-readable reason, `\"\"` on a match: `no_proven_internal_wallet`, `attribution_disabled`, `already_attributed`, `no_attributed_entity`, `directory_match_no_route`, `gtr_address_lookup_runs_on_dispatch`, `no_membership`, `network_switched_off`, `no_network_address_lookup`, `no_directory_match`, `awaiting_end_user` or `picked_member_without_proven_wallet`." + }, + "vasp": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The directory VASP the step named, when it named one." + }, + "rail": { + "type": "string", + "nullable": true, + "description": "The rail that reaches that counterparty, when the step found one." + }, + "at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleNextAction": { + "type": "object", + "description": "One thing that moves a `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE` transfer on. See [Why a transfer is stuck](/transaction-monitoring/travel-rule#why-a-transfer-is-stuck).", + "properties": { + "action": { + "type": "string", + "enum": [ + "select_counterparty_vasp", + "declare_self_hosted", + "add_vasp_hint", + "update_compliance_email", + "resend" + ], + "description": "`select_counterparty_vasp`: the end user picks the destination VASP in the wallet-ownership widget, or you PATCH `action=beneficiary_vasp`. `declare_self_hosted`: the end user declares the wallet self-hosted in the widget. `add_vasp_hint`: tell Didit who the counterparty is, with a VASP name, LEI or travel address (for example a `travel_address` on the destination wallet's address-book entry), then resend. `update_compliance_email` and `resend` are `action` values of `PATCH /v3/transactions/{transaction_id}/travel-rule/`: give the counterparty's compliance email again, and re-run routing." + }, + "url": { + "type": "string", + "format": "uri", + "description": "The wallet-ownership widget link, on `select_counterparty_vasp` and `declare_self_hosted` while the transfer has an open widget session." + } + }, + "required": [ + "action" + ] + }, + "TransactionTravelRule": { + "type": "object", + "description": "A transaction's Travel Rule transfer, as `GET /v3/transactions/{transaction_id}/`, the `201` of `POST /v3/transactions/` and each row of `GET /v3/transactions/` return it. `direction`, `rail`, `counterparty_vasp`, `delivery`, `discovery`, `reason` and `next_actions` have the same shape as on `PATCH /v3/transactions/{transaction_id}/travel-rule/`, so one read of a transaction says why its transfer is stuck and what moves it on.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string" + }, + "protocol": { + "type": "string", + "nullable": true + }, + "required": { + "type": "boolean" + }, + "obligations_count": { + "type": "integer" + }, + "originator_data": { + "type": "object", + "description": "Originator party. For an inbound transfer the counterparty's values are masked until ownership is confirmed." + }, + "beneficiary_data": { + "type": "object", + "description": "Beneficiary party. For an inbound transfer the counterparty's values are masked until ownership is confirmed." + }, + "name_match": { + "type": "object", + "nullable": true, + "description": "The name comparison that decided the transfer (score and strictness). Null until one ran. The compared names are masked together with the parties." + }, + "data_match": { + "$ref": "#/components/schemas/TravelRuleDataMatch" + }, + "mismatched_fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Shortcut for `data_match.mismatched_fields`. Empty when nothing differs." + }, + "data_unmasked": { + "$ref": "#/components/schemas/TravelRuleDataUnmasked" + }, + "data_masked": { + "type": "boolean", + "description": "True while the counterparty's personal data is masked in this response." + }, + "metadata": { + "type": "object" + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Which side of the exchange this transfer is." + }, + "rail": { + "type": "string", + "nullable": true, + "description": "The rail the transfer went over: `INTERNAL`, `TRP`, `GTR`, `TRUST`, `VERIFYVASP`, `SYGNA` or `EMAIL` (`TEST_VASP` on a sandbox application, when the Didit Test VASP answered), or `null` until a rail is chosen." + }, + "counterparty_vasp": { + "type": "object", + "nullable": true, + "properties": { + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "dd_score": { + "type": "integer", + "description": "Due-diligence score from 0 to 100, where higher is better. Same scale as in the VASP directory." + }, + "dd_score_breakdown": { + "type": "object", + "description": "Per-component contributions to dd_score, one object per component as in the VASP directory (jurisdiction, verification, protocol, lei, history, essentials, responsiveness), each {points, max, reason, detail}. {} until the counterparty has been assessed.", + "additionalProperties": { + "type": "object", + "properties": { + "points": { + "type": "number", + "description": "Points awarded for this component." + }, + "max": { + "type": "integer", + "description": "Maximum points this component can award." + }, + "reason": { + "type": "string", + "description": "Machine-readable code of the component's verdict." + }, + "detail": { + "type": "string", + "description": "Human-readable reason for the awarded points." + } + } + } + }, + "dd_assessed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the due-diligence assessment last ran. Null until the counterparty has been assessed." + } + }, + "description": "The counterparty's directory entry, or `null` when the transfer links none." + }, + "delivery": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleDelivery" + } + ], + "description": "The newest message the transfer owes another party. `null` when it has owed none, for example below `threshold_amount` or for a declared self-hosted wallet." + }, + "discovery": { + "type": "array", + "description": "How Didit looked for the counterparty, one entry per step that ran. Empty when no discovery ran, for example on an inbound transfer.", + "items": { + "$ref": "#/components/schemas/TravelRuleDiscoveryStep" + } + }, + "reason": { + "type": "string", + "description": "Why a `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE` transfer is stuck; `\"\"` for any other status. A failed delivery decides it first, on `COUNTERPARTY_VASP_NOT_REACHABLE` only: `email_undeliverable` (the mail provider refused the compliance email and no address is left to try: when it was sent, on a reminder, or as a permanent bounce reported later) or `delivery_failed` (the rail that was delivering failed for good: a network rail refused the message or ran out of retries, a `TRP` identity host did not answer, or the counterparty's `TRP` host did not accept the inquiry). Otherwise the `discovery` trace decides it: `awaiting_end_user` (no step named a counterparty Didit can reach), `directory_match_no_route` (a step named a directory VASP that has no route), `picked_member_without_proven_wallet` (the end user picked a Didit customer that has not proven control of the wallet), or `no_directory_match` when the trace gives no other reason. A resend moves the transfer on, so the reason clears. See [Why a transfer is stuck](/transaction-monitoring/travel-rule#why-a-transfer-is-stuck)." + }, + "next_actions": { + "type": "array", + "description": "What moves the transfer on; `[]` for any status but `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE`. `email_undeliverable` offers `update_compliance_email`, then `resend`. `delivery_failed` offers `resend`. A discovery reason offers `select_counterparty_vasp`, `declare_self_hosted`, `add_vasp_hint` and `resend`.", + "items": { + "$ref": "#/components/schemas/TravelRuleNextAction" + } + }, + "chain_details": { + "$ref": "#/components/schemas/TravelRuleChainDetails" + } + } + }, + "NetworkListCounts": { + "type": "object", + "description": "Network counts for the list's views and filter options, each network counted once. Returned only when `summary` is not `false`. `scopes` count every network visible to the application. `views` count the visible networks inside the selected `scope`. `facets` count the networks in the selected `scope` and `status` view (dismissed networks are left out unless `status` includes them). The other list filters do not change these counts.", + "properties": { + "scopes": { + "type": "object", + "description": "Visible networks by product.", + "properties": { + "all": { + "type": "integer" + }, + "identity": { + "type": "integer", + "description": "Every other network." + }, + "transactions": { + "type": "integer", + "description": "Networks whose members are all transaction parties." + } + } + }, + "views": { + "type": "object", + "description": "Visible networks inside the selected `scope`, by status, plus two saved views.", + "properties": { + "all": { + "type": "integer" + }, + "active": { + "type": "integer" + }, + "in_review": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "dismissed": { + "type": "integer" + }, + "mine": { + "type": "integer", + "description": "Networks assigned to the calling console member. Always 0 for an API key." + }, + "high_risk": { + "type": "integer", + "description": "Networks with a risk score of 80 or more that are not dismissed." + } + } + }, + "facets": { + "type": "object", + "description": "Counts for each filter option, inside the selected `scope` and status view.", + "properties": { + "total": { + "type": "integer", + "description": "Networks in the selected `scope` and status view." + }, + "last_activity_days": { + "type": "object", + "description": "Networks with activity in the last 7, 30 and 90 days, keyed by the number of days.", + "properties": { + "7": { + "type": "integer" + }, + "30": { + "type": "integer" + }, + "90": { + "type": "integer" + } + } + }, + "risk_min": { + "type": "object", + "description": "Networks with a risk score of at least 50 and at least 80, keyed by the threshold.", + "properties": { + "50": { + "type": "integer" + }, + "80": { + "type": "integer" + } + } + }, + "signal_types": { + "type": "object", + "description": "Networks with at least one signal of each type.", + "properties": { + "ip_address": { + "type": "integer" + }, + "device": { + "type": "integer" + }, + "face": { + "type": "integer" + }, + "document_number": { + "type": "integer" + }, + "phone": { + "type": "integer" + }, + "email": { + "type": "integer" + }, + "address": { + "type": "integer" + } + } + }, + "priority": { + "type": "object", + "description": "Networks by priority. `none` counts networks without a priority.", + "properties": { + "low": { + "type": "integer" + }, + "medium": { + "type": "integer" + }, + "high": { + "type": "integer" + }, + "none": { + "type": "integer" + } + } + }, + "assignee": { + "type": "object", + "description": "Networks by assignee.", + "properties": { + "me": { + "type": "integer", + "description": "Assigned to the calling console member. Always 0 for an API key." + }, + "unassigned": { + "type": "integer", + "description": "Assigned to nobody." + }, + "users": { + "type": "array", + "description": "One entry per assignee, most networks first.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "full_name": { + "type": "string", + "description": "The assignee's name, or their email when no name is set." + }, + "email": { + "type": "string" + }, + "count": { + "type": "integer" + } + } + } + } + } + }, + "has_case": { + "type": "integer", + "description": "Networks this application opened a case for." + }, + "min_size": { + "type": "object", + "description": "Networks with at least 10 members, keyed by the minimum size.", + "properties": { + "10": { + "type": "integer" + } + } + } + } + } + } + }, + "NetworkTag": { + "type": "object", + "description": "One of your application's tags on the network.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "The tag's UUID, the same value you pass as `tag_uuid`." + }, + "name": { + "type": "string" + }, + "color": { + "type": "string", + "description": "Hex color, for example `#6366F1`." + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ], + "description": "`didit` for a Didit preset tag, `custom` for a tag your team created." + }, + "added_by": { + "type": "string", + "nullable": true, + "description": "Email of the console member who added the tag. Null when an API key added it." + }, + "added_at": { + "type": "string", + "format": "date-time" + } + } + }, + "NetworkOutcomes": { + "type": "object", + "description": "Where the network's people and businesses stand. Each member you may see counts once, by the status of their most recent verification that reached a decision. Transaction parties are not counted.", + "properties": { + "approved": { + "type": "integer", + "description": "Members whose most recent decided verification was approved." + }, + "declined": { + "type": "integer", + "description": "Members whose most recent decided verification was declined." + }, + "in_review": { + "type": "integer", + "description": "Members whose most recent decided verification is in review." + }, + "total": { + "type": "integer", + "description": "Members with at least one verification, decided or not. A member whose verifications are all undecided (not started, in progress, expired or abandoned) counts here and in no other key." + } + } + }, + "NetworkPatternScore": { + "type": "object", + "description": "How alike one kind of the members' data is across the whole network. A pattern is not a signal: no shared value links the members.", + "properties": { + "type": { + "type": "string", + "enum": [ + "background" + ], + "description": "`background`: the mean similarity of every pair of member selfie backgrounds, with the person masked out. It reads the newest selfie of each person, at most the 50 newest, and leaves out age assurance sessions. It covers the whole network, not only the members of your applications." + }, + "score": { + "type": "number", + "nullable": true, + "minimum": 0, + "maximum": 1, + "description": "From 0 to 1, rounded to 4 decimals. Null when `reason` is set." + }, + "members": { + "type": "integer", + "nullable": true, + "description": "How many members' selfies the score compared. A selfie that cannot be read, or shows too little background, is not compared. Null while `computing`; 0 or 1 with `not_enough_selfies`." + }, + "reason": { + "type": "string", + "enum": [ + "computing", + "not_enough_selfies" + ], + "nullable": true, + "description": "Why there is no score. `computing`: the score for the current members is being computed; read the network again later. `not_enough_selfies`: fewer than two members have a usable selfie. Null when there is a score." + }, + "computed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the score was computed. Null while `computing`." + } + } + }, + "NetworkDetailResponse": { + "description": "The network detail, plus the organization's sharing mode, whether cross-organization signals apply to it, your application's tags on the network, member outcomes and pattern scores.", + "allOf": [ + { + "$ref": "#/components/schemas/NetworkDetail" + }, + { + "type": "object", + "properties": { + "sharing_mode": { + "type": "string", + "enum": [ + "off", + "private", + "global" + ], + "description": "The organization's cross-organization sharing mode. Only `global` compares networks across organizations; `private` and `off` keep them to the organization's own history.", + "example": "global" + }, + "cross_org_available": { + "type": "boolean", + "description": "Whether cross-organization signals apply to this organization. They need both the Enterprise plan and the `global` sharing mode.", + "example": true + }, + "cross_org_unavailable_reason": { + "type": "string", + "enum": [ + "plan", + "sharing_mode" + ], + "nullable": true, + "description": "Why cross-organization signals do not apply, checked in this order. `plan`: the organization's plan does not include the cross-organization fraud network (it needs the Enterprise plan), whatever the sharing mode. `sharing_mode`: the plan includes it, but the sharing mode is `private` or `off`. Null when `cross_org_available` is `true`.", + "example": null + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkTag" + }, + "description": "Your application's tags on the network, oldest first. Each application sees only its own tags. `fraud_tags` is separate and unchanged: the tags the network's members carry." + }, + "outcomes": { + "$ref": "#/components/schemas/NetworkOutcomes" + }, + "patterns": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkPatternScore" + }, + "description": "Pattern scores over the whole network. Today one item, `background`." + } + } + } + ] + }, + "NetworkTagRequest": { + "type": "object", + "required": [ + "tag_uuid" + ], + "properties": { + "tag_uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of one of your application's tags." + } + }, + "example": { + "tag_uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7" + } + }, + "NetworkTimeline": { + "title": "Default timeline (no contract)", + "type": "object", + "description": "Without `contract`: the network's own events, oldest first, not paginated.", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkTimelineEvent" + } + } + } + }, + "NetworkTimelinePage": { + "title": "Typed timeline page (contract=2)", + "type": "object", + "description": "With `contract=2`: one page of typed events, newest first.", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkTypedTimelineEvent" + } + }, + "next_cursor": { + "type": "string", + "nullable": true, + "description": "Pass as `cursor` to read the next page. Null on the last page." + } + } + }, + "NetworkTypedTimelineEvent": { + "type": "object", + "description": "One typed timeline event: the four default fields, plus its id, when Didit stored it, the member account and record it belongs to, and the detail block of its event type. The other detail blocks are null.", + "properties": { + "id": { + "type": "string", + "description": "`:`, unique within the timeline.", + "example": "session:3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "event_type": { + "type": "string", + "enum": [ + "network_detected", + "signal_observed", + "network_active", + "network_in_review", + "network_resolved", + "network_dismissed", + "verification_attempt", + "transaction", + "login", + "rule_hit", + "case_linked", + "note_added" + ] + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "description": "When it happened." + }, + "ingested_at": { + "type": "string", + "format": "date-time", + "description": "When Didit stored it." + }, + "label": { + "type": "string", + "description": "A short English label, for example `Verification attempt 2 of 3`." + }, + "metadata": { + "type": "object", + "description": "For the network's own events, the same metadata as the default timeline. Empty otherwise." + }, + "account": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineAccount" + } + ], + "nullable": true, + "description": "The member account. Null for the network's own events, `case_linked` and `note_added`." + }, + "source_ref": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineSourceRef" + } + ], + "nullable": true, + "description": "The source record. Null for the network's own events and `note_added`." + }, + "verification_attempt": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineAttempt" + } + ], + "nullable": true, + "description": "Set on `verification_attempt` events." + }, + "transaction": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineTransaction" + } + ], + "nullable": true, + "description": "Set on `transaction` events." + }, + "login": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineLogin" + } + ], + "nullable": true, + "description": "Set on `login` events." + }, + "rule_hit": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineRuleHit" + } + ], + "nullable": true, + "description": "Set on `rule_hit` events." + }, + "case_linked": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineCaseLinked" + } + ], + "nullable": true, + "description": "Set on `case_linked` events." + }, + "note_added": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineNoteAdded" + } + ], + "nullable": true, + "description": "Set on `note_added` events. Never returned to an API key." + } + } + }, + "NetworkTimelineAccount": { + "type": "object", + "description": "The member account the event belongs to.", + "properties": { + "node_id": { + "type": "string", + "description": "`:`, where kind is `vendor_user`, `vendor_business` or `transaction_party`.", + "example": "vendor_user:3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "node_type": { + "type": "string", + "enum": [ + "account", + "business_account", + "transaction_party" + ] + }, + "uuid": { + "type": "string", + "format": "uuid" + }, + "vendor_data": { + "type": "string", + "nullable": true + }, + "display_name": { + "type": "string", + "nullable": true + }, + "ownership": { + "type": "string", + "enum": [ + "owned", + "organization" + ], + "description": "`owned` when the record belongs to the application you called (for a console member, to an application they were granted). `organization` when it belongs to another application of your organization." + }, + "route": { + "$ref": "#/components/schemas/NetworkTimelineRoute" + } + } + }, + "NetworkTimelineSourceRef": { + "type": "object", + "description": "The record the event was read from.", + "properties": { + "kind": { + "type": "string", + "enum": [ + "session", + "business_session", + "transaction", + "monitoring_event", + "case" + ] + }, + "id": { + "type": "string", + "format": "uuid" + }, + "ownership": { + "type": "string", + "enum": [ + "owned", + "organization" + ], + "description": "`owned` when the record belongs to the application you called (for a console member, to an application they were granted). `organization` when it belongs to another application of your organization." + }, + "route": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineRoute" + } + ], + "description": "Absent for `monitoring_event` and `case`." + } + } + }, + "NetworkTimelineRoute": { + "type": "object", + "description": "Where the record opens: its kind, its UUID and the application it belongs to.", + "properties": { + "kind": { + "type": "string", + "description": "`user`, `business` or `transaction_party` for an account; `session`, `business_session` or `transaction` for a source record." + }, + "id": { + "type": "string", + "format": "uuid" + }, + "application_id": { + "type": "string", + "format": "uuid" + } + } + }, + "NetworkTimelineAttempt": { + "type": "object", + "description": "One verification session of the account.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The session UUID, or the business session UUID for a company." + }, + "session_number": { + "type": "integer", + "nullable": true + }, + "attempt": { + "type": "integer", + "description": "This session's position among the account's sessions, in creation order." + }, + "attempt_count": { + "type": "integer", + "description": "How many sessions the account has." + }, + "status": { + "type": "string", + "description": "The session status, for example `Approved` or `Declined`." + }, + "status_changed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "decline_reasons": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkTimelineDeclineReason" + }, + "description": "For a declined session only: its warnings, oldest first, once per warning. Empty otherwise." + } + } + }, + "NetworkTimelineDeclineReason": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "description": "The warning code." + }, + "feature": { + "type": "string", + "description": "The feature that raised the warning." + }, + "short_description": { + "type": "string" + } + } + }, + "NetworkTimelineTransaction": { + "type": "object", + "description": "A transaction a member account took part in, dated by its transaction date.", + "properties": { + "transaction_id": { + "type": "string", + "format": "uuid" + }, + "transaction_number": { + "type": "integer", + "nullable": true + }, + "transaction_type": { + "type": "string" + }, + "action_type": { + "type": "string" + }, + "amount": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineAmount" + } + ], + "nullable": true, + "description": "Null when the transaction has no amount." + }, + "status": { + "type": "string" + }, + "decision_reason_code": { + "type": "string", + "nullable": true + }, + "decision_reason_label": { + "type": "string", + "nullable": true + }, + "party_role": { + "type": "string", + "description": "The account's role in the transaction: `APPLICANT`, `REMITTER`, `BENEFICIARY` or `COUNTERPARTY`." + } + } + }, + "NetworkTimelineAmount": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Decimal amount as a string." + }, + "currency": { + "type": "string" + }, + "direction": { + "type": "string", + "nullable": true + } + } + }, + "NetworkTimelineLogin": { + "type": "object", + "description": "A sign-in event you posted for a person through the events API.", + "properties": { + "action": { + "type": "string", + "description": "`.`: `auth.login` or `security.login_succeeded` for a sign-in, `auth.login_failed` or `security.login_failed` for a failed one." + }, + "succeeded": { + "type": "boolean" + }, + "status": { + "type": "string", + "description": "The event's status." + }, + "ip": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineIP" + } + ], + "nullable": true, + "description": "Null when the event has no IP address." + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineDevice" + } + ], + "nullable": true, + "description": "Null when the event has no device fingerprint or context." + }, + "is_first_login": { + "type": "boolean", + "description": "The account's first successful sign-in on record." + }, + "is_new_device": { + "type": "boolean", + "description": "A successful sign-in from a device none of the account's earlier successful sign-ins came from. Never true on the first sign-in." + } + } + }, + "NetworkTimelineIP": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "country_code": { + "type": "string", + "nullable": true + }, + "city": { + "type": "string", + "nullable": true + }, + "connection_type": { + "type": "string", + "nullable": true, + "description": "The connection type label for the address, as free text." + }, + "is_vpn_or_tor": { + "type": "boolean" + }, + "is_data_center": { + "type": "boolean" + } + } + }, + "NetworkTimelineDevice": { + "type": "object", + "properties": { + "fingerprint": { + "type": "string", + "nullable": true + }, + "label": { + "type": "string", + "nullable": true, + "description": "A readable device label built from the event's device context." + } + } + }, + "NetworkTimelineRuleHit": { + "type": "object", + "description": "A live rule that matched a member's transaction or event. Test-mode matches are not included.", + "properties": { + "rule_id": { + "type": "string", + "format": "uuid" + }, + "rule_title": { + "type": "string" + }, + "rule_category": { + "type": "string" + }, + "rule_source": { + "type": "string" + }, + "severity": { + "type": "string", + "nullable": true + }, + "score_delta": { + "type": "integer" + }, + "status_target": { + "type": "string", + "nullable": true + }, + "action_summary": { + "type": "string", + "nullable": true + } + } + }, + "NetworkTimelineCaseLinked": { + "type": "object", + "description": "A case opened for the network in your organization. `source_ref.ownership` says whether it belongs to the application you called.", + "properties": { + "case_id": { + "type": "string", + "format": "uuid" + }, + "case_number": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string" + }, + "author_email": { + "type": "string", + "nullable": true + }, + "author_name": { + "type": "string", + "nullable": true + } + } + }, + "NetworkTimelineNoteAdded": { + "type": "object", + "description": "A note left on the network. The note text is not included.", + "properties": { + "note_id": { + "type": "string", + "format": "uuid" + }, + "author_email": { + "type": "string" + } + } + }, + "FeatureStatusOverride": { + "type": "object", + "description": "One override of a check (feature) status made with `PATCH /v3/session/{sessionId}/features/{nodeId}/update-status/`: what it changes, whether it applied or still waits for the person to finish the flow, who asked and why.", + "required": [ + "id", + "node_id", + "feature_type", + "feature_model", + "previous_status", + "new_status", + "state", + "forced", + "reason", + "requested_by", + "requested_at", + "applied_at", + "cancelled_at", + "cancel_reason" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Identifier of the override. Pass it to the cancel endpoint.", + "example": "7c9e6679-7425-40de-944b-e07fc1f90ae7" + }, + "node_id": { + "type": "string", + "description": "The workflow node the request named (`default` for a step without a node).", + "example": "feature_ocr_1" + }, + "feature_type": { + "type": "string", + "description": "The `feature_type` the request passed, or an empty string when it passed none.", + "example": "" + }, + "feature_model": { + "type": "string", + "description": "The kind of check that was overridden, for example `KYC`, `Face`, `AML` or `GeolocationCheck`.", + "example": "KYC" + }, + "previous_status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "The check's status when the override was requested.", + "example": "In Review" + }, + "new_status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "description": "The status the override sets.", + "example": "Approved" + }, + "state": { + "type": "string", + "enum": [ + "applied", + "pending_until_flow_finishes", + "cancelled" + ], + "description": "`applied`: the check has the new status. `pending_until_flow_finishes`: the override waits and applies when the person finishes the flow. `cancelled`: it waited and never applied (see `cancel_reason`).", + "example": "applied" + }, + "forced": { + "type": "boolean", + "description": "`true` when the override applied at once while the person was still in the flow.", + "example": false + }, + "reason": { + "type": "string", + "nullable": true, + "description": "The `comment` sent with the request, or `null`.", + "example": "Document manually verified by compliance." + }, + "requested_by": { + "type": "object", + "nullable": true, + "description": "Who requested the override, or `null` when that is not known.", + "properties": { + "email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email of the console user, or `null` for API keys and the system.", + "example": "reviewer@example.com" + }, + "name": { + "type": "string", + "nullable": true, + "description": "Display name of the requester, or `null`.", + "example": "Alex Reviewer" + }, + "type": { + "type": "string", + "enum": [ + "CONSOLE_USER", + "API_KEY", + "SYSTEM" + ], + "description": "Who made the request: a console user, an API key, or Didit itself.", + "example": "CONSOLE_USER" + } + } + }, + "requested_at": { + "type": "string", + "format": "date-time", + "description": "When the override was requested.", + "example": "2026-10-13T09:15:00Z" + }, + "applied_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the check took the new status, or `null` while it waits or once it is cancelled.", + "example": "2026-10-13T09:15:00Z" + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When a waiting override was cancelled, or `null`.", + "example": null + }, + "cancel_reason": { + "type": "string", + "nullable": true, + "enum": [ + "cancelled_by_reviewer", + "replaced", + "check_changed", + "session_closed", + null + ], + "description": "Why a waiting override never applied, or `null`. `cancelled_by_reviewer`: cancelled with the cancel endpoint. `replaced`: a newer override on the same check replaced it. `check_changed`: the person redid the check before finishing the flow. `session_closed`: the session left the flow before the person finished it (abandoned, expired, or decided mid-flow).", + "example": null + } + } + }, + "SandboxSimulateRequest": { + "type": "object", + "description": "Force a sandbox session's status. Every field is optional, but at least one of `new_status`, `scenario` or `risk` is required.", + "properties": { + "new_status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned" + ], + "description": "The status to set. When you leave it out, it defaults to the default status of the risk case (`risk`, or the first risk the scenario's magic values carry), else to the scenario's default status.", + "example": "Declined" + }, + "comment": { + "type": "string", + "maxLength": 500, + "description": "Note added to the activity entry the status change records.", + "example": "Testing the decline path" + }, + "scenario": { + "type": "string", + "maxLength": 100, + "description": "A scenario slug from the catalog (`GET /v1/sandbox/scenarios/`). It supplies the default status and, without `risk`, the warning its magic values carry. An unknown slug returns `400`.", + "example": "decline_aml_hit" + }, + "risk": { + "type": "string", + "maxLength": 128, + "description": "A risk case from the catalog. Its warning is added to the session, with a log type that follows `new_status`. An unknown value returns `400`.", + "example": "POSSIBLE_MATCH_FOUND" + }, + "node_id": { + "type": "string", + "maxLength": 128, + "description": "Workflow node the risk warning is attached to. Defaults to the session's current node.", + "example": "feature_aml_1" + }, + "send_webhooks": { + "type": "boolean", + "default": true, + "description": "`true` sends exactly the webhooks a real status change sends to the sandbox application's destinations: `status.updated`, plus the `user.status.updated` / `user.data.updated` (or `business.status.updated` / `business.data.updated`) events the change causes, for destinations subscribed to them. `false` changes the status without logging or sending any webhook: the missed-webhook recovery never re-sends that decision, the console still updates live, and the next status change of the session sends webhooks as usual.", + "example": true + } + } + }, + "SandboxSimulateResponse": { + "type": "object", + "required": [ + "session_id", + "previous_status", + "status", + "scenario", + "risk", + "send_webhooks" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "previous_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "The session's status before the simulation.", + "example": "In Progress" + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned" + ], + "description": "The status the simulation set.", + "example": "Declined" + }, + "scenario": { + "type": "string", + "nullable": true, + "description": "The scenario slug you passed, or `null`.", + "example": "decline_aml_hit" + }, + "risk": { + "type": "string", + "nullable": true, + "description": "The risk case applied (`risk`, or the one taken from the scenario), or `null` when none.", + "example": "POSSIBLE_MATCH_FOUND" + }, + "send_webhooks": { + "type": "boolean", + "description": "The `send_webhooks` value the simulation applied.", + "example": false + } + } + }, + "CaseTeamSummary": { + "type": "object", + "nullable": true, + "description": "The review team that owns the case, or null when no team owns it. Routing sets it when a routing rule places the case with one of its teams. Change or clear it with `PATCH .../cases/{case_uuid}/` (`team`).", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + } + } + }, + "CaseChecker": { + "type": "object", + "nullable": true, + "description": "The member named to approve or reject the pending resolution. Set only while a four-eyes resolution waits on a named checker. Null when nothing is pending, while the pending resolution waits on the checker queue, and once the resolution is approved, rejected, or returned for a new review because the evidence changed.", + "properties": { + "member_uuid": { + "type": "string", + "format": "uuid", + "description": "The member's user uuid: the value you pass as `checker` when you resolve the case." + }, + "name": { + "type": "string", + "nullable": true, + "description": "The member's full name, or null when it is not known." + }, + "email": { + "type": "string", + "format": "email" + } + } + }, + "ServiceStatusFeed": { + "type": "object", + "description": "Public service status: the state of each Didit service, the overall state, and the incidents that are ongoing or were resolved in the last 7 days.", + "required": [ + "status", + "updated_at", + "services", + "incidents" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "operational", + "degraded", + "down" + ], + "description": "The worst state of any service right now, decided by the ongoing incidents only.", + "example": "degraded" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When Didit built this response (ISO 8601, UTC). Responses are cached for 30 seconds, so it can be up to 30 seconds old.", + "example": "2026-10-13T09:41:07.512345Z" + }, + "services": { + "type": "array", + "description": "One entry per service, every service always listed.", + "items": { + "$ref": "#/components/schemas/ServiceStatusService" + } + }, + "incidents": { + "type": "array", + "description": "Newest `started_at` first. Every ongoing incident, plus the incidents resolved in the last 7 days (at most 50 resolved incidents). Empty when nothing happened in that time.", + "items": { + "$ref": "#/components/schemas/ServiceStatusIncident" + } + } + } + }, + "ServiceStatusService": { + "type": "object", + "required": [ + "key", + "name", + "status" + ], + "properties": { + "key": { + "type": "string", + "enum": [ + "service_id_verification", + "service_liveness_face_match", + "service_aml_screening", + "service_kyb_registries", + "service_database_validation", + "service_phone_delivery", + "service_email_delivery", + "service_transaction_monitoring" + ], + "description": "Stable service key. Match on this, never on the display name. `service_id_verification`: ID verification, `service_liveness_face_match`: Liveness and face match, `service_aml_screening`: AML screening, `service_kyb_registries`: KYB registries, `service_database_validation`: Database validation, `service_phone_delivery`: Phone verification delivery, `service_email_delivery`: Email verification delivery, `service_transaction_monitoring`: Transaction monitoring.", + "example": "service_database_validation" + }, + "name": { + "type": "string", + "description": "English display name of the service.", + "example": "Database validation" + }, + "status": { + "type": "string", + "enum": [ + "operational", + "degraded", + "down" + ], + "description": "`operational` when no incident is ongoing for the service, otherwise the worst state of its ongoing incidents.", + "example": "degraded" + } + } + }, + "ServiceStatusIncident": { + "type": "object", + "required": [ + "id", + "service", + "service_name", + "state", + "status", + "title", + "body", + "affected", + "country_name", + "started_at", + "resolved_at", + "duration_minutes" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Incident ID.", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "service": { + "type": "string", + "enum": [ + "service_id_verification", + "service_liveness_face_match", + "service_aml_screening", + "service_kyb_registries", + "service_database_validation", + "service_phone_delivery", + "service_email_delivery", + "service_transaction_monitoring" + ], + "description": "Key of the affected service (see `services[].key`).", + "example": "service_database_validation" + }, + "service_name": { + "type": "string", + "description": "English display name of the affected service.", + "example": "Database validation" + }, + "state": { + "type": "string", + "enum": [ + "degraded", + "down" + ], + "description": "How the service is affected. A resolved incident keeps the last state it had.", + "example": "degraded" + }, + "status": { + "type": "string", + "enum": [ + "ongoing", + "resolved" + ], + "description": "`ongoing` until the service recovers, then `resolved`.", + "example": "ongoing" + }, + "title": { + "type": "string", + "description": "English headline. To translate it, build your own text from `service`, `state` and `country_name`.", + "example": "Database validation is degraded in Brazil." + }, + "body": { + "type": "string", + "description": "English detail line: since when checks may be slow or fail, or when it recovered.", + "example": "Checks against Brazil (CPF) may be slow or fail since 08:55 UTC." + }, + "affected": { + "type": "string", + "description": "Countries or registries affected, for example `Brazil (CPF)`. Empty when the incident is not tied to a country or registry.", + "example": "Brazil (CPF)" + }, + "country_name": { + "type": "string", + "description": "Country affected, in English. Empty when the incident is not tied to a country.", + "example": "Brazil" + }, + "started_at": { + "type": "string", + "format": "date-time", + "description": "When the incident started (ISO 8601, UTC).", + "example": "2026-10-13T08:55:00Z" + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the service recovered (ISO 8601, UTC). `null` while the incident is ongoing." + }, + "duration_minutes": { + "type": "integer", + "nullable": true, + "description": "Whole minutes from `started_at` to `resolved_at`. `null` while the incident is ongoing." + } + } + }, + "TravelRuleJoinInvitation": { + "type": "object", + "description": "What the join page shows before anything is typed.", + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "The address the invitation went to." + }, + "vasp_name": { + "type": "string" + }, + "source": { + "type": "string", + "description": "Where the invitation came from, for example `pickup`." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "prefill": { + "type": "object", + "properties": { + "vasp_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "legal_name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "jurisdiction": { + "type": "string" + }, + "domains": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "TravelRuleJoinRegisterRequest": { + "type": "object", + "required": [ + "email", + "password" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "maxLength": 254, + "description": "Must be the address the invitation went to." + }, + "password": { + "type": "string", + "minLength": 8, + "maxLength": 128 + } + } + }, + "TravelRuleJoinVerifyRequest": { + "type": "object", + "required": [ + "email", + "code" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "maxLength": 254 + }, + "code": { + "type": "string", + "minLength": 6, + "maxLength": 6, + "description": "The 6-character code emailed after registering." + } + } + }, + "TravelRuleJoinCredentials": { + "type": "object", + "description": "The new free organization and the credentials its application calls the API with.", + "properties": { + "organization": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + } + } + }, + "application": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + } + } + }, + "client_id": { + "type": "string" + }, + "api_key": { + "type": "string", + "description": "Send as the `x-api-key` header on the workspace endpoints." + } + } + }, + "TravelRuleWorkspaceClaimRequest": { + "type": "object", + "required": [ + "vasp_id" + ], + "properties": { + "vasp_id": { + "type": "string", + "format": "uuid", + "description": "The directory entry this workspace is. Read ids from `GET /v3/travel-rule/vasps/`." + } + } + }, + "TravelRuleWorkspaceDomainConfirmRequest": { + "type": "object", + "required": [ + "code" + ], + "properties": { + "code": { + "type": "string", + "minLength": 6, + "maxLength": 6 + } + } + }, + "TravelRuleWorkspaceBillingRequest": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": true, + "description": "Only `true` is accepted: outbound billing cannot be turned back off through the API." + } + } + }, + "TravelRuleWorkspaceDomainCodeResponse": { + "type": "object", + "properties": { + "domain": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "unverified", + "code_sent", + "verified" + ] + }, + "txt": { + "$ref": "#/components/schemas/TravelRuleWorkspaceTxtRecord" + } + } + }, + "TravelRuleWorkspaceTxtRecord": { + "type": "object", + "description": "The TXT record to publish for the DNS proof. Empty until a proof has been started.", + "properties": { + "host": { + "type": "string", + "example": "didit-tr-verify.example.com" + }, + "value": { + "type": "string" + } + } + }, + "TravelRuleWorkspace": { + "type": "object", + "description": "The free Travel Rule workspace of an application that joined free.", + "properties": { + "joined_free": { + "type": "boolean" + }, + "work_email": { + "type": "string", + "format": "email" + }, + "joined_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "claim": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "unclaimed", + "pending_review", + "claimed", + "rejected" + ] + }, + "proved_by_domain": { + "type": "boolean" + }, + "claimed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reviewed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "review_note": { + "type": "string" + }, + "vasp_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "vasp_name": { + "type": "string" + } + } + }, + "prefill": { + "type": "object", + "description": "Profile values taken from the directory entry: `vasp_id`, `legal_name`, `lei`, `jurisdiction`, `compliance_email`, `domains`. Nothing is written until the claim is made." + }, + "domain": { + "type": "object", + "properties": { + "domain": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "unverified", + "code_sent", + "verified" + ] + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "txt": { + "$ref": "#/components/schemas/TravelRuleWorkspaceTxtRecord" + } + } + }, + "billing_enabled": { + "type": "boolean", + "description": "Receiving Travel Rule data is free. Sending is billed only after billing is enabled." + }, + "billing_enabled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "didit_network": { + "type": "object", + "description": "Whether the Didit network lists this VASP and, when not, each condition it still misses." + }, + "settings": { + "$ref": "#/components/schemas/TravelRuleSettingsDetail" + } + } + }, + "InvestigatorDecision": { + "type": "object", + "description": "How the item left the Investigator queue. `decided_by`, `original_outcome` and `overrides` are returned by the accept and override endpoints and the report; the `investigator` block of a session decision carries the other fields.", + "properties": { + "outcome": { + "type": "string", + "enum": [ + "accepted", + "edited", + "overridden", + "decided_elsewhere", + "automated" + ], + "description": "`accepted` or `edited` took the recommendation (`edited` with a rewritten narrative), `overridden` took another action, `decided_elsewhere` is a decision taken outside the Investigator while the report was open, `automated` is a decision the Investigator took on its own." + }, + "action": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ] + }, + "actor_email": { + "type": "string", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "override_category": { + "type": "string", + "enum": [ + "missing_context", + "needs_information", + "policy_differs", + "evidence_misread", + "other", + "" + ], + "nullable": true + }, + "override_reason": { + "type": "string", + "nullable": true + }, + "final_status": { + "type": "string", + "description": "The item's status after the decision." + }, + "agreed": { + "type": "boolean", + "description": "Whether the decision matched the recommendation.", + "nullable": true + }, + "decided_at": { + "type": "string", + "format": "date-time" + }, + "billed": { + "type": "boolean", + "description": "The review was charged." + }, + "decided_by": { + "type": "string", + "enum": [ + "investigator", + "member" + ] + }, + "original_outcome": { + "type": "object", + "description": "The decision the Investigator took on its own, unchanged by later overrides. Null for a decision the team took.", + "properties": { + "action": { + "type": "string" + }, + "final_status": { + "type": "string" + }, + "decided_at": { + "type": "string", + "format": "date-time" + } + }, + "nullable": true + }, + "overrides": { + "type": "array", + "description": "Every override of a decision the Investigator took on its own, oldest first.", + "items": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "actor_email": { + "type": "string", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "override_category": { + "type": "string" + }, + "override_reason": { + "type": "string" + }, + "previous_status": { + "type": "string" + }, + "final_status": { + "type": "string" + }, + "agreed": { + "type": "boolean", + "nullable": true + }, + "overridden_at": { + "type": "string", + "format": "date-time" + } + } + } + } + } + }, + "InvestigatorBlock": { + "type": "object", + "nullable": true, + "description": "The item's latest Investigator review, or null when it never had one. Carried by the V3 session and business session decision (and so by `status.updated` webhooks).", + "properties": { + "run_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "running", + "completed", + "failed" + ] + }, + "stage": { + "type": "string", + "enum": [ + "shadow", + "live" + ] + }, + "queue": { + "type": "string", + "enum": [ + "kyc", + "kyb", + "aml", + "transactions", + "cases" + ] + }, + "is_open": { + "type": "boolean" + }, + "recommendation": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ], + "nullable": true + }, + "recommendation_detail": { + "type": "string", + "nullable": true + }, + "confidence": { + "type": "string", + "enum": [ + "high", + "moderate", + "low" + ], + "nullable": true + }, + "summary": { + "type": "string", + "nullable": true + }, + "ready_to_accept": { + "type": "boolean" + }, + "prepared_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "automated": { + "type": "boolean", + "description": "The Investigator decided on its own, even if the decision was later overridden." + }, + "decided_by": { + "type": "string", + "enum": [ + "investigator", + "member" + ], + "nullable": true + }, + "decision": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/InvestigatorDecision" + } + ], + "type": "object" + } + } + }, + "InvestigatorQueueRow": { + "type": "object", + "properties": { + "run_id": { + "type": "string", + "format": "uuid" + }, + "kind": { + "type": "string", + "enum": [ + "session", + "business_session", + "transaction", + "case" + ] + }, + "queue": { + "type": "string", + "enum": [ + "kyc", + "kyb", + "aml", + "transactions", + "cases" + ] + }, + "stage": { + "type": "string", + "enum": [ + "shadow", + "live" + ] + }, + "subject": { + "type": "object", + "description": "The item under review: `id`, `number`, `status`, `name` and, depending on the kind, `country`, `workflow`, `vendor_data`, `txn_id`, `amount`, `currency`, `direction`, `title` or `priority`.", + "additionalProperties": true + }, + "recommendation": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ] + }, + "recommendation_detail": { + "type": "string" + }, + "confidence": { + "type": "string", + "enum": [ + "high", + "moderate", + "low" + ] + }, + "summary": { + "type": "string" + }, + "sources_count": { + "type": "integer" + }, + "ready_to_accept": { + "type": "boolean" + }, + "guardrails": { + "description": "The locked guardrails that hold this item, if any." + }, + "waiting_since": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "prepared_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "InvestigatorRunDetail": { + "allOf": [ + { + "$ref": "#/components/schemas/InvestigatorQueueRow" + }, + { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "is_open": { + "type": "boolean" + }, + "lookups": { + "type": "integer", + "description": "How many sections and linked records it read." + }, + "duration_ms": { + "type": "integer", + "nullable": true + }, + "agent_version": { + "type": "string" + }, + "prepared_before_opened": { + "type": "boolean", + "nullable": true + }, + "report": { + "type": "object", + "additionalProperties": true, + "description": "The report: reasoning, evidence points with their verdict (`ok`, `risk`, `look`, `explained`, `context`) and cited sources, screening hit verdicts (`same`, `not_same`, `unclear`) and suggested next steps." + }, + "trail": { + "description": "What I checked: the overview, sections and records it read, in order." + }, + "decision": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/InvestigatorDecision" + } + ], + "type": "object" + }, + "autonomy": { + "type": "object", + "additionalProperties": true, + "description": "What autonomy made of the report (`result`, `reason`, `tier`, `policy_version`, `rule`, `agreement`, `decisions`, `threshold`, `at`). Null when the queue is not automated.", + "nullable": true + } + } + } + ] + }, + "InvestigatorAcceptRequest": { + "type": "object", + "properties": { + "narrative": { + "type": "string", + "maxLength": 4000, + "description": "A rewritten narrative. Sending one records the decision as edited and accepted." + }, + "send_email": { + "type": "boolean", + "default": false, + "description": "Email the person about the decision." + }, + "email_address": { + "type": "string", + "format": "email", + "description": "Required when `send_email` is true." + }, + "email_language": { + "type": "string", + "default": "en", + "maxLength": 16 + } + } + }, + "InvestigatorOverrideRequest": { + "type": "object", + "required": [ + "action", + "category", + "reason" + ], + "properties": { + "action": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ] + }, + "category": { + "type": "string", + "enum": [ + "missing_context", + "needs_information", + "policy_differs", + "evidence_misread", + "other" + ] + }, + "reason": { + "type": "string", + "minLength": 10, + "maxLength": 500 + }, + "send_email": { + "type": "boolean", + "default": false, + "description": "Email the person about the decision." + }, + "email_address": { + "type": "string", + "format": "email", + "description": "Required when `send_email` is true." + }, + "email_language": { + "type": "string", + "default": "en", + "maxLength": 16 + } + } + }, + "InvestigatorBatchAcceptRequest": { + "type": "object", + "required": [ + "run_ids" + ], + "properties": { + "run_ids": { + "type": "array", + "minItems": 1, + "maxItems": 50, + "items": { + "type": "string", + "format": "uuid" + } + } + } + }, + "InvestigatorBatchAcceptResponse": { + "type": "object", + "properties": { + "accepted": { + "type": "integer" + }, + "skipped": { + "type": "integer" + }, + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "run_id": { + "type": "string", + "format": "uuid" + }, + "result": { + "type": "string", + "enum": [ + "accepted", + "skipped" + ] + }, + "reason": { + "type": "string", + "enum": [ + "not_found", + "forbidden", + "already_decided", + "not_ready", + "guardrail", + "invalid" + ] + }, + "detail": {}, + "action": { + "type": "string" + }, + "final_status": { + "type": "string" + } + } + } + } + } + }, + "InvestigatorSettings": { + "type": "object", + "properties": { + "stage": { + "type": "string", + "enum": [ + "none", + "shadow", + "live" + ] + }, + "stage_changed_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "nullable": true + }, + "stage_changed_by": { + "type": "string", + "readOnly": true + }, + "monthly_spend_limit": { + "type": "string", + "format": "decimal", + "description": "Monthly limit in your billing currency, 0.01 to 1,000,000. Null turns it off.", + "nullable": true + }, + "spend_limit": { + "type": "object", + "readOnly": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "limit": { + "type": "string", + "format": "decimal", + "nullable": true + }, + "spent": { + "type": "string", + "format": "decimal" + }, + "reviews": { + "type": "integer" + }, + "reserved": { + "type": "string", + "format": "decimal", + "description": "The price of every open live report, held back because accepting it charges it." + }, + "currency": { + "type": "string" + }, + "paused": { + "type": "boolean", + "description": "At the limit: no new review starts." + } + } + } + } + }, + "TravelRuleUtxoEntry": { + "type": "object", + "description": "One input or output of a UTXO transaction, as the chain recorded it.", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string", + "maxLength": 128 + }, + "amount": { + "type": "string", + "nullable": true, + "description": "Decimal string in the coin's own unit (BTC, not satoshis). Optional.", + "example": "0.0125" + }, + "txid": { + "type": "string", + "maxLength": 128, + "description": "Optional. The transaction an input spends." + }, + "vout": { + "type": "integer", + "nullable": true, + "minimum": 0, + "description": "Optional. The output index an input spends." + } + } + }, + "TravelRuleUtxo": { + "type": "object", + "description": "The inputs and outputs of a UTXO transfer (BTC, LTC, BCH, DOGE). Only accepted on those chains: on any other chain it is a `400`. From them Didit infers the originator wallet (the declared one, else the input address that contributed the most) and the beneficiary output (the declared one, else the only output that does not pay an input address back as change, else the one output paying the transfer amount). The result is on the transfer's `chain_details`.", + "required": [ + "outputs" + ], + "properties": { + "inputs": { + "type": "array", + "maxItems": 1000, + "items": { + "$ref": "#/components/schemas/TravelRuleUtxoEntry" + }, + "description": "Optional. Without inputs no originator is inferred and no change output is recognised." + }, + "outputs": { + "type": "array", + "minItems": 1, + "maxItems": 1000, + "items": { + "$ref": "#/components/schemas/TravelRuleUtxoEntry" + } + } + } + }, + "TravelRuleChainSide": { + "type": "object", + "description": "One side's wallet as Didit resolved it from the chain data.", + "properties": { + "address": { + "type": "string", + "description": "The wallet. On a memo network the classic address (an XRP X-address is split into its classic address and its tag); on Lightning the payee node id or Lightning Address." + }, + "memo": { + "type": "string", + "description": "The destination tag or memo that is part of the wallet; `\"\"` for none." + }, + "source": { + "type": "string", + "enum": [ + "declared", + "x_address", + "largest_input", + "only_non_change_output", + "amount_match", + "invoice_payee", + "updated" + ], + "description": "Where the wallet came from: `declared` (your request), `x_address` (split from an XRP X-address), `largest_input` (the UTXO input address that contributed the most), `only_non_change_output` (the only UTXO output that is not change), `amount_match` (the one UTXO output paying the transfer amount), `invoice_payee` (the payee a Lightning payment request names), `updated` (set later with `action: update_originator_wallet`)." + } + } + }, + "TravelRuleChainDetails": { + "type": "object", + "description": "How the transfer's chain names its two wallets, when that is more than one address paying another. Empty (`{}`) for an account-chain transfer (EVM, Solana, Tron), where the two addresses are the wallets. Matching, routing and the address book read the wallets from here first.", + "properties": { + "chain": { + "type": "string", + "description": "Canonical chain, e.g. `BTC`, `XRP`, or `LIGHTNING` for a Lightning transfer." + }, + "shape": { + "type": "string", + "enum": [ + "utxo", + "memo", + "lightning" + ] + }, + "originator": { + "$ref": "#/components/schemas/TravelRuleChainSide" + }, + "beneficiary": { + "$ref": "#/components/schemas/TravelRuleChainSide" + }, + "utxo": { + "type": "object", + "description": "On a UTXO transfer: the inputs and outputs as stored (amounts as decimal strings) and which outputs Didit read as what.", + "properties": { + "inputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleUtxoEntry" + } + }, + "outputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleUtxoEntry" + } + }, + "change_output_indexes": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "Outputs that pay an input address back." + }, + "beneficiary_output_index": { + "type": "integer", + "nullable": true, + "description": "The output that pays the beneficiary; null when none could be told apart." + } + } + }, + "lightning": { + "type": "object", + "description": "On a Lightning transfer: what the payment request says. Always `kind` (`bolt11`, `bolt12_offer`, `bolt12_invoice`, `bolt12_invoice_request`, `lnurl`, `lightning_address`, `node_id` or `unknown`); a BOLT11 invoice adds `network`, `payee_node_id`, `payment_hash`, `amount_msat`, `created_at` and `expires_at`; a BOLT12 string `blinded_paths` and `payee_node_id`; an LNURL or a Lightning Address its `domain`. Nothing is resolved over the network: the request is decoded only.", + "additionalProperties": true + }, + "warnings": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "declared_originator_not_an_input", + "declared_beneficiary_not_an_output", + "beneficiary_output_ambiguous", + "beneficiary_output_amount_differs" + ] + }, + "description": "UTXO inconsistencies worth a look: the declared originator is not an input, the declared beneficiary is not an output, no single beneficiary output could be told apart, or the beneficiary output pays a different amount than the transaction states." + }, + "not_applicable_reason": { + "type": "string", + "enum": [ + "LIGHTNING_NO_PAYEE_IDENTIFIER", + "LIGHTNING_BOLT12_BLINDED_PAYEE", + "LIGHTNING_LNURL_NAMES_NO_PAYEE" + ], + "description": "On an outbound Lightning transfer whose payment request names no payee: why the transfer is `NOT_APPLICABLE`. `LIGHTNING_NO_PAYEE_IDENTIFIER` (the request names no payee node, or could not be decoded), `LIGHTNING_BOLT12_BLINDED_PAYEE` (the offer hides its payee behind blinded paths), `LIGHTNING_LNURL_NAMES_NO_PAYEE` (an LNURL names a service, not a payee). The same code opens the transfer's last `timeline` entry." + } + } + }, + "WalletAddressImportRow": { + "type": "object", + "description": "One wallet to import: by its `address`, its `address_hash`, or both. The camelCase forms of these fields are accepted too, as are `walletAddress`, `walletAddressHash`, `destinationTag` and `externalUserId`.", + "properties": { + "address": { + "type": "string", + "maxLength": 128, + "description": "The wallet address. Alias: `walletAddress`." + }, + "address_hash": { + "type": "string", + "description": "SHA-256 of the lowercased address, as 64 hex characters or 44 base64 characters. Send it alone to register a wallet without disclosing its address; sent with `address`, it must be that address's hash. Alias: `walletAddressHash`." + }, + "chain": { + "type": "string", + "maxLength": 32 + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "The destination tag or memo. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`." + }, + "holder_name": { + "type": "string", + "maxLength": 255 + }, + "holder_vendor_data": { + "type": "string", + "maxLength": 255, + "description": "Your internal identifier for the holder. Alias: `externalUserId`." + }, + "entity_type": { + "type": "string", + "enum": [ + "individual", + "company" + ], + "default": "individual" + }, + "label": { + "type": "string", + "maxLength": 255 + }, + "owner_type": { + "type": "string", + "enum": [ + "customer", + "internal", + "vasp" + ], + "default": "customer" + }, + "travel_address": { + "type": "string", + "maxLength": 512 + }, + "self_declared": { + "type": "boolean", + "default": false, + "description": "Create the entry already verified through a self-declaration, when your settings allow it." + } + } + }, + "WalletAddressImportReport": { + "type": "object", + "description": "What happened to each row of an import, in the order sent.", + "properties": { + "total": { + "type": "integer" + }, + "created": { + "type": "integer" + }, + "existing": { + "type": "integer" + }, + "duplicate": { + "type": "integer" + }, + "failed": { + "type": "integer" + }, + "rows": { + "type": "array", + "items": { + "type": "object", + "properties": { + "row": { + "type": "integer", + "description": "The row's number, from 1." + }, + "status": { + "type": "string", + "enum": [ + "created", + "existing", + "duplicate", + "failed" + ], + "description": "`created`: added to the book. `existing`: the wallet is already in the book and was left untouched. `duplicate`: repeats an earlier row of this import. `failed`: see `errors`." + }, + "entry_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The address-book entry the row created or found." + }, + "errors": { + "type": "object", + "description": "Field errors of a `failed` row; empty otherwise." + }, + "duplicate_of_row": { + "type": "integer", + "nullable": true, + "description": "On a `duplicate` row, the row it repeats." + } + } + } + } + } + }, + "TravelRuleCounterpartyContact": { + "type": "object", + "description": "A compliance address the EMAIL rail writes to for one directory entry.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "counterparty": { + "type": "string", + "format": "uuid", + "description": "The directory entry's `id`, as GET /v3/travel-rule/vasps/ shows it." + }, + "counterparty_name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "name": { + "type": "string", + "description": "Optional label, for example the team name." + }, + "source": { + "type": "string", + "enum": [ + "staff", + "application", + "learned" + ], + "description": "staff: curated by Didit. learned: an address at one of the VASP's own domains that answered a request through its response link. application: added by your application." + }, + "scope": { + "type": "string", + "enum": [ + "platform", + "application" + ], + "description": "platform: curated by Didit or learned from a verified answer, used for every Didit customer, read-only for you. application: private to your application." + }, + "created_by": { + "type": "string", + "description": "Empty for platform contacts and for contacts an API key added." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this address last answered a request through its response link." + }, + "is_invalid": { + "type": "boolean", + "description": "true once the address bounced or complained; the EMAIL rail skips it." + }, + "invalid_reason": { + "type": "string", + "enum": [ + "", + "hard_bounce", + "soft_bounce", + "spam_complaint", + "suppressed" + ] + }, + "invalid_detail": { + "type": "string", + "description": "The receiving mail server's reason, when it gave one." + }, + "invalid_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleCounterpartyContactCreate": { + "type": "object", + "required": [ + "counterparty", + "email" + ], + "properties": { + "counterparty": { + "type": "string", + "format": "uuid", + "description": "The directory entry's `id`." + }, + "email": { + "type": "string", + "format": "email", + "maxLength": 254 + }, + "name": { + "type": "string", + "description": "Optional label.", + "maxLength": 255 + } + } + }, + "TravelRuleNameMatch": { + "type": "object", + "nullable": true, + "description": "The name comparison that decided the transfer, or null until one ran. While the parties are masked, and on the other customer's copy of an INTERNAL exchange, `transfer_name`, `compared_name`, `matched_names` and `mismatched_names` are hidden. `score` and `matched` can disagree: `matched` follows the strictness, `score` is the similarity of the two full names.", + "properties": { + "score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Similarity of the two full names, 0 to 100. 0 when either name is blank." + }, + "matched": { + "type": "boolean", + "description": "Whether the names satisfy `strictness`. The transfer's outcome follows `data_match`, which compares every field, so a transfer can be COUNTERPARTY_MISMATCHED_DATA while this is true." + }, + "method": { + "type": "string", + "enum": [ + "none", + "exact_token", + "token_subset", + "token_containment", + "sequence_ratio" + ], + "description": "How `matched` was decided: none (`NONE`), exact_token (`STRICT`), token_subset (`DEFAULT`), token_containment (`CONTAINS`) or sequence_ratio (`FUZZY`)." + }, + "strictness": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ] + }, + "compared_with": { + "type": "string", + "enum": [ + "wallet_holder", + "counterparty_vasp" + ], + "description": "wallet_holder: the holder recorded for the wallet. counterparty_vasp: the name the counterparty answered on the email response page, or the Didit Test VASP's answer in sandbox." + }, + "transfer_name": { + "type": "string", + "description": "The name on the transfer." + }, + "compared_name": { + "type": "string", + "description": "The name it was compared with." + }, + "matched_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Words of `transfer_name` found in `compared_name`." + }, + "mismatched_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Words of `transfer_name` missing from `compared_name`, then words of `compared_name` missing from `transfer_name`." + } + } + }, + "TravelRuleTransferRow": { + "type": "object", + "description": "One Travel Rule transfer as the transfers list shows it: VASP names, wallets and memos only, never the originator or beneficiary person data.", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The transfer's id." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "The Didit transaction the transfer belongs to." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "Your own id for the transaction. Transfers Didit books for you, such as a Didit Test VASP inbound transfer, carry a generated one." + }, + "txn_date": { + "type": "string", + "format": "date-time" + }, + "kind": { + "type": "string", + "enum": [ + "withdrawal", + "deposit", + "message" + ], + "description": "withdrawal: outbound. message: an inbound transfer a rail created from a counterparty's message (a TRP inquiry, the INTERNAL inbound leg, or a GTR, VerifyVASP or Sygna Bridge message). deposit: any other inbound transfer." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ] + }, + "status": { + "type": "string", + "enum": [ + "AWAITING_COUNTERPARTY", + "ON_HOLD", + "COMPLETED", + "FINISHED", + "CANCELLED", + "EXPIRED", + "COUNTERPARTY_MISMATCHED_DATA", + "COUNTERPARTY_VASP_NOT_FOUND", + "COUNTERPARTY_VASP_NOT_REACHABLE", + "UNCONFIRMED_OWNERSHIP", + "COUNTERPARTY_VASP_GENERAL_DECLINE", + "NOT_ENOUGH_COUNTERPARTY_DATA", + "NOT_APPLICABLE" + ] + }, + "reason": { + "type": "string", + "description": "Why a `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE` transfer is stuck, the same value as the transfer's own `reason`, so the list tells a bounced compliance email from a counterparty with no route without opening each transfer. `\"\"` for any other status. See [Why a transfer is stuck](/transaction-monitoring/travel-rule#why-a-transfer-is-stuck)." + }, + "rail": { + "type": "string", + "nullable": true, + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL", + "TEST_VASP", + null + ] + }, + "asset": { + "type": "string", + "description": "The transaction's currency." + }, + "amount": { + "type": "string", + "description": "The amount as a plain decimal, for example \"1.25\"." + }, + "originator_vasp": { + "type": "string", + "description": "Outbound: your VASP's legal name. Inbound: the counterparty's name, from its directory entry, the Didit network member, the TRP sender, the Didit Test VASP, or the name the transfer declared. Empty when none is known." + }, + "beneficiary_vasp": { + "type": "string", + "description": "Outbound: the counterparty's name, worked out the same way. Inbound: your VASP's legal name." + }, + "originator_wallet": { + "type": "string" + }, + "beneficiary_wallet": { + "type": "string" + }, + "originator_memo": { + "type": "string", + "description": "Memo or destination tag, empty on chains without one." + }, + "beneficiary_memo": { + "type": "string" + }, + "originator_wallet_type": { + "type": "string", + "enum": [ + "vasp", + "self_hosted", + "own" + ], + "description": "vasp, self_hosted (declared unhosted) or own (a wallet your address book records as yours). Your own side is always vasp." + }, + "beneficiary_wallet_type": { + "type": "string", + "enum": [ + "vasp", + "self_hosted", + "own" + ] + }, + "matched_transaction_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The other side of a deposit you matched to an announced transfer by hand." + }, + "deadline_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the transfer expires if it is still waiting on its counterparty. Set on every transfer you create with `POST /v3/transactions/` that needs an exchange, outbound or inbound. Null below the threshold, on a Lightning payment the Travel Rule does not apply to, and on inbound transfers Didit creates when a counterparty reaches you (an inbound TRP inquiry, a network message or the INTERNAL inbound leg)." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BankAccountCheckHolder": { + "description": "The name the account is checked against. Send exactly one of `full_name` and `business_name`.", + "oneOf": [ + { + "$ref": "#/components/schemas/BankAccountCheckPersonHolder" + }, + { + "$ref": "#/components/schemas/BankAccountCheckBusinessHolder" + } + ] + }, + "BankAccountCheckRequest": { + "type": "object", + "required": [ + "country", + "account", + "holder" + ], + "properties": { + "country": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "description": "The account's country, as an ISO 3166-1 alpha-3 code, for example `GBR`. Lowercase is accepted.", + "example": "GBR" + }, + "account": { + "type": "object", + "description": "The account the way its country writes it: an `iban`, or an `account_number` with the bank or branch code the country needs (`sort_code`, `routing_number`, `transit_institution`, `bsb`, `ifsc`, `bank_code` or `bic`). Mexico takes a `clabe` and Argentina a `cbu_cvu`, which already name the bank. A business account adds a `registration_id` where the country asks for one. Spaces, dashes and dots are ignored. Keys the country's check does not use are ignored; a key it needs and does not get is a `400` that names it.", + "properties": { + "iban": { + "type": "string", + "maxLength": 64, + "description": "The IBAN, in a country that uses IBANs. It must belong to `country`." + }, + "account_number": { + "type": "string", + "maxLength": 64, + "description": "The account number, in a country that writes the account with a separate bank or branch code." + }, + "sort_code": { + "type": "string", + "maxLength": 64, + "description": "United Kingdom: the 6-digit sort code." + }, + "routing_number": { + "type": "string", + "maxLength": 64, + "description": "United States: the 9-digit ABA routing number." + }, + "transit_institution": { + "type": "string", + "maxLength": 64, + "description": "Canada: the 8-digit transit and institution number." + }, + "bsb": { + "type": "string", + "maxLength": 64, + "description": "Australia: the 6-digit BSB." + }, + "ifsc": { + "type": "string", + "maxLength": 64, + "description": "India: the 11-character IFSC." + }, + "bank_code": { + "type": "string", + "maxLength": 64, + "description": "The bank's code, where the country needs one, for example in Nigeria." + }, + "bic": { + "type": "string", + "maxLength": 64, + "description": "The bank's BIC, where the country's check asks for one." + }, + "clabe": { + "type": "string", + "maxLength": 64, + "description": "Mexico: the 18-digit CLABE. It already names the bank." + }, + "cbu_cvu": { + "type": "string", + "maxLength": 64, + "description": "Argentina: the 22-digit CBU or CVU. It already names the bank." + }, + "registration_id": { + "type": "string", + "maxLength": 64, + "description": "A business account's registration number, where the country asks for one." + } + }, + "additionalProperties": { + "type": "string", + "maxLength": 64 + }, + "example": { + "sort_code": "20-45-67", + "account_number": "31926819" + } + }, + "holder": { + "$ref": "#/components/schemas/BankAccountCheckHolder" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this check, returned unchanged.", + "example": "payout_20481" + }, + "consent": { + "type": "boolean", + "default": false, + "description": "Set to `true` when the account holder has explicitly consented to this check. Required in the countries whose account check needs that consent: without it the check is refused with a `400` whose `field` is `consent` and whose `reason` is `required`, and nothing is billed. Elsewhere it is optional, and Didit records it with the check's charge when you send it.", + "example": true + } + } + }, + "BankAccountCheckResponse": { + "type": "object", + "required": [ + "request_id", + "result", + "holder_name", + "account", + "vendor_data", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "The ID of this check." + }, + "result": { + "type": "string", + "enum": [ + "match", + "close_match", + "no_match", + "cannot_verify" + ], + "description": "`match`: the bank holds the account under the name you sent. `close_match`: under a close name, for example a missing middle name or a typo. `no_match`: under another name, or there is no such account. `cannot_verify`: the bank answered without comparing the name, for example for a blocked or closed account." + }, + "holder_name": { + "type": "string", + "nullable": true, + "description": "Only on a `close_match`, and only when the bank returned it: the name on the account, as the bank holds it. `null` otherwise." + }, + "account": { + "type": "string", + "description": "The account, masked to its last 4 characters." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference, as you sent it." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the check ran, in UTC." + } + } + }, + "BankAccountCheckError": { + "type": "object", + "required": [ + "detail" + ], + "properties": { + "detail": { + "type": "string", + "description": "What went wrong, in English." + }, + "code": { + "type": "string", + "description": "A stable code to branch on. Absent on `401` and `403`." + }, + "field": { + "type": "string", + "description": "`400` only: the body field to correct, as a dotted path, for example `account.sort_code`." + }, + "reason": { + "type": "string", + "enum": [ + "required", + "length", + "format", + "checksum", + "country_mismatch", + "rejected" + ], + "description": "`400` only: why the field was refused. `required`, `length`, `format`, `checksum` and `country_mismatch` (an IBAN of another country) are Didit's own checks; `rejected` means the bank refused the account as sent." + } + } + }, + "BankVerificationResult": { + "type": "object", + "description": "One Bank Verification step's result, for User and Business Verification. `method` says how the account was verified, and `none` that the person continued without a bank before any attempt was evaluated. `continued_without_bank` says the person continued without a bank, also after an evaluated attempt. See [Bank verification methods](/core-technology/bank-verification/verification-methods) and [When the person cannot verify their bank](/core-technology/bank-verification/fallbacks).", + "properties": { + "schema_version": { + "type": "integer", + "description": "Version of this result shape. `1` today." + }, + "bank_verification_id": { + "type": "string", + "format": "uuid", + "description": "The ID of this result." + }, + "node_id": { + "type": "string", + "description": "The workflow node this result belongs to." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Expired" + ], + "description": "The step's verdict, after its actions and status rules." + }, + "original_status": { + "type": "string", + "nullable": true, + "description": "The first verdict the step reached, before any later change such as a manual review." + }, + "method": { + "type": "string", + "nullable": true, + "enum": [ + "open_banking", + "account_lookup", + "microdeposits", + "statement", + "none" + ], + "description": "How the account was verified: `open_banking` (bank sign-in: the person signed in at their bank), `account_lookup` (account check: the person typed the account details and the bank confirmed the name), `microdeposits` or `statement` (a statement upload). `none` when the person continued without a bank before any attempt was evaluated, so no account was verified. After an evaluated attempt, it stays the method of that attempt. `null` until an account is evaluated, and when the workflow narrows the data it returns. Workflow rules read the same value as `bank.method`." + }, + "provider": { + "type": "string", + "nullable": true, + "description": "The key of the route that ran the connection. Read `method`, not this key, to tell how the account was verified." + }, + "credential_source": { + "type": "string", + "nullable": true, + "enum": [ + "native", + "byok" + ], + "description": "`native`: Didit's own route, billed to your Didit balance. `byok`: a marketplace provider you connected with your own credentials." + }, + "route": { + "type": "object", + "nullable": true, + "description": "The route chosen before consent. It never changes afterwards.", + "properties": { + "provider_key": { + "type": "string", + "nullable": true, + "description": "The route's key." + }, + "credential_source": { + "type": "string", + "enum": [ + "native", + "byok" + ] + }, + "is_fallback": { + "type": "boolean", + "description": "Whether Didit's own route stood in for a marketplace provider that could not connect." + }, + "provider_connection_id": { + "type": "string", + "nullable": true, + "description": "The marketplace connection used, for `byok`." + }, + "requested_scopes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The data the connection asked for, for example `ownership` or `balances`." + } + } + }, + "connection": { + "type": "object", + "nullable": true, + "description": "The latest attempt's connection. `null` before the first attempt.", + "properties": { + "status": { + "type": "string", + "enum": [ + "pending", + "connected", + "cancelled", + "timeout", + "failed", + "unsupported", + "revoked", + "expired" + ] + }, + "institution_id": { + "type": "string", + "nullable": true, + "description": "The bank's ID. `null` for an account check, where no bank is picked." + }, + "institution_name": { + "type": "string", + "nullable": true, + "description": "The bank's name. `null` for an account check." + }, + "institution_country": { + "type": "string", + "nullable": true, + "description": "The account's country, ISO 3166-1 alpha-3." + }, + "oauth": { + "type": "boolean", + "nullable": true, + "description": "Whether the bank sign-in ran over OAuth or app-to-app. `null` for an account check." + }, + "connected_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the connection succeeded." + }, + "consent_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the bank consent ends, for bank sign-in." + }, + "last_refreshed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the financial data was last read again." + }, + "attempts": { + "type": "integer", + "description": "The attempts the person spent on this step, every method together." + }, + "reason_code": { + "type": "string", + "nullable": true, + "description": "Why the connection is not simply `connected`, for example `USER_CANCELLED`, `INSTITUTION_DOWN`, `ACCOUNT_NOT_FOUND`, `REFERENCE_IDENTITY_MISSING` or `VERIFICATION_COMPLETED`." + } + } + }, + "account": { + "type": "object", + "nullable": true, + "description": "The evaluated account. `null` until an account is evaluated.", + "properties": { + "account_id_masked": { + "type": "string", + "nullable": true, + "description": "The account number, masked." + }, + "iban_masked": { + "type": "string", + "nullable": true, + "description": "The IBAN, masked." + }, + "sort_code": { + "type": "string", + "nullable": true, + "description": "The sort code, in the United Kingdom." + }, + "routing_number_masked": { + "type": "string", + "nullable": true, + "description": "The routing number, masked." + }, + "type": { + "type": "string", + "nullable": true, + "enum": [ + "checking", + "current", + "savings", + "business", + "other" + ] + }, + "subtype": { + "type": "string", + "nullable": true, + "description": "The bank's own account subtype." + }, + "currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 currency of the account." + }, + "holder_type": { + "type": "string", + "nullable": true, + "enum": [ + "person", + "business", + "unknown" + ] + }, + "holders_count": { + "type": "integer", + "nullable": true + }, + "is_joint": { + "type": "boolean", + "nullable": true + }, + "is_business": { + "type": "boolean", + "nullable": true + } + } + }, + "ownership": { + "type": "object", + "nullable": true, + "description": "How the account holder compared with the verified identity. `null` until an account is evaluated.", + "properties": { + "result": { + "type": "string", + "enum": [ + "match", + "partial_match", + "no_match", + "unknown", + "not_verifiable" + ], + "description": "`partial_match` is a close match. `not_verifiable`: the bank answered with no holder to compare. `unknown`: the evidence could not be read, or there was no identity to compare." + }, + "reference_identity_source": { + "type": "string", + "nullable": true, + "description": "The step whose identity the holder was compared against: `kyc`, `database_validation`, `expected_details`, `questionnaire`, `document_ai` or `kyb`." + }, + "name_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "Didit's holder name score." + }, + "provider_name_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The bank's own holder name score, when it returned one." + }, + "address_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The holder address score, when address matching ran." + }, + "email_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The holder email score, when email matching ran." + }, + "phone_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The holder phone score, when phone matching ran." + }, + "dob_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The holder date of birth score, when the bank shared one." + }, + "close_match": { + "type": "boolean", + "nullable": true, + "description": "Whether the name landed in the close-match band." + }, + "holder_confirmation": { + "type": "string", + "nullable": true, + "enum": [ + "pending", + "confirmed", + "rejected" + ], + "description": "The person's answer when asked to confirm a close-match name. `null` when never asked." + }, + "method": { + "type": "string", + "nullable": true, + "enum": [ + "exact", + "token_set", + "nickname", + "transliteration", + "fuzzy", + "provider_only", + "microdeposits" + ], + "description": "Which name comparison produced the score. This is not the verification method: read the top-level `method` for that." + }, + "matched_holder_index": { + "type": "integer", + "nullable": true + }, + "holders": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Each holder the bank listed, with its scores." + }, + "owner_identity_available": { + "type": "boolean", + "nullable": true + }, + "evidence_freshness": { + "type": "string", + "nullable": true, + "enum": [ + "live", + "cached", + "refreshed" + ] + }, + "evaluated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the verdict was computed." + } + } + }, + "financial_data": { + "type": "object", + "description": "The financial data the step read. Only bank sign-in reads it.", + "properties": { + "balances": { + "type": "object", + "nullable": true, + "description": "With the `balances` scope." + }, + "transactions": { + "type": "object", + "nullable": true, + "description": "With the `transactions` scope." + }, + "income": { + "type": "object", + "nullable": true, + "description": "With the `income` scope." + }, + "categorization": { + "type": "object", + "nullable": true, + "description": "Didit's categorization of the transactions read. `null` without them." + }, + "not_collected": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "balances", + "income", + "transactions" + ] + }, + "description": "The data the step asked for that the method which verified the account does not collect. Balances, transactions and income come from bank sign-in only, so an account verified by account check lists them here. Never a failure. Empty otherwise." + } + } + }, + "billing": { + "type": "object", + "nullable": true, + "properties": { + "route": { + "type": "string", + "enum": [ + "native", + "byok" + ] + }, + "billed": { + "type": "boolean" + }, + "marketplace_fee": { + "type": "string", + "nullable": true, + "description": "The marketplace platform fee charged, for `byok`." + }, + "usage_type": { + "type": "string", + "nullable": true, + "description": "The usage line the charge was booked on: `bank_verification` for a bank sign-in or an account check, `bank_statement_upload` for a statement upload with no charged bank attempt before it, and `bank_byok_fee` for the marketplace fee on your own provider. `null` when nothing was billed." + } + } + }, + "reference_identity": { + "type": "object", + "nullable": true, + "description": "The identity the holder was compared against, with its source." + }, + "signals": { + "type": "object", + "nullable": true, + "description": "Account signals read from the transaction history, when the step read one." + }, + "connection_abandoned": { + "type": "boolean", + "nullable": true, + "description": "Whether the step ended without a match after the person left at their bank." + }, + "not_covered": { + "type": "boolean", + "nullable": true, + "description": "Whether the person's bank or country could not be reached on this step. Also `true` when the person continued without a bank because no method could verify their country or bank." + }, + "continued_without_bank": { + "type": "boolean", + "description": "Whether the person left the step with **Continue without your bank**. `continuation` then says why. `method` is `none` when no attempt was evaluated before they continued. After an evaluated attempt, such as a holder that does not match, `method` stays the method of that attempt and its verdict and reason codes stand. Returned even when the workflow narrows the data it returns." + }, + "continuation": { + "type": "object", + "nullable": true, + "description": "How the person continued without a bank. `null` when they did not. When the workflow narrows the data it returns, `reason` is still returned and every other key is `null`.", + "properties": { + "reason": { + "type": "string", + "enum": [ + "no_route", + "institution_unsupported", + "attempts_exhausted", + "person_choice" + ], + "description": "Why, as Didit worked it out when the person continued: `no_route` (no bank sign-in or account check serves their country), `institution_unsupported` (the bank they picked cannot be used on this step), `attempts_exhausted` (every attempt was already spent and none was in progress), or `person_choice` (a way to verify was there and they chose to continue, also when their bank could not be reached right now). `no_route` and `institution_unsupported` follow `bank_unsupported_action`, `attempts_exhausted` follows `bank_attempts_exhausted_action`, and `person_choice` follows `bank_skipped_action`, unless an evaluated attempt already decided the step." + }, + "country": { + "type": "string", + "nullable": true, + "description": "The country the person was verifying in, ISO 3166-1 alpha-3. `null` when they continued before picking one." + }, + "offered": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "statement", + "microdeposits" + ] + }, + "nullable": true, + "description": "The other ways to verify that country offered the person when they continued, in the order it offered them: `statement` (a statement upload), then `microdeposits`. Empty when it offered none." + }, + "institution_id": { + "type": "string", + "nullable": true, + "description": "For `institution_unsupported`, the ID of the bank the person could not use: the bank they picked, or the bank their last attempt ended on. Didit takes it from its own list of banks, never from the person's device, so a bank that list does not have is `null`. `null` for every other reason." + }, + "institution_name": { + "type": "string", + "nullable": true, + "description": "For `institution_unsupported`, the name of that bank, from the same list as `institution_id`. `null` when that list does not have the bank, and for every other reason." + }, + "continued_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the person continued." + } + } + }, + "evidence_purged_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the bank evidence was deleted under `bank_evidence_retention_days`. `null` otherwise." + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Informational codes, for example `holder_declared`. A warning never changes the status on its own." + }, + "reason_codes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Why the verdict came out the way it did, for example `OWNER_NAME_PARTIAL`. Several may apply. `NOT_COVERED`: the person continued without a bank because no method could verify their country or bank. `SKIPPED`: the person chose to continue without a bank while a way to verify was there. Neither is added when an evaluated attempt already decided the step." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BankAccountCheckPersonHolder": { + "type": "object", + "description": "The person who should hold the account.", + "required": [ + "full_name" + ], + "properties": { + "full_name": { + "type": "string", + "maxLength": 255, + "description": "The full name of the person who should hold the account.", + "example": "Maria Oliveira" + } + } + }, + "BankAccountCheckBusinessHolder": { + "type": "object", + "description": "The company that should hold the account. Checks a business account.", + "required": [ + "business_name" + ], + "properties": { + "business_name": { + "type": "string", + "maxLength": 255, + "description": "The name of the company that should hold the account. Checks a business account." + } + } + }, + "BankVerificationPricing": { + "type": "object", + "description": "Public Bank Verification list prices: the statement upload, the own provider key fee and each country's methods.", + "required": [ + "currency", + "statement_upload", + "own_provider_fee_usd", + "countries" + ], + "properties": { + "currency": { + "type": "string", + "enum": [ + "USD" + ], + "description": "Currency of every price in the response.", + "example": "USD" + }, + "statement_upload": { + "$ref": "#/components/schemas/BankStatementUploadPricing" + }, + "own_provider_fee_usd": { + "type": "string", + "description": "Fee for each billable ownership check on a route that uses your own provider contract instead of Didit's, charged whatever the result. A cancelled, timed out or abandoned connection is not billed. Decimal string with 4 decimals, in USD.", + "example": "0.0200" + }, + "countries": { + "type": "object", + "description": "Every country, keyed by ISO 3166-1 alpha-3 code.", + "additionalProperties": { + "$ref": "#/components/schemas/BankVerificationPricingCountry" + } + } + } + }, + "BankStatementUploadPricing": { + "type": "object", + "description": "How the bank statement upload is priced.", + "required": [ + "price_usd", + "included_after_charged_bank_attempt", + "standalone_available" + ], + "properties": { + "price_usd": { + "type": "string", + "description": "Price of a statement upload after a charged bank attempt. Decimal string with 4 decimals, in USD.", + "example": "0.0000" + }, + "included_after_charged_bank_attempt": { + "type": "boolean", + "description": "`true` when the upload is included at no extra charge after a charged bank attempt in the same session.", + "example": true + }, + "standalone_available": { + "type": "boolean", + "description": "`true` when a statement upload can run on its own, with no bank attempt first. `false` in production; sandbox sessions can test it without billing.", + "example": false + } + } + }, + "BankVerificationPricingCountry": { + "type": "object", + "description": "One country's Bank Verification methods and their list prices.", + "required": [ + "name", + "methods" + ], + "properties": { + "name": { + "type": "string", + "description": "Country name in English.", + "example": "United Kingdom" + }, + "methods": { + "$ref": "#/components/schemas/BankVerificationPricingMethods" + } + } + }, + "BankVerificationPricingMethods": { + "type": "object", + "description": "The two Bank Verification methods in a country.", + "required": [ + "open_banking", + "account_lookup" + ], + "properties": { + "open_banking": { + "$ref": "#/components/schemas/BankSignInPricing" + }, + "account_lookup": { + "$ref": "#/components/schemas/BankAccountCheckPricing" + } + } + }, + "BankSignInPricing": { + "type": "object", + "description": "Bank sign-in: the person signs in to their bank. Priced by market, the same for every bank in it.", + "required": [ + "available", + "market", + "price_usd", + "add_ons" + ], + "properties": { + "available": { + "type": "boolean", + "description": "`true` only where Didit serves bank sign-in in this country in production.", + "example": true + }, + "market": { + "type": "string", + "nullable": true, + "description": "The market whose price applies. `null` when not available.", + "example": "UK and EEA" + }, + "price_usd": { + "type": "string", + "nullable": true, + "description": "List price of one bank sign-in. `null` when not available. Decimal string with 4 decimals, in USD.", + "example": "0.7900" + }, + "add_ons": { + "type": "object", + "description": "Add-ons priced in this country, each a decimal string with 4 decimals in USD, added to the bank sign-in price. Only the add-ons the country offers are listed; empty when bank sign-in is not available.", + "properties": { + "balance": { + "type": "string", + "description": "Account balance add-on.", + "example": "0.3500" + }, + "transactions": { + "type": "string", + "description": "Transactions add-on.", + "example": "1.3000" + } + } + } + } + }, + "BankAccountCheckPricing": { + "type": "object", + "description": "Account check: the person types their account number and Didit checks it with the bank. Priced per country.", + "required": [ + "available", + "price_usd" + ], + "properties": { + "available": { + "type": "boolean", + "description": "`true` only where Didit serves the account check in this country in production.", + "example": true + }, + "price_usd": { + "type": "string", + "nullable": true, + "description": "List price of one account check the bank answers. `null` when not available. Decimal string with 4 decimals, in USD.", + "example": "0.4000" + } + } + }, + "ImportProvider": { + "type": "object", + "properties": { + "key": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ] + }, + "name": { + "type": "string" + }, + "mode": { + "type": "string", + "enum": [ + "api_list", + "api_ids", + "export_file" + ], + "description": "`api_list`: Didit lists records with the key. `api_ids`: upload the record IDs from an export. `export_file`: upload an export in Didit's format." + }, + "record_noun": { + "type": "string" + }, + "credential_fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "label": { + "type": "string" + }, + "secret": { + "type": "boolean" + } + } + } + }, + "regions": { + "type": "array", + "items": { + "type": "string" + } + }, + "required_permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "docs_url": { + "type": "string", + "nullable": true + }, + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + } + } + } + }, + "ImportJobSummary": { + "type": "object", + "description": "One import job of any kind, in the shape the console's Import jobs drawer reads.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "kind": { + "type": "string", + "enum": [ + "sessions", + "businesses", + "users", + "status_rules", + "transactions" + ] + }, + "source": { + "type": "string", + "enum": [ + "file", + "api", + "provider" + ] + }, + "provider": { + "type": "string", + "nullable": true + }, + "provider_name": { + "type": "string", + "nullable": true + }, + "filename": { + "type": "string", + "nullable": true + }, + "state": { + "type": "string", + "enum": [ + "ready", + "queued", + "running", + "paused", + "completed", + "errors", + "failed", + "cancelled" + ], + "description": "The job's state as the console shows it; `errors` is a completed job with rejected rows." + }, + "status": { + "type": "string" + }, + "is_dry_run": { + "type": "boolean" + }, + "credential_hint": { + "type": "string", + "nullable": true + }, + "resumes_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_error": { + "type": "string", + "nullable": true + }, + "started_by": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + } + } + }, + "progress": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int64" + }, + "processed": { + "type": "integer", + "format": "int64" + }, + "imported": { + "type": "integer", + "format": "int64" + }, + "updated": { + "type": "integer", + "format": "int64" + }, + "already_in_didit": { + "type": "integer", + "format": "int64" + }, + "failed": { + "type": "integer", + "format": "int64" + }, + "not_finished": { + "type": "integer", + "format": "int64" + }, + "same_person": { + "type": "integer", + "format": "int64" + } + } + }, + "face_templates": { + "type": "object", + "nullable": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "built": { + "type": "integer", + "format": "int64" + }, + "duplicates": { + "type": "integer", + "format": "int64" + }, + "blocklisted": { + "type": "integer", + "format": "int64" + } + } + }, + "eta_seconds": { + "type": "integer", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "started_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "FiledRegulatoryReport": { + "type": "object", + "description": "A finalized regulatory report that prevents the requested deletion.", + "required": [ + "uuid", + "report_number", + "report_type", + "case_uuid" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Report UUID." + }, + "report_number": { + "type": "string", + "description": "Report number shown in the Console (for example, SAR-0001)." + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ], + "description": "Regulatory report type." + }, + "case_uuid": { + "type": "string", + "format": "uuid", + "description": "Case the report belongs to." + } + } + }, + "FiledReportTransaction": { + "type": "object", + "description": "A transaction that cannot be deleted because a finalized regulatory report lists it.", + "required": [ + "uuid", + "transaction_id", + "reports" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit transaction UUID." + }, + "transaction_id": { + "type": "string", + "description": "The transaction identifier supplied when the transaction was created." + }, + "reports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FiledRegulatoryReport" + }, + "description": "Finalized reports that list this transaction." + } + } + }, + "InFiledReportError": { + "type": "object", + "description": "The deletion is refused because the selected data is in a finalized regulatory report. Nothing is deleted. Delete the report, then repeat the request.", + "required": [ + "detail", + "code" + ], + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "in_filed_report" + ], + "description": "Stable error code for a deletion blocked by a finalized regulatory report." + }, + "reports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FiledRegulatoryReport" + }, + "description": "Reports that block a person, company, case or session deletion." + }, + "transactions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FiledReportTransaction" + }, + "description": "Transactions that block a transaction deletion, each with the reports that list it." + } + } + }, + "SubjectDeletionInProgressError": { + "type": "object", + "description": "Another request is still deleting this person or company. This request deleted nothing and can be repeated once that deletion has finished; a repeat then deletes whatever is still selected.", + "required": [ + "detail", + "code" + ], + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "subject_deletion_in_progress" + ], + "description": "Stable error code for a person or company another request is still deleting." + } + } + }, + "TravelRuleNetworkParticipantSummary": { + "type": "object", + "nullable": true, + "description": "This application's own participant on the network, or null when it has none.", + "properties": { + "mode": { + "type": "string", + "enum": [ + "platform", + "own" + ], + "description": "platform: carried by Didit's membership; own: onboarded under the application's own membership." + }, + "status": { + "type": "string", + "enum": [ + "not_requested", + "questionnaire_pending", + "submitted_to_network", + "active", + "suspended", + "rejected" + ], + "description": "not_requested: never requested, or the network refused the first request (see last_error); questionnaire_pending: waiting for the due-diligence questionnaire to be approved; submitted_to_network: handed to the network's onboarding; active: the network gave this application its own participant code; suspended / rejected: Didit suspended or rejected it." + }, + "participant_code": { + "type": "string", + "description": "The code the network gave this application (for GTR, its VASP code). Outbound messages carry this code, never Didit's own." + }, + "requested_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "activated_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_error": { + "type": "string", + "description": "The network's answer when it refused the registration; empty otherwise." + } + } + }, + "TravelRuleNetworkState": { + "type": "object", + "description": "How Travel Rule messages on one network would be sent for this application, and why they cannot be. Configuration status, not a live health or delivery check.", + "properties": { + "rail": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA" + ] + }, + "mode": { + "type": "string", + "enum": [ + "own", + "didit", + "off", + "unavailable" + ], + "description": "own: the application's connected membership is used; didit: Didit's membership carries the application as its own active participant; off: platform routing disabled in disabled_networks with no own membership; unavailable: nothing can send on this network yet (see state and reason)." + }, + "state": { + "type": "string", + "enum": [ + "own_membership", + "live_through_didit", + "off", + "didit_onboarding", + "questionnaire_needed", + "questionnaire_in_review", + "request_access", + "request_sent", + "unavailable", + "error" + ], + "description": "own_membership: your connected membership sends; live_through_didit: your own participant on Didit's membership sends; off: you switched platform routing off; didit_onboarding: Didit's own membership of this network is not live yet, so it cannot carry anyone; questionnaire_needed / questionnaire_in_review: the due-diligence questionnaire must be submitted or is with Didit; request_access: the questionnaire is approved, request the network; request_sent: your participant is being onboarded; unavailable: see reason; error: see reason." + }, + "reason": { + "type": "string", + "enum": [ + "own_membership_connected", + "platform_network_switched_off", + "own_membership_only", + "sandbox_application", + "didit_membership_not_live", + "participant_suspended", + "participant_rejected", + "questionnaire_not_submitted", + "questionnaire_submitted", + "questionnaire_changes_requested", + "questionnaire_rejected", + "participant_not_requested", + "participant_active", + "participant_key_missing", + "participant_key_unresolved", + "provisioning_failed", + "questionnaire_pending", + "submitted_to_network" + ], + "description": "Machine-readable detail of state. own_membership_only: Didit does not carry applications on this network (TRUST), connect your own membership; sandbox_application: sandbox transfers never reach a network; provisioning_failed: the network refused the registration, the participant's last_error says why and requesting the network again retries." + }, + "platform_available": { + "type": "boolean", + "description": "Whether Didit's own membership of this network is configured; not a live health check." + }, + "connected": { + "type": "boolean", + "description": "Whether a connected customer membership exists. Credentials are validated locally; network validation occurs on a live exchange." + }, + "participant": { + "$ref": "#/components/schemas/TravelRuleNetworkParticipantSummary" + }, + "vasps_reachable": { + "type": "integer", + "description": "Directory VASPs reachable on this network." + }, + "routed_30d": { + "type": "integer", + "description": "This application's transfers carried on this network in the last 30 days." + } + } + }, + "TravelRuleNetworkQuestionnaireAnswers": { + "type": "object", + "description": "Didit's network due-diligence questionnaire. Saves are partial: a save keeps every answer it does not name. Submitting requires every field except lei and data_protection_contact_email, and the three attestations true.", + "properties": { + "legal_name": { + "type": "string", + "maxLength": 255 + }, + "registration_number": { + "type": "string", + "maxLength": 255 + }, + "incorporation_country": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code." + }, + "registered_address": { + "type": "string", + "maxLength": 1000 + }, + "website": { + "type": "string", + "format": "uri", + "maxLength": 500 + }, + "lei": { + "type": "string", + "maxLength": 20, + "description": "Optional. A valid ISO 17442 LEI when given." + }, + "licences": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "regulator", + "jurisdiction", + "licence_type", + "licence_number" + ], + "properties": { + "regulator": { + "type": "string", + "maxLength": 255 + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code." + }, + "licence_type": { + "type": "string", + "maxLength": 255 + }, + "licence_number": { + "type": "string", + "maxLength": 255 + } + } + }, + "description": "Every licence or registration the VASP holds." + }, + "operating_jurisdictions": { + "type": "array", + "minItems": 1, + "maxItems": 250, + "items": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code." + } + }, + "compliance_officer_name": { + "type": "string", + "maxLength": 255 + }, + "compliance_officer_email": { + "type": "string", + "format": "email" + }, + "aml_ctf_policy_attested": { + "type": "boolean", + "description": "Must be true to submit." + }, + "sanctions_screening_attested": { + "type": "boolean", + "description": "Must be true to submit." + }, + "data_protection_attested": { + "type": "boolean", + "description": "Must be true to submit." + }, + "data_protection_contact_email": { + "type": "string", + "format": "email", + "description": "Optional." + }, + "expected_monthly_transfers": { + "type": "integer", + "minimum": 0 + }, + "expected_monthly_volume_usd": { + "type": "string", + "format": "decimal", + "description": "Expected monthly volume in USD, up to 2 decimal places. Returned as a string." + }, + "assets": { + "type": "array", + "minItems": 1, + "maxItems": 200, + "items": { + "type": "string", + "maxLength": 32 + }, + "description": "Asset tickers, stored upper-case, sorted and de-duplicated." + } + } + }, + "TravelRuleNetworkQuestionnaire": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "draft", + "submitted", + "changes_requested", + "approved", + "rejected" + ], + "description": "Answers can be saved while draft or changes_requested; submitted, approved and rejected are Didit's to move." + }, + "answers": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaireAnswers" + }, + "submitted_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reviewed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "review_note": { + "type": "string", + "description": "Didit's note on its last decision, for example what to change." + }, + "history": { + "type": "array", + "readOnly": true, + "description": "Every status change, oldest first.", + "items": { + "type": "object", + "properties": { + "at": { + "type": "string", + "format": "date-time" + }, + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "note": { + "type": "string", + "description": "Review note or the network's answer, when there is one." + } + } + } + }, + "updated_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "TravelRuleNetworkParticipant": { + "type": "object", + "description": "This application's own participant on one network.", + "properties": { + "network": { + "type": "string", + "enum": [ + "gtr", + "trust", + "verifyvasp", + "sygna" + ] + }, + "rail": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA" + ] + }, + "mode": { + "type": "string", + "enum": [ + "platform", + "own" + ] + }, + "status": { + "type": "string", + "enum": [ + "not_requested", + "questionnaire_pending", + "submitted_to_network", + "active", + "suspended", + "rejected" + ], + "description": "not_requested: never requested, or the network refused the first request (see last_error); questionnaire_pending: waiting for the due-diligence questionnaire to be approved; submitted_to_network: handed to the network's onboarding; active: the network gave this application its own participant code; suspended / rejected: Didit suspended or rejected it." + }, + "participant_code": { + "type": "string" + }, + "requested_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "submitted_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "activated_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "suspended_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_error": { + "type": "string", + "description": "The network's answer when it refused the registration; empty otherwise." + }, + "history": { + "type": "array", + "readOnly": true, + "description": "Every status change, oldest first.", + "items": { + "type": "object", + "properties": { + "at": { + "type": "string", + "format": "date-time" + }, + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "note": { + "type": "string", + "description": "Review note or the network's answer, when there is one." + } + } + } + } + } + }, + "TravelRuleNetworksOverview": { + "type": "object", + "properties": { + "platform_networks_enabled": { + "type": "boolean", + "description": "Whether network access through Didit's membership is open. While false, questionnaire saves, submissions and network requests answer 403." + }, + "questionnaire": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaire" + }, + "participants": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleNetworkParticipant" + } + }, + "networks": { + "type": "object", + "readOnly": true, + "description": "Per network, how this application would send and why it cannot. No network, including GTR, is included by default: a network is reachable through your own connected membership, or through Didit's membership once Didit's membership of that network is live and your own participant on it is active. Not a live health or delivery check.", + "properties": { + "gtr": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "trust": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "verifyvasp": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "sygna": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + } + } + } + } + }, + "AddressValidationResult": { + "type": "object", + "description": "The result of one Address Validation: whether the address exists, and its normalised form. It confirms the address only; it never links the address to a person.", + "required": [ + "status", + "verification_level", + "address", + "formatted_address", + "latitude", + "longitude" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "verified", + "partially_verified", + "not_verified", + "unavailable" + ], + "description": "`verified`: the address was confirmed down to the building (or the flat or PO box). `partially_verified`: it was confirmed only to the street, or to the most precise level the postal reference data holds for that place. `not_verified`: only the region or the city matched, or nothing did. `unavailable`: the validation could not run (for example the reference service did not answer); it is not charged and never affects a verification." + }, + "verification_level": { + "type": "string", + "nullable": true, + "enum": [ + "administrative_area", + "locality", + "thoroughfare", + "premise", + "delivery_point", + null + ], + "description": "The most precise level the address was matched to. `null` when nothing matched or `status` is `unavailable`." + }, + "address": { + "type": "object", + "nullable": true, + "description": "The normalised address. `null` when `status` is `unavailable`.", + "properties": { + "address_line_1": { + "type": "string", + "nullable": true, + "description": "First delivery line, written the way the country writes it.", + "example": "1600 Amphitheatre Pkwy" + }, + "address_line_2": { + "type": "string", + "nullable": true, + "description": "Second delivery line (a flat, building or neighbourhood), when the address has one." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Mountain View" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State, province or region.", + "example": "CA" + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "94043-1351" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code.", + "example": "US" + } + } + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "The normalised address on one line.", + "example": "1600 Amphitheatre Pkwy, Mountain View CA 94043-1351" + }, + "latitude": { + "type": "number", + "nullable": true, + "example": 37.422022 + }, + "longitude": { + "type": "number", + "nullable": true, + "example": -122.08429 + } + } + }, + "AddressValidationRequest": { + "type": "object", + "required": [ + "country" + ], + "description": "One address: on one line (`address`) or as structured fields with at least `address_line_1`, never both.", + "properties": { + "address": { + "type": "string", + "maxLength": 500, + "description": "The whole address on one line.", + "example": "Paseo de la Castellana 259, 28046 Madrid" + }, + "address_line_1": { + "type": "string", + "maxLength": 255, + "description": "Street and number.", + "example": "1600 Amphitheatre Pkwy" + }, + "address_line_2": { + "type": "string", + "maxLength": 255, + "description": "Apartment, suite, floor or building." + }, + "city": { + "type": "string", + "maxLength": 128, + "description": "City or locality.", + "example": "Mountain View" + }, + "region": { + "type": "string", + "maxLength": 128, + "description": "State, province or region.", + "example": "CA" + }, + "postal_code": { + "type": "string", + "maxLength": 32, + "example": "94043" + }, + "country": { + "type": "string", + "maxLength": 3, + "description": "ISO 3166-1 alpha-2 or alpha-3 code of the address's country.", + "example": "US" + } + } + }, + "AddressValidationResponse": { + "type": "object", + "required": [ + "request_id", + "address_validation", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Identifier of this request; quote it to support. No session is created." + }, + "address_validation": { + "$ref": "#/components/schemas/AddressValidationResult" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleWalletProofFailureReason": { + "type": "string", + "enum": [ + "", + "SIGNATURE_INVALID", + "SIGNATURE_UNSUPPORTED_SCHEME", + "DEPOSIT_NOT_FOUND", + "DEPOSIT_UNCONFIRMED", + "DEPOSIT_LOOKUP_FAILED", + "DEPOSIT_MISMATCH", + "DEPOSIT_STALE", + "TXID_REUSED", + "CHAIN_UNSUPPORTED", + "WINDOW_EXPIRED", + "OWNERSHIP_CHANGED", + "METHOD_SWITCHED", + "REVIEW_REJECTED", + "ABANDONED" + ], + "description": "Why a wallet-ownership proof, or its last attempt, fell short of VERIFIED. Empty when nothing failed. A PENDING proof can carry one: the last attempt failed and the end user can try again. DEPOSIT_MISMATCH, DEPOSIT_STALE, TXID_REUSED and REVIEW_REJECTED close the proof as REJECTED. SIGNATURE_INVALID: the signature does not match the wallet and challenge. SIGNATURE_UNSUPPORTED_SCHEME: the wallet signed in a format Didit does not verify. DEPOSIT_NOT_FOUND, DEPOSIT_UNCONFIRMED, DEPOSIT_LOOKUP_FAILED: the test deposit is not readable or not confirmed yet; Didit keeps checking it until the test window closes. DEPOSIT_MISMATCH: the transaction does not send the expected amount from the wallet to the deposit address. DEPOSIT_STALE: the transaction predates the challenge. TXID_REUSED: the transaction already proved another wallet. CHAIN_UNSUPPORTED: the method is not available on this chain. WINDOW_EXPIRED: the test window closed. OWNERSHIP_CHANGED: the wallet's holder changed, or the wallet was deleted from your address book, after the widget session was minted. METHOD_SWITCHED: the end user picked another method before finishing this one. REVIEW_REJECTED: your reviewer rejected the screenshot. ABANDONED: the widget session lapsed before the end user finished." + }, + "TravelRuleWidgetProofError": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable message in English." + }, + "code": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleWalletProofFailureReason" + } + ], + "description": "Machine-readable reason, so a client can show its own translated message." + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ], + "description": "Present when the proof is closed: REJECTED by the deposit just submitted (400), or REJECTED or EXPIRED when evidence is sent to a proof that is already closed (409)." + }, + "proof_id": { + "type": "string", + "format": "uuid", + "description": "Present with status: the closed proof." + }, + "method": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SATOSHI_TEST", + "SCREENSHOT", + "SELF_DECLARATION" + ], + "description": "Present with status: the closed proof's method." + }, + "max_attempts": { + "type": "integer", + "description": "Present with status: rejected wallet proofs this session allows." + }, + "attempts_used": { + "type": "integer", + "description": "Present with status: rejected proofs so far." + }, + "attempts_remaining": { + "type": "integer", + "description": "Present with status: when 0, starting another proof answers 409 and you mint a new widget session." + } + } + }, + "TravelRuleWidgetProofCreate": { + "type": "object", + "required": [ + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SATOSHI_TEST", + "SCREENSHOT", + "SELF_DECLARATION" + ], + "description": "The proof method the end user picked. It must be one of the context's allowed_methods." + }, + "wallet_address": { + "type": "string", + "maxLength": 128, + "description": "Defaults to the session's wallet_address. Must match the transfer's wallet when the session is linked to a transfer." + }, + "chain": { + "type": "string", + "maxLength": 32, + "description": "Defaults to the session's chain." + } + } + }, + "TravelRuleWidgetProofVerifyResult": { + "type": "object", + "properties": { + "proof_id": { + "type": "string", + "format": "uuid" + }, + "method": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SATOSHI_TEST", + "SCREENSHOT", + "SELF_DECLARATION" + ] + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ] + }, + "is_ownership_verified": { + "type": "boolean", + "description": "Present when the proof is VERIFIED: the address book entry is now ownership-verified." + }, + "transfer_status": { + "type": "string", + "nullable": true, + "description": "Present when the proof is VERIFIED: the linked transfer's status, null without one." + }, + "detail": { + "type": "string", + "description": "Present while the proof stays PENDING: a submitted screenshot awaiting review, or a test deposit that is not confirmed yet." + }, + "code": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleWalletProofFailureReason" + } + ], + "description": "Present on a pending test deposit: why it is not confirmed yet." + } + } + }, + "TravelRuleWalletProofReview": { + "type": "object", + "properties": { + "proof_id": { + "type": "string", + "format": "uuid" + }, + "method": { + "type": "string", + "enum": [ + "SCREENSHOT" + ] + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ] + }, + "wallet_address": { + "type": "string" + }, + "chain": { + "type": "string", + "description": "Canonical chain identifier, e.g. ETH, BTC, SOL, TRX." + }, + "holder_name": { + "type": "string" + }, + "is_ownership_verified": { + "type": "boolean", + "description": "Whether the address book entry is ownership-verified, by this proof or another one." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The transaction whose Travel Rule transfer the widget session was minted for. Null for a session minted without a transaction_id." + }, + "file_name": { + "type": "string", + "nullable": true + }, + "content_type": { + "type": "string", + "nullable": true + }, + "screenshot_url": { + "type": "string", + "nullable": true, + "description": "Time-limited link to the uploaded screenshot. List the queue again for a fresh link." + }, + "attempts": { + "type": "integer", + "description": "How many files the end user uploaded for this proof." + }, + "failure_reason": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleWalletProofFailureReason" + } + ], + "description": "REVIEW_REJECTED after a rejection, OWNERSHIP_CHANGED when the wallet changed holder or was deleted before a decision. Empty otherwise." + }, + "method_chosen_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the end user picked the screenshot method." + }, + "decided_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "decided_by": { + "type": "string", + "description": "Who closed the proof: review: for a console reviewer, review for an API-key decision, widget or sweeper when Didit closed it automatically. Empty while PENDING." + }, + "review_note": { + "type": "string", + "description": "The note sent with the decision. Empty when none." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleWalletProofDecision": { + "type": "object", + "required": [ + "decision" + ], + "properties": { + "decision": { + "type": "string", + "enum": [ + "approve", + "reject" + ] + }, + "note": { + "type": "string", + "maxLength": 512, + "description": "Optional. Kept with the proof and returned as review_note." + } + } + }, + "DigitalIdWalletApiWalletDigitalCredentialsRequest": { + "type": "object", + "properties": { + "protocol": { + "type": "string", + "description": "openid4vp-v1-signed." + }, + "request": { + "type": "object", + "additionalProperties": {}, + "description": "Pass as is: navigator.credentials.get({digital: request}) in the browser, or JSON.stringify(request) as GetDigitalCredentialOption's requestJson on Android. Signed by Didit for your application." + } + }, + "required": [ + "protocol", + "request" + ] + }, + "DigitalIdWalletApiGoogleWalletWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Google Wallet verification (USA, GBR, BRA, SGP, TWN).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiGoogleWalletWalletVerificationStartCountryEnum" + } + ], + "description": "ISO 3166-1 alpha-3 country of the Google Wallet identity.\n\n* `USA` - USA\n* `GBR` - GBR\n* `BRA` - BRA\n* `SGP` - SGP\n* `TWN` - TWN" + }, + "origin": { + "type": "string", + "minLength": 1, + "description": "Where your page or app will ask the wallet: the https origin of your page (its host must be in the application's allowed_return_hosts, or be its white-label domain), e.g. https://app.example.com, or android:apk-key-hash:. The wallet refuses the request anywhere else.", + "maxLength": 256 + } + }, + "required": [ + "country", + "origin" + ] + }, + "DigitalIdWalletApiGoogleWalletWalletVerificationStartCountryEnum": { + "enum": [ + "USA", + "GBR", + "BRA", + "SGP", + "TWN" + ], + "type": "string", + "description": "* `USA` - USA\n* `GBR` - GBR\n* `BRA` - BRA\n* `SGP` - SGP\n* `TWN` - TWN" + }, + "DigitalIdWalletApiWalletDigitalCredentialResponseRequest": { + "type": "object", + "description": "What the wallet answered your page or app, for a ``request_digital_credential`` verification.", + "properties": { + "response": { + "type": "object", + "additionalProperties": {}, + "description": "Exactly what the wallet returned: {protocol, data} of the DigitalCredential from navigator.credentials.get, or the parsed credentialJson of Android's DigitalCredential. Encrypted to Didit; do not alter it." + } + }, + "required": [ + "response" + ] + }, + "SessionFilesExport": { + "type": "object", + "description": "One ZIP with every file of a session, in original quality.", + "required": [ + "export_id", + "session_id", + "status", + "file_count", + "size_bytes", + "download_url", + "expires_at", + "error", + "created_at", + "completed_at" + ], + "properties": { + "export_id": { + "type": "string", + "format": "uuid", + "description": "ID of the export. Pass it to `GET /v3/session/{sessionId}/files-export/{exportId}/`." + }, + "session_id": { + "type": "string", + "format": "uuid", + "description": "The session the files are from." + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "READY", + "FAILED", + "EXPIRED" + ], + "description": "`PENDING` and `PROCESSING` while the ZIP is prepared, `READY` once it can be downloaded, `FAILED` when it could not be prepared (request a new export), `EXPIRED` once the ZIP was deleted." + }, + "file_count": { + "type": "integer", + "nullable": true, + "description": "Files in the ZIP. Set only while `status` is `READY`." + }, + "size_bytes": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "Size of the ZIP in bytes. Set only while `status` is `READY`." + }, + "download_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Signed URL of the ZIP, valid for a short time. Set only while `status` is `READY`; read the export again for a new one." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the ZIP is deleted, 24 hours after it was ready. Set only while `status` is `READY`." + }, + "error": { + "type": "string", + "nullable": true, + "description": "Why the export failed, when `status` is `FAILED`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the export was requested." + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the ZIP was ready or the export failed." + } + } + }, + "TransactionRuleSuggestionsCreatedEvent": { + "description": "A transaction rule suggestion run proposed new rule changes for your application. Sent when the rule-suggestion settings have webhook notifications on. There is no top-level `status`.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "transaction_rule_suggestions.created" + ], + "description": "Always `transaction_rule_suggestions.created`." + }, + "run_id": { + "type": "string", + "format": "uuid", + "description": "The suggestion run that produced the new suggestions." + }, + "open_count": { + "type": "integer", + "description": "Number of suggestions that are still open after this run, new ones included." + }, + "suggestions": { + "type": "array", + "description": "The suggestions this run created.", + "items": { + "type": "object", + "required": [ + "suggestion_id", + "kind", + "title", + "rule_id", + "expected_impact" + ], + "properties": { + "suggestion_id": { + "type": "string", + "format": "uuid" + }, + "kind": { + "type": "string", + "enum": [ + "ADD", + "TWEAK", + "DISABLE" + ], + "description": "`ADD` proposes a new rule, `TWEAK` changes an existing one, `DISABLE` turns one off." + }, + "title": { + "type": "string" + }, + "rule_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The rule a `TWEAK` or `DISABLE` suggestion applies to; `null` for `ADD`." + }, + "expected_impact": { + "type": "object", + "additionalProperties": true, + "description": "What the change would do, as the suggestion shows it in the Console." + } + } + } + } + }, + "required": [ + "webhook_type", + "run_id", + "open_count", + "suggestions" + ] + } + ] + }, + "GeolocationCheck": { + "type": "object", + "description": "One Location verification result, the accepted attempt of one workflow node. Measurement, uncertainty and authenticity are separate keys, so a precise unattested fix reads differently from an imprecise attested one.", + "properties": { + "node_id": { + "type": "string", + "nullable": true, + "description": "Workflow node that produced this result, so a workflow with two Location verification steps stays readable." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Feature-level status of this Location verification step, resolved from its warnings. Independent of the session `status`." + }, + "outcome": { + "type": "string", + "nullable": true, + "description": "What happened on this attempt.", + "enum": [ + "verified", + "low_accuracy", + "accuracy_undetermined", + "stale", + "approximate", + "denied", + "restricted", + "unavailable", + "timeout", + "mock_detected", + "outside_geofence", + "in_blocked_area", + "anonymizer_detected", + "skipped", + "replay_rejected", + "unsupported_client", + null + ] + }, + "permission": { + "type": "string", + "nullable": true, + "description": "What the operating system or browser granted. `not_requested` means the step uses the connection's IP location only, so the person is never asked for a permission.", + "enum": [ + "granted_precise", + "granted_approximate", + "denied", + "restricted", + "unavailable", + "timeout", + "dismissed", + "not_requested", + null + ] + }, + "latitude": { + "type": "number", + "nullable": true, + "description": "WGS84 latitude of the fix. `null` when there is no fix.", + "format": "double" + }, + "longitude": { + "type": "number", + "nullable": true, + "description": "WGS84 longitude of the fix. `null` when there is no fix.", + "format": "double" + }, + "accuracy_m": { + "type": "number", + "nullable": true, + "description": "Radius of uncertainty in metres, at the confidence level in `accuracy_confidence`.", + "format": "double" + }, + "accuracy_confidence": { + "type": "string", + "nullable": true, + "description": "Confidence level the platform attaches to `accuracy_m`: `p95` (browsers), `p68` (Android) or `unspecified` (iOS).", + "enum": [ + "p95", + "p68", + "unspecified", + null + ] + }, + "measured_at": { + "type": "string", + "nullable": true, + "description": "When the device took the fix, by its own clock.", + "format": "date-time" + }, + "age_seconds": { + "type": "integer", + "nullable": true, + "description": "Seconds between the device taking the fix and Didit receiving it." + }, + "source": { + "type": "string", + "nullable": true, + "description": "Where the fix came from, when the platform says. `ip` means the position is the connection's IP location.", + "enum": [ + "gps", + "fused", + "network", + "wifi", + "cell", + "browser", + "unknown", + "ip", + null + ] + }, + "platform": { + "type": "string", + "nullable": true, + "description": "The client that produced the fix.", + "enum": [ + "web", + "ios", + "android", + "react_native", + "flutter", + null + ] + }, + "sdk_version": { + "type": "string", + "nullable": true, + "description": "Version of the SDK that produced the fix." + }, + "is_mock": { + "type": "boolean", + "nullable": true, + "description": "`true` when the device says the fix came from a mock location provider or simulation software. `null` when the platform does not report it (browsers cannot tell); `null` is not `false`." + }, + "produced_by_accessory": { + "type": "boolean", + "nullable": true, + "description": "iOS only: `true` when the fix came from an external accessory. `null` elsewhere." + }, + "integrity": { + "type": "string", + "enum": [ + "attested", + "not_attested", + "failed", + "unsupported", + "not_determined" + ], + "description": "Whether device attestation backs the fix. `unsupported` is what browsers report, because the web has no attestation mechanism; it is not a failure." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country the fix is in." + }, + "region_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision the fix is in." + }, + "city": { + "type": "string", + "nullable": true, + "description": "Nearest populated place, a label for reviewers." + }, + "geofence": { + "type": "object", + "description": "Result of the country and area rules of the step.", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether the step applies any country or area rule." + }, + "result": { + "type": "string", + "nullable": true, + "description": "Where the position sits relative to the rules. `undetermined` means the accuracy circle straddles the border or the position resolved to no country; it is not evidence of being outside.", + "enum": [ + "inside", + "outside", + "undetermined", + null + ] + } + } + }, + "inside_geofence": { + "type": "boolean", + "nullable": true, + "description": "`true` when `geofence.result` is `inside`, `false` when `outside`, `null` otherwise." + }, + "geofence_basis": { + "type": "string", + "nullable": true, + "description": "Which position the area result was judged from: the `device` fix, or the connection's `ip` location.", + "enum": [ + "device", + "ip", + null + ] + }, + "inside_areas": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Ids of the configured allowed areas the position is inside." + }, + "blocked_areas": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Ids of the configured blocked areas the position is in." + }, + "position_source": { + "type": "string", + "nullable": true, + "description": "Where the step takes the position from, as configured: the device checked against the IP location, the device only, or the IP location only.", + "enum": [ + "device_and_ip", + "device_only", + "ip_only", + null + ] + }, + "anonymizers": { + "type": "object", + "nullable": true, + "description": "What the connection and device look like, each `true`, `false` or `null` when the source cannot tell.", + "properties": { + "vpn": { + "type": "boolean", + "nullable": true + }, + "proxy": { + "type": "boolean", + "nullable": true + }, + "tor": { + "type": "boolean", + "nullable": true + }, + "spoofing": { + "type": "boolean", + "nullable": true + }, + "emulator": { + "type": "boolean", + "nullable": true + } + } + }, + "ip_distance_km": { + "type": "number", + "nullable": true, + "description": "Distance to the session's IP location after both uncertainty radii are subtracted; `0` when they overlap.", + "format": "double" + }, + "ip_distance_uncertainty_km": { + "type": "number", + "nullable": true, + "description": "Combined radius that was subtracted: the IP location's radius plus this fix's accuracy.", + "format": "double" + }, + "ip_consistent": { + "type": "boolean", + "nullable": true, + "description": "Whether `ip_distance_km` is within the distance configured on the step. `null` when either side is unknown." + }, + "document_distance_km": { + "type": "number", + "nullable": true, + "description": "Distance to the geocoded address on the ID document or proof of address. `null` when no address is on file.", + "format": "double" + }, + "travel_excused": { + "type": "boolean", + "nullable": true, + "description": "Whether the step's travel tolerance excused the distance from the address. `null` when tolerance is off." + }, + "consecutive_mismatches": { + "type": "integer", + "nullable": true, + "description": "On the final attempt: this check plus the same person's earlier mismatches in a row." + }, + "attempts": { + "type": "integer", + "description": "Number of this attempt within the node, starting at 1." + }, + "consent": { + "type": "object", + "nullable": true, + "description": "The consent the person gave before the system prompt, recorded with the wording version and language they saw.", + "properties": { + "accepted_at": { + "type": "string", + "format": "date-time" + }, + "version": { + "type": "string", + "nullable": true + }, + "locale": { + "type": "string", + "nullable": true + } + } + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "NFC", + "LIVENESS", + "FACEMATCH", + "PROOF_OF_ADDRESS", + "PHONE", + "EMAIL", + "AML", + "LOCATION", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "GEOLOCATION", + "SIGNATURE" + ], + "description": "The feature that generated this warning. The value identifies the feature block the warning belongs to. Each module's warnings array typically uses the value matching its module (e.g. `PHONE` inside `phone_verifications[].warnings`), but the enum above is authoritative across all feature warnings on the decision payload." + }, + "risk": { + "type": "string" + }, + "additional_data": { + "type": "object", + "nullable": true + }, + "log_type": { + "type": "string" + }, + "short_description": { + "type": "string" + }, + "long_description": { + "type": "string" + }, + "node_id": { + "type": "string", + "description": "The workflow graph node ID that generated this warning" + } + } + }, + "description": "Warnings this Location verification step raised, with `feature` set to `GEOLOCATION`. Warning codes start with `LOCATION_`." + } + } + }, + "GeolocationAttempt": { + "type": "object", + "description": "One attempt of a Location verification step: the same keys as `geolocation_checks[]` except `warnings`, plus when Didit received it.", + "properties": { + "node_id": { + "type": "string", + "nullable": true, + "description": "Workflow node that produced this result, so a workflow with two Location verification steps stays readable." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Feature-level status of this Location verification step, resolved from its warnings. Independent of the session `status`." + }, + "outcome": { + "type": "string", + "nullable": true, + "description": "What happened on this attempt.", + "enum": [ + "verified", + "low_accuracy", + "accuracy_undetermined", + "stale", + "approximate", + "denied", + "restricted", + "unavailable", + "timeout", + "mock_detected", + "outside_geofence", + "in_blocked_area", + "anonymizer_detected", + "skipped", + "replay_rejected", + "unsupported_client", + null + ] + }, + "permission": { + "type": "string", + "nullable": true, + "description": "What the operating system or browser granted. `not_requested` means the step uses the connection's IP location only, so the person is never asked for a permission.", + "enum": [ + "granted_precise", + "granted_approximate", + "denied", + "restricted", + "unavailable", + "timeout", + "dismissed", + "not_requested", + null + ] + }, + "latitude": { + "type": "number", + "nullable": true, + "description": "WGS84 latitude of the fix. `null` when there is no fix.", + "format": "double" + }, + "longitude": { + "type": "number", + "nullable": true, + "description": "WGS84 longitude of the fix. `null` when there is no fix.", + "format": "double" + }, + "accuracy_m": { + "type": "number", + "nullable": true, + "description": "Radius of uncertainty in metres, at the confidence level in `accuracy_confidence`.", + "format": "double" + }, + "accuracy_confidence": { + "type": "string", + "nullable": true, + "description": "Confidence level the platform attaches to `accuracy_m`: `p95` (browsers), `p68` (Android) or `unspecified` (iOS).", + "enum": [ + "p95", + "p68", + "unspecified", + null + ] + }, + "measured_at": { + "type": "string", + "nullable": true, + "description": "When the device took the fix, by its own clock.", + "format": "date-time" + }, + "age_seconds": { + "type": "integer", + "nullable": true, + "description": "Seconds between the device taking the fix and Didit receiving it." + }, + "source": { + "type": "string", + "nullable": true, + "description": "Where the fix came from, when the platform says. `ip` means the position is the connection's IP location.", + "enum": [ + "gps", + "fused", + "network", + "wifi", + "cell", + "browser", + "unknown", + "ip", + null + ] + }, + "platform": { + "type": "string", + "nullable": true, + "description": "The client that produced the fix.", + "enum": [ + "web", + "ios", + "android", + "react_native", + "flutter", + null + ] + }, + "sdk_version": { + "type": "string", + "nullable": true, + "description": "Version of the SDK that produced the fix." + }, + "is_mock": { + "type": "boolean", + "nullable": true, + "description": "`true` when the device says the fix came from a mock location provider or simulation software. `null` when the platform does not report it (browsers cannot tell); `null` is not `false`." + }, + "produced_by_accessory": { + "type": "boolean", + "nullable": true, + "description": "iOS only: `true` when the fix came from an external accessory. `null` elsewhere." + }, + "integrity": { + "type": "string", + "enum": [ + "attested", + "not_attested", + "failed", + "unsupported", + "not_determined" + ], + "description": "Whether device attestation backs the fix. `unsupported` is what browsers report, because the web has no attestation mechanism; it is not a failure." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country the fix is in." + }, + "region_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision the fix is in." + }, + "city": { + "type": "string", + "nullable": true, + "description": "Nearest populated place, a label for reviewers." + }, + "geofence": { + "type": "object", + "description": "Result of the country and area rules of the step.", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether the step applies any country or area rule." + }, + "result": { + "type": "string", + "nullable": true, + "description": "Where the position sits relative to the rules. `undetermined` means the accuracy circle straddles the border or the position resolved to no country; it is not evidence of being outside.", + "enum": [ + "inside", + "outside", + "undetermined", + null + ] + } + } + }, + "inside_geofence": { + "type": "boolean", + "nullable": true, + "description": "`true` when `geofence.result` is `inside`, `false` when `outside`, `null` otherwise." + }, + "geofence_basis": { + "type": "string", + "nullable": true, + "description": "Which position the area result was judged from: the `device` fix, or the connection's `ip` location.", + "enum": [ + "device", + "ip", + null + ] + }, + "inside_areas": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Ids of the configured allowed areas the position is inside." + }, + "blocked_areas": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Ids of the configured blocked areas the position is in." + }, + "position_source": { + "type": "string", + "nullable": true, + "description": "Where the step takes the position from, as configured: the device checked against the IP location, the device only, or the IP location only.", + "enum": [ + "device_and_ip", + "device_only", + "ip_only", + null + ] + }, + "anonymizers": { + "type": "object", + "nullable": true, + "description": "What the connection and device look like, each `true`, `false` or `null` when the source cannot tell.", + "properties": { + "vpn": { + "type": "boolean", + "nullable": true + }, + "proxy": { + "type": "boolean", + "nullable": true + }, + "tor": { + "type": "boolean", + "nullable": true + }, + "spoofing": { + "type": "boolean", + "nullable": true + }, + "emulator": { + "type": "boolean", + "nullable": true + } + } + }, + "ip_distance_km": { + "type": "number", + "nullable": true, + "description": "Distance to the session's IP location after both uncertainty radii are subtracted; `0` when they overlap.", + "format": "double" + }, + "ip_distance_uncertainty_km": { + "type": "number", + "nullable": true, + "description": "Combined radius that was subtracted: the IP location's radius plus this fix's accuracy.", + "format": "double" + }, + "ip_consistent": { + "type": "boolean", + "nullable": true, + "description": "Whether `ip_distance_km` is within the distance configured on the step. `null` when either side is unknown." + }, + "document_distance_km": { + "type": "number", + "nullable": true, + "description": "Distance to the geocoded address on the ID document or proof of address. `null` when no address is on file.", + "format": "double" + }, + "travel_excused": { + "type": "boolean", + "nullable": true, + "description": "Whether the step's travel tolerance excused the distance from the address. `null` when tolerance is off." + }, + "consecutive_mismatches": { + "type": "integer", + "nullable": true, + "description": "On the final attempt: this check plus the same person's earlier mismatches in a row." + }, + "attempts": { + "type": "integer", + "description": "Number of this attempt within the node, starting at 1." + }, + "consent": { + "type": "object", + "nullable": true, + "description": "The consent the person gave before the system prompt, recorded with the wording version and language they saw.", + "properties": { + "accepted_at": { + "type": "string", + "format": "date-time" + }, + "version": { + "type": "string", + "nullable": true + }, + "locale": { + "type": "string", + "nullable": true + } + } + }, + "received_at": { + "type": "string", + "nullable": true, + "description": "When Didit received the attempt.", + "format": "date-time" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BiometricRetention": { + "type": "object", + "description": "When the live biometric data of this session is deleted.", + "required": [ + "jurisdictions", + "deletes_at" + ], + "properties": { + "jurisdictions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Codes of the US states whose biometric rules set a deletion limit for this person (`US` when the state is unknown). Empty when none applies." + }, + "deletes_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "The date the live biometric data (face images and templates) of this session is deleted, from your application's retention setting and, where one applies, the legal limit for the person's jurisdiction. `null` when no date applies yet." + } + } + }, + "TravelRuleManualMatchRequest": { + "type": "object", + "required": [ + "transaction_id" + ], + "properties": { + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "The `transaction_id` of the received Travel Rule message (a row with `kind: message`)." + } + } + }, + "TravelRuleManualMatchResponse": { + "type": "object", + "required": [ + "deposit", + "message" + ], + "properties": { + "deposit": { + "$ref": "#/components/schemas/TravelRuleTransferRow" + }, + "message": { + "$ref": "#/components/schemas/TravelRuleTransferRow" + } + } + } + }, + "securitySchemes": { + "ApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "x-api-key", + "description": "Your application's API key, from Developers -> API keys in the Business Console. The primary key has full access. A named key can be scoped: none, read or write per resource, limited to some workflows or to approved sessions, to a list of IP addresses, and to an expiry date. 401 means the key is wrong, revoked or expired; 403 means no key was sent, the key has no access to this resource or action, or the request came from an address outside its IP list; 404 on a session route means the session is outside the key's workflows or statuses. A key without media access receives image, video and PDF URLs as null, and a key without sessions write receives session links and tokens as null. See https://docs.didit.me/console/api-keys." + }, + "TransactionTokenAuth": { + "type": "apiKey", + "in": "header", + "name": "X-Transaction-Token", + "description": "Short-lived scoped token minted by your backend via POST /v3/transactions/sdk-token/. Used by the Didit SDKs on the device-facing /v1/transactions/ endpoints." + }, + "SessionTokenAuth": { + "type": "apiKey", + "in": "header", + "name": "Session-Token", + "description": "Short-lived token returned in the `session_token` field of POST /v3/session/. Used by the hosted verification flow (and custom sandbox UIs) to call session-scoped endpoints on behalf of the verifying user, without your server-side API key." + }, + "BearerAuth": { + "type": "http", + "scheme": "bearer", + "description": "The application's client-credentials access token." + }, + "CaseRequestToken": { + "type": "apiKey", + "in": "header", + "name": "X-Case-Request-Token", + "description": "Request-specific credential from the secure link. Grants access only to this request." + } + }, + "responses": { + "Unauthorized": { + "description": "Unauthorized", + "content": { + "application/json": { + "examples": { + "Invalid API Key": { + "summary": "Invalid API Key", + "value": { + "detail": "Invalid API Key." + } + } + } + } + } + }, + "Forbidden": { + "description": "Forbidden", + "content": { + "application/json": { + "examples": { + "No Permission": { + "summary": "No Permission", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not Enough Credits": { + "summary": "Not Enough Credits", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "NotFound": { + "description": "Not Found", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Not Found", + "value": { + "detail": "Resource not found." + } + } + } + } + } + }, + "PhoneRiskValidationError": { + "description": "Request validation failed.", + "content": { + "application/json": { + "examples": { + "Missing phone number": { + "summary": "Missing phone number", + "value": { + "phone_number": [ + "This field is required." + ] + } + }, + "Invalid phone number": { + "summary": "Invalid phone number", + "value": { + "phone_number": [ + "Invalid phone number provided." + ] + } + }, + "Invalid signal IP": { + "summary": "Invalid signal IP", + "value": { + "signals": { + "ip": [ + "Enter a valid IPv4 or IPv6 address." + ] + } + } + } + } + } + } + }, + "EmailRiskValidationError": { + "description": "Request validation failed.", + "content": { + "application/json": { + "examples": { + "Missing email": { + "summary": "Missing email", + "value": { + "email": [ + "This field is required." + ] + } + }, + "Invalid email": { + "summary": "Invalid email", + "value": { + "email": [ + "Enter a valid email address." + ] + } + }, + "Invalid country hint": { + "summary": "Invalid country hint", + "value": { + "country_code": [ + "Ensure this field has no more than 3 characters." + ] + } + } + } + } + } + }, + "IPRiskValidationError": { + "description": "Request validation failed.", + "content": { + "application/json": { + "examples": { + "Missing IP address": { + "summary": "Missing IP address", + "value": { + "ip_address": [ + "This field is required." + ] + } + }, + "Invalid IP address": { + "summary": "Invalid IP address", + "value": { + "ip_address": [ + "Enter a valid IPv4 or IPv6 address." + ] + } + }, + "Country hint too long": { + "summary": "Country hint too long", + "value": { + "claimed_country": [ + "Ensure this field has no more than 3 characters." + ] + } + } + } + } + } + }, + "RiskPermissionDenied": { + "description": "The API key is missing or invalid, or the organization cannot cover the live lookup. This API never returns `401`.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid or revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the lookup price", + "value": { + "error": "You don't have enough credits to perform this request." + } + } + } + } + } + }, + "RateLimitedWrite": { + "description": "The shared write limit of 300 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "detail" + ], + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + }, + "AddressValidationValidationError": { + "description": "The request body is invalid: a missing or unknown `country`, no address, or both `address` and the structured fields.", + "content": { + "application/json": { + "examples": { + "Unknown country": { + "value": { + "country": [ + "Use an ISO 3166-1 alpha-2 or alpha-3 country code, e.g. 'US' or 'USA'." + ] + } + }, + "No address": { + "value": { + "address": [ + "Send the address: 'address' on one line, or the structured fields with 'address_line_1'." + ] + } + }, + "Both shapes": { + "value": { + "address": [ + "Send either 'address' or the structured fields (address_line_1, ...), not both." + ] + } + } + } + } + } + }, + "AddressValidationPermissionDenied": { + "description": "The API key is missing or invalid, a scoped key lacks `sessions` write access, or the organization cannot cover the validation. This API never returns `401`.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "value": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me", + "code": "insufficient_credits" + } + } + } + } + } + } + }, + "parameters": { + "WebhookSignature": { + "name": "X-Signature", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Lowercase hex HMAC-SHA256, keyed with the destination's secret, of the exact raw request body." + }, + "WebhookSignatureV2": { + "name": "X-Signature-V2", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Lowercase hex HMAC-SHA256, keyed with the destination's secret, of the canonical JSON of the body (keys sorted, no whitespace, non-ASCII characters unescaped). Recommended, because it survives middleware that re-encodes the body." + }, + "WebhookSignatureSimple": { + "name": "X-Signature-Simple", + "in": "header", + "required": true, + "deprecated": true, + "schema": { + "type": "string" + }, + "description": "Lowercase hex HMAC-SHA256, keyed with the destination's secret, of `{timestamp}:{session_id}:{status}:{webhook_type}` (missing fields as empty strings). Covers the envelope only, never the payload." + }, + "WebhookTimestamp": { + "name": "X-Timestamp", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Unix time in seconds, equal to the body's `timestamp`. Reject deliveries older than a few minutes." + } + } + }, + "x-webhooks": { + "status.updated": { + "post": { + "summary": "Session status changed", + "description": "A verification session changed status. `decision` is sent only for `Approved`, `Declined`, `In Review` and `Abandoned`; it has the shape of `GET /v3/session/{sessionId}/decision/` for destinations on webhook version 3 (the default), and the older reduced shape for version 1 or 2 destinations. Business sessions always receive the version 3 business decision.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "data.updated": { + "post": { + "summary": "Session data changed", + "description": "Data of a verification session changed without a status change, for example after a manual review edit, a step override or an ongoing-monitoring rescreen. `decision` is sent only for `Approved`, `Declined`, `In Review` and `Abandoned`; it has the shape of `GET /v3/session/{sessionId}/decision/` for destinations on webhook version 3 (the default), and the older reduced shape for version 1 or 2 destinations. Business sessions always receive the version 3 business decision.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionDataUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "user.status.updated": { + "post": { + "summary": "User status changed", + "description": "A user changed status. Not sent when the user is created.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "user.data.updated": { + "post": { + "summary": "User data changed", + "description": "Fields of a user changed. Not sent when the user is created. One save can produce both a status and a data event.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDataUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "business.status.updated": { + "post": { + "summary": "Business status changed", + "description": "A business changed status. Not sent when the business is created.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "business.data.updated": { + "post": { + "summary": "Business data changed", + "description": "Fields of a business changed. Not sent when the business is created. One save can produce both a status and a data event.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDataUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "transaction.created": { + "post": { + "summary": "Transaction created", + "description": "A transaction was created and screened. Sent for live applications only.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionCreatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "transaction.status.updated": { + "post": { + "summary": "Transaction status changed", + "description": "A transaction changed status, from a rule, a review or an end-user action. Sent for live applications only.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "travel_rule.status.updated": { + "post": { + "summary": "Travel Rule status changed", + "description": "A Travel Rule exchange changed status. Sent on every transition up to and including `FINISHED`, `CANCELLED` or `EXPIRED`, never after. Sent for live applications and, for Travel Rule exchanges, for sandbox applications too (`environment: sandbox`). There is no top-level `status`.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "workflow.compliance_changed": { + "post": { + "summary": "Workflow compliance changed", + "description": "The compliance status of a published workflow changed after a re-evaluation. One event per workflow. There is no top-level `status`.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowComplianceChangedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "kyb.registry_search.resolved": { + "post": { + "summary": "Company search resolved (unsigned callback)", + "description": "Body of the callback sent to the `webhook_url` of `POST /v3/kyb/search/` once the registry search resolves. It is not signed: the request carries `X-Didit-Unsigned-Callback: true` and none of the signature headers, so confirm it by matching `request_id` against a search you started. Uses `event_type`, not `webhook_type`.", + "tags": [ + "Webhook" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBRegistrySearchResolvedCallback" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "transaction_rule_suggestions.created": { + "post": { + "summary": "Transaction rule suggestions created", + "description": "A transaction rule suggestion run proposed new rule changes for your application. Sent when the rule-suggestion settings have webhook notifications on. There is no top-level `status`.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleSuggestionsCreatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + } + } +} diff --git a/codegen/tests/fixtures/verification-fe9f57a1dfb6374bfd391750d2044fc7a206e4ea.json b/codegen/tests/fixtures/verification-fe9f57a1dfb6374bfd391750d2044fc7a206e4ea.json new file mode 100644 index 0000000..69e64de --- /dev/null +++ b/codegen/tests/fixtures/verification-fe9f57a1dfb6374bfd391750d2044fc7a206e4ea.json @@ -0,0 +1,128151 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "3.0.0", + "title": "Didit Verification API", + "description": "Identity verification API. Authenticate with x-api-key header." + }, + "servers": [ + { + "url": "https://verification.didit.me" + } + ], + "tags": [], + "paths": { + "/system/healthcheck": { + "get": { + "summary": "Service healthcheck (public, unauthenticated)", + "description": "Public unauthenticated liveness probe. Returns `HTTP 200` with `{status: \"ok\", timestamp}` when service, DB, cache, and broker are healthy.", + "operationId": "get_system_healthcheck", + "x-didit-sdk": { + "resource": "system", + "method": "healthcheck", + "auth": "public", + "server": false + }, + "tags": [ + "System" + ], + "security": [ + {} + ], + "parameters": [], + "responses": { + "200": { + "description": "Service is healthy and accepting requests. Body is always `{\"status\": \"ok\", \"timestamp\": \"\"}`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "status", + "timestamp" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "ok" + ], + "description": "Liveness marker. Always the literal string `\"ok\"` on a 200 response.", + "example": "ok" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Server time at which the healthcheck was evaluated, in ISO 8601 with millisecond precision and a `Z` (UTC) suffix.", + "example": "2026-04-21T16:42:50.445Z" + } + } + }, + "examples": { + "Healthy": { + "summary": "Service is up", + "value": { + "status": "ok", + "timestamp": "2026-04-21T16:42:50.445Z" + } + } + } + } + } + }, + "503": { + "description": "Service is unhealthy — one of the configured health-check plugins (database, cache, broker) is failing. The response body is intentionally minimal; the HTTP status is the load-bearing signal. Retry with exponential backoff." + }, + "5XX": { + "description": "Verification service is degraded or unreachable. Treat any non-2xx response as \"unhealthy\" and retry with exponential backoff." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -i https://verification.didit.me/system/healthcheck" + }, + { + "lang": "Python", + "label": "Python (requests)", + "source": "import requests\n\nresp = requests.get(\"https://verification.didit.me/system/healthcheck\", timeout=5)\nresp.raise_for_status()\nprint(resp.json()) # {'status': 'ok', 'timestamp': '2026-04-21T16:42:50.445Z'}" + }, + { + "lang": "JavaScript", + "label": "Node.js (fetch)", + "source": "const res = await fetch(\"https://verification.didit.me/system/healthcheck\");\nif (!res.ok) throw new Error(`Didit unhealthy: ${res.status}`);\nconst body = await res.json();\nconsole.log(body); // { status: 'ok', timestamp: '2026-04-21T16:42:50.445Z' }" + } + ] + } + }, + "/v3/email/risk/": { + "post": { + "summary": "Risk-score an email address", + "description": "Return deliverability, disposable-address, breach, cross-session match, and normalized risk-intelligence signals without sending an OTP. The endpoint creates an API session and returns its id as `request_id`.\n\n**Decision.** The top-level `status` mirrors `email.status`. It is normally `Approved`; the risk evaluation can produce `In Review` or `Declined`.\n\n**Compatibility aliases.** Live responses expose the normalized intelligence object as both `email.email_intelligence` and `email.enrichment`. The values are identical. New integrations can read `email.enrichment`. Both keys are absent when no usable intelligence data was returned.\n\n**Billing.** Live calls use Email Risk API pricing. The request is billable once the lookup starts, even when some best-effort fields are unavailable.\n\n**Sandbox.** Sandbox keys validate the request and return deterministic approved data without sending an email, consuming credits, creating a session, or contributing to Networks.\n\n**Authentication.** Send your application's API key in `x-api-key`. Missing or invalid credentials return `403` - this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests per minute per API key across POST/PATCH/DELETE endpoints (transaction creates and event ingestion have a separate budget of 600).\n\n**Social footprint.** Set `enable_social` to `true` to add a `email_social` block: the online platforms this address is registered on, with per-category counts and any profile details available. `profiles_registered: 0` means the platforms were checked and the address was found on none of them - a valid, billable answer. The block is absent when the add-on was not requested or the check could not be completed, and it is not billed in that case. Billing adds one `email_social` unit per request on top of this endpoint's own price.", + "operationId": "post_v3email_risk", + "x-didit-sdk": { + "resource": "email", + "method": "assess_risk", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/email/risk/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"email\": \"alice@example.com\",\n \"country_code\": \"US\",\n \"vendor_data\": \"lead-123\",\n \"metadata\": {\"source\": \"signup_prescreen\"}\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.email.assess_risk(\n email=\"alice@example.com\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.email.assessRisk({\n email: \"alice@example.com\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Email.AssessRisk(ctx, didit.EmailRiskRequest{\n\tEmail: \"alice@example.com\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.EmailRiskRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.email().assessRisk(EmailRiskRequest.builder()\n .email(\"alice@example.com\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "email->assessRisk([\n 'email' => 'alice@example.com',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.email.assess_risk(email: \"alice@example.com\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Email.AssessRiskAsync(new EmailRiskRequest\n{\n Email = \"alice@example.com\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailRiskRequest" + }, + "examples": { + "Minimal": { + "summary": "Score an address with defaults", + "value": { + "email": "alice@example.com" + } + }, + "Full context": { + "summary": "Score with signals, country hint, and correlation ids", + "value": { + "email": "alice@example.com", + "country_code": "US", + "signals": { + "ip": "192.0.2.1", + "device_id": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2", + "device_platform": "web", + "os_version": "14.4", + "app_version": "1.2.34", + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15" + }, + "vendor_data": "lead-123", + "metadata": { + "source": "signup_prescreen" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The risk evaluation completed. Inspect `status` for the decision; HTTP is always `200` for completed lookups.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailRiskResponse" + }, + "examples": { + "Clean address": { + "summary": "Deliverable address with no breach exposure", + "value": { + "request_id": "82397be9-5921-4970-a979-2d5b43e08b33", + "status": "Approved", + "email": { + "status": "Approved", + "email": "alice@example.com", + "is_breached": false, + "breaches": [], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 0, + "verified_at": null, + "warnings": [], + "lifecycle": [], + "matches": [], + "email_intelligence": { + "score": 4.0, + "deliverable": "high", + "breach_risk_level": "Low", + "breach_risk_index": 4, + "is_recent_breach": false, + "is_frequent_breached": false, + "is_disposable": false, + "is_free_domain": false, + "is_breached": false, + "no_of_breaches": 0, + "reason_codes": [] + }, + "enrichment": { + "score": 4.0, + "deliverable": "high", + "breach_risk_level": "Low", + "breach_risk_index": 4, + "is_recent_breach": false, + "is_frequent_breached": false, + "is_disposable": false, + "is_free_domain": false, + "is_breached": false, + "no_of_breaches": 0, + "reason_codes": [] + } + }, + "vendor_data": "lead-123", + "metadata": { + "source": "signup_prescreen" + }, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + }, + "Breached address": { + "summary": "Address found in known breaches", + "value": { + "request_id": "d1e5c2a8-6f30-4c1a-8b2d-3e4f5a6b7c8d", + "status": "In Review", + "email": { + "status": "In Review", + "email": "bob@example.org", + "is_breached": true, + "breaches": [ + { + "name": "ExampleBreach2023", + "domain": "example.org", + "breach_date": "2023-06-01", + "breach_emails_count": 1200000, + "description": "A data breach affecting example.org accounts.", + "logo_path": "https://logos.example.org/example.png", + "data_classes": [ + "email_addresses", + "passwords" + ], + "is_verified": true + } + ], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 0, + "verified_at": null, + "warnings": [], + "lifecycle": [], + "matches": [], + "email_intelligence": { + "score": 58.0, + "deliverable": "high", + "breach_risk_level": "High", + "breach_risk_index": 72, + "is_recent_breach": true, + "is_frequent_breached": true, + "is_disposable": false, + "is_free_domain": true, + "is_breached": true, + "no_of_breaches": 4, + "reason_codes": [ + { + "code": "IR-F784B630", + "direction": "risk", + "message": "Email's digital footprint diversity is Very Low", + "impact_level": "low" + } + ] + }, + "enrichment": { + "score": 58.0, + "deliverable": "high", + "breach_risk_level": "High", + "breach_risk_index": 72, + "is_recent_breach": true, + "is_frequent_breached": true, + "is_disposable": false, + "is_free_domain": true, + "is_breached": true, + "no_of_breaches": 4, + "reason_codes": [ + { + "code": "IR-F784B630", + "direction": "risk", + "message": "Email's digital footprint diversity is Very Low", + "impact_level": "low" + } + ] + } + }, + "vendor_data": "lead-125", + "metadata": null, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/EmailRiskValidationError" + }, + "403": { + "$ref": "#/components/responses/RiskPermissionDenied" + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/ip/risk/": { + "post": { + "summary": "Risk-score an IP address", + "description": "Return geolocation, network ownership, VPN/Tor and data-center flags, threat data, and `country_mismatch` for a supplied IP address. The endpoint creates an API session and returns its id as `request_id`.\n\n**Decision.** `status` is `In Review` when the resolved country conflicts with `claimed_country`, or when the address is associated with VPN, Tor, or data-center infrastructure. Otherwise it is `Approved`.\n\n**Best effort.** IP enrichment is best effort. If no enrichment source returns usable data, the endpoint returns an approved response with `ip.ip_address: null`, `ip.country_mismatch: null`, and an empty `ip.threat` object.\n\n**Billing.** Live calls use IP Risk API pricing. The request is billable once the lookup starts.\n\n**Sandbox.** Sandbox keys validate the request and return deterministic approved data without consuming credits, creating a session, or contributing to Networks.\n\n**Authentication.** Send your application's API key in `x-api-key`. Missing or invalid credentials return `403` - this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests per minute per API key across POST/PATCH/DELETE endpoints (transaction creates and event ingestion have a separate budget of 600).", + "operationId": "post_v3ip_risk", + "x-didit-sdk": { + "resource": "ip", + "method": "assess_risk", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/ip/risk/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"ip_address\": \"8.8.8.8\",\n \"claimed_country\": \"ESP\",\n \"user_agent\": \"Mozilla/5.0\",\n \"vendor_data\": \"lead-123\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.ip.assess_risk(\n ip_address=\"8.8.8.8\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.ip.assessRisk({\n ip_address: \"8.8.8.8\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IP.AssessRisk(ctx, didit.IPRiskRequest{\n\tIPAddress: \"8.8.8.8\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.IPRiskRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.ip().assessRisk(IPRiskRequest.builder()\n .ipAddress(\"8.8.8.8\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "ip->assessRisk([\n 'ip_address' => '8.8.8.8',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.ip.assess_risk(ip_address: \"8.8.8.8\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Ip.AssessRiskAsync(new IPRiskRequest\n{\n IpAddress = \"8.8.8.8\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IPRiskRequest" + }, + "examples": { + "Minimal": { + "summary": "Score an address with defaults", + "value": { + "ip_address": "8.8.8.8" + } + }, + "With claimed country": { + "summary": "Score with a claimed country for mismatch detection", + "value": { + "ip_address": "8.8.8.8", + "claimed_country": "ESP", + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15", + "vendor_data": "lead-123" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The risk evaluation completed. Inspect `status` for the decision; HTTP is always `200` for completed lookups.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IPRiskResponse" + }, + "examples": { + "Clean residential IP": { + "summary": "Residential address, claimed country matches", + "value": { + "request_id": "9a96ed2a-721e-4b86-8cbf-d61eda529279", + "status": "Approved", + "ip": { + "status": "Approved", + "ip_address": "192.0.2.10", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_state": "Madrid", + "ip_city": "Madrid", + "latitude": 40.4168, + "longitude": -3.7038, + "isp": "Example Telecom", + "organization": "Example Telecom SA", + "is_vpn_or_tor": false, + "is_data_center": false, + "time_zone": "Europe/Madrid", + "time_zone_offset": "+0200", + "asn_number": 3352, + "asn_organization": "EXAMPLE-TELECOM", + "connection_type": "broadband", + "carrier": "Example Telecom", + "proxy_type": null, + "claimed_country": "ESP", + "country_mismatch": false, + "threat": {} + }, + "vendor_data": "lead-123", + "metadata": null, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + }, + "Datacenter mismatch": { + "summary": "Data-center address conflicting with the claimed country", + "value": { + "request_id": "5b1d4e9a-3c7b-4f8e-9a2d-1b0c9d8e7f6a", + "status": "In Review", + "ip": { + "status": "In Review", + "ip_address": "8.8.8.8", + "ip_country": "United States", + "ip_country_code": "US", + "ip_state": "California", + "ip_city": "Mountain View", + "latitude": 37.386, + "longitude": -122.0838, + "isp": "Google", + "organization": "Google LLC", + "is_vpn_or_tor": true, + "is_data_center": true, + "time_zone": "America/Los_Angeles", + "time_zone_offset": "-0700", + "asn_number": 15169, + "asn_organization": "GOOGLE, US", + "connection_type": "hosting", + "carrier": null, + "proxy_type": "VPN", + "claimed_country": "ESP", + "country_mismatch": true, + "threat": { + "is_proxy": true, + "is_data_center": true, + "is_tor": false + } + }, + "vendor_data": "lead-126", + "metadata": null, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/IPRiskValidationError" + }, + "403": { + "$ref": "#/components/responses/RiskPermissionDenied" + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/phone/risk/": { + "post": { + "summary": "Risk-score a phone number", + "description": "Resolve a phone number and return carrier, line-type, porting, disposable-number, cross-session match, and normalized risk-intelligence signals without sending an OTP. The endpoint creates an API session and returns its id as `request_id`.\n\n**Decision.** The top-level `status` mirrors `phone.status`. It is normally `Approved`; the risk evaluation can produce `In Review` or `Declined`.\n\n**Compatibility aliases.** Live responses expose the normalized intelligence object as both `phone.phone_intelligence` and `phone.enrichment`. The values are identical. New integrations can read `phone.enrichment`. Both keys are absent when no usable intelligence data was returned.\n\n**Billing.** Live calls use Phone Risk API pricing. The request is billable once the lookup starts, even when some best-effort fields are unavailable.\n\n**Sandbox.** Sandbox keys validate the request and return deterministic approved data without sending an OTP, consuming credits, creating a session, or contributing to Networks.\n\n**Authentication.** Send your application's API key in `x-api-key`. Missing or invalid credentials return `403` - this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests per minute per API key across POST/PATCH/DELETE endpoints (transaction creates and event ingestion have a separate budget of 600).\n\n**Social footprint.** Set `enable_social` to `true` to add a `phone_social` block: the online platforms this number is registered on, with per-category counts and any profile details available. `profiles_registered: 0` means the platforms were checked and the number was found on none of them - a valid, billable answer. The block is absent when the add-on was not requested or the check could not be completed, and it is not billed in that case. Billing adds one `phone_social` unit per request on top of this endpoint's own price.", + "operationId": "post_v3phone_risk", + "x-didit-sdk": { + "resource": "phone", + "method": "assess_risk", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/phone/risk/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"phone_number\": \"+14155552671\",\n \"country_code\": \"US\",\n \"vendor_data\": \"lead-123\",\n \"metadata\": {\"source\": \"signup_prescreen\"}\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.phone.assess_risk(\n phone_number=\"+14155552671\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.phone.assessRisk({\n phone_number: \"+14155552671\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Phone.AssessRisk(ctx, didit.PhoneRiskRequest{\n\tPhoneNumber: \"+14155552671\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.PhoneRiskRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.phone().assessRisk(PhoneRiskRequest.builder()\n .phoneNumber(\"+14155552671\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "phone->assessRisk([\n 'phone_number' => '+14155552671',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.phone.assess_risk(phone_number: \"+14155552671\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Phone.AssessRiskAsync(new PhoneRiskRequest\n{\n PhoneNumber = \"+14155552671\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PhoneRiskRequest" + }, + "examples": { + "Minimal": { + "summary": "Score a number with defaults", + "value": { + "phone_number": "+14155552671" + } + }, + "Full context": { + "summary": "Score with signals, country hint, and correlation ids", + "value": { + "phone_number": "+14155552671", + "country_code": "US", + "signals": { + "ip": "192.0.2.1", + "device_id": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2", + "device_platform": "ios", + "device_model": "iPhone17,2", + "os_version": "18.0.1", + "app_version": "1.2.34", + "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1" + }, + "vendor_data": "lead-123", + "metadata": { + "source": "signup_prescreen" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The risk evaluation completed. Inspect `status` for the decision; HTTP is always `200` for completed lookups.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PhoneRiskResponse" + }, + "examples": { + "Clean mobile number": { + "summary": "Clean mobile number with intelligence coverage", + "value": { + "request_id": "4fd09d2e-9bc9-4c4e-9c46-7f625ac0ab62", + "status": "Approved", + "phone": { + "status": "Approved", + "phone_number_prefix": "+1", + "phone_number": "4155552671", + "full_number": "+14155552671", + "country_code": "US", + "country_name": "United States", + "carrier": { + "name": "Example Mobile", + "type": "mobile" + }, + "is_disposable": false, + "is_virtual": false, + "verification_method": null, + "verification_attempts": 0, + "verified_at": null, + "warnings": [], + "lifecycle": [], + "matches": [], + "phone_intelligence": { + "score": 3.0, + "phone_type_risk": "Low", + "phone_trust_index": 97, + "network_stability_index": 95, + "is_disposable": false, + "is_ported": false, + "is_recently_ported": false, + "carrier_type": "mobile", + "reason_codes": [] + }, + "enrichment": { + "score": 3.0, + "phone_type_risk": "Low", + "phone_trust_index": 97, + "network_stability_index": 95, + "is_disposable": false, + "is_ported": false, + "is_recently_ported": false, + "carrier_type": "mobile", + "reason_codes": [] + } + }, + "vendor_data": "lead-123", + "metadata": { + "source": "signup_prescreen" + }, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + }, + "Number in review": { + "summary": "Disposable or virtual number flagged for review", + "value": { + "request_id": "8c22e1f0-4b3d-4a3a-9d1e-2f6b7c8d9e0f", + "status": "In Review", + "phone": { + "status": "In Review", + "phone_number_prefix": "+44", + "phone_number": "7700900123", + "full_number": "+447700900123", + "country_code": "GB", + "country_name": "United Kingdom", + "carrier": { + "name": "Example VoIP", + "type": "voip" + }, + "is_disposable": true, + "is_virtual": true, + "verification_method": null, + "verification_attempts": 0, + "verified_at": null, + "warnings": [], + "lifecycle": [], + "matches": [], + "phone_intelligence": { + "score": 72.0, + "phone_type_risk": "High", + "phone_trust_index": 28, + "network_stability_index": 40, + "is_disposable": true, + "is_ported": true, + "is_recently_ported": true, + "carrier_type": "voip", + "reason_codes": [ + { + "code": "IR-5AC67A32", + "direction": "risk", + "message": "Phone type identified as non-mobile", + "impact_level": "medium" + } + ] + }, + "enrichment": { + "score": 72.0, + "phone_type_risk": "High", + "phone_trust_index": 28, + "network_stability_index": 40, + "is_disposable": true, + "is_ported": true, + "is_recently_ported": true, + "carrier_type": "voip", + "reason_codes": [ + { + "code": "IR-5AC67A32", + "direction": "risk", + "message": "Phone type identified as non-mobile", + "impact_level": "medium" + } + ] + } + }, + "vendor_data": "lead-124", + "metadata": null, + "created_at": "2026-09-01T10:30:00.123456+00:00" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/PhoneRiskValidationError" + }, + "403": { + "$ref": "#/components/responses/RiskPermissionDenied" + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/": { + "post": { + "summary": "Create a verification session", + "description": "Create a User Verification (KYC) or Business Verification (KYB) session and receive a hosted verification `url` plus a `session_token` to redirect or embed for your end user. The `workflow_id` selects which verification steps run and whether the session is KYC or KYB.\n\nCall this from your backend (never the browser — the API key is a secret) whenever a user or business needs to be verified: at signup, before a sensitive action, or when re-verification is required. Prerequisites: a published workflow (created in the Console [Workflows](https://docs.didit.me/console/workflows) page) and an application API key with available credits. Sandbox applications bypass the credit check.\n\n**Idempotency:** when `vendor_data` is provided and an unfinished session (`Not Started`, `In Progress`, `Resubmitted`, or `Awaiting User`) with the same `vendor_data` already exists on the workflow's **current latest published version** (and that session already has a hosted verification URL), the existing session is returned (still `201`) instead of creating a duplicate — with its `callback` and `metadata` updated to the newly provided values. An unfinished session created on an older version of the workflow (one that has since been republished) is **not** reused; a new session pinned to the latest published version is created instead. Sessions in `Approved`, `Declined`, `In Review`, `Expired`, `Abandoned`, or `Kyc Expired` are never reused.\n\n**Side effects:** the hosted verification URL is generated and stored; if `contact_details.send_notification_emails` is `true` and an email is provided, a verification invite email is sent. The session expires after the workflow's configured session expiration time. For KYB workflows, `contact_details.phone` and `expected_details` are ignored.", + "operationId": "post_v3_session_create", + "x-didit-sdk": { + "resource": "sessions", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "201": { + "description": "Session created (or returned, if an unfinished session with the same `vendor_data` already exists on the workflow's latest published version). The response is the serialized session, including the hosted verification `url` to redirect the user to.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "session_id", + "session_number", + "session_token", + "url", + "vendor_data", + "metadata", + "status", + "workflow_id", + "workflow_version", + "callback" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of the verification session. Use this id when calling `GET /v3/session/{sessionId}/decision/`.", + "example": "11111111-2222-3333-4444-555555555555" + }, + "session_number": { + "type": "integer", + "description": "Sequential, human-friendly number assigned to the session inside your application. Useful for support and dashboards.", + "example": 43762 + }, + "session_token": { + "type": "string", + "description": "12-character URL-safe token that authorizes the end user to access the hosted verification flow at `url`. Valid until the session expires. Treat it as a secret — anyone holding it can open the verification flow for this session.", + "example": "3FaJ9wLqX2Mz" + }, + "url": { + "type": "string", + "format": "uri", + "description": "Hosted verification URL to redirect the end user to. The URL embeds the `session_token` and, if configured, uses your white-label domain. When the request includes `language`, the URL contains a language path segment — `https://verify.didit.me/{language}/session/{session_token}` (e.g. `/es/session/...`); without `language`, the segment is omitted.", + "example": "https://verify.didit.me/session/3FaJ9wLqX2Mz" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Identifier you passed in the request to link the session to a user or business in your own system. Echoed back verbatim. Null when not provided.", + "example": "user-123" + }, + "metadata": { + "description": "Arbitrary JSON payload you stored with the session at creation time. Echoed back verbatim — whatever JSON value you sent (object, string, number, array) is returned as-is, though a JSON object is recommended. Not shown to the end user. Always present in responses; `null` when not provided at creation time.", + "example": { + "user_type": "premium", + "account_id": "ABC123" + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The tags on the session, by name.", + "example": [ + "VIP", + "ONBOARDING Q4" + ] + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Current status of the session. Newly created sessions return `Not Started`. If a non-finished session already existed for the same `vendor_data` on the workflow's latest published version, the returned status reflects that existing session.", + "example": "Not Started" + }, + "callback": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Final redirect URL the user is sent to after completing the flow. Didit appends `?verificationSessionId={session_id}&status={status}` to this URL. Falls back to the workflow's configured callback URL when not provided in the request; `null` when neither is set. When the application has allowed domains and this URL is a web URL outside them (for example a workflow default set before the list changed), the hosted flow does not redirect the user to it.", + "example": "https://example.com/verification/callback" + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier of the workflow this session runs on. Always the workflow's stable group identifier — even if you referenced a specific workflow version UUID in the request (backward-compatible lookup), the response carries the stable `workflow_id`. Use it to correlate the session with the workflow you configured in the Console.", + "example": "11111111-2222-3333-4444-555555555555" + }, + "workflow_version": { + "type": "integer", + "description": "Version number of the published workflow version the session was pinned to at creation. Didit resolves `workflow_id` to the workflow's latest published version when the session is created, and the session keeps running that version even if the workflow is republished later.", + "example": 3 + } + } + }, + "examples": { + "KYC session": { + "summary": "KYC session created", + "value": { + "session_id": "11111111-2222-3333-4444-555555555555", + "session_number": 43762, + "session_token": "3FaJ9wLqX2Mz", + "url": "https://verify.didit.me/en/session/3FaJ9wLqX2Mz", + "vendor_data": "user-123", + "metadata": { + "user_type": "premium", + "account_id": "ABC123" + }, + "status": "Not Started", + "workflow_id": "11111111-2222-3333-4444-555555555555", + "workflow_version": 3, + "callback": "https://example.com/verification/callback" + } + }, + "KYB session": { + "summary": "KYB session created", + "value": { + "session_id": "22222222-3333-4444-5555-666666666666", + "session_number": 43763, + "session_token": "Yk7pQ2vN8aBc", + "url": "https://verify.didit.me/en/session/Yk7pQ2vN8aBc", + "vendor_data": "company-acme-001", + "metadata": { + "tier": "enterprise" + }, + "status": "Not Started", + "workflow_id": "33333333-4444-5555-6666-777777777777", + "workflow_version": 1, + "callback": "https://example.com/kyb/callback" + } + } + } + } + } + }, + "400": { + "description": "Bad request — the payload failed validation (missing or unknown `workflow_id`, invalid `language`, blocklisted `vendor_data`, malformed `portrait_image`, `address` without `country`/`poa_country`, invalid `sandbox_scenario`, `sign` documents, fields or values the workflow's E-signature step refuses, such as `signature_document_not_supported`, `signature_document_conversion_failed`, `field_anchor_not_found` or `field_value_missing`, etc.) or your organization does not have enough credits to start the session.\n\nThe body is an object keyed by the offending field name (or `detail` for non-field errors). Values are an **array of strings** for input-validation failures (e.g. missing required field, invalid language, oversized portrait image) and a **plain string** for failures detected while creating the session (unknown `workflow_id`, blocklisted `vendor_data`, no reference face available for a Face Match workflow). Handle both shapes.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message for non-field errors (e.g. insufficient credits). Field-level failures use the field name as the key instead, with a string or array-of-strings value.", + "example": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + }, + "examples": { + "Missing workflow_id": { + "summary": "Required field omitted (array-of-strings shape)", + "value": { + "workflow_id": [ + "This field is required." + ] + } + }, + "Invalid workflow_id": { + "summary": "Unknown workflow_id (plain-string shape)", + "value": { + "workflow_id": "Invalid workflow_id." + } + }, + "Insufficient credits": { + "summary": "Organization is out of credits", + "value": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + }, + "Blocklisted vendor_data": { + "summary": "vendor_data is blocklisted", + "value": { + "vendor_data": "This user or business has been blocked and cannot create new verification sessions." + } + }, + "Portrait image too large": { + "summary": "portrait_image fails size validation", + "value": { + "portrait_image": [ + "Image size exceeds 2MB." + ] + } + }, + "No stored face for vendor_data": { + "summary": "Face Match workflow without portrait_image and no stored face for the vendor_data user", + "value": { + "portrait_image": "No stored face image was found for this user. Send a portrait_image, or complete an approved verification with face liveness or an ID document for this vendor_data first." + } + }, + "Portrait image required without vendor_data": { + "summary": "Face Match workflow without portrait_image and without vendor_data", + "value": { + "portrait_image": "A portrait image is required to perform face match for this workflow. Send a portrait_image, or provide the vendor_data of a user with a previously stored face image to reuse it." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failed. A wrong, revoked or expired key returns `401`. The endpoint returns `403` when the `x-api-key` header is missing or the API key is valid but the client does not have permission to create sessions in this application (for example the API key belongs to a different organization). These failures share the body `{\"detail\": \"You do not have permission to perform this action.\"}`. Check that the `x-api-key` header is present and belongs to the intended application, re-fetch the application API key via the [Auth API](/auth-api/get-credentials) if needed, and retry.\n\n**Deleted application:** for a few minutes after an application is deleted, a request made with one of its API keys can answer `{\"detail\": \"This application was deleted.\", \"code\": \"application_deleted\"}`. Do not retry: the application and its API keys no longer exist. After those few minutes, the key is refused like any other invalid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You do not have permission to perform this action." + }, + "code": { + "type": "string", + "description": "Machine-readable reason, present only on some failures. `application_deleted`: the API key belongs to an application that was deleted.", + "example": "application_deleted" + } + }, + "required": [ + "detail" + ] + }, + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header or invalid API key", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "No permission": { + "summary": "Client cannot create sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Application deleted": { + "summary": "API key of a deleted application", + "value": { + "detail": "This application was deleted.", + "code": "application_deleted" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded — more than 600 session-create requests in a 60-second window from the same API key / IP. Inspect `Retry-After` and back off before retrying.\n\n**Note:** The source code also defines a `FREE_SESSION_RATE_LIMIT` of 10 calls/minute alongside `PAID_SESSION_RATE_LIMIT=600` (`sessions/serializers/session.py`), but that free-tier ceiling is **not applied to this V3 endpoint** — the rate-limit middleware enforces the 600/min `SESSION_CREATE_RATE_LIMIT` regardless of plan. 600/min is the only limit you can hit here.", + "headers": { + "Retry-After": { + "description": "Seconds the caller must wait before retrying the request.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 42 + } + }, + "X-RateLimit-Limit": { + "description": "Maximum number of requests allowed in the current 60-second window for the limit that was breached (600 for session-create). Absent on the sandbox-quota variant.", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Number of requests still allowed in the current window for the breached limit. Absent on the sandbox-quota variant.", + "schema": { + "type": "integer", + "minimum": 0, + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "UTC epoch seconds when the current rate-limit window resets. Absent on the sandbox-quota variant.", + "schema": { + "type": "integer", + "minimum": 0, + "example": 1747497600 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable explanation of the rate-limit breach. Use this message to distinguish which of the three limits was hit.", + "example": "Session creation rate limit exceeded. You can make up to 600 requests per minute." + } + } + }, + "examples": { + "Session-create limit": { + "summary": "More than 600 session-create requests per minute", + "value": { + "detail": "Session creation rate limit exceeded. You can make up to 600 requests per minute." + } + }, + "Sandbox session quota": { + "summary": "Sandbox application exceeded 500 sessions in 24 hours", + "value": { + "detail": "Sandbox session quota exceeded (500 sessions per 24h). Wait for the window to reset. Expected available in 3600 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/session/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"workflow_id\": \"11111111-2222-3333-4444-555555555555\",\n \"vendor_data\": \"user-123\",\n \"callback\": \"https://example.com/verification/callback\",\n \"callback_method\": \"both\",\n \"metadata\": {\"user_type\": \"premium\", \"account_id\": \"ABC123\"},\n \"language\": \"en\",\n \"contact_details\": {\n \"email\": \"john.doe@example.com\",\n \"send_notification_emails\": true,\n \"email_lang\": \"en\",\n \"phone\": \"+14155552671\"\n },\n \"expected_details\": {\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"date_of_birth\": \"1990-05-15\",\n \"id_country\": \"USA\",\n \"expected_document_types\": [\"P\", \"ID\"]\n }\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.create(\n workflow_id=\"11111111-2222-3333-4444-555555555555\",\n vendor_data=\"user-123\",\n callback=\"https://example.com/verification/callback\",\n callback_method=\"both\",\n metadata={\"user_type\": \"premium\", \"account_id\": \"ABC123\"},\n language=\"en\",\n contact_details={\n \"email\": \"john.doe@example.com\",\n \"send_notification_emails\": True,\n \"email_lang\": \"en\",\n \"phone\": \"+14155552671\",\n },\n expected_details={\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"date_of_birth\": \"1990-05-15\",\n \"nationality\": \"USA\",\n \"id_country\": \"USA\",\n \"expected_document_types\": [\"P\", \"ID\"],\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.create({\n workflow_id: \"11111111-2222-3333-4444-555555555555\",\n vendor_data: \"user-123\",\n callback: \"https://example.com/verification/callback\",\n callback_method: \"both\",\n metadata: { user_type: \"premium\", account_id: \"ABC123\" },\n language: \"en\",\n contact_details: {\n email: \"john.doe@example.com\",\n send_notification_emails: true,\n email_lang: \"en\",\n phone: \"+14155552671\",\n },\n expected_details: {\n first_name: \"John\",\n last_name: \"Doe\",\n date_of_birth: \"1990-05-15\",\n nationality: \"USA\",\n id_country: \"USA\",\n expected_document_types: [\"P\", \"ID\"],\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Create(ctx, didit.SessionsCreateParams{\n\tWorkflowID: \"11111111-2222-3333-4444-555555555555\",\n\tVendorData: didit.String(\"user-123\"),\n\tCallback: didit.String(\"https://example.com/verification/callback\"),\n\tCallbackMethod: didit.Some(didit.SessionsCreateParamsCallbackMethod(\"both\")),\n\tMetadata: didit.Some[any](map[string]any{\"user_type\": \"premium\", \"account_id\": \"ABC123\"}),\n\tLanguage: didit.Some(didit.SessionsCreateParamsLanguage(\"en\")),\n\tContactDetails: didit.Some(didit.SessionsCreateParamsContactDetails{\n\t\tEmail: didit.String(\"john.doe@example.com\"),\n\t\tSendNotificationEmails: didit.Bool(true),\n\t\tEmailLang: didit.Some(didit.SessionsCreateParamsContactDetailsEmailLang(\"en\")),\n\t\tPhone: didit.String(\"+14155552671\"),\n\t}),\n\tExpectedDetails: didit.Some(didit.SessionsCreateParamsExpectedDetails{\n\t\tFirstName: didit.String(\"John\"),\n\t\tLastName: didit.String(\"Doe\"),\n\t\tDateOfBirth: didit.String(\"1990-05-15\"),\n\t\tNationality: didit.String(\"USA\"),\n\t\tIDCountry: didit.String(\"USA\"),\n\t\tExpectedDocumentTypes: didit.Some([]didit.SessionsCreateParamsExpectedDetailsExpectedDocumentTypes{\"P\", \"ID\"}),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SessionsCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().create(SessionsCreateParams.builder()\n .workflowId(\"11111111-2222-3333-4444-555555555555\")\n .vendorData(\"user-123\")\n .callback(\"https://example.com/verification/callback\")\n .callbackMethod(SessionsCreateParams.CallbackMethod.of(\"both\"))\n .metadata(Map.of(\"user_type\", \"premium\", \"account_id\", \"ABC123\"))\n .language(SessionsCreateParams.Language.of(\"en\"))\n .contactDetails(SessionsCreateParams.ContactDetails.builder()\n .email(\"john.doe@example.com\")\n .sendNotificationEmails(true)\n .emailLang(SessionsCreateParams.ContactDetails.EmailLang.of(\"en\"))\n .phone(\"+14155552671\")\n .build())\n .expectedDetails(SessionsCreateParams.ExpectedDetails.builder()\n .firstName(\"John\")\n .lastName(\"Doe\")\n .dateOfBirth(LocalDate.parse(\"1990-05-15\"))\n .nationality(\"USA\")\n .idCountry(\"USA\")\n .expectedDocumentTypes(List.of(SessionsCreateParams.ExpectedDetails.ExpectedDocumentTypes.of(\"P\"), SessionsCreateParams.ExpectedDetails.ExpectedDocumentTypes.of(\"ID\")))\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->create([\n 'workflow_id' => '11111111-2222-3333-4444-555555555555',\n 'vendor_data' => 'user-123',\n 'callback' => 'https://example.com/verification/callback',\n 'callback_method' => 'both',\n 'metadata' => ['user_type' => 'premium', 'account_id' => 'ABC123'],\n 'language' => 'en',\n 'contact_details' => [\n 'email' => 'john.doe@example.com',\n 'send_notification_emails' => true,\n 'email_lang' => 'en',\n 'phone' => '+14155552671',\n ],\n 'expected_details' => [\n 'first_name' => 'John',\n 'last_name' => 'Doe',\n 'date_of_birth' => '1990-05-15',\n 'nationality' => 'USA',\n 'id_country' => 'USA',\n 'expected_document_types' => ['P', 'ID'],\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.create(\n workflow_id: \"11111111-2222-3333-4444-555555555555\",\n vendor_data: \"user-123\",\n callback: \"https://example.com/verification/callback\",\n callback_method: \"both\",\n metadata: { user_type: \"premium\", account_id: \"ABC123\" },\n language: \"en\",\n contact_details: {\n email: \"john.doe@example.com\",\n send_notification_emails: true,\n email_lang: \"en\",\n phone: \"+14155552671\",\n },\n expected_details: {\n first_name: \"John\",\n last_name: \"Doe\",\n date_of_birth: \"1990-05-15\",\n nationality: \"USA\",\n id_country: \"USA\",\n expected_document_types: [\"P\", \"ID\"],\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.CreateAsync(new SessionsCreateParams\n{\n WorkflowId = \"11111111-2222-3333-4444-555555555555\",\n VendorData = \"user-123\",\n Callback = \"https://example.com/verification/callback\",\n CallbackMethod = new SessionsCreateParamsCallbackMethod(\"both\"),\n Metadata = new Dictionary { [\"user_type\"] = \"premium\", [\"account_id\"] = \"ABC123\" },\n Language = new SessionsCreateParamsLanguage(\"en\"),\n ContactDetails = new SessionsCreateParamsContactDetails\n {\n Email = \"john.doe@example.com\",\n SendNotificationEmails = true,\n EmailLang = new SessionsCreateParamsContactDetailsEmailLang(\"en\"),\n Phone = \"+14155552671\",\n },\n ExpectedDetails = new SessionsCreateParamsExpectedDetails\n {\n FirstName = \"John\",\n LastName = \"Doe\",\n DateOfBirth = \"1990-05-15\",\n Nationality = \"USA\",\n IdCountry = \"USA\",\n ExpectedDocumentTypes = new SessionsCreateParamsExpectedDetailsExpectedDocumentTypes[] { \"P\", \"ID\" },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "workflow_id" + ], + "properties": { + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier of the workflow that defines which verification steps the session will run. Workflows are created and managed in the [Workflows](/console/workflows) page of the Console. The `workflow_id` also implicitly selects whether the session is KYC or KYB.", + "example": "11111111-2222-3333-4444-555555555555", + "x-readme-id": "0.0" + }, + "vendor_data": { + "type": "string", + "description": "A unique identifier for the user being verified, such as a UUID, email, or internal user ID. This field is used for: (1) **User grouping** — sessions with the same vendor_data are linked to the same user profile in the Users tab. (2) **Cross-session duplicate detection** — when checking for duplicated faces, documents, phone numbers, emails, IP addresses, or device fingerprints, sessions with the same vendor_data are treated as the same user and excluded from matches. Without vendor_data, every session is treated as a unique user and all potential duplicates are surfaced. We strongly recommend always providing a vendor_data to reduce noise in duplicate detection.", + "example": "user-123", + "x-readme-id": "0.1" + }, + "multi_account_segment": { + "type": "string", + "maxLength": 64, + "description": "Optional duplicate-detection realm inside your organization. Duplicate face checks compare this session only with other sessions that carry the same segment, so separate account bases (personal and business accounts, or one per B2B sub-client) do not flag a person who legitimately holds one account in each. Omit it to use the single default realm, which is what every session uses today. Blocklist and allowlist matching ignore this field.", + "example": "business-accounts" + }, + "callback": { + "type": "string", + "description": "URL to redirect the user to after verification completes. Didit automatically appends `verificationSessionId` and `status` (Approved, Declined, In Review) as query parameters. Custom URL schemes (e.g. `myapp://`) are supported for mobile callbacks. If omitted, the workflow's default `callback_url` is used. When the application has allowed domains (`allowed_domains` on `PATCH /v3/webhook/`), an `http` or `https` callback must be on one of those origins (same scheme, host and port), otherwise the request fails with 400 on `callback`: `callback must be on one of the application's allowed domains: https://app.example.com`. Custom URL schemes are always accepted.", + "example": "https://example.com/verification/callback", + "x-readme-id": "0.2" + }, + "callback_method": { + "type": "string", + "description": "Determines which device should handle the redirect to the provided callback URL. Use `initiator` to redirect only the device that started the flow, `completer` for the device that finishes it, or `both` to allow either device to trigger the callback. If you ever notice the callback not triggering reliably, we recommend setting this value to `both`.", + "enum": [ + "initiator", + "completer", + "both" + ], + "default": "initiator", + "example": "both", + "x-readme-id": "0.3" + }, + "metadata": { + "description": "Arbitrary JSON stored with the session and echoed back in the response and webhooks. Any JSON value is accepted (object, string, number, array), but a JSON object of key/value pairs is recommended. Not shown to the end user. Use it to pass your own correlation ids, A/B variants, or business context.", + "example": { + "user_type": "premium", + "account_id": "ABC123" + }, + "x-readme-id": "0.4" + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "maxLength": 50 + }, + "maxItems": 10, + "description": "Tags added to the session when it is created, by name. Up to 10, each up to 50 characters. Names are case-insensitive and stored in upper case. A name that does not exist yet in the application is created as a new tag. The tags show on the session in the console, where you can filter by them.", + "example": [ + "VIP", + "ONBOARDING Q4" + ] + }, + "language": { + "type": "string", + "description": "Language code (ISO 639-1) for the verification process interface. Controls the language displayed to the end user during verification. If not provided, the browser's language will be automatically detected and used. Check all the supported languages [here](/integration/supported-languages).", + "enum": [ + "en", + "ar", + "bg", + "bn", + "bs", + "ca", + "cnr", + "cs", + "da", + "de", + "el", + "es", + "et", + "fa", + "fi", + "fr", + "he", + "hi", + "hr", + "hu", + "hy", + "id", + "it", + "ja", + "ka", + "kk", + "ko", + "ky", + "lt", + "lv", + "mk", + "mn", + "ms", + "nl", + "no", + "pl", + "pt-BR", + "pt", + "ro", + "ru", + "sk", + "sl", + "so", + "sq", + "sr", + "sv", + "th", + "tr", + "uk", + "uz", + "vi", + "zh-CN", + "zh-TW", + "zh" + ], + "example": "en", + "x-readme-id": "0.5" + }, + "contact_details": { + "type": "object", + "description": "User contact information that can be used for notifications, prefilling verification forms, and phone verification. This includes email address, preferred language for communications, and phone number.", + "example": { + "email": "john.doe@example.com", + "send_notification_emails": true, + "email_lang": "en", + "phone": "+14155552671" + }, + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "Email address of the user (e.g., \"john.doe@example.com\") that will be used during the [Email Verification](/core-technology/email-verification/overview) step. If not provided, the user must provide it during the verification flow.", + "example": "john.doe@example.com", + "x-readme-id": "1.0" + }, + "send_notification_emails": { + "type": "boolean", + "default": false, + "description": "If true and an email is provided, Didit sends the initial \"Verify your identity\" email asynchronously when the User Verification (KYC) or Business Verification (KYB) session is created. Didit also sends verification status notifications for sessions requiring manual review to the provided email address (e.g., from 'In Review' to 'Approved' or 'Declined'). This helps users return to your application once their verification is complete. If you have white-label activated for the session, the email sent will be white-labeled.", + "example": true, + "x-readme-id": "1.1" + }, + "email_lang": { + "type": "string", + "description": "Language code (ISO 639-1) for email notifications. Controls the language of all email communications (e.g., \"en\", \"es\", \"fr\"). There is no stored default — when omitted, the verification invite email simply falls back to English (`en`) at send time. The enum below is a snapshot; the live source of truth for accepted values is the [supported languages](/integration/supported-languages) doc — always consult it before relying on a specific code.", + "enum": [ + "en", + "ar", + "bg", + "bn", + "bs", + "ca", + "cnr", + "cs", + "da", + "de", + "el", + "es", + "et", + "fa", + "fi", + "fr", + "he", + "hi", + "hr", + "hu", + "hy", + "id", + "it", + "ja", + "ka", + "kk", + "ko", + "ky", + "lt", + "lv", + "mk", + "mn", + "ms", + "nl", + "no", + "pl", + "pt-BR", + "pt", + "ro", + "ru", + "sk", + "sl", + "so", + "sq", + "sr", + "sv", + "th", + "tr", + "uk", + "uz", + "vi", + "zh-CN", + "zh-TW", + "zh" + ], + "example": "en", + "x-readme-id": "1.2" + }, + "phone": { + "type": "string", + "description": "Phone number in E.164 format (e.g., \"+14155552671\") that will be used during the [Phone Verification](/core-technology/phone-verification/overview) step. If not provided, the user must provide it during the verification flow. Ignored for Business Verification (KYB) workflows. **Important:** This phone number is only enforced if it is a valid E.164 phone number. If the provided number is invalid or cannot be parsed, it will be ignored and the user will be able to input any valid phone number during the Phone Verification step.", + "example": "+14155552671", + "x-readme-id": "1.3" + } + }, + "x-readme-id": "0.4" + }, + "expected_details": { + "type": "object", + "description": "Expected user details used to cross-validate against the data extracted from the user's ID document, Proof of Address, and other verification steps. Mismatches are surfaced as warnings on the decision; some fields (e.g. `id_country`, `expected_document_types`) also alter the user-facing flow. For Business Verification (KYB) workflows only the business fields (`company_name`, `registry_country`, `registration_number`) are used — they pre-fill the company registry search for the user — and all person-level fields are ignored.", + "example": { + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1990-05-15", + "nationality": "USA", + "id_country": "USA", + "expected_document_types": [ + "P", + "ID" + ] + }, + "properties": { + "first_name": { + "type": "string", + "description": "User's first name. For example, `John`. The matching uses fuzzy comparison, and you can tune the strictness by configuring the name match score threshold in the Console for both ID Document and Proof of Address workflows.", + "example": "John", + "x-readme-id": "1.0" + }, + "last_name": { + "type": "string", + "description": "User's last name. For example, `Doe`. The matching uses fuzzy comparison, and you can tune the strictness by configuring the name match score threshold in the Console for both ID Document and Proof of Address workflows.", + "example": "Doe", + "x-readme-id": "1.1" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "User's date of birth with format: YYYY-MM-DD. For example, `1990-05-15`.", + "example": "1990-05-15", + "x-readme-id": "1.2" + }, + "gender": { + "type": "string", + "nullable": true, + "enum": [ + "M", + "F" + ], + "default": null, + "description": "User's gender. Must be either 'M', 'F', or null.", + "example": "M", + "x-readme-id": "1.3" + }, + "nationality": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the applicant's country of origin. For example, `USA`. See the [full list of supported country codes](https://docs.didit.me/core-technology/id-verification/supported-documents-id-verification#supported-documents-by-country).", + "example": "USA", + "x-readme-id": "1.4" + }, + "country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code used as a fallback for both ID Verification and Proof of Address country-mismatch checks. Required when `address` is provided and neither `id_country` nor `poa_country` is set.", + "example": "USA", + "x-readme-id": "1.4.1" + }, + "id_country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the expected country of the applicant's ID document, which may differ from nationality. For example, `GBR`. Takes priority over `country` for ID verification country mismatch checks. See the [full list of supported country codes](https://docs.didit.me/core-technology/id-verification/supported-documents-id-verification#supported-documents-by-country).", + "example": "GBR", + "x-readme-id": "1.5" + }, + "poa_country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the expected country of the Proof of Address document. For example, `USA`. Takes priority over `country` for POA country mismatch checks. See the [full list of supported country codes](https://docs.didit.me/core-technology/id-verification/supported-documents-id-verification#supported-documents-by-country).", + "example": "USA", + "x-readme-id": "1.5.1" + }, + "address": { + "type": "string", + "description": "The address in a human readable format, including as much information as possible. For example, `123 Main St, San Francisco, CA 94105, USA`. When provided, you must also pass `country` or `poa_country`, otherwise the request fails with `400`.", + "example": "123 Main St, San Francisco, CA 94105, USA", + "x-readme-id": "1.6" + }, + "identification_number": { + "type": "string", + "description": "The user's document number, personal number, or tax number. For example, `123456789`.", + "example": "123456789", + "x-readme-id": "1.7" + }, + "ip_address": { + "type": "string", + "description": "Expected IP address for the session, in IPv4 or IPv6 format. If the actual IP address differs from this value, a warning will be logged. For example, `192.168.1.100` or `2001:db8::1`.", + "example": "192.168.1.100", + "x-readme-id": "1.8" + }, + "expected_document_types": { + "type": "array", + "description": "Restricts the document types the user can present during the ID verification (OCR) step. When set, the document selection screen only shows the requested types and the returned `documents_allowed` is filtered accordingly. Values are case-insensitive and deduplicated; unknown values are rejected with `400`. Allowed codes: `P` (passport), `ID` (national ID), `DL` (driver's license), `RP` (residence permit), `HIC` (health insurance card), `TC` (tax card), `SSC` (social security card).", + "items": { + "type": "string", + "enum": [ + "P", + "ID", + "DL", + "RP", + "HIC", + "TC", + "SSC" + ] + }, + "example": [ + "P", + "ID" + ], + "x-readme-id": "1.9" + }, + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Expected legal name of the business being verified. For example, `Tesco PLC`. Pre-fills the company-name field of the registry search in Business Verification (KYB) workflows; when `registration_number` is also provided, the registry lookup combines both identifiers. Ignored for KYC workflows.", + "example": "Tesco PLC", + "x-readme-id": "1.10" + }, + "registry_country": { + "type": "string", + "maxLength": 10, + "description": "ISO 3166-1 alpha-2 code of the country whose company registry the business is registered in. For example, `GB`. Where company registries operate at state or province level, append the ISO 3166-2 subdivision code as `XX-YY`: `US-CA` targets the California registry. Same format as `country_code` on the [KYB Registry Search API](https://docs.didit.me/standalone-apis/kyb-registry-search). Pre-selects the registry country in Business Verification (KYB) workflows; ignored for KYC workflows.", + "example": "US-CA", + "x-readme-id": "1.11" + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Expected registration number of the business in the company registry. For example, `00445790`. Pre-fills the registration-number field of the registry search in Business Verification (KYB) workflows; when `company_name` is also provided, the registry lookup combines both identifiers. Ignored for KYC workflows.", + "example": "00445790", + "x-readme-id": "1.12" + } + }, + "x-readme-id": "0.5" + }, + "portrait_image": { + "type": "string", + "format": "byte", + "description": "Base64-encoded portrait image of the end user's face (max 2MB; JPEG, PNG, WebP, or TIFF). Used as the reference image to match against the liveness capture when the workflow is a `Biometric Authentication` workflow with Face Match enabled, or any graph workflow where Face Match runs before ID Verification (OCR). Optional when the `vendor_data` user already has a stored face image: Didit reuses it automatically, resolved in priority order: approved liveness face, ePassport chip photo, ID document portrait, manually enrolled profile face (only faces from approved sessions or faces you enrolled yourself are reused). When provided, `portrait_image` always takes precedence over the stored face. If omitted and no stored face exists (or `vendor_data` is not sent), session creation fails with `400`. Ignored for other workflow types.", + "example": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", + "x-readme-id": "0.6" + }, + "sandbox_scenario": { + "type": "string", + "description": "Sandbox-only scenario slug. Only accepted when the API key belongs to a sandbox-mode application; rejected on live applications. When set, the create flow expands the scenario's bundled magic values onto the session's input fields (contact_details, expected_details) so the mocked providers reproduce the scenario's outcome through the real workflow pipeline. See the scenario catalog at GET /v1/sandbox/scenarios/ or [Sandbox & Test Data](/integration/sandbox-testing). You can also arm (or re-arm) a scenario after create via POST /v3/session/{sessionId}/sandbox/arm/, as long as the session is still `Not Started`. For non-doc lookup, use `lookup_partial_match`, `lookup_no_match`, or `lookup_provider_error` to test the configured attempt budget and fallback. For identity wallets, use `wallet_cancelled`, `wallet_timeout`, or `wallet_provider_error`; `approve` simulates a verified wallet. All catalog wallets can be tested in sandbox, including schemes unavailable on live applications. Simulated attempts are not charged.", + "example": "decline_document_expired" + }, + "sign": { + "type": "object", + "description": "E-signature input for this session. `documents` are the documents to sign, for a workflow whose E-signature step takes documents from the session (`signature_documents_source` `session` or `both`): up to 20 documents and 50 MB in total. `field_values` fills the fields whose `prefill` is `api.`, on workflow and session documents alike. Creating a session again for the same `workflow_id` and `vendor_data` returns the pending session; if that call sends `sign`, its documents, fields and values must be identical to the first ones, and anything different returns `400` with `{\"sign\": \"signature_document_changed\"}`. See [E-signature documents](/sessions-api/create-session#e-signature-documents).", + "properties": { + "documents": { + "type": "array", + "minItems": 1, + "maxItems": 20, + "items": { + "$ref": "#/components/schemas/SessionSignDocument" + } + }, + "field_values": { + "type": "object", + "description": "Values keyed by the `` of each `api.` pre-fill source: strings of 1 to 500 characters for `text` and `data` fields, booleans for a `checkbox`. `400` with `field_value_unknown` for a key no field uses, `field_value_missing` for a required field without its value, and `field_value_invalid` for a value that fails its field's validation.", + "additionalProperties": { + "oneOf": [ + { + "type": "string", + "minLength": 1, + "maxLength": 500 + }, + { + "type": "boolean" + } + ] + }, + "example": { + "credit_limit": "3,000 EUR" + } + } + } + }, + "user_state": { + "type": "string", + "maxLength": 16, + "description": "Where the person lives, when you know it: an ISO 3166-2 subdivision code (`US-IL`, `ES-M`) or a US state code or name (`IL`, `Illinois`). A state whose law requires explicit biometric consent (Illinois, Texas, Colorado) makes the verification flow ask for it before capturing a face, with trigger `declared_state`. An unrecognized value returns `400`. See [Biometric consent](/getting-started/biometric-consent).", + "example": "US-IL" + }, + "biometric_consent": { + "type": "object", + "description": "Explicit biometric consent you already collected from the person. It satisfies the requirement, so the verification flow does not ask again, and the decision records it as `status: \"given\"` with trigger `customer_collected`. An answer the person already gave in the flow is never replaced. See [Biometric consent](/getting-started/biometric-consent).", + "required": [ + "given_at", + "text_version" + ], + "properties": { + "given_at": { + "type": "string", + "format": "date-time", + "description": "When the person gave explicit biometric consent (ISO 8601). Cannot be in the future." + }, + "text_version": { + "type": "string", + "maxLength": 64, + "description": "Version id of the consent text the person accepted, in your own records." + } + } + } + }, + "example": { + "workflow_id": "11111111-2222-3333-4444-555555555555", + "vendor_data": "user-123", + "callback": "https://example.com/verification/callback", + "callback_method": "both", + "metadata": { + "user_type": "premium", + "account_id": "ABC123" + }, + "language": "en", + "contact_details": { + "email": "john.doe@example.com", + "send_notification_emails": true, + "email_lang": "en", + "phone": "+14155552671" + }, + "expected_details": { + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1990-05-15", + "id_country": "USA", + "expected_document_types": [ + "P", + "ID" + ] + } + } + }, + "examples": { + "Create KYC session": { + "summary": "Full happy-path payload for a User Verification (KYC) session", + "description": "Realistic create call that uses every commonly-set field: workflow selection, vendor linkage, callback routing, metadata, locale, contact prefill, and expected user details. Copy-paste this directly into the cURL sample above.", + "value": { + "workflow_id": "11111111-2222-3333-4444-555555555555", + "vendor_data": "user-123", + "callback": "https://example.com/verification/callback", + "callback_method": "both", + "metadata": { + "user_type": "premium", + "account_id": "ABC123" + }, + "language": "en", + "contact_details": { + "email": "john.doe@example.com", + "send_notification_emails": true, + "email_lang": "en", + "phone": "+14155552671" + }, + "expected_details": { + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1990-05-15", + "nationality": "USA", + "id_country": "USA", + "expected_document_types": [ + "P", + "ID" + ] + } + } + }, + "Create KYB session": { + "summary": "Happy-path payload for a Business Verification (KYB) session", + "description": "Same shape as the KYC call — the `workflow_id` is what makes the session KYB. `vendor_data` typically references the company in your system and `contact_details.email` targets the legal representative who will complete the flow. `expected_details` accepts the business fields (`company_name`, `registry_country`, `registration_number`), which pre-fill the company registry search; person-level fields and `contact_details.phone` are ignored for KYB workflows.", + "value": { + "workflow_id": "33333333-4444-5555-6666-777777777777", + "vendor_data": "company-acme-001", + "callback": "https://example.com/kyb/callback", + "callback_method": "both", + "metadata": { + "tier": "enterprise", + "company_id": "ACME-001" + }, + "expected_details": { + "company_name": "Tesco PLC", + "registry_country": "GB", + "registration_number": "00445790" + }, + "language": "en", + "contact_details": { + "email": "legal-rep@acme.example.com", + "send_notification_emails": true, + "email_lang": "en" + } + } + } + } + } + } + } + } + }, + "/v3/session/imports/": { + "post": { + "summary": "Create a verification import job", + "description": "Create an asynchronous import job for historical User Verification sessions, Business Verification sessions, transaction monitoring records, or workflow custom status rules. Set `source=provider` to migrate from another provider: Didit reads Sumsub, Onfido, Persona and ComplyCube with your read-only key, reads Veriff, iDenfy and MetaMap records by the IDs you upload in `file`, and imports an export file in Didit's format for the other providers. Record IDs are namespaced by provider (`sumsub:`) so a re-run skips everything already in Didit, and a record imported through a file with the provider's bare ID is recognised too, in both directions, so moving between a file import and a provider migration never imports a record twice. An application can run up to 4 import jobs at once; a job interrupted by a deploy or a worker restart continues from its last imported row by itself. Imports are disabled by default and must be enabled per organization by Didit.", + "operationId": "post_v3_session_imports_create", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "import_type": { + "type": "string", + "enum": [ + "user_verification", + "business_verification", + "status_rules", + "transactions" + ], + "default": "user_verification", + "description": "What to import: historical User Verification sessions, historical Business Verification sessions, workflow custom status rules, or historical transaction monitoring records." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Workflow UUID that imported sessions or status rules should be attached to. Required for user_verification, business_verification, and status_rules. Omit for transactions." + }, + "source_format": { + "type": "string", + "enum": [ + "csv", + "ndjson" + ], + "description": "Optional. Inferred from the uploaded file extension when omitted." + }, + "file": { + "type": "string", + "format": "binary", + "description": "Canonical CSV or NDJSON file. Use NDJSON for large imports. Maximum upload size 25 MB (use `source_file_url` for bigger files). Mutually exclusive with `source_file_url`." + }, + "source_file_url": { + "type": "string", + "format": "uri", + "description": "Alternative to file upload. Must be an HTTPS URL resolving to a public address (private/loopback networks are rejected). Mutually exclusive with `file`." + }, + "source": { + "type": "string", + "enum": [ + "file", + "provider" + ], + "description": "`provider` makes this a migration from another identity verification provider. Omit for a file import." + }, + "provider": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ], + "description": "Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`." + }, + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "options": { + "type": "object", + "properties": { + "region": { + "type": "string", + "description": "The provider region, one of its `regions` (Onfido: `eu`, `us`, `ca`)." + } + } + }, + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + }, + "description": "Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported." + }, + "dry_run": { + "type": "boolean", + "default": false, + "description": "Count what the migration would import (by status, already in Didit, same person, not finished) and write nothing. The job ends `READY`; start it with `POST /v3/session/imports/{importId}/start/`." + }, + "face_templates": { + "type": "boolean", + "default": false, + "description": "Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`." + }, + "lawful_basis_confirmed": { + "type": "boolean", + "default": false, + "description": "Confirms you have a lawful basis to share these selfies with Didit as processor." + } + } + } + }, + "application/json": { + "schema": { + "type": "object", + "required": [ + "source_file_url" + ], + "properties": { + "import_type": { + "type": "string", + "enum": [ + "user_verification", + "business_verification", + "status_rules", + "transactions" + ], + "default": "user_verification", + "description": "What to import: historical User Verification sessions, historical Business Verification sessions, workflow custom status rules, or historical transaction monitoring records." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Workflow UUID that imported sessions or status rules should be attached to. Required for user_verification, business_verification, and status_rules. Omit for transactions." + }, + "source_format": { + "type": "string", + "enum": [ + "csv", + "ndjson" + ], + "description": "Optional. Inferred from the URL's file extension when omitted." + }, + "source_file_url": { + "type": "string", + "format": "uri", + "description": "Alternative to file upload. Must be an HTTPS URL resolving to a public address (private/loopback networks are rejected). Mutually exclusive with `file`." + }, + "source": { + "type": "string", + "enum": [ + "file", + "provider" + ], + "description": "`provider` makes this a migration from another identity verification provider. Omit for a file import." + }, + "provider": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ], + "description": "Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`." + }, + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "options": { + "type": "object", + "properties": { + "region": { + "type": "string", + "description": "The provider region, one of its `regions` (Onfido: `eu`, `us`, `ca`)." + } + } + }, + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + }, + "description": "Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported." + }, + "dry_run": { + "type": "boolean", + "default": false, + "description": "Count what the migration would import (by status, already in Didit, same person, not finished) and write nothing. The job ends `READY`; start it with `POST /v3/session/imports/{importId}/start/`." + }, + "face_templates": { + "type": "boolean", + "default": false, + "description": "Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`." + }, + "lawful_basis_confirmed": { + "type": "boolean", + "default": false, + "description": "Confirms you have a lawful basis to share these selfies with Didit as processor." + } + } + }, + "examples": { + "URL-based import": { + "value": { + "import_type": "user_verification", + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "source_file_url": "https://files.example.com/exports/sessions.ndjson" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Import job accepted. Poll the returned `uuid` until `status` is `COMPLETED` or `FAILED`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + }, + "examples": { + "Job accepted": { + "value": { + "uuid": "3f9c1a2b-4d5e-4f60-8a7b-9c0d1e2f3a4b", + "status": "PENDING", + "import_type": "user_verification", + "source_format": "ndjson", + "source_filename": "sessions.ndjson", + "total_rows": 0, + "processed_rows": 0, + "checkpoint_row": 0, + "created_count": 0, + "updated_count": 0, + "skipped_count": 0, + "failed_count": 0, + "total_media_bytes": 0, + "last_error": null, + "created_at": "2026-05-17T08:42:11Z", + "updated_at": "2026-05-17T08:42:11Z", + "started_at": null, + "completed_at": null + } + } + } + } + } + }, + "400": { + "description": "Invalid workflow, file type, file size, source URL, or selector combination. A migration whose key the provider refuses answers `code` (`invalid_credentials`, `missing_permission`, `wrong_region`, `provider_unavailable`, `rate_limited`, `ids_required`, `record_not_found`), `missing_permissions` and `suggested_region`.", + "content": { + "application/json": { + "examples": { + "Invalid workflow": { + "value": { + "workflow_id": [ + "Invalid workflow_id." + ] + } + }, + "Workflow required": { + "value": { + "workflow_id": [ + "This field is required for this import type." + ] + } + }, + "Wrong file type": { + "value": { + "file": [ + "Only CSV and NDJSON files are supported." + ] + } + }, + "File too large": { + "value": { + "file": [ + "Import file exceeds the maximum allowed size." + ] + } + }, + "No source": { + "value": { + "file": [ + "Upload a file or provide source_file_url." + ] + } + }, + "Both sources": { + "value": { + "file": [ + "Use either file or source_file_url, not both." + ] + } + } + } + } + } + }, + "403": { + "description": "Imports not enabled, sandbox application, or missing API key (a wrong, revoked or expired key gets `401`). Verification imports are disabled by default and must be enabled per organization by Didit.", + "content": { + "application/json": { + "examples": { + "Imports not enabled": { + "value": { + "detail": "Verification imports are not enabled for this organization." + } + }, + "Sandbox": { + "value": { + "detail": "Verification imports are not enabled for sandbox applications." + } + }, + "Bad API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "The application already has 4 active import jobs (PENDING/PROCESSING/PAUSED), the most it can run at once. Wait for one to finish before starting another.", + "content": { + "application/json": { + "examples": { + "Active job": { + "value": { + "detail": "This application already has 4 active import jobs, the most it can run at once. Wait for one to finish before starting another." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.create(\n import_type=\"user_verification\",\n workflow_id=\"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n source_file_url=\"https://files.example.com/exports/sessions.ndjson\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.create({\n import_type: \"user_verification\",\n workflow_id: \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n source_file_url: \"https://files.example.com/exports/sessions.ndjson\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Create(ctx, didit.SessionsImportsCreateParams{\n\tImportType: didit.Some(didit.SessionsImportsCreateParamsImportType(\"user_verification\")),\n\tWorkflowID: didit.String(\"9f9b1234-aaaa-bbbb-cccc-1234567890ab\"),\n\tSourceFileURL: didit.String(\"https://files.example.com/exports/sessions.ndjson\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsImportsCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().create(SessionsImportsCreateParams.builder()\n .importType(SessionsImportsCreateParams.ImportType.of(\"user_verification\"))\n .workflowId(\"9f9b1234-aaaa-bbbb-cccc-1234567890ab\")\n .sourceFileUrl(\"https://files.example.com/exports/sessions.ndjson\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->create([\n 'import_type' => 'user_verification',\n 'workflow_id' => '9f9b1234-aaaa-bbbb-cccc-1234567890ab',\n 'source_file_url' => 'https://files.example.com/exports/sessions.ndjson',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.create(\n import_type: \"user_verification\",\n workflow_id: \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n source_file_url: \"https://files.example.com/exports/sessions.ndjson\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.CreateAsync(new SessionsImportsCreateParams\n{\n ImportType = new SessionsImportsCreateParamsImportType(\"user_verification\"),\n WorkflowId = \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n SourceFileUrl = \"https://files.example.com/exports/sessions.ndjson\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/template/": { + "get": { + "summary": "Download verification import template", + "description": "Download the canonical CSV header for verification imports. The column set is determined by `import_type`. The first column of session templates is the idempotency key `external_id`.", + "operationId": "get_v3_session_imports_template", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "retrieve_template", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "import_type", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "user_verification", + "business_verification", + "status_rules", + "transactions" + ], + "default": "user_verification" + } + } + ], + "responses": { + "200": { + "description": "CSV template file.", + "content": { + "text/csv": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot download import templates. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "summary": "Missing, invalid, or revoked API key", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.sessions.imports.retrieve_template()\nwith open(\"download.csv\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.sessions.imports.retrieveTemplate();\nawait writeFile(\"download.csv\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Sessions.Imports.RetrieveTemplate(ctx, didit.SessionsImportsRetrieveTemplateParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.csv\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().imports().retrieveTemplate()\n .writeTo(Path.of(\"download.csv\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->retrieveTemplate();\n$content->saveTo('download.csv');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.sessions.imports.retrieve_template\nFile.binwrite(\"download.csv\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Sessions.Imports.RetrieveTemplateAsync();\nawait content.SaveToAsync(\"download.csv\");" + } + ] + } + }, + "/v3/session/imports/{importId}/": { + "get": { + "summary": "Retrieve a verification import job", + "description": "Retrieve import progress counters and terminal status for one job. Poll until `status` is `COMPLETED` or `FAILED`; `failed_count` > 0 means some rows were rejected — list them via the errors endpoint.", + "operationId": "get_v3_session_imports_retrieve", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Import job status.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "404": { + "description": "Import job not found for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read import jobs for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "summary": "Missing, invalid, or revoked API key", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/{importId}/errors/": { + "get": { + "summary": "List verification import row errors", + "description": "List row-level errors for a verification import job, ordered by `row_number`. Use this endpoint to produce a corrected retry file containing only failed rows.", + "operationId": "get_v3_session_imports_errors", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "list_errors", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size. Defaults to 50 when omitted." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0 + }, + "description": "Zero-based offset of the first record to return." + } + ], + "responses": { + "200": { + "description": "Paginated row errors.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of error rows — exact up to 100, capped at 100 beyond that. Rely on `next` being `null` to detect the last page." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionImportError" + } + } + } + }, + "examples": { + "Row errors": { + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d", + "row_number": 17, + "external_id": "ext-000017", + "error": "status: invalid value 'unknown_status'", + "raw_row": { + "external_id": "ext-000017", + "status": "unknown_status", + "first_name": "Jane" + }, + "created_at": "2026-05-17T08:44:02Z" + } + ] + } + } + } + } + } + }, + "404": { + "description": "Import job not found for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read import jobs for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "summary": "Missing, invalid, or revoked API key", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.sessions.imports.list_errors(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.sessions.imports.listErrors(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Sessions.Imports.ListErrorsAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsImportsListErrorsParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.sessions().imports().listErrors(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->listErrors('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.imports.list_errors(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Sessions.Imports.ListErrorsAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/session/{sessionId}/decision/": { + "get": { + "summary": "Retrieve the full decision for a verification session", + "description": "Retrieve the complete decision report for a verification session — the rolled-up session `status` plus one report per verification feature the workflow executed. This is the canonical read endpoint for both User Verification (KYC) and Business Verification (KYB) sessions: the same path serves both, and the top-level `session_kind` discriminator tells you which shape you received (`\"user\"` or `\"business\"`).\n\n**Every feature report is a plural array.** V3 workflows are graphs that can run the same feature more than once, so each block (`id_verifications`, `nfc_verifications`, `liveness_checks`, `face_matches`, `poa_verifications`, `document_ai_documents`, `phone_verifications`, `email_verifications`, `aml_screenings`, `ip_analyses`, `database_validations`, `questionnaire_responses`) is a JSON array whose items each carry a `node_id` identifying the workflow graph node that produced them. Read NFC as `response.nfc_verifications[0]` — never as a singular `nfc` field. A block is `null` until the workflow has run that feature at least once; features the workflow does not include never appear with data. E-signature results are in `signatures`, which is present only when the workflow has an E-signature step.\n\n**When to call.** The recommended pattern is event-driven: wait for the `status.updated` webhook that fires when the session reaches a decision (`Approved`, `Declined`, `In Review`), then call this endpoint once to fetch the full report. Polling also works — the endpoint can be called at any point in the session lifecycle and returns the data produced so far — but webhook-then-fetch is cheaper and faster. Media URLs in the response (document images, videos, PDFs) are short-lived presigned links: fetch them promptly or re-request the decision to get fresh ones, and do not persist them as long-term references.\n\n**Statuses.** The top-level `status` is the session lifecycle status (`Not Started`, `In Progress`, `Awaiting User`, `In Review`, `Approved`, `Declined`, `Resubmitted`, `Expired`, `Kyc Expired`, `Abandoned`). Each feature item additionally carries its own feature-level `status` (`Not Finished`, `Approved`, `Declined`, `In Review`, ...). A session can be `Approved` overall while an individual feature is `In Review`, depending on the workflow's decision rules.\n\n**Authentication and visibility.** Authenticate with the application API key in the `x-api-key` header (a console user Bearer token with the `read:sessions` permission also works). Only sessions owned by the authenticated application are visible. If your workflow restricts response attributes, non-allowed fields inside feature items are returned as `null` while `status`, `warnings`, and `node_id` are always preserved.\n\n**Rate limits.** GET requests are limited to 600 per minute per API key. Exceeding the limit returns `429` with `Retry-After` and `X-RateLimit-*` headers.", + "operationId": "get_v3_session_decision", + "x-didit-sdk": { + "resource": "sessions", + "method": "retrieve_decision", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "200": { + "description": "Full decision report. `session_kind` discriminates the two shapes: `user` sessions carry the KYC feature arrays shown below; `business` sessions instead carry `registry_checks`, `document_verifications` and `key_people_checks` alongside the shared arrays. Feature arrays are `null` until the workflow has run that feature; media URLs are short-lived presigned links.", + "content": { + "application/json": { + "examples": { + "Example": { + "summary": "Full Example", + "value": { + "session_id": "11111111-2222-3333-4444-555555555555", + "session_kind": "user", + "session_number": 43762, + "session_url": "https://verify.didit.me/session/11111111-2222-3333-4444-555555555555", + "status": "In Review", + "environment": "live", + "workflow_id": "11111111-2222-3333-4444-555555555555", + "features": [ + "ID_VERIFICATION", + "NFC", + "LIVENESS", + "FACE_MATCH", + "POA", + "PHONE", + "DATABASE_VALIDATION", + "AML", + "IP_ANALYSIS" + ], + "vendor_data": "11111111-1111-1111-1111-111111111111", + "metadata": { + "user_type": "premium", + "account_id": "ABC123" + }, + "expected_details": { + "first_name": "Carmen", + "last_name": "Española Española" + }, + "contact_details": { + "email": "carmen@example.com", + "email_lang": "es", + "send_notification_emails": false, + "phone": null + }, + "callback": "https://verify.didit.me/", + "id_verifications": [ + { + "node_id": "feature_ocr_1", + "status": "Approved", + "document_type": "Identity Card", + "document_subtype": "ID_CARD_GENERIC", + "document_number": "CAA000000", + "personal_number": "99999999R", + "portrait_image": "https:///portrait.jpg", + "front_image": "https:///front.jpg", + "front_video": "https:///front.mp4", + "back_image": "https:///back.jpg", + "back_video": "https:///back.mp4", + "full_front_image": "https:///full_front.jpg", + "full_back_image": "https:///full_back.jpg", + "front_image_camera_front": "https:///front_camera_front.jpg", + "back_image_camera_front": "https:///back_camera_front.jpg", + "date_of_birth": "1980-01-01", + "age": 45, + "expiration_date": "2031-06-02", + "date_of_issue": "2021-06-02", + "issuing_state": "ESP", + "issuing_state_name": "Spain", + "first_name": "Carmen", + "last_name": "Española Española", + "full_name": "Carmen Española Española", + "gender": "F", + "address": "Avda de Madrid 34, Madrid, Madrid", + "formatted_address": "Avda de Madrid 34, Madrid, Madrid 28822, Spain", + "place_of_birth": "Madrid", + "marital_status": "Single", + "nationality": "ESP", + "extra_fields": { + "dl_categories": [], + "blood_group": null + }, + "mrz": { + "surname": "ESPANOLA ESPANOLA", + "name": "CARMEN", + "country": "ESP", + "nationality": "ESP", + "birth_date": "800101", + "expiry_date": "310602", + "sex": "F", + "document_type": "ID", + "document_number": "CAA000000", + "optional_data": "99999999R", + "optional_data_2": "", + "birth_date_hash": "4", + "expiry_date_hash": "8", + "document_number_hash": "4", + "final_hash": "1", + "personal_number": "99999999R", + "warnings": [], + "errors": [], + "mrz_type": "TD1", + "mrz_string": "IDESPCAA000000499999999R<<<<<<\n8001014F3106028ESP<<<<<<<<<<<1\nESPANOLA/match_face.jpg", + "status": "Approved", + "is_blocklisted": false, + "is_allowlisted": false, + "api_service": null, + "source": "session" + } + ], + "front_image_quality_score": { + "focus_score": 85.3, + "brightness_score": 92.1, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 72.4, + "overall_score": 88.7 + }, + "back_image_quality_score": { + "focus_score": 79.1, + "brightness_score": 90.5, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 72.4, + "overall_score": 85.2 + }, + "extra_files": [ + "https:///extra_id_verification.jpg" + ], + "matches": [ + { + "session_id": "7d4f1f60-9c2e-4b8a-8f3d-2a1b5c6d7e8f", + "session_number": 1234, + "vendor_data": "user_ref_001", + "verification_date": "2024-06-15T10:30:00Z", + "user_details": { + "name": "John Smith", + "document_type": "ID", + "document_number": "CLM148418" + }, + "status": "Approved", + "is_blocklisted": false, + "api_service": null, + "front_image_url": "https:///document_front.jpg" + }, + { + "session_id": "b2c8e4d1-6a3f-4e7b-95c0-1d8f2a6b4c9e", + "session_number": 1235, + "vendor_data": null, + "verification_date": "2024-05-20T14:45:00Z", + "user_details": { + "name": "John Smith", + "document_type": "ID", + "document_number": "CLM148418" + }, + "status": "Declined", + "is_blocklisted": true, + "api_service": "ID_VERIFICATION", + "front_image_url": "https:///document_front_2.jpg" + } + ], + "warnings": [ + { + "feature": "ID_VERIFICATION", + "risk": "QR_NOT_DETECTED", + "additional_data": null, + "log_type": "information", + "short_description": "QR not detected", + "long_description": "The system couldn't find or read the QR code on the document, which is necessary for document verification. This could be due to poor image quality or an unsupported document type.", + "node_id": "feature_ocr_1" + } + ], + "verification_method": "document", + "assurance": "documentary", + "wallet_provider": null, + "fallback_from": null, + "id_lookup": null, + "wallet_verification": null, + "document_liveness": { + "portrait_replace": { + "score": 96.46, + "front": 96.46, + "back": 98.2, + "decline_threshold": 35, + "review_threshold": 70, + "bucket": "approve" + }, + "printed_copy": { + "score": 91.2, + "front": 91.2, + "back": 93.7, + "decline_threshold": 35, + "review_threshold": 77, + "bucket": "approve" + }, + "screen_replay": { + "score": 99.9, + "front": 99.9, + "back": 99.5, + "decline_threshold": 30, + "review_threshold": 70, + "bucket": "approve" + } + } + }, + { + "node_id": "feature_ocr_2", + "status": "Approved", + "issuing_state": "ZAF", + "issuing_state_name": "South Africa", + "first_name": "Thabo", + "last_name": "Mokoena", + "full_name": "Thabo Mokoena", + "date_of_birth": "1991-06-15", + "personal_number": "9106155043088", + "verification_method": "id_lookup", + "assurance": "data_match", + "wallet_provider": null, + "fallback_from": null, + "id_lookup": { + "source": "Department of Home Affairs", + "checked_at": "2026-09-04T09:12:44Z", + "attempts": 1, + "max_attempts": 2, + "outcome": "match", + "comparison": [ + { + "field": "identification_number", + "label": "ID number", + "provided": "9106155043088", + "record": "9106155043088", + "result": "match" + }, + { + "field": "full_name", + "label": "Full name", + "provided": "Thabo Mokoena", + "record": "Thabo Mokoena", + "result": "match" + }, + { + "field": "date_of_birth", + "label": "Date of birth", + "provided": "1991-06-15", + "record": "1991-06-15", + "result": "match" + } + ], + "registry_portrait": "https:///registry_portrait.jpg", + "face_match_score": 96.4, + "source_errors": [] + }, + "wallet_verification": null, + "warnings": [] + }, + { + "node_id": "feature_ocr_3", + "status": "Approved", + "issuing_state": "DNK", + "issuing_state_name": "Denmark", + "first_name": "Freja", + "last_name": "Nielsen", + "full_name": "Freja Nielsen", + "date_of_birth": "1988-03-02", + "verification_method": "wallet", + "assurance": "cryptographic", + "wallet_provider": "mitid", + "fallback_from": null, + "id_lookup": null, + "wallet_verification": { + "provider": "mitid", + "provider_name": "MitID", + "issuing_authority": "Danish Agency for Digital Government", + "issuing_country": "DNK", + "credential_type": "person_identification", + "level_of_assurance": "substantial", + "verified_at": "2026-09-04T09:12:44Z", + "signature_valid": true, + "attributes": { + "full_name": "Freja Nielsen", + "date_of_birth": "1988-03-02", + "cpr_alias": "b0f1c2d3-e4f5-4678-9abc-def012345678" + }, + "portrait": null, + "face_match_score": null + }, + "warnings": [] + } + ], + "nfc_verifications": [ + { + "node_id": "feature_nfc_1", + "status": "In Review", + "is_nfc_skipped": false, + "skip_reason": null, + "chip_protocol": "icao", + "jpki": null, + "portrait_image": "https:///portrait.jpg", + "signature_image": "https:///signature.jpg", + "chip_data": { + "document_type": "ID", + "issuing_country": "ESP", + "document_number": "123456789", + "expiration_date": "2030-01-01", + "first_name": "John", + "last_name": "Smith", + "birth_date": "1990-05-15", + "gender": "M", + "nationality": "ESP", + "address": "CALLE MAYOR 123 4B, MADRID, MADRID", + "place_of_birth": "MADRID, MADRID" + }, + "authenticity": { + "sod_integrity": true, + "dg_integrity": true + }, + "certificate_summary": { + "issuer": "Common Name: CSCA SPAIN, Serial Number: 3, Organization: DIRECCION GENERAL DE LA POLICIA, Country: ES", + "subject": "Common Name: DS n-eID SPAIN 2, Organizational Unit: PASSPORT, Organization: DIRECCION GENERAL DE LA POLICIA, Country: ES", + "serial_number": "118120836164494130086420187336801405660", + "not_valid_after": "2031-02-18 10:21:11", + "not_valid_before": "2020-11-18 10:21:11" + }, + "warnings": [ + { + "feature": "NFC", + "risk": "DATA_INCONSISTENT", + "additional_data": null, + "log_type": "warning", + "short_description": "OCR and NFC mrz code extracted are not the same", + "long_description": "The Optical Character Recognition (OCR) data and the NFC chip data don't match, indicating potential document tampering or data inconsistency.", + "node_id": "feature_nfc_1" + } + ] + } + ], + "liveness_checks": [ + { + "node_id": "feature_liveness_1", + "status": "Approved", + "method": "ACTIVE_3D", + "score": 89.92, + "reference_image": "https:///reference.jpg", + "video_url": "https:///video.mp4", + "age_estimation": 24.3, + "matches": [ + { + "session_id": "7d4f1f60-9c2e-4b8a-8f3d-2a1b5c6d7e8f", + "session_number": 1234, + "vendor_data": "user_ref_001", + "verification_date": "2024-06-15T10:30:00Z", + "user_details": { + "full_name": "John Smith", + "document_type": "ID", + "document_number": "CLM148418" + }, + "status": "Approved", + "is_blocklisted": false, + "is_allowlisted": false, + "api_service": null, + "source": "session", + "match_image_url": "https:///face_match.jpg", + "similarity_percentage": 98.5 + }, + { + "session_id": "3e9a7c21-5b48-4f0e-9c6a-8d2f4b1e0a35", + "session_number": 1100, + "vendor_data": "customer_xyz", + "verification_date": "2024-04-10T09:15:00Z", + "user_details": { + "full_name": "John Smith", + "document_type": "P", + "document_number": "DZ8375806" + }, + "status": "Approved", + "is_blocklisted": false, + "is_allowlisted": false, + "api_service": "PASSIVE_LIVENESS", + "source": "session", + "match_image_url": "https:///face_match_2.jpg", + "similarity_percentage": 95.2 + } + ], + "warnings": [ + { + "feature": "LIVENESS", + "risk": "LOW_LIVENESS_SCORE", + "additional_data": null, + "log_type": "information", + "short_description": "Low liveness score", + "long_description": "The liveness check resulted in a low score, indicating potential use of non-live facial representations or poor-quality biometric data.", + "node_id": "feature_liveness_1" + } + ], + "face_quality": null, + "face_luminance": null + } + ], + "face_matches": [ + { + "node_id": "feature_face_match_1", + "status": "In Review", + "score": 65.43, + "source_image_session_id": null, + "source_image": "https:///source-image.jpg", + "target_image": "https:///target-image.jpg", + "warnings": [ + { + "feature": "FACEMATCH", + "risk": "LOW_FACE_MATCH_SIMILARITY", + "additional_data": null, + "log_type": "warning", + "short_description": "Low face match similarity", + "long_description": "The facial features of the provided image don't closely match the reference image, suggesting a potential identity mismatch.", + "node_id": "feature_face_match_1" + } + ] + } + ], + "phone_verifications": [ + { + "node_id": "feature_phone_1", + "status": "Approved", + "phone_number_prefix": "+34", + "phone_number": "600600600", + "full_number": "+34600600600", + "country_code": "ES", + "country_name": "Spain", + "carrier": { + "name": "Orange", + "type": "mobile" + }, + "is_disposable": false, + "is_virtual": false, + "verification_method": "sms", + "verification_attempts": 1, + "verified_at": "2024-07-28T06:47:35.654321Z", + "lifecycle": [ + { + "type": "PHONE_VERIFICATION_MESSAGE_SENT", + "timestamp": "2025-08-24T09:12:39.580554+00:00", + "details": { + "status": "Success", + "reason": null, + "channel": "sms", + "actual_channel": "sms" + }, + "fee": 0.1 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2025-08-24T09:12:39.662157+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "PHONE_VERIFICATION_APPROVED", + "timestamp": "2025-08-24T09:12:39.684292+00:00", + "details": null, + "fee": 0 + } + ], + "warnings": [], + "matches": [ + { + "session_id": null, + "session_number": null, + "vendor_data": null, + "verification_date": null, + "phone_number": "+34600000000", + "status": null, + "is_blocklisted": true, + "api_service": null, + "source": "list_entry" + } + ] + } + ], + "email_verifications": [ + { + "node_id": "feature_email_1", + "status": "Approved", + "email": "test@example.com", + "is_breached": true, + "breaches": [ + { + "name": "TAPAirPortugal", + "domain": "flytap.com", + "logo_path": "https://logos.example.com/TAPAirPortugal.png", + "breach_date": "2022-08-25", + "description": "In August 2022, the Portuguese airline TAP Air Portugal was the target of a ransomware attack perpetrated by the Ragnar Locker gang who later leaked the compromised data via a public dark web site. Over 5M unique email addresses were exposed alongside other personal data including names, genders, DoBs, phone numbers and physical addresses.", + "is_verified": true, + "data_classes": [ + "dates_of_birth", + "email_addresses", + "genders", + "names", + "nationalities", + "phone_numbers", + "physical_addresses", + "salutations", + "spoken_languages" + ], + "breach_emails_count": 6083479 + } + ], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 1, + "verified_at": "2025-09-15T17:36:19.963451Z", + "warnings": [ + { + "feature": "EMAIL", + "risk": "BREACHED_EMAIL_DETECTED", + "additional_data": null, + "log_type": "information", + "short_description": "Breached email detected", + "long_description": "This email address was found in one or more known data breaches.", + "node_id": "feature_email_1" + } + ], + "lifecycle": [ + { + "type": "EMAIL_VERIFICATION_MESSAGE_SENT", + "timestamp": "2025-09-15T17:36:19.792684+00:00", + "details": { + "status": "Success", + "reason": null + }, + "fee": 0.02 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2025-09-15T17:36:19.963427+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "EMAIL_VERIFICATION_DECLINED", + "timestamp": "2025-09-15T17:36:20.081471+00:00", + "details": { + "reason": "BREACHED_EMAIL_DETECTED" + }, + "fee": 0 + } + ], + "matches": [ + { + "session_id": null, + "session_number": null, + "vendor_data": null, + "verification_date": null, + "email": "test@example.com", + "status": null, + "is_blocklisted": true, + "api_service": null, + "source": "list_entry" + } + ] + } + ], + "poa_verifications": [ + { + "node_id": "feature_poa_1", + "status": "Declined", + "document_file": "https:///poa_document.pdf", + "issuing_state": "AR", + "document_type": "UTILITY_BILL", + "document_subtype": null, + "document_language": "es", + "document_metadata": { + "file_size": 1000, + "content_type": "application/pdf", + "creation_date": "2021-01-01", + "modified_date": "2021-01-01", + "overlay_manipulation": null, + "creator": null, + "producer": "PDF Generator", + "software": null, + "encryption": null, + "is_signed": false, + "is_tampered": false, + "signature_info": null, + "exif_original_date": null, + "exif_digitized_date": null, + "processed_by_known_editor": false, + "has_different_creation_mod_date": true + }, + "issuer": "Aguas del Norte", + "issue_date": "2021-02-01", + "expiration_date": null, + "poa_address": "AVDA. MONSEÑOR TAVELLA N° 3396, SALTA", + "poa_formatted_address": "Av. Monseñor Tavella 3396, A4400 Salta, Argentina", + "poa_parsed_address": { + "address_type": "Avenida", + "street_1": "Avenida Monseñor Tavella 3396", + "street_2": null, + "city": "Salta", + "region": "Salta", + "country": "AR", + "postal_code": "A4400", + "document_location": { + "latitude": -24.8208664, + "longitude": -65.4131 + } + }, + "name_on_document": "Fernando De Lima", + "name_match_score_expected_details": null, + "name_match_score_id_verification": 38.5, + "expected_details_address": null, + "expected_details_formatted_address": null, + "expected_details_parsed_address": {}, + "extra_fields": { + "bank_account_number": "1234567890", + "bank_iban": null, + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": "+5491112345678", + "additional_names": [] + }, + "extra_files": [ + "https:///extra_poa.pdf" + ], + "detected_codes": [ + { + "type": "QR_CODE", + "page_number": 1, + "position": [ + [ + 820, + 1120 + ], + [ + 1002, + 1120 + ], + [ + 1002, + 1302 + ], + [ + 820, + 1302 + ] + ], + "x": 820, + "y": 1120, + "width": 182, + "height": 182, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "aHR0cHM6Ly9hZ3Vhc2RlbG5vcnRlLmNvbS9mYWN0dXJhLzk5ODgxMQ==", + "parsed_payload": "https://aguasdelnorte.com/factura/998811" + }, + { + "type": "CODE_128", + "page_number": 1, + "position": [ + [ + 112, + 1480 + ], + [ + 560, + 1480 + ], + [ + 560, + 1540 + ], + [ + 112, + 1540 + ] + ], + "x": 112, + "y": 1480, + "width": 448, + "height": 60, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "MDA5OTg4MTEwMDA0Mg==", + "parsed_payload": "0099881100042" + } + ], + "warnings": [] + } + ], + "document_ai_documents": [ + { + "status": "Approved", + "node_id": "feature_document_ai_1", + "items": [ + { + "uuid": "c1a2b3c4-d5e6-7f80-9a1b-2c3d4e5f6071", + "node_id": "feature_document_ai_1", + "document_key": "proof_of_funds", + "original_filename": "bank_statement.pdf", + "file_size": 184320, + "file_url": "https:///document_ai/proof_of_funds.pdf", + "status": "Approved", + "extracted_data": { + "account_holder": "John Doe", + "balance": 15230.55, + "currency": "EUR", + "statement_date": "2026-05-31" + }, + "detected_codes": [ + { + "type": "QR_CODE", + "page_number": 2, + "position": [ + [ + 124, + 86 + ], + [ + 306, + 86 + ], + [ + 306, + 268 + ], + [ + 124, + 268 + ] + ], + "x": 124, + "y": 86, + "width": 182, + "height": 182, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "SU5WLTIwMjYtMDA0Mg==", + "parsed_payload": "INV-2026-0042" + } + ], + "fields": [ + { + "key": "account_holder", + "name": "Account holder", + "instruction": "Extract the full name of the account holder.", + "type": "text", + "required": true + }, + { + "key": "balance", + "name": "Closing balance", + "instruction": "Extract the closing balance amount.", + "type": "number", + "required": true + } + ], + "document_metadata": { + "is_tampered": false + }, + "cross_check_result": null, + "created_at": "2026-06-01T12:34:56Z" + } + ], + "warnings": [] + } + ], + "questionnaire_responses": [ + { + "node_id": "feature_questionnaire_1", + "questionnaire_id": "9f7485e0-b00b-4d56-9d3e-7859b68d1213", + "title": "Source of Funds", + "description": "Reply to this questionnaire to help us understand the source of funds.", + "languages": [ + "en" + ], + "default_language": "en", + "is_active": true, + "is_simple_questionnaire": true, + "questionnaire_group_id": "2b6a9d40-13a8-4b1e-9a6c-3f1d2e4b5a60", + "version": 1, + "published_at": "2025-11-02T09:14:55.000000Z", + "sections": [ + { + "title": null, + "description": null, + "items": [ + { + "uuid": "618636d9-7d5c-41ce-b8d0-1ab2a4be20db", + "value": "question_1", + "element_type": "DROPDOWN", + "is_required": true, + "title": "What is your primary source of funds?", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Salary", + "value": "salary" + }, + { + "label": "Business Income", + "value": "business_income" + }, + { + "label": "Savings", + "value": "savings" + }, + { + "label": "Investments", + "value": "investments" + }, + { + "label": "Sale of Asset", + "value": "sale_of_asset" + }, + { + "label": "Inheritance/Gift", + "value": "inheritance_gift" + }, + { + "label": "Pension", + "value": "pension" + }, + { + "label": "Crypto Proceeds", + "value": "crypto_proceeds" + }, + { + "label": "Other", + "value": "other", + "requires_text_input": true + } + ], + "max_files": 1, + "answer": { + "value": "salary" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "2d16b527-72be-42e0-8e96-b164b818386c", + "value": "question_2", + "element_type": "LONG_TEXT", + "is_required": true, + "title": "Provide details of the primary source.", + "description": null, + "placeholder": null, + "choices": null, + "max_files": 1, + "answer": { + "value": "Test answer" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "a9239b7c-fbfb-4ea7-b7e4-c835cd4016e0", + "value": "question_3", + "element_type": "DROPDOWN", + "is_required": true, + "title": "What is your annual income range (pre-tax)?", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Less than $25,000", + "value": "<25k" + }, + { + "label": "$25,000 - $50,000", + "value": "25k-50k" + }, + { + "label": "$50,001 - $100,000", + "value": "50k-100k" + }, + { + "label": "$100,001 - $250,000", + "value": "100k-250k" + }, + { + "label": "More than $250,000", + "value": ">250k" + } + ], + "max_files": 1, + "answer": { + "value": "<25k" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "93dede62-6d0c-4a7f-934d-42175eb94011", + "value": "question_4", + "element_type": "DROPDOWN", + "is_required": true, + "title": "What is your approximate net worth (range)?", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Less than $50,000", + "value": "<50k" + }, + { + "label": "$50,000 - $250,000", + "value": "50k-250k" + }, + { + "label": "$250,001 - $1,000,000", + "value": "250k-1m" + }, + { + "label": "$1,000,001 - $5,000,000", + "value": "1m-5m" + }, + { + "label": "More than $5,000,000", + "value": ">5m" + } + ], + "max_files": 1, + "answer": { + "value": "<50k" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "2af60d6f-6961-4692-9ee7-24b3033610a6", + "value": "question_5", + "element_type": "LONG_TEXT", + "is_required": true, + "title": "For your initial deposit/funding, indicate the exact source and amount.", + "description": null, + "placeholder": null, + "choices": null, + "max_files": 1, + "answer": { + "value": "Test answer" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "0ad9d1aa-736f-46cf-947b-9e1241b53e40", + "value": "question_6", + "element_type": "LONG_TEXT", + "is_required": true, + "title": "Breakdown of sources by percentage.", + "description": null, + "placeholder": null, + "choices": null, + "max_files": 1, + "answer": { + "value": "Test answer" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "1bf8a2a3-14ee-48a8-bb3d-175481aae4aa", + "value": "question_7", + "element_type": "SINGLE_CHOICE", + "is_required": true, + "title": "Will any funds originate from third parties? If yes, please provide details.", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Yes", + "value": "yes", + "requires_text_input": true + }, + { + "label": "No", + "value": "no" + } + ], + "max_files": 1, + "answer": { + "text": "Additional details", + "value": "yes" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "e18d4dd3-b7bf-4883-ba18-d6046adbe313", + "value": "question_8", + "element_type": "SINGLE_CHOICE", + "is_required": true, + "title": "Do you hold or plan to use virtual assets (e.g., crypto) to fund this account? If yes, please provide details.", + "description": null, + "placeholder": null, + "choices": [ + { + "label": "Yes", + "value": "yes", + "requires_text_input": true + }, + { + "label": "No", + "value": "no" + } + ], + "max_files": 1, + "answer": { + "text": "Additional details", + "value": "yes" + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "a90a073d-e098-404c-bbce-5130d8b12a13", + "value": "question_9", + "element_type": "FILE_UPLOAD", + "is_required": true, + "title": "Upload a proof of funds document", + "description": "You can upload your three (3) most recent bank statements, three (3) most recent payslips, your most recent tax return, proof of company ownership, trading exchange statement, sale of property, or proof of inheritance.", + "placeholder": null, + "choices": null, + "max_files": 3, + "answer": { + "files": [ + "/media/ocr/", + "/media/ocr/" + ] + }, + "required_if": null, + "repeatable_config": null + }, + { + "uuid": "9503b486-7cb8-4c3b-a97d-4b64926a5a47", + "value": "question_10", + "element_type": "LONG_TEXT", + "is_required": false, + "title": "Would you like to add any details to help us expedite our review of the documents you submitted?", + "description": null, + "placeholder": null, + "choices": null, + "max_files": 1, + "answer": { + "value": "Test answer" + }, + "required_if": null, + "repeatable_config": null + } + ] + } + ], + "status": "Approved" + } + ], + "aml_screenings": [ + { + "node_id": "feature_aml_1", + "status": "In Review", + "total_hits": 1, + "entity_type": "person", + "hits": [ + { + "id": "hit_7c1e9a40b2d35f86e0a4c912", + "url": "https://news.example.org/articles/sample", + "match": false, + "score": 0.73, + "target": null, + "caption": "Javier Ejemplo Modelo", + "datasets": [ + "PEP" + ], + "features": null, + "rca_name": "", + "last_seen": "2025-06-13T00:00:00", + "risk_view": { + "crimes": { + "score": 0, + "weightage": 20, + "risk_level": "Low", + "risk_scores": {} + }, + "countries": { + "score": 0, + "weightage": 30, + "risk_level": "Low", + "risk_scores": {} + }, + "categories": { + "score": 100, + "weightage": 50, + "risk_level": "High", + "risk_scores": { + "PEP": 100 + } + }, + "custom_list": {} + }, + "first_seen": "2025-01-18T00:00:00", + "properties": { + "name": [ + "Javier Ejemplo Modelo" + ], + "alias": [ + "Javier Ejemplo Modelo", + "David Azagra" + ], + "notes": [ + "Spanish orchestra conductor" + ], + "title": null, + "gender": [ + "male" + ], + "height": null, + "topics": null, + "weight": null, + "address": null, + "country": null, + "website": null, + "eyeColor": null, + "keywords": null, + "lastName": [ + "Ejemplo", + "Modelo" + ], + "position": null, + "religion": null, + "birthDate": [ + "1974" + ], + "education": [ + "Tokyo Arts and Space (2012-2012)", + "Saint Petersburg Conservatory (-2010)", + "Comillas Pontifical University" + ], + "ethnicity": null, + "firstName": [ + "David" + ], + "hairColor": null, + "weakAlias": null, + "birthPlace": [ + "Madrid" + ], + "modifiedAt": null, + "wikidataId": null, + "citizenship": null, + "nationality": null + }, + "match_score": 98, + "risk_score": 73, + "review_status": "Unreviewed", + "score_breakdown": { + "name_score": 95, + "name_weight": 60, + "name_weight_normalized": 70.59, + "name_contribution": 67.06, + "dob_score": 100, + "dob_weight": 25, + "dob_weight_normalized": 29.41, + "dob_contribution": 29.41, + "country_score": 0, + "country_weight": 15, + "country_weight_normalized": 0, + "country_contribution": 0, + "document_number_match_type": "NEUTRAL", + "document_number_effect": "No document number provided for screening", + "total_score": 98 + }, + "pep_matches": [ + { + "aliases": [ + "Javier Ejemplo Modelo", + "David Azagra" + ], + "education": [ + "Tokyo Arts and Space (2012-2012)", + "Saint Petersburg Conservatory (-2010)", + "Comillas Pontifical University" + ], + "list_name": "Wikidata", + "publisher": "Wikidata", + "source_url": "https://news.example.org/articles/sample", + "description": "Wikidata is the structured data project of the Wikipedia community, providing fact-based information edited by humans and machines", + "matched_name": "Javier Ejemplo Modelo", + "pep_position": "", + "date_of_birth": "1974", + "other_sources": [], + "place_of_birth": "Madrid" + } + ], + "linked_entities": [ + { + "name": [ + "Antonio Ejemplo" + ], + "active": [], + "status": [], + "details": [], + "relation": [] + } + ], + "warning_matches": [], + "sanction_matches": [], + "adverse_media_details": { + "sentiment": "Moderately Negative", + "entity_type": "person", + "sentiment_score": -2, + "adverse_keywords": { + "fraud": 3, + "juicio": 1, + "delitos": 2, + "imputado": 1, + "impunidad": 1, + "corruption": 2, + "favoritism": 2, + "privilegio": 1, + "corrupción": 1, + "persecution": 2, + "desconfianza": 1, + "desprestigio": 1, + "fraude de ley": 2, + "manipulación": 1, + "investigación": 3, + "prevaricación": 8, + "trato de favor": 1, + "irregularidades": 2, + "crisis de confianza": 1, + "tráfico de influencias": 8 + } + }, + "adverse_media_matches": [ + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in legal violations related to favoritism and influence trafficking, leading to a negative portrayal in the article.", + "headline": "La Diputación de Villaejemplo consolida la plaza a medida para el ex asesor de Moncloa amigo del \"hermanito\" de Ejemplo", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "fraud", + "prevaricación", + "tráfico de influencias", + "irregularidades", + "imputado" + ], + "sentiment_score": -2, + "publication_date": "2025-06-10T20:46:38" + }, + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in legal issues regarding favoritism and corruption, leading to a negative portrayal in the article.", + "headline": "La Diputación de Villaejemplo consolida la plaza a medida para el ex asesor de Moncloa amigo del \"hermanito\" de Ejemplo", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "fraud", + "prevaricación", + "tráfico de influencias", + "irregularidades", + "trato de favor" + ], + "sentiment_score": -2, + "publication_date": "2025-06-10T20:46:38" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is mentioned in connection with Carlos Ficticio's corruption case, indicating his involvement in alleged influence peddling.", + "headline": "España, paraíso del aforado", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias", + "corrupción", + "impunidad", + "privilegio", + "desconfianza", + "manipulación", + "fraude de ley" + ], + "sentiment_score": -2, + "publication_date": "2025-06-10T05:05:34" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is implicated in the same investigation as Ficticio, which raises questions about his conduct and political ethics.", + "headline": "Aforamientos: esa figura incómoda", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias", + "investigación", + "juicio", + "fraude de ley", + "desprestigio", + "crisis de confianza", + "delitos" + ], + "sentiment_score": -2, + "publication_date": "2025-06-08T06:01:00" + }, + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in a legal investigation regarding irregularities in his hiring, which raises serious concerns about his conduct.", + "headline": "La jueza del 'caso Javier Ejemplo' denuncia un «fraude de ley» en el aforamiento de Ficticio (Partido Ejemplo)", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "fraud", + "irregularities", + "prevaricación", + "tráfico de influencias" + ], + "sentiment_score": -2, + "publication_date": "2025-06-09T10:19:10" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is mentioned in connection with legal allegations of corruption, contributing to a negative perception.", + "headline": "'Caso aforamiento': la letrada mayor de la Asamblea certifica a la juez que Ficticio es diputado desde antes de prometer su cargo en el Pleno", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias" + ], + "sentiment_score": -2, + "publication_date": "2025-06-09T08:08:30" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is implicated in the same investigation as Ficticio, raising concerns about political corruption and influence.", + "headline": "Aforamientos: esa figura incómoda", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias", + "investigación" + ], + "sentiment_score": -2, + "publication_date": "2025-06-08T06:01:00" + }, + { + "country": "spain", + "summary": "Javier Ejemplo Modelo is implicated in the same investigation as Ficticio, which raises concerns about political misconduct.", + "headline": "Aforamientos: esa figura incómoda", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "prevaricación", + "tráfico de influencias", + "investigación", + "delitos" + ], + "sentiment_score": -2, + "publication_date": "2025-06-08T06:00:59" + }, + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in a case of favoritism and corruption, which casts a negative light on his character and actions.", + "headline": "'Caso Javier Ejemplo': las asociaciones judiciales salen en defensa de la juez Luisa Inventada ante las \"presiones externas\" que sufre", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "favoritism", + "corruption", + "persecution" + ], + "sentiment_score": -2, + "publication_date": "2025-06-06T10:08:15" + }, + { + "country": "spain", + "summary": "Javier Ejemplo is implicated in a case of alleged favoritism and corruption, which casts a negative light on his character and actions.", + "headline": "'Caso Javier Ejemplo': las asociaciones judiciales salen en defensa de la juez Luisa Inventada ante las \"presiones externas\" que sufre", + "sentiment": "moderately negative", + "thumbnail": "https://news.example.org/articles/sample", + "source_url": "https://news.example.org/articles/sample", + "author_name": null, + "other_sources": [], + "adverse_keywords": [ + "favoritism", + "corruption", + "persecution" + ], + "sentiment_score": -2, + "publication_date": "2025-06-06T08:08:15" + } + ], + "additional_information": {} + } + ], + "score": 80, + "screened_data": { + "full_name": "Javier Ejemplo Modelo", + "nationality": "ES", + "date_of_birth": "1974-01-01", + "document_number": null + }, + "is_ongoing_monitoring_enabled": true, + "next_ongoing_monitoring_bill_date": "2026-07-24", + "last_ongoing_screening_at": "2026-09-12T04:03:11Z", + "last_ongoing_screening_datasets_updated_at": "2026-09-11T06:00:00Z", + "warnings": [ + { + "feature": "AML", + "risk": "POSSIBLE_MATCH_FOUND", + "additional_data": null, + "log_type": "warning", + "short_description": "Possible match found in AML screening", + "long_description": "The Anti-Money Laundering (AML) screening process identified potential matches with watchlists or high-risk databases, requiring further review.", + "node_id": "feature_aml_1" + } + ] + } + ], + "ip_analyses": [ + { + "node_id": "feature_ip_1", + "status": "Approved", + "device_brand": "Apple", + "device_model": "iPhone", + "browser_family": "Mobile Safari", + "os_family": "iOS", + "platform": "mobile", + "device_fingerprint": "didit-fp-a1b2c3d4e5f6g7h8", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_state": "Barcelona", + "ip_city": "Barcelona", + "latitude": 41.4022, + "longitude": 2.1407, + "ip_address": "83.50.226.71", + "isp": null, + "organization": null, + "is_vpn_or_tor": false, + "is_data_center": false, + "time_zone": "Europe/Madrid", + "time_zone_offset": "+0100", + "ip": { + "location": { + "latitude": 40.2206327, + "longitude": 1.5770097 + }, + "distance_from_id_document": 23.4, + "distance_from_poa_document": 12.3 + }, + "id_document": { + "location": { + "latitude": 41.2706327, + "longitude": 1.9770097 + }, + "distance_from_ip": 23.4, + "distance_from_poa_document": 18.7 + }, + "poa_document": { + "location": { + "latitude": 41.2706327, + "longitude": 1.9770097 + }, + "distance_from_ip": 12.3, + "distance_from_id_document": 18.7 + }, + "warnings": [], + "matches": [ + { + "session_id": "99999999-8888-7777-6666-555555555555", + "session_number": 41205, + "vendor_data": "22222222-2222-2222-2222-222222222222", + "verification_date": "2024-07-20T10:15:30Z", + "match_type": "ip_address", + "match_source": "ip_address", + "matched_value": "83.50.226.71", + "status": "Approved", + "is_blocklisted": false, + "api_service": null, + "source": "session", + "device_info": { + "device_brand": "Apple", + "device_model": "iPhone", + "browser_family": "Mobile Safari", + "os_family": "iOS", + "platform": "mobile", + "device_fingerprint": "didit-fp-f9e8d7c6b5a40312" + }, + "location_info": { + "ip_address": "83.50.226.71", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_state": "Barcelona", + "ip_city": "Barcelona", + "is_vpn_or_tor": false, + "is_data_center": false + }, + "confidence": 0.0, + "match_mode": "co_occurrence" + }, + { + "session_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "session_number": 42830, + "vendor_data": "33333333-3333-3333-3333-333333333333", + "verification_date": "2024-06-30T18:22:05Z", + "match_type": "device_fingerprint", + "match_source": "recovered_high", + "matched_value": "f47ac10b-58cc-4372-a567-0e02b2c3d479", + "status": "Declined", + "is_blocklisted": false, + "api_service": null, + "source": "session", + "device_info": { + "device_brand": "Samsung", + "device_model": "SM-G991B", + "browser_family": "Chrome Mobile", + "os_family": "Android", + "platform": "mobile", + "device_fingerprint": null + }, + "location_info": { + "ip_address": "83.50.231.18", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_state": "Madrid", + "ip_city": "Madrid", + "is_vpn_or_tor": false, + "is_data_center": false + }, + "confidence": 1.0, + "match_mode": "deterministic", + "recovery_similarity": 0.9874, + "tls_ja4_corroborated": true, + "recovery_gate_reason": "hardware_root_match" + } + ] + } + ], + "database_validations": [ + { + "node_id": "feature_db_validation_1", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "last_name": "De Lima", + "first_name": "Fernando", + "tax_number": "01234567890", + "date_of_birth": "1980-01-01", + "document_type": "ID", + "expiration_date": "2030-01-01" + }, + "validations": [ + { + "service_id": "bra_cpf", + "service_name": "Brazil - CPF status check", + "outcome_code": "MATCH", + "validation": { + "full_name": "full_match", + "date_of_birth": "full_match", + "identification_number": "full_match" + }, + "source_data": { + "identification_number": "01234567890", + "first_name": "FERNANDO", + "last_name": "DE LIMA", + "date_of_birth": "1980-01-01", + "gender": "M", + "nationality": "BRA" + } + } + ], + "match_type": "full_match", + "status": "Approved", + "warnings": [] + } + ], + "reviews": [ + { + "user": "compliance@example.com", + "new_status": "Declined", + "comment": "Possible match found in AML screening", + "created_at": "2024-07-18T13:29:00.366811Z" + } + ], + "created_at": "2024-07-24T08:54:25.443172Z", + "expires_at": "2025-07-24T08:54:25.443172Z", + "biometric_consent": { + "required": false, + "trigger": null, + "status": "not_required", + "given_at": null, + "declined_at": null, + "text_version": null, + "text_hash": null, + "locale": null, + "policy_url": null, + "step": null, + "node_id": null, + "ip_address": null, + "ip_country_code": null, + "ip_state": null, + "ip_city": null, + "user_agent": null, + "detected_ip_address": "83.50.226.71", + "detected_country_code": "ES", + "detected_state": "ES-B", + "detected_city": "Barcelona", + "declared_state": null, + "document_state": null, + "jurisdictions": [], + "retention_months": null, + "reason": null, + "decline_reason": null, + "face_data_deleted_at": null + } + } + }, + "BusinessExample": { + "summary": "Business (KYB) session example", + "description": "A `session_kind = \"business\"` decision: KYB sessions replace the KYC feature arrays with `registry_checks`, `document_verifications` and `key_people_checks`, while sharing `aml_screenings`, `phone_verifications`, `email_verifications`, `questionnaire_responses`, `ip_analyses`, `reviews` and the session envelope.", + "value": { + "session_id": "66666666-7777-8888-9999-000000000000", + "session_kind": "business", + "session_number": 31415, + "session_url": "https://verify.didit.me/session/business123", + "status": "In Review", + "environment": "live", + "workflow_id": "abababab-cdcd-efef-0101-232323232323", + "features": [ + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE" + ], + "vendor_data": "merchant-001234", + "metadata": null, + "callback": "https://example.com/verification/callback", + "registry_checks": [ + { + "status": "Approved", + "node_id": "feature_kyb_registry_1", + "data_resolved": true, + "company": { + "uuid": "dddd4444-eeee-5555-ffff-666677778888", + "node_id": "feature_kyb_registry_1", + "status": "Approved", + "registry_status": "active", + "data_resolved": true, + "company_name": "ACME HOLDINGS LTD", + "registration_number": "12345678", + "country_code": "GB", + "region": null, + "company_type": "Private Limited Company", + "incorporation_date": "2015-03-12", + "registered_address": "1 Example Street, London, EC1A 1AA", + "tax_number": null, + "risk_level": null, + "verification_status": "verified", + "is_from_registry": true, + "fetch_status": "resolved", + "alternative_names": null, + "nature_of_business": "62012 - Business and domestic software development", + "registered_capital": "100 GBP", + "registered_capital_amount": "100.00", + "registered_capital_currency": "GBP", + "website": null, + "email": null, + "phone": null, + "legal_entity_identifier": null, + "location_of_registration": "England and Wales", + "financial_summary": null, + "officers": [ + { + "uuid": "aaaa1111-bbbb-2222-cccc-333344445555", + "name": "Jane Smith", + "designation": "Director", + "role": "director", + "nationality": "British", + "is_active": true, + "kyc_status": "Approved", + "kyc_session_url": "https://verify.didit.me/session/abc123def456" + } + ], + "beneficial_owners": [ + { + "uuid": "bbbb2222-cccc-3333-dddd-444455556666", + "name": "John Doe", + "first_name": "John", + "last_name": "Doe", + "entity_type": "person", + "roles": [ + "ubo", + "shareholder" + ], + "ownership_min_shares": "50", + "ownership_max_shares": "75", + "is_active": true, + "kyc_status": "Pending", + "kyc_session_url": "https://verify.didit.me/session/def456ghi789", + "effective_ownership_percent": 75.0 + } + ], + "addresses": [ + { + "type": "registered", + "address": "1 Example Street, London, EC1A 1AA", + "country": "GB" + } + ], + "industries": [ + { + "code": "62012", + "description": "Business and domestic software development" + } + ], + "accounts": null, + "registry_data": {}, + "user_provided_data": { + "company_name": "ACME HOLDINGS", + "registration_number": "12345678" + }, + "confirmed_by_user_at": "2026-06-10T12:01:30.000000Z", + "last_console_edit_at": null, + "is_editable": false + }, + "ownership_structure": { + "shareholders": [ + { + "name": "John Doe", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": null, + "country_of_birth": null, + "company_name": null, + "registration_number": null, + "entity_type": "person", + "nationality": null, + "designation": null, + "roles": [ + "shareholder" + ], + "ownership_min_shares": "75", + "ownership_max_shares": "75", + "voting_min_shares": null, + "voting_max_shares": null, + "appointment_date": null, + "is_active": true, + "shareholding": { + "share_class": "ORDINARY", + "number_of_shares": 750.0, + "currency": "GBP", + "nominal_value": 1.0, + "total_value": null, + "percentage": 75.0 + } + } + ], + "beneficial_owners": [] + }, + "warnings": [] + } + ], + "aml_screenings": null, + "document_verifications": [ + { + "status": "In Review", + "node_id": "feature_kyb_documents_1", + "items": [ + { + "uuid": "eeee5555-ffff-6666-0000-777788889999", + "document_type": "LEGAL_PRESENCE", + "document_subtype": "CERTIFICATE_OF_INCORPORATION", + "document_group": "LEGAL_PRESENCE", + "file_url": "https:///kyb/certificate-of-incorporation.pdf", + "original_filename": "certificate_of_incorporation.pdf", + "file_size": 482133, + "status": "Approved", + "created_at": "2026-06-10T12:03:11.000000Z", + "cross_check_result": { + "critical": { + "company_name": { + "document": "ACME HOLDINGS LTD", + "registry": "ACME HOLDINGS LTD", + "score": 1.0, + "match": true + }, + "registration_number": { + "document": "12345678", + "registry": "12345678", + "match": true + } + }, + "non_critical": {}, + "people": {} + }, + "document_metadata": { + "file_size": 482133, + "content_type": "application/pdf", + "creation_date": "2015-03-12T09:00:00", + "modified_date": "2015-03-12T09:00:00", + "creator": null, + "producer": null, + "software": null, + "encryption": null, + "is_signed": true, + "is_tampered": false, + "signature_info": null, + "exif_original_date": null, + "exif_digitized_date": null, + "processed_by_known_editor": false, + "has_different_creation_mod_date": false, + "overlay_manipulation": null + }, + "description": null, + "is_requested": false, + "ocr_data": { + "company_name": "ACME HOLDINGS LTD", + "registration_number": "12345678", + "incorporation_date": "2015-03-12" + }, + "is_console_upload": false, + "is_confirmed": true + }, + { + "uuid": "ffff6666-0000-7777-1111-888899990000", + "document_type": "OWNERSHIP_STRUCTURE", + "document_subtype": null, + "document_group": "OWNERSHIP_STRUCTURE", + "file_url": null, + "original_filename": null, + "file_size": null, + "status": "Not Finished", + "created_at": "2026-06-10T15:40:02.000000Z", + "cross_check_result": null, + "document_metadata": null, + "description": "Please upload an up-to-date shareholder registry", + "is_requested": true, + "ocr_data": null, + "is_console_upload": false, + "is_confirmed": true + } + ], + "groups": { + "LEGAL_PRESENCE": { + "total": 1, + "approved": 1, + "pending": 0, + "declined": 0, + "in_review": 0, + "other": 0, + "missing": 0 + }, + "OWNERSHIP_STRUCTURE": { + "total": 1, + "approved": 0, + "pending": 0, + "declined": 0, + "in_review": 0, + "other": 0, + "missing": 1 + } + }, + "required_groups": [ + "LEGAL_PRESENCE", + "OWNERSHIP_STRUCTURE" + ], + "warnings": [] + } + ], + "key_people_checks": [ + { + "status": "In Review", + "node_id": "feature_kyb_key_people_1", + "registry": { + "officers": [ + { + "uuid": "aaaa1111-bbbb-2222-cccc-333344445555", + "name": "Jane Smith", + "designation": "Director", + "role": "director", + "nationality": "British", + "appointment_date": "2015-03-12", + "termination_date": null, + "is_active": true, + "address": "1 Example Street, London, EC1A 1AA", + "aml_status": "Approved", + "kyc_status": "Approved", + "kyc_session_url": "https://verify.didit.me/session/abc123def456", + "kyc_session_id": "77777777-8888-9999-aaaa-bbbbbbbbbbbb", + "verification_workflow_type": "kyc", + "verification_workflow_id": "12121212-3434-5656-7878-909090909090", + "verification_workflow_label": "Key People KYC", + "verification_workflow_features": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "didit_internal_id": "99999999-0000-1111-2222-333333333333", + "kyc_document_type": "P", + "kyc_nationality": "GBR", + "kyc_liveness_passed": true, + "kyc_verified_at": "2026-06-10T14:22:31.123456Z", + "kyc_aml_categories": { + "sanctions": "clear", + "pep": "clear", + "adverse_media": "clear", + "warnings": "clear" + }, + "kyc_notification_sent_at": "2026-06-09T09:00:00.000000Z" + } + ], + "beneficial_owners": [ + { + "uuid": "bbbb2222-cccc-3333-dddd-444455556666", + "name": "John Doe", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-05-04", + "country_of_birth": null, + "email": null, + "company_name": null, + "registration_number": null, + "entity_type": "person", + "nationality": "British", + "designation": null, + "roles": [ + "ubo", + "shareholder" + ], + "ownership_min_shares": "50", + "ownership_max_shares": "75", + "is_active": true, + "aml_status": null, + "kyc_status": "In Progress", + "kyc_session_url": "https://verify.didit.me/session/def456ghi789", + "kyc_session_id": "88888888-9999-aaaa-bbbb-cccccccccccc", + "verification_workflow_type": "kyc", + "verification_workflow_id": "12121212-3434-5656-7878-909090909090", + "verification_workflow_label": "Key People KYC", + "verification_workflow_features": [ + { + "feature": "ID_VERIFICATION", + "status": "Not Finished" + }, + { + "feature": "LIVENESS", + "status": "Not Finished" + }, + { + "feature": "AML", + "status": "Not Finished" + } + ], + "didit_internal_id": null, + "kyc_document_type": null, + "kyc_nationality": null, + "kyc_liveness_passed": null, + "kyc_verified_at": null, + "kyc_aml_categories": null, + "kyc_notification_sent_at": null, + "child_beneficial_owners": [], + "effective_ownership_percent": 75.0 + } + ] + }, + "submitted": { + "parties": [ + { + "uuid": "cccc3333-dddd-4444-eeee-555566667777", + "entity_type": "person", + "source": "USER", + "name": "John Doe", + "first_name": "John", + "last_name": "Doe", + "company_name": null, + "registration_number": null, + "email": "john.doe@example.com", + "nationality": "GBR", + "phone_number": "+447700900123", + "date_of_birth": "1980-05-04", + "position": "Chief Executive Officer", + "custom_fields": {}, + "is_active": true, + "is_skipped": false, + "requires_verification": true, + "roles": [ + { + "role": "ubo", + "ownership_percent": 75.0, + "voting_percent": 75.0 + }, + { + "role": "director", + "ownership_percent": null, + "voting_percent": null + } + ], + "kyc_session_id": "88888888-9999-aaaa-bbbb-cccccccccccc", + "kyc_session_status": "In Progress", + "kyc_session_url": "https://verify.didit.me/session/def456ghi789", + "kyb_sub_session_id": null, + "kyb_sub_session_status": null, + "kyb_sub_session_url": null, + "verification_workflow_type": "kyc", + "verification_workflow_id": "12121212-3434-5656-7878-909090909090", + "verification_workflow_label": "Key People KYC", + "verification_workflow_features": [ + { + "feature": "ID_VERIFICATION", + "status": "Not Finished" + }, + { + "feature": "LIVENESS", + "status": "Not Finished" + }, + { + "feature": "AML", + "status": "Not Finished" + } + ], + "didit_internal_id": null + } + ] + }, + "ubo_kyc_summary": { + "total": 1, + "approved": 0, + "flagged": 0, + "pending": 1 + }, + "warnings": [] + } + ], + "phone_verifications": null, + "email_verifications": null, + "questionnaire_responses": null, + "ip_analyses": null, + "reviews": [], + "contact_details": null, + "expected_details": { + "company_name": "Tesco PLC", + "registry_country": "GB", + "registration_number": "00445790" + }, + "created_at": "2026-06-10T11:58:04.000000Z", + "expires_at": "2026-06-24T11:58:04.000000Z" + } + } + }, + "schema": { + "$ref": "#/components/schemas/SessionDecision" + } + } + } + }, + "403": { + "description": "Authentication or authorization failed. A missing key returns `403`; a wrong, revoked or expired key returns `401`. Two `detail` strings distinguish the failure modes:\n\n- `\"Authentication credentials were not provided or are invalid.\"` — no `x-api-key` header (and no `Authorization: Bearer` token), or the supplied credential failed introspection (revoked, expired, or malformed).\n- `\"You do not have permission to perform this action.\"` — the credential is valid but not allowed to read this session: a console-user Bearer token lacks the `read:sessions` permission or cannot access the owning application, or an API key belongs to a different organization than the session.\n\nNote that for a completely unknown `sessionId` the endpoint resolves the owning application before validating credentials, so a missing session returns `404` even when the credential is missing or invalid.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Authentication credentials were not provided or are invalid." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed introspection", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Insufficient permissions": { + "summary": "Valid credential lacking read:sessions or access to the owning application", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Session not found. Returned when no live session matches the supplied `sessionId` as either a User Verification (KYC) or a Business Verification (KYB) session — a session that has been deleted also lands here. Body is the standard `{\"detail\": ...}` envelope.\n\n**Two distinct `detail` strings.** If the `sessionId` is unknown as both a KYC and a KYB session, the pre-auth owner lookup fails and you receive `{\"detail\": \"Not found.\"}` — this happens before credential validation, so it is returned even for unauthenticated calls. If the session exists but cannot be retrieved under the authenticated application (for example a stale or just-deleted record), the view returns `{\"detail\": \"Session not found.\"}`. Both indicate the same outcome — the session is not accessible — so match on the `404` status code rather than the string.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Not found." + } + } + }, + "examples": { + "Unknown session id": { + "summary": "sessionId is unknown to this application", + "value": { + "detail": "Not found." + } + }, + "Session not retrievable": { + "summary": "Session exists but is not retrievable for this application", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. GET requests are limited to **600 per minute per credential** (API key, Bearer token, or source IP when unauthenticated). Inspect `Retry-After` / `X-RateLimit-Reset` and back off before retrying. If you hit this limit while polling for decisions, switch to the `status.updated` webhook and fetch the decision once per status change instead.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying the request.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Maximum number of requests allowed in the current window (600 for GET requests).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests remaining in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix timestamp (seconds) at which the current window resets.", + "schema": { + "type": "integer", + "example": 1750000000 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable explanation of the rate-limit breach.", + "example": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + }, + "examples": { + "Throttled": { + "summary": "More than 600 GET requests in one minute from the same credential", + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session whose decision you want to retrieve. This is the `session_id` returned by `POST /v3/session/`. The same path works for both User Verification (KYC) and Business Verification (KYB) sessions — the server resolves the id against both session types.\n\n**Must be a valid UUID.** The route only matches well-formed UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below. Validate the UUID format client-side so you can distinguish an input error from a genuine missing-session `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "include", + "in": "query", + "required": false, + "description": "Comma-separated list of extra payload sections. Supported values: `events` and `resubmissions`, alone or together (`events,resubmissions`). When `include=events` is passed, the response additionally contains the session event timeline (`events`, including the `WORKFLOW_BRANCH_EVALUATED` / `WORKFLOW_STATUS_NODE_REACHED` routing events described on the Retrieve Session page), the full activity log (`activity_reviews`), blocklist flags (`blocklisted`), a per-session `cost_breakdown`, workflow metadata (`workflow_type`, `workflow_version`, `workflow_version_id`, and `workflow_terminal_node_id` - the last recorded workflow status node that set a session status, or `null` if none has been recorded; historical provenance that is not cleared by manual approvals, declines or resubmissions, and is replaced by a later workflow status node), and session metadata (`api_service` — string or `null`, set only for standalone-API sessions; `session_type` — `API`, `HOSTED`, or `MIGRATED`; `has_device_nfc_support` — boolean). Two fields change relative to the plain shape: the top-level `session_kind` discriminator is **omitted** (the events shape only exists for user sessions), and the `features` array switches from plain strings to `{feature, node_id}` objects — where the email feature is reported as `EMAIL` instead of `EMAIL_VERIFICATION`. This expanded shape is primarily used by the Didit Console; most API integrations should omit the parameter. **KYB limitation:** `include=events` only applies to user (KYC) sessions. Business sessions (`session_kind = \"business\"`) always return the standard business decision shape — `events`, `activity_reviews`, `blocklisted`, `cost_breakdown` and the extra `workflow_*`/session metadata are never returned for business sessions, and their `features` array stays a plain string array. With `include=resubmissions` (user and business sessions alike) the response also contains `resubmissions`: one entry per resubmission request, oldest first, each with `number`, `created_at`, `previous_status`, `nodes`, `reasons`, `requested_by` and `results` (what the resubmitted steps returned before the request, under the same keys and in the same shape as the current results). Each `cost_breakdown` item carries `usage_type`, `price`, `details` and `created_at`: a resubmitted step that runs again is billed as a new item.", + "schema": { + "type": "string", + "enum": [ + "events", + "resubmissions", + "events,resubmissions" + ], + "example": "events" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/decision/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Accept: application/json'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.retrieve_decision(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.retrieveDecision(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.RetrieveDecision(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsRetrieveDecisionParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().retrieveDecision(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->retrieveDecision('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.retrieve_decision(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.RetrieveDecisionAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/delete/": { + "delete": { + "summary": "Delete a verification session (KYC or KYB)", + "description": "Delete a single verification session, User Verification (KYC) or Business Verification (KYB), by its `session_id`. The id is resolved against KYC sessions first, then KYB sessions, so both kinds are deleted through this one URL.\n\n**What is deleted:**\n- The session and every verification attempt it holds, including results that a resubmission replaced: the decision, extracted data, all related feature records (ID verification, face, liveness, face match, AML, registry checks, IP analysis, and so on), its reviews and its activity log.\n- All stored files owned by the session: document front/back photos (full, cropped, and privacy-blurred variants), document-capture videos, portrait crops, NFC chip portrait and signature images, face reference images, liveness videos, face-match source/target images, Proof of Address documents, and any extra uploaded files (KYC); uploaded company documents and extra files (KYB). E-signature documents, signed PDFs, evidence PDFs and signature images (KYC and KYB): their download paths stop working. Previously issued media URLs (`https:///...`) stop resolving.\n- The session's face data. Its face embedding is deleted too, unless you choose to keep an image-free template (see **Face biometric data**).\n- Webhook deliveries still queued for the session, and their retries. Webhooks already delivered are not affected.\n- For a Business Verification (KYB) session: the verifications it created for its key people (KYC) and its corporate owners (KYB), recursively, reported as `deleted_child_verifications`. A verification it only links to, such as an existing approved KYC it reused, is not deleted.\n\nThe session disappears from every read endpoint immediately: `GET /v3/session/{sessionId}/decision/` returns `404` and `GET /v3/sessions/` stops listing it. Ongoing AML monitoring and KYB registry monitoring started by the deleted verifications stop.\n\n**What is not affected:** blocklist entries created from this session (face or document) are **not** removed; manage those with the blocklist endpoints. Share tokens issued for the session are not revoked, but there is nothing left for them to import: an import with one returns `400` with `{\"share_token\": [\"Original session does not exist.\"]}`. Credits already consumed are **not** refunded. The parent User or Business entity is **not** deleted; use `POST /v3/users/delete/` or `POST /v3/businesses/delete/` for those. If the User's profile portrait came from this session, the portrait is removed with it.\n\n**Face biometric data:** by default the session's face embedding is deleted with the session (`face_retention_outcome: deleted`). An application that opted in to biometric-template retention (`face_retention_policy: retain_until_user_deleted` on `PATCH /v3/webhook/`), or a request with `retain_face_embeddings: true`, keeps one separately managed, image-free face biometric template anchored to the session's User (`retained_with_user`) so that duplicate detection, Face Search, and biometric authentication keep working. The template never contains the face image, media, identity fields, the decision, or the deleted session's id; it is biometric data deleted at `biometric_template_expires_at`, or earlier on User deletion, privacy erasure, or an explicit purge through `/v3/biometric-templates/`. A session without a linked User cannot retain a template (`ineligible_no_vendor_user`). Send `deletion_instruction: privacy_erasure` for data-subject requests: it purges every retained template for that User and never retains a new one.\n\n**Irreversible:** deletion is permanent and there is no restore endpoint. Export anything you need (for example a decision PDF) before you call it.\n\n**Side effects:** no webhook is emitted for deletions.\n\n**Response:** `200 OK` once the deletion has completed, with a JSON body reporting the `face_retention_outcome`, the retained template's expiry and, for a KYB session, `deleted_child_verifications`. Earlier versions of this endpoint returned `204 No Content`; update clients that assert on `204`. Keep your `instruction_id` in your own erasure log.\n\n**Idempotency:** not idempotent at the HTTP level: the first call returns `200`; repeating it returns `404` because the session no longer resolves. After a `503` the session is still in place; repeat the same request to finish deleting it.\n\n**Authentication:** send your application's API key in the `x-api-key` header; the session must belong to that application. Console user access tokens (`Authorization: Bearer ...`) may also call this endpoint when they carry the `delete:sessions` permission. Authentication and permission failures both return `403`; this API never returns `401`.\n\n**Rate limit:** shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.\n\nTo bulk-delete KYC sessions by their numeric `session_number`, use `POST /v3/sessions/delete/` instead.\n\n**Filed regulatory reports:** if the selected data is in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "delete_v3_session_by_id", + "x-didit-sdk": { + "resource": "sessions", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID (`session_id`) of the User Verification (KYC) or Business Verification (KYB) session to delete, as returned when the session was created. Must be a canonical hyphenated UUID — a non-UUID value does not match the route and returns `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "responses": { + "200": { + "description": "Session deleted. The body reports what happened to the session's face biometric data and, for a KYB session, how many verifications it created were deleted with it.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The deleted session." + }, + "session_number": { + "type": "integer", + "nullable": true, + "description": "The deleted session's number." + }, + "face_retention_outcome": { + "type": "string", + "enum": [ + "retained_with_user", + "deleted", + "none", + "ineligible_no_vendor_user" + ], + "description": "What happened to the session's face biometric data. `deleted`: the face embedding was deleted with the session (default). `retained_with_user`: the session is deleted and one image-free biometric template stays anchored to the User; see `biometric_template_uuid`. `none`: the session had no face embedding (for example a KYB session, or a KYC session without a liveness selfie). `ineligible_no_vendor_user`: retention was requested but the session has no linked User, so the embedding was deleted with the session. A biometric-store failure is never reported through this field on this endpoint: it returns `503` and the session is not deleted." + }, + "biometric_template_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Id of the retained biometric template when `face_retention_outcome` is `retained_with_user`; `null` otherwise. Manage it through `/v3/biometric-templates/{template_uuid}/`." + }, + "biometric_template_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the retained biometric template is deleted, when `face_retention_outcome` is `retained_with_user`; `null` when no template is retained." + }, + "deleted_child_verifications": { + "type": "integer", + "description": "For a Business Verification (KYB) session: how many verifications it created (its key people's KYC sessions and its corporate owners' KYB sessions, recursively) were deleted with it. Always `0` for a KYC session." + } + } + }, + "examples": { + "Deleted (default)": { + "value": { + "session_id": "4c5c7f3a-3b1d-4b7e-9c39-2b4f7e6a1d02", + "session_number": 1024, + "face_retention_outcome": "deleted", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + } + }, + "Retained with User": { + "value": { + "session_id": "4c5c7f3a-3b1d-4b7e-9c39-2b4f7e6a1d02", + "session_number": 1024, + "face_retention_outcome": "retained_with_user", + "biometric_template_uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "biometric_template_expires_at": "2027-10-09T10:15:00Z", + "deleted_child_verifications": 0 + } + }, + "Ineligible: no User": { + "summary": "Retention requested on a session created without vendor_data", + "value": { + "session_id": "7a1e9c2d-5f4b-4c3a-8e2d-1b0a9f8e7d6c", + "session_number": 1031, + "face_retention_outcome": "ineligible_no_vendor_user", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + } + }, + "KYB session": { + "value": { + "session_id": "0f3c9b7e-2d1a-4e5f-8c6b-7a9d8e1f2c3b", + "session_number": 88, + "face_retention_outcome": "none", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 3 + }, + "summary": "A business verification deleted with the three verifications it created for its key people" + } + } + } + } + }, + "400": { + "description": "Validation error — nothing is deleted.", + "content": { + "application/json": { + "examples": { + "Privacy erasure cannot retain": { + "value": { + "retain_face_embeddings": [ + "Privacy erasure cannot retain biometric templates." + ] + } + }, + "No finite retention duration": { + "summary": "Retention requested without face_retention_days on the request or the application", + "value": { + "detail": "A finite biometric-template retention duration is required." + } + } + } + } + } + }, + "403": { + "description": "Authentication or permission failure. A missing key also returns `403`; a wrong, revoked or expired key returns `401`. Exception: when the `sessionId` does not resolve to a live session, the pre-auth owner lookup returns `404` first, even with missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid API key": { + "summary": "No x-api-key header, or the key is invalid", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Insufficient permission": { + "summary": "User token without delete:sessions, or credentials for an application that does not own the session", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No live KYC or KYB session with this `session_id` exists — the id is unknown or the session was already deleted (repeating a successful delete lands here).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not Found": { + "summary": "Unknown or already-deleted session", + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Shared write rate limit exceeded (300 POST/PATCH/DELETE requests per minute per API key). Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "Write budget exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + }, + "503": { + "description": "The deletion could not finish because a storage service, a biometric store, or a monitoring provider was unavailable. The session is still in place; repeat the same request until it returns `200`. Some of its stored files or its face embedding may already be deleted, so do not rely on them after a `503`. Retries converge on a single template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Session deletion unavailable": { + "summary": "A storage service or a monitoring provider was unavailable", + "value": { + "detail": "The session could not be deleted because a storage service or a monitoring provider is unavailable. The session is still in place; repeat the request to finish deleting it." + } + }, + "Biometric store unavailable": { + "summary": "A biometric store was unavailable while retaining or purging a template", + "value": { + "detail": "The biometric-template operation could not finish because an external store is unavailable. The operation is retryable." + } + } + } + } + } + }, + "409": { + "description": "The session is part of a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl (default policy)", + "source": "curl -X DELETE \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/delete/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "curl", + "label": "curl (retain template)", + "source": "curl -X DELETE \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/delete/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"retain_face_embeddings\": true, \"face_retention_days\": 365}'" + }, + { + "lang": "curl", + "label": "curl (privacy erasure)", + "source": "curl -X DELETE \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/delete/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"deletion_instruction\": \"privacy_erasure\", \"instruction_id\": \"dsar-2026-0142\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.delete(\n \"11111111-2222-3333-4444-555555555555\",\n retain_face_embeddings=True,\n face_retention_days=365,\n instruction_id=\"post-approval-cleanup-2026-08\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.delete(\"11111111-2222-3333-4444-555555555555\", {\n retain_face_embeddings: true,\n face_retention_days: 365,\n instruction_id: \"post-approval-cleanup-2026-08\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Delete(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsDeleteParams{\n\tRetainFaceEmbeddings: didit.Bool(true),\n\tFaceRetentionDays: didit.Int(365),\n\tInstructionID: didit.String(\"post-approval-cleanup-2026-08\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsDeleteParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().delete(\"11111111-2222-3333-4444-555555555555\", SessionsDeleteParams.builder()\n .retainFaceEmbeddings(true)\n .faceRetentionDays(365L)\n .instructionId(\"post-approval-cleanup-2026-08\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->delete('11111111-2222-3333-4444-555555555555', [\n 'retain_face_embeddings' => true,\n 'face_retention_days' => 365,\n 'instruction_id' => 'post-approval-cleanup-2026-08',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.delete(\n \"11111111-2222-3333-4444-555555555555\",\n retain_face_embeddings: true,\n face_retention_days: 365,\n instruction_id: \"post-approval-cleanup-2026-08\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.DeleteAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsDeleteParams\n{\n RetainFaceEmbeddings = true,\n FaceRetentionDays = 365,\n InstructionId = \"post-approval-cleanup-2026-08\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": false, + "description": "Optional. Omit the body entirely to follow the application's retention policy with an operational deletion.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "retain_face_embeddings": { + "type": "boolean", + "nullable": true, + "description": "Override the application's `face_retention_policy` for this deletion. `true` keeps one image-free face biometric template anchored to the session's User after the session is deleted; `false` deletes the face embedding with the session; omit or `null` to follow the application policy. Ignored for KYB sessions and for sessions without a face embedding.", + "example": true + }, + "face_retention_days": { + "type": "integer", + "minimum": 1, + "maximum": 3650, + "description": "Finite retention duration, in days, for a retained template. Required when a template is retained unless the application already sets `face_retention_days`. The template's `expires_at` is the earliest of this duration, the application's general data-retention window, and `face_retention_deadline`.", + "example": 365 + }, + "face_retention_deadline": { + "type": "string", + "format": "date-time", + "description": "Optional hard expiry for a retained template. Must be in the future.", + "example": "2027-08-28T00:00:00Z" + }, + "deletion_instruction": { + "type": "string", + "enum": [ + "operational_session_delete", + "privacy_erasure" + ], + "default": "operational_session_delete", + "description": "The class of instruction you are giving. `operational_session_delete` deletes the session and retains a template only when the policy or `retain_face_embeddings` says so. `privacy_erasure` purges every retained biometric template anchored to the session's User before deleting the session, cannot be overridden by the application policy, and cannot be combined with `retain_face_embeddings: true` (`400`).", + "example": "operational_session_delete" + }, + "instruction_id": { + "type": "string", + "maxLength": 255, + "description": "Your durable reference for this deletion instruction (for example an erasure-ticket id). Recorded on the audit trail and on any retained template. Generated by Didit when omitted.", + "example": "dsar-2026-0142" + } + } + }, + "examples": { + "Retain a biometric template": { + "summary": "Delete the session, keep an image-free template for 365 days", + "value": { + "retain_face_embeddings": true, + "face_retention_days": 365, + "instruction_id": "post-approval-cleanup-2026-08" + } + }, + "Force deletion on an opted-in application": { + "summary": "Delete the face embedding regardless of the application policy", + "value": { + "retain_face_embeddings": false + } + }, + "Privacy erasure": { + "summary": "Data-subject request: delete the session and purge every retained template for that person", + "value": { + "deletion_instruction": "privacy_erasure", + "instruction_id": "dsar-2026-0142" + } + } + } + } + } + } + } + }, + "/v3/session/{sessionId}/generate-pdf/": { + "get": { + "summary": "Download a PDF report for a verification session", + "description": "Render and download a compliance-ready PDF report for a finished verification session. The same path serves both User Verification (KYC) and Business Verification (KYB) sessions — the server resolves the `sessionId` against both session types and picks the matching report template.\n\n**Eligible statuses.** The session must already carry a final or reviewable status:\n\n- KYC sessions: `Approved`, `Declined`, `In Review`, or `Kyc Expired`.\n- KYB sessions: `Approved`, `Declined`, or `In Review`.\n\nAny other status (`Not Started`, `In Progress`, `Expired`, `Abandoned`, …) returns `403` with an explanatory `detail` string — see the `403` response below.\n\n**What the report contains.** The PDF mirrors the console session view at the moment of the request:\n\n- **KYC** — rolled-up session status, the warnings list, and one section per executed feature: ID Verification (document images plus extracted fields), NFC, Liveness, Face Match, Email Verification, Phone Verification, Proof of Address, Questionnaire answers, Database Validation, AML Screening, and IP Analysis — followed by session tags, console review activity (comments and status changes), and the session event timeline.\n- **KYB** — registry checks (company data), AML screenings, business document verifications, key-people checks, questionnaire responses, phone/email verifications, IP analyses, and the session event timeline, followed by tags and review activity.\n\n**Branding.** If your application has white-label customization, the report is rendered with your logo and links to your privacy-policy URL; otherwise it carries Didit branding. Reports are rendered in English — there is no language parameter.\n\n**No caching.** Every call re-renders the PDF from the current session data, so a report generated after a manual review reflects the reviewer’s decision. Two calls for the same session can produce byte-different files — archive the downloaded file if you need an immutable copy.\n\n**Latency.** Rendering downloads every stored image (document sides, selfies, extra files) before composing the PDF, so media-heavy sessions can take several seconds. Use a generous client read timeout (60 s recommended) and stream the body to disk.\n\n**Trailing slash.** The canonical route ends with a trailing slash (`…/generate-pdf/`). A request without it is served directly with the same response — there is no `301` redirect, so no client has to follow redirects and `curl` does not need `-L`. Use the slashed URL as in the samples.\n\nIf you need the underlying data as JSON instead of a rendered report, use `GET /v3/session/{sessionId}/decision/`. For every reportable session of one user in a single document, use `GET /v3/users/{vendor_data}/generate-pdf/`.", + "operationId": "get_v3_session_generate_pdf", + "x-didit-sdk": { + "resource": "sessions", + "method": "generate_pdf", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) or Business Verification (KYB) session to render — the `session_id` returned by `POST /v3/session/`. The same path works for both session kinds; use the `Content-Disposition` filename on the response to tell which report type you received.\n\n**Must be a well-formed, lowercase UUID.** The route only matches valid UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below. The route's UUID converter only matches the canonical lowercase form — uppercase-hex UUIDs are rejected with the HTML 404.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "responses": { + "200": { + "description": "The rendered PDF document, returned directly as binary `application/pdf` — there is no JSON wrapper and no download-URL indirection. The body starts with the `%PDF` magic bytes (PDF 1.7). Save it to a `.pdf` file or stream it through to your caller.", + "headers": { + "Content-Disposition": { + "description": "`attachment; filename=session_{sessionId}.pdf` for KYC sessions, `attachment; filename=business_{sessionId}.pdf` for KYB sessions — the prefix tells you which report template was rendered.", + "schema": { + "type": "string", + "example": "attachment; filename=session_11111111-2222-3333-4444-555555555555.pdf" + } + }, + "Content-Length": { + "description": "Total size of the PDF in bytes; the body is not chunked.", + "schema": { + "type": "integer", + "example": 25191 + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "403": { + "description": "Authentication, authorization, or session-status failure. A missing key returns `403`; a wrong, revoked or expired key returns `401`. Distinguish the failure modes by the `detail` string:\n\n- `\"Authentication credentials were not provided or are invalid.\"` — no `x-api-key` header (and no `Authorization: Bearer` token), or the supplied credential failed introspection (revoked, expired, or malformed).\n- `\"You do not have permission to perform this action.\"` — the credential is valid but cannot read this session: a console-user Bearer token lacks the `read:sessions` permission or cannot access the owning application, or an API key belongs to a different organization than the session.\n- `\"You can only generate a PDF for sessions in review, declined, approved or kyc expired\"` — the KYC session exists but is not in an eligible status yet (for example `Not Started` or `In Progress`). Wait for the `status.updated` webhook before requesting the report.\n- `\"You can only generate a PDF for sessions in review, declined or approved\"` — same condition for KYB sessions.\n\nNote that for a completely unknown `sessionId` the endpoint resolves the owning application before validating credentials, so a missing session returns `404` even when the credential is missing or invalid.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You can only generate a PDF for sessions in review, declined, approved or kyc expired" + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed introspection", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Insufficient permissions": { + "summary": "Valid credential lacking read:sessions or access to the owning application", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "KYC session not finished": { + "summary": "KYC session still Not Started / In Progress", + "value": { + "detail": "You can only generate a PDF for sessions in review, declined, approved or kyc expired" + } + }, + "KYB session not finished": { + "summary": "KYB session still Not Started / In Progress", + "value": { + "detail": "You can only generate a PDF for sessions in review, declined or approved" + } + } + } + } + } + }, + "404": { + "description": "No active session with this `sessionId` exists — it was never created, or it has been deleted. Owner resolution happens before credential validation, so this `404` is returned even for unauthenticated requests. Depending on which lookup misses, the `detail` string is `\"Not found.\"` (the common case) or `\"Session not found.\"`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Not found." + } + } + }, + "examples": { + "Unknown session": { + "summary": "sessionId does not match any KYC or KYB session", + "value": { + "detail": "Not found." + } + }, + "Recently deleted session": { + "summary": "Session was deleted between lookups", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. PDF generation has a dedicated limit of **50 requests per minute per credential** (API key, Bearer token, or source IP when unauthenticated), in addition to the global 600/min GET limit. Wait `Retry-After` seconds (also reflected in the `X-RateLimit-*` headers), then retry. Reports are not cached server-side, so download each report once and store the file instead of re-fetching it.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying the request.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Maximum number of PDF generations allowed in the current window (50).", + "schema": { + "type": "integer", + "example": 50 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests remaining in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix timestamp (seconds) at which the current window resets.", + "schema": { + "type": "integer", + "example": 1750000000 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable explanation of the rate-limit breach.", + "example": "Session PDF generation rate limit exceeded. You can make up to 50 requests per minute." + } + } + }, + "examples": { + "Throttled": { + "summary": "More than 50 PDF generations in one minute from the same credential", + "value": { + "detail": "Session PDF generation rate limit exceeded. You can make up to 50 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --fail \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/generate-pdf/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n --output report.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.sessions.generate_pdf(\n \"11111111-2222-3333-4444-555555555555\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.sessions.generatePdf(\"11111111-2222-3333-4444-555555555555\");\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Sessions.GeneratePDF(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().generatePdf(\"11111111-2222-3333-4444-555555555555\")\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->generatePdf('11111111-2222-3333-4444-555555555555');\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.sessions.generate_pdf(\"11111111-2222-3333-4444-555555555555\")\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Sessions.GeneratePdfAsync(\"11111111-2222-3333-4444-555555555555\");\nawait content.SaveToAsync(\"download.pdf\");" + } + ] + } + }, + "/v3/session/{sessionId}/sandbox/arm/": { + "post": { + "summary": "Arm a sandbox scenario on a session", + "description": "Persist a sandbox scenario on a `Not Started` session and expand its bundled magic values (email, phone, or `expected_details` fields) onto the session, so the mocked providers reproduce the scenario's outcome once the flow runs. This is the endpoint the pre-flow scenario picker in Didit's hosted verification UI calls when a tester chooses an outcome before starting capture. Build your own picker against it, or call it directly from a sandbox test harness.\n\nAuthenticate with the session's own `Session-Token` (returned in `session_token` by [Create Session](/sessions-api/create-session)), not your application `x-api-key`: this endpoint is meant to be called from the client, not your backend. A scenario is only accepted while `status` is `Not Started`. Calling it again while still `Not Started` re-arms the session: it swaps in the new scenario's magic values and clears only the values the *previous* scenario wrote, so any value the user already typed by hand (email, first name, etc.) survives the re-arm. Arming a session that has moved past `Not Started`, or that belongs to a live (non-sandbox) application, is rejected.\n\nSee [Sandbox & Test Data](/integration/sandbox-testing) for the full scenario catalog and the other two ways to drive an outcome: `sandbox_scenario` at session create, and typing magic values directly.", + "operationId": "post_v3_session_sandbox_arm", + "x-didit-sdk": { + "resource": "sessions", + "method": "arm_sandbox", + "auth": "device_token", + "server": false + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "SessionTokenAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the sandbox verification session to arm. Must match the session encoded in the Session-Token used to authenticate.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "scenario" + ], + "properties": { + "scenario": { + "type": "string", + "description": "Slug of the scenario to arm. See the catalog at GET /v1/sandbox/scenarios/ or the scenario table in [Sandbox & Test Data](/integration/sandbox-testing).", + "example": "decline_document_expired" + } + } + }, + "examples": { + "Arm": { + "summary": "Arm the AML PEP-hit scenario", + "value": { + "scenario": "decline_aml_hit" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Scenario armed. Its magic values are now on the session. `status` reflects the session's current status, which this call never changes: it is always `Not Started` on success.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid" + }, + "scenario": { + "type": "string", + "description": "The armed scenario slug (echoes the request)." + }, + "status": { + "type": "string", + "example": "Not Started" + } + } + }, + "examples": { + "Armed": { + "summary": "Scenario armed on a Not Started session", + "value": { + "session_id": "11111111-2222-3333-4444-555555555555", + "scenario": "decline_aml_hit", + "status": "Not Started" + } + } + } + } + } + }, + "400": { + "description": "Validation error: an unknown scenario slug, or the session has already progressed past `Not Started`.", + "content": { + "application/json": { + "examples": { + "Unknown Scenario": { + "summary": "scenario is not in the catalog", + "value": { + "scenario": [ + "Unknown sandbox scenario: 'not-a-real-scenario'." + ] + } + }, + "Already Started": { + "summary": "Session moved past Not Started", + "value": [ + "Scenario can only be armed before the verification starts." + ] + } + } + } + } + }, + "404": { + "description": "The Session-Token does not resolve to this `session_id`, or the session belongs to a live (non-sandbox) application.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Live session, or session_id / token mismatch", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/sandbox/arm/ \\\n -H 'Session-Token: YOUR_SESSION_TOKEN' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"scenario\": \"decline_aml_hit\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/sandbox/arm/\",\n headers={\n \"Session-Token\": \"YOUR_SESSION_TOKEN\",\n \"Content-Type\": \"application/json\",\n },\n json={\"scenario\": \"decline_aml_hit\"},\n)\nresponse.raise_for_status()\nprint(response.json()) # {'session_id': '...', 'scenario': 'decline_aml_hit', 'status': 'Not Started'}" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const response = await fetch(\n 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/sandbox/arm/',\n {\n method: 'POST',\n headers: {\n 'Session-Token': sessionToken,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ scenario: 'decline_aml_hit' }),\n },\n);\nif (!response.ok) throw new Error(`HTTP ${response.status}`);\nconst { session_id, scenario, status } = await response.json();" + } + ] + } + }, + "/v3/session/{sessionId}/update-status/": { + "patch": { + "summary": "Approve, decline, or request resubmission for a verification session", + "description": "Manually override the final decision of a KYC or KYB verification session. Set `new_status` to `Approved` or `Declined` to record a manual decision, or to `Resubmitted` to clear specific verification steps and let the user redo them through the same verification URL. The endpoint accepts both user (KYC) and business (KYB) session IDs.\n\nCall it once a session has reached a reviewable state: the current status must be `Approved`, `Declined`, `In Review`, `Kyc Expired`, `Abandoned`, or `Resubmitted`. Sessions that are still `Not Started`, `In Progress`, or `Awaiting User`, or that ended as `Expired`, return `400`. Your API key needs the `write:sessions` permission (a scoped API key needs write access to decisions). The eligible-state check runs before body validation, so an ineligible session returns the `Wrong Current Status` error even when the payload is also invalid.\n\nFor `Resubmitted`, pass `nodes_to_resubmit` to choose which workflow steps the user must redo, or omit it to auto-select existing feature attempts in a resubmittable state (`Declined`, `In Review`, `Not Finished`, `Expired`). Never-attempted features are not auto-selected: list them explicitly in `nodes_to_resubmit`, or the request returns `400` when no recorded attempt needs resubmission. The selected steps' previous results move to the resubmission's history (returned by Get Decision with `include=resubmissions`), the steps are reordered to match the workflow graph, the session's expiration window restarts, and the original verification URL becomes usable again. Backend-only steps (`AML`, `DATABASE_VALIDATION`, `IP_ANALYSIS`) at the head of the list run immediately without user interaction; if every resubmitted step is backend-only, the session re-finalizes within the same request. `KYB_REGISTRY` and `KYB_KEY_PEOPLE` cannot be resubmitted directly: resubmit the relevant child KYC sessions instead and the parent business session recomputes automatically.\n\nEvery call that changes the status fires the `status.updated` [webhook](/integration/webhooks) once the change commits and appends an activity entry (with actor attribution and your `comment`) to the session's `reviews`, visible in [Get Decision](#get-/v3/session/-sessionId-/decision/) and in the console activity timeline. The user-profile aggregates linked to the session's `vendor_data` update as well. Declining disables ongoing AML monitoring for the session; re-approving re-enables it (from `Resubmitted`, `Declined`, or `Kyc Expired`) when the workflow has monitoring turned on. Approving or declining a child KYC that belongs to a KYB key-people check recomputes the parent business session's status. Set `send_email: true` (with `email_address`) to notify the user: a status notice for `Approved`/`Declined`, or a resubmission email containing the verification link and per-step reasons for `Resubmitted`. To check the email first, preview it with [Preview Decision Email](/sessions-api/preview-decision-email) (`Approved`, `Declined`) or [Preview Resubmission Email](/sessions-api/preview-resubmission) (`Resubmitted`) and pass its `preview_token` as `email_preview_token`: the update then sends exactly the previewed email, or returns `409` before changing anything when the email or the session changed since the preview or the receipt is older than 15 minutes.\n\nRepeating `Approved` or `Declined` on a session that already has that status changes nothing and returns `200`: no webhook, no activity entry and no email. Repeating `Resubmitted` returns `400` (`The new status is the same as the current status.`). Missing or invalid credentials and missing permissions return `403`; an expired scoped API key returns `401`.", + "operationId": "patch_v3_session_update_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session to update. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "new_status" + ], + "properties": { + "new_status": { + "type": "string", + "description": "Target status. `Approved` and `Declined` record a final manual decision (each can also overturn the other). `Resubmitted` clears the selected steps and sends the session back to the user. Any other value returns `400`.", + "enum": [ + "Approved", + "Declined", + "Resubmitted" + ], + "example": "Approved" + }, + "comment": { + "type": "string", + "description": "Free-text reason for the change, stored on the session's review trail and returned in the `reviews` array of [Get Decision](#get-/v3/session/-sessionId-/decision/). For example `Duplicated user`.", + "example": "All checks passed manual review" + }, + "nodes_to_resubmit": { + "type": "array", + "description": "Workflow steps the user must redo. Only acted on when `new_status` is `Resubmitted`. For `Approved`/`Declined` the entries are still schema-validated (an invalid `feature` value returns `400`) but schema-valid entries are semantically ignored. If omitted, the server auto-selects existing OCR, Liveness, Face Match, POA, Phone, Email, AML, Database Validation, and Questionnaire attempts whose status is `Declined`, `In Review`, `Not Finished`, or `Expired` (NFC, IP analysis, age estimation, face search, and KYB document attempts are never auto-selected; non-face-match face attempts are selected as `LIVENESS`) — features the user never attempted (no recorded attempt) are NOT selected, so a session with zero attempts returns `400` (\"No features found that need resubmission\") even though nothing was approved; pass `nodes_to_resubmit` explicitly in that case. Steps are executed in workflow-graph order regardless of the order you send them. `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and the `KYB` alias are rejected with `400` — those parent checks recompute from their child KYC sessions.", + "items": { + "type": "object", + "required": [ + "node_id", + "feature" + ], + "properties": { + "node_id": { + "type": "string", + "description": "Node identifier as it appears in the session's workflow definition (for example `feature_ocr`, `feature_liveness`).", + "example": "feature_ocr" + }, + "feature": { + "type": "string", + "description": "Feature type of the node. The aliases `ID_VERIFICATION`, `POA`, `PHONE`, and `EMAIL` are normalized server-side to `OCR`, `PROOF_OF_ADDRESS`, `PHONE_VERIFICATION`, and `EMAIL_VERIFICATION`. `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and `KYB` pass schema validation but are always rejected with the business-rule `400` shown in the examples.", + "enum": [ + "OCR", + "OCR_BACK", + "NFC", + "AML", + "FACE", + "LIVENESS", + "FACE_MATCH", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "PROOF_OF_ADDRESS", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "FACE_SEARCH", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_DOCUMENTS", + "ID_VERIFICATION", + "POA", + "PHONE", + "EMAIL", + "KYB_REGISTRY", + "KYB_KEY_PEOPLE", + "KYB", + "GEOLOCATION" + ], + "example": "OCR" + } + } + } + }, + "send_email": { + "type": "boolean", + "description": "Whether to email the user about the change. Requires `email_address`. For `Approved`/`Declined` the user receives a status notice; for `Resubmitted` the email includes the verification link and the per-step resubmission reasons.", + "default": false, + "example": false + }, + "email_address": { + "type": "string", + "format": "email", + "description": "Recipient for the notification email. **Required when `send_email` is `true`** — omitting it returns `400`.", + "example": "user@example.com" + }, + "email_language": { + "type": "string", + "description": "Language for the notification email. Accepts any string at schema level; unsupported codes silently fall back to English (`en`).", + "default": "en", + "example": "en" + }, + "email_preview_token": { + "type": "string", + "writeOnly": true, + "minLength": 1, + "maxLength": 512, + "description": "Optional receipt from [Preview Decision Email](/sessions-api/preview-decision-email) (for `Approved` and `Declined`) or [Preview Resubmission Email](/sessions-api/preview-resubmission) (for `Resubmitted`): its `preview_token`. Valid only with `send_email: true`; without it the request returns `400`. The recipient, language, session status, sender and email (and, for a resubmission, the selected steps) must still match the preview, and the receipt expires after 15 minutes. Otherwise the request returns `409` before the session changes or an email is sent. Existing integrations may omit this field." + } + } + }, + "examples": { + "Approve Session": { + "summary": "Approve a session after manual review", + "value": { + "new_status": "Approved", + "comment": "All checks passed manual review" + } + }, + "Decline Session": { + "summary": "Decline a session", + "value": { + "new_status": "Declined", + "comment": "Suspected fraud" + } + }, + "Resubmit Specific Steps": { + "summary": "Request resubmission of chosen steps, with email", + "value": { + "new_status": "Resubmitted", + "nodes_to_resubmit": [ + { + "node_id": "feature_ocr", + "feature": "OCR" + }, + { + "node_id": "feature_liveness", + "feature": "LIVENESS" + } + ], + "send_email": true, + "email_address": "user@example.com", + "email_language": "en" + } + }, + "Resubmit Auto-select": { + "summary": "Request resubmission (auto-select non-approved steps)", + "value": { + "new_status": "Resubmitted" + } + }, + "Approve With Previewed Email": { + "summary": "Approve and send the email returned by Preview Decision Email", + "value": { + "new_status": "Approved", + "send_email": true, + "email_address": "person@example.com", + "email_language": "en", + "email_preview_token": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Status updated. The response contains only the `session_id`; fetch the updated session via [Get Decision](#get-/v3/session/-sessionId-/decision/). The `status.updated` webhook fires once the change commits. Repeating `Approved` or `Declined` returns the same body and changes nothing.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the updated session." + } + } + }, + "examples": { + "Updated": { + "summary": "Returned for Approved, Declined, and Resubmitted alike", + "value": { + "session_id": "3472fb7c-8f7c-4d1a-9cf4-cf3d74ce1a60" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field errors arrive as `{\"\": [\"\"]}` (some business-rule rejections use a string value instead: `{\"nodes_to_resubmit\": \"\"}`), cross-state errors as `{\"detail\": \"\"}`, and the same-status error as a bare JSON array.", + "content": { + "application/json": { + "examples": { + "Wrong Current Status": { + "summary": "Session is not in an updatable state", + "value": { + "detail": "Only sessions with status Approved, Declined, In Review, Kyc Expired, Abandoned, Resubmitted can be updated. Current status: In Progress" + } + }, + "Invalid Status Value": { + "summary": "new_status is a real session status but not one of Approved/Declined/Resubmitted", + "value": { + "new_status": [ + "You can only update the status to 'Approved', 'Declined', or 'Resubmitted'." + ] + } + }, + "Missing new_status": { + "summary": "Required field omitted", + "value": { + "new_status": [ + "This field is required." + ] + } + }, + "Same Status": { + "summary": "Resubmitted requested on a session that is already Resubmitted", + "value": [ + "The new status is the same as the current status." + ] + }, + "Email Address Required": { + "summary": "send_email true without email_address", + "value": { + "email_address": [ + "Email address is required when send_email is true." + ] + } + }, + "Non-resubmittable Feature": { + "summary": "Resubmit targets a KYB parent check", + "value": { + "nodes_to_resubmit": "The following features cannot be resubmitted directly: KYB_REGISTRY. Resubmit the relevant child KYC sessions instead; the parent will recompute automatically." + } + }, + "Nothing To Resubmit": { + "summary": "Auto-select found no non-approved features", + "value": { + "nodes_to_resubmit": "No features found that need resubmission. All features are already approved." + } + }, + "Invalid Feature Choice": { + "summary": "Unknown feature in nodes_to_resubmit", + "value": { + "nodes_to_resubmit": [ + { + "feature": [ + "\"BOGUS\" is not a valid choice. Valid choices are: OCR, OCR_BACK, NFC, AML, FACE, LIVENESS, FACE_MATCH, IP_ANALYSIS, AGE_ESTIMATION, PROOF_OF_ADDRESS, PHONE_VERIFICATION, EMAIL_VERIFICATION, FACE_SEARCH, DATABASE_VALIDATION, QUESTIONNAIRE, KYB_REGISTRY, KYB_DOCUMENTS, KYB_KEY_PEOPLE, ID_VERIFICATION, POA, PHONE, EMAIL, KYB" + ] + } + ] + } + }, + "Invalid Status Value (not a status at all)": { + "summary": "new_status is not a valid StatusChoices value", + "value": { + "new_status": [ + "\"Bogus\" is not a valid choice." + ] + } + }, + "Preview Receipt Without Email": { + "summary": "email_preview_token sent without send_email: true", + "value": { + "email_preview_token": [ + "A preview receipt is only valid when the email is sent." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "API key lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists (deleted sessions also return `404`).", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "A resubmit targeting only backend-only features (AML, DATABASE_VALIDATION, IP_ANALYSIS) was repeated within the 30-second cooldown window; an immediate repeat cannot change the outcome. Back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Backend-only resubmit throttled": { + "value": { + "detail": "This session was already resubmitted for backend-only features within the last 30 seconds; repeating it cannot change the outcome. Wait before resubmitting again." + } + } + } + } + } + }, + "409": { + "description": "The email preview changed or expired. Generate a new preview before confirming; no session changes or email delivery occur.", + "content": { + "application/json": { + "examples": { + "Preview Changed": { + "summary": "The receipt no longer matches, or is older than 15 minutes", + "value": { + "detail": "This email preview changed or expired. Preview the message again before confirming." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"new_status\": \"Approved\",\n \"comment\": \"All checks passed manual review\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_status(\n \"11111111-2222-3333-4444-555555555555\",\n new_status=\"Approved\",\n comment=\"All checks passed manual review\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateStatus(\"11111111-2222-3333-4444-555555555555\", {\n new_status: \"Approved\",\n comment: \"All checks passed manual review\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateStatus(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsUpdateStatusParams{\n\tNewStatus: \"Approved\",\n\tComment: didit.String(\"All checks passed manual review\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsUpdateStatusParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateStatus(\"11111111-2222-3333-4444-555555555555\", SessionsUpdateStatusParams.builder()\n .newStatus(SessionsUpdateStatusParams.NewStatus.of(\"Approved\"))\n .comment(\"All checks passed manual review\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateStatus('11111111-2222-3333-4444-555555555555', [\n 'new_status' => 'Approved',\n 'comment' => 'All checks passed manual review',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_status(\n \"11111111-2222-3333-4444-555555555555\",\n new_status: \"Approved\",\n comment: \"All checks passed manual review\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateStatusAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsUpdateStatusParams\n{\n NewStatus = \"Approved\",\n Comment = \"All checks passed manual review\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/id-verification/": { + "post": { + "summary": "ID Verification (document OCR + fraud checks)", + "description": "OCR an identity document and run document fraud checks in one call — returns the extracted holder fields, the parsed MRZ, an `Approved`/`Declined`/`In Review` `status`, and a `warnings` list explaining every issue found. Supports 14,000+ document types from 220+ countries and territories.\n\n**How it works.** Send the document's `front_image` (required) and, for two-sided documents, the `back_image`. Upload uncropped photos with all four corners of the document visible — the service detects, aligns, and crops the document itself, so do not pre-crop. The document is classified automatically (you never declare the country or document type), the visual zone is read with OCR, and the MRZ and any barcodes are decoded. Cross-checks then validate dates, number formats, and MRZ check digits, and compare the visual zone against the MRZ. With `perform_document_liveness=true`, the images are additionally screened for screen replays, printed copies, and portrait manipulation.\n\n**Decision logic.** `status` is `Approved` unless at least one warning resolves to a decline. Fraud and hard-failure risks always decline: `DOCUMENT_EXPIRED`, `SCREEN_CAPTURE_DETECTED`, `PRINTED_COPY_DETECTED`, `PORTRAIT_MANIPULATION_DETECTED`, `PUBLIC_DOCUMENT_IMAGE_DETECTED`, plus extraction failures (`NAME_NOT_DETECTED`, `DATE_OF_BIRTH_NOT_DETECTED`, `DOCUMENT_NUMBER_NOT_DETECTED`). (`PORTRAIT_IMAGE_NOT_DETECTED`, `INVALID_DATE`, and `MRZ_NOT_DETECTED` exist in workflow sessions but are never produced by this standalone endpoint. `COULD_NOT_DETECT_DOCUMENT_TYPE` is returned here when the front image does not match a known official document design with high confidence, and declines the request.) Three risk groups are configurable per request via `invalid_mrz_action`, `inconsistent_data_action`, and `expiration_date_not_detected_action` (`DECLINE` or `NO_ACTION`). All other warnings (e.g. `POSSIBLE_DUPLICATED_USER`) are informational and never decline on their own. When `perform_document_liveness=true` (the default) and the forgery check returns no usable score — the provider failed, answered non-200, or answered HTTP 200 with `status=\"ERR\"` - the call returns `200` with `status: \"In Review\"` and a `DOCUMENT_LIVENESS_NOT_COMPUTED` warning (`log_type: \"warning\"`) instead of `Approved`: a check that did not run is not evidence that the document is genuine. This escalation is not configurable and never overrides a `Declined`; with `perform_document_liveness=false` the check is skipped and the status follows the remaining warnings as before. A document that cannot be processed at all returns `400` with `{\"error\": \"COULD_NOT_RECOGNIZE_DOCUMENT\"}`; a readable but problematic document returns `200` with `status: \"Declined\"` — always inspect `id_verification.status` and `id_verification.warnings`, not just the HTTP code.\n\n**Billing.** Each `200` response consumes one ID Verification API credit (paid from your plan credit or top-up balance). When the organization's balance cannot cover the call, the endpoint returns `403` with the not-enough-credits error before any image processing.\n\n**Session persistence (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session: it appears in the Business Console, the returned `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/`, the cropped document/portrait images are stored and returned as short-lived media URLs (`https:///ocr/...`), and a `status.updated` webhook is emitted to your configured webhook endpoints. When `false`, nothing is stored, `request_id` is a one-off correlation UUID, and `portrait_image`/`front_image`/`back_image` are returned inline as base64-encoded JPEG strings.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload (a fictional \"Sandbox User\" USA identity card), no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3id-verification", + "x-didit-sdk": { + "resource": "id_verification", + "method": "verify", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "front_image" + ], + "properties": { + "front_image": { + "type": "string", + "format": "binary", + "description": "Front side of the identity document. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp`, `pdf`. Maximum upload size: **10 MB** (larger files are rejected with `400`). PDFs are rendered to an image before OCR; encrypted PDFs require `front_image_password`. Images are automatically compressed to ~0.5 MB and EXIF orientation is applied. Upload the full, uncropped capture with all four corners of the document visible." + }, + "front_image_password": { + "type": "string", + "description": "Password to decrypt `front_image` when it is an encrypted PDF. Sending an encrypted PDF without (or with a wrong) password returns `400`.", + "writeOnly": true + }, + "back_image": { + "type": "string", + "format": "binary", + "description": "Back side of the document — send it whenever the document has one (many ID cards carry the MRZ or a barcode on the back; omitting it simply leaves the MRZ and back-side fields empty in the response — this endpoint does not raise a missing-MRZ warning). Same format and size limits as `front_image`. When omitted, `back_image` and back-side fields are `null` in the response." + }, + "back_image_password": { + "type": "string", + "description": "Password to decrypt `back_image` when it is an encrypted PDF.", + "writeOnly": true + }, + "perform_document_liveness": { + "type": "boolean", + "default": true, + "description": "Screens both images for presentation fraud: screen replays (`SCREEN_CAPTURE_DETECTED`), printed copies (`PRINTED_COPY_DETECTED`), and portrait manipulation (`PORTRAIT_MANIPULATION_DETECTED`). Any of these auto-declines. Enabled by default; set to `false` to skip fraud screening and reduce latency. When enabled and the provider returns no usable score, the response is `In Review` with a `DOCUMENT_LIVENESS_NOT_COMPUTED` warning rather than `Approved`.", + "example": true + }, + "minimum_age": { + "type": "integer", + "nullable": true, + "default": null, + "minimum": 1, + "maximum": 120, + "description": "Accepted and validated (1–120) but **currently not applied** by this endpoint — it never produces a `MINIMUM_AGE_NOT_MET` warning from this field. Enforce age rules from the returned `date_of_birth`/`age`, or use a verification-session workflow with age restrictions." + }, + "expiration_date_not_detected_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when no expiration date can be read (`EXPIRATION_DATE_NOT_DETECTED`). Default `NO_ACTION` because many identity documents print no expiry date. Note: a *detected and past* expiry date always declines (`DOCUMENT_EXPIRED`), regardless of this option." + }, + "invalid_mrz_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "DECLINE", + "description": "What to do when the extracted MRZ fails check-digit validation (`MRZ_VALIDATION_FAILED`). A missing MRZ raises no warning on this endpoint. Only relevant for documents that carry an MRZ. `REVIEW` is not supported on this endpoint and returns `400`." + }, + "inconsistent_data_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "DECLINE", + "description": "What to do when extracted data is internally inconsistent: `DATA_INCONSISTENT`, `MRZ_AND_DATA_EXTRACTED_FROM_OCR_NOT_SAME` (visual zone disagrees with the MRZ), or `DOCUMENT_NAME_DIFFERENT_FROM_OTHER_APPROVED_DOCUMENTS`." + }, + "critical_field_occlusion_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "DECLINE", + "description": "What to do when a critical printed field was physically covered on the capture (`CRITICAL_FIELD_OCCLUSION_DETECTED`) - an identity number, a name, the date of birth, or the expiry date. Declines by default, like this endpoint's other document-integrity risks: covering the numbers a report is reconciled against is what a redacted document looks like. The value read for a covered field is dropped from the extraction either way, so the matching `*_NOT_DETECTED` warning usually accompanies it. `REVIEW` is not supported on this endpoint and returns `400`." + }, + "document_or_personal_number_format_mismatch_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when a number does not match the format its issuing authority uses for that document type (`DOCUMENT_NUMBER_FORMAT_MISMATCH`, `PERSONAL_NUMBER_FORMAT_MISMATCH`). Default `NO_ACTION`, which reports the warning without changing the status. `REVIEW` is not supported on this endpoint and returns `400`." + }, + "preferred_characters": { + "type": "string", + "enum": [ + "latin", + "non_latin" + ], + "default": "latin", + "description": "Preferred script for name/address fields on documents that print both Latin and non-Latin text (Arabic, Cyrillic, CJK, …). `latin` returns transliterated/Latin values; `non_latin` prefers the native script." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, announced through a `status.updated` webhook, and with document images returned as media URLs. When `false`, nothing is stored, `request_id` is a transient UUID, and images are returned inline as base64 JPEG strings.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"onboarding\"}`) — it is parsed into an object.", + "example": { + "flow": "onboarding" + } + } + } + }, + "example": { + "front_image": "(binary JPEG/PNG/PDF of the document front)", + "back_image": "(binary JPEG/PNG/PDF of the document back)", + "perform_document_liveness": true, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "onboarding" + } + } + } + } + }, + "responses": { + "200": { + "description": "Document processed. `id_verification.status` is `Approved`, `Declined`, or `In Review`; every detected issue is itemized in `id_verification.warnings`. `In Review` is returned when a requested document-liveness check produced no usable result (`DOCUMENT_LIVENESS_NOT_COMPUTED`). A problematic document still returns `200` with `status: \"Declined\"` — inspect the body, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id and image fields are short-lived media URLs; with `save_api_request=false` they are inline base64 JPEG strings.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Two-sided ID card read cleanly (saved — images as media URLs)", + "value": { + "request_id": "11d219ed-d59c-4b1d-8d65-9b933f12d5b8", + "id_verification": { + "status": "Approved", + "document_type": "Identity Card", + "document_subtype": "ID_CARD_GENERIC", + "document_number": "CBX164224", + "personal_number": "20446581H", + "portrait_image": "https:///ocr/11d219ed-d59c-4b1d-8d65-9b933f12d5b8-portrait_image-8c2f.jpg?signature=...", + "front_image": "https:///ocr/11d219ed-d59c-4b1d-8d65-9b933f12d5b8-front_image-4a1e.jpg?signature=...", + "back_image": "https:///ocr/11d219ed-d59c-4b1d-8d65-9b933f12d5b8-back_image-77b0.jpg?signature=...", + "front_image_camera_front": null, + "back_image_camera_front": null, + "front_image_camera_front_face_match_score": null, + "back_image_camera_front_face_match_score": null, + "front_image_quality_score": { + "focus_score": 78.0, + "brightness_score": 83.2, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 40.7, + "overall_score": 70.2 + }, + "back_image_quality_score": { + "focus_score": 100.0, + "brightness_score": 95.6, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 43.0, + "overall_score": 84.4 + }, + "date_of_birth": "1980-01-13", + "age": 46, + "expiration_date": "2032-03-31", + "date_of_issue": "2022-03-31", + "issuing_state": "ESP", + "issuing_state_name": "Spain", + "first_name": "Julio Francisco", + "last_name": "Fores Sena", + "full_name": "Julio Francisco Fores Sena", + "gender": "M", + "address": "Brda. Urb. El Cardonal 0052 52 Po3 B,Taco,San Cristobal De La Laguna,Santa Cruz De Tenerife", + "formatted_address": "Av. el Cardonal, 52, b, 38108 La Laguna, Santa Cruz de Tenerife, Spain", + "place_of_birth": "Valencia, Valencia", + "marital_status": "UNKNOWN", + "nationality": "ESP", + "extra_fields": { + "first_surname": "Fores", + "second_surname": "Sena" + }, + "mrz": { + "surname": "FORES SENA", + "name": "JULIO FRANCISCO", + "country": "ESP", + "nationality": "ESP", + "birth_date": "800113", + "expiry_date": "320331", + "sex": "M", + "document_type": "ID", + "document_number": "CBX164224", + "optional_data": "20446581H", + "optional_data_2": "", + "birth_date_hash": "9", + "expiry_date_hash": "8", + "document_number_hash": "3", + "final_hash": "3", + "personal_number": "20446581H", + "warnings": [], + "errors": [], + "mrz_type": "TD1", + "mrz_string": "IDESPCBX164224320446581H<<<<<<\n8001139M3203318ESP<<<<<<<<<<<3\nFORES/ocr/f61681b8-9422-4378-ac3e-04c6a247fb45-portrait_image-1d9a.jpg?signature=...", + "front_image": "https:///ocr/f61681b8-9422-4378-ac3e-04c6a247fb45-front_image-5c2b.jpg?signature=...", + "back_image": null, + "front_image_camera_front": null, + "back_image_camera_front": null, + "front_image_camera_front_face_match_score": null, + "back_image_camera_front_face_match_score": null, + "front_image_quality_score": { + "focus_score": 100.0, + "brightness_score": 95.7, + "brightness_issue": "ok", + "is_document_fully_visible": true, + "resolution_score": 22.2, + "overall_score": 79.3 + }, + "back_image_quality_score": null, + "date_of_birth": "1997-01-30", + "age": 29, + "expiration_date": "2025-11-26", + "date_of_issue": null, + "issuing_state": "ESP", + "issuing_state_name": "Spain", + "first_name": "Lucia", + "last_name": "Marin Castro", + "full_name": "Lucia Marin Castro", + "gender": "F", + "address": null, + "formatted_address": null, + "place_of_birth": null, + "marital_status": "UNKNOWN", + "nationality": "ESP", + "extra_fields": { + "first_surname": "Marin", + "second_surname": "Castro" + }, + "mrz": {}, + "parsed_address": null, + "warnings": [ + { + "risk": "DOCUMENT_EXPIRED", + "feature": "ID_VERIFICATION", + "additional_data": null, + "log_type": "error", + "short_description": "Document expired", + "long_description": "The document's expiration date has passed, rendering it no longer valid for use." + } + ], + "barcodes": [] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:25:48.117204+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "id_verification": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "description": "`Approved` when no warning resolves to a decline and the requested document-liveness check produced a usable result; `In Review` when that requested check produced no usable result (`DOCUMENT_LIVENESS_NOT_COMPUTED`); `Declined` when a warning resolves to a decline — the `warnings` list explains why." + }, + "document_type": { + "type": "string", + "nullable": true, + "description": "Detected document type display name: `Identity Card`, `Passport`, `Driver's License`, `Residence Permit`, `Health Insurance Card`, or `Tax Card`. `null` when the document could not be classified.", + "example": "Identity Card" + }, + "document_subtype": { + "type": "string", + "nullable": true, + "description": "Finer-grained subtype when available (e.g. `ID_CARD_GENERIC`, `EPASSPORT`).", + "example": "ID_CARD_GENERIC" + }, + "document_number": { + "type": "string", + "nullable": true, + "example": "CBX164224" + }, + "personal_number": { + "type": "string", + "nullable": true, + "description": "Secondary personal/national number printed on some documents.", + "example": "20446581H" + }, + "portrait_image": { + "type": "string", + "nullable": true, + "description": "Cropped portrait photo from the document. A short-lived media URL (`https:///ocr/...`) when `save_api_request=true` (default); an inline base64-encoded JPEG string when `save_api_request=false`. `null` when no portrait was detected." + }, + "front_image": { + "type": "string", + "nullable": true, + "description": "Aligned crop of the document front. Media URL when saved, inline base64 JPEG otherwise." + }, + "back_image": { + "type": "string", + "nullable": true, + "description": "Aligned crop of the document back. `null` when no `back_image` was uploaded." + }, + "front_image_camera_front": { + "type": "string", + "nullable": true, + "description": "Always `null` on this endpoint (populated only in camera-capture verification sessions)." + }, + "back_image_camera_front": { + "type": "string", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "front_image_camera_front_face_match_score": { + "type": "number", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "back_image_camera_front_face_match_score": { + "type": "number", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "front_image_quality_score": { + "type": "object", + "nullable": true, + "description": "Capture-quality diagnostics for the uploaded image (`null` when the side was not provided or scoring failed). All scores are 0–100.", + "properties": { + "focus_score": { + "type": "number", + "format": "float", + "description": "Sharpness of the document crop (higher is sharper)." + }, + "brightness_score": { + "type": "number", + "format": "float", + "description": "Exposure quality (higher is better)." + }, + "brightness_issue": { + "type": "string", + "nullable": true, + "description": "`ok` when exposure is fine, otherwise the detected issue (e.g. too dark / too bright)." + }, + "is_document_fully_visible": { + "type": "boolean", + "nullable": true, + "description": "Whether all four corners of the document are inside the frame." + }, + "resolution_score": { + "type": "number", + "format": "float", + "description": "Effective resolution of the document area." + }, + "overall_score": { + "type": "number", + "format": "float", + "description": "Aggregate quality score." + } + } + }, + "back_image_quality_score": { + "type": "object", + "nullable": true, + "description": "Capture-quality diagnostics for the uploaded image (`null` when the side was not provided or scoring failed). All scores are 0–100.", + "properties": { + "focus_score": { + "type": "number", + "format": "float", + "description": "Sharpness of the document crop (higher is sharper)." + }, + "brightness_score": { + "type": "number", + "format": "float", + "description": "Exposure quality (higher is better)." + }, + "brightness_issue": { + "type": "string", + "nullable": true, + "description": "`ok` when exposure is fine, otherwise the detected issue (e.g. too dark / too bright)." + }, + "is_document_fully_visible": { + "type": "boolean", + "nullable": true, + "description": "Whether all four corners of the document are inside the frame." + }, + "resolution_score": { + "type": "number", + "format": "float", + "description": "Effective resolution of the document area." + }, + "overall_score": { + "type": "number", + "format": "float", + "description": "Aggregate quality score." + } + } + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "example": "1980-01-13" + }, + "age": { + "type": "integer", + "nullable": true, + "description": "Holder age in years, computed from `date_of_birth`.", + "example": 46 + }, + "expiration_date": { + "type": "string", + "format": "date", + "nullable": true, + "example": "2032-03-31" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "nullable": true, + "example": "2022-03-31" + }, + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Issuing country (ISO 3166-1 alpha-3).", + "example": "ESP" + }, + "issuing_state_name": { + "type": "string", + "nullable": true, + "example": "Spain" + }, + "first_name": { + "type": "string", + "nullable": true, + "example": "Julio Francisco" + }, + "last_name": { + "type": "string", + "nullable": true, + "example": "Fores Sena" + }, + "full_name": { + "type": "string", + "nullable": true, + "example": "Julio Francisco Fores Sena" + }, + "gender": { + "type": "string", + "nullable": true, + "description": "`M`, `F`, or `null` when not printed/detected.", + "example": "M" + }, + "address": { + "type": "string", + "nullable": true, + "description": "Address exactly as printed on the document." + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "Geocoded, normalized version of `address`." + }, + "place_of_birth": { + "type": "string", + "nullable": true, + "example": "Valencia, Valencia" + }, + "marital_status": { + "type": "string", + "nullable": true, + "description": "`UNKNOWN` unless the document prints marital status.", + "example": "UNKNOWN" + }, + "nationality": { + "type": "string", + "nullable": true, + "description": "Holder nationality (ISO 3166-1 alpha-3).", + "example": "ESP" + }, + "extra_fields": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Document-specific extras that have no dedicated column (e.g. `first_surname`/`second_surname` on Spanish IDs, license classes on driving licenses)." + }, + "mrz": { + "type": "object", + "description": "Parsed machine-readable zone. An **empty object** (`{}`) when the document has no MRZ or it could not be read — also check the `warnings` list in that case. Dates inside the MRZ use the raw `YYMMDD` format.", + "properties": { + "surname": { + "type": "string", + "example": "FORES SENA" + }, + "name": { + "type": "string", + "example": "JULIO FRANCISCO" + }, + "country": { + "type": "string", + "description": "Issuing country (ISO 3166-1 alpha-3).", + "example": "ESP" + }, + "nationality": { + "type": "string", + "description": "Holder nationality (ISO 3166-1 alpha-3).", + "example": "ESP" + }, + "birth_date": { + "type": "string", + "description": "Raw MRZ date of birth (`YYMMDD`).", + "example": "800113" + }, + "expiry_date": { + "type": "string", + "description": "Raw MRZ expiry date (`YYMMDD`).", + "example": "320331" + }, + "sex": { + "type": "string", + "example": "M" + }, + "document_type": { + "type": "string", + "description": "MRZ document-type code (e.g. `ID`, `P`).", + "example": "ID" + }, + "document_number": { + "type": "string", + "example": "CBX164224" + }, + "optional_data": { + "type": "string", + "nullable": true, + "example": "20446581H" + }, + "optional_data_2": { + "type": "string", + "nullable": true, + "example": "" + }, + "birth_date_hash": { + "type": "string", + "description": "MRZ check digit for the birth date.", + "example": "9" + }, + "expiry_date_hash": { + "type": "string", + "description": "MRZ check digit for the expiry date.", + "example": "8" + }, + "document_number_hash": { + "type": "string", + "description": "MRZ check digit for the document number.", + "example": "3" + }, + "final_hash": { + "type": "string", + "description": "Composite MRZ check digit.", + "example": "3" + }, + "personal_number": { + "type": "string", + "nullable": true, + "example": "20446581H" + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Non-fatal MRZ parsing warnings." + }, + "errors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "MRZ check-digit/parsing errors. Non-empty errors surface as an `MRZ_VALIDATION_FAILED` warning." + }, + "mrz_type": { + "type": "string", + "description": "MRZ layout: `TD1` (ID cards, 3 lines), `TD2`, or `TD3` (passports, 2 lines).", + "example": "TD1" + }, + "mrz_string": { + "type": "string", + "description": "Raw MRZ text as read, lines separated by `\\n`." + }, + "mrz_key": { + "type": "string", + "description": "Composite key (document number + check digits) used for cross-checks." + } + } + }, + "parsed_address": { + "type": "object", + "nullable": true, + "description": "Structured, geocoded breakdown of `address`. `null` when the document carries no address or it could not be parsed.", + "properties": { + "street_1": { + "type": "string", + "nullable": true, + "example": "Avenida el Cardonal 52" + }, + "street_2": { + "type": "string", + "nullable": true, + "example": "b" + }, + "city": { + "type": "string", + "nullable": true, + "example": "La Laguna" + }, + "region": { + "type": "string", + "nullable": true, + "example": "Canarias" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code.", + "example": "ES" + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "38108" + }, + "address_type": { + "type": "string", + "nullable": true, + "example": "Avenida" + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "Canonical geocoded address string.", + "example": "Av. el Cardonal, 52, b, 38108 La Laguna, Santa Cruz de Tenerife, Spain" + }, + "raw_results": { + "type": "object", + "additionalProperties": true, + "description": "Raw geocoding payload (`address_components`, `geometry.location`, `place_id`, …) for advanced consumers." + }, + "document_location": { + "type": "object", + "nullable": true, + "properties": { + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + } + }, + "description": "Geocoded coordinates of the document address." + }, + "label": { + "type": "string", + "nullable": true, + "description": "Which document field the address was read from.", + "example": "Spain Identity Card Address" + }, + "is_verified": { + "type": "boolean", + "description": "Whether geocoding confirmed the address exists." + }, + "category": { + "type": "string", + "nullable": true, + "description": "Address category (e.g. `Residential`, `Commercial`).", + "example": "Residential" + } + } + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. Every entry explains one detected risk; entries with `log_type: \"error\"` set `status` to `Declined`, and `DOCUMENT_LIVENESS_NOT_COMPUTED` (`log_type: \"warning\"`) sets it to `In Review`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "description": "Machine-readable risk code. Always-decline risks: `DOCUMENT_EXPIRED`, `MINIMUM_AGE_NOT_MET`, `PORTRAIT_IMAGE_NOT_DETECTED`, `SCREEN_CAPTURE_DETECTED`, `PRINTED_COPY_DETECTED`, `PORTRAIT_MANIPULATION_DETECTED`, `PUBLIC_DOCUMENT_IMAGE_DETECTED`, `INVALID_DATE`, `COULD_NOT_RECOGNIZE_DOCUMENT` (400-only, never a warning), `DOCUMENT_NUMBER_NOT_DETECTED`, `COULD_NOT_DETECT_DOCUMENT_TYPE`, `NAME_NOT_DETECTED`, `DATE_OF_BIRTH_NOT_DETECTED`. Governed by request options: `MRZ_NOT_DETECTED` / `MRZ_VALIDATION_FAILED` (`invalid_mrz_action`), `DATA_INCONSISTENT` / `MRZ_AND_DATA_EXTRACTED_FROM_OCR_NOT_SAME` / `DOCUMENT_NAME_DIFFERENT_FROM_OTHER_APPROVED_DOCUMENTS` (`inconsistent_data_action`), `EXPIRATION_DATE_NOT_DETECTED` (`expiration_date_not_detected_action`). Other risks (e.g. `POSSIBLE_DUPLICATED_USER`) are informational. `DOCUMENT_LIVENESS_NOT_COMPUTED` is raised when `perform_document_liveness=true` but the forgery check returned no usable score; it sets `status` to `In Review`. `COULD_NOT_DETECT_DOCUMENT_TYPE` is also returned when the front of the document does not match a known official document design with high confidence, for example an edited or computer-generated document, or a design that is not supported yet.", + "example": "DOCUMENT_EXPIRED" + }, + "feature": { + "type": "string", + "enum": [ + "ID_VERIFICATION" + ], + "description": "Feature that raised the warning. Always `ID_VERIFICATION` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Extra context for some risks (e.g. `POSSIBLE_DUPLICATED_USER` includes `duplicated_session_id`); `null` for most." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `warning` (only `DOCUMENT_LIVENESS_NOT_COMPUTED`, when the requested forgery check produced no usable score) drives `status` to `In Review`; `information` entries are advisory and never change the status." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "barcodes": { + "type": "array", + "description": "Barcodes decoded from the document (PDF417, QR, …). Empty when the document has none or none could be read.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Barcode type (e.g. `PDF417`, `QR_CODE`, `UNKNOWN`)." + }, + "position": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Location of the barcode in the image, when available." + }, + "data": { + "type": "string", + "description": "Decoded payload." + }, + "data_raw": { + "type": "string", + "description": "Raw (undecoded) payload, when different." + }, + "side": { + "type": "string", + "nullable": true, + "description": "`front` or `back`." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated." + } + } + } + } + } + }, + "400": { + "description": "Validation or processing error. Field-level problems return DRF's standard envelope (one array of messages per offending field). Encrypted-PDF problems return `{\"detail\": ...}`. A document image in which no document can be recognized at all returns `{\"error\": \"COULD_NOT_RECOGNIZE_DOCUMENT\"}`. PDF uploads can additionally fail with \"The submitted PDF file is corrupted or invalid.\" or a no-pages error.", + "content": { + "application/json": { + "examples": { + "Missing front image": { + "summary": "`front_image` not included in the form data", + "value": { + "front_image": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp/pdf", + "value": { + "front_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp, pdf." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 10 MB limit", + "value": { + "front_image": [ + "File size should not exceed 10 MB" + ] + } + }, + "Invalid option value": { + "summary": "Options only accept DECLINE or NO_ACTION", + "value": { + "invalid_mrz_action": [ + "\"REVIEW\" is not a valid choice." + ] + } + }, + "Encrypted PDF": { + "summary": "PDF uploaded without the required password", + "value": { + "detail": "The PDF is encrypted. Please upload a decrypted PDF or a photo instead." + } + }, + "Wrong PDF password": { + "summary": "`front_image_password` / `back_image_password` does not decrypt the PDF", + "value": { + "detail": "The PDF password is incorrect. Please provide the correct password." + } + }, + "Document not recognized": { + "summary": "No identity document could be detected in the image", + "value": { + "error": "COULD_NOT_RECOGNIZE_DOCUMENT" + } + }, + "PDF conversion failure": { + "summary": "Uploaded PDF could not be converted to an image", + "value": { + "error": "Failed to convert front image PDF." + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/id-verification/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'front_image=@./id_front.jpg' \\\n -F 'back_image=@./id_back.jpg' \\\n -F 'perform_document_liveness=true' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_verification.verify(\n front_image=open(\"front_image.jpg\", \"rb\"),\n back_image=open(\"back_image.jpg\", \"rb\"),\n perform_document_liveness=True,\n save_api_request=True,\n vendor_data=\"user-123\",\n metadata={\"flow\": \"onboarding\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idVerification.verify({\n front_image: await fileFromPath(\"front_image.jpg\"),\n back_image: await fileFromPath(\"back_image.jpg\"),\n perform_document_liveness: true,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"onboarding\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDVerification.Verify(ctx, didit.IDVerificationVerifyParams{\n\tFrontImage: didit.FileFromPath(\"front_image.jpg\"),\n\tBackImage: didit.Some(didit.FileFromPath(\"back_image.jpg\")),\n\tPerformDocumentLiveness: didit.Bool(true),\n\tSaveAPIRequest: didit.Bool(true),\n\tVendorData: didit.String(\"user-123\"),\n\tMetadata: didit.Some(map[string]any{\"flow\": \"onboarding\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.IdVerificationVerifyParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idVerification().verify(IdVerificationVerifyParams.builder()\n .frontImage(Path.of(\"front_image.jpg\"))\n .backImage(Path.of(\"back_image.jpg\"))\n .performDocumentLiveness(true)\n .saveApiRequest(true)\n .vendorData(\"user-123\")\n .metadata(Map.of(\"flow\", \"onboarding\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idVerification->verify([\n 'front_image' => File::fromPath('front_image.jpg'),\n 'back_image' => File::fromPath('back_image.jpg'),\n 'perform_document_liveness' => true,\n 'save_api_request' => true,\n 'vendor_data' => 'user-123',\n 'metadata' => ['flow' => 'onboarding'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_verification.verify(\n front_image: Pathname(\"front_image.jpg\"),\n back_image: Pathname(\"back_image.jpg\"),\n perform_document_liveness: true,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"onboarding\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdVerification.VerifyAsync(new IdVerificationVerifyParams\n{\n FrontImage = FileUpload.FromPath(\"front_image.jpg\"),\n BackImage = FileUpload.FromPath(\"back_image.jpg\"),\n PerformDocumentLiveness = true,\n SaveApiRequest = true,\n VendorData = \"user-123\",\n Metadata = new Dictionary { [\"flow\"] = \"onboarding\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/face-match/": { + "post": { + "summary": "Face Match", + "description": "Compare two face images (1:1) and get back a similarity `score` (0–100) plus an `Approved`/`Declined` `status`.\n\n**How it works.** Both images are decoded and analyzed by a biometric model (set `rotate_image=true` if captures may be sideways or upside down), then a similarity score between the two faces is computed. A score **strictly above** `face_match_score_decline_threshold` (default `30`) returns `status: \"Approved\"`; a score at or below it returns `Declined` with a `LOW_FACE_MATCH_SIMILARITY` warning. If no face pair can be scored at all, the call still returns `200` with `status: \"Declined\"` and a `NO_REFERENCE_IMAGE` warning — `score` is `null` on the default save path, or `0` with `save_api_request=false`. Submit one clear, front-facing face per image for best results.\n\n**Billing.** Each `200` response consumes one Face Match API credit (paid from your plan credit or top-up balance). When the organization's balance cannot cover the call, the endpoint returns `403` with the not-enough-credits error before any image processing.\n\n**Session persistence (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session: it appears in the Business Console, the returned `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/`, both images are stored as the session's face-match source/target images, and a `status.updated` webhook is emitted to your configured webhook endpoints. When `false`, nothing is stored and `request_id` is a one-off correlation UUID that cannot be looked up later.\n\n**Face search index.** This endpoint never enrolls faces into your face search index and never runs blocklist or duplicate screening — use `POST /v3/passive-liveness/` or `POST /v3/face-search/` for those.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload, no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3face-match", + "x-didit-sdk": { + "resource": "face_match", + "method": "compare", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "user_image", + "ref_image" + ], + "properties": { + "user_image": { + "type": "string", + "format": "binary", + "description": "Live or candidate face image to verify (e.g. a selfie). Submit a single front-facing photo with the subject's face clearly visible and well lit. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp`. Maximum upload size: **5 MB** (larger files are rejected with `400`). Images are automatically compressed to ~0.5 MB before processing, so very high resolutions do not improve accuracy." + }, + "ref_image": { + "type": "string", + "format": "binary", + "description": "Reference face image to compare against (e.g. an ID document portrait or a previously enrolled photo). Same format and size limits as `user_image`." + }, + "face_match_score_decline_threshold": { + "type": "number", + "format": "float", + "default": 30, + "minimum": 0, + "maximum": 100, + "description": "Similarity threshold (0–100) that drives the response `status`. A computed score **at or below** this value sets `status` to `Declined` and adds a `LOW_FACE_MATCH_SIMILARITY` warning; a score strictly above it returns `Approved`. Default `30` is permissive — raise it (e.g. `50`–`60`) for stricter verification. Values outside 0–100 return `400`.", + "example": 50 + }, + "rotate_image": { + "type": "boolean", + "default": false, + "description": "When `true`, the service tries the input images in 90-degree increments (0, 90, 180, 270) and uses the orientation that yields the best face detection — useful for mobile captures with missing EXIF orientation. Adds latency, so only enable it if you cannot guarantee upright images.", + "example": false + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, and announced through a `status.updated` webhook. When `false`, nothing is stored and `request_id` is a transient UUID for response correlation only.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"login\"}`) — it is parsed into an object.", + "example": { + "flow": "login", + "device_id": "abc123" + } + } + } + }, + "example": { + "user_image": "(binary JPEG/PNG of the live selfie)", + "ref_image": "(binary JPEG/PNG of the reference photo)", + "face_match_score_decline_threshold": 50, + "rotate_image": false, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "login" + } + } + } + } + }, + "responses": { + "200": { + "description": "Face match computed. `face_match.score` is a 0–100 float (two decimals); `status` is `Approved` when the score is strictly above `face_match_score_decline_threshold`, otherwise `Declined` with an explanatory warning. A low similarity or unscorable pair still returns `200` — inspect `face_match.status` and `face_match.warnings`, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Same person — score above the threshold", + "value": { + "request_id": "f9f5a777-8002-44c6-baaf-128bcfd6e226", + "face_match": { + "status": "Approved", + "score": 99.41, + "user_image": { + "entities": [ + { + "bbox": [ + 661, + 728, + 1688, + 2188 + ], + "confidence": 0.732973, + "age": 26.91, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "ref_image": { + "entities": [ + { + "bbox": [ + 653, + 745, + 1691, + 2185 + ], + "confidence": 0.722082, + "age": 27.0, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": { + "flow": "login" + }, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "Declined - low similarity": { + "summary": "Score at or below the decline threshold", + "value": { + "request_id": "7cdda24e-311a-46d3-a3bb-e5d9b564ce73", + "face_match": { + "status": "Declined", + "score": 24.87, + "user_image": { + "entities": [ + { + "bbox": [ + 661, + 728, + 1688, + 2188 + ], + "confidence": 0.732973, + "age": 26.91, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "ref_image": { + "entities": [ + { + "bbox": [ + 156, + 234, + 679, + 898 + ], + "confidence": 0.717775, + "age": 41.2, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "LOW_FACE_MATCH_SIMILARITY", + "feature": "FACEMATCH", + "additional_data": null, + "log_type": "error", + "short_description": "Low face match similarity", + "long_description": "The facial features of the provided image don't closely match the reference image, suggesting a potential identity mismatch." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:08:19.859116+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "face_match": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Approved` when the similarity score is strictly above `face_match_score_decline_threshold` and a score could be computed; `Declined` otherwise (a warning explains why)." + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Similarity score from 0 to 100 — the model's confidence that both images depict the same person. Rounded to two decimals only when `save_api_request` is true; with `save_api_request=false` the raw float is returned. `null` when no face pair could be scored (with the default `save_api_request=true`); with `save_api_request=false` the same condition can surface as `score: 0`. Either way a `NO_REFERENCE_IMAGE` warning is present and `status` is `Declined`.", + "example": 99.41 + }, + "user_image": { + "type": "object", + "description": "Face-detection results for the analyzed images.", + "properties": { + "entities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box of the detected face as `[x_min, y_min, x_max, y_max]` pixel coordinates in the processed image.", + "example": [ + 661, + 728, + 1688, + 2188 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.732973 + }, + "age": { + "type": "number", + "format": "float", + "description": "Model-estimated age of the detected face, in years. Informational only — it does not affect the face-match decision.", + "example": 26.91 + }, + "gender": { + "type": "string", + "description": "Model-predicted gender of the detected face (`male` or `female`). Informational only.", + "example": "male" + }, + "race": { + "type": "string", + "nullable": true, + "description": "Reserved field — always `null` in responses from this endpoint.", + "example": null + } + } + }, + "description": "One entry per detected face." + }, + "best_angle": { + "type": "integer", + "description": "Rotation (degrees: 0, 90, 180, or 270) that produced the best face detection. Only non-zero when `rotate_image=true` corrected the orientation.", + "example": 0 + } + } + }, + "ref_image": { + "type": "object", + "description": "Face-detection results for the reference image, same shape as `user_image`.", + "properties": { + "entities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box of the detected face as `[x_min, y_min, x_max, y_max]` pixel coordinates in the processed image.", + "example": [ + 661, + 728, + 1688, + 2188 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.732973 + }, + "age": { + "type": "number", + "format": "float", + "description": "Model-estimated age of the detected face, in years. Informational only — it does not affect the face-match decision.", + "example": 26.91 + }, + "gender": { + "type": "string", + "description": "Model-predicted gender of the detected face (`male` or `female`). Informational only.", + "example": "male" + }, + "race": { + "type": "string", + "nullable": true, + "description": "Reserved field — always `null` in responses from this endpoint.", + "example": null + } + } + }, + "description": "One entry per detected face." + }, + "best_angle": { + "type": "integer", + "example": 0 + } + } + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. `NO_REFERENCE_IMAGE` (`error`) — no face pair could be scored; `LOW_FACE_MATCH_SIMILARITY` (`error`) — score at or below the decline threshold. Any of these sets `status` to `Declined`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "NO_REFERENCE_IMAGE", + "LOW_FACE_MATCH_SIMILARITY" + ], + "description": "Machine-readable risk code." + }, + "feature": { + "type": "string", + "enum": [ + "FACEMATCH" + ], + "description": "Feature that raised the warning. Always `FACEMATCH` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Always `null` for face-match warnings." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `warning` and `information` entries are advisory and never decline on their own." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated, e.g. `2026-06-12T01:04:42.763237+00:00`." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when a required image is missing, exceeds 5 MB, has an unsupported extension, or when an option is out of range. The body is DRF's standard field-error envelope: one array of messages per offending field.", + "content": { + "application/json": { + "examples": { + "Missing required image": { + "summary": "`ref_image` (or `user_image`) not included in the form data", + "value": { + "ref_image": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp", + "value": { + "user_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 5 MB limit", + "value": { + "user_image": [ + "File size should not exceed 5 MB" + ] + } + }, + "Threshold out of range": { + "summary": "`face_match_score_decline_threshold` outside 0–100", + "value": { + "face_match_score_decline_threshold": [ + "Ensure this value is less than or equal to 100." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/face-match/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'user_image=@./selfie.jpg' \\\n -F 'ref_image=@./id_portrait.jpg' \\\n -F 'face_match_score_decline_threshold=50' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.face_match.compare(\n user_image=open(\"user_image.jpg\", \"rb\"),\n ref_image=open(\"ref_image.jpg\", \"rb\"),\n face_match_score_decline_threshold=50.0,\n rotate_image=False,\n save_api_request=True,\n vendor_data=\"user-123\",\n metadata={\"flow\": \"login\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.faceMatch.compare({\n user_image: await fileFromPath(\"user_image.jpg\"),\n ref_image: await fileFromPath(\"ref_image.jpg\"),\n face_match_score_decline_threshold: 50.0,\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"login\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.FaceMatch.Compare(ctx, didit.FaceMatchCompareParams{\n\tUserImage: didit.FileFromPath(\"user_image.jpg\"),\n\tRefImage: didit.FileFromPath(\"ref_image.jpg\"),\n\tFaceMatchScoreDeclineThreshold: didit.Float(50.0),\n\tRotateImage: didit.Bool(false),\n\tSaveAPIRequest: didit.Bool(true),\n\tVendorData: didit.String(\"user-123\"),\n\tMetadata: didit.Some(map[string]any{\"flow\": \"login\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.FaceMatchCompareParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.faceMatch().compare(FaceMatchCompareParams.builder()\n .userImage(Path.of(\"user_image.jpg\"))\n .refImage(Path.of(\"ref_image.jpg\"))\n .faceMatchScoreDeclineThreshold(50.0)\n .rotateImage(false)\n .saveApiRequest(true)\n .vendorData(\"user-123\")\n .metadata(Map.of(\"flow\", \"login\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "faceMatch->compare([\n 'user_image' => File::fromPath('user_image.jpg'),\n 'ref_image' => File::fromPath('ref_image.jpg'),\n 'face_match_score_decline_threshold' => 50.0,\n 'rotate_image' => false,\n 'save_api_request' => true,\n 'vendor_data' => 'user-123',\n 'metadata' => ['flow' => 'login'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.face_match.compare(\n user_image: Pathname(\"user_image.jpg\"),\n ref_image: Pathname(\"ref_image.jpg\"),\n face_match_score_decline_threshold: 50.0,\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"login\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.FaceMatch.CompareAsync(new FaceMatchCompareParams\n{\n UserImage = FileUpload.FromPath(\"user_image.jpg\"),\n RefImage = FileUpload.FromPath(\"ref_image.jpg\"),\n FaceMatchScoreDeclineThreshold = 50.0,\n RotateImage = false,\n SaveApiRequest = true,\n VendorData = \"user-123\",\n Metadata = new Dictionary { [\"flow\"] = \"login\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/age-estimation/": { + "post": { + "summary": "Age Estimation (face age + passive liveness)", + "description": "Estimate a person's age from a single face photo and run a passive liveness check in the same call — returns the model-predicted `age_estimation` (in years), a liveness `score` (0–100), and an `Approved`/`Declined` `status`. The age is **model-predicted from the face, not verified against a document** — use it for age gates and low-friction checks, not as legal proof of age.\n\n**How it works.** The image is analyzed for faces (set `rotate_image=true` if captures may be sideways or upside down); each detected face gets an estimated age, and the largest face in the frame drives `age_estimation`. In parallel, a passive liveness model scores whether the photo shows a live person (no user challenge required) and detects presentation attacks (photos of photos, screens, masks). Submit one clear, front-facing, well-lit face per image for best results.\n\n**Decision logic.** Any warning declines: `status` is `Approved` only when the `warnings` array is empty. A liveness `score` at or below `face_liveness_score_decline_threshold` (default `30`) adds `LOW_LIVENESS_SCORE`; a detected attack adds `LIVENESS_FACE_ATTACK`; no face found by the liveness model adds `NO_FACE_DETECTED`; no estimable face age adds `AGE_NOT_DETECTED` (`age_estimation` is `null`); an estimated age below `age_estimation_decline_threshold` (default `18`) adds `AGE_BELOW_MINIMUM`. Borderline ages near your threshold deserve a fallback to document-based verification — the model's error grows for faces close to the limit.\n\n**Billing.** Each `200` response consumes one Age Estimation API credit (paid from your plan credit or top-up balance). When the organization's balance cannot cover the call, the endpoint returns `403` with the not-enough-credits error before any image processing.\n\n**Session persistence (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session: it appears in the Business Console, the returned `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/` (the result is exposed there in `liveness_checks[]`, with `features: [\"AGE_ESTIMATION\"]`), the face crop and reference image are stored, the face embedding is added to your application's face index (used by face search and duplicate detection), and a `status.updated` webhook is emitted. When `false`, nothing is stored and `request_id` is a one-off correlation UUID that cannot be looked up later.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload (`age_estimation: 30`, no warnings), no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3age-estimation", + "x-didit-sdk": { + "resource": "age_estimation", + "method": "estimate", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "user_image" + ], + "properties": { + "user_image": { + "type": "string", + "format": "binary", + "description": "Face photo to analyze (e.g. a selfie). Submit a single front-facing photo with the subject's face clearly visible and well lit. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp` (no PDF). Maximum upload size: **5 MB** (larger files are rejected with `400`). Images are automatically compressed to ~0.3 MB before processing, so very high resolutions do not improve accuracy." + }, + "face_liveness_score_decline_threshold": { + "type": "number", + "format": "float", + "default": 30, + "minimum": 0, + "maximum": 100, + "description": "Liveness threshold (0–100). A liveness `score` **at or below** this value adds a `LOW_LIVENESS_SCORE` warning and declines. Raise it for stricter anti-spoofing; values outside 0–100 return `400`.", + "example": 30 + }, + "age_estimation_decline_threshold": { + "type": "integer", + "default": 18, + "minimum": 0, + "maximum": 100, + "description": "Minimum acceptable estimated age in years. An estimated age **below** this value adds an `AGE_BELOW_MINIMUM` warning and declines. Set `0` to disable the age check (the call then only runs liveness + age estimation without an age-based decision).", + "example": 18 + }, + "rotate_image": { + "type": "boolean", + "default": false, + "description": "When `true`, the service tries the input image in 90-degree increments (0, 90, 180, 270) and uses the orientation that yields the best face detection — useful for mobile captures with missing EXIF orientation. Adds latency, so only enable it if you cannot guarantee upright images.", + "example": false + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, announced through a `status.updated` webhook, and with the detected face added to your application's face index. When `false`, nothing is stored and `request_id` is a transient UUID for response correlation only.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"age-gate\"}`) — it is parsed into an object.", + "example": { + "flow": "age-gate" + } + } + } + }, + "example": { + "user_image": "(binary JPEG/PNG of the selfie)", + "face_liveness_score_decline_threshold": 30, + "age_estimation_decline_threshold": 18, + "rotate_image": false, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "age-gate" + } + } + } + } + }, + "responses": { + "200": { + "description": "Age estimation and passive liveness completed. `age_estimation.age_estimation` is the model-predicted age in years of the largest detected face (`null` if none); `age_estimation.score` is the passive-liveness score (0–100); `status` is `Approved` only when `warnings` is empty. An underage or spoofed capture still returns `200` with `status: \"Declined\"` — inspect the body, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Live adult face — both checks passed", + "value": { + "request_id": "0c40ba43-64ab-4e2e-b4b8-7d1f12f81bc1", + "age_estimation": { + "status": "Approved", + "method": "PASSIVE", + "score": 97.5, + "user_image": { + "entities": [ + { + "age": 27.33, + "bbox": [ + 40, + 40, + 100, + 100 + ], + "confidence": 0.7177750468254089, + "gender": "male" + } + ], + "best_angle": 0 + }, + "age_estimation": 27.33, + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": { + "flow": "age-gate" + }, + "created_at": "2026-06-12T02:24:11.512941+00:00" + } + }, + "Declined - age below threshold": { + "summary": "Estimated age below `age_estimation_decline_threshold`", + "value": { + "request_id": "9be41a6f-2f4e-4f57-92f4-b3a4f6f0a1c2", + "age_estimation": { + "status": "Declined", + "method": "PASSIVE", + "score": 95.0, + "user_image": { + "entities": [ + { + "age": 16.5, + "bbox": [ + 40, + 40, + 100, + 100 + ], + "confidence": 0.7177750468254089, + "gender": "female" + } + ], + "best_angle": 0 + }, + "age_estimation": 16.5, + "warnings": [ + { + "risk": "AGE_BELOW_MINIMUM", + "feature": "LIVENESS", + "additional_data": null, + "log_type": "error", + "short_description": "Age below minimum", + "long_description": "The age of the face is below the minimum age threshold for the application." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:26:40.118332+00:00" + } + }, + "Declined - liveness too low": { + "summary": "Liveness score at or below the decline threshold (possible spoof)", + "value": { + "request_id": "5f6f2f1f-7c4f-43b9-8d62-0a8f4c2d9e77", + "age_estimation": { + "status": "Declined", + "method": "PASSIVE", + "score": 5.0, + "user_image": { + "entities": [ + { + "age": 25.0, + "bbox": [ + 40, + 40, + 100, + 100 + ], + "confidence": 0.7177750468254089, + "gender": "male" + } + ], + "best_angle": 0 + }, + "age_estimation": 25.0, + "warnings": [ + { + "risk": "LOW_LIVENESS_SCORE", + "feature": "LIVENESS", + "additional_data": null, + "log_type": "error", + "short_description": "Low liveness score", + "long_description": "The liveness check resulted in a low score, indicating potential use of non-live facial representations or poor-quality biometric data." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:27:02.901547+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "age_estimation": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Approved` when no warnings were raised; `Declined` otherwise (every warning this endpoint produces declines)." + }, + "method": { + "type": "string", + "enum": [ + "PASSIVE" + ], + "description": "Liveness method. Always `PASSIVE` on this endpoint (single-image, no user challenge)." + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Passive-liveness score from 0 to 100 — the model's confidence that the photo shows a live person. Compared against `face_liveness_score_decline_threshold`; at or below it the call declines with `LOW_LIVENESS_SCORE`. `null` when the liveness model returns no confidence — the call still declines with `LOW_LIVENESS_SCORE` because null is treated as 0 for the threshold comparison.", + "example": 97.5 + }, + "user_image": { + "type": "object", + "description": "Face-detection results for the analyzed image.", + "properties": { + "entities": { + "type": "array", + "description": "One entry per detected face. Empty when no face was found (then `age_estimation` is `null` and an `AGE_NOT_DETECTED` warning is added).", + "items": { + "type": "object", + "properties": { + "age": { + "type": "number", + "format": "float", + "description": "Model-estimated age of this face, in years.", + "example": 27.33 + }, + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box of the detected face as `[x_min, y_min, x_max, y_max]` pixel coordinates in the processed image.", + "example": [ + 40, + 40, + 100, + 100 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.7177750468254089 + }, + "gender": { + "type": "string", + "description": "Model-predicted gender of the detected face (`male` or `female`). Informational only.", + "example": "male" + } + } + } + }, + "best_angle": { + "type": "integer", + "nullable": true, + "description": "Rotation (degrees: 0, 90, 180, or 270) that produced the best face detection. Only non-zero when `rotate_image=true` corrected the orientation.", + "example": 0 + } + } + }, + "age_estimation": { + "type": "number", + "format": "float", + "nullable": true, + "description": "Model-predicted age in years of the **largest** detected face. `null` when no face age could be estimated (an `AGE_NOT_DETECTED` warning is added and the call declines).", + "example": 27.33 + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. Any entry sets `status` to `Declined`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "NO_FACE_DETECTED", + "LOW_LIVENESS_SCORE", + "LIVENESS_FACE_ATTACK", + "AGE_NOT_DETECTED", + "AGE_BELOW_MINIMUM" + ], + "description": "Machine-readable risk code. `NO_FACE_DETECTED` — the liveness model found no face; `LOW_LIVENESS_SCORE` — liveness `score` at or below `face_liveness_score_decline_threshold`; `LIVENESS_FACE_ATTACK` — a presentation attack (photo/screen/mask) was detected; `AGE_NOT_DETECTED` — no face age could be estimated; `AGE_BELOW_MINIMUM` — estimated age below `age_estimation_decline_threshold`. `NO_FACE_DETECTED` and `LOW_LIVENESS_SCORE` are mutually exclusive — when no face is found only `NO_FACE_DETECTED` is emitted." + }, + "feature": { + "type": "string", + "enum": [ + "LIVENESS" + ], + "description": "Feature that raised the warning. Always `LIVENESS` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Always `null` for age-estimation warnings." + }, + "log_type": { + "type": "string", + "enum": [ + "error" + ], + "description": "Severity. Every warning this endpoint produces is an `error` and sets `status` to `Declined`." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when `user_image` is missing, exceeds 5 MB, has an unsupported extension, or when a threshold is out of range. The body is DRF's standard field-error envelope: one array of messages per offending field.", + "content": { + "application/json": { + "examples": { + "Missing user image": { + "summary": "`user_image` not included in the form data", + "value": { + "user_image": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp", + "value": { + "user_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 5 MB limit", + "value": { + "user_image": [ + "File size should not exceed 5 MB" + ] + } + }, + "Threshold out of range": { + "summary": "`face_liveness_score_decline_threshold` outside 0–100", + "value": { + "face_liveness_score_decline_threshold": [ + "Ensure this value is less than or equal to 100." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/age-estimation/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'user_image=@./selfie.jpg' \\\n -F 'age_estimation_decline_threshold=18' \\\n -F 'face_liveness_score_decline_threshold=30' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.age_estimation.estimate(\n user_image=open(\"user_image.jpg\", \"rb\"),\n face_liveness_score_decline_threshold=30.0,\n age_estimation_decline_threshold=18,\n rotate_image=False,\n save_api_request=True,\n vendor_data=\"user-123\",\n metadata={\"flow\": \"age-gate\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.ageEstimation.estimate({\n user_image: await fileFromPath(\"user_image.jpg\"),\n face_liveness_score_decline_threshold: 30.0,\n age_estimation_decline_threshold: 18,\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"age-gate\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.AgeEstimation.Estimate(ctx, didit.AgeEstimationEstimateParams{\n\tUserImage: didit.FileFromPath(\"user_image.jpg\"),\n\tFaceLivenessScoreDeclineThreshold: didit.Float(30.0),\n\tAgeEstimationDeclineThreshold: didit.Int(18),\n\tRotateImage: didit.Bool(false),\n\tSaveAPIRequest: didit.Bool(true),\n\tVendorData: didit.String(\"user-123\"),\n\tMetadata: didit.Some(map[string]any{\"flow\": \"age-gate\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.AgeEstimationEstimateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.ageEstimation().estimate(AgeEstimationEstimateParams.builder()\n .userImage(Path.of(\"user_image.jpg\"))\n .faceLivenessScoreDeclineThreshold(30.0)\n .ageEstimationDeclineThreshold(18L)\n .rotateImage(false)\n .saveApiRequest(true)\n .vendorData(\"user-123\")\n .metadata(Map.of(\"flow\", \"age-gate\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "ageEstimation->estimate([\n 'user_image' => File::fromPath('user_image.jpg'),\n 'face_liveness_score_decline_threshold' => 30.0,\n 'age_estimation_decline_threshold' => 18,\n 'rotate_image' => false,\n 'save_api_request' => true,\n 'vendor_data' => 'user-123',\n 'metadata' => ['flow' => 'age-gate'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.age_estimation.estimate(\n user_image: Pathname(\"user_image.jpg\"),\n face_liveness_score_decline_threshold: 30.0,\n age_estimation_decline_threshold: 18,\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"age-gate\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.AgeEstimation.EstimateAsync(new AgeEstimationEstimateParams\n{\n UserImage = FileUpload.FromPath(\"user_image.jpg\"),\n FaceLivenessScoreDeclineThreshold = 30.0,\n AgeEstimationDeclineThreshold = 18,\n RotateImage = false,\n SaveApiRequest = true,\n VendorData = \"user-123\",\n Metadata = new Dictionary { [\"flow\"] = \"age-gate\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/poa/": { + "post": { + "summary": "Proof of Address (document OCR + address checks)", + "description": "Extract and validate a Proof of Address document — utility bills, bank statements, and government-issued documents — in one call. Returns the issuer, issue/expiry dates, the holder name, the raw and parsed/geocoded address, an `Approved`/`Declined` `status`, and a `warnings` list explaining every issue found.\n\n**Latency.** Extraction is LLM-based: typical calls take **5–15 seconds**, multi-page PDFs up to **~30 seconds**. Configure a client timeout of **at least 45 seconds** and do not retry before the call completes.\n\n**How it works.** The document (image or PDF, up to 15 MB; encrypted PDFs require `document_password`) is classified into a document type and subtype (e.g. `UTILITY_BILL` / `WATER_BILL`), and the issuer, dates, holder name, and address are extracted with LLM-based extraction. The address is parsed and geocoded into `poa_parsed_address`. PDF/EXIF forensics screen for tampering — modification after digital signing, known PDF editors, overlay-text manipulation, suspicious re-exports — surfacing `SUSPECTED_DOCUMENT_MANIPULATION` or `DOCUMENT_METADATA_MISMATCH`. Document age is validated against `poa_document_age_months`, and the language against `poa_languages_allowed`. If you pass `expected_address`, `expected_country`, or `expected_first_name`/`expected_last_name`, the extracted values are cross-checked and mismatches are flagged.\n\n**Decision logic.** `status` is `Approved` unless at least one warning resolves to a decline. Hard failures always decline: `MISSING_ADDRESS_INFORMATION`, `POA_DOCUMENT_EXPIRED`, `INVALID_DOCUMENT_TYPE`, `UNABLE_TO_VALIDATE_DOCUMENT_AGE`, plus `UNABLE_TO_EXTRACT_ISSUE_DATE` and `POA_NAME_NOT_DETECTED` (not configurable on this endpoint). Five risk groups are configurable per request via the `poa_*_action` options (`DECLINE` or `NO_ACTION`, all defaulting to `DECLINE`). `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION` and `UNPARSABLE_OR_INVALID_ADDRESS` are informational on this endpoint and never decline. A document that cannot be processed at all returns `400`; a readable but problematic document returns `200` with `status: \"Declined\"` — always inspect `poa.status` and `poa.warnings`, not just the HTTP code.\n\n**Billing.** Each `200` response consumes one Proof of Address API credit (paid from your plan credit or top-up balance). When the organization's balance cannot cover the call, the endpoint returns `403` with the not-enough-credits error before any document processing.\n\n**Session persistence (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session: it appears in the Business Console, the uploaded document is stored, the returned `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/` (the decision additionally exposes `document_file` and the `document_metadata` forensics, which this response omits), and a `status.updated` webhook is emitted. When `false`, nothing is stored and `request_id` is a one-off correlation UUID that cannot be looked up later.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload (a fictional \"Sandbox Power Co.\" electricity bill), no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.\n\nNote: `FUTURE_ISSUE_DATE` and `POOR_DOCUMENT_QUALITY` exist in workflow sessions but are never produced by this standalone endpoint — a future-dated document is not auto-declined here.", + "operationId": "post_v3poa", + "x-didit-sdk": { + "resource": "proof_of_address", + "method": "verify", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "document" + ], + "properties": { + "document": { + "type": "string", + "format": "binary", + "description": "The Proof of Address document. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `pdf`, `webp`. Maximum upload size: **15 MB** (larger files are rejected with `400`). Multi-page PDFs are supported (expect higher latency); encrypted PDFs require `document_password`. Images are automatically compressed before processing." + }, + "document_password": { + "type": "string", + "description": "Password to decrypt `document` when it is an encrypted PDF. Sending an encrypted PDF without (or with a wrong) password returns `400`.", + "writeOnly": true + }, + "expected_address": { + "type": "string", + "description": "Address to verify against the document. It is geocoded and compared with the extracted address; a mismatch (or an expected address that cannot be geocoded/verified) adds `ADDRESS_MISMATCH_WITH_PROVIDED`, governed by `poa_address_mismatch_action`. The geocoded form is echoed back in `expected_details_formatted_address` / `expected_details_parsed_address`.", + "example": "Av. Monseñor Tavella 1291, Salta, Argentina" + }, + "expected_country": { + "type": "string", + "description": "Country to verify against the document (ISO 3166-1 code, e.g. `ARG` or `AR`). A mismatch with the document's country adds `POA_COUNTRY_MISMATCH_WITH_PROVIDED`, governed by `poa_address_mismatch_action`.", + "example": "ARG" + }, + "expected_first_name": { + "type": "string", + "description": "First name to verify against `name_on_document` (fuzzy match, transliteration-aware). A score below the match threshold adds `NAME_MISMATCH_WITH_PROVIDED`, governed by `poa_address_mismatch_action`.", + "example": "Sophia" + }, + "expected_last_name": { + "type": "string", + "description": "Last name to verify against `name_on_document`, combined with `expected_first_name` for the match score.", + "example": "Martinez" + }, + "poa_languages_allowed": { + "type": "string", + "description": "Comma-separated list of accepted document languages as ISO 639-1 codes (e.g. `en,es,fr`). When omitted or blank, **all 51 supported languages** are accepted. A document in a language outside the list adds `UNSUPPORTED_DOCUMENT_LANGUAGE` (governed by `poa_unsupported_language_action`); an unknown code in the list returns `400` with the full set of supported codes.", + "example": "en,es" + }, + "poa_document_age_months": { + "type": "string", + "description": "Comma-separated `key:value` pairs setting the maximum document age in months per document type — keys: `utility_bill`, `bank_statement`, `government_issued_document`, `other_poa_document`; values: `1`–`120`, or `-1` for unlimited (no age check). Defaults when omitted: `utility_bill:3,bank_statement:3,government_issued_document:12,other_poa_document:12`. A document older than its limit declines with `POA_DOCUMENT_EXPIRED`. **Caution:** when you provide this field, list *every* type you accept — omitted types are treated as not accepted (informational `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION`) and their age check is skipped.", + "example": "utility_bill:3,bank_statement:6" + }, + "poa_name_mismatch_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "Accepted but **currently not applied** — name-mismatch warnings (`NAME_MISMATCH_WITH_PROVIDED`) follow `poa_address_mismatch_action` instead. Set `poa_address_mismatch_action` to control both name and address mismatch behavior." + }, + "poa_document_issues_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do on file-integrity problems — in practice only `DOCUMENT_METADATA_MISMATCH` is produced by this endpoint." + }, + "poa_document_authenticity_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when document manipulation is suspected (`SUSPECTED_DOCUMENT_MANIPULATION`) — e.g. the PDF was modified after digital signing, processed by a known PDF editor, shows overlay-text manipulation, or has inconsistent EXIF dates." + }, + "poa_unsupported_language_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when the document language is outside `poa_languages_allowed` (`UNSUPPORTED_DOCUMENT_LANGUAGE`)." + }, + "poa_address_mismatch_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when the extracted details disagree with the `expected_*` fields: `ADDRESS_MISMATCH_WITH_PROVIDED`, `NAME_MISMATCH_WITH_PROVIDED`, and `POA_COUNTRY_MISMATCH_WITH_PROVIDED` all follow this action." + }, + "poa_issuer_not_identified_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when no issuing company/organization can be identified on the document (`ISSUER_NOT_IDENTIFIED`)." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, announced through a `status.updated` webhook, and with the uploaded document stored. When `false`, nothing is stored and `request_id` is a transient UUID for response correlation only.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"onboarding\"}`) — it is parsed into an object.", + "example": { + "flow": "onboarding" + } + } + } + }, + "example": { + "document": "(binary JPEG/PNG/PDF of the utility bill or bank statement)", + "expected_address": "Av. Monseñor Tavella 1291, Salta, Argentina", + "expected_country": "ARG", + "expected_first_name": "Sophia", + "expected_last_name": "Martinez", + "poa_languages_allowed": "en,es", + "save_api_request": true, + "vendor_data": "user-123" + } + } + } + }, + "responses": { + "200": { + "description": "Document processed. `poa.status` is `Approved` or `Declined`; every detected issue is itemized in `poa.warnings`. An expired, mismatched, or suspicious document still returns `200` with `status: \"Declined\"` — inspect the body, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Utility bill read cleanly, no expected-details checks", + "value": { + "request_id": "4b523a8d-ed32-45e4-8aa8-a89eda459d88", + "poa": { + "issuing_state": "ARG", + "document_type": "UTILITY_BILL", + "document_subtype": "WATER_BILL", + "document_language": "es", + "issuer": "Aguas del Norte", + "issue_date": "2026-04-10", + "expiration_date": null, + "poa_address": "AVDA. MONSEÑOR TAVELLA N° 1291, B° VILLA LAVALLE CP: 4400, CAPITAL - SALTA", + "poa_formatted_address": "Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina", + "poa_parsed_address": { + "street_1": "Avenida Monseñor Tavella 1291", + "street_2": "b", + "city": "Salta", + "region": "Salta", + "country": "AR", + "postal_code": "A4400", + "document_location": { + "latitude": -24.8208682, + "longitude": -65.4130954 + } + }, + "expected_details_address": null, + "expected_details_formatted_address": null, + "expected_details_parsed_address": null, + "name_on_document": "OESTE EMBOTELLADORA S.A.", + "extra_fields": { + "bank_account_number": null, + "bank_iban": null, + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": null, + "additional_names": [ + "LA EMBOTELLADORA DEL NORTE SA" + ] + }, + "detected_codes": [ + { + "type": "QR_CODE", + "page_number": 1, + "position": [ + [ + 820, + 1120 + ], + [ + 1002, + 1120 + ], + [ + 1002, + 1302 + ], + [ + 820, + 1302 + ] + ], + "x": 820, + "y": 1120, + "width": 182, + "height": 182, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "aHR0cHM6Ly9hZ3Vhc2RlbG5vcnRlLmNvbS9mYWN0dXJhLzk5ODgxMQ==", + "parsed_payload": "https://aguasdelnorte.com/factura/998811" + }, + { + "type": "CODE_128", + "page_number": 1, + "position": [ + [ + 112, + 1480 + ], + [ + 560, + 1480 + ], + [ + 560, + 1540 + ], + [ + 112, + 1540 + ] + ], + "x": 112, + "y": 1480, + "width": 448, + "height": 60, + "page_width": 1190, + "page_height": 1684, + "raw_payload": "MDA5OTg4MTEwMDA0Mg==", + "parsed_payload": "0099881100042" + } + ], + "status": "Approved", + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:29:02.439655+00:00" + } + }, + "Declined - document too old": { + "summary": "Issue date older than the allowed `poa_document_age_months`", + "value": { + "request_id": "f61681b8-9422-4378-ac3e-04c6a247fb45", + "poa": { + "issuing_state": "ARG", + "document_type": "UTILITY_BILL", + "document_subtype": "WATER_BILL", + "document_language": "es", + "issuer": "Aguas del Norte", + "issue_date": "2021-02-10", + "expiration_date": null, + "poa_address": "AVDA. MONSEÑOR TAVELLA N° 1291, B° VILLA LAVALLE CP: 4400, CAPITAL - SALTA", + "poa_formatted_address": "Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina", + "poa_parsed_address": { + "street_1": "Avenida Monseñor Tavella 1291", + "street_2": "b", + "city": "Salta", + "region": "Salta", + "country": "AR", + "postal_code": "A4400", + "document_location": { + "latitude": -24.8208682, + "longitude": -65.4130954 + } + }, + "expected_details_address": null, + "expected_details_formatted_address": null, + "expected_details_parsed_address": null, + "name_on_document": "OESTE EMBOTELLADORA S.A.", + "extra_fields": { + "bank_account_number": null, + "bank_iban": null, + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": null, + "additional_names": [ + "LA EMBOTELLADORA DEL NORTE SA" + ] + }, + "status": "Declined", + "warnings": [ + { + "risk": "POA_DOCUMENT_EXPIRED", + "feature": "PROOF_OF_ADDRESS", + "additional_data": { + "expiration_reason": "age_window", + "max_age_months": 3, + "document_subtype": "WATER_BILL", + "document_type": "UTILITY_BILL", + "issue_date": "2021-02-10" + }, + "log_type": "error", + "short_description": "Document expired", + "long_description": "The submitted document is older than the maximum accepted age of 3 months from its issue date, which exceeds the acceptable time period for validity." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:23:02.165493+00:00" + } + }, + "Declined - name mismatch": { + "summary": "`expected_first_name`/`expected_last_name` do not match `name_on_document`", + "value": { + "request_id": "0a7f2f9c-58f1-4b8e-b1e3-2f6f9d4f7a21", + "poa": { + "issuing_state": "ARG", + "document_type": "UTILITY_BILL", + "document_subtype": "WATER_BILL", + "document_language": "es", + "issuer": "Aguas del Norte", + "issue_date": "2021-02-10", + "expiration_date": null, + "poa_address": "AVDA. MONSEÑOR TAVELLA N° 1291, B° VILLA LAVALLE CP: 4400, CAPITAL - SALTA", + "poa_formatted_address": "Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina", + "poa_parsed_address": { + "street_1": "Avenida Monseñor Tavella 1291", + "street_2": "b", + "city": "Salta", + "region": "Salta", + "country": "AR", + "postal_code": "A4400", + "document_location": { + "latitude": -24.8208682, + "longitude": -65.4130954 + } + }, + "expected_details_address": null, + "expected_details_formatted_address": null, + "expected_details_parsed_address": null, + "name_on_document": "OESTE EMBOTELLADORA S.A.", + "extra_fields": { + "bank_account_number": null, + "bank_iban": null, + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": null, + "additional_names": [ + "LA EMBOTELLADORA DEL NORTE SA" + ] + }, + "status": "Declined", + "warnings": [ + { + "risk": "NAME_MISMATCH_WITH_PROVIDED", + "feature": "PROOF_OF_ADDRESS", + "additional_data": null, + "log_type": "error", + "short_description": "Name mismatch with provided information", + "long_description": "The full name on the document does not match the name from the user's verified identity documents, or the full name sent by API." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T02:27:21.330172+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "poa": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Approved` when no warning resolves to a decline; `Declined` otherwise — the `warnings` list explains why." + }, + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Country the document was issued in, as extracted from the document — usually ISO 3166-1 alpha-3 (e.g. `ARG`).", + "example": "ARG" + }, + "document_type": { + "type": "string", + "enum": [ + "UTILITY_BILL", + "BANK_STATEMENT", + "GOVERNMENT_ISSUED_DOCUMENT", + "OTHER_POA_DOCUMENT", + "UNKNOWN" + ], + "description": "Detected document category. `UNKNOWN` means the document could not be classified as an acceptable Proof of Address and auto-declines with `INVALID_DOCUMENT_TYPE`. Never null — classification failures fall back to `UNKNOWN`." + }, + "document_subtype": { + "type": "string", + "description": "Finer-grained classification within `document_type` — e.g. `ELECTRICITY_BILL`, `WATER_BILL`, `GAS_BILL`, `INTERNET_BILL`, `PHONE_BILL` (utility bills); `ACCOUNT_STATEMENT`, `CREDIT_CARD_STATEMENT` (bank statements); `TAX_ASSESSMENT`, `RESIDENCY_CERTIFICATE` (government documents). `UNKNOWN` when no subtype could be determined. Never null — classification failures fall back to `UNKNOWN`.", + "example": "WATER_BILL" + }, + "document_language": { + "type": "string", + "nullable": true, + "description": "Detected document language (ISO 639-1).", + "example": "es" + }, + "issuer": { + "type": "string", + "nullable": true, + "description": "Company or organization that issued the document. `null` adds `ISSUER_NOT_IDENTIFIED`.", + "example": "Aguas del Norte" + }, + "issue_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Document issue date (`YYYY-MM-DD`). `null` when it could not be extracted — that declines with `UNABLE_TO_EXTRACT_ISSUE_DATE` on this endpoint.", + "example": "2021-02-10" + }, + "expiration_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Explicit expiry/validity date printed on the document, when present. Independent of the `poa_document_age_months` age check." + }, + "poa_address": { + "type": "string", + "nullable": true, + "description": "Address exactly as printed on the document. `null` auto-declines with `MISSING_ADDRESS_INFORMATION`." + }, + "poa_formatted_address": { + "type": "string", + "nullable": true, + "description": "Geocoded, normalized version of `poa_address`.", + "example": "Av. Monseñor Tavella 1291 b, A4400 Salta, Argentina" + }, + "poa_parsed_address": { + "type": "object", + "nullable": true, + "description": "Structured, geocoded breakdown of the document address. `null` when the address could not be parsed.", + "properties": { + "street_1": { + "type": "string", + "nullable": true, + "example": "Avenida Monseñor Tavella 1291" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "city": { + "type": "string", + "nullable": true, + "example": "Salta" + }, + "region": { + "type": "string", + "nullable": true, + "example": "Salta" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code.", + "example": "AR" + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "A4400" + }, + "document_location": { + "type": "object", + "nullable": true, + "properties": { + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + } + }, + "description": "Geocoded coordinates of the document address." + } + } + }, + "expected_details_address": { + "type": "string", + "nullable": true, + "description": "Echo of the `expected_address` you sent, or `null`." + }, + "expected_details_formatted_address": { + "type": "string", + "nullable": true, + "description": "Geocoded, normalized version of `expected_address`; `null` when not provided or not geocodable." + }, + "expected_details_parsed_address": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Full geocoding result for `expected_address` (street/city/region/country/postal code plus raw geocoder output and an `is_verified` flag). `null` when `expected_address` was not provided." + }, + "name_on_document": { + "type": "string", + "nullable": true, + "description": "Primary holder name extracted from the document; compared against `expected_first_name`/`expected_last_name` when provided. `null` declines with `POA_NAME_NOT_DETECTED` on this endpoint.", + "example": "OESTE EMBOTELLADORA S.A." + }, + "extra_fields": { + "type": "object", + "description": "Document-specific extras. Bank fields are populated for bank statements when printed on the document; `null` otherwise.", + "properties": { + "bank_account_number": { + "type": "string", + "nullable": true + }, + "bank_iban": { + "type": "string", + "nullable": true + }, + "bank_sort_code": { + "type": "string", + "nullable": true + }, + "bank_routing_number": { + "type": "string", + "nullable": true + }, + "bank_swift_bic": { + "type": "string", + "nullable": true + }, + "bank_branch_name": { + "type": "string", + "nullable": true + }, + "bank_branch_address": { + "type": "string", + "nullable": true + }, + "document_phone_number": { + "type": "string", + "nullable": true, + "description": "Contact phone number printed on the document." + }, + "additional_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Other holder names found on the document (e.g. co-account holders, previous account names)." + } + } + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. Every entry explains one detected risk; entries with `log_type: \"error\"` set `status` to `Declined`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "description": "Machine-readable risk code. Always-decline: `MISSING_ADDRESS_INFORMATION`, `POA_DOCUMENT_EXPIRED`, `INVALID_DOCUMENT_TYPE`, `UNABLE_TO_VALIDATE_DOCUMENT_AGE`, plus `UNABLE_TO_EXTRACT_ISSUE_DATE` and `POA_NAME_NOT_DETECTED` (fixed to decline on this endpoint). Governed by request options: `NAME_MISMATCH_WITH_PROVIDED` / `ADDRESS_MISMATCH_WITH_PROVIDED` / `POA_COUNTRY_MISMATCH_WITH_PROVIDED` (`poa_address_mismatch_action`), `POOR_DOCUMENT_QUALITY` (never produced by this endpoint) / `DOCUMENT_METADATA_MISMATCH` (`poa_document_issues_action`), `SUSPECTED_DOCUMENT_MANIPULATION` (`poa_document_authenticity_action`), `UNSUPPORTED_DOCUMENT_LANGUAGE` (`poa_unsupported_language_action`), `ISSUER_NOT_IDENTIFIED` (`poa_issuer_not_identified_action`). Informational on this endpoint: `POA_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION`, `UNPARSABLE_OR_INVALID_ADDRESS`.", + "example": "POA_DOCUMENT_EXPIRED" + }, + "feature": { + "type": "string", + "enum": [ + "PROOF_OF_ADDRESS" + ], + "description": "Feature that raised the warning. Always `PROOF_OF_ADDRESS` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Extra context for some risks — e.g. `POA_DOCUMENT_EXPIRED` includes `expiration_reason` (`printed_expiration` or `age_window`), `document_type`, `document_subtype`, and `issue_date`, plus `expiration_date` on the `printed_expiration` path or `max_age_months` on the `age_window` path; `SUSPECTED_DOCUMENT_MANIPULATION` includes `reason` and `detection_method`. `null` for most risks." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `information` entries are advisory and never decline on their own. (`warning` is not produced by this endpoint — its options only accept `DECLINE` or `NO_ACTION`.)" + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "detected_codes": { + "type": "array", + "description": "QR codes and barcodes detected across every processed page of the document. Empty when none are found. A located but undecodable QR code has `parsed_payload: null`.", + "items": { + "$ref": "#/components/schemas/DocumentAIDetectedCode" + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated." + } + } + } + } + } + }, + "400": { + "description": "Validation or processing error. Field-level problems return DRF's standard envelope (one array of messages per offending field). Encrypted-PDF problems return `{\"detail\": ...}`. A document the extraction pipeline cannot process returns `{\"error\": [\"Error extracting POA information\"]}`.", + "content": { + "application/json": { + "examples": { + "Missing document": { + "summary": "`document` not included in the form data", + "value": { + "document": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/pdf/webp", + "value": { + "document": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, pdf, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 15 MB limit", + "value": { + "document": [ + "File size should not exceed 15 MB" + ] + } + }, + "Encrypted PDF": { + "summary": "PDF uploaded without the required password", + "value": { + "detail": "The PDF is encrypted. Please upload a decrypted PDF or a photo instead." + } + }, + "Wrong PDF password": { + "summary": "`document_password` does not decrypt the PDF", + "value": { + "detail": "The PDF password is incorrect. Please provide the correct password." + } + }, + "Invalid language code": { + "summary": "`poa_languages_allowed` contains an unsupported code", + "value": { + "poa_languages_allowed": [ + "Invalid language code: 'xx'. Must be one of the supported languages: ['ar', 'bn', 'hy', 'bg', 'bs', 'ca', 'cnr', 'sq', 'zh', 'hr', 'cs', 'da', 'nl', 'en', 'et', 'fi', 'fr', 'ka', 'kk', 'de', 'el', 'he', 'hi', 'hu', 'id', 'it', 'ja', 'ko', 'ky', 'lv', 'lt', 'mk', 'mn', 'ms', 'no', 'fa', 'pl', 'pt', 'ro', 'ru', 'sr', 'sk', 'sl', 'so', 'es', 'sv', 'th', 'tr', 'uk', 'uz', 'vi']." + ] + } + }, + "Invalid document age value": { + "summary": "`poa_document_age_months` value outside 1–120 / -1", + "value": { + "poa_document_age_months": [ + "Invalid integer value for 'utility_bill': '999'. Must be -1 (unlimited) or a positive integer between 1 and 120." + ] + } + }, + "Extraction failure": { + "summary": "The document could not be processed by the extraction pipeline", + "value": { + "error": [ + "Error extracting POA information" + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any document processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/poa/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'document=@./utility-bill.pdf' \\\n -F 'expected_address=Av. Monseñor Tavella 1291, Salta, Argentina' \\\n -F 'expected_country=ARG' \\\n -F 'expected_first_name=Sophia' \\\n -F 'expected_last_name=Martinez' \\\n -F 'poa_languages_allowed=en,es' \\\n -F 'vendor_data=user-123' \\\n --max-time 45" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.proof_of_address.verify(\n document=open(\"document.pdf\", \"rb\"),\n expected_address=\"Av. Monseñor Tavella 1291, Salta, Argentina\",\n expected_country=\"ARG\",\n expected_first_name=\"Sophia\",\n expected_last_name=\"Martinez\",\n poa_languages_allowed=\"en,es\",\n save_api_request=True,\n vendor_data=\"user-123\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.proofOfAddress.verify({\n document: await fileFromPath(\"document.pdf\"),\n expected_address: \"Av. Monseñor Tavella 1291, Salta, Argentina\",\n expected_country: \"ARG\",\n expected_first_name: \"Sophia\",\n expected_last_name: \"Martinez\",\n poa_languages_allowed: \"en,es\",\n save_api_request: true,\n vendor_data: \"user-123\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.ProofOfAddress.Verify(ctx, didit.ProofOfAddressVerifyParams{\n\tDocument: didit.FileFromPath(\"document.pdf\"),\n\tExpectedAddress: didit.String(\"Av. Monseñor Tavella 1291, Salta, Argentina\"),\n\tExpectedCountry: didit.String(\"ARG\"),\n\tExpectedFirstName: didit.String(\"Sophia\"),\n\tExpectedLastName: didit.String(\"Martinez\"),\n\tPOALanguagesAllowed: didit.String(\"en,es\"),\n\tSaveAPIRequest: didit.Bool(true),\n\tVendorData: didit.String(\"user-123\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.types.ProofOfAddressVerifyParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.proofOfAddress().verify(ProofOfAddressVerifyParams.builder()\n .document(Path.of(\"document.pdf\"))\n .expectedAddress(\"Av. Monseñor Tavella 1291, Salta, Argentina\")\n .expectedCountry(\"ARG\")\n .expectedFirstName(\"Sophia\")\n .expectedLastName(\"Martinez\")\n .poaLanguagesAllowed(\"en,es\")\n .saveApiRequest(true)\n .vendorData(\"user-123\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "proofOfAddress->verify([\n 'document' => File::fromPath('document.pdf'),\n 'expected_address' => 'Av. Monseñor Tavella 1291, Salta, Argentina',\n 'expected_country' => 'ARG',\n 'expected_first_name' => 'Sophia',\n 'expected_last_name' => 'Martinez',\n 'poa_languages_allowed' => 'en,es',\n 'save_api_request' => true,\n 'vendor_data' => 'user-123',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.proof_of_address.verify(\n document: Pathname(\"document.pdf\"),\n expected_address: \"Av. Monseñor Tavella 1291, Salta, Argentina\",\n expected_country: \"ARG\",\n expected_first_name: \"Sophia\",\n expected_last_name: \"Martinez\",\n poa_languages_allowed: \"en,es\",\n save_api_request: true,\n vendor_data: \"user-123\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.ProofOfAddress.VerifyAsync(new ProofOfAddressVerifyParams\n{\n Document = FileUpload.FromPath(\"document.pdf\"),\n ExpectedAddress = \"Av. Monseñor Tavella 1291, Salta, Argentina\",\n ExpectedCountry = \"ARG\",\n ExpectedFirstName = \"Sophia\",\n ExpectedLastName = \"Martinez\",\n PoaLanguagesAllowed = \"en,es\",\n SaveApiRequest = true,\n VendorData = \"user-123\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/document-ai/": { + "post": { + "summary": "Document AI (custom field extraction + tamper checks)", + "description": "Extract the fields **you** define from any supporting document — payslips, proof of funds, source-of-wealth letters, tax statements, contracts, invoices — in one call. Send the file plus a `fields` definition (key, name, extraction instruction, type) and get back typed values (`text`, `number`, `date` as `YYYY-MM-DD`), an optional name-match score against the expected person or company, PDF/EXIF tamper signals, an `Approved`/`Declined`/`In Review` `status`, and a `warnings` list explaining every issue found. This is the server-to-server counterpart of the hosted Document AI workflow step (see `/core-technology/document-ai/overview`); it uses Didit's default extraction model and does not expose provider selection.\n\n**Latency.** Extraction is LLM-based: typical calls take **5–15 seconds**, multi-page PDFs up to **~30 seconds**. Configure a client timeout of **at least 45 seconds** and do not retry before the call completes.\n\n**Request format.** `multipart/form-data` only. `fields` is **one JSON-encoded array string** (1–30 items, at most 16 KB); bracket-style form arrays are rejected with `400`. Each item is `{key, name, instruction, type, required, is_full_name}` — `key` is the JSON key you get back (`^[a-z0-9_]{1,64}$`, unique), `instruction` tells the model what to read, `type` is `text` (default) | `number` | `date`, `required` raises `DOCUMENT_AI_MISSING_REQUIRED_FIELDS` when the value cannot be read, and at most one field may set `is_full_name: true` to mark the value compared against the expected name.\n\n**Name matching.** Pass `expected_first_name` + `expected_last_name` (person) **or** `expected_company_name` (+ optional `expected_company_country`) — not both. The value of the `is_full_name` field (or, when none is marked, the first text field whose key looks like a name) is compared fuzzily; `name_match_score` (0–100) and `name_on_document` are always returned when both sides exist, and a score below `document_ai_name_match_score_threshold` (default 80) adds `DOCUMENT_AI_NAME_MISMATCH`, governed by `document_ai_name_mismatch_action`.\n\n**Decision logic.** Each risk maps to a configurable action: `DECLINE` → `status: \"Declined\"`, `REVIEW` → `status: \"In Review\"`, `NO_ACTION` → informational only. Defaults mirror the workflow step: unreadable document `REVIEW`, missing required fields `REVIEW`, suspected manipulation `DECLINE`, name mismatch `NO_ACTION`, unsupported file `REVIEW`. A document that cannot be processed at all returns `400`; a readable but problematic document returns `200` with `Declined`/`In Review` — always inspect `document_ai.status` and `document_ai.warnings`, not just the HTTP code.\n\n**Billing.** Each `200` response consumes one Document AI API credit — **$0.20 per document** at list, paid from your plan credit or top-up balance. When the organization's balance cannot cover the call, the endpoint returns `403` before any processing. Sandbox API keys return a deterministic mock response and are not charged.\n\n**Persistence.** With `save_api_request=true` (default) the call is stored as an API-type session: the document, the extracted values, the field definitions and the name-match snapshot are kept, the session shows up in the Business Console, and `request_id` works with `GET /v3/session/{sessionId}/decision/`.", + "operationId": "post_v3document-ai", + "x-didit-sdk": { + "resource": "document_ai", + "method": "analyze", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "document", + "fields" + ], + "properties": { + "document": { + "type": "string", + "format": "binary", + "description": "The document to read. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `pdf`, `webp`, `zip`. Maximum upload size: **30 MB** (larger files are rejected with `400`). Multi-page PDFs are supported (expect higher latency); encrypted PDFs are rejected with `400`. Images are automatically compressed before processing." + }, + "fields": { + "type": "string", + "description": "One JSON-encoded array string with **1–30** field definitions (maximum 16 KB). Each item: `key` (required, `^[a-z0-9_]{1,64}$`, unique — the key returned in `extracted_data`), `name` (≤100 chars, shown in messages), `instruction` (≤500 chars, what the model should read), `type` (`text` default | `number` | `date`), `required` (boolean, default `false`), `is_full_name` (boolean, default `false`, at most one per request). Bracket-style form arrays, invalid JSON, duplicate keys, or more than one `is_full_name` return `400` before any processing or billing.", + "example": "[{\"key\": \"employer_name\", \"name\": \"Employer name\", \"instruction\": \"The employer or company issuing the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": false}, {\"key\": \"employee_name\", \"name\": \"Employee name\", \"instruction\": \"The full name of the employee as printed on the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": true}, {\"key\": \"net_pay\", \"name\": \"Net pay\", \"instruction\": \"The net amount paid to the employee for the period.\", \"type\": \"number\", \"required\": true, \"is_full_name\": false}, {\"key\": \"pay_date\", \"name\": \"Pay date\", \"instruction\": \"The payment date shown on the payslip.\", \"type\": \"date\", \"required\": false, \"is_full_name\": false}]" + }, + "expected_first_name": { + "type": "string", + "description": "First name to verify against the document's name field (fuzzy match, transliteration-aware). Use together with `expected_last_name`; cannot be combined with `expected_company_name`.", + "example": "Sophia" + }, + "expected_last_name": { + "type": "string", + "description": "Last name to verify against the document's name field, combined with `expected_first_name` for `name_match_score`.", + "example": "Martinez" + }, + "expected_company_name": { + "type": "string", + "description": "Company name to verify against the document's name field (for business documents). Cannot be combined with `expected_first_name` / `expected_last_name` (no example value on purpose — the generated request sample uses the person fields; swap them for this one when verifying a business document)." + }, + "expected_company_country": { + "type": "string", + "description": "Optional ISO 3166-1 alpha-2 country code (two ASCII letters, e.g. `FR`) that tunes company-name normalization for `expected_company_name`. Only used with `expected_company_name`." + }, + "document_ai_name_match_score_threshold": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 80, + "description": "Minimum `name_match_score` (0–100) to consider the name matched. A lower score adds `DOCUMENT_AI_NAME_MISMATCH`, governed by `document_ai_name_mismatch_action`.", + "example": 80 + }, + "document_ai_unreadable_document_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "REVIEW", + "description": "What to do when no field could be read from the document (`DOCUMENT_AI_UNREADABLE_DOCUMENT`)." + }, + "document_ai_missing_required_fields_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "REVIEW", + "description": "What to do when at least one `required` field could not be read (`DOCUMENT_AI_MISSING_REQUIRED_FIELDS`; `additional_data.missing_fields` lists them)." + }, + "document_ai_document_tampering_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What to do when PDF/EXIF forensics suspect manipulation — modified after digital signing, known PDF editor, overlay-text manipulation, suspicious re-export, image anomalies (`DOCUMENT_AI_SUSPECTED_MANIPULATION`)." + }, + "document_ai_name_mismatch_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "NO_ACTION", + "description": "What to do when `name_match_score` is below the threshold (`DOCUMENT_AI_NAME_MISMATCH`)." + }, + "document_ai_unsupported_file_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ], + "default": "REVIEW", + "description": "What to do when the upload is empty or not a processable file (`DOCUMENT_AI_UNSUPPORTED_FILE`)." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session — visible in the Business Console, retrievable via `GET /v3/session/{sessionId}/decision/` using the returned `request_id`, with the uploaded document, the extracted values, the field definitions and the name-match snapshot stored. When `false`, nothing is stored and `request_id` is a transient UUID for response correlation only; the call is still billed.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque string (your internal user id, email, UUID…) stored on the persisted session and echoed back in the response. Use it to correlate API calls with your own records and to filter sessions later.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back in the response. In multipart requests, send it as a JSON-encoded string field (e.g. `metadata={\"flow\":\"onboarding\"}`) — it is parsed into an object.", + "example": { + "flow": "onboarding" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Document processed. `document_ai.status` is `Approved`, `Declined`, or `In Review`; every detected issue is itemized in `document_ai.warnings`. A problematic document still returns `200` — inspect the body, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Payslip read cleanly, name matched", + "value": { + "request_id": "9d6a3b1e-2f0c-4a8e-9b7d-1c2e3f4a5b6c", + "document_ai": { + "status": "Approved", + "extracted_data": { + "employer_name": "Acme Payments Ltd", + "employee_name": "Sophia Martinez", + "net_pay": 3120.5, + "pay_date": "2026-07-31" + }, + "detected_codes": [ + { + "type": "CODE_128", + "page_number": 1, + "position": [ + [ + 96, + 456 + ], + [ + 96, + 616 + ], + [ + 1012, + 611 + ], + [ + 1015, + 456 + ] + ], + "x": 96, + "y": 456, + "width": 919, + "height": 160, + "page_width": 1200, + "page_height": 1700, + "raw_payload": "SU5WLTIwMjYtMDA0Mg==", + "parsed_payload": "INV-2026-0042" + } + ], + "fields": [ + { + "key": "employer_name", + "name": "Employer name", + "instruction": "The employer or company issuing the payslip.", + "type": "text", + "required": true, + "is_full_name": false + }, + { + "key": "employee_name", + "name": "Employee name", + "instruction": "The full name of the employee as printed on the payslip.", + "type": "text", + "required": true, + "is_full_name": true + }, + { + "key": "net_pay", + "name": "Net pay", + "instruction": "The net amount paid to the employee for the period.", + "type": "number", + "required": true, + "is_full_name": false + }, + { + "key": "pay_date", + "name": "Pay date", + "instruction": "The payment date shown on the payslip.", + "type": "date", + "required": false, + "is_full_name": false + } + ], + "name_match_score": 100.0, + "name_on_document": "Sophia Martinez", + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": { + "flow": "onboarding" + }, + "created_at": "2026-08-19T16:40:12.318Z" + } + }, + "In Review — missing required field": { + "summary": "`net_pay` could not be read; default action REVIEW", + "value": { + "request_id": "3f2a9c7b-5d4e-4b1a-8c6f-0e9d8c7b6a5f", + "document_ai": { + "status": "In Review", + "extracted_data": { + "employer_name": "Acme Payments Ltd", + "employee_name": "Sophia Martinez", + "net_pay": null, + "pay_date": "2026-07-31" + }, + "detected_codes": [], + "fields": [ + { + "key": "employer_name", + "name": "Employer name", + "instruction": "The employer or company issuing the payslip.", + "type": "text", + "required": true, + "is_full_name": false + }, + { + "key": "employee_name", + "name": "Employee name", + "instruction": "The full name of the employee as printed on the payslip.", + "type": "text", + "required": true, + "is_full_name": true + }, + { + "key": "net_pay", + "name": "Net pay", + "instruction": "The net amount paid to the employee for the period.", + "type": "number", + "required": true, + "is_full_name": false + }, + { + "key": "pay_date", + "name": "Pay date", + "instruction": "The payment date shown on the payslip.", + "type": "date", + "required": false, + "is_full_name": false + } + ], + "name_match_score": 100.0, + "name_on_document": "Sophia Martinez", + "warnings": [ + { + "risk": "DOCUMENT_AI_MISSING_REQUIRED_FIELDS", + "feature": "DOCUMENT_AI", + "additional_data": { + "missing_fields": [ + "Net pay" + ] + }, + "log_type": "warning", + "short_description": "Required fields could not be extracted", + "long_description": "One or more fields configured as required could not be extracted from the uploaded document." + } + ] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-08-19T16:41:05.002Z" + } + }, + "Declined — suspected manipulation": { + "summary": "PDF forensics flagged a known editor; default action DECLINE", + "value": { + "request_id": "7c1d2e3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f", + "document_ai": { + "status": "Declined", + "extracted_data": { + "employer_name": "Acme Payments Ltd", + "employee_name": "Sophia Martinez", + "net_pay": 3120.5, + "pay_date": "2026-07-31" + }, + "detected_codes": [], + "fields": [ + { + "key": "employer_name", + "name": "Employer name", + "instruction": "The employer or company issuing the payslip.", + "type": "text", + "required": true, + "is_full_name": false + }, + { + "key": "employee_name", + "name": "Employee name", + "instruction": "The full name of the employee as printed on the payslip.", + "type": "text", + "required": true, + "is_full_name": true + }, + { + "key": "net_pay", + "name": "Net pay", + "instruction": "The net amount paid to the employee for the period.", + "type": "number", + "required": true, + "is_full_name": false + }, + { + "key": "pay_date", + "name": "Pay date", + "instruction": "The payment date shown on the payslip.", + "type": "date", + "required": false, + "is_full_name": false + } + ], + "name_match_score": null, + "name_on_document": "Sophia Martinez", + "warnings": [ + { + "risk": "DOCUMENT_AI_SUSPECTED_MANIPULATION", + "feature": "DOCUMENT_AI", + "additional_data": { + "detection_method": "known_editor", + "processed_by_known_editor": "ilovepdf" + }, + "log_type": "error", + "short_description": "Suspected document manipulation", + "long_description": "The system detected signs of potential manipulation or editing in the uploaded document." + } + ] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-08-19T16:42:40.771Z" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "document_ai": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "description": "`Approved` when no warning resolves to a decline or review; `Declined` when at least one risk maps to `DECLINE`; otherwise `In Review` when at least one maps to `REVIEW`. The `warnings` list explains why." + }, + "extracted_data": { + "type": "object", + "additionalProperties": true, + "description": "One entry per requested field key. Values are typed per the field `type` — strings for `text`, numbers for `number`, `YYYY-MM-DD` strings for `date` — or `null` when the value could not be read.", + "example": { + "employer_name": "Acme Payments Ltd", + "employee_name": "Sophia Martinez", + "net_pay": 3120.5, + "pay_date": "2026-07-31" + } + }, + "detected_codes": { + "type": "array", + "description": "QR codes and barcodes detected across every processed page. Empty when none are found. A located but undecodable QR code has `parsed_payload: null`.", + "items": { + "$ref": "#/components/schemas/DocumentAIDetectedCode" + } + }, + "fields": { + "type": "array", + "description": "The normalized field definitions that were used for this extraction, echoed back (also stored with the session when `save_api_request=true`).", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "instruction": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "text", + "number", + "date" + ] + }, + "required": { + "type": "boolean" + }, + "is_full_name": { + "type": "boolean" + } + } + } + }, + "name_match_score": { + "type": "number", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Fuzzy similarity (0–100) between `name_on_document` and the expected person/company name. Returned whenever both a name value and an expected name exist — on matches **and** mismatches; `null` otherwise.", + "example": 96.0 + }, + "name_on_document": { + "type": "string", + "nullable": true, + "description": "The name value taken from the document (the `is_full_name` field, or the first name-like text field) that was compared against the expected name. `null` when no name value was available.", + "example": "Sophia Martinez" + }, + "warnings": { + "type": "array", + "description": "Empty on a clean approval. Every entry explains one detected risk; entries with `log_type: \"error\"` set `status` to `Declined`, entries with `log_type: \"warning\"` set it to `In Review`.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "DOCUMENT_AI_UNREADABLE_DOCUMENT", + "DOCUMENT_AI_MISSING_REQUIRED_FIELDS", + "DOCUMENT_AI_SUSPECTED_MANIPULATION", + "DOCUMENT_AI_NAME_MISMATCH", + "DOCUMENT_AI_UNSUPPORTED_FILE" + ], + "description": "Machine-readable risk code. Each one is governed by the matching `document_ai_*_action` request option.", + "example": "DOCUMENT_AI_MISSING_REQUIRED_FIELDS" + }, + "feature": { + "type": "string", + "enum": [ + "DOCUMENT_AI" + ], + "description": "Feature that raised the warning. Always `DOCUMENT_AI` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Extra context for the risk: `{\"missing_fields\": [\"Net pay\"]}` for missing required fields, `{\"name_on_document\", \"score\", \"threshold\"}` for a name mismatch, and the forensic finding (`detection_method`, producer/editor details) for suspected manipulation. `null` when there is nothing to add.", + "example": { + "missing_fields": [ + "Net pay" + ] + } + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity derived from the configured action: `error` (DECLINE), `warning` (REVIEW), `information` (NO_ACTION)." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-liner for the risk.", + "example": "Required fields could not be extracted" + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation, interpolated with `additional_data` where relevant." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the request was processed (UTC, ISO 8601)." + } + } + } + } + } + }, + "400": { + "description": "Validation or processing error. Field-level problems return DRF's standard envelope (one array of messages per offending field). Encrypted or unreadable documents and extraction failures return `{\"error\": ...}`.", + "content": { + "application/json": { + "examples": { + "Missing document": { + "summary": "`document` not included in the form data", + "value": { + "document": [ + "No file was submitted." + ] + } + }, + "Invalid fields payload": { + "summary": "`fields` is not one JSON-encoded array string", + "value": { + "fields": [ + "fields must be a valid JSON array string." + ] + } + }, + "Duplicate field keys": { + "summary": "Two items share the same `key`", + "value": { + "fields": [ + "Every fields item must have a unique key." + ] + } + }, + "Conflicting expectations": { + "summary": "Person and company expectations sent together", + "value": { + "non_field_errors": [ + "Provide either person expected-name fields or expected_company_name, not both." + ] + } + }, + "Invalid metadata": { + "summary": "`metadata` is not a JSON object (or contains a NUL character)", + "value": { + "metadata": [ + "Metadata must be a JSON object." + ] + } + }, + "Oversized vendor_data": { + "summary": "`vendor_data` over the 2,000-byte cap", + "value": { + "vendor_data": [ + "vendor_data is too long (maximum 2000 bytes)." + ] + } + }, + "Encrypted PDF": { + "summary": "Password-protected PDF", + "value": { + "error": "The PDF is encrypted. Please upload a decrypted PDF or a photo instead." + } + }, + "Extraction failed": { + "summary": "The extraction pipeline could not process the document", + "value": { + "error": [ + "Error extracting Document AI information" + ] + } + } + }, + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls — and contract-billed organizations whose rate card has no Document AI API entry — return `403` with `{\"error\": ...}` before any document processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + }, + "Contract rate missing": { + "summary": "Contract-billed organization whose rate card has no Document AI API entry", + "value": { + "error": "Document AI API is not included in your contract rate card. Contact support@didit.me to enable it." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "error": { + "type": "string" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.document_ai.analyze(\n document=open(\"document.pdf\", \"rb\"),\n fields=\"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.documentAi.analyze({\n document: await fileFromPath(\"document.pdf\"),\n fields: \"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.DocumentAi.Analyze(ctx, didit.DocumentAiAnalyzeParams{\n\tDocument: didit.FileFromPath(\"document.pdf\"),\n\tFields: \"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.types.DocumentAiAnalyzeParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.documentAi().analyze(DocumentAiAnalyzeParams.builder()\n .document(Path.of(\"document.pdf\"))\n .fields(\"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "documentAi->analyze([\n 'document' => File::fromPath('document.pdf'),\n 'fields' => '[{\"key\": \"employer_name\", \"name\": \"Employer name\", \"instruction\": \"The employer or company issuing the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": false}, {\"key\": \"employee_name\", \"name\": \"Employee name\", \"instruction\": \"The full name of the employee as printed on the payslip.\", \"type\": \"text\", \"required\": true, \"is_full_name\": true}, {\"key\": \"net_pay\", \"name\": \"Net pay\", \"instruction\": \"The net amount paid to the employee for the period.\", \"type\": \"number\", \"required\": true, \"is_full_name\": false}, {\"key\": \"pay_date\", \"name\": \"Pay date\", \"instruction\": \"The payment date shown on the payslip.\", \"type\": \"date\", \"required\": false, \"is_full_name\": false}]',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.document_ai.analyze(\n document: Pathname(\"document.pdf\"),\n fields: \"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.DocumentAi.AnalyzeAsync(new DocumentAiAnalyzeParams\n{\n Document = FileUpload.FromPath(\"document.pdf\"),\n Fields = \"[{\\\"key\\\": \\\"employer_name\\\", \\\"name\\\": \\\"Employer name\\\", \\\"instruction\\\": \\\"The employer or company issuing the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"employee_name\\\", \\\"name\\\": \\\"Employee name\\\", \\\"instruction\\\": \\\"The full name of the employee as printed on the payslip.\\\", \\\"type\\\": \\\"text\\\", \\\"required\\\": true, \\\"is_full_name\\\": true}, {\\\"key\\\": \\\"net_pay\\\", \\\"name\\\": \\\"Net pay\\\", \\\"instruction\\\": \\\"The net amount paid to the employee for the period.\\\", \\\"type\\\": \\\"number\\\", \\\"required\\\": true, \\\"is_full_name\\\": false}, {\\\"key\\\": \\\"pay_date\\\", \\\"name\\\": \\\"Pay date\\\", \\\"instruction\\\": \\\"The payment date shown on the payslip.\\\", \\\"type\\\": \\\"date\\\", \\\"required\\\": false, \\\"is_full_name\\\": false}]\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/aml/": { + "post": { + "summary": "AML screening (standalone)", + "operationId": "post_v3aml", + "x-didit-sdk": { + "resource": "aml", + "method": "screen", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "Idempotency-Key", + "in": "header", + "required": false, + "description": "Optional retry-safety key, up to 255 characters. Reusing the same key with the same request body returns the original response without running or billing a second screening. Reusing the key with a different body returns `409`.", + "schema": { + "type": "string", + "maxLength": 255 + } + } + ], + "description": "Screen a person or company against PEP, sanctions, warning/watchlist, and adverse-media datasets in one call.\n\n**Two-score model.** Each hit carries a `match_score` (0–100, identity confidence) and a `risk_score` (0–100, entity risk). Hits whose `match_score` is below `aml_match_score_threshold` (default **93**) are tagged `review_status: \"False Positive\"` and ignored for risk purposes; the highest `risk_score` among the remaining hits becomes `aml.score`. The final `aml.status` follows your thresholds: `score` > `aml_score_review_threshold` (default **100**) → `Declined`; `score` > `aml_score_approve_threshold` (default **80**) → `In Review`; otherwise `Approved`. Whenever `score` is non-zero and ≥ the approve threshold a `POSSIBLE_MATCH_FOUND` warning is added (a score of exactly 0 never warns).\n\n**Match-score weights.** `aml_name_weight` + `aml_dob_weight` + `aml_country_weight` must sum to exactly 100 (defaults 60/25/15). Components missing from the request are re-normalized away; a matching `document_number` acts as a golden key that overrides `match_score` to 100.\n\n**Latency.** Plain screenings typically complete in a few seconds. With `include_adverse_media=true` the API waits for adverse-media enrichment (an initial ~5 s pause plus up to ~25 s of polling) — configure a client timeout of **at least 60 seconds**.\n\n**Persistence.** `save_api_request` defaults to **true**: the screening is stored as a session (`request_id` works with `GET /v3/session/{sessionId}/decision/`), it appears in the console, a `status.updated` webhook fires, and hit review statuses can be updated later. With `save_api_request=false` nothing is stored and `request_id` is a transient correlation id. `include_ongoing_monitoring=true` keeps re-screening the profile and requires `save_api_request=true` (enforced with a `400`).\n\n**Idempotency.** Send an `Idempotency-Key` header when retrying after a timeout or transient error. The key is scoped to your application and the exact request body. The first completed request stores its response; later identical retries return that same response and do not run or bill another AML screening. If the original request is still running, retry later with the same key. If the same key is reused with a different body, the API returns `409`.\n\n**Billing.** One AML API credit is charged after the first successful screening. Replayed idempotent responses are not charged again; an insufficient balance returns `403` before any new screening happens.\n\n**Sandbox.** Keys from sandbox applications skip screening and billing entirely and return a mocked result — `Approved` with zero hits unless you pass `sandbox_scenario`. `save_api_request` works the same as on live keys, so a saved sandbox screening is retrievable through `GET /v3/session/{sessionId}/decision/` and `GET /v3/session/{sessionId}/generate-pdf/`.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "full_name" + ], + "properties": { + "full_name": { + "type": "string", + "minLength": 2, + "description": "Full name of the person or company to screen. Minimum **2 characters** - the screening provider's own floor. A shorter or blank name returns `400` on this field before any screening runs, so nothing is billed.", + "example": "Antonio Ejemplo Modelo" + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "default": "person", + "description": "Type of entity to screen. Defaults to `person`." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth (persons) or incorporation date (companies), `YYYY-MM-DD`. Improves match precision via the DOB weight.", + "example": "1972-02-29" + }, + "nationality": { + "type": "string", + "description": "ISO 3166-1 **alpha-2** country code (e.g. `ES`). Invalid codes return `400`. Improves match precision via the country weight.", + "example": "ES" + }, + "document_number": { + "type": "string", + "description": "Identity document number. Not weighted — acts as a golden key: an exact match overrides the hit's `match_score` to 100, a hard mismatch penalizes it. Sent to the screening provider as a search filter only when it is **2-50 characters**; a value outside that range is accepted and stored, and the screening still runs and is billed, just not narrowed by document number." + }, + "aml_score_approve_threshold": { + "type": "number", + "minimum": 0, + "maximum": 100, + "default": 80, + "description": "Risk-score threshold at or below which the result is `Approved`. Must be ≤ `aml_score_review_threshold` (else `400`)." + }, + "aml_score_review_threshold": { + "type": "number", + "minimum": 0, + "maximum": 100, + "default": 100, + "description": "Risk-score threshold above which the result is `Declined`. Scores between the two thresholds produce `In Review`." + }, + "aml_name_weight": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 60, + "description": "Weight of name similarity in the per-hit match score. The three weights must sum to exactly 100 (else `400`)." + }, + "aml_dob_weight": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 25, + "description": "Weight of date of birth in the per-hit match score." + }, + "aml_country_weight": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 15, + "description": "Weight of country/nationality in the per-hit match score." + }, + "aml_match_score_threshold": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 93, + "description": "Per-hit cutoff: hits with `match_score` below this are `False Positive` (excluded from risk assessment); at or above are `Unreviewed` possible matches." + }, + "include_adverse_media": { + "type": "boolean", + "default": false, + "description": "Also search news media for negative coverage. Adds up to ~30 s of latency while results are gathered — use a ≥60 s client timeout." + }, + "include_ongoing_monitoring": { + "type": "boolean", + "default": false, + "description": "Keep re-screening this profile and push changes via webhook. Requires `save_api_request=true` (else `400`)." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "Persist the screening as a session (console visibility, decision endpoint, webhooks, hit review). Set to `false` for a stateless check." + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for the screened user; echoed back and stored with the session." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON stored with the request and echoed back." + }, + "sandbox_scenario": { + "type": "string", + "description": "Sandbox-only scenario slug. Only accepted when the API key belongs to a sandbox-mode application; rejected on live applications. A standalone screening has no session to arm, so the slug goes on the screening request itself: `decline_aml_hit` returns one production-shaped hit and settles the screening as `Declined`, `review_aml_possible_match` settles it `In Review`, and a scenario that arms no AML outcome leaves the screening clean. See the scenario catalog at GET /v1/sandbox/scenarios/ or [Sandbox & Test Data](/integration/sandbox-testing).", + "example": "decline_aml_hit" + } + } + }, + "example": { + "full_name": "Antonio Ejemplo Modelo", + "entity_type": "person", + "date_of_birth": "1972-02-29", + "nationality": "ES", + "include_adverse_media": false, + "vendor_data": "user-1234" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/aml/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -H 'Idempotency-Key: aml-user-1234-2026-08-20' \\\n -d '{\n \"full_name\": \"Antonio Ejemplo Modelo\",\n \"entity_type\": \"person\",\n \"date_of_birth\": \"1972-02-29\",\n \"nationality\": \"ES\",\n \"include_adverse_media\": false,\n \"vendor_data\": \"user-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.aml.screen(\n full_name=\"Antonio Ejemplo Modelo\",\n entity_type=\"person\",\n date_of_birth=\"1972-02-29\",\n nationality=\"ES\",\n include_adverse_media=False,\n vendor_data=\"user-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.aml.screen({\n full_name: \"Antonio Ejemplo Modelo\",\n entity_type: \"person\",\n date_of_birth: \"1972-02-29\",\n nationality: \"ES\",\n include_adverse_media: false,\n vendor_data: \"user-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.AML.Screen(ctx, didit.AMLScreenParams{\n\tFullName: \"Antonio Ejemplo Modelo\",\n\tEntityType: didit.Some(didit.AMLScreenParamsEntityType(\"person\")),\n\tDateOfBirth: didit.String(\"1972-02-29\"),\n\tNationality: didit.String(\"ES\"),\n\tIncludeAdverseMedia: didit.Bool(false),\n\tVendorData: didit.String(\"user-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport me.didit.Didit;\nimport me.didit.types.AmlScreenParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.aml().screen(AmlScreenParams.builder()\n .fullName(\"Antonio Ejemplo Modelo\")\n .entityType(AmlScreenParams.EntityType.of(\"person\"))\n .dateOfBirth(LocalDate.parse(\"1972-02-29\"))\n .nationality(\"ES\")\n .includeAdverseMedia(false)\n .vendorData(\"user-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "aml->screen([\n 'full_name' => 'Antonio Ejemplo Modelo',\n 'entity_type' => 'person',\n 'date_of_birth' => '1972-02-29',\n 'nationality' => 'ES',\n 'include_adverse_media' => false,\n 'vendor_data' => 'user-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.aml.screen(\n full_name: \"Antonio Ejemplo Modelo\",\n entity_type: \"person\",\n date_of_birth: \"1972-02-29\",\n nationality: \"ES\",\n include_adverse_media: false,\n vendor_data: \"user-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Aml.ScreenAsync(new AmlScreenParams\n{\n FullName = \"Antonio Ejemplo Modelo\",\n EntityType = new AmlScreenParamsEntityType(\"person\"),\n DateOfBirth = \"1972-02-29\",\n Nationality = \"ES\",\n IncludeAdverseMedia = false,\n VendorData = \"user-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Screening completed. `aml.status` reflects your thresholds; every hit at or above the internal inclusion floor is listed in `aml.hits` with its identity `match_score`, entity `risk_score`, and a `score_breakdown` explaining the match.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "aml": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "`Declined` when `score` > `aml_score_review_threshold`; `In Review` when `score` > `aml_score_approve_threshold`; `Approved` otherwise (including when no score is available)." + }, + "total_hits": { + "type": "integer", + "description": "Number of entries in `hits`." + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Echo of the screened entity type." + }, + "hits": { + "type": "array", + "items": { + "type": "object", + "description": "One screening hit. `match_score` (0–100) measures identity confidence — hits below `aml_match_score_threshold` are tagged `review_status: \"False Positive\"` and excluded from the risk assessment; hits at or above it are `\"Unreviewed\"` possible matches. `risk_score` (0–100) measures how risky the matched entity is and drives the top-level `aml.score`.", + "properties": { + "id": { + "type": "string", + "nullable": true, + "description": "Didit's stable identifier for the hit: `hit_` followed by 24 hexadecimal characters. The same matched entity keeps the same `id` when the subject is screened again. Pass it as `hit_id` to the AML hit review endpoints." + }, + "url": { + "type": "string", + "nullable": true, + "description": "Source URL for the matched entity, when available." + }, + "match": { + "type": "boolean", + "nullable": true, + "description": "Legacy exact-match flag. Prefer `match_score` + `review_status`." + }, + "score": { + "type": "number", + "nullable": true, + "description": "DEPRECATED weighted match score in the 0–1 range. Use `match_score` instead." + }, + "match_score": { + "type": "number", + "nullable": true, + "description": "Identity confidence (0–100). Compared against `aml_match_score_threshold` to split False Positives from Possible Matches." + }, + "risk_score": { + "type": "number", + "nullable": true, + "description": "Entity risk score (0–100). The highest `risk_score` among non-false-positive hits becomes the top-level `aml.score`." + }, + "target": { + "type": "boolean", + "nullable": true + }, + "caption": { + "type": "string", + "nullable": true, + "description": "Display name of the matched entity." + }, + "datasets": { + "type": "array", + "nullable": true, + "items": { + "type": "string", + "enum": [ + "PEP", + "PEP Level 1", + "PEP Level 2", + "PEP Level 3", + "PEP Level 4", + "Sanctions", + "Adverse Media", + "Warnings and Regulatory Enforcement", + "Fitness and Probity", + "SIP", + "SIE", + "Insolvency", + "Other Lists" + ] + }, + "description": "Risk categories the entity is listed under: `PEP` (with its level, `PEP Level 1` to `PEP Level 4`), `Sanctions`, `Adverse Media`, `Warnings and Regulatory Enforcement`, `Fitness and Probity`, `SIP`, `SIE` or `Insolvency`. `Other Lists` when the entity's source states none of these categories." + }, + "features": { + "type": "object", + "nullable": true + }, + "properties": { + "type": "object", + "nullable": true, + "description": "Entity profile: `name`, `alias`, `notes`, `gender`, `birthDate`, `birthPlace`, `nationality`, `position`, and other biographical arrays (each nullable)." + }, + "rca_name": { + "type": "string", + "nullable": true, + "description": "Name of the related close associate through which the entity was matched, when the hit is an RCA." + }, + "pep_matches": { + "type": "array", + "description": "PEP list entries backing this hit.", + "items": { + "type": "object", + "properties": { + "list_name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "source_url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "matched_name": { + "type": "string" + }, + "pep_position": { + "type": "string" + }, + "date_of_birth": { + "type": "string" + }, + "place_of_birth": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "type": "string" + } + }, + "education": { + "type": "array", + "items": { + "type": "string" + } + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "sanction_matches": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Sanctions list entries backing this hit (same backing-list shape as `pep_matches`, plus sanction-specific fields)." + }, + "warning_matches": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Warning/watchlist entries backing this hit." + }, + "adverse_media_matches": { + "type": "array", + "description": "Adverse-media articles. Populated only when `include_adverse_media=true`.", + "items": { + "type": "object", + "properties": { + "headline": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "sentiment": { + "type": "string" + }, + "sentiment_score": { + "type": "number" + }, + "source_url": { + "type": "string" + }, + "thumbnail": { + "type": "string", + "nullable": true + }, + "country": { + "type": "string", + "nullable": true + }, + "author_name": { + "type": "string", + "nullable": true + }, + "publication_date": { + "type": "string", + "nullable": true + }, + "adverse_keywords": { + "type": "array", + "items": { + "type": "string" + } + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "adverse_media_details": { + "type": "object", + "nullable": true, + "description": "Aggregated adverse-media sentiment for the entity (`sentiment`, `sentiment_score`, `adverse_keywords` frequency map). Populated only when `include_adverse_media=true`." + }, + "first_seen": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the entity first appeared in the datasets." + }, + "last_seen": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Most recent dataset refresh that included the entity." + }, + "linked_entities": { + "type": "array", + "description": "Known relationships of the matched entity (family, associates, companies).", + "items": { + "type": "object", + "properties": { + "name": { + "type": "array", + "items": { + "type": "string" + } + }, + "relation": { + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "type": "array", + "items": { + "type": "string" + } + }, + "details": { + "type": "array", + "items": { + "type": "string" + } + }, + "active": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "risk_view": { + "type": "object", + "nullable": true, + "description": "Per-dimension risk breakdown (`crimes`, `countries`, `categories`, `custom_list`), each with `score`, `weightage`, `risk_level`, and a `risk_scores` map." + }, + "additional_information": { + "type": "object", + "nullable": true, + "description": "Free-form extra context, e.g. a `flag_summary` array explaining why the entity is flagged." + }, + "review_status": { + "type": "string", + "nullable": true, + "enum": [ + "False Positive", + "Unreviewed", + "Confirmed Match", + "Inconclusive" + ], + "description": "`False Positive` — `match_score` below `aml_match_score_threshold`, excluded from risk assessment. `Unreviewed` — possible match awaiting review. `Confirmed Match` / `Inconclusive` — set manually by a reviewer (via the Business Console) on saved sessions; that review capability is not part of this spec." + }, + "score_breakdown": { + "type": "object", + "nullable": true, + "description": "How `match_score` was computed. Name, date of birth, and country each contribute `_score` × `_weight_normalized` points; weights are re-normalized when a component is missing from the screened data. The document number is not weighted — it acts as a 'golden key': `MATCH` overrides the score to 100, `NEUTRAL` leaves it unchanged, `HARD_MISMATCH` applies a penalty.", + "properties": { + "name_score": { + "type": "integer", + "nullable": true, + "description": "Raw name similarity (0–100)." + }, + "name_weight": { + "type": "integer", + "nullable": true, + "description": "Configured name weight (`aml_name_weight`)." + }, + "name_weight_normalized": { + "type": "number", + "nullable": true, + "description": "Name weight after re-normalization (0–100)." + }, + "name_contribution": { + "type": "number", + "nullable": true, + "description": "Points the name contributed to `total_score`." + }, + "dob_score": { + "type": "integer", + "nullable": true, + "description": "Date-of-birth score as a percentage of its weight (−100 to 100)." + }, + "dob_weight": { + "type": "integer", + "nullable": true, + "description": "Configured DOB weight (`aml_dob_weight`)." + }, + "dob_weight_normalized": { + "type": "number", + "nullable": true, + "description": "DOB weight after re-normalization (0–100)." + }, + "dob_contribution": { + "type": "number", + "nullable": true, + "description": "Points the DOB contributed to `total_score`." + }, + "country_score": { + "type": "integer", + "nullable": true, + "description": "Country score as a percentage of its weight (−100 to 100)." + }, + "country_weight": { + "type": "integer", + "nullable": true, + "description": "Configured country weight (`aml_country_weight`)." + }, + "country_weight_normalized": { + "type": "number", + "nullable": true, + "description": "Country weight after re-normalization (0–100)." + }, + "country_contribution": { + "type": "number", + "nullable": true, + "description": "Points the country contributed to `total_score`." + }, + "document_number_match_type": { + "type": "string", + "nullable": true, + "enum": [ + "MATCH", + "NEUTRAL", + "HARD_MISMATCH" + ], + "description": "Golden-key result for the document number comparison." + }, + "document_number_effect": { + "type": "string", + "nullable": true, + "description": "Human-readable description of the document-number effect." + }, + "total_score": { + "type": "integer", + "nullable": true, + "description": "Final weighted match score (0–100) — mirrors `match_score`." + } + } + } + } + } + }, + "score": { + "type": "number", + "nullable": true, + "description": "Overall AML risk score (0–100): the highest `risk_score` among hits that are not False Positives. Drives `status` via the two thresholds." + }, + "screened_data": { + "type": "object", + "description": "Echo of what was screened: `full_name`, `date_of_birth`, `nationality`, `document_number` (absent fields are `null`).", + "properties": { + "full_name": { + "type": "string" + }, + "date_of_birth": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "document_number": { + "type": "string", + "nullable": true + } + } + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature that raised the warning." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk identifier." + }, + "additional_data": { + "type": "object", + "nullable": true, + "description": "Extra context for the warning, when available." + }, + "log_type": { + "type": "string", + "description": "`warning`, `information`, or `error` — how the risk affected the outcome." + }, + "short_description": { + "type": "string", + "description": "One-line human-readable summary." + }, + "long_description": { + "type": "string", + "description": "Full human-readable explanation." + } + } + }, + "description": "Contains a `POSSIBLE_MATCH_FOUND` warning whenever `score` is non-zero and ≥ `aml_score_approve_threshold` (a score of exactly 0 never warns); empty otherwise." + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "No hits (Approved)": { + "summary": "Clean screening — zero hits", + "value": { + "request_id": "d27c5509-7a66-45a9-b05e-d69d9f4c336a", + "aml": { + "status": "Approved", + "total_hits": 0, + "entity_type": "person", + "hits": [], + "score": 0, + "screened_data": { + "full_name": "Maximilian Mustermann-Doe", + "date_of_birth": "1992-03-14", + "nationality": "DE", + "document_number": null + }, + "warnings": [] + }, + "vendor_data": "user-1234", + "metadata": { + "internal_ref": "abc-123" + }, + "created_at": "2026-06-11T10:21:07.013938+00:00" + } + }, + "PEP hit (Approved — risk below approve threshold)": { + "summary": "One PEP possible match; risk score 73 stays under the default approve threshold of 80", + "value": { + "request_id": "0b7e7a3e-65a1-4f3d-9c7a-1de2f9a40111", + "aml": { + "status": "Approved", + "total_hits": 1, + "entity_type": "person", + "hits": [ + { + "id": "hit_7c1e9a40b2d35f86e0a4c912", + "url": "https://news.example.org/articles/sample", + "match": false, + "score": 0.93, + "match_score": 93.0, + "risk_score": 73.0, + "target": null, + "caption": "Antonio Ejemplo Modelo", + "datasets": [ + "PEP Level 1", + "PEP" + ], + "features": null, + "rca_name": "", + "first_seen": "2026-01-07T00:00:00", + "last_seen": "2026-01-07T00:00:00", + "review_status": "Unreviewed", + "properties": { + "name": [ + "Antonio Ejemplo Modelo", + "Antonio Ejemplo" + ], + "alias": [ + "Antonio Ejemplo Modelo", + "Ejemplo Modelo, Pedro" + ], + "notes": [ + "Prime Minister of Spain" + ], + "gender": [ + "male" + ], + "birthDate": [ + "1972-02-29" + ], + "birthPlace": [ + "Madrid, Spain" + ], + "nationality": null, + "position": null + }, + "risk_view": { + "crimes": { + "score": 0.0, + "weightage": 20, + "risk_level": "Low", + "risk_scores": {} + }, + "countries": { + "score": 9.64, + "weightage": 30, + "risk_level": "Low", + "risk_scores": { + "Spain": 25.71 + } + }, + "categories": { + "score": 62.5, + "weightage": 50, + "risk_level": "High", + "risk_scores": { + "PEP": 100.0, + "PEP Level 1": 100.0 + } + }, + "custom_list": {} + }, + "score_breakdown": { + "name_score": 89, + "name_weight": 60, + "name_weight_normalized": 60.0, + "name_contribution": 53.4, + "dob_score": 100, + "dob_weight": 25, + "dob_weight_normalized": 25.0, + "dob_contribution": 25.0, + "country_score": 100, + "country_weight": 15, + "country_weight_normalized": 15.0, + "country_contribution": 15.0, + "document_number_match_type": "NEUTRAL", + "document_number_effect": "No document number provided for screening", + "total_score": 93 + }, + "pep_matches": [ + { + "aliases": [], + "education": [], + "list_name": "Presidency of the Government of Spain", + "publisher": "Presidency of the Government of Spain", + "source_url": "https://news.example.org/articles/sample", + "description": "The Presidency of the Government is the central institutional body in Spain that supports the Prime Minister and coordinates the general action of the central government.", + "matched_name": "Antonio Ejemplo Modelo", + "pep_position": "", + "date_of_birth": "1972-02-29", + "other_sources": [], + "place_of_birth": "Madrid, Spain" + } + ], + "sanction_matches": [], + "warning_matches": [], + "adverse_media_matches": [], + "adverse_media_details": null, + "linked_entities": [ + { + "name": [ + "Maria Begona Gomez Fernandez" + ], + "active": [], + "status": [], + "details": [], + "relation": [ + "spouse" + ] + } + ], + "additional_information": { + "flag_summary": [ + "Antonio Ejemplo Modelo has been flagged as a Level 1 Politically Exposed Person (PEP) from Spain, serving as the President of the Government of Spain." + ] + } + } + ], + "score": 73.0, + "screened_data": { + "full_name": "Pedro Sanchez Perez-Castejon", + "date_of_birth": "1972-02-29", + "nationality": "ES", + "document_number": null + }, + "warnings": [] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-11T10:23:44.812000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems return DRF's standard envelope (one array of messages per offending field); cross-field rules (weight sum, threshold ordering, monitoring-without-save) return `{\"error\": [...]}`.", + "content": { + "application/json": { + "examples": { + "Missing full_name": { + "summary": "`full_name` is the only required field", + "value": { + "full_name": [ + "This field is required." + ] + } + }, + "Short full_name": { + "summary": "`full_name` must be at least 2 characters", + "value": { + "full_name": [ + "Ensure this field has at least 2 characters." + ] + } + }, + "Invalid nationality": { + "summary": "`nationality` must be ISO 3166-1 alpha-2", + "value": { + "nationality": [ + "Invalid ISO 3166-1 alpha-2 country code." + ] + } + }, + "Weights do not sum to 100": { + "summary": "`aml_name_weight` + `aml_dob_weight` + `aml_country_weight` ≠ 100", + "value": { + "error": [ + "AML weights must sum to 100. Current sum: 90 (name: 50, dob: 25, country: 15)" + ] + } + }, + "Thresholds out of order": { + "summary": "Approve threshold above review threshold", + "value": { + "error": [ + "Approve threshold (90.0) cannot be greater than review threshold (80.0)" + ] + } + }, + "Monitoring without saving": { + "summary": "`include_ongoing_monitoring=true` with `save_api_request=false`", + "value": { + "error": [ + "Ongoing monitoring can only be enabled when the API request is saved." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/face-search/": { + "post": { + "summary": "Face Search", + "description": "Search a face image (1:N) against your application's face search index — faces enrolled from verification sessions, saved standalone API calls, direct user-profile face uploads, and your block/allow lists — and get back ranked similarity matches plus blocklist and duplicate warnings.\n\n**How it works.** The largest detected face in `user_image` is embedded and searched against the index, scoped to your application. Up to **5 matches** above the similarity floor are returned in `matches`, each with `similarity_percentage`, the originating session (`session_id`, `session_number`, `status`, `vendor_data`), `user_details` extracted during that session's document verification, `is_blocklisted` / `is_allowlisted` flags, and a `source` discriminator (`session`, `imported`, or `list_entry`).\n\n- `search_type=most_similar` (default) ranks every enrolled face by similarity — use it for deduplication and fraud-ring investigation.\n- `search_type=blocklisted_or_approved` restricts candidates to blocklisted faces, allowlisted faces, faces from approved sessions, and imported user-profile faces, ranking blocklisted entries first — use it when the primary goal is blocklist screening.\n\n`status` is `Declined` only when a `FACE_IN_BLOCKLIST` or `POSSIBLE_FACE_IN_BLOCKLIST` warning fires; `DUPLICATED_FACE` / `POSSIBLE_DUPLICATED_FACE` (`information`) and `MULTIPLE_FACES_DETECTED` (`warning`) never decline. Unlike Passive Liveness, this endpoint does **not** exclude prior sessions with the same `vendor_data` from matching. A passive-liveness analysis also runs internally and is stored with the persisted session, but its result is not part of this response.\n\n**Billing.** Each `200` response consumes one Face Search API credit (paid from your plan credit or top-up balance). Insufficient balance returns `403` before any image processing.\n\n**Session persistence and face enrollment (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session (Business Console, `GET /v3/session/{sessionId}/decision/` via the returned `request_id`, `status.updated` webhook) and the searched face is enrolled into your face search index. Faces enrolled by Face Search calls are excluded from future Face Search results, so repeated searches never match each other. When `false`, the call is one-shot: nothing is stored, the face is not enrolled, and `match_image_url` values are internal storage paths rather than downloadable URLs.\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload, no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3face-search", + "x-didit-sdk": { + "resource": "face_search", + "method": "search", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "user_image" + ], + "properties": { + "user_image": { + "type": "string", + "format": "binary", + "description": "Front-facing face image to search with. Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp`. Maximum upload size: **5 MB** (larger files are rejected with `400`). Images are automatically compressed to ~0.5 MB before processing, so very high resolutions do not improve accuracy. The image must contain at least one detectable face — otherwise the endpoint returns `400`. When several faces are present, the largest one is searched and a `MULTIPLE_FACES_DETECTED` warning is added." + }, + "search_type": { + "type": "string", + "default": "most_similar", + "enum": [ + "most_similar", + "blocklisted_or_approved" + ], + "description": "Search policy. `most_similar` (default) ranks every enrolled face in your application by similarity. `blocklisted_or_approved` restricts candidates to blocklisted faces, allowlisted faces, faces from approved sessions, and imported user-profile faces — with blocklisted entries ranked first.", + "example": "most_similar" + }, + "rotate_image": { + "type": "boolean", + "default": false, + "description": "When `true`, the service tries 90-degree rotations of the input and uses the orientation that yields the best face detection. Useful when EXIF orientation is missing. Adds latency.", + "example": false + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session, emits a `status.updated` webhook, presigns `match_image_url` values, and enrolls the searched face into your face search index (Face Search enrollments are excluded from future searches). When `false`, the search is one-shot — no session is stored and the face is not enrolled.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque identifier (your user id, email, UUID…) stored on the persisted session and echoed back. It does not exclude same-user faces from the search results.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back. In multipart requests, send it as a JSON-encoded string field — it is parsed into an object.", + "example": { + "flow": "dedup_check" + } + } + } + }, + "example": { + "user_image": "(binary JPEG/PNG selfie)", + "search_type": "most_similar", + "rotate_image": false, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "dedup_check" + } + } + } + } + }, + "responses": { + "200": { + "description": "Face search completed. `face_search.matches` holds up to 5 similar faces ordered by similarity; an empty array means no enrolled face exceeded the similarity floor. `status` is `Declined` only on blocklist hits — duplicate matches alone return `Approved`, so inspect `matches` and `warnings`, not just `status`. When `save_api_request=true`, `request_id` is the persisted session id. Each match carries `source` (`session`, `imported`, `list_entry`, or `retained_template`), `vendor_user_id`, and `biometric_template_id`; a `retained_template` match comes from an image-free biometric template kept after the source session was deleted and has `null` session fields, `match_image_url`, and `user_details`.", + "content": { + "application/json": { + "examples": { + "Blocklist hit (Declined)": { + "summary": "The face matches a blocklisted face", + "value": { + "request_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef", + "face_search": { + "status": "Declined", + "total_matches": 1, + "matches": [ + { + "session_id": "882c42d5-8a4d-4d20-8080-a22f57822c86", + "session_number": 323442, + "similarity_percentage": 99.99, + "source": "session", + "vendor_data": "user-1", + "verification_date": "2025-01-01T00:00:00Z", + "user_details": { + "full_name": "Jane Marie Doe", + "document_type": "ID", + "document_number": "X1234567" + }, + "match_image_url": "https:///face/3f6a1c2e/reference.jpg", + "status": "Approved", + "is_blocklisted": true, + "is_allowlisted": false, + "api_service": null, + "vendor_user_id": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f", + "biometric_template_id": null + } + ], + "user_image": { + "entities": [ + { + "bbox": [ + 40, + 40, + 120, + 120 + ], + "confidence": 0.732973 + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "FACE_IN_BLOCKLIST", + "feature": "LIVENESS", + "additional_data": { + "blocklisted_session_id": "882c42d5-8a4d-4d20-8080-a22f57822c86", + "blocklisted_session_number": 323442, + "api_service": null + }, + "log_type": "error", + "short_description": "Face in blocklist", + "long_description": "The system identified a face in the blocklist, which means the face is not allowed to be verified." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "Duplicate found (Approved)": { + "summary": "The face already appeared in another approved session — informational, no decline", + "value": { + "request_id": "5e0c3a1f-7b2d-4c8e-9f10-2a3b4c5d6e7f", + "face_search": { + "status": "Approved", + "total_matches": 1, + "matches": [ + { + "session_id": "1f2e3d4c-5b6a-7980-1122-334455667788", + "session_number": 1024, + "similarity_percentage": 87.42, + "source": "session", + "vendor_data": "user-9", + "verification_date": "2025-11-20T09:15:00Z", + "user_details": null, + "match_image_url": "https:///face/9c8b7a6d/reference.jpg", + "status": "Approved", + "is_blocklisted": false, + "is_allowlisted": false, + "api_service": "PASSIVE_LIVENESS", + "vendor_user_id": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f", + "biometric_template_id": null + } + ], + "user_image": { + "entities": [ + { + "bbox": [ + 40, + 40, + 120, + 120 + ], + "confidence": 0.732973 + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "DUPLICATED_FACE", + "feature": "LIVENESS", + "additional_data": { + "duplicated_session_id": "1f2e3d4c-5b6a-7980-1122-334455667788", + "duplicated_session_number": 1024, + "api_service": "PASSIVE_LIVENESS" + }, + "log_type": "information", + "short_description": "Duplicated face from other approved session", + "long_description": "The system identified a duplicated face from another approved session, requiring further investigation." + } + ] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "No matches (Approved)": { + "summary": "No enrolled face exceeded the similarity floor", + "value": { + "request_id": "9d8c7b6a-5f4e-3d2c-1b0a-998877665544", + "face_search": { + "status": "Approved", + "total_matches": 0, + "matches": [], + "user_image": { + "entities": [ + { + "bbox": [ + 40, + 40, + 120, + 120 + ], + "confidence": 0.732973 + } + ], + "best_angle": 0 + }, + "warnings": [] + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "face_search": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Declined` only when a `FACE_IN_BLOCKLIST` or `POSSIBLE_FACE_IN_BLOCKLIST` warning fired. Duplicate matches and multiple-face warnings never decline." + }, + "total_matches": { + "type": "integer", + "description": "Number of entries in `matches` (0–5).", + "example": 1 + }, + "matches": { + "type": "array", + "maxItems": 5, + "description": "Up to 5 faces from your face search index that exceed the similarity floor, ordered by similarity (with blocklisted entries ranked first when `search_type=blocklisted_or_approved`). Empty when no enrolled face is similar enough.", + "items": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Id of the verification session the matched face belongs to. `null` when `source` is `imported`, `list_entry`, or `retained_template`.", + "example": "882c42d5-8a4d-4d20-8080-a22f57822c86" + }, + "session_number": { + "type": "integer", + "nullable": true, + "description": "Human-friendly session number shown in the Business Console. `null` for non-session matches.", + "example": 323442 + }, + "similarity_percentage": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 100, + "description": "Similarity between the submitted face and the matched face (0–100).", + "example": 99.99 + }, + "source": { + "type": "string", + "enum": [ + "session", + "imported", + "list_entry", + "retained_template" + ], + "description": "Where the matched face was enrolled from: `session` — a verification session or a saved standalone API call; `imported` — a face uploaded directly to a User profile; `list_entry` — a face attached to one of your lists; `retained_template` — an image-free biometric template stored on a User, listed under `/v3/biometric-templates/`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of the matched session or linked User for imported and retained-template faces.", + "example": "user-1" + }, + "vendor_user_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Linked User id. `null` for list entries and session matches without a User.", + "example": "2f7c1a9e-4b28-4a6d-97f1-83576f80e1b4" + }, + "biometric_template_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Id of the retained biometric template. Set only when `source` is `retained_template`; `null` otherwise.", + "example": null + }, + "verification_date": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the matched face was originally captured, or when a retained template was created (`YYYY-MM-DDThh:mm:ssZ`). `null` for `list_entry` matches.", + "example": "2025-01-01T00:00:00Z" + }, + "user_details": { + "type": "object", + "nullable": true, + "description": "Identity data extracted during the matched session's document verification. `null` when the matched session has no document data (e.g. liveness-only sessions) and always `null` for `retained_template` matches. For `source: imported` matches it is populated from the User profile instead: `full_name` set, `document_type`/`document_number` null.", + "properties": { + "full_name": { + "type": "string", + "nullable": true, + "example": "Jane Marie Doe" + }, + "document_type": { + "type": "string", + "nullable": true, + "example": "ID" + }, + "document_number": { + "type": "string", + "nullable": true, + "example": "X1234567" + } + } + }, + "match_image_url": { + "type": "string", + "nullable": true, + "description": "Time-limited URL (`https:///...`) of the matched face's reference image when `save_api_request=true`. With `save_api_request=false` this is an internal storage path that is not directly downloadable. Always `null` for `retained_template` matches because retained templates contain no image.", + "example": "https:///face/3f6a1c2e/reference.jpg" + }, + "status": { + "type": "string", + "nullable": true, + "description": "Status of the matched session (`Approved`, `Declined`, `In Review`, …). `null` for `imported`/`list_entry` matches.", + "example": "Approved" + }, + "is_blocklisted": { + "type": "boolean", + "description": "`true` when the matched face is on your face blocklist. Any sufficiently similar blocklisted match declines the search." + }, + "is_allowlisted": { + "type": "boolean", + "description": "`true` when the matched face is on one of your allowlists. Allowlisted matches suppress duplicate warnings." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "Set when the matched face was enrolled by a standalone API call (e.g. `PASSIVE_LIVENESS`); `null` for regular verification sessions and imported faces.", + "example": null + } + } + } + }, + "user_image": { + "type": "object", + "description": "Face-detection results for the submitted image. Entries here carry only `bbox` and `confidence`.", + "properties": { + "entities": { + "type": "array", + "description": "One entry per detected face. The largest face is the one searched.", + "items": { + "type": "object", + "properties": { + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box `[x_min, y_min, x_max, y_max]` in pixels.", + "example": [ + 40, + 40, + 120, + 120 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.732973 + } + } + } + }, + "best_angle": { + "type": "integer", + "description": "Rotation (degrees) that produced the best face detection; non-zero only when `rotate_image=true` corrected the orientation. Never null on this endpoint (defaults to 0 when the model omits it).", + "example": 0 + } + } + }, + "warnings": { + "type": "array", + "description": "Risk signals. `FACE_IN_BLOCKLIST` / `POSSIBLE_FACE_IN_BLOCKLIST` (`error`) decline the search; `MULTIPLE_FACES_DETECTED` (`warning`) and `DUPLICATED_FACE` / `POSSIBLE_DUPLICATED_FACE` (`information`) are advisory.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "MULTIPLE_FACES_DETECTED", + "FACE_IN_BLOCKLIST", + "POSSIBLE_FACE_IN_BLOCKLIST", + "DUPLICATED_FACE", + "POSSIBLE_DUPLICATED_FACE" + ], + "description": "Machine-readable risk code." + }, + "feature": { + "type": "string", + "enum": [ + "LIVENESS" + ], + "description": "Feature that raised the warning. Always `LIVENESS` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "`null` for `MULTIPLE_FACES_DETECTED`. Blocklist hits carry `{blocklisted_session_id, blocklisted_session_number, api_service}`; duplicate hits carry `{duplicated_session_id, duplicated_session_number, api_service}` pointing at the first matching session." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `warning` and `information` entries are advisory and never decline on their own." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated, e.g. `2026-06-12T01:04:42.763237+00:00`." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when `user_image` is missing, exceeds 5 MB, has an unsupported extension, or when **no face is detected** in the submitted image. Field errors use DRF's `{field: [messages]}` envelope; face-detection failures use `{\"error\": ...}`.", + "content": { + "application/json": { + "examples": { + "No face detected": { + "summary": "The image decodes fine but contains no detectable face", + "value": { + "error": "No face detected in the image" + } + }, + "Missing image": { + "summary": "`user_image` not included in the form data", + "value": { + "user_image": [ + "No file was submitted." + ] + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp", + "value": { + "user_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 5 MB limit", + "value": { + "user_image": [ + "File size should not exceed 5 MB" + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/face-search/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'user_image=@./selfie.jpg' \\\n -F 'search_type=most_similar' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.face_search.search(\n user_image=open(\"user_image.jpg\", \"rb\"),\n search_type=\"most_similar\",\n rotate_image=False,\n save_api_request=True,\n vendor_data=\"user-123\",\n metadata={\"flow\": \"dedup_check\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.faceSearch.search({\n user_image: await fileFromPath(\"user_image.jpg\"),\n search_type: \"most_similar\",\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"dedup_check\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.FaceSearch.Search(ctx, didit.FaceSearchSearchParams{\n\tUserImage: didit.FileFromPath(\"user_image.jpg\"),\n\tSearchType: didit.Some(didit.FaceSearchSearchParamsSearchType(\"most_similar\")),\n\tRotateImage: didit.Bool(false),\n\tSaveAPIRequest: didit.Bool(true),\n\tVendorData: didit.String(\"user-123\"),\n\tMetadata: didit.Some(map[string]any{\"flow\": \"dedup_check\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.FaceSearchSearchParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.faceSearch().search(FaceSearchSearchParams.builder()\n .userImage(Path.of(\"user_image.jpg\"))\n .searchType(FaceSearchSearchParams.SearchType.of(\"most_similar\"))\n .rotateImage(false)\n .saveApiRequest(true)\n .vendorData(\"user-123\")\n .metadata(Map.of(\"flow\", \"dedup_check\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "faceSearch->search([\n 'user_image' => File::fromPath('user_image.jpg'),\n 'search_type' => 'most_similar',\n 'rotate_image' => false,\n 'save_api_request' => true,\n 'vendor_data' => 'user-123',\n 'metadata' => ['flow' => 'dedup_check'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.face_search.search(\n user_image: Pathname(\"user_image.jpg\"),\n search_type: \"most_similar\",\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"dedup_check\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.FaceSearch.SearchAsync(new FaceSearchSearchParams\n{\n UserImage = FileUpload.FromPath(\"user_image.jpg\"),\n SearchType = new FaceSearchSearchParamsSearchType(\"most_similar\"),\n RotateImage = false,\n SaveApiRequest = true,\n VendorData = \"user-123\",\n Metadata = new Dictionary { [\"flow\"] = \"dedup_check\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/sessions/": { + "get": { + "summary": "List verification sessions (KYC and/or KYB) with filters and pagination", + "description": "Returns a paginated list of verification sessions for the application identified by your API key, newest first (descending `session_number`).\n\n**Canonical URL:** the route is `/v3/sessions/` (trailing slash). Requests to `/v3/sessions` without the slash receive a `301` redirect to the slashed URL with the query string preserved; most HTTP clients follow it transparently on GET.\n\n**When to use this endpoint vs webhooks.** Use this endpoint for dashboards, back-office search, reconciliation, and batch exports. Do not poll it to track the progress of individual verifications - configure [webhooks](/integration/webhooks) instead, which push `status.updated` events the moment a session changes. For one session's full result, call `GET /v3/session/{sessionId}/decision/`.\n\n**KYC and KYB sessions.** `session_kind` selects which kinds are listed:\n- `user` (default) - User Verification (KYC) sessions only. Backward-compatible behavior.\n- `business` - Business Verification (KYB) sessions only. Rows have a different, smaller shape (see the response schema).\n- `all` - both kinds in one response: the KYC page and the KYB page for the same `limit`/`offset` are concatenated in `results` (KYC rows first), so a page can contain up to `2 x limit` rows. `count` is the sum of both kinds and `next`/`previous` reflect the KYC-side pagination. For strict pagination of mixed data, call `user` and `business` separately.\n\nEvery row carries a `session_kind` discriminator (`\"user\"` or `\"business\"`).\n\n**Pagination.** Standard limit/offset: `limit` (default 50) and `offset` (default 0), with `next`/`previous` ready-made page URLs. For KYC rows `count` is exact. For KYB rows `count` is computed with a bounded scan and is capped at 100 - when there are more than 100 matches it stays at 100, so iterate using `next` (or by checking whether a full page came back) rather than computing total pages from `count`.\n\n**Filter semantics.** All filters combine with AND and apply to KYC rows. When `session_kind` is `business` or `all`, the KYB side honors the shared parameters `vendor_data`, `status`, `workflow_id`, `country` (registry-company country code), `date_from`, `date_to`, `search` (company name, registration number — type registration numbers without separators, e.g. `DPC001` not `DPC-001` — vendor_data, session number, or session UUID), `warning`, and `organization_name`; the remaining KYC-only filters are ignored for KYB rows. Comma-separated parameters (`status`, `country`, `document_type`, `region`, `workflow_id`, `document_name`, `warning`, `organization_name`, `session_type`) match any of the listed values (OR within the parameter). `vendor_data` is an exact match - use `search` for partial matching. Unknown values in list filters simply return no rows (no error); malformed typed values (`date_from`, `date_to`, `didit_internal_id`, `api_service`, `session_kind`) return `400` with a per-field error object.\n\n**Search.** `search` adapts to the input: a full UUID looks up `session_id`; digits (with optional `+ - . space`) match session-number prefix, document/personal numbers, phone-number prefix, or `vendor_data`; values containing `@` match email prefixes; any other text matches names (accent-insensitive) and `vendor_data`.\n\nNo fixed per-endpoint rate limit is enforced, but keep list polling infrequent and rely on webhooks for real-time updates.", + "operationId": "get_v3_sessions", + "x-didit-sdk": { + "resource": "sessions", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "session_kind", + "in": "query", + "required": false, + "description": "Which kind of sessions to list: `user` (KYC only, default), `business` (KYB only), or `all` (both, concatenated - see the description for merged-pagination semantics). Matching is case-insensitive (`BUSINESS` works); any value other than a case variant of `user`/`business`/`all` returns `400`.", + "schema": { + "type": "string", + "enum": [ + "user", + "business", + "all" + ], + "default": "user" + }, + "example": "all" + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of rows per page (per kind when `session_kind=all`). Defaults to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "example": 20 + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of rows to skip before the first returned row. Defaults to 0. Prefer following the `next`/`previous` URLs from the response.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "example": 20 + }, + { + "name": "status", + "in": "query", + "required": false, + "description": "Comma-separated list of session statuses to include, e.g. `Approved,In Review`. Values are case-sensitive; see [Verification Statuses](/integration/verification-statuses). Unknown values match nothing.", + "schema": { + "type": "string" + }, + "example": "Approved,In Review" + }, + { + "name": "vendor_data", + "in": "query", + "required": false, + "description": "Exact-match filter on the `vendor_data` you sent at session creation (your own user/business identifier). For partial matches use `search` instead.", + "schema": { + "type": "string" + }, + "example": "your-user-id-123" + }, + { + "name": "search", + "in": "query", + "required": false, + "description": "A complete displayed reference such as `S-0001` (user) or `B-0001` (business) matches only that session number and kind; leading zeros and letter case do not affect matching. Smart free-text search. A full UUID matches `session_id`; numeric input matches session-number prefix, document/personal numbers, phone prefix, or `vendor_data`; input containing `@` matches email prefixes; other text matches names (accent-insensitive) and `vendor_data`. Free text containing digits additionally matches document/personal-number search fields and session-number prefixes.", + "schema": { + "type": "string" + }, + "example": "jane doe" + }, + { + "name": "workflow_id", + "in": "query", + "required": false, + "description": "Comma-separated workflow UUIDs (find them on the Workflows page in the Console). Malformed UUIDs in the list are ignored; if none are valid the result is empty.", + "schema": { + "type": "string" + }, + "example": "9f9b1234-aaaa-bbbb-cccc-1234567890ab" + }, + { + "name": "date_from", + "in": "query", + "required": false, + "description": "Only sessions created on or after this date (UTC), format `YYYY-MM-DD`. Malformed dates return `400`.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-06-01" + }, + { + "name": "date_to", + "in": "query", + "required": false, + "description": "Only sessions created up to this date (UTC), format `YYYY-MM-DD`. For KYC rows the entire day is included; for KYB rows the comparison is `created_at <= date_to` (midnight), so use the next day's date to include a full day of KYB sessions.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-06-30" + }, + { + "name": "tags", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated tag ids (`tags` of your application). Returns the sessions that carry any of them. Ids that are not valid UUIDs are ignored, and when none is valid the list is empty." + }, + { + "name": "decided_from", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + }, + "description": "ISO 8601 datetime. Returns the sessions whose status last changed at or after it. It filters on the time of the last status change, not on creation time." + }, + { + "name": "decided_to", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date-time" + }, + "description": "ISO 8601 datetime. Returns the sessions whose status last changed before it. Combine with `decided_from` for a half-open window." + }, + { + "name": "country", + "in": "query", + "required": false, + "description": "Comma-separated country filter. For KYC rows: ISO 3166-1 alpha-3 codes of the ID document's issuing state (e.g. `ESP,USA`), which may differ from nationality. For KYB rows: the registry company's country code.", + "schema": { + "type": "string" + }, + "example": "ESP,USA" + }, + { + "name": "document_type", + "in": "query", + "required": false, + "description": "Comma-separated ID-document type codes verified in the session (KYC rows only): `P` (Passport), `ID` (Identity Card), `DL` (Driver's License), `RP` (Residence Permit), `SSC` (Social Security Card), `HIC` (Health Insurance Card), `WP` (Work Permit), `TC` (Tax Card), `VISA`, `PSC` (Public Service Card), `BC` (Birth Certificate), `OTHER`. Note the response field `document_type` contains the display name, not the code.", + "schema": { + "type": "string" + }, + "example": "P,ID" + }, + { + "name": "document_name", + "in": "query", + "required": false, + "description": "Exact document names; multiple names can be passed comma-separated, but the value is only split on commas when it contains more than one ` - ` separator (a lone name with an embedded comma is matched whole). Names are as recognized by document verification (front or back side), e.g. `Spain - Id Card (2021)`. KYC rows only.", + "schema": { + "type": "string" + }, + "example": "Spain - Id Card (2021)" + }, + { + "name": "region", + "in": "query", + "required": false, + "description": "Comma-separated document region/state values (for documents issued per region, such as US driver's licenses). KYC rows only.", + "schema": { + "type": "string" + }, + "example": "CA,TX" + }, + { + "name": "didit_internal_id", + "in": "query", + "required": false, + "description": "Filter by Didit's internal user identifier (the `didit_internal_id` response field) to list all sessions of one verified user. Must be a valid UUID; malformed values return `400`. KYC rows only.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147" + }, + { + "name": "session_type", + "in": "query", + "required": false, + "description": "Comma-separated session creation channels: `API`, `HOSTED`, `MIGRATED`. The special value `ALL` (or omitting the parameter) disables the filter. KYC rows only.", + "schema": { + "type": "string" + }, + "example": "HOSTED" + }, + { + "name": "api_service", + "in": "query", + "required": false, + "description": "Filter standalone API sessions by service. Invalid values return `400`. KYC rows only.", + "schema": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "FACE_MATCH", + "AGE_ESTIMATION", + "FACE_SEARCH", + "POA", + "AML", + "KYB_REGISTRY", + "PASSIVE_LIVENESS", + "DATABASE_VALIDATION", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION" + ] + }, + "example": "AML" + }, + { + "name": "screened_full_name", + "in": "query", + "required": false, + "description": "Accent-insensitive name search inside AML and database-validation screened data. When combined with `api_service=AML` or `api_service=DATABASE_VALIDATION`, the search is restricted to that feature's screened data. KYC rows only.", + "schema": { + "type": "string" + }, + "example": "Jane Doe" + }, + { + "name": "warning", + "in": "query", + "required": false, + "description": "Comma-separated warning risk codes; returns sessions that raised any of them (e.g. `POSSIBLE_FRAUD`). Risk codes are listed per feature in the [warnings documentation](/integration/webhooks). Applies to KYC rows and, with business warning codes, to KYB rows.", + "schema": { + "type": "string" + }, + "example": "POSSIBLE_FRAUD" + }, + { + "name": "status_initialized_as_in_review", + "in": "query", + "required": false, + "description": "When `true`, only sessions whose decision initially landed in `In Review` (useful to audit manual-review volume). KYC rows only.", + "schema": { + "type": "boolean" + }, + "example": true + }, + { + "name": "is_kyc_to_be_reviewed", + "in": "query", + "required": false, + "description": "`true` returns sessions whose document verification is flagged for manual review; `false` returns sessions with document verification explicitly not flagged. Omit to disable. KYC rows only.", + "schema": { + "type": "boolean" + }, + "example": true + }, + { + "name": "euuid", + "in": "query", + "required": false, + "description": "Filter by the end user's euuid (UUID assigned to the verifying user, used by Console deep links). Must be a valid UUID; malformed values return a 400 field error. KYC rows only.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "0f8fad5b-d9cb-469f-a165-70867728950e" + }, + { + "name": "organization_name", + "in": "query", + "required": false, + "description": "Comma-separated organization names. Console-oriented; with an API key (already scoped to one application) it only matches your own organization's name.", + "schema": { + "type": "string" + }, + "example": "Acme Inc" + } + ], + "responses": { + "200": { + "description": "Paginated session list. `results` items are KYC rows, KYB rows, or both depending on `session_kind`; discriminate with each row's `session_kind` field.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Number of sessions matching the filters: exact up to 100 and capped at 100 beyond that, so do not use it as a total. Use `next` to know whether more pages exist. For `session_kind=all` it is the sum of both kinds, each capped at 100.", + "example": 2 + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page.", + "example": "https://verification.didit.me/v3/sessions/?limit=20&offset=20" + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page.", + "example": null + }, + "results": { + "type": "array", + "description": "Session rows, newest first. With `session_kind=all`, KYC rows come first, then KYB rows.", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/UserSessionListItem" + }, + { + "$ref": "#/components/schemas/BusinessSessionListItem" + } + ], + "discriminator": { + "propertyName": "session_kind", + "mapping": { + "user": "#/components/schemas/UserSessionListItem", + "business": "#/components/schemas/BusinessSessionListItem" + } + } + } + } + } + }, + "examples": { + "KYC sessions (default)": { + "summary": "session_kind=user - one completed and one not-started session", + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "session_id": "5b3720ed-d429-42ef-b67f-37ea805f48ee", + "session_kind": "user", + "session_number": 720, + "session_url": "https://verify.didit.me/session/CuXoBYCYVO5Y", + "portrait_image": "https:///portraits/5b3720ed.jpg?X-Amz-Expires=3600&X-Amz-Signature=...", + "document_type": "Passport", + "full_name": "Jane Marie Doe", + "country": "ESP", + "status": "Approved", + "vendor_data": "your-user-id-123", + "didit_internal_id": "d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147", + "created_at": "2026-06-09T12:26:54.328364Z", + "features": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + }, + { + "feature": "IP_ANALYSIS", + "status": "Approved" + } + ], + "phone_number": { + "number": "+14155552671", + "is_verified": true + }, + "email_address": { + "email": "jane@example.com", + "is_verified": true + }, + "additional_details": { + "aml": { + "total_hits": 0, + "entity_type": "person", + "is_adverse_media_fetched": false + }, + "liveness": { + "method": "PASSIVE", + "score": 0.98 + }, + "face_match": { + "score": 0.95 + } + } + }, + { + "session_id": "038ad62f-5ebd-46c2-afe9-4b2956bf687f", + "session_kind": "user", + "session_number": 719, + "session_url": "https://verify.didit.me/session/7RGKiO6ioGH6", + "portrait_image": null, + "document_type": null, + "full_name": null, + "country": null, + "status": "Not Started", + "vendor_data": "your-user-id-124", + "didit_internal_id": "eb4e679d-ca4b-461f-9685-687bac29ebdb", + "created_at": "2026-06-09T08:02:23.343053Z", + "features": [ + { + "feature": "ID_VERIFICATION", + "status": "Not Finished" + }, + { + "feature": "LIVENESS", + "status": "Not Finished" + }, + { + "feature": "FACE_MATCH", + "status": "Not Finished" + }, + { + "feature": "AML", + "status": "Not Finished" + }, + { + "feature": "IP_ANALYSIS", + "status": "Not Finished" + } + ], + "phone_number": null, + "email_address": null, + "additional_details": null + } + ] + } + }, + "Mixed KYC + KYB (session_kind=all)": { + "summary": "session_kind=all - KYC row followed by a KYB row", + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "session_id": "5b3720ed-d429-42ef-b67f-37ea805f48ee", + "session_kind": "user", + "session_number": 720, + "session_url": "https://verify.didit.me/session/CuXoBYCYVO5Y", + "portrait_image": null, + "document_type": "Identity Card", + "full_name": "Jane Marie Doe", + "country": "ESP", + "status": "In Review", + "vendor_data": "your-user-id-123", + "didit_internal_id": "d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147", + "created_at": "2026-06-09T12:26:54.328364Z", + "features": [ + { + "feature": "ID_VERIFICATION", + "status": "In Review" + }, + { + "feature": "LIVENESS", + "status": "Approved" + } + ], + "phone_number": null, + "email_address": null, + "additional_details": { + "liveness": { + "method": "PASSIVE", + "score": 0.98 + } + } + }, + { + "session_id": "8a1f63c2-1b9e-4f3a-9c41-2f6a1f0b7d52", + "session_kind": "business", + "session_number": 42, + "status": "Approved", + "vendor_data": "your-company-id-77", + "created_at": "2026-06-08T09:14:02.118450Z", + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "workflow_type": "kyb", + "workflow_label": "Standard KYB", + "company_name": "Acme Holdings S.L.", + "registration_number": "B12345678", + "country": "ES" + } + ] + } + } + } + } + } + }, + "301": { + "description": "Redirect to the canonical trailing-slash URL. Returned when the request path is `/v3/sessions` (no trailing slash); the `Location` header points to `/v3/sessions/` with the query string preserved. GET clients typically follow it automatically." + }, + "400": { + "description": "Invalid query parameter. The body is an object keyed by the offending parameter.", + "content": { + "application/json": { + "examples": { + "Invalid session_kind": { + "summary": "session_kind not one of user/business/all", + "value": { + "session_kind": "Must be \"user\", \"business\", or \"all\"." + } + }, + "Invalid date": { + "summary": "Malformed date_from/date_to", + "value": { + "date_from": [ + "Enter a valid date." + ] + } + }, + "Invalid didit_internal_id": { + "summary": "didit_internal_id is not a UUID", + "value": { + "didit_internal_id": [ + "Enter a valid UUID." + ] + } + }, + "Invalid api_service": { + "summary": "api_service not in the allowed choices", + "value": { + "api_service": [ + "Select a valid choice. FOO is not one of the available choices." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, malformed, or revoked API key. A missing key returns `403`; a wrong, revoked or expired key returns `401`.\n\n**Deleted application:** for a few minutes after an application is deleted, a request made with one of its API keys can answer `{\"detail\": \"This application was deleted.\", \"code\": \"application_deleted\"}`. Do not retry: the application and its API keys no longer exist. After those few minutes, the key is refused like any other invalid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You do not have permission to perform this action." + }, + "code": { + "type": "string", + "description": "Machine-readable reason, present only on some failures. `application_deleted`: the API key belongs to an application that was deleted.", + "example": "application_deleted" + } + }, + "required": [ + "detail" + ] + }, + "examples": { + "Forbidden": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "ApplicationDeleted": { + "summary": "API key of a deleted application", + "value": { + "detail": "This application was deleted.", + "code": "application_deleted" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -s 'https://verification.didit.me/v3/sessions/?status=Approved,In%20Review&date_from=2026-06-01&limit=20' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.sessions.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.sessions.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Sessions.ListAutoPaging(ctx, didit.SessionsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.sessions().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->list() as $item) {\n print_r($item);\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Sessions.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/session/{sessionId}/share/": { + "post": { + "summary": "Mint a share token for a finished verification session", + "description": "Mint a short-lived JWT that lets a specific Didit application import this finished session. Pair with `POST /v3/session/import-shared/`.", + "operationId": "post_v3_session_share", + "x-didit-sdk": { + "resource": "sessions", + "method": "share", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "200": { + "description": "Share token minted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "share_token": { + "type": "string", + "description": "HS256-signed JWT. Pass this verbatim as `share_token` to [`POST /v3/session/import-shared/`](/sessions-api/import-shared-session) on the target application.", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + }, + "for_application_id": { + "type": "string", + "format": "uuid", + "description": "Echoes the target application UUID that the token is bound to.", + "example": "a5f3bca2-46e2-411e-90ef-a580900a57ee" + }, + "session_kind": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Whether the source session is a User Verification (KYC) or Business Verification (KYB) session." + } + } + }, + "examples": { + "User session": { + "summary": "Share a KYC session", + "value": { + "share_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uX2lkIjoiMjBiYjNjM2ItNjE0Ni00MzlmLTg0YTQtYmQzMGQwMGFjNmEyIiwiZnJvbV9hcHBsaWNhdGlvbl9pZCI6ImRiZDIwZTM0LTQyZTktNGYyYy1iYTkxLWNmMDc2MjAxNmY2NCIsImZvcl9hcHBsaWNhdGlvbl9pZCI6ImE1ZjNiY2EyLTQ2ZTItNDExZS05MGVmLWE1ODA5MDBhNTdlZSIsImlhdCI6MTc1MzYzMDY2NiwiZXhwIjoxNzUzNjM0MjY2fQ.JJ9pNE_hqZsOtbR0XYZIWw4JzidjdEl279iUrsIkhGE", + "for_application_id": "a5f3bca2-46e2-411e-90ef-a580900a57ee", + "session_kind": "user" + } + }, + "Business session": { + "summary": "Share a KYB session", + "value": { + "share_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "for_application_id": "a5f3bca2-46e2-411e-90ef-a580900a57ee", + "session_kind": "business" + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials. Unlike most v3 endpoints, this endpoint authenticates through the permission decorator and returns `401` when no valid token/key is presented.", + "content": { + "application/json": { + "examples": { + "Unauthorized": { + "summary": "Unauthorized", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "404": { + "description": "No session with the given `session_id` exists in your application.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Session not found", + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field errors are keyed by field name; the not-finished error arrives under a `detail` key (as an array).", + "content": { + "application/json": { + "examples": { + "Wrong status": { + "summary": "Source session is not finished", + "value": { + "detail": [ + "Only finished sessions (\"Approved\", \"Declined\", \"In Review\") can be shared." + ] + } + }, + "Target missing": { + "summary": "Target application does not exist", + "value": { + "for_application_id": [ + "Target application does not exist." + ] + } + }, + "Self-share": { + "summary": "Target is the calling application", + "value": { + "for_application_id": [ + "Cannot share a session with the same application." + ] + } + }, + "TTL bounds": { + "summary": "ttl_in_seconds outside bounds", + "value": { + "ttl_in_seconds": [ + "Ensure this value is greater than or equal to 60." + ] + } + } + } + } + } + }, + "403": { + "description": "The credentials are valid but lack the `write:sessions` permission for this application.", + "content": { + "application/json": { + "examples": { + "No Permission": { + "value": { + "detail": "You do not have permission to perform this action." + }, + "summary": "No Permission" + } + } + } + } + } + }, + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the source verification session to mint a token for.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "for_application_id" + ], + "properties": { + "for_application_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the Didit application that will redeem the token. Must exist, must not be deleted, and must differ from the calling application. Find it in the Business Console under **Settings → Application**.", + "example": "a5f3bca2-46e2-411e-90ef-a580900a57ee" + }, + "ttl_in_seconds": { + "type": "integer", + "description": "Token lifetime, in seconds. Minimum `60`, maximum `86400` (24 h). Defaults to `3600` (1 h).", + "minimum": 60, + "maximum": 86400, + "default": 3600, + "example": 3600 + } + } + }, + "example": { + "for_application_id": "a5f3bca2-46e2-411e-90ef-a580900a57ee", + "ttl_in_seconds": 7200 + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/share/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"for_application_id\": \"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n \"ttl_in_seconds\": 7200\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.share(\n \"11111111-2222-3333-4444-555555555555\",\n for_application_id=\"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n ttl_in_seconds=7200,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.share(\"11111111-2222-3333-4444-555555555555\", {\n for_application_id: \"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n ttl_in_seconds: 7200,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Share(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsShareParams{\n\tForApplicationID: \"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n\tTTLInSeconds: didit.Int(7200),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsShareParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().share(\"11111111-2222-3333-4444-555555555555\", SessionsShareParams.builder()\n .forApplicationId(\"a5f3bca2-46e2-411e-90ef-a580900a57ee\")\n .ttlInSeconds(7200L)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->share('11111111-2222-3333-4444-555555555555', [\n 'for_application_id' => 'a5f3bca2-46e2-411e-90ef-a580900a57ee',\n 'ttl_in_seconds' => 7200,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.share(\n \"11111111-2222-3333-4444-555555555555\",\n for_application_id: \"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n ttl_in_seconds: 7200\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ShareAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsShareParams\n{\n ForApplicationId = \"a5f3bca2-46e2-411e-90ef-a580900a57ee\",\n TtlInSeconds = 7200,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/import-shared/": { + "post": { + "summary": "Redeem a share token to clone a verification session into your application", + "description": "Redeem a share token to clone a KYC or KYB session into the calling application with a fresh `session_id`. Not idempotent — a token redeems once per receiver.", + "operationId": "post_v3_session_import_shared", + "x-didit-sdk": { + "resource": "sessions", + "method": "import_shared", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "201": { + "description": "Session cloned. The body is the V3 decision payload for both kinds, discriminated by `session_kind`: `\"user\"` (KYC) with list-based feature blocks such as `id_verifications`, `liveness_checks`, `aml_screenings`; `\"business\"` (KYB) with blocks like `registry_checks` and `key_people_checks`. Both include `shared_from_session` — the `session_id` of the original session in the source application. With `trust_review: false` the cloned session's `status` is forced to `In Review`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionDecision" + }, + "examples": { + "User session imported": { + "summary": "Cloned KYC session (truncated — full V3 decision payload in reality)", + "value": { + "session_id": "11111111-2222-3333-4444-555555555555", + "session_kind": "user", + "shared_from_session": "99999999-8888-7777-6666-555555555555", + "session_number": 43762, + "session_url": null, + "status": "In Review", + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "vendor_data": "user-1", + "created_at": "2026-05-17T08:42:11Z", + "expires_at": "2026-05-24T08:42:11Z" + } + }, + "Business session imported": { + "summary": "Cloned KYB session (truncated — full V3 KYB decision payload in reality)", + "value": { + "session_id": "22222222-3333-4444-5555-666666666666", + "session_kind": "business", + "shared_from_session": "88888888-7777-6666-5555-444444444444", + "session_number": 982, + "session_url": null, + "status": "Approved", + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "vendor_data": "company-1" + } + } + } + } + } + }, + "400": { + "description": "Validation error (token invalid, expired, wrong target, or references a session that no longer exists).", + "content": { + "application/json": { + "examples": { + "Invalid Share Token": { + "summary": "Invalid JWT", + "value": { + "share_token": [ + "Invalid share token." + ] + } + }, + "Expired Share Token": { + "summary": "Token past `exp`", + "value": { + "share_token": [ + "Share token has expired." + ] + } + }, + "Wrong audience": { + "summary": "Token not bound to this application", + "value": { + "share_token": [ + "This token is not valid for this application." + ] + } + }, + "Original missing": { + "summary": "Source session was deleted", + "value": { + "share_token": [ + "Original session does not exist." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing API key (a wrong, revoked or expired key gets `401`), or this source session has already been imported into the calling application (one redeem per receiver).", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "summary": "Forbidden", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Already imported": { + "summary": "Source already imported once", + "value": { + "detail": "This session has already been shared with your application." + } + } + } + } + } + }, + "404": { + "description": "`workflow_id` does not belong to the calling application.", + "content": { + "application/json": { + "examples": { + "Workflow not found": { + "summary": "Workflow unknown", + "value": { + "detail": "Workflow does not exist for this application." + } + } + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "share_token", + "trust_review", + "workflow_id" + ], + "properties": { + "share_token": { + "type": "string", + "description": "JWT share token issued by [`POST /v3/session/{sessionId}/share/`](/sessions-api/share-session).", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + }, + "trust_review": { + "type": "boolean", + "description": "If `true`, the cloned session keeps the source's final `status`. If `false`, it is forced into `In Review`.", + "example": false + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "UUID of a workflow in the calling application. Cross-application IDs are rejected with `404`.", + "example": "9f9b1234-aaaa-bbbb-cccc-1234567890ab" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Optional override for the cloned session's `vendor_data`.", + "example": "user-1" + } + } + }, + "example": { + "share_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "trust_review": false, + "workflow_id": "9f9b1234-aaaa-bbbb-cccc-1234567890ab", + "vendor_data": "user-1" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/import-shared/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"share_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n \"trust_review\": false,\n \"workflow_id\": \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n \"vendor_data\": \"user-1\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.import_shared(\n share_token=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n trust_review=False,\n workflow_id=\"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n vendor_data=\"user-1\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.importShared({\n share_token: \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n trust_review: false,\n workflow_id: \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n vendor_data: \"user-1\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.ImportShared(ctx, didit.SessionsImportSharedParams{\n\tShareToken: \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n\tTrustReview: false,\n\tWorkflowID: \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n\tVendorData: didit.String(\"user-1\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsImportSharedParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().importShared(SessionsImportSharedParams.builder()\n .shareToken(\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\")\n .trustReview(false)\n .workflowId(\"9f9b1234-aaaa-bbbb-cccc-1234567890ab\")\n .vendorData(\"user-1\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->importShared([\n 'share_token' => 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',\n 'trust_review' => false,\n 'workflow_id' => '9f9b1234-aaaa-bbbb-cccc-1234567890ab',\n 'vendor_data' => 'user-1',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.import_shared(\n share_token: \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n trust_review: false,\n workflow_id: \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n vendor_data: \"user-1\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ImportSharedAsync(new SessionsImportSharedParams\n{\n ShareToken = \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n TrustReview = false,\n WorkflowId = \"9f9b1234-aaaa-bbbb-cccc-1234567890ab\",\n VendorData = \"user-1\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/kyb/search/": { + "post": { + "summary": "KYB registry company search (standalone)", + "operationId": "post_v3_kyb_search", + "x-didit-sdk": { + "resource": "kyb", + "method": "search", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Search official company registries for candidate companies by name and/or registration number. This is step 1 of the two-step registry flow: **search** returns lightweight candidates, then `POST /v3/kyb/select/` retrieves the full profile of one candidate.\n\n**Search billing.** Successful searches using a paid company-data source cost USD 0.50 per request, regardless of candidate count. Empty results, failures and results served only by other sources are not charged. The applicable price is reserved before searching; insufficient balance returns 403 with code `insufficient_credits`. Contract rates apply where agreed, and an unconfigured contract search rate returns 403. Sandbox searches are free. Repeating a successful search can incur another charge; callback delivery does not add a charge. Profile retrieval is billed separately. This search fee also applies to v2. Candidates always come back with `fetch_status: \"pending\"`: the full company data is only fetched (and billed) on select.\n\n**`kyb_response_id` is ephemeral.** It is a per-search handle, not a stable company id. Select promptly after searching and re-search to get a fresh handle instead of storing it.\n\n**`tier` picks the provider chain.** `tier` names the registry tier the search runs on: `basic` (the default, the Lite chain), `shareholders` or `ubo`. The search uses the provider chain that serves that tier, and it is not always the same chain the Lite search reads, so an empty Lite search is not proof that the registry does not hold the company — search again at the tier you need. A country that does not offer the tier you name is searched at the highest tier it offers up to that one. Retrieve the candidate from the endpoint that sells that tier: `POST /v3/kyb/select/` (Lite), `POST /v3/kyb/shareholders/` or `POST /v3/kyb/ubo/`.\n\n**Authentication.** `POST /v3/kyb/search/` (and `/v2/`) takes your application's API key in the `x-api-key` header. The hosted `POST /v1/kyb/search/` is the session-authenticated route a hosted verification flow calls: it answers `403` to an API key and takes no `tier` — the workflow's KYB registry configuration picks the chain.\n\n**Two modes.**\n- *Synchronous (default):* the API polls the registry until the search resolves and returns the final candidate list (`search_resolved: true`).\n- *Asynchronous:* pass `webhook_url` and the API returns immediately — usually with `search_status: \"pending\"` and an empty candidate list — then POSTs a `kyb.registry_search.resolved` callback to your URL when the registry finishes. The callback body is `{\"event_id\", \"event_type\": \"kyb.registry_search.resolved\", \"request_id\", \"vendor_data\", \"metadata\", \"search_status\", \"search_resolved\", \"kyb_registry\", \"timestamp\", \"created_at\"}` — note `timestamp` and `created_at` in the callback are epoch integers, unlike this endpoint's ISO `created_at` — and is sent **unsigned** (header `X-Didit-Unsigned-Callback: true`, no `X-Signature`).\n\n**Country codes.** ISO 3166-1 alpha-2 (`GB`, `DE`), plus `XX-YY` subdivision codes where registries are regional — e.g. `US-CA` for California.\n\n**Sandbox.** Keys from sandbox applications return one static candidate without contacting any registry.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "country_code" + ], + "properties": { + "country_code": { + "type": "string", + "maxLength": 10, + "description": "ISO 3166-1 alpha-2 code of the country whose company registry to search. For example, `GB`. Where company registries operate at state or province level, append the ISO 3166-2 subdivision code as `XX-YY`: `US-CA` searches the California registry. United States searches always require the subdivision — a bare `US` is rejected. Case-insensitive; normalized to upper case.", + "example": "GB" + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "Company name to search for. Either `name` or `registration_number` is required.", + "example": "Tesco" + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Registry registration number to search for. Either `name` or `registration_number` is required." + }, + "tier": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "default": "basic", + "description": "Registry tier whose provider chain this search runs on. `basic` (default) searches the country's Lite chain; `shareholders` and `ubo` search that tier's ownership chain, which can be a different source, so an empty Lite search does not prove the registry lacks the company. A country that does not offer the named tier is searched at the highest tier it offers up to that one. Retrieve the candidate from the endpoint that sells the tier you searched: `POST /v3/kyb/select/` (Lite), `POST /v3/kyb/shareholders/` or `POST /v3/kyb/ubo/`.", + "example": "basic" + }, + "search_type": { + "type": "string", + "enum": [ + "contains" + ], + "default": "contains", + "description": "Name-matching strategy applied by the registry search. `contains` is the only accepted value: no registry source behind the search offers a prefix or fuzzy mode, so `start_with` and `fuzzy` are rejected with `400` (`not a valid choice`)." + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for this lookup; echoed back and included in the webhook callback." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON; echoed back and included in the webhook callback." + }, + "webhook_url": { + "type": "string", + "format": "uri", + "maxLength": 500, + "description": "Switches the search to asynchronous mode: the API returns immediately and POSTs an unsigned `kyb.registry_search.resolved` callback to this URL when the candidate list is ready." + } + } + }, + "example": { + "country_code": "GB", + "name": "Tesco", + "search_type": "contains", + "vendor_data": "company-1234" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/kyb/search/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"country_code\": \"GB\",\n \"name\": \"Tesco\",\n \"search_type\": \"contains\",\n \"vendor_data\": \"company-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.kyb.search(\n country_code=\"GB\",\n name=\"Tesco\",\n search_type=\"contains\",\n vendor_data=\"company-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.kyb.search({\n country_code: \"GB\",\n name: \"Tesco\",\n search_type: \"contains\",\n vendor_data: \"company-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.KYB.Search(ctx, didit.KYBSearchParams{\n\tCountryCode: \"GB\",\n\tName: didit.String(\"Tesco\"),\n\tSearchType: didit.Some(didit.KYBSearchParamsSearchType(\"contains\")),\n\tVendorData: didit.String(\"company-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.KybSearchParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.kyb().search(KybSearchParams.builder()\n .countryCode(\"GB\")\n .name(\"Tesco\")\n .searchType(KybSearchParams.SearchType.of(\"contains\"))\n .vendorData(\"company-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "kyb->search([\n 'country_code' => 'GB',\n 'name' => 'Tesco',\n 'search_type' => 'contains',\n 'vendor_data' => 'company-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.kyb.search(\n country_code: \"GB\",\n name: \"Tesco\",\n search_type: \"contains\",\n vendor_data: \"company-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Kyb.SearchAsync(new KybSearchParams\n{\n CountryCode = \"GB\",\n Name = \"Tesco\",\n SearchType = new KybSearchParamsSearchType(\"contains\"),\n VendorData = \"company-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Search accepted. Synchronous mode returns the resolved candidate list; webhook mode returns immediately (often `search_status: \"pending\"`) and delivers the final list via the `kyb.registry_search.resolved` callback.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "With `webhook_url`: the persisted search-request id, echoed as `request_id` in the `kyb.registry_search.resolved` callback. Without `webhook_url`: a transient correlation UUID (the search is not stored)." + }, + "kyb_registry": { + "$ref": "#/components/schemas/KYBRegistrySearchResults" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Resolved search": { + "summary": "Synchronous mode — final candidate list", + "value": { + "request_id": "0c7e9a40-3a13-4f7e-8a44-9d2e2f1c5b6a", + "kyb_registry": { + "companies": [ + { + "kyb_response_id": "69aeeb95febb0f1704042259", + "name": "Tesco PLC", + "registration_number": "00445790", + "status": "active", + "type": "Public Limited Company", + "risk_level": null, + "fetch_status": "pending" + }, + { + "kyb_response_id": "69aeeb95febb0f170404225a", + "name": "E-TESCO LTD", + "registration_number": "15168476", + "status": "active", + "type": "Private Limited Company", + "risk_level": null, + "fetch_status": "pending" + } + ], + "pagination": { + "total": 2, + "page": 1, + "per_page": 25 + }, + "search_status": "resolved", + "search_resolved": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:40:00.000000+00:00" + } + }, + "Pending search (webhook mode)": { + "summary": "`webhook_url` provided — candidates arrive via the kyb.registry_search.resolved callback", + "value": { + "request_id": "5f2d8c1b-7e4a-4b9c-9f0d-3a6e8c2b1d4f", + "kyb_registry": { + "companies": [], + "pagination": { + "total": 0, + "page": 1, + "per_page": 25 + }, + "search_status": "pending", + "search_resolved": false + }, + "vendor_data": "company-1234", + "metadata": { + "source": "onboarding" + }, + "created_at": "2026-06-11T10:41:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems use DRF's standard envelope; the name/registration-number rule reports under `non_field_errors`. A country the registries do not cover answers `400` with `code: kyb_registry_country_unsupported`.", + "content": { + "application/json": { + "examples": { + "Missing search criteria": { + "summary": "Neither `name` nor `registration_number` provided", + "value": { + "non_field_errors": [ + "Either 'name' or 'registration_number' is required." + ] + } + }, + "Invalid country_code": { + "summary": "Must be `XX` (alpha-2) or `XX-YY` subdivision", + "value": { + "country_code": [ + "country_code must be a 2-letter ISO code." + ] + } + }, + "Invalid search_type": { + "summary": "Only `contains` is accepted", + "value": { + "search_type": [ + "\"start_with\" is not a valid choice." + ] + } + }, + "Invalid webhook_url": { + "summary": "`webhook_url` must be a valid URL", + "value": { + "webhook_url": [ + "Enter a valid URL." + ] + } + }, + "Country not covered": { + "summary": "No company registry covers the country", + "value": { + "detail": "Company registry search is not available for this country. Enter the company details manually to continue.", + "code": "kyb_registry_country_unsupported" + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Also returned before searching when the applicable search fee cannot be covered (`code: \"insufficient_credits\"`), or when a contract search rate is missing.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded, or the search allowance is spent. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600), with `X-RateLimit-*` and `Retry-After` headers. Separately, paid registry searches are limited to 600 per application per hour: past that the response is `429` with `code: kyb_registry_search_limit_reached`, and searches served only by free sources still work. Wait and search again later.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + }, + "Search allowance spent": { + "summary": "More than 600 paid searches in the last hour", + "value": { + "detail": "The maximum number of company registry searches for this session has been reached. Enter the company details manually to continue.", + "code": "kyb_registry_search_limit_reached" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + } + } + } + }, + "502": { + "description": "The KYB registry provider is temporarily unavailable - a 5xx response, timeout, or connection failure from the upstream registry. The search is not billed. Retry later, or, in Didit's hosted verification flow, the user is automatically offered manual company entry instead of a retry-only error.", + "content": { + "application/json": { + "examples": { + "Registry provider outage": { + "summary": "The registry provider did not answer the search", + "value": { + "detail": "The KYB registry provider is temporarily unavailable. Please try again shortly.", + "code": "kyb_registry_provider_unavailable" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/kyb/select/": { + "post": { + "summary": "KYB registry company select — Lite tier (standalone)", + "operationId": "post_v3_kyb_select", + "x-didit-sdk": { + "resource": "kyb", + "method": "select", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Retrieve the **Lite** registry profile of one candidate returned by `POST /v3/kyb/search/` — company details, registered address, identifiers, status, activity and the officers, plus the raw registry payload. Shareholders and ultimate beneficial owners are sold by `POST /v3/kyb/shareholders/` and `POST /v3/kyb/ubo/`.\n\n**Pairing with search.** Pass the candidate's `kyb_response_id` exactly as returned by a search of the same application. The handle is ephemeral and scoped to that search — select promptly, and re-search for a fresh handle rather than storing it.\n\n**Billing.** Each select is billed at the country's Lite price (any search fee is separate); see [KYB registry pricing](/getting-started/kyb-registry-pricing). The balance is checked before any registry call (`403` when short) and the charge is recorded against the created business session.\n\n**Persistence is mandatory.** Every select creates a business session (its id is the `request_id`) so billing and retrieval can be tracked; it appears in the console. `save_api_request` is accepted for symmetry but must be `true` (the default) — sending `false` returns `400`.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBRegistryCandidateRequest" + }, + "example": { + "kyb_response_id": "69aeeb95febb0f1704042259", + "vendor_data": "company-1234" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/kyb/select/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"kyb_response_id\": \"69aeeb95febb0f1704042259\",\n \"vendor_data\": \"company-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.kyb.select(\n kyb_response_id=\"69aeeb95febb0f1704042259\",\n vendor_data=\"company-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.kyb.select({\n kyb_response_id: \"69aeeb95febb0f1704042259\",\n vendor_data: \"company-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.KYB.Select(ctx, didit.KYBRegistryCandidateRequest{\n\tKYBResponseID: \"69aeeb95febb0f1704042259\",\n\tVendorData: didit.String(\"company-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.KYBRegistryCandidateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.kyb().select(KYBRegistryCandidateRequest.builder()\n .kybResponseId(\"69aeeb95febb0f1704042259\")\n .vendorData(\"company-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "kyb->select([\n 'kyb_response_id' => '69aeeb95febb0f1704042259',\n 'vendor_data' => 'company-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.kyb.select(\n kyb_response_id: \"69aeeb95febb0f1704042259\",\n vendor_data: \"company-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Kyb.SelectAsync(new KYBRegistryCandidateRequest\n{\n KybResponseId = \"69aeeb95febb0f1704042259\",\n VendorData = \"company-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Company retrieved and billed. `kyb_registry` carries the full profile; check `data_resolved` — when `false` the registry was still processing and the stored record completes automatically once it resolves.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the business session created for this retrieval. The stored record (visible in the console) keeps updating if the registry resolves after the response." + }, + "registry_tier_requested": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "The tier this endpoint sells: `basic` (Lite) for select, `shareholders`, `ubo`." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "The tier actually delivered and billed. Lower than requested when the country's registries hold no ownership data, or the registry has no ownership record for this company." + }, + "kyb_registry": { + "type": "object", + "description": "Full registry company profile.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit's id for this company record." + }, + "node_id": { + "type": "string", + "example": "feature_kyb_registry" + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review" + ], + "description": "Didit's assessment of the registry check (e.g. `In Review` when the company is not active in the registry or ownership data could not be derived)." + }, + "registry_status": { + "type": "string", + "nullable": true, + "enum": [ + "active", + "dissolved", + "deregistered", + "see full details", + "authorised", + "appointed representative", + "unauthorised", + "inactive", + "no longer authorised", + "closed", + "struck off" + ], + "description": "Normalized company status as reported by the registry." + }, + "data_resolved": { + "type": "boolean", + "description": "`true` once the registry returned the full profile. `false` when the registry was still processing after polling — the stored record completes automatically once the registry resolves." + }, + "company_name": { + "type": "string", + "example": "Tesco PLC" + }, + "registration_number": { + "type": "string", + "nullable": true, + "example": "00445790" + }, + "country_code": { + "type": "string", + "description": "ISO 3166-1 alpha-2.", + "example": "GB" + }, + "region": { + "type": "string", + "nullable": true, + "description": "Subdivision code when the registry is regional (e.g. `CA` for `US-CA`); empty otherwise." + }, + "company_type": { + "type": "string", + "nullable": true, + "example": "Public Limited Company" + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true, + "example": "1947-11-27" + }, + "registered_address": { + "type": "string", + "nullable": true, + "example": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA" + }, + "tax_number": { + "type": "string", + "nullable": true, + "example": "GB412 8925 93" + }, + "risk_level": { + "type": "string", + "nullable": true, + "description": "Registry-reported risk level, when available." + }, + "verification_status": { + "type": "string", + "enum": [ + "verified", + "failed", + "unknown" + ], + "nullable": true, + "description": "Cross-jurisdiction verification status: `verified` when the registry status establishes standing (`active`), `failed` when it says the company is no longer standing (for example `dissolved`), otherwise `unknown`. `null` for a company that did not come from a registry and while a selection is still `pending`." + }, + "is_from_registry": { + "type": "boolean", + "description": "Always `true` for this endpoint — the profile was retrieved from an official registry." + }, + "fetch_status": { + "type": "string", + "enum": [ + "pending", + "resolved" + ], + "description": "Registry fetch lifecycle. Mirrored by `data_resolved`." + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true, + "description": "Activity description or classification code, when reported." + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "nullable": true, + "description": "Decimal amount as a string, when reported." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 code, when reported." + }, + "website": { + "type": "string", + "nullable": true, + "example": "https://www.tesco.com/" + }, + "email": { + "type": "string", + "nullable": true, + "example": "customer.service@tesco.co.uk" + }, + "phone": { + "type": "string", + "nullable": true, + "example": "03301231688" + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true, + "description": "LEI, when reported." + }, + "location_of_registration": { + "type": "string", + "nullable": true, + "description": "Registry/jurisdiction the company is registered with, when reported." + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards. At select time this is typically not yet set — VAT and tax ID validation runs when the end user submits the registry step in the hosted flow." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true, + "description": "Registry-reported share/capital details (currency, total shares, share classes), when available." + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "example": "MELISSA BETHELL" + }, + "designation": { + "type": "string", + "nullable": true, + "description": "Raw registry designation (e.g. `Director`, `Secretary`)." + }, + "role": { + "type": "string", + "description": "Normalized role derived from the designation. Full 17-value model set; registry parsing on this endpoint typically emits director, non_executive_director, secretary, chairman, or company_officer.", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "company_officer", + "settlor", + "protector", + "investor", + "other" + ] + }, + "nationality": { + "type": "string", + "nullable": true + }, + "is_active": { + "type": "boolean", + "description": "`false` when the registry marks the appointment as resigned." + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending" + ], + "description": "Status of an associated KYC session, when one was started for this officer. `null` otherwise." + }, + "kyc_session_url": { + "type": "string", + "nullable": true, + "description": "Verification URL of the associated KYC session, when one exists." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + }, + "description": "Directors, secretaries, and other officers extracted from the registry." + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the owner is a natural person or a corporate entity." + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Normalized roles (e.g. `ubo`, `shareholder`)." + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership band reported by the registry (e.g. `25%`)." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership band reported by the registry." + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending" + ] + }, + "kyc_session_url": { + "type": "string", + "nullable": true + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true, + "description": "Parsed ownership percentage (max bound, falling back to min bound)." + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + }, + "shareholding": { + "$ref": "#/components/schemas/KYBRegistryShareholding" + } + } + }, + "description": "Beneficial owners / persons with significant control extracted from the registry. Can be empty when the registry does not expose ownership." + }, + "addresses": { + "type": "array", + "nullable": true, + "description": "Every address the registry states, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryAddress" + } + }, + "industries": { + "type": "array", + "nullable": true, + "description": "The activities the registry classifies the company under, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryIndustry" + } + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "$ref": "#/components/schemas/KYBRegistryData" + }, + "user_provided_data": { + "type": "object", + "nullable": true, + "description": "Always `null` on this endpoint (used by the hosted KYB flow when end users edit registry data)." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set when a console reviewer edits the record later." + }, + "is_editable": { + "type": "boolean", + "description": "Whether the registry-sourced data can still be edited in the hosted flow/console." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "Same as the top-level `registry_tier_delivered`." + }, + "registry_ownership_status": { + "type": "string", + "nullable": true, + "enum": [ + "not_requested", + "delivered", + "not_held", + "unavailable" + ], + "description": "`delivered` when the ownership data came back, `not_held` when the ownership registry answered and does not list this company (see `registry_data.ownership_error`), `unavailable` when an ownership tier was requested but the registry had none for this company or could not be asked (see `registry_data.ownership_error`), `not_requested` on the Lite tier." + }, + "ownership": { + "$ref": "#/components/schemas/KYBRegistryOwnership" + }, + "registry_tier": { + "type": "string", + "nullable": true, + "description": "The registry tier the workflow ordered: `basic`, `shareholders` or `ubo`. Also in `ownership.tier_requested`." + }, + "registry_monitoring": { + "type": "object", + "nullable": true, + "description": "The latest registry monitor for the company, or `null` when none exists.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string" + }, + "unavailable_reason": { + "type": "string", + "nullable": true + }, + "enrolled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "next_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "last_event_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "events_count": { + "type": "integer" + }, + "watched_fields": { + "description": "The company fields the monitor watches." + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Resolved company": { + "summary": "Full registry profile (officer list truncated for brevity)", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "f1e2d3c4-b5a6-4789-9c0d-1e2f3a4b5c6d", + "node_id": "feature_kyb_registry", + "status": "In Review", + "registry_status": "active", + "data_resolved": true, + "company_name": "Tesco PLC", + "registration_number": "00445790", + "country_code": "GB", + "region": "", + "company_type": "Public Limited Company", + "incorporation_date": "1947-11-27", + "registered_address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "tax_number": "GB412 8925 93", + "risk_level": "low risk", + "verification_status": "verified", + "is_from_registry": true, + "fetch_status": "resolved", + "alternative_names": "TESCO STORES (HOLDINGS) PUBLIC LIMITED COMPANY", + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": "https://www.tesco.com/", + "email": "customer.service@tesco.co.uk", + "phone": "03301231688", + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": { + "currency": "Pound (£)", + "total_number_of_shares": "8174378721", + "meta_detail": { + "Class_Of_Share": "Ordinary" + } + }, + "officers": [ + { + "uuid": "a1b2c3d4-e5f6-4789-9a0b-1c2d3e4f5a6b", + "name": "CHRISTOPHER JON TAYLOR", + "designation": "Secretary", + "role": "secretary", + "nationality": null, + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + }, + { + "uuid": "b2c3d4e5-f6a7-4890-8b1c-2d3e4f5a6b7c", + "name": "MELISSA BETHELL", + "designation": "Director", + "role": "director", + "nationality": "British", + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + } + ], + "beneficial_owners": [], + "addresses": [ + { + "address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "type": "legal_entity_registry_address", + "description": "registered office address" + } + ], + "industries": null, + "accounts": { + "last_account": "26 February 2025", + "next_account": "26 February 2026", + "due_by_date": "26 August 2026" + }, + "registry_data": {}, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00" + } + }, + "Pending (registry still resolving)": { + "summary": "Select stayed pending after polling — sparse fields, auto-completes server-side", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "7c0a4f7e-2b1d-4f6a-9c3e-8d5b2a1f0e9d", + "node_id": "feature_kyb_registry", + "status": "Not Finished", + "registry_status": null, + "data_resolved": false, + "company_name": "Tesco PLC", + "registration_number": "00445790", + "country_code": "GB", + "region": null, + "company_type": null, + "incorporation_date": null, + "registered_address": null, + "tax_number": null, + "risk_level": null, + "verification_status": null, + "is_from_registry": true, + "fetch_status": "pending", + "alternative_names": null, + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": null, + "email": null, + "phone": null, + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": {}, + "officers": [], + "beneficial_owners": [], + "addresses": null, + "industries": null, + "accounts": null, + "registry_data": {}, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems use DRF's standard envelope. A country the registries do not cover answers `400` with `code: kyb_registry_country_unsupported`.", + "content": { + "application/json": { + "examples": { + "Missing kyb_response_id": { + "summary": "`kyb_response_id` is required", + "value": { + "kyb_response_id": [ + "This field is required." + ] + } + }, + "save_api_request=false rejected": { + "summary": "Selects are always persisted", + "value": { + "save_api_request": [ + "KYB registry select must be saved so billing and retrieval can be tracked." + ] + } + }, + "Country not covered": { + "summary": "No company registry covers the country", + "value": { + "detail": "Company registry search is not available for this country. Enter the company details manually to continue.", + "code": "kyb_registry_country_unsupported" + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/passive-liveness/": { + "post": { + "summary": "Passive Liveness", + "description": "Detect presentation attacks — printed photos, screen replays, masks, deepfakes — from a single face image. No video, motion, or user interaction required.\n\n**How it works.** The image is analyzed by a biometric model; the largest detected face is evaluated and a liveness `score` (0–100) is computed. `status` is `Declined` when any `error`-severity warning fires:\n- `NO_FACE_DETECTED` — the liveness model found no face,\n- `LOW_LIVENESS_SCORE` — score at or below `face_liveness_score_decline_threshold` (default `30`),\n- `LIVENESS_FACE_ATTACK` — the model detected a presentation attack,\n- `FACE_IN_BLOCKLIST` / `POSSIBLE_FACE_IN_BLOCKLIST` — the face matches an entry on your face blocklist.\n\n`MULTIPLE_FACES_DETECTED` is a `warning`-severity signal (does not decline), and `DUPLICATED_FACE` / `POSSIBLE_DUPLICATED_FACE` are `information`-severity signals that the same face already appeared in another approved session (they never decline). `face_quality` and `face_luminance` (0–100, nullable) describe capture quality.\n\n**Blocklist and duplicate screening.** Runs on **every** call — with or without `save_api_request` — by matching the detected face against your application's face search index. Prior faces with the same `vendor_data` are excluded from the entire index search — so re-running liveness for the same user does not trigger `DUPLICATED_FACE`, and a blocklisted face originating from a session with the caller's own `vendor_data` is also NOT flagged (`FACE_IN_BLOCKLIST` screening is bypassed for same-`vendor_data` faces).\n\n**Billing.** Each `200` response consumes one Passive Liveness API credit (paid from your plan credit or top-up balance). Insufficient balance returns `403` before any image processing.\n\n**Session persistence and face enrollment (`save_api_request`, default `true`).** When `true`, the call is persisted as an API-type session (Business Console, `GET /v3/session/{sessionId}/decision/` via the returned `request_id`, `status.updated` webhook), the reference image and face crop are stored, and the detected face is **enrolled into your face search index** so later Face Search calls and duplicate checks can match it. When `false`, the call is one-shot: nothing is stored and the face is not enrolled (blocklist/duplicate screening still runs).\n\n**Sandbox.** Sandbox API keys skip all processing and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Approved` mock payload, no session is persisted, and no credits are consumed.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3passive-liveness", + "x-didit-sdk": { + "resource": "passive_liveness", + "method": "check", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "user_image" + ], + "properties": { + "user_image": { + "type": "string", + "format": "binary", + "description": "Front-facing face image (e.g. a selfie). Allowed extensions: `tiff`, `jpg`, `jpeg`, `png`, `webp`. Maximum upload size: **5 MB** (larger files are rejected with `400`). Images are automatically compressed to ~0.5 MB before processing, so very high resolutions do not improve accuracy. The image should contain a single subject — when several faces are present, the largest one is evaluated and a `MULTIPLE_FACES_DETECTED` warning is added." + }, + "face_liveness_score_decline_threshold": { + "type": "number", + "format": "float", + "default": 30, + "minimum": 0, + "maximum": 100, + "description": "Liveness score threshold (0–100). A computed score **at or below** this value emits a `LOW_LIVENESS_SCORE` warning and sets `status` to `Declined`. Default `30`; raise it for a stricter anti-spoofing posture. Values outside 0–100 return `400`.", + "example": 30 + }, + "rotate_image": { + "type": "boolean", + "default": false, + "description": "When `true`, the service tries 90-degree rotations of the input and uses the orientation that yields the best face detection. Useful when EXIF orientation is missing. Adds latency.", + "example": false + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "When `true` (default), persists the call as an API-type session, stores the face images, emits a `status.updated` webhook, and enrolls the detected face into your face search index. When `false`, the call is one-shot — nothing is stored and the face is not enrolled. Blocklist and duplicate screening run either way.", + "example": true + }, + "vendor_data": { + "type": "string", + "description": "Optional opaque identifier (your user id, email, UUID…) stored on the persisted session and echoed back. Also used to exclude prior sessions with the same `vendor_data` from the duplicate-face check, so re-verifying the same user does not raise `DUPLICATED_FACE`.", + "example": "user-123" + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Optional JSON object stored with the session (when `save_api_request=true`) and echoed back. In multipart requests, send it as a JSON-encoded string field — it is parsed into an object.", + "example": { + "flow": "withdrawal" + } + } + } + }, + "example": { + "user_image": "(binary JPEG/PNG selfie)", + "face_liveness_score_decline_threshold": 30, + "rotate_image": false, + "save_api_request": true, + "vendor_data": "user-123", + "metadata": { + "flow": "withdrawal" + } + } + } + } + }, + "responses": { + "200": { + "description": "Passive liveness completed. `liveness.score` is the model's 0–100 confidence that the image shows a live person; `status` is `Approved` unless an `error`-severity warning fired. A spoofed or blocklisted face still returns `200` — inspect `liveness.status` and `liveness.warnings`, not just the HTTP code. When `save_api_request=true`, `request_id` is the persisted session id.", + "content": { + "application/json": { + "examples": { + "Approved": { + "summary": "Live face — no warnings", + "value": { + "request_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef", + "liveness": { + "status": "Approved", + "method": "PASSIVE", + "score": 95.0, + "user_image": { + "entities": [ + { + "bbox": [ + 661, + 728, + 1688, + 2188 + ], + "confidence": 0.732973, + "age": 26.91, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [], + "face_quality": 84.21, + "face_luminance": 50.33 + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "Declined - presentation attack": { + "summary": "The model flagged a spoof (print, replay, mask…)", + "value": { + "request_id": "f1c8c9b2-d3e4-4f5a-9b8c-7d6e5f4a3b2c", + "liveness": { + "status": "Declined", + "method": "PASSIVE", + "score": 12.5, + "user_image": { + "entities": [ + { + "bbox": [ + 100, + 120, + 420, + 540 + ], + "confidence": 0.81, + "age": 30.1, + "gender": "female", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "LOW_LIVENESS_SCORE", + "feature": "LIVENESS", + "additional_data": null, + "log_type": "error", + "short_description": "Low liveness score", + "long_description": "The liveness check resulted in a low score, indicating potential use of non-live facial representations or poor-quality biometric data." + }, + { + "risk": "LIVENESS_FACE_ATTACK", + "feature": "LIVENESS", + "additional_data": null, + "log_type": "error", + "short_description": "Liveness Face Attack", + "long_description": "The system detected a potential attempt to bypass the liveness check." + } + ], + "face_quality": 72.4, + "face_luminance": 48.2 + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + }, + "Declined - face on blocklist": { + "summary": "Live face, but it matches a blocklist entry", + "value": { + "request_id": "0b54a1de-22aa-4e0f-9c84-5a2b1f3c4d5e", + "liveness": { + "status": "Declined", + "method": "PASSIVE", + "score": 93.75, + "user_image": { + "entities": [ + { + "bbox": [ + 40, + 40, + 100, + 100 + ], + "confidence": 0.722082, + "age": 27.0, + "gender": "male", + "race": null + } + ], + "best_angle": 0 + }, + "warnings": [ + { + "risk": "FACE_IN_BLOCKLIST", + "feature": "LIVENESS", + "additional_data": { + "blocklisted_session_id": "882c42d5-8a4d-4d20-8080-a22f57822c86", + "blocklisted_session_number": 3242, + "api_service": null + }, + "log_type": "error", + "short_description": "Face in blocklist", + "long_description": "The system identified a face in the blocklist, which means the face is not allowed to be verified." + } + ], + "face_quality": null, + "face_luminance": null + }, + "vendor_data": "user-123", + "metadata": null, + "created_at": "2026-06-12T01:04:42.763237+00:00" + } + } + }, + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "liveness": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "`Declined` when at least one `error`-severity warning fired (no face, low score, presentation attack, or a blocklist hit); `Approved` otherwise. `warning`/`information` entries (multiple faces, duplicates) never decline on their own." + }, + "method": { + "type": "string", + "enum": [ + "PASSIVE" + ], + "description": "Always `PASSIVE` for this endpoint." + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Liveness confidence (0–100, two decimals) that the image depicts a live person rather than a spoof. `null` when the model returned no confidence.", + "example": 95.0 + }, + "user_image": { + "type": "object", + "description": "Face-detection results for the submitted image.", + "properties": { + "entities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "bbox": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 4, + "maxItems": 4, + "description": "Bounding box of the detected face as `[x_min, y_min, x_max, y_max]` pixel coordinates in the processed image.", + "example": [ + 661, + 728, + 1688, + 2188 + ] + }, + "confidence": { + "type": "number", + "format": "float", + "minimum": 0, + "maximum": 1, + "description": "Face-detection confidence (0–1).", + "example": 0.732973 + }, + "age": { + "type": "number", + "format": "float", + "description": "Model-estimated age of the detected face, in years. Informational only — it does not affect the liveness decision.", + "example": 26.91 + }, + "gender": { + "type": "string", + "description": "Model-predicted gender of the detected face (`male` or `female`). Informational only.", + "example": "male" + }, + "race": { + "type": "string", + "nullable": true, + "description": "Reserved field — always `null` in responses from this endpoint.", + "example": null + } + } + }, + "description": "One entry per detected face. The largest face is the one evaluated." + }, + "best_angle": { + "type": "integer", + "nullable": true, + "description": "Rotation (degrees) that produced the best face detection; non-zero only when `rotate_image=true` corrected the orientation.", + "example": 0 + } + } + }, + "warnings": { + "type": "array", + "description": "Risk signals. `error` severity (declines): `NO_FACE_DETECTED`, `LOW_LIVENESS_SCORE` (score at or below the threshold), `LIVENESS_FACE_ATTACK`, `FACE_IN_BLOCKLIST`, `POSSIBLE_FACE_IN_BLOCKLIST`. `warning` severity (review, no decline): `MULTIPLE_FACES_DETECTED`. `information` severity (no decline): `DUPLICATED_FACE`, `POSSIBLE_DUPLICATED_FACE` — the same face already appeared in another approved session.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "enum": [ + "NO_FACE_DETECTED", + "LOW_LIVENESS_SCORE", + "LIVENESS_FACE_ATTACK", + "MULTIPLE_FACES_DETECTED", + "FACE_IN_BLOCKLIST", + "POSSIBLE_FACE_IN_BLOCKLIST", + "DUPLICATED_FACE", + "POSSIBLE_DUPLICATED_FACE" + ], + "description": "Machine-readable risk code." + }, + "feature": { + "type": "string", + "enum": [ + "LIVENESS" + ], + "description": "Feature that raised the warning. Always `LIVENESS` on this endpoint." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "`null` for most warnings. Blocklist hits carry `{blocklisted_session_id, blocklisted_session_number, api_service}`; duplicate hits carry `{duplicated_session_id, duplicated_session_number, api_service}` pointing at the matching session." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` warnings drive `status` to `Declined`; `warning` and `information` entries are advisory and never decline on their own." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "face_quality": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Capture-quality score of the detected face, normalized to 0–100. `null` when the model could not produce a reliable value.", + "example": 84.21 + }, + "face_luminance": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Brightness of the detected face, normalized to 0–100. `null` when unavailable.", + "example": 50.33 + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, or `null`." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` object you sent, or `null`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp (UTC) of when the response was generated, e.g. `2026-06-12T01:04:42.763237+00:00`." + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when `user_image` is missing, exceeds 5 MB, has an unsupported extension, fails server-side image decoding, or when an option is out of range. Field errors use DRF's `{field: [messages]}` envelope; image-decoding failures use `{\"error\": ...}`.", + "content": { + "application/json": { + "examples": { + "Missing image": { + "summary": "`user_image` not included in the form data", + "value": { + "user_image": [ + "No file was submitted." + ] + } + }, + "Invalid image format": { + "summary": "Allowed extension but undecodable image content", + "value": { + "error": "Invalid user image format.", + "code": "invalid_image" + } + }, + "Unsupported file extension": { + "summary": "File extension outside tiff/jpg/jpeg/png/webp", + "value": { + "user_image": [ + "File extension “txt” is not allowed. Allowed extensions are: tiff, jpg, jpeg, png, webp." + ] + } + }, + "File too large": { + "summary": "Upload exceeds the 5 MB limit", + "value": { + "user_image": [ + "File size should not exceed 5 MB" + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any image processing happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/passive-liveness/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'user_image=@./selfie.jpg' \\\n -F 'face_liveness_score_decline_threshold=30' \\\n -F 'save_api_request=true' \\\n -F 'vendor_data=user-123'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.passive_liveness.check(\n user_image=open(\"user_image.jpg\", \"rb\"),\n face_liveness_score_decline_threshold=30.0,\n rotate_image=False,\n save_api_request=True,\n vendor_data=\"user-123\",\n metadata={\"flow\": \"withdrawal\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.passiveLiveness.check({\n user_image: await fileFromPath(\"user_image.jpg\"),\n face_liveness_score_decline_threshold: 30.0,\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"withdrawal\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.PassiveLiveness.Check(ctx, didit.PassiveLivenessCheckParams{\n\tUserImage: didit.FileFromPath(\"user_image.jpg\"),\n\tFaceLivenessScoreDeclineThreshold: didit.Float(30.0),\n\tRotateImage: didit.Bool(false),\n\tSaveAPIRequest: didit.Bool(true),\n\tVendorData: didit.String(\"user-123\"),\n\tMetadata: didit.Some(map[string]any{\"flow\": \"withdrawal\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.PassiveLivenessCheckParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.passiveLiveness().check(PassiveLivenessCheckParams.builder()\n .userImage(Path.of(\"user_image.jpg\"))\n .faceLivenessScoreDeclineThreshold(30.0)\n .rotateImage(false)\n .saveApiRequest(true)\n .vendorData(\"user-123\")\n .metadata(Map.of(\"flow\", \"withdrawal\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "passiveLiveness->check([\n 'user_image' => File::fromPath('user_image.jpg'),\n 'face_liveness_score_decline_threshold' => 30.0,\n 'rotate_image' => false,\n 'save_api_request' => true,\n 'vendor_data' => 'user-123',\n 'metadata' => ['flow' => 'withdrawal'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.passive_liveness.check(\n user_image: Pathname(\"user_image.jpg\"),\n face_liveness_score_decline_threshold: 30.0,\n rotate_image: false,\n save_api_request: true,\n vendor_data: \"user-123\",\n metadata: { flow: \"withdrawal\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.PassiveLiveness.CheckAsync(new PassiveLivenessCheckParams\n{\n UserImage = FileUpload.FromPath(\"user_image.jpg\"),\n FaceLivenessScoreDeclineThreshold = 30.0,\n RotateImage = false,\n SaveApiRequest = true,\n VendorData = \"user-123\",\n Metadata = new Dictionary { [\"flow\"] = \"withdrawal\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/database-validation/": { + "post": { + "summary": "Database Validation (standalone)", + "operationId": "post_v3database-validation", + "x-didit-sdk": { + "resource": "database_validation", + "method": "validate", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Validate a person's identity data against official government and registry sources — CPF in Brazil, RENAPER in Argentina, DNI registries in Spain and Peru, INE in Mexico, and 100+ more services across 60+ countries.\n\n**Service selection.** Each country exposes one or more catalog services identified by a `service_id` (e.g. `bra_cpf`, `arg_renaper`, `pan_cedula_sib`). Pin the services you want with the `services` array — if you omit it, exactly **one** default service runs (the longest-established live service for the country). Sending `services` also switches the response to the extended shape with `services_used` and `match_score`. Discover services, their required fields, and prices via `GET /v1/organization/database-validation-countries/` (a catalog endpoint not documented in this spec) or the Business Console workflow editor. Some services are flagged `requires_consent=true` in the catalog and demand `consent=true`; others require onboarding for your organization before they can be called.\n\n**Input fields.** `identification_number` is a universal field that maps to the right country-specific field automatically (see its description). Country format rules are enforced before any provider is called (e.g. BRA CPF must be exactly 11 digits) and format failures are never billed. Biometric services need a `selfie` upload (multipart only); Argentina RENAPER additionally requires `gender`.\n\n**Results.** Each service returns a field-by-field `validation` map plus a vendor-neutral `outcome_code` (`MATCH`, `PARTIAL_MATCH`, `NO_MATCH`, `DOCUMENT_NOT_FOUND`, `INVALID_DOCUMENT_FORMAT`, `INVALID_INPUT`, `MINOR_BLOCKED`, `DECEASED`, `BIOMETRIC_NO_MATCH`, `BIOMETRIC_IMAGE_UNUSABLE`, `INCONCLUSIVE`, `DOCUMENT_SUPERSEDED`, `REGISTRY_UNAVAILABLE`, `REGISTRY_ERROR`). Note the distinctions: `NO_MATCH` is a definitive mismatch, `INCONCLUSIVE` means the registry could not confirm either way (review, not decline), and `BIOMETRIC_IMAGE_UNUSABLE` is a technical selfie problem (retake), unlike the definitive `BIOMETRIC_NO_MATCH`. The overall `match_type` aggregates across services (any full → `full_match`, else any partial → `partial_match`, else `no_match`) and `no_match_action`/`partial_match_action` translate it into the final `status`. `validation_type` is derived: `two_by_two` when two or more distinct services produced a full match (on the identification number, on full name + date of birth together, or on the address without contradictions), else `one_by_one`.\n\n**Persistence.** `save_api_request` defaults to **true**: the result is stored as a session (`request_id` works with `GET /v3/session/{sessionId}/decision/`), appears in the console, and fires a `status.updated` webhook. It also controls the `validations` shape — per-service objects when saved, a merged `{field: match}` map when not.\n\n**Billing.** Per-service: each service that returns a billable result is charged its own catalog price. Failed or skipped services are not billed, and requests rejected with `400` cost nothing. The pre-flight balance check covers the sum of the selected services' prices (`403` when short).\n\n**Failures.** When no selected service returns a usable result, the API returns `400` with `validation_errors` if every unanswered attempt explicitly refused the input (`provider_rejected_input`, `retryable: false`). Correct the input before trying again. Otherwise it returns `502` with `validation_errors` (`empty_provider_response`, `retryable: true`), including mixed input rejection and unavailable fallback attempts; nothing is billed (a session is still recorded with status `Not Finished` when `save_api_request=true`). When only some services fail, the `200` response carries their failures in `database_validation.errors`.\n\n**Sandbox.** Keys from sandbox applications skip the registry calls and billing and return a static `full_match` response — the sandbox payload always includes `services_used` and `match_score: 100` regardless of how `services` was sent, so don't validate the live extended-shape gating or the 0.0–1.0 `match_score` fraction against sandbox responses.\n\nSend the request as `application/json`, or as `multipart/form-data` when uploading a `selfie`.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "issuing_state" + ], + "properties": { + "issuing_state": { + "type": "string", + "description": "ISO 3166-1 **alpha-3** country code of the registry to validate against (e.g. `BRA`, `ESP`, `COL`). Determines which services are available. Unsupported codes return `400` with the full list of valid options.", + "example": "BRA" + }, + "services": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Catalog `service_id`s to run for this country (e.g. `[\"bra_cpf\"]`). Also accepts a single string or a comma-separated/JSON-encoded string. **If omitted or empty, exactly one default service runs** — the longest-established live service for the country; newer, biometric, and pay-as-you-go services must be named explicitly. Sending `services` switches the response to the extended shape (adds `services_used` and `match_score`). Every id must exist and be live for the `issuing_state`; services that require onboarding return `400` until activated for your organization. Discover available services per country via `GET /v1/organization/database-validation-countries/` (a catalog endpoint not documented in this spec) or the Business Console workflow editor.", + "example": [ + "bra_cpf" + ] + }, + "validation_type": { + "type": "string", + "deprecated": true, + "description": "DEPRECATED. Accepted for backward compatibility but ignored — the response `validation_type` is now derived from how many services full-matched. Use `services` to pin specific services." + }, + "consent": { + "type": "boolean", + "default": false, + "description": "Set to `true` when the end user has explicitly consented to the selected validation services. Required for services flagged `requires_consent=true` in the catalog — without it those services return `400` with `services_requiring_consent`." + }, + "identification_number": { + "type": "string", + "description": "Universal identification number — automatically mapped to the correct country-specific field, so you can use it instead of `personal_number`/`tax_number`/`document_number`: ARG→document_number (DNI), BOL→document_number (CI), BRA→tax_number (CPF, 11 digits), CHL→personal_number (RUT), COL→personal_number (Cédula), CRI→personal_number (Cédula), DOM→personal_number (Cédula, 11 digits), ECU→personal_number (Cédula, 10 digits), ESP→personal_number (DNI/NIE), GTM→document_number (DPI), HND→document_number (DNI), MEX→personal_number (CURP, 18 chars), PAN→personal_number (Cédula), PER→personal_number (DNI, 8 digits), PRY→document_number (CI), SLV→document_number (DUI), URY→personal_number (CI), VEN→document_number (Cédula). It never overrides an explicitly provided country-specific field." + }, + "first_name": { + "type": "string", + "description": "The individual's first name. Required by some services/countries.", + "example": "John" + }, + "last_name": { + "type": "string", + "description": "The individual's last name. Required by some services/countries.", + "example": "Doe" + }, + "middle_name": { + "type": "string", + "description": "Middle name, used by some country services (AUS, NZL, …)." + }, + "full_name": { + "type": "string", + "description": "Full name — some services accept this in lieu of first/last name (e.g. CHN services, which match the native-script name)." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth, `YYYY-MM-DD`. Required by many services.", + "example": "1980-01-01" + }, + "personal_number": { + "type": "string", + "description": "Government-issued unique personal identifier. Used by: CHL (RUT), COL (Cédula), CRI, DOM, ECU, ESP (DNI/NIE), MEX (CURP), PAN (Cédula), PER (DNI), URY. Consider `identification_number` instead." + }, + "tax_number": { + "type": "string", + "description": "Tax identification number. Used by: BRA (CPF, 11 digits). Consider `identification_number` instead." + }, + "document_number": { + "type": "string", + "description": "Document number. Used by: ARG (DNI), BOL (CI), GTM (DPI), HND (DNI), PRY (CI), SLV (DUI), VEN (Cédula). Consider `identification_number` instead." + }, + "document_type": { + "type": "string", + "enum": [ + "P", + "DL", + "ID", + "RP", + "SSC", + "HIC", + "WP", + "TC", + "VISA", + "PSC", + "BC", + "OTHER" + ], + "description": "Type of document being validated: `P` passport, `DL` driver license, `ID` national ID, `RP` residence permit (plus `SSC`, `HIC`, `WP`, `TC`, `VISA`, `PSC`, `BC`, `OTHER`). Required by some services (e.g. ESP)." + }, + "expiration_date": { + "type": "string", + "format": "date", + "description": "Document expiration date, `YYYY-MM-DD`. Required for ESP (Spain) validation.", + "example": "2030-01-15" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "description": "Document issue date, `YYYY-MM-DD` (fecha de expedición). Required for COL (Colombia) cédula validation.", + "example": "2015-06-20" + }, + "nationality": { + "type": "string", + "description": "Nationality as ISO 3166-1 alpha-3. Required by some services." + }, + "gender": { + "type": "string", + "enum": [ + "M", + "F", + "X" + ], + "description": "Gender: `M`, `F`, or `X` (other/unknown). Required for Argentina's RENAPER validation (`arg_renaper`)." + }, + "address": { + "description": "Residential address. Prefer the structured object `{\"street_1\":\"123 Main St\",\"street_2\":\"Apt 4B\",\"city\":\"Springfield\",\"region\":\"IL\",\"postal_code\":\"62701\",\"country\":\"US\"}`; a complete single-line string is still accepted. Required by address-verification services; when a service defines address requirements, missing parts return `400` with `address_fields_required_by`.", + "oneOf": [ + { + "type": "object", + "properties": { + "street_1": { + "type": "string" + }, + "street_2": { + "type": "string" + }, + "city": { + "type": "string" + }, + "region": { + "type": "string" + }, + "postal_code": { + "type": "string" + }, + "country": { + "type": "string" + } + } + }, + { + "type": "string", + "description": "Complete single-line address (legacy)." + } + ] + }, + "address_element_1": { + "type": "string", + "description": "Street address including street number and type. If omitted, derived from `address`." + }, + "address_element_2": { + "type": "string", + "description": "Apartment, unit, building, floor, or extra address line. Only send when you have it explicitly." + }, + "address_element_3": { + "type": "string", + "description": "City, suburb, district, locality, or neighborhood. If omitted, derived from `address`." + }, + "address_element_4": { + "type": "string", + "description": "State, province, region, or town. If omitted, derived from `address`." + }, + "address_element_5": { + "type": "string", + "description": "Postcode or postal code. `postal_code` is accepted as an alias." + }, + "postal_code": { + "type": "string", + "description": "Postal code for address-based services (alias of `address_element_5`)." + }, + "driver_license_number": { + "type": "string", + "description": "Driver licence number for government document-verification services (AUS, NZL, IND)." + }, + "driver_license_card_number": { + "type": "string", + "description": "Physical driver licence card number, where required separately from the licence number (AUS)." + }, + "driver_license_state": { + "type": "string", + "description": "Driver licence state or territory of issue (AUS)." + }, + "driver_license_version": { + "type": "string", + "description": "Driver licence version code (NZL)." + }, + "passport_number": { + "type": "string", + "description": "Passport number for government passport-verification services (AUS, NZL, IND, CHN)." + }, + "passport_expiration_date": { + "type": "string", + "format": "date", + "description": "Passport expiry date, `YYYY-MM-DD`, for passport-verification services." + }, + "passport_file_number": { + "type": "string", + "description": "Passport file number (IND)." + }, + "passport_issue_country": { + "type": "string", + "description": "Issuing country of the passport (ISO 3166-1 alpha-3) for passport-verification services." + }, + "medicare_card_number": { + "type": "string", + "description": "Medicare card number (AUS)." + }, + "immi_card_number": { + "type": "string", + "description": "Australian ImmiCard number." + }, + "immi_card_expiry_date": { + "type": "string", + "format": "date", + "description": "Australian ImmiCard expiry date." + }, + "citizenship_certificate_number": { + "type": "string", + "description": "Citizenship certificate number (AUS)." + }, + "birth_registration_number": { + "type": "string", + "description": "Birth-certificate registration number (AUS)." + }, + "birth_registration_date": { + "type": "string", + "format": "date", + "description": "Birth-certificate registration date (AUS)." + }, + "birth_registration_state": { + "type": "string", + "description": "Birth-certificate registration state (AUS)." + }, + "marriage_certificate_number": { + "type": "string", + "description": "Marriage certificate number (AUS)." + }, + "change_of_name_certificate_number": { + "type": "string", + "description": "Change-of-name certificate number (AUS)." + }, + "first_partner_name": { + "type": "string", + "description": "First name of the partner on a marriage certificate (AUS)." + }, + "last_partner_name": { + "type": "string", + "description": "Last name of the partner on a marriage certificate (AUS)." + }, + "voter_id": { + "type": "string", + "description": "Voter registration number (IND EPIC, IRL)." + }, + "epic_card": { + "type": "string", + "description": "India EPIC voter card number." + }, + "pan": { + "type": "string", + "description": "India PAN (Permanent Account Number)." + }, + "national_id": { + "type": "string", + "description": "National ID number (CHN, MYS, KEN, KHM, NGA, ZAF, …)." + }, + "cic": { + "type": "string", + "description": "Mexican INE/IFE Código de Identificación de Credencial (CIC), 9 digits. Used by the MEX INE credential-validity service (`mex_ine_vigencia`)." + }, + "identificador_ciudadano": { + "type": "string", + "description": "Mexican INE Identificador del Ciudadano (9 digits). Used with `cic` for modern INE models (E/F/G/H) in `mex_ine_vigencia`." + }, + "ocr": { + "type": "string", + "description": "Mexican INE OCR number (13 digits, back of card). Used with `cic` for Model D in `mex_ine_vigencia`." + }, + "voter_number": { + "type": "string", + "description": "Mexican INE Clave de Elector (18 chars). Used with `emission_number` for legacy IFE models (A/B/C) in `mex_ine_vigencia`." + }, + "emission_number": { + "type": "string", + "description": "Mexican INE Número de Emisión. Used with `voter_number` for legacy IFE models (A/B/C) in `mex_ine_vigencia`." + }, + "bvn": { + "type": "string", + "description": "Nigerian Bank Verification Number." + }, + "bank_card_number": { + "type": "string", + "description": "Bank card number (CHN bank-card verification)." + }, + "company_registration_number": { + "type": "string", + "description": "Company registration number as issued by the companies register: the CNPJ in Brazil (e.g. `11222333000181`), the registration number in South Africa (e.g. `1936/007721/06`). Required by the company registry and company directors services (`bra_company_registry`, `bra_company_directors`, `zaf_company_registry`, `zaf_company_directors`)." + }, + "company_name": { + "type": "string", + "description": "Registered company name, cross-checked against the register entry. Optional for `zaf_company_registry`." + }, + "initials": { + "type": "string", + "description": "The person's initials as printed on the document. Required by `zaf_drivers_license`; accepted by `zaf_bank_account_holder`." + }, + "licence_number": { + "type": "string", + "description": "Driving licence number as printed on the card, for `zaf_drivers_license`. When omitted, `document_number` is used." + }, + "refugee_file_number": { + "type": "string", + "description": "Refugee file number issued by South Africa's Department of Home Affairs. Required by `zaf_refugee`." + }, + "bank_name": { + "type": "string", + "description": "Name of the bank that holds the account. Required by `zaf_bank_account_holder`." + }, + "bank_account_number": { + "type": "string", + "description": "Bank account number. Required by `zaf_bank_account_holder`; accepted by `zaf_fraud_prevention`." + }, + "account_type": { + "type": "string", + "description": "Bank account type for `zaf_bank_account_holder`: `current`, `savings`, `transmission`, `subscriptionShare` or `notKnown`, case-insensitive. Any other value returns `400`. Omit it when you do not know the type: the account-type cross-check is then reported as not confirmed." + }, + "phone_number": { + "type": "string", + "description": "Contact mobile number, for the services that cross-check contactability (`zaf_bank_account_holder`, `zaf_fraud_prevention`, `zaf_refugee`)." + }, + "right_thumb": { + "type": "string", + "description": "Base64-encoded right thumb fingerprint for `zaf_dha_fingerprint_match`, with `left_thumb` or on its own." + }, + "left_thumb": { + "type": "string", + "description": "Base64-encoded left thumb fingerprint for `zaf_dha_fingerprint_match`, with `right_thumb` or on its own." + }, + "score_level": { + "type": "integer", + "description": "Minimum biometric score level for `zaf_criminal_face_screening`. Defaults to `3`." + }, + "max_matches": { + "type": "integer", + "description": "Maximum number of candidate matches `zaf_criminal_face_screening` returns. Defaults to `1`." + }, + "vehicle_register_number": { + "type": "string", + "description": "Vehicle register number. Required, with `vehicle_licence_plate` and `vin`, by `zaf_vehicle_ownership`." + }, + "vehicle_licence_plate": { + "type": "string", + "description": "Vehicle licence plate number. Required, with `vehicle_register_number` and `vin`, by `zaf_vehicle_ownership`." + }, + "vin": { + "type": "string", + "description": "Vehicle Identification Number (VIN or chassis number). Required, with `vehicle_register_number` and `vehicle_licence_plate`, by `zaf_vehicle_ownership`." + }, + "ssn": { + "type": "string", + "description": "Social Security Number (USA)." + }, + "phone": { + "type": "string", + "description": "Phone number for phone-verification services." + }, + "landline": { + "type": "string", + "description": "Landline number for phone-verification services." + }, + "email": { + "type": "string", + "format": "email", + "description": "Email address for identity-verification services." + }, + "country_of_residence": { + "type": "string", + "description": "ISO 3166-1 alpha-2 country of residence. Used by global identity-enrichment services (e.g. `glb_identity_enrichment`) to focus the lookup.", + "example": "US" + }, + "partial_match_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "NO_ACTION", + "description": "What a `partial_match` does to `database_validation.status`. Defaults to `NO_ACTION` (status stays `Approved`, with a warning)." + }, + "no_match_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What a `no_match` does to `database_validation.status`. Defaults to `DECLINE`." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "Persist the validation as a session (console visibility, decision endpoint, `status.updated` webhook). Also changes the `validations` response shape: per-service objects when `true` (default), a merged field map when `false`." + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for the validated user; echoed back and stored with the session." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON stored with the request and echoed back." + } + } + }, + "example": { + "issuing_state": "BRA", + "services": [ + "bra_cpf" + ], + "identification_number": "12345678900", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-01-01", + "vendor_data": "user-1234" + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "issuing_state" + ], + "properties": { + "issuing_state": { + "type": "string", + "description": "ISO 3166-1 **alpha-3** country code of the registry to validate against (e.g. `BRA`, `ESP`, `COL`). Determines which services are available. Unsupported codes return `400` with the full list of valid options.", + "example": "BRA" + }, + "services": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Catalog `service_id`s to run for this country (e.g. `[\"bra_cpf\"]`). Also accepts a single string or a comma-separated/JSON-encoded string. **If omitted or empty, exactly one default service runs** — the longest-established live service for the country; newer, biometric, and pay-as-you-go services must be named explicitly. Sending `services` switches the response to the extended shape (adds `services_used` and `match_score`). Every id must exist and be live for the `issuing_state`; services that require onboarding return `400` until activated for your organization. Discover available services per country via `GET /v1/organization/database-validation-countries/` (a catalog endpoint not documented in this spec) or the Business Console workflow editor.", + "example": [ + "bra_cpf" + ] + }, + "validation_type": { + "type": "string", + "deprecated": true, + "description": "DEPRECATED. Accepted for backward compatibility but ignored — the response `validation_type` is now derived from how many services full-matched. Use `services` to pin specific services." + }, + "consent": { + "type": "boolean", + "default": false, + "description": "Set to `true` when the end user has explicitly consented to the selected validation services. Required for services flagged `requires_consent=true` in the catalog — without it those services return `400` with `services_requiring_consent`." + }, + "identification_number": { + "type": "string", + "description": "Universal identification number — automatically mapped to the correct country-specific field, so you can use it instead of `personal_number`/`tax_number`/`document_number`: ARG→document_number (DNI), BOL→document_number (CI), BRA→tax_number (CPF, 11 digits), CHL→personal_number (RUT), COL→personal_number (Cédula), CRI→personal_number (Cédula), DOM→personal_number (Cédula, 11 digits), ECU→personal_number (Cédula, 10 digits), ESP→personal_number (DNI/NIE), GTM→document_number (DPI), HND→document_number (DNI), MEX→personal_number (CURP, 18 chars), PAN→personal_number (Cédula), PER→personal_number (DNI, 8 digits), PRY→document_number (CI), SLV→document_number (DUI), URY→personal_number (CI), VEN→document_number (Cédula). It never overrides an explicitly provided country-specific field." + }, + "first_name": { + "type": "string", + "description": "The individual's first name. Required by some services/countries.", + "example": "John" + }, + "last_name": { + "type": "string", + "description": "The individual's last name. Required by some services/countries.", + "example": "Doe" + }, + "middle_name": { + "type": "string", + "description": "Middle name, used by some country services (AUS, NZL, …)." + }, + "full_name": { + "type": "string", + "description": "Full name — some services accept this in lieu of first/last name (e.g. CHN services, which match the native-script name)." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth, `YYYY-MM-DD`. Required by many services.", + "example": "1980-01-01" + }, + "personal_number": { + "type": "string", + "description": "Government-issued unique personal identifier. Used by: CHL (RUT), COL (Cédula), CRI, DOM, ECU, ESP (DNI/NIE), MEX (CURP), PAN (Cédula), PER (DNI), URY. Consider `identification_number` instead." + }, + "tax_number": { + "type": "string", + "description": "Tax identification number. Used by: BRA (CPF, 11 digits). Consider `identification_number` instead." + }, + "document_number": { + "type": "string", + "description": "Document number. Used by: ARG (DNI), BOL (CI), GTM (DPI), HND (DNI), PRY (CI), SLV (DUI), VEN (Cédula). Consider `identification_number` instead." + }, + "document_type": { + "type": "string", + "enum": [ + "P", + "DL", + "ID", + "RP", + "SSC", + "HIC", + "WP", + "TC", + "VISA", + "PSC", + "BC", + "OTHER" + ], + "description": "Type of document being validated: `P` passport, `DL` driver license, `ID` national ID, `RP` residence permit (plus `SSC`, `HIC`, `WP`, `TC`, `VISA`, `PSC`, `BC`, `OTHER`). Required by some services (e.g. ESP)." + }, + "expiration_date": { + "type": "string", + "format": "date", + "description": "Document expiration date, `YYYY-MM-DD`. Required for ESP (Spain) validation.", + "example": "2030-01-15" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "description": "Document issue date, `YYYY-MM-DD` (fecha de expedición). Required for COL (Colombia) cédula validation.", + "example": "2015-06-20" + }, + "nationality": { + "type": "string", + "description": "Nationality as ISO 3166-1 alpha-3. Required by some services." + }, + "gender": { + "type": "string", + "enum": [ + "M", + "F", + "X" + ], + "description": "Gender: `M`, `F`, or `X` (other/unknown). Required for Argentina's RENAPER validation (`arg_renaper`)." + }, + "address": { + "description": "Residential address. Prefer the structured object `{\"street_1\":\"123 Main St\",\"street_2\":\"Apt 4B\",\"city\":\"Springfield\",\"region\":\"IL\",\"postal_code\":\"62701\",\"country\":\"US\"}`; a complete single-line string is still accepted. Required by address-verification services; when a service defines address requirements, missing parts return `400` with `address_fields_required_by`.", + "oneOf": [ + { + "type": "object", + "properties": { + "street_1": { + "type": "string" + }, + "street_2": { + "type": "string" + }, + "city": { + "type": "string" + }, + "region": { + "type": "string" + }, + "postal_code": { + "type": "string" + }, + "country": { + "type": "string" + } + } + }, + { + "type": "string", + "description": "Complete single-line address (legacy)." + } + ] + }, + "address_element_1": { + "type": "string", + "description": "Street address including street number and type. If omitted, derived from `address`." + }, + "address_element_2": { + "type": "string", + "description": "Apartment, unit, building, floor, or extra address line. Only send when you have it explicitly." + }, + "address_element_3": { + "type": "string", + "description": "City, suburb, district, locality, or neighborhood. If omitted, derived from `address`." + }, + "address_element_4": { + "type": "string", + "description": "State, province, region, or town. If omitted, derived from `address`." + }, + "address_element_5": { + "type": "string", + "description": "Postcode or postal code. `postal_code` is accepted as an alias." + }, + "postal_code": { + "type": "string", + "description": "Postal code for address-based services (alias of `address_element_5`)." + }, + "driver_license_number": { + "type": "string", + "description": "Driver licence number for government document-verification services (AUS, NZL, IND)." + }, + "driver_license_card_number": { + "type": "string", + "description": "Physical driver licence card number, where required separately from the licence number (AUS)." + }, + "driver_license_state": { + "type": "string", + "description": "Driver licence state or territory of issue (AUS)." + }, + "driver_license_version": { + "type": "string", + "description": "Driver licence version code (NZL)." + }, + "passport_number": { + "type": "string", + "description": "Passport number for government passport-verification services (AUS, NZL, IND, CHN)." + }, + "passport_expiration_date": { + "type": "string", + "format": "date", + "description": "Passport expiry date, `YYYY-MM-DD`, for passport-verification services." + }, + "passport_file_number": { + "type": "string", + "description": "Passport file number (IND)." + }, + "passport_issue_country": { + "type": "string", + "description": "Issuing country of the passport (ISO 3166-1 alpha-3) for passport-verification services." + }, + "medicare_card_number": { + "type": "string", + "description": "Medicare card number (AUS)." + }, + "immi_card_number": { + "type": "string", + "description": "Australian ImmiCard number." + }, + "immi_card_expiry_date": { + "type": "string", + "format": "date", + "description": "Australian ImmiCard expiry date." + }, + "citizenship_certificate_number": { + "type": "string", + "description": "Citizenship certificate number (AUS)." + }, + "birth_registration_number": { + "type": "string", + "description": "Birth-certificate registration number (AUS)." + }, + "birth_registration_date": { + "type": "string", + "format": "date", + "description": "Birth-certificate registration date (AUS)." + }, + "birth_registration_state": { + "type": "string", + "description": "Birth-certificate registration state (AUS)." + }, + "marriage_certificate_number": { + "type": "string", + "description": "Marriage certificate number (AUS)." + }, + "change_of_name_certificate_number": { + "type": "string", + "description": "Change-of-name certificate number (AUS)." + }, + "first_partner_name": { + "type": "string", + "description": "First name of the partner on a marriage certificate (AUS)." + }, + "last_partner_name": { + "type": "string", + "description": "Last name of the partner on a marriage certificate (AUS)." + }, + "voter_id": { + "type": "string", + "description": "Voter registration number (IND EPIC, IRL)." + }, + "epic_card": { + "type": "string", + "description": "India EPIC voter card number." + }, + "pan": { + "type": "string", + "description": "India PAN (Permanent Account Number)." + }, + "national_id": { + "type": "string", + "description": "National ID number (CHN, MYS, KEN, KHM, NGA, ZAF, …)." + }, + "cic": { + "type": "string", + "description": "Mexican INE/IFE Código de Identificación de Credencial (CIC), 9 digits. Used by the MEX INE credential-validity service (`mex_ine_vigencia`)." + }, + "identificador_ciudadano": { + "type": "string", + "description": "Mexican INE Identificador del Ciudadano (9 digits). Used with `cic` for modern INE models (E/F/G/H) in `mex_ine_vigencia`." + }, + "ocr": { + "type": "string", + "description": "Mexican INE OCR number (13 digits, back of card). Used with `cic` for Model D in `mex_ine_vigencia`." + }, + "voter_number": { + "type": "string", + "description": "Mexican INE Clave de Elector (18 chars). Used with `emission_number` for legacy IFE models (A/B/C) in `mex_ine_vigencia`." + }, + "emission_number": { + "type": "string", + "description": "Mexican INE Número de Emisión. Used with `voter_number` for legacy IFE models (A/B/C) in `mex_ine_vigencia`." + }, + "bvn": { + "type": "string", + "description": "Nigerian Bank Verification Number." + }, + "bank_card_number": { + "type": "string", + "description": "Bank card number (CHN bank-card verification)." + }, + "company_registration_number": { + "type": "string", + "description": "Company registration number as issued by the companies register: the CNPJ in Brazil (e.g. `11222333000181`), the registration number in South Africa (e.g. `1936/007721/06`). Required by the company registry and company directors services (`bra_company_registry`, `bra_company_directors`, `zaf_company_registry`, `zaf_company_directors`)." + }, + "company_name": { + "type": "string", + "description": "Registered company name, cross-checked against the register entry. Optional for `zaf_company_registry`." + }, + "initials": { + "type": "string", + "description": "The person's initials as printed on the document. Required by `zaf_drivers_license`; accepted by `zaf_bank_account_holder`." + }, + "licence_number": { + "type": "string", + "description": "Driving licence number as printed on the card, for `zaf_drivers_license`. When omitted, `document_number` is used." + }, + "refugee_file_number": { + "type": "string", + "description": "Refugee file number issued by South Africa's Department of Home Affairs. Required by `zaf_refugee`." + }, + "bank_name": { + "type": "string", + "description": "Name of the bank that holds the account. Required by `zaf_bank_account_holder`." + }, + "bank_account_number": { + "type": "string", + "description": "Bank account number. Required by `zaf_bank_account_holder`; accepted by `zaf_fraud_prevention`." + }, + "account_type": { + "type": "string", + "description": "Bank account type for `zaf_bank_account_holder`: `current`, `savings`, `transmission`, `subscriptionShare` or `notKnown`, case-insensitive. Any other value returns `400`. Omit it when you do not know the type: the account-type cross-check is then reported as not confirmed." + }, + "phone_number": { + "type": "string", + "description": "Contact mobile number, for the services that cross-check contactability (`zaf_bank_account_holder`, `zaf_fraud_prevention`, `zaf_refugee`)." + }, + "right_thumb": { + "type": "string", + "description": "Base64-encoded right thumb fingerprint for `zaf_dha_fingerprint_match`, with `left_thumb` or on its own." + }, + "left_thumb": { + "type": "string", + "description": "Base64-encoded left thumb fingerprint for `zaf_dha_fingerprint_match`, with `right_thumb` or on its own." + }, + "score_level": { + "type": "integer", + "description": "Minimum biometric score level for `zaf_criminal_face_screening`. Defaults to `3`." + }, + "max_matches": { + "type": "integer", + "description": "Maximum number of candidate matches `zaf_criminal_face_screening` returns. Defaults to `1`." + }, + "vehicle_register_number": { + "type": "string", + "description": "Vehicle register number. Required, with `vehicle_licence_plate` and `vin`, by `zaf_vehicle_ownership`." + }, + "vehicle_licence_plate": { + "type": "string", + "description": "Vehicle licence plate number. Required, with `vehicle_register_number` and `vin`, by `zaf_vehicle_ownership`." + }, + "vin": { + "type": "string", + "description": "Vehicle Identification Number (VIN or chassis number). Required, with `vehicle_register_number` and `vehicle_licence_plate`, by `zaf_vehicle_ownership`." + }, + "ssn": { + "type": "string", + "description": "Social Security Number (USA)." + }, + "phone": { + "type": "string", + "description": "Phone number for phone-verification services." + }, + "landline": { + "type": "string", + "description": "Landline number for phone-verification services." + }, + "email": { + "type": "string", + "format": "email", + "description": "Email address for identity-verification services." + }, + "country_of_residence": { + "type": "string", + "description": "ISO 3166-1 alpha-2 country of residence. Used by global identity-enrichment services (e.g. `glb_identity_enrichment`) to focus the lookup.", + "example": "US" + }, + "partial_match_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "NO_ACTION", + "description": "What a `partial_match` does to `database_validation.status`. Defaults to `NO_ACTION` (status stays `Approved`, with a warning)." + }, + "no_match_action": { + "type": "string", + "enum": [ + "DECLINE", + "NO_ACTION" + ], + "default": "DECLINE", + "description": "What a `no_match` does to `database_validation.status`. Defaults to `DECLINE`." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "Persist the validation as a session (console visibility, decision endpoint, `status.updated` webhook). Also changes the `validations` response shape: per-service objects when `true` (default), a merged field map when `false`." + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for the validated user; echoed back and stored with the session." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON stored with the request and echoed back." + }, + "selfie": { + "type": "string", + "format": "binary", + "description": "Selfie image (`jpg`, `jpeg`, `png`, `webp`; max 2 MB — images are compressed to ~1.5 MB before upload to the registry) for biometric validation services, e.g. Argentina RENAPER (`arg_renaper`) and Panama SIB (`pan_cedula_sib`, `pan_cedula_sib_plus`). Required whenever a selected service lists it as a required field. Only available via `multipart/form-data`." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/database-validation/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"issuing_state\": \"BRA\",\n \"services\": [\"bra_cpf\"],\n \"identification_number\": \"12345678900\",\n \"first_name\": \"John\",\n \"last_name\": \"Doe\",\n \"date_of_birth\": \"1980-01-01\",\n \"vendor_data\": \"user-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.database_validation.validate(\n issuing_state=\"BRA\",\n services=[\"bra_cpf\"],\n identification_number=\"12345678900\",\n first_name=\"John\",\n last_name=\"Doe\",\n date_of_birth=\"1980-01-01\",\n vendor_data=\"user-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.databaseValidation.validate({\n issuing_state: \"BRA\",\n services: [\"bra_cpf\"],\n identification_number: \"12345678900\",\n first_name: \"John\",\n last_name: \"Doe\",\n date_of_birth: \"1980-01-01\",\n vendor_data: \"user-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.DatabaseValidation.Validate(ctx, didit.DatabaseValidationValidateParams{\n\tIssuingState: \"BRA\",\n\tServices: didit.Some([]string{\"bra_cpf\"}),\n\tIdentificationNumber: didit.String(\"12345678900\"),\n\tFirstName: didit.String(\"John\"),\n\tLastName: didit.String(\"Doe\"),\n\tDateOfBirth: didit.String(\"1980-01-01\"),\n\tVendorData: didit.String(\"user-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.DatabaseValidationValidateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.databaseValidation().validate(DatabaseValidationValidateParams.builder()\n .issuingState(\"BRA\")\n .services(List.of(\"bra_cpf\"))\n .identificationNumber(\"12345678900\")\n .firstName(\"John\")\n .lastName(\"Doe\")\n .dateOfBirth(LocalDate.parse(\"1980-01-01\"))\n .vendorData(\"user-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "databaseValidation->validate([\n 'issuing_state' => 'BRA',\n 'services' => ['bra_cpf'],\n 'identification_number' => '12345678900',\n 'first_name' => 'John',\n 'last_name' => 'Doe',\n 'date_of_birth' => '1980-01-01',\n 'vendor_data' => 'user-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.database_validation.validate(\n issuing_state: \"BRA\",\n services: [\"bra_cpf\"],\n identification_number: \"12345678900\",\n first_name: \"John\",\n last_name: \"Doe\",\n date_of_birth: \"1980-01-01\",\n vendor_data: \"user-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.DatabaseValidation.ValidateAsync(new DatabaseValidationValidateParams\n{\n IssuingState = \"BRA\",\n Services = new string[] { \"bra_cpf\" },\n IdentificationNumber = \"12345678900\",\n FirstName = \"John\",\n LastName = \"Doe\",\n DateOfBirth = \"1980-01-01\",\n VendorData = \"user-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Validation completed — at least one selected service returned a usable result. Inspect `database_validation.match_type` for the aggregate outcome, `validations` for the per-service field comparisons and `outcome_code`s, and `errors` (when present) for services that failed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Persisted session id when `save_api_request=true` (usable with `GET /v3/session/{sessionId}/decision/`); otherwise a transient correlation UUID." + }, + "database_validation": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "description": "`Declined` when the overall `match_type` triggers a `DECLINE` action (`no_match_action` defaults to `DECLINE`, `partial_match_action` to `NO_ACTION`); `In Review` when no service produced a usable comparison, for example when the only selected service came back `REGISTRY_UNAVAILABLE` or `INCONCLUSIVE`; `Approved` otherwise. A request that selected several services can be `Approved` on one service's match while another was unavailable, so check `errors` and the per-service `outcome_code`s too." + }, + "issuing_state": { + "type": "string", + "description": "Echo of the ISO 3166-1 alpha-3 country validated against.", + "example": "BRA" + }, + "validation_type": { + "type": "string", + "enum": [ + "one_by_one", + "two_by_two" + ], + "description": "Derived from the results: `two_by_two` when ≥2 distinct services produced a full match (on the identification number, on full name + date of birth together, or on the address without contradictions); `one_by_one` otherwise. (The request field of the same name is deprecated and ignored.)" + }, + "screened_data": { + "type": "object", + "description": "Echo of the input fields that were sent to the selected services (dates normalized to `YYYY-MM-DD`; a `selfie`, when sent, is returned as a pre-signed URL; `consent_obtained: true` is added when consent was given)." + }, + "match_type": { + "type": "string", + "nullable": true, + "enum": [ + "full_match", + "partial_match", + "no_match" + ], + "description": "Overall result aggregated across services with field-level comparisons: any `full_match` → `full_match`; else any `partial_match` → `partial_match`; else `no_match`. Outcome-only services, such as biometric criminal screening, can return `null`; inspect each item in `validations` for its `outcome_code`." + }, + "validations": { + "description": "Shape depends on `save_api_request`. **`true` (default):** an array of per-service objects (`service_id`, `service_name`, `validation`, `outcome_code`, `outcome_detail`, `source_data`). **`false`:** a single flat object merging the field-level results of all services (`{\"full_name\": \"full_match\", ...}`).", + "oneOf": [ + { + "type": "array", + "items": { + "type": "object", + "description": "Per-service validation result (returned when `save_api_request=true`, the default).", + "properties": { + "service_id": { + "type": "string", + "description": "Catalog id of the service that produced this result (e.g. `bra_cpf`).", + "example": "bra_cpf" + }, + "service_name": { + "type": "string", + "description": "Human-readable catalog name of the service.", + "example": "Brazil - CPF status check" + }, + "validation": { + "type": "object", + "description": "Field-by-field comparison between your input and the registry record. Keys are canonical field names (`full_name`, `date_of_birth`, `identification_number`, `address`, …); values are `full_match`, `partial_match`, or `no_match`.", + "additionalProperties": { + "type": "string", + "enum": [ + "full_match", + "partial_match", + "no_match" + ] + } + }, + "outcome_code": { + "type": "string", + "enum": [ + "MATCH", + "PARTIAL_MATCH", + "NO_MATCH", + "DOCUMENT_NOT_FOUND", + "INVALID_DOCUMENT_FORMAT", + "INVALID_INPUT", + "MINOR_BLOCKED", + "DECEASED", + "BIOMETRIC_NO_MATCH", + "BIOMETRIC_IMAGE_UNUSABLE", + "INCONCLUSIVE", + "DOCUMENT_SUPERSEDED", + "REGISTRY_UNAVAILABLE", + "REGISTRY_ERROR" + ], + "description": "Vendor-neutral outcome of the registry lookup. `MATCH`/`PARTIAL_MATCH`/`NO_MATCH` describe the comparison; `DOCUMENT_NOT_FOUND` means the identifier is not in the registry; `INCONCLUSIVE` means the registry could not confirm either way (routed to review — NOT a no-match); `BIOMETRIC_NO_MATCH` is a definitive face mismatch while `BIOMETRIC_IMAGE_UNUSABLE` is a technical image problem (retake the selfie); `DECEASED`/`MINOR_BLOCKED` are registry-policy outcomes; `DOCUMENT_SUPERSEDED` means the identity matched but the edition of the document presented is not the one on file (a newer edition has been issued); `INVALID_DOCUMENT_FORMAT`/`INVALID_INPUT` mean the registry itself rejected the data it was sent; `REGISTRY_UNAVAILABLE`/`REGISTRY_ERROR` indicate upstream failures." + }, + "outcome_detail": { + "type": "string", + "nullable": true, + "description": "Raw upstream status detail backing the outcome code (e.g. an HTTP or registry status code)." + }, + "source_data": { + "type": "object", + "nullable": true, + "description": "Cleaned, normalized view of the registry record using canonical field names (`identification_number`, `first_name`, `last_name`, `date_of_birth`, plus registry-specific flags). Image fields, when present, are returned as pre-signed URLs." + } + } + } + }, + { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "full_match", + "partial_match", + "no_match" + ] + }, + "description": "Merged field-level results (stateless `save_api_request=false` shape)." + } + ] + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature that raised the warning." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk identifier." + }, + "additional_data": { + "type": "object", + "nullable": true, + "description": "Extra context for the warning, when available." + }, + "log_type": { + "type": "string", + "description": "`warning`, `information`, or `error` — how the risk affected the outcome." + }, + "short_description": { + "type": "string", + "description": "One-line human-readable summary." + }, + "long_description": { + "type": "string", + "description": "Full human-readable explanation." + } + } + }, + "description": "`DATABASE_VALIDATION_NO_MATCH` or `DATABASE_VALIDATION_PARTIAL_MATCH` when applicable; empty on a full match." + }, + "errors": { + "type": "array", + "description": "Present only when some (but not all) selected services failed. Each entry carries `service_id`, `code`, `message`, and `retryable`. A service reported here is absent from `validations` and `services_used`, and is not billed. This array is the only place it appears, so inspect it even when `status` is `Approved`.", + "items": { + "type": "object", + "properties": { + "service_id": { + "type": "string" + }, + "code": { + "type": "string", + "example": "empty_provider_response" + }, + "message": { + "type": "string" + }, + "retryable": { + "type": "boolean", + "description": "`true` when the service produced no answer and retrying it later can succeed; `false` when the source refused the data it was sent and the input must be corrected first.", + "example": true + } + } + } + }, + "services_used": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Catalog ids of the services that returned a billable result. **Only present when the request explicitly sent `services`.**" + }, + "match_score": { + "type": "number", + "description": "Fraction (0.0–1.0) of attempted services that produced a full match (on the identification number, on full name + date of birth together, or on the address without contradictions). **Only present when the request explicitly sent `services`.**" + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Full match (Approved)": { + "summary": "Default save_api_request=true — per-service `validations` objects", + "value": { + "request_id": "7f9a1c0e-1f2b-4f6e-9d3a-2b1c0e7f9a1c", + "database_validation": { + "status": "Approved", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "tax_number": "12345678900", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-01-01" + }, + "match_type": "full_match", + "validations": [ + { + "validation": { + "full_name": "full_match", + "date_of_birth": "full_match", + "identification_number": "full_match" + }, + "outcome_code": "MATCH", + "outcome_detail": "200", + "service_id": "bra_cpf", + "service_name": "Brazil - CPF status check", + "source_data": { + "identification_number": "12345678900", + "first_name": "JOHN", + "last_name": "DOE", + "date_of_birth": "1980-01-01", + "lgpd_minor": false, + "minor_under_16": false, + "minor_under_18": false + } + } + ], + "warnings": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-11T10:30:00.000000+00:00" + } + }, + "Explicit services (extended shape)": { + "summary": "Request sent `services` — response adds `services_used` and `match_score`", + "value": { + "request_id": "b3e7c1a2-9d4f-4f0b-8a6c-5e2d1f0a9b3e", + "database_validation": { + "status": "Approved", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "tax_number": "12345678900", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-01-01" + }, + "match_type": "full_match", + "validations": [ + { + "validation": { + "full_name": "full_match", + "date_of_birth": "full_match", + "identification_number": "full_match" + }, + "outcome_code": "MATCH", + "outcome_detail": "200", + "service_id": "bra_cpf", + "service_name": "Brazil - CPF status check", + "source_data": { + "identification_number": "12345678900", + "first_name": "JOHN", + "last_name": "DOE", + "date_of_birth": "1980-01-01", + "lgpd_minor": false, + "minor_under_16": false, + "minor_under_18": false + } + } + ], + "warnings": [], + "services_used": [ + "bra_cpf" + ], + "match_score": 1.0 + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-11T10:30:00.000000+00:00" + } + }, + "No match (Declined)": { + "summary": "Registry record does not match — default `no_match_action=DECLINE`", + "value": { + "request_id": "0a1b2c3d-4e5f-6789-abcd-ef0123456789", + "database_validation": { + "status": "Declined", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "tax_number": "12345678900", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-01-01" + }, + "match_type": "no_match", + "validations": [ + { + "validation": { + "full_name": "no_match", + "date_of_birth": "no_match", + "identification_number": "no_match" + }, + "outcome_code": "NO_MATCH", + "outcome_detail": "200", + "service_id": "bra_cpf", + "service_name": "Brazil - CPF status check", + "source_data": { + "identification_number": "00987654321", + "first_name": "TOTALLY", + "last_name": "DIFFERENT NAME", + "date_of_birth": "1999-01-01", + "lgpd_minor": false, + "minor_under_16": false, + "minor_under_18": false + } + } + ], + "warnings": [ + { + "feature": "DATABASE_VALIDATION", + "risk": "DATABASE_VALIDATION_NO_MATCH", + "additional_data": null, + "log_type": "error", + "short_description": "Database validation no match", + "long_description": "The system identified a no match in the database validation, requiring further investigation." + } + ] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-11T10:31:00.000000+00:00" + } + }, + "Stateless (save_api_request=false)": { + "summary": "`validations` collapses to a merged field map; nothing is stored", + "value": { + "request_id": "c4d5e6f7-8901-2345-6789-abcdef012345", + "database_validation": { + "status": "Approved", + "issuing_state": "BRA", + "validation_type": "one_by_one", + "screened_data": { + "tax_number": "12345678900", + "first_name": "John", + "last_name": "Doe", + "date_of_birth": "1980-01-01" + }, + "match_type": "full_match", + "validations": { + "full_name": "full_match", + "date_of_birth": "full_match", + "identification_number": "full_match" + }, + "warnings": [] + }, + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-11T10:32:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error - nothing is billed. Preflight field errors occur before any external call. If every unanswered attempt explicitly refuses the input, the response instead includes `validation_errors` with `code: provider_rejected_input` and `retryable: false`; correct the input before retrying. Field-level problems use DRF's standard envelope; catalog problems return a `services` array (plus `requires_onboarding` or `services_requiring_consent` when relevant); missing-field errors include `fields_required_by` mapping each missing field to the services that demand it; services that need at least one extra identifier (e.g. `usa_states_credit_bureau`, which cannot verify a name alone) return `any_field_required_by` mapping each service to the fields of which at least one must be present; format problems checked per service return `invalid_fields_by_service`.", + "content": { + "application/json": { + "examples": { + "Invalid issuing_state": { + "summary": "Country not supported — message lists every valid alpha-3 code", + "value": { + "error": [ + "Invalid issuing state. Valid options are: ARE, ARG, AUS, AUT, BEL, BGD, BOL, BRA, CAN, CHE, CHL, CHN, CIV, COL, CRI, CZE, DEU, DNK, DOM, ECU, ESP, FIN, FRA, GBR, GHA, GLB, GRC, GTM, HKG, HND, IDN, IND, IRL, ITA, KEN, KHM, MAR, MEX, MYS, NGA, NLD, NOR, NZL, OMN, PAN, PER, PHL, POL, PRT, PRY, QAT, SGP, SLV, SVK, SWE, THA, UGA, URY, USA, VEN, ZAF, ZMB, ZWE" + ] + } + }, + "Missing required fields": { + "summary": "Union of required fields across selected services, with the universal-field hint", + "value": { + "error": [ + "Missing required fields: tax_number (or use 'identification_number' which maps to 'tax_number' for BRA)" + ], + "fields_required_by": { + "tax_number": [ + "bra_cpf" + ] + } + } + }, + "Unknown service id": { + "summary": "`services` contains an id that is not in the catalog", + "value": { + "services": [ + "Unknown service_id(s): ['bogus_service']" + ] + } + }, + "Service not live for country": { + "summary": "`services` names a service from another country (or a stub entry)", + "value": { + "services": [ + "Services not currently live for PER: ['bra_cpf']. Available services: ['per_dni', 'per_residential', 'per_tax_registration']" + ] + } + }, + "Service requires onboarding": { + "summary": "Service exists but is not activated for your organization yet", + "value": { + "services": [ + "Services require onboarding before they can be enabled for AUS: ['aus_australia_driver_licence']. Contact Didit support to activate them." + ], + "requires_onboarding": [ + "aus_australia_driver_licence" + ] + } + }, + "Consent missing": { + "summary": "A selected service is flagged requires_consent=true and `consent` was not `true`", + "value": { + "consent": [ + "Explicit end-user consent is required for these database validation services: ['chn_national_id']. Send `consent=true`." + ], + "services_requiring_consent": [ + "chn_national_id" + ] + } + }, + "Country format rule violation": { + "summary": "Identifier fails the country's format rule (length/digits/regex)", + "value": { + "tax_number": [ + "Invalid tax number for BRA: Brazilian CPF (exactly 11 digits). Must be exactly 11 characters long (got 10)." + ] + } + }, + "Service-specific format violation": { + "summary": "A field fails the selected service's own format requirements", + "value": { + "error": [ + "One or more fields do not match the format required by the selected service." + ], + "invalid_fields_by_service": { + "chn_national_id": [ + "national_id_format" + ] + } + } + }, + "Input refused by every attempt": { + "summary": "Correct the input before retrying", + "value": { + "error": "The source refused to run this query with the supplied input.", + "validation_errors": [ + { + "service_id": "bra_cpf", + "code": "provider_rejected_input", + "message": "The source refused to run this query with the supplied input.", + "retryable": false + } + ] + } + } + }, + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "error": { + "description": "Request-level explanation or preflight field errors.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "validation_errors": { + "type": "array", + "description": "Present when every unanswered attempt refused the input after preflight.", + "items": { + "type": "object", + "properties": { + "service_id": { + "type": "string" + }, + "code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retryable": { + "type": "boolean", + "description": "Whether retrying later can resolve this service failure. False means correct the supplied input first." + } + } + } + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "502": { + "description": "No selected service returned a usable result. Inspect each validation_errors entry before retrying; HTTP 502 alone does not establish a transient failure. An empty_provider_response with retryable: true indicates unavailable or unexplained results, including a refused primary followed by an unavailable fallback. Legacy provider_invalid_input and provider_validation_error entries instead require corrected input and may omit retryable. Nothing is billed. When save_api_request=true, the failed attempt is recorded as a session with status Not Finished. Partial failures return 200 with database_validation.errors.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "validation_errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "service_id": { + "type": "string" + }, + "code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "retryable": { + "type": "boolean", + "description": "Whether retrying later can resolve this service failure. False means correct the supplied input first." + } + } + } + } + } + }, + "examples": { + "Provider returned no result": { + "summary": "Every selected service failed", + "value": { + "error": "Database validation could not be performed. The external validation service did not return a result. Please try again later.", + "validation_errors": [ + { + "service_id": "bra_cpf", + "code": "empty_provider_response", + "message": "The provider did not return a database validation result.", + "retryable": true + } + ] + } + } + } + } + } + } + } + } + }, + "/v3/phone/send/": { + "post": { + "summary": "Send Phone Code", + "description": "Send a one-time passcode (OTP) to a phone number over WhatsApp, SMS, or another supported channel, then verify it with [`POST /v3/phone/check/`](/standalone-apis/phone-check).\n\n**How send and check pair up.** Verification state is keyed by your application plus the E.164 `phone_number` — the check call does not take `request_id`. Each new send creates a pending verification that lives for **5 minutes**: call the check with the same number and the code the user received before the window closes. Calling send again for the same number while a verification is pending re-sends a code for that same verification (`status: \"Retry\"`, same `request_id`). At most one retry is attached this way (two sends total); a further send starts a fresh verification with a new `request_id`. The 5-minute window is measured from the first send and is not extended by retries.\n\n**Delivery channels.** `options.preferred_channel` selects the channel (`whatsapp` by default; `sms`, `telegram`, `voice`, `rcs`, `viber`, and `zalo` are also supported). When the preferred channel is unavailable for the destination country or number, delivery automatically falls back to SMS — the send still reports `Success`, and the channel actually used is reported by the check response (`phone.verification_method` and the `lifecycle` events).\n\n**Branding.** SMS codes carry your application's SMS sender name, set in the Business Console, at no extra cost. Pass `options.sms_sender_name` to sign one code with another name, or `options.use_white_label_customization: true` to fall back to your white-label public name when no sender name is set. WhatsApp codes use a fixed message; connect your own WhatsApp Business number in the Business Console to send them from your business name and number, billed at the Didit fee only.\n\n**Send statuses.** `Success` — OTP sent for a new verification; `Retry` — OTP re-sent for the pending verification; `Blocked` — the anti-fraud layer refused to send (see `reason`, e.g. `repeated_attempts`, `suspicious`, `spam`). A `Blocked` send immediately finalizes the verification as `Declined` with a high-risk warning, so a follow-up check returns `Expired or Not Found`.\n\n**Billing.** The price depends on the destination country and the channel that actually delivers the message — see [Phone Verification Pricing](/getting-started/phone-verification-pricing). Only `Blocked` sends are guaranteed free: any other unbilled send attempt is charged at verification finalization or expiration, even if delivery was never confirmed or the delivery provider reported the message undeliverable. Checks are always free. As an anti-abuse measure, phone verification is disabled until your organization's first top-up (`403`).\n\n**Session persistence.** Every new verification is persisted as an API-type session: `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/`, the verification appears in the Business Console, and `status.updated` webhooks fire as it progresses.\n\n**Sandbox.** Sandbox API keys skip delivery and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Success` payload with a random `request_id`; no message is sent and nothing is persisted. Use code `123456` on the sandbox check.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limits.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints, plus an anti-abuse cap of 4 send attempts per phone number per hour; exceeding either returns `429`.", + "operationId": "post_v3phone_send", + "x-didit-sdk": { + "resource": "phone", + "method": "send_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/phone/send/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"phone_number\": \"+14155552671\",\n \"options\": {\n \"code_size\": 6,\n \"preferred_channel\": \"whatsapp\",\n \"locale\": \"en-US\"\n },\n \"vendor_data\": \"user-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.phone.send_code(\n phone_number=\"+14155552671\",\n options={\n \"locale\": \"en-US\",\n \"preferred_channel\": \"whatsapp\",\n },\n vendor_data=\"user-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.phone.sendCode({\n phone_number: \"+14155552671\",\n options: {\n locale: \"en-US\",\n preferred_channel: \"whatsapp\",\n },\n vendor_data: \"user-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Phone.SendCode(ctx, didit.PhoneSendCodeParams{\n\tPhoneNumber: \"+14155552671\",\n\tOptions: didit.Some(didit.PhoneSendCodeParamsOptions{\n\t\tLocale: didit.String(\"en-US\"),\n\t\tPreferredChannel: didit.Some(didit.PhoneSendCodeParamsOptionsPreferredChannel(\"whatsapp\")),\n\t}),\n\tVendorData: didit.String(\"user-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.PhoneSendCodeParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.phone().sendCode(PhoneSendCodeParams.builder()\n .phoneNumber(\"+14155552671\")\n .options(PhoneSendCodeParams.Options.builder()\n .locale(\"en-US\")\n .preferredChannel(PhoneSendCodeParams.Options.PreferredChannel.of(\"whatsapp\"))\n .build())\n .vendorData(\"user-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "phone->sendCode([\n 'phone_number' => '+14155552671',\n 'options' => [\n 'locale' => 'en-US',\n 'preferred_channel' => 'whatsapp',\n ],\n 'vendor_data' => 'user-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.phone.send_code(\n phone_number: \"+14155552671\",\n options: {\n locale: \"en-US\",\n preferred_channel: \"whatsapp\",\n },\n vendor_data: \"user-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Phone.SendCodeAsync(new PhoneSendCodeParams\n{\n PhoneNumber = \"+14155552671\",\n Options = new PhoneSendCodeParamsOptions\n {\n Locale = \"en-US\",\n PreferredChannel = new PhoneSendCodeParamsOptionsPreferredChannel(\"whatsapp\"),\n },\n VendorData = \"user-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "phone_number": { + "type": "string", + "maxLength": 20, + "description": "Recipient phone number in E.164 format — leading `+` and country code (e.g., `+14155552671`). Numbers are validated and normalized to E.164; unparseable or invalid numbers return `400` with a `phone_number` field error.", + "example": "+14155552671" + }, + "options": { + "type": "object", + "description": "OTP delivery options. All fields are optional.", + "properties": { + "code_size": { + "type": "integer", + "minimum": 4, + "maximum": 8, + "default": 6, + "description": "Number of digits in the OTP." + }, + "locale": { + "type": "string", + "maxLength": 5, + "pattern": "^[a-z]{2,3}(-[A-Z]{2,3})?$", + "description": "BCP-47 locale used to localize the verification message (e.g., `en-US`, `fr`, `es`). Invalid formats return `400`.", + "example": "en-US" + }, + "preferred_channel": { + "type": "string", + "enum": [ + "whatsapp", + "sms", + "telegram", + "voice", + "rcs", + "viber", + "zalo" + ], + "default": "whatsapp", + "description": "Preferred delivery channel. If the channel is unavailable for the destination country or number, the message automatically falls back to SMS — the send still reports `Success`." + }, + "use_white_label_customization": { + "type": "boolean", + "default": false, + "description": "When `true`, the send is white-labelled: if your application has no SMS sender name, the SMS names your white-label public name instead (\"123456 is your verification code for Acme Bank\"). Your SMS sender name, when set, is used with or without this flag. Applies to SMS messages, including an SMS fallback from another channel; WhatsApp codes are sent with a fixed message and are not branded. Defaults to `false`." + }, + "sms_sender_name": { + "type": "string", + "nullable": true, + "maxLength": 30, + "example": "Acme Bank", + "description": "The name this code's SMS carries (\"123456 is your verification code for Acme Bank\"), instead of your application's SMS sender name. Free. 1 to 30 characters: letters, digits, spaces and ' & - ! only, not only digits, with single spaces between words; any other value returns `400` and no code is sent. Omitted, `null` or blank, your application's SMS sender name is used. Each application can start using up to two new names per day (UTC); past that, the code is sent with your application's name. Applies to SMS messages, including an SMS fallback from another channel; WhatsApp codes are not branded.", + "pattern": "^$|^(?![\\d ]+$)[\\p{L}\\p{N}'&!-]+( [\\p{L}\\p{N}'&!-]+)*$" + } + } + }, + "signals": { + "type": "object", + "description": "Optional device and network signals about the end user, forwarded to the anti-fraud layer to improve detection of abusive or automated traffic. All fields are optional.", + "properties": { + "ip": { + "type": "string", + "description": "IP address of the end user's device. IPv4 or IPv6.", + "example": "192.0.2.1" + }, + "device_id": { + "type": "string", + "maxLength": 255, + "description": "Unique identifier of the end user's device. Android: `ANDROID_ID`; iOS: `identifierForVendor`.", + "example": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2" + }, + "device_platform": { + "type": "string", + "enum": [ + "android", + "ios", + "ipados", + "tvos", + "web" + ], + "description": "Platform of the end user's device." + }, + "device_model": { + "type": "string", + "maxLength": 255, + "description": "Model of the end user's device.", + "example": "iPhone17,2" + }, + "os_version": { + "type": "string", + "maxLength": 64, + "description": "Operating-system version of the end user's device.", + "example": "18.0.1" + }, + "app_version": { + "type": "string", + "maxLength": 64, + "description": "Version of your application.", + "example": "1.2.34" + }, + "user_agent": { + "type": "string", + "maxLength": 512, + "description": "User agent of the end user's browser or app.", + "example": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1" + } + } + }, + "vendor_data": { + "type": "string", + "description": "Optional caller-controlled identifier (your internal user id, an email, a UUID, etc.) persisted on the session and echoed back in the send response, the matching check response, webhooks, and the Business Console. Use it to correlate Didit's `request_id` with your user record." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Optional free-form JSON object persisted on the session and echoed back in the send response, the matching check response, webhooks, and the Business Console." + } + }, + "required": [ + "phone_number" + ] + }, + "examples": { + "WhatsApp (default)": { + "summary": "6-digit OTP via WhatsApp, falling back to SMS automatically", + "value": { + "phone_number": "+14155552671", + "options": { + "preferred_channel": "whatsapp", + "locale": "en-US" + }, + "vendor_data": "user-1234" + } + }, + "SMS with custom code length": { + "summary": "Force SMS delivery with a 4-digit code", + "value": { + "phone_number": "+34699999999", + "options": { + "preferred_channel": "sms", + "code_size": 4, + "locale": "es" + } + } + }, + "With fraud signals": { + "summary": "Forward device signals to strengthen anti-fraud screening", + "value": { + "phone_number": "+14155552671", + "signals": { + "ip": "192.0.2.1", + "device_id": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2", + "device_platform": "ios", + "device_model": "iPhone17,2", + "os_version": "18.0.1", + "app_version": "1.2.34" + }, + "vendor_data": "user-1234" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Send acknowledged. Inspect `status`: `Success` and `Retry` mean a code is on its way; `Blocked` means the anti-fraud layer refused and the verification is already finalized as `Declined`. `request_id` is the persisted session id (same id on a `Retry`).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PhoneVerificationSendResponse" + }, + "examples": { + "Success": { + "summary": "OTP sent for a new verification", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Success", + "reason": null, + "vendor_data": "user-1234", + "metadata": null + } + }, + "Retry": { + "summary": "Second send for the same number inside the 5-minute window — same request_id", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Retry", + "reason": null, + "vendor_data": "user-1234", + "metadata": null + } + }, + "Blocked": { + "summary": "Anti-fraud refusal — verification finalized as Declined, nothing billed", + "value": { + "request_id": "0d8af1f4-0c43-46d7-b75c-6e4e22c0a7b3", + "status": "Blocked", + "reason": "repeated_attempts", + "vendor_data": "user-1234", + "metadata": null + } + } + } + } + } + }, + "400": { + "description": "Validation error. Malformed input returns DRF's field-error envelope (one array of messages per offending field, nested under `options` for option errors). A number that parses but is rejected at send time (unreachable, or an ineligible line type such as a short code) returns a `{\"detail\": ...}` envelope instead.", + "content": { + "application/json": { + "examples": { + "Invalid phone number (field error)": { + "summary": "Number is not valid E.164", + "value": { + "phone_number": [ + "Invalid phone number provided." + ] + } + }, + "Missing phone number": { + "summary": "`phone_number` not included in the body", + "value": { + "phone_number": [ + "This field is required." + ] + } + }, + "Invalid options": { + "summary": "Out-of-range `code_size`, malformed `locale`, unknown `preferred_channel`", + "value": { + "options": { + "code_size": [ + "Ensure this value is less than or equal to 8." + ], + "locale": [ + "Ensure this field has no more than 5 characters." + ], + "preferred_channel": [ + "\"carrier_pigeon\" is not a valid choice." + ] + } + } + }, + "Number rejected at send time": { + "summary": "Valid E.164 shape but the number cannot receive an OTP", + "value": { + "detail": "Invalid phone number provided." + } + }, + "Ineligible line type": { + "summary": "Line type cannot receive an OTP (e.g., short code)", + "value": { + "detail": "Invalid phone line type provided." + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing (`{\"detail\": ...}`); a wrong, revoked or expired key returns `401`. Also returned before any send when the organization has never topped up (anti-abuse gate) or when its balance cannot cover the destination's send price (`{\"error\": ...}`).", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Top-up required": { + "summary": "Phone verification stays disabled until the organization's first top-up", + "value": { + "detail": "To protect against abuse, phone verification is disabled until your first top-up." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the send price", + "value": { + "error": "You don't have enough credits to perform this request." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). Additionally, an anti-abuse cap of 4 verification attempts per phone number per hour applies upstream. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers. Note: the `X-RateLimit-*`/`Retry-After` headers accompany only the 300/min write-limit 429; the per-number cap 429 carries no rate-limit headers.", + "content": { + "application/json": { + "examples": { + "Per-number cap": { + "summary": "More than 4 verification attempts for the same number in the last hour", + "value": { + "detail": "Maximum verification attempts reached for this phone number. Only 4 authentication attempts are allowed per hour. Try again later or use a different number." + } + }, + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "502": { + "description": "The phone verification provider is temporarily unavailable - a 5xx response, timeout, or connection failure that survived the built-in retries. Safe to retry; nothing is billed and no attempt is consumed.", + "content": { + "application/json": { + "examples": { + "Provider outage": { + "summary": "The provider did not answer the send request", + "value": { + "detail": "Phone verification is temporarily unavailable. Please try again shortly.", + "code": "phone_provider_unavailable" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "Unexpected failure while creating the verification that is not a known provider outage. Safe to retry.", + "content": { + "application/json": { + "examples": { + "Provider error": { + "summary": "OTP delivery could not be initiated", + "value": { + "detail": "Error creating phone verification" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/phone/check/": { + "post": { + "summary": "Check Phone Code", + "description": "Verify the OTP delivered by [`POST /v3/phone/send/`](/standalone-apis/phone-send) and get the final verification result plus phone intelligence: carrier name and line type, virtual (VoIP) and disposable flags, and duplicate usage of the number across your sessions.\n\n**How the check finds the verification.** Matching is by your application plus the E.164 `phone_number` — `request_id` is not an input. The most recent pending verification created within the last **5 minutes** is checked. If there is none (never sent, already finalized, or older than 5 minutes) the endpoint returns `200` with `status: \"Expired or Not Found\"`.\n\n**Attempt budget.** Each verification allows **3 code attempts**. The first two wrong codes return `status: \"Failed\"` with the attempts remaining and `phone: null`; the third wrong code finalizes the verification as `Declined` with a `VERIFICATION_CODE_ATTEMPTS_EXCEEDED` warning and returns the full phone report.\n\n**Outcomes.** `Approved` — correct code and no declining risk. `Declined` — terminal: the code was correct but a declining risk matched (a `DECLINE` action below, a blocklisted or high-risk number), or the attempt budget was exhausted. `Failed` — wrong code, attempts remaining. `Expired or Not Found` — nothing to check. On `Approved`/`Declined` the `request_id` equals the send's `request_id` (the session id) and `phone` carries the full report (`carrier`, `is_virtual`, `is_disposable`, `warnings`, `lifecycle`, `matches`); on `Failed` and `Expired or Not Found` the `request_id` is a one-off random UUID.\n\n**Risk actions.** `duplicated_phone_number_action`, `disposable_number_action`, and `voip_number_action` decide what happens when the corresponding risk is detected on a correct code: `DECLINE` flips the final status to `Declined`; `NO_ACTION` (default) records the risk in `phone.warnings` without affecting the status.\n\n**Billing.** Checks are free — delivery is billed on the send side (see [Phone Verification Pricing](/getting-started/phone-verification-pricing)).\n\n**Session persistence.** A finalized check updates the session created by the send (visible in the Business Console, queryable via `GET /v3/session/{sessionId}/decision/`) and fires a `status.updated` webhook.\n\n**Sandbox.** Sandbox API keys skip all processing: any well-formed code (4–8 digits, `123456` included) returns a static `Approved` payload with a simplified flat `phone` object (`status`, `phone_number`, `country_code`, `carrier_name`, `line_type`, `flags`); malformed input still returns `400`. Nothing is persisted.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limits.** Shared write budget of 300 requests/min per API key, plus the upstream anti-abuse cap of 4 attempts per number per hour; exceeding either returns `429`.\n\n**Social footprint.** Set `enable_social` to `true` to add a `phone_social` block: the online platforms this number is registered on, with per-category counts and any profile details available. `profiles_registered: 0` means the platforms were checked and the number was found on none of them - a valid, billable answer. The block is absent when the add-on was not requested or the check could not be completed, and it is not billed in that case. Billing adds one `phone_social` unit per request on top of this endpoint's own price.", + "operationId": "post_v3phone_check", + "x-didit-sdk": { + "resource": "phone", + "method": "check_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/phone/check/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"phone_number\": \"+14155552671\",\n \"code\": \"123456\",\n \"voip_number_action\": \"DECLINE\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.phone.check_code(\n phone_number=\"+14155552671\",\n code=\"123456\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.phone.checkCode({\n phone_number: \"+14155552671\",\n code: \"123456\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Phone.CheckCode(ctx, didit.PhoneCheckCodeParams{\n\tPhoneNumber: \"+14155552671\",\n\tCode: \"123456\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.PhoneCheckCodeParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.phone().checkCode(PhoneCheckCodeParams.builder()\n .phoneNumber(\"+14155552671\")\n .code(\"123456\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "phone->checkCode([\n 'phone_number' => '+14155552671',\n 'code' => '123456',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.phone.check_code(\n phone_number: \"+14155552671\",\n code: \"123456\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Phone.CheckCodeAsync(new PhoneCheckCodeParams\n{\n PhoneNumber = \"+14155552671\",\n Code = \"123456\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "phone_number": { + "type": "string", + "maxLength": 20, + "description": "The same phone number used in the matching [`POST /v3/phone/send/`](/standalone-apis/phone-send) call, in E.164 format. This is what links the check to the send.", + "example": "+14155552671" + }, + "code": { + "type": "string", + "minLength": 4, + "maxLength": 8, + "pattern": "^[0-9]{4,8}$", + "description": "The OTP the end user received. Must be 4–8 numeric digits — anything else returns `400` without consuming an attempt. A well-formed but wrong code returns `200` with `status: \"Failed\"` and consumes one of the 3 attempts.", + "example": "123456" + }, + "duplicated_phone_number_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the same number was already used by a different user (different `vendor_data`) of your application. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `phone.warnings` and fills `phone.matches`." + }, + "disposable_number_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the carrier lookup flags the number as a temporary/burner line (`phone.is_disposable`). `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `phone.warnings`." + }, + "voip_number_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the carrier lookup reports a virtual line type — `voip`, `isp`, or `vpn` (`phone.is_virtual`). `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `phone.warnings`." + }, + "enable_enrichment": { + "type": "boolean", + "default": false, + "description": "When `true`, also run phone intelligence on the number: a risk score, the line type, whether it was recently ported and a trust index. Billed as one `phone_intelligence_api` unit per request ($0.05 at list price) in addition to this endpoint's own price. The request returns `403` when your balance cannot cover it.", + "example": false + }, + "phone_intelligence_score_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the phone intelligence risk score is above the threshold. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "phone_type_risk_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the number is a high-risk line type. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "recent_port_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the number was ported to another carrier recently. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "low_phone_trust_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the phone intelligence trust index is at or below the threshold. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "enable_social": { + "type": "boolean", + "default": false, + "description": "When true, also look up the social footprint of the number - the messaging, social, e-commerce and professional platforms it is registered on. Billed as one `phone_social` unit per request in addition to this endpoint's own price.", + "example": true + }, + "phone_no_social_presence_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the social footprint check finds the number registered on none of the platforms it covers. Only NO_ACTION and DECLINE are accepted on this endpoint; REVIEW is available on the workflow configuration key of the same name." + } + }, + "required": [ + "phone_number", + "code" + ] + }, + "examples": { + "Default": { + "summary": "Record any risks but never auto-decline", + "value": { + "phone_number": "+14155552671", + "code": "123456" + } + }, + "Strict risk policy": { + "summary": "Auto-decline VoIP and disposable lines", + "value": { + "phone_number": "+14155552671", + "code": "123456", + "voip_number_action": "DECLINE", + "disposable_number_action": "DECLINE" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Check completed — wrong codes and missing verifications also return `200`; inspect `status`, not the HTTP code. `phone` is populated only on finalized outcomes (`Approved`/`Declined`), `null` on `Failed`, and absent on `Expired or Not Found`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PhoneVerificationCheckResponse" + }, + "examples": { + "Approved": { + "summary": "Correct code — full phone report with carrier, lifecycle, and matches", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Approved", + "message": "The verification code is correct.", + "phone": { + "status": "Approved", + "phone_number_prefix": "+1", + "phone_number": "4155552671", + "full_number": "+14155552671", + "country_code": "US", + "country_name": "United States", + "carrier": { + "name": "AT&T", + "type": "mobile" + }, + "is_disposable": false, + "is_virtual": false, + "verification_method": "whatsapp", + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [], + "lifecycle": [ + { + "type": "PHONE_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null, + "channel": "whatsapp", + "actual_channel": "whatsapp" + }, + "fee": 0.0509 + }, + { + "type": "PHONE_DELIVERY_DELIVERED", + "timestamp": "2026-06-12T01:23:43.118220+00:00", + "details": { + "channel": "whatsapp", + "status": "delivered" + }, + "fee": 0 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "PHONE_VERIFICATION_APPROVED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": null, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Failed (attempts remaining)": { + "summary": "Wrong code — the user can retry; request_id here is a one-off UUID", + "value": { + "request_id": "11111111-2222-3333-4444-555555555555", + "status": "Failed", + "message": "The verification code is incorrect. Attempts remaining: 2", + "phone": null, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:21.512340+00:00" + } + }, + "Declined (max attempts)": { + "summary": "Third wrong code — verification finalized as Declined", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Declined", + "message": "The verification code is incorrect. Maximum attempts reached.", + "phone": { + "status": "Declined", + "phone_number_prefix": "+1", + "phone_number": "4155552671", + "full_number": "+14155552671", + "country_code": "US", + "country_name": "United States", + "carrier": { + "name": "AT&T", + "type": "mobile" + }, + "is_disposable": false, + "is_virtual": false, + "verification_method": "whatsapp", + "verification_attempts": 1, + "verified_at": null, + "warnings": [ + { + "feature": "PHONE", + "risk": "VERIFICATION_CODE_ATTEMPTS_EXCEEDED", + "additional_data": null, + "log_type": "error", + "short_description": "Verification code attempts exceeded", + "long_description": "The system detected that the phone number has exceeded the maximum number of verification code attempts." + } + ], + "lifecycle": [ + { + "type": "PHONE_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null, + "channel": "whatsapp", + "actual_channel": "whatsapp" + }, + "fee": 0.0509 + }, + { + "type": "INVALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:01.118220+00:00", + "details": { + "code_tried": "111111", + "status": "Failed" + }, + "fee": 0 + }, + { + "type": "INVALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:21.512340+00:00", + "details": { + "code_tried": "222222", + "status": "Failed" + }, + "fee": 0 + }, + { + "type": "INVALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "333333", + "status": "Failed" + }, + "fee": 0 + }, + { + "type": "PHONE_VERIFICATION_DECLINED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": { + "reason": "VERIFICATION_CODE_ATTEMPTS_EXCEEDED" + }, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Declined (risk policy)": { + "summary": "Correct code, but the line is VoIP and voip_number_action=DECLINE", + "value": { + "request_id": "7f1c9d2e-44a1-4b6e-9a3e-5b8e6f1c2d3a", + "status": "Declined", + "message": "The verification code is correct.", + "phone": { + "status": "Declined", + "phone_number_prefix": "+1", + "phone_number": "5005550006", + "full_number": "+15005550006", + "country_code": "US", + "country_name": "United States", + "carrier": { + "name": "CLEC LLC", + "type": "voip" + }, + "is_disposable": false, + "is_virtual": true, + "verification_method": "sms", + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [ + { + "feature": "PHONE", + "risk": "VOIP_NUMBER_DETECTED", + "additional_data": null, + "log_type": "error", + "short_description": "VoIP number detected", + "long_description": "The system detected that the phone number is a VoIP number, which is not allowed." + } + ], + "lifecycle": [ + { + "type": "PHONE_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null, + "channel": "whatsapp", + "actual_channel": "sms" + }, + "fee": 0.0509 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "PHONE_VERIFICATION_DECLINED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": { + "reason": "VOIP_NUMBER_DETECTED" + }, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Expired or Not Found": { + "summary": "No pending verification for this number in the last 5 minutes", + "value": { + "request_id": "69cc35c6-280a-47a6-ab8c-4f32abc02ed0", + "status": "Expired or Not Found", + "message": "No pending phone verification found in the last 5 minutes.", + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-12T01:24:47.311323+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error — DRF's field-error envelope: one array of messages per offending field.", + "content": { + "application/json": { + "examples": { + "Invalid code format": { + "summary": "`code` is not 4–8 numeric digits", + "value": { + "code": [ + "Invalid code format. Enter 4 to 8 numeric digits." + ] + } + }, + "Invalid phone number": { + "summary": "`phone_number` is not valid E.164", + "value": { + "phone_number": [ + "Invalid phone number provided." + ] + } + }, + "Invalid action": { + "summary": "Risk actions accept only NO_ACTION or DECLINE", + "value": { + "voip_number_action": [ + "\"REVIEW\" is not a valid choice." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing; a wrong, revoked or expired key returns `401`. Checks are free, so there is no credit check here.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). Additionally, an anti-abuse cap of 4 verification attempts per phone number per hour applies upstream. The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers. Note: the `X-RateLimit-*`/`Retry-After` headers accompany only the 300/min write-limit 429; the per-number cap 429 carries no rate-limit headers.", + "content": { + "application/json": { + "examples": { + "Per-number cap": { + "summary": "More than 4 verification attempts for the same number in the last hour", + "value": { + "detail": "Maximum verification attempts reached for this phone number. Only 4 authentication attempts are allowed per hour. Try again later or use a different number." + } + }, + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "502": { + "description": "The phone verification provider is temporarily unavailable - a 5xx response, timeout, or connection failure that survived the built-in retries. Safe to retry; the attempt budget is not consumed.", + "content": { + "application/json": { + "examples": { + "Provider outage": { + "summary": "The provider did not answer the check request", + "value": { + "detail": "Phone verification is temporarily unavailable. Please try again shortly.", + "code": "phone_provider_unavailable" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + } + } + } + }, + "500": { + "description": "Unexpected failure while verifying the code upstream that is not a known provider outage. Safe to retry; the attempt budget is only consumed by completed checks.", + "content": { + "application/json": { + "examples": { + "Provider error": { + "summary": "Code verification could not be completed", + "value": { + "detail": "Error checking phone verification" + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/bank/account-check/": { + "post": { + "summary": "Check a bank account", + "description": "**Not available in production yet.** Bank Verification is switched off in production, so until it is switched on this endpoint answers `404` with `bank_verification_not_available`. Do not build on it yet. The rest of this description is what the endpoint does once it is available.\n\nCheck that a bank account belongs to the person or company you name, before you send money to it. No session and no sign-in: send the name and the account, read the result. It is the [account check](/core-technology/bank-verification/account-check) of the Bank Verification step, run from your backend.\n\n**What you send.** The account's `country` (ISO 3166-1 alpha-3), the `account` the way that country writes it, and the `holder` name: `full_name` for a person or `business_name` for a company. A business name checks a business account.\n\n**What you get back.** `result` is `match`, `close_match`, `no_match` or `cannot_verify`. On a `close_match`, `holder_name` is the name the bank holds the account under, when the bank returned it. `account` comes back masked to its last 4 characters.\n\n**Billing.** Each check the bank answers is billed once at the Account check price of the account's country, whatever the result: a `no_match`, an account that does not exist and a `cannot_verify` are billed too. A request answered with an error is never billed.\n\n**Every request is a new check.** There is no idempotency key and no endpoint to read a check back, so a retried request runs, and is billed, again. Store the response.\n\n**Sandbox.** Where Bank Verification is available, a sandbox key calls no bank and is charged nothing. The last 4 characters of the IBAN, else of the account number, choose the answer: `0001` a `close_match` with a `holder_name`, `0002` a `close_match` without one, `0003` and `0006` a `no_match`, `0004` `cannot_verify`, `0005` a `503` and `0009` a `400`. Any other ending is a `match`.\n\n**API keys.** A scoped API key needs write access to sessions.", + "operationId": "post_v3_bank_account_check", + "tags": [ + "Standalone APIs" + ], + "x-didit-sdk": { + "resource": "bank", + "method": "account_check", + "auth": "api_key", + "server": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckRequest" + }, + "examples": { + "Person, United Kingdom": { + "summary": "A sort code and account number, checked against a person's name", + "value": { + "country": "GBR", + "account": { + "sort_code": "20-45-67", + "account_number": "31926819" + }, + "holder": { + "full_name": "Maria Oliveira" + }, + "vendor_data": "payout_20481" + } + }, + "Company, IBAN": { + "summary": "An IBAN, checked against a company's name", + "value": { + "country": "DEU", + "account": { + "iban": "DE89 3704 0044 0532 0130 00" + }, + "holder": { + "business_name": "Northwind Trading GmbH" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The bank answered. The check is billed, whatever the result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckResponse" + }, + "examples": { + "Close match": { + "summary": "The bank holds the account under a close name", + "value": { + "request_id": "c2f4a9e0-7b1d-4c3a-9e55-1d0a6b8f2a47", + "result": "close_match", + "holder_name": "MARIA R OLIVEIRA", + "account": "****6819", + "vendor_data": "payout_20481", + "created_at": "2026-10-21T14:32:08Z" + } + }, + "Match": { + "summary": "The bank holds the account under the name sent", + "value": { + "request_id": "5b0e2d7c-41a8-4f6e-9c3b-8e7d1a2f6c90", + "result": "match", + "holder_name": null, + "account": "****3000", + "vendor_data": null, + "created_at": "2026-10-21T14:35:41Z" + } + } + } + } + } + }, + "400": { + "description": "Refused as sent, and not billed. `field` names the body field to correct and `reason` says why, with the same reasons the hosted account form shows. Only the first refused field is reported.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Missing field": { + "summary": "The country's check needs a sort code", + "value": { + "detail": "The sort code is not valid (required).", + "code": "invalid_account_input", + "field": "account.sort_code", + "reason": "required" + } + }, + "Invalid IBAN": { + "summary": "The IBAN has the wrong length for its country", + "value": { + "detail": "The iban is not valid (length).", + "code": "invalid_account_input", + "field": "account.iban", + "reason": "length" + } + }, + "Refused by the bank": { + "summary": "The bank refused the account as sent", + "value": { + "detail": "The account was refused as typed (rejected).", + "code": "invalid_account_input", + "field": "account.account_number", + "reason": "rejected" + } + }, + "No name": { + "summary": "`holder` has neither `full_name` nor `business_name`", + "value": { + "detail": "Send full_name for a person or business_name for a company.", + "code": "invalid_account_input", + "field": "holder", + "reason": "required" + } + }, + "Consent required": { + "summary": "The country's account check needs the account holder's consent", + "value": { + "detail": "Account checks in this country need the account holder's explicit consent. Ask for it, then send `consent=true`.", + "code": "invalid_account_input", + "field": "consent", + "reason": "required" + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "402": { + "description": "The check cannot be paid for, so it did not run. Top up your balance, then send it again.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Out of credit": { + "summary": "Your balance cannot cover one check", + "value": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me", + "code": "insufficient_credits" + } + }, + "Not in your contract": { + "summary": "Your contract has no price for the Account check API", + "value": { + "detail": "Your contract does not price the Account check API. Contact your account manager.", + "code": "contract_rate_missing" + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header or the bearer token is missing or invalid, the request came from an address outside the key's IP list, or a scoped key has no write access to sessions.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No valid API key or token", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "No sessions write": { + "summary": "A scoped key without write access to sessions", + "value": { + "detail": "This API key has no write access to sessions." + } + } + } + } + } + }, + "404": { + "description": "No account check serves this account yet, so nothing was checked or billed. `country_not_covered`: none serves its country, or its kind of account (person or business) there. `bank_not_covered`: none serves its bank. `bank_verification_not_available`: Bank Verification is not enabled in production yet, which is the answer for every request there until it is switched on.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Country not covered": { + "summary": "No account check serves the country", + "value": { + "detail": "No account check serves this country yet.", + "code": "country_not_covered" + } + }, + "Bank not covered": { + "summary": "No account check serves the account's bank", + "value": { + "detail": "No account check serves this account's bank yet.", + "code": "bank_not_covered" + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + }, + "503": { + "description": "The bank could not answer. Nothing was checked or billed; send the check again later.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankAccountCheckError" + }, + "examples": { + "Unavailable": { + "summary": "The bank did not answer", + "value": { + "detail": "The account check is temporarily unavailable and this request was not charged. Please try again.", + "code": "account_check_unavailable" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/bank/account-check/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"country\": \"GBR\",\n \"account\": {\"sort_code\": \"20-45-67\", \"account_number\": \"31926819\"},\n \"holder\": {\"full_name\": \"Maria Oliveira\"},\n \"vendor_data\": \"payout_20481\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.bank.account_check(\n country=\"GBR\",\n account={\n \"account_number\": \"31926819\",\n \"sort_code\": \"20-45-67\",\n },\n holder={\n \"full_name\": \"Maria Oliveira\",\n },\n vendor_data=\"payout_20481\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.bank.accountCheck({\n country: \"GBR\",\n account: {\n account_number: \"31926819\",\n sort_code: \"20-45-67\",\n },\n holder: {\n full_name: \"Maria Oliveira\",\n },\n vendor_data: \"payout_20481\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Bank.AccountCheck(ctx, didit.BankAccountCheckRequest{\n\tCountry: \"GBR\",\n\tAccount: didit.BankAccountCheckRequestAccount{\n\t\tAccountNumber: didit.String(\"31926819\"),\n\t\tSortCode: didit.String(\"20-45-67\"),\n\t},\n\tHolder: didit.BankAccountCheckHolder{\n\t\tFullName: didit.String(\"Maria Oliveira\"),\n\t},\n\tVendorData: didit.String(\"payout_20481\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BankAccountCheckHolder;\nimport me.didit.types.BankAccountCheckPersonHolder;\nimport me.didit.types.BankAccountCheckRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.bank().accountCheck(BankAccountCheckRequest.builder()\n .country(\"GBR\")\n .account(BankAccountCheckRequest.Account.builder()\n .accountNumber(\"31926819\")\n .sortCode(\"20-45-67\")\n .build())\n .holder(BankAccountCheckHolder.ofBankAccountCheckPersonHolder(BankAccountCheckPersonHolder.builder()\n .fullName(\"Maria Oliveira\")\n .build()))\n .vendorData(\"payout_20481\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "bank->accountCheck([\n 'country' => 'GBR',\n 'account' => [\n 'account_number' => '31926819',\n 'sort_code' => '20-45-67',\n ],\n 'holder' => [\n 'full_name' => 'Maria Oliveira',\n ],\n 'vendor_data' => 'payout_20481',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.bank.account_check(\n country: \"GBR\",\n account: {\n account_number: \"31926819\",\n sort_code: \"20-45-67\",\n },\n holder: {\n full_name: \"Maria Oliveira\",\n },\n vendor_data: \"payout_20481\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Bank.AccountCheckAsync(new BankAccountCheckRequest\n{\n Country = \"GBR\",\n Account = new()\n {\n AccountNumber = \"31926819\",\n SortCode = \"20-45-67\",\n },\n Holder = new()\n {\n FullName = \"Maria Oliveira\",\n },\n VendorData = \"payout_20481\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/email/send/": { + "post": { + "summary": "Send Email Code", + "description": "Send a one-time passcode (OTP) to an email address, then verify it with [`POST /v3/email/check/`](/standalone-apis/email-check).\n\n**How send and check pair up.** Verification state is keyed by your application plus the `email` address — the check call does not take `request_id`. Each new send creates a pending verification that lives for **5 minutes**: call the check with the same address and the code the user received before the window closes. Calling send again for the same address while a verification is pending generates a fresh code for that same verification (`status: \"Retry\"`, same `request_id`); the previous code stops working. At most one retry is attached this way (two sends total); a further send starts a fresh verification with a new `request_id`. The 5-minute window is measured from the first send and is not extended by retries.\n\n**Deliverability pre-check.** Before anything is sent, the address goes through syntax and DNS/MX validation. Addresses that cannot receive mail return `200` with `status: \"Undeliverable\"` and `reason: \"email_can_not_be_delivered\"` — no email is sent, nothing is billed, and the verification is immediately finalized as `Declined` (a follow-up check returns `Expired or Not Found`). A downstream send failure reports the same way.\n\n**Code format and branding.** Codes are 4–8 characters (`options.code_size`, default 6), numeric by default; set `options.alphanumeric_code: true` for uppercase letters and digits (the check comparison is case-insensitive). The email template is localized via `options.locale` (54 supported languages) and can use your application's white-label branding via `options.use_white_label_customization`.\n\n**Billing.** One Email Verification API credit per successful send (`status: \"Success\"`), charged at send time. `Retry` and `Undeliverable` sends are free, and checks are free.\n\n**Session persistence.** Every new verification is persisted as an API-type session: `request_id` is a real session id you can pass to `GET /v3/session/{sessionId}/decision/`, the verification appears in the Business Console, and `status.updated` webhooks fire as it progresses.\n\n**Sandbox.** Sandbox API keys skip delivery and billing: after request validation (malformed input still returns `400`), the endpoint returns a static `Success` payload with a random `request_id`; no email is sent and nothing is persisted. Use code `123456` on the sandbox check.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.", + "operationId": "post_v3email_send", + "x-didit-sdk": { + "resource": "email", + "method": "send_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/email/send/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"email\": \"alice@example.com\",\n \"options\": { \"code_size\": 6, \"locale\": \"en\" },\n \"vendor_data\": \"user-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.email.send_code(\n email=\"alice@example.com\",\n options={\n \"locale\": \"en\",\n },\n vendor_data=\"user-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.email.sendCode({\n email: \"alice@example.com\",\n options: {\n locale: \"en\",\n },\n vendor_data: \"user-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Email.SendCode(ctx, didit.EmailSendCodeParams{\n\tEmail: \"alice@example.com\",\n\tOptions: didit.Some(didit.EmailSendCodeParamsOptions{\n\t\tLocale: didit.Some(didit.EmailSendCodeParamsOptionsLocale(\"en\")),\n\t}),\n\tVendorData: didit.String(\"user-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.EmailSendCodeParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.email().sendCode(EmailSendCodeParams.builder()\n .email(\"alice@example.com\")\n .options(EmailSendCodeParams.Options.builder()\n .locale(EmailSendCodeParams.Options.Locale.of(\"en\"))\n .build())\n .vendorData(\"user-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "email->sendCode([\n 'email' => 'alice@example.com',\n 'options' => [\n 'locale' => 'en',\n ],\n 'vendor_data' => 'user-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.email.send_code(\n email: \"alice@example.com\",\n options: {\n locale: \"en\",\n },\n vendor_data: \"user-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Email.SendCodeAsync(new EmailSendCodeParams\n{\n Email = \"alice@example.com\",\n Options = new EmailSendCodeParamsOptions\n {\n Locale = new EmailSendCodeParamsOptionsLocale(\"en\"),\n },\n VendorData = \"user-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "Recipient email address. Malformed addresses return `400`; syntactically valid addresses that cannot receive mail (failed DNS/MX validation) return `200` with `status: \"Undeliverable\"`.", + "example": "alice@example.com" + }, + "options": { + "type": "object", + "description": "OTP format, localization, and branding options. All fields are optional.", + "properties": { + "code_size": { + "type": "integer", + "minimum": 4, + "maximum": 8, + "default": 6, + "description": "Number of characters in the OTP (digits by default; uppercase letters and digits when `alphanumeric_code` is `true`)." + }, + "alphanumeric_code": { + "type": "boolean", + "default": false, + "description": "When `true`, the OTP is composed of uppercase letters `A–Z` and digits `0–9` instead of digits only. The `/v3/email/check/` comparison is case-insensitive." + }, + "locale": { + "type": "string", + "maxLength": 5, + "enum": [ + "en", + "ar", + "bn", + "bg", + "bs", + "ca", + "cs", + "da", + "de", + "el", + "es", + "et", + "fa", + "fi", + "fr", + "he", + "hi", + "hr", + "hu", + "hy", + "id", + "it", + "ja", + "ka", + "kk", + "ko", + "ky", + "lt", + "lv", + "cnr", + "mk", + "mn", + "ms", + "nl", + "no", + "pl", + "pt-BR", + "pt", + "ro", + "ru", + "sk", + "sl", + "so", + "sq", + "sr", + "sv", + "th", + "tr", + "uk", + "uz", + "vi", + "zh-CN", + "zh-TW", + "zh" + ], + "description": "Language of the OTP email template. Defaults to `en`. Unsupported values return `400` listing all supported locales.", + "example": "en" + }, + "use_white_label_customization": { + "type": "boolean", + "default": false, + "description": "When `true`, the OTP email uses your application's white-label customization (logo, colors, sender branding) instead of the default Didit template." + } + } + }, + "signals": { + "type": "object", + "description": "Optional device and network signals about the end user, forwarded to the anti-fraud layer to improve detection of abusive or automated traffic. All fields are optional.", + "properties": { + "ip": { + "type": "string", + "description": "IP address of the end user's device. IPv4 or IPv6.", + "example": "192.0.2.1" + }, + "device_id": { + "type": "string", + "maxLength": 255, + "description": "Unique identifier of the end user's device. Android: `ANDROID_ID`; iOS: `identifierForVendor`.", + "example": "8F0B8FDD-C2CB-4387-B20A-56E9B2E5A0D2" + }, + "device_platform": { + "type": "string", + "enum": [ + "android", + "ios", + "ipados", + "tvos", + "web" + ], + "description": "Platform of the end user's device." + }, + "device_model": { + "type": "string", + "maxLength": 255, + "description": "Model of the end user's device.", + "example": "iPhone17,2" + }, + "os_version": { + "type": "string", + "maxLength": 64, + "description": "Operating-system version of the end user's device.", + "example": "18.0.1" + }, + "app_version": { + "type": "string", + "maxLength": 64, + "description": "Version of your application.", + "example": "1.2.34" + }, + "user_agent": { + "type": "string", + "maxLength": 512, + "description": "User agent of the end user's browser or app.", + "example": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1" + } + } + }, + "vendor_data": { + "type": "string", + "description": "Optional caller-controlled identifier (your internal user id, an email, a UUID, etc.) persisted on the session and echoed back in the send response, the matching check response, webhooks, and the Business Console. Use it to correlate Didit's `request_id` with your user record." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Optional free-form JSON object persisted on the session and echoed back in the send response, the matching check response, webhooks, and the Business Console." + } + }, + "required": [ + "email" + ] + }, + "examples": { + "Numeric 6-digit code": { + "summary": "Default — 6-digit numeric code in English", + "value": { + "email": "alice@example.com", + "options": { + "locale": "en" + }, + "vendor_data": "user-1234" + } + }, + "Alphanumeric 8-character code": { + "summary": "Uppercase letters + digits, Spanish template", + "value": { + "email": "bob@example.com", + "options": { + "code_size": 8, + "alphanumeric_code": true, + "locale": "es" + } + } + }, + "White-label branding": { + "summary": "Send the OTP email with your application's white-label customization", + "value": { + "email": "carol@example.com", + "options": { + "locale": "en", + "use_white_label_customization": true + }, + "vendor_data": "user-5678" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Send acknowledged. Inspect `status`: `Success` and `Retry` mean a code is on its way; `Undeliverable` means the address cannot receive mail and the verification is already finalized as `Declined`. `request_id` is the persisted session id (same id on a `Retry`).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailVerificationSendResponse" + }, + "examples": { + "Success": { + "summary": "OTP emailed for a new verification (billed)", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Success", + "reason": null, + "vendor_data": "user-1234", + "metadata": null + } + }, + "Retry": { + "summary": "Second send inside the 5-minute window — fresh code, same request_id, free", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Retry", + "reason": null, + "vendor_data": "user-1234", + "metadata": null + } + }, + "Undeliverable": { + "summary": "Address failed DNS/MX validation — verification finalized as Declined, nothing billed", + "value": { + "request_id": "cb1a5011-344d-444d-bdce-218904745d7f", + "status": "Undeliverable", + "reason": "email_can_not_be_delivered", + "vendor_data": "user-1234", + "metadata": null + } + } + } + } + } + }, + "400": { + "description": "Validation error — DRF's field-error envelope: one array of messages per offending field, nested under `options` for option errors.", + "content": { + "application/json": { + "examples": { + "Invalid email": { + "summary": "Malformed email address", + "value": { + "email": [ + "Enter a valid email address." + ] + } + }, + "Unsupported locale": { + "summary": "`options.locale` outside the supported set", + "value": { + "options": { + "locale": [ + "Invalid locale. Supported locales are en, ar, bn, bg, bs, ca, cs, da, de, el, es, et, fa, fi, fr, he, hi, hr, hu, hy, id, it, ja, ka, kk, ko, ky, lt, lv, cnr, mk, mn, ms, nl, no, pl, pt-BR, pt, ro, ru, sk, sl, so, sq, sr, sv, th, tr, uk, uz, vi, zh-CN, zh-TW, zh." + ] + } + } + }, + "Code size out of range": { + "summary": "`options.code_size` outside 4–8", + "value": { + "options": { + "code_size": [ + "Ensure this value is less than or equal to 8." + ] + } + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing (`{\"detail\": ...}`); a wrong, revoked or expired key returns `401`. Also returned before any send when the organization's balance cannot cover one Email Verification API credit (`{\"error\": ...}`).", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the send price", + "value": { + "error": "You don't have enough credits to perform this request." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "503": { + "description": "The email provider could not be reached (an outage or a timeout), so no code was sent and nothing was billed or counted as a send attempt. Repeat the same request.", + "content": { + "application/json": { + "examples": { + "Email delivery unavailable": { + "summary": "The email provider failed after one automatic retry", + "value": { + "detail": "Email delivery is temporarily unavailable. Please try again." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/email/check/": { + "post": { + "summary": "Check Email Code", + "description": "Verify the OTP delivered by [`POST /v3/email/send/`](/standalone-apis/email-send) and get the final verification result plus email intelligence: deliverability, breach exposure (`is_breached` and the `breaches` list from a breach-intelligence database), disposable-provider detection, and duplicate usage of the address across your sessions.\n\n**How the check finds the verification.** Matching is by your application plus the `email` address — `request_id` is not an input. The most recent pending verification created within the last **5 minutes** is checked. If there is none (never sent, already finalized, undeliverable at send time, or older than 5 minutes) the endpoint returns `200` with `status: \"Expired or Not Found\"`.\n\n**Attempt budget.** Each verification allows **3 code attempts**. The first two wrong codes return `status: \"Failed\"` with the attempts remaining and `email: null`; the third wrong code finalizes the verification as `Declined` with an `EMAIL_CODE_ATTEMPTS_EXCEEDED` warning and returns the full email report. Codes are compared case-insensitively (relevant for `alphanumeric_code` sends); only the most recently sent code is valid.\n\n**Outcomes.** `Approved` — correct code and no declining risk. `Declined` — terminal: the code was correct but a declining risk matched (a `DECLINE` action below, a blocklisted address, or the address found undeliverable at finalization), or the attempt budget was exhausted. `Failed` — wrong code, attempts remaining. `Expired or Not Found` — nothing to check. On `Approved`/`Declined` the `request_id` equals the send's `request_id` (the session id) and `email` carries the full report (`is_breached`, `breaches`, `is_disposable`, `is_undeliverable`, `warnings`, `lifecycle`, `matches`); on `Failed` and `Expired or Not Found` the `request_id` is a one-off random UUID.\n\n**Risk actions.** `duplicated_email_action`, `breached_email_action`, and `disposable_email_action` decide what happens when the corresponding risk is detected on a correct code: `DECLINE` flips the final status to `Declined`; `NO_ACTION` (default) records the risk in `email.warnings` without affecting the status.\n\n**Billing.** Checks are free — the credit is consumed by the successful send.\n\n**Session persistence.** A finalized check updates the session created by the send (visible in the Business Console, queryable via `GET /v3/session/{sessionId}/decision/`) and fires a `status.updated` webhook.\n\n**Sandbox.** Sandbox API keys skip all processing: any well-formed code (`123456` included) returns a static `Approved` payload with a simplified `email` object (`status`, `email`, `is_breached`, `is_disposable`, `is_undeliverable`); malformed input still returns `400`. Nothing is persisted.\n\n**Authentication.** Send your application's API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`) — this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.\n\n**Social footprint.** Set `enable_social` to `true` to add a `email_social` block: the online platforms this address is registered on, with per-category counts and any profile details available. `profiles_registered: 0` means the platforms were checked and the address was found on none of them - a valid, billable answer. The block is absent when the add-on was not requested or the check could not be completed, and it is not billed in that case. Billing adds one `email_social` unit per request on top of this endpoint's own price.", + "operationId": "post_v3email_check", + "x-didit-sdk": { + "resource": "email", + "method": "check_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/email/check/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"email\": \"alice@example.com\",\n \"code\": \"123456\",\n \"disposable_email_action\": \"DECLINE\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.email.check_code(\n email=\"alice@example.com\",\n code=\"123456\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.email.checkCode({\n email: \"alice@example.com\",\n code: \"123456\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Email.CheckCode(ctx, didit.EmailCheckCodeParams{\n\tEmail: \"alice@example.com\",\n\tCode: \"123456\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.EmailCheckCodeParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.email().checkCode(EmailCheckCodeParams.builder()\n .email(\"alice@example.com\")\n .code(\"123456\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "email->checkCode([\n 'email' => 'alice@example.com',\n 'code' => '123456',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.email.check_code(\n email: \"alice@example.com\",\n code: \"123456\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Email.CheckCodeAsync(new EmailCheckCodeParams\n{\n Email = \"alice@example.com\",\n Code = \"123456\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "The same email address used in the matching [`POST /v3/email/send/`](/standalone-apis/email-send) call. This is what links the check to the send.", + "example": "alice@example.com" + }, + "code": { + "type": "string", + "maxLength": 10, + "description": "The OTP the end user received: 4–8 digits, or 4–8 letters/digits when `alphanumeric_code: true` was used at send time. Comparison is case-insensitive.", + "example": "123456" + }, + "duplicated_email_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the same address was already used and approved by a different user (only previously Approved verifications count) (different `vendor_data`) of your application. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `email.warnings` and fills `email.matches`." + }, + "breached_email_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the address appears in known data breaches (`email.is_breached`). `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `email.warnings`." + }, + "disposable_email_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "What to do when the domain belongs to a disposable/temporary-mail provider (`email.is_disposable`). `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the risk in `email.warnings`." + }, + "enable_enrichment": { + "type": "boolean", + "default": false, + "description": "When `true`, also run email intelligence on the address: a risk score and its breach history. Billed as one `email_intelligence_api` unit per request ($0.05 at list price) in addition to this endpoint's own price. The request returns `403` when your balance cannot cover it.", + "example": false + }, + "email_intelligence_score_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the email intelligence risk score is above the threshold. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "recent_email_breach_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the address appears in a recent known data breach. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "frequent_email_breach_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the address appears in many known data breaches. `DECLINE` flips the final `status` to `Declined`; `NO_ACTION` records the signal without declining. Only takes effect when `enable_enrichment` is `true`." + }, + "enable_social": { + "type": "boolean", + "default": false, + "description": "When true, also look up the social footprint of the address - the email, social, professional, e-commerce and entertainment platforms it is registered on. Billed as one `email_social` unit per request in addition to this endpoint's own price.", + "example": true + }, + "email_no_social_presence_action": { + "type": "string", + "enum": [ + "NO_ACTION", + "DECLINE" + ], + "default": "NO_ACTION", + "description": "Action when the social footprint check finds the address registered on none of the platforms it covers. Only NO_ACTION and DECLINE are accepted on this endpoint; REVIEW is available on the workflow configuration key of the same name." + } + }, + "required": [ + "email", + "code" + ] + }, + "examples": { + "Default": { + "summary": "Record any risks but never auto-decline", + "value": { + "email": "alice@example.com", + "code": "123456" + } + }, + "Strict risk policy": { + "summary": "Auto-decline disposable and breached inboxes", + "value": { + "email": "alice@example.com", + "code": "123456", + "disposable_email_action": "DECLINE", + "breached_email_action": "DECLINE" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Check completed — wrong codes and missing verifications also return `200`; inspect `status`, not the HTTP code. `email` is populated only on finalized outcomes (`Approved`/`Declined`), `null` on `Failed`, and absent on `Expired or Not Found`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmailVerificationCheckResponse" + }, + "examples": { + "Approved": { + "summary": "Correct code — clean address, full email report", + "value": { + "request_id": "e39cb057-92fc-4b59-b84e-02fec29a0f24", + "status": "Approved", + "message": "The verification code is correct.", + "email": { + "status": "Approved", + "email": "alice@example.com", + "is_breached": false, + "breaches": [], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [], + "lifecycle": [ + { + "type": "EMAIL_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null + }, + "fee": 0.02 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "EMAIL_VERIFICATION_APPROVED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": null, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Approved with breach exposure": { + "summary": "Correct code; breach exposure recorded as a warning (breached_email_action=NO_ACTION)", + "value": { + "request_id": "3a1f2b6c-9d4e-4f7a-8b2c-1e5d6f7a8b9c", + "status": "Approved", + "message": "The verification code is correct.", + "email": { + "status": "Approved", + "email": "bob@example.com", + "is_breached": true, + "breaches": [ + { + "name": "ExampleApp", + "domain": "exampleapp.com", + "breach_date": "2019-03-21", + "breach_emails_count": 763117, + "description": "In March 2019, the social platform ExampleApp suffered a data breach that exposed email addresses and passwords.", + "logo_path": "https:///logos/ExampleApp.png", + "data_classes": [ + "email_addresses", + "passwords" + ], + "is_verified": true + } + ], + "is_disposable": false, + "is_undeliverable": false, + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [ + { + "feature": "EMAIL", + "risk": "BREACHED_EMAIL_DETECTED", + "additional_data": null, + "log_type": "information", + "short_description": "Breached email detected", + "long_description": "This email address was found in one or more known data breaches." + } + ], + "lifecycle": [ + { + "type": "EMAIL_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null + }, + "fee": 0.02 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "835126", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "EMAIL_VERIFICATION_APPROVED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": null, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-5678", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Failed (attempts remaining)": { + "summary": "Wrong code — the user can retry; request_id here is a one-off UUID", + "value": { + "request_id": "11111111-2222-3333-4444-555555555555", + "status": "Failed", + "message": "The verification code is incorrect. Attempts remaining: 2", + "email": null, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:21.512340+00:00" + } + }, + "Declined (risk policy)": { + "summary": "Correct code, but the address is disposable and disposable_email_action=DECLINE", + "value": { + "request_id": "8c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "status": "Declined", + "message": "The verification code is correct.", + "email": { + "status": "Declined", + "email": "tempuser42@mailinator.com", + "is_breached": false, + "breaches": [], + "is_disposable": true, + "is_undeliverable": false, + "verification_attempts": 1, + "verified_at": "2026-06-12T01:24:47.311323Z", + "warnings": [ + { + "feature": "EMAIL", + "risk": "DISPOSABLE_EMAIL_DETECTED", + "additional_data": null, + "log_type": "error", + "short_description": "Disposable email detected", + "long_description": "The system detected that the email is disposable, which is not allowed." + } + ], + "lifecycle": [ + { + "type": "EMAIL_VERIFICATION_MESSAGE_SENT", + "timestamp": "2026-06-12T01:23:39.580554+00:00", + "details": { + "status": "Success", + "reason": null + }, + "fee": 0.02 + }, + { + "type": "VALID_CODE_ENTERED", + "timestamp": "2026-06-12T01:24:47.311201+00:00", + "details": { + "code_tried": "123456", + "status": "Approved" + }, + "fee": 0 + }, + { + "type": "EMAIL_VERIFICATION_DECLINED", + "timestamp": "2026-06-12T01:24:47.384292+00:00", + "details": { + "reason": "DISPOSABLE_EMAIL_DETECTED" + }, + "fee": 0 + } + ], + "matches": [] + }, + "vendor_data": "user-1234", + "metadata": null, + "created_at": "2026-06-12T01:24:47.401719+00:00" + } + }, + "Expired or Not Found": { + "summary": "No pending verification for this address in the last 5 minutes", + "value": { + "request_id": "88808a77-06e9-4cb0-85f5-9c052ddfd987", + "status": "Expired or Not Found", + "message": "No pending email verification found in the last 5 minutes.", + "vendor_data": null, + "metadata": null, + "created_at": "2026-06-12T01:24:59.887904+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error — DRF's field-error envelope: one array of messages per offending field.", + "content": { + "application/json": { + "examples": { + "Invalid email": { + "summary": "Malformed email address", + "value": { + "email": [ + "Enter a valid email address." + ] + } + }, + "Missing code": { + "summary": "`code` not included in the body", + "value": { + "code": [ + "This field is required." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing; a wrong, revoked or expired key returns `401`. Checks are free, so there is no credit check here.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/workflows/": { + "get": { + "summary": "List workflows", + "description": "List your workflows, newest first, in a limit/offset pagination envelope (`{count, next, previous, results}`, default page size 50). Each row is one version; `workflow_id` is the stable group ID and new sessions use the latest `published` version.", + "operationId": "list_workflows", + "x-didit-sdk": { + "resource": "workflows", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50 + }, + "description": "Page size (number of workflows per page)." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0 + }, + "description": "Number of rows to skip before the first returned workflow." + } + ], + "responses": { + "200": { + "description": "One page of workflows ordered by `created_at` descending, wrapped in the pagination envelope. Each item in `results` is a `WorkflowListItem`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of workflow versions for the application." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WorkflowListItem" + } + } + } + }, + "examples": { + "Two workflows": { + "summary": "A default KYC workflow and a draft KYC + AML workflow", + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "uuid": "a1b2c3d4-5678-90ab-cdef-111111111111", + "workflow_id": "a1b2c3d4-5678-90ab-cdef-111111111111", + "workflow_label": "Standard KYC", + "workflow_type": "kyc", + "is_default": true, + "is_archived": false, + "is_white_label_enabled": false, + "total_price": 0.15, + "min_price": 0.15, + "max_price": 0.15, + "features": "OCR + LIVENESS + FACE_MATCH", + "is_simple_workflow": true, + "is_editable": false, + "workflow_url": "https://verify.didit.me/u/_yljPOx8RWG3UAz5ZRQGVQ", + "max_retry_attempts": 3, + "retry_window_days": 7, + "session_expiration_time": 604800, + "version": 2, + "status": "published", + "has_draft": false, + "created_by": { + "name": "Alex Mateo", + "email": "alex@example.com", + "type": "user" + }, + "updated_at": "2026-04-12T10:30:00Z" + }, + { + "uuid": "b2c3d4e5-6789-01bc-defa-222222222222", + "workflow_id": "b2c3d4e5-6789-01bc-defa-222222222222", + "workflow_label": "Full Verification + AML", + "workflow_type": "kyc", + "is_default": false, + "is_archived": false, + "is_white_label_enabled": false, + "total_price": 0.25, + "min_price": 0.25, + "max_price": 0.25, + "features": "OCR + LIVENESS + FACE_MATCH + AML", + "is_simple_workflow": true, + "is_editable": true, + "workflow_url": "https://verify.didit.me/u/Aq3kPOx8RWG3UAz5ZRQGVQ", + "max_retry_attempts": 3, + "retry_window_days": 7, + "session_expiration_time": 604800, + "version": 1, + "status": "draft", + "has_draft": true, + "created_by": null, + "updated_at": "2026-04-15T18:05:21Z" + } + ] + } + } + } + } + } + }, + "403": { + "description": "The API key is missing, malformed, expired, or belongs to a different application. Body: `{\"detail\": \"You do not have permission to perform this action.\"}`. Refresh the token via the [Auth API](/auth-api/get-credentials) and retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff and honour any `Retry-After` header." + }, + "500": { + "description": "Unexpected server error while building the list. The request can be safely retried." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/workflows/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Accept: application/json'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.workflows.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.workflows.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Workflows.ListAutoPaging(ctx, didit.WorkflowsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.workflows().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "workflows->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.workflows.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Workflows.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "summary": "Create workflow", + "description": "Create a simple workflow. Send `features` in execution order (ordering rules apply); defaults to `published` v1. The body accepts ONLY the fields listed in `SimpleWorkflowRequest` — any other key (including `workflow_type`) is rejected with a 400. Not idempotent — each call creates a new workflow. Maximum 50 workflows per application.", + "operationId": "create_workflow", + "x-didit-sdk": { + "resource": "workflows", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleWorkflowRequest" + }, + "examples": { + "Simple KYC": { + "summary": "Linear ID, liveness, and face match workflow", + "value": { + "workflow_label": "Standard KYC", + "features": [ + { + "feature": "OCR", + "config": { + "duplicated_user_action": "review" + } + }, + { + "feature": "LIVENESS", + "config": { + "face_liveness_method": "passive" + } + }, + { + "feature": "FACE_MATCH", + "config": { + "face_match_score_decline_threshold": 40, + "face_match_score_review_threshold": 60 + } + } + ] + } + }, + "KYC + AML": { + "summary": "Run AML after ID verification", + "value": { + "workflow_label": "KYC with AML", + "features": [ + { + "feature": "OCR" + }, + { + "feature": "AML", + "config": { + "aml_score_review_threshold": 70, + "aml_score_approve_threshold": 30 + } + } + ] + } + }, + "Questionnaire workflow": { + "summary": "Run a questionnaire created with POST /v3/questionnaires/", + "value": { + "workflow_label": "Source of Funds", + "features": [ + { + "feature": "QUESTIONNAIRE", + "config": { + "questionnaire_uuid": "11111111-2222-3333-4444-555555555555", + "review_questionnaire_manually": true + } + } + ] + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Workflow created. The response body is the FULL workflow configuration — the same raw `VerificationSettings` serialization returned by `GET /v3/workflows/{settings_uuid}/` (~190 fields: every feature toggle, threshold, action and the generated `workflow_graph`), not the compact list item. Store the `uuid` (also exposed as `workflow_id`) so you can reference it from later session-creation calls. Note: `features` is a `\" + \"`-joined string and prices are JSON numbers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowDetail" + }, + "examples": { + "Created (abbreviated)": { + "summary": "Key fields of the full configuration object", + "value": { + "uuid": "c3d4e5f6-7890-12cd-ef34-333333333333", + "workflow_id": "c3d4e5f6-7890-12cd-ef34-333333333333", + "workflow_label": "Standard KYC", + "workflow_type": null, + "is_default": false, + "is_archived": false, + "total_price": 0.15, + "min_price": 0.15, + "max_price": 0.15, + "features": "OCR + LIVENESS + FACE_MATCH", + "is_simple_workflow": false, + "is_editable": false, + "workflow_url": "https://verify.didit.me/u/w9TkPOx8RWG3UAz5ZRQGVQ", + "workflow_graph": { + "start_node": "ocr", + "nodes": "... one node per feature plus a final status node ..." + }, + "max_retry_attempts": 3, + "retry_window_days": 7, + "session_expiration_time": 604800, + "version": 1, + "status": "published", + "published_at": "2026-05-17T10:30:00Z", + "created_at": "2026-05-17T10:30:00Z", + "updated_at": "2026-05-17T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Common causes: a top-level key outside the accepted whitelist (e.g. `workflow_type`), an unknown `feature` value, a feature placed before its dependency (e.g., `FACE_MATCH` before `OCR`), a passive feature (`AML`, `IP_ANALYSIS`, `DATABASE_VALIDATION`, `KYB_KEY_PEOPLE`) used as the first step, a `QUESTIONNAIRE` feature whose `config.questionnaire_uuid` does not exist for this application, a `SIGNATURE` feature that would go live with no document to sign, a `SIGNATURE` feature at `signature_level: \"aes\"` while Advanced (AES) is not available (`aes_not_available`, see `signature_availability` on the workflow), or an out-of-range threshold. Body is a DRF validation error object — keys are field names, values are arrays of human-readable messages.", + "content": { + "application/json": { + "examples": { + "Advanced (AES) not available": { + "value": { + "features": { + "signature_level": [ + "Node 'signature': Advanced (AES) is not available yet (aes_not_available)." + ] + } + } + }, + "Signature step with no document": { + "value": { + "features": { + "non_field_errors": [ + "Node 'signature': Add a document to sign, or let each session bring its own." + ] + } + } + }, + "Missing feature": { + "value": { + "features": [ + "At least one feature is required." + ] + } + }, + "Unknown top-level field": { + "value": { + "workflow_type": [ + "The v3 workflow API only accepts `workflow_label`, `is_default`, `status`, `features`, `is_white_label_enabled`, `is_desktop_allowed`, `max_retry_attempts`, `retry_window_days`, `session_expiration_time`, `face_liveness_max_attempts`, `face_match_max_attempts`." + ] + } + }, + "Unknown feature": { + "value": { + "features": [ + "Invalid feature 'FOO'. Allowed values: AGE_ESTIMATION, AML, DATABASE_VALIDATION, EMAIL_VERIFICATION, FACE_MATCH, IP_ANALYSIS, KYB_DOCUMENTS, KYB_KEY_PEOPLE, KYB_REGISTRY, LIVENESS, NFC, OCR, PHONE_VERIFICATION, PROOF_OF_ADDRESS, QUESTIONNAIRE." + ] + } + }, + "Ordering": { + "value": { + "features": [ + "Feature 'FACE_MATCH' must appear after one of: OCR." + ] + } + }, + "Questionnaire not found": { + "value": { + "features": "Questionnaire '11111111-2222-3333-4444-555555555555' was not found for this application. Create the questionnaire first, then use its questionnaire_id." + } + } + } + } + } + }, + "403": { + "description": "The API key is missing, malformed, expired, or scoped to a different application. Refresh via the [Auth API](/auth-api/get-credentials) and retry.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "415": { + "description": "Unsupported media type. Set `Content-Type: application/json` and send a JSON body." + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error while creating the workflow. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/workflows/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"workflow_label\": \"Standard KYC\",\n \"features\": [\n {\"feature\": \"OCR\"},\n {\"feature\": \"LIVENESS\", \"config\": {\"face_liveness_method\": \"passive\"}},\n {\"feature\": \"FACE_MATCH\", \"config\": {\"face_match_score_decline_threshold\": 40, \"face_match_score_review_threshold\": 60}}\n ]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.workflows.create(\n workflow_label=\"Standard KYC\",\n features=[{\n \"feature\": \"OCR\",\n \"config\": {\"duplicated_user_action\": \"review\"},\n }, {\n \"feature\": \"LIVENESS\",\n \"config\": {\"face_liveness_method\": \"passive\"},\n }, {\n \"feature\": \"FACE_MATCH\",\n \"config\": {\"face_match_score_decline_threshold\": 40, \"face_match_score_review_threshold\": 60},\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.workflows.create({\n workflow_label: \"Standard KYC\",\n features: [{\n feature: \"OCR\",\n config: { duplicated_user_action: \"review\" },\n }, {\n feature: \"LIVENESS\",\n config: { face_liveness_method: \"passive\" },\n }, {\n feature: \"FACE_MATCH\",\n config: { face_match_score_decline_threshold: 40, face_match_score_review_threshold: 60 },\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Workflows.Create(ctx, didit.SimpleWorkflowRequest{\n\tWorkflowLabel: didit.String(\"Standard KYC\"),\n\tFeatures: []didit.SimpleWorkflowFeature{{\n\t\tFeature: \"OCR\",\n\t\tConfig: didit.Some(map[string]any{\"duplicated_user_action\": \"review\"}),\n\t}, {\n\t\tFeature: \"LIVENESS\",\n\t\tConfig: didit.Some(map[string]any{\"face_liveness_method\": \"passive\"}),\n\t}, {\n\t\tFeature: \"FACE_MATCH\",\n\t\tConfig: didit.Some(map[string]any{\"face_match_score_decline_threshold\": 40, \"face_match_score_review_threshold\": 60}),\n\t}},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SimpleWorkflowFeature;\nimport me.didit.types.SimpleWorkflowRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.workflows().create(SimpleWorkflowRequest.builder()\n .workflowLabel(\"Standard KYC\")\n .features(List.of(\n SimpleWorkflowFeature.builder()\n .feature(SimpleWorkflowFeature.Feature.of(\"OCR\"))\n .config(Map.of(\"duplicated_user_action\", \"review\"))\n .build(),\n SimpleWorkflowFeature.builder()\n .feature(SimpleWorkflowFeature.Feature.of(\"LIVENESS\"))\n .config(Map.of(\"face_liveness_method\", \"passive\"))\n .build(),\n SimpleWorkflowFeature.builder()\n .feature(SimpleWorkflowFeature.Feature.of(\"FACE_MATCH\"))\n .config(Map.of(\"face_match_score_decline_threshold\", 40, \"face_match_score_review_threshold\", 60))\n .build()))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "workflows->create([\n 'workflow_label' => 'Standard KYC',\n 'features' => [[\n 'feature' => 'OCR',\n 'config' => ['duplicated_user_action' => 'review'],\n ], [\n 'feature' => 'LIVENESS',\n 'config' => ['face_liveness_method' => 'passive'],\n ], [\n 'feature' => 'FACE_MATCH',\n 'config' => ['face_match_score_decline_threshold' => 40, 'face_match_score_review_threshold' => 60],\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.workflows.create(\n workflow_label: \"Standard KYC\",\n features: [{\n feature: \"OCR\",\n config: { duplicated_user_action: \"review\" },\n }, {\n feature: \"LIVENESS\",\n config: { face_liveness_method: \"passive\" },\n }, {\n feature: \"FACE_MATCH\",\n config: { face_match_score_decline_threshold: 40, face_match_score_review_threshold: 60 },\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Workflows.CreateAsync(new SimpleWorkflowRequest\n{\n WorkflowLabel = \"Standard KYC\",\n Features = new SimpleWorkflowFeature[]\n {\n new()\n {\n Feature = \"OCR\",\n Config = new Dictionary { [\"duplicated_user_action\"] = \"review\" },\n },\n new()\n {\n Feature = \"LIVENESS\",\n Config = new Dictionary { [\"face_liveness_method\"] = \"passive\" },\n },\n new()\n {\n Feature = \"FACE_MATCH\",\n Config = new Dictionary { [\"face_match_score_decline_threshold\"] = 40, [\"face_match_score_review_threshold\"] = 60 },\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/workflows/{settings_uuid}/": { + "get": { + "summary": "Get workflow", + "description": "Fetch full configuration for one workflow version by `settings_uuid`: feature toggles, thresholds, allowed documents, graph, retry policy, callbacks, and pricing.", + "operationId": "get_workflow", + "x-didit-sdk": { + "resource": "workflows", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "parameters": [ + { + "name": "settings_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the workflow (the `uuid` value returned by the list endpoint). Each version of a workflow has its own `settings_uuid`." + } + ], + "responses": { + "200": { + "description": "Full workflow configuration. The shape is the raw `VerificationSettings` serialization (~190 fields: every feature toggle, threshold, warning action, allowed-documents config, the `workflow_graph`, retry policy, callbacks and pricing). The example below is abbreviated. Notes: `features` is a `\" + \"`-joined string (e.g. `\"OCR + LIVENESS\"`), prices are JSON numbers, and `is_editable` is `true` only for `draft` versions.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowDetail" + }, + "examples": { + "KYC workflow with face match (abbreviated)": { + "value": { + "uuid": "a1b2c3d4-5678-90ab-cdef-111111111111", + "workflow_id": "a1b2c3d4-5678-90ab-cdef-111111111111", + "workflow_label": "Standard KYC", + "workflow_type": "kyc", + "is_default": true, + "is_archived": false, + "is_liveness_enabled": true, + "face_liveness_method": "passive", + "face_liveness_score_decline_threshold": 50, + "face_liveness_score_review_threshold": 70, + "is_face_match_enabled": true, + "face_match_score_decline_threshold": 40, + "face_match_score_review_threshold": 60, + "is_aml_enabled": false, + "is_phone_verification_enabled": false, + "is_email_verification_enabled": false, + "is_database_validation_enabled": false, + "is_ip_analysis_enabled": false, + "is_nfc_enabled": false, + "duplicated_user_action": "no_action", + "documents_allowed": { + "ESP": { + "P": { + "sides": 1, + "enabled": 1, + "subtypes": [ + "EPASSPORT", + "PASSPORT_GENERIC" + ], + "preferred_characters": "latin", + "expiration_check_mode": "strict" + } + } + }, + "max_retry_attempts": 3, + "retry_window_days": 7, + "session_expiration_time": 604800, + "version": 2, + "status": "published", + "published_at": "2026-04-12T10:30:00Z", + "is_simple_workflow": false, + "is_editable": false, + "workflow_url": "https://verify.didit.me/u/_yljPOx8RWG3UAz5ZRQGVQ", + "total_price": 0.15, + "min_price": 0.15, + "max_price": 0.15, + "features": "OCR + LIVENESS + FACE_MATCH", + "created_at": "2025-01-15T10:30:00Z", + "updated_at": "2026-04-12T10:30:00Z" + } + } + } + } + } + }, + "403": { + "description": "API key is missing, malformed, expired, or scoped to a different application.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No workflow with this `settings_uuid` exists for the authenticated application. Either the UUID is wrong or it belongs to another application — Didit deliberately does not distinguish the two cases to avoid leaking the existence of cross-application records.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/workflows/a1b2c3d4-5678-90ab-cdef-111111111111/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.workflows.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.workflows.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Workflows.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.workflows().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "workflows->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.workflows.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Workflows.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "patch": { + "summary": "Update workflow", + "description": "Update a workflow. Versioning depends on the `status` field you send and the current state of the version: (1) no `status` on a **published** version → the published version is updated in place (same `uuid`, same `version`); (2) no `status` on a **draft** → the draft is updated and immediately published; (3) `status: \"published\"` on a **published** version → a NEW published version is created under the same `workflow_id` (new `uuid`, `version` + 1) and returned, and any existing draft in the group is deleted; (4) `status: \"draft\"` keeps a draft as a draft (autosave) but is rejected with 400 on a published version (`\"Cannot revert a published version to draft.\"`). The body accepts the same strict field whitelist as create — unknown keys are rejected with 400.", + "operationId": "update_workflow", + "x-didit-sdk": { + "resource": "workflows", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "parameters": [ + { + "name": "settings_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the workflow to update." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleWorkflowUpdateRequest" + }, + "examples": { + "Replace features": { + "summary": "Send the full desired feature order", + "value": { + "features": [ + { + "feature": "OCR" + }, + { + "feature": "AML" + } + ] + } + }, + "Rename": { + "summary": "Rename a workflow", + "value": { + "workflow_label": "Premium KYC" + } + }, + "Set as default": { + "summary": "Make this workflow the application's default", + "value": { + "is_default": true + } + }, + "Update config": { + "summary": "Change feature configuration", + "value": { + "features": [ + { + "feature": "OCR" + }, + { + "feature": "LIVENESS", + "config": { + "face_liveness_method": "passive" + } + } + ] + } + }, + "Publish a draft": { + "summary": "Promote a draft to published", + "value": { + "status": "published" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Workflow updated. The body is the FULL configuration of the resulting version (same raw `VerificationSettings` shape as `GET /v3/workflows/{settings_uuid}/`). When the update created a new version (`status: \"published\"` sent on a published row) the returned `uuid` and `version` differ from the ones you patched — use the returned `uuid` for subsequent calls.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowDetail" + } + } + } + }, + "400": { + "description": "Validation error — a top-level key outside the accepted whitelist, an unknown feature, invalid ordering, out-of-range threshold, referencing a non-existent questionnaire, a `SIGNATURE` feature that would go live with no document to sign, a new `SIGNATURE` feature at `signature_level: \"aes\"` while Advanced (AES) is not available (`aes_not_available`, see `signature_availability` on the workflow; a step that already stores `aes` keeps it), or `status: \"draft\"` sent for a published version (`\"Cannot revert a published version to draft.\"`). The response body is a DRF validation error keyed by field name." + }, + "403": { + "description": "API key missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No workflow with this `settings_uuid` exists for the authenticated application." + }, + "415": { + "description": "Unsupported media type. Set `Content-Type: application/json`." + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X PATCH 'https://verification.didit.me/v3/workflows/a1b2c3d4-5678-90ab-cdef-111111111111/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"workflow_label\": \"Premium KYC\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.workflows.update(\n \"11111111-2222-3333-4444-555555555555\",\n features=[{\n \"feature\": \"OCR\",\n }, {\n \"feature\": \"AML\",\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.workflows.update(\"11111111-2222-3333-4444-555555555555\", {\n features: [{\n feature: \"OCR\",\n }, {\n feature: \"AML\",\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Workflows.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SimpleWorkflowUpdateRequest{\n\tFeatures: didit.Some([]didit.SimpleWorkflowFeature{{\n\t\tFeature: \"OCR\",\n\t}, {\n\t\tFeature: \"AML\",\n\t}}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.SimpleWorkflowFeature;\nimport me.didit.types.SimpleWorkflowUpdateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.workflows().update(\"11111111-2222-3333-4444-555555555555\", SimpleWorkflowUpdateRequest.builder()\n .features(List.of(\n SimpleWorkflowFeature.builder()\n .feature(SimpleWorkflowFeature.Feature.of(\"OCR\"))\n .build(),\n SimpleWorkflowFeature.builder()\n .feature(SimpleWorkflowFeature.Feature.of(\"AML\"))\n .build()))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "workflows->update('11111111-2222-3333-4444-555555555555', [\n 'features' => [[\n 'feature' => 'OCR',\n ], [\n 'feature' => 'AML',\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.workflows.update(\n \"11111111-2222-3333-4444-555555555555\",\n features: [{\n feature: \"OCR\",\n }, {\n feature: \"AML\",\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Workflows.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new SimpleWorkflowUpdateRequest\n{\n Features = new SimpleWorkflowFeature[]\n {\n new()\n {\n Feature = \"OCR\",\n },\n new()\n {\n Feature = \"AML\",\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "summary": "Delete workflow", + "description": "Permanently delete one workflow version (no undo). Existing sessions keep their verification results but their workflow reference is cleared; other versions in the same `workflow_id` group are not deleted.", + "operationId": "delete_workflow", + "x-didit-sdk": { + "resource": "workflows", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Workflows" + ], + "parameters": [ + { + "name": "settings_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the workflow to delete." + } + ], + "responses": { + "204": { + "description": "Workflow deleted. The response body is empty." + }, + "403": { + "description": "API key missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No workflow with this `settings_uuid` exists for the authenticated application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X DELETE 'https://verification.didit.me/v3/workflows/a1b2c3d4-5678-90ab-cdef-111111111111/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.workflows.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.workflows.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Workflows.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.workflows().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "workflows->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.workflows.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Workflows.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ] + } + }, + "/v3/questionnaires/": { + "get": { + "summary": "List questionnaires", + "description": "List your questionnaires, newest first, in a limit/offset pagination envelope (`{count, next, previous, results}`, default page size 50). Each row is one version; `questionnaire_group_id` is the stable group ID resolved to the latest `published` version.", + "operationId": "list_questionnaires", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50 + }, + "description": "Page size (number of questionnaires per page)." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0 + }, + "description": "Number of rows to skip before the first returned questionnaire." + } + ], + "responses": { + "200": { + "description": "One page of questionnaires ordered by `created_at` descending, wrapped in the pagination envelope. Each item in `results` is a `QuestionnaireListItem`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of questionnaire versions for the application." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QuestionnaireListItem" + } + } + } + }, + "examples": { + "One questionnaire": { + "summary": "A simple bilingual questionnaire", + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "11111111-2222-3333-4444-555555555555", + "title": "KYC Questionnaire", + "description": "Additional verification questions", + "languages": [ + "en", + "es" + ], + "default_language": "en", + "is_active": true, + "is_simple_questionnaire": true, + "created_at": "2026-04-23T09:55:00Z", + "updated_at": "2026-04-23T10:00:00Z", + "question_types": [ + "SHORT_TEXT", + "MULTIPLE_CHOICE", + "FILE_UPLOAD" + ], + "workflow_names": [], + "questionnaire_group_id": "11111111-2222-3333-4444-555555555555", + "version": 1, + "status": "published", + "published_at": "2026-04-23T10:00:00Z", + "is_editable": false + } + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error while building the list. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/questionnaires/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.questionnaires.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.questionnaires.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Questionnaires.ListAutoPaging(ctx, didit.QuestionnairesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.questionnaires().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "questionnaires->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.questionnaires.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Questionnaires.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "summary": "Create questionnaire", + "description": "Create a simple linear questionnaire. Send `form_elements` in display order; branching and repeatable groups require the Console. Defaults to `published` v1.", + "operationId": "create_questionnaire", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleQuestionnaireRequest" + }, + "examples": { + "Simple": { + "summary": "Basic questionnaire", + "value": { + "title": "Customer Onboarding", + "description": "Additional information needed for verification", + "default_language": "en", + "languages": [ + "en" + ], + "is_active": true, + "form_elements": [ + { + "id": "occupation", + "element_type": "short_text", + "label": { + "en": "What is your occupation?" + }, + "is_required": true, + "placeholder": { + "en": "Software engineer" + } + }, + { + "id": "source_of_funds", + "element_type": "multiple_choice", + "label": { + "en": "Source of funds" + }, + "is_required": true, + "options": [ + { + "value": "employment", + "label": { + "en": "Employment" + } + }, + { + "value": "business", + "label": { + "en": "Business" + } + }, + { + "value": "investments", + "label": { + "en": "Investments" + } + }, + { + "value": "other", + "label": { + "en": "Other" + } + } + ] + } + ] + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Questionnaire created. The response includes the `questionnaire_id` — store it to reference this questionnaire from workflows or future update/delete calls.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QuestionnaireDetail" + }, + "examples": { + "Created": { + "value": { + "questionnaire_id": "11111111-2222-3333-4444-555555555555", + "title": "Customer Onboarding", + "description": "Additional information needed for verification", + "languages": [ + "en" + ], + "default_language": "en", + "is_active": true, + "is_simple_questionnaire": true, + "graph": { + "start_node": "occupation", + "nodes": { + "occupation": { + "element_type": "SHORT_TEXT", + "is_required": true, + "title": { + "en": "What is your occupation?" + }, + "next": "source_of_funds" + }, + "source_of_funds": { + "element_type": "MULTIPLE_CHOICE", + "is_required": true, + "title": { + "en": "Source of funds" + }, + "choices": [ + { + "value": "employment", + "label": { + "en": "Employment" + } + }, + { + "value": "business", + "label": { + "en": "Business" + } + } + ], + "next": null + } + } + }, + "sections": [], + "questionnaire_group_id": "11111111-2222-3333-4444-555555555555", + "version": 1, + "status": "published", + "published_at": "2026-04-23T10:00:00Z", + "is_editable": false + } + } + } + } + } + }, + "400": { + "description": "Validation error. Common causes: an unknown `element_type`, a translatable field missing a translation for one of `languages`, `languages` missing `en`, `default_language` not in `languages`, an option without a `label`, a question that has `branches`/`required_if`/`repeatable_config` (not supported by this endpoint), or sending a raw `graph` instead of `form_elements`. The body is a DRF validation error keyed by field name.", + "content": { + "application/json": { + "examples": { + "Graph not accepted": { + "value": { + "graph": "The v3 questionnaire API only accepts simple `form_elements`, not `graph`." + } + }, + "Missing label": { + "value": { + "form_elements": [ + { + "label": [ + "This field is required." + ] + } + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "415": { + "description": "Unsupported media type. Set `Content-Type: application/json` and send a JSON body." + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error while creating the questionnaire. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X POST 'https://verification.didit.me/v3/questionnaires/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"title\": \"Customer Onboarding\",\n \"default_language\": \"en\",\n \"languages\": [\"en\"],\n \"form_elements\": [\n {\n \"id\": \"occupation\",\n \"element_type\": \"short_text\",\n \"label\": {\"en\": \"What is your occupation?\"},\n \"is_required\": true\n }\n ]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.questionnaires.create(\n title=\"Customer Onboarding\",\n description=\"Additional information needed for verification\",\n languages=[\"en\"],\n default_language=\"en\",\n is_active=True,\n form_elements=[{\n \"id\": \"occupation\",\n \"element_type\": \"short_text\",\n \"label\": {\"en\": \"What is your occupation?\"},\n \"placeholder\": {\"en\": \"Software engineer\"},\n \"is_required\": True,\n }, {\n \"id\": \"source_of_funds\",\n \"element_type\": \"multiple_choice\",\n \"label\": {\"en\": \"Source of funds\"},\n \"is_required\": True,\n \"options\": [{\n \"value\": \"employment\",\n \"label\": {\"en\": \"Employment\"},\n }, {\n \"value\": \"business\",\n \"label\": {\"en\": \"Business\"},\n }, {\n \"value\": \"investments\",\n \"label\": {\"en\": \"Investments\"},\n }, {\n \"value\": \"other\",\n \"label\": {\"en\": \"Other\"},\n }],\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.questionnaires.create({\n title: \"Customer Onboarding\",\n description: \"Additional information needed for verification\",\n languages: [\"en\"],\n default_language: \"en\",\n is_active: true,\n form_elements: [{\n id: \"occupation\",\n element_type: \"short_text\",\n label: { en: \"What is your occupation?\" },\n placeholder: { en: \"Software engineer\" },\n is_required: true,\n }, {\n id: \"source_of_funds\",\n element_type: \"multiple_choice\",\n label: { en: \"Source of funds\" },\n is_required: true,\n options: [{\n value: \"employment\",\n label: { en: \"Employment\" },\n }, {\n value: \"business\",\n label: { en: \"Business\" },\n }, {\n value: \"investments\",\n label: { en: \"Investments\" },\n }, {\n value: \"other\",\n label: { en: \"Other\" },\n }],\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Questionnaires.Create(ctx, didit.SimpleQuestionnaireRequest{\n\tTitle: \"Customer Onboarding\",\n\tDescription: didit.String(\"Additional information needed for verification\"),\n\tLanguages: []string{\"en\"},\n\tDefaultLanguage: didit.String(\"en\"),\n\tIsActive: didit.Bool(true),\n\tFormElements: []didit.SimpleQuestionnaireElement{{\n\t\tID: didit.String(\"occupation\"),\n\t\tElementType: \"short_text\",\n\t\tLabel: map[string]string{\"en\": \"What is your occupation?\"},\n\t\tPlaceholder: didit.Some(map[string]string{\"en\": \"Software engineer\"}),\n\t\tIsRequired: didit.Bool(true),\n\t}, {\n\t\tID: didit.String(\"source_of_funds\"),\n\t\tElementType: \"multiple_choice\",\n\t\tLabel: map[string]string{\"en\": \"Source of funds\"},\n\t\tIsRequired: didit.Bool(true),\n\t\tOptions: didit.Some([]didit.SimpleQuestionnaireElementOptions{{\n\t\t\tValue: didit.String(\"employment\"),\n\t\t\tLabel: map[string]string{\"en\": \"Employment\"},\n\t\t}, {\n\t\t\tValue: didit.String(\"business\"),\n\t\t\tLabel: map[string]string{\"en\": \"Business\"},\n\t\t}, {\n\t\t\tValue: didit.String(\"investments\"),\n\t\t\tLabel: map[string]string{\"en\": \"Investments\"},\n\t\t}, {\n\t\t\tValue: didit.String(\"other\"),\n\t\t\tLabel: map[string]string{\"en\": \"Other\"},\n\t\t}}),\n\t}},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SimpleQuestionnaireElement;\nimport me.didit.types.SimpleQuestionnaireRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.questionnaires().create(SimpleQuestionnaireRequest.builder()\n .title(\"Customer Onboarding\")\n .description(\"Additional information needed for verification\")\n .languages(List.of(\"en\"))\n .defaultLanguage(\"en\")\n .isActive(true)\n .formElements(List.of(\n SimpleQuestionnaireElement.builder()\n .id(\"occupation\")\n .elementType(SimpleQuestionnaireElement.ElementType.of(\"short_text\"))\n .label(Map.of(\"en\", \"What is your occupation?\"))\n .placeholder(Map.of(\"en\", \"Software engineer\"))\n .isRequired(true)\n .build(),\n SimpleQuestionnaireElement.builder()\n .id(\"source_of_funds\")\n .elementType(SimpleQuestionnaireElement.ElementType.of(\"multiple_choice\"))\n .label(Map.of(\"en\", \"Source of funds\"))\n .isRequired(true)\n .options(List.of(\n SimpleQuestionnaireElement.Options.builder()\n .value(\"employment\")\n .label(Map.of(\"en\", \"Employment\"))\n .build(),\n SimpleQuestionnaireElement.Options.builder()\n .value(\"business\")\n .label(Map.of(\"en\", \"Business\"))\n .build(),\n SimpleQuestionnaireElement.Options.builder()\n .value(\"investments\")\n .label(Map.of(\"en\", \"Investments\"))\n .build(),\n SimpleQuestionnaireElement.Options.builder()\n .value(\"other\")\n .label(Map.of(\"en\", \"Other\"))\n .build()))\n .build()))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "questionnaires->create([\n 'title' => 'Customer Onboarding',\n 'description' => 'Additional information needed for verification',\n 'languages' => ['en'],\n 'default_language' => 'en',\n 'is_active' => true,\n 'form_elements' => [[\n 'id' => 'occupation',\n 'element_type' => 'short_text',\n 'label' => ['en' => 'What is your occupation?'],\n 'placeholder' => ['en' => 'Software engineer'],\n 'is_required' => true,\n ], [\n 'id' => 'source_of_funds',\n 'element_type' => 'multiple_choice',\n 'label' => ['en' => 'Source of funds'],\n 'is_required' => true,\n 'options' => [[\n 'value' => 'employment',\n 'label' => ['en' => 'Employment'],\n ], [\n 'value' => 'business',\n 'label' => ['en' => 'Business'],\n ], [\n 'value' => 'investments',\n 'label' => ['en' => 'Investments'],\n ], [\n 'value' => 'other',\n 'label' => ['en' => 'Other'],\n ]],\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.questionnaires.create(\n title: \"Customer Onboarding\",\n description: \"Additional information needed for verification\",\n languages: [\"en\"],\n default_language: \"en\",\n is_active: true,\n form_elements: [{\n id: \"occupation\",\n element_type: \"short_text\",\n label: { en: \"What is your occupation?\" },\n placeholder: { en: \"Software engineer\" },\n is_required: true,\n }, {\n id: \"source_of_funds\",\n element_type: \"multiple_choice\",\n label: { en: \"Source of funds\" },\n is_required: true,\n options: [{\n value: \"employment\",\n label: { en: \"Employment\" },\n }, {\n value: \"business\",\n label: { en: \"Business\" },\n }, {\n value: \"investments\",\n label: { en: \"Investments\" },\n }, {\n value: \"other\",\n label: { en: \"Other\" },\n }],\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Questionnaires.CreateAsync(new SimpleQuestionnaireRequest\n{\n Title = \"Customer Onboarding\",\n Description = \"Additional information needed for verification\",\n Languages = new string[] { \"en\" },\n DefaultLanguage = \"en\",\n IsActive = true,\n FormElements = new SimpleQuestionnaireElement[]\n {\n new()\n {\n Id = \"occupation\",\n ElementType = \"short_text\",\n Label = new Dictionary { [\"en\"] = \"What is your occupation?\" },\n Placeholder = new Dictionary { [\"en\"] = \"Software engineer\" },\n IsRequired = true,\n },\n new()\n {\n Id = \"source_of_funds\",\n ElementType = \"multiple_choice\",\n Label = new Dictionary { [\"en\"] = \"Source of funds\" },\n IsRequired = true,\n Options = new SimpleQuestionnaireElementOptions[]\n {\n new()\n {\n Value = \"employment\",\n Label = new Dictionary { [\"en\"] = \"Employment\" },\n },\n new()\n {\n Value = \"business\",\n Label = new Dictionary { [\"en\"] = \"Business\" },\n },\n new()\n {\n Value = \"investments\",\n Label = new Dictionary { [\"en\"] = \"Investments\" },\n },\n new()\n {\n Value = \"other\",\n Label = new Dictionary { [\"en\"] = \"Other\" },\n },\n },\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/questionnaires/{questionnaire_uuid}/": { + "get": { + "summary": "Get questionnaire", + "description": "Fetch full questionnaire detail for one version by `questionnaire_uuid`: title, `languages`, canonical `graph`, derived `sections`, and version metadata.", + "operationId": "get_questionnaire", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "parameters": [ + { + "name": "questionnaire_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the questionnaire (the `uuid` value returned by the list endpoint, also returned as `questionnaire_id` on detail endpoints)." + } + ], + "responses": { + "200": { + "description": "Full questionnaire detail including the canonical `graph`, the derived `sections` view, version metadata, and `is_editable`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QuestionnaireDetail" + }, + "examples": { + "Published questionnaire": { + "value": { + "questionnaire_id": "11111111-2222-3333-4444-555555555555", + "title": "Customer Onboarding", + "description": "Additional information needed for verification", + "languages": [ + "en" + ], + "default_language": "en", + "is_active": true, + "is_simple_questionnaire": true, + "graph": { + "start_node": "occupation", + "nodes": { + "occupation": { + "element_type": "SHORT_TEXT", + "is_required": true, + "title": { + "en": "What is your occupation?" + }, + "next": "source_of_funds" + }, + "source_of_funds": { + "element_type": "MULTIPLE_CHOICE", + "is_required": true, + "title": { + "en": "Source of funds" + }, + "choices": [ + { + "value": "employment", + "label": { + "en": "Employment" + } + }, + { + "value": "business", + "label": { + "en": "Business" + } + } + ], + "next": null + } + } + }, + "sections": [ + { + "title": {}, + "description": {}, + "items": [ + { + "uuid": "11111111-2222-3333-4444-666666666666", + "value": "occupation", + "element_type": "SHORT_TEXT", + "is_required": true, + "title": { + "en": "What is your occupation?" + } + } + ] + } + ], + "questionnaire_group_id": "11111111-2222-3333-4444-555555555555", + "version": 1, + "status": "published", + "published_at": "2026-04-23T10:00:00Z", + "is_editable": false + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No questionnaire with this `questionnaire_uuid` exists for the authenticated application. Either the UUID is wrong or it belongs to another application — the API deliberately does not distinguish the two cases.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X GET 'https://verification.didit.me/v3/questionnaires/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.questionnaires.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.questionnaires.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Questionnaires.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.questionnaires().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "questionnaires->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.questionnaires.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Questionnaires.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "patch": { + "summary": "Update questionnaire", + "description": "Update a questionnaire. Versioning depends on the `status` field you send and the current state of the version: (1) no `status` on a **published** version → the published version is updated in place (same `questionnaire_id`, same `version`); (2) no `status` on a **draft** → the draft is updated and immediately published; (3) `status: \"published\"` on a **published** version → a NEW published version is created under the same `questionnaire_group_id` (new `questionnaire_id`, `version` + 1) and returned, and any existing draft in the group is deleted; (4) `status: \"draft\"` keeps a draft as a draft (autosave) but is rejected with 400 on a published version (`\"Cannot revert a published version to draft.\"`).", + "operationId": "update_questionnaire", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "parameters": [ + { + "name": "questionnaire_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the questionnaire to update." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleQuestionnaireUpdateRequest" + }, + "examples": { + "Rename": { + "summary": "Rename a questionnaire", + "value": { + "title": "Updated Customer Onboarding" + } + }, + "Replace questions": { + "summary": "Replace the full linear question list", + "value": { + "form_elements": [ + { + "id": "occupation", + "element_type": "short_text", + "label": { + "en": "What is your occupation?" + }, + "is_required": true + }, + { + "id": "source_of_funds", + "element_type": "multiple_choice", + "label": { + "en": "Source of funds" + }, + "options": [ + { + "value": "employment", + "label": { + "en": "Employment" + } + }, + { + "value": "business", + "label": { + "en": "Business" + } + } + ] + } + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Questionnaire updated. The body is the latest state of the resulting version — the in-place updated version, or the newly created published version when you sent `status: \"published\"` on a published row (in that case `questionnaire_id` and `version` differ from the ones you patched). `questionnaire_id` in the response is the per-version UUID; use `questionnaire_group_id` if you need the stable group identifier.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QuestionnaireDetail" + } + } + } + }, + "400": { + "description": "Validation error — unknown `element_type`, missing translation, unsupported branching/conditional fields, `languages` missing `en`, `default_language` not in `languages`, option without a label, sending a raw `graph`, or `status: \"draft\"` on a published version (`\"Cannot revert a published version to draft.\"`). Response body is a DRF validation error keyed by field name." + }, + "403": { + "description": "API key missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No questionnaire with this `questionnaire_uuid` exists for the authenticated application." + }, + "415": { + "description": "Unsupported media type. Set `Content-Type: application/json`." + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X PATCH 'https://verification.didit.me/v3/questionnaires/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"title\": \"Updated Customer Onboarding\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.questionnaires.update(\n \"11111111-2222-3333-4444-555555555555\",\n title=\"Updated Customer Onboarding\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.questionnaires.update(\"11111111-2222-3333-4444-555555555555\", {\n title: \"Updated Customer Onboarding\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Questionnaires.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SimpleQuestionnaireUpdateRequest{\n\tTitle: didit.String(\"Updated Customer Onboarding\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SimpleQuestionnaireUpdateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.questionnaires().update(\"11111111-2222-3333-4444-555555555555\", SimpleQuestionnaireUpdateRequest.builder()\n .title(\"Updated Customer Onboarding\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "questionnaires->update('11111111-2222-3333-4444-555555555555', [\n 'title' => 'Updated Customer Onboarding',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.questionnaires.update(\n \"11111111-2222-3333-4444-555555555555\",\n title: \"Updated Customer Onboarding\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Questionnaires.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new SimpleQuestionnaireUpdateRequest\n{\n Title = \"Updated Customer Onboarding\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "summary": "Delete questionnaire", + "description": "Permanently delete one questionnaire version (no undo). Stored questionnaire responses linked to this version are deleted with it, and sessions referencing it lose the link; other versions in the same group are not deleted. Workflows that referenced it keep running but the questionnaire step can no longer load it.", + "operationId": "delete_questionnaire", + "x-didit-sdk": { + "resource": "questionnaires", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Questionnaires" + ], + "parameters": [ + { + "name": "questionnaire_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Per-version UUID of the questionnaire to delete." + } + ], + "responses": { + "204": { + "description": "Questionnaire deleted. The response body is empty." + }, + "403": { + "description": "API key missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No questionnaire with this `questionnaire_uuid` exists for the authenticated application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + }, + "500": { + "description": "Unexpected server error. Safe to retry." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl -X DELETE 'https://verification.didit.me/v3/questionnaires/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.questionnaires.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.questionnaires.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Questionnaires.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.questionnaires().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "questionnaires->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.questionnaires.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Questionnaires.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ] + } + }, + "/v3/users/": { + "get": { + "summary": "List users", + "description": "List [users](/entities/users) Didit assembles from KYC sessions sharing the same `vendor_data` (free-form string, NOT a UUID), plus users created via the API. Paginated, ordered by `last_session_at` desc. This endpoint does not support filtering or search parameters — fetch pages and filter client-side.", + "operationId": "list_users", + "x-didit-sdk": { + "resource": "users", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "description": "Page size. Defaults to 50 when omitted. There is no enforced maximum, but keep pages small for latency." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "description": "Zero-based offset of the first record to return. Prefer following the `next` URL from the previous page over computing this by hand." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/users/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.users.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.users.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Users.ListAutoPaging(ctx, didit.UsersListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.users().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.users.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Users.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated list of users with verification status, session counts, and feature breakdown.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows — exact up to 100, capped at 100 beyond that for performance. Do not use it to detect the last page; rely on `next` being `null` instead." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserListItem" + } + } + } + }, + "examples": { + "Example": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": null, + "full_name": "John Michael Doe", + "date_of_birth": "1990-05-15", + "effective_name": "John Michael Doe", + "status": "ACTIVE", + "portrait_image_url": "https:///...", + "session_count": 3, + "approved_count": 2, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [ + "USA" + ], + "approved_emails": [ + "john@example.com" + ], + "approved_phones": [ + "+14155551234" + ], + "features": { + "ID_VERIFICATION": "Approved", + "LIVENESS": "Approved", + "FACE_MATCH": "Approved", + "AML": "Approved" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-01T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2025-06-01T08:00:00Z" + }, + { + "didit_internal_id": "6ed99d53-e8f5-4cf8-9ac1-4a506cb40f6b", + "vendor_data": null, + "display_name": "Jane S.", + "full_name": "Jane Elizabeth Smith", + "date_of_birth": "1985-11-22", + "effective_name": "Jane S.", + "status": "FLAGGED", + "portrait_image_url": null, + "session_count": 1, + "approved_count": 0, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [], + "approved_emails": [], + "approved_phones": [], + "features": { + "ID_VERIFICATION": "In Review" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "In Review" + } + ], + "last_session_at": "2025-06-20T14:00:00Z", + "last_activity_at": "2025-06-20T14:05:00Z", + "first_session_at": "2025-06-20T14:00:00Z", + "tags": [], + "created_at": "2025-06-20T14:00:00Z" + } + ] + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "The key cannot list users for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/users/{vendor_data}/": { + "get": { + "summary": "Get user", + "description": "Fetch one [user](/entities/users) by `vendor_data` (exact match). Adds `metadata`, `comments`, and `updated_at` to the list view, and switches `tags` to the detailed tag-link shape.", + "operationId": "get_user", + "x-didit-sdk": { + "resource": "users", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the user — a free-form string (NOT a UUID). This is the same value you passed as `vendor_data` when creating the session that first introduced this user, matched exactly as sent.", + "example": "user-abc-123" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/users/user-abc-123/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.users.retrieve(\n \"user-abc-123\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.users.retrieve(\"user-abc-123\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Users.Retrieve(ctx, \"user-abc-123\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.users().retrieve(\"user-abc-123\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->retrieve('user-abc-123');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.users.retrieve(\"user-abc-123\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Users.RetrieveAsync(\"user-abc-123\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Full user detail including metadata, comments/activity log, and aggregated session data.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDetailItem" + }, + "examples": { + "Active user": { + "value": { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": null, + "full_name": "John Michael Doe", + "date_of_birth": "1990-05-15", + "effective_name": "John Michael Doe", + "status": "ACTIVE", + "metadata": { + "tier": "premium", + "source": "website" + }, + "portrait_image_url": "https:///...", + "session_count": 3, + "approved_count": 2, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [ + "USA" + ], + "approved_emails": [ + "john@example.com" + ], + "approved_phones": [ + "+14155551234" + ], + "features": { + "ID_VERIFICATION": "Approved", + "LIVENESS": "Approved", + "FACE_MATCH": "Approved", + "AML": "Approved" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-01T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "tag": { + "uuid": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b", + "name": "VIP", + "color": "#2567FF", + "description": null, + "source": "custom", + "created_at": "2025-05-01T09:00:00Z", + "updated_at": "2025-05-01T09:00:00Z" + }, + "added_by_email": "analyst@acme.com", + "added_by_name": "Jane Analyst", + "created_at": "2025-06-02T11:00:00Z" + } + ], + "comments": [ + { + "uuid": "c1111111-2222-4333-8444-555555555555", + "comment_type": "STATUS_CHANGED", + "comment": null, + "actor_email": "analyst@acme.com", + "actor_name": "Jane Analyst", + "previous_status": "FLAGGED", + "new_status": "ACTIVE", + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "mentioned_emails": [], + "created_at": "2025-06-01T10:30:00Z" + } + ], + "created_at": "2025-06-01T08:00:00Z", + "updated_at": "2025-06-15T10:30:00Z" + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "Invalid or revoked API key, or the key cannot read this application's users. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No user with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update user", + "description": "Partial update of a user: `display_name`, `full_name`, `date_of_birth`, `status`, `metadata`, `approved_emails`/`approved_phones`/`issuing_states`. `metadata` fully replaces. Changing `status` here works like [Update User Status](#patch-/v3/users/-vendor_data-/update-status/): it records a `STATUS_CHANGED` activity with the optional `reason`, and `BLOCKED` adds the `vendor_data` to the system blocklist (moving away from `BLOCKED` removes that entry). Changes to `display_name`, `full_name`, `date_of_birth` or `metadata` are recorded as a `PROFILE_UPDATED` activity.", + "operationId": "update_user", + "x-didit-sdk": { + "resource": "users", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the user (free-form string, NOT a UUID).", + "example": "user-abc-123" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/users/user-abc-123/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"display_name\": \"Jane S.\", \"status\": \"FLAGGED\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.users.update(\n \"user-abc-123\",\n display_name=\"Jane S.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.users.update(\"user-abc-123\", {\n display_name: \"Jane S.\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Users.Update(ctx, \"user-abc-123\", didit.UsersUpdateParams{\n\tDisplayName: didit.String(\"Jane S.\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.UsersUpdateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.users().update(\"user-abc-123\", UsersUpdateParams.builder()\n .displayName(\"Jane S.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->update('user-abc-123', [\n 'display_name' => 'Jane S.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.users.update(\n \"user-abc-123\",\n display_name: \"Jane S.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Users.UpdateAsync(\"user-abc-123\", new UsersUpdateParams\n{\n DisplayName = \"Jane S.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "User updated. The full updated user record (same shape as Get User) is returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDetailItem" + }, + "examples": { + "Updated": { + "value": { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": "Jane S.", + "full_name": "Jane Elizabeth Smith", + "date_of_birth": "1985-11-22", + "effective_name": "Jane S.", + "status": "FLAGGED", + "metadata": { + "tier": "premium" + }, + "portrait_image_url": "https:///...", + "session_count": 3, + "approved_count": 2, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [ + "USA" + ], + "approved_emails": [ + "john@example.com" + ], + "approved_phones": [ + "+14155551234" + ], + "features": { + "ID_VERIFICATION": "Approved", + "LIVENESS": "Approved", + "FACE_MATCH": "Approved", + "AML": "Approved" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-01T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "tag": { + "uuid": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b", + "name": "VIP", + "color": "#2567FF", + "description": null, + "source": "custom", + "created_at": "2025-05-01T09:00:00Z", + "updated_at": "2025-05-01T09:00:00Z" + }, + "added_by_email": "analyst@acme.com", + "added_by_name": "Jane Analyst", + "created_at": "2025-06-02T11:00:00Z" + } + ], + "comments": [], + "created_at": "2025-06-01T08:00:00Z", + "updated_at": "2025-06-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Invalid body — typically an unknown `status` or a `date_of_birth` not in `YYYY-MM-DD`.", + "content": { + "application/json": { + "examples": { + "Bad status": { + "value": { + "status": [ + "\"INVALID\" is not a valid choice." + ] + } + }, + "Bad date_of_birth": { + "value": { + "date_of_birth": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + }, + "Reason too long": { + "value": { + "reason": [ + "Ensure this field has no more than 1000 characters." + ] + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "The key cannot update users for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No user with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "full_name": { + "type": "string", + "nullable": true, + "maxLength": 512, + "description": "Full name of the user" + }, + "display_name": { + "type": "string", + "nullable": true, + "description": "Custom display name for this user" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth in YYYY-MM-DD format" + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "User lifecycle status. A change is recorded as a `STATUS_CHANGED` activity, and `BLOCKED` adds the `vendor_data` to the system blocklist (moving away from `BLOCKED` removes that entry)." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Arbitrary JSON object you attach to the user. **Fully replaces** the existing metadata on update — merge client-side if you only want to add keys." + }, + "approved_emails": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Verified email addresses for this user, e.g. `[\"john@example.com\"]`. Fully replaces the stored list." + }, + "approved_phones": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Verified phone numbers for this user. Fully replaces the stored list." + }, + "issuing_states": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Issuing countries (ISO 3166-1 alpha-3), e.g. `[\"USA\"]`. Fully replaces the stored list." + }, + "reason": { + "type": "string", + "nullable": true, + "maxLength": 1000, + "description": "Why the status changed. Recorded on the status-change entry of the user's activity (`comments[].comment`, and **Activity** in the Business Console), only when the status actually changes. Webhooks do not carry it." + } + } + }, + "examples": { + "Rename": { + "summary": "Set display name", + "value": { + "display_name": "Jane S." + } + }, + "Override status": { + "summary": "Flag the user for review", + "value": { + "status": "FLAGGED" + } + }, + "Add metadata": { + "summary": "Replace metadata JSON", + "value": { + "metadata": { + "tier": "premium", + "risk_level": "low" + } + } + }, + "Fix extracted DOB": { + "summary": "Correct extracted profile fields", + "value": { + "full_name": "Jane Elizabeth Smith", + "date_of_birth": "1985-11-22" + } + }, + "Flag with reason": { + "summary": "Flag the user and record why", + "value": { + "status": "FLAGGED", + "reason": "Address does not match the billing country" + } + } + } + } + } + } + } + }, + "/v3/users/{vendor_data}/generate-pdf/": { + "get": { + "summary": "Download the full user history PDF report", + "description": "Render and download one compliance-ready PDF containing the full verification history of the user identified by `vendor_data`. It is the same document the Console produces from **User detail → Actions → Download PDF**, so periodic compliance exports no longer need a person clicking in the Console.\n\n**What the report contains.** A cover page with the profile summary (name, date of birth, issuing states, approved emails and phones), the latest status of every feature, the session counters and an index of the sessions included — followed by the standard report of every included session, in chronological order (oldest first). Each session report is the same document `GET /v3/session/{sessionId}/generate-pdf/` returns for that session.\n\n**Eligible sessions.** Only User Verification (KYC) sessions in `Approved`, `Declined`, `In Review` or `Kyc Expired` are included; sessions in any other status are skipped. If the user has no session in one of those statuses the endpoint returns `403` — see below.\n\n**Cap.** The report is capped at the **20 most recent** reportable sessions. When a user has more, the newest 20 are included and the cover page states how many older sessions were left out. Use `GET /v3/sessions/?vendor_data=…` for the complete list as JSON.\n\n**Branding.** If your application has white-label customization, the cover and every session report are rendered with your logo and privacy-policy URL; otherwise they carry Didit branding. Reports are rendered in English — there is no language parameter.\n\n**No caching.** Every call re-renders the PDF from the current data, so a report generated after a manual review reflects the reviewer's decision. Two calls for the same user can produce byte-different files — archive the downloaded file if you need an immutable copy.\n\n**Latency.** Rendering is synchronous and downloads every stored image of every included session, so a user with many media-heavy sessions can take tens of seconds. Use a generous client read timeout (120 s recommended) and stream the body to disk. Generation stops at a server-side time budget instead of running until your client gives up: if that budget is reached, the response is still a valid PDF containing the most recent sessions, and the cover states how many were omitted.\n\n**Scope.** The user is resolved inside the application that owns the API key. A key can never export a user of another application — such a user does not exist for it and the endpoint returns `404`.\n\n**Trailing slash.** The canonical route ends with a trailing slash (`…/generate-pdf/`). A request without it is served directly with the same response — there is no `301` redirect, so no client has to follow redirects and `curl` does not need `-L`. Use the slashed URL as in the samples.", + "operationId": "get_user_generate_pdf", + "x-didit-sdk": { + "resource": "users", + "method": "generate_pdf", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the user — a free-form string (NOT a UUID). This is the same value you passed as `vendor_data` when creating the user's sessions, matched exactly as sent. URL-encode it; a value containing `/` cannot be addressed by this route.", + "example": "user-abc-123" + } + ], + "responses": { + "200": { + "description": "The rendered PDF document, returned directly as binary `application/pdf` — there is no JSON wrapper and no download-URL indirection. The body starts with the `%PDF` magic bytes. Save it to a `.pdf` file or stream it through to your caller.", + "headers": { + "Content-Disposition": { + "description": "`attachment; filename=user_{didit_internal_id}.pdf` — the file is named after the user's stable Didit identifier (`didit_internal_id` on `GET /v3/users/{vendor_data}/`), not after `vendor_data`.", + "schema": { + "type": "string", + "example": "attachment; filename=user_f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418.pdf" + } + }, + "Content-Length": { + "description": "Total size of the PDF in bytes; the body is not chunked.", + "schema": { + "type": "integer", + "example": 148213 + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "Authorization or eligibility failure. Distinguish the two by the `detail` string:\n\n- `\"You do not have permission to perform this action.\"` — the API key is invalid or revoked, or the request carries a Console user token instead of an application API key.\n- `\"This user has no sessions in review, declined, approved or kyc expired to report.\"` — the user exists in your application but none of its sessions is in a reportable status yet (for example every session is still `In Progress`, or the user was created through `POST /v3/users/create/` and has never verified). Wait for a `status.updated` webhook before requesting the report.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "This user has no sessions in review, declined, approved or kyc expired to report." + } + } + }, + "examples": { + "Invalid credentials": { + "summary": "Revoked or invalid API key, or a Console user token", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "No reportable sessions": { + "summary": "User exists but has no Approved / Declined / In Review / Kyc Expired session", + "value": { + "detail": "This user has no sessions in review, declined, approved or kyc expired to report." + } + } + } + } + } + }, + "404": { + "description": "No user with the supplied `vendor_data` exists in the application behind the API key. Users of other applications are invisible to this key and return the same `404`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "User not found." + } + } + }, + "examples": { + "Unknown user": { + "summary": "vendor_data does not match any user of this application", + "value": { + "detail": "User not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. PDF generation has a dedicated limit of **50 requests per minute per credential** (API key, Bearer token, or source IP when unauthenticated), shared between this endpoint and `GET /v3/session/{sessionId}/generate-pdf/`, in addition to the global 600/min GET limit. Wait `Retry-After` seconds (also reflected in the `X-RateLimit-*` headers), then retry. Reports are not cached server-side, so download each report once and store the file instead of re-fetching it.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying the request.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Maximum number of PDF generations allowed in the current window (50).", + "schema": { + "type": "integer", + "example": 50 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests remaining in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix timestamp (seconds) at which the current window resets.", + "schema": { + "type": "integer", + "example": 1750000000 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable explanation of the rate-limit breach.", + "example": "Session PDF generation rate limit exceeded. You can make up to 50 requests per minute." + } + } + }, + "examples": { + "Throttled": { + "summary": "More than 50 PDF generations in one minute from the same credential", + "value": { + "detail": "Session PDF generation rate limit exceeded. You can make up to 50 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --fail \\\n 'https://verification.didit.me/v3/users/user-abc-123/generate-pdf/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n --output user-history.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.users.generate_pdf(\n \"user-abc-123\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.users.generatePdf(\"user-abc-123\");\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Users.GeneratePDF(ctx, \"user-abc-123\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.users().generatePdf(\"user-abc-123\")\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->generatePdf('user-abc-123');\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.users.generate_pdf(\"user-abc-123\")\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Users.GeneratePdfAsync(\"user-abc-123\");\nawait content.SaveToAsync(\"download.pdf\");" + } + ] + } + }, + "/v3/users/delete/": { + "post": { + "summary": "Batch delete users", + "description": "**Permanently delete** users: the user records are removed permanently. For everyday blocking prefer [Update User Status](#patch-/v3/users/-vendor_data-/update-status/) with `BLOCKED`. Select users with `vendor_data_list`, with `didit_internal_id_list`, or with both at once; or pass `delete_all: true`. This endpoint never narrows `delete_all` with filters. The Console's People filters are refused with a `400` (see the `400` response), so a narrowed intent does not turn into a full wipe. Two exceptions: `filter_has_approved_sessions` and `filter_has_declined_sessions` are not refused and not applied, so with `delete_all: true` they still delete every user; do not send them.\n\n**Biometric templates:** unless you set `retain_face_embeddings` to `true`, every image-free biometric template retained for these users after session deletion (see `/v3/biometric-templates/`) is purged before the user is deleted, so the person stops matching in duplicate detection and Face Search. With `retain_face_embeddings: true` and `face_retention_days`, you choose to keep one image-free biometric template per person for that many days, and nothing else about the person is kept. If a template cannot be purged because a biometric store is unavailable, the call returns `503`, no user in the request is deleted, and repeating the request finishes the job.\n\n**Filed regulatory reports:** if the selected data is in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "batch_delete_users", + "x-didit-sdk": { + "resource": "users", + "method": "batch_delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Provide `vendor_data_list`, `didit_internal_id_list`, or `delete_all: true`. The two lists can be combined and are OR'd. When `delete_all` is `true` it takes precedence and both lists are ignored.", + "properties": { + "vendor_data_list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Your own identifiers to delete, matched exactly as sent." + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit internal user IDs to delete - the `didit_internal_id` returned by the create and get endpoints." + }, + "delete_all": { + "type": "boolean", + "default": false, + "description": "If `true`, deletes every user in the application." + }, + "retain_face_embeddings": { + "type": "boolean", + "default": false, + "description": "Your explicit choice to keep one image-free biometric template of each deleted person for duplicate detection, for `face_retention_days`. Everything else about the person is deleted. Omit it, or send `false`, to delete the templates with the users. When `true`, `face_retention_days` is required." + }, + "face_retention_days": { + "type": "integer", + "minimum": 1, + "maximum": 3650, + "description": "Number of days the image-free biometric template is kept when `retain_face_embeddings` is `true`. Required with it, and rejected without it with a `400` on `face_retention_days`.", + "example": 365 + } + } + }, + "examples": { + "Specific users": { + "summary": "Delete users by vendor_data", + "value": { + "vendor_data_list": [ + "user-123", + "user-456" + ] + } + }, + "By Didit internal ID": { + "summary": "Delete users by didit_internal_id", + "value": { + "didit_internal_id_list": [ + "3f1c0d3e-8f0a-4c2e-9a3d-9b1f5f4a2c11" + ] + } + }, + "All users": { + "summary": "Wipe every user in this application", + "value": { + "delete_all": true + } + }, + "Keep face templates": { + "summary": "Delete users and keep anonymous face templates for 365 days", + "value": { + "vendor_data_list": [ + "user-123" + ], + "retain_face_embeddings": true, + "face_retention_days": 365 + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/users/delete/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vendor_data_list\": [\"user-123\", \"user-456\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.users.batch_delete(\n vendor_data_list=[\"user-123\", \"user-456\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.users.batchDelete({\n vendor_data_list: [\"user-123\", \"user-456\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Users.BatchDelete(ctx, didit.UsersBatchDeleteParams{\n\tVendorDataList: didit.Some([]string{\"user-123\", \"user-456\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.UsersBatchDeleteParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.users().batchDelete(UsersBatchDeleteParams.builder()\n .vendorDataList(List.of(\"user-123\", \"user-456\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->batchDelete([\n 'vendor_data_list' => ['user-123', 'user-456'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.users.batch_delete(vendor_data_list: [\"user-123\", \"user-456\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Users.BatchDeleteAsync(new UsersBatchDeleteParams\n{\n VendorDataList = new string[] { \"user-123\", \"user-456\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Users deleted. Returns the number of users actually deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deleted": { + "type": "integer", + "description": "Number of users deleted (excludes vendor_data values that didn't match anything)." + } + } + }, + "examples": { + "Deleted some": { + "value": { + "deleted": 2 + } + }, + "Nothing matched": { + "value": { + "deleted": 0 + } + } + } + } + } + }, + "400": { + "description": "Validation error; nothing is deleted. Errors are keyed by field name. With no selector (neither list and `delete_all` not `true`) the body is `{\"vendor_data_list\": [\"This field is required unless delete_all is true.\"]}`; an empty list is refused under its own key with `\"This list may not be empty.\"`. A Console-only filter (`filter_search`, `filter_status`, `filter_country`, `filter_date_from`, `filter_date_to`, `filter_tag`, `filter_latest_verification_status`, `filter_risk`, `filter_has_sessions`, `filter_has_open_alerts`, `filter_aml_monitoring`, `filter_can_sign_in`, `filter_auth_status`, `filter_auth_method` or `filter_auth_mfa_enabled`) is refused whenever it carries a value, even `false`, with an object keyed by each such field. Any other unknown `filter_*` key is refused with `\"Unknown filter.\"`.", + "content": { + "application/json": { + "examples": { + "Missing selector": { + "summary": "Neither list nor delete_all provided", + "value": { + "vendor_data_list": [ + "This field is required unless delete_all is true." + ] + } + }, + "Console-only filter": { + "value": { + "filter_has_sessions": "This filter is Console-only and is not supported by this endpoint." + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "Invalid or revoked API key, or the key cannot delete users for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + }, + "503": { + "description": "A biometric store was unavailable while retaining or purging a biometric template. Nothing is deleted; repeat the same request. Retries are idempotent and converge on a single template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Retryable": { + "value": { + "detail": "The biometric-template operation could not finish because an external store is unavailable. The operation is retryable." + } + } + } + } + } + }, + "409": { + "description": "Nothing is deleted. `code` says why: `in_filed_report` when the selection includes a person in a finalized regulatory report, `subject_deletion_in_progress` when another request is still deleting one of the selected people. Repeat the request once that deletion has finished.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/InFiledReportError" + }, + { + "$ref": "#/components/schemas/SubjectDeletionInProgressError" + } + ] + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/sessions/delete/": { + "post": { + "summary": "Bulk delete KYC sessions by session_number", + "description": "Bulk delete User Verification (KYC) sessions by their numeric `session_number`, or every KYC session in your application with `delete_all: true`.\n\n`session_number` is the human-friendly incrementing counter shown in the Console session list and returned as `session_number` by the create-session and decision endpoints; it is **not** the `session_id` UUID. To delete a session by UUID, or to delete a Business Verification (KYB) session, which this endpoint never touches, use `DELETE /v3/session/{sessionId}/delete/` instead.\n\n**Up to 20 sessions are deleted in the request, more run as a job.** The number of matching sessions decides, whether they are selected by `session_numbers` or with `delete_all`:\n- Up to 20 matching sessions: they are deleted before the response, which is `200` with one entry per session in `results[]`.\n- More than 20: the response is `202` with a deletion job, and the sessions are deleted in the background. Poll `GET /v3/sessions/delete/{job_id}/` until `status` is `COMPLETED` or `FAILED`. A job reports counts per outcome, never which sessions it deleted. A `delete_all` job deletes only the sessions created before the request; sessions started while it runs are not deleted.\n\nEach deleted session gets exactly the same treatment as the single-session delete endpoint: the session, every verification attempt it holds, its decision and extracted data, its related feature records (ID verification, face, liveness, face match, and so on) and all of its stored files are deleted, it disappears from every read endpoint, and previously issued media URLs stop resolving. Webhook deliveries still queued for a deleted session, and their retries, are dropped; webhooks already delivered are not affected. Blocklist entries are **not** removed, credits already consumed are **not** refunded, and the parent User entities are **not** deleted (use `POST /v3/users/delete/` for those); no webhook is emitted for the deletion. Deletion is permanent and irreversible.\n\n**Selection semantics:**\n- With `delete_all: true` you can narrow the selection with the filters of `GET /v3/sessions/`, each prefixed with `filter_` (for example `filter_status`, `filter_workflow_id`, `filter_date_from`). Filters that take several values in List Sessions (`status`, `workflow_id`, `country`, `region`, `document_type`, `document_name`, `session_type`, `warning`, `organization_name`) accept a comma-separated string or an array; the others take one value. An empty value is ignored. Filters apply only together with `delete_all`, and a deletion job applies the same filters.\n- An unknown `filter_*` key, or a filter value that cannot be read, rejects the whole request with `400` before anything is deleted, so a mistyped filter never widens the deletion to every session.\n- Entries in `session_numbers` that do not match a live KYC session in your application (unknown numbers, already-deleted sessions, or sessions owned by another application) are **silently skipped**: they do not appear in `results` and are not counted in a job's `total`.\n- Validation failures (a non-numeric entry, an empty list, neither `session_numbers` nor `delete_all` provided, or a template retention the application cannot honour) reject the **whole request** with `400` and delete nothing.\n- Sessions are deleted in groups. If a group cannot be deleted (for example because a storage service is unavailable), every session in that group is reported as `face_retention_outcome: failed_retryable` and stays in place; the other groups are unaffected. Repeat the request for those sessions. A job retries such a group by itself.\n- There is no documented cap on the list length; the shared write rate limit (below) is the practical bound.\n\n**Face biometric data:** the optional `retain_face_embeddings`, `face_retention_days`, `face_retention_deadline`, `deletion_instruction`, and `instruction_id` fields have the same meaning as on `DELETE /v3/session/{sessionId}/delete/` and apply to every matched session. Each result reports its own `face_retention_outcome`.\n\n**Response:** `200 OK` with `results[]` for up to 20 matching sessions, or `202 Accepted` with a deletion job for more. Every result confirms deletion except `face_retention_outcome: failed_retryable`, which reports a session that was NOT deleted and should be retried. Earlier versions of this endpoint returned `204 No Content`; update clients that assert on `204`.\n\n**Idempotency:** repeating the same request deletes nothing more: numbers that no longer match a session are skipped.\n\n**`delete_all: true` (destructive):** deletes **every** remaining KYC session in the application; `session_numbers` is ignored when set. There is no confirmation step and no undo.\n\n**Authentication:** API key in the `x-api-key` header. Missing or invalid credentials return `403` (`{\"detail\": \"You do not have permission to perform this action.\"}`); this API never returns `401`.\n\n**Rate limit:** shared write budget of 300 requests/min per API key across all POST/PATCH/DELETE endpoints; exceeding it returns `429`.\n\nThis endpoint accepts API keys only. Business Console user Bearer tokens are rejected with `403` (unlike the single-session delete, which accepts both).\n\n**Filed regulatory reports:** if the selected data is in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "batch_delete_sessions", + "x-didit-sdk": { + "resource": "sessions", + "method": "batch_delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_numbers": { + "type": "array", + "description": "Numeric `session_number` values of the KYC sessions to delete, as digit-only strings (JSON numbers are also accepted and coerced). Required unless `delete_all` is `true`; an empty list returns `400`. Any non-digit entry rejects the whole request with `400` (unless `delete_all` is `true`, in which case validation is skipped and the entries are ignored). Numbers that do not match a live session in your application are silently skipped.", + "items": { + "type": "string", + "pattern": "^[0-9]+$", + "example": "1001" + }, + "minItems": 1, + "example": [ + "1001", + "1002", + "1003" + ] + }, + "delete_all": { + "type": "boolean", + "description": "When `true`, every remaining KYC session in the calling application is deleted and `session_numbers` is ignored, unless you narrow the selection with `filter_*` keys. More than 20 matching sessions run as a deletion job (`202`) that covers only the sessions created before the request. Defaults to `false`. Irreversible; there is no confirmation step.", + "default": false, + "example": false + }, + "retain_face_embeddings": { + "type": "boolean", + "nullable": true, + "description": "Override the application's `face_retention_policy` for this deletion. `true` keeps one image-free face biometric template anchored to the session's User after the session is deleted; `false` deletes the face embedding with the session; omit or `null` to follow the application policy. Ignored for KYB sessions and for sessions without a face embedding.", + "example": true + }, + "face_retention_days": { + "type": "integer", + "minimum": 1, + "maximum": 3650, + "description": "Finite retention duration, in days, for a retained template. Required when a template is retained unless the application already sets `face_retention_days`. The template's `expires_at` is the earliest of this duration, the application's general data-retention window, and `face_retention_deadline`.", + "example": 365 + }, + "face_retention_deadline": { + "type": "string", + "format": "date-time", + "description": "Optional hard expiry for a retained template. Must be in the future.", + "example": "2027-08-28T00:00:00Z" + }, + "deletion_instruction": { + "type": "string", + "enum": [ + "operational_session_delete", + "privacy_erasure" + ], + "default": "operational_session_delete", + "description": "The class of instruction you are giving. `operational_session_delete` deletes the session and retains a template only when the policy or `retain_face_embeddings` says so. `privacy_erasure` purges every retained biometric template anchored to the session's User before deleting the session, cannot be overridden by the application policy, and cannot be combined with `retain_face_embeddings: true` (`400`).", + "example": "operational_session_delete" + }, + "instruction_id": { + "type": "string", + "maxLength": 255, + "description": "Your durable reference for this deletion instruction (for example an erasure-ticket id). Recorded on the audit trail and on any retained template. Generated by Didit when omitted.", + "example": "dsar-2026-0142" + }, + "filter_status": { + "description": "Narrows a `delete_all` to the sessions that match the `status` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_vendor_data": { + "description": "Narrows a `delete_all` to the sessions that match the `vendor_data` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated text, so several values are not matched as alternatives. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string" + }, + "filter_search": { + "description": "Narrows a `delete_all` to the sessions that match the `search` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated text, so several values are not matched as alternatives. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string" + }, + "filter_workflow_id": { + "description": "Narrows a `delete_all` to the sessions that match the `workflow_id` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_date_from": { + "description": "Narrows a `delete_all` to the sessions that match the `date_from` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string", + "format": "date" + }, + "filter_date_to": { + "description": "Narrows a `delete_all` to the sessions that match the `date_to` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string", + "format": "date" + }, + "filter_country": { + "description": "Narrows a `delete_all` to the sessions that match the `country` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_document_type": { + "description": "Narrows a `delete_all` to the sessions that match the `document_type` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_document_name": { + "description": "Narrows a `delete_all` to the sessions that match the `document_name` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_region": { + "description": "Narrows a `delete_all` to the sessions that match the `region` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_didit_internal_id": { + "description": "Narrows a `delete_all` to the sessions that match the `didit_internal_id` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string", + "format": "uuid" + }, + "filter_session_type": { + "description": "Narrows a `delete_all` to the sessions that match the `session_type` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_api_service": { + "description": "Narrows a `delete_all` to the sessions that match the `api_service` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string" + }, + "filter_screened_full_name": { + "description": "Narrows a `delete_all` to the sessions that match the `screened_full_name` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated text, so several values are not matched as alternatives. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string" + }, + "filter_warning": { + "description": "Narrows a `delete_all` to the sessions that match the `warning` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "filter_status_initialized_as_in_review": { + "description": "Narrows a `delete_all` to the sessions that match the `status_initialized_as_in_review` filter of [List Sessions](/sessions-api/list-sessions), with the same values. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "boolean" + }, + "filter_is_kyc_to_be_reviewed": { + "description": "Narrows a `delete_all` to the sessions that match the `is_kyc_to_be_reviewed` filter of [List Sessions](/sessions-api/list-sessions), with the same values. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "boolean" + }, + "filter_euuid": { + "description": "Narrows a `delete_all` to the sessions that match the `euuid` filter of [List Sessions](/sessions-api/list-sessions), with the same value. One value, not a list: an array is joined into one comma-separated value, so several values are refused with `400`. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "type": "string", + "format": "uuid" + }, + "filter_organization_name": { + "description": "Narrows a `delete_all` to the sessions that match the `organization_name` filter of [List Sessions](/sessions-api/list-sessions), with the same values. A comma-separated string or an array. With an API key it can only match your own organization's name, so it selects all of your sessions or none. Ignored without `delete_all`. An empty value is ignored and does not narrow the selection.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + } + }, + "examples": { + "Specific sessions": { + "summary": "Delete specific sessions by number", + "value": { + "session_numbers": [ + "1001", + "1002", + "1003" + ] + } + }, + "All sessions": { + "summary": "Delete every KYC session in the application", + "value": { + "delete_all": true + } + }, + "Delete and retain templates": { + "summary": "Delete sessions but keep image-free biometric templates for 365 days", + "value": { + "session_numbers": [ + "1001", + "1002" + ], + "retain_face_embeddings": true, + "face_retention_days": 365, + "instruction_id": "post-approval-cleanup-2026-08" + } + }, + "Privacy erasure": { + "summary": "Delete sessions and purge every retained template for their Users", + "value": { + "session_numbers": [ + "1001" + ], + "deletion_instruction": "privacy_erasure", + "instruction_id": "dsar-2026-0142" + } + }, + "Filtered delete_all": { + "summary": "Delete every Declined session of one workflow created in September 2026", + "value": { + "delete_all": true, + "filter_status": [ + "Declined" + ], + "filter_workflow_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "filter_date_from": "2026-09-01", + "filter_date_to": "2026-09-30" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Up to 20 sessions matched and were processed in the request. One result per matched session; numbers that matched nothing (unknown or already deleted) are skipped and do not appear. A result confirms deletion except when `face_retention_outcome` is `failed_retryable`: that session was NOT deleted and should be retried.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The deleted session." + }, + "session_number": { + "type": "integer", + "nullable": true, + "description": "The deleted session's number." + }, + "face_retention_outcome": { + "type": "string", + "enum": [ + "retained_with_user", + "deleted", + "none", + "ineligible_no_vendor_user", + "failed_retryable" + ], + "description": "What happened to the session's face biometric data. `deleted`: the face embedding was deleted with the session (default). `retained_with_user`: the session is deleted and one image-free biometric template stays anchored to the User; see `biometric_template_uuid`. `none`: the session had no face embedding (for example a document-only KYC session without a liveness selfie; KYB sessions are never selected by this endpoint). `ineligible_no_vendor_user`: retention was requested but the session has no linked User, so the embedding was deleted with the session. `failed_retryable` (batch endpoint only): the group this session was deleted with could not be deleted (a storage service, a biometric store, or a monitoring provider was unavailable); this session is still in place, repeat the request for it." + }, + "biometric_template_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Id of the retained biometric template when `face_retention_outcome` is `retained_with_user`; `null` otherwise. Manage it through `/v3/biometric-templates/{template_uuid}/`." + }, + "biometric_template_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the retained biometric template is deleted, when `face_retention_outcome` is `retained_with_user`; `null` when no template is retained." + }, + "deleted_child_verifications": { + "type": "integer", + "description": "Always `0` on this endpoint, which deletes KYC sessions only. Deleting a Business Verification (KYB) session with `DELETE /v3/session/{sessionId}/delete/` reports the verifications it created that were deleted with it." + } + } + } + } + } + }, + "examples": { + "Mixed outcomes": { + "value": { + "results": [ + { + "session_id": "4c5c7f3a-3b1d-4b7e-9c39-2b4f7e6a1d02", + "session_number": 1001, + "face_retention_outcome": "deleted", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + }, + { + "session_id": "b8e1d2c3-4f5a-4b6c-8d7e-9f0a1b2c3d4e", + "session_number": 1002, + "face_retention_outcome": "retained_with_user", + "biometric_template_uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "biometric_template_expires_at": "2027-10-09T10:15:00Z", + "deleted_child_verifications": 0 + }, + { + "session_id": "7a1e9c2d-5f4b-4c3a-8e2d-1b0a9f8e7d6c", + "session_number": 1003, + "face_retention_outcome": "ineligible_no_vendor_user", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + }, + { + "session_id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f", + "session_number": 1004, + "face_retention_outcome": "failed_retryable", + "biometric_template_uuid": null, + "biometric_template_expires_at": null, + "deleted_child_verifications": 0 + } + ] + } + } + } + } + } + }, + "202": { + "description": "More than 20 sessions matched. They are deleted in the background; poll `GET /v3/sessions/delete/{job_id}/` until `status` is `COMPLETED` or `FAILED`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionDeletionJob" + }, + "examples": { + "Job accepted": { + "value": { + "job_id": "5b8f2c1e-7d3a-4f6b-9e2c-1a0d8f7e6c5b", + "status": "PENDING", + "total": 250, + "processed": 0, + "outcomes": { + "deleted": 0, + "none": 0, + "retained_with_user": 0, + "ineligible_no_vendor_user": 0, + "failed_retryable": 0, + "already_deleted": 0 + }, + "deleted_child_verifications": 0, + "error": null, + "created_at": "2026-10-09T10:15:00Z", + "completed_at": null + } + } + } + } + } + }, + "400": { + "description": "Validation error — nothing was deleted. Error values are arrays of message strings keyed by field name.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_numbers": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": { + "description": "Other keys: a `filter_*` key that is not a List Sessions filter (`\"Unknown filter.\"`), or a `filter_*` key whose value cannot be read (keyed by that `filter_*` key, with an array of messages)." + } + }, + "examples": { + "Missing selector": { + "summary": "Neither session_numbers nor delete_all provided", + "value": { + "session_numbers": [ + "This field is required unless delete_all is true." + ] + } + }, + "Empty list": { + "summary": "session_numbers is an empty array", + "value": { + "session_numbers": [ + "This list may not be empty." + ] + } + }, + "Non-numeric entry": { + "summary": "session_numbers contains a non-digit string", + "value": { + "session_numbers": [ + "All session_numbers must be numeric." + ] + } + }, + "Privacy erasure cannot retain": { + "summary": "deletion_instruction privacy_erasure combined with retain_face_embeddings true", + "value": { + "retain_face_embeddings": [ + "Privacy erasure cannot retain biometric templates." + ] + } + }, + "No finite retention duration": { + "summary": "Retention requested without face_retention_days on the request or the application", + "value": { + "detail": "A finite biometric-template retention duration is required." + } + }, + "Unknown filter": { + "summary": "A filter_* key that List Sessions does not have", + "value": { + "filter_reviewer": "Unknown filter." + } + }, + "Unreadable filter value": { + "summary": "A filter value that cannot be read", + "value": { + "filter_date_from": [ + "Enter a valid date." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing API key. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid API key": { + "summary": "No x-api-key header, or the key is invalid", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Shared write rate limit exceeded (300 POST/PATCH/DELETE requests per minute per API key). Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "Write budget exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + }, + "409": { + "description": "A selected session is part of a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/sessions/delete/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"session_numbers\": [\"1001\", \"1002\", \"1003\"]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.batch_delete(\n session_numbers=[\"1001\", \"1002\", \"1003\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.batchDelete({\n session_numbers: [\"1001\", \"1002\", \"1003\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.BatchDelete(ctx, didit.SessionsBatchDeleteParams{\n\tSessionNumbers: didit.Some([]string{\"1001\", \"1002\", \"1003\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.SessionsBatchDeleteParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().batchDelete(SessionsBatchDeleteParams.builder()\n .sessionNumbers(List.of(\"1001\", \"1002\", \"1003\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->batchDelete([\n 'session_numbers' => ['1001', '1002', '1003'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.batch_delete(session_numbers: [\"1001\", \"1002\", \"1003\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.BatchDeleteAsync(new SessionsBatchDeleteParams\n{\n SessionNumbers = new string[] { \"1001\", \"1002\", \"1003\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/sessions/delete/{job_id}/": { + "get": { + "summary": "Get a bulk session deletion job", + "description": "Progress of a bulk session deletion that `POST /v3/sessions/delete/` answered with `202` (more than 20 matching sessions, selected by `session_numbers` or with `delete_all`). Poll every few seconds while `status` is `PENDING` or `PROCESSING`; stop when it is `COMPLETED` or `FAILED`.\n\nThe job reports how many sessions ended in each outcome and how many it has processed, never which sessions it deleted. A `FAILED` job carries the reason in `error`; the sessions it had not reached are still in place, so repeat the deletion request to delete them.\n\n**Authentication:** API key in the `x-api-key` header. A job is only visible to the application that created it; any other `job_id` returns `404`. Missing or invalid credentials return `403`; this API never returns `401`.", + "operationId": "get_session_deletion_job", + "x-didit-sdk": { + "resource": "sessions", + "method": "retrieve_deletion_job", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The `job_id` returned with the `202` from `POST /v3/sessions/delete/`." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl 'https://verification.didit.me/v3/sessions/delete/JOB_ID/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.retrieve_deletion_job(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.retrieveDeletionJob(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.RetrieveDeletionJob(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().retrieveDeletionJob(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->retrieveDeletionJob('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.retrieve_deletion_job(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.RetrieveDeletionJobAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionDeletionJob" + }, + "examples": { + "Processing": { + "value": { + "job_id": "5b8f2c1e-7d3a-4f6b-9e2c-1a0d8f7e6c5b", + "status": "PROCESSING", + "total": 250, + "processed": 100, + "outcomes": { + "deleted": 82, + "none": 18, + "retained_with_user": 0, + "ineligible_no_vendor_user": 0, + "failed_retryable": 0, + "already_deleted": 0 + }, + "deleted_child_verifications": 0, + "error": null, + "created_at": "2026-10-09T10:15:00Z", + "completed_at": null + } + }, + "Completed": { + "value": { + "job_id": "5b8f2c1e-7d3a-4f6b-9e2c-1a0d8f7e6c5b", + "status": "COMPLETED", + "total": 250, + "processed": 250, + "outcomes": { + "deleted": 204, + "none": 44, + "retained_with_user": 0, + "ineligible_no_vendor_user": 0, + "failed_retryable": 0, + "already_deleted": 2 + }, + "deleted_child_verifications": 0, + "error": null, + "created_at": "2026-10-09T10:15:00Z", + "completed_at": "2026-10-09T10:16:40Z" + } + }, + "Failed": { + "value": { + "job_id": "5b8f2c1e-7d3a-4f6b-9e2c-1a0d8f7e6c5b", + "status": "FAILED", + "total": 250, + "processed": 100, + "outcomes": { + "deleted": 82, + "none": 18, + "retained_with_user": 0, + "ineligible_no_vendor_user": 0, + "failed_retryable": 0, + "already_deleted": 0 + }, + "deleted_child_verifications": 0, + "error": "The deletion stopped before it finished. The sessions it had not reached are still in place; repeat the request to delete them.", + "created_at": "2026-10-09T10:15:00Z", + "completed_at": "2026-10-09T10:15:52Z" + } + } + } + } + } + }, + "403": { + "description": "Missing API key. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid API key": { + "summary": "No x-api-key header, or the key is invalid", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No deletion job with this `job_id` exists for the calling application. Jobs are only visible to the application that created them." + } + } + } + }, + "/v3/sessions/{session_id}/reviews/": { + "get": { + "summary": "List session reviews and activity log", + "description": "Return the audit trail / activity feed for a User Verification (KYC) session, newest first, in a paginated envelope (`count` / `next` / `previous` / `results`, 50 entries per page by default).\n\nFeed entries are written by every actor that touches the session: manual decisions made through `PATCH /v3/session/{session_id}/update-status/` (which records `previous_status` → `new_status`), Console reviewer activity (comments with `@email` mentions, KYC/POA data edits, file uploads/removals, tag changes, detail-page views), AML ongoing-monitoring matches, and notes appended via `POST` on this same path. Reading this feed is the API-side equivalent of the **Activity** panel in the Didit Console session view.\n\nOnly User Verification (KYC) sessions are addressable here; Business Verification (KYB) session IDs return `404`.", + "operationId": "list_session_reviews", + "x-didit-sdk": { + "resource": "sessions.reviews", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "session_id", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session. Must belong to the same application as the API key — cross-application UUIDs, deleted sessions, and Business Verification (KYB) session IDs all return `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Page size. Defaults to `50`.", + "schema": { + "type": "integer", + "default": 50, + "example": 50 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of entries to skip from the start of the (ordered) feed. Defaults to `0`. The `next` / `previous` URLs in the response carry the right values for walking the feed.", + "schema": { + "type": "integer", + "default": 0, + "example": 0 + } + }, + { + "name": "ordering", + "in": "query", + "required": false, + "description": "Sort order. The feed defaults to newest first (`-created_at`); pass `created_at` to read it oldest first. Unknown values are silently ignored (default order is used); other serializer field names are also accepted.", + "schema": { + "type": "string", + "default": "-created_at", + "example": "created_at" + } + } + ], + "responses": { + "200": { + "description": "Paginated activity feed, newest first by default. `count` is the total number of entries; `next` / `previous` are ready-made page URLs (`null` at the ends). A `count` of `0` with empty `results` means no activity has been recorded yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of feed entries for this session (across all pages).", + "example": 3 + }, + "next": { + "type": "string", + "nullable": true, + "description": "URL of the next page, or `null` on the last page.", + "example": null + }, + "previous": { + "type": "string", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page.", + "example": null + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ReviewItem" + }, + "description": "Feed entries for this page." + } + } + }, + "examples": { + "DecisionTrail": { + "summary": "Manual approval plus API-added notes", + "value": { + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "uuid": "de0561bc-5583-4409-9171-1a31a4c8a5f4", + "activity_type": "STATUS_UPDATED", + "actor_display": "API Client", + "actor_email": null, + "actor_type": "API_KEY", + "new_status": "Approved", + "previous_status": "In Review", + "comment": "Document re-checked manually; address matches.", + "mentioned_emails": [], + "previous_value": { + "status": "In Review" + }, + "new_value": { + "status": "Approved" + }, + "changed_fields": [ + "status" + ], + "metadata": null, + "created_at": "2026-06-12T00:19:31.918735Z" + }, + { + "uuid": "35370f58-5098-44ee-8a06-8c9af80b2455", + "activity_type": "STATUS_UPDATED", + "actor_display": "Unknown", + "actor_email": null, + "actor_type": "CONSOLE_USER", + "new_status": null, + "previous_status": null, + "comment": "Note without status.", + "mentioned_emails": [], + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "created_at": "2026-06-12T00:18:44.504157Z" + }, + { + "uuid": "00338f82-f1c5-4f57-8614-c49bb258af1a", + "activity_type": "STATUS_UPDATED", + "actor_display": "Unknown", + "actor_email": null, + "actor_type": "CONSOLE_USER", + "new_status": "In Review", + "previous_status": null, + "comment": "Flagging for manual document re-check.", + "mentioned_emails": [], + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "created_at": "2026-06-12T00:18:44.466537Z" + } + ] + } + }, + "PaginatedFirstPage": { + "summary": "First page with limit=2 — next holds the follow-up URL", + "value": { + "count": 4, + "next": "https://verification.didit.me/v3/sessions/8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d/reviews/?limit=2&offset=2", + "previous": null, + "results": [ + { + "uuid": "de0561bc-5583-4409-9171-1a31a4c8a5f4", + "activity_type": "STATUS_UPDATED", + "actor_display": "API Client", + "actor_email": null, + "actor_type": "API_KEY", + "new_status": "Approved", + "previous_status": "In Review", + "comment": "Document re-checked manually; address matches.", + "mentioned_emails": [], + "previous_value": { + "status": "In Review" + }, + "new_value": { + "status": "Approved" + }, + "changed_fields": [ + "status" + ], + "metadata": null, + "created_at": "2026-06-12T00:19:31.918735Z" + }, + { + "uuid": "bd4e6227-bdd2-42b2-a726-1d12f785a457", + "activity_type": "STATUS_UPDATED", + "actor_display": "Unknown", + "actor_email": null, + "actor_type": "CONSOLE_USER", + "new_status": null, + "previous_status": null, + "comment": null, + "mentioned_emails": [], + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "created_at": "2026-06-12T00:18:44.540238Z" + } + ] + } + }, + "Empty": { + "summary": "Session with no recorded activity yet", + "value": { + "count": 0, + "next": null, + "previous": null, + "results": [] + } + } + } + } + } + }, + "403": { + "description": "Missing, malformed, expired, or revoked `x-api-key`. Authentication is checked before the session lookup, so a missing key returns `403` even when the session UUID does not exist.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You do not have permission to perform this action." + } + } + }, + "examples": { + "InvalidKey": { + "summary": "Missing or invalid x-api-key header", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No User Verification (KYC) session with this UUID is visible to your application: the UUID does not exist, the session was deleted, or it belongs to a different application. Business Verification (KYB) session IDs also return `404` here — the reviews feed is only addressable for KYC sessions.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Session not found." + } + } + }, + "examples": { + "NotFound": { + "summary": "Unknown, deleted, or cross-application session UUID", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Inspect `Retry-After` and `X-RateLimit-*` response headers. See [Rate limiting](/integration/rate-limiting)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -s 'https://verification.didit.me/v3/sessions/8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d/reviews/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.sessions.reviews.list(\n \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.sessions.reviews.list(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Sessions.Reviews.ListAutoPaging(ctx, \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\", didit.SessionsReviewsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.sessions().reviews().list(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->reviews->list('8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.reviews.list(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Sessions.Reviews.ListAsync(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "summary": "Add a review note to a session's audit trail", + "description": "Append a note to a User Verification (KYC) session's review / activity feed. **This endpoint only records an entry — it never changes the session.** The session's `status` is left untouched, no status-transition validation runs, and no webhook fires. To actually approve, decline, or re-open a session (which also writes a fully-attributed `STATUS_UPDATED` entry to this feed), use `PATCH /v3/session/{session_id}/update-status/` instead.\n\nBoth body fields are optional and an empty JSON object is accepted. `new_status` is stored purely as a label on the entry and accepts any of the ten session status values (case-sensitive). `@email` mentions inside `comment` are stored as plain text only — they trigger no notifications on this endpoint, and the entry's `mentioned_emails` stays empty.\n\nNote on attribution: entries created here carry no actor information. In the feed they show `actor_display: \"Unknown\"`, `actor_type: \"CONSOLE_USER\"`, and `activity_type: \"STATUS_UPDATED\"` (storage defaults), and the `201` body's `user` field is `\"Unknown\"`.", + "operationId": "create_session_review", + "x-didit-sdk": { + "resource": "sessions.reviews", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "Content-Type", + "in": "header", + "required": false, + "description": "Set to `application/json` when sending a JSON body.", + "schema": { + "type": "string", + "example": "application/json" + } + }, + { + "name": "session_id", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session. Must belong to the same application as the API key — cross-application UUIDs, deleted sessions, and Business Verification (KYB) session IDs all return `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "new_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Optional status label to record on the entry. Accepts any session status value, case-sensitive (`\"approved\"` is rejected with `400`). Recording a status here does **not** change the session's actual status — use the update-status endpoint for that.", + "example": "In Review" + }, + "comment": { + "type": "string", + "nullable": true, + "description": "Optional free-text note (no enforced length limit). Stored verbatim; `@email` mentions are not processed on this endpoint.", + "example": "Flagging for manual document re-check." + } + } + }, + "examples": { + "NoteWithStatusLabel": { + "summary": "Note carrying a status label (session status unchanged)", + "value": { + "new_status": "In Review", + "comment": "Flagging for manual document re-check." + } + }, + "CommentOnly": { + "summary": "Plain note without a status label", + "value": { + "comment": "Customer emailed an updated utility bill; awaiting upload." + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Entry recorded on the session's audit trail. The session itself is unchanged — its `status` keeps its previous value and no webhook is sent. The new entry also appears at the top of `GET …/reviews/` (with `actor_display: \"Unknown\"`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "user": { + "type": "string", + "description": "Display name of the entry's author. Always `\"Unknown\"` for entries created through this endpoint, because it records no actor attribution.", + "example": "Unknown" + }, + "new_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "The status label recorded on the entry, or `null` if none was sent. The session's actual status is not affected.", + "example": "In Review" + }, + "comment": { + "type": "string", + "nullable": true, + "description": "The note recorded on the entry, or `null` if none was sent.", + "example": "Flagging for manual document re-check." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the entry was written, ISO 8601 UTC.", + "example": "2026-06-12T00:18:44.466537Z" + } + } + }, + "examples": { + "NoteWithStatusLabel": { + "summary": "Note with a status label", + "value": { + "user": "Unknown", + "new_status": "In Review", + "comment": "Flagging for manual document re-check.", + "created_at": "2026-06-12T00:18:44.466537Z" + } + }, + "CommentOnly": { + "summary": "Plain note", + "value": { + "user": "Unknown", + "new_status": null, + "comment": "Note without status.", + "created_at": "2026-06-12T00:18:44.504157Z" + } + } + } + } + } + }, + "400": { + "description": "Validation error, returned as a per-field map of messages. The usual cause is a `new_status` value that is not one of the ten allowed statuses — the check is case-sensitive.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "new_status": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "\"approved\" is not a valid choice." + ] + } + } + }, + "examples": { + "InvalidStatusChoice": { + "summary": "Lowercase / unknown status value", + "value": { + "new_status": [ + "\"approved\" is not a valid choice." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, malformed, expired, or revoked `x-api-key`. Authentication is checked before the session lookup, so a missing key returns `403` even when the session UUID does not exist.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "You do not have permission to perform this action." + } + } + }, + "examples": { + "InvalidKey": { + "summary": "Missing or invalid x-api-key header", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No User Verification (KYC) session with this UUID is visible to your application: the UUID does not exist, the session was deleted, or it belongs to a different application. Business Verification (KYB) session IDs also return `404` here — the reviews feed is only addressable for KYC sessions.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "example": "Session not found." + } + } + }, + "examples": { + "NotFound": { + "summary": "Unknown, deleted, or cross-application session UUID", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Inspect `Retry-After` and `X-RateLimit-*` response headers. See [Rate limiting](/integration/rate-limiting)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "# Records an audit-trail note only -- the session's status does NOT change.\ncurl -s -X POST https://verification.didit.me/v3/sessions/8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d/reviews/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"new_status\": \"In Review\",\n \"comment\": \"Flagging for manual document re-check.\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.reviews.create(\n \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\",\n new_status=\"In Review\",\n comment=\"Flagging for manual document re-check.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.reviews.create(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\", {\n new_status: \"In Review\",\n comment: \"Flagging for manual document re-check.\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Reviews.Create(ctx, \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\", didit.SessionsReviewsCreateParams{\n\tNewStatus: didit.Some(didit.SessionsReviewsCreateParamsNewStatus(\"In Review\")),\n\tComment: didit.String(\"Flagging for manual document re-check.\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsReviewsCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().reviews().create(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\", SessionsReviewsCreateParams.builder()\n .newStatus(SessionsReviewsCreateParams.NewStatus.of(\"In Review\"))\n .comment(\"Flagging for manual document re-check.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->reviews->create('8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d', [\n 'new_status' => 'In Review',\n 'comment' => 'Flagging for manual document re-check.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.reviews.create(\n \"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\",\n new_status: \"In Review\",\n comment: \"Flagging for manual document re-check.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Reviews.CreateAsync(\"8c2f3a14-7e9b-4d23-9e83-3f7d5e8a1c6d\", new SessionsReviewsCreateParams\n{\n NewStatus = new SessionsReviewsCreateParamsNewStatus(\"In Review\"),\n Comment = \"Flagging for manual document re-check.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/billing/balance/": { + "get": { + "summary": "Get credit balance", + "description": "Get current pre-paid credit balance in USD (organization-wide) plus auto-refill config. Amounts are decimal strings — parse with a decimal library.", + "operationId": "get_billing_balance", + "x-didit-sdk": { + "resource": "billing", + "method": "retrieve_balance", + "auth": "api_key", + "server": true + }, + "tags": [ + "Billing" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/billing/balance/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.billing.retrieve_balance()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.billing.retrieveBalance();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Billing.RetrieveBalance(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.billing().retrieveBalance();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "billing->retrieveBalance();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.billing.retrieve_balance\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Billing.RetrieveBalanceAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Current credit balance plus the organization's auto-refill configuration.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "balance", + "auto_refill_enabled" + ], + "properties": { + "balance": { + "type": "string", + "description": "Current pre-paid credit balance in USD, as a fixed-point decimal string. Decreases as verifications are billed and increases when a top-up settles." + }, + "auto_refill_enabled": { + "type": "boolean", + "description": "When `true`, Didit will automatically charge the saved payment method to add credits once the balance crosses `auto_refill_threshold`. Configured from Console → Billing." + }, + "auto_refill_amount": { + "type": "string", + "nullable": true, + "description": "USD amount that will be charged and added to the balance on each auto-refill, at face value. Set from \\$50 to \\$20,000 in Console → Billing. `null` when auto-refill is disabled or unset." + }, + "auto_refill_threshold": { + "type": "string", + "nullable": true, + "description": "Trigger threshold in USD. Auto-refill fires when `balance` falls below this value. `null` when auto-refill is disabled or unset." + } + } + }, + "examples": { + "Healthy balance with auto-refill": { + "value": { + "balance": "1234.5600", + "auto_refill_enabled": true, + "auto_refill_amount": "500.00", + "auto_refill_threshold": "100.00" + } + }, + "Low balance, manual top-up only": { + "value": { + "balance": "12.0000", + "auto_refill_enabled": false, + "auto_refill_amount": null, + "auto_refill_threshold": null + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read billing data for this organization. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/billing/top-up/": { + "post": { + "summary": "Top up credits", + "description": "Add credits via a hosted Stripe Checkout session (**\\$50 to \\$20,000** per top-up). Top-ups are available on the Free, Growth and Scale plans, added at face value with no discount or bonus, and last 12 months; Enterprise annual contracts cannot top up. Redirect the customer to the returned `checkout_session_url`.", + "operationId": "billing_top_up", + "x-didit-sdk": { + "resource": "billing", + "method": "top_up", + "auth": "api_key", + "server": true + }, + "tags": [ + "Billing" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "amount_in_dollars" + ], + "properties": { + "amount_in_dollars": { + "type": "number", + "minimum": 50, + "maximum": 20000, + "description": "Amount to charge, in USD. **Must be from 50 to 20,000.** The balance receives the same amount, with no discount or bonus. Tax is added automatically at checkout based on the customer's billing address." + }, + "success_url": { + "type": "string", + "format": "uri", + "description": "URL the hosted checkout redirects the customer to after a successful payment. Currently defaults to `https://console.didit.me`. Pass a working URL explicitly; the default host is unavailable. Append the literal placeholder `{CHECKOUT_SESSION_ID}` if you want the hosted checkout to substitute the session ID." + }, + "cancel_url": { + "type": "string", + "format": "uri", + "description": "URL the hosted checkout redirects the customer to when they abort or close the Checkout window. Currently defaults to `https://console.didit.me`. Pass a working URL explicitly; the default host is unavailable." + } + } + }, + "examples": { + "Minimum top-up": { + "value": { + "amount_in_dollars": 50 + } + }, + "Custom amount with redirects": { + "value": { + "amount_in_dollars": 500, + "success_url": "https://yourapp.com/billing/top-up/success", + "cancel_url": "https://yourapp.com/billing/top-up/cancel" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/billing/top-up/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"amount_in_dollars\": 500,\n \"success_url\": \"https://yourapp.com/billing/success\",\n \"cancel_url\": \"https://yourapp.com/billing/cancel\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.billing.top_up(\n amount_in_dollars=50.0,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.billing.topUp({\n amount_in_dollars: 50.0,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Billing.TopUp(ctx, didit.BillingTopUpParams{\n\tAmountInDollars: 50.0,\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BillingTopUpParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.billing().topUp(BillingTopUpParams.builder()\n .amountInDollars(50.0)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "billing->topUp([\n 'amount_in_dollars' => 50.0,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.billing.top_up(amount_in_dollars: 50.0)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Billing.TopUpAsync(new BillingTopUpParams\n{\n AmountInDollars = 50.0,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Hosted checkout session created. Redirect the user to `checkout_session_url` to complete payment.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "checkout_session_id", + "checkout_session_url" + ], + "properties": { + "checkout_session_id": { + "type": "string", + "description": "Hosted checkout session identifier (e.g. `cs_test_a1B2c3...`). Useful for server-side reconciliation." + }, + "checkout_session_url": { + "type": "string", + "format": "uri", + "description": "Single-use hosted payment page URL. Redirect the customer here to complete payment." + } + } + }, + "examples": { + "Created": { + "value": { + "checkout_session_id": "cs_test_a1B2c3D4e5F6g7H8i9J0", + "checkout_session_url": "https://checkout.example.com/c/pay/cs_test_a1B2c3D4e5F6g7H8i9J0" + } + } + } + } + } + }, + "400": { + "description": "Invalid payload: the amount is missing, not a number, below the \\$50 minimum or above the \\$20,000 maximum, or the organization is on an Enterprise annual contract. The error body is a JSON array.", + "content": { + "application/json": { + "examples": { + "Missing amount": { + "value": [ + "amount_in_dollars is required." + ] + }, + "Not a number": { + "value": [ + "amount_in_dollars must be a number." + ] + }, + "Below minimum": { + "value": [ + "Amount must be at least $50." + ] + }, + "Above maximum": { + "value": [ + "Amount must be at most $20,000." + ] + }, + "Annual plan": { + "value": [ + "You have an annual plan. Contact your account manager to top up." + ] + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot create top-up sessions for this organization. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/": { + "get": { + "summary": "List webhook destinations", + "description": "List all webhook destinations (unpaginated array, oldest first). Each item carries the destination's configuration, its delivery metrics for the last 30 days (`metrics_window_days`) with one `daily_points` entry per day, and its health (`health_status`, `failing_streak`, `failing_since`, `last_success_at`). The list has no `summary` object and never includes the signing secret: each item shows only `secret_last4`. Fetch a single destination (`GET /v3/webhook/destinations/{destination_uuid}/`) to read `secret_shared_key`.", + "operationId": "list_webhook_destinations", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.list()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.list();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.List(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().list();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->list();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.webhooks.destinations.list.each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.ListAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "List of webhook destinations (unpaginated, oldest first).", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Stable destination identifier; use as `{destination_uuid}` for update/delete." + }, + "label": { + "type": "string", + "description": "Human-readable name shown in the Console." + }, + "url": { + "type": "string", + "format": "uri", + "description": "Destination URL that receives `POST` requests with the webhook payload." + }, + "enabled": { + "type": "boolean", + "description": "When `false`, Didit silently skips delivery to this destination." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload schema version sent to this destination. `v3` is the current/recommended format." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + }, + "subscribed_events_count": { + "type": "integer", + "description": "Cached length of `subscribed_events`, useful for UI rendering." + }, + "deliveries_count": { + "type": "integer", + "description": "Delivery attempts (retries included) made to this destination in the last 30 days." + }, + "failed_deliveries_count": { + "type": "integer", + "description": "Delivery attempts in the last 30 days that the destination answered with HTTP 400 or above, or did not answer (timeout, refused connection or blocked address)." + }, + "average_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Average response time in milliseconds over the last 30 days, rounded to an integer. `null` when there were no deliveries in that window." + }, + "error_rate_percentage": { + "type": "integer", + "description": "`round(failed_deliveries_count / deliveries_count * 100)` over the last 30 days; `0` when there are no deliveries." + }, + "last_delivery_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent delivery attempt (success or failure). `null` if never delivered." + }, + "metrics_window_days": { + "type": "integer", + "description": "Length in days of the window that `deliveries_count`, `failed_deliveries_count`, `average_response_time_ms`, `error_rate_percentage` and `daily_points` cover. Always `30`." + }, + "health_status": { + "type": "string", + "enum": [ + "healthy", + "degraded", + "failing" + ], + "description": "Delivery health. `failing`: 3 or more consecutive events failed their first delivery attempt since the destination last answered successfully (see `failing_streak`). `degraded`: not failing, but a delivery attempt failed in the last 24 hours and no later attempt has delivered that event; a retry or resend that delivers it clears this. `healthy`: neither. Independent of `enabled`. A failed attempt is one answered with HTTP 400 or above, or not answered at all." + }, + "failing_streak": { + "type": "integer", + "description": "How many consecutive events failed their first delivery attempt since the destination last answered successfully, within the last 30 days. Retries are not counted, and any successful attempt, including a retry or a resend, ends the streak. `0` when no event has failed since the last success." + }, + "failing_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the first failed attempt of the current `failing_streak` was made. `null` when `failing_streak` is `0`." + }, + "last_success_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the destination last answered a delivery with an HTTP status under 400, within the last 30 days. `null` if it has not in that window." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the destination." + }, + "daily_points": { + "type": "array", + "description": "One entry per UTC day of the last 30 days, oldest first, including days with no deliveries. Counts every attempt, retries included.", + "items": { + "type": "object", + "properties": { + "date": { + "type": "string", + "format": "date", + "description": "The UTC day." + }, + "total_deliveries": { + "type": "integer", + "description": "Delivery attempts made that day." + }, + "failed_deliveries": { + "type": "integer", + "description": "Attempts made that day that were answered with HTTP 400 or above, or not answered at all." + } + } + } + }, + "secret_last4": { + "type": "string", + "description": "Last four characters of the destination's current signing secret, so you can tell secrets apart. Not enough to verify a signature." + }, + "created_by_email": { + "type": "string", + "nullable": true, + "description": "Email of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_by_name": { + "type": "string", + "nullable": true, + "description": "Name of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + }, + "examples": { + "Two destinations": { + "value": [ + { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "subscribed_events_count": 2, + "deliveries_count": 12453, + "failed_deliveries_count": 14, + "average_response_time_ms": 187, + "error_rate_percentage": 0, + "last_delivery_at": "2026-05-17T09:21:18Z", + "metrics_window_days": 30, + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": "2026-05-17T09:21:18Z", + "environment": "live", + "daily_points": [ + { + "date": "2026-04-18", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-19", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-20", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-21", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-22", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-23", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-24", + "total_deliveries": 415, + "failed_deliveries": 9 + }, + { + "date": "2026-04-25", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-26", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-27", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-28", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-29", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-04-30", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-01", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-02", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-03", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-04", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-05", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-06", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-07", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-08", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-09", + "total_deliveries": 415, + "failed_deliveries": 5 + }, + { + "date": "2026-05-10", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-11", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-12", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-13", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-14", + "total_deliveries": 415, + "failed_deliveries": 0 + }, + { + "date": "2026-05-15", + "total_deliveries": 416, + "failed_deliveries": 0 + }, + { + "date": "2026-05-16", + "total_deliveries": 416, + "failed_deliveries": 0 + }, + { + "date": "2026-05-17", + "total_deliveries": 416, + "failed_deliveries": 0 + } + ], + "secret_last4": "0xnQ", + "created_by_email": null, + "created_by_name": null, + "created_at": "2026-01-04T08:00:00Z", + "updated_at": "2026-04-22T14:09:11Z" + }, + { + "uuid": "22222222-3333-4444-5555-666666666666", + "label": "Staging mirror", + "url": "https://staging.yourapp.com/webhooks/didit", + "enabled": false, + "webhook_version": "v3", + "subscribed_events": [ + "status.updated" + ], + "subscribed_events_count": 1, + "deliveries_count": 0, + "failed_deliveries_count": 0, + "average_response_time_ms": null, + "error_rate_percentage": 0, + "last_delivery_at": null, + "metrics_window_days": 30, + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": null, + "environment": "live", + "daily_points": [ + { + "date": "2026-04-18", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-19", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-20", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-21", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-22", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-23", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-24", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-25", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-26", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-27", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-28", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-29", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-04-30", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-01", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-02", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-03", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-04", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-05", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-06", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-07", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-08", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-09", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-10", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-11", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-12", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-13", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-14", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-15", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-16", + "total_deliveries": 0, + "failed_deliveries": 0 + }, + { + "date": "2026-05-17", + "total_deliveries": 0, + "failed_deliveries": 0 + } + ], + "secret_last4": "k9Qe", + "created_by_email": "reviewer@example.com", + "created_by_name": "Example Reviewer", + "created_at": "2026-03-12T10:15:02Z", + "updated_at": "2026-03-12T10:15:02Z" + } + ] + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create webhook destination", + "description": "Create a webhook destination. The response includes the auto-generated `secret_shared_key` HMAC signing secret (also readable later via `GET /v3/webhook/destinations/{destination_uuid}/`). Always send `subscribed_events` with at least one event (no wildcards): an explicit empty list is rejected with 400, and omitting the field creates a destination subscribed to nothing, which never receives a webhook. The `(application, url)` pair must be unique among the application's destinations.", + "operationId": "create_webhook_destination", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/webhook/destinations/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"label\": \"Production session webhooks\",\n \"url\": \"https://yourapp.com/webhooks/didit\",\n \"webhook_version\": \"v3\",\n \"subscribed_events\": [\"status.updated\", \"data.updated\"]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.create(\n label=\"Production session webhooks\",\n url=\"https://yourapp.com/webhooks/didit\",\n webhook_version=\"v3\",\n subscribed_events=[\"status.updated\", \"data.updated\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.create({\n label: \"Production session webhooks\",\n url: \"https://yourapp.com/webhooks/didit\",\n webhook_version: \"v3\",\n subscribed_events: [\"status.updated\", \"data.updated\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.Create(ctx, didit.WebhooksDestinationsCreateParams{\n\tLabel: \"Production session webhooks\",\n\tURL: \"https://yourapp.com/webhooks/didit\",\n\tWebhookVersion: didit.Some(didit.WebhooksDestinationsCreateParamsWebhookVersion(\"v3\")),\n\tSubscribedEvents: didit.Some([]didit.WebhookEvent{didit.WebhookEvent(\"status.updated\"), didit.WebhookEvent(\"data.updated\")}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.WebhookEvent;\nimport me.didit.types.WebhooksDestinationsCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().create(WebhooksDestinationsCreateParams.builder()\n .label(\"Production session webhooks\")\n .url(\"https://yourapp.com/webhooks/didit\")\n .webhookVersion(WebhooksDestinationsCreateParams.WebhookVersion.of(\"v3\"))\n .subscribedEvents(List.of(WebhookEvent.of(\"status.updated\"), WebhookEvent.of(\"data.updated\")))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->create([\n 'label' => 'Production session webhooks',\n 'url' => 'https://yourapp.com/webhooks/didit',\n 'webhook_version' => 'v3',\n 'subscribed_events' => ['status.updated', 'data.updated'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.destinations.create(\n label: \"Production session webhooks\",\n url: \"https://yourapp.com/webhooks/didit\",\n webhook_version: \"v3\",\n subscribed_events: [\"status.updated\", \"data.updated\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.CreateAsync(new WebhooksDestinationsCreateParams\n{\n Label = \"Production session webhooks\",\n Url = \"https://yourapp.com/webhooks/didit\",\n WebhookVersion = new WebhooksDestinationsCreateParamsWebhookVersion(\"v3\"),\n SubscribedEvents = new WebhookEvent[] { \"status.updated\", \"data.updated\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "label", + "url" + ], + "properties": { + "label": { + "type": "string", + "maxLength": 255, + "description": "Human-readable name displayed in Console → Webhooks. Helps distinguish destinations when you have several (e.g. \"Prod\", \"Staging mirror\")." + }, + "url": { + "type": "string", + "format": "uri", + "description": "HTTPS URL that will receive `POST` requests. Must be unique among the application's destinations. Use a stable, hardened endpoint: Didit retries failed deliveries on a backoff schedule.", + "maxLength": 2000 + }, + "enabled": { + "type": "boolean", + "default": true, + "description": "When `false`, the destination is saved but Didit will not push any webhooks to it. Useful for staging configs you want to keep parked." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload schema version Didit will send. `v3` is the current/recommended format; older versions are kept for backward compatibility with legacy integrations." + }, + "subscribed_events": { + "allOf": [ + { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + } + ], + "description": "Events to deliver. Effectively required: an explicit `[]` is rejected with 400, and omitting the field creates a destination with no subscriptions that receives nothing." + } + } + }, + "examples": { + "session_updates": { + "summary": "Session status and data updates", + "value": { + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "webhook_version": "v3", + "subscribed_events": [ + "status.updated", + "data.updated" + ] + } + }, + "transaction_updates": { + "summary": "Transaction monitoring updates", + "value": { + "label": "Transaction monitoring webhooks", + "url": "https://yourapp.com/webhooks/didit-transactions", + "webhook_version": "v3", + "subscribed_events": [ + "transaction.created", + "transaction.status.updated" + ] + } + }, + "entity_updates": { + "summary": "User and Business entity updates", + "value": { + "label": "Entity lifecycle webhooks", + "url": "https://yourapp.com/webhooks/didit-entities", + "webhook_version": "v3", + "subscribed_events": [ + "user.status.updated", + "user.data.updated", + "business.status.updated", + "business.data.updated", + "activity.created" + ] + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Destination created. `secret_shared_key` is the HMAC signing secret — store it now.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Stable destination identifier. Use as `{destination_uuid}` in subsequent calls." + }, + "label": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "enabled": { + "type": "boolean" + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ] + }, + "secret_shared_key": { + "type": "string", + "description": "Auto-generated HMAC signing secret bound to this destination (43-character URL-safe token). Use it to verify the signature headers on incoming webhooks. Always returned in full to API-key callers; Console users need the `read:webhooks` permission, otherwise they see an empty string." + }, + "secret_last4": { + "type": "string", + "description": "Last four characters of the destination's current signing secret, so you can tell secrets apart. Not enough to verify a signature." + }, + "previous_secret_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set for 24 hours after the destination's signing secret is rotated in the Business Console. Until this time, every delivery to the destination also carries `X-Signature-Previous`, `X-Signature-V2-Previous` and `X-Signature-Simple-Previous`, made with the replaced secret (see [Signature verification](/integration/webhooks#signature-verification)). `null` when no rotation happened in the last 24 hours." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + }, + "created_by_email": { + "type": "string", + "nullable": true, + "description": "Email of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_by_name": { + "type": "string", + "nullable": true, + "description": "Name of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "health_status": { + "type": "string", + "enum": [ + "healthy", + "degraded", + "failing" + ], + "description": "Delivery health. `failing`: 3 or more consecutive events failed their first delivery attempt since the destination last answered successfully (see `failing_streak`). `degraded`: not failing, but a delivery attempt failed in the last 24 hours and no later attempt has delivered that event; a retry or resend that delivers it clears this. `healthy`: neither. Independent of `enabled`. A failed attempt is one answered with HTTP 400 or above, or not answered at all." + }, + "failing_streak": { + "type": "integer", + "description": "How many consecutive events failed their first delivery attempt since the destination last answered successfully, within the last 30 days. Retries are not counted, and any successful attempt, including a retry or a resend, ends the streak. `0` when no event has failed since the last success." + }, + "failing_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the first failed attempt of the current `failing_streak` was made. `null` when `failing_streak` is `0`." + }, + "last_success_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the destination last answered a delivery with an HTTP status under 400, within the last 30 days. `null` if it has not in that window." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the destination." + }, + "summary": { + "type": "object", + "description": "Delivery metrics for the last `metrics_window_days` (30) days. `last_delivery_at` is the most recent attempt, whenever it was made.", + "properties": { + "total_deliveries": { + "type": "integer" + }, + "failed_deliveries": { + "type": "integer" + }, + "error_rate_percentage": { + "type": "integer" + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true + }, + "last_delivery_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "metrics_window_days": { + "type": "integer", + "description": "Length in days of the window the other summary metrics cover. Always `30`." + } + } + } + } + }, + "examples": { + "Created": { + "value": { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "secret_shared_key": "tno2NTeRC1ZK3sBOYlBJDyEzBVENl3pQ1AcZyAW0xnQ", + "secret_last4": "0xnQ", + "previous_secret_expires_at": null, + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_by_email": null, + "created_by_name": null, + "created_at": "2026-05-17T10:00:00Z", + "updated_at": "2026-05-17T10:00:00Z", + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": null, + "environment": "live", + "summary": { + "total_deliveries": 0, + "failed_deliveries": 0, + "error_rate_percentage": 0, + "min_response_time_ms": null, + "avg_response_time_ms": null, + "max_response_time_ms": null, + "last_delivery_at": null, + "metrics_window_days": 30 + } + } + } + } + } + } + }, + "400": { + "description": "Validation failed — usually a duplicate URL, missing/invalid `subscribed_events`, or unknown event name.", + "content": { + "application/json": { + "examples": { + "Duplicate URL": { + "value": { + "url": [ + "This webhook destination already exists for the application." + ] + } + }, + "Empty events list": { + "value": { + "subscribed_events": [ + "At least one subscribed event is required." + ] + } + }, + "Unknown event": { + "value": { + "subscribed_events": [ + "Unsupported webhook events: foo.bar" + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/": { + "get": { + "summary": "Get webhook destination", + "description": "Retrieve one webhook destination with its `secret_shared_key` signing secret, its health (`health_status`, `failing_streak`, `failing_since`, `last_success_at`) and a delivery `summary` for the last 30 days. API-key callers always receive the secret in full; Console users without `read:webhooks` see an empty string.", + "operationId": "get_webhook_destination", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination (the `uuid` returned from create / list).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.destinations.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Webhook destination details.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "enabled": { + "type": "boolean" + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ] + }, + "secret_shared_key": { + "type": "string", + "description": "HMAC signing secret (43-character URL-safe token). Always returned in full to API-key callers; Console users need `read:webhooks`, otherwise an empty string." + }, + "secret_last4": { + "type": "string", + "description": "Last four characters of the destination's current signing secret, so you can tell secrets apart. Not enough to verify a signature." + }, + "previous_secret_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set for 24 hours after the destination's signing secret is rotated in the Business Console. Until this time, every delivery to the destination also carries `X-Signature-Previous`, `X-Signature-V2-Previous` and `X-Signature-Simple-Previous`, made with the replaced secret (see [Signature verification](/integration/webhooks#signature-verification)). `null` when no rotation happened in the last 24 hours." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + }, + "created_by_email": { + "type": "string", + "nullable": true, + "description": "Email of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_by_name": { + "type": "string", + "nullable": true, + "description": "Name of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "health_status": { + "type": "string", + "enum": [ + "healthy", + "degraded", + "failing" + ], + "description": "Delivery health. `failing`: 3 or more consecutive events failed their first delivery attempt since the destination last answered successfully (see `failing_streak`). `degraded`: not failing, but a delivery attempt failed in the last 24 hours and no later attempt has delivered that event; a retry or resend that delivers it clears this. `healthy`: neither. Independent of `enabled`. A failed attempt is one answered with HTTP 400 or above, or not answered at all." + }, + "failing_streak": { + "type": "integer", + "description": "How many consecutive events failed their first delivery attempt since the destination last answered successfully, within the last 30 days. Retries are not counted, and any successful attempt, including a retry or a resend, ends the streak. `0` when no event has failed since the last success." + }, + "failing_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the first failed attempt of the current `failing_streak` was made. `null` when `failing_streak` is `0`." + }, + "last_success_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the destination last answered a delivery with an HTTP status under 400, within the last 30 days. `null` if it has not in that window." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the destination." + }, + "summary": { + "type": "object", + "description": "Delivery metrics for the last `metrics_window_days` (30) days. `last_delivery_at` is the most recent attempt, whenever it was made.", + "properties": { + "total_deliveries": { + "type": "integer" + }, + "failed_deliveries": { + "type": "integer" + }, + "error_rate_percentage": { + "type": "integer" + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true + }, + "last_delivery_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "metrics_window_days": { + "type": "integer", + "description": "Length in days of the window the other summary metrics cover. Always `30`." + } + } + } + } + }, + "examples": { + "Healthy destination": { + "value": { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "secret_shared_key": "tno2NTeRC1ZK3sBOYlBJDyEzBVENl3pQ1AcZyAW0xnQ", + "secret_last4": "0xnQ", + "previous_secret_expires_at": null, + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_by_email": null, + "created_by_name": null, + "created_at": "2026-01-04T08:00:00Z", + "updated_at": "2026-04-22T14:09:11Z", + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": "2026-05-17T09:21:18Z", + "environment": "live", + "summary": { + "total_deliveries": 12453, + "failed_deliveries": 14, + "error_rate_percentage": 0, + "min_response_time_ms": 41, + "avg_response_time_ms": 187, + "max_response_time_ms": 2103, + "last_delivery_at": "2026-05-17T09:21:18Z", + "metrics_window_days": 30 + } + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No destination with this UUID exists, or it belongs to a different application, or it has been deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update webhook destination", + "description": "Update a webhook destination. **`subscribed_events` is REPLACED wholesale, not merged**: fetch first, append, resend. Secret is not rotated. A `uuid` in the body is ignored: the path names the destination.", + "operationId": "update_webhook_destination", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination to update.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "label": { + "type": "string", + "description": "New human-readable name." + }, + "url": { + "type": "string", + "format": "uri", + "description": "New target URL. Must be unique within the application." + }, + "enabled": { + "type": "boolean", + "description": "Set to `false` to pause deliveries to this destination without deleting it." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "New payload schema version for future deliveries." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + } + } + }, + "examples": { + "Pause destination": { + "value": { + "enabled": false + } + }, + "Add transaction events": { + "value": { + "subscribed_events": [ + "status.updated", + "data.updated", + "transaction.created", + "transaction.status.updated" + ] + } + }, + "Move target URL": { + "value": { + "label": "Production session webhooks v2", + "url": "https://api.yourapp.com/webhooks/didit/v2" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"subscribed_events\": [\n \"status.updated\",\n \"data.updated\",\n \"transaction.created\"\n ]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.update(\n \"11111111-2222-3333-4444-555555555555\",\n enabled=False,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.update(\"11111111-2222-3333-4444-555555555555\", {\n enabled: false,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.WebhooksDestinationsUpdateParams{\n\tEnabled: didit.Bool(false),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.WebhooksDestinationsUpdateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().update(\"11111111-2222-3333-4444-555555555555\", WebhooksDestinationsUpdateParams.builder()\n .enabled(false)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->update('11111111-2222-3333-4444-555555555555', [\n 'enabled' => false,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.destinations.update(\n \"11111111-2222-3333-4444-555555555555\",\n enabled: false\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new WebhooksDestinationsUpdateParams\n{\n Enabled = false,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated webhook destination. Body has the same shape as `GET /v3/webhook/destinations/{destination_uuid}/`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "enabled": { + "type": "boolean" + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ] + }, + "secret_shared_key": { + "type": "string" + }, + "secret_last4": { + "type": "string", + "description": "Last four characters of the destination's current signing secret, so you can tell secrets apart. Not enough to verify a signature." + }, + "previous_secret_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set for 24 hours after the destination's signing secret is rotated in the Business Console. Until this time, every delivery to the destination also carries `X-Signature-Previous`, `X-Signature-V2-Previous` and `X-Signature-Simple-Previous`, made with the replaced secret (see [Signature verification](/integration/webhooks#signature-verification)). `null` when no rotation happened in the last 24 hours." + }, + "subscribed_events": { + "$ref": "#/components/schemas/WebhookSubscribedEvents" + }, + "created_by_email": { + "type": "string", + "nullable": true, + "description": "Email of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_by_name": { + "type": "string", + "nullable": true, + "description": "Name of the Business Console member who created the destination. `null` for a destination created with an API key." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "health_status": { + "type": "string", + "enum": [ + "healthy", + "degraded", + "failing" + ], + "description": "Delivery health. `failing`: 3 or more consecutive events failed their first delivery attempt since the destination last answered successfully (see `failing_streak`). `degraded`: not failing, but a delivery attempt failed in the last 24 hours and no later attempt has delivered that event; a retry or resend that delivers it clears this. `healthy`: neither. Independent of `enabled`. A failed attempt is one answered with HTTP 400 or above, or not answered at all." + }, + "failing_streak": { + "type": "integer", + "description": "How many consecutive events failed their first delivery attempt since the destination last answered successfully, within the last 30 days. Retries are not counted, and any successful attempt, including a retry or a resend, ends the streak. `0` when no event has failed since the last success." + }, + "failing_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the first failed attempt of the current `failing_streak` was made. `null` when `failing_streak` is `0`." + }, + "last_success_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the destination last answered a delivery with an HTTP status under 400, within the last 30 days. `null` if it has not in that window." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the destination." + }, + "summary": { + "type": "object", + "properties": { + "total_deliveries": { + "type": "integer" + }, + "failed_deliveries": { + "type": "integer" + }, + "error_rate_percentage": { + "type": "integer" + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true + }, + "last_delivery_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "metrics_window_days": { + "type": "integer", + "description": "Length in days of the window the other summary metrics cover. Always `30`." + } + }, + "description": "Delivery metrics for the last `metrics_window_days` (30) days. `last_delivery_at` is the most recent attempt, whenever it was made." + } + } + }, + "examples": { + "Paused": { + "value": { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": false, + "webhook_version": "v3", + "secret_shared_key": "tno2NTeRC1ZK3sBOYlBJDyEzBVENl3pQ1AcZyAW0xnQ", + "secret_last4": "0xnQ", + "previous_secret_expires_at": null, + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_by_email": null, + "created_by_name": null, + "created_at": "2026-01-04T08:00:00Z", + "updated_at": "2026-05-17T11:02:55Z", + "health_status": "healthy", + "failing_streak": 0, + "failing_since": null, + "last_success_at": "2026-05-17T09:21:18Z", + "environment": "live", + "summary": { + "total_deliveries": 12453, + "failed_deliveries": 14, + "error_rate_percentage": 0, + "min_response_time_ms": 41, + "avg_response_time_ms": 187, + "max_response_time_ms": 2103, + "last_delivery_at": "2026-05-17T09:21:18Z", + "metrics_window_days": 30 + } + } + } + } + } + } + }, + "400": { + "description": "Validation failed (duplicate URL, empty/unknown events list, malformed body).", + "content": { + "application/json": { + "examples": { + "Duplicate URL": { + "value": { + "url": [ + "This webhook destination already exists for the application." + ] + } + }, + "Empty events": { + "value": { + "subscribed_events": [ + "At least one subscribed event is required." + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Destination with this UUID does not exist (or is deleted, or belongs to another application).", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete webhook destination", + "description": "Permanently delete a webhook destination, its signing secret and its entire delivery log. This cannot be undone. No new webhook is sent to the destination's URL; other destinations subscribed to the same events keep receiving them. Afterwards the destination no longer appears in `GET /v3/webhook/destinations/`, and every route under `/v3/webhook/destinations/{destination_uuid}/`, including its deliveries, a delivery's detail and its resend, answers 404. The application's legacy `webhook_url` is set to the URL of the oldest remaining enabled destination, or cleared when no enabled destination is left. Calling DELETE again returns 404.", + "operationId": "delete_webhook_destination", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination to delete.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.webhooks.destinations.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.webhooks.destinations.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Webhooks.Destinations.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.webhooks().destinations().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.webhooks.destinations.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Webhooks.Destinations.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ], + "responses": { + "204": { + "description": "Destination, signing secret and delivery log deleted permanently. No response body." + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No destination with this UUID exists for the application (already deleted or never existed).", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/wallet-screening/": { + "post": { + "summary": "Screen a wallet address", + "description": "Run on-demand AML screening for a single crypto wallet/address without creating a transaction. Resolves your application's configured blockchain analytics provider, screens the address synchronously, and returns the normalised risk result. LIGHTNING is available only with an Elliptic or Chainalysis BYOK connection added through Marketplace. Nothing is written to the transactions table. One AML monitoring usage is billed per successful screening (sandbox applications are not billed).", + "operationId": "screenWallet", + "x-didit-sdk": { + "resource": "wallet_screening", + "method": "screen", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "wallet_address" + ], + "properties": { + "wallet_address": { + "type": "string", + "description": "The crypto address or network identifier to screen. Must match the selected network (e.g. a `0x`-prefixed 40-hex-char address for EVM chains). Alias: `address`.", + "example": "0x94f1b9b64e2932f6a2db338f616844400cd58e8a" + }, + "blockchain": { + "type": "string", + "description": "Optional network identifier. When omitted, the network is read from the address format: `bc1`, `1` and `3` addresses are Bitcoin (BTC), `T` addresses are Tron (TRX) and base58 public keys are Solana (SOL). A `0x` address is valid on every EVM network, so omitting `blockchain` for it answers 422 with the candidate networks in `candidate_chains`. Managed screening supports BTC, ETH, LTC, XRP, BCH, DOGE, TRX, SOL, MATIC, BNB, BASE (Base), OP (Optimism), ARB (Arbitrum One), AVAX (Avalanche C-Chain), ADA (Cardano), XLM (Stellar) and HBAR (Hedera). USDT and USDC are legacy Ethereum aliases, not multi-network searches. Connected-provider coverage varies. LIGHTNING requires a supported connected provider. CELO (Celo), MODE (Mode) and PLUME (Plume) are screened through a connected TRM Labs account; managed screening answers 400 for them. Alias: `currency`.", + "enum": [ + "BTC", + "LIGHTNING", + "ETH", + "LTC", + "XRP", + "BCH", + "DOGE", + "TRX", + "SOL", + "MATIC", + "BNB", + "USDT", + "USDC", + "BASE", + "OP", + "ARB", + "AVAX", + "ADA", + "XLM", + "HBAR", + "CELO", + "MODE", + "PLUME" + ], + "example": "ETH" + }, + "direction": { + "type": "string", + "description": "Optional. Whether the address is being screened as an inbound (deposit) or outbound (withdrawal) counterparty. Accepts `inbound`, `outbound`, `deposit`, or `withdrawal`. Defaults to a neutral pre-transfer screen when omitted. This is passed to the provider as context only; it does not change the returned `risk_score`, `severity`, or the `source_of_funds`/`destination_of_funds` breakdowns, which are always both returned regardless of `direction`. Changing or omitting `direction` therefore does not require re-screening an address.", + "enum": [ + "inbound", + "outbound", + "deposit", + "withdrawal" + ] + } + } + }, + "example": { + "wallet_address": "0x94f1b9b64e2932f6a2db338f616844400cd58e8a", + "blockchain": "ETH" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/wallet-screening/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"wallet_address\": \"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\",\n \"blockchain\": \"ETH\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.wallet_screening.screen(\n wallet_address=\"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\",\n blockchain=\"ETH\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.walletScreening.screen({\n wallet_address: \"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\",\n blockchain: \"ETH\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.WalletScreening.Screen(ctx, didit.WalletScreeningScreenParams{\n\tWalletAddress: \"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\",\n\tBlockchain: didit.Some(didit.WalletScreeningScreenParamsBlockchain(\"ETH\")),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.WalletScreeningScreenParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.walletScreening().screen(WalletScreeningScreenParams.builder()\n .walletAddress(\"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\")\n .blockchain(WalletScreeningScreenParams.Blockchain.of(\"ETH\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "walletScreening->screen([\n 'wallet_address' => '0x94f1b9b64e2932f6a2db338f616844400cd58e8a',\n 'blockchain' => 'ETH',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.wallet_screening.screen(\n wallet_address: \"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\",\n blockchain: \"ETH\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.WalletScreening.ScreenAsync(new WalletScreeningScreenParams\n{\n WalletAddress = \"0x94f1b9b64e2932f6a2db338f616844400cd58e8a\",\n Blockchain = new WalletScreeningScreenParamsBlockchain(\"ETH\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The normalised wallet screening result.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletScreeningResult" + }, + "example": { + "provider": "merklescience", + "screening_type": "WALLET_SCREENING", + "risk_score": 95, + "severity": "CRITICAL", + "status": "SCREENED", + "summary": "Merkle Science risk: Critical (5/5). SANCTIONS HIT. Dominant risk: sanctioned. Driven by: the address is attributed to Sanctions / OFAC (Example Sanctions)", + "wallet_address": "0x94f1b9b64e2932f6a2db338f616844400cd58e8a", + "transaction_hash": null, + "blockchain": "ETH", + "block_timestamp": null, + "total_value_usd": null, + "fee_usd": null, + "sender_rows": [], + "receiver_rows": [], + "transaction_flows": [], + "transaction_flows_status": null, + "transaction_flows_error": null, + "source_of_funds": [ + { + "category": "sanctioned", + "percentage": 100.0, + "amount_usd": 9813695.0, + "entity_name": "Example Sanctions", + "entity_type": "Sanctions", + "entity_subtype": "OFAC", + "hops": null, + "description": "Sanctions / OFAC", + "is_direct": true, + "exposure_direction": "incoming", + "exposure_type": "direct", + "provider_entity_id": "118885", + "provider_entity_type_id": "30", + "provider_entity_subtype_id": "125", + "country": "", + "raw_amount": null, + "raw_currency": null + } + ], + "destination_of_funds": [ + { + "category": "defi", + "percentage": 84.824453, + "amount_usd": 7783414.38, + "entity_name": "Example DeFi 7", + "entity_type": "DeFi", + "entity_subtype": "Bridges", + "hops": null, + "description": "DeFi / Bridges", + "is_direct": false, + "exposure_direction": "outgoing", + "exposure_type": "indirect", + "provider_entity_id": "118459", + "provider_entity_type_id": "33", + "provider_entity_subtype_id": "143", + "country": "", + "raw_amount": null, + "raw_currency": null + }, + { + "category": "defi", + "percentage": 15.175547, + "amount_usd": 1392494.37, + "entity_name": "Example DeFi 2", + "entity_type": "DeFi", + "entity_subtype": "Yield Aggregator", + "hops": null, + "description": "DeFi / Yield Aggregator", + "is_direct": false, + "exposure_direction": "outgoing", + "exposure_type": "indirect", + "provider_entity_id": "90192", + "provider_entity_type_id": "33", + "provider_entity_subtype_id": "142", + "country": "", + "raw_amount": null, + "raw_currency": null + } + ], + "counterparty_connections": [ + { + "entity_name": "Example Sanctions", + "entity_type": "Sanctions", + "entity_subtype": "OFAC", + "risk_level": "critical", + "categories": [ + "sanctioned" + ], + "received_usd": null, + "sent_usd": null, + "received_hops": null, + "sent_hops": null, + "percentage": 0.0, + "is_direct": true, + "country": null, + "provider_entity_id": null + }, + { + "entity_name": "Example Sanctions", + "entity_type": "Sanctions", + "entity_subtype": "OFAC", + "risk_level": "critical", + "categories": [ + "sanctioned" + ], + "received_usd": null, + "sent_usd": null, + "received_hops": null, + "sent_hops": null, + "percentage": 0.0, + "is_direct": true, + "country": null, + "provider_entity_id": null + } + ], + "network_nodes": [], + "network_edges": [], + "sanctions_hit": true, + "pep_counterparty": null, + "dominant_risk_category": "sanctioned", + "risk_factors": [ + { + "source": "attribution", + "category": "sanctioned", + "label": "Sanctions / OFAC", + "entity_name": "Example Sanctions", + "direction": "attribution", + "exposure_type": "attribution", + "amount_usd": null, + "percentage": 0.0, + "is_high_risk": true, + "description": "the address is attributed to Sanctions / OFAC (Example Sanctions)" + }, + { + "source": "exposure", + "category": "sanctioned", + "label": "Sanctions / OFAC", + "entity_name": "Example Sanctions", + "direction": "incoming", + "exposure_type": "direct", + "amount_usd": 9813695.0, + "percentage": 51.679304, + "is_high_risk": true, + "description": "51.6793% direct incoming exposure to Sanctions / OFAC (Example Sanctions)" + }, + { + "source": "exposure", + "category": "defi", + "label": "DeFi / Bridges", + "entity_name": "Example DeFi 7", + "direction": "outgoing", + "exposure_type": "indirect", + "amount_usd": 7783414.38, + "percentage": 40.987766, + "is_high_risk": false, + "description": "40.9878% indirect outgoing exposure to DeFi / Bridges (Example DeFi 7)" + }, + { + "source": "exposure", + "category": "defi", + "label": "DeFi / Yield Aggregator", + "entity_name": "Example DeFi 2", + "direction": "outgoing", + "exposure_type": "indirect", + "amount_usd": 1392494.37, + "percentage": 7.33293, + "is_high_risk": false, + "description": "7.33293% indirect outgoing exposure to DeFi / Yield Aggregator (Example DeFi 2)" + } + ], + "raw_response": { + "...": "provider-specific details" + }, + "report_signature": "" + } + } + } + }, + "400": { + "description": "Validation error — malformed wallet address, missing `wallet_address`, unsupported `blockchain`, `blockchain` omitted for an address whose format names no network, or LIGHTNING without an Elliptic or Chainalysis BYOK Marketplace connection." + }, + "401": { + "description": "Missing or invalid `x-api-key`." + }, + "409": { + "description": "No AML provider is configured for the application, or the configured provider does not support wallet screening yet. Configure transaction monitoring first." + }, + "422": { + "description": "`blockchain` omitted for a `0x` address, which every EVM network shares. Nothing is screened or billed; resend with `blockchain` set to one of `candidate_chains`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "blockchain": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "A 0x address is valid on every EVM chain. Send blockchain as one of: ETH, BNB, MATIC, BASE, ARB, OP, CELO, MODE, PLUME." + ] + }, + "candidate_chains": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "ETH", + "BNB", + "MATIC", + "BASE", + "ARB", + "OP", + "CELO", + "MODE", + "PLUME" + ] + } + } + } + } + } + }, + "502": { + "description": "The AML provider could not complete the screening. Retry later." + } + } + } + }, + "/v3/wallet-screening/pdf/": { + "post": { + "summary": "Render a wallet-screening result as a PDF", + "description": "Stateless companion to `POST /v3/wallet-screening/`: repost the exact JSON body that endpoint returned — including the `report_signature` it added — and get back a Didit-branded PDF of the same result. No provider is called and nothing is billed or persisted; this only re-renders a result Didit already produced. The signature ties the PDF to an unmodified result: editing `risk_score`, `severity`, `sanctions_hit`, or any other signed field before reposting fails with `400`.", + "operationId": "screenWalletPdf", + "x-didit-sdk": { + "resource": "wallet_screening", + "method": "screen_pdf", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "description": "The unmodified JSON object returned by `POST /v3/wallet-screening/`, including `report_signature`.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/WalletScreeningResult" + } + ], + "required": [ + "report_signature" + ], + "description": "Same shape as the `POST /v3/wallet-screening/` response — pass that response body back verbatim, including `report_signature`." + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "# $RESULT is the saved JSON body from POST /v3/wallet-screening/\ncurl -X POST 'https://verification.didit.me/v3/wallet-screening/pdf/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d \"$RESULT\" \\\n -o wallet_screening_report.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.wallet_screening.screen_pdf(\n provider=\"your-provider\",\n screening_type=\"WALLET_SCREENING\",\n risk_score=1,\n severity=\"UNKNOWN\",\n status=\"SCREENED\",\n summary=\"your-summary\",\n wallet_address=\"your-wallet-address\",\n blockchain=\"your-blockchain\",\n source_of_funds=[{}],\n destination_of_funds=[{}],\n counterparty_connections=[{}],\n sanctions_hit=True,\n dominant_risk_category=\"your-dominant-risk-category\",\n report_signature=\"your-report-signature\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.walletScreening.screenPdf({\n provider: \"your-provider\",\n screening_type: \"WALLET_SCREENING\",\n risk_score: 1,\n severity: \"UNKNOWN\",\n status: \"SCREENED\",\n summary: \"your-summary\",\n wallet_address: \"your-wallet-address\",\n blockchain: \"your-blockchain\",\n source_of_funds: [{}],\n destination_of_funds: [{}],\n counterparty_connections: [{}],\n sanctions_hit: true,\n dominant_risk_category: \"your-dominant-risk-category\",\n report_signature: \"your-report-signature\",\n});\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.WalletScreening.ScreenPDF(ctx, didit.WalletScreeningResultParam{\n\tProvider: \"your-provider\",\n\tScreeningType: \"WALLET_SCREENING\",\n\tRiskScore: 1,\n\tSeverity: \"UNKNOWN\",\n\tStatus: \"SCREENED\",\n\tSummary: \"your-summary\",\n\tWalletAddress: \"your-wallet-address\",\n\tBlockchain: \"your-blockchain\",\n\tSourceOfFunds: []didit.WalletScreeningResultParamSourceOfFunds{{}},\n\tDestinationOfFunds: []didit.WalletScreeningResultParamDestinationOfFunds{{}},\n\tCounterpartyConnections: []didit.WalletScreeningResultParamCounterpartyConnections{{}},\n\tSanctionsHit: true,\n\tDominantRiskCategory: didit.String(\"your-dominant-risk-category\"),\n\tReportSignature: \"your-report-signature\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.WalletScreeningResult;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.walletScreening().screenPdf(WalletScreeningResult.builder()\n .provider(\"your-provider\")\n .screeningType(WalletScreeningResult.ScreeningType.of(\"WALLET_SCREENING\"))\n .riskScore(1L)\n .severity(WalletScreeningResult.Severity.of(\"UNKNOWN\"))\n .status(WalletScreeningResult.Status.of(\"SCREENED\"))\n .summary(\"your-summary\")\n .walletAddress(\"your-wallet-address\")\n .blockchain(\"your-blockchain\")\n .sourceOfFunds(List.of(WalletScreeningResult.SourceOfFunds.builder()\n .build()))\n .destinationOfFunds(List.of(WalletScreeningResult.DestinationOfFunds.builder()\n .build()))\n .counterpartyConnections(List.of(WalletScreeningResult.CounterpartyConnections.builder()\n .build()))\n .sanctionsHit(true)\n .dominantRiskCategory(\"your-dominant-risk-category\")\n .reportSignature(\"your-report-signature\")\n .build())\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "walletScreening->screenPdf([\n 'provider' => 'your-provider',\n 'screening_type' => 'WALLET_SCREENING',\n 'risk_score' => 1,\n 'severity' => 'UNKNOWN',\n 'status' => 'SCREENED',\n 'summary' => 'your-summary',\n 'wallet_address' => 'your-wallet-address',\n 'blockchain' => 'your-blockchain',\n 'source_of_funds' => [[]],\n 'destination_of_funds' => [[]],\n 'counterparty_connections' => [[]],\n 'sanctions_hit' => true,\n 'dominant_risk_category' => 'your-dominant-risk-category',\n 'report_signature' => 'your-report-signature',\n]);\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.wallet_screening.screen_pdf(\n provider: \"your-provider\",\n screening_type: \"WALLET_SCREENING\",\n risk_score: 1,\n severity: \"UNKNOWN\",\n status: \"SCREENED\",\n summary: \"your-summary\",\n wallet_address: \"your-wallet-address\",\n blockchain: \"your-blockchain\",\n source_of_funds: [{}],\n destination_of_funds: [{}],\n counterparty_connections: [{}],\n sanctions_hit: true,\n dominant_risk_category: \"your-dominant-risk-category\",\n report_signature: \"your-report-signature\"\n)\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.WalletScreening.ScreenPdfAsync(new WalletScreeningResult\n{\n Provider = \"your-provider\",\n ScreeningType = \"WALLET_SCREENING\",\n RiskScore = 1,\n Severity = \"UNKNOWN\",\n Status = \"SCREENED\",\n Summary = \"your-summary\",\n WalletAddress = \"your-wallet-address\",\n Blockchain = \"your-blockchain\",\n SourceOfFunds = new WalletScreeningResultSourceOfFunds[] { new() },\n DestinationOfFunds = new WalletScreeningResultDestinationOfFunds[] { new() },\n CounterpartyConnections = new WalletScreeningResultCounterpartyConnections[] { new() },\n SanctionsHit = true,\n DominantRiskCategory = \"your-dominant-risk-category\",\n ReportSignature = \"your-report-signature\",\n});\nawait content.SaveToAsync(\"download.pdf\");" + } + ], + "responses": { + "200": { + "description": "The rendered PDF document, returned directly as binary `application/pdf` — there is no JSON wrapper.", + "headers": { + "Content-Disposition": { + "description": "`attachment; filename=wallet_screening_{address}.pdf`, derived from the `wallet_address` (or `transaction_hash`) in the reposted result.", + "schema": { + "type": "string", + "example": "attachment; filename=wallet_screening_0x28c6c06298d514db0899.pdf" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "400": { + "description": "The body is not a JSON object matching the wallet-screening result shape, or `report_signature` is missing or does not match the reposted fields (edited or truncated before repost).", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "examples": { + "Not an object": { + "value": { + "non_field_errors": [ + "Expected a JSON object matching a wallet-screening result." + ] + } + }, + "Missing or invalid signature": { + "value": { + "report_signature": [ + "This wallet-screening result is missing or fails its report signature - repost the exact JSON returned by the wallet-screening endpoint, unmodified." + ] + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid `x-api-key`." + } + } + } + }, + "/v3/transactions/": { + "get": { + "summary": "List transactions", + "description": "Paginated list of monitored transactions, newest first (`txn_date` desc). `count` is capped at 100; use `total_transactions` for the true row count. This endpoint does not support filtering or search parameters.", + "operationId": "listTransactions", + "x-didit-sdk": { + "resource": "transactions", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "description": "Page size. Defaults to 50 when omitted. There is no enforced maximum, but keep pages small for latency." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "description": "Zero-based offset of the first record to return. Prefer following the `next` URL from the previous page." + }, + { + "name": "travel_rule_status", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Return only transactions whose Travel Rule transfer is in this exact status (for example `ON_HOLD` or `AWAITING_COUNTERPARTY`). `count` follows this filter; `total_transactions` still counts every transaction in the application." + }, + { + "name": "assignee", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated; a transaction matches when it meets any value. `unassigned` is what nobody holds yet, `rules` is what the rules decided on their own without a reviewer, and a member email matches the reviewer assigned to it. `me` is the signed-in Console reviewer, so an API key sending `me` gets `400`." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.transactions.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.transactions.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Transactions.ListAutoPaging(ctx, didit.TransactionsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.transactions().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Transactions.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated list of transaction summaries, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Capped count of matching transactions (exact up to 100, capped thereafter). Use `total_transactions` for the true count when paginating." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "total_transactions": { + "type": "integer", + "description": "Exact total count of (non-deleted) transactions for the application. Present on paginated responses." + }, + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Stable Didit transaction identifier. Use as `{transaction_id}` in detail/notes/related calls." + }, + "transaction_number": { + "type": "integer", + "description": "Application-scoped sequential transaction number shown in Console (e.g. `4123`)." + }, + "txn_id": { + "type": "string", + "description": "Customer-provided identifier from the create call (max 128 chars). Unique per application." + }, + "transaction_type": { + "type": "string", + "description": "Top-level category as stored: `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet`, `gamblingLimitChange`, `gamblingBonusChange`, `auditTrailEvent`. Note multi-word values are echoed back in camelCase even when submitted in snake_case." + }, + "action_type": { + "type": "string", + "description": "Sub-type within the category (e.g. `deposit`, `withdrawal`, `transfer`)." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Current monitoring verdict." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject. Stored uppercase regardless of the casing submitted." + }, + "amount": { + "type": "string", + "description": "Transaction amount as a decimal string with trailing zeros stripped (e.g. `\"1500\"`, `\"0.5\"`, `\"0.123456789012345678\"`). Up to 18 decimal places." + }, + "currency": { + "type": "string", + "description": "Currency code of `amount`. Any ISO 4217 alphabetic code is accepted (e.g. `EUR`, `USD`, `NGN`), as is a crypto asset symbol (e.g. `BTC`). Prefer a currently-circulating code. Not every accepted code has an exchange rate: when none is available for the code (for example the ISO accounting unit `XSU`, or a crypto symbol Didit does not price), automatic conversion fails and `preferred_currency_amount` is returned null unless the request supplied a pre-converted `amount_in_default_currency`." + }, + "preferred_currency_amount": { + "type": "string", + "nullable": true, + "description": "`amount` converted to the application's preferred currency, when available." + }, + "preferred_currency_code": { + "type": "string", + "nullable": true, + "description": "Preferred currency code used for `preferred_currency_amount` - the application's base currency, any currently-circulating ISO 4217 code. A code with no available exchange rate leaves `preferred_currency_amount` null unless the request supplied a pre-converted `amount_in_default_currency`." + }, + "payment_details": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference or memo from the submission." + }, + "score": { + "type": "integer", + "description": "Risk score (typically 0–100). Higher = riskier." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ], + "description": "Categorical risk severity set by rules/providers (`UNKNOWN`, `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`). `null` until something sets it." + }, + "subject_name": { + "type": "string", + "nullable": true, + "description": "Full name of the `APPLICANT` party." + }, + "subject_vendor_data": { + "type": "string", + "nullable": true, + "description": "Your internal user/business identifier for the applicant." + }, + "subject_country": { + "type": "string", + "nullable": true, + "description": "Country code of the applicant, from the submitted `address.country` (typically ISO 3166-1 alpha-3)." + }, + "counterparty_name": { + "type": "string", + "nullable": true, + "description": "Full name of the `COUNTERPARTY` party, if any." + }, + "counterparty_vendor_data": { + "type": "string", + "nullable": true, + "description": "Your internal identifier for the counterparty." + }, + "counterparty_country": { + "type": "string", + "nullable": true, + "description": "Country code of the counterparty, from the submitted `address.country`." + }, + "tags": { + "type": "array", + "description": "Tags attached to the transaction (manually or by rules).", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ], + "description": "Whether the tag is a Didit preset or a custom tag." + } + } + } + }, + "txn_date": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred (from `transaction_at` at create time)." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When Didit recorded the submission." + }, + "decision_reason_code": { + "type": "string", + "nullable": true, + "description": "Machine-readable reason for the current `status` (e.g. `WALLET_HIGH_RISK`)." + }, + "decision_reason_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label for `decision_reason_code`." + }, + "assigned_to_name": { + "type": "string", + "nullable": true, + "description": "Display name of the Console reviewer assigned to this transaction." + }, + "travel_rule_status": { + "type": "string", + "nullable": true, + "description": "Status of the transaction's Travel Rule transfer (for example `COMPLETED`), or `null` when it has none." + }, + "travel_rule": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TransactionTravelRule" + } + ], + "description": "The transaction's Travel Rule transfer, the same block as `travel_rule` on `GET /v3/transactions/{transaction_id}/`, or `null` when the transaction has none." + } + } + } + } + } + }, + "examples": { + "Single transaction": { + "value": { + "count": 1, + "next": null, + "previous": null, + "total_transactions": 1, + "results": [ + { + "uuid": "abcdef12-3456-4890-abcd-ef1234567890", + "transaction_number": 4123, + "txn_id": "your-txn-2026-05-17-001", + "transaction_type": "finance", + "action_type": "withdrawal", + "status": "IN_REVIEW", + "direction": "OUTBOUND", + "amount": "1500", + "currency": "EUR", + "preferred_currency_amount": "1620.45", + "preferred_currency_code": "USD", + "payment_details": "Withdrawal to crypto wallet", + "txn_date": "2026-05-17T08:42:00Z", + "created_at": "2026-05-17T08:42:11Z", + "score": 78, + "severity": "HIGH", + "decision_reason_code": "WALLET_HIGH_RISK", + "decision_reason_label": "Destination wallet flagged as high risk", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "Manual review", + "color": "#F59E0B", + "description": null, + "source": "custom" + } + ], + "subject_name": "Maria Garcia", + "subject_vendor_data": "user-12345", + "counterparty_name": null, + "counterparty_vendor_data": null, + "counterparty_country": null, + "subject_country": null, + "assigned_to_name": "Alex Reviewer", + "travel_rule_status": null, + "travel_rule": null + } + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot list transactions for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create transaction", + "description": "Submit a transaction for synchronous AML/risk monitoring. The transaction is created `APPROVED`, then your rules (and crypto screening, when applicable) run inline and may flip the returned `status` to `IN_REVIEW` or `DECLINED`. **`transaction_id` is your idempotency key** (max 128 chars, unique per app); reusing it returns 400. A `transaction.created` webhook is dispatched on success for live applications. Sandbox applications are not billed and do not emit `transaction.created` or `transaction.status.updated` webhooks. Sandbox Travel Rule exchanges do emit `travel_rule.status.updated` webhooks with `environment: sandbox`. A Travel Rule transfer is stored and returned before any counterparty is contacted: `travel_rule.status` comes back `AWAITING_COUNTERPARTY` (or `NOT_APPLICABLE` below your threshold, `UNCONFIRMED_OWNERSHIP` for a declared self-hosted wallet, `COMPLETED` when the other wallet is your own verified wallet) and `travel_rule.protocol` is null. Didit then chooses the rail and sends the exchange, and every later status change of the transfer, including `COUNTERPARTY_VASP_NOT_FOUND` when no rail reaches the counterparty, arrives as a `travel_rule.status.updated` webhook. Your rules run again when the rail or the status changes.", + "operationId": "createTransaction", + "tags": [ + "Transactions" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"transaction_id\": \"your-txn-2026-05-17-001\",\n \"transaction_category\": \"finance\",\n \"transaction_details\": {\n \"direction\": \"outbound\",\n \"amount\": 1500,\n \"currency\": \"EUR\",\n \"action_type\": \"withdrawal\"\n },\n \"subject\": {\n \"entity_type\": \"individual\",\n \"vendor_data\": \"user-12345\",\n \"full_name\": \"Maria Garcia\"\n }\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.create(\n transaction_id=\"your-txn-2026-05-17-001\",\n transaction_category=\"finance\",\n transaction_details={\n \"direction\": \"outbound\",\n \"amount\": 1500.0,\n \"currency\": \"EUR\",\n \"action_type\": \"withdrawal\",\n },\n subject={\n \"entity_type\": \"individual\",\n \"vendor_data\": \"user-12345\",\n \"full_name\": \"Maria Garcia\",\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.create({\n transaction_id: \"your-txn-2026-05-17-001\",\n transaction_category: \"finance\",\n transaction_details: {\n direction: \"outbound\",\n amount: 1500.0,\n currency: \"EUR\",\n action_type: \"withdrawal\",\n },\n subject: {\n entity_type: \"individual\",\n vendor_data: \"user-12345\",\n full_name: \"Maria Garcia\",\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Create(ctx, didit.TransactionsCreateParams{\n\tTransactionID: \"your-txn-2026-05-17-001\",\n\tTransactionCategory: \"finance\",\n\tTransactionDetails: didit.TransactionsCreateParamsTransactionDetails{\n\t\tDirection: \"outbound\",\n\t\tAmount: 1500.0,\n\t\tCurrency: \"EUR\",\n\t\tActionType: didit.String(\"withdrawal\"),\n\t},\n\tSubject: didit.TransactionsCreateParamsSubject{\n\t\tEntityType: didit.String(\"individual\"),\n\t\tVendorData: \"user-12345\",\n\t\tFullName: \"Maria Garcia\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TransactionsCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().create(TransactionsCreateParams.builder()\n .transactionId(\"your-txn-2026-05-17-001\")\n .transactionCategory(TransactionsCreateParams.TransactionCategory.of(\"finance\"))\n .transactionDetails(TransactionsCreateParams.TransactionDetails.builder()\n .direction(TransactionsCreateParams.TransactionDetails.Direction.of(\"outbound\"))\n .amount(1500.0)\n .currency(\"EUR\")\n .actionType(\"withdrawal\")\n .build())\n .subject(TransactionsCreateParams.Subject.builder()\n .entityType(\"individual\")\n .vendorData(\"user-12345\")\n .fullName(\"Maria Garcia\")\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->create([\n 'transaction_id' => 'your-txn-2026-05-17-001',\n 'transaction_category' => 'finance',\n 'transaction_details' => [\n 'direction' => 'outbound',\n 'amount' => 1500.0,\n 'currency' => 'EUR',\n 'action_type' => 'withdrawal',\n ],\n 'subject' => [\n 'entity_type' => 'individual',\n 'vendor_data' => 'user-12345',\n 'full_name' => 'Maria Garcia',\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.create(\n transaction_id: \"your-txn-2026-05-17-001\",\n transaction_category: \"finance\",\n transaction_details: {\n direction: \"outbound\",\n amount: 1500.0,\n currency: \"EUR\",\n action_type: \"withdrawal\",\n },\n subject: {\n entity_type: \"individual\",\n vendor_data: \"user-12345\",\n full_name: \"Maria Garcia\",\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.CreateAsync(new TransactionsCreateParams\n{\n TransactionId = \"your-txn-2026-05-17-001\",\n TransactionCategory = \"finance\",\n TransactionDetails = new()\n {\n Direction = \"outbound\",\n Amount = 1500.0,\n Currency = \"EUR\",\n ActionType = \"withdrawal\",\n },\n Subject = new()\n {\n EntityType = \"individual\",\n VendorData = \"user-12345\",\n FullName = \"Maria Garcia\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "transaction_id": { + "type": "string", + "description": "Your unique identifier for this transaction (max 128 characters).", + "example": "your-txn-2026-05-17-001" + }, + "transaction_at": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred. Defaults to now if omitted." + }, + "time_zone": { + "type": "string", + "description": "IANA time zone identifier, e.g. `Europe/Madrid`." + }, + "transaction_category": { + "type": "string", + "description": "Category of the transaction. CamelCase aliases (`travelRule`, `userPlatformEvent`, `gamblingBet`, …) are also accepted; the stored/echoed value is camelCase for multi-word categories.", + "example": "finance", + "enum": [ + "finance", + "kyc", + "travel_rule", + "user_event", + "audit_trail_event", + "gambling_bet", + "gambling_limit_change", + "gambling_bonus_change", + "travelRule" + ] + }, + "transaction_details": { + "type": "object", + "description": "Core financial details of the transaction.", + "required": [ + "direction", + "amount", + "currency" + ], + "properties": { + "direction": { + "type": "string", + "description": "Direction of the transaction relative to the subject. Case-insensitive; `in`/`out` are accepted as aliases. Echoed back uppercase (`INBOUND`/`OUTBOUND`).", + "example": "outbound", + "enum": [ + "inbound", + "outbound", + "in", + "out" + ] + }, + "amount": { + "type": "number", + "description": "Transaction amount (up to 18 decimal places). Send it as a decimal string (e.g. `\"0.123456789012345678\"`) when exact precision matters: a JSON number is parsed as a binary float first, so digits beyond about 15 significant digits are rounded before the amount is stored. Must be zero or greater: the direction carries the sign, and a negative amount returns `400`.", + "example": 1500 + }, + "currency": { + "type": "string", + "description": "Currency code. Any ISO 4217 alphabetic code is accepted (e.g. `EUR`, `USD`, `NGN`), as is a crypto asset symbol (e.g. `BTC`). Prefer a currently-circulating code. Not every accepted code has an exchange rate: when none is available for the code (for example the ISO accounting unit `XSU`, or a crypto symbol Didit does not price), automatic conversion fails and `preferred_currency_amount` is returned null unless the request supplied a pre-converted `amount_in_default_currency`.", + "example": "EUR" + }, + "currency_kind": { + "type": "string", + "description": "Whether the currency is `fiat` or `crypto`." + }, + "amount_in_default_currency": { + "type": "number", + "nullable": true, + "description": "Pre-converted amount in the default currency. If omitted, automatic FX conversion is attempted. Must be zero or greater." + }, + "default_currency": { + "type": "string", + "description": "The default currency code for the converted amount. Any currently-circulating ISO 4217 code." + }, + "payment_details": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference or memo." + }, + "payment_reference_id": { + "type": "string", + "nullable": true, + "description": "External payment system reference. For crypto AML transaction-hash screening, send the blockchain transaction hash and also provide `subject.payment_method.account_id` with the related address." + }, + "action_type": { + "type": "string", + "nullable": true, + "description": "Sub-type of the transaction, e.g. `deposit`, `withdrawal`, `transfer`." + }, + "crypto_params": { + "type": "object", + "description": "Crypto network selection, separate from currency (the asset symbol). Recording a custom token symbol does not guarantee transaction-hash coverage for its contract.", + "properties": { + "crypto_chain": { + "type": "string", + "description": "Actual network used for screening. Managed screening supports BTC, ETH, LTC, XRP, BCH, DOGE, TRX, SOL, MATIC, BNB, BASE (Base), OP (Optimism), ARB (Arbitrum One), AVAX (Avalanche C-Chain), ADA (Cardano), XLM (Stellar) and HBAR (Hedera). Common network names are accepted too, for example POLYGON or POL for MATIC, BSC for BNB and TRON for TRX. Always specify this for multi-network tokens. When it is missing for USDT, USDC or ETH sent with a 0x transaction hash, Didit looks the hash up on Ethereum, BNB Chain, Polygon, Base, Arbitrum and Optimism and screens it on the network where it is found; if no network has it, the transaction is not screened and custom_values.aml_screening_status is FAILED with the reason in custom_values.aml_screening_error. When it is missing for USDT or USDC screened by wallet (no transaction hash), a Tron address is screened on Tron, a Solana address on Solana, and a 0x address on the first of those six networks where it has sent a transaction or holds the token, or on Ethereum when it is active on none of them. Connected-provider coverage varies; LIGHTNING requires a supported connected provider.", + "example": "BASE" + } + } + } + } + }, + "subject": { + "type": "object", + "description": "The subject (applicant) of the transaction.", + "required": [ + "vendor_data", + "full_name" + ], + "properties": { + "entity_type": { + "type": "string", + "description": "Entity type: `individual` or `company`." + }, + "vendor_data": { + "type": "string", + "description": "Your internal user/business identifier.", + "example": "user-12345" + }, + "full_name": { + "type": "string", + "description": "Full name of the subject.", + "example": "Maria Garcia" + }, + "first_name": { + "type": "string", + "description": "First name of the subject." + }, + "last_name": { + "type": "string", + "description": "Last name of the subject." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth (YYYY-MM-DD)." + }, + "address": { + "type": "object", + "description": "Address object.", + "properties": { + "country": { + "type": "string", + "description": "Country code (ISO 3166-1 alpha-3 recommended). Copied to the party's `country_code`." + }, + "town": { + "type": "string" + }, + "state": { + "type": "string" + }, + "street": { + "type": "string" + }, + "post_code": { + "type": "string" + } + } + }, + "institution_details": { + "type": "object", + "description": "Institution details (for institutional subjects).", + "properties": { + "name": { + "type": "string" + }, + "code": { + "type": "string" + }, + "address": { + "type": "object" + } + } + }, + "device_context": { + "type": "object", + "description": "Device context captured at transaction time. browser_family and platform must be sent as explicit top-level keys — they are not derived from user_agent. The camelCase alias device is also accepted on the request.", + "properties": { + "fingerprint": { + "type": "string", + "description": "Unique device fingerprint." + }, + "persistent_device_id": { + "type": "string", + "description": "Stable cross-session device identifier (v2 fingerprint schema)." + }, + "user_agent": { + "type": "string", + "description": "Browser or app user agent string. CamelCase alias: userAgent." + }, + "browser_family": { + "type": "string", + "description": "Browser family (e.g. Chrome, Firefox, Safari). Must be sent explicitly — it is not derived from user_agent. CamelCase alias: browserFamily." + }, + "browser_version": { + "type": "string", + "description": "Browser version string. Must be sent explicitly. CamelCase alias: browserVersion." + }, + "platform": { + "type": "string", + "description": "Platform identifier (web, ios, android, macos, windows, linux). Must be sent explicitly — it is not derived from user_agent." + }, + "os_family": { + "type": "string", + "description": "Operating system family (e.g. iOS, Android, Windows). CamelCase alias: osFamily." + }, + "os_version": { + "type": "string", + "description": "Operating system version string. CamelCase alias: osVersion." + }, + "accept_language": { + "type": "string", + "description": "Accept-Language header value. CamelCase alias: acceptLang." + }, + "session_id": { + "type": "string", + "description": "Session identifier of the user current session. Stored in metadata. CamelCase alias: sessionId." + }, + "session_age_ms": { + "type": "number", + "description": "Age of the user session in milliseconds at transaction time. CamelCase alias: sessionAgeMs." + }, + "network_context": { + "type": "object", + "description": "Network context with IP address and geo-location. A top-level ipInfo block is also folded into this object.", + "properties": { + "ip_address": { + "type": "string", + "description": "Client IP address. CamelCase alias: ip." + }, + "ip_country_code": { + "type": "string", + "description": "Geo-resolved country of the IP (ISO 3166-1 alpha-3). CamelCase alias: countryCode3." + }, + "latitude": { + "type": "number", + "description": "Geographic latitude of the IP. CamelCase alias: lat." + }, + "longitude": { + "type": "number", + "description": "Geographic longitude of the IP. CamelCase alias: lon." + } + } + } + } + }, + "payment_method": { + "type": "object", + "description": "Payment method used by the subject.", + "properties": { + "method_type": { + "type": "string", + "description": "Type of payment method: `bank_card`, `bank_account`, `crypto_wallet`, `ewallet`, `unhosted_wallet`, `other`." + }, + "account_id": { + "type": "string", + "description": "Account identifier (e.g. IBAN, wallet address). For crypto screening, this should contain the wallet address for this participant. Inbound transaction-hash screening requires the service/customer deposit address on the subject. Outbound screening uses the destination wallet on the counterparty. On a crypto transfer on bitcoin or Lightning it may be a Lightning payment request (BOLT11 invoice, BOLT12 offer, LNURL) or a Lightning Address: Didit stores the payee it names as the account (\"\" when it names none) and what the request says on the Travel Rule transfer's `chain_details.lightning`. At most 255 characters once stored. On a crypto transfer on bitcoin or Lightning it may be a Lightning payment request (BOLT11 invoice, BOLT12 offer, LNURL) or a Lightning Address: Didit stores the payee it names as the account (\"\" when it names none) and what the request says on the Travel Rule transfer's `chain_details.lightning`. At most 255 characters once stored." + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "The destination tag or memo that is part of the wallet on a memo network (XRP, XLM, EOS, ATOM, HBAR). On one shared exchange address each memo is its own payment method. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`. Alias: `destinationTag` or `destination_tag`." + }, + "issuing_country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code of the issuer." + } + } + } + } + }, + "counterparty": { + "type": "object", + "description": "The counterparty of the transaction (optional).", + "properties": { + "entity_type": { + "type": "string", + "description": "Entity type: `individual` or `company`." + }, + "vendor_data": { + "type": "string", + "description": "Your internal identifier for the counterparty." + }, + "full_name": { + "type": "string", + "description": "Full name of the counterparty." + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "address": { + "type": "object", + "description": "Address object with `country` (ISO 3166-1 alpha-3)." + }, + "institution_details": { + "type": "object" + }, + "device_context": { + "type": "object", + "description": "Device context for the counterparty. browser_family and platform must be sent as explicit top-level keys — they are not derived from user_agent.", + "properties": { + "fingerprint": { + "type": "string", + "description": "Unique device fingerprint." + }, + "persistent_device_id": { + "type": "string", + "description": "Stable cross-session device identifier (v2 fingerprint schema)." + }, + "user_agent": { + "type": "string", + "description": "Browser or app user agent string. CamelCase alias: userAgent." + }, + "browser_family": { + "type": "string", + "description": "Browser family (e.g. Chrome, Firefox, Safari). Must be sent explicitly — it is not derived from user_agent. CamelCase alias: browserFamily." + }, + "browser_version": { + "type": "string", + "description": "Browser version string. Must be sent explicitly. CamelCase alias: browserVersion." + }, + "platform": { + "type": "string", + "description": "Platform identifier (web, ios, android, macos, windows, linux). Must be sent explicitly — it is not derived from user_agent." + }, + "os_family": { + "type": "string", + "description": "Operating system family (e.g. iOS, Android, Windows). CamelCase alias: osFamily." + }, + "os_version": { + "type": "string", + "description": "Operating system version string. CamelCase alias: osVersion." + }, + "accept_language": { + "type": "string", + "description": "Accept-Language header value. CamelCase alias: acceptLang." + }, + "session_id": { + "type": "string", + "description": "Session identifier for the counterparty session. Stored in metadata. CamelCase alias: sessionId." + }, + "session_age_ms": { + "type": "number", + "description": "Age of the user session in milliseconds at transaction time. CamelCase alias: sessionAgeMs." + }, + "network_context": { + "type": "object", + "description": "Network context with IP address and geo-location. A top-level ipInfo block is also folded into this object.", + "properties": { + "ip_address": { + "type": "string", + "description": "Client IP address. CamelCase alias: ip." + }, + "ip_country_code": { + "type": "string", + "description": "Geo-resolved country of the IP (ISO 3166-1 alpha-3). CamelCase alias: countryCode3." + }, + "latitude": { + "type": "number", + "description": "Geographic latitude of the IP. CamelCase alias: lat." + }, + "longitude": { + "type": "number", + "description": "Geographic longitude of the IP. CamelCase alias: lon." + } + } + } + } + }, + "payment_method": { + "type": "object", + "description": "Payment method used by the counterparty. On travelRule transactions, `method_type: \"unhosted_wallet\"` declares the counterparty wallet as self-hosted: the transfer skips counterparty-VASP routing, is created directly in `UNCONFIRMED_OWNERSHIP`, and (when `auto_wallet_verification` is enabled) a wallet-ownership widget session is auto-minted and returned in the transaction's `action_required` block.", + "properties": { + "method_type": { + "type": "string", + "description": "Type of payment method: `bank_card`, `bank_account`, `crypto_wallet`, `ewallet`, `unhosted_wallet`, `other`. Use `unhosted_wallet` to declare a self-hosted wallet for the Travel Rule." + }, + "account_id": { + "type": "string", + "description": "Account identifier (e.g. IBAN or wallet address). On a crypto transfer on bitcoin or Lightning it may be a Lightning payment request (BOLT11 invoice, BOLT12 offer, LNURL) or a Lightning Address: Didit stores the payee it names as the account (\"\" when it names none) and what the request says on the Travel Rule transfer's `chain_details.lightning`. At most 255 characters once stored. On a crypto transfer on bitcoin or Lightning it may be a Lightning payment request (BOLT11 invoice, BOLT12 offer, LNURL) or a Lightning Address: Didit stores the payee it names as the account (\"\" when it names none) and what the request says on the Travel Rule transfer's `chain_details.lightning`. At most 255 characters once stored." + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "The destination tag or memo that is part of the wallet on a memo network (XRP, XLM, EOS, ATOM, HBAR). On one shared exchange address each memo is its own payment method. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`. Alias: `destinationTag` or `destination_tag`." + }, + "issuing_country": { + "type": "string" + } + } + } + } + }, + "custom_properties": { + "type": "object", + "description": "Arbitrary key-value pairs attached to the transaction. Each key can be referenced in rule conditions with the field path `custom_values.`.", + "additionalProperties": true, + "example": { + "merchant_id": "test", + "bank_name": "test_2" + } + }, + "travel_rule_details": { + "type": "object", + "description": "Travel Rule data. When Travel Rule is enabled and transaction_category is travel_rule, Didit runs the exchange itself: it ignores status, protocol, required and obligations_count, and uses originator_data, beneficiary_data and metadata. Otherwise Didit stores these values as a passive Travel Rule record. While Travel Rule is on, an outbound transfer at or above your threshold must carry what its regime requires (EU TFR or FATF Recommendation 16), checked on the sides you send: a missing field answers 400 under `travel_rule_details.originator_data` or `travel_rule_details.beneficiary_data`, for example \"EU TFR requires the originator name.\"", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "description": "Required whenever you send travel_rule_details. One of the Travel Rule transfer statuses (for example AWAITING_COUNTERPARTY, COMPLETED, NOT_APPLICABLE); lowercase and camelCase spellings and the legacy values UNKNOWN, COMPLIANT, PENDING_ACTION, PENDING_COUNTERPARTY, FAILED and EXEMPT are also accepted and mapped. Any other value returns 400." + }, + "protocol": { + "type": "string" + }, + "required": { + "type": "boolean" + }, + "obligations_count": { + "type": "integer" + }, + "originator_data": { + "type": "object", + "description": "Originator party, as flat fields or one IVMS 101 person. For your user with an approved KYC verification, Didit replaces the fields the verification holds with the verified values (see `metadata.originator_provenance`). It must be an object: anything else is a `400`. On a memo network a `memo` key names the originator wallet's memo or tag." + }, + "beneficiary_data": { + "type": "object", + "description": "Beneficiary party, as flat fields or one IVMS 101 person. It must be an object: anything else is a `400`. To route the transfer, Didit reads `wallet_address` (an empty one is filled from `counterparty.payment_method.account_id`), `chain` (filled from `transaction_details.crypto_params.crypto_chain` or the `currency` when that names a supported chain), `name` (falling back to `counterparty.full_name`), `travel_address`, `counterparty_vasp_name`, `counterparty_vasp_lei` and `counterparty_vasp_email`. On a memo network `memo` names the beneficiary wallet's memo or tag; `wallet_address` and `memo` win over the counterparty's payment method." + }, + "metadata": { + "type": "object" + }, + "utxo": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleUtxo" + } + ], + "description": "Optional, UTXO chains only (BTC, LTC, BCH, DOGE). The transaction's inputs and outputs, from which Didit infers the originator wallet and the beneficiary output when you do not declare them." + }, + "originator": { + "type": "object", + "description": "An IVMS 101 originator block: `originatorPersons` (one `naturalPerson` or `legalPerson`; a legal person makes the party a company) and `accountNumber` (wallet addresses). Used for `originator_data` when that object is empty. A malformed block answers 400 keyed by its path, for example `originator.originatorPersons`." + }, + "beneficiary": { + "type": "object", + "description": "An IVMS 101 beneficiary block: `beneficiaryPersons` (one `naturalPerson` or `legalPerson`; a legal person makes the party a company) and `accountNumber` (wallet addresses). Used for `beneficiary_data` when that object is empty. A malformed block answers 400 keyed by its path, for example `beneficiary.beneficiaryPersons`." + }, + "ivms101": { + "type": "object", + "description": "The `originator` and `beneficiary` blocks wrapped in one IVMS 101 object (`IVMS101` is accepted too). Top-level blocks win over the wrapper's." + } + } + }, + "network_snapshot": { + "type": "object", + "description": "Pre-computed network graph snapshot.", + "properties": { + "nodes": { + "type": "array", + "items": { + "type": "object" + } + }, + "edges": { + "type": "array", + "items": { + "type": "object" + } + }, + "metrics": { + "type": "object" + } + } + }, + "include_crypto_screening": { + "type": "boolean", + "nullable": true, + "description": "Per-transaction override for crypto blockchain analytics screening. When `true`, crypto screening is performed regardless of the application default. When `false`, crypto screening is skipped even if enabled in the application settings. When omitted or `null`, the application-level default configured in the Console is used. Crypto screening requires `transaction_details.direction`, `currency_kind: \"crypto\"`, a chain/currency, and the wallet address selected by the direction: inbound pre-transfer screening uses `counterparty.payment_method.account_id`, inbound transaction-hash screening uses `subject.payment_method.account_id`, and outbound screening uses `counterparty.payment_method.account_id`. When `transaction_details.payment_reference_id` contains a blockchain transaction hash, Didit performs transaction screening and returns transaction summary enrichment when available." + } + }, + "required": [ + "transaction_id", + "transaction_category", + "transaction_details", + "subject" + ] + }, + "examples": { + "Minimal fiat withdrawal": { + "summary": "Smallest acceptable payload for a fiat outbound transfer", + "value": { + "transaction_id": "your-txn-2026-05-17-001", + "transaction_category": "finance", + "transaction_details": { + "direction": "outbound", + "amount": 1500, + "currency": "EUR", + "action_type": "withdrawal" + }, + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia" + } + } + }, + "Crypto outbound with screening": { + "summary": "Outbound crypto withdrawal to an unhosted wallet, opt-in screening", + "value": { + "transaction_id": "your-txn-2026-05-17-002", + "transaction_category": "finance", + "include_crypto_screening": true, + "transaction_details": { + "direction": "outbound", + "amount": 0.5, + "currency": "BTC", + "currency_kind": "crypto", + "amount_in_default_currency": 32500, + "default_currency": "USD", + "action_type": "withdrawal" + }, + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "payment_method": { + "method_type": "crypto_wallet", + "account_id": "bc1qexampleexchangewalletaddressxyz", + "issuing_country": "ESP" + } + }, + "counterparty": { + "entity_type": "individual", + "payment_method": { + "method_type": "unhosted_wallet", + "account_id": "bc1qexampleexternalwalletaddressabc" + } + } + } + }, + "Travel Rule transfer": { + "summary": "Outbound crypto transfer with Travel Rule originator/beneficiary data", + "value": { + "transaction_id": "your-txn-2026-05-17-003", + "transaction_category": "travel_rule", + "transaction_details": { + "direction": "outbound", + "amount": 25000, + "currency": "USDT", + "currency_kind": "crypto", + "action_type": "transfer" + }, + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "date_of_birth": "1992-03-14", + "address": { + "country": "ESP", + "town": "Madrid", + "street": "Calle Mayor 1", + "post_code": "28013" + } + }, + "counterparty": { + "entity_type": "individual", + "full_name": "John Beneficiary", + "institution_details": { + "name": "Beneficiary VASP, Inc.", + "code": "BVASP-US" + } + }, + "travel_rule_details": { + "status": "AWAITING_COUNTERPARTY", + "protocol": "IVMS101", + "required": true, + "originator_data": { + "name": "Maria Garcia" + }, + "beneficiary_data": { + "name": "John Beneficiary" + } + } + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Transaction submitted and monitored. The body is the full transaction detail (same shape as `GET /v3/transactions/{transaction_id}/`), reflecting any rule outcomes already applied.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionDetail" + }, + "examples": { + "Approved with no rule hits": { + "value": { + "uuid": "abcdef12-3456-4890-abcd-ef1234567890", + "environment": "live", + "transaction_number": 4123, + "txn_id": "your-txn-2026-05-17-001", + "txn_date": "2026-05-17T08:42:00Z", + "zone_id": "Europe/Madrid", + "transaction_type": "finance", + "action_type": "withdrawal", + "direction": "OUTBOUND", + "status": "APPROVED", + "source": "LIVE", + "amount": "1500", + "currency": "EUR", + "currency_type": "fiat", + "amount_in_default_currency": "1620.45", + "default_currency_code": "USD", + "preferred_currency_amount": "1620.45", + "preferred_currency_code": "USD", + "payment_details": "Withdrawal to crypto wallet", + "payment_txn_id": null, + "score": 0, + "severity": null, + "decision_reason_code": null, + "decision_reason_label": null, + "vendor_data": "user-12345", + "metadata": { + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "address": {}, + "institution_details": {}, + "device_context": {} + }, + "counterparty": {} + }, + "props": {}, + "tags": [], + "parties": [ + { + "uuid": "b1111111-2222-4333-8444-555555555555", + "role": "APPLICANT", + "entity_type": "individual", + "kind": "USER", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "first_name": null, + "last_name": null, + "country_code": null, + "dob": null, + "address": {}, + "institution_info": {}, + "device": {}, + "external_party_snapshot": null + } + ], + "payment_methods": [], + "documents": [], + "activities": [ + { + "uuid": "d4e5f6a7-8901-4bcd-9ef0-123456789abc", + "activity_type": "TRANSACTION_CREATED", + "source": "TRANSACTION", + "status": "APPROVED", + "title": "withdrawal", + "description": "Transaction recorded.", + "metadata": {}, + "occurred_at": "2026-05-17T08:42:11Z" + } + ], + "alerts": [], + "rule_runs": [], + "provider_results": [], + "travel_rule": null, + "network_snapshot": null, + "remediation": null, + "action_required": null, + "cost_breakdown": null + } + }, + "Flagged for review": { + "value": { + "uuid": "abcdef12-3456-4890-abcd-ef1234567891", + "environment": "live", + "transaction_number": 4124, + "txn_id": "your-txn-2026-05-17-002", + "txn_date": "2026-05-17T08:42:00Z", + "zone_id": "Europe/Madrid", + "transaction_type": "finance", + "action_type": "withdrawal", + "direction": "OUTBOUND", + "status": "IN_REVIEW", + "source": "LIVE", + "amount": "0.5", + "currency": "BTC", + "currency_type": "crypto", + "amount_in_default_currency": "32500", + "default_currency_code": "USD", + "preferred_currency_amount": "32500", + "preferred_currency_code": "USD", + "payment_details": "Withdrawal to crypto wallet", + "payment_txn_id": null, + "score": 78, + "severity": "HIGH", + "decision_reason_code": "WALLET_HIGH_RISK", + "decision_reason_label": "Destination wallet flagged as high risk", + "vendor_data": "user-12345", + "metadata": { + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "address": {}, + "institution_details": {}, + "device_context": {} + }, + "counterparty": {} + }, + "props": { + "order_id": "ord-9988" + }, + "tags": [], + "parties": [ + { + "uuid": "b1111111-2222-4333-8444-555555555555", + "role": "APPLICANT", + "entity_type": "individual", + "kind": "USER", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "first_name": null, + "last_name": null, + "country_code": null, + "dob": null, + "address": {}, + "institution_info": {}, + "device": {}, + "external_party_snapshot": null + } + ], + "payment_methods": [], + "documents": [], + "activities": [], + "alerts": [ + { + "uuid": "a7b8c9d0-1234-4abc-9def-567890abcdef", + "title": "Destination wallet flagged as high risk", + "description": "Provider risk score exceeded the configured threshold.", + "severity": "HIGH", + "status": "OPEN", + "source": "RULE", + "metadata": {}, + "created_at": "2026-05-17T08:42:12Z", + "due_at": null + } + ], + "rule_runs": [ + { + "uuid": "e5f6a7b8-9012-4cde-af01-23456789abcd", + "rule": "f6a7b8c9-0123-4def-b012-3456789abcde", + "rule_title": "High-risk wallet screening", + "rule_description": null, + "rule_category": "crypto", + "matched": true, + "is_test": false, + "mode": "ACTIVE", + "severity": "HIGH", + "score_delta": 78, + "status_target": "IN_REVIEW", + "action_summary": "score +78; change_status:IN_REVIEW", + "metadata": {}, + "created_at": "2026-05-17T08:42:12Z" + } + ], + "provider_results": [], + "travel_rule": null, + "network_snapshot": null, + "remediation": null, + "action_required": null, + "cost_breakdown": null + } + } + } + } + } + }, + "400": { + "description": "Validation failed (missing required fields, duplicate `transaction_id`, unknown enum value, invalid currency, etc.).", + "content": { + "application/json": { + "examples": { + "Missing required field": { + "value": { + "transaction_details": { + "amount": [ + "This field is required." + ] + } + } + }, + "Duplicate transaction_id": { + "value": { + "transaction_id": [ + "A transaction with this transaction_id already exists." + ] + } + }, + "Invalid category": { + "value": { + "non_field_errors": [ + "Unsupported transaction category." + ] + } + }, + "Missing subject identity": { + "value": { + "subject": [ + "subject.vendor_data and subject.full_name are required." + ] + } + }, + "Blocklisted participant": { + "value": { + "vendor_data": [ + "The individual 'user-12345' is in the blocklist and cannot participate in transactions." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot submit transactions for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "409": { + "description": "Another request with the same `transaction_id` created the transaction while this one was being processed, so only one of two simultaneous submissions succeeds. Read the existing transaction instead of resubmitting.", + "content": { + "application/json": { + "examples": { + "Concurrent duplicate": { + "value": { + "detail": "A transaction with this transaction_id already exists." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: transaction creates and event ingestion (`POST /v3/transactions/`, `POST /v3/events/`, `POST /v3/events/batch/`) share a budget of 600 requests per minute per API key, separate from the 300 shared by every other write request. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying, rounded up so the retry lands in the next window.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Ingestion rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-didit-sdk": { + "resource": "transactions", + "method": "create", + "auth": "api_key", + "server": true + } + } + }, + "/v3/transactions/{transaction_id}/": { + "get": { + "summary": "Get transaction", + "description": "Retrieve the full monitoring record for one transaction: parties, score/severity, rule runs, alerts, Travel Rule outcome, remediation session.", + "operationId": "getTransaction", + "x-didit-sdk": { + "resource": "transactions", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID (the `uuid` returned from create / list, **not** your `txn_id`).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/abcdef12-3456-7890-abcd-ef1234567890/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.retrieve(\n \"abcdef12-3456-7890-abcd-ef1234567890\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.retrieve(\"abcdef12-3456-7890-abcd-ef1234567890\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Retrieve(ctx, \"abcdef12-3456-7890-abcd-ef1234567890\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().retrieve(\"abcdef12-3456-7890-abcd-ef1234567890\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->retrieve('abcdef12-3456-7890-abcd-ef1234567890');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.retrieve(\"abcdef12-3456-7890-abcd-ef1234567890\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.RetrieveAsync(\"abcdef12-3456-7890-abcd-ef1234567890\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Full transaction detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionDetail" + }, + "examples": { + "In review with an alert": { + "value": { + "uuid": "abcdef12-3456-4890-abcd-ef1234567890", + "environment": "live", + "transaction_number": 4123, + "txn_id": "your-txn-2026-05-17-001", + "txn_date": "2026-05-17T08:42:00Z", + "zone_id": "Europe/Madrid", + "transaction_type": "finance", + "action_type": "withdrawal", + "direction": "OUTBOUND", + "status": "IN_REVIEW", + "source": "LIVE", + "amount": "1500", + "currency": "EUR", + "currency_type": "fiat", + "amount_in_default_currency": "1620.45", + "default_currency_code": "USD", + "preferred_currency_amount": "1620.45", + "preferred_currency_code": "USD", + "payment_details": "Withdrawal to crypto wallet", + "payment_txn_id": null, + "score": 78, + "severity": "HIGH", + "decision_reason_code": "WALLET_HIGH_RISK", + "decision_reason_label": "Destination wallet flagged as high risk", + "vendor_data": "user-12345", + "metadata": { + "subject": { + "entity_type": "individual", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "address": {}, + "institution_details": {}, + "device_context": {} + }, + "counterparty": {} + }, + "props": { + "order_id": "ord-9988" + }, + "tags": [], + "parties": [ + { + "uuid": "b1111111-2222-4333-8444-555555555555", + "role": "APPLICANT", + "entity_type": "individual", + "kind": "USER", + "vendor_data": "user-12345", + "full_name": "Maria Garcia", + "first_name": null, + "last_name": null, + "country_code": null, + "dob": null, + "address": {}, + "institution_info": {}, + "device": {}, + "external_party_snapshot": null + } + ], + "payment_methods": [], + "documents": [], + "activities": [], + "alerts": [ + { + "uuid": "a7b8c9d0-1234-4abc-9def-567890abcdef", + "title": "Destination wallet flagged as high risk", + "description": "Provider risk score exceeded the configured threshold.", + "severity": "HIGH", + "status": "OPEN", + "source": "RULE", + "metadata": {}, + "created_at": "2026-05-17T08:42:12Z", + "due_at": null + } + ], + "rule_runs": [], + "provider_results": [], + "travel_rule": null, + "network_snapshot": null, + "remediation": null, + "action_required": null, + "cost_breakdown": null + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read this application's transactions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists for the application (deleted, never created, belongs to another application, or the path segment is not a valid UUID).", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/transactions/sdk-token/": { + "post": { + "summary": "Mint an SDK transaction token", + "description": "Mint a short-lived scoped token your app passes to the Didit SDKs so they can submit transactions directly from the end-user's device via `POST /v1/transactions/`. The token is bound to one `vendor_data` (your user id): every transaction submitted with it has its subject identity enforced server-side from that binding, so a tampered client cannot submit on behalf of another user. Mint tokens from your backend - never ship the API key itself in an app. camelCase aliases (`vendorData`, `ttlSeconds`, `maxUses`) are also accepted.", + "operationId": "createTransactionSdkToken", + "x-didit-sdk": { + "resource": "transactions", + "method": "create_sdk_token", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "vendor_data": { + "type": "string", + "description": "Your internal identifier for the end user this token is scoped to. Enforced as the subject identity of every transaction submitted with the token.", + "example": "user-042" + }, + "ttl_seconds": { + "type": "integer", + "default": 900, + "maximum": 86400, + "description": "Token lifetime in seconds. Defaults to 900 (15 minutes); maximum 86400 (24 hours)." + }, + "max_uses": { + "type": "integer", + "nullable": true, + "description": "Maximum number of successful submissions allowed with this token. Omit or null for unlimited uses within the TTL." + } + }, + "required": [ + "vendor_data" + ] + }, + "example": { + "vendor_data": "user-042", + "ttl_seconds": 900, + "max_uses": 3 + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/transactions/sdk-token/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"vendor_data\": \"user-042\", \"ttl_seconds\": 900, \"max_uses\": 3}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.create_sdk_token(\n vendor_data=\"user-042\",\n ttl_seconds=900,\n max_uses=3,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.createSdkToken({\n vendor_data: \"user-042\",\n ttl_seconds: 900,\n max_uses: 3,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.CreateSDKToken(ctx, didit.TransactionsCreateSDKTokenParams{\n\tVendorData: \"user-042\",\n\tTTLSeconds: didit.Int(900),\n\tMaxUses: didit.Int(3),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TransactionsCreateSdkTokenParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().createSdkToken(TransactionsCreateSdkTokenParams.builder()\n .vendorData(\"user-042\")\n .ttlSeconds(900L)\n .maxUses(3L)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->createSdkToken([\n 'vendor_data' => 'user-042',\n 'ttl_seconds' => 900,\n 'max_uses' => 3,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.create_sdk_token(\n vendor_data: \"user-042\",\n ttl_seconds: 900,\n max_uses: 3\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.CreateSdkTokenAsync(new TransactionsCreateSdkTokenParams\n{\n VendorData = \"user-042\",\n TtlSeconds = 900,\n MaxUses = 3,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The minted token. Hand `sdk_token` to your app; it authenticates the device-facing `/v1/transactions/` endpoints via the `X-Transaction-Token` header until `expires_at`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sdk_token": { + "type": "string", + "description": "The scoped transaction token to pass to the SDK." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the token stops being accepted." + } + } + }, + "example": { + "sdk_token": "q7Zt...urlsafe", + "expires_at": "2026-07-07T12:15:00Z" + } + } + } + }, + "400": { + "description": "Validation failed: missing `vendor_data`, or `ttl_seconds` outside the allowed range.", + "content": { + "application/json": { + "examples": { + "Missing vendor_data": { + "value": { + "vendor_data": [ + "This field is required." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key.", + "content": { + "application/json": { + "examples": { + "Invalid API Key": { + "value": { + "detail": "Invalid API Key." + } + } + } + } + } + } + } + } + }, + "/v1/transactions/": { + "post": { + "summary": "Submit a transaction from an SDK", + "description": "Device-facing endpoint the Didit SDKs call to submit a transaction directly from the end-user's device. Authentication is a scoped token from `POST /v3/transactions/sdk-token/` sent in the `X-Transaction-Token` header instead of an API key, and the endpoint is CORS-enabled so browsers can call it cross-origin. The payload uses the camelCase wire aliases shown below (the SDK `submitTransaction` methods build it for you). Three server-side guarantees: the application is always taken from the token; the subject identity (`externalUserId`) is always enforced from the token's `vendor_data`, so spoofed payload values are ignored; and device intelligence (the SDK device fingerprint plus server-derived IP and user agent) is attached to the subject automatically.", + "operationId": "submitTransactionFromSdk", + "x-didit-sdk": { + "resource": "transactions", + "method": "submit_from_device", + "auth": "device_token", + "server": false + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "TransactionTokenAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "txnId": { + "type": "string", + "description": "Your unique identifier for this transaction (maps to `transaction_id`, max 128 characters).", + "example": "wd-2026-07-07-0042" + }, + "txnDate": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred (maps to `transaction_at`). Defaults to now if omitted." + }, + "zoneId": { + "type": "string", + "description": "IANA time zone identifier (maps to `time_zone`), e.g. `Europe/Madrid`." + }, + "type": { + "type": "string", + "description": "Transaction category (maps to `transaction_category`), e.g. `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet`.", + "example": "travelRule" + }, + "info": { + "type": "object", + "description": "Core financial details of the transaction (maps to `transaction_details`).", + "required": [ + "direction", + "amount", + "currency" + ], + "properties": { + "direction": { + "type": "string", + "description": "Direction relative to the subject. Case-insensitive; `in`/`out` are accepted as aliases. Echoed back uppercase (`INBOUND`/`OUTBOUND`).", + "example": "out" + }, + "amount": { + "type": "number", + "description": "Transaction amount (up to 18 decimal places). A decimal string is also accepted and is the only way to preserve more than about 15 significant digits: a JSON number is parsed as a binary float first, so `0.123456789012345678` is stored as `0.12345678901234568`.", + "example": 0.25 + }, + "currency": { + "type": "string", + "description": "Currency code. Any ISO 4217 alphabetic code is accepted (e.g. `EUR`, `USD`, `NGN`), as is a crypto asset symbol (e.g. `BTC`). Prefer a currently-circulating code. Not every accepted code has an exchange rate: when none is available for the code (for example the ISO accounting unit `XSU`, or a crypto symbol Didit does not price), automatic conversion fails and `preferred_currency_amount` is returned null unless the request supplied a pre-converted `amountInDefaultCurrency`.", + "example": "ETH" + }, + "currencyType": { + "type": "string", + "description": "Whether the currency is `fiat` or `crypto` (maps to `currency_kind`)." + }, + "amountInDefaultCurrency": { + "type": "number", + "nullable": true, + "description": "Pre-converted amount in the default currency (maps to `amount_in_default_currency`). If omitted, automatic FX conversion is attempted." + }, + "defaultCurrencyCode": { + "type": "string", + "description": "The default currency code for the converted amount (maps to `default_currency`). Any currently-circulating ISO 4217 code." + }, + "paymentDetails": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference or memo (maps to `payment_details`)." + }, + "paymentTxnId": { + "type": "string", + "nullable": true, + "description": "External payment reference (maps to `payment_reference_id`); the on-chain transaction hash for crypto transactions." + }, + "type": { + "type": "string", + "nullable": true, + "description": "Sub-type of the transaction (maps to `action_type`), e.g. `deposit`, `withdrawal`, `transfer`." + }, + "cryptoParams": { + "type": "object", + "description": "Crypto-specific parameters, e.g. `{\"crypto_chain\": \"ETH\"}`." + } + } + }, + "subject": { + "type": "object", + "description": "The subject (applicant) of the transaction. `externalUserId` is ignored here: the subject identity is always enforced from the token's `vendor_data`.", + "required": [ + "fullName" + ], + "properties": { + "type": { + "type": "string", + "description": "Entity type (maps to `entity_type`): `individual` (default) or `company`." + }, + "externalUserId": { + "type": "string", + "description": "Ignored on the subject - enforced server-side from the token's `vendor_data`." + }, + "fullName": { + "type": "string", + "example": "Jane Doe" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "dob": { + "type": "string", + "format": "date", + "description": "Date of birth (YYYY-MM-DD; maps to `date_of_birth`)." + }, + "address": { + "type": "object", + "description": "Address object with `country` (ISO 3166-1 alpha-3), `town`, `state`, `street`, `post_code`." + }, + "institutionInfo": { + "type": "object", + "description": "Institution details for institutional participants (maps to `institution_details`)." + }, + "device": { + "type": "object", + "description": "Optional explicit device context. Merged with the automatically captured device intelligence; server-derived fields win on conflict." + }, + "paymentMethod": { + "type": "object", + "description": "Payment method used by the subject (maps to `payment_method`).", + "properties": { + "type": { + "type": "string", + "description": "Payment method kind (maps to `method_type`), e.g. `bank_card`, `bank_account`, `crypto_wallet`, `ewallet`, `unhosted_wallet`, `other`." + }, + "accountId": { + "type": "string", + "description": "Account identifier (maps to `account_id`): IBAN, card fingerprint, or wallet address." + }, + "issuingCountry": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code of the issuer (maps to `issuing_country`)." + } + } + } + } + }, + "counterparty": { + "type": "object", + "description": "The counterparty of the transaction (optional). Same shape as `subject`; `externalUserId` is meaningful here.", + "properties": { + "type": { + "type": "string", + "description": "Entity type (maps to `entity_type`): `individual` or `company`." + }, + "externalUserId": { + "type": "string", + "description": "Your internal identifier for the counterparty (maps to `vendor_data`)." + }, + "fullName": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "dob": { + "type": "string", + "format": "date" + }, + "address": { + "type": "object" + }, + "institutionInfo": { + "type": "object" + }, + "device": { + "type": "object" + }, + "paymentMethod": { + "type": "object", + "description": "Payment method used by the counterparty (maps to `payment_method`). On travelRule transactions, `type: \"unhosted_wallet\"` declares the counterparty wallet as self-hosted: the transfer skips counterparty-VASP routing, is created directly in `UNCONFIRMED_OWNERSHIP`, and (when `auto_wallet_verification` is enabled) a wallet-ownership widget session is auto-minted and returned in `action_required` so the SDK can launch it.", + "properties": { + "type": { + "type": "string", + "description": "Payment method kind (maps to `method_type`), e.g. `bank_card`, `bank_account`, `crypto_wallet`, `ewallet`, `unhosted_wallet`, `other`. Use `unhosted_wallet` to declare a self-hosted wallet for the Travel Rule." + }, + "accountId": { + "type": "string", + "description": "Account identifier (maps to `account_id`): IBAN, card fingerprint, or wallet address." + }, + "issuingCountry": { + "type": "string" + } + } + } + } + }, + "props": { + "type": "object", + "additionalProperties": true, + "description": "Custom key-value pairs (maps to `custom_properties`). Each key can be referenced in rule conditions as `custom_values.`." + }, + "travelRule": { + "type": "object", + "description": "Travel Rule details (maps to `travel_rule_details`). When Travel Rule is enabled for the application, Didit ignores any `status` you send and drives the exchange itself.", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string" + }, + "protocol": { + "type": "string" + }, + "required": { + "type": "boolean" + }, + "obligationsCount": { + "type": "integer" + }, + "originatorData": { + "type": "object" + }, + "beneficiaryData": { + "type": "object" + }, + "metadata": { + "type": "object" + } + } + }, + "includeCryptoScreening": { + "type": "boolean", + "nullable": true, + "description": "Per-transaction override for crypto blockchain analytics screening (maps to `include_crypto_screening`)." + }, + "fingerprint_v2": { + "type": "object", + "description": "The `didit-fp-v2` device fingerprint payload. Injected automatically by the SDKs; only relevant when calling the wire API directly." + } + }, + "required": [ + "txnId", + "type", + "info", + "subject" + ] + }, + "example": { + "txnId": "wd-2026-07-07-0042", + "type": "travelRule", + "info": { + "direction": "out", + "amount": "0.25", + "currency": "ETH", + "currencyType": "crypto", + "cryptoParams": { + "crypto_chain": "ETH" + } + }, + "subject": { + "fullName": "Jane Doe" + }, + "counterparty": { + "fullName": "Ana Diaz", + "paymentMethod": { + "type": "unhosted_wallet", + "accountId": "0xBeneficiaryWallet01" + } + }, + "travelRule": { + "status": "AWAITING_COUNTERPARTY", + "beneficiaryData": { + "wallet_address": "0xBeneficiaryWallet01", + "name": "Ana Diaz" + } + }, + "includeCryptoScreening": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v1/transactions/ \\\n -H 'X-Transaction-Token: SDK_TOKEN' -H 'Content-Type: application/json' \\\n -d '{\"txnId\": \"wd-2026-07-07-0042\", \"type\": \"travelRule\", \"info\": {\"direction\": \"out\", \"amount\": \"0.25\", \"currency\": \"ETH\", \"currencyType\": \"crypto\"}, \"subject\": {\"fullName\": \"Jane Doe\"}, \"counterparty\": {\"fullName\": \"Ana Diaz\", \"paymentMethod\": {\"type\": \"unhosted_wallet\", \"accountId\": \"0xBeneficiaryWallet01\"}}, \"travelRule\": {\"status\": \"AWAITING_COUNTERPARTY\", \"beneficiaryData\": {\"wallet_address\": \"0xBeneficiaryWallet01\", \"name\": \"Ana Diaz\"}}, \"includeCryptoScreening\": true}'" + }, + { + "lang": "javascript", + "label": "Web SDK", + "source": "import { DiditSdk } from '@didit-protocol/sdk-web';\n\nconst result = await DiditSdk.shared.submitTransaction({\n transactionToken: sdkToken,\n transaction: {\n txnId: 'wd-2026-07-07-0042',\n category: 'travelRule',\n details: { direction: 'out', amount: '0.25', currency: 'ETH', currencyType: 'crypto' },\n subject: { fullName: 'Jane Doe' },\n counterparty: { fullName: 'Ana Diaz', paymentMethod: { type: 'unhosted_wallet', accountId: '0xBeneficiaryWallet01' } },\n travelRule: { status: 'AWAITING_COUNTERPARTY', beneficiaryData: { wallet_address: '0xBeneficiaryWallet01', name: 'Ana Diaz' } },\n includeCryptoScreening: true\n },\n onActionCompleted: (refreshed) => console.log(refreshed.status)\n});" + } + ], + "responses": { + "201": { + "description": "Transaction submitted and monitored. Same detail shape as `GET /v3/transactions/{transaction_id}/`, including the `action_required` block when the end user must complete a follow-up action (the SDKs auto-launch it by default).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionDetail" + }, + "examples": { + "Wallet ownership required": { + "value": { + "uuid": "22222222-3333-4444-5555-666666666666", + "txn_id": "wd-2026-07-07-0042", + "transaction_type": "travelRule", + "direction": "OUTBOUND", + "status": "APPROVED", + "amount": "0.25", + "currency": "ETH", + "travel_rule": { + "uuid": "33333333-4444-5555-6666-777777777777", + "status": "UNCONFIRMED_OWNERSHIP", + "required": true + }, + "action_required": { + "type": "wallet_ownership", + "url": "https://verify.didit.me/wallet-ownership/3s9x...urlsafe", + "widget_session_id": "77777777-8888-9999-aaaa-bbbbbbbbbbbb", + "expires_at": "2026-07-07T13:00:00Z" + } + } + } + } + } + } + }, + "400": { + "description": "Validation failed (missing required fields, duplicate `txnId`, unknown category).", + "content": { + "application/json": { + "examples": { + "Missing fields": { + "value": { + "transaction_details": { + "currency": [ + "This field is required." + ] + } + } + }, + "Duplicate txnId": { + "value": { + "transaction_id": [ + "A transaction with this transaction_id already exists." + ] + } + } + } + } + } + }, + "401": { + "description": "The transaction token was rejected: missing, unknown, revoked, over its `max_uses`, or expired. Expired-token responses always contain the word `expired` in the detail, which is how the SDKs distinguish `expired_token` from `invalid_token` errors.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "Invalid transaction token." + } + }, + "Expired token": { + "value": { + "detail": "Transaction token expired." + } + } + } + } + } + }, + "409": { + "description": "Another request with the same `txnId` created the transaction while this one was being processed, so only one of two simultaneous submissions succeeds. Read the existing transaction instead of resubmitting.", + "content": { + "application/json": { + "examples": { + "Concurrent duplicate": { + "value": { + "detail": "A transaction with this transaction_id already exists." + } + } + } + } + } + } + } + } + }, + "/v1/transactions/{transaction_id}/": { + "get": { + "summary": "Get a transaction from an SDK", + "description": "Device-facing read endpoint, authenticated with the same `X-Transaction-Token` used to submit. The SDKs poll it after an auto-launched action flow completes or its window closes, so the final state never depends on a single notification. Returns the same transaction detail as the submit response, including the current `action_required` block (`null` once the action is resolved).", + "operationId": "getTransactionFromSdk", + "x-didit-sdk": { + "resource": "transactions", + "method": "retrieve_from_device", + "auth": "device_token", + "server": false + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID (the `uuid` from the submit response, **not** your `txnId`).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + } + ], + "security": [ + { + "TransactionTokenAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v1/transactions/22222222-3333-4444-5555-666666666666/' \\\n -H 'X-Transaction-Token: SDK_TOKEN'" + } + ], + "responses": { + "200": { + "description": "The full transaction detail, including `action_required` (`null` once the pending action is resolved).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionDetail" + } + } + } + }, + "401": { + "description": "The transaction token was rejected: missing, unknown, revoked, over its `max_uses`, or expired.", + "content": { + "application/json": { + "examples": { + "Expired token": { + "value": { + "detail": "Transaction token expired." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists for the token's application, or the path segment is not a valid UUID.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + } + } + } + }, + "/v3/businesses/": { + "get": { + "summary": "List businesses", + "description": "List [businesses](/entities/businesses) Didit assembles from KYB sessions sharing the same `vendor_data` (free-form string, NOT a UUID), plus businesses created via the API. Paginated, ordered by `last_session_at` desc. This endpoint does not support filtering or search parameters — fetch pages and filter client-side.", + "operationId": "list_businesses", + "x-didit-sdk": { + "resource": "businesses", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "description": "Page size. Defaults to 50 when omitted. There is no enforced maximum, but keep pages small for latency." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "description": "Zero-based offset of the first record to return. Prefer following the `next` URL from the previous page over computing this by hand." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/businesses/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.businesses.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.businesses.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Businesses.ListAutoPaging(ctx, didit.BusinessesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.businesses().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.businesses.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Businesses.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated list of businesses with status, session counts, and feature breakdown.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows — exact up to 100, capped at 100 beyond that for performance. Do not use it to detect the last page; rely on `next` being `null` instead." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BusinessListItem" + } + } + } + }, + "examples": { + "Example": { + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-123", + "display_name": null, + "legal_name": "Acme Trading Ltd", + "registration_number": "12345678", + "country_code": "GB", + "effective_name": "Acme Trading Ltd", + "status": "ACTIVE", + "session_count": 2, + "approved_count": 1, + "declined_count": 0, + "in_review_count": 1, + "features": { + "KYB": "Approved" + }, + "features_list": [ + { + "feature": "KYB", + "status": "Approved" + } + ], + "last_session_at": "2026-03-15T10:30:00Z", + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": "2026-01-10T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2026-01-10T08:00:00Z" + } + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot list businesses for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/customization/": { + "get": { + "summary": "Get branding customization", + "description": "Returns the white-label branding for the application resolved from the API key: colors, fonts, border radii, logo URLs and UI/UX options. Custom domain and white-label email settings are managed only in the Business Console and are not part of this response.", + "operationId": "get_customization", + "x-didit-sdk": { + "resource": "customization", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Customization" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/customization/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.customization.retrieve()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.customization.retrieve();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Customization.Retrieve(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.customization().retrieve();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "customization->retrieve();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.customization.retrieve\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Customization.RetrieveAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Current branding customization for the application.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrandingCustomization" + }, + "examples": { + "Default branding": { + "value": { + "color_primary": "#000000", + "color_secondary": "#9DA1A1", + "color_background": "#FFFFFF", + "color_panel": "#2567FF", + "color_panel_10": "#1A1A1A", + "color_on_panel_1": "#9DA1A1", + "color_on_panel_2": "#F4F4F6", + "color_on_background": "#000000", + "color_button_1": "#2567FF", + "color_button_2": "#F8F8F8", + "color_button_text_1": "#FFFFFF", + "color_button_text_2": "#000000", + "color_pill_text": "#111827", + "border_radius_panel": 10, + "border_radius_buttons": 12, + "font_family": "Inter", + "font_weight": "regular", + "font_url": "https://fonts.googleapis.com/css2?family=Inter", + "logo_square": null, + "logo_rectangular": null, + "logo_rectangular_dark": null, + "favicon": null, + "app_public_name": "Acme Verify", + "privacy_policy_url": "https://acme.com/privacy", + "skip_welcome_screen": false, + "disable_login_with_didit": false, + "hide_progress_bar": false, + "callback_seconds": 3 + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update branding customization", + "description": "Partial update of the application's white-label branding. Send `application/json` to change colors, fonts, radii and UI options, or `multipart/form-data` to upload logo images. Only the fields you include are changed. Custom domain and white-label email cannot be set through this endpoint — use the Business Console for those.", + "operationId": "update_customization", + "x-didit-sdk": { + "resource": "customization", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Customization" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrandingCustomizationUpdate" + }, + "examples": { + "Update colors and font": { + "summary": "Change branding colors and font", + "value": { + "color_primary": "#123456", + "color_button_1": "#2567FF", + "font_family": "AR One Sans", + "border_radius_buttons": 16 + } + }, + "Update UI options": { + "summary": "Change public name and flow options", + "value": { + "app_public_name": "Acme Verify", + "privacy_policy_url": "https://acme.com/privacy", + "skip_welcome_screen": true, + "hide_progress_bar": true, + "callback_seconds": 5 + } + }, + "Remove a logo": { + "summary": "Delete the current rectangular logo", + "value": { + "delete_logo_rectangular": true + } + }, + "Remove the dark-mode logo": { + "summary": "Delete the current dark-mode logo", + "value": { + "delete_logo_rectangular_dark": true + } + } + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "description": "Use multipart/form-data only to upload logo images. Branding fields above may also be sent as form fields in the same request.", + "properties": { + "image_square": { + "type": "string", + "format": "binary", + "description": "Square logo (1:1 aspect ratio). PNG, JPG or WebP, at least 512 px on the long side, at most **1 MB**." + }, + "image_rectangular": { + "type": "string", + "format": "binary", + "description": "Rectangular logo (2:1 aspect ratio). PNG, JPG or WebP, at least 512 px on the long side, at most **1 MB**." + }, + "image_rectangular_dark": { + "type": "string", + "format": "binary", + "description": "Rectangular logo for dark mode (2:1 aspect ratio), shown on a dark background. PNG, JPG or WebP, at least 512 px on the long side, at most **1 MB**. Returned as `logo_rectangular_dark`." + }, + "image_favicon": { + "type": "string", + "format": "binary", + "description": "Favicon image. Max **500 KB**." + } + } + }, + "example": { + "image_rectangular": "(binary PNG logo, 2:1)", + "image_rectangular_dark": "(binary PNG logo for dark mode, 2:1)", + "image_favicon": "(binary favicon)" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/customization/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"color_primary\": \"#123456\", \"font_family\": \"AR One Sans\"}'" + }, + { + "lang": "curl", + "label": "curl (logo upload)", + "source": "curl -X PATCH 'https://verification.didit.me/v3/customization/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'image_rectangular=@logo.png' \\\n -F 'image_rectangular_dark=@logo-dark.png' \\\n -F 'image_favicon=@favicon.ico'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.customization.update(\n image_rectangular=open(\"image_rectangular.jpg\", \"rb\"),\n image_rectangular_dark=open(\"image_rectangular_dark.jpg\", \"rb\"),\n image_favicon=open(\"image_favicon.jpg\", \"rb\"),\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.customization.update({\n image_rectangular: await fileFromPath(\"image_rectangular.jpg\"),\n image_rectangular_dark: await fileFromPath(\"image_rectangular_dark.jpg\"),\n image_favicon: await fileFromPath(\"image_favicon.jpg\"),\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Customization.Update(ctx, didit.CustomizationUpdateParams{\n\tImageRectangular: didit.Some(didit.FileFromPath(\"image_rectangular.jpg\")),\n\tImageRectangularDark: didit.Some(didit.FileFromPath(\"image_rectangular_dark.jpg\")),\n\tImageFavicon: didit.Some(didit.FileFromPath(\"image_favicon.jpg\")),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.types.CustomizationUpdateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.customization().update(CustomizationUpdateParams.builder()\n .imageRectangular(Path.of(\"image_rectangular.jpg\"))\n .imageRectangularDark(Path.of(\"image_rectangular_dark.jpg\"))\n .imageFavicon(Path.of(\"image_favicon.jpg\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "customization->update([\n 'image_rectangular' => File::fromPath('image_rectangular.jpg'),\n 'image_rectangular_dark' => File::fromPath('image_rectangular_dark.jpg'),\n 'image_favicon' => File::fromPath('image_favicon.jpg'),\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.customization.update(\n image_rectangular: Pathname(\"image_rectangular.jpg\"),\n image_rectangular_dark: Pathname(\"image_rectangular_dark.jpg\"),\n image_favicon: Pathname(\"image_favicon.jpg\")\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Customization.UpdateAsync(new CustomizationUpdateParams\n{\n ImageRectangular = FileUpload.FromPath(\"image_rectangular.jpg\"),\n ImageRectangularDark = FileUpload.FromPath(\"image_rectangular_dark.jpg\"),\n ImageFavicon = FileUpload.FromPath(\"image_favicon.jpg\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Branding updated. The FULL branding object is returned (all fields, not just the ones you changed).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrandingCustomization" + }, + "examples": { + "Updated": { + "value": { + "color_primary": "#123456", + "color_secondary": "#9DA1A1", + "color_background": "#FFFFFF", + "color_panel": "#2567FF", + "color_panel_10": "#1A1A1A", + "color_on_panel_1": "#9DA1A1", + "color_on_panel_2": "#F4F4F6", + "color_on_background": "#000000", + "color_button_1": "#2567FF", + "color_button_2": "#F8F8F8", + "color_button_text_1": "#FFFFFF", + "color_button_text_2": "#000000", + "color_pill_text": "#111827", + "border_radius_panel": 10, + "border_radius_buttons": 16, + "font_family": "AR One Sans", + "font_weight": "400", + "font_url": "https://fonts.googleapis.com/css2?family=AR+One+Sans", + "logo_square": null, + "logo_rectangular": "https://cdn.didit.me/white_label/logo_rectangular/...", + "logo_rectangular_dark": null, + "favicon": null, + "app_public_name": "Acme Verify", + "privacy_policy_url": "https://acme.com/privacy", + "skip_welcome_screen": true, + "disable_login_with_didit": false, + "hide_progress_bar": false, + "callback_seconds": 3 + } + } + } + } + } + }, + "400": { + "description": "Invalid body. For example: a malformed hex color, an unknown font family or weight, a Business-Console-only field (`domain`, `from_email`, `verify_domain`, `force_remove_domain`, `verify_email_domain`, `force_remove_email_domain`, and their read-only companions), or a logo upload that is not a PNG, JPG or WebP image, is under 512 px on its long side, is over 1 MB, or has the wrong aspect ratio. Also returned when setting `disable_login_with_didit` to `false` while a custom domain is configured.", + "content": { + "application/json": { + "examples": { + "Bad color": { + "value": { + "color_primary": [ + "Invalid color format. Use '#RRGGBB' or '#RRGGBBAA' format." + ] + } + }, + "Bad logo": { + "value": { + "image_rectangular": [ + "Image aspect ratio must be 2:1. Got 1024x1024 image dimensions" + ] + } + }, + "Unsupported logo format": { + "summary": "Not a PNG, JPG or WebP image (an SVG, for example)", + "value": { + "image_rectangular_dark": [ + "Upload a PNG, JPG or WebP image." + ] + } + }, + "Logo too small": { + "summary": "Under 512 px on the long side", + "value": { + "image_rectangular": [ + "Image must be at least 512 px on its long side. Got 400x200 image dimensions" + ] + } + }, + "Logo too large": { + "summary": "Over 1 MB", + "value": { + "image_square": [ + "File size should not exceed 1 MB" + ] + } + }, + "Console-only field": { + "value": { + "domain": [ + "Manage this field in the Business Console." + ] + } + }, + "Bad font weight": { + "value": { + "font_weight": [ + "Font weight 950 is not available for Inter. Available weights are: 100, 200, 300, 400, 500, 600, 700, 800, 900" + ] + } + } + } + } + } + }, + "403": { + "description": "API key missing, malformed, expired, or scoped to a different application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid token": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. Retry with exponential backoff." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/businesses/{vendor_data}/": { + "get": { + "summary": "Get business", + "description": "Fetch one [business](/entities/businesses) by `vendor_data` (exact match). Adds `metadata` and `updated_at` to the list view. Deleted businesses return 404.", + "operationId": "get_business", + "x-didit-sdk": { + "resource": "businesses", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the business — a free-form string (NOT a UUID), matched exactly as sent.", + "example": "company-123" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/businesses/company-123/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.businesses.retrieve(\n \"company-123\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.businesses.retrieve(\"company-123\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.Retrieve(ctx, \"company-123\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.businesses().retrieve(\"company-123\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->retrieve('company-123');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.businesses.retrieve(\"company-123\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.RetrieveAsync(\"company-123\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Full business detail including metadata and aggregated session data.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDetailItem" + }, + "examples": { + "Active business": { + "value": { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-123", + "display_name": null, + "legal_name": "Acme Trading Ltd", + "registration_number": "12345678", + "country_code": "GB", + "effective_name": "Acme Trading Ltd", + "status": "ACTIVE", + "session_count": 2, + "approved_count": 1, + "declined_count": 0, + "in_review_count": 1, + "features": { + "KYB": "Approved" + }, + "features_list": [ + { + "feature": "KYB", + "status": "Approved" + } + ], + "last_session_at": "2026-03-15T10:30:00Z", + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": "2026-01-10T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2026-01-10T08:00:00Z", + "metadata": { + "industry": "fintech" + }, + "updated_at": "2026-03-15T10:30:00Z" + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot read this application's businesses. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No (non-deleted) business with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update business", + "description": "Partial update of a business: `display_name`, `legal_name`, `registration_number`, `country_code`, `status`, `metadata`. `metadata` fully replaces. Setting `status` to `BLOCKED` adds the `vendor_data` to the system blocklist; moving away from `BLOCKED` removes that entry. Every edit is recorded in the company's activity: a status change with the optional `reason`, and a change to `display_name`, `legal_name`, `registration_number`, `country_code` or `metadata` as a profile update.", + "operationId": "update_business", + "x-didit-sdk": { + "resource": "businesses", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the business (free-form string, NOT a UUID).", + "example": "company-123" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Partial-update body. Provide only the fields you want to change.", + "properties": { + "display_name": { + "type": "string", + "nullable": true, + "description": "Friendly display name shown in the console (takes precedence over `legal_name` for UI display)." + }, + "legal_name": { + "type": "string", + "nullable": true, + "description": "Official legal name from registry or manual entry." + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Company registration or incorporation number." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "Country of incorporation (ISO 3166-1 alpha-2, e.g. `GB`, `US`)." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "Business lifecycle status. Setting `BLOCKED` automatically adds the `vendor_data` to the system blocklist; flipping away from `BLOCKED` removes that blocklist entry." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Arbitrary JSON object you attach to the business. **Fully replaces** the existing metadata on update — merge client-side if you only want to add keys." + }, + "reason": { + "type": "string", + "nullable": true, + "maxLength": 1000, + "description": "Why the status changed. Recorded on the status-change entry of the company's **Activity** in the Business Console, only when the status actually changes. Webhooks do not carry it." + } + } + }, + "examples": { + "Update name": { + "summary": "Update display name", + "value": { + "display_name": "Acme Corp" + } + }, + "Flag for review": { + "summary": "Flip status", + "value": { + "status": "FLAGGED" + } + }, + "Add metadata": { + "summary": "Replace metadata JSON", + "value": { + "metadata": { + "industry": "fintech", + "risk_level": "low" + } + } + }, + "Fix registry data": { + "summary": "Correct extracted fields", + "value": { + "legal_name": "Acme Trading International Ltd", + "registration_number": "12345678", + "country_code": "GB" + } + }, + "Flag with reason": { + "summary": "Flag the business and record why", + "value": { + "status": "FLAGGED", + "reason": "Ownership structure under review" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/businesses/company-123/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"display_name\": \"Acme Corp\", \"status\": \"FLAGGED\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.businesses.update(\n \"company-123\",\n display_name=\"Acme Corp\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.businesses.update(\"company-123\", {\n display_name: \"Acme Corp\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.Update(ctx, \"company-123\", didit.BusinessesUpdateParams{\n\tDisplayName: didit.String(\"Acme Corp\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BusinessesUpdateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.businesses().update(\"company-123\", BusinessesUpdateParams.builder()\n .displayName(\"Acme Corp\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->update('company-123', [\n 'display_name' => 'Acme Corp',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.businesses.update(\n \"company-123\",\n display_name: \"Acme Corp\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.UpdateAsync(\"company-123\", new BusinessesUpdateParams\n{\n DisplayName = \"Acme Corp\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Business updated. The full updated business record is returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDetailItem" + }, + "examples": { + "Updated": { + "value": { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-123", + "display_name": "Acme Corp", + "legal_name": "Acme Trading Ltd", + "registration_number": "12345678", + "country_code": "GB", + "effective_name": "Acme Corp", + "status": "FLAGGED", + "session_count": 2, + "approved_count": 1, + "declined_count": 0, + "in_review_count": 1, + "features": { + "KYB": "Approved" + }, + "features_list": [ + { + "feature": "KYB", + "status": "Approved" + } + ], + "last_session_at": "2026-03-15T10:30:00Z", + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": "2026-01-10T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2026-01-10T08:00:00Z", + "metadata": { + "industry": "fintech", + "risk_level": "low" + }, + "updated_at": "2026-03-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Invalid body: typically an unknown `status`, malformed `metadata` or a `reason` over 1000 characters.", + "content": { + "application/json": { + "examples": { + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Bad metadata": { + "value": { + "metadata": [ + "Metadata must be a JSON object." + ] + } + }, + "Reason too long": { + "value": { + "reason": [ + "Ensure this field has no more than 1000 characters." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot update businesses for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No (non-deleted) business with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/businesses/{vendor_data}/update-status/": { + "patch": { + "summary": "Update business status", + "description": "Flip only the lifecycle `status` of a business: `ACTIVE`, `FLAGGED` or `BLOCKED` (not session statuses). `BLOCKED` adds the `vendor_data` to the system blocklist; moving away from `BLOCKED` removes that entry. Each change is recorded in the company's activity, with the optional `reason`. Sending the current status changes nothing and records no `reason`. The `business.status.updated` webhook does not carry `reason`.", + "operationId": "update_business_status", + "x-didit-sdk": { + "resource": "businesses", + "method": "update_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the business (free-form string, NOT a UUID).", + "example": "company-123" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/EntityStatusUpdateRequest" + }, + { + "type": "object", + "properties": { + "reason": { + "type": "string", + "nullable": true, + "maxLength": 1000, + "description": "Why the status changed. Recorded on the status-change entry of the company's **Activity** in the Business Console, only when the status actually changes. Webhooks do not carry it." + } + } + } + ] + }, + "examples": { + "Flag": { + "summary": "Flag the business for review", + "value": { + "status": "FLAGGED" + } + }, + "Block": { + "summary": "Block the business (adds to system blocklist)", + "value": { + "status": "BLOCKED" + } + }, + "Reactivate": { + "summary": "Reactivate the business", + "value": { + "status": "ACTIVE" + } + }, + "Block with reason": { + "summary": "Block the business and record why", + "value": { + "status": "BLOCKED", + "reason": "Registry shows the company dissolved" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/businesses/company-123/update-status/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"status\": \"BLOCKED\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.businesses.update_status(\n \"company-123\",\n status=\"FLAGGED\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.businesses.updateStatus(\"company-123\", {\n status: \"FLAGGED\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.UpdateStatus(ctx, \"company-123\", didit.BusinessesUpdateStatusParams{\n\tStatus: \"FLAGGED\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BusinessesUpdateStatusParams;\nimport me.didit.types.EntityStatusUpdateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.businesses().updateStatus(\"company-123\", BusinessesUpdateStatusParams.builder()\n .status(EntityStatusUpdateRequest.Status.of(\"FLAGGED\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->updateStatus('company-123', [\n 'status' => 'FLAGGED',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.businesses.update_status(\n \"company-123\",\n status: \"FLAGGED\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.UpdateStatusAsync(\"company-123\", new BusinessesUpdateStatusParams\n{\n Status = \"FLAGGED\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Business status updated. Full business record returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDetailItem" + }, + "examples": { + "Blocked": { + "value": { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-123", + "display_name": null, + "legal_name": "Acme Trading Ltd", + "registration_number": "12345678", + "country_code": "GB", + "effective_name": "Acme Trading Ltd", + "status": "BLOCKED", + "session_count": 2, + "approved_count": 1, + "declined_count": 0, + "in_review_count": 1, + "features": { + "KYB": "Approved" + }, + "features_list": [ + { + "feature": "KYB", + "status": "Approved" + } + ], + "last_session_at": "2026-03-15T10:30:00Z", + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": "2026-01-10T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "name": "VIP", + "color": "#2567FF" + } + ], + "created_at": "2026-01-10T08:00:00Z", + "metadata": { + "industry": "fintech" + }, + "updated_at": "2026-03-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Missing or invalid `status`, or a `reason` over 1000 characters.", + "content": { + "application/json": { + "examples": { + "Missing status": { + "value": { + "status": [ + "This field is required." + ] + } + }, + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Reason too long": { + "value": { + "reason": [ + "Ensure this field has no more than 1000 characters." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot update businesses for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No (non-deleted) business with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/businesses/delete/": { + "post": { + "summary": "Batch delete businesses", + "description": "**Permanently delete** businesses — this cannot be undone. For blocking prefer [Update Business Status](#patch-/v3/businesses/-vendor_data-/update-status/) with `BLOCKED`.\n\n**Filed regulatory reports:** if the selected data is in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "batch_delete_businesses", + "x-didit-sdk": { + "resource": "businesses", + "method": "batch_delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Provide `vendor_data_list`, `didit_internal_id_list`, or `delete_all: true`. Precedence when several are sent: `delete_all` > `vendor_data_list` > `didit_internal_id_list` (only the highest-precedence selector is used).", + "properties": { + "vendor_data_list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Your own identifiers to delete, matched exactly as sent." + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit's internal UUIDs (`didit_internal_id`) to delete." + }, + "delete_all": { + "type": "boolean", + "default": false, + "description": "If `true`, deletes every business in the application. Cannot be combined with the list selectors." + } + } + }, + "examples": { + "Specific businesses": { + "summary": "Delete by vendor_data", + "value": { + "vendor_data_list": [ + "company-123", + "company-456" + ] + } + }, + "Specific internal IDs": { + "summary": "Delete by didit_internal_id", + "value": { + "didit_internal_id_list": [ + "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "b2c3d4e5-f6a7-8901-bcde-f12345678901" + ] + } + }, + "All businesses": { + "summary": "Wipe every business in this application", + "value": { + "delete_all": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/businesses/delete/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vendor_data_list\": [\"company-123\", \"company-456\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.businesses.batch_delete(\n vendor_data_list=[\"company-123\", \"company-456\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.businesses.batchDelete({\n vendor_data_list: [\"company-123\", \"company-456\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.BatchDelete(ctx, didit.BusinessesBatchDeleteParams{\n\tVendorDataList: didit.Some([]string{\"company-123\", \"company-456\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.BusinessesBatchDeleteParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.businesses().batchDelete(BusinessesBatchDeleteParams.builder()\n .vendorDataList(List.of(\"company-123\", \"company-456\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->batchDelete([\n 'vendor_data_list' => ['company-123', 'company-456'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.businesses.batch_delete(vendor_data_list: [\"company-123\", \"company-456\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.BatchDeleteAsync(new BusinessesBatchDeleteParams\n{\n VendorDataList = new string[] { \"company-123\", \"company-456\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Businesses deleted. Returns the number of rows actually removed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deleted": { + "type": "integer", + "description": "Number of businesses deleted (excludes vendor_data values that didn't match anything)." + } + } + }, + "examples": { + "Deleted some": { + "value": { + "deleted": 2 + } + }, + "Nothing matched": { + "value": { + "deleted": 0 + } + } + } + } + } + }, + "400": { + "description": "No selector supplied (all of `vendor_data_list`, `didit_internal_id_list`, and `delete_all` missing or empty). The error body is a JSON array.", + "content": { + "application/json": { + "examples": { + "Missing selector": { + "value": [ + "Provide vendor_data_list, didit_internal_id_list, or set delete_all=true." + ] + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot delete businesses for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + }, + "409": { + "description": "Nothing is deleted. `code` says why: `in_filed_report` when the selection includes a company in a finalized regulatory report, `subject_deletion_in_progress` when another request is still deleting one of the selected companies. Repeat the request once that deletion has finished.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/InFiledReportError" + }, + { + "$ref": "#/components/schemas/SubjectDeletionInProgressError" + } + ] + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/users/aml-monitoring/": { + "post": { + "summary": "Enable or disable AML ongoing monitoring for users", + "description": "Turns AML ongoing monitoring on or off for the selected users, with the same semantics as the Business Console. Exactly one monitored AML screening is kept per user, so a profile that verified several times is monitored and billed once; disabling turns monitoring off across all of the profile's screenings. Users without any AML screening are screened first (billed as a regular AML screening) when they have the full name, date of birth and country needed; otherwise they are reported as `skipped_no_identity_data`.\n\nSet `estimate_only: true` to preview counts and costs without writing anything. Selections of 20 profiles or fewer resolve inline and return `200` with the completed job; larger selections return `202` and run in the background - poll [Get AML monitoring job](#get-/v3/aml-monitoring/jobs/-job_id-/) with the returned `job_id`. Enabling is refused with `403` when the organization balance does not cover the estimate. Identifiers that match nothing are listed in `unmatched` and never fail the request: a selection that matches nothing completes as an empty job (`total` 0), the same convention as the batch delete endpoints.", + "operationId": "toggle_users_aml_monitoring", + "x-didit-sdk": { + "resource": "users", + "method": "set_aml_monitoring", + "auth": "api_key", + "server": true + }, + "tags": [ + "AML Ongoing Monitoring" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_enabled" + ], + "description": "Select users with `vendor_data_list`, `didit_internal_id_list`, or both (the two lists are OR'd). At most 1000 identifiers per request across both lists.", + "properties": { + "is_enabled": { + "type": "boolean", + "description": "true to enable monitoring, false to disable it." + }, + "estimate_only": { + "type": "boolean", + "default": false, + "description": "When true, returns the cost estimate and performs no writes - no screening, no billing, no job." + }, + "vendor_data_list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Your own identifiers of the users to toggle, matched exactly as sent." + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit internal ids of the users to toggle - the `didit_internal_id` returned by the create, get and list endpoints." + } + } + }, + "examples": { + "Estimate first": { + "summary": "Preview the cost without writing", + "value": { + "is_enabled": true, + "estimate_only": true, + "vendor_data_list": [ + "user-123", + "user-456" + ] + } + }, + "Enable": { + "summary": "Enable monitoring for two users", + "value": { + "is_enabled": true, + "vendor_data_list": [ + "user-123", + "user-456" + ] + } + }, + "Disable": { + "summary": "Disable monitoring by Didit internal id", + "value": { + "is_enabled": false, + "didit_internal_id_list": [ + "3f1c0d3e-8f0a-4c2e-9a3d-9b1f5f4a2c11" + ] + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/users/aml-monitoring/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"is_enabled\": true, \"vendor_data_list\": [\"user-123\", \"user-456\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.users.set_aml_monitoring(\n is_enabled=True,\n estimate_only=True,\n vendor_data_list=[\"user-123\", \"user-456\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.users.setAmlMonitoring({\n is_enabled: true,\n estimate_only: true,\n vendor_data_list: [\"user-123\", \"user-456\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Users.SetAMLMonitoring(ctx, didit.UsersSetAMLMonitoringParams{\n\tIsEnabled: true,\n\tEstimateOnly: didit.Bool(true),\n\tVendorDataList: didit.Some([]string{\"user-123\", \"user-456\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.UsersSetAmlMonitoringParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.users().setAmlMonitoring(UsersSetAmlMonitoringParams.builder()\n .isEnabled(true)\n .estimateOnly(true)\n .vendorDataList(List.of(\"user-123\", \"user-456\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->setAmlMonitoring([\n 'is_enabled' => true,\n 'estimate_only' => true,\n 'vendor_data_list' => ['user-123', 'user-456'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.users.set_aml_monitoring(\n is_enabled: true,\n estimate_only: true,\n vendor_data_list: [\"user-123\", \"user-456\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Users.SetAmlMonitoringAsync(new UsersSetAmlMonitoringParams\n{\n IsEnabled = true,\n EstimateOnly = true,\n VendorDataList = new string[] { \"user-123\", \"user-456\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The estimate (when `estimate_only` is true) or the completed job for a selection processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AMLMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger selections run in the background. Poll the job endpoint with `job_id`; `results` fills in as profiles are processed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AMLMonitoringJob" + } + } + } + }, + "400": { + "description": "Validation error: `is_enabled` missing, no selector, or more than 1000 identifiers across both lists." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Insufficient balance to enable monitoring for this selection. The response carries the `estimate` so you can top up the right amount.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + } + } + } + } + } + } + } + } + } + } + }, + "/v3/businesses/aml-monitoring/": { + "post": { + "summary": "Enable or disable AML ongoing monitoring for businesses", + "description": "Turns AML ongoing monitoring on or off for the selected businesses, with the same semantics as the Business Console. Exactly one monitored AML screening is kept per business, so a profile that verified several times is monitored and billed once; disabling turns monitoring off across all of the profile's screenings. Businesses without any AML screening are screened first (billed as a regular AML screening) when they have the legal or display name needed; otherwise they are reported as `skipped_no_identity_data`.\n\nSet `estimate_only: true` to preview counts and costs without writing anything. Selections of 20 profiles or fewer resolve inline and return `200` with the completed job; larger selections return `202` and run in the background - poll [Get AML monitoring job](#get-/v3/aml-monitoring/jobs/-job_id-/) with the returned `job_id`. Enabling is refused with `403` when the organization balance does not cover the estimate. Identifiers that match nothing are listed in `unmatched` and never fail the request: a selection that matches nothing completes as an empty job (`total` 0), the same convention as the batch delete endpoints.", + "operationId": "toggle_businesses_aml_monitoring", + "x-didit-sdk": { + "resource": "businesses", + "method": "set_aml_monitoring", + "auth": "api_key", + "server": true + }, + "tags": [ + "AML Ongoing Monitoring" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_enabled" + ], + "description": "Select businesses with `vendor_data_list`, `didit_internal_id_list`, or both (the two lists are OR'd). At most 1000 identifiers per request across both lists.", + "properties": { + "is_enabled": { + "type": "boolean", + "description": "true to enable monitoring, false to disable it." + }, + "estimate_only": { + "type": "boolean", + "default": false, + "description": "When true, returns the cost estimate and performs no writes - no screening, no billing, no job." + }, + "vendor_data_list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Your own identifiers of the businesses to toggle, matched exactly as sent." + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit internal ids of the businesses to toggle - the `didit_internal_id` returned by the create, get and list endpoints." + } + } + }, + "examples": { + "Estimate first": { + "summary": "Preview the cost without writing", + "value": { + "is_enabled": true, + "estimate_only": true, + "vendor_data_list": [ + "company-123", + "company-456" + ] + } + }, + "Enable": { + "summary": "Enable monitoring for two businesses", + "value": { + "is_enabled": true, + "vendor_data_list": [ + "company-123", + "company-456" + ] + } + }, + "Disable": { + "summary": "Disable monitoring by Didit internal id", + "value": { + "is_enabled": false, + "didit_internal_id_list": [ + "3f1c0d3e-8f0a-4c2e-9a3d-9b1f5f4a2c11" + ] + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/businesses/aml-monitoring/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"is_enabled\": true, \"vendor_data_list\": [\"company-123\", \"company-456\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.businesses.set_aml_monitoring(\n is_enabled=True,\n estimate_only=True,\n vendor_data_list=[\"company-123\", \"company-456\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.businesses.setAmlMonitoring({\n is_enabled: true,\n estimate_only: true,\n vendor_data_list: [\"company-123\", \"company-456\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.SetAMLMonitoring(ctx, didit.BusinessesSetAMLMonitoringParams{\n\tIsEnabled: true,\n\tEstimateOnly: didit.Bool(true),\n\tVendorDataList: didit.Some([]string{\"company-123\", \"company-456\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.BusinessesSetAmlMonitoringParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.businesses().setAmlMonitoring(BusinessesSetAmlMonitoringParams.builder()\n .isEnabled(true)\n .estimateOnly(true)\n .vendorDataList(List.of(\"company-123\", \"company-456\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->setAmlMonitoring([\n 'is_enabled' => true,\n 'estimate_only' => true,\n 'vendor_data_list' => ['company-123', 'company-456'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.businesses.set_aml_monitoring(\n is_enabled: true,\n estimate_only: true,\n vendor_data_list: [\"company-123\", \"company-456\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.SetAmlMonitoringAsync(new BusinessesSetAmlMonitoringParams\n{\n IsEnabled = true,\n EstimateOnly = true,\n VendorDataList = new string[] { \"company-123\", \"company-456\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The estimate (when `estimate_only` is true) or the completed job for a selection processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AMLMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger selections run in the background. Poll the job endpoint with `job_id`; `results` fills in as profiles are processed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AMLMonitoringJob" + } + } + } + }, + "400": { + "description": "Validation error: `is_enabled` missing, no selector, or more than 1000 identifiers across both lists." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Insufficient balance to enable monitoring for this selection. The response carries the `estimate` so you can top up the right amount.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + } + } + } + } + } + } + } + } + } + } + }, + "/v3/aml-monitoring/jobs/{job_id}/": { + "get": { + "summary": "Get AML ongoing monitoring job", + "description": "Status, outcome counters and per-record results of a monitoring toggle job started from `POST /v3/users/aml-monitoring/` or `POST /v3/businesses/aml-monitoring/`. Poll every couple of seconds while `status` is `PENDING` or `PROCESSING`; `results` is complete once it is `COMPLETED`.", + "operationId": "get_aml_monitoring_job", + "x-didit-sdk": { + "resource": "aml_monitoring", + "method": "retrieve_job", + "auth": "api_key", + "server": true + }, + "tags": [ + "AML Ongoing Monitoring" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The `job_id` returned by the toggle endpoint." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl 'https://verification.didit.me/v3/aml-monitoring/jobs/JOB_ID/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.aml_monitoring.retrieve_job(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.amlMonitoring.retrieveJob(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.AMLMonitoring.RetrieveJob(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.amlMonitoring().retrieveJob(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "amlMonitoring->retrieveJob('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.aml_monitoring.retrieve_job(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.AmlMonitoring.RetrieveJobAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ] + }, + "is_enabled": { + "type": "boolean", + "description": "true when the job enables monitoring, false when it disables it." + }, + "target_type": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Which profile family the job toggles." + }, + "total": { + "type": "integer", + "description": "Profiles the job processes." + }, + "processed": { + "type": "integer", + "description": "Profiles processed so far." + }, + "outcomes": { + "type": "object", + "description": "Per-record outcome counters. Every selected profile resolves to exactly one counter; nothing is silently skipped.", + "properties": { + "enabled": { + "type": "integer", + "description": "Monitoring enabled on an existing AML screening." + }, + "screened_and_enabled": { + "type": "integer", + "description": "A new AML screening was created (billed) and monitoring enabled on it." + }, + "already_enabled": { + "type": "integer", + "description": "Monitoring was already active; nothing changed and nothing was billed." + }, + "disabled": { + "type": "integer", + "description": "Monitoring turned off across every AML screening of the profile." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Skipped: the profile lacks the full name, date of birth and country (users) or a name (businesses) needed to screen." + }, + "skipped_blocked": { + "type": "integer", + "description": "Skipped: the user is blocked." + }, + "skipped_sandbox": { + "type": "integer", + "description": "Skipped: sandbox application, no screening is created." + }, + "failed_provider": { + "type": "integer", + "description": "The screening could not be completed; retry later." + }, + "failed_insufficient_balance": { + "type": "integer", + "description": "Balance ran out while the job was running." + } + } + }, + "results": { + "type": "array", + "description": "One entry per processed profile, in processing order. Grows while the job runs; complete once status is COMPLETED. Recorded for jobs started through the API; a job started from the Business Console reports counters only.", + "items": { + "type": "object", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit internal id of the profile." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the profile." + }, + "outcome": { + "type": "string", + "enum": [ + "enabled", + "screened_and_enabled", + "already_enabled", + "disabled", + "skipped_no_identity_data", + "skipped_blocked", + "skipped_sandbox", + "failed_provider", + "failed_insufficient_balance" + ] + } + } + } + }, + "error": { + "type": "string", + "nullable": true, + "description": "Generic failure message when status is FAILED; the detailed cause is kept on the platform side." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "404": { + "description": "No such job for this application." + } + } + } + }, + "/v3/users/{vendor_data}/update-status/": { + "patch": { + "summary": "Update user status", + "description": "Flip only the lifecycle `status` of a user: `ACTIVE`, `FLAGGED` or `BLOCKED` (not session statuses). `BLOCKED` adds the `vendor_data` to the system blocklist; moving away from `BLOCKED` removes that entry. Each change is recorded as a `STATUS_CHANGED` activity on the user, with the optional `reason` as its `comment`. Sending the current status changes nothing and records no `reason`. The `user.status.updated` webhook does not carry `reason`.", + "operationId": "update_user_status", + "x-didit-sdk": { + "resource": "users", + "method": "update_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Your unique identifier for the user." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/EntityStatusUpdateRequest" + }, + { + "type": "object", + "properties": { + "reason": { + "type": "string", + "nullable": true, + "maxLength": 1000, + "description": "Why the status changed. Recorded on the status-change entry of the user's activity (`comments[].comment`, and **Activity** in the Business Console), only when the status actually changes. Webhooks do not carry it." + } + } + } + ] + }, + "examples": { + "Activate": { + "summary": "Reactivate the user", + "value": { + "status": "ACTIVE" + } + }, + "Flag": { + "summary": "Flag the user for review", + "value": { + "status": "FLAGGED" + } + }, + "Block": { + "summary": "Block the user (adds to system blocklist)", + "value": { + "status": "BLOCKED" + } + }, + "Block with reason": { + "summary": "Block the user and record why", + "value": { + "status": "BLOCKED", + "reason": "Chargeback fraud pattern confirmed" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/users/user-abc-123/update-status/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"status\": \"BLOCKED\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.users.update_status(\n \"user-123\",\n status=\"ACTIVE\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.users.updateStatus(\"user-123\", {\n status: \"ACTIVE\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Users.UpdateStatus(ctx, \"user-123\", didit.UsersUpdateStatusParams{\n\tStatus: \"ACTIVE\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.EntityStatusUpdateRequest;\nimport me.didit.types.UsersUpdateStatusParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.users().updateStatus(\"user-123\", UsersUpdateStatusParams.builder()\n .status(EntityStatusUpdateRequest.Status.of(\"ACTIVE\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->updateStatus('user-123', [\n 'status' => 'ACTIVE',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.users.update_status(\n \"user-123\",\n status: \"ACTIVE\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Users.UpdateStatusAsync(\"user-123\", new UsersUpdateStatusParams\n{\n Status = \"ACTIVE\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "User status updated. Full user record returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDetailItem" + }, + "examples": { + "Blocked": { + "value": { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": null, + "full_name": "John Michael Doe", + "date_of_birth": "1990-05-15", + "effective_name": "John Michael Doe", + "status": "BLOCKED", + "metadata": { + "tier": "premium", + "source": "website" + }, + "portrait_image_url": "https:///...", + "session_count": 3, + "approved_count": 2, + "declined_count": 0, + "in_review_count": 1, + "issuing_states": [ + "USA" + ], + "approved_emails": [ + "john@example.com" + ], + "approved_phones": [ + "+14155551234" + ], + "features": { + "ID_VERIFICATION": "Approved", + "LIVENESS": "Approved", + "FACE_MATCH": "Approved", + "AML": "Approved" + }, + "features_list": [ + { + "feature": "ID_VERIFICATION", + "status": "Approved" + }, + { + "feature": "LIVENESS", + "status": "Approved" + }, + { + "feature": "FACE_MATCH", + "status": "Approved" + }, + { + "feature": "AML", + "status": "Approved" + } + ], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-01T08:00:00Z", + "tags": [ + { + "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", + "tag": { + "uuid": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b", + "name": "VIP", + "color": "#2567FF", + "description": null, + "source": "custom", + "created_at": "2025-05-01T09:00:00Z", + "updated_at": "2025-05-01T09:00:00Z" + }, + "added_by_email": "analyst@acme.com", + "added_by_name": "Jane Analyst", + "created_at": "2025-06-02T11:00:00Z" + } + ], + "comments": [ + { + "uuid": "c1111111-2222-4333-8444-555555555555", + "comment_type": "STATUS_CHANGED", + "comment": "Chargeback fraud pattern confirmed", + "actor_email": null, + "actor_name": null, + "previous_status": "ACTIVE", + "new_status": "BLOCKED", + "previous_value": null, + "new_value": null, + "changed_fields": [], + "metadata": null, + "mentioned_emails": [], + "created_at": "2025-06-15T10:30:00Z" + } + ], + "created_at": "2025-06-01T08:00:00Z", + "updated_at": "2025-06-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Missing or invalid `status`, or a `reason` over 1000 characters.", + "content": { + "application/json": { + "examples": { + "Missing status": { + "value": { + "status": [ + "This field is required." + ] + } + }, + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Reason too long": { + "value": { + "reason": [ + "Ensure this field has no more than 1000 characters." + ] + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "The key cannot update users for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No (non-deleted) user with the supplied `vendor_data` exists for this application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/businesses/create/": { + "post": { + "summary": "Create business", + "description": "Pre-create a [business](/entities/businesses) *without* a KYB session. All fields are optional, but you should always send `vendor_data` — without it the record cannot be linked to sessions or transactions. When provided, `vendor_data` must be unique among non-deleted businesses (exact match; conflicts return 400). Not idempotent.", + "operationId": "create_business", + "x-didit-sdk": { + "resource": "businesses", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Businesses" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your unique identifier for this business (free-form string, NOT a UUID). Optional but strongly recommended. When provided, must be unique among non-deleted businesses for the application; matched exactly as sent." + }, + "display_name": { + "type": "string", + "nullable": true, + "maxLength": 255, + "description": "Friendly display name shown in the console (takes precedence over `legal_name` for UI display)." + }, + "legal_name": { + "type": "string", + "nullable": true, + "maxLength": 255, + "description": "Official legal name of the company." + }, + "registration_number": { + "type": "string", + "nullable": true, + "maxLength": 100, + "description": "Company registration or incorporation number." + }, + "country_code": { + "type": "string", + "nullable": true, + "maxLength": 10, + "description": "Country of incorporation (ISO 3166-1 alpha-2, e.g. `GB`, `US`)." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "default": "ACTIVE", + "description": "Initial lifecycle status. Defaults to `ACTIVE`." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Arbitrary JSON object you attach to the business. Defaults to `{}`." + } + } + }, + "examples": { + "Basic": { + "summary": "Recommended minimum", + "value": { + "vendor_data": "company-456", + "legal_name": "New Corp Ltd", + "country_code": "US" + } + }, + "Full": { + "summary": "All fields", + "value": { + "vendor_data": "company-789", + "display_name": "New Corp", + "legal_name": "New Corp International Ltd", + "registration_number": "98765432", + "country_code": "DE", + "status": "ACTIVE", + "metadata": { + "industry": "fintech", + "tier": "enterprise" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/businesses/create/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vendor_data\": \"company-456\", \"legal_name\": \"New Corp Ltd\", \"country_code\": \"US\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.businesses.create(\n vendor_data=\"company-456\",\n legal_name=\"New Corp Ltd\",\n country_code=\"US\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.businesses.create({\n vendor_data: \"company-456\",\n legal_name: \"New Corp Ltd\",\n country_code: \"US\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.Create(ctx, didit.BusinessesCreateParams{\n\tVendorData: didit.String(\"company-456\"),\n\tLegalName: didit.String(\"New Corp Ltd\"),\n\tCountryCode: didit.String(\"US\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BusinessesCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.businesses().create(BusinessesCreateParams.builder()\n .vendorData(\"company-456\")\n .legalName(\"New Corp Ltd\")\n .countryCode(\"US\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->create([\n 'vendor_data' => 'company-456',\n 'legal_name' => 'New Corp Ltd',\n 'country_code' => 'US',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.businesses.create(\n vendor_data: \"company-456\",\n legal_name: \"New Corp Ltd\",\n country_code: \"US\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.CreateAsync(new BusinessesCreateParams\n{\n VendorData = \"company-456\",\n LegalName = \"New Corp Ltd\",\n CountryCode = \"US\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Business created. Full business record returned (same shape as Get Business).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDetailItem" + }, + "examples": { + "Created": { + "value": { + "didit_internal_id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890", + "vendor_data": "company-456", + "display_name": null, + "legal_name": "New Corp Ltd", + "registration_number": null, + "country_code": "US", + "effective_name": "New Corp Ltd", + "status": "ACTIVE", + "session_count": 0, + "approved_count": 0, + "declined_count": 0, + "in_review_count": 0, + "features": {}, + "features_list": [], + "last_session_at": null, + "last_activity_at": "2026-03-15T10:30:00Z", + "first_session_at": null, + "tags": [], + "created_at": "2026-03-15T10:30:00Z", + "metadata": {}, + "updated_at": "2026-03-15T10:30:00Z" + } + } + } + } + } + }, + "400": { + "description": "Invalid body — typically a duplicate `vendor_data`, an unknown `status`, or malformed `metadata`.", + "content": { + "application/json": { + "examples": { + "Duplicate": { + "value": { + "vendor_data": [ + "A business with this vendor_data already exists." + ] + } + }, + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Bad metadata": { + "value": { + "metadata": [ + "Metadata must be a JSON object." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the key cannot create businesses for this application. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/users/create/": { + "post": { + "summary": "Create user", + "description": "Create a [user](/entities/users) by `vendor_data`, before or without a verification session. `vendor_data` must be unique among the application's users (a duplicate returns `400`). Not idempotent.\n\n**Contact details:** `email` and `phone_number` are stored in `metadata.contact`, and `country` in `metadata.address`. When you send `first_name` or `last_name` without `full_name`, Didit joins them into `full_name`.\n\n**Verification link:** with `send_verification_link: true`, Didit also creates a verification session for the person on `workflow_id` and emails them the link at `email`. The session runs the same checks as [Create Session](/sessions-api/create-session), such as the blocklist and your credit balance. The person and the session are created together or not at all: if any check fails, nothing is written and no email is sent. The response's `verification_link` describes the session, and is `null` when you don't ask for a link.\n\n**API key access:** a scoped API key needs `users` write. With `send_verification_link: true` it also needs `sessions` write, or the call answers `403` before anything is written. Full-access keys are unaffected. See [API keys](/console/api-keys).", + "operationId": "create_user", + "x-didit-sdk": { + "resource": "users", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Users" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "vendor_data" + ], + "properties": { + "vendor_data": { + "type": "string", + "description": "Your unique identifier for this user (free-form string, NOT a UUID). Must be unique among the application's users; matched exactly as sent." + }, + "full_name": { + "type": "string", + "nullable": true, + "maxLength": 512, + "description": "Full legal name of the user." + }, + "display_name": { + "type": "string", + "nullable": true, + "description": "Friendly display name shown in the console (takes precedence over `full_name` for UI display).", + "maxLength": 255 + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth in `YYYY-MM-DD` format." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "default": "ACTIVE", + "description": "Initial lifecycle status. Defaults to `ACTIVE`." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Arbitrary JSON object you attach to the user. Defaults to `{}`." + }, + "approved_emails": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Pre-trusted email addresses for this user, e.g. `[\"john@example.com\"]`." + }, + "approved_phones": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Pre-trusted phone numbers for this user, e.g. `[\"+14155551234\"]`." + }, + "issuing_states": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Pre-recorded issuing countries (ISO 3166-1 alpha-3), e.g. `[\"USA\"]`." + }, + "first_name": { + "type": "string", + "maxLength": 255, + "description": "Given name. When `full_name` is not sent, Didit joins `first_name` and `last_name` with a space into `full_name`. Not returned as a separate field." + }, + "last_name": { + "type": "string", + "maxLength": 255, + "description": "Family name. Joined with `first_name` into `full_name` when `full_name` is not sent. Not returned as a separate field." + }, + "email": { + "type": "string", + "format": "email", + "description": "Contact email, stored in `metadata.contact.email` (it replaces an `email` you send inside `metadata.contact`). Required when `send_verification_link` is `true`: the link is sent to this address. If `metadata.contact` is not a JSON object, the call answers `400` on this field." + }, + "phone_number": { + "type": "string", + "maxLength": 32, + "description": "Contact phone number, stored in `metadata.contact.phone_number`. If `metadata.contact` is not a JSON object, the call answers `400` on this field." + }, + "country": { + "type": "string", + "maxLength": 3, + "description": "Country as an ISO 3166 code, alpha-2 or alpha-3 (`ES` or `ESP`). Stored uppercased in `metadata.address.country`. If `metadata.address` is not a JSON object, the call answers `400` on this field." + }, + "send_verification_link": { + "type": "boolean", + "default": false, + "description": "When `true`, Didit creates a verification session for the person on `workflow_id` and emails them the link at `email`. Requires `email` and `workflow_id`. A scoped API key also needs `sessions` write." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "The workflow of the verification session. Required when `send_verification_link` is `true`. It must verify a person: a Business Verification (KYB) workflow is refused. Ignored otherwise." + }, + "language": { + "type": "string", + "maxLength": 10, + "description": "Language of the verification email, as a [supported language](/integration/supported-languages) code. English when omitted. Ignored when `send_verification_link` is not `true`." + } + } + }, + "examples": { + "Basic": { + "summary": "Create with basic info", + "value": { + "vendor_data": "user-abc-123", + "full_name": "John Doe" + } + }, + "Full": { + "summary": "All fields", + "value": { + "vendor_data": "user-abc-456", + "full_name": "Jane Smith", + "display_name": "Jane S.", + "date_of_birth": "1990-05-15", + "status": "ACTIVE", + "metadata": { + "tier": "premium", + "source": "api" + }, + "approved_emails": [ + "jane@example.com" + ] + } + }, + "Contact details": { + "summary": "Name, email, phone and country", + "value": { + "vendor_data": "user-abc-789", + "first_name": "Julia", + "last_name": "Martin", + "email": "julia.martin@example.com", + "phone_number": "+34600000000", + "country": "ES" + } + }, + "With verification link": { + "summary": "Create the person and email them a verification link", + "value": { + "vendor_data": "user-abc-789", + "first_name": "Julia", + "last_name": "Martin", + "email": "julia.martin@example.com", + "send_verification_link": true, + "workflow_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "language": "es" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/users/create/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vendor_data\": \"user-abc-123\", \"full_name\": \"John Doe\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.users.create(\n vendor_data=\"user-abc-123\",\n full_name=\"John Doe\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.users.create({\n vendor_data: \"user-abc-123\",\n full_name: \"John Doe\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Users.Create(ctx, didit.UsersCreateParams{\n\tVendorData: \"user-abc-123\",\n\tFullName: didit.String(\"John Doe\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.UsersCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.users().create(UsersCreateParams.builder()\n .vendorData(\"user-abc-123\")\n .fullName(\"John Doe\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->create([\n 'vendor_data' => 'user-abc-123',\n 'full_name' => 'John Doe',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.users.create(\n vendor_data: \"user-abc-123\",\n full_name: \"John Doe\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Users.CreateAsync(new UsersCreateParams\n{\n VendorData = \"user-abc-123\",\n FullName = \"John Doe\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "User created. The full user record (same shape as Get User), plus `verification_link`: the verification session Didit emailed when `send_verification_link` was `true`, otherwise `null`.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UserDetailItem" + }, + { + "type": "object", + "required": [ + "verification_link" + ], + "properties": { + "verification_link": { + "type": "object", + "nullable": true, + "description": "The verification session Didit created and emailed to the person. Always present: `null` when `send_verification_link` was not `true`.", + "required": [ + "session_id", + "session_number", + "status", + "url", + "workflow_id", + "email" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "ID of the verification session." + }, + "session_number": { + "type": "integer", + "description": "Sequential number of the session inside your application." + }, + "status": { + "type": "string", + "description": "Status of the session, `Not Started` for a session that was just created." + }, + "url": { + "type": "string", + "format": "uri", + "description": "The verification link Didit emailed to the person." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The workflow the session runs." + }, + "email": { + "type": "string", + "format": "email", + "description": "The address the link was sent to." + } + } + } + } + } + ] + }, + "examples": { + "Created": { + "value": { + "didit_internal_id": "f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418", + "vendor_data": "user-abc-123", + "display_name": null, + "full_name": "John Doe", + "date_of_birth": null, + "effective_name": "John Doe", + "status": "ACTIVE", + "metadata": {}, + "portrait_image_url": null, + "session_count": 0, + "approved_count": 0, + "declined_count": 0, + "in_review_count": 0, + "issuing_states": [], + "approved_emails": [], + "approved_phones": [], + "features": {}, + "features_list": [], + "last_session_at": null, + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": null, + "tags": [], + "comments": [], + "created_at": "2025-06-15T10:30:00Z", + "updated_at": "2025-06-15T10:30:00Z", + "verification_link": null + } + }, + "Created with verification link": { + "value": { + "didit_internal_id": "9b2f6c1e-3d4a-4e5f-8a7b-1c2d3e4f5a6b", + "vendor_data": "user-abc-789", + "display_name": null, + "full_name": "Julia Martin", + "date_of_birth": null, + "effective_name": "Julia Martin", + "status": "ACTIVE", + "metadata": { + "contact": { + "email": "julia.martin@example.com" + } + }, + "portrait_image_url": null, + "session_count": 1, + "approved_count": 0, + "declined_count": 0, + "in_review_count": 0, + "issuing_states": [], + "approved_emails": [], + "approved_phones": [], + "features": {}, + "features_list": [], + "last_session_at": "2025-06-15T10:30:00Z", + "last_activity_at": "2025-06-15T10:30:00Z", + "first_session_at": "2025-06-15T10:30:00Z", + "tags": [], + "comments": [], + "created_at": "2025-06-15T10:30:00Z", + "updated_at": "2025-06-15T10:30:00Z", + "verification_link": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa7", + "session_number": 43762, + "status": "Not Started", + "url": "https://verify.didit.me/en/session/3FaJ9wLqX2Mz", + "workflow_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "email": "julia.martin@example.com" + } + } + } + } + } + } + }, + "400": { + "description": "Invalid body, keyed by field: typically a duplicate `vendor_data`, a missing required field or a malformed value. With `send_verification_link: true`, also a missing `email` or `workflow_id`, an unknown or Business Verification (KYB) workflow, an unsupported `language` (keyed `contact_details.email_lang`), a blocklisted `vendor_data`, or too few credits to start the session. Nothing is written.", + "content": { + "application/json": { + "examples": { + "Duplicate": { + "value": { + "vendor_data": [ + "A user with this vendor_data already exists." + ] + } + }, + "Missing vendor_data": { + "value": { + "vendor_data": [ + "This field is required." + ] + } + }, + "Bad metadata": { + "value": { + "metadata": [ + "Metadata must be a JSON object." + ] + } + }, + "Bad status": { + "value": { + "status": [ + "Invalid status. Valid options are: ACTIVE, FLAGGED, BLOCKED" + ] + } + }, + "Bad country": { + "value": { + "country": [ + "Use an ISO 3166 country code, like ES or ESP." + ] + } + }, + "Contact not an object": { + "value": { + "email": [ + "metadata.contact must be a JSON object to store this value." + ] + } + }, + "Address not an object": { + "value": { + "country": [ + "metadata.address must be a JSON object to store this value." + ] + } + }, + "Link without email or workflow": { + "value": { + "email": [ + "An email address is required to send a verification link." + ], + "workflow_id": [ + "Pick the workflow to send a verification link for." + ] + } + }, + "Unknown workflow": { + "value": { + "workflow_id": [ + "Invalid workflow_id." + ] + } + }, + "Business workflow": { + "value": { + "workflow_id": [ + "This workflow verifies a business. Pick a workflow that verifies a person to send them a verification link." + ] + } + }, + "Blocklisted vendor_data": { + "value": { + "vendor_data": "This user or business has been blocked and cannot create new verification sessions." + } + }, + "Insufficient credits": { + "value": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "401": { + "description": "No credentials supplied. Requests to `/v3/users/*` paths without an `x-api-key` header (or `Authorization: Bearer` token) are rejected by the authentication middleware before reaching the API.", + "content": { + "application/json": { + "examples": { + "Missing credentials": { + "value": { + "detail": "You must be authenticated with a valid access token to access this endpoint." + } + } + } + } + } + }, + "403": { + "description": "Invalid or revoked API key, or the key cannot create users for this application. A scoped API key needs `users` write, and also `sessions` write when `send_verification_link` is `true`; nothing is written. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "No sessions write": { + "summary": "Scoped key asks for a verification link without sessions write", + "value": { + "detail": "This API key has no write access to sessions." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry after the interval indicated in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-users/by-id/{didit_internal_id}/faces/upload/": { + "post": { + "summary": "Upload user face", + "description": "Attach a trusted imported face image to an existing User profile. Use this after creating or retrieving a User by `vendor_data` and reading its `didit_internal_id`. The uploaded face is stored on the User profile and indexed for duplicate detection and face search. It is not added to a face blocklist.", + "operationId": "upload_user_face", + "x-didit-sdk": { + "resource": "users", + "method": "upload_face", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID that owns the application." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID that owns the User profile." + }, + { + "name": "didit_internal_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Didit's internal User UUID, returned as `didit_internal_id` by `GET /v3/users/{vendor_data}/` and `POST /v3/users/create/`." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "image" + ], + "properties": { + "image": { + "type": "string", + "format": "byte", + "description": "Raw base64-encoded JPG or PNG face image. Do not include a `data:image/...;base64,` prefix. Max decoded size: 2 MB." + }, + "comment": { + "type": "string", + "description": "Optional operator-visible note for the uploaded face.", + "default": "" + } + } + }, + "examples": { + "Imported face": { + "summary": "Attach a face imported from a previous provider", + "value": { + "image": "/9j/4AAQSkZJRgABAQEA...", + "comment": "Imported from previous KYC provider" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/vendor-users/by-id/f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418/faces/upload/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"image\": \"/9j/4AAQSkZJRgABAQEA...\", \"comment\": \"Imported from previous KYC provider\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.users.upload_face(\n \"11111111-2222-3333-4444-555555555555\",\n image=\"/9j/4AAQSkZJRgABAQEA...\",\n comment=\"Imported from previous KYC provider\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.users.uploadFace(\"11111111-2222-3333-4444-555555555555\", {\n image: \"/9j/4AAQSkZJRgABAQEA...\",\n comment: \"Imported from previous KYC provider\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Users.UploadFace(ctx, \"11111111-2222-3333-4444-555555555555\", didit.UsersUploadFaceParams{\n\tImage: \"/9j/4AAQSkZJRgABAQEA...\",\n\tComment: didit.String(\"Imported from previous KYC provider\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.UsersUploadFaceParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.users().uploadFace(\"11111111-2222-3333-4444-555555555555\", UsersUploadFaceParams.builder()\n .image(\"/9j/4AAQSkZJRgABAQEA...\")\n .comment(\"Imported from previous KYC provider\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->uploadFace('11111111-2222-3333-4444-555555555555', [\n 'image' => '/9j/4AAQSkZJRgABAQEA...',\n 'comment' => 'Imported from previous KYC provider',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.users.upload_face(\n \"11111111-2222-3333-4444-555555555555\",\n image: \"/9j/4AAQSkZJRgABAQEA...\",\n comment: \"Imported from previous KYC provider\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Users.UploadFaceAsync(\"11111111-2222-3333-4444-555555555555\", new UsersUploadFaceParams\n{\n Image = \"/9j/4AAQSkZJRgABAQEA...\",\n Comment = \"Imported from previous KYC provider\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Face uploaded and attached to the User profile.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "uuid", + "image_url", + "comment", + "uploaded_by", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the uploaded User face record." + }, + "image_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Signed URL for the uploaded face image." + }, + "comment": { + "type": "string", + "nullable": true, + "description": "Comment supplied when the face was uploaded." + }, + "uploaded_by": { + "type": "string", + "nullable": true, + "description": "Email or actor identifier of the uploader when available." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Upload timestamp." + } + } + }, + "examples": { + "Uploaded": { + "value": { + "uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "image_url": "https://signed-url.example/face.jpg", + "comment": "Imported from previous KYC provider", + "uploaded_by": "api", + "created_at": "2026-05-18T12:00:00Z" + } + } + } + } + } + }, + "400": { + "description": "Invalid request body or image. Common causes: invalid base64, invalid image, no detectable face, image over 2 MB, missing embedding, or the User already has 5 uploaded faces.", + "content": { + "application/json": { + "examples": { + "No face": { + "value": { + "image": "No face detected in the image. Please upload a clear photo with a visible face." + } + }, + "Too many faces": { + "value": { + "detail": "Maximum of 5 faces per user reached." + } + }, + "Too large": { + "value": { + "image": "Image exceeds maximum size of 2 MB." + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key.", + "content": { + "application/json": { + "examples": { + "Missing key": { + "value": { + "detail": "Authentication credentials were not provided." + } + } + } + } + } + }, + "403": { + "description": "The credential is valid but cannot write User profiles for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "No User exists for that `didit_internal_id` in the application.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded; back off and retry.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "Request was throttled. Expected available in 30 seconds." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/update-data/": { + "patch": { + "summary": "Update KYC data extracted from the ID document on a verification session", + "description": "Correct the identity fields that were extracted (OCR / NFC) from the user's ID document — misread names, dates, document numbers, and so on — after the verification has finished. Typical callers are a compliance analyst working from your own back office, or an automated reconciliation job. Every field in the body is optional: only the keys you send are written, everything else is left untouched (`PATCH` semantics).\n\nThe session must belong to your application and be in `Approved`, `Declined`, `In Review`, or `Kyc Expired` status. Sessions in any other state (e.g. still `In Progress`) return `404` with `\"No Session matches the given query.\"` — distinct from the `\"Not found.\"` returned for an unknown or deleted `sessionId`. The session must also carry an ID-verification (KYC) record: sessions whose workflow never ran an ID-verification step return `404` (`\"The session does not have a KYC.\"`). For graph workflows with multiple ID-verification steps, target a specific step with `node_id` (query parameter or body field); when omitted, the most recent KYC record is patched.\n\nTwo derived fields are recomputed on every call: `full_name` (from `first_name` + `last_name`) and `issuing_state_name` (from `issuing_state`). If the free-text `address` changes, the server may also re-geocode it and refresh `parsed_address` (skipped for sandbox sessions). `extra_fields` and `parsed_address` are merged key-by-key rather than replaced — see the per-field descriptions for the `null`-to-delete semantics.\n\nSubmitting at least one valid field fires the `data.updated` [webhook](/integration/webhooks) (trigger `manual_review`) — even when the submitted values equal the stored ones. When values actually change, a `KYC_DATA_UPDATED` activity entry recording the changed fields with previous and new values is appended to the session's `reviews`, visible in [Get Decision](#get-/v3/session/-sessionId-/decision/) and the console activity timeline. The session's status and decision are **not** re-evaluated — use [Update Status](#patch-/v3/session/-sessionId-/update-status/) to change the outcome. An empty JSON body (`{}`) is accepted and simply returns the current KYC data without firing the webhook.\n\nAccepts an API key or a console user token; either way the credential needs the `write:sessions` permission. Authentication and permission failures both return `403` — this API never responds `401`. Note that the session lookup runs before credential checks, so an unknown `sessionId` returns `404` even with missing credentials. This endpoint has a dedicated rate limit of 10 requests per minute per API key (invalid requests count too); the shared write budget of 300 requests per minute also applies.", + "operationId": "patch_v3_session_update_data", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_data", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session whose extracted document data you are correcting. Business (KYB) session IDs are not valid here — for company data use [Update KYB Company Data](#patch-/v3/session/-sessionId-/kyb/-companyUuid-/update-data/).", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node whose KYC record to patch, for graph workflows with multiple ID-verification steps (also accepted as a `node_id` body field). When omitted, the most recent KYC record on the session is patched. If no KYC exists for the given node, the call returns `404` (`\"The session does not have a KYC for node ''.\"`).", + "schema": { + "type": "string", + "example": "feature_ocr" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "All fields optional — send only what you want to change.", + "properties": { + "document_type": { + "type": "string", + "description": "Document type code. Accepts the short codes listed in the enum, plus six long-form aliases normalized server-side before validation: `Passport` → `P`, `Identity Card` → `ID`, `Driver's License` → `DL`, `Residence Permit` → `RP`, `Health Insurance Card` → `HIC`, `Tax Card` → `TC`. Any other value (including `PASSPORT` in caps) returns `400`.", + "enum": [ + "P", + "DL", + "ID", + "RP", + "SSC", + "HIC", + "WP", + "TC", + "VISA", + "PSC", + "BC", + "OTHER" + ], + "nullable": true, + "example": "P" + }, + "document_subtype": { + "type": "string", + "maxLength": 128, + "nullable": true, + "description": "Document subtype from the document registry, including the region prefix when the registry distinguishes regional variants." + }, + "document_number": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Document number as printed on the ID.", + "example": "PAB123456" + }, + "personal_number": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Personal / national identification number where the document carries one (e.g. Spanish DNI on a passport).", + "example": "99999999R" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth in `YYYY-MM-DD`. Any other format returns `400`.", + "example": "1980-01-01" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Issue date of the document in `YYYY-MM-DD`." + }, + "expiration_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Expiration date of the document in `YYYY-MM-DD`." + }, + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Issuing country as an ISO 3166-1 alpha-3 code. Case-insensitive (`esp` is stored as `ESP`); the human-readable `issuing_state_name` shown in [Get Decision](#get-/v3/session/-sessionId-/decision/) is recomputed from this value. Invalid codes return `400`.", + "example": "ESP" + }, + "first_name": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Given name(s). The stored `full_name` is recomputed as `first_name + ' ' + last_name` on every call to this endpoint.", + "example": "Carmen" + }, + "last_name": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Family name(s). Also feeds the recomputed `full_name`.", + "example": "Espanola Espanola" + }, + "gender": { + "type": "string", + "enum": [ + "M", + "F", + "U" + ], + "nullable": true, + "description": "Sex as printed on the document: `M` (male), `F` (female), or `U` (unknown). Any other value — including `X` — returns `400`." + }, + "address": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it synchronously before responding — the 200 body already reflects the refreshed `parsed_address`. The re-geocode also requires `issuing_state` to be present on the KYC record." + }, + "place_of_birth": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Place of birth as printed on the document." + }, + "nationality": { + "type": "string", + "maxLength": 32, + "nullable": true, + "description": "Nationality as printed on the document. Free text up to 32 characters — typically an ISO 3166-1 alpha-3 code (e.g. `ESP`), but not validated as one." + }, + "marital_status": { + "type": "string", + "enum": [ + "SINGLE", + "MARRIED", + "DIVORCED", + "WIDOWED", + "UNKNOWN" + ], + "description": "Marital status where the document carries it. Any other value returns `400`." + }, + "extra_fields": { + "type": "object", + "additionalProperties": true, + "nullable": true, + "description": "Per-key overrides for document-specific extra fields (e.g. `blood_group`, `dl_categories`, `mrz_string`). Keys are merged into the existing override map: send a key with a value to set it, or with `null` / `\"\"` to remove that key from the merged view. Send the whole field as `null` to clear all previously applied overrides. Must be a JSON object, otherwise `400`. The response always returns the merged view (OCR-extracted values with your overrides applied)." + }, + "parsed_address": { + "type": "object", + "nullable": true, + "description": "Structured address. Only these nested keys are accepted: `address_type`, `city`, `region`, `street_1`, `street_2`, `postal_code`, `country` — any other key returns `400`. Per-key semantics: a value sets the key, `null` removes it. `formatted_address` is recomputed server-side by joining `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country` and is returned in the response — you cannot set it directly.", + "properties": { + "address_type": { + "type": "string", + "nullable": true, + "description": "Free-form address classification (e.g. `home`)." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Madrid" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State / province / autonomous community." + }, + "street_1": { + "type": "string", + "nullable": true, + "example": "Avda de Madrid 34" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "28013" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`.", + "example": "ES" + } + } + } + } + }, + "examples": { + "Correct name and DOB": { + "summary": "Fix OCR-misread name, date of birth, and structured address", + "value": { + "first_name": "Carmen", + "last_name": "Espanola Espanola", + "date_of_birth": "1980-01-01", + "issuing_state": "ESP", + "marital_status": "MARRIED", + "extra_fields": { + "blood_group": "O+" + }, + "parsed_address": { + "street_1": "Avda de Madrid 34", + "city": "Madrid", + "postal_code": "28013", + "country": "ES" + } + } + }, + "Remove an extra field": { + "summary": "Delete one extra-field key, keep the rest", + "value": { + "extra_fields": { + "blood_group": null + } + } + }, + "Long-form document type": { + "summary": "Long-form alias is normalized to the short code (here P)", + "value": { + "document_type": "Passport" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "KYC data updated. Returns the **full current KYC record** (every updatable field with its stored value, plus the merged `extra_fields` and the current `parsed_address`) — not just the fields you submitted. The `data.updated` webhook fires when at least one valid field was in the payload.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "document_type": { + "type": "string", + "description": "Document type code. Accepts the short codes listed in the enum, plus six long-form aliases normalized server-side before validation: `Passport` → `P`, `Identity Card` → `ID`, `Driver's License` → `DL`, `Residence Permit` → `RP`, `Health Insurance Card` → `HIC`, `Tax Card` → `TC`. Any other value (including `PASSPORT` in caps) returns `400`.", + "enum": [ + "P", + "DL", + "ID", + "RP", + "SSC", + "HIC", + "WP", + "TC", + "VISA", + "PSC", + "BC", + "OTHER" + ], + "nullable": true, + "example": "P" + }, + "document_subtype": { + "type": "string", + "maxLength": 128, + "nullable": true, + "description": "Document subtype from the document registry, including the region prefix when the registry distinguishes regional variants." + }, + "document_number": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Document number as printed on the ID.", + "example": "PAB123456" + }, + "personal_number": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Personal / national identification number where the document carries one (e.g. Spanish DNI on a passport).", + "example": "99999999R" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth in `YYYY-MM-DD`. Any other format returns `400`.", + "example": "1980-01-01" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Issue date of the document in `YYYY-MM-DD`." + }, + "expiration_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Expiration date of the document in `YYYY-MM-DD`." + }, + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Issuing country as an ISO 3166-1 alpha-3 code. Case-insensitive (`esp` is stored as `ESP`); the human-readable `issuing_state_name` shown in [Get Decision](#get-/v3/session/-sessionId-/decision/) is recomputed from this value. Invalid codes return `400`.", + "example": "ESP" + }, + "first_name": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Given name(s). The stored `full_name` is recomputed as `first_name + ' ' + last_name` on every call to this endpoint.", + "example": "Carmen" + }, + "last_name": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Family name(s). Also feeds the recomputed `full_name`.", + "example": "Espanola Espanola" + }, + "gender": { + "type": "string", + "enum": [ + "M", + "F", + "U" + ], + "nullable": true, + "description": "Sex as printed on the document: `M` (male), `F` (female), or `U` (unknown). Any other value — including `X` — returns `400`." + }, + "address": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it in the background of the same request and may overwrite `parsed_address` with the geocoder result." + }, + "place_of_birth": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Place of birth as printed on the document." + }, + "nationality": { + "type": "string", + "maxLength": 32, + "nullable": true, + "description": "Nationality as printed on the document. Free text up to 32 characters — typically an ISO 3166-1 alpha-3 code (e.g. `ESP`), but not validated as one." + }, + "marital_status": { + "type": "string", + "enum": [ + "SINGLE", + "MARRIED", + "DIVORCED", + "WIDOWED", + "UNKNOWN" + ], + "description": "Marital status where the document carries it. Any other value returns `400`." + }, + "extra_fields": { + "type": "object", + "additionalProperties": true, + "nullable": true, + "description": "Merged view of the document's extra fields: the OCR-extracted values with all operator overrides applied. Keys removed via a `null` override are absent from this map." + }, + "parsed_address": { + "type": "object", + "nullable": true, + "description": "Current structured address (with internal bookkeeping keys stripped), or `null` when none exists. Includes the recomputed `formatted_address` alongside the seven editable keys.", + "properties": { + "address_type": { + "type": "string", + "nullable": true, + "description": "Free-form address classification (e.g. `home`)." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Madrid" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State / province / autonomous community." + }, + "street_1": { + "type": "string", + "nullable": true, + "example": "Avda de Madrid 34" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "28013" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`.", + "example": "ES" + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "Joined, human-readable form of the structured address. Recomputed server-side after every `parsed_address` edit.", + "example": "Avda de Madrid 34, Madrid, 28013, ES" + } + } + } + } + }, + "examples": { + "Updated KYC": { + "summary": "Full KYC record after the correction", + "value": { + "document_type": "P", + "document_subtype": null, + "document_number": "PAB123456", + "personal_number": "99999999R", + "date_of_birth": "1980-01-01", + "date_of_issue": "2021-02-09", + "expiration_date": "2031-02-08", + "issuing_state": "ESP", + "first_name": "Carmen", + "last_name": "Espanola Espanola", + "gender": "F", + "address": "AVDA DE MADRID 34, MADRID, MADRID 28013", + "place_of_birth": "MADRID", + "nationality": "ESP", + "marital_status": "MARRIED", + "extra_fields": { + "mrz_string": "P\": [\"\"]}`; errors on nested `parsed_address` / `extra_fields` keys use an object value: `{\"parsed_address\": {\"\": \"\"}}`.", + "content": { + "application/json": { + "examples": { + "Invalid gender": { + "summary": "gender must be M, F, or U", + "value": { + "gender": [ + "\"X\" is not a valid choice." + ] + } + }, + "Invalid document type": { + "summary": "Not a short code or long-form alias", + "value": { + "document_type": [ + "\"PASSPORT\" is not a valid choice." + ] + } + }, + "Invalid issuing state": { + "summary": "issuing_state is not ISO 3166-1 alpha-3", + "value": { + "issuing_state": [ + "Invalid ISO 3166-1 alpha-3 country code for issuing_state." + ] + } + }, + "Invalid marital status": { + "summary": "marital_status not in the enum", + "value": { + "marital_status": [ + "\"ENGAGED\" is not a valid choice." + ] + } + }, + "Bad date format": { + "summary": "Dates must be YYYY-MM-DD", + "value": { + "date_of_birth": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + }, + "Unsupported parsed_address key": { + "summary": "Only the seven documented keys are allowed", + "value": { + "parsed_address": { + "zipcode": "Unsupported key. Allowed keys: address_type, city, region, street_1, street_2, postal_code, country" + } + } + }, + "Invalid parsed_address country": { + "summary": "Nested country must be alpha-2, not alpha-3", + "value": { + "parsed_address": { + "country": "Invalid ISO 3166-1 alpha-2 country code." + } + } + }, + "extra_fields not an object": { + "summary": "extra_fields must be a JSON object", + "value": { + "extra_fields": [ + "This field must be a JSON object." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Credential lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown session, session in a non-editable state, or no KYC record to patch. Each case has a distinct `detail` message (see examples). The unknown-session lookup runs before credential checks.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "summary": "No session with this ID in your application (or it was deleted)", + "value": { + "detail": "Not found." + } + }, + "Wrong session state": { + "summary": "Session exists but is not Approved / Declined / In Review / Kyc Expired", + "value": { + "detail": "No Session matches the given query." + } + }, + "No KYC record": { + "summary": "The workflow never ran an ID-verification step", + "value": { + "detail": "The session does not have a KYC." + } + }, + "No KYC for node": { + "summary": "node_id given but no KYC exists for it", + "value": { + "detail": "The session does not have a KYC for node 'feature_missing'." + } + } + } + } + } + }, + "429": { + "description": "Dedicated rate limit exceeded — 10 KYC data updates per minute per API key (rejected requests count toward the budget). The shared 300/min write budget can also trigger with its own message. Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "KYC update budget exhausted", + "value": { + "detail": "Session KYC data update rate limit exceeded. You can make up to 10 requests per minute." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-data/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"first_name\": \"Carmen\",\n \"last_name\": \"Espanola Espanola\",\n \"date_of_birth\": \"1980-01-01\",\n \"issuing_state\": \"ESP\",\n \"parsed_address\": {\n \"street_1\": \"Avda de Madrid 34\",\n \"city\": \"Madrid\",\n \"postal_code\": \"28013\",\n \"country\": \"ES\"\n }\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_data(\n \"11111111-2222-3333-4444-555555555555\",\n date_of_birth=\"1980-01-01\",\n issuing_state=\"ESP\",\n first_name=\"Carmen\",\n last_name=\"Espanola Espanola\",\n marital_status=\"MARRIED\",\n extra_fields={\"blood_group\": \"O+\"},\n parsed_address={\n \"city\": \"Madrid\",\n \"street_1\": \"Avda de Madrid 34\",\n \"postal_code\": \"28013\",\n \"country\": \"ES\",\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateData(\"11111111-2222-3333-4444-555555555555\", {\n date_of_birth: \"1980-01-01\",\n issuing_state: \"ESP\",\n first_name: \"Carmen\",\n last_name: \"Espanola Espanola\",\n marital_status: \"MARRIED\",\n extra_fields: { blood_group: \"O+\" },\n parsed_address: {\n city: \"Madrid\",\n street_1: \"Avda de Madrid 34\",\n postal_code: \"28013\",\n country: \"ES\",\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateData(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsUpdateDataParams{\n\tDateOfBirth: didit.String(\"1980-01-01\"),\n\tIssuingState: didit.String(\"ESP\"),\n\tFirstName: didit.String(\"Carmen\"),\n\tLastName: didit.String(\"Espanola Espanola\"),\n\tMaritalStatus: didit.Some(didit.SessionsUpdateDataParamsMaritalStatus(\"MARRIED\")),\n\tExtraFields: didit.Some(map[string]any{\"blood_group\": \"O+\"}),\n\tParsedAddress: didit.Some(didit.SessionsUpdateDataParamsParsedAddress{\n\t\tCity: didit.String(\"Madrid\"),\n\t\tStreet1: didit.String(\"Avda de Madrid 34\"),\n\t\tPostalCode: didit.String(\"28013\"),\n\t\tCountry: didit.String(\"ES\"),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SessionsUpdateDataParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateData(\"11111111-2222-3333-4444-555555555555\", SessionsUpdateDataParams.builder()\n .dateOfBirth(LocalDate.parse(\"1980-01-01\"))\n .issuingState(\"ESP\")\n .firstName(\"Carmen\")\n .lastName(\"Espanola Espanola\")\n .maritalStatus(SessionsUpdateDataParams.MaritalStatus.of(\"MARRIED\"))\n .extraFields(Map.of(\"blood_group\", \"O+\"))\n .parsedAddress(SessionsUpdateDataParams.ParsedAddress.builder()\n .city(\"Madrid\")\n .street1(\"Avda de Madrid 34\")\n .postalCode(\"28013\")\n .country(\"ES\")\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateData('11111111-2222-3333-4444-555555555555', [\n 'date_of_birth' => '1980-01-01',\n 'issuing_state' => 'ESP',\n 'first_name' => 'Carmen',\n 'last_name' => 'Espanola Espanola',\n 'marital_status' => 'MARRIED',\n 'extra_fields' => ['blood_group' => 'O+'],\n 'parsed_address' => [\n 'city' => 'Madrid',\n 'street_1' => 'Avda de Madrid 34',\n 'postal_code' => '28013',\n 'country' => 'ES',\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_data(\n \"11111111-2222-3333-4444-555555555555\",\n date_of_birth: \"1980-01-01\",\n issuing_state: \"ESP\",\n first_name: \"Carmen\",\n last_name: \"Espanola Espanola\",\n marital_status: \"MARRIED\",\n extra_fields: { blood_group: \"O+\" },\n parsed_address: {\n city: \"Madrid\",\n street_1: \"Avda de Madrid 34\",\n postal_code: \"28013\",\n country: \"ES\",\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateDataAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsUpdateDataParams\n{\n DateOfBirth = \"1980-01-01\",\n IssuingState = \"ESP\",\n FirstName = \"Carmen\",\n LastName = \"Espanola Espanola\",\n MaritalStatus = new SessionsUpdateDataParamsMaritalStatus(\"MARRIED\"),\n ExtraFields = new Dictionary { [\"blood_group\"] = \"O+\" },\n ParsedAddress = new SessionsUpdateDataParamsParsedAddress\n {\n City = \"Madrid\",\n Street1 = \"Avda de Madrid 34\",\n PostalCode = \"28013\",\n Country = \"ES\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/features/{nodeId}/update-status/": { + "patch": { + "summary": "Update an individual feature (step) status", + "description": "Set the status of a single verification feature (step) within a session. Target the feature by its workflow graph `nodeId`. On non-graph workflows, whose steps have no node, use `default` and also pass `feature_type` to say which step you mean.\n\nWorks for both User Verification (KYC) and Business Verification (KYB) sessions and every step type: ID/OCR, Liveness, Face Match, NFC, AML, Proof of Address, Phone, Email, Database Validation, Questionnaire, and the KYB registry / documents / key-people checks. This endpoint is **not** available for standalone API sessions (`session_type = API`).\n\n`new_status` must be one of `Approved`, `Declined`, or `In Review`, and must differ from the feature's current status.\n\n**When the override applies.** Without `defer_until_flow_finishes` (or with `false`) the override applies at once, as it always did; when the person is still in the flow it is recorded as `forced`. With `defer_until_flow_finishes: true` while the person is still in the flow (the session is `Not Started`, `In Progress` or `Resubmitted` and has not finished), the override is stored as `pending_until_flow_finishes` and applies when the flow finishes, after the flow reaches its own decision. On a session whose flow already ended it applies at once either way. A waiting override is cancelled instead when the person redoes its step before finishing, when a newer override on the same check replaces it, when the session leaves the flow unfinished (abandoned, expired, or decided mid-flow), or when you cancel it with `POST /v3/session/{sessionId}/features/overrides/{overrideId}/cancel/`. List a session's overrides with `GET /v3/session/{sessionId}/features/overrides/`.\n\n**Effect on the session.** A session that already has a decision keeps it: only the check changes. When the session has no decision yet and its workflow is waiting on exactly this step, applying the override completes the step: the workflow moves on, later steps run, and a status node can set the session decision and fire `status.updated`.\n\n**Webhooks and activity.** A `data.updated` webhook fires when the override applies (at once, or when the flow finishes), never while it waits or when it is cancelled. Every override is recorded in the session's activity as `FEATURE_OVERRIDE_PENDING`, `FEATURE_OVERRIDE_APPLIED` or `FEATURE_OVERRIDE_CANCELLED`, with who made it; the request's entry carries your `comment`.\n\nAuthenticate with your API key (`x-api-key`) or a user authorization header carrying the `write:sessions` privilege; a scoped API key needs write access to decisions.", + "operationId": "patch_v3_session_update_feature_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_feature_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "in": "path", + "name": "nodeId", + "required": true, + "description": "Workflow graph node ID of the feature to update (for example `feature_ocr_1`). Use the literal value `default` for non-graph (single-step) workflows where the feature has no node ID.", + "schema": { + "type": "string", + "example": "feature_ocr_1" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "new_status" + ], + "properties": { + "new_status": { + "type": "string", + "description": "Target status for this feature. Must differ from the current value.", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "example": "Approved" + }, + "comment": { + "type": "string", + "description": "Optional free-text note recorded on the session's review trail for this feature change.", + "example": "Document manually verified by compliance." + }, + "feature_type": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "OCR", + "OCR_BACK", + "NFC", + "LIVENESS", + "FACE", + "AGE_ESTIMATION", + "AGE_VERIFICATION", + "FACE_SEARCH", + "FACE_MATCH", + "IP_ANALYSIS", + "AML", + "PHONE", + "PHONE_VERIFICATION", + "EMAIL", + "EMAIL_VERIFICATION", + "POA", + "PROOF_OF_ADDRESS", + "DATABASE_VALIDATION", + "DATA_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "BANK_VERIFICATION", + "GEOLOCATION" + ], + "description": "Which kind of step to target. Needed on non-graph workflows (`nodeId` = `default`), where every step shares no node; optional otherwise. An unknown value returns `400`.", + "example": "LIVENESS" + }, + "defer_until_flow_finishes": { + "type": "boolean", + "default": false, + "description": "While the person is still in the flow, store the override as `pending_until_flow_finishes` and apply it when the flow finishes, instead of at once. Omitted or `false`, the override applies at once. On a session whose flow already ended it applies at once either way.", + "example": false + } + } + }, + "examples": { + "Apply Now": { + "summary": "Approve the ID step at once", + "value": { + "new_status": "Approved", + "comment": "Document manually verified by compliance." + } + }, + "Wait For The Flow": { + "summary": "Approve the liveness step once the person finishes the flow", + "value": { + "new_status": "Approved", + "comment": "Liveness retried with a reviewer present.", + "defer_until_flow_finishes": true + } + }, + "Non-graph Workflow": { + "summary": "Target the liveness step of a workflow without nodes (nodeId = default)", + "value": { + "new_status": "In Review", + "feature_type": "LIVENESS" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The override was recorded. `new_status` is the check's status as stored: the requested one once the override applied, the unchanged one while it waits for the flow to finish. `override` says which happened.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid" + }, + "node_id": { + "type": "string", + "nullable": true, + "description": "The updated step's workflow node, or `null` for a step without a node (the `default` path in a non-graph workflow).", + "example": "feature_ocr_1" + }, + "feature_type": { + "type": "string", + "description": "Model name of the updated feature (e.g. `KYC`, `Face`, `FaceMatch`, `AML`, `POA`).", + "example": "KYC" + }, + "new_status": { + "type": "string", + "description": "The check's stored status: the requested one once the override applied, the unchanged one while the override waits for the flow to finish.", + "example": "Approved" + }, + "override": { + "$ref": "#/components/schemas/FeatureStatusOverride" + } + }, + "required": [ + "session_id", + "node_id", + "feature_type", + "new_status", + "override" + ] + }, + "examples": { + "Applied": { + "summary": "The override applied at once", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "node_id": "feature_ocr_1", + "feature_type": "KYC", + "new_status": "Approved", + "override": { + "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "node_id": "feature_ocr_1", + "feature_type": "", + "feature_model": "KYC", + "previous_status": "In Review", + "new_status": "Approved", + "state": "applied", + "forced": false, + "reason": "Document manually verified by compliance.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": "2026-10-13T09:15:00Z", + "cancelled_at": null, + "cancel_reason": null + } + } + }, + "Waiting For The Flow": { + "summary": "defer_until_flow_finishes while the person is still in the flow", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "node_id": "feature_liveness_1", + "feature_type": "Face", + "new_status": "Declined", + "override": { + "id": "9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3", + "node_id": "feature_liveness_1", + "feature_type": "", + "feature_model": "Face", + "previous_status": "Declined", + "new_status": "Approved", + "state": "pending_until_flow_finishes", + "forced": false, + "reason": "Liveness retried with a reviewer present.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": null, + "cancelled_at": null, + "cancel_reason": null + } + } + } + } + } + } + }, + "400": { + "description": "Validation error. Returned when `new_status` is invalid or unchanged, when `feature_type` is unknown, or when the session is a standalone API session.", + "content": { + "application/json": { + "examples": { + "Same Status": { + "summary": "new_status equals the current status", + "value": { + "new_status": [ + "The new status is the same as the current status." + ] + } + }, + "API Session": { + "summary": "Feature status cannot be changed on standalone API sessions", + "value": [ + "Cannot change feature status for API sessions." + ] + }, + "Unknown feature_type": { + "summary": "feature_type is not a known step type", + "value": { + "feature_type": [ + "Unknown feature_type 'SELFIE'." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "API key lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without write access to decisions", + "value": { + "detail": "This API key has no write access to decisions." + } + } + } + } + } + }, + "404": { + "description": "The session was not found, or it has no feature with the given `nodeId`.", + "content": { + "application/json": { + "examples": { + "Session Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Session not found." + } + }, + "Feature Not Found": { + "summary": "No feature for this node_id", + "value": { + "detail": "No feature found with node_id 'feature_ocr_1' in this session." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/features/feature_ocr_1/update-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"new_status\": \"Approved\",\n \"comment\": \"Document manually verified by compliance.\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_feature_status(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_ocr_1\",\n new_status=\"Approved\",\n comment=\"Document manually verified by compliance.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateFeatureStatus(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_ocr_1\",\n {\n new_status: \"Approved\",\n comment: \"Document manually verified by compliance.\",\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateFeatureStatus(ctx, \"11111111-2222-3333-4444-555555555555\", \"feature_ocr_1\", didit.SessionsUpdateFeatureStatusParams{\n\tNewStatus: \"Approved\",\n\tComment: didit.String(\"Document manually verified by compliance.\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsUpdateFeatureStatusParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateFeatureStatus(\"11111111-2222-3333-4444-555555555555\", \"feature_ocr_1\", SessionsUpdateFeatureStatusParams.builder()\n .newStatus(SessionsUpdateFeatureStatusParams.NewStatus.of(\"Approved\"))\n .comment(\"Document manually verified by compliance.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateFeatureStatus('11111111-2222-3333-4444-555555555555', 'feature_ocr_1', [\n 'new_status' => 'Approved',\n 'comment' => 'Document manually verified by compliance.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_feature_status(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_ocr_1\",\n new_status: \"Approved\",\n comment: \"Document manually verified by compliance.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateFeatureStatusAsync(\"11111111-2222-3333-4444-555555555555\", \"feature_ocr_1\", new SessionsUpdateFeatureStatusParams\n{\n NewStatus = \"Approved\",\n Comment = \"Document manually verified by compliance.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/update-aml-hit-status/": { + "patch": { + "summary": "Update an AML hit review status", + "description": "Set the review status of a **single AML hit** (a potential sanction / PEP / watchlist / adverse-media match) returned by AML screening, without changing the overall session decision. Identify the hit by its `hit_id` (the `id` of an entry in the AML check's `hits` array — see the session decision). A hit id returned in the earlier format is still accepted; the response always names the hit by its current `id`.\n\n`review_status` is one of `Unreviewed`, `Confirmed Match`, `False Positive`, or `Inconclusive`. Reviewer decisions are preserved across ongoing-monitoring updates, so a hit you mark `False Positive` stays that way when the provider re-screens.\n\nA successful call fires a `data.updated` webhook and appends an activity entry (with actor attribution) to the session's review trail. For graph workflows with multiple AML checks, pass `node_id` to target a specific check.\n\nAuthenticate with your API key (`x-api-key`) or a user authorization header — either must carry the `write:sessions` privilege.", + "operationId": "patch_v3_session_update_aml_hit_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_aml_hit_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "in": "query", + "name": "node_id", + "required": false, + "description": "Workflow graph node ID. Required only when the session has multiple AML checks (one per node); omit it for single-AML sessions.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "hit_id", + "review_status" + ], + "properties": { + "hit_id": { + "type": "string", + "description": "The `id` of the hit in the AML check's `hits` array (`hit_...`). A hit id returned in the earlier format is still accepted.", + "example": "hit_7c1e9a40b2d35f86e0a4c912" + }, + "review_status": { + "type": "string", + "description": "The new review status for the hit.", + "enum": [ + "Unreviewed", + "Confirmed Match", + "False Positive", + "Inconclusive" + ], + "example": "False Positive" + }, + "node_id": { + "type": "string", + "description": "Optional graph node ID. Use it (here or as a query parameter) when the session has multiple AML checks.", + "example": "feature_aml_1" + } + } + }, + "example": { + "hit_id": "hit_7c1e9a40b2d35f86e0a4c912", + "review_status": "False Positive" + } + } + } + }, + "responses": { + "200": { + "description": "Hit review status updated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message", + "hit_id", + "review_status" + ], + "properties": { + "message": { + "type": "string", + "description": "Always `Hit status updated successfully`." + }, + "hit_id": { + "type": "string", + "description": "The `id` of the updated hit, as the session decision returns it, also when the request named the hit by an id in the earlier format." + }, + "review_status": { + "type": "string", + "enum": [ + "Unreviewed", + "Confirmed Match", + "False Positive", + "Inconclusive" + ], + "description": "The review status now stored on the hit." + } + } + }, + "example": { + "message": "Hit status updated successfully", + "hit_id": "hit_7c1e9a40b2d35f86e0a4c912", + "review_status": "False Positive" + } + } + } + }, + "400": { + "description": "Validation error — `hit_id` empty or `review_status` not one of the allowed values.", + "content": { + "application/json": { + "example": { + "review_status": [ + "\"Maybe\" is not a valid choice." + ] + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "API key lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "The session was not found, the session has no AML check (optionally for the given `node_id`), or no hit matches `hit_id`.", + "content": { + "application/json": { + "examples": { + "No AML Check": { + "summary": "Session has no AML check", + "value": { + "detail": "The session does not have an AML check." + } + }, + "Hit Not Found": { + "summary": "Unknown hit_id", + "value": { + "detail": "Hit with ID 'hit_9d2e7f41c0a8b3e65d1f0c47' not found in the AML check." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-aml-hit-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"hit_id\": \"abc123\",\n \"review_status\": \"False Positive\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_aml_hit_status(\n \"11111111-2222-3333-4444-555555555555\",\n hit_id=\"hit_7c1e9a40b2d35f86e0a4c912\",\n review_status=\"False Positive\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateAmlHitStatus(\"11111111-2222-3333-4444-555555555555\", {\n hit_id: \"hit_7c1e9a40b2d35f86e0a4c912\",\n review_status: \"False Positive\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateAMLHitStatus(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsUpdateAMLHitStatusParams{\n\tHitID: \"hit_7c1e9a40b2d35f86e0a4c912\",\n\tReviewStatus: \"False Positive\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsUpdateAmlHitStatusParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateAmlHitStatus(\"11111111-2222-3333-4444-555555555555\", SessionsUpdateAmlHitStatusParams.builder()\n .hitId(\"hit_7c1e9a40b2d35f86e0a4c912\")\n .reviewStatus(SessionsUpdateAmlHitStatusParams.ReviewStatus.of(\"False Positive\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateAmlHitStatus('11111111-2222-3333-4444-555555555555', [\n 'hit_id' => 'hit_7c1e9a40b2d35f86e0a4c912',\n 'review_status' => 'False Positive',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_aml_hit_status(\n \"11111111-2222-3333-4444-555555555555\",\n hit_id: \"hit_7c1e9a40b2d35f86e0a4c912\",\n review_status: \"False Positive\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateAmlHitStatusAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsUpdateAmlHitStatusParams\n{\n HitId = \"hit_7c1e9a40b2d35f86e0a4c912\",\n ReviewStatus = \"False Positive\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/bulk-update-aml-hit-status/": { + "patch": { + "summary": "Bulk update AML hit review statuses", + "description": "Update the review status of **multiple AML hits** in one call. Provide `hit_updates`, a list of `{ hit_id, review_status }` pairs. `review_status` is one of `Unreviewed`, `Confirmed Match`, `False Positive`, or `Inconclusive`.\n\nThe update is all-or-nothing: if any `hit_id` is not found in the AML check, the request returns `404` and no hit is changed. A successful call fires a single `data.updated` webhook and records one activity entry covering all changes. For graph workflows with multiple AML checks, pass `node_id` to target a specific check.\n\nAuthenticate with your API key (`x-api-key`) or a user authorization header — either must carry the `write:sessions` privilege.", + "operationId": "patch_v3_session_bulk_update_aml_hit_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "bulk_update_aml_hit_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "in": "query", + "name": "node_id", + "required": false, + "description": "Workflow graph node ID. Required only when the session has multiple AML checks (one per node); omit it for single-AML sessions.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "hit_updates" + ], + "properties": { + "hit_updates": { + "type": "array", + "description": "Non-empty list of hit updates.", + "items": { + "type": "object", + "required": [ + "hit_id", + "review_status" + ], + "properties": { + "hit_id": { + "type": "string", + "example": "hit_7c1e9a40b2d35f86e0a4c912", + "description": "The `id` of the hit in the AML check's `hits` array (`hit_...`). A hit id returned in the earlier format is still accepted." + }, + "review_status": { + "type": "string", + "enum": [ + "Unreviewed", + "Confirmed Match", + "False Positive", + "Inconclusive" + ], + "example": "Confirmed Match" + } + } + } + }, + "node_id": { + "type": "string", + "description": "Optional graph node ID. Use it (here or as a query parameter) when the session has multiple AML checks.", + "example": "feature_aml_1" + } + } + }, + "example": { + "hit_updates": [ + { + "hit_id": "hit_7c1e9a40b2d35f86e0a4c912", + "review_status": "Confirmed Match" + }, + { + "hit_id": "hit_3b8f20d6a91c4e75f0b2d6a8", + "review_status": "False Positive" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "All hits updated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message", + "updated_hits" + ], + "properties": { + "message": { + "type": "string", + "description": "`Updated {n} hit(s) successfully`, where `n` is the number of entries in `hit_updates`." + }, + "updated_hits": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The `id` of the hit each entry in `hit_updates` names, as the session decision returns it, in request order, including hits whose status did not change." + } + } + }, + "example": { + "message": "Updated 2 hit(s) successfully", + "updated_hits": [ + "hit_7c1e9a40b2d35f86e0a4c912", + "hit_3b8f20d6a91c4e75f0b2d6a8" + ] + } + } + } + }, + "400": { + "description": "Validation error — `hit_updates` empty, or an entry is missing `hit_id` / `review_status` or carries an invalid value.", + "content": { + "application/json": { + "example": { + "hit_updates": [ + "hit_updates cannot be empty" + ] + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "API key lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "The session was not found, the session has no AML check, or one or more `hit_id`s do not exist (no hit is changed in that case).", + "content": { + "application/json": { + "example": { + "detail": "Hits not found: hit_9d2e7f41c0a8b3e65d1f0c47" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/bulk-update-aml-hit-status/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"hit_updates\": [\n { \"hit_id\": \"abc123\", \"review_status\": \"Confirmed Match\" },\n { \"hit_id\": \"def456\", \"review_status\": \"False Positive\" }\n ]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.bulk_update_aml_hit_status(\n \"11111111-2222-3333-4444-555555555555\",\n hit_updates=[{\n \"hit_id\": \"hit_7c1e9a40b2d35f86e0a4c912\",\n \"review_status\": \"Confirmed Match\",\n }, {\n \"hit_id\": \"hit_3b8f20d6a91c4e75f0b2d6a8\",\n \"review_status\": \"False Positive\",\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.bulkUpdateAmlHitStatus(\"11111111-2222-3333-4444-555555555555\", {\n hit_updates: [{\n hit_id: \"hit_7c1e9a40b2d35f86e0a4c912\",\n review_status: \"Confirmed Match\",\n }, {\n hit_id: \"hit_3b8f20d6a91c4e75f0b2d6a8\",\n review_status: \"False Positive\",\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.BulkUpdateAMLHitStatus(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsBulkUpdateAMLHitStatusParams{\n\tHitUpdates: []didit.SessionsBulkUpdateAMLHitStatusParamsHitUpdates{{\n\t\tHitID: \"hit_7c1e9a40b2d35f86e0a4c912\",\n\t\tReviewStatus: \"Confirmed Match\",\n\t}, {\n\t\tHitID: \"hit_3b8f20d6a91c4e75f0b2d6a8\",\n\t\tReviewStatus: \"False Positive\",\n\t}},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.SessionsBulkUpdateAmlHitStatusParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().bulkUpdateAmlHitStatus(\"11111111-2222-3333-4444-555555555555\", SessionsBulkUpdateAmlHitStatusParams.builder()\n .hitUpdates(List.of(\n SessionsBulkUpdateAmlHitStatusParams.HitUpdates.builder()\n .hitId(\"hit_7c1e9a40b2d35f86e0a4c912\")\n .reviewStatus(SessionsBulkUpdateAmlHitStatusParams.HitUpdates.ReviewStatus.of(\"Confirmed Match\"))\n .build(),\n SessionsBulkUpdateAmlHitStatusParams.HitUpdates.builder()\n .hitId(\"hit_3b8f20d6a91c4e75f0b2d6a8\")\n .reviewStatus(SessionsBulkUpdateAmlHitStatusParams.HitUpdates.ReviewStatus.of(\"False Positive\"))\n .build()))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->bulkUpdateAmlHitStatus('11111111-2222-3333-4444-555555555555', [\n 'hit_updates' => [[\n 'hit_id' => 'hit_7c1e9a40b2d35f86e0a4c912',\n 'review_status' => 'Confirmed Match',\n ], [\n 'hit_id' => 'hit_3b8f20d6a91c4e75f0b2d6a8',\n 'review_status' => 'False Positive',\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.bulk_update_aml_hit_status(\n \"11111111-2222-3333-4444-555555555555\",\n hit_updates: [{\n hit_id: \"hit_7c1e9a40b2d35f86e0a4c912\",\n review_status: \"Confirmed Match\",\n }, {\n hit_id: \"hit_3b8f20d6a91c4e75f0b2d6a8\",\n review_status: \"False Positive\",\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.BulkUpdateAmlHitStatusAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsBulkUpdateAmlHitStatusParams\n{\n HitUpdates = new SessionsBulkUpdateAmlHitStatusParamsHitUpdates[]\n {\n new()\n {\n HitId = \"hit_7c1e9a40b2d35f86e0a4c912\",\n ReviewStatus = \"Confirmed Match\",\n },\n new()\n {\n HitId = \"hit_3b8f20d6a91c4e75f0b2d6a8\",\n ReviewStatus = \"False Positive\",\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/update-poa-data/": { + "patch": { + "summary": "Update Proof of Address data extracted from the POA document on a verification session", + "description": "Correct the fields extracted from the user's Proof of Address document — utility bill, bank statement, or other address evidence — after the verification has finished: issuer, dates, the printed name, the address itself, and banking details. Every field in the body is optional: only the keys you send are written, everything else is left untouched (`PATCH` semantics).\n\nThe session must belong to your application and be in `Approved`, `Declined`, `In Review`, or `Kyc Expired` status. Sessions in any other state (e.g. still `In Progress`) return `404` with `\"No Session matches the given query.\"` — distinct from the `\"Not found.\"` returned for an unknown or deleted `sessionId`. The session must also carry a POA record: sessions whose workflow never ran a Proof of Address step return `404` (`\"The session does not have a POA.\"`). For graph workflows with multiple POA steps, target a specific step with `node_id` (query parameter or body field); when omitted, the most recent POA record is patched.\n\n`extra_fields` is merged key-by-key: nine known keys (`bank_iban`, `additional_names`, …) write through to dedicated POA columns, any other key is kept as a custom extra, and `null` / `\"\"` deletes a key. `poa_parsed_address` accepts only the seven documented keys and recomputes `formatted_address` / `poa_formatted_address` after every edit. If the free-text `poa_address` changes, the server may also re-geocode it and refresh the structured address (skipped for sandbox sessions).\n\nUnlike the KYC variant of this endpoint, the `data.updated` [webhook](/integration/webhooks) (trigger `manual_review`) fires only when at least one stored value **actually changed** — re-sending identical values is a silent no-op. When values change, a `POA_DATA_UPDATED` activity entry recording the changed fields with previous and new values is appended to the session's `reviews`, visible in [Get Decision](#get-/v3/session/-sessionId-/decision/) and the console activity timeline. The session's status and decision are **not** re-evaluated — use [Update Status](#patch-/v3/session/-sessionId-/update-status/) to change the outcome.\n\nAccepts an API key or a console user token; either way the credential needs the `write:sessions` permission. Authentication and permission failures both return `403` — this API never responds `401`. Note that the session lookup runs before credential checks, so an unknown `sessionId` returns `404` even with missing credentials. This endpoint has a dedicated rate limit of 10 requests per minute per API key (invalid requests count too); the shared write budget of 300 requests per minute also applies.\n\nAn empty JSON body `{}` is a no-op: it returns `200` echoing the current POA record without firing the webhook (`has_changes` stays false).", + "operationId": "patch_v3_session_update_poa_data", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_poa_data", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session whose attached POA record you are correcting.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node whose POA record to patch, for graph workflows with multiple Proof of Address steps (also accepted as a `node_id` body field). When omitted, the most recent POA record on the session is patched. If no POA exists for the given node, the call returns `404` (`\"The session does not have a POA for node ''.\"`).", + "schema": { + "type": "string", + "example": "feature_poa" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "All fields optional — send only what you want to change.", + "properties": { + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Country that issued the POA document, ISO 3166-1 alpha-3. Case-insensitive (normalized to uppercase); invalid codes return `400`.", + "example": "ESP" + }, + "document_type": { + "type": "string", + "enum": [ + "UTILITY_BILL", + "BANK_STATEMENT", + "GOVERNMENT_ISSUED_DOCUMENT", + "OTHER_POA_DOCUMENT", + "UNKNOWN" + ], + "description": "POA document category. Values are uppercase — lowercase variants like `utility_bill` return `400`.", + "example": "UTILITY_BILL" + }, + "document_language": { + "type": "string", + "maxLength": 50, + "nullable": true, + "description": "Language of the document, typically an ISO 639-1 code (e.g. `es`). Free text — not validated as a language code.", + "example": "es" + }, + "issuer": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Organization that issued the document (utility company, bank, government agency).", + "example": "Energy Provider S.A." + }, + "issue_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Issue date of the POA document in `YYYY-MM-DD`. Any other format returns `400`.", + "example": "2026-04-12" + }, + "poa_address": { + "type": "string", + "nullable": true, + "description": "Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it in the background of the same request and may overwrite `poa_parsed_address` / `poa_formatted_address` with the geocoder result. The re-geocode also requires `issuing_state` to be present on the POA record.", + "example": "Avda de Madrid 34, 28013 Madrid" + }, + "name_on_document": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Recipient name as printed on the document.", + "example": "Carmen Espanola" + }, + "extra_fields": { + "type": "object", + "additionalProperties": true, + "nullable": true, + "description": "Document-specific extras, merged key-by-key. Nine keys map to dedicated POA columns: `bank_account_number`, `bank_iban`, `bank_sort_code`, `bank_routing_number`, `bank_swift_bic`, `bank_branch_name`, `bank_branch_address`, `document_phone_number`, and `additional_names` (must be a list of strings or `null`, otherwise `400`). Any other key is stored as a custom extra field. Per-key semantics: a value sets the key; `null` or `\"\"` deletes it. Must be a JSON object, otherwise `400`." + }, + "poa_parsed_address": { + "type": "object", + "nullable": true, + "description": "Structured address. Only these nested keys are accepted: `address_type`, `city`, `region`, `street_1`, `street_2`, `postal_code`, `country` — any other key returns `400`. Per-key semantics: a value sets the key, `null` removes it. `formatted_address` (and the top-level `poa_formatted_address`) is recomputed server-side by joining `street_1`, `street_2`, `city`, `region`, `postal_code`, and `country` — you cannot set it directly.", + "properties": { + "address_type": { + "type": "string", + "nullable": true, + "description": "Free-form address classification (e.g. `home`)." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Madrid" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State / province / autonomous community." + }, + "street_1": { + "type": "string", + "nullable": true, + "example": "Avda de Madrid 34" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "28013" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`.", + "example": "ES" + } + } + } + } + }, + "examples": { + "Correct issuer and address": { + "summary": "Fix document classification, issuer, and the structured address", + "value": { + "issuing_state": "ESP", + "document_type": "BANK_STATEMENT", + "issuer": "Banco Example S.A.", + "issue_date": "2026-05-02", + "name_on_document": "Carmen Espanola", + "poa_address": "Avda de Madrid 34, 28013 Madrid", + "extra_fields": { + "bank_iban": "ES9121000418450200051332", + "additional_names": [ + "Carmen E. Espanola" + ] + }, + "poa_parsed_address": { + "street_1": "Avda de Madrid 34", + "city": "Madrid", + "postal_code": "28013", + "country": "ES" + } + } + }, + "Remove a banking detail": { + "summary": "Delete one extra-field key, keep the rest", + "value": { + "extra_fields": { + "bank_iban": null + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "POA data updated. Returns the **full current POA record** (every updatable field with its stored value, plus the complete `extra_fields` map, `poa_parsed_address`, and `poa_formatted_address`) — not just the fields you submitted. The `data.updated` webhook fires only when at least one value actually changed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "issuing_state": { + "type": "string", + "nullable": true, + "description": "Country that issued the POA document, ISO 3166-1 alpha-3. Case-insensitive (normalized to uppercase); invalid codes return `400`.", + "example": "ESP" + }, + "document_type": { + "type": "string", + "enum": [ + "UTILITY_BILL", + "BANK_STATEMENT", + "GOVERNMENT_ISSUED_DOCUMENT", + "OTHER_POA_DOCUMENT", + "UNKNOWN" + ], + "description": "POA document category. Values are uppercase — lowercase variants like `utility_bill` return `400`.", + "example": "UTILITY_BILL" + }, + "document_language": { + "type": "string", + "maxLength": 50, + "nullable": true, + "description": "Language of the document, typically an ISO 639-1 code (e.g. `es`). Free text — not validated as a language code.", + "example": "es" + }, + "issuer": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Organization that issued the document (utility company, bank, government agency).", + "example": "Energy Provider S.A." + }, + "issue_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Issue date of the POA document in `YYYY-MM-DD`. Any other format returns `400`.", + "example": "2026-04-12" + }, + "poa_address": { + "type": "string", + "nullable": true, + "description": "Free-text address as printed on the document. When this value changes (non-sandbox sessions only) and the structured address is still missing or incomplete, the server re-geocodes it in the background of the same request and may overwrite `poa_parsed_address` / `poa_formatted_address` with the geocoder result.", + "example": "Avda de Madrid 34, 28013 Madrid" + }, + "name_on_document": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "Recipient name as printed on the document.", + "example": "Carmen Espanola" + }, + "extra_fields": { + "type": "object", + "additionalProperties": true, + "nullable": true, + "description": "Always present in the response: the nine known keys (with `null` for unset ones, `additional_names` defaulting to `[]`) plus any custom keys previously stored." + }, + "poa_parsed_address": { + "type": "object", + "nullable": true, + "description": "Current structured address, or `null` when none exists. Includes the recomputed `formatted_address` alongside the seven editable keys. Returned as stored — after a server-side geocode it can contain bookkeeping keys (e.g. `raw_results`) alongside the editable keys.", + "properties": { + "address_type": { + "type": "string", + "nullable": true, + "description": "Free-form address classification (e.g. `home`)." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Madrid" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State / province / autonomous community." + }, + "street_1": { + "type": "string", + "nullable": true, + "example": "Avda de Madrid 34" + }, + "street_2": { + "type": "string", + "nullable": true + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "28013" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code. Lowercase input is normalized to uppercase; alpha-3 codes are rejected with `400`.", + "example": "ES" + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "Joined, human-readable form of the structured address. Recomputed server-side after every `poa_parsed_address` edit.", + "example": "Avda de Madrid 34, Madrid, 28013, ES" + } + } + }, + "poa_formatted_address": { + "type": "string", + "nullable": true, + "description": "Top-level copy of the recomputed `formatted_address`, kept in sync when the structured address changes.", + "example": "Avda de Madrid 34, Madrid, 28013, ES" + } + } + }, + "examples": { + "Updated POA": { + "summary": "Full POA record after the correction", + "value": { + "issuing_state": "ESP", + "document_type": "BANK_STATEMENT", + "document_language": "es", + "issuer": "Banco Example S.A.", + "issue_date": "2026-05-02", + "poa_address": "Avda de Madrid 34, 28013 Madrid", + "name_on_document": "Carmen Espanola", + "extra_fields": { + "bank_account_number": null, + "bank_iban": "ES9121000418450200051332", + "bank_sort_code": null, + "bank_routing_number": null, + "bank_swift_bic": null, + "bank_branch_name": null, + "bank_branch_address": null, + "document_phone_number": null, + "additional_names": [ + "Carmen E. Espanola" + ] + }, + "poa_parsed_address": { + "city": "Madrid", + "street_1": "Avda de Madrid 34", + "postal_code": "28013", + "country": "ES", + "formatted_address": "Avda de Madrid 34, Madrid, 28013, ES" + }, + "poa_formatted_address": "Avda de Madrid 34, Madrid, 28013, ES" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field errors arrive as `{\"\": [\"\"]}`; errors on nested `poa_parsed_address` / `extra_fields` keys use an object value: `{\"extra_fields\": {\"\": \"\"}}`.", + "content": { + "application/json": { + "examples": { + "Invalid document type": { + "summary": "Enum is uppercase — lowercase variants are rejected", + "value": { + "document_type": [ + "\"utility_bill\" is not a valid choice." + ] + } + }, + "Invalid issuing state": { + "summary": "issuing_state is not ISO 3166-1 alpha-3", + "value": { + "issuing_state": [ + "Invalid ISO 3166-1 alpha-3 country code for issuing_state." + ] + } + }, + "additional_names not a list": { + "summary": "additional_names accepts a list of strings or null", + "value": { + "extra_fields": { + "additional_names": "Must be a list or null." + } + } + }, + "Unsupported poa_parsed_address key": { + "summary": "Only the seven documented keys are allowed", + "value": { + "poa_parsed_address": { + "zipcode": "Unsupported key. Allowed keys: address_type, city, region, street_1, street_2, postal_code, country" + } + } + }, + "Bad date format": { + "summary": "Dates must be YYYY-MM-DD", + "value": { + "issue_date": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Credential lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown session, session in a non-editable state, or no POA record to patch. Each case has a distinct `detail` message (see examples). The unknown-session lookup runs before credential checks.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "summary": "No session with this ID in your application (or it was deleted)", + "value": { + "detail": "Not found." + } + }, + "Wrong session state": { + "summary": "Session exists but is not Approved / Declined / In Review / Kyc Expired", + "value": { + "detail": "No Session matches the given query." + } + }, + "No POA record": { + "summary": "The workflow never ran a Proof of Address step", + "value": { + "detail": "The session does not have a POA." + } + }, + "No POA for node": { + "summary": "node_id given but no POA exists for it", + "value": { + "detail": "The session does not have a POA for node 'feature_missing'." + } + } + } + } + } + }, + "429": { + "description": "Dedicated rate limit exceeded — 10 POA data updates per minute per API key (rejected requests count toward the budget). The shared 300/min write budget can also trigger with its own message. Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "POA update budget exhausted", + "value": { + "detail": "Session POA data update rate limit exceeded. You can make up to 10 requests per minute." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-poa-data/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"issuing_state\": \"ESP\",\n \"document_type\": \"BANK_STATEMENT\",\n \"issuer\": \"Banco Example S.A.\",\n \"issue_date\": \"2026-05-02\",\n \"name_on_document\": \"Carmen Espanola\",\n \"poa_address\": \"Avda de Madrid 34, 28013 Madrid\",\n \"extra_fields\": {\n \"bank_iban\": \"ES9121000418450200051332\"\n },\n \"poa_parsed_address\": {\n \"city\": \"Madrid\",\n \"postal_code\": \"28013\",\n \"country\": \"ES\"\n }\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_poa_data(\n \"11111111-2222-3333-4444-555555555555\",\n issuing_state=\"ESP\",\n document_type=\"BANK_STATEMENT\",\n issuer=\"Banco Example S.A.\",\n issue_date=\"2026-05-02\",\n poa_address=\"Avda de Madrid 34, 28013 Madrid\",\n name_on_document=\"Carmen Espanola\",\n extra_fields={\"bank_iban\": \"ES9121000418450200051332\", \"additional_names\": [\"Carmen E. Espanola\"]},\n poa_parsed_address={\n \"city\": \"Madrid\",\n \"street_1\": \"Avda de Madrid 34\",\n \"postal_code\": \"28013\",\n \"country\": \"ES\",\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updatePoaData(\"11111111-2222-3333-4444-555555555555\", {\n issuing_state: \"ESP\",\n document_type: \"BANK_STATEMENT\",\n issuer: \"Banco Example S.A.\",\n issue_date: \"2026-05-02\",\n poa_address: \"Avda de Madrid 34, 28013 Madrid\",\n name_on_document: \"Carmen Espanola\",\n extra_fields: { bank_iban: \"ES9121000418450200051332\", additional_names: [\"Carmen E. Espanola\"] },\n poa_parsed_address: {\n city: \"Madrid\",\n street_1: \"Avda de Madrid 34\",\n postal_code: \"28013\",\n country: \"ES\",\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdatePOAData(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsUpdatePOADataParams{\n\tIssuingState: didit.String(\"ESP\"),\n\tDocumentType: didit.Some(didit.SessionsUpdatePOADataParamsDocumentType(\"BANK_STATEMENT\")),\n\tIssuer: didit.String(\"Banco Example S.A.\"),\n\tIssueDate: didit.String(\"2026-05-02\"),\n\tPOAAddress: didit.String(\"Avda de Madrid 34, 28013 Madrid\"),\n\tNameOnDocument: didit.String(\"Carmen Espanola\"),\n\tExtraFields: didit.Some(map[string]any{\"bank_iban\": \"ES9121000418450200051332\", \"additional_names\": []any{\"Carmen E. Espanola\"}}),\n\tPOAParsedAddress: didit.Some(didit.SessionsUpdatePOADataParamsPOAParsedAddress{\n\t\tCity: didit.String(\"Madrid\"),\n\t\tStreet1: didit.String(\"Avda de Madrid 34\"),\n\t\tPostalCode: didit.String(\"28013\"),\n\t\tCountry: didit.String(\"ES\"),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SessionsUpdatePoaDataParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updatePoaData(\"11111111-2222-3333-4444-555555555555\", SessionsUpdatePoaDataParams.builder()\n .issuingState(\"ESP\")\n .documentType(SessionsUpdatePoaDataParams.DocumentType.of(\"BANK_STATEMENT\"))\n .issuer(\"Banco Example S.A.\")\n .issueDate(LocalDate.parse(\"2026-05-02\"))\n .poaAddress(\"Avda de Madrid 34, 28013 Madrid\")\n .nameOnDocument(\"Carmen Espanola\")\n .extraFields(Map.of(\"bank_iban\", \"ES9121000418450200051332\", \"additional_names\", List.of(\"Carmen E. Espanola\")))\n .poaParsedAddress(SessionsUpdatePoaDataParams.PoaParsedAddress.builder()\n .city(\"Madrid\")\n .street1(\"Avda de Madrid 34\")\n .postalCode(\"28013\")\n .country(\"ES\")\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updatePoaData('11111111-2222-3333-4444-555555555555', [\n 'issuing_state' => 'ESP',\n 'document_type' => 'BANK_STATEMENT',\n 'issuer' => 'Banco Example S.A.',\n 'issue_date' => '2026-05-02',\n 'poa_address' => 'Avda de Madrid 34, 28013 Madrid',\n 'name_on_document' => 'Carmen Espanola',\n 'extra_fields' => ['bank_iban' => 'ES9121000418450200051332', 'additional_names' => ['Carmen E. Espanola']],\n 'poa_parsed_address' => [\n 'city' => 'Madrid',\n 'street_1' => 'Avda de Madrid 34',\n 'postal_code' => '28013',\n 'country' => 'ES',\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_poa_data(\n \"11111111-2222-3333-4444-555555555555\",\n issuing_state: \"ESP\",\n document_type: \"BANK_STATEMENT\",\n issuer: \"Banco Example S.A.\",\n issue_date: \"2026-05-02\",\n poa_address: \"Avda de Madrid 34, 28013 Madrid\",\n name_on_document: \"Carmen Espanola\",\n extra_fields: { bank_iban: \"ES9121000418450200051332\", additional_names: [\"Carmen E. Espanola\"] },\n poa_parsed_address: {\n city: \"Madrid\",\n street_1: \"Avda de Madrid 34\",\n postal_code: \"28013\",\n country: \"ES\",\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdatePoaDataAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsUpdatePoaDataParams\n{\n IssuingState = \"ESP\",\n DocumentType = new SessionsUpdatePoaDataParamsDocumentType(\"BANK_STATEMENT\"),\n Issuer = \"Banco Example S.A.\",\n IssueDate = \"2026-05-02\",\n PoaAddress = \"Avda de Madrid 34, 28013 Madrid\",\n NameOnDocument = \"Carmen Espanola\",\n ExtraFields = new Dictionary { [\"bank_iban\"] = \"ES9121000418450200051332\", [\"additional_names\"] = new object[] { \"Carmen E. Espanola\" } },\n PoaParsedAddress = new SessionsUpdatePoaDataParamsPoaParsedAddress\n {\n City = \"Madrid\",\n Street1 = \"Avda de Madrid 34\",\n PostalCode = \"28013\",\n Country = \"ES\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/{companyUuid}/update-data/": { + "patch": { + "summary": "Update registry-extracted company data on a Business Verification (KYB) session", + "description": "Correct the registry-extracted company data on a Business Verification (KYB) session — company name, registration number, addresses, status, contact details — identified by `companyUuid`. Typical callers are a compliance analyst fixing a registry misread, or your back office syncing corrected company data. Every field in the body is optional (`PATCH` semantics); an empty JSON body (`{}`) is a no-op that simply echoes the current company record.\n\nEdits are written in two places at once: fields with a canonical company column (name, registration number, country, incorporation date, tax number, type, statuses, capital fields (`registered_capital`, `registered_capital_amount`, `registered_capital_currency`), alternative names, nature of business, website, email, phone, LEI, location, and `legal_address` → `registered_address`) update that column, and **every** submitted key is also merged into the `registry_data` snapshot (dates as ISO strings) so the \"extracted from registry\" view reflects the correction. Fields without a column — `region`, `registration_date`, `dissolution_date`, `control_scheme` — land in `registry_data` only. `user_provided_data` (what the end user confirmed in the hosted flow) is never touched, and `last_console_edit_at` is stamped on every non-empty PATCH.\n\nUnlike the KYC/POA update endpoints, there is **no session-status gate**: the company can be edited at any point in the business session's lifecycle. The company must belong to the session in the path (`404` with `\"KYB company does not belong to the given session.\"` otherwise) and to your application. Every non-empty PATCH fires the `data.updated` [webhook](/integration/webhooks) on the business session (trigger `console_registry_edit`) — even when the submitted values equal the stored ones. The session's status and decision are **not** re-evaluated, and no review/audit entry is recorded.\n\nAccepts an API key or a console user token; either way the credential needs the `write:businesses` permission. Authentication and permission failures both return `403` — this API never responds `401`. Note that the session lookup runs before credential checks, so an unknown `sessionId` returns `404` even with missing credentials. The shared write budget of 300 requests per minute per API key applies.\n\nFor User Verification sessions, the KYC counterpart is `PATCH /v3/session/{sessionId}/update-data/`; to change the session decision itself use `PATCH /v3/session/{sessionId}/update-status/`.", + "operationId": "patch_v3_session_kyb_company_update_data", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_kyb_company_data", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the parent Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "companyUuid", + "in": "path", + "required": true, + "description": "UUID of the company record to update (`registry_checks[].company.uuid` on the business session decision). Must belong to the session referenced by `sessionId`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "88888888-9999-aaaa-bbbb-cccccccccccc" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "All fields optional — send only what you want to change. Provided fields update the canonical company columns (where one exists) and are merged into the `registry_data` snapshot.", + "properties": { + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Legal name of the company. Written to the canonical column and the registry snapshot.", + "example": "Docs Probe Corp" + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Official registration number with the company registry.", + "example": "DPC-001" + }, + "country_code": { + "type": "string", + "maxLength": 2, + "description": "Country of incorporation, ISO 3166-1 alpha-2. Case-insensitive (`us` is stored as `US`). Alpha-3 codes fail the 2-character limit with `400`.", + "example": "US" + }, + "region": { + "type": "string", + "maxLength": 10, + "nullable": true, + "description": "Subdivision / region tag where the registry has one (e.g. US state). Stored in the registry snapshot only — it has no canonical column." + }, + "incorporation_date": { + "type": "string", + "format": "date", + "description": "Incorporation date in `YYYY-MM-DD`. Optional like every other field — the `PATCH` is partial. Any other date format returns `400`.", + "example": "2015-04-12" + }, + "registration_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Registration date where the registry distinguishes it from incorporation. Stored in the registry snapshot only." + }, + "dissolution_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Dissolution date for companies that are no longer active. Stored in the registry snapshot only." + }, + "tax_number": { + "type": "string", + "maxLength": 100, + "description": "Tax identification number." + }, + "legal_address": { + "type": "string", + "description": "Legal / registered address. Aliased server-side to the canonical `registered_address` column — the response returns it under `registered_address`, while the registry snapshot keeps the `legal_address` key you sent.", + "example": "1 Example Plaza, Wilmington, DE 19801" + }, + "company_type": { + "type": "string", + "maxLength": 100, + "description": "Legal entity type (e.g. `Limited Liability Company`, `PLC`, `GmbH`).", + "example": "Limited Liability Company" + }, + "registry_status": { + "type": "string", + "enum": [ + "active", + "dissolved", + "deregistered", + "see full details", + "authorised", + "appointed representative", + "unauthorised", + "inactive", + "no longer authorised", + "closed", + "struck off" + ], + "nullable": true, + "description": "Registry-reported company status. Lowercase, some values contain spaces (e.g. `struck off`, `no longer authorised`). Any other value returns `400`.", + "example": "active" + }, + "verification_status": { + "type": "string", + "enum": [ + "verified", + "failed", + "unknown" + ], + "nullable": true, + "description": "Outcome of the registry verification. Any other value returns `400`.", + "example": "verified" + }, + "alternative_names": { + "type": "string", + "description": "Trade names, former legal names, DBA names (free text)." + }, + "nature_of_business": { + "type": "string", + "description": "Short description of the company's activity.", + "example": "Software development" + }, + "registered_capital": { + "type": "string", + "maxLength": 100, + "description": "Human-readable registered capital (e.g. `USD 50000`). Use `registered_capital_amount` + `registered_capital_currency` for structured values, or this field for free-form text.", + "example": "USD 50000" + }, + "registered_capital_amount": { + "type": "number", + "format": "double", + "nullable": true, + "description": "Numeric registered-capital amount (up to 20 digits, 2 decimal places). Sets the `registered_capital_amount` column and is mirrored into `registry_data` as a decimal string; the response echoes it as a decimal string (e.g. `\"1500000.50\"`)." + }, + "registered_capital_currency": { + "type": "string", + "maxLength": 3, + "nullable": true, + "description": "ISO 4217 currency code for `registered_capital_amount`. Case-insensitive (`usd` is stored as `USD`); values that are not exactly 3 letters return `400` with the ISO 4217 message. Sets the canonical `registered_capital_currency` column and is mirrored into `registry_data`.", + "example": "USD" + }, + "website": { + "type": "string", + "maxLength": 500, + "description": "Company website. Bare domains are accepted and normalized with an `https://` prefix (`docsprobe.example.com` is stored as `https://docsprobe.example.com`); values that still fail URL validation return `400`.", + "example": "https://docsprobe.example.com" + }, + "email": { + "type": "string", + "format": "email", + "description": "Contact email on file with the registry.", + "example": "legal@docsprobe.example.com" + }, + "phone": { + "type": "string", + "maxLength": 50, + "description": "Contact phone. Must match `+`, digits, spaces, dashes, dots, or parentheses, with 7-15 digits total — otherwise `400`.", + "example": "+1 302 555 0143" + }, + "legal_entity_identifier": { + "type": "string", + "maxLength": 50, + "description": "LEI, where the company has one." + }, + "location_of_registration": { + "type": "string", + "maxLength": 255, + "description": "City / subdivision of the registry of record." + }, + "vat_number": { + "type": "string", + "maxLength": 64, + "description": "EU VAT number. Merged into the `registry_data` snapshot only — editing it here does not re-run VAT and tax ID validation, so `vat_validation_status` and the other `vat_*` result fields are unchanged." + }, + "control_scheme": { + "type": "string", + "description": "Control / governance scheme when the registry exposes it. Stored in the registry snapshot only." + } + } + }, + "examples": { + "Correct registry data": { + "summary": "Fix the company's registry-extracted fields", + "value": { + "company_name": "Docs Probe Corp", + "registration_number": "DPC-001", + "country_code": "US", + "incorporation_date": "2015-04-12", + "company_type": "Limited Liability Company", + "legal_address": "1 Example Plaza, Wilmington, DE 19801", + "registry_status": "active", + "verification_status": "verified", + "website": "docsprobe.example.com", + "email": "legal@docsprobe.example.com", + "phone": "+1 302 555 0143", + "registered_capital": "USD 50000", + "nature_of_business": "Software development" + } + }, + "Single field": { + "summary": "Only the keys you send are written", + "value": { + "registry_status": "dissolved" + } + }, + "No-op": { + "summary": "Empty body echoes the current record without firing the webhook", + "value": {} + } + } + } + } + }, + "responses": { + "200": { + "description": "Company data updated (or echoed unchanged for an empty body). Returns the full KYB company record — the same shape as `registry_checks[].company` on the business session decision. Note how `legal_address` comes back as `registered_address`. `registered_capital_amount`/`registered_capital_currency` populate their canonical columns and are mirrored into `registry_data`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Full KYB company record — the same shape as the `company` object inside `registry_checks[]` on the business session decision.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Company record identifier (the `companyUuid` path parameter)." + }, + "node_id": { + "type": "string", + "nullable": true, + "description": "Workflow graph node this company record belongs to, when the workflow is graph-based." + }, + "status": { + "type": "string", + "description": "Feature lifecycle status of the registry check (e.g. `Not Finished`, `Approved`, `Declined`, `In Review`)." + }, + "registry_status": { + "type": "string", + "nullable": true, + "description": "Registry-reported company status (e.g. `active`, `dissolved`, `struck off`)." + }, + "data_resolved": { + "type": "boolean", + "description": "`true` when the company data is complete — always `true` for manual-entry companies; for registry-sourced companies, `true` once the registry fetch resolved." + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of incorporation." + }, + "region": { + "type": "string", + "nullable": true + }, + "company_type": { + "type": "string", + "nullable": true + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "registered_address": { + "type": "string", + "nullable": true, + "description": "Canonical registered address — updated when you send `legal_address`." + }, + "tax_number": { + "type": "string", + "nullable": true + }, + "risk_level": { + "type": "string", + "nullable": true + }, + "verification_status": { + "type": "string", + "nullable": true, + "description": "`verified`, `failed`, or `unknown`." + }, + "is_from_registry": { + "type": "boolean", + "description": "`true` when the company was pre-filled from a registry search rather than entered manually." + }, + "fetch_status": { + "type": "string", + "nullable": true, + "description": "Registry fetch lifecycle for registry-sourced companies (`pending` / `resolved`); `null` for manual entries." + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "nullable": true, + "description": "Canonical column. Returned as a decimal string (e.g. `\"2750000.00\"`)." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "Canonical column. Set by the registry flow or by console/API edits via this endpoint." + }, + "website": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "phone": { + "type": "string", + "nullable": true + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true + }, + "location_of_registration": { + "type": "string", + "nullable": true + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true, + "additionalProperties": true + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Directors / officers attached to the company, including per-person KYC progress." + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Beneficial owners attached to the company, including per-person KYC progress." + }, + "addresses": { + "type": "array", + "nullable": true, + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Registry address detail, when the registry returned any." + }, + "industries": { + "type": "array", + "nullable": true, + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Registry industry classification, when available." + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "$ref": "#/components/schemas/KYBRegistryData" + }, + "user_provided_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "What the end user typed during the hosted registry-confirmation step. Never modified by this endpoint." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the end user confirmed the company data in the hosted flow." + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Stamped on every successful non-empty PATCH, so UIs can surface \"edited by operator\" context." + }, + "is_editable": { + "type": "boolean", + "description": "`true` while the end user can still edit the registry-sourced data in the hosted flow (registry-sourced and not yet confirmed)." + } + } + }, + "examples": { + "Updated company": { + "summary": "Full company record after the correction", + "value": { + "uuid": "6b9479f5-6769-46c8-baf1-df63bbbc5f8c", + "node_id": null, + "status": "Not Finished", + "registry_status": "active", + "data_resolved": true, + "company_name": "Docs Probe Corp", + "registration_number": "DPC-001", + "country_code": "US", + "region": null, + "company_type": "Limited Liability Company", + "incorporation_date": "2015-04-12", + "registered_address": "1 Example Plaza, Wilmington, DE 19801", + "tax_number": null, + "risk_level": null, + "verification_status": "verified", + "is_from_registry": false, + "fetch_status": null, + "alternative_names": null, + "nature_of_business": "Software development", + "registered_capital": "USD 50000", + "registered_capital_amount": null, + "registered_capital_currency": "USD", + "website": "https://docsprobe.example.com", + "email": "legal@docsprobe.example.com", + "phone": "+1 302 555 0143", + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": null, + "officers": [], + "beneficial_owners": [], + "addresses": null, + "industries": null, + "accounts": null, + "registry_data": { + "company_name": "Docs Probe Corp", + "registration_number": "DPC-001", + "country_code": "US", + "incorporation_date": "2015-04-12", + "legal_address": "1 Example Plaza, Wilmington, DE 19801", + "company_type": "Limited Liability Company", + "registry_status": "active", + "verification_status": "verified", + "nature_of_business": "Software development", + "registered_capital": "USD 50000", + "registered_capital_currency": "USD", + "website": "https://docsprobe.example.com", + "email": "legal@docsprobe.example.com", + "phone": "+1 302 555 0143" + }, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": "2026-06-12T00:02:34.276061Z", + "is_editable": false + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field errors arrive as `{\"\": [\"\"]}`. Max-length checks run before format checks, so `country_code: \"USA\"` reports the 2-character limit rather than the ISO message.", + "content": { + "application/json": { + "examples": { + "Alpha-3 country code": { + "summary": "country_code must be alpha-2 — length check fires first", + "value": { + "country_code": [ + "Ensure this field has no more than 2 characters." + ] + } + }, + "Non-alphabetic country code": { + "summary": "2 characters but not letters", + "value": { + "country_code": [ + "country_code must be a 2-letter ISO 3166-1 alpha-2 code." + ] + } + }, + "Invalid currency": { + "summary": "3 characters but not an ISO 4217 code", + "value": { + "registered_capital_currency": [ + "Currency must be a 3-letter ISO 4217 code (e.g. USD, EUR, GBP)." + ] + } + }, + "Invalid phone": { + "summary": "Phone fails the format / digit-count check", + "value": { + "phone": [ + "Invalid phone number format." + ] + } + }, + "Invalid registry status": { + "summary": "registry_status not in the enum", + "value": { + "registry_status": [ + "\"liquidated\" is not a valid choice." + ] + } + }, + "Invalid email": { + "summary": "Malformed email address", + "value": { + "email": [ + "Enter a valid email address." + ] + } + }, + "Invalid website": { + "summary": "Value fails URL validation even after https:// normalization", + "value": { + "website": [ + "Enter a valid URL." + ] + } + }, + "Bad date format": { + "summary": "Dates must be YYYY-MM-DD", + "value": { + "incorporation_date": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Credential lacks write:businesses", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown session, unknown company, or a company that belongs to a different session. Each case has a distinct `detail` message (see examples). The unknown-session lookup runs before credential checks.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "summary": "No session with this ID in your application (or it was deleted)", + "value": { + "detail": "Not found." + } + }, + "Unknown company": { + "summary": "No company with this UUID in your application", + "value": { + "detail": "No KYBCompany matches the given query." + } + }, + "Company / session mismatch": { + "summary": "companyUuid exists but belongs to another session", + "value": { + "detail": "KYB company does not belong to the given session." + } + } + } + } + } + }, + "429": { + "description": "Shared write rate limit exceeded (300 POST/PATCH/DELETE requests per minute per API key). Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "Write budget exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH \\\n https://verification.didit.me/v3/session/33333333-4444-5555-6666-777777777777/kyb/88888888-9999-aaaa-bbbb-cccccccccccc/update-data/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"company_name\": \"Docs Probe Corp\",\n \"registration_number\": \"DPC-001\",\n \"country_code\": \"US\",\n \"incorporation_date\": \"2015-04-12\",\n \"company_type\": \"Limited Liability Company\",\n \"legal_address\": \"1 Example Plaza, Wilmington, DE 19801\",\n \"registry_status\": \"active\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_kyb_company_data(\n \"33333333-4444-5555-6666-777777777777\",\n \"88888888-9999-aaaa-bbbb-cccccccccccc\",\n company_name=\"Docs Probe Corp\",\n registration_number=\"DPC-001\",\n country_code=\"US\",\n incorporation_date=\"2015-04-12\",\n legal_address=\"1 Example Plaza, Wilmington, DE 19801\",\n company_type=\"Limited Liability Company\",\n registry_status=\"active\",\n verification_status=\"verified\",\n nature_of_business=\"Software development\",\n registered_capital=\"USD 50000\",\n website=\"docsprobe.example.com\",\n email=\"legal@docsprobe.example.com\",\n phone=\"+1 302 555 0143\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateKybCompanyData(\n \"33333333-4444-5555-6666-777777777777\",\n \"88888888-9999-aaaa-bbbb-cccccccccccc\",\n {\n company_name: \"Docs Probe Corp\",\n registration_number: \"DPC-001\",\n country_code: \"US\",\n incorporation_date: \"2015-04-12\",\n legal_address: \"1 Example Plaza, Wilmington, DE 19801\",\n company_type: \"Limited Liability Company\",\n registry_status: \"active\",\n verification_status: \"verified\",\n nature_of_business: \"Software development\",\n registered_capital: \"USD 50000\",\n website: \"docsprobe.example.com\",\n email: \"legal@docsprobe.example.com\",\n phone: \"+1 302 555 0143\",\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateKYBCompanyData(ctx, \"33333333-4444-5555-6666-777777777777\", \"88888888-9999-aaaa-bbbb-cccccccccccc\", didit.SessionsUpdateKYBCompanyDataParams{\n\tCompanyName: didit.String(\"Docs Probe Corp\"),\n\tRegistrationNumber: didit.String(\"DPC-001\"),\n\tCountryCode: didit.String(\"US\"),\n\tIncorporationDate: didit.String(\"2015-04-12\"),\n\tLegalAddress: didit.String(\"1 Example Plaza, Wilmington, DE 19801\"),\n\tCompanyType: didit.String(\"Limited Liability Company\"),\n\tRegistryStatus: didit.Some(didit.SessionsUpdateKYBCompanyDataParamsRegistryStatus(\"active\")),\n\tVerificationStatus: didit.Some(didit.SessionsUpdateKYBCompanyDataParamsVerificationStatus(\"verified\")),\n\tNatureOfBusiness: didit.String(\"Software development\"),\n\tRegisteredCapital: didit.String(\"USD 50000\"),\n\tWebsite: didit.String(\"docsprobe.example.com\"),\n\tEmail: didit.String(\"legal@docsprobe.example.com\"),\n\tPhone: didit.String(\"+1 302 555 0143\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport me.didit.Didit;\nimport me.didit.types.SessionsUpdateKybCompanyDataParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateKybCompanyData(\"33333333-4444-5555-6666-777777777777\", \"88888888-9999-aaaa-bbbb-cccccccccccc\", SessionsUpdateKybCompanyDataParams.builder()\n .companyName(\"Docs Probe Corp\")\n .registrationNumber(\"DPC-001\")\n .countryCode(\"US\")\n .incorporationDate(LocalDate.parse(\"2015-04-12\"))\n .legalAddress(\"1 Example Plaza, Wilmington, DE 19801\")\n .companyType(\"Limited Liability Company\")\n .registryStatus(SessionsUpdateKybCompanyDataParams.RegistryStatus.of(\"active\"))\n .verificationStatus(SessionsUpdateKybCompanyDataParams.VerificationStatus.of(\"verified\"))\n .natureOfBusiness(\"Software development\")\n .registeredCapital(\"USD 50000\")\n .website(\"docsprobe.example.com\")\n .email(\"legal@docsprobe.example.com\")\n .phone(\"+1 302 555 0143\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateKybCompanyData('33333333-4444-5555-6666-777777777777', '88888888-9999-aaaa-bbbb-cccccccccccc', [\n 'company_name' => 'Docs Probe Corp',\n 'registration_number' => 'DPC-001',\n 'country_code' => 'US',\n 'incorporation_date' => '2015-04-12',\n 'legal_address' => '1 Example Plaza, Wilmington, DE 19801',\n 'company_type' => 'Limited Liability Company',\n 'registry_status' => 'active',\n 'verification_status' => 'verified',\n 'nature_of_business' => 'Software development',\n 'registered_capital' => 'USD 50000',\n 'website' => 'docsprobe.example.com',\n 'email' => 'legal@docsprobe.example.com',\n 'phone' => '+1 302 555 0143',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_kyb_company_data(\n \"33333333-4444-5555-6666-777777777777\",\n \"88888888-9999-aaaa-bbbb-cccccccccccc\",\n company_name: \"Docs Probe Corp\",\n registration_number: \"DPC-001\",\n country_code: \"US\",\n incorporation_date: \"2015-04-12\",\n legal_address: \"1 Example Plaza, Wilmington, DE 19801\",\n company_type: \"Limited Liability Company\",\n registry_status: \"active\",\n verification_status: \"verified\",\n nature_of_business: \"Software development\",\n registered_capital: \"USD 50000\",\n website: \"docsprobe.example.com\",\n email: \"legal@docsprobe.example.com\",\n phone: \"+1 302 555 0143\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateKybCompanyDataAsync(\"33333333-4444-5555-6666-777777777777\", \"88888888-9999-aaaa-bbbb-cccccccccccc\", new SessionsUpdateKybCompanyDataParams\n{\n CompanyName = \"Docs Probe Corp\",\n RegistrationNumber = \"DPC-001\",\n CountryCode = \"US\",\n IncorporationDate = \"2015-04-12\",\n LegalAddress = \"1 Example Plaza, Wilmington, DE 19801\",\n CompanyType = \"Limited Liability Company\",\n RegistryStatus = new SessionsUpdateKybCompanyDataParamsRegistryStatus(\"active\"),\n VerificationStatus = new SessionsUpdateKybCompanyDataParamsVerificationStatus(\"verified\"),\n NatureOfBusiness = \"Software development\",\n RegisteredCapital = \"USD 50000\",\n Website = \"docsprobe.example.com\",\n Email = \"legal@docsprobe.example.com\",\n Phone = \"+1 302 555 0143\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/parties/{partyUuid}/": { + "patch": { + "summary": "Skip (or re-require) a Key People party on a Business Verification (KYB) session", + "description": "Exempt a Key People party from verification, or put it back in scope. This is the operator escape hatch for a party that can never finish its own check - most often because the individual KYC session handed to a UBO or director was deleted, which freezes that child's status and leaves the KYB parked in `Awaiting User` with nothing left that could move it.\n\nSetting `is_skipped: true` drops the party out of the Key People aggregation and immediately recomputes the `KYBKeyPeopleCheck` and the parent session status, so the business session can settle and then be approved or declined. Sending `is_skipped: false` puts the party back in scope and the check goes back to waiting on it.\n\nUnlike the applicant-facing skip in the hosted Key People screen, this endpoint is **not** gated by the role's `allow_skip` setting - that flag limits what the applicant may decide, not what your analyst may override. `requires_verification` is deliberately left untouched, so an overridden party reads as `requires_verification: true` **and** `is_skipped: true` and the record still shows what the rules asked for.\n\nThe party must belong to the session in the path (`404` with `\"Party does not belong to the given session.\"` otherwise) and to your application. A PATCH that actually changes the flag fires the `data.updated` [webhook](/integration/webhooks) on the business session with trigger `console_key_people_skip`; a PATCH that sets the flag to its current value is a no-op that echoes the party back.\n\nAccepts an API key or a console user token; either way the credential needs the `write:businesses` permission.", + "operationId": "patch_v3_session_kyb_party_update", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_kyb_party", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the parent Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "partyUuid", + "in": "path", + "required": true, + "description": "UUID of the Key People party to update (`key_people_checks[].submitted.parties[].uuid` on the business session decision). Must belong to the session referenced by `sessionId`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_skipped" + ], + "properties": { + "is_skipped": { + "type": "boolean", + "description": "`true` exempts the party from verification so the Key People check can settle without it; `false` puts it back in scope.", + "example": true + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Party updated (or echoed unchanged when the flag already had this value). Returns the party - the same shape as an entry of `key_people_checks[].submitted.parties[]` on the business session decision.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Key People party record - the same shape as an entry of `key_people_checks[].submitted.parties[]` on the business session decision.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Party identifier." + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "name": { + "type": "string", + "description": "Display name of the party." + }, + "is_skipped": { + "type": "boolean", + "description": "Whether the party is exempt from verification." + }, + "requires_verification": { + "type": "boolean", + "description": "What the role and threshold rules asked for. Left untouched by this endpoint." + }, + "kyc_session_status": { + "type": "string", + "nullable": true, + "description": "Status of the linked individual KYC session, frozen at deletion time when the session was deleted." + }, + "kyc_session_url": { + "type": "string", + "nullable": true, + "description": "Link to the linked individual KYC session. `null` when there is none or when it was deleted." + }, + "kyc_session_deleted": { + "type": "boolean", + "description": "The linked individual KYC session was deleted, so it can never settle." + }, + "kyb_sub_session_deleted": { + "type": "boolean", + "description": "Corporate-party counterpart of `kyc_session_deleted`." + } + } + } + } + } + }, + "400": { + "description": "Validation error on the request body. `is_skipped` is the only field this endpoint reads, and it is required. Field errors arrive as `{\"\": [\"\"]}`. Body validation runs *after* the party lookup, so a malformed `is_skipped` sent to a party that does not exist reports `404`, not `400`.", + "content": { + "application/json": { + "examples": { + "Missing is_skipped": { + "summary": "Empty body, or a body that omits `is_skipped`", + "value": { + "is_skipped": [ + "This field is required." + ] + } + }, + "Non-boolean is_skipped": { + "summary": "Value is not a boolean — `\"maybe\"`, `2`, `[]`. Booleans plus `\"true\"`/`\"false\"`, `1`/`0` and `\"yes\"`/`\"no\"` are all accepted", + "value": { + "is_skipped": [ + "Must be a valid boolean." + ] + } + }, + "Null is_skipped": { + "summary": "`is_skipped` is present but `null`", + "value": { + "is_skipped": [ + "This field may not be null." + ] + } + } + } + } + } + }, + "403": { + "description": "Missing credentials or insufficient permissions. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Credential lacks write:businesses", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown party, or a party that belongs to a different session. Each case has a distinct `detail` message (see examples). Credential and permission checks run before the party lookup, so an unauthorised call reports `403` rather than `404`. There is no separate session lookup: an unknown `sessionId` paired with a party of yours reports the mismatch message.", + "content": { + "application/json": { + "examples": { + "Unknown party": { + "summary": "No party with this UUID in your application", + "value": { + "detail": "Not found." + } + }, + "Party / session mismatch": { + "summary": "partyUuid exists but hangs off another session", + "value": { + "detail": "Party does not belong to the given session." + } + } + } + } + } + }, + "429": { + "description": "Shared write rate limit exceeded (300 POST/PATCH/DELETE requests per minute per API key). Inspect `Retry-After` and the `X-RateLimit-*` response headers before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Rate limited": { + "summary": "Write budget exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_kyb_party(\n \"33333333-4444-5555-6666-777777777777\",\n \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\",\n is_skipped=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateKybParty(\n \"33333333-4444-5555-6666-777777777777\",\n \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\",\n {\n is_skipped: true,\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateKYBParty(ctx, \"33333333-4444-5555-6666-777777777777\", \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\", didit.SessionsUpdateKYBPartyParams{\n\tIsSkipped: true,\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsUpdateKybPartyParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateKybParty(\"33333333-4444-5555-6666-777777777777\", \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\", SessionsUpdateKybPartyParams.builder()\n .isSkipped(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateKybParty('33333333-4444-5555-6666-777777777777', '0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41', [\n 'is_skipped' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_kyb_party(\n \"33333333-4444-5555-6666-777777777777\",\n \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\",\n is_skipped: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateKybPartyAsync(\"33333333-4444-5555-6666-777777777777\", \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\", new SessionsUpdateKybPartyParams\n{\n IsSkipped = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/travel-rule/settings/": { + "get": { + "summary": "Get Travel Rule settings", + "description": "Read the application's Travel Rule settings and VASP profile. Auto-creates a default (disabled) settings row and an empty profile on first read.", + "operationId": "getTravelRuleSettings", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_settings", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/settings/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.retrieve_settings()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.retrieveSettings();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.RetrieveSettings(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().retrieveSettings();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->retrieveSettings();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.retrieve_settings\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.RetrieveSettingsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Current Travel Rule settings and VASP profile.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleSettingsDetail" + }, + "examples": { + "Disabled (default)": { + "value": { + "is_enabled": false, + "jurisdiction": "EU", + "name_matching_strictness": "DEFAULT", + "confirmation_timeout_hours": 48, + "timeout_outcome": "HOLD", + "threshold_amount": "0.00", + "inbound_auto_accept": true, + "allow_self_declaration": true, + "allow_screenshot_proof": true, + "auto_wallet_verification": true, + "vasp_attribution_enabled": true, + "legal_name": "", + "lei": "", + "compliance_email": "", + "is_discoverable": true, + "counterparty_screening_enabled": false, + "sunrise_policy": "", + "sunrise_jurisdictions": [ + "CO", + "KH", + "VN" + ], + "non_responsive_policy": "", + "travel_address": "", + "disabled_networks": [], + "networks": { + "gtr": { + "rail": "GTR", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "trust": { + "rail": "TRUST", + "mode": "unavailable", + "state": "unavailable", + "reason": "own_membership_only", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "verifyvasp": { + "rail": "VERIFYVASP", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "sygna": { + "rail": "SYGNA", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + } + }, + "didit_network": { + "listed": false, + "missing": [ + "travel_rule_off", + "legal_name_missing", + "compliance_email_missing" + ], + "compliance_email_confirmed": false + } + } + } + } + } + } + } + } + }, + "put": { + "summary": "Update Travel Rule settings", + "description": "Upsert the application's Travel Rule settings and VASP profile. Partial update — only send the fields you want to change. Setting `is_enabled: true` requires a non-blank `legal_name` (already stored or included in the same request), otherwise returns 400.", + "operationId": "updateTravelRuleSettings", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "update_settings", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleSettingsUpdate" + }, + "example": { + "is_enabled": true, + "legal_name": "Origin CASP SL", + "jurisdiction": "EU", + "name_matching_strictness": "DEFAULT", + "confirmation_timeout_hours": 48, + "timeout_outcome": "HOLD", + "is_discoverable": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PUT https://verification.didit.me/v3/travel-rule/settings/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"is_enabled\": true, \"legal_name\": \"Origin CASP SL\", \"jurisdiction\": \"EU\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.update_settings(\n is_enabled=True,\n legal_name=\"Origin CASP SL\",\n jurisdiction=\"EU\",\n name_matching_strictness=\"DEFAULT\",\n confirmation_timeout_hours=48,\n timeout_outcome=\"HOLD\",\n is_discoverable=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.updateSettings({\n is_enabled: true,\n legal_name: \"Origin CASP SL\",\n jurisdiction: \"EU\",\n name_matching_strictness: \"DEFAULT\",\n confirmation_timeout_hours: 48,\n timeout_outcome: \"HOLD\",\n is_discoverable: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.UpdateSettings(ctx, didit.TravelRuleSettingsUpdate{\n\tIsEnabled: didit.Bool(true),\n\tLegalName: didit.String(\"Origin CASP SL\"),\n\tJurisdiction: didit.String(\"EU\"),\n\tNameMatchingStrictness: didit.Some(didit.TravelRuleSettingsUpdateNameMatchingStrictness(\"DEFAULT\")),\n\tConfirmationTimeoutHours: didit.Int(48),\n\tTimeoutOutcome: didit.Some(didit.TravelRuleSettingsUpdateTimeoutOutcome(\"HOLD\")),\n\tIsDiscoverable: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleSettingsUpdate;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().updateSettings(TravelRuleSettingsUpdate.builder()\n .isEnabled(true)\n .legalName(\"Origin CASP SL\")\n .jurisdiction(\"EU\")\n .nameMatchingStrictness(TravelRuleSettingsUpdate.NameMatchingStrictness.of(\"DEFAULT\"))\n .confirmationTimeoutHours(48L)\n .timeoutOutcome(TravelRuleSettingsUpdate.TimeoutOutcome.of(\"HOLD\"))\n .isDiscoverable(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->updateSettings([\n 'is_enabled' => true,\n 'legal_name' => 'Origin CASP SL',\n 'jurisdiction' => 'EU',\n 'name_matching_strictness' => 'DEFAULT',\n 'confirmation_timeout_hours' => 48,\n 'timeout_outcome' => 'HOLD',\n 'is_discoverable' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.update_settings(\n is_enabled: true,\n legal_name: \"Origin CASP SL\",\n jurisdiction: \"EU\",\n name_matching_strictness: \"DEFAULT\",\n confirmation_timeout_hours: 48,\n timeout_outcome: \"HOLD\",\n is_discoverable: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.UpdateSettingsAsync(new TravelRuleSettingsUpdate\n{\n IsEnabled = true,\n LegalName = \"Origin CASP SL\",\n Jurisdiction = \"EU\",\n NameMatchingStrictness = new TravelRuleSettingsUpdateNameMatchingStrictness(\"DEFAULT\"),\n ConfirmationTimeoutHours = 48,\n TimeoutOutcome = new TravelRuleSettingsUpdateTimeoutOutcome(\"HOLD\"),\n IsDiscoverable = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated settings and profile, in the same shape as GET.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleSettingsDetail" + } + } + } + }, + "400": { + "description": "Validation error, e.g. enabling without a legal_name, a sunrise_policy or non_responsive_policy other than HOLD, REJECT, PROCEED or empty, an unknown field in `matching_rules`, or a `data_sharing_fields` choice that cannot meet the originator obligations of your jurisdiction's regime (the errors are keyed by entity type under `data_sharing_fields`).", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "legal_name": [ + "legal_name is required to enable travel rule." + ] + } + } + } + } + } + } + }, + "/v3/travel-rule/settings/compliance-email/confirmation/": { + "post": { + "summary": "Send the compliance email confirmation", + "description": "Emails a single-use confirmation link to your VASP profile's compliance_email. Opening the link and pressing its confirm button confirms the address, which the Didit network needs before it lists your profile. A new request replaces the previous link; the link stops working when the compliance email changes. One email per minute per address.", + "operationId": "sendTravelRuleComplianceEmailConfirmation", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "send_compliance_email_confirmation", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/settings/compliance-email/confirmation/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.send_compliance_email_confirmation()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.sendComplianceEmailConfirmation();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.SendComplianceEmailConfirmation(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().sendComplianceEmailConfirmation();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->sendComplianceEmailConfirmation();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.send_compliance_email_confirmation\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.SendComplianceEmailConfirmationAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "202": { + "description": "The confirmation email was sent.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sent_to": { + "type": "string", + "format": "email" + } + } + }, + "example": { + "sent_to": "compliance@your-vasp.example" + } + } + } + }, + "400": { + "description": "No compliance email is set, or it is already confirmed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "No compliance email": { + "value": { + "compliance_email": [ + "Set a compliance email before confirming it." + ] + } + }, + "Already confirmed": { + "value": { + "compliance_email": [ + "This compliance email is already confirmed." + ] + } + } + } + } + } + }, + "429": { + "description": "A confirmation went to this address less than a minute ago." + }, + "503": { + "description": "The email could not be sent. Nothing changed; try again in a moment." + } + } + } + }, + "/v3/travel-rule/wallet-addresses/": { + "get": { + "summary": "List wallet address book entries", + "description": "Paginated list of your application's Travel Rule wallet address book, newest first.", + "operationId": "listTravelRuleWalletAddresses", + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/wallet-addresses/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.travel_rule.wallet_addresses.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.travelRule.walletAddresses.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.TravelRule.WalletAddresses.ListAutoPaging(ctx, didit.TravelRuleWalletAddressesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.travelRule().walletAddresses().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletAddresses->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.wallet_addresses.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.TravelRule.WalletAddresses.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated wallet address book entries.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WalletAddressEntry" + } + } + } + } + } + } + } + } + }, + "post": { + "summary": "Register a wallet address", + "description": "Add an entry to your Travel Rule wallet address book so inbound INTERNAL-rail transfers can resolve against it.", + "operationId": "createTravelRuleWalletAddress", + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressEntryCreate" + }, + "example": { + "address": "0xBeneficiaryWallet01", + "chain": "ETH", + "holder_name": "Ana Diaz", + "holder_vendor_data": "user-042", + "self_declared": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/wallet-addresses/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"address\": \"0xBeneficiaryWallet01\", \"chain\": \"ETH\", \"holder_name\": \"Ana Diaz\", \"self_declared\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.wallet_addresses.create(\n address=\"0xBeneficiaryWallet01\",\n chain=\"ETH\",\n holder_vendor_data=\"user-042\",\n holder_name=\"Ana Diaz\",\n self_declared=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.walletAddresses.create({\n address: \"0xBeneficiaryWallet01\",\n chain: \"ETH\",\n holder_vendor_data: \"user-042\",\n holder_name: \"Ana Diaz\",\n self_declared: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.WalletAddresses.Create(ctx, didit.WalletAddressEntryCreate{\n\tAddress: \"0xBeneficiaryWallet01\",\n\tChain: didit.String(\"ETH\"),\n\tHolderVendorData: didit.String(\"user-042\"),\n\tHolderName: didit.String(\"Ana Diaz\"),\n\tSelfDeclared: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.WalletAddressEntryCreate;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().walletAddresses().create(WalletAddressEntryCreate.builder()\n .address(\"0xBeneficiaryWallet01\")\n .chain(\"ETH\")\n .holderVendorData(\"user-042\")\n .holderName(\"Ana Diaz\")\n .selfDeclared(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletAddresses->create([\n 'address' => '0xBeneficiaryWallet01',\n 'chain' => 'ETH',\n 'holder_vendor_data' => 'user-042',\n 'holder_name' => 'Ana Diaz',\n 'self_declared' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.wallet_addresses.create(\n address: \"0xBeneficiaryWallet01\",\n chain: \"ETH\",\n holder_vendor_data: \"user-042\",\n holder_name: \"Ana Diaz\",\n self_declared: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.WalletAddresses.CreateAsync(new WalletAddressEntryCreate\n{\n Address = \"0xBeneficiaryWallet01\",\n Chain = \"ETH\",\n HolderVendorData = \"user-042\",\n HolderName = \"Ana Diaz\",\n SelfDeclared = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The created entry.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressEntry" + } + } + } + }, + "400": { + "description": "Duplicate wallet (address, chain and memo) for this application, a memo or X-address the chain cannot carry, a missing `address`, an invalid `travel_address`, `self_declared: true` while `allow_self_declaration` is off, or `self_declared: true` while `ownership_validation_mode` is `FULL`.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "examples": { + "Duplicate entry": { + "value": { + "address": [ + "A wallet address entry with this address and chain already exists." + ] + } + }, + "Invalid travel address": { + "value": { + "travel_address": [ + "Invalid travel address." + ] + } + }, + "Self-declaration disabled": { + "value": { + "self_declared": [ + "Self-declared ownership is disabled for this application." + ] + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/wallet-addresses/{entry_uuid}/": { + "patch": { + "summary": "Update wallet address holder metadata", + "description": "Partial update of holder_name, holder_vendor_data, entity_type, and travel_address. Setting travel_address attaches a TRP routing address to the entry; invalid values are rejected with 400. The response echoes only these four fields.", + "operationId": "updateTravelRuleWalletAddress", + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "entry_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressEntryUpdate" + }, + "example": { + "holder_name": "Ana Updated", + "travel_address": "ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH https://verification.didit.me/v3/travel-rule/wallet-addresses/{entry_uuid}/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"holder_name\": \"Ana Updated\", \"travel_address\": \"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.wallet_addresses.update(\n \"11111111-2222-3333-4444-555555555555\",\n holder_name=\"Ana Updated\",\n travel_address=\"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.walletAddresses.update(\"11111111-2222-3333-4444-555555555555\", {\n holder_name: \"Ana Updated\",\n travel_address: \"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.WalletAddresses.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.WalletAddressEntryUpdateParam{\n\tHolderName: didit.String(\"Ana Updated\"),\n\tTravelAddress: didit.String(\"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.WalletAddressEntryUpdate;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().walletAddresses().update(\"11111111-2222-3333-4444-555555555555\", WalletAddressEntryUpdate.builder()\n .holderName(\"Ana Updated\")\n .travelAddress(\"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletAddresses->update('11111111-2222-3333-4444-555555555555', [\n 'holder_name' => 'Ana Updated',\n 'travel_address' => 'ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.wallet_addresses.update(\n \"11111111-2222-3333-4444-555555555555\",\n holder_name: \"Ana Updated\",\n travel_address: \"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.WalletAddresses.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new WalletAddressEntryUpdate\n{\n HolderName = \"Ana Updated\",\n TravelAddress = \"ta2ih1MicegQ3JqiKz9xT89KsSuwaDJYw8DgzmeSEp1h9tduJcNDZ5SgKLEHeyGMcS1s2HgbM6undFrMQknhM5aTXw3G9KBzi16hx\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The updated fields.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressEntryUpdate" + } + } + } + }, + "400": { + "description": "`travel_address` is not a valid travel address.", + "content": { + "application/json": { + "examples": { + "Invalid travel address": { + "value": { + "travel_address": [ + "Invalid travel address." + ] + } + } + } + } + } + }, + "404": { + "description": "Unknown or deleted entry." + } + } + }, + "delete": { + "summary": "Delete a wallet address", + "description": "Deletes the entry together with its ownership proofs and the screenshots they stored. It stops matching new transfers immediately and the address is freed up for re-registration. Transfers that already used the wallet keep their own record of it and read the same. Deletion is irreversible.", + "operationId": "deleteTravelRuleWalletAddress", + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "entry_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE https://verification.didit.me/v3/travel-rule/wallet-addresses/{entry_uuid}/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.travel_rule.wallet_addresses.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.travelRule.walletAddresses.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.TravelRule.WalletAddresses.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.travelRule().walletAddresses().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletAddresses->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.wallet_addresses.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.TravelRule.WalletAddresses.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ], + "responses": { + "204": { + "description": "Deleted." + }, + "404": { + "description": "Unknown or already deleted entry." + }, + "503": { + "description": "`wallet_address_deletion_incomplete`: a stored file could not be deleted, so the deletion stopped before it finished. The address-book entry is still there; retry the same request to finish it.", + "content": { + "application/json": { + "example": { + "detail": "A stored file could not be deleted, so the deletion stopped before it finished. Try again.", + "code": "wallet_address_deletion_incomplete" + } + } + } + } + } + } + }, + "/v3/travel-rule/vasps/": { + "get": { + "summary": "Search the VASP directory", + "description": "Paginated, name-sorted search of the VASP directory: the shared directory entries, which Didit builds from the ESMA interim MiCA register, the public TRISA directory, the global LEI register and its own curation, plus the members of the Didit network. Each row carries the routes your application has to it today. Your own application's profile is never listed. Rows also carry the shared due-diligence assessment and your application's own decision, entity screening and questionnaires.", + "operationId": "listTravelRuleVasps", + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "search", + "in": "query", + "schema": { + "type": "string", + "maxLength": 255 + }, + "description": "A name or alias fragment (case-insensitive), an exact LEI, or a domain (example.com or a URL on it). Didit network members match on their legal name, LEI or the domain of their compliance email." + }, + { + "name": "network", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "TRISA" + ] + }, + "description": "Only directory entries a network's member directory currently lists." + }, + { + "name": "jurisdiction", + "in": "query", + "schema": { + "type": "string" + }, + "description": "ISO 3166 alpha-2 or alpha-3 country code. 400 for anything else." + }, + { + "name": "reachable", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "true: only VASPs your application has an available route to (Didit network members included). false: only directory entries it cannot reach today." + }, + { + "name": "kind", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "directory", + "didit" + ] + }, + "description": "Only directory entries, or only Didit network members." + }, + { + "name": "risk", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "description": "Only directory entries whose risk_level is this band. Didit network members, which are not scored, never match." + }, + { + "name": "responsiveness", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "RESPONSIVE", + "UNRESPONSIVE", + "UNKNOWN" + ] + }, + "description": "Only directory entries whose responsiveness.status is this value. Didit network members never match." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/travel-rule/vasps/?search=counterparty&reachable=true' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.travel_rule.list_vasps():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.travelRule.listVasps()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.TravelRule.ListVASPsAutoPaging(ctx, didit.TravelRuleListVASPsParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.travelRule().listVasps()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->listVasps() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.list_vasps.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.TravelRule.ListVaspsAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated, name-sorted VASP directory results.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VaspDirectoryEntry" + } + } + } + } + } + } + }, + "400": { + "description": "A filter is not valid: the body names each field, for example an unknown network, country code, risk band or responsiveness status.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "jurisdiction": [ + "Use an ISO 3166 alpha-2 or alpha-3 country code." + ], + "network": [ + "\"SWIFT\" is not a valid choice." + ] + } + } + } + } + }, + "x-didit-sdk": { + "resource": "travel_rule", + "method": "list_vasps", + "auth": "api_key", + "server": true + } + } + }, + "/v3/travel-rule/vasps/{vasp_id}/": { + "get": { + "summary": "Get a VASP directory entry", + "description": "One directory entry or Didit network member by the id the search returned, with the same fields and the routes your application has to it.", + "operationId": "getTravelRuleVasp", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_vasp", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.retrieve_vasp(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.retrieveVasp(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.RetrieveVASP(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().retrieveVasp(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->retrieveVasp('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.retrieve_vasp(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.RetrieveVaspAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The entry.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VaspDirectoryEntry" + } + } + } + }, + "404": { + "description": "No entry or listed Didit network member has this id.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry has this id." + } + } + } + } + } + } + }, + "/v3/travel-rule/vasps/{vasp_id}/decision/": { + "get": { + "summary": "Get your decision on a VASP", + "description": "Your application's decision on a counterparty (a directory entry or a Didit network member): approved, restricted or blocked. An entity of a brand with no decision of its own follows the brand's decision, named in inherited_from. Private to your application.", + "operationId": "getTravelRuleVaspDecision", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_vasp_decision", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/decision/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.retrieve_vasp_decision(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.retrieveVaspDecision(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.RetrieveVASPDecision(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().retrieveVaspDecision(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->retrieveVaspDecision('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.retrieve_vasp_decision(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.RetrieveVaspDecisionAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The decision that applies, or null.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecisionResponse" + }, + "examples": { + "Own decision": { + "value": { + "decision": { + "decision": "APPROVED", + "reason": "Licensed in DE, questionnaire reviewed", + "decided_by": "", + "updated_at": "2026-10-14T10:05:00Z", + "inherited_from": null + } + } + }, + "Inherited from the brand": { + "value": { + "decision": { + "decision": "BLOCKED", + "reason": "Group refused after review", + "decided_by": "", + "updated_at": "2026-10-14T10:05:00Z", + "inherited_from": { + "id": "5e4d3c2b-1a0f-4e9d-8c7b-6a5f4e3d2c1b", + "name": "Example Group" + } + } + } + }, + "No decision": { + "value": { + "decision": null + } + } + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + } + } + }, + "put": { + "summary": "Set your decision on a VASP", + "description": "Make or change your application's decision on a counterparty: APPROVED, RESTRICTED or BLOCKED, with an optional reason. It never changes the shared directory entry and acts only through your transaction rules (counterparty_dd_status). A decision on a brand also covers each of its entities that has no decision of its own.", + "operationId": "updateTravelRuleVaspDecision", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "update_vasp_decision", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecisionRequest" + }, + "example": { + "decision": "BLOCKED", + "reason": "Unlicensed in its home jurisdiction" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PUT https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/decision/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"decision\": \"BLOCKED\", \"reason\": \"Unlicensed in its home jurisdiction\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.update_vasp_decision(\n \"11111111-2222-3333-4444-555555555555\",\n decision=\"BLOCKED\",\n reason=\"Unlicensed in its home jurisdiction\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.updateVaspDecision(\"11111111-2222-3333-4444-555555555555\", {\n decision: \"BLOCKED\",\n reason: \"Unlicensed in its home jurisdiction\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.UpdateVASPDecision(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleCounterpartyDecisionRequest{\n\tDecision: \"BLOCKED\",\n\tReason: didit.String(\"Unlicensed in its home jurisdiction\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleCounterpartyDecisionRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().updateVaspDecision(\"11111111-2222-3333-4444-555555555555\", TravelRuleCounterpartyDecisionRequest.builder()\n .decision(TravelRuleCounterpartyDecisionRequest.Decision.of(\"BLOCKED\"))\n .reason(\"Unlicensed in its home jurisdiction\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->updateVaspDecision('11111111-2222-3333-4444-555555555555', [\n 'decision' => 'BLOCKED',\n 'reason' => 'Unlicensed in its home jurisdiction',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.update_vasp_decision(\n \"11111111-2222-3333-4444-555555555555\",\n decision: \"BLOCKED\",\n reason: \"Unlicensed in its home jurisdiction\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.UpdateVaspDecisionAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleCounterpartyDecisionRequest\n{\n Decision = \"BLOCKED\",\n Reason = \"Unlicensed in its home jurisdiction\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The decision as it now applies.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecisionResponse" + }, + "example": { + "decision": { + "decision": "BLOCKED", + "reason": "Unlicensed in its home jurisdiction", + "decided_by": "", + "updated_at": "2026-10-14T10:05:00Z", + "inherited_from": null + } + } + } + } + }, + "400": { + "description": "The decision is missing or not one of APPROVED, RESTRICTED or BLOCKED, or the reason is longer than 255 characters.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "decision": [ + "\"MAYBE\" is not a valid choice." + ] + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + } + } + }, + "delete": { + "summary": "Clear your decision on a VASP", + "description": "Clear your application's own decision on a counterparty. The response shows the decision that applies afterwards: null, or for an entity of a brand the brand's decision, which the entity follows again.", + "operationId": "clearTravelRuleVaspDecision", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "clear_vasp_decision", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/decision/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.clear_vasp_decision(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.clearVaspDecision(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.ClearVASPDecision(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().clearVaspDecision(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->clearVaspDecision('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.clear_vasp_decision(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.ClearVaspDecisionAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The decision cleared; the body shows the decision that applies now.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecisionResponse" + }, + "example": { + "decision": null + } + } + } + }, + "404": { + "description": "No counterparty has this id, or your application has no decision of its own on it (a decision it only inherits from its brand cannot be cleared here: clear the brand's).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Unknown id": { + "value": { + "detail": "No directory entry or Didit network member has this id." + } + }, + "No decision of its own": { + "value": { + "detail": "This application has no decision of its own on this counterparty." + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/vasps/{vasp_id}/screening/": { + "get": { + "summary": "Get a VASP's entity screening", + "description": "Whether counterparty screening is on for your application, what one screening costs your organization, how long a result stays fresh, and your latest sanctions and adverse-media screening of the counterparty. Reading it is free.", + "operationId": "getTravelRuleVaspScreening", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_vasp_screening", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/screening/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.retrieve_vasp_screening(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.retrieveVaspScreening(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.RetrieveVASPScreening(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().retrieveVaspScreening(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->retrieveVaspScreening('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.retrieve_vasp_screening(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.RetrieveVaspScreeningAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Your screening setting, its price and your latest screening.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreeningStatus" + }, + "example": { + "enabled": true, + "price": "0.20", + "fresh_days": 30, + "screening": { + "id": "7a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d", + "status": "Approved", + "total_hits": 0, + "score": null, + "screened_at": "2026-10-14T09:58:00Z", + "fresh": true, + "flagged": false + } + } + } + } + }, + "403": { + "description": "Your organization's contract has no rate configured for counterparty screening.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No contract rate is configured for this usage type." + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + } + } + }, + "post": { + "summary": "Screen a VASP", + "description": "Screen the counterparty entity against sanctions and adverse media for your application. Opt-in: counterparty_screening_enabled must be on in your Travel Rule settings. Each new screening is billed at your organization's Company AML Screening price; a screening younger than 30 days is returned instead (200, not billed) unless you send refresh: true. One screening of a counterparty runs at a time per application. The result is private to your application. Sandbox applications are never screened.", + "operationId": "screenTravelRuleVasp", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "screen_vasp", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreeningRequest" + }, + "example": { + "refresh": false + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/screening/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"refresh\": false}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.screen_vasp(\n \"11111111-2222-3333-4444-555555555555\",\n refresh=False,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.screenVasp(\"11111111-2222-3333-4444-555555555555\", {\n refresh: false,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.ScreenVASP(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleCounterpartyScreeningRequest{\n\tRefresh: didit.Bool(false),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleCounterpartyScreeningRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().screenVasp(\"11111111-2222-3333-4444-555555555555\", TravelRuleCounterpartyScreeningRequest.builder()\n .refresh(false)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->screenVasp('11111111-2222-3333-4444-555555555555', [\n 'refresh' => false,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.screen_vasp(\n \"11111111-2222-3333-4444-555555555555\",\n refresh: false\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.ScreenVaspAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleCounterpartyScreeningRequest\n{\n Refresh = false,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "A fresh screening already existed and was returned. Nothing was billed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreeningResult" + }, + "example": { + "outcome": "reused", + "screening": { + "id": "7a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d", + "status": "Approved", + "total_hits": 0, + "score": null, + "screened_at": "2026-10-14T09:58:00Z", + "fresh": true, + "flagged": false + } + } + } + } + }, + "201": { + "description": "A new screening ran and was billed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreeningResult" + }, + "example": { + "outcome": "screened", + "screening": { + "id": "7a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d", + "status": "In Review", + "total_hits": 2, + "score": 85.0, + "screened_at": "2026-10-14T09:58:00Z", + "fresh": true, + "flagged": true + } + } + } + } + }, + "400": { + "description": "The body is not valid, or the counterparty has no name to screen.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "detail": [ + "This counterparty has no name to screen." + ] + } + } + } + }, + "403": { + "description": "Your organization's balance cannot cover the screening (code insufficient_credits; nothing ran and nothing was billed), or your contract has no rate configured for it.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me", + "code": "insufficient_credits" + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + }, + "409": { + "description": "Nothing ran and nothing was billed: counterparty screening is off for your application, the application is a sandbox one, or a screening of this counterparty is already running.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Screening off": { + "value": { + "detail": "Counterparty screening is turned off. Enable it in the Travel Rule settings first." + } + }, + "Sandbox application": { + "value": { + "detail": "Counterparty screening does not run for sandbox applications." + } + }, + "Already running": { + "value": { + "detail": "A screening of this counterparty is already running. Try again in a moment." + } + } + } + } + } + }, + "502": { + "description": "The screening could not be completed. Nothing was billed; try again in a moment.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "The screening provider did not answer. Try again in a moment." + } + } + } + } + } + } + }, + "/v3/travel-rule/vasps/{vasp_id}/questionnaires/": { + "get": { + "summary": "List due-diligence questionnaires with a VASP", + "description": "Every due-diligence questionnaire your application sent the counterparty, newest first, with its status and, once answered, the answers. Private to your application.", + "operationId": "listTravelRuleVaspQuestionnaires", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "list_vasp_questionnaires", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/questionnaires/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.list_vasp_questionnaires(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.listVaspQuestionnaires(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.ListVASPQuestionnaires(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().listVaspQuestionnaires(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->listVaspQuestionnaires('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.list_vasp_questionnaires(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.ListVaspQuestionnairesAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Your questionnaires with this counterparty.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaireList" + }, + "example": { + "questionnaires": [ + { + "id": "3f9e8d7c-6b5a-4c3d-9e2f-1a0b9c8d7e6f", + "status": "SENT", + "form_version": "1", + "sent_to": "travelrule@counterparty.example", + "sent_at": "2026-10-14T10:00:00Z", + "expires_at": "2026-11-13T10:00:00Z", + "answered_at": null, + "valid_until": null, + "current": false, + "answers": {} + }, + { + "id": "8b7a6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d", + "status": "ANSWERED", + "form_version": "1", + "sent_to": "travelrule@counterparty.example", + "sent_at": "2026-09-02T08:00:00Z", + "expires_at": "2026-10-02T08:00:00Z", + "answered_at": "2026-09-04T15:20:00Z", + "valid_until": "2027-09-04T15:20:00Z", + "current": true, + "answers": { + "legal_name": "Example Counterparty VASP, incorporated in the example jurisdiction.", + "licensing": "Crypto-asset service provider authorised under MiCA by its national regulator, licence active.", + "jurisdictions": "EU and EEA. We do not serve the United States.", + "aml_program": "Written AML/CFT policy, annual independent review, last completed in March 2026.", + "compliance_officer": "Head of Compliance, reachable at compliance@counterparty.example.", + "sanctions_screening": "Customers at onboarding and daily; counterparties on every transfer.", + "transaction_monitoring": "Rule-based monitoring with analyst review; suspicious activity reports filed with the FIU.", + "travel_rule": "TRP and email; we answer requests within one business day.", + "custody": "Segregated customer wallets held with a qualified custodian, insured.", + "ownership": "No shareholder holds 25 percent or more; parent company Example Holding Group.", + "record_keeping": "Five-year record-keeping for Travel Rule and transaction data, as our regulator requires.", + "data_protection": "Encrypted at rest and in transit, stored in the EU." + } + } + ] + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + } + } + }, + "post": { + "summary": "Send a due-diligence questionnaire to a VASP", + "description": "Email Didit's standard due-diligence questionnaire to a compliance address on record for the counterparty, with a single-use answer link. The counterparty has 30 days to answer every question; the answers stay current for 365 days. A new send replaces your earlier unanswered questionnaire to the same counterparty, whose link stops working. One email per minute per address.", + "operationId": "sendTravelRuleVaspQuestionnaire", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "send_vasp_questionnaire", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "vasp_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The id of a row of GET /v3/travel-rule/vasps/: a directory entry or a Didit network member." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaireRequest" + }, + "example": { + "sent_to": "travelrule@counterparty.example" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/vasps/{vasp_id}/questionnaires/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"sent_to\": \"travelrule@counterparty.example\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.send_vasp_questionnaire(\n \"11111111-2222-3333-4444-555555555555\",\n sent_to=\"travelrule@counterparty.example\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.sendVaspQuestionnaire(\"11111111-2222-3333-4444-555555555555\", {\n sent_to: \"travelrule@counterparty.example\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.SendVASPQuestionnaire(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleCounterpartyQuestionnaireRequest{\n\tSentTo: didit.String(\"travelrule@counterparty.example\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleCounterpartyQuestionnaireRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().sendVaspQuestionnaire(\"11111111-2222-3333-4444-555555555555\", TravelRuleCounterpartyQuestionnaireRequest.builder()\n .sentTo(\"travelrule@counterparty.example\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->sendVaspQuestionnaire('11111111-2222-3333-4444-555555555555', [\n 'sent_to' => 'travelrule@counterparty.example',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.send_vasp_questionnaire(\n \"11111111-2222-3333-4444-555555555555\",\n sent_to: \"travelrule@counterparty.example\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.SendVaspQuestionnaireAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleCounterpartyQuestionnaireRequest\n{\n SentTo = \"travelrule@counterparty.example\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "202": { + "description": "The questionnaire was emailed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaire" + }, + "example": { + "id": "3f9e8d7c-6b5a-4c3d-9e2f-1a0b9c8d7e6f", + "status": "SENT", + "form_version": "1", + "sent_to": "travelrule@counterparty.example", + "sent_at": "2026-10-14T10:00:00Z", + "expires_at": "2026-11-13T10:00:00Z", + "answered_at": null, + "valid_until": null, + "current": false, + "answers": {} + } + } + } + }, + "400": { + "description": "sent_to is not a valid email address or not a compliance address on record for the counterparty, or the counterparty has no compliance address on record.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "Not on record": { + "value": { + "sent_to": [ + "Send the questionnaire to a compliance address on record for this counterparty." + ] + } + }, + "No address": { + "value": { + "sent_to": [ + "No compliance address is on record for this counterparty. Add a contact for it first." + ] + } + } + } + } + } + }, + "404": { + "description": "No directory entry or listed Didit network member has this id. Your own VASP profile is never a counterparty of yours.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No directory entry or Didit network member has this id." + } + } + } + }, + "429": { + "description": "A questionnaire went to this address less than a minute ago." + }, + "503": { + "description": "The email could not be sent and no questionnaire was created. Try again in a moment." + } + } + } + }, + "/v3/transactions/{transaction_id}/travel-rule/": { + "patch": { + "summary": "Finish, cancel, resend, or update the originator wallet of a Travel Rule transfer", + "description": "Provide `payment_txn_id` to report the on-chain hash and move an outbound COMPLETED transfer to FINISHED (its INTERNAL-rail counterpart also moves to FINISHED), `{\"action\": \"cancel\"}` to cancel a non-terminal transfer, or `{\"action\": \"resend\"}` to route an outbound transfer stuck in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE, or NOT_ENOUGH_COUNTERPARTY_DATA again (improve the counterparty data first, e.g. add a travel_address to the destination wallet entry). resend applies to outbound transfers only. `{\"action\": \"update_compliance_email\", \"compliance_email\": \"desk@example.com\"}` replaces the compliance email of an outbound transfer in one of those three statuses, the fix for `reason: email_undeliverable`: the status does not change, and the next `resend` sends the request to that address. A resend is queued, not run inside the request: the response shows the transfer back in AWAITING_COUNTERPARTY with a `resend queued` timeline entry and a queued `delivery`, and where it went arrives as a `travel_rule.status.updated` webhook. When the transfer went over GTR, VerifyVASP or Sygna Bridge, the network learns the on-chain hash or the cancellation after the response, with retries; `delivery` shows how that message stands. `{\"action\": \"update_originator_wallet\", \"wallet_address\": \"...\"}` records the wallet the funds actually left from on an outbound transfer, for example the address a UTXO withdrawal was broadcast from rather than the main wallet you declared. It changes the record only: nothing is sent to the counterparty again and the status stays. The originator becomes `chain_details.originator` with `source: updated`, the change is added to the `timeline` with the previous and the new wallet, and the INTERNAL-rail counterpart of the transfer is updated the same way. `{\"action\": \"beneficiary_vasp\", \"beneficiary_vasp\": \"\"}` names the counterparty yourself when discovery could not, on an outbound transfer in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE or NOT_ENOUGH_COUNTERPARTY_DATA: the transfer is routed to that VASP and sent again.", + "operationId": "patchTravelRuleTransfer", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "update_transfer", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID that owns the transfer.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTransferPatchRequest" + }, + "examples": { + "Finish": { + "value": { + "payment_txn_id": "0xchainhash" + } + }, + "Cancel": { + "value": { + "action": "cancel" + } + }, + "Resend": { + "value": { + "action": "resend" + } + }, + "Update compliance email": { + "value": { + "action": "update_compliance_email", + "compliance_email": "desk@example.com" + } + }, + "Update originator wallet": { + "value": { + "action": "update_originator_wallet", + "wallet_address": "bc1qOriginatorInputAddress01" + } + }, + "Name the counterparty": { + "value": { + "action": "beneficiary_vasp", + "beneficiary_vasp": "9f1c2b34-5d6e-4f70-8a91-b2c3d4e5f607" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH https://verification.didit.me/v3/transactions/{transaction_id}/travel-rule/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"payment_txn_id\": \"0xchainhash\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.travel_rule.update_transfer(\n \"11111111-2222-3333-4444-555555555555\",\n payment_txn_id=\"0xchainhash\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.travelRule.updateTransfer(\"11111111-2222-3333-4444-555555555555\", {\n payment_txn_id: \"0xchainhash\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.TravelRule.UpdateTransfer(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleTransferPatchRequest{\n\tPaymentTxnID: didit.String(\"0xchainhash\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleTransferPatchRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().travelRule().updateTransfer(\"11111111-2222-3333-4444-555555555555\", TravelRuleTransferPatchRequest.builder()\n .paymentTxnId(\"0xchainhash\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->travelRule->updateTransfer('11111111-2222-3333-4444-555555555555', [\n 'payment_txn_id' => '0xchainhash',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.travel_rule.update_transfer(\n \"11111111-2222-3333-4444-555555555555\",\n payment_txn_id: \"0xchainhash\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.TravelRule.UpdateTransferAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleTransferPatchRequest\n{\n PaymentTxnId = \"0xchainhash\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The updated transfer. After `update_compliance_email` its status is unchanged and it keeps `reason: email_undeliverable` until the next `resend`.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTransferDetail" + } + } + } + }, + "400": { + "description": "Neither payment_txn_id nor a supported action was provided, `compliance_email` is missing or not an email address on `update_compliance_email`, or, for `update_originator_wallet`, no wallet_address, a wallet or memo the transfer's chain cannot carry, or a wallet that is not one of the transfer's UTXO inputs, or `beneficiary_vasp` is missing or not a valid directory VASP id.", + "content": { + "application/json": { + "examples": { + "No action": { + "value": { + "detail": [ + "Provide payment_txn_id, action=cancel, action=resend, action=beneficiary_vasp, action=update_originator_wallet or action=update_compliance_email." + ] + } + }, + "Invalid compliance email": { + "value": { + "compliance_email": [ + "Enter a valid email address." + ] + } + } + } + } + } + }, + "404": { + "description": "Unknown transaction, the transaction has no Travel Rule transfer, or (action `beneficiary_vasp`) no directory entry has that id." + }, + "409": { + "description": "The transfer is not in a state that allows this operation (e.g. not COMPLETED/outbound for finish, already terminal for cancel, or not an outbound transfer in a resendable status for resend). `update_compliance_email` answers `409` for an inbound transfer, and for an outbound one outside COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE and NOT_ENOUGH_COUNTERPARTY_DATA. `update_originator_wallet` answers `409` for an inbound, cancelled or expired transfer. `beneficiary_vasp` answers `409` unless the transfer is outbound and in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE or NOT_ENOUGH_COUNTERPARTY_DATA.", + "content": { + "application/json": { + "examples": { + "Cancel from a terminal status": { + "value": { + "detail": "Transfer is already in a terminal state." + } + }, + "Resend not allowed": { + "value": { + "detail": "Transfer cannot be resent from its current state." + } + }, + "Finish not allowed": { + "value": { + "detail": "Transfer must be an outbound transfer in COMPLETED status to finish." + } + }, + "Compliance email cannot change": { + "value": { + "detail": "The compliance email of this transfer cannot change from its current state." + } + } + } + } + } + } + } + } + }, + "/v3/transactions/{transaction_id}/travel-rule/ownership/": { + "post": { + "summary": "Confirm or deny wallet ownership", + "description": "Resolve a transfer that waits on wallet ownership: call it on your own side of the transfer when it is UNCONFIRMED_OWNERSHIP, or when it is an inbound transfer in ON_HOLD. Confirming marks the matching wallet address book entry as ownership-verified (for an inbound ON_HOLD transfer with no entry, Didit creates one from beneficiary_data.wallet_address) and runs the beneficiary name match on both sides of the transfer; denying declines both sides. An inbound transfer whose deposit already settled moves on to FINISHED when the match succeeds. When the counterparty wallet was declared self-hosted (counterparty.payment_method.method_type: unhosted_wallet), confirming records that the customer verified who controls that wallet and completes the transfer (COMPLETED) in both directions, or FINISHED for an inbound deposit whose on-chain hash is already known. The wallet verified is the counterparty's: the destination of an outbound withdrawal, or the source (originator's wallet) of an inbound deposit. If it is not in your wallet address book yet, Didit adds it as ownership-verified under the counterparty's name; if the book already holds it under a different holder name, the transfer moves to COUNTERPARTY_MISMATCHED_DATA. For an inbound TRP transfer in UNCONFIRMED_OWNERSHIP, ON_HOLD or COUNTERPARTY_MISMATCHED_DATA, Didit sends your decision to the originating VASP instead: approving returns the payment address (wallet_address, or the matched entry's address) and moves the transfer to COMPLETED, and denying declines it. The same call also decides an inbound transfer that a GTR or Sygna Bridge counterparty sent and that waits for your review (`UNCONFIRMED_OWNERSHIP`, `ON_HOLD` or `COUNTERPARTY_MISMATCHED_DATA`): `confirmed: true` approves it (pass `wallet_address` when the transfer names none) and `confirmed: false` declines it, and the network is answered after the response, with retries. VerifyVASP takes its answer when it asks, so a VerifyVASP transfer that would need your review is declined at once and cannot be decided later.", + "operationId": "confirmTravelRuleOwnership", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "confirm_ownership", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable UUID of the transaction your application holds for this transfer: the beneficiary side's transaction on an INTERNAL-rail transfer, or the transaction you created (outbound or inbound) when the counterparty wallet was declared self-hosted.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleOwnershipConfirmRequest" + }, + "example": { + "confirmed": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/transactions/{transaction_id}/travel-rule/ownership/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"confirmed\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.travel_rule.confirm_ownership(\n \"11111111-2222-3333-4444-555555555555\",\n confirmed=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.travelRule.confirmOwnership(\"11111111-2222-3333-4444-555555555555\", {\n confirmed: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.TravelRule.ConfirmOwnership(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleOwnershipConfirmRequest{\n\tConfirmed: true,\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleOwnershipConfirmRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().travelRule().confirmOwnership(\"11111111-2222-3333-4444-555555555555\", TravelRuleOwnershipConfirmRequest.builder()\n .confirmed(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->travelRule->confirmOwnership('11111111-2222-3333-4444-555555555555', [\n 'confirmed' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.travel_rule.confirm_ownership(\n \"11111111-2222-3333-4444-555555555555\",\n confirmed: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.TravelRule.ConfirmOwnershipAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleOwnershipConfirmRequest\n{\n Confirmed = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The updated transfer. For an inbound transfer the counterparty's originator_data/beneficiary_data stay masked (`data_masked: true`) until ownership is confirmed, then `data_unmasked` records when, why and by whom.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTransferDetail" + } + } + } + }, + "409": { + "description": "The transfer is not waiting on ownership confirmation, no wallet address book entry matches its counterparty wallet, an INTERNAL transfer from another Didit customer needs a control proof (a signed message or a test deposit) before it can be confirmed, or a network transfer was already answered over its network (a VerifyVASP transfer, or a GTR or Sygna Bridge transfer you or Didit already decided).", + "content": { + "application/json": { + "examples": { + "Not awaiting confirmation": { + "value": { + "detail": "Transfer is not awaiting ownership confirmation." + } + }, + "No matching address book entry": { + "value": { + "detail": "wallet address entry no longer available" + } + } + } + } + } + }, + "400": { + "description": "Validation error: confirmed is missing, an inbound TRP approval has no wallet_address and no address to fall back on, or `confirmed: true` on a declared self-hosted wallet transfer that names no counterparty wallet and no `wallet_address` was sent.", + "content": { + "application/json": { + "examples": { + "No payment address": { + "value": { + "wallet_address": [ + "wallet_address is required to approve." + ] + } + } + } + } + } + }, + "404": { + "description": "Unknown transaction, or the transaction has no Travel Rule transfer." + }, + "502": { + "description": "Inbound TRP transfer only: Didit could not deliver your decision to the originating VASP, so the transfer status does not change.", + "content": { + "application/json": { + "examples": { + "Callback not delivered": { + "value": { + "detail": "Counterparty inquiry-resolution callback could not be delivered." + } + } + } + } + } + } + } + } + }, + "/v3/transactions/{transaction_id}/travel-rule/messages/": { + "get": { + "summary": "List Travel Rule messages", + "description": "Messages and document requests exchanged with the counterparty VASP about this transfer.", + "operationId": "listTravelRuleMessages", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "list_messages", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID that owns the transfer.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/transactions/{transaction_id}/travel-rule/messages/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.travel_rule.list_messages(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.travelRule.listMessages(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.TravelRule.ListMessages(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().travelRule().listMessages(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->travelRule->listMessages('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.travel_rule.list_messages(\"11111111-2222-3333-4444-555555555555\").each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.TravelRule.ListMessagesAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The messages, oldest first.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleMessage" + } + } + } + } + }, + "404": { + "description": "Unknown transaction or token, or no Travel Rule transfer on the transaction." + } + } + }, + "post": { + "summary": "Send a Travel Rule message or document request", + "description": "Sends a message or a document request to the counterparty VASP. Works on INTERNAL transfers (the other Didit customer sees it on its side of the pair) and EMAIL transfers (the counterparty is emailed and answers in the pickup portal). A transfer over TRP, GTR, TRUST, VerifyVASP or Sygna carries no messages, because those protocols have none. Limits: 100 messages per transfer, 5000 characters per body, 10 requested documents, 5 files of 10 MB each.", + "operationId": "createTravelRuleMessage", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "create_message", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit-stable transaction UUID that owns the transfer.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessageCreateRequest" + }, + "example": { + "kind": "DOCUMENT_REQUEST", + "body": "Please confirm the date of birth of the beneficiary.", + "requested_documents": [ + "Proof of address" + ] + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessageCreateRequest" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/transactions/{transaction_id}/travel-rule/messages/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"kind\": \"DOCUMENT_REQUEST\", \"body\": \"Please confirm the date of birth of the beneficiary.\", \"requested_documents\": [\"Proof of address\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.travel_rule.create_message(\n \"11111111-2222-3333-4444-555555555555\",\n kind=\"DOCUMENT_REQUEST\",\n body=\"Please confirm the date of birth of the beneficiary.\",\n requested_documents=[\"Proof of address\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.travelRule.createMessage(\"11111111-2222-3333-4444-555555555555\", {\n kind: \"DOCUMENT_REQUEST\",\n body: \"Please confirm the date of birth of the beneficiary.\",\n requested_documents: [\"Proof of address\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.TravelRule.CreateMessage(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleMessageCreateRequest{\n\tKind: didit.Some(didit.TravelRuleMessageCreateRequestKind(\"DOCUMENT_REQUEST\")),\n\tBody: didit.String(\"Please confirm the date of birth of the beneficiary.\"),\n\tRequestedDocuments: didit.Some([]string{\"Proof of address\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.TravelRuleMessageCreateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().travelRule().createMessage(\"11111111-2222-3333-4444-555555555555\", TravelRuleMessageCreateRequest.builder()\n .kind(TravelRuleMessageCreateRequest.Kind.of(\"DOCUMENT_REQUEST\"))\n .body(\"Please confirm the date of birth of the beneficiary.\")\n .requestedDocuments(List.of(\"Proof of address\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->travelRule->createMessage('11111111-2222-3333-4444-555555555555', [\n 'kind' => 'DOCUMENT_REQUEST',\n 'body' => 'Please confirm the date of birth of the beneficiary.',\n 'requested_documents' => ['Proof of address'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.travel_rule.create_message(\n \"11111111-2222-3333-4444-555555555555\",\n kind: \"DOCUMENT_REQUEST\",\n body: \"Please confirm the date of birth of the beneficiary.\",\n requested_documents: [\"Proof of address\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.TravelRule.CreateMessageAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleMessageCreateRequest\n{\n Kind = new TravelRuleMessageCreateRequestKind(\"DOCUMENT_REQUEST\"),\n Body = \"Please confirm the date of birth of the beneficiary.\",\n RequestedDocuments = new string[] { \"Proof of address\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The stored message.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessage" + } + } + } + }, + "400": { + "description": "Invalid body, a file type or size that is not accepted, more than 100 messages on the transfer, or a `reply_to` that is not a message of this transfer.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "requested_documents": [ + "Name at least one document the counterparty should send." + ] + } + } + } + }, + "404": { + "description": "Unknown transaction or token, or no Travel Rule transfer on the transaction." + }, + "409": { + "description": "No message can reach the counterparty. `detail` gives the reason: the transfer runs over a network rail (TRP, GTR, TRUST, VerifyVASP or Sygna), it has no counterparty yet, an INTERNAL transfer's data did not match the wallet holder so nothing reached the recipient, or an EMAIL transfer's pickup link has expired or the transfer is closed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/pickup/{token}/": { + "get": { + "summary": "Get email-rail pickup info", + "description": "Public, unauthenticated. Lets a non-Didit counterparty VASP preview the pending exchange from the emailed pickup link. The token itself is the authentication.", + "operationId": "getTravelRulePickup", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_pickup", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "contact", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "The link's `?contact=` value, the contact the link was sent to. Echoed back as `contact` unchanged; it is not validated." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/pickup/{token}/" + } + ], + "responses": { + "200": { + "description": "Pickup preview.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRulePickupInfo" + }, + "example": { + "reference": "33333333-4444-5555-6666-777777777777", + "requested_at": "2026-07-05T10:00:00Z", + "originator_vasp_name": "Origin CASP SL", + "originator_vasp_lei": "529900EXAMPLEORIG001", + "recipient_vasp_name": "Example Counterparty VASP", + "language": "es", + "amount": "0.250000000000000000", + "currency": "ETH", + "fiat_amount": "", + "fiat_currency": "", + "chain": "ETH", + "beneficiary_wallet": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb", + "status": "AWAITING_COUNTERPARTY", + "ivms_version": "ivms101.2023", + "deadline_at": "2026-07-07T10:00:00Z", + "reminders_sent": 0, + "contact": "", + "decline_reasons": [ + "data_mismatch", + "not_our_customer", + "wallet_not_hosted", + "sanctions", + "risk_appetite", + "jurisdiction", + "insufficient_data", + "other" + ], + "join_invitation": { + "eligible": false, + "url": "" + }, + "originator_data": { + "name": "Jane Doe", + "wallet_address": "0x77ccff0cc624a53cd8b8d8703ced826710211ec7" + }, + "beneficiary_data": { + "name": "Ana Diaz", + "wallet_address": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb" + }, + "ivms_request": { + "originator": { + "originatorPersons": [ + { + "naturalPerson": { + "name": { + "nameIdentifier": [ + { + "primaryIdentifier": "Doe", + "secondaryIdentifier": "Jane", + "nameIdentifierType": "LEGL" + } + ] + } + } + } + ], + "accountNumber": [ + "0x77ccff0cc624a53cd8b8d8703ced826710211ec7" + ] + }, + "beneficiary": { + "beneficiaryPersons": [ + { + "naturalPerson": { + "name": { + "nameIdentifier": [ + { + "primaryIdentifier": "Diaz", + "secondaryIdentifier": "Ana", + "nameIdentifierType": "LEGL" + } + ] + } + } + } + ], + "accountNumber": [ + "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb" + ] + } + }, + "data_masked": false + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The window has closed: the counterparty already answered, the transfer left AWAITING_COUNTERPARTY, or the deadline passed." + } + } + } + }, + "/v3/travel-rule/pickup/{token}/respond/": { + "post": { + "summary": "Respond to an email-rail pickup", + "description": "Public, unauthenticated. Accepts or declines the exchange from the emailed pickup link. On accept, beneficiary_name is checked against the originator's name_matching_strictness policy.", + "operationId": "respondTravelRulePickup", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "respond_pickup", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "contact", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "The link's `?contact=` value. Used when the body sends no `contact`." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRulePickupRespondRequest" + }, + "example": { + "decision": "accept", + "beneficiary_name": "Carlos Ruiz" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/pickup/{token}/respond/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"decision\": \"accept\", \"beneficiary_name\": \"Carlos Ruiz\"}'" + } + ], + "responses": { + "200": { + "description": "The resulting exchange status: COMPLETED or COUNTERPARTY_MISMATCHED_DATA after an accept, COUNTERPARTY_VASP_GENERAL_DECLINE after a decline, which also echoes `decline_reason`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "decline_reason": { + "type": "string", + "description": "Present after a decline." + } + } + }, + "examples": { + "accepted": { + "value": { + "status": "COMPLETED" + } + }, + "declined": { + "value": { + "status": "COUNTERPARTY_VASP_GENERAL_DECLINE", + "decline_reason": "not_our_customer" + } + } + } + } + } + }, + "400": { + "description": "A field is invalid: for example `decision` missing or not `accept` or `decline`, `decline_detail` missing on a decline with `decline_reason: other`, an `ivms_response` that is not an IVMS 101 object or is longer than 16384 characters, or a `contact` in the body that is not a UUID (`{\"contact\": [\"Must be a valid UUID.\"]}`).", + "content": { + "application/json": { + "example": { + "decline_detail": [ + "Say what the reason is when you choose another reason." + ] + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The window has closed: the counterparty already answered, the transfer left AWAITING_COUNTERPARTY, or the deadline passed." + } + } + } + }, + "/v3/travel-rule/pickup/{token}/messages/": { + "get": { + "summary": "List pickup messages", + "description": "Public, unauthenticated. The messages of an EMAIL-rail transfer as the counterparty VASP reads them: what the customer sent is `INBOUND` and is signed with the customer's VASP name. The token is the authentication.", + "operationId": "listTravelRulePickupMessages", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "list_pickup_messages", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/pickup/{token}/messages/" + } + ], + "responses": { + "200": { + "description": "The messages, oldest first, from the counterparty's side.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleMessage" + } + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The pickup link has expired or the transfer is closed." + } + } + }, + "post": { + "summary": "Reply in the pickup portal", + "description": "Public, unauthenticated. The counterparty VASP writes back on an EMAIL-rail transfer. Same body as the transaction's messages; attachments must be PDF, PNG, JPEG or WebP. The token is the authentication.", + "operationId": "createTravelRulePickupMessage", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "create_pickup_message", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessageCreateRequest" + }, + "example": { + "kind": "MESSAGE", + "body": "Attached is the proof of address of the wallet holder." + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessageCreateRequest" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/pickup/{token}/messages/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"kind\": \"MESSAGE\", \"body\": \"Attached is the proof of address of the wallet holder.\"}'" + } + ], + "responses": { + "201": { + "description": "The stored message.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleMessage" + } + } + } + }, + "400": { + "description": "Invalid body, a file type or size that is not accepted, more than 100 messages on the transfer, or a `reply_to` that is not a message of this transfer.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "requested_documents": [ + "Name at least one document the counterparty should send." + ] + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The pickup link has expired or the transfer is closed." + } + } + } + }, + "/v3/travel-rule/inbound/": { + "post": { + "summary": "Register an after-deposit (sunrise) inbound transfer", + "description": "Register Travel Rule data for a crypto deposit that already settled on-chain before any exchange took place. Creates an inbound travelRule transaction (or reuses an unclaimed deposit) and resolves the transfer against your wallet address book. For a newly created transaction, Didit sends transaction.created first and the travel_rule.status.updated webhooks after it.", + "operationId": "registerTravelRuleInbound", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "register_inbound", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleInboundRequest" + }, + "example": { + "chain": "ETH", + "txid": "0xdeposit-hash", + "wallet_address": "0xYourDepositWallet", + "amount": "500.00", + "currency": "USDC", + "originator_data": { + "name": "Origin CASP SL" + }, + "beneficiary_data": { + "name": "Ana Diaz" + }, + "originating_vasp": { + "name": "Origin CASP SL", + "lei": "", + "travel_address": "ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/inbound/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"chain\":\"ETH\",\"txid\":\"0xdeposit-hash\",\"wallet_address\":\"0xYourDepositWallet\",\"amount\":\"500.00\",\"currency\":\"USDC\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.register_inbound(\n chain=\"ETH\",\n txid=\"0xdeposit-hash\",\n wallet_address=\"0xYourDepositWallet\",\n amount=\"500.00\",\n currency=\"USDC\",\n originator_data={\"name\": \"Origin CASP SL\"},\n beneficiary_data={\"name\": \"Ana Diaz\"},\n originating_vasp={\n \"name\": \"Origin CASP SL\",\n \"lei\": \"\",\n \"travel_address\": \"ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC\",\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.registerInbound({\n chain: \"ETH\",\n txid: \"0xdeposit-hash\",\n wallet_address: \"0xYourDepositWallet\",\n amount: \"500.00\",\n currency: \"USDC\",\n originator_data: { name: \"Origin CASP SL\" },\n beneficiary_data: { name: \"Ana Diaz\" },\n originating_vasp: {\n name: \"Origin CASP SL\",\n lei: \"\",\n travel_address: \"ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC\",\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.RegisterInbound(ctx, didit.TravelRuleInboundRequest{\n\tChain: \"ETH\",\n\tTxid: \"0xdeposit-hash\",\n\tWalletAddress: \"0xYourDepositWallet\",\n\tAmount: \"500.00\",\n\tCurrency: \"USDC\",\n\tOriginatorData: didit.Some(map[string]any{\"name\": \"Origin CASP SL\"}),\n\tBeneficiaryData: didit.Some(map[string]any{\"name\": \"Ana Diaz\"}),\n\tOriginatingVASP: didit.Some(didit.TravelRuleInboundRequestOriginatingVASP{\n\t\tName: didit.String(\"Origin CASP SL\"),\n\t\tLei: didit.String(\"\"),\n\t\tTravelAddress: didit.String(\"ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC\"),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.TravelRuleInboundRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().registerInbound(TravelRuleInboundRequest.builder()\n .chain(\"ETH\")\n .txid(\"0xdeposit-hash\")\n .walletAddress(\"0xYourDepositWallet\")\n .amount(\"500.00\")\n .currency(\"USDC\")\n .originatorData(Map.of(\"name\", \"Origin CASP SL\"))\n .beneficiaryData(Map.of(\"name\", \"Ana Diaz\"))\n .originatingVasp(TravelRuleInboundRequest.OriginatingVasp.builder()\n .name(\"Origin CASP SL\")\n .lei(\"\")\n .travelAddress(\"ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC\")\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->registerInbound([\n 'chain' => 'ETH',\n 'txid' => '0xdeposit-hash',\n 'wallet_address' => '0xYourDepositWallet',\n 'amount' => '500.00',\n 'currency' => 'USDC',\n 'originator_data' => ['name' => 'Origin CASP SL'],\n 'beneficiary_data' => ['name' => 'Ana Diaz'],\n 'originating_vasp' => [\n 'name' => 'Origin CASP SL',\n 'lei' => '',\n 'travel_address' => 'ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC',\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.register_inbound(\n chain: \"ETH\",\n txid: \"0xdeposit-hash\",\n wallet_address: \"0xYourDepositWallet\",\n amount: \"500.00\",\n currency: \"USDC\",\n originator_data: { name: \"Origin CASP SL\" },\n beneficiary_data: { name: \"Ana Diaz\" },\n originating_vasp: {\n name: \"Origin CASP SL\",\n lei: \"\",\n travel_address: \"ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC\",\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.RegisterInboundAsync(new TravelRuleInboundRequest\n{\n Chain = \"ETH\",\n Txid = \"0xdeposit-hash\",\n WalletAddress = \"0xYourDepositWallet\",\n Amount = \"500.00\",\n Currency = \"USDC\",\n OriginatorData = new Dictionary { [\"name\"] = \"Origin CASP SL\" },\n BeneficiaryData = new Dictionary { [\"name\"] = \"Ana Diaz\" },\n OriginatingVasp = new TravelRuleInboundRequestOriginatingVasp\n {\n Name = \"Origin CASP SL\",\n Lei = \"\",\n TravelAddress = \"ta9JbXbsBSuS5ozArJRFVgvBHMVnDGUnrPPChSZYQsuZTn3kPrJXUQ7RSLUEJ5KjyPvr8C9sAxmPtZfGsQVKCi4n8ShxP9BkbDdbtjtA9aumNybAZ1BxC\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "A transfer for this txid and wallet_address was already registered; the existing one is returned unchanged (created is false).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleInboundResponse" + } + } + } + }, + "201": { + "description": "A new Travel Rule transfer was registered, on a new inbound transaction or on an existing unclaimed deposit (created is true).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleInboundResponse" + } + } + } + }, + "400": { + "description": "A required field (`chain`, `txid`, `wallet_address`, `amount`, `currency`) is missing, `originating_vasp.lei` is not a valid LEI, `originating_vasp.travel_address` is not a valid travel address, a memo, X-address or `utxo` the chain cannot carry, or `originator_data` / `beneficiary_data` that is not an object.", + "content": { + "application/json": { + "examples": { + "Missing field": { + "value": { + "txid": [ + "This field is required." + ] + } + }, + "Invalid LEI": { + "value": { + "originating_vasp": { + "lei": [ + "Invalid LEI: expected 20 alphanumeric characters (ISO 17442)." + ] + } + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/test-vasp/inbound/": { + "post": { + "summary": "Trigger an inbound transfer from the Didit Test VASP (sandbox)", + "description": "Sandbox applications only. The Didit Test VASP sends your application an inbound Travel Rule transfer, so you can exercise the receiving side of an exchange end to end before going live. The transfer is booked and decided by the same logic a real inbound transfer from a network runs against your wallet address book, so the inbox, the decision and the wallet ownership flow behave as they do live. Nothing leaves Didit. A live application receives 400. Send the same `reference` again to get the transfer the first call booked instead of a second one.", + "operationId": "triggerTravelRuleTestVaspInbound", + "x-didit-sdk": { + "resource": "transactions.travel_rule", + "method": "trigger_test_vasp_inbound", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTestVaspInboundRequest" + }, + "example": { + "beneficiary_wallet_address": "0xYourSandboxWallet", + "scenario": "accept", + "amount": "1", + "reference": "demo-001" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/test-vasp/inbound/ \\\n -H 'x-api-key: YOUR_SANDBOX_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"beneficiary_wallet_address\":\"0xYourSandboxWallet\",\"scenario\":\"accept\",\"reference\":\"demo-001\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.travel_rule.trigger_test_vasp_inbound(\n beneficiary_wallet_address=\"0xYourSandboxWallet\",\n scenario=\"accept\",\n amount=\"1\",\n reference=\"demo-001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.travelRule.triggerTestVaspInbound({\n beneficiary_wallet_address: \"0xYourSandboxWallet\",\n scenario: \"accept\",\n amount: \"1\",\n reference: \"demo-001\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.TravelRule.TriggerTestVASPInbound(ctx, didit.TravelRuleTestVASPInboundRequest{\n\tBeneficiaryWalletAddress: \"0xYourSandboxWallet\",\n\tScenario: didit.Some(didit.TravelRuleTestVASPInboundRequestScenario(\"accept\")),\n\tAmount: didit.String(\"1\"),\n\tReference: didit.String(\"demo-001\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleTestVaspInboundRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().travelRule().triggerTestVaspInbound(TravelRuleTestVaspInboundRequest.builder()\n .beneficiaryWalletAddress(\"0xYourSandboxWallet\")\n .scenario(TravelRuleTestVaspInboundRequest.Scenario.of(\"accept\"))\n .amount(\"1\")\n .reference(\"demo-001\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->travelRule->triggerTestVaspInbound([\n 'beneficiary_wallet_address' => '0xYourSandboxWallet',\n 'scenario' => 'accept',\n 'amount' => '1',\n 'reference' => 'demo-001',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.travel_rule.trigger_test_vasp_inbound(\n beneficiary_wallet_address: \"0xYourSandboxWallet\",\n scenario: \"accept\",\n amount: \"1\",\n reference: \"demo-001\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.TravelRule.TriggerTestVaspInboundAsync(new TravelRuleTestVaspInboundRequest\n{\n BeneficiaryWalletAddress = \"0xYourSandboxWallet\",\n Scenario = new TravelRuleTestVaspInboundRequestScenario(\"accept\"),\n Amount = \"1\",\n Reference = \"demo-001\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The inbound transfer was booked and decided. A repeat with the same `reference` returns the same body.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleTestVaspInboundResponse" + } + } + } + }, + "400": { + "description": "Invalid body, an unknown or unsupported `scenario`, or the application is a live application.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "409": { + "description": "The transfer could not be booked, for example because Travel Rule is not enabled for the application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/widget-session/": { + "post": { + "summary": "Mint a wallet-ownership widget session", + "description": "Mint a hosted wallet-ownership widget URL/token. Send the customer to the returned url to prove control of a wallet (message signing, Satoshi test, screenshot, or self-declaration). The widget is web-only in this release - open the url in the system browser or a custom tab, never inside a mobile SDK webview.", + "operationId": "createTravelRuleWidgetSession", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "create_widget_session", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetSessionCreate" + }, + "example": { + "wallet_address": "0xd3b62acb76fb27a7f8961ec40fa11746b113aca9", + "chain": "ETH", + "holder_name": "Jane Doe", + "vendor_data": "user-042", + "transaction_id": "99999999-aaaa-bbbb-cccc-dddddddddddd", + "expires_in_minutes": 60 + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/widget-session/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"wallet_address\":\"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\",\"chain\":\"ETH\",\"holder_name\":\"Jane Doe\",\"transaction_id\":\"99999999-aaaa-bbbb-cccc-dddddddddddd\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.create_widget_session(\n wallet_address=\"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\",\n chain=\"ETH\",\n holder_name=\"Jane Doe\",\n vendor_data=\"user-042\",\n transaction_id=\"99999999-aaaa-bbbb-cccc-dddddddddddd\",\n expires_in_minutes=60,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.createWidgetSession({\n wallet_address: \"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\",\n chain: \"ETH\",\n holder_name: \"Jane Doe\",\n vendor_data: \"user-042\",\n transaction_id: \"99999999-aaaa-bbbb-cccc-dddddddddddd\",\n expires_in_minutes: 60,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.CreateWidgetSession(ctx, didit.TravelRuleWidgetSessionCreate{\n\tWalletAddress: didit.String(\"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\"),\n\tChain: didit.String(\"ETH\"),\n\tHolderName: didit.String(\"Jane Doe\"),\n\tVendorData: didit.String(\"user-042\"),\n\tTransactionID: didit.String(\"99999999-aaaa-bbbb-cccc-dddddddddddd\"),\n\tExpiresInMinutes: didit.Int(60),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleWidgetSessionCreate;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().createWidgetSession(TravelRuleWidgetSessionCreate.builder()\n .walletAddress(\"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\")\n .chain(\"ETH\")\n .holderName(\"Jane Doe\")\n .vendorData(\"user-042\")\n .transactionId(\"99999999-aaaa-bbbb-cccc-dddddddddddd\")\n .expiresInMinutes(60L)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->createWidgetSession([\n 'wallet_address' => '0xd3b62acb76fb27a7f8961ec40fa11746b113aca9',\n 'chain' => 'ETH',\n 'holder_name' => 'Jane Doe',\n 'vendor_data' => 'user-042',\n 'transaction_id' => '99999999-aaaa-bbbb-cccc-dddddddddddd',\n 'expires_in_minutes' => 60,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.create_widget_session(\n wallet_address: \"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\",\n chain: \"ETH\",\n holder_name: \"Jane Doe\",\n vendor_data: \"user-042\",\n transaction_id: \"99999999-aaaa-bbbb-cccc-dddddddddddd\",\n expires_in_minutes: 60\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.CreateWidgetSessionAsync(new TravelRuleWidgetSessionCreate\n{\n WalletAddress = \"0xd3b62acb76fb27a7f8961ec40fa11746b113aca9\",\n Chain = \"ETH\",\n HolderName = \"Jane Doe\",\n VendorData = \"user-042\",\n TransactionId = \"99999999-aaaa-bbbb-cccc-dddddddddddd\",\n ExpiresInMinutes = 60,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The minted widget session.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetSessionResponse" + } + } + } + }, + "400": { + "description": "Invalid body: `callback_url` is not an absolute http(s) URL, `expires_in_minutes` is outside 5 to 10080, `transaction_id` is not a UUID, or a field is longer than its limit.", + "content": { + "application/json": { + "examples": { + "Invalid callback_url": { + "value": { + "callback_url": [ + "callback_url must be an absolute http(s) URL." + ] + } + } + } + } + } + }, + "404": { + "description": "`transaction_id` does not name a Travel Rule transfer in this application." + } + } + } + }, + "/v3/travel-rule/widget/{token}/": { + "get": { + "summary": "Get wallet-ownership widget context", + "description": "Public read endpoint keyed by the widget session token - no API key required. Returns the widget context plus the status of every proof, so you can poll it to detect completion; completed_at becomes non-null once a proof is verified. A SATOSHI_TEST proof can sit at status PENDING while its deposit is seen on-chain but unconfirmed - a background job automatically rechecks it and completes it once the deposit confirms, so no client action is needed while it waits. Each proof carries the fields its method needs: deposit_address, expected_amount, and its own expires_at for SATOSHI_TEST; challenge for MESSAGE_SIGNING. Prefer the travel_rule.status.updated webhook over polling when the session is linked to a transfer.", + "operationId": "getTravelRuleWidgetContext", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "retrieve_widget_context", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/widget/{token}/" + } + ], + "responses": { + "200": { + "description": "The widget context and proof statuses.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetContext" + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The session expired without completing." + } + } + } + }, + "/v3/travel-rule/widget/{token}/vasps/": { + "get": { + "summary": "Search VASPs from the wallet widget", + "description": "Public read endpoint keyed by the widget session token, no API key required: the VASP picker of the end user's widget. Same search and filters as GET /v3/travel-rule/vasps/ for the widget's application, answering only who each VASP is and whether it can be reached.", + "operationId": "listTravelRuleWidgetVasps", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "list_widget_vasps", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + }, + { + "name": "search", + "in": "query", + "schema": { + "type": "string", + "maxLength": 255 + }, + "description": "A name or alias fragment (case-insensitive), an exact LEI, or a domain (example.com or a URL on it). Didit network members match on their legal name, LEI or the domain of their compliance email." + }, + { + "name": "network", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "TRISA" + ] + }, + "description": "Only directory entries a network's member directory currently lists." + }, + { + "name": "jurisdiction", + "in": "query", + "schema": { + "type": "string" + }, + "description": "ISO 3166 alpha-2 or alpha-3 country code. 400 for anything else." + }, + { + "name": "reachable", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "true: only VASPs your application has an available route to (Didit network members included). false: only directory entries it cannot reach today." + }, + { + "name": "kind", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "directory", + "didit" + ] + }, + "description": "Only directory entries, or only Didit network members." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl 'https://verification.didit.me/v3/travel-rule/widget/{token}/vasps/?search=counterparty'" + } + ], + "responses": { + "200": { + "description": "Paginated, name-sorted results.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VaspDirectoryWidgetEntry" + } + } + } + } + } + } + }, + "400": { + "description": "A filter is not valid: the body names each field, for example an unknown network or country code.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "jurisdiction": [ + "Use an ISO 3166 alpha-2 or alpha-3 country code." + ], + "network": [ + "\"SWIFT\" is not a valid choice." + ] + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "410": { + "description": "The session expired." + } + } + }, + "post": { + "summary": "Pick the counterparty VASP in the wallet widget", + "description": "Public write endpoint keyed by the widget session token, no API key required: the end user chose the destination VASP that automatic discovery could not name. Only an outbound transfer in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE or NOT_ENOUGH_COUNTERPARTY_DATA accepts it. The transfer is routed to that VASP and sent again; automatic discovery never overrides the pick.", + "operationId": "selectTravelRuleWidgetVasp", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "select_widget_vasp", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "vasp" + ], + "properties": { + "vasp": { + "type": "string", + "format": "uuid", + "description": "The `id` of a VASP from `GET /v3/travel-rule/widget/{token}/vasps/`." + } + } + }, + "example": { + "vasp": "9f1c2b34-5d6e-4f70-8a91-b2c3d4e5f607" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/widget/{token}/vasps/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"vasp\": \"9f1c2b34-5d6e-4f70-8a91-b2c3d4e5f607\"}'" + } + ], + "responses": { + "200": { + "description": "The pick was recorded and the transfer is being sent again.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "transfer_status": { + "type": "string", + "description": "The transfer status after the pick." + }, + "counterparty_vasp": { + "type": "string", + "description": "The name of the VASP the end user picked." + } + } + }, + "example": { + "detail": "Counterparty VASP selected; the transfer is being resent.", + "transfer_status": "AWAITING_COUNTERPARTY", + "counterparty_vasp": "Northwind Exchange" + } + } + } + }, + "400": { + "description": "`vasp` is missing, or no live directory entry has that id.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "vasp": [ + "No directory entry has this id." + ] + } + } + } + }, + "404": { + "description": "Unknown token." + }, + "409": { + "description": "The widget session is not linked to a Travel Rule transfer, or the transfer is not an outbound one in a status that accepts a counterparty." + }, + "410": { + "description": "The session expired." + } + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-users/toggle-ongoing-monitoring/": { + "patch": { + "summary": "Toggle AML ongoing monitoring for users", + "description": "Enables or disables AML ongoing monitoring at the user level. Exactly one monitored AML screening is kept per user; users without any AML screening (for example imported users) get one created and screened first, billed as a regular AML screening. Set estimate_only to preview counts and costs without writing. Batches of 20 or fewer resolve inline (200, COMPLETED); larger batches return 202 and run asynchronously.", + "operationId": "toggle_vendor_users_ongoing_monitoring", + "x-didit-sdk": { + "resource": "users", + "method": "set_ongoing_monitoring", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleRequest" + }, + "example": { + "is_enabled": true, + "didit_internal_id_list": [ + "5b9a0000-0000-4000-8000-00000000f00d" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Estimate (when estimate_only) or the completed job for small batches processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger batches are processed asynchronously; poll the job endpoint with the returned job_id.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "403": { + "description": "Missing write:sessions privilege, or insufficient balance to enable (the estimate is included in the error body)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.users.set_ongoing_monitoring(\n is_enabled=True,\n didit_internal_id_list=[\"5b9a0000-0000-4000-8000-00000000f00d\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.users.setOngoingMonitoring({\n is_enabled: true,\n didit_internal_id_list: [\"5b9a0000-0000-4000-8000-00000000f00d\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Users.SetOngoingMonitoring(ctx, didit.OngoingMonitoringToggleRequest{\n\tIsEnabled: true,\n\tDiditInternalIDList: didit.Some([]string{\"5b9a0000-0000-4000-8000-00000000f00d\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.OngoingMonitoringToggleRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.users().setOngoingMonitoring(OngoingMonitoringToggleRequest.builder()\n .isEnabled(true)\n .diditInternalIdList(List.of(\"5b9a0000-0000-4000-8000-00000000f00d\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->setOngoingMonitoring([\n 'is_enabled' => true,\n 'didit_internal_id_list' => ['5b9a0000-0000-4000-8000-00000000f00d'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.users.set_ongoing_monitoring(\n is_enabled: true,\n didit_internal_id_list: [\"5b9a0000-0000-4000-8000-00000000f00d\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Users.SetOngoingMonitoringAsync(new OngoingMonitoringToggleRequest\n{\n IsEnabled = true,\n DiditInternalIdList = new string[] { \"5b9a0000-0000-4000-8000-00000000f00d\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-users/toggle-ongoing-monitoring/{job_id}/": { + "get": { + "summary": "Get AML ongoing monitoring job", + "description": "Progress and per-outcome counters of a monitoring toggle job. Poll every couple of seconds while status is PENDING or PROCESSING.", + "operationId": "get_vendor_users_ongoing_monitoring_job", + "x-didit-sdk": { + "resource": "users", + "method": "retrieve_ongoing_monitoring_job", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Users" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Monitoring job UUID returned by the toggle endpoint." + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "404": { + "description": "Unknown job for this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.users.retrieve_ongoing_monitoring_job(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.users.retrieveOngoingMonitoringJob(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Users.RetrieveOngoingMonitoringJob(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.users().retrieveOngoingMonitoringJob(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->retrieveOngoingMonitoringJob('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.users.retrieve_ongoing_monitoring_job(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Users.RetrieveOngoingMonitoringJobAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-businesses/toggle-ongoing-monitoring/": { + "patch": { + "summary": "Toggle AML ongoing monitoring for businesses", + "description": "Business-level equivalent of the user toggle: one monitored AML screening per business, with businesses lacking any AML screening screened first (company entity). Same estimate_only, inline fast-path and async job contract.", + "operationId": "toggle_vendor_businesses_ongoing_monitoring", + "x-didit-sdk": { + "resource": "businesses", + "method": "set_ongoing_monitoring", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleRequest" + }, + "example": { + "is_enabled": true, + "didit_internal_id_list": [ + "b0000000-0000-4000-8000-0000000000b1" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Estimate (when estimate_only) or the completed job for small batches processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger batches are processed asynchronously; poll the job endpoint with the returned job_id.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "403": { + "description": "Missing write:sessions privilege, or insufficient balance to enable (the estimate is included in the error body)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.businesses.set_ongoing_monitoring(\n is_enabled=True,\n didit_internal_id_list=[\"b0000000-0000-4000-8000-0000000000b1\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.businesses.setOngoingMonitoring({\n is_enabled: true,\n didit_internal_id_list: [\"b0000000-0000-4000-8000-0000000000b1\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.SetOngoingMonitoring(ctx, didit.OngoingMonitoringToggleRequest{\n\tIsEnabled: true,\n\tDiditInternalIDList: didit.Some([]string{\"b0000000-0000-4000-8000-0000000000b1\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.OngoingMonitoringToggleRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.businesses().setOngoingMonitoring(OngoingMonitoringToggleRequest.builder()\n .isEnabled(true)\n .diditInternalIdList(List.of(\"b0000000-0000-4000-8000-0000000000b1\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->setOngoingMonitoring([\n 'is_enabled' => true,\n 'didit_internal_id_list' => ['b0000000-0000-4000-8000-0000000000b1'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.businesses.set_ongoing_monitoring(\n is_enabled: true,\n didit_internal_id_list: [\"b0000000-0000-4000-8000-0000000000b1\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.SetOngoingMonitoringAsync(new OngoingMonitoringToggleRequest\n{\n IsEnabled = true,\n DiditInternalIdList = new string[] { \"b0000000-0000-4000-8000-0000000000b1\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/vendor-businesses/toggle-ongoing-monitoring/{job_id}/": { + "get": { + "summary": "Get AML ongoing monitoring job", + "description": "Progress and per-outcome counters of a monitoring toggle job. Poll every couple of seconds while status is PENDING or PROCESSING.", + "operationId": "get_vendor_businesses_ongoing_monitoring_job", + "x-didit-sdk": { + "resource": "businesses", + "method": "retrieve_ongoing_monitoring_job", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Monitoring job UUID returned by the toggle endpoint." + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "404": { + "description": "Unknown job for this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.businesses.retrieve_ongoing_monitoring_job(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.businesses.retrieveOngoingMonitoringJob(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.RetrieveOngoingMonitoringJob(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.businesses().retrieveOngoingMonitoringJob(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->retrieveOngoingMonitoringJob('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.businesses.retrieve_ongoing_monitoring_job(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.RetrieveOngoingMonitoringJobAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/businesses/toggle-ongoing-monitoring/": { + "patch": { + "summary": "Toggle AML ongoing monitoring for business sessions", + "description": "Session-level toggle for KYB verifications, addressed by session number. Sessions without an AML screening get one created and attached to the session itself. Same estimate_only, inline fast-path and async job contract as the user-level endpoints. Replaces the legacy synchronous behavior that skipped sessions without an AML screening.", + "operationId": "toggle_business_sessions_ongoing_monitoring", + "x-didit-sdk": { + "resource": "businesses", + "method": "set_session_ongoing_monitoring", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_enabled" + ], + "properties": { + "is_enabled": { + "type": "boolean", + "description": "true to enable monitoring, false to disable it." + }, + "estimate_only": { + "type": "boolean", + "default": false, + "description": "When true, returns the cost estimate and performs no writes." + }, + "update_all": { + "type": "boolean", + "default": false, + "description": "Apply to every entity of the application (scoped by the filter_* params when present)." + }, + "filter_search": { + "type": "string" + }, + "filter_status": { + "type": "array", + "items": { + "type": "string" + } + }, + "filter_country": { + "type": "string" + }, + "filter_date_from": { + "type": "string", + "format": "date" + }, + "filter_date_to": { + "type": "string", + "format": "date" + }, + "session_numbers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Business session numbers to toggle. Required unless update_all (or the legacy toggle_all alias) is true." + }, + "toggle_all": { + "type": "boolean", + "default": false, + "description": "Legacy alias of update_all." + }, + "filter_workflow_id": { + "type": "string" + } + } + }, + "example": { + "is_enabled": true, + "session_numbers": [ + "1001", + "1002" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Estimate (when estimate_only) or the completed job for small batches processed inline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringToggleResult" + } + } + } + }, + "202": { + "description": "Larger batches are processed asynchronously; poll the job endpoint with the returned job_id.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "403": { + "description": "Missing write:sessions privilege, or insufficient balance to enable (the estimate is included in the error body)." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.businesses.set_session_ongoing_monitoring(\n is_enabled=True,\n session_numbers=[\"1001\", \"1002\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.businesses.setSessionOngoingMonitoring({\n is_enabled: true,\n session_numbers: [\"1001\", \"1002\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.SetSessionOngoingMonitoring(ctx, didit.BusinessesSetSessionOngoingMonitoringParams{\n\tIsEnabled: true,\n\tSessionNumbers: didit.Some([]string{\"1001\", \"1002\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.BusinessesSetSessionOngoingMonitoringParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.businesses().setSessionOngoingMonitoring(BusinessesSetSessionOngoingMonitoringParams.builder()\n .isEnabled(true)\n .sessionNumbers(List.of(\"1001\", \"1002\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->setSessionOngoingMonitoring([\n 'is_enabled' => true,\n 'session_numbers' => ['1001', '1002'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.businesses.set_session_ongoing_monitoring(\n is_enabled: true,\n session_numbers: [\"1001\", \"1002\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.SetSessionOngoingMonitoringAsync(new BusinessesSetSessionOngoingMonitoringParams\n{\n IsEnabled = true,\n SessionNumbers = new string[] { \"1001\", \"1002\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/businesses/toggle-ongoing-monitoring/{job_id}/": { + "get": { + "summary": "Get AML ongoing monitoring job", + "description": "Progress and per-outcome counters of a monitoring toggle job. Poll every couple of seconds while status is PENDING or PROCESSING.", + "operationId": "get_business_sessions_ongoing_monitoring_job", + "x-didit-sdk": { + "resource": "businesses", + "method": "retrieve_session_ongoing_monitoring_job", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "job_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Monitoring job UUID returned by the toggle endpoint." + } + ], + "responses": { + "200": { + "description": "The job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + } + } + }, + "404": { + "description": "Unknown job for this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.businesses.retrieve_session_ongoing_monitoring_job(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.businesses.retrieveSessionOngoingMonitoringJob(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.RetrieveSessionOngoingMonitoringJob(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.businesses().retrieveSessionOngoingMonitoringJob(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->retrieveSessionOngoingMonitoringJob('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.businesses.retrieve_session_ongoing_monitoring_job(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.RetrieveSessionOngoingMonitoringJobAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/": { + "get": { + "summary": "List cases", + "description": "List cases for the application, newest first. Supports filtering, search, and sorting; see query parameters. Paginated with `limit`/`offset`.", + "operationId": "list_cases", + "x-didit-sdk": { + "resource": "cases", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size. Defaults to 50." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset of the first record." + }, + { + "name": "status__in", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated statuses to include, e.g. OPEN,AWAITING_USER." + }, + { + "name": "priority", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated priorities to include." + }, + { + "name": "source__in", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated sources to include." + }, + { + "name": "blueprint", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter to cases on this blueprint." + }, + { + "name": "assigned_to", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter to cases assigned to this user." + }, + { + "name": "unassigned", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "true for unassigned cases, false for assigned cases." + }, + { + "name": "tag", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Filter to cases carrying this exact tag." + }, + { + "name": "search", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Matches title, case_number, tag, subject name/UUID/external ID, linked session UUID, or linked transaction UUID." + }, + { + "name": "date_from", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Created-at lower bound (inclusive)." + }, + { + "name": "date_to", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Created-at upper bound (inclusive)." + }, + { + "name": "due_before", + "in": "query", + "schema": { + "type": "string", + "format": "date-time" + }, + "description": "due_at upper bound." + }, + { + "name": "overdue", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "true to return only overdue open/awaiting-user cases." + }, + { + "name": "ordering", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "created_at", + "-created_at", + "due_at", + "-due_at" + ], + "default": "-created_at" + }, + "description": "Sort order. due_at sorts nulls last." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/?status__in=OPEN,AWAITING_USER&ordering=-due_at' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.ListAutoPaging(ctx, didit.CasesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated case list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows. Exact up to 100, capped at 100 beyond that for performance. Rely on `next` being null to detect the last page." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or null." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or null." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseListItem" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create case", + "description": "Create a case anchored to exactly one subject (a user or a business), directly or by deriving the subject from an attached session/transaction link. Source is always `MANUAL` for API-created cases.", + "operationId": "create_case", + "x-didit-sdk": { + "resource": "cases", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "title" + ], + "properties": { + "title": { + "type": "string", + "maxLength": 255 + }, + "blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "CaseBlueprint UUID. Omit for no blueprint." + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ], + "default": "MEDIUM" + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "subject": { + "type": "object", + "description": "Required unless links is provided; the case's exactly-one subject.", + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "business" + ] + }, + "uuid": { + "type": "string", + "format": "uuid" + } + } + }, + "links": { + "type": "array", + "description": "Sessions/business sessions/transactions to attach on creation, e.g. [{\"entity_type\": \"transaction\", \"entity_uuid\": \"...\"}]. When subject is omitted, the subject is derived from the first link's owner.", + "items": { + "type": "object" + } + }, + "assigned_to": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "User UUID to assign immediately." + }, + "review_duplicates": { + "type": "boolean", + "default": false, + "description": "Recheck existing investigations before creating the case. When true, a subject with existing cases requires a nonblank separate_incident_reason. The console always enables this protection. Omitting it preserves existing API behavior." + }, + "separate_incident_reason": { + "type": "string", + "maxLength": 1000 + }, + "source_context": { + "$ref": "#/components/schemas/CaseDuplicateCaseFraudSourceContextRequest" + } + } + }, + "example": { + "title": "Manual - Jane Doe", + "blueprint": "b1de0000-0000-4000-8000-000000000002", + "priority": "HIGH", + "subject": { + "type": "user", + "uuid": "5b9a0000-0000-4000-8000-00000000f00d" + }, + "tags": [ + "review" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"title\": \"Manual - Jane Doe\", \"blueprint\": \"b1de0000-0000-4000-8000-000000000002\", \"priority\": \"HIGH\", \"subject\": {\"type\": \"user\", \"uuid\": \"5b9a0000-0000-4000-8000-00000000f00d\"}, \"tags\": [\"review\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.create(\n title=\"Manual - Jane Doe\",\n blueprint=\"b1de0000-0000-4000-8000-000000000002\",\n priority=\"HIGH\",\n tags=[\"review\"],\n subject={\n \"type\": \"user\",\n \"uuid\": \"5b9a0000-0000-4000-8000-00000000f00d\",\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.create({\n title: \"Manual - Jane Doe\",\n blueprint: \"b1de0000-0000-4000-8000-000000000002\",\n priority: \"HIGH\",\n tags: [\"review\"],\n subject: {\n type: \"user\",\n uuid: \"5b9a0000-0000-4000-8000-00000000f00d\",\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Create(ctx, didit.CasesCreateParams{\n\tTitle: \"Manual - Jane Doe\",\n\tBlueprint: didit.String(\"b1de0000-0000-4000-8000-000000000002\"),\n\tPriority: didit.Some(didit.CasesCreateParamsPriority(\"HIGH\")),\n\tTags: didit.Some([]string{\"review\"}),\n\tSubject: didit.Some(didit.CasesCreateParamsSubject{\n\t\tType: didit.Some(didit.CasesCreateParamsSubjectType(\"user\")),\n\t\tUUID: didit.String(\"5b9a0000-0000-4000-8000-00000000f00d\"),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CasesCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().create(CasesCreateParams.builder()\n .title(\"Manual - Jane Doe\")\n .blueprint(\"b1de0000-0000-4000-8000-000000000002\")\n .priority(CasesCreateParams.Priority.of(\"HIGH\"))\n .tags(List.of(\"review\"))\n .subject(CasesCreateParams.Subject.builder()\n .type(CasesCreateParams.Subject.Type.of(\"user\"))\n .uuid(\"5b9a0000-0000-4000-8000-00000000f00d\")\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->create([\n 'title' => 'Manual - Jane Doe',\n 'blueprint' => 'b1de0000-0000-4000-8000-000000000002',\n 'priority' => 'HIGH',\n 'tags' => ['review'],\n 'subject' => [\n 'type' => 'user',\n 'uuid' => '5b9a0000-0000-4000-8000-00000000f00d',\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.create(\n title: \"Manual - Jane Doe\",\n blueprint: \"b1de0000-0000-4000-8000-000000000002\",\n priority: \"HIGH\",\n tags: [\"review\"],\n subject: {\n type: \"user\",\n uuid: \"5b9a0000-0000-4000-8000-00000000f00d\",\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.CreateAsync(new CasesCreateParams\n{\n Title = \"Manual - Jane Doe\",\n Blueprint = \"b1de0000-0000-4000-8000-000000000002\",\n Priority = new CasesCreateParamsPriority(\"HIGH\"),\n Tags = new string[] { \"review\" },\n Subject = new CasesCreateParamsSubject\n {\n Type = new CasesCreateParamsSubjectType(\"user\"),\n Uuid = \"5b9a0000-0000-4000-8000-00000000f00d\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Case created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "400": { + "description": "Neither subject nor links provided, or the subject/blueprint/assignee was not found.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "409": { + "description": "Duplicate review is enabled and an existing case was found without a separate-incident reason. No case was created." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/": { + "get": { + "summary": "Get case", + "description": "Retrieve a case with its subject, blueprint, notes, events, links, and checklist.", + "operationId": "get_case", + "x-didit-sdk": { + "resource": "cases", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.retrieve(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.retrieve(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Retrieve(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().retrieve(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->retrieve('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.retrieve(\"c1a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RetrieveAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Case detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "404": { + "description": "Case (or nested resource) not found in this application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update case", + "description": "Update the case's title, priority, due date, tags, or owning review team. Send only the fields you want to change; fields you omit keep their value. `tags` replaces the whole tag list. `team` takes the uuid of an active review team of this application (list them with `GET .../cases/teams/`), or null to clear the team. Requires a full-access API key, or `write:cases` for a console member: a key with restricted access gets 403. Logs PRIORITY_CHANGED, DUE_DATE_CHANGED, TAG_ADDED, TAG_REMOVED and TEAM_CHANGED events as applicable; a title change is saved without a timeline event. Returns the updated case.", + "operationId": "update_case", + "x-didit-sdk": { + "resource": "cases", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string", + "maxLength": 255, + "description": "New case title." + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "New deadline. Send null to clear it." + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The complete tag list. It replaces the current tags." + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "UUID of the review team that owns the case. It must be an active team of this application. Send null to clear the team." + } + } + }, + "examples": { + "Change priority and deadline": { + "value": { + "priority": "HIGH", + "due_at": "2026-11-01T00:00:00Z" + } + }, + "Move the case to a team": { + "value": { + "team": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + }, + "Clear the team": { + "value": { + "team": null + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"priority\": \"HIGH\", \"due_at\": \"2026-08-01T00:00:00Z\"}'" + }, + { + "lang": "curl", + "label": "curl (change team)", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"team\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.update(\n \"c1a5e000-0000-4000-8000-000000000001\",\n priority=\"HIGH\",\n due_at=\"2026-11-01T00:00:00Z\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.update(\"c1a5e000-0000-4000-8000-000000000001\", {\n priority: \"HIGH\",\n due_at: \"2026-11-01T00:00:00Z\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Update(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesUpdateParams{\n\tPriority: didit.Some(didit.CasesUpdateParamsPriority(\"HIGH\")),\n\tDueAt: didit.Time(time.Date(2026, 11, 1, 0, 0, 0, 0, time.UTC)),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.OffsetDateTime;\nimport me.didit.Didit;\nimport me.didit.types.CasesUpdateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().update(\"c1a5e000-0000-4000-8000-000000000001\", CasesUpdateParams.builder()\n .priority(CasesUpdateParams.Priority.of(\"HIGH\"))\n .dueAt(OffsetDateTime.parse(\"2026-11-01T00:00:00Z\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->update('c1a5e000-0000-4000-8000-000000000001', [\n 'priority' => 'HIGH',\n 'due_at' => '2026-11-01T00:00:00Z',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.update(\n \"c1a5e000-0000-4000-8000-000000000001\",\n priority: \"HIGH\",\n due_at: \"2026-11-01T00:00:00Z\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.UpdateAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesUpdateParams\n{\n Priority = new CasesUpdateParamsPriority(\"HIGH\"),\n DueAt = DateTimeOffset.Parse(\"2026-11-01T00:00:00Z\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated case.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "400": { + "description": "A field failed validation. The body is keyed by field name. `team` is rejected when it is not an active review team of this application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "Team not active in this application": { + "value": { + "team": [ + "Choose an active team of this application." + ] + } + }, + "Invalid priority": { + "value": { + "priority": [ + "\"URGENT\" is not a valid choice." + ] + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Cases endpoints need a full-access key. A console member token needs `write:cases`.", + "content": { + "application/json": { + "examples": { + "Restricted API Key": { + "summary": "API key with restricted access", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "Missing Privilege": { + "summary": "Console member without write:cases, or a key of another application", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Case not found in this application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No Case matches the given query." + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete case", + "description": "Delete a case permanently. Its draft regulatory reports are deleted with it. Linked verifications, people, companies and transactions are not deleted. There is no undelete endpoint.\n\nIf the case has a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each report with `uuid`, `report_number`, `report_type` and `case_uuid`. Delete the reports, then repeat the request.", + "operationId": "delete_case", + "x-didit-sdk": { + "resource": "cases", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.cases.delete(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.cases.delete(\"c1a5e000-0000-4000-8000-000000000001\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.Cases.Delete(ctx, \"c1a5e000-0000-4000-8000-000000000001\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().delete(\"c1a5e000-0000-4000-8000-000000000001\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->delete('c1a5e000-0000-4000-8000-000000000001');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.delete(\"c1a5e000-0000-4000-8000-000000000001\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Cases.DeleteAsync(\"c1a5e000-0000-4000-8000-000000000001\");" + } + ], + "responses": { + "204": { + "description": "Case deleted." + }, + "403": { + "description": "The key cannot delete cases. It requires a full-access key with `write:cases`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "The case does not exist in this application or was already deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "409": { + "description": "The case has a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/assign/": { + "post": { + "summary": "Assign or unassign case", + "description": "Assign the case to a user, or unassign it by sending assignee_user_id: null. assignee_email/assignee_name auto-provision a User row when the assignee does not exist yet.", + "operationId": "assign_case", + "x-didit-sdk": { + "resource": "cases", + "method": "assign", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "assignee_user_id" + ], + "properties": { + "assignee_user_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "null to unassign." + }, + "assignee_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "assignee_name": { + "type": "string", + "nullable": true + } + } + }, + "example": { + "assignee_user_id": "9a000000-0000-4000-8000-0000000000aa" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/assign/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"assignee_user_id\": \"9a000000-0000-4000-8000-0000000000aa\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.assign(\n \"c1a5e000-0000-4000-8000-000000000001\",\n assignee_user_id=\"9a000000-0000-4000-8000-0000000000aa\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.assign(\"c1a5e000-0000-4000-8000-000000000001\", {\n assignee_user_id: \"9a000000-0000-4000-8000-0000000000aa\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Assign(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesAssignParams{\n\tAssigneeUserID: didit.String(\"9a000000-0000-4000-8000-0000000000aa\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesAssignParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().assign(\"c1a5e000-0000-4000-8000-000000000001\", CasesAssignParams.builder()\n .assigneeUserId(\"9a000000-0000-4000-8000-0000000000aa\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->assign('c1a5e000-0000-4000-8000-000000000001', [\n 'assignee_user_id' => '9a000000-0000-4000-8000-0000000000aa',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.assign(\n \"c1a5e000-0000-4000-8000-000000000001\",\n assignee_user_id: \"9a000000-0000-4000-8000-0000000000aa\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.AssignAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesAssignParams\n{\n AssigneeUserId = \"9a000000-0000-4000-8000-0000000000aa\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Case assigned/unassigned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/resolve/": { + "post": { + "summary": "Resolve case", + "description": "Resolve the case with one of five resolutions: FALSE_POSITIVE (no concern found), VALID_THREAT (concern confirmed), INCONCLUSIVE, REQUIREMENTS_NOT_MET or REQUIREMENTS_SATISFIED. Blocked with 400 if the blueprint's checklist is not optional and items remain incomplete, or if the blueprint requires a resolution note and none is given. When the case's blueprint has a four-eyes checker step (four_eyes_checker_blueprint), the resolution is staged (case.pending_resolution) and the case moves to the checker blueprint instead of resolving immediately; a different officer must call approve or reject-approval. Submit a four-eyes resolution as a console member: a resolution an API key submits has no recorded maker, so approve and reject-approval return 403 for every caller. On a four-eyes blueprint you can name the member who must approve with `checker`: then only that member can approve or reject the resolution, and the response's `checker` names them. Without `checker`, the resolution waits on the checker blueprint's queue. Sending `checker` for a blueprint without a checker step returns 400.", + "operationId": "resolve_case", + "x-didit-sdk": { + "resource": "cases", + "method": "resolve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "resolution" + ], + "properties": { + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ] + }, + "note": { + "type": "string", + "nullable": true, + "description": "Required when the blueprint sets require_resolution_note." + }, + "checker": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Four-eyes blueprints only. The user uuid of the member who must approve this resolution (the same id as `assigned_to.uuid` on a case, or `members[].user_id` on a review team). The member must have case access to this application and cannot be the officer who submits the resolution. Omit it or send null to leave the resolution to the checker queue." + } + } + }, + "examples": { + "Resolve": { + "value": { + "resolution": "FALSE_POSITIVE", + "note": "Confirmed legitimate activity after reviewing documents." + } + }, + "Four-eyes blueprint with a named checker": { + "value": { + "resolution": "VALID_THREAT", + "note": "Document tampering confirmed.", + "checker": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/resolve/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"resolution\": \"FALSE_POSITIVE\", \"note\": \"Confirmed legitimate activity after reviewing documents.\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.resolve(\n \"c1a5e000-0000-4000-8000-000000000001\",\n resolution=\"FALSE_POSITIVE\",\n note=\"Confirmed legitimate activity after reviewing documents.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.resolve(\"c1a5e000-0000-4000-8000-000000000001\", {\n resolution: \"FALSE_POSITIVE\",\n note: \"Confirmed legitimate activity after reviewing documents.\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Resolve(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesResolveParams{\n\tResolution: \"FALSE_POSITIVE\",\n\tNote: didit.String(\"Confirmed legitimate activity after reviewing documents.\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesResolveParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().resolve(\"c1a5e000-0000-4000-8000-000000000001\", CasesResolveParams.builder()\n .resolution(CasesResolveParams.Resolution.of(\"FALSE_POSITIVE\"))\n .note(\"Confirmed legitimate activity after reviewing documents.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->resolve('c1a5e000-0000-4000-8000-000000000001', [\n 'resolution' => 'FALSE_POSITIVE',\n 'note' => 'Confirmed legitimate activity after reviewing documents.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.resolve(\n \"c1a5e000-0000-4000-8000-000000000001\",\n resolution: \"FALSE_POSITIVE\",\n note: \"Confirmed legitimate activity after reviewing documents.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ResolveAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesResolveParams\n{\n Resolution = \"FALSE_POSITIVE\",\n Note = \"Confirmed legitimate activity after reviewing documents.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Resolved, or staged for 4-eyes approval.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "400": { + "description": "The resolution was refused. Non-field errors come as a list of strings: an incomplete checklist, a missing required note, or a `checker` that could not be checked or is not an active reviewer with case access to this application. `checker` errors come keyed by the field: the named member is the officer submitting the resolution, or the case's blueprint has no checker step.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + } + ] + }, + "examples": { + "Incomplete checklist": { + "value": [ + "Case has incomplete checklist items: Verify source of funds" + ] + }, + "Resolution note required": { + "value": [ + "A resolution note is required to resolve this case." + ] + }, + "Reviewer access could not be verified": { + "value": [ + "Reviewer access could not be verified. Try again." + ] + }, + "Checker is not an active reviewer": { + "value": [ + "Choose an active reviewer with case access to this application." + ] + }, + "Checker is the submitting officer": { + "value": { + "checker": [ + "The officer who submitted the resolution cannot be its checker." + ] + } + }, + "Policy has no checker step": { + "value": { + "checker": [ + "This review policy has no checker step, so the case resolves directly." + ] + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/reopen/": { + "post": { + "summary": "Reopen case", + "description": "Move an awaiting-user or resolved case back to open, clearing resolution, resolution_notes, resolved_at, and resolved_by_email.", + "operationId": "reopen_case", + "x-didit-sdk": { + "resource": "cases", + "method": "reopen", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/reopen/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.reopen(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.reopen(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Reopen(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().reopen(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->reopen('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.reopen(\"c1a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ReopenAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Reopen case.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/request-info/": { + "post": { + "summary": "Request info (awaiting user)", + "description": "Move an open case to awaiting_user while more information is collected from the verified user. Fails with 400 if a resolution is currently pending 4-eyes approval.", + "operationId": "request_case_info", + "x-didit-sdk": { + "resource": "cases", + "method": "request_info", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/request-info/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.request_info(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.requestInfo(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RequestInfo(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().requestInfo(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requestInfo('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.request_info(\"c1a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RequestInfoAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Request info (awaiting user).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/escalate/": { + "post": { + "summary": "Escalate case", + "description": "Move the case to its blueprint's configured escalation_blueprint. Requires the current blueprint to have escalation_enabled and an escalation_blueprint set, otherwise 400.", + "operationId": "escalate_case", + "x-didit-sdk": { + "resource": "cases", + "method": "escalate", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/escalate/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.escalate(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.escalate(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Escalate(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().escalate(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->escalate('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.escalate(\"c1a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.EscalateAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Escalate case.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/transfer/": { + "post": { + "summary": "Transfer case", + "description": "Move the case to a different, active blueprint of the same application. Requires the current blueprint to have transfer_enabled, otherwise 400.", + "operationId": "transfer_case", + "x-didit-sdk": { + "resource": "cases", + "method": "transfer", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "target_blueprint" + ], + "properties": { + "target_blueprint": { + "type": "string", + "format": "uuid" + } + } + }, + "example": { + "target_blueprint": "b1de0000-0000-4000-8000-000000000002" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/transfer/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"target_blueprint\": \"b1de0000-0000-4000-8000-000000000002\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.transfer(\n \"c1a5e000-0000-4000-8000-000000000001\",\n target_blueprint=\"b1de0000-0000-4000-8000-000000000002\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.transfer(\"c1a5e000-0000-4000-8000-000000000001\", {\n target_blueprint: \"b1de0000-0000-4000-8000-000000000002\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Transfer(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesTransferParams{\n\tTargetBlueprint: \"b1de0000-0000-4000-8000-000000000002\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesTransferParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().transfer(\"c1a5e000-0000-4000-8000-000000000001\", CasesTransferParams.builder()\n .targetBlueprint(\"b1de0000-0000-4000-8000-000000000002\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->transfer('c1a5e000-0000-4000-8000-000000000001', [\n 'target_blueprint' => 'b1de0000-0000-4000-8000-000000000002',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.transfer(\n \"c1a5e000-0000-4000-8000-000000000001\",\n target_blueprint: \"b1de0000-0000-4000-8000-000000000002\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.TransferAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesTransferParams\n{\n TargetBlueprint = \"b1de0000-0000-4000-8000-000000000002\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Case transferred.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/approve/": { + "post": { + "summary": "Approve staged resolution (4-eyes)", + "description": "Apply a resolution staged by resolve on a four-eyes blueprint. The caller must be a different officer than the one who submitted it, otherwise 403. When the resolution names a checker (the case's `checker`), only that member can approve it: any other caller, including an API key, gets 403.", + "operationId": "approve_case_resolution", + "x-didit-sdk": { + "resource": "cases", + "method": "approve_resolution", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/approve/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.approve_resolution(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.approveResolution(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.ApproveResolution(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().approveResolution(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->approveResolution('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.approve_resolution(\"c1a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ApproveResolutionAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Resolution applied.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "409": { + "description": "Case has no resolution pending approval.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "403": { + "description": "The caller cannot approve this resolution: they submitted it, the case has no recorded maker, or the resolution names a different checker. An API key always gets this 403 when a checker is named.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Caller submitted the resolution": { + "value": { + "detail": "The officer who submitted the resolution cannot approve it." + } + }, + "No recorded maker": { + "value": { + "detail": "Case has no recorded maker for this pending resolution; approve is not permitted." + } + }, + "Caller is not the named checker": { + "value": { + "detail": "Only the named checker can approve this resolution." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/reject-approval/": { + "post": { + "summary": "Reject staged resolution (4-eyes)", + "description": "Reject a resolution staged by resolve and return the case to its maker blueprint. The caller must be a different officer than the one who submitted it, otherwise 403. When the resolution names a checker (the case's `checker`), only that member can reject it: any other caller, including an API key, gets 403.", + "operationId": "reject_case_resolution", + "x-didit-sdk": { + "resource": "cases", + "method": "reject_resolution", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "notes": { + "type": "string", + "nullable": true + } + } + }, + "example": { + "notes": "Needs a clearer rationale before this can be approved." + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/reject-approval/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"notes\": \"Needs a clearer rationale before this can be approved.\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.reject_resolution(\n \"c1a5e000-0000-4000-8000-000000000001\",\n notes=\"Needs a clearer rationale before this can be approved.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.rejectResolution(\"c1a5e000-0000-4000-8000-000000000001\", {\n notes: \"Needs a clearer rationale before this can be approved.\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RejectResolution(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesRejectResolutionParams{\n\tNotes: didit.String(\"Needs a clearer rationale before this can be approved.\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesRejectResolutionParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().rejectResolution(\"c1a5e000-0000-4000-8000-000000000001\", CasesRejectResolutionParams.builder()\n .notes(\"Needs a clearer rationale before this can be approved.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->rejectResolution('c1a5e000-0000-4000-8000-000000000001', [\n 'notes' => 'Needs a clearer rationale before this can be approved.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.reject_resolution(\n \"c1a5e000-0000-4000-8000-000000000001\",\n notes: \"Needs a clearer rationale before this can be approved.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RejectResolutionAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesRejectResolutionParams\n{\n Notes = \"Needs a clearer rationale before this can be approved.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Resolution rejected, case returned to the maker blueprint.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDetail" + } + } + } + }, + "409": { + "description": "Case has no resolution pending approval.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "403": { + "description": "The caller cannot reject this resolution: they submitted it, the case has no recorded maker, or the resolution names a different checker. An API key always gets this 403 when a checker is named.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Caller submitted the resolution": { + "value": { + "detail": "The officer who submitted the resolution cannot reject it." + } + }, + "No recorded maker": { + "value": { + "detail": "Case has no recorded maker for this pending resolution; reject is not permitted." + } + }, + "Caller is not the named checker": { + "value": { + "detail": "Only the named checker can reject this resolution." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/entity-actions/": { + "post": { + "operationId": "apply_case_entity_action", + "x-didit-sdk": { + "resource": "cases", + "method": "apply_entity_action", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseEntityActionRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseEntityActionRequest" + }, + "example": { + "client_reference": "e1a00000-0000-4000-8000-000000000001", + "action": "add_tags", + "tags": [ + "source-of-funds-reviewed" + ] + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseEntityActionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseEntityActionResult" + } + } + }, + "description": "" + }, + "409": { + "description": "The reference belongs to a different action payload." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Apply one explicit action to the case subject. Supply a unique client_reference to obtain an action receipt and make retries idempotent. Reusing that reference with a different payload returns 409. With a reference, inspect receipt.status even when HTTP status is 200: FAILED means no action was applied. Repeating a failed request does not execute it again unless retry is true. Omitting client_reference preserves the legacy response.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.apply_entity_action(\n \"11111111-2222-3333-4444-555555555555\",\n client_reference=\"e1a00000-0000-4000-8000-000000000001\",\n action=\"add_tags\",\n tags=[\"source-of-funds-reviewed\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.applyEntityAction(\"11111111-2222-3333-4444-555555555555\", {\n client_reference: \"e1a00000-0000-4000-8000-000000000001\",\n action: \"add_tags\",\n tags: [\"source-of-funds-reviewed\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.ApplyEntityAction(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CaseActionCaseEntityActionRequest{\n\tClientReference: didit.String(\"e1a00000-0000-4000-8000-000000000001\"),\n\tAction: didit.CaseActionActionEnum(\"add_tags\"),\n\tTags: didit.Some([]string{\"source-of-funds-reviewed\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CaseActionActionEnum;\nimport me.didit.types.CaseActionCaseEntityActionRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().applyEntityAction(\"11111111-2222-3333-4444-555555555555\", CaseActionCaseEntityActionRequest.builder()\n .clientReference(\"e1a00000-0000-4000-8000-000000000001\")\n .action(CaseActionActionEnum.of(\"add_tags\"))\n .tags(List.of(\"source-of-funds-reviewed\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->applyEntityAction('11111111-2222-3333-4444-555555555555', [\n 'client_reference' => 'e1a00000-0000-4000-8000-000000000001',\n 'action' => 'add_tags',\n 'tags' => ['source-of-funds-reviewed'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.apply_entity_action(\n \"11111111-2222-3333-4444-555555555555\",\n client_reference: \"e1a00000-0000-4000-8000-000000000001\",\n action: \"add_tags\",\n tags: [\"source-of-funds-reviewed\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ApplyEntityActionAsync(\"11111111-2222-3333-4444-555555555555\", new CaseActionCaseEntityActionRequest\n{\n ClientReference = \"e1a00000-0000-4000-8000-000000000001\",\n Action = \"add_tags\",\n Tags = new string[] { \"source-of-funds-reviewed\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/print/": { + "get": { + "summary": "Print case (PDF)", + "description": "Render the full case (summary, subject, checklist state, notes, attached sessions/transactions, events, FIU report list) as a PDF and return a presigned download URL.", + "operationId": "print_case", + "x-didit-sdk": { + "resource": "cases", + "method": "print", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/print/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.print(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.print(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Print(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().print(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->print('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.print(\"c1a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.PrintAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Presigned PDF URL.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/notes/": { + "get": { + "operationId": "list_case_notes", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "name": "ordering", + "required": false, + "in": "query", + "description": "Which field to use when ordering the results.", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "offset", + "required": false, + "in": "query", + "description": "The initial index from which to return the results.", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationPaginatedCaseNoteList" + } + } + }, + "description": "" + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The case or note is unavailable for this caller." + } + }, + "tags": [ + "Case Management" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.notes.list(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.notes.list(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.Notes.ListAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasesNotesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().notes().list(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->notes->list('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.notes.list(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.Notes.ListAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "operationId": "create_case_note", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNoteCreateRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNoteCreateRequest" + }, + "example": { + "comment": "Proof of address matches the ID document." + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNoteCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNote" + } + } + }, + "description": "" + }, + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNote" + } + } + }, + "description": "" + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The case or note is unavailable for this caller." + }, + "409": { + "description": "This client reference was already used for another note or for a note that has been deleted." + } + }, + "tags": [ + "Case Management" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Add a case-scoped note. An optional client_reference makes an unchanged retry return the same note without duplicate activity or notifications. reply_to must identify a live note in this case. The response includes a bounded parent excerpt without copying that excerpt into the saved reply.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.notes.create(\n \"11111111-2222-3333-4444-555555555555\",\n comment=\"Proof of address matches the ID document.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.notes.create(\"11111111-2222-3333-4444-555555555555\", {\n comment: \"Proof of address matches the ID document.\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Notes.Create(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CaseCollaborationCaseNoteCreateRequest{\n\tComment: \"Proof of address matches the ID document.\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CaseCollaborationCaseNoteCreateRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().notes().create(\"11111111-2222-3333-4444-555555555555\", CaseCollaborationCaseNoteCreateRequest.builder()\n .comment(\"Proof of address matches the ID document.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->notes->create('11111111-2222-3333-4444-555555555555', [\n 'comment' => 'Proof of address matches the ID document.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.notes.create(\n \"11111111-2222-3333-4444-555555555555\",\n comment: \"Proof of address matches the ID document.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Notes.CreateAsync(\"11111111-2222-3333-4444-555555555555\", new CaseCollaborationCaseNoteCreateRequest\n{\n Comment = \"Proof of address matches the ID document.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/notes/{note_uuid}/": { + "patch": { + "operationId": "root_partial_update", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "note_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationPatchedCaseNoteUpdateRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationPatchedCaseNoteUpdateRequest" + }, + "example": { + "is_pinned": true + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationPatchedCaseNoteUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseCollaborationCaseNote" + } + } + }, + "description": "" + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The case or note is unavailable for this caller." + } + }, + "tags": [ + "Case Management" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Pin or unpin an existing note. Pinned notes appear first. Pin changes are recorded in case activity and do not change evidence or source decisions.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.notes.update(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n is_pinned=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.notes.update(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n {\n is_pinned: true,\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Notes.Update(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\", didit.CaseCollaborationPatchedCaseNoteUpdateRequest{\n\tIsPinned: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CaseCollaborationPatchedCaseNoteUpdateRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().notes().update(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\", CaseCollaborationPatchedCaseNoteUpdateRequest.builder()\n .isPinned(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->notes->update('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555', [\n 'is_pinned' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.notes.update(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n is_pinned: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Notes.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\", new CaseCollaborationPatchedCaseNoteUpdateRequest\n{\n IsPinned = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "operationId": "delete_case_note", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "note_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "responses": { + "204": { + "description": "The note is permanently deleted. Replies remain, without the deleted parent excerpt. Attachments are deleted unless another note still uses them." + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The case or note is unavailable for this caller." + } + }, + "tags": [ + "Case Management" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.cases.notes.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.cases.notes.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.Cases.Notes.Delete(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().notes().delete(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->notes->delete('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.notes.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\"\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Cases.Notes.DeleteAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/notes/presign/": { + "post": { + "summary": "Upload note attachment", + "description": "Upload a file (JPEG, PNG, WebP, PDF, plain text, or CSV; max 5 MB) to attach to a note. Returns the s3_key to pass in the note's attachments array.", + "operationId": "upload_case_note_attachment", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "create_attachment_upload", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "file" + ], + "properties": { + "file": { + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/notes/presign/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'file=@evidence.pdf'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.notes.create_attachment_upload(\n \"c1a5e000-0000-4000-8000-000000000001\",\n file=open(\"document.pdf\", \"rb\"),\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.notes.createAttachmentUpload(\"c1a5e000-0000-4000-8000-000000000001\", {\n file: await fileFromPath(\"document.pdf\"),\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Notes.CreateAttachmentUpload(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesNotesCreateAttachmentUploadParams{\n\tFile: didit.FileFromPath(\"document.pdf\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.types.CasesNotesCreateAttachmentUploadParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().notes().createAttachmentUpload(\"c1a5e000-0000-4000-8000-000000000001\", CasesNotesCreateAttachmentUploadParams.builder()\n .file(Path.of(\"document.pdf\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->notes->createAttachmentUpload('c1a5e000-0000-4000-8000-000000000001', [\n 'file' => File::fromPath('document.pdf'),\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.notes.create_attachment_upload(\n \"c1a5e000-0000-4000-8000-000000000001\",\n file: Pathname(\"document.pdf\")\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Notes.CreateAttachmentUploadAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesNotesCreateAttachmentUploadParams\n{\n File = FileUpload.FromPath(\"document.pdf\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "File uploaded.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "s3_key": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "filename": { + "type": "string" + }, + "file_size": { + "type": "integer" + } + } + } + } + } + }, + "400": { + "description": "No file, unsupported content type, or file exceeds 5 MB.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "get": { + "summary": "Get note attachment URL", + "description": "Mint a fresh presigned download URL for an attachment already stored on one of the case's notes.", + "operationId": "get_case_note_attachment_url", + "x-didit-sdk": { + "resource": "cases.notes", + "method": "retrieve_attachment_url", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "s3_key", + "in": "query", + "schema": { + "type": "string" + }, + "description": "The attachment's s3_key, as returned by upload or listed on the note.", + "required": true + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/notes/presign/?s3_key=case-notes/c1a5e000-0000-4000-8000-000000000001/evidence.pdf' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.notes.retrieve_attachment_url(\n \"c1a5e000-0000-4000-8000-000000000001\",\n s3_key=\"your-s3-key\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.notes.retrieveAttachmentUrl(\"c1a5e000-0000-4000-8000-000000000001\", {\n s3_key: \"your-s3-key\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Notes.RetrieveAttachmentURL(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesNotesRetrieveAttachmentURLParams{\n\tS3Key: \"your-s3-key\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesNotesRetrieveAttachmentUrlParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().notes().retrieveAttachmentUrl(\"c1a5e000-0000-4000-8000-000000000001\", CasesNotesRetrieveAttachmentUrlParams.builder()\n .s3Key(\"your-s3-key\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->notes->retrieveAttachmentUrl('c1a5e000-0000-4000-8000-000000000001', [\n 's3_key' => 'your-s3-key',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.notes.retrieve_attachment_url(\n \"c1a5e000-0000-4000-8000-000000000001\",\n s3_key: \"your-s3-key\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Notes.RetrieveAttachmentUrlAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesNotesRetrieveAttachmentUrlParams\n{\n S3Key = \"your-s3-key\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Presigned URL.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "s3_key": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "filename": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "s3_key missing or does not belong to this case.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/links/": { + "get": { + "summary": "List case links", + "description": "List sessions, business sessions, and transactions attached to the case.", + "operationId": "list_case_links", + "x-didit-sdk": { + "resource": "cases.links", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/links/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.links.list(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.links.list(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Links.List(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().links().list(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->links->list('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.links.list(\"c1a5e000-0000-4000-8000-000000000001\").each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Links.ListAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Case links.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseLink" + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Add case link", + "description": "Attach a session, business session, or transaction to the case. Rejected with 400 if the entity belongs to a different subject than the case.", + "operationId": "create_case_link", + "x-didit-sdk": { + "resource": "cases.links", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "entity_type", + "entity_uuid" + ], + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "session", + "business_session", + "transaction" + ] + }, + "entity_uuid": { + "type": "string", + "format": "uuid" + } + } + }, + "example": { + "entity_type": "transaction", + "entity_uuid": "77000000-0000-4000-8000-000000000077" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/links/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"entity_type\": \"transaction\", \"entity_uuid\": \"77000000-0000-4000-8000-000000000077\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.links.create(\n \"c1a5e000-0000-4000-8000-000000000001\",\n entity_type=\"transaction\",\n entity_uuid=\"77000000-0000-4000-8000-000000000077\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.links.create(\"c1a5e000-0000-4000-8000-000000000001\", {\n entity_type: \"transaction\",\n entity_uuid: \"77000000-0000-4000-8000-000000000077\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Links.Create(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesLinksCreateParams{\n\tEntityType: \"transaction\",\n\tEntityUUID: \"77000000-0000-4000-8000-000000000077\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesLinksCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().links().create(\"c1a5e000-0000-4000-8000-000000000001\", CasesLinksCreateParams.builder()\n .entityType(CasesLinksCreateParams.EntityType.of(\"transaction\"))\n .entityUuid(\"77000000-0000-4000-8000-000000000077\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->links->create('c1a5e000-0000-4000-8000-000000000001', [\n 'entity_type' => 'transaction',\n 'entity_uuid' => '77000000-0000-4000-8000-000000000077',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.links.create(\n \"c1a5e000-0000-4000-8000-000000000001\",\n entity_type: \"transaction\",\n entity_uuid: \"77000000-0000-4000-8000-000000000077\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Links.CreateAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesLinksCreateParams\n{\n EntityType = \"transaction\",\n EntityUuid = \"77000000-0000-4000-8000-000000000077\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Link created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseLink" + } + } + } + }, + "400": { + "description": "Entity belongs to a different profile than the case subject.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/links/{link_uuid}/": { + "delete": { + "summary": "Remove case link", + "description": "Detach a linked session, business session, or transaction from the case.", + "operationId": "delete_case_link", + "x-didit-sdk": { + "resource": "cases.links", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "link_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "11000000-0000-4000-8000-000000000004" + }, + "description": "Link UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/links/11000000-0000-4000-8000-000000000004/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.cases.links.delete(\n \"c1a5e000-0000-4000-8000-000000000001\",\n \"11000000-0000-4000-8000-000000000004\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.cases.links.delete(\n \"c1a5e000-0000-4000-8000-000000000001\",\n \"11000000-0000-4000-8000-000000000004\",\n);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.Cases.Links.Delete(ctx, \"c1a5e000-0000-4000-8000-000000000001\", \"11000000-0000-4000-8000-000000000004\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().links().delete(\"c1a5e000-0000-4000-8000-000000000001\", \"11000000-0000-4000-8000-000000000004\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->links->delete('c1a5e000-0000-4000-8000-000000000001', '11000000-0000-4000-8000-000000000004');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.links.delete(\n \"c1a5e000-0000-4000-8000-000000000001\",\n \"11000000-0000-4000-8000-000000000004\"\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Cases.Links.DeleteAsync(\"c1a5e000-0000-4000-8000-000000000001\", \"11000000-0000-4000-8000-000000000004\");" + } + ], + "responses": { + "204": { + "description": "Link removed." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/events/": { + "get": { + "summary": "List case events", + "description": "Read-only audit timeline of the case (created, status changes, assignment, resolution, escalation, transfers, tag/priority/due-date changes, entity actions, and more).", + "operationId": "list_case_events", + "x-didit-sdk": { + "resource": "cases", + "method": "list_events", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/events/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.list_events(\n \"c1a5e000-0000-4000-8000-000000000001\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.listEvents(\"c1a5e000-0000-4000-8000-000000000001\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.ListEventsAutoPaging(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesListEventsParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().listEvents(\"c1a5e000-0000-4000-8000-000000000001\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->listEvents('c1a5e000-0000-4000-8000-000000000001') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.list_events(\"c1a5e000-0000-4000-8000-000000000001\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.ListEventsAsync(\"c1a5e000-0000-4000-8000-000000000001\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated events, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows. Exact up to 100, capped at 100 beyond that for performance. Rely on `next` being null to detect the last page." + }, + "next": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the next page, or null." + }, + "previous": { + "type": "string", + "nullable": true, + "description": "Absolute URL of the previous page, or null." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseEvent" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/checklist/": { + "get": { + "summary": "List case checklist", + "description": "List the case's checklist items, copied from the blueprint's content_config.checklist.items at creation, ordered.", + "operationId": "list_case_checklist", + "x-didit-sdk": { + "resource": "cases.checklist", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/checklist/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.checklist.list(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.checklist.list(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Checklist.List(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().checklist().list(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->checklist->list('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.checklist.list(\"c1a5e000-0000-4000-8000-000000000001\").each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Checklist.ListAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Checklist items.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseChecklistItem" + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/checklist/{item_uuid}/": { + "patch": { + "summary": "Toggle checklist item", + "description": "Mark a checklist item complete or incomplete. Resolution is blocked while any item is incomplete, unless the blueprint marks the checklist optional.", + "operationId": "toggle_case_checklist_item", + "x-didit-sdk": { + "resource": "cases.checklist", + "method": "toggle_item", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "item_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c4ec0000-0000-4000-8000-000000000005" + }, + "description": "Checklist item UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "is_completed" + ], + "properties": { + "is_completed": { + "type": "boolean" + } + } + }, + "example": { + "is_completed": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/checklist/c4ec0000-0000-4000-8000-000000000005/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"is_completed\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.checklist.toggle_item(\n \"c1a5e000-0000-4000-8000-000000000001\",\n \"c4ec0000-0000-4000-8000-000000000005\",\n is_completed=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.checklist.toggleItem(\n \"c1a5e000-0000-4000-8000-000000000001\",\n \"c4ec0000-0000-4000-8000-000000000005\",\n {\n is_completed: true,\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Checklist.ToggleItem(ctx, \"c1a5e000-0000-4000-8000-000000000001\", \"c4ec0000-0000-4000-8000-000000000005\", didit.CasesChecklistToggleItemParams{\n\tIsCompleted: true,\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesChecklistToggleItemParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().checklist().toggleItem(\"c1a5e000-0000-4000-8000-000000000001\", \"c4ec0000-0000-4000-8000-000000000005\", CasesChecklistToggleItemParams.builder()\n .isCompleted(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->checklist->toggleItem('c1a5e000-0000-4000-8000-000000000001', 'c4ec0000-0000-4000-8000-000000000005', [\n 'is_completed' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.checklist.toggle_item(\n \"c1a5e000-0000-4000-8000-000000000001\",\n \"c4ec0000-0000-4000-8000-000000000005\",\n is_completed: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Checklist.ToggleItemAsync(\"c1a5e000-0000-4000-8000-000000000001\", \"c4ec0000-0000-4000-8000-000000000005\", new CasesChecklistToggleItemParams\n{\n IsCompleted = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated checklist item.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseChecklistItem" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/aml/": { + "get": { + "summary": "List case AML hits", + "description": "List AML screening hits for the case's subject. Same shape as the hits returned by POST /v3/aml/.", + "operationId": "list_case_aml_hits", + "x-didit-sdk": { + "resource": "cases", + "method": "list_aml_hits", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/aml/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.list_aml_hits(\n \"c1a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.listAmlHits(\"c1a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.ListAMLHits(ctx, \"c1a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().listAmlHits(\"c1a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->listAmlHits('c1a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.list_aml_hits(\"c1a5e000-0000-4000-8000-000000000001\").each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ListAmlHitsAsync(\"c1a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "AML hits.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AMLScreening" + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/statistics/": { + "get": { + "summary": "Case analytics", + "description": "Aggregate case analytics for the date range (defaults to the last 30 days; max 731 days). Charts, tables, totals, assignment events, and current queue counts use the same application-scoped filters. Filters use current case values. Dates apply to each metric event, so older cases assigned or resolved during the period remain included. Queue depth is independent of the date range. Invalid filters return 400; an unknown blueprint or assignee returns an empty result.", + "operationId": "get_case_analytics", + "x-didit-sdk": { + "resource": "cases", + "method": "retrieve_statistics", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "date_from", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Range start (inclusive). Defaults to 30 days ago." + }, + { + "name": "date_to", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Range end (inclusive). Defaults to today." + }, + { + "name": "blueprint", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter by the current blueprint UUID." + }, + { + "name": "assigned_to", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter by the current assigned reviewer user UUID." + }, + { + "name": "priority", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "description": "Filter by current case priority." + }, + { + "name": "source", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ] + }, + "description": "Filter by case creation source." + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ] + }, + "description": "Filter by current case status." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/statistics/?date_from=2026-06-01&date_to=2026-06-30' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.retrieve_statistics()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.retrieveStatistics();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RetrieveStatistics(ctx, didit.CasesRetrieveStatisticsParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().retrieveStatistics();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->retrieveStatistics();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.retrieve_statistics\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RetrieveStatisticsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Analytics payload.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "series": { + "type": "array", + "items": { + "type": "object", + "properties": { + "date": { + "type": "string", + "format": "date" + }, + "created": { + "type": "integer" + }, + "reviewed": { + "type": "integer" + } + } + } + }, + "tables": { + "type": "object", + "properties": { + "officer": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Dimension value: officer email, \"blueprint_name:uuid\", source, or status. \"unassigned\"/\"none\" for missing dimensions." + }, + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + } + }, + "blueprint": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Dimension value: officer email, \"blueprint_name:uuid\", source, or status. \"unassigned\"/\"none\" for missing dimensions." + }, + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + } + }, + "source": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Dimension value: officer email, \"blueprint_name:uuid\", source, or status. \"unassigned\"/\"none\" for missing dimensions." + }, + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + } + }, + "status": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "Dimension value: officer email, \"blueprint_name:uuid\", source, or status. \"unassigned\"/\"none\" for missing dimensions." + }, + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + } + } + } + }, + "totals": { + "type": "object", + "properties": { + "created": { + "type": "integer" + }, + "assigned": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "threat_rate": { + "type": "number", + "format": "float" + }, + "false_positive_rate": { + "type": "number", + "format": "float" + }, + "unresolved": { + "type": "integer" + }, + "reassignment_rate": { + "type": "number", + "format": "float" + }, + "escalation_rate": { + "type": "number", + "format": "float" + }, + "avg_assign_hours": { + "type": "number", + "format": "float" + }, + "avg_resolve_hours": { + "type": "number", + "format": "float" + }, + "avg_handling_hours": { + "type": "number", + "format": "float" + } + } + }, + "queue": { + "type": "object", + "description": "Live snapshot, not scoped to the date range.", + "properties": { + "open": { + "type": "integer" + }, + "overdue": { + "type": "integer" + } + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/bulk/status/": { + "post": { + "summary": "Bulk update case status", + "description": "Apply the same status transition to several cases in one call: OPEN reopens, AWAITING_USER requests info, RESOLVED resolves (resolution required). Each case is evaluated independently; gate failures (incomplete checklist, missing required note) are reported per case without aborting the batch.", + "operationId": "bulk_update_case_status", + "x-didit-sdk": { + "resource": "cases", + "method": "bulk_update_status", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "case_uuids", + "status" + ], + "properties": { + "case_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1 + }, + "status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ] + }, + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ], + "description": "Required when status is RESOLVED." + }, + "note": { + "type": "string", + "nullable": true + } + } + }, + "example": { + "case_uuids": [ + "c1a5e000-0000-4000-8000-000000000001" + ], + "status": "RESOLVED", + "resolution": "FALSE_POSITIVE" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/bulk/status/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"case_uuids\": [\"c1a5e000-0000-4000-8000-000000000001\"], \"status\": \"RESOLVED\", \"resolution\": \"FALSE_POSITIVE\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.bulk_update_status(\n case_uuids=[\"c1a5e000-0000-4000-8000-000000000001\"],\n status=\"RESOLVED\",\n resolution=\"FALSE_POSITIVE\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.bulkUpdateStatus({\n case_uuids: [\"c1a5e000-0000-4000-8000-000000000001\"],\n status: \"RESOLVED\",\n resolution: \"FALSE_POSITIVE\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.BulkUpdateStatus(ctx, didit.CasesBulkUpdateStatusParams{\n\tCaseUUIDs: []string{\"c1a5e000-0000-4000-8000-000000000001\"},\n\tStatus: \"RESOLVED\",\n\tResolution: didit.Some(didit.CasesBulkUpdateStatusParamsResolution(\"FALSE_POSITIVE\")),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CasesBulkUpdateStatusParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().bulkUpdateStatus(CasesBulkUpdateStatusParams.builder()\n .caseUuids(List.of(\"c1a5e000-0000-4000-8000-000000000001\"))\n .status(CasesBulkUpdateStatusParams.Status.of(\"RESOLVED\"))\n .resolution(CasesBulkUpdateStatusParams.Resolution.of(\"FALSE_POSITIVE\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->bulkUpdateStatus([\n 'case_uuids' => ['c1a5e000-0000-4000-8000-000000000001'],\n 'status' => 'RESOLVED',\n 'resolution' => 'FALSE_POSITIVE',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.bulk_update_status(\n case_uuids: [\"c1a5e000-0000-4000-8000-000000000001\"],\n status: \"RESOLVED\",\n resolution: \"FALSE_POSITIVE\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.BulkUpdateStatusAsync(new CasesBulkUpdateStatusParams\n{\n CaseUuids = new string[] { \"c1a5e000-0000-4000-8000-000000000001\" },\n Status = \"RESOLVED\",\n Resolution = new CasesBulkUpdateStatusParamsResolution(\"FALSE_POSITIVE\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Per-case results.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBulkUpdateResult" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/bulk/assign/": { + "post": { + "summary": "Bulk assign cases", + "description": "Assign (or unassign, with assignee_user_id: null) several cases at once. All cases must share the same blueprint; mismatches are reported per case without aborting the batch.", + "operationId": "bulk_assign_cases", + "x-didit-sdk": { + "resource": "cases", + "method": "bulk_assign", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Cases" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "case_uuids", + "assignee_user_id" + ], + "properties": { + "case_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1 + }, + "assignee_user_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "assignee_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "assignee_name": { + "type": "string", + "nullable": true + } + } + }, + "example": { + "case_uuids": [ + "c1a5e000-0000-4000-8000-000000000001" + ], + "assignee_user_id": "9a000000-0000-4000-8000-0000000000aa" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/bulk/assign/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"case_uuids\": [\"c1a5e000-0000-4000-8000-000000000001\"], \"assignee_user_id\": \"9a000000-0000-4000-8000-0000000000aa\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.bulk_assign(\n case_uuids=[\"c1a5e000-0000-4000-8000-000000000001\"],\n assignee_user_id=\"9a000000-0000-4000-8000-0000000000aa\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.bulkAssign({\n case_uuids: [\"c1a5e000-0000-4000-8000-000000000001\"],\n assignee_user_id: \"9a000000-0000-4000-8000-0000000000aa\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.BulkAssign(ctx, didit.CasesBulkAssignParams{\n\tCaseUUIDs: []string{\"c1a5e000-0000-4000-8000-000000000001\"},\n\tAssigneeUserID: didit.String(\"9a000000-0000-4000-8000-0000000000aa\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CasesBulkAssignParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().bulkAssign(CasesBulkAssignParams.builder()\n .caseUuids(List.of(\"c1a5e000-0000-4000-8000-000000000001\"))\n .assigneeUserId(\"9a000000-0000-4000-8000-0000000000aa\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->bulkAssign([\n 'case_uuids' => ['c1a5e000-0000-4000-8000-000000000001'],\n 'assignee_user_id' => '9a000000-0000-4000-8000-0000000000aa',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.bulk_assign(\n case_uuids: [\"c1a5e000-0000-4000-8000-000000000001\"],\n assignee_user_id: \"9a000000-0000-4000-8000-0000000000aa\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.BulkAssignAsync(new CasesBulkAssignParams\n{\n CaseUuids = new string[] { \"c1a5e000-0000-4000-8000-000000000001\" },\n AssigneeUserId = \"9a000000-0000-4000-8000-0000000000aa\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Per-case results.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBulkUpdateResult" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/": { + "get": { + "summary": "List blueprints", + "description": "List case blueprints for the application.", + "operationId": "list_case_blueprints", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.blueprints.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.blueprints.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.Blueprints.ListAutoPaging(ctx, didit.CasesBlueprintsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().blueprints().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.blueprints.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.Blueprints.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated blueprint list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows, capped at 100 for performance." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseBlueprintListItem" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create blueprint", + "description": "Create a case blueprint: general settings (assignment, escalation, transfer, 4-eyes pairing) plus content_config, the six sections that drive the case page. Set publish=false to create an unpublished draft; Publish creates its first live version. Discarding a never-published draft deletes it.", + "operationId": "create_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "assignee_pool" + ], + "properties": { + "name": { + "type": "string", + "maxLength": 24, + "description": "Max 24 characters, e.g. \"AML investigation\"." + }, + "description": { + "type": "string", + "nullable": true + }, + "assignee_pool": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1, + "description": "List of User UUIDs. Empty arrays are rejected with \"Pick at least one assignee\"." + }, + "assignment_method": { + "type": "string", + "enum": [ + "MANUAL", + "AUTOMATIC" + ], + "default": "MANUAL" + }, + "max_active_cases": { + "type": "integer", + "nullable": true, + "minimum": 1, + "description": "Automatic-mode threshold: \"fewer than N cases of this blueprint assigned\"." + }, + "escalation_enabled": { + "type": "boolean", + "default": false + }, + "escalation_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Target blueprint UUID. Not enforced at save time, but the escalate action fails with 400 until both escalation_enabled and escalation_blueprint are set." + }, + "transfer_enabled": { + "type": "boolean", + "default": false + }, + "four_eyes_checker_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Setting this enables 4-eyes review: resolutions on this blueprint stage on the checker blueprint's queue." + }, + "require_resolution_note": { + "type": "boolean", + "default": false + }, + "content_config": { + "$ref": "#/components/schemas/CaseContentConfig" + }, + "is_active": { + "type": "boolean", + "default": true + }, + "default_deadline_hours": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 8760, + "description": "Default deadline for newly opened cases. Existing case deadlines are unchanged." + }, + "publish": { + "type": "boolean", + "writeOnly": true, + "default": true, + "description": "Set false to save a never-published draft. The record remains inactive at version 0 until the draft is explicitly published. Omitting this field preserves immediate creation for existing integrations." + } + } + }, + "example": { + "name": "AML investigation", + "assignee_pool": [ + "9a000000-0000-4000-8000-0000000000aa" + ], + "assignment_method": "AUTOMATIC", + "max_active_cases": 15, + "require_resolution_note": true, + "content_config": { + "aml_control": { + "enabled": true, + "financial": true, + "identity": false + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"name\": \"AML investigation\", \"assignee_pool\": [\"9a000000-0000-4000-8000-0000000000aa\"], \"assignment_method\": \"AUTOMATIC\", \"max_active_cases\": 15, \"require_resolution_note\": true, \"content_config\": {\"aml_control\": {\"enabled\": true, \"financial\": true, \"identity\": false}}}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.create(\n name=\"AML investigation\",\n assignee_pool=[\"9a000000-0000-4000-8000-0000000000aa\"],\n assignment_method=\"AUTOMATIC\",\n max_active_cases=15,\n require_resolution_note=True,\n content_config={\n \"aml_control\": {\n \"enabled\": True,\n \"financial\": True,\n \"identity\": False,\n },\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.create({\n name: \"AML investigation\",\n assignee_pool: [\"9a000000-0000-4000-8000-0000000000aa\"],\n assignment_method: \"AUTOMATIC\",\n max_active_cases: 15,\n require_resolution_note: true,\n content_config: {\n aml_control: {\n enabled: true,\n financial: true,\n identity: false,\n },\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.Create(ctx, didit.CasesBlueprintsCreateParams{\n\tName: \"AML investigation\",\n\tAssigneePool: []string{\"9a000000-0000-4000-8000-0000000000aa\"},\n\tAssignmentMethod: didit.Some(didit.CasesBlueprintsCreateParamsAssignmentMethod(\"AUTOMATIC\")),\n\tMaxActiveCases: didit.Int(15),\n\tRequireResolutionNote: didit.Bool(true),\n\tContentConfig: didit.Some(didit.CaseContentConfigParam{\n\t\tAMLControl: didit.Some(didit.CaseContentConfigParamAMLControl{\n\t\t\tEnabled: didit.Bool(true),\n\t\t\tFinancial: didit.Bool(true),\n\t\t\tIdentity: didit.Bool(false),\n\t\t}),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CaseContentConfig;\nimport me.didit.types.CasesBlueprintsCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().create(CasesBlueprintsCreateParams.builder()\n .name(\"AML investigation\")\n .assigneePool(List.of(\"9a000000-0000-4000-8000-0000000000aa\"))\n .assignmentMethod(CasesBlueprintsCreateParams.AssignmentMethod.of(\"AUTOMATIC\"))\n .maxActiveCases(15L)\n .requireResolutionNote(true)\n .contentConfig(CaseContentConfig.builder()\n .amlControl(CaseContentConfig.AmlControl.builder()\n .enabled(true)\n .financial(true)\n .identity(false)\n .build())\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->create([\n 'name' => 'AML investigation',\n 'assignee_pool' => ['9a000000-0000-4000-8000-0000000000aa'],\n 'assignment_method' => 'AUTOMATIC',\n 'max_active_cases' => 15,\n 'require_resolution_note' => true,\n 'content_config' => [\n 'aml_control' => [\n 'enabled' => true,\n 'financial' => true,\n 'identity' => false,\n ],\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.create(\n name: \"AML investigation\",\n assignee_pool: [\"9a000000-0000-4000-8000-0000000000aa\"],\n assignment_method: \"AUTOMATIC\",\n max_active_cases: 15,\n require_resolution_note: true,\n content_config: {\n aml_control: {\n enabled: true,\n financial: true,\n identity: false,\n },\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.CreateAsync(new CasesBlueprintsCreateParams\n{\n Name = \"AML investigation\",\n AssigneePool = new string[] { \"9a000000-0000-4000-8000-0000000000aa\" },\n AssignmentMethod = new CasesBlueprintsCreateParamsAssignmentMethod(\"AUTOMATIC\"),\n MaxActiveCases = 15,\n RequireResolutionNote = true,\n ContentConfig = new CaseContentConfig\n {\n AmlControl = new CaseContentConfigAmlControl\n {\n Enabled = true,\n Financial = true,\n Identity = false,\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Blueprint created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintDetail" + } + } + } + }, + "400": { + "description": "Name over 24 chars, empty assignee_pool, or a cross-referenced blueprint (escalation/checker) not found.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/{blueprint_uuid}/": { + "get": { + "summary": "Get blueprint", + "description": "Retrieve a blueprint, including its connected creation sources (AML config, transaction rules, workflow nodes) and which blueprints escalate to it.", + "operationId": "get_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "blueprint_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "b1de0000-0000-4000-8000-000000000002" + }, + "description": "Blueprint UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/b1de0000-0000-4000-8000-000000000002/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.retrieve(\n \"b1de0000-0000-4000-8000-000000000002\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.retrieve(\"b1de0000-0000-4000-8000-000000000002\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.Retrieve(ctx, \"b1de0000-0000-4000-8000-000000000002\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().retrieve(\"b1de0000-0000-4000-8000-000000000002\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->retrieve('b1de0000-0000-4000-8000-000000000002');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.retrieve(\"b1de0000-0000-4000-8000-000000000002\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.RetrieveAsync(\"b1de0000-0000-4000-8000-000000000002\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Blueprint detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update blueprint", + "description": "Partially update a blueprint's general settings or content_config.", + "operationId": "update_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "blueprint_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "b1de0000-0000-4000-8000-000000000002" + }, + "description": "Blueprint UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 24, + "description": "Max 24 characters, e.g. \"AML investigation\"." + }, + "description": { + "type": "string", + "nullable": true + }, + "assignee_pool": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1, + "description": "List of User UUIDs. Empty arrays are rejected with \"Pick at least one assignee\"." + }, + "assignment_method": { + "type": "string", + "enum": [ + "MANUAL", + "AUTOMATIC" + ] + }, + "max_active_cases": { + "type": "integer", + "nullable": true, + "minimum": 1, + "description": "Automatic-mode threshold: \"fewer than N cases of this blueprint assigned\"." + }, + "escalation_enabled": { + "type": "boolean" + }, + "escalation_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Target blueprint UUID. Not enforced at save time, but the escalate action fails with 400 until both escalation_enabled and escalation_blueprint are set." + }, + "transfer_enabled": { + "type": "boolean" + }, + "four_eyes_checker_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Setting this enables 4-eyes review: resolutions on this blueprint stage on the checker blueprint's queue." + }, + "require_resolution_note": { + "type": "boolean" + }, + "content_config": { + "$ref": "#/components/schemas/CaseContentConfig" + }, + "is_active": { + "type": "boolean" + }, + "default_deadline_hours": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 8760, + "description": "Default deadline for newly opened cases. Existing case deadlines are unchanged." + }, + "expected_version": { + "type": "integer", + "minimum": 1, + "description": "The version opened by the editor. A stale version returns 409 without changing the review policy." + } + } + }, + "example": { + "transfer_enabled": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/b1de0000-0000-4000-8000-000000000002/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"transfer_enabled\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.update(\n \"b1de0000-0000-4000-8000-000000000002\",\n transfer_enabled=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.update(\"b1de0000-0000-4000-8000-000000000002\", {\n transfer_enabled: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.Update(ctx, \"b1de0000-0000-4000-8000-000000000002\", didit.CasesBlueprintsUpdateParams{\n\tTransferEnabled: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesBlueprintsUpdateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().update(\"b1de0000-0000-4000-8000-000000000002\", CasesBlueprintsUpdateParams.builder()\n .transferEnabled(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->update('b1de0000-0000-4000-8000-000000000002', [\n 'transfer_enabled' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.update(\n \"b1de0000-0000-4000-8000-000000000002\",\n transfer_enabled: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.UpdateAsync(\"b1de0000-0000-4000-8000-000000000002\", new CasesBlueprintsUpdateParams\n{\n TransferEnabled = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated blueprint.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintDetail" + } + } + } + }, + "409": { + "description": "The review policy changed since expected_version. Review the latest version before retrying." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete blueprint", + "description": "Delete a blueprint. Deletion always succeeds and does not touch the blueprint's cases: existing cases keep their blueprint reference and its settings continue to apply to them. A deleted blueprint no longer appears in blueprint lists and can no longer be chosen as a transfer target; if a maker blueprint is deleted while a resolution is pending 4-eyes approval, a rejected resolution leaves the case on the checker blueprint.", + "operationId": "delete_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "blueprint_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "b1de0000-0000-4000-8000-000000000002" + }, + "description": "Blueprint UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/b1de0000-0000-4000-8000-000000000002/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.cases.blueprints.delete(\n \"b1de0000-0000-4000-8000-000000000002\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.cases.blueprints.delete(\"b1de0000-0000-4000-8000-000000000002\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.Cases.Blueprints.Delete(ctx, \"b1de0000-0000-4000-8000-000000000002\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().blueprints().delete(\"b1de0000-0000-4000-8000-000000000002\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->delete('b1de0000-0000-4000-8000-000000000002');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.blueprints.delete(\"b1de0000-0000-4000-8000-000000000002\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Cases.Blueprints.DeleteAsync(\"b1de0000-0000-4000-8000-000000000002\");" + } + ], + "responses": { + "204": { + "description": "Blueprint deleted." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/presets/": { + "get": { + "summary": "List blueprint presets", + "description": "List the four built-in presets (Screening hit, AML investigation, Suspicious activity, KYC review) with their content_config and whether each is already installed on this application.", + "operationId": "list_case_blueprint_presets", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "list_presets", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/presets/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.list_presets()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.listPresets();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.ListPresets(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().listPresets();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->listPresets();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.list_presets\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.ListPresetsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Preset catalogue.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "preset_key": { + "type": "string", + "enum": [ + "screening_hit", + "aml_investigation", + "suspicious_activity", + "kyc" + ] + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "content_config": { + "$ref": "#/components/schemas/CaseContentConfig" + }, + "installed": { + "type": "boolean" + } + } + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/presets/install/": { + "post": { + "summary": "Install blueprint presets", + "description": "Copy one or more presets into the application as editable blueprints. Idempotent per preset_key; omit preset_keys to install every preset not yet installed.", + "operationId": "install_case_blueprint_presets", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "install_presets", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Blueprints" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "preset_keys": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Subset of screening_hit, aml_investigation, suspicious_activity, kyc. Omit to install all." + } + } + }, + "example": { + "preset_keys": [ + "aml_investigation", + "kyc" + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/blueprints/presets/install/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"preset_keys\": [\"aml_investigation\", \"kyc\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.install_presets(\n preset_keys=[\"aml_investigation\", \"kyc\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.installPresets({\n preset_keys: [\"aml_investigation\", \"kyc\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.InstallPresets(ctx, didit.CasesBlueprintsInstallPresetsParams{\n\tPresetKeys: didit.Some([]string{\"aml_investigation\", \"kyc\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CasesBlueprintsInstallPresetsParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().installPresets(CasesBlueprintsInstallPresetsParams.builder()\n .presetKeys(List.of(\"aml_investigation\", \"kyc\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->installPresets([\n 'preset_keys' => ['aml_investigation', 'kyc'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.install_presets(preset_keys: [\"aml_investigation\", \"kyc\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.InstallPresetsAsync(new CasesBlueprintsInstallPresetsParams\n{\n PresetKeys = new string[] { \"aml_investigation\", \"kyc\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "One or more presets installed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintPresetInstallResult" + } + } + } + }, + "200": { + "description": "No new presets to install (all requested presets already exist).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseBlueprintPresetInstallResult" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/report-templates/": { + "get": { + "summary": "List report templates", + "description": "List FIU report templates for the application.", + "operationId": "list_report_templates", + "x-didit-sdk": { + "resource": "report_templates", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.report_templates.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.reportTemplates.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.ReportTemplates.ListAutoPaging(ctx, didit.ReportTemplatesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.reportTemplates().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "reportTemplates->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.report_templates.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.ReportTemplates.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated templates.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows, capped at 100 for performance." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ReportTemplateDetail" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create report template", + "description": "Create a reusable per-jurisdiction, per-report-type goAML header template. Rejected with 400 if any of the template-level obligatory fields (rentity_id, submission_code, currency_code_local) is missing from field_config; an invalid template cannot be saved or used to create a report. Per-filing header fields (action, submission_date, transactions) are enforced at report validate/finalize time instead.", + "operationId": "create_report_template", + "x-didit-sdk": { + "resource": "report_templates", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "jurisdiction", + "report_type" + ], + "properties": { + "name": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code, upper-cased on save." + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "field_config": { + "type": "object", + "description": "Reusable goAML header defaults. Template-level obligatory fields: rentity_id, submission_code, and currency_code_local (3-letter). Optional: enabled_fields (which optional report blocks to include) plus reporting_person, location, reason, and action defaults. Per-filing header fields (action, submission_date, transactions, jurisdiction) are enforced when the report is validated or finalized, not on the template." + } + } + }, + "example": { + "name": "US SAR default", + "jurisdiction": "US", + "report_type": "SAR", + "field_config": { + "rentity_id": "FIU-US-00123", + "submission_code": "E", + "currency_code_local": "USD" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"name\": \"US SAR default\", \"jurisdiction\": \"US\", \"report_type\": \"SAR\", \"field_config\": {\"rentity_id\": \"FIU-US-00123\", \"submission_code\": \"E\", \"currency_code_local\": \"USD\"}}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.report_templates.create(\n name=\"US SAR default\",\n jurisdiction=\"US\",\n report_type=\"SAR\",\n field_config={\"rentity_id\": \"FIU-US-00123\", \"submission_code\": \"E\", \"currency_code_local\": \"USD\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.reportTemplates.create({\n name: \"US SAR default\",\n jurisdiction: \"US\",\n report_type: \"SAR\",\n field_config: { rentity_id: \"FIU-US-00123\", submission_code: \"E\", currency_code_local: \"USD\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.ReportTemplates.Create(ctx, didit.ReportTemplatesCreateParams{\n\tName: \"US SAR default\",\n\tJurisdiction: \"US\",\n\tReportType: \"SAR\",\n\tFieldConfig: didit.Some(map[string]any{\"rentity_id\": \"FIU-US-00123\", \"submission_code\": \"E\", \"currency_code_local\": \"USD\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.ReportTemplatesCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.reportTemplates().create(ReportTemplatesCreateParams.builder()\n .name(\"US SAR default\")\n .jurisdiction(\"US\")\n .reportType(ReportTemplatesCreateParams.ReportType.of(\"SAR\"))\n .fieldConfig(Map.of(\"rentity_id\", \"FIU-US-00123\", \"submission_code\", \"E\", \"currency_code_local\", \"USD\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "reportTemplates->create([\n 'name' => 'US SAR default',\n 'jurisdiction' => 'US',\n 'report_type' => 'SAR',\n 'field_config' => ['rentity_id' => 'FIU-US-00123', 'submission_code' => 'E', 'currency_code_local' => 'USD'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.report_templates.create(\n name: \"US SAR default\",\n jurisdiction: \"US\",\n report_type: \"SAR\",\n field_config: { rentity_id: \"FIU-US-00123\", submission_code: \"E\", currency_code_local: \"USD\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.ReportTemplates.CreateAsync(new ReportTemplatesCreateParams\n{\n Name = \"US SAR default\",\n Jurisdiction = \"US\",\n ReportType = \"SAR\",\n FieldConfig = new Dictionary { [\"rentity_id\"] = \"FIU-US-00123\", [\"submission_code\"] = \"E\", [\"currency_code_local\"] = \"USD\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Template created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReportTemplateDetail" + } + } + } + }, + "400": { + "description": "Unrecognized jurisdiction, or field_config fails obligatory-field validation.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/report-templates/{template_uuid}/": { + "get": { + "summary": "Get report template", + "description": "Retrieve a report template.", + "operationId": "get_report_template", + "x-didit-sdk": { + "resource": "report_templates", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "template_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "7ec00000-0000-4000-8000-000000000006" + }, + "description": "Report template UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/7ec00000-0000-4000-8000-000000000006/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.report_templates.retrieve(\n \"7ec00000-0000-4000-8000-000000000006\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.reportTemplates.retrieve(\"7ec00000-0000-4000-8000-000000000006\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.ReportTemplates.Retrieve(ctx, \"7ec00000-0000-4000-8000-000000000006\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.reportTemplates().retrieve(\"7ec00000-0000-4000-8000-000000000006\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "reportTemplates->retrieve('7ec00000-0000-4000-8000-000000000006');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.report_templates.retrieve(\"7ec00000-0000-4000-8000-000000000006\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.ReportTemplates.RetrieveAsync(\"7ec00000-0000-4000-8000-000000000006\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Template detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReportTemplateDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update report template", + "description": "Partially update a report template. Re-validates obligatory goAML fields on save.", + "operationId": "update_report_template", + "x-didit-sdk": { + "resource": "report_templates", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "template_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "7ec00000-0000-4000-8000-000000000006" + }, + "description": "Report template UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code, upper-cased on save." + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "field_config": { + "type": "object", + "description": "Reusable goAML header defaults. Template-level obligatory fields: rentity_id, submission_code, and currency_code_local (3-letter). Optional: enabled_fields (which optional report blocks to include) plus reporting_person, location, reason, and action defaults. Per-filing header fields (action, submission_date, transactions, jurisdiction) are enforced when the report is validated or finalized, not on the template." + } + } + }, + "example": { + "field_config": { + "rentity_id": "FIU-US-00124" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/7ec00000-0000-4000-8000-000000000006/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"field_config\": {\"rentity_id\": \"FIU-US-00124\"}}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.report_templates.update(\n \"7ec00000-0000-4000-8000-000000000006\",\n field_config={\"rentity_id\": \"FIU-US-00124\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.reportTemplates.update(\"7ec00000-0000-4000-8000-000000000006\", {\n field_config: { rentity_id: \"FIU-US-00124\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.ReportTemplates.Update(ctx, \"7ec00000-0000-4000-8000-000000000006\", didit.ReportTemplatesUpdateParams{\n\tFieldConfig: didit.Some(map[string]any{\"rentity_id\": \"FIU-US-00124\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.ReportTemplatesUpdateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.reportTemplates().update(\"7ec00000-0000-4000-8000-000000000006\", ReportTemplatesUpdateParams.builder()\n .fieldConfig(Map.of(\"rentity_id\", \"FIU-US-00124\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "reportTemplates->update('7ec00000-0000-4000-8000-000000000006', [\n 'field_config' => ['rentity_id' => 'FIU-US-00124'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.report_templates.update(\n \"7ec00000-0000-4000-8000-000000000006\",\n field_config: { rentity_id: \"FIU-US-00124\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.ReportTemplates.UpdateAsync(\"7ec00000-0000-4000-8000-000000000006\", new ReportTemplatesUpdateParams\n{\n FieldConfig = new Dictionary { [\"rentity_id\"] = \"FIU-US-00124\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated template.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReportTemplateDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete report template", + "description": "Delete a report template. Reports already created from it are unaffected.", + "operationId": "delete_report_template", + "x-didit-sdk": { + "resource": "report_templates", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Report Templates" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "template_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "7ec00000-0000-4000-8000-000000000006" + }, + "description": "Report template UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/report-templates/7ec00000-0000-4000-8000-000000000006/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.report_templates.delete(\n \"7ec00000-0000-4000-8000-000000000006\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.reportTemplates.delete(\"7ec00000-0000-4000-8000-000000000006\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.ReportTemplates.Delete(ctx, \"7ec00000-0000-4000-8000-000000000006\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.reportTemplates().delete(\"7ec00000-0000-4000-8000-000000000006\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "reportTemplates->delete('7ec00000-0000-4000-8000-000000000006');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.report_templates.delete(\"7ec00000-0000-4000-8000-000000000006\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.ReportTemplates.DeleteAsync(\"7ec00000-0000-4000-8000-000000000006\");" + } + ], + "responses": { + "204": { + "description": "Template deleted." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/regulatory-reports/": { + "get": { + "summary": "List reports for case", + "description": "List FIU reports generated for the case.", + "operationId": "list_case_regulatory_reports", + "x-didit-sdk": { + "resource": "cases.regulatory_reports", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/regulatory-reports/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.regulatory_reports.list(\n \"c1a5e000-0000-4000-8000-000000000001\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.regulatoryReports.list(\"c1a5e000-0000-4000-8000-000000000001\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.RegulatoryReports.ListAutoPaging(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesRegulatoryReportsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().regulatoryReports().list(\"c1a5e000-0000-4000-8000-000000000001\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->regulatoryReports->list('c1a5e000-0000-4000-8000-000000000001') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.regulatory_reports.list(\"c1a5e000-0000-4000-8000-000000000001\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.RegulatoryReports.ListAsync(\"c1a5e000-0000-4000-8000-000000000001\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated reports for the case.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportList" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create report from case", + "description": "Create a draft FIU report for the case: DRAFT status, auto-populated from the optional template plus the case's subject, linked transactions, and narrative. Validate then finalize to produce the goAML XML and PDF.", + "operationId": "create_case_regulatory_report", + "x-didit-sdk": { + "resource": "cases.regulatory_reports", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "case_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "c1a5e000-0000-4000-8000-000000000001" + }, + "description": "Case UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ], + "description": "Required unless a template is given; the template's report_type is used otherwise." + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "Required unless a template is given." + }, + "template": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Report template UUID; its field_config seeds the draft before case-derived data is merged in." + }, + "transactions": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "nullable": true, + "description": "Transactions to attach; defaults to the case's linked transactions." + }, + "rentity_id": { + "type": "string", + "nullable": true + }, + "rentity_branch": { + "type": "string", + "nullable": true + }, + "submission_code": { + "type": "string", + "enum": [ + "E", + "M" + ], + "default": "E" + }, + "entity_reference": { + "type": "string", + "nullable": true + }, + "fiu_ref_number": { + "type": "string", + "nullable": true + }, + "currency_code_local": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "nullable": true + }, + "submission_date": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reporting_person": { + "type": "object" + }, + "location": { + "type": "object" + }, + "reason": { + "type": "string", + "nullable": true + }, + "action": { + "type": "string", + "nullable": true + }, + "activity": { + "type": "string", + "nullable": true + }, + "narrative": { + "type": "object", + "properties": { + "who": { + "type": "string" + }, + "what": { + "type": "string" + }, + "when": { + "type": "string" + }, + "where": { + "type": "string" + }, + "why": { + "type": "string" + }, + "how": { + "type": "string" + } + } + }, + "report_indicators": { + "type": "array", + "items": { + "type": "string" + } + }, + "included_fields": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "example": { + "template": "7ec00000-0000-4000-8000-000000000006" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/regulatory-reports/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"template\": \"7ec00000-0000-4000-8000-000000000006\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.regulatory_reports.create(\n \"c1a5e000-0000-4000-8000-000000000001\",\n template=\"7ec00000-0000-4000-8000-000000000006\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.regulatoryReports.create(\"c1a5e000-0000-4000-8000-000000000001\", {\n template: \"7ec00000-0000-4000-8000-000000000006\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RegulatoryReports.Create(ctx, \"c1a5e000-0000-4000-8000-000000000001\", didit.CasesRegulatoryReportsCreateParams{\n\tTemplate: didit.String(\"7ec00000-0000-4000-8000-000000000006\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasesRegulatoryReportsCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().regulatoryReports().create(\"c1a5e000-0000-4000-8000-000000000001\", CasesRegulatoryReportsCreateParams.builder()\n .template(\"7ec00000-0000-4000-8000-000000000006\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->regulatoryReports->create('c1a5e000-0000-4000-8000-000000000001', [\n 'template' => '7ec00000-0000-4000-8000-000000000006',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.regulatory_reports.create(\n \"c1a5e000-0000-4000-8000-000000000001\",\n template: \"7ec00000-0000-4000-8000-000000000006\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RegulatoryReports.CreateAsync(\"c1a5e000-0000-4000-8000-000000000001\", new CasesRegulatoryReportsCreateParams\n{\n Template = \"7ec00000-0000-4000-8000-000000000006\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Draft report created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "400": { + "description": "The referenced template was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/": { + "get": { + "summary": "List reports", + "description": "Application-wide FIU report list, optionally filtered by status, report_type, or case.", + "operationId": "list_regulatory_reports", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + }, + "description": "Page size." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset." + }, + { + "name": "status", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "DRAFT", + "VALIDATED", + "FINALIZED" + ] + }, + "description": "Filter by status." + }, + { + "name": "report_type", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "description": "Filter by report type." + }, + { + "name": "case", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Filter to reports on this case." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.regulatory_reports.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.regulatoryReports.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.RegulatoryReports.ListAutoPaging(ctx, didit.RegulatoryReportsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.regulatoryReports().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.regulatory_reports.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.RegulatoryReports.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated reports across the application.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportList" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create standalone report", + "description": "Create a draft FIU report from the standalone Reports page: both case and template are required (unlike the case-scoped create, which can omit template).", + "operationId": "create_standalone_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "case", + "template" + ], + "properties": { + "case": { + "type": "string", + "format": "uuid" + }, + "template": { + "type": "string", + "format": "uuid" + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ], + "description": "Required unless a template is given; the template's report_type is used otherwise." + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "Required unless a template is given." + }, + "transactions": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "nullable": true, + "description": "Transactions to attach; defaults to the case's linked transactions." + }, + "rentity_id": { + "type": "string", + "nullable": true + }, + "rentity_branch": { + "type": "string", + "nullable": true + }, + "submission_code": { + "type": "string", + "enum": [ + "E", + "M" + ], + "default": "E" + }, + "entity_reference": { + "type": "string", + "nullable": true + }, + "fiu_ref_number": { + "type": "string", + "nullable": true + }, + "currency_code_local": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "nullable": true + }, + "submission_date": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reporting_person": { + "type": "object" + }, + "location": { + "type": "object" + }, + "reason": { + "type": "string", + "nullable": true + }, + "action": { + "type": "string", + "nullable": true + }, + "activity": { + "type": "string", + "nullable": true + }, + "narrative": { + "type": "object", + "properties": { + "who": { + "type": "string" + }, + "what": { + "type": "string" + }, + "when": { + "type": "string" + }, + "where": { + "type": "string" + }, + "why": { + "type": "string" + }, + "how": { + "type": "string" + } + } + }, + "report_indicators": { + "type": "array", + "items": { + "type": "string" + } + }, + "included_fields": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "example": { + "case": "c1a5e000-0000-4000-8000-000000000001", + "template": "7ec00000-0000-4000-8000-000000000006" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"case\": \"c1a5e000-0000-4000-8000-000000000001\", \"template\": \"7ec00000-0000-4000-8000-000000000006\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.regulatory_reports.create(\n case=\"c1a5e000-0000-4000-8000-000000000001\",\n template=\"7ec00000-0000-4000-8000-000000000006\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.regulatoryReports.create({\n case: \"c1a5e000-0000-4000-8000-000000000001\",\n template: \"7ec00000-0000-4000-8000-000000000006\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.RegulatoryReports.Create(ctx, didit.RegulatoryReportsCreateParams{\n\tCase: \"c1a5e000-0000-4000-8000-000000000001\",\n\tTemplate: \"7ec00000-0000-4000-8000-000000000006\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.RegulatoryReportsCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.regulatoryReports().create(RegulatoryReportsCreateParams.builder()\n .case_(\"c1a5e000-0000-4000-8000-000000000001\")\n .template(\"7ec00000-0000-4000-8000-000000000006\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->create([\n 'case' => 'c1a5e000-0000-4000-8000-000000000001',\n 'template' => '7ec00000-0000-4000-8000-000000000006',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.regulatory_reports.create(\n case: \"c1a5e000-0000-4000-8000-000000000001\",\n template: \"7ec00000-0000-4000-8000-000000000006\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.RegulatoryReports.CreateAsync(new RegulatoryReportsCreateParams\n{\n Case = \"c1a5e000-0000-4000-8000-000000000001\",\n Template = \"7ec00000-0000-4000-8000-000000000006\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "Draft report created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "400": { + "description": "The referenced template was not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/{report_uuid}/": { + "get": { + "summary": "Get report", + "description": "Retrieve a regulatory report.", + "operationId": "get_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.regulatory_reports.retrieve(\n \"5ec00000-0000-4000-8000-000000000007\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.regulatoryReports.retrieve(\"5ec00000-0000-4000-8000-000000000007\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.RegulatoryReports.Retrieve(ctx, \"5ec00000-0000-4000-8000-000000000007\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.regulatoryReports().retrieve(\"5ec00000-0000-4000-8000-000000000007\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->retrieve('5ec00000-0000-4000-8000-000000000007');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.regulatory_reports.retrieve(\"5ec00000-0000-4000-8000-000000000007\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.RegulatoryReports.RetrieveAsync(\"5ec00000-0000-4000-8000-000000000007\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Report detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update report", + "description": "Edit a draft or validated report's fields. Finalized reports cannot be edited (409).", + "operationId": "update_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2 + }, + "rentity_id": { + "type": "string", + "nullable": true + }, + "rentity_branch": { + "type": "string", + "nullable": true + }, + "submission_code": { + "type": "string", + "enum": [ + "E", + "M" + ], + "default": "E" + }, + "entity_reference": { + "type": "string", + "nullable": true + }, + "fiu_ref_number": { + "type": "string", + "nullable": true + }, + "currency_code_local": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "nullable": true + }, + "submission_date": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reporting_person": { + "type": "object" + }, + "location": { + "type": "object" + }, + "reason": { + "type": "string", + "nullable": true + }, + "action": { + "type": "string", + "nullable": true + }, + "activity": { + "type": "string", + "nullable": true + }, + "narrative": { + "type": "object", + "properties": { + "who": { + "type": "string" + }, + "what": { + "type": "string" + }, + "when": { + "type": "string" + }, + "where": { + "type": "string" + }, + "why": { + "type": "string" + }, + "how": { + "type": "string" + } + } + }, + "report_indicators": { + "type": "array", + "items": { + "type": "string" + } + }, + "included_fields": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "example": { + "rentity_id": "FIU-US-00123", + "currency_code_local": "USD" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"rentity_id\": \"FIU-US-00123\", \"currency_code_local\": \"USD\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.regulatory_reports.update(\n \"5ec00000-0000-4000-8000-000000000007\",\n rentity_id=\"FIU-US-00123\",\n currency_code_local=\"USD\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.regulatoryReports.update(\"5ec00000-0000-4000-8000-000000000007\", {\n rentity_id: \"FIU-US-00123\",\n currency_code_local: \"USD\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.RegulatoryReports.Update(ctx, \"5ec00000-0000-4000-8000-000000000007\", didit.RegulatoryReportsUpdateParams{\n\tRentityID: didit.String(\"FIU-US-00123\"),\n\tCurrencyCodeLocal: didit.String(\"USD\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.RegulatoryReportsUpdateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.regulatoryReports().update(\"5ec00000-0000-4000-8000-000000000007\", RegulatoryReportsUpdateParams.builder()\n .rentityId(\"FIU-US-00123\")\n .currencyCodeLocal(\"USD\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->update('5ec00000-0000-4000-8000-000000000007', [\n 'rentity_id' => 'FIU-US-00123',\n 'currency_code_local' => 'USD',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.regulatory_reports.update(\n \"5ec00000-0000-4000-8000-000000000007\",\n rentity_id: \"FIU-US-00123\",\n currency_code_local: \"USD\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.RegulatoryReports.UpdateAsync(\"5ec00000-0000-4000-8000-000000000007\", new RegulatoryReportsUpdateParams\n{\n RentityId = \"FIU-US-00123\",\n CurrencyCodeLocal = \"USD\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Updated report.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "409": { + "description": "Report is already finalized.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete report", + "description": "Delete a regulatory report permanently. The report, its copied narrative and reasons, its transaction links, and its generated goAML XML and PDF are erased, including every stored version of those files. The report disappears from lists and download URLs stop working. Other records, including the case, transactions, people and companies, are not deleted. There is no undo.\n\nA draft or validated report can be deleted without a request body. Deleting a finalized report requires `confirm_delete: true` in the request body. If the report is missing, the request returns `404`; if a finalized report is sent without confirmation, it returns `400` and nothing is deleted.", + "operationId": "delete_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl (draft report)", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "curl", + "label": "curl (finalized report)", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"confirm_delete\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.regulatory_reports.delete(\n \"5ec00000-0000-4000-8000-000000000007\",\n confirm_delete=True,\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.regulatoryReports.delete(\"5ec00000-0000-4000-8000-000000000007\", {\n confirm_delete: true,\n});" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.RegulatoryReportsDeleteParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.regulatoryReports().delete(\"5ec00000-0000-4000-8000-000000000007\", RegulatoryReportsDeleteParams.builder()\n .confirmDelete(true)\n .build());" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->delete('5ec00000-0000-4000-8000-000000000007', [\n 'confirm_delete' => true,\n]);" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.regulatory_reports.delete(\n \"5ec00000-0000-4000-8000-000000000007\",\n confirm_delete: true\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.RegulatoryReports.DeleteAsync(\"5ec00000-0000-4000-8000-000000000007\", new RegulatoryReportsDeleteParams\n{\n ConfirmDelete = true,\n});" + } + ], + "responses": { + "204": { + "description": "Report deleted, including its generated files." + }, + "400": { + "description": "A finalized report was sent without `confirm_delete: true`. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "confirm_delete": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "example": { + "confirm_delete": [ + "This field is required to delete a finalized report." + ] + } + } + } + }, + "403": { + "description": "The API key cannot delete reports. The endpoint requires `write:cases`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "Report not found in this application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "409": { + "description": "The report is already being deleted. Repeat the request to finish erasing its files.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "This report is being deleted. Retry deleting it to finish erasing its files.", + "code": "report_deletion_pending" + } + } + } + }, + "503": { + "description": "The report files could not be erased right now. The report is still in place. Repeat the request.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "The report files could not be erased. Please retry deleting the report.", + "code": "report_storage_unavailable" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": false, + "description": "Required only for a finalized report.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "confirm_delete": { + "type": "boolean", + "description": "Set to true to confirm that a finalized report and its generated files are deleted permanently." + } + }, + "example": { + "confirm_delete": true + } + }, + "example": { + "confirm_delete": true + } + } + } + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/{report_uuid}/validate/": { + "post": { + "summary": "Validate report", + "description": "Check the report against the obligatory goAML header fields (reporting entity ID, submission code, local currency, action taken, submission date), plus at least one attached transaction and a 2-letter jurisdiction, without generating any files. Also stores the result on the report: validation_errors and validated_at are updated, and a non-finalized report's status moves to VALIDATED (no errors) or back to DRAFT (errors).", + "operationId": "validate_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "validate", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/validate/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.regulatory_reports.validate(\n \"5ec00000-0000-4000-8000-000000000007\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.regulatoryReports.validate(\"5ec00000-0000-4000-8000-000000000007\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.RegulatoryReports.Validate(ctx, \"5ec00000-0000-4000-8000-000000000007\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.regulatoryReports().validate(\"5ec00000-0000-4000-8000-000000000007\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->validate('5ec00000-0000-4000-8000-000000000007');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.regulatory_reports.validate(\"5ec00000-0000-4000-8000-000000000007\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.RegulatoryReports.ValidateAsync(\"5ec00000-0000-4000-8000-000000000007\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Validation result.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "DRAFT", + "VALIDATED", + "FINALIZED" + ] + }, + "is_valid": { + "type": "boolean" + }, + "errors": { + "type": "array", + "description": "Empty when valid.", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/{report_uuid}/finalize/": { + "post": { + "summary": "Finalize report", + "description": "Re-validate, then render the goAML XML and PDF and upload both to S3. On SAR/STR/TFR reports, this also moves the case's transaction alerts from Pending SAR to SAR filed.", + "operationId": "finalize_regulatory_report", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "finalize", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/finalize/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.regulatory_reports.finalize(\n \"5ec00000-0000-4000-8000-000000000007\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.regulatoryReports.finalize(\"5ec00000-0000-4000-8000-000000000007\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.RegulatoryReports.Finalize(ctx, \"5ec00000-0000-4000-8000-000000000007\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.regulatoryReports().finalize_(\"5ec00000-0000-4000-8000-000000000007\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->finalize('5ec00000-0000-4000-8000-000000000007');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.regulatory_reports.finalize(\"5ec00000-0000-4000-8000-000000000007\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.RegulatoryReports.FinalizeAsync(\"5ec00000-0000-4000-8000-000000000007\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Report finalized; xml_s3_key and pdf_s3_key are set.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "400": { + "description": "Report failed validation.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "errors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "409": { + "description": "Report is not in a finalizable state.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/regulatory-reports/{report_uuid}/download-url/": { + "get": { + "summary": "Get report download URL", + "description": "Mint a presigned URL for a finalized report's goAML XML or PDF.", + "operationId": "get_regulatory_report_download_url", + "x-didit-sdk": { + "resource": "regulatory_reports", + "method": "retrieve_download_url", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Regulatory Reports" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "report_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "5ec00000-0000-4000-8000-000000000007" + }, + "description": "Report UUID." + }, + { + "name": "file", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "pdf", + "xml" + ], + "default": "pdf" + }, + "description": "Which file to download." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/regulatory-reports/5ec00000-0000-4000-8000-000000000007/download-url/?file=xml' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.regulatory_reports.retrieve_download_url(\n \"5ec00000-0000-4000-8000-000000000007\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.regulatoryReports.retrieveDownloadUrl(\"5ec00000-0000-4000-8000-000000000007\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.RegulatoryReports.RetrieveDownloadURL(ctx, \"5ec00000-0000-4000-8000-000000000007\", didit.RegulatoryReportsRetrieveDownloadURLParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.regulatoryReports().retrieveDownloadUrl(\"5ec00000-0000-4000-8000-000000000007\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "regulatoryReports->retrieveDownloadUrl('5ec00000-0000-4000-8000-000000000007');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.regulatory_reports.retrieve_download_url(\"5ec00000-0000-4000-8000-000000000007\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.RegulatoryReports.RetrieveDownloadUrlAsync(\"5ec00000-0000-4000-8000-000000000007\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Presigned download URL.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "download_url": { + "type": "string", + "format": "uri" + }, + "file": { + "type": "string", + "enum": [ + "pdf", + "xml" + ] + } + } + } + } + } + }, + "409": { + "description": "Report is not finalized yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "status": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/transactions/rules/": { + "get": { + "summary": "List rules", + "description": "List the application's transaction-monitoring rules: your custom rules plus any library presets you have installed. Filter by source, category, mode, bundle or free-text search; order with `ordering` (run_count, approved_pct, reviewed_pct, declined_pct, latest_triggered_at, title, created_at, source, each with an optional `-` prefix).", + "operationId": "listTransactionRules", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "source", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "PRESET", + "CUSTOM" + ] + }, + "description": "PRESET or CUSTOM" + }, + { + "name": "category", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Filter by rule category" + }, + { + "name": "mode", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ] + }, + "description": "ACTIVE, DISABLED or TEST" + }, + { + "name": "bundle", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Filter presets by bundle" + }, + { + "name": "search", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Case-insensitive match against title and description" + }, + { + "name": "ordering", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated ordering fields, default source,-run_count" + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "description": "Page size" + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "description": "Page offset" + } + ], + "responses": { + "200": { + "description": "Paginated rules.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleDetail" + } + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/rules/?source=CUSTOM&ordering=-run_count' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.transactions.rules.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.transactions.rules.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Transactions.Rules.ListAutoPaging(ctx, didit.TransactionsRulesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.transactions().rules().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.rules.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Transactions.Rules.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "summary": "Create rule", + "description": "Create a CUSTOM transaction-monitoring rule. Titles are unique per application (duplicate -> 400). A `change_status` action targeting `AWAITING_USER` must carry `workflow_id`. Rules created in ACTIVE mode apply to new transactions immediately - the recommended workflow is create in TEST mode, backtest, then switch mode to ACTIVE. To change a library rule, create it as a copy: send the library rule's definition with `metadata.based_on` (its library key) and `metadata.based_on_rule_uuid`.", + "operationId": "createTransactionRule", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleCreateRequest" + }, + "example": { + "title": "Structuring - inbound (custom)", + "description": "20+ inbound EUR transfers, all under 10,000 EUR, 30 days", + "category": "finance", + "mode": "TEST", + "evaluation_mode": "ALL", + "scope": { + "transaction_types": [ + "finance" + ], + "directions": [ + "INBOUND" + ] + }, + "conditions": [ + { + "field": "currency", + "operator": "eq", + "value": "EUR" + }, + { + "field": "amount", + "operator": "lt", + "value": 10000 + } + ], + "aggregation": [ + { + "metric": "count", + "operator": "gte", + "value": 20, + "window": "30d", + "filters": { + "direction": "INBOUND", + "transaction_type": "finance", + "subject_vendor_data": "__current__", + "currency": "EUR" + } + }, + { + "metric": "max", + "field": "amount", + "operator": "lt", + "value": 10000, + "window": "30d", + "filters": { + "direction": "INBOUND", + "transaction_type": "finance", + "subject_vendor_data": "__current__", + "currency": "EUR" + } + } + ], + "actions": [ + { + "type": "add_score", + "value": 35 + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "The created rule.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleDetail" + } + } + } + }, + "400": { + "description": "Validation error - duplicate title, unknown operator/metric, malformed window, invalid scope direction, or AWAITING_USER without workflow_id.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "title": [ + "A rule with this name already exists." + ] + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/rules/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"title\": \"Structuring - inbound (custom)\", \"description\": \"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\", \"category\": \"finance\", \"mode\": \"TEST\", \"evaluation_mode\": \"ALL\", \"scope\": {\"transaction_types\": [\"finance\"], \"directions\": [\"INBOUND\"]}, \"conditions\": [{\"field\": \"currency\", \"operator\": \"eq\", \"value\": \"EUR\"}, {\"field\": \"amount\", \"operator\": \"lt\", \"value\": 10000}], \"aggregation\": [{\"metric\": \"count\", \"operator\": \"gte\", \"value\": 20, \"window\": \"30d\", \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}}, {\"metric\": \"max\", \"field\": \"amount\", \"operator\": \"lt\", \"value\": 10000, \"window\": \"30d\", \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}}], \"actions\": [{\"type\": \"add_score\", \"value\": 35}]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.rules.create(\n title=\"Structuring - inbound (custom)\",\n description=\"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\",\n category=\"finance\",\n mode=\"TEST\",\n evaluation_mode=\"ALL\",\n scope={\n \"transaction_types\": [\"finance\"],\n \"directions\": [\"INBOUND\"],\n },\n conditions=[{\n \"field\": \"currency\",\n \"operator\": \"eq\",\n \"value\": \"EUR\",\n }, {\n \"field\": \"amount\",\n \"operator\": \"lt\",\n \"value\": 10000,\n }],\n aggregation=[{\n \"metric\": \"count\",\n \"operator\": \"gte\",\n \"value\": 20.0,\n \"window\": \"30d\",\n \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"},\n }, {\n \"metric\": \"max\",\n \"field\": \"amount\",\n \"operator\": \"lt\",\n \"value\": 10000.0,\n \"window\": \"30d\",\n \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"},\n }],\n actions=[{\n \"type\": \"add_score\",\n \"value\": 35,\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.rules.create({\n title: \"Structuring - inbound (custom)\",\n description: \"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\",\n category: \"finance\",\n mode: \"TEST\",\n evaluation_mode: \"ALL\",\n scope: {\n transaction_types: [\"finance\"],\n directions: [\"INBOUND\"],\n },\n conditions: [{\n field: \"currency\",\n operator: \"eq\",\n value: \"EUR\",\n }, {\n field: \"amount\",\n operator: \"lt\",\n value: 10000,\n }],\n aggregation: [{\n metric: \"count\",\n operator: \"gte\",\n value: 20.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }, {\n metric: \"max\",\n field: \"amount\",\n operator: \"lt\",\n value: 10000.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }],\n actions: [{\n type: \"add_score\",\n value: 35,\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Rules.Create(ctx, didit.TransactionRuleCreateRequest{\n\tTitle: \"Structuring - inbound (custom)\",\n\tDescription: didit.String(\"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\"),\n\tCategory: \"finance\",\n\tMode: \"TEST\",\n\tEvaluationMode: didit.Some(didit.TransactionRuleCreateRequestEvaluationMode(\"ALL\")),\n\tScope: didit.Some(didit.TransactionRuleScopeParam{\n\t\tTransactionTypes: didit.Some([]string{\"finance\"}),\n\t\tDirections: didit.Some([]didit.TransactionRuleScopeDirections{\"INBOUND\"}),\n\t}),\n\tConditions: didit.Some([]didit.TransactionRuleConditionParam{{\n\t\tField: \"currency\",\n\t\tOperator: \"eq\",\n\t\tValue: didit.Some[any](\"EUR\"),\n\t}, {\n\t\tField: \"amount\",\n\t\tOperator: \"lt\",\n\t\tValue: didit.Some[any](10000),\n\t}}),\n\tAggregation: didit.Some([]didit.TransactionRuleAggregationParam{{\n\t\tMetric: didit.Some(didit.TransactionRuleAggregationMetric(\"count\")),\n\t\tOperator: didit.Some(didit.TransactionRuleAggregationOperator(\"gte\")),\n\t\tValue: 20.0,\n\t\tWindow: didit.String(\"30d\"),\n\t\tFilters: didit.Some(map[string]any{\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}),\n\t}, {\n\t\tMetric: didit.Some(didit.TransactionRuleAggregationMetric(\"max\")),\n\t\tField: didit.String(\"amount\"),\n\t\tOperator: didit.Some(didit.TransactionRuleAggregationOperator(\"lt\")),\n\t\tValue: 10000.0,\n\t\tWindow: didit.String(\"30d\"),\n\t\tFilters: didit.Some(map[string]any{\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}),\n\t}}),\n\tActions: didit.Some([]didit.TransactionRuleActionParam{{\n\t\tType: \"add_score\",\n\t\tValue: didit.Some[any](35),\n\t}}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.TransactionRuleAction;\nimport me.didit.types.TransactionRuleActionAddScore;\nimport me.didit.types.TransactionRuleAggregation;\nimport me.didit.types.TransactionRuleCondition;\nimport me.didit.types.TransactionRuleCreateRequest;\nimport me.didit.types.TransactionRuleScope;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().rules().create(TransactionRuleCreateRequest.builder()\n .title(\"Structuring - inbound (custom)\")\n .description(\"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\")\n .category(\"finance\")\n .mode(TransactionRuleCreateRequest.Mode.of(\"TEST\"))\n .evaluationMode(TransactionRuleCreateRequest.EvaluationMode.of(\"ALL\"))\n .scope(TransactionRuleScope.builder()\n .transactionTypes(List.of(\"finance\"))\n .directions(List.of(TransactionRuleScope.Directions.of(\"INBOUND\")))\n .build())\n .conditions(List.of(\n TransactionRuleCondition.builder()\n .field(\"currency\")\n .operator(TransactionRuleCondition.Operator.of(\"eq\"))\n .value(\"EUR\")\n .build(),\n TransactionRuleCondition.builder()\n .field(\"amount\")\n .operator(TransactionRuleCondition.Operator.of(\"lt\"))\n .value(10000)\n .build()))\n .aggregation(List.of(\n TransactionRuleAggregation.builder()\n .metric(TransactionRuleAggregation.Metric.of(\"count\"))\n .operator(TransactionRuleAggregation.Operator.of(\"gte\"))\n .value(20.0)\n .window(\"30d\")\n .filters(Map.of(\"direction\", \"INBOUND\", \"transaction_type\", \"finance\", \"subject_vendor_data\", \"__current__\", \"currency\", \"EUR\"))\n .build(),\n TransactionRuleAggregation.builder()\n .metric(TransactionRuleAggregation.Metric.of(\"max\"))\n .field(\"amount\")\n .operator(TransactionRuleAggregation.Operator.of(\"lt\"))\n .value(10000.0)\n .window(\"30d\")\n .filters(Map.of(\"direction\", \"INBOUND\", \"transaction_type\", \"finance\", \"subject_vendor_data\", \"__current__\", \"currency\", \"EUR\"))\n .build()))\n .actions(List.of(TransactionRuleAction.ofAddScore(TransactionRuleActionAddScore.builder()\n .type(TransactionRuleActionAddScore.Type.of(\"add_score\"))\n .value(35L)\n .build())))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->create([\n 'title' => 'Structuring - inbound (custom)',\n 'description' => '20+ inbound EUR transfers, all under 10,000 EUR, 30 days',\n 'category' => 'finance',\n 'mode' => 'TEST',\n 'evaluation_mode' => 'ALL',\n 'scope' => [\n 'transaction_types' => ['finance'],\n 'directions' => ['INBOUND'],\n ],\n 'conditions' => [[\n 'field' => 'currency',\n 'operator' => 'eq',\n 'value' => 'EUR',\n ], [\n 'field' => 'amount',\n 'operator' => 'lt',\n 'value' => 10000,\n ]],\n 'aggregation' => [[\n 'metric' => 'count',\n 'operator' => 'gte',\n 'value' => 20.0,\n 'window' => '30d',\n 'filters' => ['direction' => 'INBOUND', 'transaction_type' => 'finance', 'subject_vendor_data' => '__current__', 'currency' => 'EUR'],\n ], [\n 'metric' => 'max',\n 'field' => 'amount',\n 'operator' => 'lt',\n 'value' => 10000.0,\n 'window' => '30d',\n 'filters' => ['direction' => 'INBOUND', 'transaction_type' => 'finance', 'subject_vendor_data' => '__current__', 'currency' => 'EUR'],\n ]],\n 'actions' => [[\n 'type' => 'add_score',\n 'value' => 35,\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.rules.create(\n title: \"Structuring - inbound (custom)\",\n description: \"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\",\n category: \"finance\",\n mode: \"TEST\",\n evaluation_mode: \"ALL\",\n scope: {\n transaction_types: [\"finance\"],\n directions: [\"INBOUND\"],\n },\n conditions: [{\n field: \"currency\",\n operator: \"eq\",\n value: \"EUR\",\n }, {\n field: \"amount\",\n operator: \"lt\",\n value: 10000,\n }],\n aggregation: [{\n metric: \"count\",\n operator: \"gte\",\n value: 20.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }, {\n metric: \"max\",\n field: \"amount\",\n operator: \"lt\",\n value: 10000.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }],\n actions: [{\n type: \"add_score\",\n value: 35,\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Rules.CreateAsync(new TransactionRuleCreateRequest\n{\n Title = \"Structuring - inbound (custom)\",\n Description = \"20+ inbound EUR transfers, all under 10,000 EUR, 30 days\",\n Category = \"finance\",\n Mode = \"TEST\",\n EvaluationMode = new TransactionRuleCreateRequestEvaluationMode(\"ALL\"),\n Scope = new TransactionRuleScope\n {\n TransactionTypes = new string[] { \"finance\" },\n Directions = new TransactionRuleScopeDirections[] { \"INBOUND\" },\n },\n Conditions = new TransactionRuleCondition[]\n {\n new()\n {\n Field = \"currency\",\n Operator = \"eq\",\n Value = \"EUR\",\n },\n new()\n {\n Field = \"amount\",\n Operator = \"lt\",\n Value = 10000,\n },\n },\n Aggregation = new TransactionRuleAggregation[]\n {\n new()\n {\n Metric = new TransactionRuleAggregationMetric(\"count\"),\n Operator = new TransactionRuleAggregationOperator(\"gte\"),\n Value = 20.0,\n Window = \"30d\",\n Filters = new Dictionary { [\"direction\"] = \"INBOUND\", [\"transaction_type\"] = \"finance\", [\"subject_vendor_data\"] = \"__current__\", [\"currency\"] = \"EUR\" },\n },\n new()\n {\n Metric = new TransactionRuleAggregationMetric(\"max\"),\n Field = \"amount\",\n Operator = new TransactionRuleAggregationOperator(\"lt\"),\n Value = 10000.0,\n Window = \"30d\",\n Filters = new Dictionary { [\"direction\"] = \"INBOUND\", [\"transaction_type\"] = \"finance\", [\"subject_vendor_data\"] = \"__current__\", [\"currency\"] = \"EUR\" },\n },\n },\n Actions = new TransactionRuleAction[]\n {\n new()\n {\n Type = new TransactionRuleActionType(\"add_score\"),\n Value = 35,\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/transactions/rules/{rule_uuid}/": { + "parameters": [ + { + "name": "rule_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The rule's uuid from list or create." + } + ], + "get": { + "summary": "Get rule", + "description": "Retrieve one rule with its full configuration and execution counters.", + "operationId": "getTransactionRule", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "The rule.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleDetail" + } + } + } + }, + "404": { + "description": "No such rule on this application.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/rules/RULE_UUID/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.rules.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.rules.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Rules.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().rules().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.rules.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Rules.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "patch": { + "summary": "Update rule", + "description": "Partially update a rule. Custom rules accept every configuration field. Library (PRESET) rules accept only `mode` - use it to activate, disable, or switch a preset to TEST; any other field returns 400, because Didit maintains library rules and applies their updates automatically. To change a library rule's conditions, create a custom copy with `POST /v3/transactions/rules/` using the library rule's definition and `metadata.based_on` (the library key) and `metadata.based_on_rule_uuid`, then switch the library rule off with `mode: DISABLED` so a transaction does not alert twice. An installed copy of a withheld library rule is switched off: setting its `mode` to anything but `DISABLED` answers 400 (\"This library rule cannot run yet: its conditions read data the rules engine cannot evaluate.\").", + "operationId": "updateTransactionRule", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleUpdateRequest" + }, + "example": { + "mode": "ACTIVE" + } + } + } + }, + "responses": { + "200": { + "description": "The updated rule.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleDetail" + } + } + } + }, + "400": { + "description": "Validation error, a non-mode field on a library rule, or a mode other than DISABLED on a withheld library rule.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/transactions/rules/RULE_UUID/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"mode\": \"ACTIVE\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.rules.update(\n \"11111111-2222-3333-4444-555555555555\",\n mode=\"ACTIVE\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.rules.update(\"11111111-2222-3333-4444-555555555555\", {\n mode: \"ACTIVE\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Rules.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TransactionRuleUpdateRequest{\n\tMode: didit.Some(didit.TransactionRuleUpdateRequestMode(\"ACTIVE\")),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TransactionRuleUpdateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().rules().update(\"11111111-2222-3333-4444-555555555555\", TransactionRuleUpdateRequest.builder()\n .mode(TransactionRuleUpdateRequest.Mode.of(\"ACTIVE\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->update('11111111-2222-3333-4444-555555555555', [\n 'mode' => 'ACTIVE',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.rules.update(\n \"11111111-2222-3333-4444-555555555555\",\n mode: \"ACTIVE\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Rules.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new TransactionRuleUpdateRequest\n{\n Mode = new TransactionRuleUpdateRequestMode(\"ACTIVE\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "summary": "Delete rule", + "description": "Delete a CUSTOM rule. Deletion is immediate and permanent: the rule stops evaluating for new transactions and disappears from listings, while past rule runs on already-evaluated transactions keep their recorded results. Library rules cannot be deleted - uninstall them via the install endpoint instead (they can always be reinstalled from the library).", + "operationId": "deleteTransactionRule", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "204": { + "description": "Deleted." + }, + "400": { + "description": "The rule is a library preset - uninstall instead.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "detail": "Library rules cannot be deleted. Uninstall them via the rules install endpoint instead." + } + } + } + }, + "404": { + "description": "No such rule on this application.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/transactions/rules/RULE_UUID/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.transactions.rules.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.transactions.rules.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Transactions.Rules.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.transactions().rules().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.rules.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Transactions.Rules.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ] + } + }, + "/v3/transactions/rules/backtest/": { + "post": { + "summary": "Backtest rule configuration", + "description": "Evaluate a hypothetical rule configuration against your recent transactions (most recent first, up to the backtest cap) without creating a rule or touching any transaction. Use it before activating a rule to see how often it would have matched and how many subjects it would have affected.", + "operationId": "backtestTransactionRules", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "backtest", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleBacktestRequest" + }, + "example": { + "conditions": [ + { + "field": "currency", + "operator": "eq", + "value": "EUR" + }, + { + "field": "amount", + "operator": "lt", + "value": 10000 + } + ], + "aggregation": [ + { + "metric": "count", + "operator": "gte", + "value": 20, + "window": "30d", + "filters": { + "direction": "INBOUND", + "transaction_type": "finance", + "subject_vendor_data": "__current__", + "currency": "EUR" + } + }, + { + "metric": "max", + "field": "amount", + "operator": "lt", + "value": 10000, + "window": "30d", + "filters": { + "direction": "INBOUND", + "transaction_type": "finance", + "subject_vendor_data": "__current__", + "currency": "EUR" + } + } + ], + "evaluation_mode": "ALL", + "scope": { + "transaction_types": [ + "finance" + ], + "directions": [ + "INBOUND" + ] + }, + "period_days": 90 + } + } + } + }, + "responses": { + "200": { + "description": "Backtest statistics.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleBacktestResult" + }, + "example": { + "evaluated": 4820, + "matched": 12, + "affected_entities": 4, + "period_days": 90 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/rules/backtest/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"conditions\": [{\"field\": \"currency\", \"operator\": \"eq\", \"value\": \"EUR\"}, {\"field\": \"amount\", \"operator\": \"lt\", \"value\": 10000}], \"aggregation\": [{\"metric\": \"count\", \"operator\": \"gte\", \"value\": 20, \"window\": \"30d\", \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}}, {\"metric\": \"max\", \"field\": \"amount\", \"operator\": \"lt\", \"value\": 10000, \"window\": \"30d\", \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}}], \"evaluation_mode\": \"ALL\", \"scope\": {\"transaction_types\": [\"finance\"], \"directions\": [\"INBOUND\"]}, \"period_days\": 90}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.rules.backtest(\n conditions=[{\n \"field\": \"currency\",\n \"operator\": \"eq\",\n \"value\": \"EUR\",\n }, {\n \"field\": \"amount\",\n \"operator\": \"lt\",\n \"value\": 10000,\n }],\n aggregation=[{\n \"metric\": \"count\",\n \"operator\": \"gte\",\n \"value\": 20.0,\n \"window\": \"30d\",\n \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"},\n }, {\n \"metric\": \"max\",\n \"field\": \"amount\",\n \"operator\": \"lt\",\n \"value\": 10000.0,\n \"window\": \"30d\",\n \"filters\": {\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"},\n }],\n evaluation_mode=\"ALL\",\n scope={\n \"transaction_types\": [\"finance\"],\n \"directions\": [\"INBOUND\"],\n },\n period_days=90,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.rules.backtest({\n conditions: [{\n field: \"currency\",\n operator: \"eq\",\n value: \"EUR\",\n }, {\n field: \"amount\",\n operator: \"lt\",\n value: 10000,\n }],\n aggregation: [{\n metric: \"count\",\n operator: \"gte\",\n value: 20.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }, {\n metric: \"max\",\n field: \"amount\",\n operator: \"lt\",\n value: 10000.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }],\n evaluation_mode: \"ALL\",\n scope: {\n transaction_types: [\"finance\"],\n directions: [\"INBOUND\"],\n },\n period_days: 90,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Rules.Backtest(ctx, didit.TransactionRuleBacktestRequest{\n\tConditions: didit.Some([]didit.TransactionRuleConditionParam{{\n\t\tField: \"currency\",\n\t\tOperator: \"eq\",\n\t\tValue: didit.Some[any](\"EUR\"),\n\t}, {\n\t\tField: \"amount\",\n\t\tOperator: \"lt\",\n\t\tValue: didit.Some[any](10000),\n\t}}),\n\tAggregation: didit.Some([]didit.TransactionRuleAggregationParam{{\n\t\tMetric: didit.Some(didit.TransactionRuleAggregationMetric(\"count\")),\n\t\tOperator: didit.Some(didit.TransactionRuleAggregationOperator(\"gte\")),\n\t\tValue: 20.0,\n\t\tWindow: didit.String(\"30d\"),\n\t\tFilters: didit.Some(map[string]any{\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}),\n\t}, {\n\t\tMetric: didit.Some(didit.TransactionRuleAggregationMetric(\"max\")),\n\t\tField: didit.String(\"amount\"),\n\t\tOperator: didit.Some(didit.TransactionRuleAggregationOperator(\"lt\")),\n\t\tValue: 10000.0,\n\t\tWindow: didit.String(\"30d\"),\n\t\tFilters: didit.Some(map[string]any{\"direction\": \"INBOUND\", \"transaction_type\": \"finance\", \"subject_vendor_data\": \"__current__\", \"currency\": \"EUR\"}),\n\t}}),\n\tEvaluationMode: didit.Some(didit.TransactionRuleBacktestRequestEvaluationMode(\"ALL\")),\n\tScope: didit.Some(didit.TransactionRuleScopeParam{\n\t\tTransactionTypes: didit.Some([]string{\"finance\"}),\n\t\tDirections: didit.Some([]didit.TransactionRuleScopeDirections{\"INBOUND\"}),\n\t}),\n\tPeriodDays: didit.Int(90),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.TransactionRuleAggregation;\nimport me.didit.types.TransactionRuleBacktestRequest;\nimport me.didit.types.TransactionRuleCondition;\nimport me.didit.types.TransactionRuleScope;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().rules().backtest(TransactionRuleBacktestRequest.builder()\n .conditions(List.of(\n TransactionRuleCondition.builder()\n .field(\"currency\")\n .operator(TransactionRuleCondition.Operator.of(\"eq\"))\n .value(\"EUR\")\n .build(),\n TransactionRuleCondition.builder()\n .field(\"amount\")\n .operator(TransactionRuleCondition.Operator.of(\"lt\"))\n .value(10000)\n .build()))\n .aggregation(List.of(\n TransactionRuleAggregation.builder()\n .metric(TransactionRuleAggregation.Metric.of(\"count\"))\n .operator(TransactionRuleAggregation.Operator.of(\"gte\"))\n .value(20.0)\n .window(\"30d\")\n .filters(Map.of(\"direction\", \"INBOUND\", \"transaction_type\", \"finance\", \"subject_vendor_data\", \"__current__\", \"currency\", \"EUR\"))\n .build(),\n TransactionRuleAggregation.builder()\n .metric(TransactionRuleAggregation.Metric.of(\"max\"))\n .field(\"amount\")\n .operator(TransactionRuleAggregation.Operator.of(\"lt\"))\n .value(10000.0)\n .window(\"30d\")\n .filters(Map.of(\"direction\", \"INBOUND\", \"transaction_type\", \"finance\", \"subject_vendor_data\", \"__current__\", \"currency\", \"EUR\"))\n .build()))\n .evaluationMode(TransactionRuleBacktestRequest.EvaluationMode.of(\"ALL\"))\n .scope(TransactionRuleScope.builder()\n .transactionTypes(List.of(\"finance\"))\n .directions(List.of(TransactionRuleScope.Directions.of(\"INBOUND\")))\n .build())\n .periodDays(90L)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->backtest([\n 'conditions' => [[\n 'field' => 'currency',\n 'operator' => 'eq',\n 'value' => 'EUR',\n ], [\n 'field' => 'amount',\n 'operator' => 'lt',\n 'value' => 10000,\n ]],\n 'aggregation' => [[\n 'metric' => 'count',\n 'operator' => 'gte',\n 'value' => 20.0,\n 'window' => '30d',\n 'filters' => ['direction' => 'INBOUND', 'transaction_type' => 'finance', 'subject_vendor_data' => '__current__', 'currency' => 'EUR'],\n ], [\n 'metric' => 'max',\n 'field' => 'amount',\n 'operator' => 'lt',\n 'value' => 10000.0,\n 'window' => '30d',\n 'filters' => ['direction' => 'INBOUND', 'transaction_type' => 'finance', 'subject_vendor_data' => '__current__', 'currency' => 'EUR'],\n ]],\n 'evaluation_mode' => 'ALL',\n 'scope' => [\n 'transaction_types' => ['finance'],\n 'directions' => ['INBOUND'],\n ],\n 'period_days' => 90,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.rules.backtest(\n conditions: [{\n field: \"currency\",\n operator: \"eq\",\n value: \"EUR\",\n }, {\n field: \"amount\",\n operator: \"lt\",\n value: 10000,\n }],\n aggregation: [{\n metric: \"count\",\n operator: \"gte\",\n value: 20.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }, {\n metric: \"max\",\n field: \"amount\",\n operator: \"lt\",\n value: 10000.0,\n window: \"30d\",\n filters: { direction: \"INBOUND\", transaction_type: \"finance\", subject_vendor_data: \"__current__\", currency: \"EUR\" },\n }],\n evaluation_mode: \"ALL\",\n scope: {\n transaction_types: [\"finance\"],\n directions: [\"INBOUND\"],\n },\n period_days: 90\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Rules.BacktestAsync(new TransactionRuleBacktestRequest\n{\n Conditions = new TransactionRuleCondition[]\n {\n new()\n {\n Field = \"currency\",\n Operator = \"eq\",\n Value = \"EUR\",\n },\n new()\n {\n Field = \"amount\",\n Operator = \"lt\",\n Value = 10000,\n },\n },\n Aggregation = new TransactionRuleAggregation[]\n {\n new()\n {\n Metric = new TransactionRuleAggregationMetric(\"count\"),\n Operator = new TransactionRuleAggregationOperator(\"gte\"),\n Value = 20.0,\n Window = \"30d\",\n Filters = new Dictionary { [\"direction\"] = \"INBOUND\", [\"transaction_type\"] = \"finance\", [\"subject_vendor_data\"] = \"__current__\", [\"currency\"] = \"EUR\" },\n },\n new()\n {\n Metric = new TransactionRuleAggregationMetric(\"max\"),\n Field = \"amount\",\n Operator = new TransactionRuleAggregationOperator(\"lt\"),\n Value = 10000.0,\n Window = \"30d\",\n Filters = new Dictionary { [\"direction\"] = \"INBOUND\", [\"transaction_type\"] = \"finance\", [\"subject_vendor_data\"] = \"__current__\", [\"currency\"] = \"EUR\" },\n },\n },\n EvaluationMode = new TransactionRuleBacktestRequestEvaluationMode(\"ALL\"),\n Scope = new TransactionRuleScope\n {\n TransactionTypes = new string[] { \"finance\" },\n Directions = new TransactionRuleScopeDirections[] { \"INBOUND\" },\n },\n PeriodDays = 90,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/transactions/rules/library/": { + "get": { + "summary": "List rule library", + "description": "Browse the full preset rule library (150+ rules) with each entry's complete configuration and an `is_installed` flag for this application. Filter by bundle, category, or free-text search. A library rule whose conditions read data the rules engine cannot evaluate yet is withheld until it can run. Withheld rules are not listed.", + "operationId": "listTransactionRuleLibrary", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "list_library", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "bundle", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Filter by bundle, e.g. finance, aml_ctf, crypto_monitoring, travel_rule" + }, + { + "name": "category", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Filter by category" + }, + { + "name": "search", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Case-insensitive match against title and description" + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "description": "Page size" + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "description": "Page offset" + } + ], + "responses": { + "200": { + "description": "Paginated library entries.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleLibraryEntry" + } + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/rules/library/?bundle=finance' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.transactions.rules.list_library():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.transactions.rules.listLibrary()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Transactions.Rules.ListLibraryAutoPaging(ctx, didit.TransactionsRulesListLibraryParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.transactions().rules().listLibrary()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->listLibrary() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.rules.list_library.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Transactions.Rules.ListLibraryAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/transactions/rules/install/": { + "post": { + "summary": "Install library rules", + "description": "Install one or more presets onto the application, by `library_keys` or a whole `bundle`. An installed preset is active at once while transaction monitoring is on, and disabled otherwise; already-installed keys are skipped. A library rule whose conditions read data the rules engine cannot evaluate yet is withheld until it can run. A request that names one in `library_keys` answers 400 and installs nothing (\"These library rules cannot run yet: their conditions read data the rules engine cannot evaluate.\" followed by the keys), and a `bundle` install skips it. A withheld rule comes back on its own once its conditions can be evaluated. A `library_keys` entry that is not in the library is skipped, not refused.", + "operationId": "installTransactionRuleLibrary", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "install_library", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleInstallRequest" + }, + "example": { + "library_keys": [ + "structuring-inbound", + "structuring-outbound" + ] + } + } + } + }, + "responses": { + "200": { + "description": "What was installed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleInstallResult" + }, + "example": { + "installed_library_keys": [ + "structuring-inbound", + "structuring-outbound" + ], + "installed_count": 2 + } + } + } + }, + "400": { + "description": "Neither library_keys nor bundle provided, `library_keys` is not a list, or a `library_keys` entry is withheld. An unknown entry is skipped and answers 200 without it.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/rules/install/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"library_keys\": [\"structuring-inbound\", \"structuring-outbound\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.rules.install_library(\n library_keys=[\"structuring-inbound\", \"structuring-outbound\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.rules.installLibrary({\n library_keys: [\"structuring-inbound\", \"structuring-outbound\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Rules.InstallLibrary(ctx, didit.TransactionRuleInstallRequest{\n\tLibraryKeys: didit.Some([]string{\"structuring-inbound\", \"structuring-outbound\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.TransactionRuleInstallRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().rules().installLibrary(TransactionRuleInstallRequest.builder()\n .libraryKeys(List.of(\"structuring-inbound\", \"structuring-outbound\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->installLibrary([\n 'library_keys' => ['structuring-inbound', 'structuring-outbound'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.rules.install_library(library_keys: [\"structuring-inbound\", \"structuring-outbound\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Rules.InstallLibraryAsync(new TransactionRuleInstallRequest\n{\n LibraryKeys = new string[] { \"structuring-inbound\", \"structuring-outbound\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "summary": "Uninstall library rules", + "description": "Remove installed presets from the application, by `library_keys` or a whole `bundle`. Uninstalled presets stop evaluating immediately and can be reinstalled from the library at any time.", + "operationId": "uninstallTransactionRuleLibrary", + "x-didit-sdk": { + "resource": "transactions.rules", + "method": "uninstall_library", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleInstallRequest" + }, + "example": { + "bundle": "responsible_gaming" + } + } + } + }, + "responses": { + "200": { + "description": "How many presets were removed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleInstallResult" + }, + "example": { + "uninstalled_count": 5 + } + } + } + }, + "400": { + "description": "Neither library_keys nor bundle provided.", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/transactions/rules/install/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"bundle\": \"responsible_gaming\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.rules.uninstall_library(\n bundle=\"responsible_gaming\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.rules.uninstallLibrary({\n bundle: \"responsible_gaming\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Rules.UninstallLibrary(ctx, didit.TransactionRuleInstallRequest{\n\tBundle: didit.String(\"responsible_gaming\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TransactionRuleInstallRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().rules().uninstallLibrary(TransactionRuleInstallRequest.builder()\n .bundle(\"responsible_gaming\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->rules->uninstallLibrary([\n 'bundle' => 'responsible_gaming',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.rules.uninstall_library(bundle: \"responsible_gaming\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Rules.UninstallLibraryAsync(new TransactionRuleInstallRequest\n{\n Bundle = \"responsible_gaming\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/": { + "get": { + "summary": "List networks", + "description": "List fraud networks detected within this application's organization, highest risk first by default. By default dismissed networks are excluded; pass status to include them. Sandbox applications never have networks: clustering excludes sandbox data entirely. Every response carries the organization's `sharing_mode` and whether cross-organization signals apply to it (`cross_org_available`, `cross_org_unavailable_reason`). `kpis` aggregate the networks that match your filters and `counts` aggregate every network the application can see; pass `summary=false` to skip both when you only need the page. Requires a full-access API key, or `list:networks` for a console member.", + "operationId": "list_networks", + "x-didit-sdk": { + "resource": "networks", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "status", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated statuses to include: active,in_review,resolved,dismissed. Omit to exclude dismissed." + }, + { + "name": "scope", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "identity", + "transactions" + ] + }, + "description": "`transactions` keeps networks whose members are all transaction parties; `identity` keeps every other network. Omit for both. It also narrows `counts.views` and `counts.facets`. Any other value returns 400." + }, + { + "name": "signal_type", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated signal types: ip_address,device,face,document_number,phone,email,address." + }, + { + "name": "pattern_type", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated pattern types, e.g. exact_same_device,same_email." + }, + { + "name": "risk_band", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated bands: low,medium,high." + }, + { + "name": "min_size", + "in": "query", + "schema": { + "type": "integer" + }, + "description": "Minimum member count." + }, + { + "name": "max_size", + "in": "query", + "schema": { + "type": "integer" + }, + "description": "Maximum member count." + }, + { + "name": "assignee", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated assignees: `unassigned`, member emails (matched case-insensitively), or `me` for the calling console member. A network matches any value. `me` needs a console member token: an API key gets 400 `{\"assignee\": [\"\\\"me\\\" needs a user token; an API key has no assignee identity.\"]}`." + }, + { + "name": "priority", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated priorities: `low`, `medium`, `high`, or `none` for networks without a priority. Any other value returns 400 `{\"priority\": \"Unknown priority: .\"}`." + }, + { + "name": "risk_min", + "in": "query", + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "description": "Minimum risk score, a whole number from 0 to 100. A value that is not a whole number returns 400 `{\"risk_min\": \"Use a whole number from 0 to 100.\"}`." + }, + { + "name": "has_case", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "`true` keeps the networks your application opened a case for, `false` the others. Any other value is ignored." + }, + { + "name": "date_from", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Lower bound on last_activity_at (ISO 8601 date or datetime)." + }, + { + "name": "date_to", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Upper bound on first_activity_at (ISO 8601 date or datetime)." + }, + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Free-text search across network name, member names, the network_id (N-0001), and exact pattern_type tokens." + }, + { + "name": "ordering", + "in": "query", + "schema": { + "type": "string" + }, + "description": "One of risk_score, -risk_score, last_activity_at, -last_activity_at, first_activity_at, -first_activity_at, network_number, -network_number. Defaults to -risk_score,-last_activity_at." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 100, + "minimum": 1, + "maximum": 500 + }, + "description": "Page size, 1-500. Defaults to 100." + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + }, + "description": "Zero-based offset of the first record." + }, + { + "name": "summary", + "in": "query", + "schema": { + "type": "boolean", + "default": true + }, + "description": "`false` returns the page and its `count` only, without `kpis` and `counts`. Defaults to `true`. Any value other than `true` or `false` returns 400." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/?status=active,in_review&ordering=-risk_score' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.networks.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.networks.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Networks.ListAutoPaging(ctx, didit.NetworksListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.networks().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.networks.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Networks.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated network list, with the organization's sharing mode and cross-organization availability, plus `kpis` and `counts` unless `summary=false`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "integer", + "nullable": true, + "description": "Offset of the next page, or null on the last page." + }, + "previous": { + "type": "integer", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkListItem" + } + }, + "kpis": { + "type": "object", + "description": "Each key is {value, delta} over the results matching the current filters. Omitted when `summary=false`.", + "properties": { + "active": { + "type": "object" + }, + "in_review": { + "type": "object" + }, + "applicants_involved": { + "type": "object" + }, + "detected_this_period": { + "type": "object" + }, + "false_positive_rate": { + "type": "object" + } + } + }, + "counts": { + "$ref": "#/components/schemas/NetworkListCounts" + }, + "sharing_mode": { + "type": "string", + "enum": [ + "off", + "private", + "global" + ], + "description": "The organization's cross-organization sharing mode. Only `global` compares networks across organizations; `private` and `off` keep them to the organization's own history.", + "example": "global" + }, + "cross_org_available": { + "type": "boolean", + "description": "Whether cross-organization signals apply to this organization. They need both the Enterprise plan and the `global` sharing mode.", + "example": true + }, + "cross_org_unavailable_reason": { + "type": "string", + "enum": [ + "plan", + "sharing_mode" + ], + "nullable": true, + "description": "Why cross-organization signals do not apply, checked in this order. `plan`: the organization's plan does not include the cross-organization fraud network (it needs the Enterprise plan), whatever the sharing mode. `sharing_mode`: the plan includes it, but the sharing mode is `private` or `off`. Null when `cross_org_available` is `true`.", + "example": null + } + }, + "required": [ + "count", + "next", + "previous", + "results", + "sharing_mode", + "cross_org_available", + "cross_org_unavailable_reason" + ] + } + } + } + }, + "400": { + "description": "An invalid filter, ordering, pagination or `summary` value.", + "content": { + "application/json": { + "examples": { + "Invalid summary": { + "summary": "Invalid summary", + "value": { + "summary": [ + "Must be \"true\" or \"false\"." + ] + } + }, + "Invalid scope": { + "summary": "Invalid scope", + "value": { + "scope": [ + "Use identity or transactions." + ] + } + }, + "Unsupported ordering": { + "summary": "Unsupported ordering", + "value": [ + "Unsupported ordering." + ] + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `list:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/": { + "get": { + "summary": "Get network", + "description": "Retrieve a single network's full detail: stats, fraud tags and risk signals, plus the organization's `sharing_mode` and cross-organization availability, your application's `tags` on the network, member `outcomes` and pattern scores (`patterns`). Requires a full-access API key, or `read:networks` for a console member.", + "operationId": "get_network", + "x-didit-sdk": { + "resource": "networks", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.retrieve(\n \"n3a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.retrieve(\"n3a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.Retrieve(ctx, \"n3a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().retrieve(\"n3a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->retrieve('n3a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.retrieve(\"n3a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.RetrieveAsync(\"n3a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Network detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkDetailResponse" + }, + "example": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "network_id": "N-0042", + "name": null, + "status": "in_review", + "risk": { + "score": 82, + "band": "high", + "bar": 0.82 + }, + "pattern_types": [ + "exact_same_device", + "same_email" + ], + "signal_types": [ + "device", + "email" + ], + "member_counts": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signal_coverage": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + }, + "first_activity_at": "2026-10-01T09:12:44Z", + "last_activity_at": "2026-10-12T16:03:10Z", + "stats": { + "members": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signals": { + "total": 2, + "by_type": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + } + } + }, + "fraud_tags": [], + "risk_signals": { + "residential_proxy_reuse": { + "detected": false, + "signal_count": 0, + "max_subject_count": 0 + }, + "email_intelligence": { + "enriched_signal_count": 1, + "high_risk_count": 0, + "social_profile_count": 0 + }, + "phone_intelligence": { + "enriched_signal_count": 0, + "high_risk_count": 0 + } + }, + "reviewed_at": "2026-10-12T16:30:00Z", + "reviewed_by": "reviewer@example.com", + "review_reason": "Shared device across three new accounts.", + "notes_count": 0, + "sharing_mode": "global", + "cross_org_available": true, + "cross_org_unavailable_reason": null, + "tags": [ + { + "uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "name": "SUSPICIOUS", + "color": "#6366F1", + "source": "custom", + "added_by": null, + "added_at": "2026-10-13T08:15:00Z" + } + ], + "outcomes": { + "approved": 1, + "declined": 1, + "in_review": 0, + "total": 3 + }, + "patterns": [ + { + "type": "background", + "score": 0.8125, + "members": 3, + "reason": null, + "computed_at": "2026-10-13T08:00:00Z" + } + ] + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `read:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/graph/": { + "get": { + "summary": "Get network graph", + "description": "Retrieve the network as a graph of subject nodes (vendor users, vendor businesses, transaction parties) and signal nodes (the shared device/IP/face/document/phone/email/address values linking them), with edges representing memberships.", + "operationId": "get_network_graph", + "x-didit-sdk": { + "resource": "networks", + "method": "retrieve_graph", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + }, + { + "name": "depth", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "1", + "2", + "3", + "all" + ] + }, + "description": "Hops from the focus subject to include. Defaults to all (unbounded)." + }, + { + "name": "focus_kind", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "vendor_user", + "vendor_business", + "transaction_party" + ] + }, + "description": "Subject type to center the graph on. Must be supplied together with focus_id; defaults to an arbitrary member when depth is bounded and neither is supplied." + }, + { + "name": "focus_id", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Subject UUID to center the graph on." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/graph/?depth=2' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.retrieve_graph(\n \"n3a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.retrieveGraph(\"n3a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.RetrieveGraph(ctx, \"n3a5e000-0000-4000-8000-000000000001\", didit.NetworksRetrieveGraphParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().retrieveGraph(\"n3a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->retrieveGraph('n3a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.retrieve_graph(\"n3a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.RetrieveGraphAsync(\"n3a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Network graph.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkGraph" + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/members/": { + "get": { + "summary": "List network members", + "description": "List the subjects (vendor users, vendor businesses, or transaction parties) that belong to this network.", + "operationId": "list_network_members", + "x-didit-sdk": { + "resource": "networks", + "method": "list_members", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/members/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.list_members(\n \"n3a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.listMembers(\"n3a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.ListMembers(ctx, \"n3a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().listMembers(\"n3a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->listMembers('n3a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.list_members(\"n3a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.ListMembersAsync(\"n3a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Network members. Not paginated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkMember" + } + } + } + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/signals/": { + "get": { + "summary": "List network signals", + "description": "List the signals (shared device, IP, face, document number, phone, email, or address values) that link this network's members.", + "operationId": "list_network_signals", + "x-didit-sdk": { + "resource": "networks", + "method": "list_signals", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/signals/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.list_signals(\n \"n3a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.listSignals(\"n3a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.ListSignals(ctx, \"n3a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().listSignals(\"n3a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->listSignals('n3a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.list_signals(\"n3a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.ListSignalsAsync(\"n3a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Network signals. Not paginated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkSignal" + } + } + } + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/timeline/": { + "get": { + "summary": "Get network timeline", + "description": "Without `contract`: the network's own events (detection, each signal's latest observation and its review), oldest first and not paginated. `limit`, `cursor` and `event_type` are ignored. With `contract=2`: the typed timeline, newest first and paginated by cursor. It adds what the member accounts did (verification attempts, transactions, sign-ins you post through the events API and the live rules they hit) and the cases opened for the network. `note_added` events are returned to console members only, never to an API key. Requires a full-access API key, or `read:networks` for a console member.", + "operationId": "get_network_timeline", + "x-didit-sdk": { + "resource": "networks", + "method": "retrieve_timeline", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + }, + { + "name": "contract", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "2" + ] + }, + "description": "`2` returns the typed timeline. Omit it for the default timeline. Any other value returns 400." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 500, + "default": 100 + }, + "description": "Typed timeline only. Events per page, 1 to 500. Defaults to 100." + }, + { + "name": "cursor", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Typed timeline only. The previous page's `next_cursor`. An invalid or empty cursor returns 400." + }, + { + "name": "event_type", + "in": "query", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "array", + "maxItems": 20, + "items": { + "type": "string", + "enum": [ + "network_detected", + "signal_observed", + "network_active", + "network_in_review", + "network_resolved", + "network_dismissed", + "verification_attempt", + "transaction", + "login", + "rule_hit", + "case_linked", + "note_added" + ] + } + }, + "description": "Typed timeline only. Event types to keep, repeated or comma-separated, at most 20 values. Defaults to every type." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/timeline/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "curl", + "label": "curl (typed timeline)", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/3fa85f64-5717-4562-b3fc-2c963f66afa6/timeline/?contract=2&limit=50&event_type=verification_attempt,login' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.retrieve_timeline(\n \"n3a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.retrieveTimeline(\"n3a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.RetrieveTimeline(ctx, \"n3a5e000-0000-4000-8000-000000000001\", didit.NetworksRetrieveTimelineParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().retrieveTimeline(\"n3a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->retrieveTimeline('n3a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.retrieve_timeline(\"n3a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.RetrieveTimelineAsync(\"n3a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Without `contract`: `{results}`, oldest first, not paginated. With `contract=2`: `{results, next_cursor}`, newest first.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/NetworkTimeline" + }, + { + "$ref": "#/components/schemas/NetworkTimelinePage" + } + ] + }, + "examples": { + "Default timeline": { + "summary": "Without contract", + "value": { + "results": [ + { + "event_type": "network_detected", + "occurred_at": "2026-10-01T09:12:44Z", + "label": "Network detected", + "metadata": { + "network_id": "N-0042" + } + }, + { + "event_type": "signal_observed", + "occurred_at": "2026-10-12T16:03:10Z", + "label": "device observed", + "metadata": { + "signal_type": "device", + "subject_count": 3, + "is_hub": false + } + }, + { + "event_type": "network_in_review", + "occurred_at": "2026-10-12T16:30:00Z", + "label": "Network in_review", + "metadata": { + "reason": "Shared device across three new accounts." + } + } + ] + } + }, + "Typed timeline": { + "summary": "With contract=2", + "value": { + "results": [ + { + "id": "monitoring_event:9b2f1c3e-4d5a-4b6c-8d7e-0f1a2b3c4d5e", + "event_type": "login", + "occurred_at": "2026-10-12T18:41:02Z", + "ingested_at": "2026-10-12T18:41:03Z", + "label": "Login", + "metadata": {}, + "account": { + "node_id": "vendor_user:5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "node_type": "account", + "uuid": "5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "vendor_data": "user-1042", + "display_name": "Alex Example", + "ownership": "owned", + "route": { + "kind": "user", + "id": "5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "application_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + } + }, + "source_ref": { + "kind": "monitoring_event", + "id": "9b2f1c3e-4d5a-4b6c-8d7e-0f1a2b3c4d5e", + "ownership": "owned" + }, + "verification_attempt": null, + "transaction": null, + "login": { + "action": "auth.login", + "succeeded": true, + "status": "APPROVED", + "ip": { + "address": "203.0.113.24", + "country_code": "ES", + "city": "Madrid", + "connection_type": null, + "is_vpn_or_tor": false, + "is_data_center": false + }, + "device": { + "fingerprint": "fp_0a1b2c3d", + "label": "Chrome · Android" + }, + "is_first_login": false, + "is_new_device": true + }, + "rule_hit": null, + "case_linked": null, + "note_added": null + }, + { + "id": "session:1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "event_type": "verification_attempt", + "occurred_at": "2026-10-12T16:03:10Z", + "ingested_at": "2026-10-12T16:03:10Z", + "label": "Verification attempt 2 of 2", + "metadata": {}, + "account": { + "node_id": "vendor_user:5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "node_type": "account", + "uuid": "5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "vendor_data": "user-1042", + "display_name": "Alex Example", + "ownership": "owned", + "route": { + "kind": "user", + "id": "5f6e7d8c-9b0a-4c1d-8e2f-3a4b5c6d7e8f", + "application_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + } + }, + "source_ref": { + "kind": "session", + "id": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "ownership": "owned", + "route": { + "kind": "session", + "id": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "application_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + } + }, + "verification_attempt": { + "session_id": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", + "session_number": 1287, + "attempt": 2, + "attempt_count": 2, + "status": "Approved", + "status_changed_at": "2026-10-12T16:05:41Z", + "decline_reasons": [] + }, + "transaction": null, + "login": null, + "rule_hit": null, + "case_linked": null, + "note_added": null + }, + { + "id": "signal_observed:8d7c6b5a-4e3f-4a2b-9c1d-0e9f8a7b6c5d", + "event_type": "signal_observed", + "occurred_at": "2026-10-12T16:03:10Z", + "ingested_at": "2026-10-01T09:12:44Z", + "label": "device observed", + "metadata": { + "signal_type": "device", + "subject_count": 3, + "is_hub": false + }, + "account": null, + "source_ref": null, + "verification_attempt": null, + "transaction": null, + "login": null, + "rule_hit": null, + "case_linked": null, + "note_added": null + } + ], + "next_cursor": "eyJ0IjoiMjAyNi0xMC0xMlQxNjowMzoxMCswMDowMCIsImMiOiIyMDI2LTEwLTAxVDA5OjEyOjQ0KzAwOjAwIiwiciI6MSwidSI6IjhkN2M2YjVhLTRlM2YtNGEyYi05YzFkLTBlOWY4YTdiNmM1ZCJ9" + } + } + } + } + } + }, + "400": { + "description": "With `contract` present: an invalid `contract`, `limit`, `cursor` or `event_type` value.", + "content": { + "application/json": { + "examples": { + "Unsupported contract": { + "summary": "Unsupported contract", + "value": { + "contract": [ + "\"3\" is not a valid choice." + ] + } + }, + "Invalid cursor": { + "summary": "Invalid cursor", + "value": { + "cursor": [ + "This cursor is not valid. Reload the feed." + ] + } + }, + "Limit too large": { + "summary": "Limit too large", + "value": { + "limit": [ + "Ensure this value is less than or equal to 500." + ] + } + }, + "Unknown event type": { + "summary": "Unknown event type", + "value": { + "event_type": { + "0": [ + "\"logout\" is not a valid choice." + ] + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `read:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/map/": { + "get": { + "summary": "Get network map", + "description": "Geographic points where this network's members' addresses/locations converge, sourced from the sessions, business sessions, and transactions backing its memberships.", + "operationId": "get_network_map", + "x-didit-sdk": { + "resource": "networks", + "method": "retrieve_map", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "n3a5e000-0000-4000-8000-000000000001" + }, + "description": "Network UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/n3a5e000-0000-4000-8000-000000000001/map/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.retrieve_map(\n \"n3a5e000-0000-4000-8000-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.retrieveMap(\"n3a5e000-0000-4000-8000-000000000001\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.RetrieveMap(ctx, \"n3a5e000-0000-4000-8000-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().retrieveMap(\"n3a5e000-0000-4000-8000-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->retrieveMap('n3a5e000-0000-4000-8000-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.retrieve_map(\"n3a5e000-0000-4000-8000-000000000001\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.RetrieveMapAsync(\"n3a5e000-0000-4000-8000-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Network map.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkMap" + } + } + } + }, + "404": { + "description": "Network not found (or not visible to this application).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/sessions/{session_id}/networks/": { + "get": { + "summary": "List networks for a session", + "description": "List the networks the session's vendor user belongs to. Resolves through the session's vendor_data-linked user.", + "operationId": "list_session_network_memberships", + "x-didit-sdk": { + "resource": "sessions", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Session UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/sessions/aaaaaaaa-0000-4000-8000-000000000001/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.sessions.list_networks(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.sessions.listNetworks(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.ListNetworks(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.sessions().listNetworks(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->listNetworks('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.sessions.list_networks(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ListNetworksAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/business-sessions/{business_session_id}/networks/": { + "get": { + "summary": "List networks for a business session", + "description": "List the networks the business session's vendor business belongs to.", + "operationId": "list_business_session_network_memberships", + "x-didit-sdk": { + "resource": "business_sessions", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "business_session_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Business session UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/business-sessions/aaaaaaaa-0000-4000-8000-000000000002/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.business_sessions.list_networks(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.businessSessions.listNetworks(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.BusinessSessions.ListNetworks(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.businessSessions().listNetworks(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businessSessions->listNetworks('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.business_sessions.list_networks(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.BusinessSessions.ListNetworksAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/users/{vendor_user_uuid}/networks/": { + "get": { + "summary": "List networks for a user", + "description": "List the networks a vendor user belongs to.", + "operationId": "list_user_network_memberships", + "x-didit-sdk": { + "resource": "users", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "vendor_user_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Vendor user UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/users/aaaaaaaa-0000-4000-8000-000000000003/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.users.list_networks(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.users.listNetworks(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Users.ListNetworks(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.users().listNetworks(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "users->listNetworks('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.users.list_networks(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Users.ListNetworksAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/businesses/{vendor_business_uuid}/networks/": { + "get": { + "summary": "List networks for a business", + "description": "List the networks a vendor business belongs to.", + "operationId": "list_business_network_memberships", + "x-didit-sdk": { + "resource": "businesses", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "vendor_business_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Vendor business UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/businesses/aaaaaaaa-0000-4000-8000-000000000004/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.businesses.list_networks(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.businesses.listNetworks(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Businesses.ListNetworks(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.businesses().listNetworks(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->listNetworks('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.businesses.list_networks(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Businesses.ListNetworksAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/transactions/{transaction_uuid}/networks/": { + "get": { + "summary": "List networks for a transaction", + "description": "List the networks the transaction's backing KYC/KYB subject belongs to (resolved through the transaction's session or business session).", + "operationId": "list_transaction_network_memberships", + "x-didit-sdk": { + "resource": "transactions", + "method": "list_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "transaction_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Transaction UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/transactions/aaaaaaaa-0000-4000-8000-000000000005/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.transactions.list_networks(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.transactions.listNetworks(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.ListNetworks(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.transactions().listNetworks(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->listNetworks('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.transactions.list_networks(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.ListNetworksAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/transactions/{transaction_uuid}/parties/{transaction_party_uuid}/networks/": { + "get": { + "summary": "List networks for a transaction party", + "description": "List the networks a transaction party (remitter, beneficiary, or counterparty) belongs to, resolved through the party itself rather than through the transaction's backing session.", + "operationId": "list_transaction_party_network_memberships", + "x-didit-sdk": { + "resource": "transactions", + "method": "list_party_networks", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "transaction_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Transaction UUID." + }, + { + "name": "transaction_party_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Transaction party UUID." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/transactions/aaaaaaaa-0000-4000-8000-000000000005/parties/bbbbbbbb-0000-4000-8000-000000000006/networks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.transactions.list_party_networks(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.transactions.listPartyNetworks(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.ListPartyNetworks(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.transactions().listPartyNetworks(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->listPartyNetworks('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.transactions.list_party_networks(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.ListPartyNetworksAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Networks this entity belongs to. Not paginated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EntityNetworkMembershipList" + } + } + } + }, + "404": { + "description": "Entity not found in this application, or has no linked subject to look up.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/biometric-templates/": { + "get": { + "summary": "List retained biometric templates", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nReturns a paginated list of the templates the application currently holds. Purged templates are not listed.", + "operationId": "list_biometric_templates", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "query", + "name": "vendor_data", + "required": false, + "description": "Case-insensitive substring match on the owning User's current `vendor_data`.", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "vendor_user_uuid", + "required": false, + "description": "Exact match on the owning User.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "query", + "name": "status", + "required": false, + "description": "Lifecycle status. Purged templates are never listed.", + "schema": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed" + ] + } + }, + { + "in": "query", + "name": "source_type", + "required": false, + "description": "How the template was created.", + "schema": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ] + } + }, + { + "in": "query", + "name": "retained_from", + "required": false, + "description": "Templates retained at or after this time.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "retained_to", + "required": false, + "description": "Templates retained at or before this time. Must not be earlier than `retained_from` (`400`).", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "ordering", + "required": false, + "description": "Sort order.", + "schema": { + "type": "string", + "enum": [ + "retained_at", + "-retained_at", + "expires_at", + "-expires_at" + ], + "default": "-retained_at" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "description": "Page size.", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "required": false, + "description": "Pagination offset.", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Paginated templates.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Template id. Randomly generated; not derived from the deleted session or its face." + }, + "vendor_user_uuid": { + "type": "string", + "format": "uuid", + "description": "The owning User." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "The User's current `vendor_data`, resolved when you read the template (never copied)." + }, + "source_type": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ], + "description": "How the template was created: a session deletion (API or Console), the application's automatic data-retention window, or a Didit-operated backfill you instructed." + }, + "provenance_reference": { + "type": "string", + "format": "uuid", + "description": "Opaque reference tying the template to its audit trail. It is not the deleted session's id and cannot be resolved to it." + }, + "retention_policy": { + "type": "string", + "enum": [ + "delete_with_session", + "retain_until_user_deleted" + ], + "description": "The application policy at retention time." + }, + "retention_override": { + "type": "boolean", + "nullable": true, + "description": "The `retain_face_embeddings` value sent on the deletion, or `null` when the policy applied." + }, + "retained_at": { + "type": "string", + "format": "date-time" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the template expires and is purged automatically. Always set." + }, + "retained_by": { + "type": "string", + "nullable": true, + "description": "Principal that gave the instruction: the application id for API keys, the Console member id for Console actions, or a `service:` identifier for automatic retention. Never an email address." + }, + "retained_by_type": { + "type": "string", + "enum": [ + "API_KEY", + "CONSOLE_USER", + "SYSTEM" + ] + }, + "instruction_class": { + "type": "string", + "enum": [ + "operational_session_delete", + "privacy_erasure" + ] + }, + "instruction_source": { + "type": "string", + "enum": [ + "api", + "console", + "automatic_retention", + "backfill", + "privacy_erasure", + "explicit_template_purge", + "vendor_user_delete", + "expiry", + "reconciliation", + "migration", + "system" + ] + }, + "instruction_id": { + "type": "string", + "description": "The `instruction_id` sent on the deletion, or a Didit-generated id." + }, + "held_out_of_use": { + "type": "boolean", + "description": "`true` only while a legal hold applies. A held template is excluded from all matching." + }, + "status": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed", + "purged" + ], + "description": "Only `active` templates that are not held, not past `expires_at`, and whose User still exists take part in matching." + }, + "purged_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + } + } + } + }, + "examples": { + "One active template": { + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "vendor_user_uuid": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f", + "vendor_data": "user-42", + "source_type": "session_delete", + "provenance_reference": "6b1e8d2f-4a3c-4f9e-8c7d-1a2b3c4d5e6f", + "retention_policy": "retain_until_user_deleted", + "retention_override": null, + "retained_at": "2026-08-28T10:15:42.318402Z", + "expires_at": "2027-08-28T10:15:42.318402Z", + "retained_by": "3c9e2b1f-7d4a-4e8b-9c1d-5f6a7b8c9d0e", + "retained_by_type": "API_KEY", + "instruction_class": "operational_session_delete", + "instruction_source": "api", + "instruction_id": "post-approval-cleanup-2026-08", + "held_out_of_use": false, + "status": "active", + "purged_at": null + } + ] + } + } + } + } + } + }, + "400": { + "description": "Invalid filter value, or `retained_to` earlier than `retained_from`. Field-keyed validation errors.", + "content": { + "application/json": { + "examples": { + "Bad range": { + "value": { + "retained_to": [ + "Must be on or after retained_from." + ] + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/biometric-templates/?vendor_data=user-42&status=active' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.biometric_templates.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.biometricTemplates.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.BiometricTemplates.ListAutoPaging(ctx, didit.BiometricTemplatesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.biometricTemplates().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "biometricTemplates->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.biometric_templates.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.BiometricTemplates.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/biometric-templates/count/": { + "get": { + "summary": "Count retained biometric templates", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nReturns the number of templates matching the same filters as the list endpoint. Use it to check what a filtered bulk purge with `delete_all: true` would touch.", + "operationId": "count_biometric_templates", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "count", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "query", + "name": "vendor_data", + "required": false, + "description": "Case-insensitive substring match on the owning User's current `vendor_data`.", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "vendor_user_uuid", + "required": false, + "description": "Exact match on the owning User.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "in": "query", + "name": "status", + "required": false, + "description": "Lifecycle status. Purged templates are never listed.", + "schema": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed" + ] + } + }, + { + "in": "query", + "name": "source_type", + "required": false, + "description": "How the template was created.", + "schema": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ] + } + }, + { + "in": "query", + "name": "retained_from", + "required": false, + "description": "Templates retained at or after this time.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "retained_to", + "required": false, + "description": "Templates retained at or before this time. Must not be earlier than `retained_from` (`400`).", + "schema": { + "type": "string", + "format": "date-time" + } + } + ], + "responses": { + "200": { + "description": "Count.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "minimum": 0 + } + } + }, + "examples": { + "Count": { + "value": { + "count": 128 + } + } + } + } + } + }, + "400": { + "description": "Invalid filter value, or `retained_to` earlier than `retained_from`. Field-keyed validation errors.", + "content": { + "application/json": { + "examples": { + "Bad range": { + "value": { + "retained_to": [ + "Must be on or after retained_from." + ] + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/biometric-templates/count/?status=active' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.biometric_templates.count()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.biometricTemplates.count();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.BiometricTemplates.Count(ctx, didit.BiometricTemplatesCountParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.biometricTemplates().count();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "biometricTemplates->count();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.biometric_templates.count\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.BiometricTemplates.CountAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/biometric-templates/{template_uuid}/": { + "get": { + "summary": "Get a retained biometric template", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nReturns one template. The response never includes an image, a vector, the deleted session's id, or identity data.", + "operationId": "get_biometric_template", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "template_uuid", + "required": true, + "description": "Template id, as returned in `biometric_template_uuid` by the session deletion endpoints, `biometric_template_id` on a Face Search match, or the list endpoint.", + "schema": { + "type": "string", + "format": "uuid", + "example": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b" + } + } + ], + "responses": { + "200": { + "description": "The template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Template id. Randomly generated; not derived from the deleted session or its face." + }, + "vendor_user_uuid": { + "type": "string", + "format": "uuid", + "description": "The owning User." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "The User's current `vendor_data`, resolved when you read the template (never copied)." + }, + "source_type": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ], + "description": "How the template was created: a session deletion (API or Console), the application's automatic data-retention window, or a Didit-operated backfill you instructed." + }, + "provenance_reference": { + "type": "string", + "format": "uuid", + "description": "Opaque reference tying the template to its audit trail. It is not the deleted session's id and cannot be resolved to it." + }, + "retention_policy": { + "type": "string", + "enum": [ + "delete_with_session", + "retain_until_user_deleted" + ], + "description": "The application policy at retention time." + }, + "retention_override": { + "type": "boolean", + "nullable": true, + "description": "The `retain_face_embeddings` value sent on the deletion, or `null` when the policy applied." + }, + "retained_at": { + "type": "string", + "format": "date-time" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the template expires and is purged automatically. Always set." + }, + "retained_by": { + "type": "string", + "nullable": true, + "description": "Principal that gave the instruction: the application id for API keys, the Console member id for Console actions, or a `service:` identifier for automatic retention. Never an email address." + }, + "retained_by_type": { + "type": "string", + "enum": [ + "API_KEY", + "CONSOLE_USER", + "SYSTEM" + ] + }, + "instruction_class": { + "type": "string", + "enum": [ + "operational_session_delete", + "privacy_erasure" + ] + }, + "instruction_source": { + "type": "string", + "enum": [ + "api", + "console", + "automatic_retention", + "backfill", + "privacy_erasure", + "explicit_template_purge", + "vendor_user_delete", + "expiry", + "reconciliation", + "migration", + "system" + ] + }, + "instruction_id": { + "type": "string", + "description": "The `instruction_id` sent on the deletion, or a Didit-generated id." + }, + "held_out_of_use": { + "type": "boolean", + "description": "`true` only while a legal hold applies. A held template is excluded from all matching." + }, + "status": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed", + "purged" + ], + "description": "Only `active` templates that are not held, not past `expires_at`, and whose User still exists take part in matching." + }, + "purged_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "examples": { + "Active template": { + "value": { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "vendor_user_uuid": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f", + "vendor_data": "user-42", + "source_type": "session_delete", + "provenance_reference": "6b1e8d2f-4a3c-4f9e-8c7d-1a2b3c4d5e6f", + "retention_policy": "retain_until_user_deleted", + "retention_override": null, + "retained_at": "2026-08-28T10:15:42.318402Z", + "expires_at": "2027-08-28T10:15:42.318402Z", + "retained_by": "3c9e2b1f-7d4a-4e8b-9c1d-5f6a7b8c9d0e", + "retained_by_type": "API_KEY", + "instruction_class": "operational_session_delete", + "instruction_source": "api", + "instruction_id": "post-approval-cleanup-2026-08", + "held_out_of_use": false, + "status": "active", + "purged_at": null + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown template, a template of another application, or (on `GET`) a purged template.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Biometric template not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/biometric-templates/9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.biometric_templates.retrieve(\n \"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.biometricTemplates.retrieve(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.BiometricTemplates.Retrieve(ctx, \"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.biometricTemplates().retrieve(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "biometricTemplates->retrieve('9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.biometric_templates.retrieve(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.BiometricTemplates.RetrieveAsync(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "summary": "Purge a retained biometric template", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nPurges one template: it is removed from every kind of matching and the stored template is deleted. Irreversible and recorded on the audit trail with the acting principal.\n\n**Retry semantics:** Didit deletes the stored template first and only then marks the template purged. If a biometric store is unavailable the call returns `503`, the template moves to `status: purge_failed` and is already excluded from matching; repeat the same call to finish. Repeating a purge of an already purged template returns `already_purged`.\n\nTemplates are also purged by `POST /v3/users/delete/` for the owning User, by any deletion of that User's sessions with `deletion_instruction: privacy_erasure`, and automatically at `expires_at`.", + "operationId": "purge_biometric_template", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "template_uuid", + "required": true, + "description": "Template id, as returned in `biometric_template_uuid` by the session deletion endpoints, `biometric_template_id` on a Face Search match, or the list endpoint.", + "schema": { + "type": "string", + "format": "uuid", + "example": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b" + } + } + ], + "responses": { + "200": { + "description": "Purge outcome.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "outcome": { + "type": "string", + "enum": [ + "purged", + "already_purged" + ] + } + } + }, + "examples": { + "Purged": { + "value": { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "outcome": "purged" + } + }, + "Already purged": { + "value": { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "outcome": "already_purged" + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Unknown template, a template of another application, or (on `GET`) a purged template.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "Biometric template not found." + } + } + } + } + } + }, + "503": { + "description": "A biometric store was unavailable while retaining or purging a biometric template. Nothing is deleted; repeat the same request. Retries are idempotent and converge on a single template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Retryable": { + "value": { + "detail": "The biometric-template operation could not finish because an external store is unavailable. The operation is retryable." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE https://verification.didit.me/v3/biometric-templates/9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.biometric_templates.delete(\n \"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.biometricTemplates.delete(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.BiometricTemplates.Delete(ctx, \"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.biometricTemplates().delete(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "biometricTemplates->delete('9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.biometric_templates.delete(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.BiometricTemplates.DeleteAsync(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/biometric-templates/delete/": { + "post": { + "summary": "Bulk purge retained biometric templates", + "description": "An opted-in application keeps one separately managed, image-free face biometric template anchored to the User when a session is deleted (see `DELETE /v3/session/{sessionId}/delete/`). Templates are biometric data with a finite `expires_at`; they never contain images, session data, or identity fields, and they are purged on User deletion, privacy erasure, expiry, or an explicit purge. These endpoints accept application API keys (`x-api-key`) only; Business Console user tokens are rejected with `403`.\n\nPurges many templates in one call, by explicit id list or with `delete_all: true` optionally narrowed by the same filters as the list endpoint. Templates are purged one by one and each result reports `purged` or `already_purged`. If a biometric store is unavailable the call returns `503` at the first failing template: templates purged earlier in the request stay purged, the failing one moves to `purge_failed` (already excluded from matching), and repeating the request finishes the job.\n\n**`delete_all: true` without filters purges every biometric template in the application and cannot be undone.** Run the count endpoint with the same filters first.", + "operationId": "bulk_purge_biometric_templates", + "x-didit-sdk": { + "resource": "biometric_templates", + "method": "batch_delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Biometric Templates" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "template_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1, + "description": "Templates to purge. Required unless `delete_all` is `true`. Already purged ids are reported as `already_purged`." + }, + "delete_all": { + "type": "boolean", + "default": false, + "description": "When `true`, purges every template matching the filters below (all of the application's templates when no filter is given). `template_uuids` is ignored. Irreversible." + }, + "vendor_data": { + "type": "string", + "description": "Only with `delete_all`: case-insensitive substring match on the owning User's `vendor_data`." + }, + "vendor_user_uuid": { + "type": "string", + "format": "uuid", + "description": "Only with `delete_all`: exact match on the owning User." + }, + "status": { + "type": "string", + "enum": [ + "pending", + "active", + "expired", + "held_out_of_use", + "purge_pending", + "purge_failed" + ], + "description": "Only with `delete_all`." + }, + "source_type": { + "type": "string", + "enum": [ + "session_delete", + "automatic_retention", + "backfill" + ], + "description": "Only with `delete_all`." + }, + "retained_from": { + "type": "string", + "format": "date-time", + "description": "Only with `delete_all`." + }, + "retained_to": { + "type": "string", + "format": "date-time", + "description": "Only with `delete_all`. Must not be earlier than `retained_from`." + } + } + }, + "examples": { + "By id": { + "value": { + "template_uuids": [ + "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d" + ] + } + }, + "Every template of one User": { + "value": { + "delete_all": true, + "vendor_user_uuid": "2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f" + } + }, + "Every template in the application": { + "value": { + "delete_all": true + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Per-template purge outcomes.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "outcome": { + "type": "string", + "enum": [ + "purged", + "already_purged" + ] + } + } + } + } + } + }, + "examples": { + "Mixed": { + "value": { + "results": [ + { + "uuid": "9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b", + "outcome": "purged" + }, + { + "uuid": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", + "outcome": "already_purged" + } + ] + } + } + } + } + } + }, + "400": { + "description": "Validation error — nothing is purged.", + "content": { + "application/json": { + "examples": { + "Missing selector": { + "value": { + "template_uuids": [ + "This field is required unless delete_all is true." + ] + } + }, + "Bad range": { + "value": { + "retained_to": [ + "Must be on or after retained_from." + ] + } + } + } + } + } + }, + "403": { + "description": "Console user token, or the API key is not allowed. These endpoints accept application API keys only.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "503": { + "description": "A biometric store was unavailable while retaining or purging a biometric template. Nothing is deleted; repeat the same request. Retries are idempotent and converge on a single template.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Retryable": { + "value": { + "detail": "The biometric-template operation could not finish because an external store is unavailable. The operation is retryable." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/biometric-templates/delete/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"delete_all\": true, \"vendor_user_uuid\": \"2f7c1a9e-8b4d-4c6a-9e1f-3d5b7a9c1e2f\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.biometric_templates.batch_delete(\n template_uuids=[\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\", \"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.biometricTemplates.batchDelete({\n template_uuids: [\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\", \"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.BiometricTemplates.BatchDelete(ctx, didit.BiometricTemplatesBatchDeleteParams{\n\tTemplateUUIDs: didit.Some([]string{\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\", \"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.BiometricTemplatesBatchDeleteParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.biometricTemplates().batchDelete(BiometricTemplatesBatchDeleteParams.builder()\n .templateUuids(List.of(\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\", \"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "biometricTemplates->batchDelete([\n 'template_uuids' => ['9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b', '1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.biometric_templates.batch_delete(template_uuids: [\"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\", \"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.BiometricTemplates.BatchDeleteAsync(new BiometricTemplatesBatchDeleteParams\n{\n TemplateUuids = new string[] { \"9d2f4b6e-1c3a-4e8f-b7d5-0a1c2e3f4a5b\", \"1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/kyb/shareholders/": { + "post": { + "summary": "KYB registry company with shareholders (standalone)", + "operationId": "post_v3_kyb_shareholders", + "x-didit-sdk": { + "resource": "kyb", + "method": "list_shareholders", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Retrieve the Lite profile **plus the direct shareholders** (natural persons and corporate entities, with their ownership bands) of one candidate returned by `POST /v3/kyb/search/`.\n\n**Pairing with search.** Pass the candidate's `kyb_response_id` exactly as returned by a search of the same application. Search at the tier you intend to buy (`tier` on `POST /v3/kyb/search/`), because the search runs on the registry chain that serves that tier and an empty Lite search is not proof that the registry does not hold the company. When the country's ownership data lives at another registry, Didit re-finds the company there by registration number, at no extra cost.\n\n**Billing.** Billed at the country's Shareholders price - see [KYB registry pricing](/getting-started/kyb-registry-pricing). When the registry holds no shareholder record for the company (or the country offers no ownership data at all) the Lite profile is delivered and billed instead: read `registry_tier_delivered` and `registry_ownership_status`. The balance is checked against the Shareholders price before any registry call (`403` when short).\n\n**Availability.** v3 only. Every call creates a business session (its id is the `request_id`).\n\n**Pending reports.** When a report is still being prepared, or the ownership section has not been retrieved from the registry yet, the endpoint returns 202 Accepted, kyb_registry.fetch_status=pending, kyb_registry.registry_ownership_status=pending, and registry_tier_delivered=null. Keep request_id and retrieve GET /v3/session/{request_id}/decision/ until registry_checks[0].company.fetch_status is resolved. Didit completes the report automatically and charges the delivered tier once it is ready. Do not repeat the purchase while it is pending; every new POST creates a separate request.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBRegistryCandidateRequest" + }, + "example": { + "kyb_response_id": "69aeeb95febb0f1704042259", + "vendor_data": "company-1234" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/kyb/shareholders/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"kyb_response_id\": \"69aeeb95febb0f1704042259\",\n \"vendor_data\": \"company-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.kyb.list_shareholders(\n kyb_response_id=\"69aeeb95febb0f1704042259\",\n vendor_data=\"company-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.kyb.listShareholders({\n kyb_response_id: \"69aeeb95febb0f1704042259\",\n vendor_data: \"company-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.KYB.ListShareholders(ctx, didit.KYBRegistryCandidateRequest{\n\tKYBResponseID: \"69aeeb95febb0f1704042259\",\n\tVendorData: didit.String(\"company-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.KYBRegistryCandidateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.kyb().listShareholders(KYBRegistryCandidateRequest.builder()\n .kybResponseId(\"69aeeb95febb0f1704042259\")\n .vendorData(\"company-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "kyb->listShareholders([\n 'kyb_response_id' => '69aeeb95febb0f1704042259',\n 'vendor_data' => 'company-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.kyb.list_shareholders(\n kyb_response_id: \"69aeeb95febb0f1704042259\",\n vendor_data: \"company-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Kyb.ListShareholdersAsync(new KYBRegistryCandidateRequest\n{\n KybResponseId = \"69aeeb95febb0f1704042259\",\n VendorData = \"company-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Company report returned and billed at the delivered tier. Ownership reports still being prepared can return 202; keep request_id to retrieve their saved decision.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the business session created for this retrieval. The stored record (visible in the console) keeps updating if the registry resolves after the response." + }, + "registry_tier_requested": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "The tier this endpoint sells: `basic` (Lite) for select, `shareholders`, `ubo`." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "The tier actually delivered and billed. Lower than requested when the country's registries hold no ownership data, or the registry has no ownership record for this company.", + "nullable": true + }, + "kyb_registry": { + "type": "object", + "description": "Full registry company profile.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit's id for this company record." + }, + "node_id": { + "type": "string", + "example": "feature_kyb_registry" + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review" + ], + "description": "Didit's assessment of the registry check (e.g. `In Review` when the company is not active in the registry or ownership data could not be derived)." + }, + "registry_status": { + "type": "string", + "nullable": true, + "enum": [ + "active", + "dissolved", + "deregistered", + "see full details", + "authorised", + "appointed representative", + "unauthorised", + "inactive", + "no longer authorised", + "closed", + "struck off", + null + ], + "description": "Normalized company status as reported by the registry." + }, + "data_resolved": { + "type": "boolean", + "description": "`true` once the registry returned the full profile. `false` when the registry was still processing after polling — the stored record completes automatically once the registry resolves." + }, + "company_name": { + "type": "string", + "example": "Tesco PLC" + }, + "registration_number": { + "type": "string", + "nullable": true, + "example": "00445790" + }, + "country_code": { + "type": "string", + "description": "ISO 3166-1 alpha-2.", + "example": "GB" + }, + "region": { + "type": "string", + "nullable": true, + "description": "Subdivision code when the registry is regional (e.g. `CA` for `US-CA`); empty otherwise." + }, + "company_type": { + "type": "string", + "nullable": true, + "example": "Public Limited Company" + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true, + "example": "1947-11-27" + }, + "registered_address": { + "type": "string", + "nullable": true, + "example": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA" + }, + "tax_number": { + "type": "string", + "nullable": true, + "example": "GB412 8925 93" + }, + "risk_level": { + "type": "string", + "nullable": true, + "description": "Registry-reported risk level, when available." + }, + "verification_status": { + "type": "string", + "enum": [ + "verified", + "failed", + "unknown" + ], + "nullable": true, + "description": "Cross-jurisdiction verification status: `verified` when the registry status establishes standing (`active`), `failed` when it says the company is no longer standing (for example `dissolved`), otherwise `unknown`. `null` for a company that did not come from a registry and while a selection is still `pending`." + }, + "is_from_registry": { + "type": "boolean", + "description": "Always `true` for this endpoint — the profile was retrieved from an official registry." + }, + "fetch_status": { + "type": "string", + "enum": [ + "pending", + "resolved" + ], + "description": "Registry fetch lifecycle. Mirrored by `data_resolved`." + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true, + "description": "Activity description or classification code, when reported." + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "nullable": true, + "description": "Decimal amount as a string, when reported." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 code, when reported." + }, + "website": { + "type": "string", + "nullable": true, + "example": "https://www.tesco.com/" + }, + "email": { + "type": "string", + "nullable": true, + "example": "customer.service@tesco.co.uk" + }, + "phone": { + "type": "string", + "nullable": true, + "example": "03301231688" + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true, + "description": "LEI, when reported." + }, + "location_of_registration": { + "type": "string", + "nullable": true, + "description": "Registry/jurisdiction the company is registered with, when reported." + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards. At select time this is typically not yet set — VAT and tax ID validation runs when the end user submits the registry step in the hosted flow." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true, + "description": "Registry-reported share/capital details (currency, total shares, share classes), when available." + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "example": "MELISSA BETHELL" + }, + "designation": { + "type": "string", + "nullable": true, + "description": "Raw registry designation (e.g. `Director`, `Secretary`)." + }, + "role": { + "type": "string", + "description": "Normalized role derived from the designation. Full 17-value model set; registry parsing on this endpoint typically emits director, non_executive_director, secretary, chairman, or company_officer.", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "company_officer", + "settlor", + "protector", + "investor", + "other" + ] + }, + "nationality": { + "type": "string", + "nullable": true + }, + "is_active": { + "type": "boolean", + "description": "`false` when the registry marks the appointment as resigned." + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending", + null + ], + "description": "Status of an associated KYC session, when one was started for this officer. `null` otherwise." + }, + "kyc_session_url": { + "type": "string", + "nullable": true, + "description": "Verification URL of the associated KYC session, when one exists." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + }, + "description": "Directors, secretaries, and other officers extracted from the registry." + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the owner is a natural person or a corporate entity." + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Normalized roles (e.g. `ubo`, `shareholder`)." + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership band reported by the registry (e.g. `25%`)." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership band reported by the registry." + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending", + null + ] + }, + "kyc_session_url": { + "type": "string", + "nullable": true + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true, + "description": "Parsed ownership percentage (max bound, falling back to min bound)." + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + }, + "shareholding": { + "$ref": "#/components/schemas/KYBRegistryShareholding" + } + } + }, + "description": "Beneficial owners / persons with significant control extracted from the registry. Can be empty when the registry does not expose ownership." + }, + "addresses": { + "type": "array", + "nullable": true, + "description": "Every address the registry states, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryAddress" + } + }, + "industries": { + "type": "array", + "nullable": true, + "description": "The activities the registry classifies the company under, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryIndustry" + } + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "$ref": "#/components/schemas/KYBRegistryData" + }, + "user_provided_data": { + "type": "object", + "nullable": true, + "description": "Always `null` on this endpoint (used by the hosted KYB flow when end users edit registry data)." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set when a console reviewer edits the record later." + }, + "is_editable": { + "type": "boolean", + "description": "Whether the registry-sourced data can still be edited in the hosted flow/console." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "Same as the top-level `registry_tier_delivered`.", + "nullable": true + }, + "registry_ownership_status": { + "type": "string", + "nullable": true, + "enum": [ + "not_requested", + "delivered", + "not_held", + "unavailable", + "pending", + null + ], + "description": "`delivered` when the ownership data came back, `not_held` when the ownership registry answered and does not list this company (see `registry_data.ownership_error`), `unavailable` when an ownership tier was requested but the registry had none for this company or could not be asked (see `registry_data.ownership_error`), `pending` when the ownership section has not been retrieved from the registry yet and Didit is still completing it, `not_requested` on the Lite tier." + }, + "ownership": { + "$ref": "#/components/schemas/KYBRegistryOwnership" + }, + "registry_tier": { + "type": "string", + "nullable": true, + "description": "The registry tier the workflow ordered: `basic`, `shareholders` or `ubo`. Also in `ownership.tier_requested`." + }, + "registry_monitoring": { + "type": "object", + "nullable": true, + "description": "The latest registry monitor for the company, or `null` when none exists.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string" + }, + "unavailable_reason": { + "type": "string", + "nullable": true + }, + "enrolled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "next_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "last_event_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "events_count": { + "type": "integer" + }, + "watched_fields": { + "description": "The company fields the monitor watches." + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Resolved company": { + "summary": "Full registry profile (officer list truncated for brevity)", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "f1e2d3c4-b5a6-4789-9c0d-1e2f3a4b5c6d", + "node_id": "feature_kyb_registry", + "status": "In Review", + "registry_status": "active", + "data_resolved": true, + "company_name": "Tesco PLC", + "registration_number": "00445790", + "country_code": "GB", + "region": "", + "company_type": "Public Limited Company", + "incorporation_date": "1947-11-27", + "registered_address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "tax_number": "GB412 8925 93", + "risk_level": "low risk", + "verification_status": "verified", + "is_from_registry": true, + "fetch_status": "resolved", + "alternative_names": "TESCO STORES (HOLDINGS) PUBLIC LIMITED COMPANY", + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": "https://www.tesco.com/", + "email": "customer.service@tesco.co.uk", + "phone": "03301231688", + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": { + "currency": "Pound (£)", + "total_number_of_shares": "8174378721", + "meta_detail": { + "Class_Of_Share": "Ordinary" + } + }, + "officers": [ + { + "uuid": "a1b2c3d4-e5f6-4789-9a0b-1c2d3e4f5a6b", + "name": "CHRISTOPHER JON TAYLOR", + "designation": "Secretary", + "role": "secretary", + "nationality": null, + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + }, + { + "uuid": "b2c3d4e5-f6a7-4890-8b1c-2d3e4f5a6b7c", + "name": "MELISSA BETHELL", + "designation": "Director", + "role": "director", + "nationality": "British", + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + } + ], + "beneficial_owners": [], + "addresses": [ + { + "address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "type": "legal_entity_registry_address", + "description": "registered office address" + } + ], + "industries": null, + "accounts": { + "last_account": "26 February 2025", + "next_account": "26 February 2026", + "due_by_date": "26 August 2026" + }, + "registry_data": {}, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems use DRF's standard envelope.", + "content": { + "application/json": { + "examples": { + "Missing kyb_response_id": { + "summary": "`kyb_response_id` is required", + "value": { + "kyb_response_id": [ + "This field is required." + ] + } + }, + "save_api_request=false rejected": { + "summary": "Selects are always persisted", + "value": { + "save_api_request": [ + "KYB registry select must be saved so billing and retrieval can be tracked." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "202": { + "description": "Report accepted and still processing. When a report is still being prepared, the endpoint returns 202 Accepted, kyb_registry.fetch_status=pending, and registry_tier_delivered=null. Keep request_id and retrieve GET /v3/session/{request_id}/decision/ until registry_checks[0].company.fetch_status is resolved. Didit completes the report automatically and charges the delivered tier once it is ready. Do not repeat the purchase while it is pending; every new POST creates a separate request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/paths/~1v3~1kyb~1shareholders~1/post/responses/200/content/application~1json/schema" + }, + "examples": { + "Pending (registry still resolving)": { + "summary": "Ownership report is still processing; retrieve the saved decision", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "7c0a4f7e-2b1d-4f6a-9c3e-8d5b2a1f0e9d", + "node_id": "feature_kyb_registry", + "status": "Not Finished", + "registry_status": null, + "data_resolved": false, + "company_name": "Example Holdings Limited", + "registration_number": "12345678", + "country_code": "HK", + "region": null, + "company_type": null, + "incorporation_date": null, + "registered_address": null, + "tax_number": null, + "risk_level": null, + "verification_status": null, + "is_from_registry": true, + "fetch_status": "pending", + "alternative_names": null, + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": null, + "email": null, + "phone": null, + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": {}, + "officers": [], + "beneficial_owners": [], + "addresses": null, + "industries": null, + "accounts": null, + "registry_data": {}, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true, + "registry_tier_delivered": null, + "registry_ownership_status": "pending" + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00", + "registry_tier_delivered": null, + "registry_tier_requested": "shareholders" + } + } + } + } + } + } + } + } + }, + "/v3/kyb/ubo/": { + "post": { + "summary": "KYB registry company with ultimate beneficial owners (standalone)", + "operationId": "post_v3_kyb_ubo", + "x-didit-sdk": { + "resource": "kyb", + "method": "list_ubos", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [], + "description": "Retrieve the Lite profile, the direct shareholders **and the ultimate beneficial owners** resolved through the ownership chain (`registry_data.ubo_resolution` carries the corporate parent chain) of one candidate returned by `POST /v3/kyb/search/`.\n\n**Pairing with search.** Pass the candidate's `kyb_response_id` exactly as returned by a search of the same application. Search at the tier you intend to buy (`tier` on `POST /v3/kyb/search/`), because the search runs on the registry chain that serves that tier and an empty Lite search is not proof that the registry does not hold the company. When the country's ownership data lives at another registry, Didit re-finds the company there by registration number, at no extra cost.\n\n**Billing.** Billed at the country's UBO price - see [KYB registry pricing](/getting-started/kyb-registry-pricing). The tier delivered is the highest the country and the registry can serve: Shareholders when the resolved report names no natural person as a beneficial owner, Lite when there is no ownership record at all; `registry_tier_delivered` names what was billed. The balance is checked against the UBO price before any registry call (`403` when short).\n\n**Availability.** v3 only. Every call creates a business session (its id is the `request_id`).\n\n**Pending reports.** When a report is still being prepared, or the ownership section has not been retrieved from the registry yet, the endpoint returns 202 Accepted, kyb_registry.fetch_status=pending, kyb_registry.registry_ownership_status=pending, and registry_tier_delivered=null. Keep request_id and retrieve GET /v3/session/{request_id}/decision/ until registry_checks[0].company.fetch_status is resolved. Didit completes the report automatically and charges the delivered tier once it is ready. Do not repeat the purchase while it is pending; every new POST creates a separate request.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBRegistryCandidateRequest" + }, + "example": { + "kyb_response_id": "69aeeb95febb0f1704042259", + "vendor_data": "company-1234" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/kyb/ubo/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"kyb_response_id\": \"69aeeb95febb0f1704042259\",\n \"vendor_data\": \"company-1234\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.kyb.list_ubos(\n kyb_response_id=\"69aeeb95febb0f1704042259\",\n vendor_data=\"company-1234\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.kyb.listUbos({\n kyb_response_id: \"69aeeb95febb0f1704042259\",\n vendor_data: \"company-1234\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.KYB.ListUBOs(ctx, didit.KYBRegistryCandidateRequest{\n\tKYBResponseID: \"69aeeb95febb0f1704042259\",\n\tVendorData: didit.String(\"company-1234\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.KYBRegistryCandidateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.kyb().listUbos(KYBRegistryCandidateRequest.builder()\n .kybResponseId(\"69aeeb95febb0f1704042259\")\n .vendorData(\"company-1234\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "kyb->listUbos([\n 'kyb_response_id' => '69aeeb95febb0f1704042259',\n 'vendor_data' => 'company-1234',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.kyb.list_ubos(\n kyb_response_id: \"69aeeb95febb0f1704042259\",\n vendor_data: \"company-1234\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Kyb.ListUbosAsync(new KYBRegistryCandidateRequest\n{\n KybResponseId = \"69aeeb95febb0f1704042259\",\n VendorData = \"company-1234\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Company report returned and billed at the delivered tier. Ownership reports still being prepared can return 202; keep request_id to retrieve their saved decision.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the business session created for this retrieval. The stored record (visible in the console) keeps updating if the registry resolves after the response." + }, + "registry_tier_requested": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo" + ], + "description": "The tier this endpoint sells: `basic` (Lite) for select, `shareholders`, `ubo`." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "The tier actually delivered and billed. Lower than requested when the country's registries hold no ownership data, when the registry has no ownership record for this company, or when the resolved report of a `ubo` request names no natural person as a beneficial owner.", + "nullable": true + }, + "kyb_registry": { + "type": "object", + "description": "Full registry company profile.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit's id for this company record." + }, + "node_id": { + "type": "string", + "example": "feature_kyb_registry" + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review" + ], + "description": "Didit's assessment of the registry check (e.g. `In Review` when the company is not active in the registry or ownership data could not be derived)." + }, + "registry_status": { + "type": "string", + "nullable": true, + "enum": [ + "active", + "dissolved", + "deregistered", + "see full details", + "authorised", + "appointed representative", + "unauthorised", + "inactive", + "no longer authorised", + "closed", + "struck off", + null + ], + "description": "Normalized company status as reported by the registry." + }, + "data_resolved": { + "type": "boolean", + "description": "`true` once the registry returned the full profile. `false` when the registry was still processing after polling — the stored record completes automatically once the registry resolves." + }, + "company_name": { + "type": "string", + "example": "Tesco PLC" + }, + "registration_number": { + "type": "string", + "nullable": true, + "example": "00445790" + }, + "country_code": { + "type": "string", + "description": "ISO 3166-1 alpha-2.", + "example": "GB" + }, + "region": { + "type": "string", + "nullable": true, + "description": "Subdivision code when the registry is regional (e.g. `CA` for `US-CA`); empty otherwise." + }, + "company_type": { + "type": "string", + "nullable": true, + "example": "Public Limited Company" + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true, + "example": "1947-11-27" + }, + "registered_address": { + "type": "string", + "nullable": true, + "example": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA" + }, + "tax_number": { + "type": "string", + "nullable": true, + "example": "GB412 8925 93" + }, + "risk_level": { + "type": "string", + "nullable": true, + "description": "Registry-reported risk level, when available." + }, + "verification_status": { + "type": "string", + "enum": [ + "verified", + "failed", + "unknown" + ], + "nullable": true, + "description": "Cross-jurisdiction verification status: `verified` when the registry status establishes standing (`active`), `failed` when it says the company is no longer standing (for example `dissolved`), otherwise `unknown`. `null` for a company that did not come from a registry and while a selection is still `pending`." + }, + "is_from_registry": { + "type": "boolean", + "description": "Always `true` for this endpoint — the profile was retrieved from an official registry." + }, + "fetch_status": { + "type": "string", + "enum": [ + "pending", + "resolved" + ], + "description": "Registry fetch lifecycle. Mirrored by `data_resolved`." + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true, + "description": "Activity description or classification code, when reported." + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "nullable": true, + "description": "Decimal amount as a string, when reported." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 code, when reported." + }, + "website": { + "type": "string", + "nullable": true, + "example": "https://www.tesco.com/" + }, + "email": { + "type": "string", + "nullable": true, + "example": "customer.service@tesco.co.uk" + }, + "phone": { + "type": "string", + "nullable": true, + "example": "03301231688" + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true, + "description": "LEI, when reported." + }, + "location_of_registration": { + "type": "string", + "nullable": true, + "description": "Registry/jurisdiction the company is registered with, when reported." + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards. At select time this is typically not yet set — VAT and tax ID validation runs when the end user submits the registry step in the hosted flow." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true, + "description": "Registry-reported share/capital details (currency, total shares, share classes), when available." + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "example": "MELISSA BETHELL" + }, + "designation": { + "type": "string", + "nullable": true, + "description": "Raw registry designation (e.g. `Director`, `Secretary`)." + }, + "role": { + "type": "string", + "description": "Normalized role derived from the designation. Full 17-value model set; registry parsing on this endpoint typically emits director, non_executive_director, secretary, chairman, or company_officer.", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "company_officer", + "settlor", + "protector", + "investor", + "other" + ] + }, + "nationality": { + "type": "string", + "nullable": true + }, + "is_active": { + "type": "boolean", + "description": "`false` when the registry marks the appointment as resigned." + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending", + null + ], + "description": "Status of an associated KYC session, when one was started for this officer. `null` otherwise." + }, + "kyc_session_url": { + "type": "string", + "nullable": true, + "description": "Verification URL of the associated KYC session, when one exists." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + }, + "description": "Directors, secretaries, and other officers extracted from the registry." + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the owner is a natural person or a corporate entity." + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Normalized roles (e.g. `ubo`, `shareholder`)." + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership band reported by the registry (e.g. `25%`)." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership band reported by the registry." + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "nullable": true, + "enum": [ + "Approved", + "Declined", + "Pending", + null + ] + }, + "kyc_session_url": { + "type": "string", + "nullable": true + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true, + "description": "Parsed ownership percentage (max bound, falling back to min bound)." + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + }, + "shareholding": { + "$ref": "#/components/schemas/KYBRegistryShareholding" + } + } + }, + "description": "Beneficial owners / persons with significant control extracted from the registry. Can be empty when the registry does not expose ownership." + }, + "addresses": { + "type": "array", + "nullable": true, + "description": "Every address the registry states, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryAddress" + } + }, + "industries": { + "type": "array", + "nullable": true, + "description": "The activities the registry classifies the company under, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryIndustry" + } + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "$ref": "#/components/schemas/KYBRegistryData" + }, + "user_provided_data": { + "type": "object", + "nullable": true, + "description": "Always `null` on this endpoint (used by the hosted KYB flow when end users edit registry data)." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Always `null` on this endpoint." + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set when a console reviewer edits the record later." + }, + "is_editable": { + "type": "boolean", + "description": "Whether the registry-sourced data can still be edited in the hosted flow/console." + }, + "registry_tier_delivered": { + "type": "string", + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "Same as the top-level `registry_tier_delivered`.", + "nullable": true + }, + "registry_ownership_status": { + "type": "string", + "nullable": true, + "enum": [ + "not_requested", + "delivered", + "not_held", + "unavailable", + "pending", + null + ], + "description": "`delivered` when the ownership data came back, `not_held` when the ownership registry answered and does not list this company (see `registry_data.ownership_error`), `unavailable` when an ownership tier was requested but the registry had none for this company or could not be asked (see `registry_data.ownership_error`), `pending` when the ownership section has not been retrieved from the registry yet and Didit is still completing it, `not_requested` on the Lite tier." + }, + "ownership": { + "$ref": "#/components/schemas/KYBRegistryOwnership" + }, + "registry_tier": { + "type": "string", + "nullable": true, + "description": "The registry tier the workflow ordered: `basic`, `shareholders` or `ubo`. Also in `ownership.tier_requested`." + }, + "registry_monitoring": { + "type": "object", + "nullable": true, + "description": "The latest registry monitor for the company, or `null` when none exists.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string" + }, + "unavailable_reason": { + "type": "string", + "nullable": true + }, + "enrolled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "next_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "last_event_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "events_count": { + "type": "integer" + }, + "watched_fields": { + "description": "The company fields the monitor watches." + } + } + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` you sent." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "examples": { + "Resolved company": { + "summary": "Full registry profile (officer list truncated for brevity)", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "f1e2d3c4-b5a6-4789-9c0d-1e2f3a4b5c6d", + "node_id": "feature_kyb_registry", + "status": "In Review", + "registry_status": "active", + "data_resolved": true, + "company_name": "Tesco PLC", + "registration_number": "00445790", + "country_code": "GB", + "region": "", + "company_type": "Public Limited Company", + "incorporation_date": "1947-11-27", + "registered_address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "tax_number": "GB412 8925 93", + "risk_level": "low risk", + "verification_status": "verified", + "is_from_registry": true, + "fetch_status": "resolved", + "alternative_names": "TESCO STORES (HOLDINGS) PUBLIC LIMITED COMPANY", + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": "https://www.tesco.com/", + "email": "customer.service@tesco.co.uk", + "phone": "03301231688", + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": { + "currency": "Pound (£)", + "total_number_of_shares": "8174378721", + "meta_detail": { + "Class_Of_Share": "Ordinary" + } + }, + "officers": [ + { + "uuid": "a1b2c3d4-e5f6-4789-9a0b-1c2d3e4f5a6b", + "name": "CHRISTOPHER JON TAYLOR", + "designation": "Secretary", + "role": "secretary", + "nationality": null, + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + }, + { + "uuid": "b2c3d4e5-f6a7-4890-8b1c-2d3e4f5a6b7c", + "name": "MELISSA BETHELL", + "designation": "Director", + "role": "director", + "nationality": "British", + "is_active": true, + "kyc_status": null, + "kyc_session_url": null + } + ], + "beneficial_owners": [], + "addresses": [ + { + "address": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, United Kingdom, AL7 1GA", + "type": "legal_entity_registry_address", + "description": "registered office address" + } + ], + "industries": null, + "accounts": { + "last_account": "26 February 2025", + "next_account": "26 February 2026", + "due_by_date": "26 August 2026" + }, + "registry_data": { + "ubo_resolution": { + "parent_chain": [], + "subsidiaries": [], + "unresolved": [] + } + }, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "description": "Validation error. Field-level problems use DRF's standard envelope.", + "content": { + "application/json": { + "examples": { + "Missing kyb_response_id": { + "summary": "`kyb_response_id` is required", + "value": { + "kyb_response_id": [ + "This field is required." + ] + } + }, + "save_api_request=false rejected": { + "summary": "Selects are always persisted", + "value": { + "save_api_request": [ + "KYB registry select must be saved so billing and retrieval can be tracked." + ] + } + } + } + } + } + }, + "403": { + "description": "Permission denied. Returned when the `x-api-key` header is missing, and also when the calling organization's balance cannot cover the call; a wrong, revoked or expired key returns `401`. A missing key returns `403` with `{\"detail\": ...}`; a wrong, revoked or expired key returns `401`. Credit shortfalls return `403` with `{\"error\": ...}` before any screening or provider call happens.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid/revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the call", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded. POST/PATCH/DELETE endpoints share a budget of 300 write requests per minute per API key (transaction creates and event ingestion have a separate budget of 600). The response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers.", + "content": { + "application/json": { + "examples": { + "Write rate limit exceeded": { + "summary": "More than 300 write requests in the current minute", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + }, + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "202": { + "description": "Report accepted and still processing. When a report is still being prepared, the endpoint returns 202 Accepted, kyb_registry.fetch_status=pending, and registry_tier_delivered=null. Keep request_id and retrieve GET /v3/session/{request_id}/decision/ until registry_checks[0].company.fetch_status is resolved. Didit completes the report automatically and charges the delivered tier once it is ready. Do not repeat the purchase while it is pending; every new POST creates a separate request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/paths/~1v3~1kyb~1ubo~1/post/responses/200/content/application~1json/schema" + }, + "examples": { + "Pending (registry still resolving)": { + "summary": "Ownership report is still processing; retrieve the saved decision", + "value": { + "request_id": "9d4f2c7a-1b3e-4a5c-8d6f-0e9a8b7c6d5e", + "kyb_registry": { + "uuid": "7c0a4f7e-2b1d-4f6a-9c3e-8d5b2a1f0e9d", + "node_id": "feature_kyb_registry", + "status": "Not Finished", + "registry_status": null, + "data_resolved": false, + "company_name": "Example Holdings Limited", + "registration_number": "12345678", + "country_code": "HK", + "region": null, + "company_type": null, + "incorporation_date": null, + "registered_address": null, + "tax_number": null, + "risk_level": null, + "verification_status": null, + "is_from_registry": true, + "fetch_status": "pending", + "alternative_names": null, + "nature_of_business": null, + "registered_capital": null, + "registered_capital_amount": null, + "registered_capital_currency": null, + "website": null, + "email": null, + "phone": null, + "legal_entity_identifier": null, + "location_of_registration": null, + "financial_summary": {}, + "officers": [], + "beneficial_owners": [], + "addresses": null, + "industries": null, + "accounts": null, + "registry_data": {}, + "user_provided_data": null, + "confirmed_by_user_at": null, + "last_console_edit_at": null, + "is_editable": true, + "registry_tier_delivered": null, + "registry_ownership_status": "pending" + }, + "vendor_data": "company-1234", + "metadata": null, + "created_at": "2026-06-11T10:45:00.000000+00:00", + "registry_tier_delivered": null, + "registry_tier_requested": "ubo" + } + } + } + } + } + } + } + } + }, + "/v3/id-verification/wallets/": { + "get": { + "operationId": "list_wallets", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "list", + "auth": "api_key", + "server": true + }, + "description": "Every Digital ID wallet with its start endpoint, countries, required fields and whether this application can start it in this environment now. Listed does not mean live.", + "summary": "List Digital ID wallets", + "tags": [ + "Digital ID wallets" + ], + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletCatalogResponse" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "Missing credentials (x-api-key or Authorization: Bearer)." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request GET \\\n --url https://verification.didit.me/v3/id-verification/wallets/ \\\n --header 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.list()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.list();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.List(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().list();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->list();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.list\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.ListAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/id-verification/wallets/verifications/{request_id}/": { + "get": { + "operationId": "get_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "retrieve_verification", + "auth": "api_key", + "server": true + }, + "description": "Status and result of a wallet verification: the normalized identity and the wallet's own attributes once verified, the error code once declined, the next action while pending. A verification past expires_at is decided timeout.", + "summary": "Get a wallet verification", + "parameters": [ + { + "in": "path", + "name": "request_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The request_id the start endpoint returned.", + "required": true + } + ], + "tags": [ + "Digital ID wallets" + ], + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "ftn", + "name": "Finnish Trust Network", + "issuing_authority": "Finnish banks and mobile operators (FTN)" + }, + "country": "FIN", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "Missing credentials (x-api-key or Authorization: Bearer)." + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error not_found: no wallet verification of this application has this request_id." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request GET \\\n --url https://verification.didit.me/v3/id-verification/wallets/verifications/REQUEST_ID/ \\\n --header 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.retrieve_verification(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.retrieveVerification(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.RetrieveVerification(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().retrieveVerification(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->retrieveVerification('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.retrieve_verification(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.RetrieveVerificationAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/id-verification/wallets/verifications/{request_id}/poll/": { + "post": { + "operationId": "poll_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "poll_verification", + "auth": "api_key", + "server": true + }, + "description": "Smart-ID and Mobile-ID: ask the provider whether the user confirmed, refused or let the prompt lapse, and verify the signed identity when they confirmed. Redirect wallets complete through the user's browser, so for them this only reads the verification back. Answers the verification as it is afterwards.", + "summary": "Poll a wallet verification", + "parameters": [ + { + "in": "path", + "name": "request_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The request_id the start endpoint returned.", + "required": true + } + ], + "tags": [ + "Digital ID wallets" + ], + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "smart_id", + "name": "Smart-ID", + "issuing_authority": "SK ID Solutions" + }, + "country": "EST", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "in_app", + "simulated": true, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "confirm_in_app", + "verification_code": "1234", + "poll_interval_seconds": 2 + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "Missing credentials (x-api-key or Authorization: Bearer)." + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error not_found: no wallet verification of this application has this request_id." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/verifications/REQUEST_ID/poll/ \\\n --header 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.poll_verification(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.pollVerification(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.PollVerification(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().pollVerification(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->pollVerification('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.poll_verification(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.PollVerificationAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/id-verification/wallets/verifications/{request_id}/cancel/": { + "post": { + "operationId": "cancel_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "cancel_verification", + "auth": "api_key", + "server": true + }, + "description": "Withdraw a pending verification: it is declined as cancelled and never billed. A decided one is returned unchanged. Smart-ID and Mobile-ID: a cancel that arrives while a poll is checking the sign-in waits a few seconds for that poll, then declines it, or returns it verified if the user had already confirmed. A verification the user is completing right now cannot be cancelled (409 verification_in_progress with Retry-After): retry after that many seconds to read its outcome.", + "summary": "Cancel a wallet verification", + "parameters": [ + { + "in": "path", + "name": "request_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The request_id the start endpoint returned.", + "required": true + } + ], + "tags": [ + "Digital ID wallets" + ], + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "ftn", + "name": "Finnish Trust Network", + "issuing_authority": "Finnish banks and mobile operators (FTN)" + }, + "country": "FIN", + "status": "Declined", + "outcome": "cancelled", + "error": "cancelled_by_customer", + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": null, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "Missing credentials (x-api-key or Authorization: Bearer)." + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error not_found: no wallet verification of this application has this request_id." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error verification_in_progress: the user is completing this sign-in right now. The Retry-After header says when to retry; the retry returns the decided verification." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/verifications/REQUEST_ID/cancel/ \\\n --header 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.cancel_verification(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.cancelVerification(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.CancelVerification(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().cancelVerification(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->cancelVerification('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.cancel_verification(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.CancelVerificationAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/id-verification/wallets/mitid/": { + "post": { + "operationId": "start_mitid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_mitid", + "auth": "api_key", + "server": true + }, + "description": "Start a MitID sign-in (Danish Agency for Digital Government; DNK) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start MitID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMitidWalletVerificationStartRequest" + }, + "example": { + "country": "DNK", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMitidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMitidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "mitid", + "name": "MitID", + "issuing_authority": "Danish Agency for Digital Government" + }, + "country": "DNK", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/mitid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"DNK\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_mitid(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"DNK\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startMitid({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"DNK\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartMitid(ctx, didit.IDWalletsStartMitidParams{\n\tBody: didit.DigitalIDWalletAPIMitidWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIMitidWalletVerificationStartCountryEnum(\"DNK\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiMitidWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiMitidWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startMitid(DigitalIdWalletApiMitidWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiMitidWalletVerificationStartCountryEnum.of(\"DNK\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startMitid([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'DNK',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_mitid(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"DNK\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartMitidAsync(new DigitalIdWalletApiMitidWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiMitidWalletVerificationStartCountryEnum(\"DNK\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "mitid" + } + }, + "/v3/id-verification/wallets/bankid-se/": { + "post": { + "operationId": "start_bankid_se_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_bankid_se", + "auth": "api_key", + "server": true + }, + "description": "Start a BankID (SWE) sign-in (Finansiell ID-Teknik / bank consortium; SWE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start BankID (SWE) verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidSeWalletVerificationStartRequest" + }, + "example": { + "country": "SWE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidSeWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidSeWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "bankid_se", + "name": "BankID", + "issuing_authority": "Finansiell ID-Teknik / bank consortium" + }, + "country": "SWE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/bankid-se/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"SWE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_bankid_se(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"SWE\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startBankidSe({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"SWE\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartBankidSe(ctx, didit.IDWalletsStartBankidSeParams{\n\tBody: didit.DigitalIDWalletAPIBankidSeWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryEe9enum(\"SWE\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiBankidSeWalletVerificationStartRequest;\nimport me.didit.types.DigitalIdWalletApiCountryEe9Enum;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startBankidSe(DigitalIdWalletApiBankidSeWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryEe9Enum.of(\"SWE\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startBankidSe([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'SWE',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_bankid_se(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"SWE\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartBankidSeAsync(new DigitalIdWalletApiBankidSeWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryEe9Enum(\"SWE\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "bankid_se" + } + }, + "/v3/id-verification/wallets/bankid-no/": { + "post": { + "operationId": "start_bankid_no_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_bankid_no", + "auth": "api_key", + "server": true + }, + "description": "Start a BankID (NOR) sign-in (BankID BankAxept AS; NOR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start BankID (NOR) verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidNoWalletVerificationStartRequest" + }, + "example": { + "country": "NOR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidNoWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidNoWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "bankid_no", + "name": "BankID", + "issuing_authority": "BankID BankAxept AS" + }, + "country": "NOR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/bankid-no/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"NOR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_bankid_no(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"NOR\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startBankidNo({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NOR\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartBankidNo(ctx, didit.IDWalletsStartBankidNoParams{\n\tBody: didit.DigitalIDWalletAPIBankidNoWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryAfcEnum(\"NOR\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiBankidNoWalletVerificationStartRequest;\nimport me.didit.types.DigitalIdWalletApiCountryAfcEnum;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startBankidNo(DigitalIdWalletApiBankidNoWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryAfcEnum.of(\"NOR\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startBankidNo([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'NOR',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_bankid_no(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NOR\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartBankidNoAsync(new DigitalIdWalletApiBankidNoWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryAfcEnum(\"NOR\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "bankid_no" + } + }, + "/v3/id-verification/wallets/vipps/": { + "post": { + "operationId": "start_vipps_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_vipps", + "auth": "api_key", + "server": true + }, + "description": "Start a Vipps sign-in (Vipps MobilePay / BankID NO; NOR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Vipps verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiVippsWalletVerificationStartRequest" + }, + "example": { + "country": "NOR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiVippsWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiVippsWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "vipps", + "name": "Vipps", + "issuing_authority": "Vipps MobilePay / BankID NO" + }, + "country": "NOR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/vipps/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"NOR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_vipps(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"NOR\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startVipps({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NOR\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartVipps(ctx, didit.IDWalletsStartVippsParams{\n\tBody: didit.DigitalIDWalletAPIVippsWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryAfcEnum(\"NOR\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiCountryAfcEnum;\nimport me.didit.types.DigitalIdWalletApiVippsWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startVipps(DigitalIdWalletApiVippsWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryAfcEnum.of(\"NOR\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startVipps([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'NOR',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_vipps(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NOR\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartVippsAsync(new DigitalIdWalletApiVippsWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryAfcEnum(\"NOR\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "vipps" + } + }, + "/v3/id-verification/wallets/buypass/": { + "post": { + "operationId": "start_buypass_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_buypass", + "auth": "api_key", + "server": true + }, + "description": "Start a Buypass ID sign-in (Buypass AS; NOR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Buypass ID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBuypassWalletVerificationStartRequest" + }, + "example": { + "country": "NOR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBuypassWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBuypassWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "buypass", + "name": "Buypass ID", + "issuing_authority": "Buypass AS" + }, + "country": "NOR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/buypass/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"NOR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_buypass(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"NOR\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startBuypass({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NOR\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartBuypass(ctx, didit.IDWalletsStartBuypassParams{\n\tBody: didit.DigitalIDWalletAPIBuypassWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryAfcEnum(\"NOR\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiBuypassWalletVerificationStartRequest;\nimport me.didit.types.DigitalIdWalletApiCountryAfcEnum;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startBuypass(DigitalIdWalletApiBuypassWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryAfcEnum.of(\"NOR\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startBuypass([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'NOR',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_buypass(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NOR\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartBuypassAsync(new DigitalIdWalletApiBuypassWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryAfcEnum(\"NOR\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "buypass" + } + }, + "/v3/id-verification/wallets/itsme/": { + "post": { + "operationId": "start_itsme_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_itsme", + "auth": "api_key", + "server": true + }, + "description": "Start a itsme sign-in (Belgian Mobile ID; BEL, LUX, NLD) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start itsme verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiItsmeWalletVerificationStartRequest" + }, + "example": { + "country": "BEL", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiItsmeWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiItsmeWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "itsme", + "name": "itsme", + "issuing_authority": "Belgian Mobile ID" + }, + "country": "BEL", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/itsme/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"BEL\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_itsme(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"BEL\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startItsme({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"BEL\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartItsme(ctx, didit.IDWalletsStartItsmeParams{\n\tBody: didit.DigitalIDWalletAPIItsmeWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.DigitalIDWalletAPIItsmeWalletVerificationStartCountryEnum(\"BEL\"),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiItsmeWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiItsmeWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startItsme(DigitalIdWalletApiItsmeWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiItsmeWalletVerificationStartCountryEnum.of(\"BEL\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startItsme([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'BEL',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_itsme(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"BEL\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartItsmeAsync(new DigitalIdWalletApiItsmeWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = \"BEL\",\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "itsme" + } + }, + "/v3/id-verification/wallets/idin/": { + "post": { + "operationId": "start_idin_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_idin", + "auth": "api_key", + "server": true + }, + "description": "Start a iDIN sign-in (Dutch banks (Currence iDIN); NLD) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start iDIN verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdinWalletVerificationStartRequest" + }, + "example": { + "country": "NLD", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdinWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdinWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "idin", + "name": "iDIN", + "issuing_authority": "Dutch banks (Currence iDIN)" + }, + "country": "NLD", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/idin/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"NLD\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_idin(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"NLD\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startIdin({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NLD\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartIdin(ctx, didit.IDWalletsStartIdinParams{\n\tBody: didit.DigitalIDWalletAPIIdinWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIIdinWalletVerificationStartCountryEnum(\"NLD\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiIdinWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiIdinWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startIdin(DigitalIdWalletApiIdinWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiIdinWalletVerificationStartCountryEnum.of(\"NLD\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startIdin([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'NLD',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_idin(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"NLD\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartIdinAsync(new DigitalIdWalletApiIdinWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiIdinWalletVerificationStartCountryEnum(\"NLD\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "idin" + } + }, + "/v3/id-verification/wallets/ftn/": { + "post": { + "operationId": "start_ftn_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_ftn", + "auth": "api_key", + "server": true + }, + "description": "Start a Finnish Trust Network sign-in (Finnish banks and mobile operators (FTN); FIN) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Finnish Trust Network verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFtnWalletVerificationStartRequest" + }, + "example": { + "country": "FIN", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFtnWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFtnWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "ftn", + "name": "Finnish Trust Network", + "issuing_authority": "Finnish banks and mobile operators (FTN)" + }, + "country": "FIN", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/ftn/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"FIN\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_ftn(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"FIN\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startFtn({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"FIN\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartFtn(ctx, didit.IDWalletsStartFtnParams{\n\tBody: didit.DigitalIDWalletAPIFtnWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIFtnWalletVerificationStartCountryEnum(\"FIN\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiFtnWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiFtnWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startFtn(DigitalIdWalletApiFtnWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiFtnWalletVerificationStartCountryEnum.of(\"FIN\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startFtn([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'FIN',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_ftn(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"FIN\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartFtnAsync(new DigitalIdWalletApiFtnWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiFtnWalletVerificationStartCountryEnum(\"FIN\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "ftn" + } + }, + "/v3/id-verification/wallets/personalausweis/": { + "post": { + "operationId": "start_personalausweis_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_personalausweis", + "auth": "api_key", + "server": true + }, + "description": "Start a Personalausweis sign-in (Bundesministerium des Innern (eID); DEU) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Personalausweis verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest" + }, + "example": { + "country": "DEU", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "personalausweis", + "name": "Personalausweis", + "issuing_authority": "Bundesministerium des Innern (eID)" + }, + "country": "DEU", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/personalausweis/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"DEU\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_personalausweis(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"DEU\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startPersonalausweis({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"DEU\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartPersonalausweis(ctx, didit.IDWalletsStartPersonalausweisParams{\n\tBody: didit.DigitalIDWalletAPIPersonalausweisWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIPersonalausweisWalletVerificationStartCountryEnum(\"DEU\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiPersonalausweisWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startPersonalausweis(DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiPersonalausweisWalletVerificationStartCountryEnum.of(\"DEU\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startPersonalausweis([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'DEU',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_personalausweis(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"DEU\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartPersonalausweisAsync(new DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiPersonalausweisWalletVerificationStartCountryEnum(\"DEU\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "personalausweis" + } + }, + "/v3/id-verification/wallets/frejaid/": { + "post": { + "operationId": "start_frejaid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_frejaid", + "auth": "api_key", + "server": true + }, + "description": "Start a Freja eID sign-in (Freja eID Group; SWE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Freja eID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFrejaidWalletVerificationStartRequest" + }, + "example": { + "country": "SWE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFrejaidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFrejaidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "frejaid", + "name": "Freja eID", + "issuing_authority": "Freja eID Group" + }, + "country": "SWE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/frejaid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"SWE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_frejaid(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"SWE\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startFrejaid({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"SWE\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartFrejaid(ctx, didit.IDWalletsStartFrejaidParams{\n\tBody: didit.DigitalIDWalletAPIFrejaidWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryEe9enum(\"SWE\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiCountryEe9Enum;\nimport me.didit.types.DigitalIdWalletApiFrejaidWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startFrejaid(DigitalIdWalletApiFrejaidWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryEe9Enum.of(\"SWE\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startFrejaid([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'SWE',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_frejaid(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"SWE\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartFrejaidAsync(new DigitalIdWalletApiFrejaidWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryEe9Enum(\"SWE\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "frejaid" + } + }, + "/v3/id-verification/wallets/uae-pass/": { + "post": { + "operationId": "start_uae_pass_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_uae_pass", + "auth": "api_key", + "server": true + }, + "description": "Start a UAE PASS sign-in (UAE Digital Government Authority; ARE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start UAE PASS verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiUaePassWalletVerificationStartRequest" + }, + "example": { + "country": "ARE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiUaePassWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiUaePassWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "uae_pass", + "name": "UAE PASS", + "issuing_authority": "UAE Digital Government Authority" + }, + "country": "ARE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/uae-pass/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"ARE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_uae_pass(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"ARE\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startUaePass({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"ARE\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartUaePass(ctx, didit.IDWalletsStartUaePassParams{\n\tBody: didit.DigitalIDWalletAPIUaePassWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIUaePassWalletVerificationStartCountryEnum(\"ARE\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiUaePassWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiUaePassWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startUaePass(DigitalIdWalletApiUaePassWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiUaePassWalletVerificationStartCountryEnum.of(\"ARE\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startUaePass([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'ARE',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_uae_pass(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"ARE\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartUaePassAsync(new DigitalIdWalletApiUaePassWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiUaePassWalletVerificationStartCountryEnum(\"ARE\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "uae_pass" + } + }, + "/v3/id-verification/wallets/govbr/": { + "post": { + "operationId": "start_govbr_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_govbr", + "auth": "api_key", + "server": true + }, + "description": "Start a gov.br sign-in (Governo Federal do Brasil; BRA) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start gov.br verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovbrWalletVerificationStartRequest" + }, + "example": { + "country": "BRA", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovbrWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovbrWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "govbr", + "name": "gov.br", + "issuing_authority": "Governo Federal do Brasil" + }, + "country": "BRA", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/govbr/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"BRA\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_govbr(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"BRA\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startGovbr({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"BRA\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartGovbr(ctx, didit.IDWalletsStartGovbrParams{\n\tBody: didit.DigitalIDWalletAPIGovbrWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIGovbrWalletVerificationStartCountryEnum(\"BRA\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiGovbrWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiGovbrWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startGovbr(DigitalIdWalletApiGovbrWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiGovbrWalletVerificationStartCountryEnum.of(\"BRA\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startGovbr([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'BRA',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_govbr(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"BRA\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartGovbrAsync(new DigitalIdWalletApiGovbrWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiGovbrWalletVerificationStartCountryEnum(\"BRA\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "govbr" + } + }, + "/v3/id-verification/wallets/oneid/": { + "post": { + "operationId": "start_oneid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_oneid", + "auth": "api_key", + "server": true + }, + "description": "Start a OneID sign-in (OneID (UK bank-verified identity); GBR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start OneID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiOneidWalletVerificationStartRequest" + }, + "example": { + "country": "GBR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiOneidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiOneidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "oneid", + "name": "OneID", + "issuing_authority": "OneID (UK bank-verified identity)" + }, + "country": "GBR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/oneid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"GBR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_oneid(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"GBR\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startOneid({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"GBR\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartOneid(ctx, didit.IDWalletsStartOneidParams{\n\tBody: didit.DigitalIDWalletAPIOneidWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountry520enum(\"GBR\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiCountry520Enum;\nimport me.didit.types.DigitalIdWalletApiOneidWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startOneid(DigitalIdWalletApiOneidWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountry520Enum.of(\"GBR\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startOneid([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'GBR',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_oneid(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"GBR\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartOneidAsync(new DigitalIdWalletApiOneidWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountry520Enum(\"GBR\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "oneid" + } + }, + "/v3/id-verification/wallets/govuk-wallet/": { + "post": { + "operationId": "start_govuk_wallet_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_govuk_wallet", + "auth": "api_key", + "server": true + }, + "description": "Start a GOV.UK Wallet sign-in (UK Government Digital Service; GBR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start GOV.UK Wallet verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovukWalletWalletVerificationStartRequest" + }, + "example": { + "country": "GBR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovukWalletWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGovukWalletWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "govuk_wallet", + "name": "GOV.UK Wallet", + "issuing_authority": "UK Government Digital Service" + }, + "country": "GBR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/govuk-wallet/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"GBR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_govuk_wallet(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"GBR\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startGovukWallet({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"GBR\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartGovukWallet(ctx, didit.IDWalletsStartGovukWalletParams{\n\tBody: didit.DigitalIDWalletAPIGovukWalletWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountry520enum(\"GBR\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiCountry520Enum;\nimport me.didit.types.DigitalIdWalletApiGovukWalletWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startGovukWallet(DigitalIdWalletApiGovukWalletWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountry520Enum.of(\"GBR\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startGovukWallet([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'GBR',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_govuk_wallet(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"GBR\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartGovukWalletAsync(new DigitalIdWalletApiGovukWalletWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountry520Enum(\"GBR\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "govuk_wallet" + } + }, + "/v3/id-verification/wallets/smart-id/": { + "post": { + "operationId": "start_smart_id_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_smart_id", + "auth": "api_key", + "server": true + }, + "description": "Start a Smart-ID sign-in (SK ID Solutions; EST, LVA, LTU, BEL) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=confirm_in_app with the comparison code to show the user; the user confirms it and enters their PIN in the app (never collect the PIN). Poll POST /v3/id-verification/wallets/verifications/{request_id}/poll/ every poll_interval_seconds, or rely on the webhook: Didit polls in the background too. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Smart-ID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiSmartIdWalletVerificationStartRequest" + }, + "example": { + "country": "EST", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "personal_code": "00000000000" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiSmartIdWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiSmartIdWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "smart_id", + "name": "Smart-ID", + "issuing_authority": "SK ID Solutions" + }, + "country": "EST", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "in_app", + "simulated": true, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "confirm_in_app", + "verification_code": "1234", + "poll_interval_seconds": 2 + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/smart-id/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"EST\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"personal_code\": \"00000000000\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_smart_id(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"EST\",\n personal_code=\"00000000000\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startSmartId({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"EST\",\n personal_code: \"00000000000\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartSmartID(ctx, didit.IDWalletsStartSmartIDParams{\n\tBody: didit.DigitalIDWalletAPISmartIDWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.DigitalIDWalletAPISmartIDWalletVerificationStartCountryEnum(\"EST\"),\n\t\tPersonalCode: \"00000000000\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiSmartIdWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiSmartIdWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startSmartId(DigitalIdWalletApiSmartIdWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiSmartIdWalletVerificationStartCountryEnum.of(\"EST\"))\n .personalCode(\"00000000000\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startSmartId([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'EST',\n 'personal_code' => '00000000000',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_smart_id(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"EST\",\n personal_code: \"00000000000\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartSmartIdAsync(new DigitalIdWalletApiSmartIdWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = \"EST\",\n PersonalCode = \"00000000000\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "smart_id" + } + }, + "/v3/id-verification/wallets/mobile-id/": { + "post": { + "operationId": "start_mobile_id_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_mobile_id", + "auth": "api_key", + "server": true + }, + "description": "Start a Mobile-ID sign-in (SK ID Solutions with the national mobile operators; EST, LTU) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=confirm_in_app with the comparison code to show the user; the user confirms it and enters their PIN in the app (never collect the PIN). Poll POST /v3/id-verification/wallets/verifications/{request_id}/poll/ every poll_interval_seconds, or rely on the webhook: Didit polls in the background too. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Mobile-ID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMobileIdWalletVerificationStartRequest" + }, + "example": { + "country": "EST", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "personal_code": "00000000000", + "phone_number": "+37200000000" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMobileIdWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMobileIdWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "mobile_id", + "name": "Mobile-ID", + "issuing_authority": "SK ID Solutions with the national mobile operators" + }, + "country": "EST", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "in_app", + "simulated": true, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "confirm_in_app", + "verification_code": "1234", + "poll_interval_seconds": 2 + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/mobile-id/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"EST\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"personal_code\": \"00000000000\",\n \"phone_number\": \"+37200000000\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_mobile_id(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"EST\",\n personal_code=\"00000000000\",\n phone_number=\"+37200000000\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startMobileId({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"EST\",\n personal_code: \"00000000000\",\n phone_number: \"+37200000000\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartMobileID(ctx, didit.IDWalletsStartMobileIDParams{\n\tBody: didit.DigitalIDWalletAPIMobileIDWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.DigitalIDWalletAPIMobileIDWalletVerificationStartCountryEnum(\"EST\"),\n\t\tPersonalCode: \"00000000000\",\n\t\tPhoneNumber: \"+37200000000\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiMobileIdWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiMobileIdWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startMobileId(DigitalIdWalletApiMobileIdWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiMobileIdWalletVerificationStartCountryEnum.of(\"EST\"))\n .personalCode(\"00000000000\")\n .phoneNumber(\"+37200000000\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startMobileId([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'EST',\n 'personal_code' => '00000000000',\n 'phone_number' => '+37200000000',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_mobile_id(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"EST\",\n personal_code: \"00000000000\",\n phone_number: \"+37200000000\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartMobileIdAsync(new DigitalIdWalletApiMobileIdWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = \"EST\",\n PersonalCode = \"00000000000\",\n PhoneNumber = \"+37200000000\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "mobile_id" + } + }, + "/v3/id-verification/wallets/idcard-ee/": { + "post": { + "operationId": "start_idcard_ee_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_idcard_ee", + "auth": "api_key", + "server": true + }, + "description": "Start a Estonian ID-card sign-in (Police and Border Guard Board (Estonia); EST) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Estonian ID-card verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdcardEeWalletVerificationStartRequest" + }, + "example": { + "country": "EST", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdcardEeWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiIdcardEeWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "idcard_ee", + "name": "Estonian ID-card", + "issuing_authority": "Police and Border Guard Board (Estonia)" + }, + "country": "EST", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/idcard-ee/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"EST\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_idcard_ee(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"EST\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startIdcardEe({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"EST\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartIdcardEe(ctx, didit.IDWalletsStartIdcardEeParams{\n\tBody: didit.DigitalIDWalletAPIIdcardEeWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIIdcardEeWalletVerificationStartCountryEnum(\"EST\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiIdcardEeWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiIdcardEeWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startIdcardEe(DigitalIdWalletApiIdcardEeWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiIdcardEeWalletVerificationStartCountryEnum.of(\"EST\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startIdcardEe([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'EST',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_idcard_ee(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"EST\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartIdcardEeAsync(new DigitalIdWalletApiIdcardEeWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiIdcardEeWalletVerificationStartCountryEnum(\"EST\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "idcard_ee" + } + }, + "/v3/id-verification/wallets/eparaksts-mobile/": { + "post": { + "operationId": "start_eparaksts_mobile_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_eparaksts_mobile", + "auth": "api_key", + "server": true + }, + "description": "Start a eParaksts mobile sign-in (LVRTC (Latvian State Radio and Television Centre); LVA) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start eParaksts mobile verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest" + }, + "example": { + "country": "LVA", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "eparaksts_mobile", + "name": "eParaksts mobile", + "issuing_authority": "LVRTC (Latvian State Radio and Television Centre)" + }, + "country": "LVA", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/eparaksts-mobile/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"LVA\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_eparaksts_mobile(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"LVA\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startEparakstsMobile({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"LVA\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartEparakstsMobile(ctx, didit.IDWalletsStartEparakstsMobileParams{\n\tBody: didit.DigitalIDWalletAPIEparakstsMobileWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIEparakstsMobileWalletVerificationStartCountryEnum(\"LVA\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiEparakstsMobileWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startEparakstsMobile(DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiEparakstsMobileWalletVerificationStartCountryEnum.of(\"LVA\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startEparakstsMobile([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'LVA',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_eparaksts_mobile(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"LVA\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartEparakstsMobileAsync(new DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiEparakstsMobileWalletVerificationStartCountryEnum(\"LVA\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "eparaksts_mobile" + } + }, + "/v3/id-verification/wallets/bankid-cz/": { + "post": { + "operationId": "start_bankid_cz_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_bankid_cz", + "auth": "api_key", + "server": true + }, + "description": "Start a Bank iD sign-in (Bankovní identita, a.s.; CZE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Bank iD verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidCzWalletVerificationStartRequest" + }, + "example": { + "country": "CZE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidCzWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiBankidCzWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "bankid_cz", + "name": "Bank iD", + "issuing_authority": "Bankovní identita, a.s." + }, + "country": "CZE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/bankid-cz/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"CZE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_bankid_cz(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"CZE\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startBankidCz({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"CZE\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartBankidCz(ctx, didit.IDWalletsStartBankidCzParams{\n\tBody: didit.DigitalIDWalletAPIBankidCzWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryE2dEnum(\"CZE\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiBankidCzWalletVerificationStartRequest;\nimport me.didit.types.DigitalIdWalletApiCountryE2dEnum;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startBankidCz(DigitalIdWalletApiBankidCzWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryE2dEnum.of(\"CZE\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startBankidCz([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'CZE',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_bankid_cz(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"CZE\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartBankidCzAsync(new DigitalIdWalletApiBankidCzWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryE2dEnum(\"CZE\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "bankid_cz" + } + }, + "/v3/id-verification/wallets/mojeid/": { + "post": { + "operationId": "start_mojeid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_mojeid", + "auth": "api_key", + "server": true + }, + "description": "Start a MojeID sign-in (CZ.NIC; CZE) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start MojeID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMojeidWalletVerificationStartRequest" + }, + "example": { + "country": "CZE", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMojeidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiMojeidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "mojeid", + "name": "MojeID", + "issuing_authority": "CZ.NIC" + }, + "country": "CZE", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/mojeid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"CZE\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_mojeid(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"CZE\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startMojeid({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"CZE\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartMojeid(ctx, didit.IDWalletsStartMojeidParams{\n\tBody: didit.DigitalIDWalletAPIMojeidWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPICountryE2dEnum(\"CZE\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiCountryE2dEnum;\nimport me.didit.types.DigitalIdWalletApiMojeidWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startMojeid(DigitalIdWalletApiMojeidWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiCountryE2dEnum.of(\"CZE\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startMojeid([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'CZE',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_mojeid(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"CZE\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartMojeidAsync(new DigitalIdWalletApiMojeidWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiCountryE2dEnum(\"CZE\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "mojeid" + } + }, + "/v3/id-verification/wallets/diia/": { + "post": { + "operationId": "start_diia_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_diia", + "auth": "api_key", + "server": true + }, + "description": "Start a Diia sign-in (Ministry of Digital Transformation of Ukraine; UKR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Diia verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiDiiaWalletVerificationStartRequest" + }, + "example": { + "country": "UKR", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiDiiaWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiDiiaWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "diia", + "name": "Diia", + "issuing_authority": "Ministry of Digital Transformation of Ukraine" + }, + "country": "UKR", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/diia/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"UKR\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_diia(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"UKR\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startDiia({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"UKR\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartDiia(ctx, didit.IDWalletsStartDiiaParams{\n\tBody: didit.DigitalIDWalletAPIDiiaWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIDiiaWalletVerificationStartCountryEnum(\"UKR\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiDiiaWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiDiiaWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startDiia(DigitalIdWalletApiDiiaWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiDiiaWalletVerificationStartCountryEnum.of(\"UKR\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startDiia([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'UKR',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_diia(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"UKR\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartDiiaAsync(new DigitalIdWalletApiDiiaWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiDiiaWalletVerificationStartCountryEnum(\"UKR\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "diia" + } + }, + "/v3/id-verification/wallets/franceconnect/": { + "post": { + "operationId": "start_franceconnect_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_franceconnect", + "auth": "api_key", + "server": true + }, + "description": "Start a FranceConnect sign-in (DINUM (French state); FRA) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start FranceConnect verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFranceconnectWalletVerificationStartRequest" + }, + "example": { + "country": "FRA", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFranceconnectWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiFranceconnectWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "franceconnect", + "name": "FranceConnect", + "issuing_authority": "DINUM (French state)" + }, + "country": "FRA", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/franceconnect/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"FRA\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_franceconnect(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"FRA\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startFranceconnect({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"FRA\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartFranceconnect(ctx, didit.IDWalletsStartFranceconnectParams{\n\tBody: didit.DigitalIDWalletAPIFranceconnectWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIFranceconnectWalletVerificationStartCountryEnum(\"FRA\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiFranceconnectWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiFranceconnectWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startFranceconnect(DigitalIdWalletApiFranceconnectWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiFranceconnectWalletVerificationStartCountryEnum.of(\"FRA\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startFranceconnect([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'FRA',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_franceconnect(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"FRA\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartFranceconnectAsync(new DigitalIdWalletApiFranceconnectWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiFranceconnectWalletVerificationStartCountryEnum(\"FRA\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "franceconnect" + } + }, + "/v3/id-verification/wallets/audkenni/": { + "post": { + "operationId": "start_audkenni_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_audkenni", + "auth": "api_key", + "server": true + }, + "description": "Start a Auðkenni sign-in (Auðkenni (Icelandic electronic ID); ISL) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Auðkenni verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiAudkenniWalletVerificationStartRequest" + }, + "example": { + "country": "ISL", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiAudkenniWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiAudkenniWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "audkenni", + "name": "Auðkenni", + "issuing_authority": "Auðkenni (Icelandic electronic ID)" + }, + "country": "ISL", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/audkenni/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"ISL\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_audkenni(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"ISL\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startAudkenni({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"ISL\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartAudkenni(ctx, didit.IDWalletsStartAudkenniParams{\n\tBody: didit.DigitalIDWalletAPIAudkenniWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIAudkenniWalletVerificationStartCountryEnum(\"ISL\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiAudkenniWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiAudkenniWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startAudkenni(DigitalIdWalletApiAudkenniWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiAudkenniWalletVerificationStartCountryEnum.of(\"ISL\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startAudkenni([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'ISL',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_audkenni(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"ISL\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartAudkenniAsync(new DigitalIdWalletApiAudkenniWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiAudkenniWalletVerificationStartCountryEnum(\"ISL\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "audkenni" + } + }, + "/v3/id-verification/wallets/connectid/": { + "post": { + "operationId": "start_connectid_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_connectid", + "auth": "api_key", + "server": true + }, + "description": "Start a ConnectID sign-in (Australian Payments Plus; AUS) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start ConnectID verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiConnectidWalletVerificationStartRequest" + }, + "example": { + "country": "AUS", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiConnectidWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiConnectidWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "connectid", + "name": "ConnectID", + "issuing_authority": "Australian Payments Plus" + }, + "country": "AUS", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/connectid/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"AUS\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_connectid(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"AUS\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startConnectid({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"AUS\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartConnectid(ctx, didit.IDWalletsStartConnectidParams{\n\tBody: didit.DigitalIDWalletAPIConnectidWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.Some(didit.DigitalIDWalletAPIConnectidWalletVerificationStartCountryEnum(\"AUS\")),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiConnectidWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiConnectidWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startConnectid(DigitalIdWalletApiConnectidWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiConnectidWalletVerificationStartCountryEnum.of(\"AUS\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startConnectid([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'AUS',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_connectid(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"AUS\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartConnectidAsync(new DigitalIdWalletApiConnectidWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = new DigitalIdWalletApiConnectidWalletVerificationStartCountryEnum(\"AUS\"),\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "connectid" + } + }, + "/v3/id-verification/wallets/eudi/": { + "post": { + "operationId": "start_eudi_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_eudi", + "auth": "api_key", + "server": true + }, + "description": "Start a EUDI Wallet sign-in (The user's own member state; the issuer differs per country; AUT, BEL, BGR, HRV, CYP, CZE, DNK, EST, FIN, FRA, DEU, GRC, HUN, IRL, ITA, LVA, LTU, LUX, MLT, NLD, POL, PRT, ROU, SVK, SVN, ESP, SWE, ISL, LIE, NOR) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=redirect with the URL to send the user's browser to; the user returns to return_url with request_id and result appended. Read the outcome with GET /v3/id-verification/wallets/verifications/{request_id}/ or from the webhook, never from the URL. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start EUDI Wallet verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEudiWalletVerificationStartRequest" + }, + "example": { + "country": "AUT", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "return_url": "https://app.example.com/identity/complete" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEudiWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiEudiWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "eudi", + "name": "EUDI Wallet", + "issuing_authority": "The user's own member state; the issuer differs per country" + }, + "country": "AUT", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "redirect", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "redirect", + "url": "https://wallet.example.com/authorize" + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/eudi/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"AUT\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"return_url\": \"https://app.example.com/identity/complete\"\n}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.id_wallets.start_eudi(\n vendor_data=\"your-customer-reference\",\n metadata={\"reference\": \"example\"},\n country=\"AUT\",\n return_url=\"https://app.example.com/identity/complete\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.idWallets.startEudi({\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"AUT\",\n return_url: \"https://app.example.com/identity/complete\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.IDWallets.StartEudi(ctx, didit.IDWalletsStartEudiParams{\n\tBody: didit.DigitalIDWalletAPIEudiWalletVerificationStartRequest{\n\t\tVendorData: didit.String(\"your-customer-reference\"),\n\t\tMetadata: didit.Some(map[string]any{\"reference\": \"example\"}),\n\t\tCountry: didit.DigitalIDWalletAPIEudiWalletVerificationStartCountryEnum(\"AUT\"),\n\t\tReturnURL: \"https://app.example.com/identity/complete\",\n\t},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.DigitalIdWalletApiEudiWalletVerificationStartCountryEnum;\nimport me.didit.types.DigitalIdWalletApiEudiWalletVerificationStartRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.idWallets().startEudi(DigitalIdWalletApiEudiWalletVerificationStartRequest.builder()\n .vendorData(\"your-customer-reference\")\n .metadata(Map.of(\"reference\", \"example\"))\n .country(DigitalIdWalletApiEudiWalletVerificationStartCountryEnum.of(\"AUT\"))\n .returnUrl(\"https://app.example.com/identity/complete\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "idWallets->startEudi([\n 'vendor_data' => 'your-customer-reference',\n 'metadata' => ['reference' => 'example'],\n 'country' => 'AUT',\n 'return_url' => 'https://app.example.com/identity/complete',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.id_wallets.start_eudi(\n vendor_data: \"your-customer-reference\",\n metadata: { reference: \"example\" },\n country: \"AUT\",\n return_url: \"https://app.example.com/identity/complete\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.IdWallets.StartEudiAsync(new DigitalIdWalletApiEudiWalletVerificationStartRequest\n{\n VendorData = \"your-customer-reference\",\n Metadata = new Dictionary { [\"reference\"] = \"example\" },\n Country = \"AUT\",\n ReturnUrl = \"https://app.example.com/identity/complete\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-wallet-id": "eudi" + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/teams/": { + "get": { + "summary": "List review teams", + "description": "Requires `list:cases` for the requested application.", + "operationId": "list_case_teams", + "x-didit-sdk": { + "resource": "cases.teams", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000000, + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseReviewTeam" + } + }, + "count": { + "type": "integer", + "description": "Matching count, capped at 100. Follow next to continue beyond the cap." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.teams.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.teams.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.Teams.ListAutoPaging(ctx, didit.CasesTeamsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().teams().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->teams->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.teams.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.Teams.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "summary": "Create review team", + "description": "Requires `write:cases` for the requested application.", + "operationId": "create_case_teams", + "x-didit-sdk": { + "resource": "cases.teams", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "responses": { + "201": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeam" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeamCreate" + }, + "example": { + "name": "AML analysts", + "members": [ + { + "user_id": "9a000000-0000-4000-8000-0000000000aa", + "capacity": 10 + } + ] + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.teams.create(\n name=\"AML analysts\",\n members=[{\n \"user_id\": \"9a000000-0000-4000-8000-0000000000aa\",\n \"capacity\": 10,\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.teams.create({\n name: \"AML analysts\",\n members: [{\n user_id: \"9a000000-0000-4000-8000-0000000000aa\",\n capacity: 10,\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Teams.Create(ctx, didit.CaseReviewTeamCreate{\n\tName: \"AML analysts\",\n\tMembers: []didit.CaseReviewTeamMemberParam{{\n\t\tUserID: \"9a000000-0000-4000-8000-0000000000aa\",\n\t\tCapacity: didit.Int(10),\n\t}},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CaseReviewTeamCreate;\nimport me.didit.types.CaseReviewTeamMember;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().teams().create(CaseReviewTeamCreate.builder()\n .name(\"AML analysts\")\n .members(List.of(CaseReviewTeamMember.builder()\n .userId(\"9a000000-0000-4000-8000-0000000000aa\")\n .capacity(10L)\n .build()))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->teams->create([\n 'name' => 'AML analysts',\n 'members' => [[\n 'user_id' => '9a000000-0000-4000-8000-0000000000aa',\n 'capacity' => 10,\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.teams.create(\n name: \"AML analysts\",\n members: [{\n user_id: \"9a000000-0000-4000-8000-0000000000aa\",\n capacity: 10,\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Teams.CreateAsync(new CaseReviewTeamCreate\n{\n Name = \"AML analysts\",\n Members = new CaseReviewTeamMember[]\n {\n new()\n {\n UserId = \"9a000000-0000-4000-8000-0000000000aa\",\n Capacity = 10,\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/fraud/checks/{check_id}/": { + "get": { + "operationId": "root_retrieve", + "x-didit-sdk": { + "resource": "fraud_checks", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "description": "Fetch a previously created check by its `check_id`.", + "summary": "Retrieve a Fraud Check", + "parameters": [ + { + "in": "path", + "name": "check_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckResponse" + } + } + }, + "description": "" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudClientCredentialsErrorMessage" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "tags": [ + "Fraud Check" + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.fraud_checks.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.fraudChecks.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.FraudChecks.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.fraudChecks().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "fraudChecks->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.fraud_checks.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.FraudChecks.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/": { + "get": { + "operationId": "organization_application_cases_requests_list", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "name": "ordering", + "required": false, + "in": "query", + "description": "Which field to use when ordering the results.", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "offset", + "required": false, + "in": "query", + "description": "The initial index from which to return the results.", + "schema": { + "type": "integer" + } + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingPaginatedCaseRequestList" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.requests.list(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.requests.list(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.Requests.ListAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasesRequestsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().requests().list(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->list('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.requests.list(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.Requests.ListAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "operationId": "organization_application_cases_requests_create", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestConfirmRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestConfirmRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestConfirmRequest" + } + } + }, + "required": true + }, + "responses": { + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "400": { + "description": "Invalid inputs, unsupported purpose, unavailable verified contact or an existing immutable response." + }, + "409": { + "description": "The preview changed or expired. Preview again before confirming." + }, + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequest" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Confirm the exact signed preview within 15 minutes. Only fixed templates and an OTP-verified contact belonging to the case subject are accepted. The request ID makes retries idempotent. Delivery is queued; SENT, SIMULATED and UNKNOWN are distinct outcomes. An actionable request sets the case to Awaiting user. A review update leaves its status unchanged.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.requests.create(\n \"11111111-2222-3333-4444-555555555555\",\n contact_id=\"11111111-2222-3333-4444-555555555555\",\n purpose=\"identity_document\",\n request_id=\"11111111-2222-3333-4444-555555555555\",\n expires_at=\"2026-01-15T10:00:00Z\",\n preview_token=\"your-preview-token\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.requests.create(\"11111111-2222-3333-4444-555555555555\", {\n contact_id: \"11111111-2222-3333-4444-555555555555\",\n purpose: \"identity_document\",\n request_id: \"11111111-2222-3333-4444-555555555555\",\n expires_at: \"2026-01-15T10:00:00Z\",\n preview_token: \"your-preview-token\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Requests.Create(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CustomerMessagingCaseRequestConfirmRequest{\n\tContactID: \"11111111-2222-3333-4444-555555555555\",\n\tPurpose: didit.CustomerMessagingPurposeEnum(\"identity_document\"),\n\tRequestID: \"11111111-2222-3333-4444-555555555555\",\n\tExpiresAt: time.Date(2026, 1, 15, 10, 0, 0, 0, time.UTC),\n\tPreviewToken: \"your-preview-token\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.OffsetDateTime;\nimport me.didit.Didit;\nimport me.didit.types.CustomerMessagingCaseRequestConfirmRequest;\nimport me.didit.types.CustomerMessagingPurposeEnum;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().requests().create(\"11111111-2222-3333-4444-555555555555\", CustomerMessagingCaseRequestConfirmRequest.builder()\n .contactId(\"11111111-2222-3333-4444-555555555555\")\n .purpose(CustomerMessagingPurposeEnum.of(\"identity_document\"))\n .requestId(\"11111111-2222-3333-4444-555555555555\")\n .expiresAt(OffsetDateTime.parse(\"2026-01-15T10:00:00Z\"))\n .previewToken(\"your-preview-token\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->create('11111111-2222-3333-4444-555555555555', [\n 'contact_id' => '11111111-2222-3333-4444-555555555555',\n 'purpose' => 'identity_document',\n 'request_id' => '11111111-2222-3333-4444-555555555555',\n 'expires_at' => '2026-01-15T10:00:00Z',\n 'preview_token' => 'your-preview-token',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.requests.create(\n \"11111111-2222-3333-4444-555555555555\",\n contact_id: \"11111111-2222-3333-4444-555555555555\",\n purpose: \"identity_document\",\n request_id: \"11111111-2222-3333-4444-555555555555\",\n expires_at: \"2026-01-15T10:00:00Z\",\n preview_token: \"your-preview-token\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Requests.CreateAsync(\"11111111-2222-3333-4444-555555555555\", new CustomerMessagingCaseRequestConfirmRequest\n{\n ContactId = \"11111111-2222-3333-4444-555555555555\",\n Purpose = \"identity_document\",\n RequestId = \"11111111-2222-3333-4444-555555555555\",\n ExpiresAt = DateTimeOffset.Parse(\"2026-01-15T10:00:00Z\"),\n PreviewToken = \"your-preview-token\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/fraud/checks/": { + "post": { + "operationId": "create_fraud_check", + "x-didit-sdk": { + "resource": "fraud_checks", + "method": "create", + "auth": "api_key", + "server": true + }, + "description": "Submit one or more typed identifiers and receive normalization, fresh enrichment, authoritative validation where the country and identifier type are covered, independent risk dimensions and a recommendation in one response.\n\nThese profiles support identifier checks. Selfie-based identity binding (`biometric_identity`) requires profile version `2026-09-14.2` and a Brazilian CPF; it runs only with the user's consent and when your organization is entitled to it. Earlier versions report it as `not_available_in_profile_version`. Modules that cannot run are reported as skipped with a deterministic reason; a source that could not answer is never reported as an identity mismatch.", + "summary": "Run a Fraud Check", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + }, + "example": { + "client_reference": "review-attempt-001", + "subject": { + "identifiers": [ + { + "type": "email", + "value": "applicant@example.com" + } + ], + "attributes": { + "first_name": "Alex", + "last_name": "Example" + } + }, + "checks": { + "include": [ + "networks_same_org", + "networks_cross_org", + "email_risk" + ] + } + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckResponse" + } + } + }, + "description": "" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudClientCredentialsErrorMessage" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "" + }, + "409": { + "description": "Conflict, returned without a charge: the `client_reference` was already used with a different request body; a check with the same `client_reference` is still running (retry after `Retry-After` seconds with the same reference); or the verification this check records into was deleted before the check completed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + }, + "examples": { + "different_body": { + "summary": "Reference reused with a different body", + "value": { + "error": "client_reference has already been used with a different request body." + } + }, + "still_running": { + "summary": "A check with this reference is still running", + "value": { + "error": "A check with this client_reference is already running. Retry with the same reference later." + } + }, + "verification_deleted": { + "summary": "The verification was deleted before the check completed", + "value": { + "error": "This verification was deleted before the check completed." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying, sent when a check with the same `client_reference` is still running.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "tags": [ + "Fraud Check" + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.fraud_checks.create(\n client_reference=\"review-attempt-001\",\n subject={\n \"identifiers\": [{\n \"type\": \"email\",\n \"value\": \"applicant@example.com\",\n }],\n \"attributes\": {\n \"first_name\": \"Alex\",\n \"last_name\": \"Example\",\n },\n },\n checks={\n \"include\": [\"networks_same_org\", \"networks_cross_org\", \"email_risk\"],\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.fraudChecks.create({\n client_reference: \"review-attempt-001\",\n subject: {\n identifiers: [{\n type: \"email\",\n value: \"applicant@example.com\",\n }],\n attributes: {\n first_name: \"Alex\",\n last_name: \"Example\",\n },\n },\n checks: {\n include: [\"networks_same_org\", \"networks_cross_org\", \"email_risk\"],\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.FraudChecks.Create(ctx, didit.FraudCheckRequestRequest{\n\tClientReference: didit.String(\"review-attempt-001\"),\n\tSubject: didit.FraudCheckSubjectRequest{\n\t\tIdentifiers: []didit.FraudCheckIdentifierRequest{{\n\t\t\tType: didit.FraudTypeEnum(\"email\"),\n\t\t\tValue: \"applicant@example.com\",\n\t\t}},\n\t\tAttributes: didit.Some(didit.FraudCheckAttributesRequest{\n\t\t\tFirstName: didit.String(\"Alex\"),\n\t\t\tLastName: didit.String(\"Example\"),\n\t\t}),\n\t},\n\tChecks: didit.Some(didit.FraudCheckModuleSelectionRequest{\n\t\tInclude: didit.Some([]didit.FraudExcludeEnum{didit.FraudExcludeEnum(\"networks_same_org\"), didit.FraudExcludeEnum(\"networks_cross_org\"), didit.FraudExcludeEnum(\"email_risk\")}),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.FraudCheckAttributesRequest;\nimport me.didit.types.FraudCheckIdentifierRequest;\nimport me.didit.types.FraudCheckModuleSelectionRequest;\nimport me.didit.types.FraudCheckRequestRequest;\nimport me.didit.types.FraudCheckSubjectRequest;\nimport me.didit.types.FraudExcludeEnum;\nimport me.didit.types.FraudTypeEnum;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.fraudChecks().create(FraudCheckRequestRequest.builder()\n .clientReference(\"review-attempt-001\")\n .subject(FraudCheckSubjectRequest.builder()\n .identifiers(List.of(FraudCheckIdentifierRequest.builder()\n .type(FraudTypeEnum.of(\"email\"))\n .value(\"applicant@example.com\")\n .build()))\n .attributes(FraudCheckAttributesRequest.builder()\n .firstName(\"Alex\")\n .lastName(\"Example\")\n .build())\n .build())\n .checks(FraudCheckModuleSelectionRequest.builder()\n .include(List.of(FraudExcludeEnum.of(\"networks_same_org\"), FraudExcludeEnum.of(\"networks_cross_org\"), FraudExcludeEnum.of(\"email_risk\")))\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "fraudChecks->create([\n 'client_reference' => 'review-attempt-001',\n 'subject' => [\n 'identifiers' => [[\n 'type' => 'email',\n 'value' => 'applicant@example.com',\n ]],\n 'attributes' => [\n 'first_name' => 'Alex',\n 'last_name' => 'Example',\n ],\n ],\n 'checks' => [\n 'include' => ['networks_same_org', 'networks_cross_org', 'email_risk'],\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.fraud_checks.create(\n client_reference: \"review-attempt-001\",\n subject: {\n identifiers: [{\n type: \"email\",\n value: \"applicant@example.com\",\n }],\n attributes: {\n first_name: \"Alex\",\n last_name: \"Example\",\n },\n },\n checks: {\n include: [\"networks_same_org\", \"networks_cross_org\", \"email_risk\"],\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.FraudChecks.CreateAsync(new FraudCheckRequestRequest\n{\n ClientReference = \"review-attempt-001\",\n Subject = new()\n {\n Identifiers = new FraudCheckIdentifierRequest[]\n {\n new()\n {\n Type = \"email\",\n Value = \"applicant@example.com\",\n },\n },\n Attributes = new FraudCheckAttributesRequest\n {\n FirstName = \"Alex\",\n LastName = \"Example\",\n },\n },\n Checks = new FraudCheckModuleSelectionRequest\n {\n Include = new FraudExcludeEnum[] { \"networks_same_org\", \"networks_cross_org\", \"email_risk\" },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/{request_uuid}/cancel/": { + "post": { + "operationId": "organization_application_cases_requests_cancel_create", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "cancel", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequest" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "400": { + "description": "Invalid inputs, unsupported purpose, unavailable verified contact or an existing immutable response." + }, + "409": { + "description": "The preview changed or expired. Preview again before confirming." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.requests.cancel(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.requests.cancel(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Requests.Cancel(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().requests().cancel(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->cancel('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.requests.cancel(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Requests.CancelAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/options/": { + "get": { + "operationId": "organization_application_cases_requests_options_retrieve", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "retrieve_options", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestOptions" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.requests.retrieve_options(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.requests.retrieveOptions(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Requests.RetrieveOptions(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().requests().retrieveOptions(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->retrieveOptions('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.requests.retrieve_options(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Requests.RetrieveOptionsAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/{request_uuid}/attachment/": { + "get": { + "operationId": "organization_application_cases_requests_attachment_retrieve", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "retrieve_attachment", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "image/jpeg": { + "schema": { + "type": "string", + "format": "binary" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\ncontent = client.cases.requests.retrieve_attachment(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nwith open(\"download.png\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst content = await didit.cases.requests.retrieveAttachment(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nawait writeFile(\"download.png\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresp, err := client.Cases.Requests.RetrieveAttachment(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.png\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().requests().retrieveAttachment(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\")\n .writeTo(Path.of(\"download.png\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->retrieveAttachment('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555');\n$content->saveTo('download.png');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ncontent = didit.cases.requests.retrieve_attachment(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nFile.binwrite(\"download.png\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar content = await client.Cases.Requests.RetrieveAttachmentAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nawait content.SaveToAsync(\"download.png\");" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/{record_uuid}/history/": { + "get": { + "operationId": "list_case_routing_rule_history", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "list_history", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "name": "ordering", + "required": false, + "in": "query", + "description": "Which field to use when ordering the results.", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "offset", + "required": false, + "in": "query", + "description": "The initial index from which to return the results.", + "schema": { + "type": "integer" + } + } + ], + "tags": [ + "Case Assignment" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPaginatedPolicyRevisionList" + } + } + }, + "description": "" + }, + "403": { + "description": "Case access is required." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.routing_rules.list_history(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.routingRules.listHistory(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.RoutingRules.ListHistoryAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasesRoutingRulesListHistoryParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().routingRules().listHistory(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->listHistory('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.routing_rules.list_history(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.RoutingRules.ListHistoryAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/session/{sessionId}/resubmission-preview/": { + "post": { + "summary": "Preview a resubmission email", + "description": "Render the email that would accompany a KYC or KYB resubmission without changing the session, resetting checks, extending its expiry, running checks or sending mail. Requires the `write:sessions` privilege (a scoped API key needs write access to decisions) and a session in `Approved`, `Declined`, `In Review`, `Kyc Expired` or `Abandoned` status. The server validates the selected workflow steps and renders its fixed translated template with the applicable verified white-label sender and branding. The request cannot set a subject, message body or action URL.\n\n`email_address` is optional. Without it, the email goes to the person's own address: the one they verified in an email verification step of the flow, else the one given in `contact_details` when the session was created. An address you pass wins (`recipient_source: request`). When no address is known and you pass none, `recipient` and `preview_token` are empty strings and no email can be sent.\n\nThe response includes the recipient and where it comes from, the sender, subject, HTML, language, resolved steps, whether confirming sends the email (`will_be_sent`, `not_sent_reason`) and a signed `preview_token`. `requires_user_action` is `false` when every selected step runs without the person: the resubmission then finishes on its own and no email goes out (`not_sent_reason: no_user_action`).\n\nThis preview does not reserve session state or authorize delivery. After reviewing it, call [Update Session Status](/sessions-api/update-status) with `new_status: Resubmitted`, the same `nodes_to_resubmit` and `email_language` you previewed, `send_email: true`, `email_address` set to `recipient` and `email_preview_token` set to `preview_token`. The receipt expires after 15 minutes. When a receipt is supplied, changed inputs, message content, sender or session state return `409` before any mutation or send. Existing integrations may omit the receipt. Regenerate the preview after a conflict. Treat the response as sensitive verification content; responses use `Cache-Control: no-store`. Render HTML in a sandboxed frame, never directly in your application DOM.", + "operationId": "post_v3_session_resubmission_preview", + "x-didit-sdk": { + "resource": "sessions", + "method": "preview_resubmission", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session to update. Accepts both user (KYC) and business (KYB) session IDs — the service resolves the ID across both session types.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "nodes_to_resubmit": { + "type": "array", + "description": "Steps to preview, using the same validation, selection and workflow ordering as a resubmission. Omit to auto-select recorded attempts needing resubmission.", + "items": { + "type": "object", + "required": [ + "node_id", + "feature" + ], + "properties": { + "node_id": { + "type": "string", + "description": "Node identifier as it appears in the session's workflow definition (for example `feature_ocr`, `feature_liveness`).", + "example": "feature_ocr" + }, + "feature": { + "type": "string", + "description": "Feature type of the node. The aliases `ID_VERIFICATION`, `POA`, `PHONE`, and `EMAIL` are normalized server-side to `OCR`, `PROOF_OF_ADDRESS`, `PHONE_VERIFICATION`, and `EMAIL_VERIFICATION`. `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and `KYB` pass schema validation but are always rejected with the business-rule `400` shown in the examples.", + "enum": [ + "OCR", + "OCR_BACK", + "NFC", + "AML", + "FACE", + "LIVENESS", + "FACE_MATCH", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "PROOF_OF_ADDRESS", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "FACE_SEARCH", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_DOCUMENTS", + "ID_VERIFICATION", + "POA", + "PHONE", + "EMAIL", + "KYB_REGISTRY", + "KYB_KEY_PEOPLE", + "KYB", + "GEOLOCATION" + ], + "example": "OCR" + } + } + } + }, + "email_address": { + "type": "string", + "format": "email", + "description": "Optional. Send the email to this address instead of the person's own. Without it, the recipient is the address the person verified in the flow, else the one given in `contact_details`. No email is sent by this call.", + "example": "user@example.com" + }, + "email_language": { + "type": "string", + "description": "Language for the notification email. Accepts any string at schema level; unsupported codes silently fall back to English (`en`).", + "default": "en", + "example": "en" + } + } + }, + "examples": { + "Person's Own Address": { + "summary": "Preview a proof-of-address resubmission for the person's own address", + "value": { + "nodes_to_resubmit": [ + { + "node_id": "feature_poa_1", + "feature": "POA" + } + ], + "email_language": "en" + } + }, + "Named Recipient": { + "summary": "Preview the email for an address you name", + "value": { + "nodes_to_resubmit": [ + { + "node_id": "feature_ocr", + "feature": "OCR" + } + ], + "email_address": "person@example.com", + "email_language": "en" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Preview only. The session is unchanged and no email is sent.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "recipient", + "recipient_source", + "sender", + "sender_name", + "sender_email", + "sender_kind", + "subject", + "html", + "text", + "language", + "will_be_sent", + "not_sent_reason", + "preview_token", + "nodes_to_resubmit", + "requires_user_action" + ], + "properties": { + "recipient": { + "type": "string", + "description": "The address the email goes to. An empty string when no address is known for the person: then no email can be sent and `preview_token` is empty too. Pass it as `email_address` when you confirm.", + "example": "person@example.com" + }, + "recipient_source": { + "type": "string", + "enum": [ + "email_verification", + "contact_details", + "request", + "" + ], + "description": "Where `recipient` comes from. `request`: the `email_address` you passed. `email_verification`: the address the person verified in an email verification step of the flow. `contact_details`: the address given in `contact_details` when the session was created. An empty string when there is no address.", + "example": "email_verification" + }, + "sender": { + "type": "string", + "description": "The `From` header the email goes out with.", + "example": "Example App " + }, + "sender_name": { + "type": "string", + "description": "The display name part of `sender`.", + "example": "Example App" + }, + "sender_email": { + "type": "string", + "format": "email", + "description": "The address part of `sender`.", + "example": "no-reply@mail.example.com" + }, + "sender_kind": { + "type": "string", + "enum": [ + "customer", + "didit" + ], + "description": "`customer`: the email goes out from your application's verified sender domain (white label). `didit`: it goes out from Didit's own sender.", + "example": "customer" + }, + "subject": { + "type": "string", + "description": "The subject line exactly as it is sent.", + "example": "Action Required: Complete your verification in Example App" + }, + "html": { + "type": "string", + "description": "The email's HTML exactly as it is sent, with its logo embedded as a data URL. Render it in a sandboxed frame, never directly in your application DOM.", + "example": "..." + }, + "text": { + "type": "string", + "description": "The email's words as plain text, as the person's Messages thread keeps them, for a compact preview (the HTML stays the email the person receives).", + "example": "Almost there\n\nSome steps need to be completed again.\nHere’s what to fix:\n\nhttps://verify.didit.me/session/abc123" + }, + "language": { + "type": "string", + "description": "The language the email is written in. English (`en`) when the `email_language` you asked for is not offered.", + "example": "en" + }, + "will_be_sent": { + "type": "boolean", + "description": "Whether confirming the change with `send_email: true` sends this email. `false` when `not_sent_reason` is set.", + "example": true + }, + "not_sent_reason": { + "type": "string", + "nullable": true, + "enum": [ + "no_user_action", + "no_recipient", + "sandbox", + null + ], + "description": "Why confirming would send no email, or `null` when it will be sent. `no_user_action`: every selected step runs without the person, so the resubmission finishes on its own and no email goes out. `no_recipient`: no address is known for the person and you passed none. `sandbox`: a sandbox session whose recipient has not opted into sandbox email delivery, so the send is only simulated.", + "example": null + }, + "nodes_to_resubmit": { + "type": "array", + "items": { + "type": "object", + "required": [ + "node_id", + "feature" + ], + "properties": { + "node_id": { + "type": "string", + "description": "Node identifier as it appears in the session's workflow definition (for example `feature_ocr`, `feature_liveness`).", + "example": "feature_ocr" + }, + "feature": { + "type": "string", + "description": "Feature type of the node. The aliases `ID_VERIFICATION`, `POA`, `PHONE`, and `EMAIL` are normalized server-side to `OCR`, `PROOF_OF_ADDRESS`, `PHONE_VERIFICATION`, and `EMAIL_VERIFICATION`. `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and `KYB` pass schema validation but are always rejected with the business-rule `400` shown in the examples.", + "enum": [ + "OCR", + "OCR_BACK", + "NFC", + "AML", + "FACE", + "LIVENESS", + "FACE_MATCH", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "PROOF_OF_ADDRESS", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "FACE_SEARCH", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_DOCUMENTS", + "ID_VERIFICATION", + "POA", + "PHONE", + "EMAIL", + "KYB_REGISTRY", + "KYB_KEY_PEOPLE", + "KYB", + "GEOLOCATION" + ], + "example": "OCR" + } + } + } + }, + "requires_user_action": { + "type": "boolean", + "description": "Whether at least one selected check requires user interaction. This does not predict the final workflow outcome." + }, + "preview_token": { + "type": "string", + "description": "Signed receipt for this exact email, valid for 15 minutes. Pass it as `email_preview_token` to [Update Session Status](/sessions-api/update-status). An empty string when there is no recipient.", + "example": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + } + }, + "examples": { + "Will Be Sent": { + "summary": "Default recipient: the address verified in the flow", + "value": { + "recipient": "person@example.com", + "recipient_source": "email_verification", + "sender": "Example App ", + "sender_name": "Example App", + "sender_email": "no-reply@mail.example.com", + "sender_kind": "customer", + "subject": "Action Required: Complete your verification in Example App", + "html": "...", + "language": "en", + "will_be_sent": true, + "not_sent_reason": null, + "nodes_to_resubmit": [ + { + "node_id": "feature_poa_1", + "feature": "PROOF_OF_ADDRESS" + } + ], + "requires_user_action": true, + "preview_token": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + }, + "No User Action": { + "summary": "Only backend steps are selected, so no email goes out", + "value": { + "recipient": "person@example.com", + "recipient_source": "contact_details", + "sender": "Example App ", + "sender_name": "Example App", + "sender_email": "no-reply@mail.example.com", + "sender_kind": "customer", + "subject": "Action Required: Complete your verification in Example App", + "html": "...", + "language": "en", + "will_be_sent": false, + "not_sent_reason": "no_user_action", + "nodes_to_resubmit": [ + { + "node_id": "feature_aml", + "feature": "AML" + } + ], + "requires_user_action": false, + "preview_token": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + } + } + } + }, + "headers": { + "Cache-Control": { + "description": "Always `no-store`: the preview carries verification content and must not be cached.", + "schema": { + "type": "string", + "example": "no-store" + } + } + } + }, + "400": { + "description": "The session is not in a status this endpoint accepts, it is already `Resubmitted`, the selected steps cannot be resubmitted, or a field is invalid.", + "content": { + "application/json": { + "examples": { + "Wrong Current Status": { + "summary": "Session is not in a status this endpoint accepts", + "value": { + "detail": "Only sessions with status Approved, Declined, In Review, Kyc Expired, Abandoned, Resubmitted can be updated. Current status: In Progress" + } + }, + "Already Resubmitted": { + "summary": "The session is already Resubmitted", + "value": [ + "The new status is the same as the current status." + ] + }, + "Nothing To Resubmit": { + "summary": "Auto-select found no step that needs resubmission", + "value": { + "nodes_to_resubmit": "No features found that need resubmission. All features are already approved." + } + }, + "Invalid Email": { + "summary": "email_address is not an email address", + "value": { + "email_address": [ + "Enter a valid email address." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials, a caller without the `write:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no write access to decisions." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + } + } + } + } + }, + "429": { + "description": "The steps to resubmit are all backend-only (AML, DATABASE_VALIDATION, IP_ANALYSIS), and the session was already resubmitted for backend-only steps within the last 30 seconds, so Update Session Status would refuse the resubmission. Retry after the interval in `Retry-After`.", + "content": { + "application/json": { + "examples": { + "Backend-only resubmit throttled": { + "value": { + "detail": "This session was already resubmitted for backend-only features within the last 30 seconds; repeating it cannot change the outcome. Wait before resubmitting again." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.preview_resubmission(\n \"11111111-2222-3333-4444-555555555555\",\n nodes_to_resubmit=[{\n \"node_id\": \"feature_poa_1\",\n \"feature\": \"POA\",\n }],\n email_language=\"en\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.previewResubmission(\"11111111-2222-3333-4444-555555555555\", {\n nodes_to_resubmit: [{\n node_id: \"feature_poa_1\",\n feature: \"POA\",\n }],\n email_language: \"en\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.PreviewResubmission(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsPreviewResubmissionParams{\n\tNodesToResubmit: didit.Some([]didit.SessionsPreviewResubmissionParamsNodesToResubmit{{\n\t\tNodeID: \"feature_poa_1\",\n\t\tFeature: \"POA\",\n\t}}),\n\tEmailLanguage: didit.String(\"en\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.SessionsPreviewResubmissionParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().previewResubmission(\"11111111-2222-3333-4444-555555555555\", SessionsPreviewResubmissionParams.builder()\n .nodesToResubmit(List.of(SessionsPreviewResubmissionParams.NodesToResubmit.builder()\n .nodeId(\"feature_poa_1\")\n .feature(SessionsPreviewResubmissionParams.NodesToResubmit.Feature.of(\"POA\"))\n .build()))\n .emailLanguage(\"en\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->previewResubmission('11111111-2222-3333-4444-555555555555', [\n 'nodes_to_resubmit' => [[\n 'node_id' => 'feature_poa_1',\n 'feature' => 'POA',\n ]],\n 'email_language' => 'en',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.preview_resubmission(\n \"11111111-2222-3333-4444-555555555555\",\n nodes_to_resubmit: [{\n node_id: \"feature_poa_1\",\n feature: \"POA\",\n }],\n email_language: \"en\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.PreviewResubmissionAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsPreviewResubmissionParams\n{\n NodesToResubmit = new SessionsPreviewResubmissionParamsNodesToResubmit[]\n {\n new()\n {\n NodeId = \"feature_poa_1\",\n Feature = \"POA\",\n },\n },\n EmailLanguage = \"en\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/{record_uuid}/": { + "get": { + "summary": "Retrieve routing rule", + "description": "Requires `read:cases` for the requested application.", + "operationId": "retrieve_case_routing_rules", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "record_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRule" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "404": { + "description": "Record not found in this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "patch": { + "summary": "Update routing rule", + "description": "Requires `write:cases` for the requested application.", + "operationId": "update_case_routing_rules", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "record_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRule" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "404": { + "description": "Record not found in this application." + }, + "409": { + "description": "The routing rule changed since expected_version. Review the latest rule before retrying." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRuleUpdate" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.update(\n \"11111111-2222-3333-4444-555555555555\",\n name=\"your-name\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.update(\"11111111-2222-3333-4444-555555555555\", {\n name: \"your-name\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CaseRoutingRuleUpdate{\n\tName: didit.String(\"your-name\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CaseRoutingRuleUpdate;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().update(\"11111111-2222-3333-4444-555555555555\", CaseRoutingRuleUpdate.builder()\n .name(\"your-name\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->update('11111111-2222-3333-4444-555555555555', [\n 'name' => 'your-name',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.update(\n \"11111111-2222-3333-4444-555555555555\",\n name: \"your-name\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new CaseRoutingRuleUpdate\n{\n Name = \"your-name\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/case-requests/{request_uuid}/": { + "get": { + "operationId": "case_requests_retrieve", + "x-didit-sdk": { + "resource": "case_requests", + "method": "retrieve", + "auth": "device_token", + "server": false + }, + "parameters": [ + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPublic" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + } + }, + "security": [ + { + "CaseRequestToken": [] + } + ], + "description": "Access one customer request using its request-specific token. No private case findings, original identity data or recipient address are returned. Actionable requests expire after seven days. Cancellation or case resolution invalidates the link. Responses are immutable and remain evidence for reviewer assessment; they never approve or update a source verification." + }, + "post": { + "operationId": "case_requests_create", + "x-didit-sdk": { + "resource": "case_requests", + "method": "respond", + "auth": "device_token", + "server": false + }, + "parameters": [ + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseCustomerResponseRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseCustomerResponseRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseCustomerResponseResult" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "400": { + "description": "Invalid inputs, unsupported purpose, unavailable verified contact or an existing immutable response." + }, + "409": { + "description": "The preview changed or expired. Preview again before confirming." + } + }, + "security": [ + { + "CaseRequestToken": [] + } + ], + "description": "Access one customer request using its request-specific token. No private case findings, original identity data or recipient address are returned. Actionable requests expire after seven days. Cancellation or case resolution invalidates the link. Responses are immutable and remain evidence for reviewer assessment; they never approve or update a source verification." + } + }, + "/v3/fraud/checks/plan/": { + "post": { + "operationId": "plan_create", + "x-didit-sdk": { + "resource": "fraud_checks", + "method": "plan", + "auth": "api_key", + "server": true + }, + "description": "Validate selected checks and estimate their price without executing, creating a session or billing.", + "summary": "Plan a Fraud Check", + "tags": [ + "Fraud Check" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + }, + "example": { + "client_reference": "review-attempt-001", + "subject": { + "identifiers": [ + { + "type": "email", + "value": "applicant@example.com" + } + ], + "attributes": { + "first_name": "Alex", + "last_name": "Example" + } + }, + "checks": { + "include": [ + "networks_same_org", + "networks_cross_org", + "email_risk" + ] + } + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/FraudCheckRequestRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudCheckPlan" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FraudBadRequestErrorMessage" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.fraud_checks.plan(\n client_reference=\"review-attempt-001\",\n subject={\n \"identifiers\": [{\n \"type\": \"email\",\n \"value\": \"applicant@example.com\",\n }],\n \"attributes\": {\n \"first_name\": \"Alex\",\n \"last_name\": \"Example\",\n },\n },\n checks={\n \"include\": [\"networks_same_org\", \"networks_cross_org\", \"email_risk\"],\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.fraudChecks.plan({\n client_reference: \"review-attempt-001\",\n subject: {\n identifiers: [{\n type: \"email\",\n value: \"applicant@example.com\",\n }],\n attributes: {\n first_name: \"Alex\",\n last_name: \"Example\",\n },\n },\n checks: {\n include: [\"networks_same_org\", \"networks_cross_org\", \"email_risk\"],\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.FraudChecks.Plan(ctx, didit.FraudCheckRequestRequest{\n\tClientReference: didit.String(\"review-attempt-001\"),\n\tSubject: didit.FraudCheckSubjectRequest{\n\t\tIdentifiers: []didit.FraudCheckIdentifierRequest{{\n\t\t\tType: didit.FraudTypeEnum(\"email\"),\n\t\t\tValue: \"applicant@example.com\",\n\t\t}},\n\t\tAttributes: didit.Some(didit.FraudCheckAttributesRequest{\n\t\t\tFirstName: didit.String(\"Alex\"),\n\t\t\tLastName: didit.String(\"Example\"),\n\t\t}),\n\t},\n\tChecks: didit.Some(didit.FraudCheckModuleSelectionRequest{\n\t\tInclude: didit.Some([]didit.FraudExcludeEnum{didit.FraudExcludeEnum(\"networks_same_org\"), didit.FraudExcludeEnum(\"networks_cross_org\"), didit.FraudExcludeEnum(\"email_risk\")}),\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.FraudCheckAttributesRequest;\nimport me.didit.types.FraudCheckIdentifierRequest;\nimport me.didit.types.FraudCheckModuleSelectionRequest;\nimport me.didit.types.FraudCheckRequestRequest;\nimport me.didit.types.FraudCheckSubjectRequest;\nimport me.didit.types.FraudExcludeEnum;\nimport me.didit.types.FraudTypeEnum;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.fraudChecks().plan(FraudCheckRequestRequest.builder()\n .clientReference(\"review-attempt-001\")\n .subject(FraudCheckSubjectRequest.builder()\n .identifiers(List.of(FraudCheckIdentifierRequest.builder()\n .type(FraudTypeEnum.of(\"email\"))\n .value(\"applicant@example.com\")\n .build()))\n .attributes(FraudCheckAttributesRequest.builder()\n .firstName(\"Alex\")\n .lastName(\"Example\")\n .build())\n .build())\n .checks(FraudCheckModuleSelectionRequest.builder()\n .include(List.of(FraudExcludeEnum.of(\"networks_same_org\"), FraudExcludeEnum.of(\"networks_cross_org\"), FraudExcludeEnum.of(\"email_risk\")))\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "fraudChecks->plan([\n 'client_reference' => 'review-attempt-001',\n 'subject' => [\n 'identifiers' => [[\n 'type' => 'email',\n 'value' => 'applicant@example.com',\n ]],\n 'attributes' => [\n 'first_name' => 'Alex',\n 'last_name' => 'Example',\n ],\n ],\n 'checks' => [\n 'include' => ['networks_same_org', 'networks_cross_org', 'email_risk'],\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.fraud_checks.plan(\n client_reference: \"review-attempt-001\",\n subject: {\n identifiers: [{\n type: \"email\",\n value: \"applicant@example.com\",\n }],\n attributes: {\n first_name: \"Alex\",\n last_name: \"Example\",\n },\n },\n checks: {\n include: [\"networks_same_org\", \"networks_cross_org\", \"email_risk\"],\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.FraudChecks.PlanAsync(new FraudCheckRequestRequest\n{\n ClientReference = \"review-attempt-001\",\n Subject = new()\n {\n Identifiers = new FraudCheckIdentifierRequest[]\n {\n new()\n {\n Type = \"email\",\n Value = \"applicant@example.com\",\n },\n },\n Attributes = new FraudCheckAttributesRequest\n {\n FirstName = \"Alex\",\n LastName = \"Example\",\n },\n },\n Checks = new FraudCheckModuleSelectionRequest\n {\n Include = new FraudExcludeEnum[] { \"networks_same_org\", \"networks_cross_org\", \"email_risk\" },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/": { + "get": { + "summary": "List routing rules", + "description": "Requires `list:cases` for the requested application.", + "operationId": "list_case_routing_rules", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000000, + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseRoutingRule" + } + }, + "count": { + "type": "integer", + "description": "Matching count, capped at 100. Follow next to continue beyond the cap." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.routing_rules.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.routingRules.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.RoutingRules.ListAutoPaging(ctx, didit.CasesRoutingRulesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().routingRules().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.routing_rules.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.RoutingRules.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "summary": "Create routing rule", + "description": "Requires `write:cases` for the requested application. Set publish=false to create an unpublished draft; Publish creates its first live version. Discarding a never-published draft deletes it.", + "operationId": "create_case_routing_rules", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "responses": { + "201": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRule" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingRuleCreate" + }, + "example": { + "name": "AML screening alerts", + "conditions": [ + { + "field": "source", + "operator": "equals", + "value": "AML_SCREENING" + } + ], + "blueprint": "b1de0000-0000-4000-8000-000000000002" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.create(\n name=\"AML screening alerts\",\n conditions=[{\n \"field\": \"source\",\n \"operator\": \"equals\",\n \"value\": \"AML_SCREENING\",\n }],\n blueprint=\"b1de0000-0000-4000-8000-000000000002\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.create({\n name: \"AML screening alerts\",\n conditions: [{\n field: \"source\",\n operator: \"equals\",\n value: \"AML_SCREENING\",\n }],\n blueprint: \"b1de0000-0000-4000-8000-000000000002\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.Create(ctx, didit.CaseRoutingRuleCreate{\n\tName: \"AML screening alerts\",\n\tConditions: []didit.CaseRoutingConditionParam{{\n\t\tField: \"source\",\n\t\tOperator: \"equals\",\n\t\tValue: \"AML_SCREENING\",\n\t}},\n\tBlueprint: \"b1de0000-0000-4000-8000-000000000002\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.CaseRoutingCondition;\nimport me.didit.types.CaseRoutingRuleCreate;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().create(CaseRoutingRuleCreate.builder()\n .name(\"AML screening alerts\")\n .conditions(List.of(CaseRoutingCondition.builder()\n .field(CaseRoutingCondition.Field.of(\"source\"))\n .operator(CaseRoutingCondition.Operator.of(\"equals\"))\n .value(CaseRoutingCondition.Value.ofString(\"AML_SCREENING\"))\n .build()))\n .blueprint(\"b1de0000-0000-4000-8000-000000000002\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->create([\n 'name' => 'AML screening alerts',\n 'conditions' => [[\n 'field' => 'source',\n 'operator' => 'equals',\n 'value' => 'AML_SCREENING',\n ]],\n 'blueprint' => 'b1de0000-0000-4000-8000-000000000002',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.create(\n name: \"AML screening alerts\",\n conditions: [{\n field: \"source\",\n operator: \"equals\",\n value: \"AML_SCREENING\",\n }],\n blueprint: \"b1de0000-0000-4000-8000-000000000002\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.CreateAsync(new CaseRoutingRuleCreate\n{\n Name = \"AML screening alerts\",\n Conditions = new CaseRoutingCondition[]\n {\n new()\n {\n Field = \"source\",\n Operator = \"equals\",\n Value = \"AML_SCREENING\",\n },\n },\n Blueprint = \"b1de0000-0000-4000-8000-000000000002\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/{record_uuid}/publish/": { + "post": { + "operationId": "publish_case_routing_rule", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "publish", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Assignment" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftPreconditionRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftPreconditionRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftPreconditionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationCaseRoutingRule" + } + } + }, + "description": "" + }, + "403": { + "description": "Case access is required." + }, + "409": { + "description": "The published version or draft revision has changed. Reload before retrying." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.publish(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version=0,\n expected_revision=\"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.publish(\"11111111-2222-3333-4444-555555555555\", {\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.Publish(ctx, \"11111111-2222-3333-4444-555555555555\", didit.RoutingPublicationPolicyDraftPreconditionRequest{\n\tExpectedVersion: 0,\n\tExpectedRevision: didit.String(\"11111111-2222-3333-4444-555555555555\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.RoutingPublicationPolicyDraftPreconditionRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().publish(\"11111111-2222-3333-4444-555555555555\", RoutingPublicationPolicyDraftPreconditionRequest.builder()\n .expectedVersion(0L)\n .expectedRevision(\"11111111-2222-3333-4444-555555555555\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->publish('11111111-2222-3333-4444-555555555555', [\n 'expected_version' => 0,\n 'expected_revision' => '11111111-2222-3333-4444-555555555555',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.publish(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.PublishAsync(\"11111111-2222-3333-4444-555555555555\", new RoutingPublicationPolicyDraftPreconditionRequest\n{\n ExpectedVersion = 0,\n ExpectedRevision = \"11111111-2222-3333-4444-555555555555\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/{blueprint_uuid}/publish/": { + "post": { + "operationId": "publish_case_blueprint", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "publish", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftPreconditionRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftPreconditionRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftPreconditionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationCaseBlueprint" + } + } + }, + "description": "" + }, + "409": { + "description": "Published version or saved draft revision changed. Reload before retrying." + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.publish(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version=0,\n expected_revision=\"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.publish(\"11111111-2222-3333-4444-555555555555\", {\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.Publish(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasePublicationPolicyDraftPreconditionRequest{\n\tExpectedVersion: 0,\n\tExpectedRevision: didit.String(\"11111111-2222-3333-4444-555555555555\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasePublicationPolicyDraftPreconditionRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().publish(\"11111111-2222-3333-4444-555555555555\", CasePublicationPolicyDraftPreconditionRequest.builder()\n .expectedVersion(0L)\n .expectedRevision(\"11111111-2222-3333-4444-555555555555\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->publish('11111111-2222-3333-4444-555555555555', [\n 'expected_version' => 0,\n 'expected_revision' => '11111111-2222-3333-4444-555555555555',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.publish(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.PublishAsync(\"11111111-2222-3333-4444-555555555555\", new CasePublicationPolicyDraftPreconditionRequest\n{\n ExpectedVersion = 0,\n ExpectedRevision = \"11111111-2222-3333-4444-555555555555\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/preview/": { + "post": { + "operationId": "organization_application_cases_requests_preview_create", + "x-didit-sdk": { + "resource": "cases.requests", + "method": "preview", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPreviewRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPreviewRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPreviewRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestPreviewResponse" + } + } + }, + "description": "" + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "400": { + "description": "Invalid inputs, unsupported purpose, unavailable verified contact or an existing immutable response." + }, + "409": { + "description": "The preview changed or expired. Preview again before confirming." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Render the production email without sending or changing the case. Returns the exact recipient, sender, subject, body and a signed preview_token. Send the same contact_id, purpose, language, request_id, expires_at and preview_token to confirm within 15 minutes.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.requests.preview(\n \"11111111-2222-3333-4444-555555555555\",\n contact_id=\"11111111-2222-3333-4444-555555555555\",\n purpose=\"identity_document\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.requests.preview(\"11111111-2222-3333-4444-555555555555\", {\n contact_id: \"11111111-2222-3333-4444-555555555555\",\n purpose: \"identity_document\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Requests.Preview(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CustomerMessagingCaseRequestPreviewRequest{\n\tContactID: \"11111111-2222-3333-4444-555555555555\",\n\tPurpose: didit.CustomerMessagingPurposeEnum(\"identity_document\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CustomerMessagingCaseRequestPreviewRequest;\nimport me.didit.types.CustomerMessagingPurposeEnum;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().requests().preview(\"11111111-2222-3333-4444-555555555555\", CustomerMessagingCaseRequestPreviewRequest.builder()\n .contactId(\"11111111-2222-3333-4444-555555555555\")\n .purpose(CustomerMessagingPurposeEnum.of(\"identity_document\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->preview('11111111-2222-3333-4444-555555555555', [\n 'contact_id' => '11111111-2222-3333-4444-555555555555',\n 'purpose' => 'identity_document',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.requests.preview(\n \"11111111-2222-3333-4444-555555555555\",\n contact_id: \"11111111-2222-3333-4444-555555555555\",\n purpose: \"identity_document\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Requests.PreviewAsync(\"11111111-2222-3333-4444-555555555555\", new CustomerMessagingCaseRequestPreviewRequest\n{\n ContactId = \"11111111-2222-3333-4444-555555555555\",\n Purpose = \"identity_document\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/action-receipts/": { + "get": { + "operationId": "action_receipts_retrieve", + "x-didit-sdk": { + "resource": "cases", + "method": "list_action_receipts", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "query", + "name": "offset", + "schema": { + "type": "integer", + "maximum": 1000000, + "minimum": 0, + "default": 0 + } + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseActionReceiptPage" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "List subject-action receipts, newest first, with at most 20 results per page. Use next_offset for the next page. Input note text is not exposed in the history.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.list_action_receipts(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.listActionReceipts(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.ListActionReceipts(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasesListActionReceiptsParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().listActionReceipts(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->listActionReceipts('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.list_action_receipts(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ListActionReceiptsAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/notes/{note_uuid}/": { + "delete": { + "operationId": "delete_network_note", + "x-didit-sdk": { + "resource": "networks.notes", + "method": "delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "summary": "Delete your network note", + "description": "Requires write:networks and the authenticated author of the note. Deletion is irreversible. Connected account and verification data are unaffected.", + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Network UUID." + }, + { + "name": "note_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Console user access token in the form Bearer ." + } + ], + "responses": { + "204": { + "description": "Note deleted." + }, + "401": { + "description": "Authentication is required." + }, + "403": { + "description": "Missing permission or Networks access." + }, + "404": { + "description": "Network or note is not visible to this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\nclient = Didit()\n\nclient.networks.notes.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n extra_headers={\"Authorization\": \"Bearer YOUR_CONSOLE_USER_TOKEN\"},\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nconst didit = new Didit({\n fetch: (url, init) => {\n const headers = new Headers(init?.headers);\n headers.set(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\");\n return fetch(url, { ...init, headers });\n },\n});\n\nawait didit.networks.notes.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nclient := didit.NewClient()\n\nif err := client.Networks.Notes.Delete(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\", didit.NetworksNotesDeleteParams{\n\tAuthorization: \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n}); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.core.RequestOptions;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nDidit client = new Didit();\n\nclient.networks().notes().delete(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\", RequestOptions.builder()\n .header(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\")\n .build());" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->notes->delete('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555', [\n 'authorization' => 'Bearer YOUR_CONSOLE_USER_TOKEN',\n]);" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\ndidit = Didit::Client.new\n\ndidit.networks.notes.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n request_options: { extra_headers: { \"Authorization\" => \"Bearer YOUR_CONSOLE_USER_TOKEN\" } }\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nusing var client = new DiditClient();\n\nawait client.Networks.Notes.DeleteAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\", requestOptions: new RequestOptions\n{\n Headers = new Dictionary\n {\n [\"Authorization\"] = \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n },\n});" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/action-receipts/{receipt_uuid}/retry/": { + "post": { + "operationId": "action_receipts_retry_create", + "x-didit-sdk": { + "resource": "cases", + "method": "retry_action_receipt", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "receipt_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseActionCaseActionReceipt" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Retry only this failed subject action using its saved inputs. Current write permission is checked. Applied actions return their receipt without executing again. Case closure is independent.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.retry_action_receipt(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.retryActionReceipt(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RetryActionReceipt(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().retryActionReceipt(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->retryActionReceipt('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.retry_action_receipt(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RetryActionReceiptAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/{blueprint_uuid}/draft/": { + "get": { + "operationId": "get_case_blueprint_draft", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "retrieve_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftResponse" + } + } + }, + "description": "" + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.retrieve_draft(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.retrieveDraft(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.RetrieveDraft(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().retrieveDraft(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->retrieveDraft('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.retrieve_draft(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.RetrieveDraftAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "put": { + "operationId": "save_case_blueprint_draft", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "save_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftWriteRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftWriteRequest" + }, + "example": { + "expected_version": 1, + "expected_revision": "4e5f0000-0000-4000-8000-000000000003", + "changes": { + "max_active_cases": 20 + } + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftWriteRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraft" + } + } + }, + "description": "" + }, + "409": { + "description": "Published version or saved draft revision changed. Reload before retrying." + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.blueprints.save_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version=1,\n expected_revision=\"4e5f0000-0000-4000-8000-000000000003\",\n changes={\"max_active_cases\": 20},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.blueprints.saveDraft(\"11111111-2222-3333-4444-555555555555\", {\n expected_version: 1,\n expected_revision: \"4e5f0000-0000-4000-8000-000000000003\",\n changes: { max_active_cases: 20 },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Blueprints.SaveDraft(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasePublicationPolicyDraftWriteRequest{\n\tExpectedVersion: 1,\n\tExpectedRevision: didit.String(\"4e5f0000-0000-4000-8000-000000000003\"),\n\tChanges: map[string]any{\"max_active_cases\": 20},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.CasePublicationPolicyDraftWriteRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().blueprints().saveDraft(\"11111111-2222-3333-4444-555555555555\", CasePublicationPolicyDraftWriteRequest.builder()\n .expectedVersion(1L)\n .expectedRevision(\"4e5f0000-0000-4000-8000-000000000003\")\n .changes(Map.of(\"max_active_cases\", 20))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->saveDraft('11111111-2222-3333-4444-555555555555', [\n 'expected_version' => 1,\n 'expected_revision' => '4e5f0000-0000-4000-8000-000000000003',\n 'changes' => ['max_active_cases' => 20],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.blueprints.save_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version: 1,\n expected_revision: \"4e5f0000-0000-4000-8000-000000000003\",\n changes: { max_active_cases: 20 }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Blueprints.SaveDraftAsync(\"11111111-2222-3333-4444-555555555555\", new CasePublicationPolicyDraftWriteRequest\n{\n ExpectedVersion = 1,\n ExpectedRevision = \"4e5f0000-0000-4000-8000-000000000003\",\n Changes = new Dictionary { [\"max_active_cases\"] = 20 },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "operationId": "discard_case_blueprint_draft", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "discard_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "204": { + "description": "No response body" + }, + "409": { + "description": "Published version or saved draft revision changed. Reload before retrying." + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPolicyDraftPreconditionRequest" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.cases.blueprints.discard_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version=0,\n expected_revision=\"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.cases.blueprints.discardDraft(\"11111111-2222-3333-4444-555555555555\", {\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\",\n});" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.Cases.Blueprints.DiscardDraft(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasePublicationPolicyDraftPreconditionRequest{\n\tExpectedVersion: 0,\n\tExpectedRevision: didit.String(\"11111111-2222-3333-4444-555555555555\"),\n}); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CasePublicationPolicyDraftPreconditionRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().blueprints().discardDraft(\"11111111-2222-3333-4444-555555555555\", CasePublicationPolicyDraftPreconditionRequest.builder()\n .expectedVersion(0L)\n .expectedRevision(\"11111111-2222-3333-4444-555555555555\")\n .build());" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->discardDraft('11111111-2222-3333-4444-555555555555', [\n 'expected_version' => 0,\n 'expected_revision' => '11111111-2222-3333-4444-555555555555',\n]);" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.blueprints.discard_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\"\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Cases.Blueprints.DiscardDraftAsync(\"11111111-2222-3333-4444-555555555555\", new CasePublicationPolicyDraftPreconditionRequest\n{\n ExpectedVersion = 0,\n ExpectedRevision = \"11111111-2222-3333-4444-555555555555\",\n});" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/teams/{record_uuid}/": { + "get": { + "summary": "Retrieve review team", + "description": "Requires `read:cases` for the requested application.", + "operationId": "retrieve_case_teams", + "x-didit-sdk": { + "resource": "cases.teams", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "record_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeam" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "404": { + "description": "Record not found in this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.teams.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.teams.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Teams.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().teams().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->teams->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.teams.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Teams.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "patch": { + "summary": "Update review team", + "description": "Requires `write:cases` for the requested application.", + "operationId": "update_case_teams", + "x-didit-sdk": { + "resource": "cases.teams", + "method": "update", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "record_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeam" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "404": { + "description": "Record not found in this application." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseReviewTeamUpdate" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.teams.update(\n \"11111111-2222-3333-4444-555555555555\",\n name=\"your-name\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.teams.update(\"11111111-2222-3333-4444-555555555555\", {\n name: \"your-name\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Teams.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CaseReviewTeamUpdate{\n\tName: didit.String(\"your-name\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CaseReviewTeamUpdate;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().teams().update(\"11111111-2222-3333-4444-555555555555\", CaseReviewTeamUpdate.builder()\n .name(\"your-name\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->teams->update('11111111-2222-3333-4444-555555555555', [\n 'name' => 'your-name',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.teams.update(\n \"11111111-2222-3333-4444-555555555555\",\n name: \"your-name\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Teams.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new CaseReviewTeamUpdate\n{\n Name = \"your-name\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/report/": { + "get": { + "operationId": "download_network_report", + "x-didit-sdk": { + "resource": "networks", + "method": "download_report", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "summary": "Download a network evidence report", + "description": "Download a PDF of the current organization network, its observed account connections, signal references and review status. Requires read:networks. Global intelligence and private analyst notes are not included. Each account and signal list is capped at 500 entries and the report states when it is incomplete. The request does not change network status. Errors are returned as JSON.", + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Network UUID." + }, + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Console user access token in the form Bearer ." + } + ], + "responses": { + "200": { + "description": "Network evidence report. Cache-Control: no-store.", + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "description": "Authentication is required." + }, + "403": { + "description": "Missing permission or Networks access." + }, + "404": { + "description": "Network or note is not visible to this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\nclient = Didit()\n\ncontent = client.networks.download_report(\n \"11111111-2222-3333-4444-555555555555\",\n extra_headers={\"Authorization\": \"Bearer YOUR_CONSOLE_USER_TOKEN\"},\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nconst didit = new Didit({\n fetch: (url, init) => {\n const headers = new Headers(init?.headers);\n headers.set(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\");\n return fetch(url, { ...init, headers });\n },\n});\n\nconst content = await didit.networks.downloadReport(\"11111111-2222-3333-4444-555555555555\");\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nclient := didit.NewClient()\n\nresp, err := client.Networks.DownloadReport(ctx, \"11111111-2222-3333-4444-555555555555\", didit.NetworksDownloadReportParams{\n\tAuthorization: \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.core.RequestOptions;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nDidit client = new Didit();\n\nclient.networks().downloadReport(\"11111111-2222-3333-4444-555555555555\", RequestOptions.builder()\n .header(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\")\n .build())\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->downloadReport('11111111-2222-3333-4444-555555555555', [\n 'authorization' => 'Bearer YOUR_CONSOLE_USER_TOKEN',\n]);\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\ndidit = Didit::Client.new\n\ncontent = didit.networks.download_report(\n \"11111111-2222-3333-4444-555555555555\",\n request_options: { extra_headers: { \"Authorization\" => \"Bearer YOUR_CONSOLE_USER_TOKEN\" } }\n)\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nusing var client = new DiditClient();\n\nvar content = await client.Networks.DownloadReportAsync(\"11111111-2222-3333-4444-555555555555\", requestOptions: new RequestOptions\n{\n Headers = new Dictionary\n {\n [\"Authorization\"] = \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n },\n});\nawait content.SaveToAsync(\"download.pdf\");" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/notes/": { + "get": { + "operationId": "list_network_notes", + "x-didit-sdk": { + "resource": "networks.notes", + "method": "list", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "summary": "List private network notes", + "description": "Console users with read:networks can read notes on a network visible to the application. Notes stay within the organization and are not contributed to shared intelligence. Newest notes appear first.", + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Network UUID." + }, + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Console user access token in the form Bearer ." + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 30 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000000, + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "A page of notes. Count is capped at 100; use next to continue.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkNote" + } + } + } + } + } + } + }, + "401": { + "description": "Authentication is required." + }, + "403": { + "description": "Missing permission or Networks access." + }, + "404": { + "description": "Network or note is not visible to this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\nclient = Didit()\n\nfor item in client.networks.notes.list(\n \"11111111-2222-3333-4444-555555555555\",\n extra_headers={\"Authorization\": \"Bearer YOUR_CONSOLE_USER_TOKEN\"},\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nconst didit = new Didit({\n fetch: (url, init) => {\n const headers = new Headers(init?.headers);\n headers.set(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\");\n return fetch(url, { ...init, headers });\n },\n});\n\nfor await (const item of didit.networks.notes.list(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nclient := didit.NewClient()\n\niter := client.Networks.Notes.ListAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.NetworksNotesListParams{\n\tAuthorization: \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.core.RequestOptions;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nDidit client = new Didit();\n\nfor (var item : client.networks().notes().list(\"11111111-2222-3333-4444-555555555555\", RequestOptions.builder()\n .header(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\")\n .build())) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->notes->list('11111111-2222-3333-4444-555555555555', [\n 'authorization' => 'Bearer YOUR_CONSOLE_USER_TOKEN',\n]) as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\ndidit = Didit::Client.new\n\ndidit.networks.notes.list(\n \"11111111-2222-3333-4444-555555555555\",\n request_options: { extra_headers: { \"Authorization\" => \"Bearer YOUR_CONSOLE_USER_TOKEN\" } }\n).auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Networks.Notes.ListAsync(\"11111111-2222-3333-4444-555555555555\", requestOptions: new RequestOptions\n{\n Headers = new Dictionary\n {\n [\"Authorization\"] = \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n },\n}))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + }, + "post": { + "operationId": "create_network_note", + "x-didit-sdk": { + "resource": "networks.notes", + "method": "create", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Networks" + ], + "summary": "Add a private network note", + "description": "Requires a console user with write:networks. The author is assigned from the authenticated user. Supply a new random UUID client_reference for each note; retry the same body and reference after an uncertain response to avoid duplicates. Reusing a reference for another body, author or network returns 409. Notes follow the surviving investigation when networks merge.", + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Network UUID." + }, + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Console user access token in the form Bearer ." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "comment", + "client_reference" + ], + "properties": { + "comment": { + "type": "string", + "minLength": 1, + "maxLength": 10000 + }, + "client_reference": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Saved private note.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkNote" + } + } + } + }, + "200": { + "description": "Saved private note.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkNote" + } + } + } + }, + "400": { + "description": "Invalid or blank note." + }, + "409": { + "description": "The reference was already used for another note." + }, + "401": { + "description": "Authentication is required." + }, + "403": { + "description": "Missing permission or Networks access." + }, + "404": { + "description": "Network or note is not visible to this application." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\nclient = Didit()\n\nresult = client.networks.notes.create(\n \"11111111-2222-3333-4444-555555555555\",\n comment=\"your-comment\",\n client_reference=\"11111111-2222-3333-4444-555555555555\",\n extra_headers={\"Authorization\": \"Bearer YOUR_CONSOLE_USER_TOKEN\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nconst didit = new Didit({\n fetch: (url, init) => {\n const headers = new Headers(init?.headers);\n headers.set(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\");\n return fetch(url, { ...init, headers });\n },\n});\n\nconst result = await didit.networks.notes.create(\"11111111-2222-3333-4444-555555555555\", {\n comment: \"your-comment\",\n client_reference: \"11111111-2222-3333-4444-555555555555\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nclient := didit.NewClient()\n\nresult, err := client.Networks.Notes.Create(ctx, \"11111111-2222-3333-4444-555555555555\", didit.NetworksNotesCreateParams{\n\tComment: \"your-comment\",\n\tClientReference: \"11111111-2222-3333-4444-555555555555\",\n\tAuthorization: \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.core.RequestOptions;\nimport me.didit.types.NetworksNotesCreateParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nDidit client = new Didit();\n\nvar result = client.networks().notes().create(\"11111111-2222-3333-4444-555555555555\", NetworksNotesCreateParams.builder()\n .comment(\"your-comment\")\n .clientReference(\"11111111-2222-3333-4444-555555555555\")\n .build(), RequestOptions.builder()\n .header(\"Authorization\", \"Bearer YOUR_CONSOLE_USER_TOKEN\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->notes->create('11111111-2222-3333-4444-555555555555', [\n 'comment' => 'your-comment',\n 'client_reference' => '11111111-2222-3333-4444-555555555555',\n 'authorization' => 'Bearer YOUR_CONSOLE_USER_TOKEN',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\n# Also needs a console user's access token.\ndidit = Didit::Client.new\n\nresult = didit.networks.notes.create(\n \"11111111-2222-3333-4444-555555555555\",\n comment: \"your-comment\",\n client_reference: \"11111111-2222-3333-4444-555555555555\",\n request_options: { extra_headers: { \"Authorization\" => \"Bearer YOUR_CONSOLE_USER_TOKEN\" } }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\n// Also needs a console user's access token.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.Notes.CreateAsync(\"11111111-2222-3333-4444-555555555555\", new NetworksNotesCreateParams\n{\n Comment = \"your-comment\",\n ClientReference = \"11111111-2222-3333-4444-555555555555\",\n}, requestOptions: new RequestOptions\n{\n Headers = new Dictionary\n {\n [\"Authorization\"] = \"Bearer YOUR_CONSOLE_USER_TOKEN\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/{record_uuid}/draft/": { + "get": { + "operationId": "get_case_routing_rule_draft", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "retrieve_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Assignment" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftResponse" + } + } + }, + "description": "" + }, + "403": { + "description": "Case access is required." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.retrieve_draft(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.retrieveDraft(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.RetrieveDraft(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().retrieveDraft(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->retrieveDraft('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.retrieve_draft(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.RetrieveDraftAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "put": { + "operationId": "save_case_routing_rule_draft", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "save_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Assignment" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftWriteRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftWriteRequest" + }, + "example": { + "expected_version": 1, + "expected_revision": "4e5f0000-0000-4000-8000-000000000003", + "changes": { + "priority": 10 + } + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftWriteRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraft" + } + } + }, + "description": "" + }, + "403": { + "description": "Case access is required." + }, + "409": { + "description": "The published version or draft revision has changed. Reload before retrying." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.save_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version=1,\n expected_revision=\"4e5f0000-0000-4000-8000-000000000003\",\n changes={\"priority\": 10},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.saveDraft(\"11111111-2222-3333-4444-555555555555\", {\n expected_version: 1,\n expected_revision: \"4e5f0000-0000-4000-8000-000000000003\",\n changes: { priority: 10 },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.SaveDraft(ctx, \"11111111-2222-3333-4444-555555555555\", didit.RoutingPublicationPolicyDraftWriteRequest{\n\tExpectedVersion: 1,\n\tExpectedRevision: didit.String(\"4e5f0000-0000-4000-8000-000000000003\"),\n\tChanges: map[string]any{\"priority\": 10},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.RoutingPublicationPolicyDraftWriteRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().saveDraft(\"11111111-2222-3333-4444-555555555555\", RoutingPublicationPolicyDraftWriteRequest.builder()\n .expectedVersion(1L)\n .expectedRevision(\"4e5f0000-0000-4000-8000-000000000003\")\n .changes(Map.of(\"priority\", 10))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->saveDraft('11111111-2222-3333-4444-555555555555', [\n 'expected_version' => 1,\n 'expected_revision' => '4e5f0000-0000-4000-8000-000000000003',\n 'changes' => ['priority' => 10],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.save_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version: 1,\n expected_revision: \"4e5f0000-0000-4000-8000-000000000003\",\n changes: { priority: 10 }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.SaveDraftAsync(\"11111111-2222-3333-4444-555555555555\", new RoutingPublicationPolicyDraftWriteRequest\n{\n ExpectedVersion = 1,\n ExpectedRevision = \"4e5f0000-0000-4000-8000-000000000003\",\n Changes = new Dictionary { [\"priority\"] = 10 },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "operationId": "discard_case_routing_rule_draft", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "discard_draft", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "record_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Assignment" + ], + "responses": { + "204": { + "description": "No response body" + }, + "403": { + "description": "Case access is required." + }, + "409": { + "description": "The published version or draft revision has changed. Reload before retrying." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraftPreconditionRequest" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.cases.routing_rules.discard_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version=0,\n expected_revision=\"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.cases.routingRules.discardDraft(\"11111111-2222-3333-4444-555555555555\", {\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\",\n});" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nif err := client.Cases.RoutingRules.DiscardDraft(ctx, \"11111111-2222-3333-4444-555555555555\", didit.RoutingPublicationPolicyDraftPreconditionRequest{\n\tExpectedVersion: 0,\n\tExpectedRevision: didit.String(\"11111111-2222-3333-4444-555555555555\"),\n}); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.RoutingPublicationPolicyDraftPreconditionRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.cases().routingRules().discardDraft(\"11111111-2222-3333-4444-555555555555\", RoutingPublicationPolicyDraftPreconditionRequest.builder()\n .expectedVersion(0L)\n .expectedRevision(\"11111111-2222-3333-4444-555555555555\")\n .build());" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->discardDraft('11111111-2222-3333-4444-555555555555', [\n 'expected_version' => 0,\n 'expected_revision' => '11111111-2222-3333-4444-555555555555',\n]);" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.routing_rules.discard_draft(\n \"11111111-2222-3333-4444-555555555555\",\n expected_version: 0,\n expected_revision: \"11111111-2222-3333-4444-555555555555\"\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Cases.RoutingRules.DiscardDraftAsync(\"11111111-2222-3333-4444-555555555555\", new RoutingPublicationPolicyDraftPreconditionRequest\n{\n ExpectedVersion = 0,\n ExpectedRevision = \"11111111-2222-3333-4444-555555555555\",\n});" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/duplicates/": { + "post": { + "operationId": "duplicates_create", + "x-didit-sdk": { + "resource": "cases", + "method": "find_duplicates", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + } + ], + "tags": [ + "Case Management" + ], + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicateQueryRequest" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicateQueryRequest" + }, + "example": { + "subject": { + "type": "user", + "uuid": "5b9a0000-0000-4000-8000-00000000f00d" + } + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicateQueryRequest" + } + } + } + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicatePage" + } + } + }, + "description": "" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Review existing cases for a subject or owned source record before manual case creation. Both open and resolved investigations are returned. Each page contains at most 20 cases; use next_offset to request another page.", + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.find_duplicates(\n subject={\n \"type\": \"user\",\n \"uuid\": \"5b9a0000-0000-4000-8000-00000000f00d\",\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.findDuplicates({\n subject: {\n type: \"user\",\n uuid: \"5b9a0000-0000-4000-8000-00000000f00d\",\n },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.FindDuplicates(ctx, didit.CaseDuplicateCaseDuplicateQueryRequest{\n\tSubject: didit.Some(didit.CaseDuplicateCaseSubjectInputRequest{\n\t\tType: didit.CaseDuplicateTypeEnum(\"user\"),\n\t\tUUID: \"5b9a0000-0000-4000-8000-00000000f00d\",\n\t}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CaseDuplicateCaseDuplicateQueryRequest;\nimport me.didit.types.CaseDuplicateCaseSubjectInputRequest;\nimport me.didit.types.CaseDuplicateTypeEnum;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().findDuplicates(CaseDuplicateCaseDuplicateQueryRequest.builder()\n .subject(CaseDuplicateCaseSubjectInputRequest.builder()\n .type(CaseDuplicateTypeEnum.of(\"user\"))\n .uuid(\"5b9a0000-0000-4000-8000-00000000f00d\")\n .build())\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->findDuplicates([\n 'subject' => [\n 'type' => 'user',\n 'uuid' => '5b9a0000-0000-4000-8000-00000000f00d',\n ],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.find_duplicates(\n subject: {\n type: \"user\",\n uuid: \"5b9a0000-0000-4000-8000-00000000f00d\",\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.FindDuplicatesAsync(new CaseDuplicateCaseDuplicateQueryRequest\n{\n Subject = new CaseDuplicateCaseSubjectInputRequest\n {\n Type = \"user\",\n Uuid = \"5b9a0000-0000-4000-8000-00000000f00d\",\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/routing-rules/preview/": { + "post": { + "summary": "Test case routing", + "description": "Requires `read:cases` for the requested application. Evaluates current rules or a supplied draft against current reviewer access, availability, skills and capacity. It does not save the draft or reserve reviewer capacity.", + "operationId": "preview_case_routing", + "x-didit-sdk": { + "resource": "cases.routing_rules", + "method": "preview", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingPreview" + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CaseRoutingPreviewRequest" + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.routing_rules.preview(\n source=\"MANUAL\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.routingRules.preview({\n source: \"MANUAL\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.RoutingRules.Preview(ctx, didit.CaseRoutingPreviewRequest{\n\tSource: \"MANUAL\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.CaseRoutingPreviewRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().routingRules().preview(CaseRoutingPreviewRequest.builder()\n .source(CaseRoutingPreviewRequest.Source.of(\"MANUAL\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->routingRules->preview([\n 'source' => 'MANUAL',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.routing_rules.preview(source: \"MANUAL\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.RoutingRules.PreviewAsync(new CaseRoutingPreviewRequest\n{\n Source = \"MANUAL\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/reviewers/": { + "get": { + "summary": "List eligible case reviewers", + "description": "Requires `list:cases` for the requested application.", + "operationId": "list_case_reviewers", + "x-didit-sdk": { + "resource": "cases", + "method": "list_reviewers", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Case Assignment" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "format": "uuid" + }, + "email": { + "type": "string", + "format": "email" + }, + "name": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "400": { + "description": "Invalid input." + }, + "403": { + "description": "Insufficient permission." + }, + "503": { + "description": "Reviewer access could not be verified. Retry later." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.list_reviewers()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.listReviewers();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.ListReviewers(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().listReviewers();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->listReviewers();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.list_reviewers\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.ListReviewersAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/blueprints/{blueprint_uuid}/history/": { + "get": { + "operationId": "list_case_blueprint_history", + "x-didit-sdk": { + "resource": "cases.blueprints", + "method": "list_history", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "in": "path", + "name": "blueprint_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true + }, + { + "name": "ordering", + "required": false, + "in": "query", + "description": "Which field to use when ordering the results.", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "required": false, + "in": "query", + "description": "Number of results to return per page.", + "schema": { + "type": "integer" + } + }, + { + "name": "offset", + "required": false, + "in": "query", + "description": "The initial index from which to return the results.", + "schema": { + "type": "integer" + } + } + ], + "tags": [ + "Case Management" + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CasePublicationPaginatedPolicyRevisionList" + } + } + }, + "description": "" + }, + "403": { + "description": "Required case permission is missing." + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nfor item in client.cases.blueprints.list_history(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nfor await (const item of didit.cases.blueprints.listHistory(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\niter := client.Cases.Blueprints.ListHistoryAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.CasesBlueprintsListHistoryParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nfor (var item : client.cases().blueprints().listHistory(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->blueprints->listHistory('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.cases.blueprints.list_history(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Cases.Blueprints.ListHistoryAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/session/{sessionId}/signatures/{nodeId}/documents/{documentId}/file/": { + "get": { + "summary": "Download a signed document", + "description": "Download the signed PDF of one document of an E-signature step. Use the `signed_file` path of `signatures[].documents[]` in the decision. The PDF holds the document the signer was shown with their field values stamped and the evidence page (unless `signature_evidence_page` is off). An Advanced (AES) PDF also carries the PAdES signature with its timestamp; a Simple PDF has no digital signature, seal or timestamp. Didit checks the file against `sha256_signed` before returning it.\n\nWorks for User Verification (KYC) and Business Verification (KYB) sessions. Needs `read:sessions`; your API key works. See the [E-signature report](/core-technology/electronic-signature/report-electronic-signature#download-the-signed-documents).", + "operationId": "get_v3_session_signature_document_file", + "x-didit-sdk": { + "resource": "sessions.signatures", + "method": "download_document", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "The User Verification (KYC) or Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "nodeId", + "in": "path", + "required": true, + "description": "The E-signature step: `node_id` of the item in `signatures[]`.", + "schema": { + "type": "string", + "example": "feature_signature" + } + }, + { + "name": "documentId", + "in": "path", + "required": true, + "description": "`id` of the document in `signatures[].documents[]`.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "The signed PDF.", + "headers": { + "Content-Disposition": { + "description": "Attachment with the file name.", + "schema": { + "type": "string", + "example": "attachment; filename=\"agreement-signed.pdf\"" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "403": { + "description": "Missing credentials, or a credential that can't read this session. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "You do not have permission to perform this action." + } + } + } + }, + "404": { + "description": "No signed document matches: the session, step or document doesn't exist, was deleted, or isn't signed yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not signed yet": { + "summary": "The signer hasn't signed", + "value": { + "code": "signature_not_signed", + "detail": "signature_not_signed" + } + }, + "Unknown document": { + "summary": "No such document in this step", + "value": { + "code": "signature_not_found", + "detail": "signature_not_found" + } + } + } + } + } + }, + "409": { + "description": "The stored file no longer matches its recorded SHA-256. Nothing is returned.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Changed file": { + "summary": "Stored file doesn't match its hash", + "value": { + "code": "signature_document_changed", + "detail": "signature_document_changed" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.sessions.signatures.download_document(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.sessions.signatures.downloadDocument(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Sessions.Signatures.DownloadDocument(ctx, \"11111111-2222-3333-4444-555555555555\", \"feature_signature\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().signatures().downloadDocument(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\", \"11111111-2222-3333-4444-555555555555\")\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->signatures->downloadDocument('11111111-2222-3333-4444-555555555555', 'feature_signature', '11111111-2222-3333-4444-555555555555');\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.sessions.signatures.download_document(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Sessions.Signatures.DownloadDocumentAsync(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\", \"11111111-2222-3333-4444-555555555555\");\nawait content.SaveToAsync(\"download.pdf\");" + } + ] + } + }, + "/v3/session/{sessionId}/signatures/{nodeId}/evidence/": { + "get": { + "summary": "Download the signature evidence", + "description": "Download a signer's evidence PDF: the level and format, the signer, the IP address, each document with its original SHA-256 and field values and the SHA-256 of every signed file, plus, for an Advanced (AES) signature, the verified identity session, the code confirmation, the certificate and the timestamp. Use the `evidence_file` path of the signer's item in `signatures[]`.\n\nAlways pass `signature_id` when a Business Verification step has several signers: without it, the route picks one of the step's signers. Needs `read:sessions`; your API key works.", + "operationId": "get_v3_session_signature_evidence", + "x-didit-sdk": { + "resource": "sessions.signatures", + "method": "download_evidence", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "The User Verification (KYC) or Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "nodeId", + "in": "path", + "required": true, + "description": "The E-signature step: `node_id` of the item in `signatures[]`.", + "schema": { + "type": "string", + "example": "feature_signature" + } + }, + { + "name": "signature_id", + "in": "query", + "required": false, + "description": "`id` of the signer's item in `signatures[]`.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "The evidence PDF.", + "headers": { + "Content-Disposition": { + "description": "Attachment with the file name.", + "schema": { + "type": "string", + "example": "attachment; filename=\"signature-evidence.pdf\"" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "403": { + "description": "Missing credentials, or a credential that can't read this session. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "You do not have permission to perform this action." + } + } + } + }, + "404": { + "description": "No evidence matches: the session, step or signer doesn't exist, was deleted, or hasn't signed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not signed yet": { + "summary": "The signer hasn't signed", + "value": { + "code": "signature_not_signed", + "detail": "signature_not_signed" + } + }, + "Unknown signer": { + "summary": "No such signature in this step", + "value": { + "code": "signature_not_found", + "detail": "signature_not_found" + } + } + } + } + } + }, + "409": { + "description": "The stored file no longer matches its recorded SHA-256. Nothing is returned.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Changed file": { + "summary": "Stored file doesn't match its hash", + "value": { + "code": "signature_document_changed", + "detail": "signature_document_changed" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.sessions.signatures.download_evidence(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.sessions.signatures.downloadEvidence(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n);\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Sessions.Signatures.DownloadEvidence(ctx, \"11111111-2222-3333-4444-555555555555\", \"feature_signature\", didit.SessionsSignaturesDownloadEvidenceParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().signatures().downloadEvidence(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\")\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->signatures->downloadEvidence('11111111-2222-3333-4444-555555555555', 'feature_signature');\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.sessions.signatures.download_evidence(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\"\n)\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Sessions.Signatures.DownloadEvidenceAsync(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\");\nawait content.SaveToAsync(\"download.pdf\");" + } + ] + } + }, + "/v3/session/{sessionId}/signatures/{nodeId}/fields/{fieldId}/image/": { + "get": { + "summary": "Download a signature image", + "description": "Download the signature or initials a signer applied to a field, as PNG. Use the `image_url` of the field in `signatures[].documents[].fields[]`. Needs `read:sessions`; your API key works.", + "operationId": "get_v3_session_signature_field_image", + "x-didit-sdk": { + "resource": "sessions.signatures", + "method": "download_field_image", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "The User Verification (KYC) or Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "nodeId", + "in": "path", + "required": true, + "description": "The E-signature step: `node_id` of the item in `signatures[]`.", + "schema": { + "type": "string", + "example": "feature_signature" + } + }, + { + "name": "fieldId", + "in": "path", + "required": true, + "description": "The field on this signer's copy, as in the field's `image_url`.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "The image.", + "content": { + "image/png": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "400": { + "description": "The stored image no longer matches its recorded SHA-256.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "example": { + "code": "signature_document_changed", + "detail": "signature_document_changed" + } + } + } + }, + "403": { + "description": "Missing credentials, or a credential that can't read this session. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "You do not have permission to perform this action." + } + } + } + }, + "404": { + "description": "No image matches: the session, step or field doesn't exist, was deleted, or has no applied signature or initials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Unknown field": { + "summary": "No such field", + "value": { + "code": "signature_field_not_found", + "detail": "signature_field_not_found" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.sessions.signatures.download_field_image(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nwith open(\"download.png\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.sessions.signatures.downloadFieldImage(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nawait writeFile(\"download.png\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Sessions.Signatures.DownloadFieldImage(ctx, \"11111111-2222-3333-4444-555555555555\", \"feature_signature\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.png\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().signatures().downloadFieldImage(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\", \"11111111-2222-3333-4444-555555555555\")\n .writeTo(Path.of(\"download.png\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->signatures->downloadFieldImage('11111111-2222-3333-4444-555555555555', 'feature_signature', '11111111-2222-3333-4444-555555555555');\n$content->saveTo('download.png');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.sessions.signatures.download_field_image(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nFile.binwrite(\"download.png\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Sessions.Signatures.DownloadFieldImageAsync(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\", \"11111111-2222-3333-4444-555555555555\");\nawait content.SaveToAsync(\"download.png\");" + } + ] + } + }, + "/v3/session/{sessionId}/signatures/{nodeId}/validate/": { + "post": { + "summary": "Validate signatures again", + "description": "Re-check the signed documents of an E-signature step. Didit re-reads every signed PDF, checks its SHA-256 and, for an Advanced (AES) signature, validates the PAdES signature, the certificate, the revocation data and the timestamp (a Simple signature has no digital signature, so only the file integrity is checked), then saves the result in each signer's `validation` and returns the updated items. Nothing is signed again. Send `signature_id` to check one signer; without it, every signer of the step is checked, and the call fails if any of them hasn't signed.\n\nNeeds `write:sessions`; your API key works. In the console it's the **Validate again** button.", + "operationId": "post_v3_session_signature_validate", + "x-didit-sdk": { + "resource": "sessions.signatures", + "method": "validate", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "The User Verification (KYC) or Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "nodeId", + "in": "path", + "required": true, + "description": "The E-signature step: `node_id` of the item in `signatures[]`.", + "schema": { + "type": "string", + "example": "feature_signature" + } + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "signature_id": { + "type": "string", + "format": "uuid", + "description": "`id` of one signer's item in `signatures[]`." + } + } + }, + "example": { + "signature_id": "0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10" + } + } + } + }, + "responses": { + "200": { + "description": "The checked signers, with their updated `validation`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "signatures": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SignatureResult" + } + } + } + } + } + } + }, + "400": { + "description": "A stored signed PDF no longer matches the SHA-256 recorded when it was signed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Document changed": { + "summary": "A signed file no longer matches its hash", + "value": { + "code": "signature_document_changed", + "detail": "signature_document_changed" + } + } + } + } + } + }, + "403": { + "description": "Missing credentials, or a credential that can't read this session. A missing key returns `403`; a wrong, revoked or expired key returns `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "You do not have permission to perform this action." + } + } + } + }, + "404": { + "description": "No signature matches: the session, step or signer doesn't exist or was deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Unknown signer": { + "summary": "No such signature in this step", + "value": { + "code": "signature_not_found", + "detail": "signature_not_found" + } + } + } + } + } + }, + "409": { + "description": "A signer of the step hasn't signed, so there's nothing to validate.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not signed": { + "summary": "A signer hasn't signed yet", + "value": { + "code": "signature_not_signed", + "detail": "signature_not_signed" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.signatures.validate(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n signature_id=\"0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.signatures.validate(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n {\n signature_id: \"0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10\",\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Signatures.Validate(ctx, \"11111111-2222-3333-4444-555555555555\", \"feature_signature\", didit.SessionsSignaturesValidateParams{\n\tSignatureID: didit.String(\"0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsSignaturesValidateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().signatures().validate(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\", SessionsSignaturesValidateParams.builder()\n .signatureId(\"0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->signatures->validate('11111111-2222-3333-4444-555555555555', 'feature_signature', [\n 'signature_id' => '0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.signatures.validate(\n \"11111111-2222-3333-4444-555555555555\",\n \"feature_signature\",\n signature_id: \"0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Signatures.ValidateAsync(\"11111111-2222-3333-4444-555555555555\", \"feature_signature\", new SessionsSignaturesValidateParams\n{\n SignatureId = \"0e6c2a8e-6c2f-4b8e-9a51-3f2f6a7d9b10\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/document-ai/{sessionId}/documents/{documentUuid}/": { + "patch": { + "summary": "Update a Document AI document", + "description": "Correct the fields Document AI extracted from one uploaded document, set its review status, or both, on a KYC or KYB session. `extracted_data` must be a JSON object and is merged key by key: a key you send replaces the stored value (a key sent as `null` is stored as `null`) and keys you leave out keep their values. The document status feeds the Document AI result of its workflow node in the session decision; the session status itself is not recomputed. Every successful call, including one that changes nothing, sends a `data.updated` [webhook](https://docs.didit.me/integration/webhooks) with trigger `manual_review`. A scoped API key needs `decisions: write`; see [Document AI](https://docs.didit.me/core-technology/document-ai/overview).", + "operationId": "patch_v3_document_ai_document", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_document_ai_data", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "documentUuid", + "in": "path", + "required": true, + "description": "UUID of the Document AI document (`document_ai_documents[].items[].uuid` in `GET /v3/session/{sessionId}/decision/`). It must belong to the session in the path.", + "schema": { + "type": "string", + "format": "uuid", + "example": "44444444-5555-6666-7777-888888888888" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "extracted_data": { + "type": "object", + "additionalProperties": true, + "description": "Field keys (the keys you configured for the document) and their corrected values. Must be a JSON object; merged key by key into the stored data. Leave it out rather than sending `null`, which returns `400`.", + "example": { + "invoice_number": "INV-2026-0042" + } + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Not Finished" + ], + "description": "New review status of the document.", + "example": "Approved" + } + } + }, + "example": { + "extracted_data": { + "invoice_number": "INV-2026-0042" + }, + "status": "Approved" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/document-ai/11111111-2222-3333-4444-555555555555/documents/44444444-5555-6666-7777-888888888888/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"extracted_data\": {\"invoice_number\": \"INV-2026-0042\"}, \"status\": \"Approved\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_document_ai_data(\n \"11111111-2222-3333-4444-555555555555\",\n \"44444444-5555-6666-7777-888888888888\",\n extracted_data={\"invoice_number\": \"INV-2026-0042\"},\n status=\"Approved\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateDocumentAiData(\n \"11111111-2222-3333-4444-555555555555\",\n \"44444444-5555-6666-7777-888888888888\",\n {\n extracted_data: { invoice_number: \"INV-2026-0042\" },\n status: \"Approved\",\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateDocumentAiData(ctx, \"11111111-2222-3333-4444-555555555555\", \"44444444-5555-6666-7777-888888888888\", didit.SessionsUpdateDocumentAiDataParams{\n\tExtractedData: didit.Some(map[string]any{\"invoice_number\": \"INV-2026-0042\"}),\n\tStatus: didit.Some(didit.SessionsUpdateDocumentAiDataParamsStatus(\"Approved\")),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SessionsUpdateDocumentAiDataParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateDocumentAiData(\"11111111-2222-3333-4444-555555555555\", \"44444444-5555-6666-7777-888888888888\", SessionsUpdateDocumentAiDataParams.builder()\n .extractedData(Map.of(\"invoice_number\", \"INV-2026-0042\"))\n .status(SessionsUpdateDocumentAiDataParams.Status.of(\"Approved\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateDocumentAiData('11111111-2222-3333-4444-555555555555', '44444444-5555-6666-7777-888888888888', [\n 'extracted_data' => ['invoice_number' => 'INV-2026-0042'],\n 'status' => 'Approved',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_document_ai_data(\n \"11111111-2222-3333-4444-555555555555\",\n \"44444444-5555-6666-7777-888888888888\",\n extracted_data: { invoice_number: \"INV-2026-0042\" },\n status: \"Approved\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateDocumentAiDataAsync(\"11111111-2222-3333-4444-555555555555\", \"44444444-5555-6666-7777-888888888888\", new SessionsUpdateDocumentAiDataParams\n{\n ExtractedData = new Dictionary { [\"invoice_number\"] = \"INV-2026-0042\" },\n Status = new SessionsUpdateDocumentAiDataParamsStatus(\"Approved\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Document updated. Returns the document after the merge.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "uuid", + "document_key", + "original_filename", + "file_url", + "status", + "extracted_data" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Document identifier." + }, + "document_key": { + "type": "string", + "description": "The configured document this upload corresponds to." + }, + "original_filename": { + "type": "string", + "nullable": true, + "description": "File name the document was uploaded with." + }, + "file_url": { + "type": "string", + "nullable": true, + "description": "Storage path of the uploaded file. It is not a download link: read the presigned `file_url` from the session decision to download the file." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Review status of the document." + }, + "extracted_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "All extracted fields after the merge, keyed by field key." + } + } + }, + "example": { + "uuid": "44444444-5555-6666-7777-888888888888", + "document_key": "invoice", + "original_filename": "invoice-2026-09.pdf", + "file_url": "document_ai/11111111-2222-3333-4444-555555555555-66666666-7777-8888-9999-000000000000.pdf", + "status": "Approved", + "extracted_data": { + "invoice_number": "INV-2026-0042", + "total_amount": 1250.0, + "issue_date": "2026-09-01" + } + } + } + } + }, + "400": { + "description": "Validation error, checked before the document is looked up: `status` not one of the allowed values, or `extracted_data` sent as `null`.", + "content": { + "application/json": { + "examples": { + "Invalid status": { + "summary": "status not in the enum", + "value": { + "status": [ + "\"Rejected\" is not a valid choice." + ] + } + }, + "Null extracted_data": { + "summary": "extracted_data is null", + "value": { + "extracted_data": [ + "This field may not be null." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits, or no current document with this UUID belongs to it. The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's limits (also returned for a session deleted moments ago)", + "value": { + "detail": "Session not found." + } + }, + "Unknown document": { + "summary": "documentUuid does not belong to the session", + "value": { + "detail": "Document AI document not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/aml/dataset-freshness/": { + "get": { + "summary": "Get AML dataset freshness", + "description": "Report how current the datasets behind AML screening are, per source family (`sanctions`, `pep`, `warnings`, `adverse_media`), as audit evidence for [ongoing AML monitoring](https://docs.didit.me/core-technology/aml-screening/continuous-monitoring-aml-screening#proving-that-monitoring-ran). The answer is platform-wide: no session in the path, no customer data in the body, the same answer for every application.\n\nAlways `200` for an accepted key. Check `availability` first: when it is not `available`, `dataset_freshness` is `null`. Dates are never estimated: a family whose datasets reported no date is absent from `sources`, and a family where only some datasets reported one has `oldest_updated_at: null`. Readings are taken while ongoing monitoring runs, not on each call; `captured_at` says when.\n\nA scoped API key needs `sessions: read`; a key limited to some workflows or to approved sessions cannot call this endpoint. Nothing is billed.", + "operationId": "get_aml_dataset_freshness", + "x-didit-sdk": { + "resource": "aml", + "method": "retrieve_dataset_freshness", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "parameters": [], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/aml/dataset-freshness/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.aml.retrieve_dataset_freshness()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.aml.retrieveDatasetFreshness();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.AML.RetrieveDatasetFreshness(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.aml().retrieveDatasetFreshness();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "aml->retrieveDatasetFreshness();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.aml.retrieve_dataset_freshness\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Aml.RetrieveDatasetFreshnessAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The latest dataset freshness reading, or `null` with the reason.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "dataset_freshness", + "availability" + ], + "properties": { + "dataset_freshness": { + "allOf": [ + { + "$ref": "#/components/schemas/AMLDatasetFreshness" + } + ], + "nullable": true, + "description": "The latest reading, or `null` when `availability` is not `available`." + }, + "availability": { + "type": "string", + "enum": [ + "available", + "not_configured", + "not_yet_collected" + ], + "description": "`available` when a reading exists; `not_configured` when dataset dates are not collected in this environment; `not_yet_collected` when collection is set up but no reading exists yet." + } + } + }, + "examples": { + "Available": { + "value": { + "dataset_freshness": { + "captured_at": "2026-10-17T06:00:12.345678+00:00", + "provider": "aml_data_source", + "catalog_version": "20261017054500-abc", + "sources": { + "sanctions": { + "last_updated_at": "2026-10-17T04:10:00+00:00", + "oldest_updated_at": "2026-10-15T22:30:00+00:00", + "dataset_count": 48, + "dated_dataset_count": 48 + }, + "pep": { + "last_updated_at": "2026-10-16T23:05:00+00:00", + "oldest_updated_at": null, + "dataset_count": 12, + "dated_dataset_count": 11 + } + } + }, + "availability": "available" + } + }, + "Not collected yet": { + "value": { + "dataset_freshness": null, + "availability": "not_yet_collected" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing key gets the generic message; a wrong, revoked or expired key gets `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions read": { + "value": { + "detail": "This API key has no read access to sessions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/": { + "get": { + "summary": "List lists", + "description": "Returns the application's lists, newest first: the system lists Didit provisions (one blocklist per entry type, the **High Risk Countries** custom list, and any system allowlist) and the allowlists and custom lists you created. Filter with `list_type` and `entry_type`; deleted lists never appear. Results are paginated with `limit` and `offset`. A scoped API key needs `workflows: read`. See [Lists](https://docs.didit.me/management-api/lists/overview) for what each list type does.", + "operationId": "list_lists", + "x-didit-sdk": { + "resource": "lists", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_type", + "in": "query", + "required": false, + "description": "Only lists of this type.", + "schema": { + "type": "string", + "enum": [ + "blocklist", + "allowlist", + "custom" + ] + } + }, + { + "name": "entry_type", + "in": "query", + "required": false, + "description": "Only lists of this entry type.", + "schema": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email", + "ip_address", + "device_fingerprint", + "wallet_address", + "bank_account", + "country", + "user", + "business", + "key" + ] + } + }, + { + "name": "ordering", + "in": "query", + "required": false, + "description": "Sort order: `-created_at` (newest first, the default) or `created_at` (oldest first).", + "schema": { + "type": "string", + "enum": [ + "-created_at", + "created_at" + ], + "default": "-created_at" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Page size. Defaults to 50. A value above 1000000 returns `400`; zero, a negative or a non-numeric value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Zero-based index of the first item to return. Prefer following `next`. A value above 1000000 returns `400`; a negative or non-numeric value falls back to 0.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/lists/?list_type=blocklist' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.lists.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.lists.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Lists.ListAutoPaging(ctx, didit.ListsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.lists().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.lists.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Lists.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "A page of lists.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "description": "Total number of lists matching the filters." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ComplianceList" + } + } + } + }, + "examples": { + "Lists": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "uuid": "22222222-3333-4444-5555-666666666666", + "name": "Trusted office IPs", + "description": "Office egress ranges", + "list_type": "allowlist", + "entry_type": "ip_address", + "is_system": false, + "entry_count": 3, + "last_entry_added_at": "2026-10-02T08:15:27.501334Z", + "created_at": "2026-09-30T13:20:45.889120Z", + "updated_at": "2026-09-30T13:20:45.889120Z" + }, + { + "uuid": "11111111-2222-3333-4444-555555555555", + "name": "Face Blocklist", + "description": "Blocked biometric faces. When a face matching an entry in this list is detected during liveness verification, the session will be automatically declined.", + "list_type": "blocklist", + "entry_type": "face", + "is_system": true, + "entry_count": 12, + "last_entry_added_at": "2026-10-09T16:42:03.118204Z", + "created_at": "2026-01-14T10:02:11.402917Z", + "updated_at": "2026-01-14T10:02:11.402917Z" + } + ] + } + } + } + } + } + }, + "400": { + "description": "A query parameter is invalid.", + "content": { + "application/json": { + "examples": { + "Unknown list type": { + "value": { + "list_type": [ + "\"denylist\" is not a valid choice." + ] + } + }, + "Unsupported ordering": { + "value": { + "ordering": [ + "\"name\" is not a valid choice." + ] + } + }, + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: read` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no read access to workflows." + } + } + } + } + } + }, + "429": { + "description": "The shared read limit of 600 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create a list", + "description": "Creates an allowlist or a custom list in the application the API key belongs to. Blocklists cannot be created: Didit provisions one system blocklist per entry type, and you add to it with [Create a list entry](https://docs.didit.me/management-api/lists/create-entry). `entry_type` cannot change after creation and decides how entry values are validated and matched. Allowlist entries mark matching items as trusted (for example, a face allowlist skips the duplicated-face action) and never override a blocklist match; reference any list from a workflow or transaction rule condition with `value_type: \"list\"`. A scoped API key needs `workflows: write`.", + "operationId": "create_list", + "x-didit-sdk": { + "resource": "lists", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "list_type", + "entry_type" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "List name. Must differ from the name of every other list of the application, system lists included." + }, + "description": { + "type": "string", + "default": "", + "description": "Optional free-text description." + }, + "list_type": { + "type": "string", + "enum": [ + "allowlist", + "custom" + ], + "description": "`allowlist` or `custom`. `blocklist` is refused with `400`." + }, + "entry_type": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email", + "ip_address", + "device_fingerprint", + "wallet_address", + "bank_account", + "country", + "user", + "business", + "key" + ], + "description": "What the list holds: `face`, `document` (document number), `phone`, `email`, `ip_address` (IP address or CIDR range), `device_fingerprint`, `wallet_address`, `bank_account`, `country` (ISO 3166-1 alpha-3), `user` or `business` (your `vendor_data`), or `key` (any string). It decides how entry values are validated and matched. Cannot be changed later." + } + } + }, + "examples": { + "IP allowlist": { + "value": { + "name": "Trusted office IPs", + "description": "Office egress ranges", + "list_type": "allowlist", + "entry_type": "ip_address" + } + }, + "Custom country list": { + "value": { + "name": "Enhanced due diligence countries", + "list_type": "custom", + "entry_type": "country" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/lists/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"name\": \"Trusted office IPs\",\n \"description\": \"Office egress ranges\",\n \"list_type\": \"allowlist\",\n \"entry_type\": \"ip_address\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.lists.create(\n name=\"Trusted office IPs\",\n description=\"Office egress ranges\",\n list_type=\"allowlist\",\n entry_type=\"ip_address\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.lists.create({\n name: \"Trusted office IPs\",\n description: \"Office egress ranges\",\n list_type: \"allowlist\",\n entry_type: \"ip_address\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Lists.Create(ctx, didit.ListsCreateParams{\n\tName: \"Trusted office IPs\",\n\tDescription: didit.String(\"Office egress ranges\"),\n\tListType: \"allowlist\",\n\tEntryType: \"ip_address\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.ListsCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.lists().create(ListsCreateParams.builder()\n .name(\"Trusted office IPs\")\n .description(\"Office egress ranges\")\n .listType(ListsCreateParams.ListType.of(\"allowlist\"))\n .entryType(ListsCreateParams.EntryType.of(\"ip_address\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->create([\n 'name' => 'Trusted office IPs',\n 'description' => 'Office egress ranges',\n 'list_type' => 'allowlist',\n 'entry_type' => 'ip_address',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.lists.create(\n name: \"Trusted office IPs\",\n description: \"Office egress ranges\",\n list_type: \"allowlist\",\n entry_type: \"ip_address\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Lists.CreateAsync(new ListsCreateParams\n{\n Name = \"Trusted office IPs\",\n Description = \"Office egress ranges\",\n ListType = \"allowlist\",\n EntryType = \"ip_address\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The list was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ComplianceList" + }, + "examples": { + "New allowlist": { + "value": { + "uuid": "22222222-3333-4444-5555-666666666666", + "name": "Trusted office IPs", + "description": "Office egress ranges", + "list_type": "allowlist", + "entry_type": "ip_address", + "is_system": false, + "entry_count": 0, + "last_entry_added_at": null, + "created_at": "2026-10-12T09:15:44.031876Z", + "updated_at": "2026-10-12T09:15:44.031876Z" + } + } + } + } + } + }, + "400": { + "description": "The body is invalid.", + "content": { + "application/json": { + "examples": { + "Blocklist refused": { + "value": { + "list_type": [ + "Cannot create additional blocklist lists. System blocklists are auto-created for each application." + ] + } + }, + "Duplicate name": { + "value": { + "name": [ + "A list with this name already exists for this application." + ] + } + }, + "Missing fields": { + "value": { + "name": [ + "This field is required." + ], + "list_type": [ + "This field is required." + ], + "entry_type": [ + "This field is required." + ] + } + }, + "Unknown entry type": { + "value": { + "entry_type": [ + "\"iban\" is not a valid choice." + ] + } + }, + "Name too long": { + "value": { + "name": [ + "Ensure this field has no more than 255 characters." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/{list_uuid}/": { + "get": { + "summary": "Get a list", + "description": "Returns one list of the application with its entry count and when its newest entry was added. A list of another application, or one that was deleted, returns `404`. This returns the list only: read its values with [List list entries](https://docs.didit.me/management-api/lists/list-entries). A scoped API key needs `workflows: read`.", + "operationId": "get_list", + "x-didit-sdk": { + "resource": "lists", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list, as returned by List lists or Create a list.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.lists.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.lists.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Lists.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.lists().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.lists.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Lists.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The list.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ComplianceList" + }, + "examples": { + "System face blocklist": { + "value": { + "uuid": "11111111-2222-3333-4444-555555555555", + "name": "Face Blocklist", + "description": "Blocked biometric faces. When a face matching an entry in this list is detected during liveness verification, the session will be automatically declined.", + "list_type": "blocklist", + "entry_type": "face", + "is_system": true, + "entry_count": 12, + "last_entry_added_at": "2026-10-09T16:42:03.118204Z", + "created_at": "2026-01-14T10:02:11.402917Z", + "updated_at": "2026-01-14T10:02:11.402917Z" + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: read` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no read access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No List matches the given query." + } + } + } + } + } + }, + "429": { + "description": "The shared read limit of 600 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update a list", + "description": "Renames a list or changes its description. Only `name` and `description` are read; any other field is ignored, so `list_type` and `entry_type` keep the values they were created with. System lists cannot be modified and return `400`. The response contains only `name` and `description`: call [Get a list](https://docs.didit.me/management-api/lists/get) for the full list. A scoped API key needs `workflows: write`.", + "operationId": "update_list", + "x-didit-sdk": { + "resource": "lists", + "method": "update", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list, as returned by List lists or Create a list.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "New name. Must differ from the name of every other list of the application." + }, + "description": { + "type": "string", + "description": "New description. Send an empty string to clear it." + } + } + }, + "examples": { + "Rename": { + "value": { + "name": "Trusted office and VPN IPs" + } + }, + "Rename and describe": { + "value": { + "name": "Trusted office and VPN IPs", + "description": "Office egress ranges and the corporate VPN" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"name\": \"Trusted office and VPN IPs\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.lists.update(\n \"11111111-2222-3333-4444-555555555555\",\n name=\"Trusted office and VPN IPs\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.lists.update(\"11111111-2222-3333-4444-555555555555\", {\n name: \"Trusted office and VPN IPs\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Lists.Update(ctx, \"11111111-2222-3333-4444-555555555555\", didit.ListsUpdateParams{\n\tName: didit.String(\"Trusted office and VPN IPs\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.ListsUpdateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.lists().update(\"11111111-2222-3333-4444-555555555555\", ListsUpdateParams.builder()\n .name(\"Trusted office and VPN IPs\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->update('11111111-2222-3333-4444-555555555555', [\n 'name' => 'Trusted office and VPN IPs',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.lists.update(\n \"11111111-2222-3333-4444-555555555555\",\n name: \"Trusted office and VPN IPs\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Lists.UpdateAsync(\"11111111-2222-3333-4444-555555555555\", new ListsUpdateParams\n{\n Name = \"Trusted office and VPN IPs\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The list was updated. Only the editable fields are returned.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "description" + ], + "properties": { + "name": { + "type": "string", + "description": "The list's name after the update." + }, + "description": { + "type": "string", + "description": "The list's description after the update." + } + } + }, + "examples": { + "Renamed": { + "value": { + "name": "Trusted office and VPN IPs", + "description": "Office egress ranges" + } + } + } + } + } + }, + "400": { + "description": "The list is a system list, or the body is invalid.", + "content": { + "application/json": { + "examples": { + "System list": { + "value": { + "non_field_errors": [ + "System lists cannot be modified." + ] + } + }, + "Duplicate name": { + "value": { + "name": [ + "A list with this name already exists for this application." + ] + } + }, + "Blank name": { + "value": { + "name": [ + "This field may not be blank." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No List matches the given query." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "delete": { + "summary": "Delete a list", + "description": "Deletes an allowlist or custom list together with its entries. Deletion is irreversible: the list and its entries no longer appear in any read, and workflow and transaction rule conditions that reference the list evaluate it as an empty list from then on. Sessions and transactions already evaluated keep their results, and the name can be reused for a new list. System lists cannot be deleted and return `400`; to empty one, [delete its entries](https://docs.didit.me/management-api/lists/delete-entry). A scoped API key needs `workflows: write`.", + "operationId": "delete_list", + "x-didit-sdk": { + "resource": "lists", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list, as returned by List lists or Create a list.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.lists.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.lists.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Lists.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.lists().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.lists.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Lists.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ], + "responses": { + "204": { + "description": "The list and its entries were deleted. No body." + }, + "400": { + "description": "The list is a system list.", + "content": { + "application/json": { + "examples": { + "System list": { + "value": { + "detail": "System lists cannot be deleted." + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was already deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No List matches the given query." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/{list_uuid}/entries/": { + "get": { + "summary": "List list entries", + "description": "Returns the current entries of one list, newest first. `search` matches part of an entry's `value` or `display_label` without regard to case, and `reference_session_id` narrows the results to the entries taken from one person verification session. Image URLs in `metadata` are short-lived signed URLs issued again on every call; a scoped API key without `media: read` receives `null` in their place. A scoped API key needs `workflows: read`.", + "operationId": "list_list_entries", + "x-didit-sdk": { + "resource": "lists.entries", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list, as returned by List lists or Create a list.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "search", + "in": "query", + "required": false, + "description": "Case-insensitive text that the entry's `value` or `display_label` must contain.", + "schema": { + "type": "string" + } + }, + { + "name": "reference_session_id", + "in": "query", + "required": false, + "description": "Only the entries taken from this person verification session, for example to see which of its faces are already listed.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Page size. Defaults to 50. A value above 1000000 returns `400`; zero, a negative or a non-numeric value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Zero-based index of the first item to return. Prefer following `next`. A value above 1000000 returns `400`; a negative or non-numeric value falls back to 0.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/entries/?search=example.com' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.lists.entries.list(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.lists.entries.list(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Lists.Entries.ListAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.ListsEntriesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.lists().entries().list(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->entries->list('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.lists.entries.list(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Lists.Entries.ListAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "A page of entries, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "description": "Total number of entries matching the filters." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ComplianceListEntry" + } + } + } + }, + "examples": { + "Entries": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "uuid": "bbbbbbbb-cccc-dddd-eeee-ffffffffffff", + "value": "X1234567", + "display_label": "Jane Doe - X1234567", + "comment": "", + "added_by": "api", + "reference_session_id": "33333333-4444-5555-6666-777777777777", + "reference_session_number": 1042, + "reference_session_type": "kyc", + "reference_object_uuid": "66666666-7777-8888-9999-000000000000", + "metadata": { + "full_name": "Jane Doe", + "issuing_state": "ESP", + "document_type": "ID", + "region": "", + "document_number": "X1234567" + }, + "created_at": "2026-10-12T10:06:12.774915Z" + }, + { + "uuid": "eeeeeeee-ffff-0000-1111-222222222222", + "value": "y7654321", + "display_label": "", + "comment": "Reported by a partner", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": null, + "metadata": null, + "created_at": "2026-10-03T07:48:30.552810Z" + } + ] + } + } + } + } + } + }, + "400": { + "description": "A query parameter is invalid.", + "content": { + "application/json": { + "examples": { + "Invalid session id": { + "value": { + "reference_session_id": [ + "Must be a valid UUID." + ] + } + }, + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: read` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no read access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "List not found." + } + } + } + } + } + }, + "429": { + "description": "The shared read limit of 600 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create a list entry", + "description": "Adds one entry to a list, in one of three ways: by `value`; from a verification session with `reference_session_id`, where Didit reads the value from the session's most recent record of the list's entry type and links the entry to it; or by `value` with a `reference_object_uuid` and `metadata.reference_type` pointing back to a transaction, user or business. On the system face, document, phone and email blocklists, the record read from the session is also flagged as blocklisted, so new matching sessions are declined; on the system user and business blocklists, matching users or businesses are set to `BLOCKED`. Sessions and transactions that already finished are not changed. A scoped API key needs `workflows: write`. See [Create a list entry](https://docs.didit.me/management-api/lists/create-entry) for each mode.", + "operationId": "create_list_entry", + "x-didit-sdk": { + "resource": "lists.entries", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list to add to. For a face you only have as an image, use Upload a face to a list instead.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Send `value`, `reference_session_id`, or both. Which fields apply depends on the list's `entry_type`.", + "properties": { + "value": { + "type": "string", + "description": "The value to list, validated against the list's `entry_type`: `email` a valid email address; `phone` a valid international number, stored in E.164; `ip_address` an IP address or CIDR range (for example `10.0.0.0/8`); `device_fingerprint` at least 8 characters among letters, digits, `-`, `_`, `:` and `.` (a blocklist refuses a fingerprint that several distinct devices have reported); `country` an ISO 3166-1 alpha-2 or alpha-3 code, stored as alpha-3; `user` and `business` the `vendor_data`; any other type a non-empty string. Stored in lower case except phone numbers and country codes, and a list holds each value once (case-insensitive). Required unless `reference_session_id` is sent; must be left empty with a document-capture `face_image_source`." + }, + "reference_session_id": { + "type": "string", + "format": "uuid", + "description": "A person or business verification session of your application to take the value from. Didit reads the session's most recent record of the list's entry type (`face`, `document`, `phone`, `email`, `ip_address` or `device_fingerprint`), links the entry to the session and to that record, and fills `display_label` and `metadata` for documents. For a `device_fingerprint` blocklist, a session that only has a device-class fingerprint, or was completed on more than one device, is refused: send the exact fingerprint as `value`. For other entry types, or when the session has no such record, send `value` too; your `value` is then stored and the entry is still linked to the session." + }, + "reference_object_uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the record the entry stands for when it does not come from a session: a transaction UUID, or the `didit_internal_id` of a user or business. Pair it with `metadata.reference_type`. Replaced by the session's record when `reference_session_id` provides one." + }, + "face_image_source": { + "type": "string", + "enum": [ + "liveness", + "front_image_camera_front", + "back_image_camera_front" + ], + "default": "liveness", + "description": "Face lists with `reference_session_id`: which of the session's images the face is taken from. `liveness` (default) is the liveness selfie. `front_image_camera_front` and `back_image_camera_front` are the selfies the front camera took while the user photographed the front or back of their document: accepted only on the system face blocklist, with `reference_session_id` and an empty `value`. Each selfie can be added once and must show one clear face." + }, + "reference_node_id": { + "type": "string", + "nullable": true, + "maxLength": 100, + "description": "With a document-capture `face_image_source`: the ID verification node whose selfie to use, for workflows with more than one. Defaults to the most recent ID verification that has that image. Refused with `liveness`." + }, + "display_label": { + "type": "string", + "maxLength": 512, + "description": "Human-readable label. When empty, Didit derives one from the session where it can (for example the holder's name and document number)." + }, + "comment": { + "type": "string", + "description": "Free-text note, such as the reason for listing." + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Free-form JSON stored with the entry and returned as stored. Use `reference_type` and `reference_id` so the console links back to the source. `reference_image_url` and `front_image_url` are set by Didit only and are dropped if you send them. For an entry read from a session, Didit's keys are merged with yours.", + "properties": { + "reference_type": { + "type": "string", + "enum": [ + "transaction", + "vendor_user", + "vendor_business" + ], + "description": "What `reference_object_uuid` points to." + }, + "reference_id": { + "type": "string", + "description": "Identifier of the source record." + } + } + } + } + }, + "examples": { + "By value": { + "value": { + "value": "+14155552671", + "comment": "Reported fraud" + } + }, + "From a session": { + "value": { + "reference_session_id": "33333333-4444-5555-6666-777777777777" + } + }, + "Face from a document-capture selfie": { + "value": { + "reference_session_id": "33333333-4444-5555-6666-777777777777", + "face_image_source": "front_image_camera_front" + } + }, + "From a transaction": { + "value": { + "value": "0x52908400098527886E0F7030069857D2E4169EE7", + "reference_object_uuid": "55555555-6666-7777-8888-999999999999", + "comment": "Linked to a chargeback", + "metadata": { + "reference_type": "transaction", + "reference_id": "55555555-6666-7777-8888-999999999999" + } + } + }, + "Country": { + "value": { + "value": "PRK", + "comment": "Sanctioned jurisdiction" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/entries/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"value\": \"+14155552671\", \"comment\": \"Reported fraud\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.lists.entries.create(\n \"11111111-2222-3333-4444-555555555555\",\n value=\"+14155552671\",\n comment=\"Reported fraud\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.lists.entries.create(\"11111111-2222-3333-4444-555555555555\", {\n value: \"+14155552671\",\n comment: \"Reported fraud\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Lists.Entries.Create(ctx, \"11111111-2222-3333-4444-555555555555\", didit.ListsEntriesCreateParams{\n\tValue: didit.String(\"+14155552671\"),\n\tComment: didit.String(\"Reported fraud\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.ListsEntriesCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.lists().entries().create(\"11111111-2222-3333-4444-555555555555\", ListsEntriesCreateParams.builder()\n .value(\"+14155552671\")\n .comment(\"Reported fraud\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->entries->create('11111111-2222-3333-4444-555555555555', [\n 'value' => '+14155552671',\n 'comment' => 'Reported fraud',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.lists.entries.create(\n \"11111111-2222-3333-4444-555555555555\",\n value: \"+14155552671\",\n comment: \"Reported fraud\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Lists.Entries.CreateAsync(\"11111111-2222-3333-4444-555555555555\", new ListsEntriesCreateParams\n{\n Value = \"+14155552671\",\n Comment = \"Reported fraud\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The entry was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ComplianceListEntry" + }, + "examples": { + "By value": { + "value": { + "uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "value": "+14155552671", + "display_label": "", + "comment": "Reported fraud", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": null, + "metadata": null, + "created_at": "2026-10-12T10:04:51.220391Z" + } + }, + "Document from a session": { + "value": { + "uuid": "bbbbbbbb-cccc-dddd-eeee-ffffffffffff", + "value": "X1234567", + "display_label": "Jane Doe - X1234567", + "comment": "", + "added_by": "api", + "reference_session_id": "33333333-4444-5555-6666-777777777777", + "reference_session_number": 1042, + "reference_session_type": "kyc", + "reference_object_uuid": "66666666-7777-8888-9999-000000000000", + "metadata": { + "full_name": "Jane Doe", + "issuing_state": "ESP", + "document_type": "ID", + "region": "", + "document_number": "X1234567" + }, + "created_at": "2026-10-12T10:06:12.774915Z" + } + }, + "From a transaction": { + "value": { + "uuid": "dddddddd-eeee-ffff-0000-111111111111", + "value": "0x52908400098527886e0f7030069857d2e4169ee7", + "display_label": "", + "comment": "Linked to a chargeback", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": "55555555-6666-7777-8888-999999999999", + "metadata": { + "reference_type": "transaction", + "reference_id": "55555555-6666-7777-8888-999999999999" + }, + "created_at": "2026-10-12T10:20:40.117062Z" + } + } + } + } + } + }, + "400": { + "description": "The body is invalid, the value is already in the list, or the session cannot provide the value.", + "content": { + "application/json": { + "examples": { + "Nothing to list": { + "value": { + "value": [ + "Either 'value' or 'reference_session_id' must be provided." + ] + } + }, + "Duplicate value": { + "value": { + "value": [ + "An entry with this value already exists in this list." + ] + } + }, + "Invalid phone": { + "value": { + "value": [ + "Invalid phone number. Provide a valid international number (e.g. +1234567890)." + ] + } + }, + "Invalid IP": { + "value": { + "value": [ + "Invalid IP address or CIDR range (e.g. 192.168.1.1 or 10.0.0.0/24)." + ] + } + }, + "Invalid country": { + "value": { + "value": [ + "Invalid country code: XX. Provide a valid ISO 3166-1 alpha-2 or alpha-3 country code." + ] + } + }, + "Device-class fingerprint": { + "value": { + "value": [ + "This value is a device-class fingerprint: several distinct devices have reported it, so blocklisting it would decline unrelated people. Block the user by document, face, email or phone instead." + ] + } + }, + "Unknown session": { + "value": { + "reference_session_id": [ + "Session not found or does not belong to this application." + ] + } + }, + "No data in session": { + "value": { + "value": [ + "Either provide a 'value' or ensure the session contains data for this entry type." + ] + } + }, + "Selfie on another list": { + "value": { + "face_image_source": [ + "A document-capture selfie can only be added to the face blocklist." + ] + } + }, + "No clear face in selfie": { + "value": { + "face_image_source": [ + "No clear face was found in this selfie, so it cannot be blocklisted." + ] + } + }, + "Selfie already listed": { + "value": { + "face_image_source": [ + "This selfie is already in the blocklist." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "List not found." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + }, + "503": { + "description": "Face analysis of a document-capture selfie is temporarily unavailable. Nothing was added; retry later.", + "content": { + "application/json": { + "examples": { + "Unavailable": { + "value": { + "detail": "Service temporarily unavailable. Please try again later." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/{list_uuid}/entries/{entry_uuid}/": { + "delete": { + "summary": "Delete a list entry", + "description": "Deletes one entry from a list, system lists included. The entry stops matching at once, and deletion is irreversible: to list the value again, create a new entry. On a system face, document, phone or email blocklist, the record the entry stands for is no longer flagged as blocklisted; on the system user or business blocklist, users or businesses of the application whose `vendor_data` equals the entry's value (or whose `didit_internal_id` equals its `reference_object_uuid`) and that are `BLOCKED` return to `ACTIVE`. Sessions and transactions that already finished keep their status. A scoped API key needs `workflows: write`.", + "operationId": "delete_list_entry", + "x-didit-sdk": { + "resource": "lists.entries", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of the list that holds the entry.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "entry_uuid", + "in": "path", + "required": true, + "description": "UUID of the entry, as returned by List list entries or Create a list entry.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/entries/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.lists.entries.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.lists.entries.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\n);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Lists.Entries.Delete(ctx, \"11111111-2222-3333-4444-555555555555\", \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.lists().entries().delete(\"11111111-2222-3333-4444-555555555555\", \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->entries->delete('11111111-2222-3333-4444-555555555555', 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.lists.entries.delete(\n \"11111111-2222-3333-4444-555555555555\",\n \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\"\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Lists.Entries.DeleteAsync(\"11111111-2222-3333-4444-555555555555\", \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\");" + } + ], + "responses": { + "204": { + "description": "The entry was deleted. No body." + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No entry with this UUID exists in this list, the list is not in the API key's application or was deleted, or the entry was already deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No ListEntry matches the given query." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/lists/{list_uuid}/entries/face-upload/": { + "post": { + "summary": "Upload a face to a list", + "description": "Adds a face to a face blocklist or face allowlist from a base64-encoded JPEG or PNG image, for when you have no verification session to reference. Didit detects the faces in the image, uses the largest one, and creates an entry whose `value` and `reference_object_uuid` are the UUID of the new face. A blocklisted face declines new sessions whose face matches; an allowlisted face skips the duplicated-face action and never overrides a blocklist match. In a sandbox application the image is not analyzed and the entry matches nothing. A scoped API key needs `workflows: write`. To list a face from an existing session, use [Create a list entry](https://docs.didit.me/management-api/lists/create-entry) with `reference_session_id` instead.", + "operationId": "upload_list_entry_face", + "x-didit-sdk": { + "resource": "lists.entries", + "method": "upload_face", + "auth": "api_key", + "server": true + }, + "tags": [ + "Lists" + ], + "parameters": [ + { + "name": "list_uuid", + "in": "path", + "required": true, + "description": "UUID of a list with `entry_type: face` and `list_type: blocklist` or `allowlist`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "image" + ], + "properties": { + "image": { + "type": "string", + "format": "byte", + "description": "Base64-encoded JPEG or PNG, at most 2 MB once decoded, showing a clearly visible face. Send the bare base64 string, without a `data:image/...;base64,` prefix." + }, + "comment": { + "type": "string", + "default": "", + "description": "Optional free-text note, such as the reason for listing." + } + } + }, + "examples": { + "Face image": { + "value": { + "image": "/9j/4AAQSkZJRgABAQAAAQABAAD...", + "comment": "Known fraud attempt" + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/lists/11111111-2222-3333-4444-555555555555/entries/face-upload/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d \"{\\\"image\\\": \\\"$(base64 < face.jpg | tr -d '\\n')\\\", \\\"comment\\\": \\\"Known fraud attempt\\\"}\"" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.lists.entries.upload_face(\n \"11111111-2222-3333-4444-555555555555\",\n image=\"/9j/4AAQSkZJRgABAQAAAQABAAD...\",\n comment=\"Known fraud attempt\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.lists.entries.uploadFace(\"11111111-2222-3333-4444-555555555555\", {\n image: \"/9j/4AAQSkZJRgABAQAAAQABAAD...\",\n comment: \"Known fraud attempt\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Lists.Entries.UploadFace(ctx, \"11111111-2222-3333-4444-555555555555\", didit.ListsEntriesUploadFaceParams{\n\tImage: \"/9j/4AAQSkZJRgABAQAAAQABAAD...\",\n\tComment: didit.String(\"Known fraud attempt\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.ListsEntriesUploadFaceParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.lists().entries().uploadFace(\"11111111-2222-3333-4444-555555555555\", ListsEntriesUploadFaceParams.builder()\n .image(\"/9j/4AAQSkZJRgABAQAAAQABAAD...\")\n .comment(\"Known fraud attempt\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "lists->entries->uploadFace('11111111-2222-3333-4444-555555555555', [\n 'image' => '/9j/4AAQSkZJRgABAQAAAQABAAD...',\n 'comment' => 'Known fraud attempt',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.lists.entries.upload_face(\n \"11111111-2222-3333-4444-555555555555\",\n image: \"/9j/4AAQSkZJRgABAQAAAQABAAD...\",\n comment: \"Known fraud attempt\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Lists.Entries.UploadFaceAsync(\"11111111-2222-3333-4444-555555555555\", new ListsEntriesUploadFaceParams\n{\n Image = \"/9j/4AAQSkZJRgABAQAAAQABAAD...\",\n Comment = \"Known fraud attempt\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The face entry was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ComplianceListEntry" + }, + "examples": { + "Uploaded face": { + "value": { + "uuid": "cccccccc-dddd-eeee-ffff-000000000000", + "value": "44444444-5555-6666-7777-888888888888", + "display_label": "Manually uploaded face", + "comment": "Known fraud attempt", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": "44444444-5555-6666-7777-888888888888", + "metadata": { + "reference_image_url": "https://media.example.com/faces/reference.jpg?expires=1760263938&signature=3f9a1c" + }, + "created_at": "2026-10-12T10:12:18.904467Z" + } + }, + "Sandbox application": { + "value": { + "uuid": "cccccccc-dddd-eeee-ffff-000000000000", + "value": "44444444-5555-6666-7777-888888888888", + "display_label": "Sandbox face entry", + "comment": "Known fraud attempt", + "added_by": "api", + "reference_session_id": null, + "reference_session_number": null, + "reference_session_type": null, + "reference_object_uuid": "44444444-5555-6666-7777-888888888888", + "metadata": { + "sandbox": true + }, + "created_at": "2026-10-12T10:12:18.904467Z" + } + } + } + } + } + }, + "400": { + "description": "The list does not take face uploads, or the image is missing, invalid, too large or shows no usable face. No entry was created.", + "content": { + "application/json": { + "examples": { + "Not a face list": { + "value": { + "detail": "Face upload is only available for face-type lists." + } + }, + "Custom face list": { + "value": { + "detail": "Face upload is only available for face blocklists and allowlists." + } + }, + "Missing image": { + "value": { + "image": [ + "This field is required." + ] + } + }, + "Invalid base64": { + "value": { + "image": [ + "Invalid base64 encoding." + ] + } + }, + "Too large": { + "value": { + "image": [ + "Image exceeds maximum size of 2 MB." + ] + } + }, + "Unreadable image": { + "value": { + "image": "Invalid image. Provide a valid base64-encoded JPG or PNG." + } + }, + "No face": { + "value": { + "image": "No face detected in the image. Please upload a clear photo with a visible face." + } + }, + "No usable face": { + "value": { + "image": "Could not extract face embedding. Try a different image." + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "404": { + "description": "No list with this UUID exists in the API key's application, or it was deleted.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "List not found." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + }, + "503": { + "description": "Face analysis is temporarily unavailable. No entry was created; retry later.", + "content": { + "application/json": { + "examples": { + "Unavailable": { + "value": { + "detail": "Service temporarily unavailable. Please try again later." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/blocklist/": { + "get": { + "summary": "List blocklisted items", + "description": "Returns one paginated feed of the faces, identity documents, phone numbers and email addresses of the application's verification sessions that are currently blocklisted, newest `blocklisted_at` first; filter with `item_type`. Items of deleted sessions are not included. The feed covers records blocklisted from a session (with [Add session items to the blocklist](https://docs.didit.me/management-api/blocklist/add), from the console, or by a list entry created from a session); values added to a list by hand, uploaded faces, faces from a document-capture selfie and the other entry types are read with [List list entries](https://docs.didit.me/management-api/lists/list-entries). `face_image` and `front_image` are short-lived signed URLs, and a scoped API key without `media: read` receives `null`. A scoped API key needs `workflows: read`.", + "operationId": "list_blocklist_items", + "x-didit-sdk": { + "resource": "blocklist", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Blocklist" + ], + "parameters": [ + { + "name": "item_type", + "in": "query", + "required": false, + "description": "Only items of this type. When omitted, every type is returned.", + "schema": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email" + ] + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Page size. Defaults to 50. A value above 1000000 returns `400`; zero, a negative or a non-numeric value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Zero-based index of the first item to return. Prefer following `next`. A value above 1000000 returns `400`; a negative or non-numeric value falls back to 0.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/blocklist/?item_type=document' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.blocklist.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.blocklist.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Blocklist.ListAutoPaging(ctx, didit.BlocklistListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.blocklist().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "blocklist->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.blocklist.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Blocklist.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "A page of blocklisted items, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "description": "Total number of blocklisted items matching the filters." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BlocklistedItem" + } + } + } + }, + "examples": { + "Mixed feed": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "type": "document", + "session_id": "33333333-4444-5555-6666-777777777777", + "session_number": 1042, + "blocklisted_at": "2026-10-12T10:06:12.701553Z", + "face_image": null, + "document_type": "ID", + "document_number": "X1234567", + "front_image": "https://media.example.com/documents/front.jpg?expires=1760263938&signature=8b2e47", + "phone_number": null, + "country_code": null, + "email": null + }, + { + "type": "phone", + "session_id": "77777777-8888-9999-0000-111111111111", + "session_number": 1017, + "blocklisted_at": "2026-10-08T15:31:09.400127Z", + "face_image": null, + "document_type": null, + "document_number": null, + "front_image": null, + "phone_number": "+14155552671", + "country_code": "US", + "email": null + } + ] + } + } + } + } + } + }, + "400": { + "description": "A query parameter is invalid.", + "content": { + "application/json": { + "examples": { + "Unknown item type": { + "value": { + "item_type": [ + "\"wallet\" is not a valid choice." + ] + } + }, + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: read` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no read access to workflows." + } + } + } + } + } + }, + "429": { + "description": "The shared read limit of 600 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "examples": { + "Rate limited": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/blocklist/add/": { + "post": { + "summary": "Add session items to the blocklist", + "description": "Blocklists the face, identity document, phone number or email address of one person verification session of the application. For each selected type, Didit takes the session's most recent record, flags it as blocklisted and adds an entry linked to the session to the matching system blocklist (`added_by: \"api\"`), so new sessions with a matching face, document number, phone number or email address are declined. Every selected item is checked first, so if one is missing nothing is blocklisted; repeating the call for an item already listed keeps its single entry and refreshes `blocklisted_at`. The session itself and sessions that already finished are not changed. A scoped API key needs `workflows: write`.", + "operationId": "add_to_blocklist", + "x-didit-sdk": { + "resource": "blocklist", + "method": "add", + "auth": "api_key", + "server": true + }, + "tags": [ + "Blocklist" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "session_id" + ], + "description": "At least one `blocklist_*` flag must be `true`.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "A person verification session of your application. Deleted sessions are refused." + }, + "blocklist_face": { + "type": "boolean", + "default": false, + "description": "Blocklist the session's face." + }, + "blocklist_document": { + "type": "boolean", + "default": false, + "description": "Blocklist the session's identity document. The document needs its issuing state, document type, document number and full name." + }, + "blocklist_phone": { + "type": "boolean", + "default": false, + "description": "Blocklist the session's phone number." + }, + "blocklist_email": { + "type": "boolean", + "default": false, + "description": "Blocklist the session's email address." + } + } + }, + "examples": { + "Face and document": { + "value": { + "session_id": "33333333-4444-5555-6666-777777777777", + "blocklist_face": true, + "blocklist_document": true + } + }, + "Phone only": { + "value": { + "session_id": "33333333-4444-5555-6666-777777777777", + "blocklist_phone": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/blocklist/add/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"session_id\": \"33333333-4444-5555-6666-777777777777\",\n \"blocklist_face\": true,\n \"blocklist_document\": true\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.blocklist.add(\n session_id=\"33333333-4444-5555-6666-777777777777\",\n blocklist_face=True,\n blocklist_document=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.blocklist.add({\n session_id: \"33333333-4444-5555-6666-777777777777\",\n blocklist_face: true,\n blocklist_document: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Blocklist.Add(ctx, didit.BlocklistAddParams{\n\tSessionID: \"33333333-4444-5555-6666-777777777777\",\n\tBlocklistFace: didit.Bool(true),\n\tBlocklistDocument: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BlocklistAddParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.blocklist().add(BlocklistAddParams.builder()\n .sessionId(\"33333333-4444-5555-6666-777777777777\")\n .blocklistFace(true)\n .blocklistDocument(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "blocklist->add([\n 'session_id' => '33333333-4444-5555-6666-777777777777',\n 'blocklist_face' => true,\n 'blocklist_document' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.blocklist.add(\n session_id: \"33333333-4444-5555-6666-777777777777\",\n blocklist_face: true,\n blocklist_document: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Blocklist.AddAsync(new BlocklistAddParams\n{\n SessionId = \"33333333-4444-5555-6666-777777777777\",\n BlocklistFace = true,\n BlocklistDocument = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The selected items were blocklisted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string", + "description": "Always `Successfully updated blocklist.`" + } + } + }, + "examples": { + "Blocklisted": { + "value": { + "message": "Successfully updated blocklist." + } + } + } + } + } + }, + "400": { + "description": "The body is invalid, the session is not a verification session of this application, or it has no record of a selected type. Nothing was blocklisted.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "value": { + "non_field_errors": [ + "Session not found" + ] + } + }, + "No item selected": { + "value": { + "non_field_errors": [ + "Must specify at least one item to blocklist" + ] + } + }, + "No face record": { + "value": { + "non_field_errors": [ + "Session does not have a face record" + ] + } + }, + "Incomplete document": { + "value": { + "non_field_errors": [ + "To blocklist a document, the document must have an issuing state, document type, document number and full name" + ] + } + }, + "Invalid session id": { + "value": { + "session_id": [ + "Must be a valid UUID." + ] + } + }, + "Missing session id": { + "value": { + "session_id": [ + "This field is required." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/blocklist/remove/": { + "post": { + "summary": "Remove session items from the blocklist", + "description": "Lifts the block on the face, identity document, phone number or email address of one person verification session of the application. For each selected type, Didit takes the session's most recent record, clears its blocklisted flag and deletes the system blocklist entries created from that record, so it no longer declines new sessions. Entries added to a list by value are not touched: find them with [List list entries](https://docs.didit.me/management-api/lists/list-entries) and delete them with [Delete a list entry](https://docs.didit.me/management-api/lists/delete-entry). Unblocking a record that is not blocklisted succeeds and changes nothing, and sessions that already finished are not changed. A scoped API key needs `workflows: write`.", + "operationId": "remove_from_blocklist", + "x-didit-sdk": { + "resource": "blocklist", + "method": "remove", + "auth": "api_key", + "server": true + }, + "tags": [ + "Blocklist" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "session_id" + ], + "description": "At least one `unblock_*` flag must be `true`.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "A person verification session of your application. Deleted sessions are refused." + }, + "unblock_face": { + "type": "boolean", + "default": false, + "description": "Lift the block on the session's face." + }, + "unblock_document": { + "type": "boolean", + "default": false, + "description": "Lift the block on the session's identity document." + }, + "unblock_phone": { + "type": "boolean", + "default": false, + "description": "Lift the block on the session's phone number." + }, + "unblock_email": { + "type": "boolean", + "default": false, + "description": "Lift the block on the session's email address." + } + } + }, + "examples": { + "Face and document": { + "value": { + "session_id": "33333333-4444-5555-6666-777777777777", + "unblock_face": true, + "unblock_document": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/blocklist/remove/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"session_id\": \"33333333-4444-5555-6666-777777777777\",\n \"unblock_face\": true,\n \"unblock_document\": true\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.blocklist.remove(\n session_id=\"33333333-4444-5555-6666-777777777777\",\n unblock_face=True,\n unblock_document=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.blocklist.remove({\n session_id: \"33333333-4444-5555-6666-777777777777\",\n unblock_face: true,\n unblock_document: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Blocklist.Remove(ctx, didit.BlocklistRemoveParams{\n\tSessionID: \"33333333-4444-5555-6666-777777777777\",\n\tUnblockFace: didit.Bool(true),\n\tUnblockDocument: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.BlocklistRemoveParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.blocklist().remove(BlocklistRemoveParams.builder()\n .sessionId(\"33333333-4444-5555-6666-777777777777\")\n .unblockFace(true)\n .unblockDocument(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "blocklist->remove([\n 'session_id' => '33333333-4444-5555-6666-777777777777',\n 'unblock_face' => true,\n 'unblock_document' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.blocklist.remove(\n session_id: \"33333333-4444-5555-6666-777777777777\",\n unblock_face: true,\n unblock_document: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Blocklist.RemoveAsync(new BlocklistRemoveParams\n{\n SessionId = \"33333333-4444-5555-6666-777777777777\",\n UnblockFace = true,\n UnblockDocument = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The block on the selected items was lifted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string", + "description": "Always `Successfully removed from blocklist.`" + } + } + }, + "examples": { + "Unblocked": { + "value": { + "message": "Successfully removed from blocklist." + } + } + } + } + } + }, + "400": { + "description": "The body is invalid, the session is not a verification session of this application, or it has no record of a selected type. Nothing was changed.", + "content": { + "application/json": { + "examples": { + "Unknown session": { + "value": { + "non_field_errors": [ + "Session not found" + ] + } + }, + "No item selected": { + "value": { + "non_field_errors": [ + "Must specify at least one item to unblock" + ] + } + }, + "No email record": { + "value": { + "non_field_errors": [ + "Session does not have an email record" + ] + } + }, + "Invalid session id": { + "value": { + "session_id": [ + "Must be a valid UUID." + ] + } + }, + "Missing session id": { + "value": { + "session_id": [ + "This field is required." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Other authentication failures return `403`.", + "content": { + "application/json": { + "examples": { + "Expired API key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The `x-api-key` header is missing (a wrong, revoked or expired key gets `401`), or a scoped API key lacks `workflows: write` or is used from an IP address outside its allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without workflows access": { + "value": { + "detail": "This API key has no write access to workflows." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/": { + "get": { + "summary": "Get webhook configuration", + "description": "Return the application's webhook settings in one call: every [webhook destination](https://docs.didit.me/management-api/webhook/list-destinations) with its signing secret, the event types a destination can subscribe to, the application-level (legacy) webhook URL, version and secret, the data and biometric-template retention policy, and the Digital ID wallet return hosts. Didit delivers webhooks to destinations, each signed with its own `secret_shared_key`; the top-level `webhook_url`, `webhook_version` and `secret_shared_key` are used only while no destination is enabled, and then only for `status.updated` and `data.updated`. The response contains signing secrets, so keep it out of logs and client-side code. A scoped API key needs `webhooks: read`.", + "operationId": "get_webhook_config", + "x-didit-sdk": { + "resource": "webhooks", + "method": "retrieve_config", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.retrieve_config()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.retrieveConfig();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.RetrieveConfig(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().retrieveConfig();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->retrieveConfig();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.retrieve_config\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.RetrieveConfigAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The webhook configuration.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookConfig" + }, + "examples": { + "One destination": { + "value": { + "webhook_url": "https://yourapp.com/webhooks/didit", + "webhook_version": "v3", + "secret_shared_key": "dGhpcy1pcy10aGUtYXBwLWxldmVsLXNlY3JldC0wMDA", + "capture_method": "MOBILE", + "data_retention_months": 24, + "face_retention_policy": "delete_with_session", + "face_retention_days": null, + "biometric_consent_mode": "auto", + "available_events": [ + { + "value": "status.updated", + "label": "Session status updated" + }, + { + "value": "data.updated", + "label": "Session data updated" + }, + { + "value": "user.status.updated", + "label": "User status updated" + }, + { + "value": "user.data.updated", + "label": "User data updated" + }, + { + "value": "business.status.updated", + "label": "Business status updated" + }, + { + "value": "business.data.updated", + "label": "Business data updated" + }, + { + "value": "activity.created", + "label": "Activity created" + }, + { + "value": "transaction.created", + "label": "Transaction created" + }, + { + "value": "transaction.status.updated", + "label": "Transaction status updated" + }, + { + "value": "travel_rule.status.updated", + "label": "Travel Rule Status Updated" + }, + { + "value": "workflow.compliance_changed", + "label": "Workflow Compliance Changed" + } + ], + "allowed_return_hosts": [ + "shop.example.com" + ], + "endpoints": [ + { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "secret_shared_key": "dGhpcy1pcy1hbi1leGFtcGxlLXNlY3JldC0wMDAwMDA", + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_at": "2026-01-04T08:00:00.000000Z", + "updated_at": "2026-04-22T14:09:11.523671Z" + } + ] + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks read": { + "value": { + "detail": "This API key has no read access to webhooks." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "patch": { + "summary": "Update webhook configuration", + "description": "Partially update application-level settings: the legacy webhook URL, version and secret, the data and biometric-template retention policy, and the Digital ID wallet return hosts. Send only the fields to change; the response is the full configuration, the same shape as `GET /v3/webhook/`. Destinations are not managed here: create, update and delete them with the `/v3/webhook/destinations/` endpoints, and do not send an `endpoints` field to this endpoint. A `data_retention_months` change also applies to existing sessions, and sessions it deletes cannot be restored; read [Data retention](https://docs.didit.me/console/data-retention) first. A scoped API key needs `webhooks: write`.", + "operationId": "update_webhook_config", + "x-didit-sdk": { + "resource": "webhooks", + "method": "update_config", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Send only the fields to change. Destinations are not managed here.", + "properties": { + "webhook_url": { + "type": "string", + "format": "uri", + "nullable": true, + "maxLength": 2000, + "description": "Legacy application-level webhook URL. Didit uses it only while the application has no enabled destination, and then only for `status.updated` and `data.updated`. It does not create a destination, and the next create, update or delete of a destination replaces it. To receive webhooks, create a destination with `POST /v3/webhook/destinations/` instead." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload version of the legacy `webhook_url`." + }, + "rotate_secret_key": { + "type": "boolean", + "default": false, + "description": "`true` replaces the application-level `secret_shared_key` with a new random value at once; the new value is in the response. Destination secrets are not changed. Every call with `true` generates a new secret, so it is not idempotent." + }, + "capture_method": { + "type": "string", + "enum": [ + "MOBILE", + "MOBILE_AND_DESKTOP" + ], + "description": "Legacy application setting. The verification flow does not read it." + }, + "data_retention_months": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 120, + "description": "Automatic deletion window in months (a month counts as 30 days), or `null` for no automatic deletion. A change applies to existing sessions too: every user verification session older than the new window is deleted automatically, and that deletion is irreversible." + }, + "face_retention_policy": { + "type": "string", + "enum": [ + "delete_with_session", + "retain_until_user_deleted" + ], + "description": "What happens to a session's face embedding when the session is deleted. `retain_until_user_deleted` needs `face_retention_days` in the same request or already set. A change applies to session deletions from then on; it creates or purges no existing biometric template." + }, + "face_retention_days": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 3650, + "description": "Retention period, in days, of biometric templates under `retain_until_user_deleted`." + }, + "biometric_consent_mode": { + "type": "string", + "enum": [ + "auto", + "always", + "never" + ], + "description": "When the hosted flow and the native SDKs ask the person for explicit biometric consent before a face capture. `auto` (default) asks only where a trigger applies: a declared state, the person's IP, or the identity document's issuer is in Illinois, Texas or Colorado, or the person looks to be in the US without a known state. `always` asks every person whose workflow captures a face. `never` never shows the step, and you collect consent yourself before the selfie. Each change is recorded in the audit log. See [Biometric consent](https://docs.didit.me/getting-started/biometric-consent)." + }, + "allowed_return_hosts": { + "type": "array", + "maxItems": 20, + "description": "Replaces the whole list of Digital ID wallet return hosts. Bare hostnames only, such as `shop.example.com`: no scheme, port, path or wildcard. Values are lowercased and duplicates are dropped. Send `[]` to clear the list.", + "items": { + "type": "string", + "maxLength": 253 + } + }, + "allowed_domains": { + "type": "array", + "maxItems": 50, + "items": { + "type": "string", + "maxLength": 300 + }, + "description": "Replaces the whole list of allowed origins. Origins (for example `https://app.example.com`; `http` only for `localhost`) the hosted verification flow may be embedded in and may send the user back to with a session callback. Server-to-server calls are not affected. An empty list means no restriction. Didit stores each origin in lower case without a default port. An invalid entry fails with `400` on `allowed_domains`. Send `[]` to remove the restriction.", + "example": [ + "https://app.example.com" + ] + } + } + }, + "examples": { + "Enable biometric template retention": { + "value": { + "face_retention_policy": "retain_until_user_deleted", + "face_retention_days": 365, + "biometric_consent_mode": "auto" + } + }, + "Rotate the legacy secret": { + "value": { + "rotate_secret_key": true + } + }, + "Set data retention": { + "value": { + "data_retention_months": 24 + } + }, + "Register return hosts": { + "value": { + "allowed_return_hosts": [ + "shop.example.com", + "app.example.com" + ] + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/webhook/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"face_retention_policy\": \"retain_until_user_deleted\", \"face_retention_days\": 365}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.update_config(\n face_retention_policy=\"retain_until_user_deleted\",\n face_retention_days=365,\n biometric_consent_mode=\"auto\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.updateConfig({\n face_retention_policy: \"retain_until_user_deleted\",\n face_retention_days: 365,\n biometric_consent_mode: \"auto\",\n});\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.WebhooksUpdateConfigParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().updateConfig(WebhooksUpdateConfigParams.builder()\n .faceRetentionPolicy(WebhooksUpdateConfigParams.FaceRetentionPolicy.of(\"retain_until_user_deleted\"))\n .faceRetentionDays(365L)\n .biometricConsentMode(WebhooksUpdateConfigParams.BiometricConsentMode.of(\"auto\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->updateConfig([\n 'face_retention_policy' => 'retain_until_user_deleted',\n 'face_retention_days' => 365,\n 'biometric_consent_mode' => 'auto',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.update_config(\n face_retention_policy: \"retain_until_user_deleted\",\n face_retention_days: 365,\n biometric_consent_mode: \"auto\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.UpdateConfigAsync(new WebhooksUpdateConfigParams\n{\n FaceRetentionPolicy = new WebhooksUpdateConfigParamsFaceRetentionPolicy(\"retain_until_user_deleted\"),\n FaceRetentionDays = 365,\n BiometricConsentMode = new WebhooksUpdateConfigParamsBiometricConsentMode(\"auto\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The full configuration after the update, the same shape as `GET /v3/webhook/`. When you rotated the secret, store the new `secret_shared_key` before you discard the response.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookConfig" + }, + "examples": { + "Retention enabled": { + "value": { + "webhook_url": "https://yourapp.com/webhooks/didit", + "webhook_version": "v3", + "secret_shared_key": "dGhpcy1pcy10aGUtYXBwLWxldmVsLXNlY3JldC0wMDA", + "capture_method": "MOBILE", + "data_retention_months": 24, + "face_retention_policy": "retain_until_user_deleted", + "face_retention_days": 365, + "biometric_consent_mode": "auto", + "available_events": [ + { + "value": "status.updated", + "label": "Session status updated" + }, + { + "value": "data.updated", + "label": "Session data updated" + }, + { + "value": "user.status.updated", + "label": "User status updated" + }, + { + "value": "user.data.updated", + "label": "User data updated" + }, + { + "value": "business.status.updated", + "label": "Business status updated" + }, + { + "value": "business.data.updated", + "label": "Business data updated" + }, + { + "value": "activity.created", + "label": "Activity created" + }, + { + "value": "transaction.created", + "label": "Transaction created" + }, + { + "value": "transaction.status.updated", + "label": "Transaction status updated" + }, + { + "value": "travel_rule.status.updated", + "label": "Travel Rule Status Updated" + }, + { + "value": "workflow.compliance_changed", + "label": "Workflow Compliance Changed" + } + ], + "allowed_return_hosts": [ + "shop.example.com" + ], + "endpoints": [ + { + "uuid": "11111111-2222-3333-4444-555555555555", + "label": "Production session webhooks", + "url": "https://yourapp.com/webhooks/didit", + "enabled": true, + "webhook_version": "v3", + "secret_shared_key": "dGhpcy1pcy1hbi1leGFtcGxlLXNlY3JldC0wMDAwMDA", + "subscribed_events": [ + "status.updated", + "data.updated" + ], + "created_at": "2026-01-04T08:00:00.000000Z", + "updated_at": "2026-04-22T14:09:11.523671Z" + } + ] + } + } + } + } + } + }, + "400": { + "description": "Validation failed: each invalid field is mapped to a list of messages, and nothing is changed. A body that is not valid JSON returns `{\"detail\": \"JSON parse error - ...\"}`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Each invalid field mapped to a list of messages.", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "Retention days missing": { + "value": { + "face_retention_days": [ + "A finite retention duration is required when face retention is enabled." + ], + "biometric_consent_mode": "auto" + } + }, + "Retention window out of range": { + "value": { + "data_retention_months": [ + "Ensure this value is less than or equal to 120." + ] + } + }, + "Invalid return host": { + "value": { + "allowed_return_hosts": [ + "'https://shop.example.com' is not a hostname. Register the bare host (e.g. shop.example.com): no scheme, port, path or wildcard." + ] + } + }, + "Invalid choice": { + "value": { + "face_retention_policy": [ + "\"forever\" is not a valid choice." + ] + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks write": { + "value": { + "detail": "This API key has no write access to webhooks." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 300 write requests (POST, PATCH, PUT, DELETE) in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (300).", + "schema": { + "type": "integer", + "example": 300 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-write" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/analytics/": { + "get": { + "summary": "Get webhook destination analytics", + "description": "Delivery totals, failure rate and response times for one webhook destination, over the whole range (`summary`) and per UTC day (`points`). Every attempt counts as a delivery: the first send of an event, each automatic [retry](https://docs.didit.me/integration/webhooks#retry-policy) and each resend. A delivery is failed when its recorded HTTP status is 400 or above, so timeouts (recorded as `504`) and connection failures (`503`) count as failed; a delivery still queued counts in `total_deliveries` only. Delivery records are available for about 30 days. A scoped API key needs `webhooks: read`.", + "operationId": "get_webhook_destination_analytics", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "retrieve_analytics", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination (the `uuid` returned by `GET /v3/webhook/destinations/`).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "date_from", + "in": "query", + "required": false, + "description": "First UTC day to include, as `YYYY-MM-DD` (inclusive). Omit to start from the oldest delivery available. `dateFrom` is accepted as an alias.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-10-01" + }, + { + "name": "date_to", + "in": "query", + "required": false, + "description": "Last UTC day to include, as `YYYY-MM-DD` (inclusive). Must not be earlier than `date_from`. `dateTo` is accepted as an alias.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-10-16" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/analytics/?date_from=2026-10-01&date_to=2026-10-16' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.retrieve_analytics(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.retrieveAnalytics(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.RetrieveAnalytics(ctx, \"11111111-2222-3333-4444-555555555555\", didit.WebhooksDestinationsRetrieveAnalyticsParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().retrieveAnalytics(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->retrieveAnalytics('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.destinations.retrieve_analytics(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.RetrieveAnalyticsAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Delivery statistics for the destination.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "summary", + "points" + ], + "properties": { + "summary": { + "type": "object", + "description": "Totals over the requested range.", + "required": [ + "total_deliveries", + "failed_deliveries", + "min_response_time_ms", + "avg_response_time_ms", + "max_response_time_ms", + "error_rate_percentage" + ], + "properties": { + "total_deliveries": { + "type": "integer", + "minimum": 0, + "description": "Delivery attempts, including queued ones." + }, + "failed_deliveries": { + "type": "integer", + "minimum": 0, + "description": "Attempts whose recorded HTTP status is 400 or above." + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Fastest attempt in milliseconds, or `null` when no attempt has a recorded time." + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Mean attempt time in milliseconds, rounded, or `null` when no attempt has a recorded time." + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Slowest attempt in milliseconds, or `null` when no attempt has a recorded time." + }, + "error_rate_percentage": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "`failed_deliveries` / `total_deliveries` x 100, rounded to a whole number. `0` when there are no deliveries." + } + } + }, + "points": { + "type": "array", + "description": "One entry per UTC day that had deliveries, oldest first. Days without deliveries are omitted, so fill the gaps with zeros when you chart them.", + "items": { + "type": "object", + "required": [ + "date", + "total_deliveries", + "failed_deliveries", + "min_response_time_ms", + "avg_response_time_ms", + "max_response_time_ms" + ], + "properties": { + "date": { + "type": "string", + "format": "date", + "description": "UTC day, `YYYY-MM-DD`." + }, + "total_deliveries": { + "type": "integer", + "minimum": 0, + "description": "Delivery attempts, including queued ones." + }, + "failed_deliveries": { + "type": "integer", + "minimum": 0, + "description": "Attempts whose recorded HTTP status is 400 or above." + }, + "min_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Fastest attempt in milliseconds, or `null` when no attempt has a recorded time." + }, + "avg_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Mean attempt time in milliseconds, rounded, or `null` when no attempt has a recorded time." + }, + "max_response_time_ms": { + "type": "integer", + "nullable": true, + "description": "Slowest attempt in milliseconds, or `null` when no attempt has a recorded time." + } + } + } + } + } + }, + "examples": { + "Three days of deliveries": { + "value": { + "summary": { + "total_deliveries": 1250, + "failed_deliveries": 12, + "min_response_time_ms": 38, + "avg_response_time_ms": 164, + "max_response_time_ms": 5004, + "error_rate_percentage": 1 + }, + "points": [ + { + "date": "2026-10-14", + "total_deliveries": 410, + "failed_deliveries": 0, + "min_response_time_ms": 38, + "avg_response_time_ms": 152, + "max_response_time_ms": 911 + }, + { + "date": "2026-10-15", + "total_deliveries": 395, + "failed_deliveries": 12, + "min_response_time_ms": 41, + "avg_response_time_ms": 190, + "max_response_time_ms": 5004 + }, + { + "date": "2026-10-16", + "total_deliveries": 445, + "failed_deliveries": 0, + "min_response_time_ms": 40, + "avg_response_time_ms": 151, + "max_response_time_ms": 1220 + } + ] + } + }, + "No deliveries in range": { + "value": { + "summary": { + "total_deliveries": 0, + "failed_deliveries": 0, + "min_response_time_ms": null, + "avg_response_time_ms": null, + "max_response_time_ms": null, + "error_rate_percentage": 0 + }, + "points": [] + } + } + } + } + } + }, + "400": { + "description": "`date_from` or `date_to` is not a `YYYY-MM-DD` date, or `date_to` is earlier than `date_from`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Each invalid field mapped to a list of messages.", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "Malformed date": { + "value": { + "date_from": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + }, + "Reversed range": { + "value": { + "date_to": [ + "date_to must be greater than or equal to date_from." + ] + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks read": { + "value": { + "detail": "This API key has no read access to webhooks." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No destination with this UUID exists for the application, or it has been deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Destination not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/deliveries/": { + "get": { + "summary": "List webhook destination deliveries", + "description": "Page through every delivery attempt to one webhook destination, newest first. The first send of each event, each automatic [retry](https://docs.didit.me/integration/webhooks#retry-policy) and each resend are separate entries. Entries carry the HTTP status and response time but not the payload: fetch one delivery to read its headers and body. `count` is exact up to 100 and reads 100 above that, so follow `next` until it is `null`. Delivery records are available for about 30 days. A scoped API key needs `webhooks: read` and `decisions: read`; a key limited to some workflows or to approved sessions cannot use this endpoint.", + "operationId": "list_webhook_destination_deliveries", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "list_deliveries", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination (the `uuid` returned by `GET /v3/webhook/destinations/`).", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of deliveries per page. Defaults to 50. A value that is not a positive integer falls back to 50; a value above 1000000 returns `400`.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "example": 50 + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of deliveries to skip. Defaults to 0. Prefer following the `next` and `previous` URLs from the response.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "example": 0 + }, + { + "name": "date_from", + "in": "query", + "required": false, + "description": "First UTC day to include, as `YYYY-MM-DD` (inclusive). Omit to start from the oldest delivery available. `dateFrom` is accepted as an alias.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-10-01" + }, + { + "name": "date_to", + "in": "query", + "required": false, + "description": "Last UTC day to include, as `YYYY-MM-DD` (inclusive). Must not be earlier than `date_from`. `dateTo` is accepted as an alias.", + "schema": { + "type": "string", + "format": "date" + }, + "example": "2026-10-16" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/deliveries/?limit=50&date_from=2026-10-01' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.webhooks.destinations.list_deliveries(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.webhooks.destinations.listDeliveries(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Webhooks.Destinations.ListDeliveriesAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.WebhooksDestinationsListDeliveriesParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.webhooks().destinations().listDeliveries(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->listDeliveries('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.webhooks.destinations.list_deliveries(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Webhooks.Destinations.ListDeliveriesAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "One page of deliveries, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "minimum": 0, + "description": "Matching deliveries, exact up to 100 and capped at 100 above that. Use `next` to know whether more pages exist." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebhookDestinationDelivery" + } + } + } + }, + "examples": { + "Failed delivery, its retry and a queued event": { + "value": { + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "uuid": "66666666-7777-8888-9999-000000000003", + "session_id": null, + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": null, + "response_time_ms": null, + "status": "APPROVED", + "webhook_type": "transaction.created", + "created_at": "2026-10-15T09:23:02.871190Z" + }, + { + "uuid": "66666666-7777-8888-9999-000000000002", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 200, + "response_time_ms": 143, + "status": "In Progress", + "webhook_type": "status.updated", + "created_at": "2026-10-15T09:22:19.104233Z" + }, + { + "uuid": "66666666-7777-8888-9999-000000000001", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 503, + "response_time_ms": 412, + "status": "In Progress", + "webhook_type": "status.updated", + "created_at": "2026-10-15T09:21:18.412345Z" + } + ] + } + } + } + } + } + }, + "400": { + "description": "`date_from` or `date_to` is not a `YYYY-MM-DD` date, `date_to` is earlier than `date_from`, or `limit` or `offset` is above 1000000 (that error is a JSON array of messages).", + "content": { + "application/json": { + "examples": { + "Malformed date": { + "value": { + "date_from": [ + "Date has wrong format. Use one of these formats instead: YYYY-MM-DD." + ] + } + }, + "Reversed range": { + "value": { + "date_to": [ + "date_to must be greater than or equal to date_from." + ] + } + }, + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` A key limited to some workflows or to approved sessions gets `This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks read": { + "value": { + "detail": "This API key has no read access to webhooks." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No destination with this UUID exists for the application, or it has been deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Destination not found": { + "value": { + "detail": "Webhook destination not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/deliveries/{delivery_uuid}/": { + "get": { + "summary": "Get webhook destination delivery", + "description": "Retrieve one delivery attempt to a webhook destination with the exact request headers and JSON body Didit sent, the HTTP status your endpoint returned and the response time. `request_header` and `request_body` are JSON text: parse them to read the signature headers and the payload, for example to check your [signature verification](https://docs.didit.me/integration/webhooks#signature-verification) against the destination's `secret_shared_key`. A delivery never contains the signing secret, and Didit does not record your endpoint's response body. A scoped API key needs `webhooks: read` and `decisions: read`; a key limited to some workflows or to approved sessions cannot use this endpoint.", + "operationId": "get_webhook_destination_delivery", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "retrieve_delivery", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination the delivery was sent to.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "delivery_uuid", + "in": "path", + "required": true, + "description": "UUID of the delivery: the `uuid` of an entry returned by `GET /v3/webhook/destinations/{destination_uuid}/deliveries/`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "66666666-7777-8888-9999-000000000001" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/deliveries/66666666-7777-8888-9999-000000000001/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.retrieve_delivery(\n \"11111111-2222-3333-4444-555555555555\",\n \"66666666-7777-8888-9999-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.retrieveDelivery(\n \"11111111-2222-3333-4444-555555555555\",\n \"66666666-7777-8888-9999-000000000001\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.RetrieveDelivery(ctx, \"11111111-2222-3333-4444-555555555555\", \"66666666-7777-8888-9999-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().retrieveDelivery(\"11111111-2222-3333-4444-555555555555\", \"66666666-7777-8888-9999-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->retrieveDelivery('11111111-2222-3333-4444-555555555555', '66666666-7777-8888-9999-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.destinations.retrieve_delivery(\n \"11111111-2222-3333-4444-555555555555\",\n \"66666666-7777-8888-9999-000000000001\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.RetrieveDeliveryAsync(\"11111111-2222-3333-4444-555555555555\", \"66666666-7777-8888-9999-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The delivery, with its request headers and body.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookDestinationDeliveryDetail" + }, + "examples": { + "Connection failure": { + "value": { + "uuid": "66666666-7777-8888-9999-000000000001", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 503, + "response_time_ms": 412, + "status": "In Progress", + "webhook_type": "status.updated", + "created_at": "2026-10-15T09:21:18.412345Z", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"a5e02a03404e1337a0198e6be15d34abbd053c658a0e51e5d0e78554213ef29b\", \"X-Signature-V2\": \"927e7cb770c94ce745939bff73f04aa200015f3bc64795ba46c2aaf78a6cedc5\", \"X-Signature-Simple\": \"e37b0d4c716c72bb5ae7127b9cc455d28ab4686e9e22312b4ec2327409a73302\", \"X-Timestamp\": \"1792056078\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792056078,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` A key limited to some workflows or to approved sessions gets `This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks read": { + "value": { + "detail": "This API key has no read access to webhooks." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The destination does not exist for the application or has been deleted, or the delivery does not exist, belongs to another destination, or is no longer available.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Destination not found": { + "value": { + "detail": "Webhook destination not found." + } + }, + "Delivery not found": { + "value": { + "detail": "Webhook delivery not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/webhook/destinations/{destination_uuid}/deliveries/{delivery_uuid}/resend/": { + "post": { + "summary": "Resend webhook destination delivery", + "description": "Send a past delivery again, for example after your endpoint was down. Didit takes the stored payload, sets a fresh `timestamp`, signs it again with the destination's current `secret_shared_key` (`X-Signature`, `X-Signature-V2`, `X-Signature-Simple` and `X-Timestamp`) and posts it to the delivery's `request_url`, even if the destination's URL has changed since or the destination is disabled. `event_id` and the rest of the payload are unchanged, so deduplicate on `event_id`. The call waits up to 5 seconds for your endpoint and returns the new delivery with `200` whatever your endpoint answered: read `response_status`. A resend is not retried automatically, every call sends again and creates a new delivery, and the request has no body. A scoped API key needs `webhooks: write` and `decisions: read`; a key limited to some workflows or to approved sessions cannot use this endpoint.", + "operationId": "resend_webhook_destination_delivery", + "x-didit-sdk": { + "resource": "webhooks.destinations", + "method": "resend_delivery", + "auth": "api_key", + "server": true + }, + "tags": [ + "Webhook" + ], + "parameters": [ + { + "name": "destination_uuid", + "in": "path", + "required": true, + "description": "UUID of the destination the delivery was sent to.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "delivery_uuid", + "in": "path", + "required": true, + "description": "UUID of the delivery to resend: the `uuid` of an entry returned by `GET /v3/webhook/destinations/{destination_uuid}/deliveries/`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "66666666-7777-8888-9999-000000000001" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/webhook/destinations/11111111-2222-3333-4444-555555555555/deliveries/66666666-7777-8888-9999-000000000001/resend/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.webhooks.destinations.resend_delivery(\n \"11111111-2222-3333-4444-555555555555\",\n \"66666666-7777-8888-9999-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.webhooks.destinations.resendDelivery(\n \"11111111-2222-3333-4444-555555555555\",\n \"66666666-7777-8888-9999-000000000001\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Webhooks.Destinations.ResendDelivery(ctx, \"11111111-2222-3333-4444-555555555555\", \"66666666-7777-8888-9999-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.webhooks().destinations().resendDelivery(\"11111111-2222-3333-4444-555555555555\", \"66666666-7777-8888-9999-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "webhooks->destinations->resendDelivery('11111111-2222-3333-4444-555555555555', '66666666-7777-8888-9999-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.webhooks.destinations.resend_delivery(\n \"11111111-2222-3333-4444-555555555555\",\n \"66666666-7777-8888-9999-000000000001\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Webhooks.Destinations.ResendDeliveryAsync(\"11111111-2222-3333-4444-555555555555\", \"66666666-7777-8888-9999-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The new delivery, already attempted. `response_status` is what your endpoint answered this time (`504` timeout, `503` connection failure). The original delivery is unchanged.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookDestinationDeliveryDetail" + }, + "examples": { + "Accepted on resend": { + "value": { + "uuid": "66666666-7777-8888-9999-000000000004", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 200, + "response_time_ms": 151, + "status": "In Progress", + "webhook_type": "status.updated", + "created_at": "2026-10-16T14:05:42.330981Z", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"b8eb1e376517ee0cad8ba98ff7f8fd35fc72eca231000963a96a335f82328835\", \"X-Signature-V2\": \"9efd2ce9ed1303e1d05273ef9608a59363ed9789a05253db4748b3e8e50cb109\", \"X-Signature-Simple\": \"bf3a9dc04b2c20ab539203b0cd5cca2a10f630bf62d8feb6d68d82af80f64774\", \"X-Timestamp\": \"1792159542\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792159542,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}" + } + } + } + } + } + }, + "400": { + "description": "The stored delivery could not be signed again. Nothing is sent.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string", + "description": "Stable machine-readable error code. Branch on this, not on `detail`." + } + } + }, + "examples": { + "Unsignable delivery": { + "value": { + "detail": "Invalid webhook data format", + "code": "invalid_webhook_payload" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing, invalid or revoked `x-api-key` gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` A key limited to some workflows or to approved sessions gets `This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks write": { + "value": { + "detail": "This API key has no write access to webhooks." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The destination does not exist for the application or has been deleted, or the delivery does not exist, belongs to another destination, or is no longer available.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Destination not found": { + "value": { + "detail": "Webhook destination not found." + } + }, + "Delivery not found": { + "value": { + "detail": "Webhook delivery not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 300 write requests (POST, PATCH, PUT, DELETE) in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (300).", + "schema": { + "type": "integer", + "example": 300 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-write" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/": { + "get": { + "summary": "List monitoring events", + "description": "List the monitoring events stored for your application, newest `occurred_at` first, with optional filters that combine with AND. Multi-value filters (`event_category`, `action_type`, `status`, `retention_class`) repeat the parameter and match any of the values; the other filters are exact matches, and unknown values simply return no events. Each result is a summary: call `GET /v3/events/{event_uuid}/` for the payload, device context, IP enrichment and rule evidence, and prefer `GET /v3/events/subjects/{vendor_data}/` for one subject's history. Reads work whether or not event ingestion is switched on. A scoped API key needs `transactions: read`. See [Event ingestion](https://docs.didit.me/transaction-monitoring/events).", + "operationId": "list_monitoring_events", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of events per page. Defaults to 50. Values above 1000000 return `400`; a non-numeric, zero or negative value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "example": 50 + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of events to skip. Defaults to 0. Prefer following the `next` URL from the response.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "example": 0 + }, + { + "name": "event_category", + "in": "query", + "required": false, + "description": "Only events in these categories. Repeat the parameter for several values (`?event_category=payment&event_category=dispute`); an event matching any of them is returned.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ] + } + }, + "example": [ + "payment", + "dispute" + ] + }, + { + "name": "action_type", + "in": "query", + "required": false, + "description": "Only events with these action types (exact, case-sensitive). Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "example": [ + "deposit_completed", + "chargeback_opened" + ] + }, + { + "name": "status", + "in": "query", + "required": false, + "description": "Only events with these statuses. Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ] + } + }, + "example": [ + "IN_REVIEW", + "DECLINED" + ] + }, + { + "name": "retention_class", + "in": "query", + "required": false, + "description": "Only events in these retention classes. Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ] + } + }, + "example": [ + "risk_relevant" + ] + }, + { + "name": "subject_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the subject's `vendor_data`. The same id can name an individual and a company; add `subject_entity_type` to pick one, or use `GET /v3/events/subjects/{vendor_data}/`.", + "schema": { + "type": "string" + }, + "example": "user-42" + }, + { + "name": "subject_entity_type", + "in": "query", + "required": false, + "description": "`company` returns only events of company subjects; any other non-empty value (for example `individual`) returns only events of individuals. Omit it to return both.", + "schema": { + "type": "string", + "enum": [ + "individual", + "company" + ] + }, + "example": "individual" + }, + { + "name": "business_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the `business.vendor_data` sent with the event.", + "schema": { + "type": "string" + }, + "example": "merchant-7" + }, + { + "name": "session_id", + "in": "query", + "required": false, + "description": "Exact match on `session.session_id`.", + "schema": { + "type": "string" + }, + "example": "web-session-abc" + }, + { + "name": "device_fingerprint", + "in": "query", + "required": false, + "description": "Exact match on `device_context.fingerprint`.", + "schema": { + "type": "string" + }, + "example": "fp-a8c1" + }, + { + "name": "ip_address", + "in": "query", + "required": false, + "description": "Exact match on the event's IP address.", + "schema": { + "type": "string" + }, + "example": "203.0.113.42" + }, + { + "name": "game_id", + "in": "query", + "required": false, + "description": "Exact match on the promoted `game_id`.", + "schema": { + "type": "string" + }, + "example": "poker-holdem-01" + }, + { + "name": "round_id", + "in": "query", + "required": false, + "description": "Exact match on the promoted `round_id`.", + "schema": { + "type": "string" + }, + "example": "round-88231" + }, + { + "name": "affiliate_code", + "in": "query", + "required": false, + "description": "Exact match on the promoted `affiliate_code`.", + "schema": { + "type": "string" + }, + "example": "aff-summer" + }, + { + "name": "payment_reference_id", + "in": "query", + "required": false, + "description": "Exact match on the payment reference (from `transaction_context.payment_reference_id` or `payload.payment_reference_id`).", + "schema": { + "type": "string" + }, + "example": "dep_735" + }, + { + "name": "counterparty_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the promoted `counterparty_vendor_data`.", + "schema": { + "type": "string" + }, + "example": "user-77" + }, + { + "name": "matched_only", + "in": "query", + "required": false, + "description": "`true` (also `1` or `yes`, case-insensitive) returns only events at least one rule matched (`rules_matched_count` above 0). Any other value is ignored.", + "schema": { + "type": "boolean", + "default": false + }, + "example": true + }, + { + "name": "occurred_from", + "in": "query", + "required": false, + "description": "Only events with `occurred_at` at or after this instant (inclusive). Send an ISO 8601 value: `2026-10-01T00:00:00Z`, an explicit offset with the `+` percent-encoded (`2026-10-01T02:00:00%2B02:00`; an unencoded `+` is decoded as a space and the value no longer parses), or a date alone (`2026-10-01`), which means 00:00:00 UTC of that day. A value without an offset is read as UTC. Values in any other format are not accepted.", + "schema": { + "type": "string", + "format": "date-time" + }, + "example": "2026-10-01T00:00:00Z" + }, + { + "name": "occurred_to", + "in": "query", + "required": false, + "description": "Only events with `occurred_at` at or before this instant (inclusive). A date alone means the start of that day, so `occurred_to=2026-10-15` excludes the rest of 15 October; send `2026-10-15T23:59:59Z` to include it. Send an ISO 8601 value: `2026-10-01T00:00:00Z`, an explicit offset with the `+` percent-encoded (`2026-10-01T02:00:00%2B02:00`; an unencoded `+` is decoded as a space and the value no longer parses), or a date alone (`2026-10-01`), which means 00:00:00 UTC of that day. A value without an offset is read as UTC. Values in any other format are not accepted.", + "schema": { + "type": "string", + "format": "date-time" + }, + "example": "2026-10-17T23:59:59Z" + }, + { + "name": "search", + "in": "query", + "required": false, + "description": "Case-insensitive substring match on `event_id`, `action_type`, `subject_vendor_data` or `counterparty_vendor_data`.", + "schema": { + "type": "string" + }, + "example": "chargeback" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/events/?event_category=payment&event_category=dispute&matched_only=true&occurred_from=2026-10-01T00:00:00Z&limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.monitoring_events.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.monitoringEvents.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.MonitoringEvents.ListAutoPaging(ctx, didit.MonitoringEventsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.monitoringEvents().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.monitoring_events.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.MonitoringEvents.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "One page of event summaries.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventPage" + }, + "examples": { + "Payment and dispute events": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "event_uuid": "11111111-2222-3333-4444-555555555555", + "event_id": "chargeback-2026-10-16-000318", + "event_category": "dispute", + "action_type": "chargeback_opened", + "occurred_at": "2026-10-16T14:02:11Z", + "received_at": "2026-10-16T14:02:12.418230Z", + "status": "IN_REVIEW", + "score": 60, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": null, + "device_fingerprint": null, + "ip_address": null, + "ip_country": null, + "amount": "250.000000000000000000", + "currency": "EUR", + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": "dep_735", + "retention_class": "monetary_or_compliance", + "rules_matched_count": 1, + "correlation_pending": false, + "is_droppable": false + }, + { + "event_uuid": "22222222-3333-4444-5555-666666666666", + "event_id": "login-2026-10-15-000912", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-15T08:30:00.120000Z", + "received_at": "2026-10-15T08:30:00.734512Z", + "status": "ACCEPTED", + "score": 0, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": "web-session-abc", + "device_fingerprint": "fp-a8c1", + "ip_address": "203.0.113.42", + "ip_country": "ES", + "amount": null, + "currency": null, + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": null, + "retention_class": "standard", + "rules_matched_count": 0, + "correlation_pending": false, + "is_droppable": true + } + ] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is larger than 1000000. The body is a JSON array with one message. A non-numeric or non-positive `limit` falls back to the default of 50 instead of failing.", + "content": { + "application/json": { + "examples": { + "limit too large": { + "summary": "limit above 1000000", + "value": [ + "limit must not exceed 1000000." + ] + }, + "offset too large": { + "summary": "offset above 1000000", + "value": [ + "offset must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: read`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: `GET` requests share a budget of 600 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create monitoring event", + "description": "Send one non-monetary event (login, session, gameplay, bonus, affiliate, payment, dispute, security, responsible gambling or custom) from your backend. The event is validated against its category contract, stored, IP-enriched once per unique public address, and evaluated by your real-time event rules before the response returns; cross-subject and long-window rules run asynchronously (`correlation_pending: true`), and in a sandbox application no rules run. `event_id` is the idempotency key: the same `event_id` with the same `event_category`, `action_type`, `occurred_at`, `subject.vendor_data` and `payload` returns `200` with `idempotent_replay: true` and the stored outcome (other envelope fields are not compared), while different values return `409`. Event ingestion is opt-in per application: until it is switched on this endpoint returns `403` with `code: event_monitoring_disabled` and stores nothing. A scoped API key needs `transactions: write`. See [Event ingestion](https://docs.didit.me/transaction-monitoring/events) and [Rules on events](https://docs.didit.me/transaction-monitoring/event-rules); to send events from a browser or app, use [SDK event submission](https://docs.didit.me/transaction-monitoring/sdk-event-submission).", + "operationId": "create_monitoring_event", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventCreateRequest" + }, + "examples": { + "Login": { + "value": { + "event_id": "login-2026-10-17-000001", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-17T09:15:02.481Z", + "time_zone": "Europe/Madrid", + "subject": { + "vendor_data": "user-42", + "entity_type": "individual" + }, + "session": { + "session_id": "web-session-abc", + "sequence": 142 + }, + "device_context": { + "fingerprint": "fp-a8c1", + "network_context": { + "ip_address": "203.0.113.42" + } + }, + "payload": { + "method": "password" + }, + "custom_values": { + "vip_tier": "gold" + } + } + }, + "Deposit completed": { + "value": { + "event_id": "deposit-2026-10-17-000002", + "event_category": "payment", + "action_type": "deposit_completed", + "occurred_at": "2026-10-17T09:16:40Z", + "subject": { + "vendor_data": "user-42" + }, + "transaction_context": { + "amount": "100.00", + "currency": "EUR", + "direction": "inbound", + "payment_reference_id": "dep_736" + }, + "payload": { + "payment_method": "card", + "status": "completed" + } + } + }, + "Chargeback opened": { + "value": { + "event_id": "chargeback-2026-10-16-000318", + "event_category": "dispute", + "action_type": "chargeback_opened", + "occurred_at": "2026-10-16T14:02:11Z", + "subject": { + "vendor_data": "user-42" + }, + "payload": { + "payment_reference_id": "dep_735", + "chargeback_id": "cb_5521", + "amount": "250.00", + "currency": "EUR", + "reason_code": "fraud" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/events/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"event_id\": \"login-2026-10-17-000001\",\n \"event_category\": \"auth\",\n \"action_type\": \"login\",\n \"occurred_at\": \"2026-10-17T09:15:02.481Z\",\n \"subject\": {\"vendor_data\": \"user-42\", \"entity_type\": \"individual\"},\n \"session\": {\"session_id\": \"web-session-abc\", \"sequence\": 142},\n \"device_context\": {\n \"fingerprint\": \"fp-a8c1\",\n \"network_context\": {\"ip_address\": \"203.0.113.42\"}\n },\n \"payload\": {\"method\": \"password\"}\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.monitoring_events.create(\n event_id=\"login-2026-10-17-000001\",\n event_category=\"auth\",\n action_type=\"login\",\n occurred_at=\"2026-10-17T09:15:02.481Z\",\n time_zone=\"Europe/Madrid\",\n subject={\n \"vendor_data\": \"user-42\",\n \"entity_type\": \"individual\",\n },\n session={\n \"session_id\": \"web-session-abc\",\n \"sequence\": 142,\n },\n device_context={\n \"fingerprint\": \"fp-a8c1\",\n \"network_context\": {\n \"ip_address\": \"203.0.113.42\",\n },\n },\n payload={\"method\": \"password\"},\n custom_values={\"vip_tier\": \"gold\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.monitoringEvents.create({\n event_id: \"login-2026-10-17-000001\",\n event_category: \"auth\",\n action_type: \"login\",\n occurred_at: \"2026-10-17T09:15:02.481Z\",\n time_zone: \"Europe/Madrid\",\n subject: {\n vendor_data: \"user-42\",\n entity_type: \"individual\",\n },\n session: {\n session_id: \"web-session-abc\",\n sequence: 142,\n },\n device_context: {\n fingerprint: \"fp-a8c1\",\n network_context: {\n ip_address: \"203.0.113.42\",\n },\n },\n payload: { method: \"password\" },\n custom_values: { vip_tier: \"gold\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.MonitoringEvents.Create(ctx, didit.MonitoringEventCreateRequest{\n\tEventID: \"login-2026-10-17-000001\",\n\tEventCategory: \"auth\",\n\tActionType: \"login\",\n\tOccurredAt: time.Date(2026, 10, 17, 9, 15, 2, 0, time.UTC),\n\tTimeZone: didit.String(\"Europe/Madrid\"),\n\tSubject: didit.MonitoringEventCreateRequestSubject{\n\t\tVendorData: \"user-42\",\n\t\tEntityType: didit.String(\"individual\"),\n\t},\n\tSession: didit.Some(didit.MonitoringEventCreateRequestSession{\n\t\tSessionID: didit.String(\"web-session-abc\"),\n\t\tSequence: didit.Int(142),\n\t}),\n\tDeviceContext: didit.Some(didit.MonitoringEventCreateRequestDeviceContext{\n\t\tFingerprint: didit.String(\"fp-a8c1\"),\n\t\tNetworkContext: didit.Some(didit.MonitoringEventCreateRequestDeviceContextNetworkContext{\n\t\t\tIPAddress: didit.String(\"203.0.113.42\"),\n\t\t}),\n\t}),\n\tPayload: didit.Some(map[string]any{\"method\": \"password\"}),\n\tCustomValues: didit.Some(map[string]any{\"vip_tier\": \"gold\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.OffsetDateTime;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.MonitoringEventCreateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.monitoringEvents().create(MonitoringEventCreateRequest.builder()\n .eventId(\"login-2026-10-17-000001\")\n .eventCategory(MonitoringEventCreateRequest.EventCategory.of(\"auth\"))\n .actionType(\"login\")\n .occurredAt(OffsetDateTime.parse(\"2026-10-17T09:15:02.481Z\"))\n .timeZone(\"Europe/Madrid\")\n .subject(MonitoringEventCreateRequest.Subject.builder()\n .vendorData(\"user-42\")\n .entityType(\"individual\")\n .build())\n .session(MonitoringEventCreateRequest.Session.builder()\n .sessionId(\"web-session-abc\")\n .sequence(142L)\n .build())\n .deviceContext(MonitoringEventCreateRequest.DeviceContext.builder()\n .fingerprint(\"fp-a8c1\")\n .networkContext(MonitoringEventCreateRequest.DeviceContext.NetworkContext.builder()\n .ipAddress(\"203.0.113.42\")\n .build())\n .build())\n .payload(Map.of(\"method\", \"password\"))\n .customValues(Map.of(\"vip_tier\", MonitoringEventCreateRequest.CustomValuesValue.ofString(\"gold\")))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->create([\n 'event_id' => 'login-2026-10-17-000001',\n 'event_category' => 'auth',\n 'action_type' => 'login',\n 'occurred_at' => '2026-10-17T09:15:02.481Z',\n 'time_zone' => 'Europe/Madrid',\n 'subject' => [\n 'vendor_data' => 'user-42',\n 'entity_type' => 'individual',\n ],\n 'session' => [\n 'session_id' => 'web-session-abc',\n 'sequence' => 142,\n ],\n 'device_context' => [\n 'fingerprint' => 'fp-a8c1',\n 'network_context' => [\n 'ip_address' => '203.0.113.42',\n ],\n ],\n 'payload' => ['method' => 'password'],\n 'custom_values' => ['vip_tier' => 'gold'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.monitoring_events.create(\n event_id: \"login-2026-10-17-000001\",\n event_category: \"auth\",\n action_type: \"login\",\n occurred_at: \"2026-10-17T09:15:02.481Z\",\n time_zone: \"Europe/Madrid\",\n subject: {\n vendor_data: \"user-42\",\n entity_type: \"individual\",\n },\n session: {\n session_id: \"web-session-abc\",\n sequence: 142,\n },\n device_context: {\n fingerprint: \"fp-a8c1\",\n network_context: {\n ip_address: \"203.0.113.42\",\n },\n },\n payload: { method: \"password\" },\n custom_values: { vip_tier: \"gold\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.MonitoringEvents.CreateAsync(new MonitoringEventCreateRequest\n{\n EventId = \"login-2026-10-17-000001\",\n EventCategory = \"auth\",\n ActionType = \"login\",\n OccurredAt = DateTimeOffset.Parse(\"2026-10-17T09:15:02.481Z\"),\n TimeZone = \"Europe/Madrid\",\n Subject = new()\n {\n VendorData = \"user-42\",\n EntityType = \"individual\",\n },\n Session = new MonitoringEventCreateRequestSession\n {\n SessionId = \"web-session-abc\",\n Sequence = 142,\n },\n DeviceContext = new MonitoringEventCreateRequestDeviceContext\n {\n Fingerprint = \"fp-a8c1\",\n NetworkContext = new MonitoringEventCreateRequestDeviceContextNetworkContext\n {\n IpAddress = \"203.0.113.42\",\n },\n },\n Payload = new Dictionary { [\"method\"] = \"password\" },\n CustomValues = new Dictionary { [\"vip_tier\"] = \"gold\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The event was stored and evaluated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventIngestResult" + }, + "examples": { + "Stored, no rule matched": { + "value": { + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "standard", + "expires_at": "2026-11-16T09:15:02.481000Z", + "idempotent_replay": false, + "rule_matches": [] + } + }, + "Rule matched, async rule pending": { + "value": { + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "IN_REVIEW", + "score": 40, + "rules_evaluated_count": 6, + "rules_matched_count": 1, + "correlation_pending": true, + "retention_class": "risk_relevant", + "expires_at": "2027-01-15T09:15:02.481000Z", + "idempotent_replay": false, + "rule_matches": [ + { + "rule_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "library_key": null, + "mode": "ACTIVE", + "is_async": false + } + ] + } + } + } + } + } + }, + "200": { + "description": "Idempotent replay: an event with this `event_id` and the same content is already stored. Nothing was stored or evaluated again; the body describes the stored event.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventIngestResult" + }, + "examples": { + "Replay": { + "value": { + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "standard", + "expires_at": "2026-11-16T09:15:02.481000Z", + "idempotent_replay": true, + "rule_matches": [] + } + } + } + } + } + }, + "400": { + "description": "Validation failed. The body is keyed by field; nested fields are nested objects. Nothing is stored.", + "content": { + "application/json": { + "examples": { + "Missing fields": { + "value": { + "event_id": [ + "This field is required." + ], + "subject": [ + "This field is required." + ] + } + }, + "Unknown category": { + "value": { + "event_category": [ + "\"login\" is not a valid choice." + ] + } + }, + "occurred_at too old": { + "value": { + "occurred_at": [ + "occurred_at is more than 7 days in the past. Use the batch import lane for backfills." + ] + } + }, + "occurred_at in the future": { + "value": { + "occurred_at": [ + "occurred_at is more than 5 minutes in the future." + ] + } + }, + "Missing subject vendor_data": { + "value": { + "subject": { + "vendor_data": [ + "This field is required." + ] + } + } + }, + "Missing required payload keys": { + "value": { + "payload": [ + "payment.deposit_completed requires: payment_method, status." + ] + } + }, + "Missing one-of payload key": { + "value": { + "payload": [ + "dispute.chargeback_opened requires one of: dispute_id, chargeback_id." + ] + } + }, + "Security change without changed_field": { + "value": { + "payload": [ + "security.*_changed requires 'changed_field'." + ] + } + }, + "Payload too large": { + "value": { + "payload": [ + "payload exceeds 16384 bytes. Summarise telemetry client-side." + ] + } + }, + "Too many custom_values": { + "value": { + "custom_values": [ + "custom_values accepts at most 100 keys." + ] + } + }, + "Nested custom value": { + "value": { + "custom_values": [ + "custom_values['limits'] must be a scalar - rule conditions cannot address nested values." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: write`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist. Also returned with `code: event_monitoring_disabled` while event ingestion is switched off for the application; nothing is stored in that case, and the check runs before the body is validated.", + "content": { + "application/json": { + "examples": { + "Event monitoring disabled": { + "value": { + "detail": "Event monitoring is not enabled for this application. Turn on event ingestion in the transaction monitoring settings before submitting events.", + "code": "event_monitoring_disabled" + } + }, + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "409": { + "description": "`event_id` was already used for an event with different content (category, action type, `occurred_at`, subject `vendor_data` or payload). Send the event under a new `event_id`.", + "content": { + "application/json": { + "examples": { + "event_id reused": { + "value": { + "event_id": "event_id 'login-2026-10-17-000001' already exists with a different payload." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: transaction creates and event ingestion (`POST /v3/transactions/`, `POST /v3/events/`, `POST /v3/events/batch/`) share a budget of 600 requests per minute per API key, separate from the 300 shared by every other write request. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying, rounded up so the retry lands in the next window.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Ingestion rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/batch/": { + "post": { + "summary": "Create monitoring events in batch", + "description": "Submit 1 to 500 events in one request, each in the same envelope as `POST /v3/events/`. Every item is validated, stored and evaluated on its own and gets its own outcome (`accepted`, `replayed` or `rejected`), so one malformed event never rolls back the others: the response is `201` when no item was rejected and `207` when at least one was, and both mean the request was processed. Send `{\"events\": [...]}`; a bare JSON array of events is also accepted. Event ingestion must be switched on for the application, otherwise the whole request returns `403` with `code: event_monitoring_disabled` and nothing is stored. A scoped API key needs `transactions: write`. See [Batches](https://docs.didit.me/transaction-monitoring/events#batches).", + "operationId": "create_monitoring_events_batch", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "create_batch", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "events" + ], + "properties": { + "events": { + "type": "array", + "minItems": 1, + "maxItems": 500, + "description": "1 to 500 events. Each item follows `MonitoringEventCreateRequest`, including the 7-day past and 5-minute future window on `occurred_at`.", + "items": { + "$ref": "#/components/schemas/MonitoringEventCreateRequest" + } + } + } + }, + "example": { + "events": [ + { + "event_id": "login-2026-10-17-000001", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-17T09:15:02.481Z", + "subject": { + "vendor_data": "user-42" + } + }, + { + "event_id": "deposit-2026-10-17-000002", + "event_category": "payment", + "action_type": "deposit_completed", + "occurred_at": "2026-10-17T09:16:40Z", + "subject": { + "vendor_data": "user-42" + }, + "transaction_context": { + "amount": "100.00", + "currency": "EUR", + "payment_reference_id": "dep_736" + }, + "payload": { + "payment_method": "card", + "status": "completed" + } + } + ] + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/events/batch/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"events\": [\n {\"event_id\": \"login-2026-10-17-000001\", \"event_category\": \"auth\", \"action_type\": \"login\",\n \"occurred_at\": \"2026-10-17T09:15:02.481Z\", \"subject\": {\"vendor_data\": \"user-42\"}},\n {\"event_id\": \"deposit-2026-10-17-000002\", \"event_category\": \"payment\", \"action_type\": \"deposit_completed\",\n \"occurred_at\": \"2026-10-17T09:16:40.000Z\", \"subject\": {\"vendor_data\": \"user-42\"},\n \"transaction_context\": {\"amount\": \"100.00\", \"currency\": \"EUR\", \"payment_reference_id\": \"dep_736\"},\n \"payload\": {\"payment_method\": \"card\", \"status\": \"completed\"}}\n ]\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.monitoring_events.create_batch(\n events=[{\n \"event_id\": \"login-2026-10-17-000001\",\n \"event_category\": \"auth\",\n \"action_type\": \"login\",\n \"occurred_at\": \"2026-10-17T09:15:02.481Z\",\n \"subject\": {\n \"vendor_data\": \"user-42\",\n },\n }, {\n \"event_id\": \"deposit-2026-10-17-000002\",\n \"event_category\": \"payment\",\n \"action_type\": \"deposit_completed\",\n \"occurred_at\": \"2026-10-17T09:16:40Z\",\n \"subject\": {\n \"vendor_data\": \"user-42\",\n },\n \"transaction_context\": {\n \"amount\": \"100.00\",\n \"currency\": \"EUR\",\n \"payment_reference_id\": \"dep_736\",\n },\n \"payload\": {\"payment_method\": \"card\", \"status\": \"completed\"},\n }],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.monitoringEvents.createBatch({\n events: [{\n event_id: \"login-2026-10-17-000001\",\n event_category: \"auth\",\n action_type: \"login\",\n occurred_at: \"2026-10-17T09:15:02.481Z\",\n subject: {\n vendor_data: \"user-42\",\n },\n }, {\n event_id: \"deposit-2026-10-17-000002\",\n event_category: \"payment\",\n action_type: \"deposit_completed\",\n occurred_at: \"2026-10-17T09:16:40Z\",\n subject: {\n vendor_data: \"user-42\",\n },\n transaction_context: {\n amount: \"100.00\",\n currency: \"EUR\",\n payment_reference_id: \"dep_736\",\n },\n payload: { payment_method: \"card\", status: \"completed\" },\n }],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.MonitoringEvents.CreateBatch(ctx, didit.MonitoringEventsCreateBatchParams{\n\tEvents: []didit.MonitoringEventCreateRequest{{\n\t\tEventID: \"login-2026-10-17-000001\",\n\t\tEventCategory: \"auth\",\n\t\tActionType: \"login\",\n\t\tOccurredAt: time.Date(2026, 10, 17, 9, 15, 2, 0, time.UTC),\n\t\tSubject: didit.MonitoringEventCreateRequestSubject{\n\t\t\tVendorData: \"user-42\",\n\t\t},\n\t}, {\n\t\tEventID: \"deposit-2026-10-17-000002\",\n\t\tEventCategory: \"payment\",\n\t\tActionType: \"deposit_completed\",\n\t\tOccurredAt: time.Date(2026, 10, 17, 9, 16, 40, 0, time.UTC),\n\t\tSubject: didit.MonitoringEventCreateRequestSubject{\n\t\t\tVendorData: \"user-42\",\n\t\t},\n\t\tTransactionContext: didit.Some(didit.MonitoringEventCreateRequestTransactionContext{\n\t\t\tAmount: didit.String(\"100.00\"),\n\t\t\tCurrency: didit.String(\"EUR\"),\n\t\t\tPaymentReferenceID: didit.String(\"dep_736\"),\n\t\t}),\n\t\tPayload: didit.Some(map[string]any{\"payment_method\": \"card\", \"status\": \"completed\"}),\n\t}},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.OffsetDateTime;\nimport java.util.List;\nimport java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.MonitoringEventCreateRequest;\nimport me.didit.types.MonitoringEventsCreateBatchParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.monitoringEvents().createBatch(MonitoringEventsCreateBatchParams.builder()\n .events(List.of(\n MonitoringEventCreateRequest.builder()\n .eventId(\"login-2026-10-17-000001\")\n .eventCategory(MonitoringEventCreateRequest.EventCategory.of(\"auth\"))\n .actionType(\"login\")\n .occurredAt(OffsetDateTime.parse(\"2026-10-17T09:15:02.481Z\"))\n .subject(MonitoringEventCreateRequest.Subject.builder()\n .vendorData(\"user-42\")\n .build())\n .build(),\n MonitoringEventCreateRequest.builder()\n .eventId(\"deposit-2026-10-17-000002\")\n .eventCategory(MonitoringEventCreateRequest.EventCategory.of(\"payment\"))\n .actionType(\"deposit_completed\")\n .occurredAt(OffsetDateTime.parse(\"2026-10-17T09:16:40Z\"))\n .subject(MonitoringEventCreateRequest.Subject.builder()\n .vendorData(\"user-42\")\n .build())\n .transactionContext(MonitoringEventCreateRequest.TransactionContext.builder()\n .amount(\"100.00\")\n .currency(\"EUR\")\n .paymentReferenceId(\"dep_736\")\n .build())\n .payload(Map.of(\"payment_method\", \"card\", \"status\", \"completed\"))\n .build()))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->createBatch([\n 'events' => [[\n 'event_id' => 'login-2026-10-17-000001',\n 'event_category' => 'auth',\n 'action_type' => 'login',\n 'occurred_at' => '2026-10-17T09:15:02.481Z',\n 'subject' => [\n 'vendor_data' => 'user-42',\n ],\n ], [\n 'event_id' => 'deposit-2026-10-17-000002',\n 'event_category' => 'payment',\n 'action_type' => 'deposit_completed',\n 'occurred_at' => '2026-10-17T09:16:40Z',\n 'subject' => [\n 'vendor_data' => 'user-42',\n ],\n 'transaction_context' => [\n 'amount' => '100.00',\n 'currency' => 'EUR',\n 'payment_reference_id' => 'dep_736',\n ],\n 'payload' => ['payment_method' => 'card', 'status' => 'completed'],\n ]],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.monitoring_events.create_batch(\n events: [{\n event_id: \"login-2026-10-17-000001\",\n event_category: \"auth\",\n action_type: \"login\",\n occurred_at: \"2026-10-17T09:15:02.481Z\",\n subject: {\n vendor_data: \"user-42\",\n },\n }, {\n event_id: \"deposit-2026-10-17-000002\",\n event_category: \"payment\",\n action_type: \"deposit_completed\",\n occurred_at: \"2026-10-17T09:16:40Z\",\n subject: {\n vendor_data: \"user-42\",\n },\n transaction_context: {\n amount: \"100.00\",\n currency: \"EUR\",\n payment_reference_id: \"dep_736\",\n },\n payload: { payment_method: \"card\", status: \"completed\" },\n }]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.MonitoringEvents.CreateBatchAsync(new MonitoringEventsCreateBatchParams\n{\n Events = new MonitoringEventCreateRequest[]\n {\n new()\n {\n EventId = \"login-2026-10-17-000001\",\n EventCategory = \"auth\",\n ActionType = \"login\",\n OccurredAt = DateTimeOffset.Parse(\"2026-10-17T09:15:02.481Z\"),\n Subject = new()\n {\n VendorData = \"user-42\",\n },\n },\n new()\n {\n EventId = \"deposit-2026-10-17-000002\",\n EventCategory = \"payment\",\n ActionType = \"deposit_completed\",\n OccurredAt = DateTimeOffset.Parse(\"2026-10-17T09:16:40Z\"),\n Subject = new()\n {\n VendorData = \"user-42\",\n },\n TransactionContext = new MonitoringEventCreateRequestTransactionContext\n {\n Amount = \"100.00\",\n Currency = \"EUR\",\n PaymentReferenceId = \"dep_736\",\n },\n Payload = new Dictionary { [\"payment_method\"] = \"card\", [\"status\"] = \"completed\" },\n },\n },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "No item was rejected: every item is `accepted` or `replayed`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "accepted_count", + "replayed_count", + "rejected_count", + "results" + ], + "properties": { + "accepted_count": { + "type": "integer", + "minimum": 0, + "description": "Items stored and evaluated." + }, + "replayed_count": { + "type": "integer", + "minimum": 0, + "description": "Items already stored with the same content." + }, + "rejected_count": { + "type": "integer", + "minimum": 0, + "description": "Items not stored." + }, + "results": { + "type": "array", + "description": "One entry per item, in request order.", + "items": { + "type": "object", + "description": "Outcome of one item. Accepted and replayed items also carry every field of `MonitoringEventIngestResult`; rejected items carry `event_id` and `errors` instead.", + "required": [ + "index", + "outcome" + ], + "properties": { + "index": { + "type": "integer", + "minimum": 0, + "description": "Position of the item in your `events` array." + }, + "outcome": { + "type": "string", + "enum": [ + "accepted", + "replayed", + "rejected" + ], + "description": "`accepted`: stored and evaluated. `replayed`: already stored with the same content. `rejected`: not stored; see `errors`." + }, + "event_id": { + "type": "string", + "nullable": true, + "description": "Your `event_id`. On items rejected by validation it is read from the item's snake_case `event_id` key, so it is null when the item omitted it or sent the `eventId` alias." + }, + "errors": { + "type": "object", + "additionalProperties": true, + "description": "Rejected items only. Validation errors keyed by field, in the same shape as the `400` of `POST /v3/events/`; a reused `event_id` with different content is reported as a string under `errors.event_id`." + }, + "event_uuid": { + "type": "string", + "format": "uuid", + "description": "Accepted and replayed items only." + }, + "status": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ], + "description": "Accepted and replayed items only." + }, + "score": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "rules_evaluated_count": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "rules_matched_count": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "correlation_pending": { + "type": "boolean", + "description": "Accepted and replayed items only." + }, + "retention_class": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ], + "description": "Accepted and replayed items only." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Accepted and replayed items only." + }, + "idempotent_replay": { + "type": "boolean", + "description": "Accepted and replayed items only." + }, + "rule_matches": { + "type": "array", + "description": "Accepted and replayed items only.", + "items": { + "type": "object", + "required": [ + "rule_id", + "library_key", + "mode", + "is_async" + ], + "properties": { + "rule_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the rule." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Library key when the rule was installed from the rules library, otherwise null." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "TEST" + ], + "description": "`TEST` rules record the match but apply no actions." + }, + "is_async": { + "type": "boolean", + "description": "`true` when the match came from the asynchronous pass." + } + } + } + } + } + } + } + } + }, + "examples": { + "All landed": { + "value": { + "accepted_count": 1, + "replayed_count": 1, + "rejected_count": 0, + "results": [ + { + "index": 0, + "outcome": "accepted", + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "standard", + "expires_at": "2026-11-16T09:15:02.481000Z", + "idempotent_replay": false, + "rule_matches": [] + }, + { + "index": 1, + "outcome": "replayed", + "event_uuid": "11111111-2222-3333-4444-555555555555", + "event_id": "deposit-2026-10-17-000002", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "monetary_or_compliance", + "expires_at": null, + "idempotent_replay": true, + "rule_matches": [] + } + ] + } + } + } + } + } + }, + "207": { + "description": "At least one item was rejected. The other items were stored; fix and resend only the rejected ones (resending an accepted item is safe, it comes back `replayed`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "accepted_count", + "replayed_count", + "rejected_count", + "results" + ], + "properties": { + "accepted_count": { + "type": "integer", + "minimum": 0, + "description": "Items stored and evaluated." + }, + "replayed_count": { + "type": "integer", + "minimum": 0, + "description": "Items already stored with the same content." + }, + "rejected_count": { + "type": "integer", + "minimum": 0, + "description": "Items not stored." + }, + "results": { + "type": "array", + "description": "One entry per item, in request order.", + "items": { + "type": "object", + "description": "Outcome of one item. Accepted and replayed items also carry every field of `MonitoringEventIngestResult`; rejected items carry `event_id` and `errors` instead.", + "required": [ + "index", + "outcome" + ], + "properties": { + "index": { + "type": "integer", + "minimum": 0, + "description": "Position of the item in your `events` array." + }, + "outcome": { + "type": "string", + "enum": [ + "accepted", + "replayed", + "rejected" + ], + "description": "`accepted`: stored and evaluated. `replayed`: already stored with the same content. `rejected`: not stored; see `errors`." + }, + "event_id": { + "type": "string", + "nullable": true, + "description": "Your `event_id`. On items rejected by validation it is read from the item's snake_case `event_id` key, so it is null when the item omitted it or sent the `eventId` alias." + }, + "errors": { + "type": "object", + "additionalProperties": true, + "description": "Rejected items only. Validation errors keyed by field, in the same shape as the `400` of `POST /v3/events/`; a reused `event_id` with different content is reported as a string under `errors.event_id`." + }, + "event_uuid": { + "type": "string", + "format": "uuid", + "description": "Accepted and replayed items only." + }, + "status": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ], + "description": "Accepted and replayed items only." + }, + "score": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "rules_evaluated_count": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "rules_matched_count": { + "type": "integer", + "description": "Accepted and replayed items only." + }, + "correlation_pending": { + "type": "boolean", + "description": "Accepted and replayed items only." + }, + "retention_class": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ], + "description": "Accepted and replayed items only." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Accepted and replayed items only." + }, + "idempotent_replay": { + "type": "boolean", + "description": "Accepted and replayed items only." + }, + "rule_matches": { + "type": "array", + "description": "Accepted and replayed items only.", + "items": { + "type": "object", + "required": [ + "rule_id", + "library_key", + "mode", + "is_async" + ], + "properties": { + "rule_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the rule." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Library key when the rule was installed from the rules library, otherwise null." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "TEST" + ], + "description": "`TEST` rules record the match but apply no actions." + }, + "is_async": { + "type": "boolean", + "description": "`true` when the match came from the asynchronous pass." + } + } + } + } + } + } + } + } + }, + "examples": { + "Partial success": { + "value": { + "accepted_count": 1, + "replayed_count": 0, + "rejected_count": 2, + "results": [ + { + "index": 0, + "outcome": "accepted", + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "status": "ACCEPTED", + "score": 0, + "rules_evaluated_count": 6, + "rules_matched_count": 0, + "correlation_pending": false, + "retention_class": "standard", + "expires_at": "2026-11-16T09:15:02.481000Z", + "idempotent_replay": false, + "rule_matches": [] + }, + { + "index": 1, + "event_id": "deposit-2026-10-17-000002", + "outcome": "rejected", + "errors": { + "payload": [ + "payment.deposit_completed requires: payment_method, status." + ] + } + }, + { + "index": 2, + "event_id": "login-2026-10-16-000777", + "outcome": "rejected", + "errors": { + "event_id": "event_id 'login-2026-10-16-000777' already exists with a different payload." + } + } + ] + } + } + } + } + } + }, + "400": { + "description": "The batch itself is malformed: `events` is missing or not an array, is empty, or has more than 500 items. Nothing is stored; split large batches.", + "content": { + "application/json": { + "examples": { + "Not a list": { + "value": { + "events": "Expected a list of events." + } + }, + "Empty": { + "value": { + "events": "At least one event is required." + } + }, + "Too many": { + "value": { + "events": "A batch accepts at most 500 events; received 612." + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: write`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist. Also returned with `code: event_monitoring_disabled` while event ingestion is switched off for the application; nothing is stored in that case, and the check runs before the body is validated.", + "content": { + "application/json": { + "examples": { + "Event monitoring disabled": { + "value": { + "detail": "Event monitoring is not enabled for this application. Turn on event ingestion in the transaction monitoring settings before submitting events.", + "code": "event_monitoring_disabled" + } + }, + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: transaction creates and event ingestion (`POST /v3/transactions/`, `POST /v3/events/`, `POST /v3/events/batch/`) share a budget of 600 requests per minute per API key, separate from the 300 shared by every other write request. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying, rounded up so the retry lands in the next window.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Ingestion rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/sdk-token/": { + "post": { + "summary": "Mint monitoring SDK token", + "description": "Mint a short-lived device credential bound to one subject, so your web or mobile app can send that user's events straight to Didit without an API key. The app sends the returned `monitoring_token` in the `X-Monitoring-Token` header to the device lane (`POST /v1/events/` and `POST /v1/events/batch/`), where the subject is always overwritten with this `vendor_data`, events outside `allowed_event_categories` are rejected, each submitted event spends one use, and a request whose `Origin` does not equal `bind_to_origin` is refused. Call it from your backend only; minting creates the credential and nothing else (no user is created). Event ingestion must be switched on for the application, otherwise this endpoint returns `403` with `code: event_monitoring_disabled` instead of minting a credential that could never write. A scoped API key needs `transactions: write`. See [SDK event submission](https://docs.didit.me/transaction-monitoring/sdk-event-submission).", + "operationId": "create_monitoring_event_sdk_token", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "create_sdk_token", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "camelCase aliases are accepted: `vendorData`, `entityType`, `allowedEventCategories`, `ttlSeconds`, `maxUses`, `bindToOrigin`.", + "required": [ + "vendor_data" + ], + "properties": { + "vendor_data": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "Your id for the subject every event submitted with this credential belongs to." + }, + "entity_type": { + "type": "string", + "maxLength": 32, + "default": "individual", + "description": "`company` binds a company subject; any other value (default `individual`) binds an individual." + }, + "allowed_event_categories": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ] + }, + "description": "Categories the credential may submit. Omit it or send `[]` to allow every category." + }, + "ttl_seconds": { + "type": "integer", + "minimum": 60, + "maximum": 86400, + "default": 900, + "description": "Lifetime of the credential in seconds." + }, + "max_uses": { + "type": "integer", + "minimum": 1, + "nullable": true, + "default": 1000, + "description": "Number of events the credential may submit (a batch spends one use per event). Send `null` for no cap within the lifetime." + }, + "bind_to_origin": { + "type": "string", + "maxLength": 255, + "nullable": true, + "description": "When set, the browser `Origin` header of every device request must equal this value exactly; a request without `Origin` is refused." + } + } + }, + "example": { + "vendor_data": "user-42", + "allowed_event_categories": [ + "auth", + "session", + "gameplay" + ], + "ttl_seconds": 900, + "max_uses": 1000, + "bind_to_origin": "https://app.example.com" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/events/sdk-token/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"vendor_data\": \"user-42\",\n \"allowed_event_categories\": [\"auth\", \"session\", \"gameplay\"],\n \"ttl_seconds\": 900,\n \"max_uses\": 1000,\n \"bind_to_origin\": \"https://app.example.com\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.monitoring_events.create_sdk_token(\n vendor_data=\"user-42\",\n allowed_event_categories=[\"auth\", \"session\", \"gameplay\"],\n ttl_seconds=900,\n max_uses=1000,\n bind_to_origin=\"https://app.example.com\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.monitoringEvents.createSdkToken({\n vendor_data: \"user-42\",\n allowed_event_categories: [\"auth\", \"session\", \"gameplay\"],\n ttl_seconds: 900,\n max_uses: 1000,\n bind_to_origin: \"https://app.example.com\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.MonitoringEvents.CreateSDKToken(ctx, didit.MonitoringEventsCreateSDKTokenParams{\n\tVendorData: \"user-42\",\n\tAllowedEventCategories: didit.Some([]didit.MonitoringEventsCreateSDKTokenParamsAllowedEventCategories{\"auth\", \"session\", \"gameplay\"}),\n\tTTLSeconds: didit.Int(900),\n\tMaxUses: didit.Int(1000),\n\tBindToOrigin: didit.String(\"https://app.example.com\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.MonitoringEventsCreateSdkTokenParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.monitoringEvents().createSdkToken(MonitoringEventsCreateSdkTokenParams.builder()\n .vendorData(\"user-42\")\n .allowedEventCategories(List.of(MonitoringEventsCreateSdkTokenParams.AllowedEventCategories.of(\"auth\"), MonitoringEventsCreateSdkTokenParams.AllowedEventCategories.of(\"session\"), MonitoringEventsCreateSdkTokenParams.AllowedEventCategories.of(\"gameplay\")))\n .ttlSeconds(900L)\n .maxUses(1000L)\n .bindToOrigin(\"https://app.example.com\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->createSdkToken([\n 'vendor_data' => 'user-42',\n 'allowed_event_categories' => ['auth', 'session', 'gameplay'],\n 'ttl_seconds' => 900,\n 'max_uses' => 1000,\n 'bind_to_origin' => 'https://app.example.com',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.monitoring_events.create_sdk_token(\n vendor_data: \"user-42\",\n allowed_event_categories: [\"auth\", \"session\", \"gameplay\"],\n ttl_seconds: 900,\n max_uses: 1000,\n bind_to_origin: \"https://app.example.com\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.MonitoringEvents.CreateSdkTokenAsync(new MonitoringEventsCreateSdkTokenParams\n{\n VendorData = \"user-42\",\n AllowedEventCategories = new MonitoringEventsCreateSdkTokenParamsAllowedEventCategories[] { \"auth\", \"session\", \"gameplay\" },\n TtlSeconds = 900,\n MaxUses = 1000,\n BindToOrigin = \"https://app.example.com\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The credential was minted. Hand `monitoring_token` to the app and treat it as a secret until `expires_at`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "monitoring_token", + "expires_at", + "allowed_event_categories", + "max_uses", + "bind_to_origin" + ], + "properties": { + "monitoring_token": { + "type": "string", + "description": "The device credential (a 43-character URL-safe token). Send it in the `X-Monitoring-Token` header." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the credential stops being accepted." + }, + "allowed_event_categories": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ] + }, + "description": "Categories the credential may submit; `[]` means every category." + }, + "max_uses": { + "type": "integer", + "nullable": true, + "description": "Event budget of the credential; null means no cap." + }, + "bind_to_origin": { + "type": "string", + "nullable": true, + "description": "Origin the credential is bound to, or null." + } + } + }, + "example": { + "monitoring_token": "q7ZtR2xKp9VbN4mWc8LdH1sYfJ3gT6uEaQ0oPiXzBkC", + "expires_at": "2026-10-17T09:30:02.481000Z", + "allowed_event_categories": [ + "auth", + "session", + "gameplay" + ], + "max_uses": 1000, + "bind_to_origin": "https://app.example.com" + } + } + } + }, + "400": { + "description": "Validation failed: missing `vendor_data`, `ttl_seconds` outside 60 to 86400, `max_uses` below 1, or an unknown category (keyed by its position in the array).", + "content": { + "application/json": { + "examples": { + "Missing vendor_data": { + "value": { + "vendor_data": [ + "This field is required." + ] + } + }, + "ttl_seconds too short": { + "value": { + "ttl_seconds": [ + "Ensure this value is greater than or equal to 60." + ] + } + }, + "Unknown category": { + "value": { + "allowed_event_categories": { + "1": [ + "\"login\" is not a valid choice." + ] + } + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: write`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist. Also returned with `code: event_monitoring_disabled` while event ingestion is switched off for the application; nothing is stored in that case, and the check runs before the body is validated.", + "content": { + "application/json": { + "examples": { + "Event monitoring disabled": { + "value": { + "detail": "Event monitoring is not enabled for this application. Turn on event ingestion in the transaction monitoring settings before submitting events.", + "code": "event_monitoring_disabled" + } + }, + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: write requests (`POST`, `PATCH`, `DELETE`) share a budget of 300 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/{event_uuid}/": { + "get": { + "summary": "Get monitoring event", + "description": "Retrieve one monitoring event with everything the list returns plus the payload, custom values, device context, field provenance (`field_sources`), the promoted domain fields, `expires_at`, every matching rule run with its evidence, the IP enrichment and the tags rules attached. While `correlation_pending` is `true` an asynchronous rule still has to run; read the event again a few minutes later for the final `status`, `score` and `rule_runs`. Reads work whether or not event ingestion is switched on. A scoped API key needs `transactions: read`. See [Evidence](https://docs.didit.me/transaction-monitoring/event-rules#evidence) and [IP enrichment](https://docs.didit.me/transaction-monitoring/ip-enrichment).", + "operationId": "get_monitoring_event", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "event_uuid", + "in": "path", + "required": true, + "description": "Didit identifier of the event (`event_uuid` from the create, batch, list or timeline response), not your own `event_id`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/events/11111111-2222-3333-4444-555555555555/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.monitoring_events.retrieve(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.monitoringEvents.retrieve(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.MonitoringEvents.Retrieve(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.monitoringEvents().retrieve(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->retrieve('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.monitoring_events.retrieve(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.MonitoringEvents.RetrieveAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The event with its evidence.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventDetail" + }, + "examples": { + "Login from a VPN, in review": { + "value": { + "event_uuid": "33333333-4444-5555-6666-777777777777", + "event_id": "login-2026-10-17-000001", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-17T09:15:02.481000Z", + "received_at": "2026-10-17T09:15:02.903114Z", + "status": "IN_REVIEW", + "score": 40, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": "web-session-abc", + "device_fingerprint": "fp-a8c1", + "ip_address": "203.0.113.42", + "ip_country": "ES", + "amount": null, + "currency": null, + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": null, + "retention_class": "risk_relevant", + "rules_matched_count": 1, + "correlation_pending": false, + "is_droppable": true, + "payload": { + "method": "password" + }, + "custom_values": { + "vip_tier": "gold", + "vpn_or_proxy_detected": true + }, + "device_context": { + "fingerprint": "fp-a8c1", + "network_context": { + "ip_address": "203.0.113.42", + "ip_country": "Spain", + "ip_country_code": "ES", + "ip_city": "Madrid", + "asn_number": 64500, + "asn_organization": "Example Networks", + "is_vpn_or_tor": true, + "is_data_center": false + } + }, + "field_sources": { + "subject.vendor_data": "customer", + "event_category": "customer", + "action_type": "customer", + "occurred_at": "customer", + "received_at": "server", + "payload.method": "customer", + "session.session_id": "customer", + "session.sequence": "customer", + "device_context.fingerprint": "customer", + "device_context.network_context.ip_address": "customer", + "device_context.network_context.ip_country": "server", + "device_context.network_context.ip_country_code": "server", + "device_context.network_context.ip_city": "server", + "device_context.network_context.asn_number": "server", + "device_context.network_context.asn_organization": "server", + "device_context.network_context.is_vpn_or_tor": "server", + "device_context.network_context.is_data_center": "server" + }, + "session_sequence": 142, + "table_id": null, + "hand_id": null, + "match_id": null, + "market_id": null, + "side": null, + "role": null, + "net_result_amount": null, + "payment_fingerprint": null, + "instrument_reference_hash": null, + "withdrawal_address_hash": null, + "dispute_id": null, + "chargeback_id": null, + "changed_field": null, + "identity_match_ref": null, + "schema_version": 1, + "expires_at": "2027-01-15T09:15:02.481000Z", + "rules_evaluated_count": 6, + "rule_runs": [ + { + "rule_uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "rule_title": "Login from VPN or Tor", + "library_key": null, + "matched": true, + "mode": "ACTIVE", + "is_async": false, + "severity": "MEDIUM", + "score_delta": 40, + "action_summary": "score +40; add_tag:VPN_LOGIN", + "matched_payload": [ + { + "field": "event.ip.is_vpn_or_tor", + "operator": "eq", + "expected_value": true, + "current_value": true, + "matched": true, + "group_index": null, + "group_logic": "AND", + "field_source": "server" + } + ], + "created_at": "2026-10-17T09:15:02.951207Z" + } + ], + "ip_enrichment": { + "ip_country_code": "ES", + "ip_city": "Madrid", + "asn_number": 64500, + "asn_organization": "Example Networks", + "is_vpn_or_tor": true, + "is_data_center": false, + "proxy_type": null, + "last_enriched_at": "2026-10-17T09:15:02.910442Z" + }, + "subject_profile": null, + "tags": [ + "VPN_LOGIN" + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: read`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No event with this `event_uuid` exists in the application: it was never stored, it was deleted, or it belongs to another application. A path segment that is not a UUID does not match the route and also returns `404`.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No MonitoringEvent matches the given query." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: `GET` requests share a budget of 600 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/subjects/{vendor_data}/": { + "get": { + "summary": "Get subject event timeline", + "description": "List every monitoring event of one subject, newest `occurred_at` first. A `vendor_data` is unique only within an entity type, so the same id can name an individual and a company: `entity_type` picks which timeline you get, and it defaults to `individual`, so a company's events need `entity_type=company`. The filters of `GET /v3/events/` apply on top, and a `vendor_data` with no events returns an empty page rather than `404`. A scoped API key needs `transactions: read`. See [Reading events back](https://docs.didit.me/transaction-monitoring/events#reading-events-back).", + "operationId": "get_monitoring_event_subject_timeline", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "retrieve_subject_timeline", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "vendor_data", + "in": "path", + "required": true, + "description": "Your id for the subject (`subject.vendor_data` of its events). URL-encode it; a value containing `/` cannot be addressed by this endpoint.", + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "example": "user-42" + }, + { + "name": "entity_type", + "in": "query", + "required": false, + "description": "`company` returns the company with this `vendor_data`; any other value, or none, returns the individual. `subject_entity_type` is accepted as an alias when `entity_type` is absent.", + "schema": { + "type": "string", + "enum": [ + "individual", + "company" + ], + "default": "individual" + }, + "example": "individual" + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of events per page. Defaults to 50. Values above 1000000 return `400`; a non-numeric, zero or negative value falls back to 50.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "example": 50 + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of events to skip. Defaults to 0. Prefer following the `next` URL from the response.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "example": 0 + }, + { + "name": "event_category", + "in": "query", + "required": false, + "description": "Only events in these categories. Repeat the parameter for several values (`?event_category=payment&event_category=dispute`); an event matching any of them is returned.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ] + } + }, + "example": [ + "payment", + "dispute" + ] + }, + { + "name": "action_type", + "in": "query", + "required": false, + "description": "Only events with these action types (exact, case-sensitive). Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "example": [ + "deposit_completed", + "chargeback_opened" + ] + }, + { + "name": "status", + "in": "query", + "required": false, + "description": "Only events with these statuses. Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ] + } + }, + "example": [ + "IN_REVIEW", + "DECLINED" + ] + }, + { + "name": "retention_class", + "in": "query", + "required": false, + "description": "Only events in these retention classes. Repeat for several values.", + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ] + } + }, + "example": [ + "risk_relevant" + ] + }, + { + "name": "business_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the `business.vendor_data` sent with the event.", + "schema": { + "type": "string" + }, + "example": "merchant-7" + }, + { + "name": "session_id", + "in": "query", + "required": false, + "description": "Exact match on `session.session_id`.", + "schema": { + "type": "string" + }, + "example": "web-session-abc" + }, + { + "name": "device_fingerprint", + "in": "query", + "required": false, + "description": "Exact match on `device_context.fingerprint`.", + "schema": { + "type": "string" + }, + "example": "fp-a8c1" + }, + { + "name": "ip_address", + "in": "query", + "required": false, + "description": "Exact match on the event's IP address.", + "schema": { + "type": "string" + }, + "example": "203.0.113.42" + }, + { + "name": "game_id", + "in": "query", + "required": false, + "description": "Exact match on the promoted `game_id`.", + "schema": { + "type": "string" + }, + "example": "poker-holdem-01" + }, + { + "name": "round_id", + "in": "query", + "required": false, + "description": "Exact match on the promoted `round_id`.", + "schema": { + "type": "string" + }, + "example": "round-88231" + }, + { + "name": "affiliate_code", + "in": "query", + "required": false, + "description": "Exact match on the promoted `affiliate_code`.", + "schema": { + "type": "string" + }, + "example": "aff-summer" + }, + { + "name": "payment_reference_id", + "in": "query", + "required": false, + "description": "Exact match on the payment reference (from `transaction_context.payment_reference_id` or `payload.payment_reference_id`).", + "schema": { + "type": "string" + }, + "example": "dep_735" + }, + { + "name": "counterparty_vendor_data", + "in": "query", + "required": false, + "description": "Exact match on the promoted `counterparty_vendor_data`.", + "schema": { + "type": "string" + }, + "example": "user-77" + }, + { + "name": "matched_only", + "in": "query", + "required": false, + "description": "`true` (also `1` or `yes`, case-insensitive) returns only events at least one rule matched (`rules_matched_count` above 0). Any other value is ignored.", + "schema": { + "type": "boolean", + "default": false + }, + "example": true + }, + { + "name": "occurred_from", + "in": "query", + "required": false, + "description": "Only events with `occurred_at` at or after this instant (inclusive). Send an ISO 8601 value: `2026-10-01T00:00:00Z`, an explicit offset with the `+` percent-encoded (`2026-10-01T02:00:00%2B02:00`; an unencoded `+` is decoded as a space and the value no longer parses), or a date alone (`2026-10-01`), which means 00:00:00 UTC of that day. A value without an offset is read as UTC. Values in any other format are not accepted.", + "schema": { + "type": "string", + "format": "date-time" + }, + "example": "2026-10-01T00:00:00Z" + }, + { + "name": "occurred_to", + "in": "query", + "required": false, + "description": "Only events with `occurred_at` at or before this instant (inclusive). A date alone means the start of that day, so `occurred_to=2026-10-15` excludes the rest of 15 October; send `2026-10-15T23:59:59Z` to include it. Send an ISO 8601 value: `2026-10-01T00:00:00Z`, an explicit offset with the `+` percent-encoded (`2026-10-01T02:00:00%2B02:00`; an unencoded `+` is decoded as a space and the value no longer parses), or a date alone (`2026-10-01`), which means 00:00:00 UTC of that day. A value without an offset is read as UTC. Values in any other format are not accepted.", + "schema": { + "type": "string", + "format": "date-time" + }, + "example": "2026-10-17T23:59:59Z" + }, + { + "name": "search", + "in": "query", + "required": false, + "description": "Case-insensitive substring match on `event_id`, `action_type`, `subject_vendor_data` or `counterparty_vendor_data`.", + "schema": { + "type": "string" + }, + "example": "chargeback" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/events/subjects/user-42/?entity_type=individual&limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.monitoring_events.retrieve_subject_timeline(\n \"user-42\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.monitoringEvents.retrieveSubjectTimeline(\"user-42\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.MonitoringEvents.RetrieveSubjectTimelineAutoPaging(ctx, \"user-42\", didit.MonitoringEventsRetrieveSubjectTimelineParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.monitoringEvents().retrieveSubjectTimeline(\"user-42\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->retrieveSubjectTimeline('user-42') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.monitoring_events.retrieve_subject_timeline(\"user-42\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.MonitoringEvents.RetrieveSubjectTimelineAsync(\"user-42\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "One page of the subject's events.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringEventPage" + }, + "examples": { + "Individual timeline": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "event_uuid": "11111111-2222-3333-4444-555555555555", + "event_id": "chargeback-2026-10-16-000318", + "event_category": "dispute", + "action_type": "chargeback_opened", + "occurred_at": "2026-10-16T14:02:11Z", + "received_at": "2026-10-16T14:02:12.418230Z", + "status": "IN_REVIEW", + "score": 60, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": null, + "device_fingerprint": null, + "ip_address": null, + "ip_country": null, + "amount": "250.000000000000000000", + "currency": "EUR", + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": "dep_735", + "retention_class": "monetary_or_compliance", + "rules_matched_count": 1, + "correlation_pending": false, + "is_droppable": false + }, + { + "event_uuid": "22222222-3333-4444-5555-666666666666", + "event_id": "login-2026-10-15-000912", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-15T08:30:00.120000Z", + "received_at": "2026-10-15T08:30:00.734512Z", + "status": "ACCEPTED", + "score": 0, + "subject_vendor_data": "user-42", + "subject_entity_type": "individual", + "subject_uuid": "66666666-7777-8888-9999-000000000000", + "counterparty_vendor_data": null, + "session_id": "web-session-abc", + "device_fingerprint": "fp-a8c1", + "ip_address": "203.0.113.42", + "ip_country": "ES", + "amount": null, + "currency": null, + "game_id": null, + "round_id": null, + "bonus_id": null, + "affiliate_code": null, + "payment_reference_id": null, + "retention_class": "standard", + "rules_matched_count": 0, + "correlation_pending": false, + "is_droppable": true + } + ] + } + }, + "No events": { + "value": { + "count": 0, + "next": null, + "previous": null, + "results": [] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is larger than 1000000. The body is a JSON array with one message. A non-numeric or non-positive `limit` falls back to the default of 50 instead of failing.", + "content": { + "application/json": { + "examples": { + "limit too large": { + "summary": "limit above 1000000", + "value": [ + "limit must not exceed 1000000." + ] + }, + "offset too large": { + "summary": "offset above 1000000", + "value": [ + "offset must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: read`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: `GET` requests share a budget of 600 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/enrichment-metrics/": { + "get": { + "summary": "Get event enrichment metrics", + "description": "Return the event ingestion and IP-enrichment counters of your application for a period, summed over every lane (server, batch and device), so you can watch event volume and confirm that enrichment is resolved once per unique address rather than once per event. The period covers today plus the previous `days` days, counted by UTC calendar date. The three ratios are null when their denominator is zero. A scoped API key needs `transactions: read`. See [IP enrichment](https://docs.didit.me/transaction-monitoring/events#ip-enrichment).", + "operationId": "get_monitoring_event_enrichment_metrics", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "retrieve_enrichment_metrics", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "days", + "in": "query", + "required": false, + "description": "Length of the period in days. Defaults to 30; values are clamped to 1 to 365, and a non-integer value falls back to 30. The value applied is returned as `period_days`.", + "schema": { + "type": "integer", + "default": 30, + "minimum": 1, + "maximum": 365 + }, + "example": 30 + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/events/enrichment-metrics/?days=30' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.monitoring_events.retrieve_enrichment_metrics()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.monitoringEvents.retrieveEnrichmentMetrics();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.MonitoringEvents.RetrieveEnrichmentMetrics(ctx, didit.MonitoringEventsRetrieveEnrichmentMetricsParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.monitoringEvents().retrieveEnrichmentMetrics();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->retrieveEnrichmentMetrics();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.monitoring_events.retrieve_enrichment_metrics\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.MonitoringEvents.RetrieveEnrichmentMetricsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Counters and ratios for the period.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "events_ingested", + "events_rejected", + "enrichment_lookups", + "enrichment_cache_hits", + "enrichment_provider_calls", + "enrichment_skipped_low_value", + "unique_ips_seen", + "period_days", + "cache_hit_rate", + "unique_ip_ratio", + "provider_calls_per_1k_events" + ], + "properties": { + "events_ingested": { + "type": "integer", + "description": "New events stored. Idempotent replays are not counted." + }, + "events_rejected": { + "type": "integer", + "description": "Items rejected inside batch requests (server and device lanes). A rejected single-event request is not counted." + }, + "enrichment_lookups": { + "type": "integer", + "description": "Events whose public IP address was resolved, from the cache or freshly." + }, + "enrichment_cache_hits": { + "type": "integer", + "description": "Lookups answered from the cache." + }, + "enrichment_provider_calls": { + "type": "integer", + "description": "Lookups that needed a fresh resolution of the address." + }, + "enrichment_skipped_low_value": { + "type": "integer", + "description": "Low-value telemetry events whose address was not already known. They never trigger a fresh resolution of their own." + }, + "unique_ips_seen": { + "type": "integer", + "description": "Addresses that needed a fresh resolution." + }, + "period_days": { + "type": "integer", + "description": "The `days` value applied after clamping." + }, + "cache_hit_rate": { + "type": "number", + "nullable": true, + "description": "`enrichment_cache_hits / enrichment_lookups`, rounded to 4 decimals. Null when there were no lookups." + }, + "unique_ip_ratio": { + "type": "number", + "nullable": true, + "description": "`unique_ips_seen / events_ingested`, rounded to 4 decimals. Null when no events were ingested." + }, + "provider_calls_per_1k_events": { + "type": "number", + "nullable": true, + "description": "Fresh resolutions per 1000 ingested events, rounded to 2 decimals. Null when no events were ingested." + } + } + }, + "examples": { + "Last 30 days": { + "value": { + "events_ingested": 128450, + "events_rejected": 214, + "enrichment_lookups": 41230, + "enrichment_cache_hits": 40118, + "enrichment_provider_calls": 1112, + "enrichment_skipped_low_value": 74320, + "unique_ips_seen": 1112, + "period_days": 30, + "cache_hit_rate": 0.973, + "unique_ip_ratio": 0.0087, + "provider_calls_per_1k_events": 8.66 + } + }, + "No events yet": { + "value": { + "events_ingested": 0, + "events_rejected": 0, + "enrichment_lookups": 0, + "enrichment_cache_hits": 0, + "enrichment_provider_calls": 0, + "enrichment_skipped_low_value": 0, + "unique_ips_seen": 0, + "period_days": 30, + "cache_hit_rate": null, + "unique_ip_ratio": null, + "provider_calls_per_1k_events": null + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: read`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: `GET` requests share a budget of 600 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/events/{event_uuid}/delete/": { + "delete": { + "summary": "Delete monitoring event", + "description": "Permanently delete one monitoring event together with its rule runs, notes and tag links, for example an event sent by mistake. After the `204` the event is gone from every read (retrieve returns `404`, and it no longer appears in the list or the subject timeline), the deletion is irreversible, and its `event_id` is free to be used for a new event. Not affected: the user or company the event resolved to, cases a rule opened, list entries a rule added, and the scores and statuses already applied to other events. To erase everything about a person or a company, delete the user or the company instead, which also deletes their events. A scoped API key needs `transactions: write`.", + "operationId": "delete_monitoring_event", + "x-didit-sdk": { + "resource": "monitoring_events", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Monitoring Events" + ], + "parameters": [ + { + "name": "event_uuid", + "in": "path", + "required": true, + "description": "Didit identifier of the event (`event_uuid` from the create, batch, list or timeline response), not your own `event_id`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "11111111-2222-3333-4444-555555555555" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE 'https://verification.didit.me/v3/events/11111111-2222-3333-4444-555555555555/delete/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.monitoring_events.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.monitoringEvents.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.MonitoringEvents.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.monitoringEvents().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "monitoringEvents->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.monitoring_events.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.MonitoringEvents.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ], + "responses": { + "204": { + "description": "The event was deleted. No body." + }, + "401": { + "description": "The scoped API key has expired. Only an expired key gets `401`; a missing or invalid key gets `403`.", + "content": { + "application/json": { + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "A missing `x-api-key` (a wrong, revoked or expired key gets `401`), a scoped key without `transactions: write`, a key limited to some workflows or to approved sessions (such a key never reaches transaction monitoring data), or a request from outside the key's IP allowlist.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without access": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "IP not allowed": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No event with this `event_uuid` exists in the application: it was never stored, it was deleted, or it belongs to another application. A path segment that is not a UUID does not match the route and also returns `404`.", + "content": { + "application/json": { + "examples": { + "Not found": { + "value": { + "detail": "No MonitoringEvent matches the given query." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: write requests (`POST`, `PATCH`, `DELETE`) share a budget of 300 requests per minute per API key. Wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + }, + "content": { + "application/json": { + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/kyb/parties/{partyUuid}/regenerate/": { + "post": { + "summary": "Regenerate a Key People party's KYC session", + "description": "Give a Key People party of a Business Verification (KYB) session a new individual KYC session after its previous one was deleted or expired before the person started it, so the Key People check can finish again; send no request body. It works when the party's KYC session was deleted (`kyc_session_deleted: true`) or expired before the person started it (`Expired`, never started). A session still in progress or one that ended in any other status (`Approved`, `Declined`, `In Review`, `Abandoned`, ...) returns `409` unless it was deleted. A party that never had one or a company party always returns `409`. Didit creates a new hosted KYC session with the workflow the party was verified with (its latest published version) or, when that is no longer known, the workflow your Key People settings assign to the party's role, so its link lasts that KYC workflow's session expiration time; it emails the link to the party when an email is on file, clears `is_skipped`, and recomputes the Key People check and the business session status. It then sends a `data.updated` [webhook](https://docs.didit.me/integration/webhooks) for the business session with trigger `console_key_people_regenerate`; to exempt the party instead, use `PATCH /v3/session/{sessionId}/kyb/parties/{partyUuid}/`. A scoped API key needs `decisions: write`, and without `sessions: write` it receives `kyc_session_url` as `null`. Deleted sessions qualify even if the person had already started or finished verification. Imported sessions do not qualify through expiry; they must have been deleted.", + "operationId": "post_v3_session_kyb_party_regenerate", + "x-didit-sdk": { + "resource": "sessions", + "method": "regenerate_kyb_party", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the parent Business Verification (KYB) session.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "partyUuid", + "in": "path", + "required": true, + "description": "UUID of the Key People party (`key_people_checks[].submitted.parties[].uuid` on the business session decision). Must belong to the session referenced by `sessionId`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/session/33333333-4444-5555-6666-777777777777/kyb/parties/0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41/regenerate/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.regenerate_kyb_party(\n \"33333333-4444-5555-6666-777777777777\",\n \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.regenerateKybParty(\n \"33333333-4444-5555-6666-777777777777\",\n \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.RegenerateKYBParty(ctx, \"33333333-4444-5555-6666-777777777777\", \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().regenerateKybParty(\"33333333-4444-5555-6666-777777777777\", \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->regenerateKybParty('33333333-4444-5555-6666-777777777777', '0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.regenerate_kyb_party(\n \"33333333-4444-5555-6666-777777777777\",\n \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.RegenerateKybPartyAsync(\"33333333-4444-5555-6666-777777777777\", \"0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "New KYC session created. Returns the updated party with its new child session, which expires after its KYC workflow's session expiration time.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBKeyPeopleParty" + }, + "example": { + "uuid": "0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41", + "entity_type": "person", + "source": "USER", + "name": "Jane Smith", + "first_name": "Jane", + "last_name": "Smith", + "company_name": null, + "registration_number": null, + "email": "user@example.com", + "nationality": "GB", + "phone_number": null, + "date_of_birth": "1980-05-04", + "position": "Director", + "custom_fields": {}, + "is_active": true, + "is_skipped": false, + "requires_verification": true, + "roles": [ + { + "role": "director", + "ownership_percent": null, + "voting_percent": null + } + ], + "kyc_session_id": "55555555-6666-7777-8888-999999999999", + "kyc_session_status": "Not Started", + "kyc_session_url": "https://verify.didit.me/session/abc123def456", + "kyc_session_deleted": false, + "kyb_sub_session_id": null, + "kyb_sub_session_status": null, + "kyb_sub_session_url": null, + "kyb_sub_session_deleted": false, + "verification_workflow_type": "kyc", + "verification_workflow_id": "12121212-3434-5656-7878-909090909090", + "verification_workflow_label": "Key People KYC", + "verification_workflow_features": [ + { + "feature": "ID_VERIFICATION", + "status": "Not Finished" + }, + { + "feature": "LIVENESS", + "status": "Not Finished" + }, + { + "feature": "AML", + "status": "Not Finished" + } + ], + "didit_internal_id": null + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits; no party with this UUID exists in your application; or the party belongs to another session. Each case has its own `detail`. The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's workflow or approved-only limits", + "value": { + "detail": "Session not found." + } + }, + "Unknown party": { + "summary": "No party with this UUID in your application", + "value": { + "detail": "No BusinessParty matches the given query." + } + }, + "Party of another session": { + "summary": "partyUuid exists but belongs to a different session", + "value": { + "detail": "Party does not belong to the given session." + } + } + } + } + } + }, + "409": { + "description": "The party has no eligible KYC session to replace. Unless deleted, a session still in progress or one that ended in another status (`Approved`, `Declined`, `In Review`, `Abandoned`, ...) cannot be regenerated. A party that never had one or a company party also returns `409`. An eligible session was deleted or expired before the person started it. Deleted sessions qualify even if the person had already started or finished verification. Imported sessions do not qualify through expiry; they must have been deleted. Branch on `code`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "detail", + "code" + ], + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string", + "enum": [ + "kyb_party_kyc_session_not_deleted" + ], + "description": "Stable machine-readable error code." + } + } + }, + "example": { + "detail": "This party's KYC session is still live (it has neither been deleted nor expired without starting), so it cannot be regenerated.", + "code": "kyb_party_kyc_session_not_deleted" + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/transactions/{transaction_id}/notes/": { + "get": { + "summary": "List transaction notes", + "description": "List the notes on one monitored transaction, newest first (`created_at` descending): notes you add through the API, notes and status changes reviewers make in the Console, and notes Didit adds automatically (a rule's `add_note` action, or a problem creating the user-action verification session), which carry `actor_name` `Rule Engine`. `metadata` tells the note types apart.\n\nPaginated with `limit` and `offset`. `count` is exact up to 100 and capped at 100 above that, so follow `next` until it is `null` instead of computing pages from `count`. This is a read with no side effects.\n\nA scoped API key needs `transactions: read`; a key limited to some workflows or to approved sessions cannot call this endpoint. Reviewers see the same list under **Notes** on the [transaction detail](https://docs.didit.me/transaction-monitoring/console#transaction-detail).", + "operationId": "list_transaction_notes", + "x-didit-sdk": { + "resource": "transactions.notes", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit transaction UUID: the `uuid` returned by `POST /v3/transactions/` and `GET /v3/transactions/`. Your own `txn_id` is not accepted.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "description": "Number of notes per page. Defaults to 50. A value that is not a positive integer falls back to 50; a value above 1000000 returns `400`." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "description": "Zero-based position of the first note to return. Prefer following `next`. A value that is not a whole number falls back to 0; a value above 1000000 returns `400`." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/abcdef12-3456-7890-abcd-ef1234567890/notes/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.transactions.notes.list(\n \"abcdef12-3456-7890-abcd-ef1234567890\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.transactions.notes.list(\"abcdef12-3456-7890-abcd-ef1234567890\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Transactions.Notes.ListAutoPaging(ctx, \"abcdef12-3456-7890-abcd-ef1234567890\", didit.TransactionsNotesListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.transactions().notes().list(\"abcdef12-3456-7890-abcd-ef1234567890\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->notes->list('abcdef12-3456-7890-abcd-ef1234567890') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.notes.list(\"abcdef12-3456-7890-abcd-ef1234567890\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Transactions.Notes.ListAsync(\"abcdef12-3456-7890-abcd-ef1234567890\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "A page of notes, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Number of notes on the transaction: exact up to 100, capped at 100 above that. Follow `next` instead of computing pages from it." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionNote" + } + } + } + }, + "examples": { + "API, Console and rule notes": { + "value": { + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "uuid": "c1111111-2222-4333-8444-555555555555", + "note": "Customer confirmed the withdrawal by phone.", + "actor_email": null, + "actor_name": null, + "metadata": { + "mentioned_emails": [ + "analyst@example.com" + ] + }, + "created_at": "2026-10-16T10:05:12.481203Z" + }, + { + "uuid": "c2222222-3333-4444-8555-666666666666", + "note": "Status changed from IN_REVIEW to APPROVED. Reason: Source of funds verified", + "actor_email": "reviewer@example.com", + "actor_name": "Alex Reviewer", + "metadata": { + "type": "status_change", + "previous_status": "IN_REVIEW", + "new_status": "APPROVED", + "reason_code": "SOF_VERIFIED", + "reason_label": "Source of funds verified" + }, + "created_at": "2026-10-15T17:22:40.103911Z" + }, + { + "uuid": "c3333333-4444-4555-8666-777777777777", + "note": "Large withdrawal to a new wallet: check the source of funds.", + "actor_email": null, + "actor_name": "Rule Engine", + "metadata": { + "rule_uuid": "d1111111-2222-4333-8444-555555555555", + "source": "rule_action" + }, + "created_at": "2026-10-15T08:42:12.004518Z" + } + ] + } + }, + "No notes yet": { + "value": { + "count": 0, + "next": null, + "previous": null, + "results": [] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is above 1000000. The body is a JSON array of messages, not an object.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "examples": { + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + }, + "Offset too large": { + "value": [ + "offset must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing key gets the generic message; a wrong, revoked or expired key gets `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without transactions read": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists in the application (never created, deleted, or owned by another application). A `transaction_id` that is not a UUID does not reach the API: it returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "No Transaction matches the given query." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + }, + "post": { + "summary": "Create transaction note", + "description": "Add a plain-text note to a monitored transaction's review timeline, where Console reviewers see it. A note is context, not a decision: the transaction's `status`, `score` and alerts do not change, no webhook is sent, and nothing is billed. The transaction's `activities` gain a `TRANSACTION_NOTE_ADDED` entry.\n\nNotes created with an API key have no author: `actor_email` and `actor_name` are `null`, so put the author in the text if you need it. Every address in `mentioned_emails` must belong to a member of your organization, otherwise the whole request fails with `400`; mentions are stored lower-cased and de-duplicated, and this endpoint does not notify the mentioned members.\n\nEach call creates a new note, so retrying a timed-out request can add it twice: list the notes before retrying. There is no API endpoint to edit or remove a note. A scoped API key needs `transactions: write`; a key limited to some workflows or to approved sessions cannot call this endpoint.", + "operationId": "create_transaction_note", + "x-didit-sdk": { + "resource": "transactions.notes", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit transaction UUID: the `uuid` returned by `POST /v3/transactions/` and `GET /v3/transactions/`. Your own `txn_id` is not accepted.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "note" + ], + "properties": { + "note": { + "type": "string", + "minLength": 1, + "description": "Plain-text note. Leading and trailing whitespace is trimmed and a blank note is rejected. No length limit." + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + }, + "default": [], + "description": "Emails of members of your organization to mention. Each must be a valid email of an organization member." + } + } + }, + "examples": { + "Note with a mention": { + "value": { + "note": "Customer confirmed the withdrawal by phone.", + "mentioned_emails": [ + "analyst@example.com" + ] + } + }, + "Plain note": { + "value": { + "note": "Source of funds document requested by email." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/transactions/abcdef12-3456-7890-abcd-ef1234567890/notes/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"note\": \"Customer confirmed the withdrawal by phone.\", \"mentioned_emails\": [\"analyst@example.com\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.transactions.notes.create(\n \"abcdef12-3456-7890-abcd-ef1234567890\",\n note=\"Customer confirmed the withdrawal by phone.\",\n mentioned_emails=[\"analyst@example.com\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.transactions.notes.create(\"abcdef12-3456-7890-abcd-ef1234567890\", {\n note: \"Customer confirmed the withdrawal by phone.\",\n mentioned_emails: [\"analyst@example.com\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Transactions.Notes.Create(ctx, \"abcdef12-3456-7890-abcd-ef1234567890\", didit.TransactionsNotesCreateParams{\n\tNote: \"Customer confirmed the withdrawal by phone.\",\n\tMentionedEmails: didit.Some([]string{\"analyst@example.com\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.TransactionsNotesCreateParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.transactions().notes().create(\"abcdef12-3456-7890-abcd-ef1234567890\", TransactionsNotesCreateParams.builder()\n .note(\"Customer confirmed the withdrawal by phone.\")\n .mentionedEmails(List.of(\"analyst@example.com\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->notes->create('abcdef12-3456-7890-abcd-ef1234567890', [\n 'note' => 'Customer confirmed the withdrawal by phone.',\n 'mentioned_emails' => ['analyst@example.com'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.transactions.notes.create(\n \"abcdef12-3456-7890-abcd-ef1234567890\",\n note: \"Customer confirmed the withdrawal by phone.\",\n mentioned_emails: [\"analyst@example.com\"]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Transactions.Notes.CreateAsync(\"abcdef12-3456-7890-abcd-ef1234567890\", new TransactionsNotesCreateParams\n{\n Note = \"Customer confirmed the withdrawal by phone.\",\n MentionedEmails = new string[] { \"analyst@example.com\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The note was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionNote" + }, + "examples": { + "Created": { + "value": { + "uuid": "c1111111-2222-4333-8444-555555555555", + "note": "Customer confirmed the withdrawal by phone.", + "actor_email": null, + "actor_name": null, + "metadata": { + "mentioned_emails": [ + "analyst@example.com" + ] + }, + "created_at": "2026-10-16T10:05:12.481203Z" + } + } + } + } + } + }, + "400": { + "description": "The body is invalid: `note` missing, null or blank, an invalid address in `mentioned_emails` (errors keyed by list position), or a mentioned address that is not a member of your organization. Nothing is created.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Field errors keyed by field name.", + "additionalProperties": {} + }, + "examples": { + "Missing note": { + "value": { + "note": [ + "This field is required." + ] + } + }, + "Blank note": { + "value": { + "note": [ + "This field may not be blank." + ] + } + }, + "Invalid email": { + "value": { + "mentioned_emails": { + "0": [ + "Enter a valid email address." + ] + } + } + }, + "Not an organization member": { + "value": { + "mentioned_emails": [ + "Mentioned users must belong to this organization and have access to this application." + ] + } + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing key gets the generic message; a wrong, revoked or expired key gets `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without transactions write": { + "value": { + "detail": "This API key has no write access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists in the application (never created, deleted, or owned by another application). A `transaction_id` that is not a UUID does not reach the API: it returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "No Transaction matches the given query." + } + } + } + } + } + }, + "429": { + "description": "More than 300 write requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/transactions/{transaction_id}/related/": { + "get": { + "summary": "List related transactions", + "description": "List the other transactions in your application that involve the same people or companies as this one. A transaction is related when any of its parties, in any role, has the same `vendor_data` as one of this transaction's parties. The transaction itself is never returned, and parties without `vendor_data` are ignored: when none of this transaction's parties has one, the result is always empty.\n\nResults use the summary shape of `GET /v3/transactions/`, newest first (`txn_date`, then `created_at`). Paginated with `limit` and `offset`; `count` is exact up to 100 and capped at 100 above that, so follow `next` until it is `null`.\n\nA scoped API key needs `transactions: read`; a key limited to some workflows or to approved sessions cannot call this endpoint. Send the same `vendor_data` for the same person or company on every [transaction](https://docs.didit.me/transaction-monitoring/transactions#subject-and-counterparty) to make this list complete.", + "operationId": "list_related_transactions", + "x-didit-sdk": { + "resource": "transactions", + "method": "list_related", + "auth": "api_key", + "server": true + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "transaction_id", + "in": "path", + "required": true, + "description": "Didit transaction UUID: the `uuid` returned by `POST /v3/transactions/` and `GET /v3/transactions/`. Your own `txn_id` is not accepted.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000 + }, + "description": "Number of transactions per page. Defaults to 50. A value that is not a positive integer falls back to 50; a value above 1000000 returns `400`." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000 + }, + "description": "Zero-based position of the first transaction to return. Prefer following `next`. A value that is not a whole number falls back to 0; a value above 1000000 returns `400`." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/transactions/abcdef12-3456-7890-abcd-ef1234567890/related/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.transactions.list_related(\n \"abcdef12-3456-7890-abcd-ef1234567890\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.transactions.listRelated(\"abcdef12-3456-7890-abcd-ef1234567890\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Transactions.ListRelatedAutoPaging(ctx, \"abcdef12-3456-7890-abcd-ef1234567890\", didit.TransactionsListRelatedParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.transactions().listRelated(\"abcdef12-3456-7890-abcd-ef1234567890\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->listRelated('abcdef12-3456-7890-abcd-ef1234567890') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.transactions.list_related(\"abcdef12-3456-7890-abcd-ef1234567890\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Transactions.ListRelatedAsync(\"abcdef12-3456-7890-abcd-ef1234567890\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "A page of related transactions, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Number of related transactions: exact up to 100, capped at 100 above that. Follow `next` instead of computing pages from it." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Absolute URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionSummary" + } + } + } + }, + "examples": { + "One related transaction": { + "value": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "abcdef99-3456-4890-abcd-ef1234567890", + "transaction_number": 4087, + "txn_id": "your-txn-2026-10-02-017", + "transaction_type": "finance", + "action_type": "deposit", + "status": "APPROVED", + "source": "LIVE", + "direction": "INBOUND", + "amount": "250", + "currency": "EUR", + "currency_type": "fiat", + "preferred_currency_amount": "270.08", + "preferred_currency_code": "USD", + "payment_details": "Card top-up", + "txn_date": "2026-10-02T14:10:00Z", + "created_at": "2026-10-02T14:10:03.512244Z", + "score": 12, + "severity": "LOW", + "decision_reason_code": null, + "decision_reason_label": null, + "tags": [], + "subject_name": "Maria Garcia", + "subject_vendor_data": "user-12345", + "subject_type": "user", + "subject_uuid": "e1111111-2222-4333-8444-555555555555", + "counterparty_name": null, + "counterparty_vendor_data": null, + "counterparty_country": null, + "subject_country": "ESP", + "assigned_to_name": null, + "travel_rule_status": null, + "application_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "application_name": "Production app", + "organization_id": "11111111-2222-3333-4444-555555555555", + "organization_name": "Example Org" + } + ] + } + }, + "No party has vendor_data": { + "value": { + "count": 0, + "next": null, + "previous": null, + "results": [] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is above 1000000. The body is a JSON array of messages, not an object.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "examples": { + "Limit too large": { + "value": [ + "limit must not exceed 1000000." + ] + }, + "Offset too large": { + "value": [ + "offset must not exceed 1000000." + ] + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. A missing key gets the generic message; a wrong, revoked or expired key gets `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without transactions read": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists in the application (never created, deleted, or owned by another application). A `transaction_id` that is not a UUID does not reach the API: it returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "No Transaction matches the given query." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/businesses/{business_session_id}/pdf/": { + "get": { + "summary": "Download a business session PDF report", + "description": "Render the PDF report of one Business Verification (KYB) session: company registry data, key people and beneficial owners, documents, AML results and the other workflow features, plus the session's tags and review activity. With white-label customization on, the report carries your logo and privacy policy URL. The body is the PDF itself (`application/pdf`), not JSON.\n\nThe session must be `Approved`, `Declined` or `In Review`; any other status returns `403`, so wait for the `status.updated` [webhook](https://docs.didit.me/integration/webhooks) first. The report is rendered on every call and nothing is billed, so store the file instead of fetching it repeatedly. `GET /v3/session/{sessionId}/generate-pdf/` renders the same kind of report by session ID alone.\n\nA PDF report counts as media: a scoped API key needs `businesses: read` and `media: read`, and a key limited to some workflows or to approved sessions cannot call it.", + "operationId": "get_business_session_pdf", + "x-didit-sdk": { + "resource": "businesses", + "method": "generate_session_pdf", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Businesses" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID.", + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID. Must be the application the API key belongs to: a key used on any other application gets `403`.", + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + }, + { + "name": "business_session_id", + "in": "path", + "required": true, + "description": "`session_id` of the Business Verification (KYB) session, as returned when the session was created. A session of another application returns `404`.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl --fail -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/businesses/22222222-3333-4444-5555-666666666666/pdf/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n --output business_report.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\ncontent = client.businesses.generate_session_pdf(\n \"22222222-3333-4444-5555-666666666666\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst content = await didit.businesses.generateSessionPdf(\"22222222-3333-4444-5555-666666666666\");\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresp, err := client.Businesses.GenerateSessionPDF(ctx, \"22222222-3333-4444-5555-666666666666\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.businesses().generateSessionPdf(\"22222222-3333-4444-5555-666666666666\")\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "businesses->generateSessionPdf('22222222-3333-4444-5555-666666666666');\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ncontent = didit.businesses.generate_session_pdf(\"22222222-3333-4444-5555-666666666666\")\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar content = await client.Businesses.GenerateSessionPdfAsync(\"22222222-3333-4444-5555-666666666666\");\nawait content.SaveToAsync(\"download.pdf\");" + } + ], + "responses": { + "200": { + "description": "The rendered PDF, returned directly as binary `application/pdf`.", + "headers": { + "Content-Disposition": { + "description": "Always `attachment; filename=business_{business_session_id}.pdf`.", + "schema": { + "type": "string", + "example": "attachment; filename=business_22222222-3333-4444-5555-666666666666.pdf" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed or the session is not finished. Read `detail`: a session that is not `Approved`, `Declined` or `In Review`, a missing API key (a wrong, revoked or expired key gets `401`), an `application_id` that is not the key's application, a scoped key without `media: read` or `businesses: read`, a key limited to workflows or approved sessions, or a request from outside the key's IP list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Session not finished": { + "value": { + "detail": "You can only generate a PDF for sessions in review, declined or approved." + } + }, + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without media read": { + "value": { + "detail": "This API key has no read access to media." + } + }, + "Scoped key without businesses read": { + "value": { + "detail": "This API key has no read access to companies." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No business session with this ID exists in the application (never created, deleted, or owned by another application). A value that is not a UUID returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "Business session not found." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/transactions/{transaction_uuid}/pdf/": { + "get": { + "summary": "Download a transaction PDF report", + "description": "Render one monitored transaction as a PDF report: overview and verdict, parties and payment methods, custom properties, alerts and rule runs, screening and Travel Rule results, the network summary, the cost breakdown, up to 50 related transactions and the 20 most recent activity entries. The body is the PDF itself (`application/pdf`), not JSON.\n\nThe report is rendered from the transaction's current data on every call, so request it again after a status change. Nothing is stored and nothing is billed. Rendering can take a few seconds, so use a generous client timeout.\n\nThis endpoint lives on an application-scoped path: put your organization ID and the ID of the application your API key belongs to in the URL ([find your organization ID](https://docs.didit.me/getting-started/api-authentication#find-your-organization-id)). A PDF report counts as media: a scoped API key needs `media: read` and `transactions: read`, and a key limited to some workflows or to approved sessions cannot call it.", + "operationId": "get_transaction_pdf", + "x-didit-sdk": { + "resource": "transactions", + "method": "generate_pdf", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID.", + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID. Must be the application the API key belongs to: a key used on any other application gets `403`.", + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + }, + { + "name": "transaction_uuid", + "in": "path", + "required": true, + "description": "Didit transaction UUID: the `uuid` returned by `POST /v3/transactions/` and `GET /v3/transactions/`. Your own `txn_id` is not accepted.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "abcdef12-3456-7890-abcd-ef1234567890" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl --fail -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/transactions/abcdef12-3456-7890-abcd-ef1234567890/pdf/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n --output transaction_report.pdf" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\ncontent = client.transactions.generate_pdf(\n \"abcdef12-3456-7890-abcd-ef1234567890\",\n)\nwith open(\"download.pdf\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst content = await didit.transactions.generatePdf(\"abcdef12-3456-7890-abcd-ef1234567890\");\nawait writeFile(\"download.pdf\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresp, err := client.Transactions.GeneratePDF(ctx, \"abcdef12-3456-7890-abcd-ef1234567890\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.pdf\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.transactions().generatePdf(\"abcdef12-3456-7890-abcd-ef1234567890\")\n .writeTo(Path.of(\"download.pdf\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->generatePdf('abcdef12-3456-7890-abcd-ef1234567890');\n$content->saveTo('download.pdf');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ncontent = didit.transactions.generate_pdf(\"abcdef12-3456-7890-abcd-ef1234567890\")\nFile.binwrite(\"download.pdf\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar content = await client.Transactions.GeneratePdfAsync(\"abcdef12-3456-7890-abcd-ef1234567890\");\nawait content.SaveToAsync(\"download.pdf\");" + } + ], + "responses": { + "200": { + "description": "The rendered PDF, returned directly as binary `application/pdf`.", + "headers": { + "Content-Disposition": { + "description": "Always `attachment; filename=transaction_{transaction_uuid}.pdf`.", + "schema": { + "type": "string", + "example": "attachment; filename=transaction_abcdef12-3456-7890-abcd-ef1234567890.pdf" + } + } + }, + "content": { + "application/pdf": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. Read `detail`: a missing API key (a wrong, revoked or expired key gets `401`), an `application_id` that is not the key's application, a scoped key without `media: read` or `transactions: read`, a key limited to workflows or approved sessions, or a request from outside the key's IP list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without media read": { + "value": { + "detail": "This API key has no read access to media." + } + }, + "Scoped key without transactions read": { + "value": { + "detail": "This API key has no read access to transactions." + } + }, + "Key limited to workflows or approved sessions": { + "value": { + "detail": "This API key only reaches sessions inside its workflow or status limits, so it can't use this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "No transaction with this UUID exists in the application (never created, deleted, or owned by another application). A `transaction_uuid` that is not a UUID does not reach the API: it returns a `404` HTML page instead of this JSON body.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Not found": { + "value": { + "detail": "No Transaction matches the given query." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/analytics/": { + "get": { + "summary": "Get application analytics", + "description": "Read the numbers behind the Business Console [analytics dashboard](https://docs.didit.me/console/analytics) for a date range. Without `scope` and `widget_keys` the response is the user verification (KYC) summary (`ApplicationAnalyticsSummary`): daily session counts, status totals, conversion rate, the same figures for the preceding period of equal length, locations, demographics, devices, workflow funnels, active verification time, resubmissions, warnings and time series. With `scope` or `widget_keys` it is the widgets of one dashboard tab (`ApplicationAnalyticsWidgetResponse`): `kyc`, `kyb`, `transactions` or `home`.\n\nSession counts are by session creation date (UTC) and reflect each session's current status. They come from daily rollups refreshed in the background, so the newest activity can take a short while to appear. Invalid workflow IDs and versions are ignored rather than rejected. Nothing is billed.\n\nOnly a full-access API key can call this endpoint: every scoped key gets `403`, whatever access it holds, and so does a key of another application. See [API keys](https://docs.didit.me/console/api-keys).", + "operationId": "get_application_analytics", + "x-didit-sdk": { + "resource": "analytics", + "method": "retrieve", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Analytics" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID.", + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID. Must be the application the API key belongs to: a key used on any other application gets `403`.", + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + }, + { + "name": "start_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date", + "example": "2026-10-01" + }, + "description": "First day of the range, `YYYY-MM-DD`, inclusive (UTC). Send it together with `end_date`: when either is missing, both are ignored and the range runs from 30 days before today through today." + }, + { + "name": "end_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date", + "example": "2026-10-15" + }, + "description": "Last day of the range, `YYYY-MM-DD`, inclusive (UTC). A future date counts as today. The range may not exceed 730 days." + }, + { + "name": "scope", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "home", + "kyc", + "kyb", + "transactions" + ] + }, + "description": "Dashboard tab to return widgets for (case-insensitive). Omit it, together with `widget_keys`, for the KYC summary. Sending only `widget_keys` implies `kyc`. `home` accepts any KYC, KYB or transaction widget and defaults to the application's saved home layout." + }, + { + "name": "widget_keys", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "tx_count_total,tx_count_daily" + }, + "description": "Comma-separated widget keys, answered in this order. Keys the scope does not accept are dropped; when none is left you get the scope's defaults. KYC: `kyc_sessions_volume`, `kyc_warning_insights`, `kyc_id_locations`, `kyc_ip_locations`, `kyc_workflow_funnel`, `kyc_average_verification_time`, `kyc_conversion_rate`, `kyc_conversion_by_country`, `kyc_resubmission_insights`, `kyc_original_status_distribution`, `kyc_database_validation_coverage`, `kyc_database_validation_latency`, `kyc_demographics`, `kyc_device_details`. KYB: `kyb_sessions_volume`, `kyb_workflow_funnel`, `kyb_average_verification_time`, `kyb_ip_locations`, `kyb_business_country_distribution`, `kyb_warning_insights`, `kyb_conversion_rate`, `kyb_conversion_by_country`, `kyb_industry_breakdown`, `kyb_resubmission_insights`, `kyb_original_status_distribution`, `kyb_registry_coverage`, `kyb_registry_data_completeness`, `kyb_device_details`. Transactions: `tx_count_total`, `tx_count_daily`, `tx_volume_total_preferred_currency`, `tx_volume_daily_preferred_currency`, `tx_average_amount`, `tx_average_amount_daily`, `tx_transaction_category_breakdown`, `tx_action_type_breakdown`, `tx_status_volume_trend`, `tx_user_ip_or_country_distribution`, `tx_tag_breakdown`, `tx_top_rules_triggered`, `tx_team_member_performance`, `tx_score_histogram`." + }, + { + "name": "workflow_ids", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "33333333-4444-5555-6666-777777777777" + }, + "description": "Comma-separated workflow IDs. Limits the KYC summary and the KYC and KYB widgets to those workflows. Values that are not UUIDs are ignored; when none is valid the filter is dropped. The summary also accepts `workflow_id`." + }, + { + "name": "versions", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "3,4" + }, + "description": "Comma-separated workflow version numbers. Applies only together with `workflow_ids`; values that are not whole numbers are ignored. `version` is accepted as an alias." + }, + { + "name": "branch_path_ids", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "default" + }, + "description": "Comma-separated branch path IDs. Applies only together with `workflow_ids`, and only to KYC figures. Figures are currently recorded only for `default` (all paths combined), so any other value returns empty figures. `branch_path_id` is accepted as an alias." + }, + { + "name": "tag", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Transaction widgets only: comma-separated tag UUIDs or tag names." + }, + { + "name": "transaction_type", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "finance" + }, + "description": "Transaction widgets only: comma-separated transaction categories." + }, + { + "name": "action_type", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "withdrawal" + }, + "description": "Transaction widgets only: comma-separated action types (case-insensitive)." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/analytics/?start_date=2026-10-01&end_date=2026-10-15' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.analytics.retrieve()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.analytics.retrieve();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Analytics.Retrieve(ctx, didit.AnalyticsRetrieveParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.analytics().retrieve();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "analytics->retrieve();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.analytics.retrieve\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Analytics.RetrieveAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The KYC summary, or the requested widgets when `scope` or `widget_keys` is sent.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/ApplicationAnalyticsSummary" + }, + { + "$ref": "#/components/schemas/ApplicationAnalyticsWidgetResponse" + } + ] + }, + "examples": { + "KYC summary": { + "value": { + "grouping_mode": "day", + "data_points": [ + { + "rounded_date": "2026-10-14", + "total": 12, + "approved": 8, + "declined": 1, + "canceled": 1, + "pending": 2, + "in_review": 1, + "expired": 1, + "abandoned": 0, + "in_progress": 1, + "not_started": 0, + "manually_approved": 1, + "manually_declined": 0 + }, + { + "rounded_date": "2026-10-15", + "total": 10, + "approved": 6, + "declined": 2, + "canceled": 0, + "pending": 2, + "in_review": 2, + "expired": 0, + "abandoned": 0, + "in_progress": 0, + "not_started": 0, + "manually_approved": 0, + "manually_declined": 1 + } + ], + "request_breakdown": { + "total_approved": 14, + "total_in_review": 3, + "total_declined": 3, + "total_canceled": 1, + "total_pending": 4, + "total_expired": 1, + "total_abandoned": 0, + "total_in_progress": 1, + "total_not_started": 0, + "total_manually_approved": 1, + "total_manually_declined": 1 + }, + "conversion_rate": 70.0, + "location_stats": { + "countries": [ + { + "session_count": 15, + "country": "Spain", + "country_code": "ES" + }, + { + "session_count": 7, + "country": "Portugal", + "country_code": "PT" + } + ], + "ip_countries": [ + { + "session_count": 15, + "country": "Spain", + "country_code": "ES" + }, + { + "session_count": 7, + "country": "Portugal", + "country_code": "PT" + } + ], + "id_countries": [ + { + "country": "Spain", + "country_code": "ES", + "session_count": 14 + }, + { + "country": "Portugal", + "country_code": "PT", + "session_count": 6 + } + ], + "conversion_by_country": [ + { + "country": "Spain", + "country_code": "ES", + "finished": 13, + "approved": 10 + }, + { + "country": "Portugal", + "country_code": "PT", + "finished": 6, + "approved": 4 + } + ], + "cities": [ + { + "session_count": 9, + "location": "Madrid, Spain", + "latitude": 40.4168, + "longitude": -3.7038 + } + ] + }, + "demographic_stats": { + "age_distribution": { + "under_18": 0, + "18_24": 5, + "25_34": 8, + "35_44": 4, + "45_64": 2, + "65_plus": 1 + }, + "gender_distribution": { + "M": 11, + "F": 9 + } + }, + "device_details": { + "device_models": [ + { + "device": "Apple", + "count": 12 + }, + { + "device": "Android", + "count": 10 + } + ], + "browsers": [ + { + "browser": "Safari", + "count": 12 + }, + { + "browser": "Chrome", + "count": 10 + } + ], + "operating_systems": [ + { + "os": "iOS", + "count": 12 + }, + { + "os": "Android", + "count": 10 + } + ] + }, + "workflow_funnel_stats": { + "33333333-4444-5555-6666-777777777777": { + "total": 22, + "steps": { + "OCR": { + "reached": 21 + }, + "LIVENESS": { + "reached": 19 + }, + "FACE_MATCH": { + "reached": 19 + }, + "AML": { + "reached": 18 + } + } + } + }, + "verification_time_stats": { + "overall_avg_seconds": 64.0, + "overall_count": 18, + "per_feature": { + "OCR": { + "avg_seconds": 41.2, + "total_seconds": 741.6, + "count": 18 + }, + "LIVENESS": { + "avg_seconds": 22.8, + "total_seconds": 410.4, + "count": 18 + } + }, + "per_sub_step": { + "OCR": { + "front_id_photo": { + "avg_seconds": 18.3, + "total_seconds": 329.4, + "count": 18 + } + } + } + }, + "resubmission_stats": { + "first_pass": 17, + "second": 2, + "third_plus": 1, + "abandoned": 0 + }, + "warning_stats": { + "ID_VERIFICATION": { + "total": 21, + "risks": { + "DOCUMENT_EXPIRED": { + "error": 1, + "warning": 0, + "information": 0 + } + } + }, + "LIVENESS": { + "total": 19, + "risks": { + "LOW_LIVENESS_SCORE": { + "error": 0, + "warning": 2, + "information": 0 + } + } + } + }, + "original_status_stats": { + "approved": 13, + "in_review": 5, + "declined": 2 + }, + "demographic_time_series": [ + { + "date": "2026-10-14", + "age_distribution": { + "under_18": 0, + "18_24": 3, + "25_34": 4, + "35_44": 2, + "45_64": 1, + "65_plus": 0 + }, + "gender_distribution": { + "M": 6, + "F": 4 + } + }, + { + "date": "2026-10-15", + "age_distribution": { + "under_18": 0, + "18_24": 2, + "25_34": 4, + "35_44": 2, + "45_64": 1, + "65_plus": 1 + }, + "gender_distribution": { + "M": 5, + "F": 5 + } + } + ], + "device_time_series": [ + { + "date": "2026-10-14", + "devices": { + "Apple": 7, + "Android": 5 + }, + "browsers": { + "Safari": 7, + "Chrome": 5 + }, + "operating_systems": { + "iOS": 7, + "Android": 5 + } + }, + { + "date": "2026-10-15", + "devices": { + "Apple": 5, + "Android": 5 + }, + "browsers": { + "Safari": 5, + "Chrome": 5 + }, + "operating_systems": { + "iOS": 5, + "Android": 5 + } + } + ], + "verification_time_time_series": [ + { + "date": "2026-10-14", + "overall_avg": 66.1, + "per_feature": { + "OCR": 42.0, + "LIVENESS": 24.1 + } + }, + { + "date": "2026-10-15", + "overall_avg": 61.5, + "per_feature": { + "OCR": 40.2, + "LIVENESS": 21.3 + } + } + ], + "resubmission_time_series": [ + { + "date": "2026-10-14", + "first_pass": 9, + "second": 1, + "third_plus": 0, + "abandoned": 0 + }, + { + "date": "2026-10-15", + "first_pass": 8, + "second": 1, + "third_plus": 1, + "abandoned": 0 + } + ], + "original_status_time_series": [ + { + "date": "2026-10-14", + "approved": 7, + "in_review": 3, + "declined": 1 + }, + { + "date": "2026-10-15", + "approved": 6, + "in_review": 2, + "declined": 1 + } + ], + "previous_data_points": [ + { + "rounded_date": "2026-10-12", + "total": 9, + "approved": 6, + "declined": 1, + "canceled": 1, + "pending": 1 + }, + { + "rounded_date": "2026-10-13", + "total": 11, + "approved": 7, + "declined": 2, + "canceled": 0, + "pending": 2 + } + ], + "previous_request_breakdown": { + "total_approved": 13, + "total_in_review": 2, + "total_declined": 3, + "total_canceled": 1, + "total_pending": 3 + } + } + }, + "Transaction widgets": { + "value": { + "scope": "transactions", + "available_widget_keys": [ + "tx_count_total", + "tx_count_daily", + "tx_volume_total_preferred_currency", + "tx_volume_daily_preferred_currency", + "tx_average_amount", + "tx_average_amount_daily", + "tx_transaction_category_breakdown", + "tx_action_type_breakdown", + "tx_status_volume_trend", + "tx_user_ip_or_country_distribution", + "tx_tag_breakdown", + "tx_top_rules_triggered", + "tx_team_member_performance", + "tx_score_histogram" + ], + "requested_widget_keys": [ + "tx_count_total", + "tx_count_daily" + ], + "widgets": { + "tx_count_total": { + "kind": "metric", + "title": "Transaction count", + "value": 42 + }, + "tx_count_daily": { + "kind": "time_series", + "title": "Transaction count", + "items": [ + { + "date": "2026-10-14", + "value": 20 + }, + { + "date": "2026-10-15", + "value": 22 + } + ], + "previous_items": [ + { + "date": "2026-10-12", + "value": 18 + }, + { + "date": "2026-10-13", + "value": 15 + } + ] + } + } + } + } + } + } + } + }, + "400": { + "description": "Invalid date range or scope. The body is an object keyed by the offending parameter, with a string message.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "examples": { + "Bad date format": { + "value": { + "start_date": "Invalid date format. Use YYYY-MM-DD (e.g., 2024-01-15)." + } + }, + "Start after end": { + "value": { + "start_date": "Start date must be before or equal to end date." + } + }, + "Range too long": { + "value": { + "date_range": "Date range cannot exceed 730 days (2 years)." + } + }, + "Unknown scope": { + "value": { + "scope": "Unsupported analytics scope." + } + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. Read `detail`: a missing API key (a wrong, revoked or expired key gets `401`), a key of another application, any scoped key, or a request from outside the key's IP list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key": { + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/analytics/today/": { + "get": { + "summary": "Get today's verifications by hour", + "operationId": "get_application_analytics_today", + "x-didit-sdk": { + "resource": "analytics", + "method": "retrieve_today", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Analytics" + ], + "description": "Today's user verification (KYC) sessions, from local midnight in `tz`, counted per local hour they were created in, next to the same hours of the same weekday one week before, and split by the outcome each one holds now: approved, in review or declined. This is the volume card of the Business Console's Home.\n\n`total` is today so far. `previous_total_so_far` counts the comparison day only up to the same local time, so a change between them compares the same part of both days. The hours still to come today are `0`. A day on which the clocks change has 23 or 25 hours: the missing hour stays `0` and the repeated hour holds both.", + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID.", + "example": "11111111-2222-3333-4444-555555555555" + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID. Must be the application the API key belongs to: a key used on any other application gets `403`.", + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + }, + { + "name": "tz", + "in": "query", + "required": false, + "schema": { + "type": "string", + "default": "UTC", + "example": "Europe/Madrid" + }, + "description": "IANA time zone whose midnight starts today, for example `Europe/Madrid` or `America/Mexico_City`. Omit it for UTC." + } + ], + "responses": { + "200": { + "description": "Today's verifications by local hour, the comparison day's, and today's outcomes.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApplicationAnalyticsToday" + }, + "examples": { + "Madrid, 14:00": { + "value": { + "time_zone": "Europe/Madrid", + "date": "2026-10-30", + "current_hour": 14, + "previous_date": "2026-10-23", + "hours": [ + 3, + 1, + 0, + 0, + 1, + 2, + 4, + 9, + 14, + 18, + 21, + 19, + 22, + 24, + 27, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "previous_hours": [ + 2, + 1, + 1, + 0, + 1, + 3, + 5, + 8, + 12, + 16, + 19, + 18, + 20, + 22, + 24, + 26, + 23, + 19, + 15, + 12, + 9, + 7, + 5, + 3 + ], + "total": 165, + "previous_total_so_far": 151, + "outcomes": { + "approved": 139, + "in_review": 6, + "declined": 18 + } + } + } + } + } + } + }, + "400": { + "description": "Unknown time zone. The body is keyed by the parameter, with a list of messages.", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "examples": { + "Unknown time zone": { + "value": { + "tz": [ + "Unknown time zone: Europe/Atlantis." + ] + } + } + } + } + } + }, + "401": { + "description": "The API key has expired. Only a key created with an expiry date can expire; after that date every request with it answers `401`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. Read `detail`: a missing or invalid API key (never `401`), a key of another application, any scoped key, or a request from outside the key's IP list.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key": { + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "Outside the key's IP list": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests per minute with this API key. Wait the number of seconds in `Retry-After`, then retry.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds until the current one-minute window resets.", + "schema": { + "type": "integer", + "example": 23 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed per minute in this budget.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer" + } + }, + "X-RateLimit-Reset": { + "description": "Unix time when the window resets.", + "schema": { + "type": "integer" + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/analytics/today/?tz=Europe%2FMadrid' \\\n -H 'x-api-key: YOUR_API_KEY'" + } + ], + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/webhooks/": { + "get": { + "summary": "List session webhook deliveries", + "description": "List every webhook delivery Didit recorded for one verification session, user (KYC) or business (KYB), newest first, as a plain JSON array (not paginated). Each entry is one attempt to one destination: the first send, each automatic [retry](https://docs.didit.me/integration/webhooks#retry-policy) and each resend are separate entries with their own `uuid`, and retries and resends keep the payload's `event_id`. `request_header` and `request_body` are JSON text; parse them to read the signatures and the payload. Delivery records are available for about 30 days, so an empty array means no webhook has been sent for the session yet or every delivery is older than that. A scoped API key needs `sessions: read` and `decisions: read`.", + "operationId": "get_v3_session_webhooks", + "x-didit-sdk": { + "resource": "sessions.webhooks", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session, user (KYC) or business (KYB): the `session_id` returned by `POST /v3/session/`. The server resolves it against both session types.\n\n**Must be a valid UUID.** The route only matches well-formed UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/22222222-3333-4444-5555-666666666666/webhooks/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.webhooks.list(\n \"22222222-3333-4444-5555-666666666666\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.webhooks.list(\"22222222-3333-4444-5555-666666666666\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Webhooks.List(ctx, \"22222222-3333-4444-5555-666666666666\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().webhooks().list(\"22222222-3333-4444-5555-666666666666\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->webhooks->list('22222222-3333-4444-5555-666666666666');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.webhooks.list(\"22222222-3333-4444-5555-666666666666\").each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Webhooks.ListAsync(\"22222222-3333-4444-5555-666666666666\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Every delivery recorded for the session, newest first.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionWebhookDelivery" + } + }, + "examples": { + "Failed delivery and its successful retry": { + "value": [ + { + "uuid": "66666666-7777-8888-9999-000000000002", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 200, + "status": "In Progress", + "webhook_type": "status.updated", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"9188851d160b5b3aacfc946e4dde1f061b7e568662518942c927ddaf507f533d\", \"X-Signature-V2\": \"381236af514d3fb538579365ac5a24f2464ce1b8224c6e770c60d8201c43ecc5\", \"X-Signature-Simple\": \"4740131dc38f29e78a8508371979be74967d5307464494bd3148a03b4d6c8716\", \"X-Timestamp\": \"1792056139\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792056139,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}", + "created_at": "2026-10-15T09:22:19.104233Z" + }, + { + "uuid": "66666666-7777-8888-9999-000000000001", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 503, + "status": "In Progress", + "webhook_type": "status.updated", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"a5e02a03404e1337a0198e6be15d34abbd053c658a0e51e5d0e78554213ef29b\", \"X-Signature-V2\": \"927e7cb770c94ce745939bff73f04aa200015f3bc64795ba46c2aaf78a6cedc5\", \"X-Signature-Simple\": \"e37b0d4c716c72bb5ae7127b9cc455d28ab4686e9e22312b4ec2327409a73302\", \"X-Timestamp\": \"1792056078\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792056078,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}", + "created_at": "2026-10-15T09:21:18.412345Z" + } + ] + }, + "No webhook sent yet": { + "value": [] + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. No `x-api-key`, or an invalid or revoked key, gets `Authentication credentials were not provided or are invalid.` A key of another application gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` An unknown `sessionId` returns `404` before the credential is checked.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Key of another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions read": { + "value": { + "detail": "This API key has no read access to sessions." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist or has been deleted (`Not found.`, returned before the credential is checked), or it is outside the workflow or status limits of a scoped key (`Session not found.`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "value": { + "detail": "Not found." + } + }, + "Outside the key's limits": { + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/webhook/{webhook_uuid}": { + "get": { + "summary": "Get session webhook delivery", + "description": "Retrieve one webhook delivery of a verification session: the URL it went to, the HTTP status your endpoint returned, and the exact headers and JSON body Didit sent. `request_header` and `request_body` are JSON text; to check your [signature verification](https://docs.didit.me/integration/webhooks#signature-verification), recompute `X-Signature-V2` from the parsed body with the secret of the destination it was sent to (or the application-level secret for a delivery to the legacy webhook URL). The path has no trailing slash: with one it returns `404`. A scoped API key needs `sessions: read` and `decisions: read`.", + "operationId": "get_v3_session_webhook", + "x-didit-sdk": { + "resource": "sessions.webhooks", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session, user (KYC) or business (KYB): the `session_id` returned by `POST /v3/session/`. The server resolves it against both session types.\n\n**Must be a valid UUID.** The route only matches well-formed UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + }, + { + "name": "webhook_uuid", + "in": "path", + "required": true, + "description": "UUID of the delivery: the `uuid` of an entry returned by `GET /v3/session/{sessionId}/webhooks/`. It must belong to the session in the path.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "66666666-7777-8888-9999-000000000001" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/22222222-3333-4444-5555-666666666666/webhook/66666666-7777-8888-9999-000000000001' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.webhooks.retrieve(\n \"22222222-3333-4444-5555-666666666666\",\n \"66666666-7777-8888-9999-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.webhooks.retrieve(\n \"22222222-3333-4444-5555-666666666666\",\n \"66666666-7777-8888-9999-000000000001\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Webhooks.Retrieve(ctx, \"22222222-3333-4444-5555-666666666666\", \"66666666-7777-8888-9999-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().webhooks().retrieve(\"22222222-3333-4444-5555-666666666666\", \"66666666-7777-8888-9999-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->webhooks->retrieve('22222222-3333-4444-5555-666666666666', '66666666-7777-8888-9999-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.webhooks.retrieve(\n \"22222222-3333-4444-5555-666666666666\",\n \"66666666-7777-8888-9999-000000000001\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Webhooks.RetrieveAsync(\"22222222-3333-4444-5555-666666666666\", \"66666666-7777-8888-9999-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The delivery, with its request headers and body.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionWebhookDelivery" + }, + "examples": { + "Connection failure": { + "value": { + "uuid": "66666666-7777-8888-9999-000000000001", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 503, + "status": "In Progress", + "webhook_type": "status.updated", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"a5e02a03404e1337a0198e6be15d34abbd053c658a0e51e5d0e78554213ef29b\", \"X-Signature-V2\": \"927e7cb770c94ce745939bff73f04aa200015f3bc64795ba46c2aaf78a6cedc5\", \"X-Signature-Simple\": \"e37b0d4c716c72bb5ae7127b9cc455d28ab4686e9e22312b4ec2327409a73302\", \"X-Timestamp\": \"1792056078\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792056078,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}", + "created_at": "2026-10-15T09:21:18.412345Z" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. No `x-api-key`, or an invalid or revoked key, gets `Authentication credentials were not provided or are invalid.` A key of another application gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` An unknown `sessionId` returns `404` before the credential is checked.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Key of another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions read": { + "value": { + "detail": "This API key has no read access to sessions." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist or has been deleted (`Not found.`, returned before the credential is checked), or it is outside the workflow or status limits of a scoped key (`Session not found.`). A delivery that does not exist, belongs to another session or is no longer available returns `No WebhookLog matches the given query.`", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "value": { + "detail": "Not found." + } + }, + "Outside the key's limits": { + "value": { + "detail": "Session not found." + } + }, + "Delivery not in this session": { + "value": { + "detail": "No WebhookLog matches the given query." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 600 GET requests in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (600).", + "schema": { + "type": "integer", + "example": 600 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-get" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/webhook/{webhook_uuid}/resend": { + "post": { + "summary": "Resend session webhook delivery", + "description": "Send a recorded session webhook again to the URL it first went to, with the same payload and `event_id` and a fresh `timestamp`, `X-Timestamp` header and signatures. Didit signs it with the current secret of the destination the delivery belongs to (or the application-level secret for a delivery to the legacy webhook URL), waits up to 5 seconds for your endpoint, records the attempt as a new delivery and returns it with `200` whatever your endpoint answered: read `response_status`. A resend is not retried automatically, and every call sends again, so make your endpoint idempotent on `event_id`. The path has no trailing slash and the request has no body. A scoped API key needs `webhooks: write` and `decisions: read`.", + "operationId": "post_v3_session_webhook_resend", + "x-didit-sdk": { + "resource": "sessions.webhooks", + "method": "resend", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session, user (KYC) or business (KYB): the `session_id` returned by `POST /v3/session/`. The server resolves it against both session types.\n\n**Must be a valid UUID.** The route only matches well-formed UUIDs, so a malformed value is rejected by the URL router before any application code runs and the response is a `404` **HTML** page rather than the JSON `{\"detail\": ...}` envelope shown below.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "22222222-3333-4444-5555-666666666666" + }, + { + "name": "webhook_uuid", + "in": "path", + "required": true, + "description": "UUID of the delivery to resend: the `uuid` of an entry returned by `GET /v3/session/{sessionId}/webhooks/`. It must belong to the session in the path.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "66666666-7777-8888-9999-000000000001" + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/session/22222222-3333-4444-5555-666666666666/webhook/66666666-7777-8888-9999-000000000001/resend' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.webhooks.resend(\n \"22222222-3333-4444-5555-666666666666\",\n \"66666666-7777-8888-9999-000000000001\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.webhooks.resend(\n \"22222222-3333-4444-5555-666666666666\",\n \"66666666-7777-8888-9999-000000000001\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Webhooks.Resend(ctx, \"22222222-3333-4444-5555-666666666666\", \"66666666-7777-8888-9999-000000000001\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().webhooks().resend(\"22222222-3333-4444-5555-666666666666\", \"66666666-7777-8888-9999-000000000001\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->webhooks->resend('22222222-3333-4444-5555-666666666666', '66666666-7777-8888-9999-000000000001');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.webhooks.resend(\n \"22222222-3333-4444-5555-666666666666\",\n \"66666666-7777-8888-9999-000000000001\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Webhooks.ResendAsync(\"22222222-3333-4444-5555-666666666666\", \"66666666-7777-8888-9999-000000000001\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The new delivery, already attempted. `response_status` is what your endpoint answered this time. The original delivery is unchanged.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionWebhookDelivery" + }, + "examples": { + "Accepted on resend": { + "value": { + "uuid": "66666666-7777-8888-9999-000000000004", + "session_id": "22222222-3333-4444-5555-666666666666", + "request_url": "https://yourapp.com/webhooks/didit", + "response_status": 200, + "status": "In Progress", + "webhook_type": "status.updated", + "request_header": "{\"Content-Type\": \"application/json\", \"X-Signature\": \"b8eb1e376517ee0cad8ba98ff7f8fd35fc72eca231000963a96a335f82328835\", \"X-Signature-V2\": \"9efd2ce9ed1303e1d05273ef9608a59363ed9789a05253db4748b3e8e50cb109\", \"X-Signature-Simple\": \"bf3a9dc04b2c20ab539203b0cd5cca2a10f630bf62d8feb6d68d82af80f64774\", \"X-Timestamp\": \"1792159542\", \"User-Agent\": \"DiditWebhook/2.0 +https://didit.me\"}", + "request_body": "{\"application_id\":\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\"created_at\":1792056057,\"environment\":\"live\",\"event_id\":\"33333333-4444-5555-6666-777777777777\",\"sandbox_scenario\":null,\"session_id\":\"22222222-3333-4444-5555-666666666666\",\"status\":\"In Progress\",\"timestamp\":1792159542,\"vendor_data\":\"user-123\",\"webhook_type\":\"status.updated\",\"workflow_id\":\"44444444-5555-6666-7777-888888888888\",\"workflow_version\":3}", + "created_at": "2026-10-16T14:05:42.330981Z" + } + } + } + } + } + }, + "400": { + "description": "The delivery could not be resent. Branch on `code`: `webhook_url_missing` means the delivery has no recorded URL and nothing is sent; `webhook_resend_error` means the stored delivery could not be signed again or the attempt could not be completed. After `webhook_resend_error`, `detail` starts with `Error processing webhook resend:` followed by a diagnostic text that can change.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string", + "description": "Stable machine-readable error code. Branch on this, not on `detail`." + } + } + }, + "examples": { + "No recorded URL": { + "value": { + "detail": "The webhook delivery does not have a request URL configured", + "code": "webhook_url_missing" + } + }, + "Resend failed": { + "value": { + "detail": "Error processing webhook resend: Invalid webhook data format", + "code": "webhook_resend_error" + } + } + } + } + } + }, + "401": { + "description": "The API key has expired: it was created with an expiry date that has passed. A wrong or revoked key also gets `401`; a missing key gets `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "The request is not allowed. No `x-api-key`, or an invalid or revoked key, gets `Authentication credentials were not provided or are invalid.` A key of another application gets `You do not have permission to perform this action.` A scoped key without the access this endpoint needs gets a message naming the missing access, and a key used from outside its IP allowlist gets `This API key can't be used from this IP address.` An unknown `sessionId` returns `404` before the credential is checked.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid key": { + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Key of another application": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without webhooks write": { + "value": { + "detail": "This API key has no write access to webhooks." + } + }, + "Scoped key without decisions read": { + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Outside the IP allowlist": { + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist or has been deleted (`Not found.`, returned before the credential is checked), or it is outside the workflow or status limits of a scoped key (`Session not found.`). A delivery that does not exist, belongs to another session or is no longer available returns `Webhook log not found`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "value": { + "detail": "Not found." + } + }, + "Outside the key's limits": { + "value": { + "detail": "Session not found." + } + }, + "Delivery not in this session": { + "value": { + "detail": "Webhook log not found" + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: more than 300 write requests (POST, PATCH, PUT, DELETE) in the current one-minute window for this API key. Wait `Retry-After` seconds before retrying.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + }, + "X-RateLimit-Limit": { + "description": "Requests allowed in the one-minute window (300).", + "schema": { + "type": "integer", + "example": 300 + } + }, + "X-RateLimit-Remaining": { + "description": "Requests left in the current window.", + "schema": { + "type": "integer", + "example": 0 + } + }, + "X-RateLimit-Reset": { + "description": "Unix time (seconds) when the current window resets.", + "schema": { + "type": "integer", + "example": 1792056120 + } + }, + "X-RateLimit-Scope": { + "description": "Name of the budget that was exceeded.", + "schema": { + "type": "string", + "example": "generic-write" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Throttled": { + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/events/": { + "get": { + "summary": "List session events", + "description": "Read the event timeline of one verification session, newest first by `sent_at`: the screens the user saw, buttons and uploads, failed attempts, status and feature-step changes, and the workflow routing trace. Works for User Verification (KYC) and Business Verification (KYB) session IDs. Events are stored asynchronously, so a new event can take a few seconds to appear. Results are paginated with `limit` and `offset`; follow `next` until it is `null`. A scoped API key needs `sessions: read` and `decisions: read`, and without `media: read` it receives `file_url` as `null`. The routing events are described in [Retrieve session](https://docs.didit.me/sessions-api/retrieve-session).", + "operationId": "get_v3_session_events", + "x-didit-sdk": { + "resource": "sessions", + "method": "list_events", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of events per page. Defaults to 50. A value below 1 or a non-numeric value falls back to 50; a value above 1,000,000 returns `400`.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000, + "example": 50 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of results to skip before the first returned one. Defaults to 0; a negative or non-numeric value falls back to 0, a value above 1,000,000 returns `400`. Prefer following the `next` and `previous` URLs.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000, + "example": 0 + } + }, + { + "name": "ordering", + "in": "query", + "required": false, + "description": "Optional sort override. The default order is `-sent_at`. Pass a result field name (for example `sent_at`, `created_at`, `event_type`, `feature_step` or `step`) to sort ascending, prefix it with `-` to sort descending, and separate several fields with commas. Unknown fields are ignored.", + "schema": { + "type": "string", + "example": "sent_at" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/events/?limit=50' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.sessions.list_events(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.sessions.listEvents(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Sessions.ListEventsAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsListEventsParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.sessions().listEvents(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->listEvents('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.list_events(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Sessions.ListEventsAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "One page of the session's events, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Total number of events of the session." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionEvent" + } + } + } + }, + "examples": { + "Completed session": { + "value": { + "count": 3, + "next": null, + "previous": null, + "results": [ + { + "uuid": "aaaaaaaa-1111-2222-3333-444444444444", + "event_type": "SESSION_STATUS_CHANGED", + "created_at": "2026-09-14T10:02:13.904512Z", + "component": null, + "feature_step": null, + "step": null, + "details": { + "old_status": "In Progress", + "current_status": "Approved" + }, + "ip_address": null, + "user_agent": null, + "device_info": null, + "file_url": null, + "sent_at": 1789380131482 + }, + { + "uuid": "bbbbbbbb-1111-2222-3333-444444444444", + "event_type": "FEATURE_STEP_CHANGED", + "created_at": "2026-09-14T10:01:49.210377Z", + "component": null, + "feature_step": null, + "step": null, + "details": { + "old_feature_step": "OCR", + "current_feature_step": "LIVENESS" + }, + "ip_address": null, + "user_agent": null, + "device_info": null, + "file_url": null, + "sent_at": 1789380107103 + }, + { + "uuid": "cccccccc-1111-2222-3333-444444444444", + "event_type": "STEP_RENDERED", + "created_at": "2026-09-14T10:01:00.581930Z", + "component": "DocumentCapture", + "feature_step": "OCR", + "step": "DOCUMENT_CAPTURE_FRONT", + "details": {}, + "ip_address": "203.0.113.24", + "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1", + "device_info": { + "device_model": "iPhone" + }, + "file_url": null, + "sent_at": 1789380058776 + } + ] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is above 1,000,000. The body is a JSON array with one message.", + "content": { + "application/json": { + "examples": { + "Limit too large": { + "summary": "limit=2000000", + "value": [ + "limit must not exceed 1000000." + ] + }, + "Offset too large": { + "summary": "offset=2000000", + "value": [ + "offset must not exceed 1000000." + ] + } + }, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions read": { + "summary": "Scoped key lacks sessions: read", + "value": { + "detail": "This API key has no read access to sessions." + } + }, + "Scoped key without decisions read": { + "summary": "Scoped key lacks decisions: read", + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted, or is outside a scoped key's workflow or approved-only limits. The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's workflow or approved-only limits", + "value": { + "detail": "Session not found." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 600 GET requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/aml-screening-history/": { + "get": { + "summary": "List AML screening history", + "description": "List every completed ongoing-monitoring re-screen of a session's AML check, newest first by `completed_at`, including the runs where nothing changed: alerts, activity entries and webhooks fire only when a result moves, so this history is the evidence that a clean subject was still being screened. Works for KYC and KYB session IDs; pass `node_id` when the session ran more than one AML step. Only ongoing-monitoring re-screens are listed (the first screening is part of the session decision) and runs from before this history existed are not reconstructed, so a session without ongoing monitoring returns an empty list. `count` is exact up to 100 and capped at 100 above that, so follow `next` until it is `null`. Pass `month` to read one calendar month at a time. A scoped API key needs `decisions: read`; see [Continuous AML monitoring](https://docs.didit.me/core-technology/aml-screening/continuous-monitoring-aml-screening).", + "operationId": "get_v3_session_aml_screening_history", + "x-didit-sdk": { + "resource": "sessions", + "method": "list_aml_screening_history", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node ID of the AML check. Needed only when the session ran more than one AML step; without it the most recent AML check is used. An unknown node returns `404`.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + }, + { + "name": "month", + "in": "query", + "required": false, + "description": "A UTC calendar month as `YYYY-MM`. Only screenings completed in that month are returned; without it every month is returned. A malformed month returns `400`.", + "schema": { + "type": "string", + "pattern": "^\\d{4}-(0[1-9]|1[0-2])$", + "example": "2026-09" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "Maximum number of screening runs per page. Defaults to 50. A value below 1 or a non-numeric value falls back to 50; a value above 1,000,000 returns `400`.", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1, + "maximum": 1000000, + "example": 50 + } + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "Number of results to skip before the first returned one. Defaults to 0; a negative or non-numeric value falls back to 0, a value above 1,000,000 returns `400`. Prefer following the `next` and `previous` URLs.", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 1000000, + "example": 0 + } + }, + { + "name": "ordering", + "in": "query", + "required": false, + "description": "Optional sort override. The default order is `-completed_at`. Pass a result field name (for example `completed_at`, `started_at`, `outcome` or `score`) to sort ascending, prefix it with `-` to sort descending, and separate several fields with commas. Unknown fields are ignored.", + "schema": { + "type": "string", + "example": "completed_at" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/aml-screening-history/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.sessions.list_aml_screening_history(\n \"11111111-2222-3333-4444-555555555555\",\n):\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.sessions.listAmlScreeningHistory(\"11111111-2222-3333-4444-555555555555\")) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Sessions.ListAMLScreeningHistoryAutoPaging(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsListAMLScreeningHistoryParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.sessions().listAmlScreeningHistory(\"11111111-2222-3333-4444-555555555555\")) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->listAmlScreeningHistory('11111111-2222-3333-4444-555555555555') as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.list_aml_screening_history(\"11111111-2222-3333-4444-555555555555\").auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Sessions.ListAmlScreeningHistoryAsync(\"11111111-2222-3333-4444-555555555555\"))\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "One page of screening runs, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "maximum": 100, + "description": "Number of runs, exact up to 100 and capped at 100 above that." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or `null` on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or `null` on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AMLScreeningRun" + } + } + } + }, + "examples": { + "Monitored subject": { + "value": { + "count": 2, + "next": null, + "previous": null, + "results": [ + { + "uuid": "dddddddd-1111-2222-3333-444444444444", + "run_id": "aaaaaaaa-1111-2222-3333-444444444444", + "completed_at": "2026-09-14T04:12:33.481207Z", + "started_at": "2026-09-14T04:00:01.002311Z", + "outcome": "NO_CHANGE", + "trigger": "ONGOING_MONITORING", + "provider": "aml_data_source", + "status": "Approved", + "score": 0.0, + "total_hits": 0, + "new_hits": 0, + "entities_screened": 1, + "dataset_freshness": { + "captured_at": "2026-09-14T03:58:02.114820+00:00", + "provider": "aml_data_source", + "catalog_version": "2026-09-13", + "sources": { + "sanctions": { + "last_updated_at": "2026-09-13T21:10:00+00:00", + "oldest_updated_at": "2026-09-11T06:00:00+00:00", + "dataset_count": 120, + "dated_dataset_count": 120 + }, + "pep": { + "last_updated_at": "2026-09-13T08:00:00+00:00", + "oldest_updated_at": null, + "dataset_count": 45, + "dated_dataset_count": 44 + }, + "warnings": { + "last_updated_at": "2026-09-12T18:45:00+00:00", + "oldest_updated_at": "2026-09-01T00:00:00+00:00", + "dataset_count": 30, + "dated_dataset_count": 30 + } + } + } + }, + { + "uuid": "eeeeeeee-1111-2222-3333-444444444444", + "run_id": "bbbbbbbb-1111-2222-3333-444444444444", + "completed_at": "2026-09-13T04:11:58.020914Z", + "started_at": "2026-09-13T04:00:00.871022Z", + "outcome": "FAILED", + "trigger": "ONGOING_MONITORING", + "provider": "aml_data_source", + "status": "Approved", + "score": 0.0, + "total_hits": 0, + "new_hits": null, + "entities_screened": 0, + "dataset_freshness": null + } + ] + } + } + } + } + } + }, + "400": { + "description": "`limit` or `offset` is above 1,000,000 (a JSON array with one message), or `month` is not a `YYYY-MM` calendar month (an object keyed by `month`).", + "content": { + "application/json": { + "examples": { + "Limit too large": { + "summary": "limit=2000000", + "value": [ + "limit must not exceed 1000000." + ] + }, + "Offset too large": { + "summary": "offset=2000000", + "value": [ + "offset must not exceed 1000000." + ] + }, + "Malformed month": { + "summary": "month=2026-13", + "value": { + "month": [ + "Not a calendar month. Use YYYY-MM." + ] + } + } + }, + "schema": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "object", + "properties": { + "month": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + ] + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions read": { + "summary": "Scoped key lacks decisions: read", + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits, or it has no AML check (for the given `node_id`). The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's limits (also returned for a session deleted moments ago)", + "value": { + "detail": "Session not found." + } + }, + "No AML check": { + "summary": "The session never ran an AML step", + "value": { + "detail": "The session does not have an AML check." + } + }, + "No AML check for node": { + "summary": "node_id does not match an AML check of the session", + "value": { + "detail": "The session does not have an AML check for node 'feature_aml_2'." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 600 GET requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/aml-screening-history/export/": { + "get": { + "summary": "Export AML screening history as CSV", + "description": "Download the same ongoing-monitoring history as `GET /v3/session/{sessionId}/aml-screening-history/` as a CSV file, one line per completed re-screen, newest first, ready to hand to an auditor. The file is streamed whole (no pagination) and starts with a header line; an empty cell means unknown, dataset dates are never estimated, and a value that would start a spreadsheet formula is prefixed with an apostrophe. Works for KYC and KYB session IDs; pass `node_id` when the session ran more than one AML step. Pass `month` to export one calendar month. Errors are returned as JSON, not CSV. A scoped API key needs `decisions: read` and `media: read`: the response is a file, so a key without `media: read` gets `403`.", + "operationId": "get_v3_session_aml_screening_history_export", + "x-didit-sdk": { + "resource": "sessions", + "method": "export_aml_screening_history", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node ID of the AML check. Needed only when the session ran more than one AML step; without it the most recent AML check is used. An unknown node returns `404`.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + }, + { + "name": "month", + "in": "query", + "required": false, + "description": "A UTC calendar month as `YYYY-MM`. Only screenings completed in that month are returned; without it every month is returned. A malformed month returns `400`.", + "schema": { + "type": "string", + "pattern": "^\\d{4}-(0[1-9]|1[0-2])$", + "example": "2026-09" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/aml-screening-history/export/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -o aml-screening-history.csv" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.sessions.export_aml_screening_history(\n \"11111111-2222-3333-4444-555555555555\",\n)\nwith open(\"download.csv\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.sessions.exportAmlScreeningHistory(\"11111111-2222-3333-4444-555555555555\");\nawait writeFile(\"download.csv\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Sessions.ExportAMLScreeningHistory(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsExportAMLScreeningHistoryParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.csv\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().exportAmlScreeningHistory(\"11111111-2222-3333-4444-555555555555\")\n .writeTo(Path.of(\"download.csv\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->exportAmlScreeningHistory('11111111-2222-3333-4444-555555555555');\n$content->saveTo('download.csv');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.sessions.export_aml_screening_history(\"11111111-2222-3333-4444-555555555555\")\nFile.binwrite(\"download.csv\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Sessions.ExportAmlScreeningHistoryAsync(\"11111111-2222-3333-4444-555555555555\");\nawait content.SaveToAsync(\"download.csv\");" + } + ], + "responses": { + "200": { + "description": "The CSV file, streamed as `text/csv` with CRLF line endings. Columns, in order: `screened_at` (when the re-screen finished), `outcome` (`NO_CHANGE`, `CHANGED` or `FAILED`), `provider` (opaque identifier of the AML data source; do not branch on it), `aml_status`, `aml_score`, `total_hits`, `datasets_captured_at`, `datasets_sanctions_last_updated_at`, `datasets_pep_last_updated_at`, `datasets_warnings_last_updated_at`, `datasets_adverse_media_last_updated_at`, `datasets_catalog_version`, `run_id` (the monitoring run), `entities_screened` (`1`, or `0` for a failed run) and `new_hits` (empty when unknown). Dates are ISO 8601 with a UTC offset. A session with no re-screens yet returns the header line only.", + "headers": { + "Content-Disposition": { + "description": "`attachment; filename=\"aml-screening-history-{sessionId}.csv\"`, or `aml-screening-history-{sessionId}-{month}.csv` when `month` is passed.", + "schema": { + "type": "string", + "example": "attachment; filename=\"aml-screening-history-11111111-2222-3333-4444-555555555555.csv\"" + } + } + }, + "content": { + "text/csv": { + "schema": { + "type": "string", + "format": "binary" + }, + "example": "screened_at,outcome,provider,aml_status,aml_score,total_hits,datasets_captured_at,datasets_sanctions_last_updated_at,datasets_pep_last_updated_at,datasets_warnings_last_updated_at,datasets_adverse_media_last_updated_at,datasets_catalog_version,run_id,entities_screened,new_hits\r\n2026-09-14T04:12:33.481207+00:00,NO_CHANGE,aml_data_source,Approved,0.0,0,2026-09-14T03:58:02.114820+00:00,2026-09-13T21:10:00+00:00,2026-09-13T08:00:00+00:00,2026-09-12T18:45:00+00:00,,2026-09-13,aaaaaaaa-1111-2222-3333-444444444444,1,0\r\n2026-09-13T04:11:58.020914+00:00,FAILED,aml_data_source,Approved,0.0,0,,,,,,,bbbbbbbb-1111-2222-3333-444444444444,0,\r\n" + } + } + }, + "400": { + "description": "`month` is not a `YYYY-MM` calendar month.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "month": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "examples": { + "Malformed month": { + "summary": "month=2026-13", + "value": { + "month": [ + "Not a calendar month. Use YYYY-MM." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions read": { + "summary": "Scoped key lacks decisions: read", + "value": { + "detail": "This API key has no read access to decisions." + } + }, + "Scoped key without media read": { + "summary": "Scoped key lacks media: read (the response is a file)", + "value": { + "detail": "This API key has no read access to media." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits, or it has no AML check (for the given `node_id`). The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's limits (also returned for a session deleted moments ago)", + "value": { + "detail": "Session not found." + } + }, + "No AML check": { + "summary": "The session never ran an AML step", + "value": { + "detail": "The session does not have an AML check." + } + }, + "No AML check for node": { + "summary": "node_id does not match an AML check of the session", + "value": { + "detail": "The session does not have an AML check for node 'feature_aml_2'." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 600 GET requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/add-images/": { + "patch": { + "summary": "Add an image to a session", + "description": "Attach one extra image or PDF to the ID verification (`model_type=kyc`) or Proof of Address (`model_type=poa`) result of a finished User Verification (KYC) session, for example evidence collected outside the flow. The session must be `Approved`, `Declined`, `In Review` or `Kyc Expired` and carry the result named by `model_type`; Business Verification (KYB) sessions are not supported. Each call appends one file (to the most recent result when the step ran more than once), keeps the existing files and records a `FILE_ADDED` entry in the session activity log; the upload sends no webhook and does not change the session status. The file is compressed on upload and is returned in `extra_files` of the session decision. This endpoint is limited to 10 requests per minute per API key. A scoped API key needs `sessions: write`, and without `media: read` it receives the `extra_files` links as `null`.", + "operationId": "patch_v3_session_add_images", + "x-didit-sdk": { + "resource": "sessions", + "method": "add_images", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the User Verification (KYC) session to add the file to (the `session_id` returned by `POST /v3/session/`). A Business Verification (KYB) session ID returns `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "file", + "model_type" + ], + "properties": { + "file": { + "type": "string", + "format": "binary", + "description": "The image or PDF. Allowed extensions: `jpg`, `jpeg`, `png`, `webp`, `tiff`, `pdf`. At most 15 MB as uploaded; the file is then compressed and rejected with `400` if it is still larger than 2 MB (2,097,152 bytes). Empty files are rejected." + }, + "model_type": { + "type": "string", + "enum": [ + "kyc", + "poa" + ], + "description": "`kyc` adds the file to the ID verification result, `poa` to the Proof of Address result.", + "example": "kyc" + }, + "file_password": { + "type": "string", + "description": "Password of an encrypted PDF. Without it, or with a wrong one, an encrypted PDF returns `400`." + } + } + }, + "example": { + "file": "(binary)", + "model_type": "kyc" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/add-images/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'file=@./back-of-id.jpg' \\\n -F 'model_type=kyc'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.add_images(\n \"11111111-2222-3333-4444-555555555555\",\n file=open(\"image.jpg\", \"rb\"),\n model_type=\"kyc\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.addImages(\"11111111-2222-3333-4444-555555555555\", {\n file: await fileFromPath(\"image.jpg\"),\n model_type: \"kyc\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.AddImages(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsAddImagesParams{\n\tFile: didit.FileFromPath(\"image.jpg\"),\n\tModelType: \"kyc\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.types.SessionsAddImagesParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().addImages(\"11111111-2222-3333-4444-555555555555\", SessionsAddImagesParams.builder()\n .file(Path.of(\"image.jpg\"))\n .modelType(SessionsAddImagesParams.ModelType.of(\"kyc\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->addImages('11111111-2222-3333-4444-555555555555', [\n 'file' => File::fromPath('image.jpg'),\n 'model_type' => 'kyc',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.add_images(\n \"11111111-2222-3333-4444-555555555555\",\n file: Pathname(\"image.jpg\"),\n model_type: \"kyc\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.AddImagesAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsAddImagesParams\n{\n File = FileUpload.FromPath(\"image.jpg\"),\n ModelType = \"kyc\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "File added. Returns every extra file of that result, including the new one.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "extra_files" + ], + "properties": { + "extra_files": { + "type": "array", + "description": "Short-lived presigned links to all extra files of the result named by `model_type`, oldest first. Each link is `null` for a scoped API key without `media: read`. Read fresh links from the session decision later.", + "items": { + "type": "string", + "format": "uri", + "nullable": true + } + } + } + }, + "example": { + "extra_files": [ + "https://media.example.com/session/11111111-2222-3333-4444-555555555555/kyc-extra-files/66666666-7777-8888-9999-000000000000.jpg?expires=1789383731&signature=abc123" + ] + } + } + } + }, + "400": { + "description": "Validation error. Field errors arrive as `{\"\": [\"\"]}`; encrypted-PDF errors as `{\"detail\": \"...\"}`.", + "content": { + "application/json": { + "examples": { + "Missing file": { + "summary": "No file part in the form", + "value": { + "file": [ + "No file was submitted." + ] + } + }, + "Missing model_type": { + "summary": "model_type omitted", + "value": { + "model_type": [ + "This field is required." + ] + } + }, + "Invalid model_type": { + "summary": "model_type is not kyc or poa", + "value": { + "model_type": [ + "\"passport\" is not a valid choice." + ] + } + }, + "File too large": { + "summary": "Upload over 15 MB", + "value": { + "file": [ + "File size should not exceed 15 MB" + ] + } + }, + "Too large after compression": { + "summary": "Still over 2 MB after compression", + "value": { + "file": [ + "File size is too big. Max allowed size is 2097152 bytes." + ] + } + }, + "Encrypted PDF": { + "summary": "Encrypted PDF without file_password", + "value": { + "detail": "The PDF is encrypted. Please upload a decrypted PDF or a photo instead." + } + }, + "Wrong PDF password": { + "summary": "file_password does not open the PDF", + "value": { + "detail": "The PDF password is incorrect. Please provide the correct password." + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without sessions write": { + "summary": "Scoped key lacks sessions: write", + "value": { + "detail": "This API key has no write access to sessions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits; it is a KYB session or not in `Approved`, `Declined`, `In Review` or `Kyc Expired`; or it has no result of the requested `model_type` (checked before the file is validated). The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's workflow or approved-only limits", + "value": { + "detail": "Session not found." + } + }, + "Not eligible": { + "summary": "Session not finished yet, or a KYB session ID", + "value": { + "detail": "No Session matches the given query." + } + }, + "No ID verification": { + "summary": "model_type=kyc on a session without ID verification", + "value": { + "detail": "The session does not have KYC data." + } + }, + "No Proof of Address": { + "summary": "model_type=poa on a session without Proof of Address", + "value": { + "detail": "The session does not have POA data." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 10 add-images requests per minute per API key (rejected requests count too). The shared budget of 300 write requests per minute also applies. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Session add-images rate limit exceeded. You can make up to 10 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/update-aml-status/": { + "patch": { + "summary": "Update an AML check status", + "description": "Set the status of a session's AML screening check as a whole (the `status` of an `aml_screenings[]` entry in the session decision), for example `Approved` after your team cleared every hit. Works for KYC and KYB session IDs; pass `node_id` (query parameter or body field) when the session ran more than one AML step, otherwise the most recent AML check is updated. When the status actually changes, an `AML_STATUS_UPDATED` activity entry is recorded and a `data.updated` [webhook](https://docs.didit.me/integration/webhooks) is sent; sending the current status returns `200` and changes nothing. The session status is not recomputed: use `PATCH /v3/session/{sessionId}/update-status/` for the session decision and `PATCH /v3/session/{sessionId}/update-aml-hit-status/` for single hits. A scoped API key needs `decisions: write`.", + "operationId": "patch_v3_session_update_aml_status", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_aml_status", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs: the server resolves the ID across both session types. Must be a well-formed UUID; a malformed value does not match the route and returns an HTML `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow graph node ID of the AML check to update. Needed only when the session ran more than one AML step. Takes precedence over `node_id` in the body.", + "schema": { + "type": "string", + "example": "feature_aml_1" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "New status of the AML check.", + "example": "Approved" + }, + "node_id": { + "type": "string", + "description": "Workflow graph node ID of the AML check, as an alternative to the query parameter.", + "example": "feature_aml_1" + } + } + }, + "example": { + "status": "Approved" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PATCH 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-aml-status/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"status\": \"Approved\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_aml_status(\n \"11111111-2222-3333-4444-555555555555\",\n status=\"Approved\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateAmlStatus(\"11111111-2222-3333-4444-555555555555\", {\n status: \"Approved\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateAMLStatus(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsUpdateAMLStatusParams{\n\tStatus: \"Approved\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsUpdateAmlStatusParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateAmlStatus(\"11111111-2222-3333-4444-555555555555\", SessionsUpdateAmlStatusParams.builder()\n .status(SessionsUpdateAmlStatusParams.Status.of(\"Approved\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateAmlStatus('11111111-2222-3333-4444-555555555555', [\n 'status' => 'Approved',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_aml_status(\n \"11111111-2222-3333-4444-555555555555\",\n status: \"Approved\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateAmlStatusAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsUpdateAmlStatusParams\n{\n Status = \"Approved\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "AML check status set (or unchanged when it already had this value).", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message", + "status", + "previous_status" + ], + "properties": { + "message": { + "type": "string", + "description": "Always `AML status updated successfully`." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Status of the AML check now." + }, + "previous_status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Status before the call. Equal to `status` when nothing changed." + } + } + }, + "example": { + "message": "AML status updated successfully", + "status": "Approved", + "previous_status": "In Review" + } + } + } + }, + "400": { + "description": "Validation error: `status` missing or not one of the allowed values. The AML check lookup runs first, so a session without an AML check returns `404` even with an invalid body.", + "content": { + "application/json": { + "examples": { + "Missing status": { + "summary": "Empty body or no status", + "value": { + "status": [ + "This field is required." + ] + } + }, + "Invalid status": { + "summary": "status not in the enum", + "value": { + "status": [ + "\"Cleared\" is not a valid choice." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted or is outside a scoped key's limits, or it has no AML check (for the given `node_id`). The session lookup runs before the credential check, so an unknown `sessionId` returns `404` even without a valid key.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Unknown session": { + "summary": "No session with this ID exists, or it was deleted", + "value": { + "detail": "Not found." + } + }, + "Outside key limits": { + "summary": "The session is outside a scoped key's limits (also returned for a session deleted moments ago)", + "value": { + "detail": "Session not found." + } + }, + "No AML check": { + "summary": "The session never ran an AML step", + "value": { + "detail": "The session does not have an AML check." + } + }, + "No AML check for node": { + "summary": "node_id does not match an AML check of the session", + "value": { + "detail": "The session does not have an AML check for node 'feature_aml_2'." + } + } + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/status/": { + "get": { + "summary": "Get service status (public, unauthenticated)", + "description": "Returns the state of each Didit service (`operational`, `degraded` or `down`), the overall state (the worst of them), and the incidents that are ongoing or were resolved in the last 7 days. Use it to power a status widget or to check Didit's health from your integration.\n\nPublic: send no auth header. The response contains no organization data and is the same for every caller. Responses are cached for 30 seconds, so poll no more than once every 30 seconds.", + "operationId": "get_v3_status", + "tags": [ + "Status" + ], + "security": [ + {} + ], + "parameters": [], + "responses": { + "200": { + "description": "Current service status.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServiceStatusFeed" + }, + "examples": { + "One service degraded": { + "summary": "An ongoing incident and one resolved this week", + "value": { + "status": "degraded", + "updated_at": "2026-10-13T09:41:07.512345Z", + "services": [ + { + "key": "service_id_verification", + "name": "ID verification", + "status": "operational" + }, + { + "key": "service_liveness_face_match", + "name": "Liveness and face match", + "status": "operational" + }, + { + "key": "service_aml_screening", + "name": "AML screening", + "status": "operational" + }, + { + "key": "service_kyb_registries", + "name": "KYB registries", + "status": "operational" + }, + { + "key": "service_database_validation", + "name": "Database validation", + "status": "degraded" + }, + { + "key": "service_phone_delivery", + "name": "Phone verification delivery", + "status": "operational" + }, + { + "key": "service_email_delivery", + "name": "Email verification delivery", + "status": "operational" + }, + { + "key": "service_transaction_monitoring", + "name": "Transaction monitoring", + "status": "operational" + } + ], + "incidents": [ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "service": "service_database_validation", + "service_name": "Database validation", + "state": "degraded", + "status": "ongoing", + "title": "Database validation is degraded in Brazil.", + "body": "Checks against Brazil (CPF) may be slow or fail since 08:55 UTC.", + "affected": "Brazil (CPF)", + "country_name": "Brazil", + "started_at": "2026-10-13T08:55:00Z", + "resolved_at": null, + "duration_minutes": null + }, + { + "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "service": "service_aml_screening", + "service_name": "AML screening", + "state": "down", + "status": "resolved", + "title": "AML screening is back to normal.", + "body": "Recovered at 14:32 UTC after 17 minutes.", + "affected": "", + "country_name": "", + "started_at": "2026-10-11T14:15:00Z", + "resolved_at": "2026-10-11T14:32:00Z", + "duration_minutes": 17 + } + ] + } + }, + "All operational": { + "summary": "No incident in the last 7 days", + "value": { + "status": "operational", + "updated_at": "2026-10-13T09:41:07.512345Z", + "services": [ + { + "key": "service_id_verification", + "name": "ID verification", + "status": "operational" + }, + { + "key": "service_liveness_face_match", + "name": "Liveness and face match", + "status": "operational" + }, + { + "key": "service_aml_screening", + "name": "AML screening", + "status": "operational" + }, + { + "key": "service_kyb_registries", + "name": "KYB registries", + "status": "operational" + }, + { + "key": "service_database_validation", + "name": "Database validation", + "status": "operational" + }, + { + "key": "service_phone_delivery", + "name": "Phone verification delivery", + "status": "operational" + }, + { + "key": "service_email_delivery", + "name": "Email verification delivery", + "status": "operational" + }, + { + "key": "service_transaction_monitoring", + "name": "Transaction monitoring", + "status": "operational" + } + ], + "incidents": [] + } + } + } + } + } + }, + "429": { + "description": "More than 600 GET requests in a minute from the same IP address (or the same API key, if you send one). Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "detail" + ], + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "GET request rate limit exceeded. You can make up to 600 requests per minute." + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -fsS https://verification.didit.me/v3/status/" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresp = requests.get('https://verification.didit.me/v3/status/', timeout=5)\nresp.raise_for_status()\nfeed = resp.json()\nprint(feed['status'])\nfor service in feed['services']:\n if service['status'] != 'operational':\n print(service['key'], service['status'])" + }, + { + "lang": "javascript", + "label": "JavaScript", + "source": "const feed = await fetch('https://verification.didit.me/v3/status/').then((r) => r.json());\nconsole.log(feed.status);\nconst affected = feed.services.filter((s) => s.status !== 'operational');\nconsole.log(affected.map((s) => `${s.key}: ${s.status}`));" + } + ], + "x-didit-sdk": { + "resource": "system", + "method": "retrieve_status", + "auth": "public", + "server": false + } + } + }, + "/v3/organization/bank-verification-pricing/": { + "get": { + "summary": "Get bank verification prices (public, unauthenticated)", + "description": "Returns the list price of each Bank Verification method in every country: bank sign-in with its balance and transactions add-ons, and the account check. Bank Verification is not enabled in production yet, so a bank step is refused on production workflows until it is; treat these prices as a list price, not as an offer you can buy today. Use it to quote a bank step before you build a workflow, or to show bank prices in your own product.\n\nPublic: send no auth header. The response contains no organization data and is the same for every caller. It is cached publicly for one hour and carries an `ETag`, so send `If-None-Match` to get a `304 Not Modified` when nothing changed.\n\nA method is `available` only where Didit serves it in production today, and its `price_usd` is `null` everywhere else, so the feed never shows a price you cannot buy. `available: false` covers a country with no approved price, a method the country does not support, and a route that is not switched on yet.\n\nThese are list prices before your plan discount. Your plan discount applies to the whole bank price, and a negotiated or contract price replaces the list price for your organization.", + "operationId": "get_v3_bank_verification_pricing", + "tags": [ + "Pricing" + ], + "security": [ + {} + ], + "parameters": [], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -fsS https://verification.didit.me/v3/organization/bank-verification-pricing/" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# This call needs no API key.\nclient = Didit()\n\nresult = client.pricing.bank_verification()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// This call needs no API key.\nconst didit = new Didit();\n\nconst result = await didit.pricing.bankVerification();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// This call needs no API key.\nclient := didit.NewClient()\n\nresult, err := client.Pricing.BankVerification(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// This call needs no API key.\nDidit client = new Didit();\n\nvar result = client.pricing().bankVerification();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "pricing->bankVerification();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# This call needs no API key.\ndidit = Didit::Client.new\n\nresult = didit.pricing.bank_verification\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// This call needs no API key.\nusing var client = new DiditClient();\n\nvar result = await client.Pricing.BankVerificationAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-sdk": { + "resource": "pricing", + "method": "bank_verification", + "auth": "none", + "server": true + }, + "responses": { + "200": { + "description": "Bank Verification list prices by country.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BankVerificationPricing" + }, + "examples": { + "Two countries": { + "summary": "One country with both methods, one with neither", + "value": { + "currency": "USD", + "statement_upload": { + "price_usd": "0.0000", + "included_after_charged_bank_attempt": true, + "standalone_available": false + }, + "own_provider_fee_usd": "0.0200", + "countries": { + "GBR": { + "name": "United Kingdom", + "methods": { + "open_banking": { + "available": true, + "market": "UK and EEA", + "price_usd": "0.7900", + "add_ons": { + "balance": "0.3500", + "transactions": "1.3000" + } + }, + "account_lookup": { + "available": true, + "price_usd": "0.4000" + } + } + }, + "CAN": { + "name": "Canada", + "methods": { + "open_banking": { + "available": false, + "market": null, + "price_usd": null, + "add_ons": {} + }, + "account_lookup": { + "available": false, + "price_usd": null + } + } + } + } + } + } + } + } + } + }, + "304": { + "description": "Not modified: the `ETag` you sent in `If-None-Match` still matches. No body." + } + } + } + }, + "/v3/session/{sessionId}/simulate/": { + "post": { + "summary": "Simulate a sandbox session's status", + "description": "Force a terminal status on a sandbox session after the fact, from your backend, without running the flow. Sandbox applications only: a live session returns `404`. Works for User Verification (KYC) and Business Verification (KYB) sessions.\n\nSet `new_status`, or let `scenario` or `risk` supply it. `risk` (or the first risk the scenario's magic values carry) adds that warning to the session. The change is recorded in the session's activity.\n\n`send_webhooks` (default `true`) sends exactly the webhooks a real status change sends. With `false` the status still changes and the console still updates live, but no webhook is logged or sent, the missed-webhook recovery never re-sends that decision, and the next status change sends webhooks as usual. The response echoes the value applied.\n\nRequires the `write:sessions` privilege; a scoped API key needs write access to sessions. An unknown `session_id` returns `404` and another application's session returns `403`, both before the body is read; a live session returns `404` only after the body is validated. To simulate from the hosted flow with the session's own token, use `POST /v3/session/{sessionId}/sandbox/simulate/`. See [Sandbox & Test Data](/integration/sandbox-testing) for the scenario catalog.", + "operationId": "post_v3_session_simulate", + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the sandbox verification session. Accepts both user (KYC) and business (KYB) session IDs.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSimulateRequest" + }, + "examples": { + "Scenario Without Webhooks": { + "summary": "Decline through a scenario without sending webhooks", + "value": { + "scenario": "decline_aml_hit", + "send_webhooks": false + } + }, + "Status Only": { + "summary": "Approve and send the usual webhooks", + "value": { + "new_status": "Approved" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The status changed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSimulateResponse" + }, + "examples": { + "Simulated": { + "summary": "Declined through a scenario, webhooks not sent", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "previous_status": "In Progress", + "status": "Declined", + "scenario": "decline_aml_hit", + "risk": "POSSIBLE_MATCH_FOUND", + "send_webhooks": false + } + } + } + } + } + }, + "400": { + "description": "Validation error: an unknown scenario or risk, no `new_status` when neither `scenario` nor `risk` supplies one, a status this endpoint does not set, or the session already has the requested status (a bare JSON array).", + "content": { + "application/json": { + "examples": { + "Unknown Scenario": { + "summary": "scenario is not in the catalog", + "value": { + "scenario": [ + "Unknown sandbox scenario: 'not-a-scenario'." + ] + } + }, + "Unknown Risk": { + "summary": "risk is not in the catalog", + "value": { + "risk": [ + "Unknown sandbox risk case: 'NOT_A_RISK'." + ] + } + }, + "Missing new_status": { + "summary": "No new_status, scenario or risk", + "value": { + "new_status": [ + "This field is required." + ] + } + }, + "Invalid new_status": { + "summary": "A status the simulation does not set", + "value": { + "new_status": [ + "\"Resubmitted\" is not a valid choice." + ] + } + }, + "Same Status": { + "summary": "The session already has this status", + "value": [ + "The new status is the same as the current status." + ] + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials, a caller without the `write:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no write access to sessions." + } + } + } + } + } + }, + "404": { + "description": "No session with this `session_id` exists, or it is a live (non-sandbox) session.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown or live session", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/simulate/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"scenario\": \"decline_aml_hit\", \"send_webhooks\": false}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.simulate(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n scenario=\"decline_aml_hit\",\n send_webhooks=False,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.simulate(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", {\n scenario: \"decline_aml_hit\",\n send_webhooks: false,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Simulate(ctx, \"3fa85f64-5717-4562-b3fc-2c963f66afa6\", didit.SandboxSimulateRequest{\n\tScenario: didit.String(\"decline_aml_hit\"),\n\tSendWebhooks: didit.Bool(false),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SandboxSimulateRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().simulate(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", SandboxSimulateRequest.builder()\n .scenario(\"decline_aml_hit\")\n .sendWebhooks(false)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->simulate('3fa85f64-5717-4562-b3fc-2c963f66afa6', [\n 'scenario' => 'decline_aml_hit',\n 'send_webhooks' => false,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.simulate(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n scenario: \"decline_aml_hit\",\n send_webhooks: false\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.SimulateAsync(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", new SandboxSimulateRequest\n{\n Scenario = \"decline_aml_hit\",\n SendWebhooks = false,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "simulate", + "auth": "api_key", + "server": true + } + } + }, + "/v3/session/{sessionId}/sandbox/simulate/": { + "post": { + "summary": "Simulate a sandbox session's status from the client", + "description": "The same simulation as `POST /v3/session/{sessionId}/simulate/`, authenticated with the session's own `Session-Token` (returned in `session_token` by [Create Session](/sessions-api/create-session)) instead of your API key, so a test harness or the hosted flow can call it from the client. The body, the response and `send_webhooks` behave exactly as on that endpoint.\n\nA `Session-Token` of a live (non-sandbox) application, or one that does not match the `sessionId` in the path, returns `404`. See [Sandbox & Test Data](/integration/sandbox-testing).", + "operationId": "post_v3_session_sandbox_simulate", + "tags": [ + "Sessions" + ], + "security": [ + { + "SessionTokenAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the sandbox verification session. Must match the session encoded in the Session-Token used to authenticate.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSimulateRequest" + }, + "examples": { + "Scenario Without Webhooks": { + "summary": "Decline through a scenario without sending webhooks", + "value": { + "scenario": "decline_aml_hit", + "send_webhooks": false + } + }, + "Status Only": { + "summary": "Approve and send the usual webhooks", + "value": { + "new_status": "Approved" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The status changed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SandboxSimulateResponse" + }, + "examples": { + "Simulated": { + "summary": "Declined through a scenario, webhooks not sent", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "previous_status": "In Progress", + "status": "Declined", + "scenario": "decline_aml_hit", + "risk": "POSSIBLE_MATCH_FOUND", + "send_webhooks": false + } + } + } + } + } + }, + "400": { + "description": "Validation error: an unknown scenario or risk, no `new_status` when neither `scenario` nor `risk` supplies one, a status this endpoint does not set, or the session already has the requested status (a bare JSON array).", + "content": { + "application/json": { + "examples": { + "Unknown Scenario": { + "summary": "scenario is not in the catalog", + "value": { + "scenario": [ + "Unknown sandbox scenario: 'not-a-scenario'." + ] + } + }, + "Unknown Risk": { + "summary": "risk is not in the catalog", + "value": { + "risk": [ + "Unknown sandbox risk case: 'NOT_A_RISK'." + ] + } + }, + "Missing new_status": { + "summary": "No new_status, scenario or risk", + "value": { + "new_status": [ + "This field is required." + ] + } + }, + "Invalid new_status": { + "summary": "A status the simulation does not set", + "value": { + "new_status": [ + "\"Resubmitted\" is not a valid choice." + ] + } + }, + "Same Status": { + "summary": "The session already has this status", + "value": [ + "The new status is the same as the current status." + ] + } + } + } + } + }, + "403": { + "description": "The `Session-Token` header is missing or invalid.", + "content": { + "application/json": { + "examples": { + "Invalid Session Token": { + "summary": "Missing or invalid Session-Token", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "The `Session-Token` belongs to a live (non-sandbox) application, or does not match the `sessionId` in the path.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Live session, or session_id / token mismatch", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/sandbox/simulate/ \\\n -H 'Session-Token: YOUR_SESSION_TOKEN' \\\n -H 'Content-Type: application/json' \\\n -d '{\"new_status\": \"Approved\", \"send_webhooks\": false}'" + }, + { + "lang": "python", + "label": "Python", + "source": "import requests\n\nresponse = requests.post(\n \"https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/sandbox/simulate/\",\n headers={\"Session-Token\": \"YOUR_SESSION_TOKEN\"},\n json={\"new_status\": \"Approved\", \"send_webhooks\": False},\n)\nresponse.raise_for_status()\nprint(response.json())" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "simulate_sandbox", + "auth": "device_token", + "server": false + } + } + }, + "/v3/session/{sessionId}/features/overrides/": { + "get": { + "summary": "List a session's check overrides", + "description": "Every override of an individual feature (step) status on a User Verification (KYC) or Business Verification (KYB) session, newest first and not paginated: applied ones, ones waiting for the person to finish the flow (`pending_until_flow_finishes`) and cancelled ones, each with who asked, their reason, and whether it applied while the person was still in the flow (`forced`).\n\n`person_in_flow` tells you whether a new override with `defer_until_flow_finishes: true` would wait. `flow_finished_at` is when the person last finished the flow; it is `null` while they are still in it (a resubmission included) and when the session closed before they finished.\n\nRequires the `read:sessions` privilege; a scoped API key needs read access to decisions.", + "operationId": "get_v3_session_feature_overrides", + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + ], + "responses": { + "200": { + "description": "The session's check overrides, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "session_id", + "person_in_flow", + "flow_finished_at", + "overrides" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "person_in_flow": { + "type": "boolean", + "description": "Whether the person is still in the flow, so a new override with `defer_until_flow_finishes: true` would wait.", + "example": true + }, + "flow_finished_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the person last finished the flow. `null` while they are still in it (a resubmission included) and when the session closed before they finished (abandoned, expired or decided mid-flow).", + "example": null + }, + "overrides": { + "type": "array", + "description": "Every override of the session, newest first.", + "items": { + "$ref": "#/components/schemas/FeatureStatusOverride" + } + } + } + }, + "examples": { + "Overrides": { + "summary": "One override waiting, one applied", + "value": { + "session_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "person_in_flow": true, + "flow_finished_at": null, + "overrides": [ + { + "id": "9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3", + "node_id": "feature_liveness_1", + "feature_type": "", + "feature_model": "Face", + "previous_status": "Declined", + "new_status": "Approved", + "state": "pending_until_flow_finishes", + "forced": false, + "reason": "Liveness retried with a reviewer present.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": null, + "cancelled_at": null, + "cancel_reason": null + }, + { + "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "node_id": "feature_ocr_1", + "feature_type": "", + "feature_model": "KYC", + "previous_status": "In Review", + "new_status": "Approved", + "state": "applied", + "forced": true, + "reason": "Document manually verified by compliance.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": "2026-10-13T09:15:00Z", + "cancelled_at": null, + "cancel_reason": null + } + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials, a caller without the `read:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks read:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no read access to decisions." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/features/overrides/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.list_feature_overrides(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.listFeatureOverrides(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.ListFeatureOverrides(ctx, \"3fa85f64-5717-4562-b3fc-2c963f66afa6\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().listFeatureOverrides(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->listFeatureOverrides('3fa85f64-5717-4562-b3fc-2c963f66afa6');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.list_feature_overrides(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ListFeatureOverridesAsync(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "list_feature_overrides", + "auth": "api_key", + "server": true + } + } + }, + "/v3/session/{sessionId}/features/overrides/{overrideId}/cancel/": { + "post": { + "summary": "Cancel a check override that waits for the flow to finish", + "description": "Cancel an override of a feature (step) status that is still `pending_until_flow_finishes`, so it never applies. The check keeps its status, the cancellation is recorded in the session's activity as `FEATURE_OVERRIDE_CANCELLED`, and no webhook is sent. The response is the override with `state: cancelled` and `cancel_reason: cancelled_by_reviewer`.\n\nAn override that already applied or was cancelled returns `409` with the code `feature_override_not_pending`. Find override IDs with `GET /v3/session/{sessionId}/features/overrides/` or in the `override` returned by the feature status update.\n\nRequires the `write:sessions` privilege; a scoped API key needs write access to decisions.", + "operationId": "post_v3_session_feature_override_cancel", + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + }, + { + "in": "path", + "name": "overrideId", + "required": true, + "description": "The `id` of the override to cancel.", + "schema": { + "type": "string", + "format": "uuid", + "example": "9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3" + } + } + ], + "responses": { + "200": { + "description": "The override is cancelled and will never apply.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FeatureStatusOverride" + }, + "examples": { + "Cancelled": { + "summary": "The cancelled override", + "value": { + "id": "9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3", + "node_id": "feature_liveness_1", + "feature_type": "", + "feature_model": "Face", + "previous_status": "Declined", + "new_status": "Approved", + "state": "cancelled", + "forced": false, + "reason": "Liveness retried with a reviewer present.", + "requested_by": { + "email": "reviewer@example.com", + "name": "Alex Reviewer", + "type": "CONSOLE_USER" + }, + "requested_at": "2026-10-13T09:15:00Z", + "applied_at": null, + "cancelled_at": "2026-10-13T09:20:00Z", + "cancel_reason": "cancelled_by_reviewer" + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials, a caller without the `write:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no write access to decisions." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists, or the session has no override with this `overrideId`.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + }, + "Override Not Found": { + "summary": "No override with this id on the session", + "value": { + "detail": "Override not found." + } + } + } + } + } + }, + "409": { + "description": "The override already applied or was cancelled.", + "content": { + "application/json": { + "examples": { + "Not Pending": { + "summary": "Only a waiting override can be cancelled", + "value": { + "detail": "This override is no longer pending: it already applied or was cancelled.", + "code": "feature_override_not_pending" + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/features/overrides/9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3/cancel/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.cancel_feature_override(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n \"9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.cancelFeatureOverride(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n \"9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.CancelFeatureOverride(ctx, \"3fa85f64-5717-4562-b3fc-2c963f66afa6\", \"9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().cancelFeatureOverride(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", \"9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->cancelFeatureOverride('3fa85f64-5717-4562-b3fc-2c963f66afa6', '9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.cancel_feature_override(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n \"9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.CancelFeatureOverrideAsync(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", \"9b2f4c1e-8d3a-4e5f-a6b7-c8d9e0f1a2b3\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "cancel_feature_override", + "auth": "api_key", + "server": true + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/tags/add/": { + "post": { + "summary": "Add a tag to a network", + "description": "Put one of your application's tags on the network. Returns the network detail, whose `tags` lists your application's tags on the network, with `201` when the tag was added and `200` when it was already on the network. A tag that belongs to another application, or does not exist, returns 404 `Tag not found.`. Requires a full-access API key, or `write:networks` for a console member.", + "operationId": "add_network_tag", + "tags": [ + "Networks" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "description": "Network UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkTagRequest" + }, + "example": { + "tag_uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/3fa85f64-5717-4562-b3fc-2c963f66afa6/tags/add/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"tag_uuid\": \"7c9e6679-7425-40de-944b-e07fc1f90ae7\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.add_tag(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n tag_uuid=\"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.addTag(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", {\n tag_uuid: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.AddTag(ctx, \"3fa85f64-5717-4562-b3fc-2c963f66afa6\", didit.NetworkTagRequest{\n\tTagUUID: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.NetworkTagRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().addTag(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", NetworkTagRequest.builder()\n .tagUuid(\"7c9e6679-7425-40de-944b-e07fc1f90ae7\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->addTag('3fa85f64-5717-4562-b3fc-2c963f66afa6', [\n 'tag_uuid' => '7c9e6679-7425-40de-944b-e07fc1f90ae7',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.add_tag(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n tag_uuid: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.AddTagAsync(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", new NetworkTagRequest\n{\n TagUuid = \"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The tag was added. Returns the network detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkDetailResponse" + }, + "example": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "network_id": "N-0042", + "name": null, + "status": "in_review", + "risk": { + "score": 82, + "band": "high", + "bar": 0.82 + }, + "pattern_types": [ + "exact_same_device", + "same_email" + ], + "signal_types": [ + "device", + "email" + ], + "member_counts": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signal_coverage": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + }, + "first_activity_at": "2026-10-01T09:12:44Z", + "last_activity_at": "2026-10-12T16:03:10Z", + "stats": { + "members": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signals": { + "total": 2, + "by_type": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + } + } + }, + "fraud_tags": [], + "risk_signals": { + "residential_proxy_reuse": { + "detected": false, + "signal_count": 0, + "max_subject_count": 0 + }, + "email_intelligence": { + "enriched_signal_count": 1, + "high_risk_count": 0, + "social_profile_count": 0 + }, + "phone_intelligence": { + "enriched_signal_count": 0, + "high_risk_count": 0 + } + }, + "reviewed_at": "2026-10-12T16:30:00Z", + "reviewed_by": "reviewer@example.com", + "review_reason": "Shared device across three new accounts.", + "notes_count": 0, + "sharing_mode": "global", + "cross_org_available": true, + "cross_org_unavailable_reason": null, + "tags": [ + { + "uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "name": "SUSPICIOUS", + "color": "#6366F1", + "source": "custom", + "added_by": null, + "added_at": "2026-10-13T08:15:00Z" + } + ], + "outcomes": { + "approved": 1, + "declined": 1, + "in_review": 0, + "total": 3 + }, + "patterns": [ + { + "type": "background", + "score": 0.8125, + "members": 3, + "reason": null, + "computed_at": "2026-10-13T08:00:00Z" + } + ] + } + } + } + }, + "200": { + "description": "The tag was already on the network and is left as it is. Returns the network detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkDetailResponse" + }, + "example": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "network_id": "N-0042", + "name": null, + "status": "in_review", + "risk": { + "score": 82, + "band": "high", + "bar": 0.82 + }, + "pattern_types": [ + "exact_same_device", + "same_email" + ], + "signal_types": [ + "device", + "email" + ], + "member_counts": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signal_coverage": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + }, + "first_activity_at": "2026-10-01T09:12:44Z", + "last_activity_at": "2026-10-12T16:03:10Z", + "stats": { + "members": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signals": { + "total": 2, + "by_type": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + } + } + }, + "fraud_tags": [], + "risk_signals": { + "residential_proxy_reuse": { + "detected": false, + "signal_count": 0, + "max_subject_count": 0 + }, + "email_intelligence": { + "enriched_signal_count": 1, + "high_risk_count": 0, + "social_profile_count": 0 + }, + "phone_intelligence": { + "enriched_signal_count": 0, + "high_risk_count": 0 + } + }, + "reviewed_at": "2026-10-12T16:30:00Z", + "reviewed_by": "reviewer@example.com", + "review_reason": "Shared device across three new accounts.", + "notes_count": 0, + "sharing_mode": "global", + "cross_org_available": true, + "cross_org_unavailable_reason": null, + "tags": [ + { + "uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7", + "name": "SUSPICIOUS", + "color": "#6366F1", + "source": "custom", + "added_by": null, + "added_at": "2026-10-13T08:15:00Z" + } + ], + "outcomes": { + "approved": 1, + "declined": 1, + "in_review": 0, + "total": 3 + }, + "patterns": [ + { + "type": "background", + "score": 0.8125, + "members": 3, + "reason": null, + "computed_at": "2026-10-13T08:00:00Z" + } + ] + } + } + } + }, + "400": { + "description": "Invalid body: `tag_uuid` is missing or is not a UUID.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tag_uuid": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "examples": { + "Missing tag_uuid": { + "summary": "Missing tag_uuid", + "value": { + "tag_uuid": [ + "This field is required." + ] + } + }, + "Invalid tag_uuid": { + "summary": "Invalid tag_uuid", + "value": { + "tag_uuid": [ + "Must be a valid UUID." + ] + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `write:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Network not found": { + "summary": "Network not found", + "value": { + "detail": "Network not found." + } + }, + "Tag not found": { + "summary": "Tag not found", + "value": { + "detail": "Tag not found." + } + } + } + } + } + } + }, + "x-didit-sdk": { + "resource": "networks", + "method": "add_tag", + "auth": "api_key", + "server": true, + "scope": "application" + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/networks/{network_uuid}/tags/remove/": { + "post": { + "summary": "Remove a tag from a network", + "description": "Remove one of your application's tags from the network. Returns the network detail. 404 when the tag is not on the network, or does not belong to your application. Requires a full-access API key, or `write:networks` for a console member.", + "operationId": "remove_network_tag", + "tags": [ + "Networks" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + }, + { + "name": "network_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "description": "Network UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkTagRequest" + }, + "example": { + "tag_uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/networks/3fa85f64-5717-4562-b3fc-2c963f66afa6/tags/remove/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"tag_uuid\": \"7c9e6679-7425-40de-944b-e07fc1f90ae7\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.networks.remove_tag(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n tag_uuid=\"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.networks.removeTag(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", {\n tag_uuid: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Networks.RemoveTag(ctx, \"3fa85f64-5717-4562-b3fc-2c963f66afa6\", didit.NetworkTagRequest{\n\tTagUUID: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.NetworkTagRequest;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.networks().removeTag(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", NetworkTagRequest.builder()\n .tagUuid(\"7c9e6679-7425-40de-944b-e07fc1f90ae7\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "networks->removeTag('3fa85f64-5717-4562-b3fc-2c963f66afa6', [\n 'tag_uuid' => '7c9e6679-7425-40de-944b-e07fc1f90ae7',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.networks.remove_tag(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n tag_uuid: \"7c9e6679-7425-40de-944b-e07fc1f90ae7\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Networks.RemoveTagAsync(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", new NetworkTagRequest\n{\n TagUuid = \"7c9e6679-7425-40de-944b-e07fc1f90ae7\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The tag was removed from the network. Returns the network detail.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkDetailResponse" + }, + "example": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "network_id": "N-0042", + "name": null, + "status": "in_review", + "risk": { + "score": 82, + "band": "high", + "bar": 0.82 + }, + "pattern_types": [ + "exact_same_device", + "same_email" + ], + "signal_types": [ + "device", + "email" + ], + "member_counts": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signal_coverage": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + }, + "first_activity_at": "2026-10-01T09:12:44Z", + "last_activity_at": "2026-10-12T16:03:10Z", + "stats": { + "members": { + "total": 3, + "users": 3, + "businesses": 0, + "transaction_parties": 0 + }, + "member_status": { + "approved": 1, + "declined": 1, + "in_review": 0, + "not_completed": 1 + }, + "signals": { + "total": 2, + "by_type": { + "device": { + "signals": 1, + "subjects": 3, + "coverage": 1.0 + }, + "email": { + "signals": 1, + "subjects": 2, + "coverage": 0.6667 + } + } + } + }, + "fraud_tags": [], + "risk_signals": { + "residential_proxy_reuse": { + "detected": false, + "signal_count": 0, + "max_subject_count": 0 + }, + "email_intelligence": { + "enriched_signal_count": 1, + "high_risk_count": 0, + "social_profile_count": 0 + }, + "phone_intelligence": { + "enriched_signal_count": 0, + "high_risk_count": 0 + } + }, + "reviewed_at": "2026-10-12T16:30:00Z", + "reviewed_by": "reviewer@example.com", + "review_reason": "Shared device across three new accounts.", + "notes_count": 0, + "sharing_mode": "global", + "cross_org_available": true, + "cross_org_unavailable_reason": null, + "tags": [], + "outcomes": { + "approved": 1, + "declined": 1, + "in_review": 0, + "total": 3 + }, + "patterns": [ + { + "type": "background", + "score": 0.8125, + "members": 3, + "reason": null, + "computed_at": "2026-10-13T08:00:00Z" + } + ] + } + } + } + }, + "400": { + "description": "Invalid body: `tag_uuid` is missing or is not a UUID.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tag_uuid": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "examples": { + "Missing tag_uuid": { + "summary": "Missing tag_uuid", + "value": { + "tag_uuid": [ + "This field is required." + ] + } + }, + "Invalid tag_uuid": { + "summary": "Invalid tag_uuid", + "value": { + "tag_uuid": [ + "Must be a valid UUID." + ] + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid credentials.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "Missing or invalid credentials", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + } + } + } + } + }, + "403": { + "description": "The API key belongs to another application, or it is a key with restricted access: Networks endpoints need a full-access key. A console member token needs `write:networks`. Also returned with `code: networks_not_entitled` when Networks is not enabled for the organization.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Networks not enabled": { + "summary": "Networks not enabled", + "value": { + "detail": "Networks is disabled for this organization. Contact Didit support if you believe this is an error.", + "code": "networks_not_entitled" + } + }, + "Restricted API key": { + "summary": "Restricted API key", + "value": { + "detail": "This API key has no access to this endpoint." + } + }, + "No permission": { + "summary": "No permission", + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + }, + "404": { + "description": "Not found.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "description": "Present on coded errors, for example `networks_not_entitled`." + } + } + }, + "examples": { + "Network not found": { + "summary": "Network not found", + "value": { + "detail": "Network not found." + } + }, + "Tag not found": { + "summary": "Tag not found", + "value": { + "detail": "Tag not found." + } + }, + "Tag not on the network": { + "summary": "Tag not on the network", + "value": { + "detail": "This tag is not on the network." + } + } + } + } + } + } + }, + "x-didit-sdk": { + "resource": "networks", + "method": "remove_tag", + "auth": "api_key", + "server": true, + "scope": "application" + } + } + }, + "/v3/organization/{organization_id}/application/{application_id}/cases/{case_uuid}/requests/{request_uuid}/remind/": { + "post": { + "summary": "Remind the customer of a request", + "description": "Send a waiting customer request's email again, to the same verified recipient, with the same content. You can remind a request at most once every 24 hours. A reminder changes nothing else: the request keeps its deadline and the case keeps its status. Each reminder is logged on the case as a CUSTOMER_REQUEST event with action `reminded`. The response's `delivery_status` is how this reminder's delivery ended: SENT, SIMULATED (the email was not sent), or UNKNOWN (the email might have been accepted; it is not sent again automatically). `next_reminder_at` is when you can remind this request again. Returns 409 with a `reason` when the customer no longer has the request to answer, and 429 with `retry_at` and a `Retry-After` header when the request was reminded in the last 24 hours. Requires `write:cases`.", + "operationId": "organization_application_cases_requests_remind_create", + "parameters": [ + { + "in": "path", + "name": "organization_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "description": "Organization UUID." + }, + { + "in": "path", + "name": "application_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "description": "Application UUID." + }, + { + "in": "path", + "name": "case_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "description": "Case UUID." + }, + { + "in": "path", + "name": "request_uuid", + "schema": { + "type": "string", + "format": "uuid" + }, + "required": true, + "description": "Customer request UUID, from the case's request list." + } + ], + "tags": [ + "Case Management" + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/cases/c1a5e000-0000-4000-8000-000000000001/requests/3fa85f64-5717-4562-b3fc-2c963f66afa6/remind/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nresult = client.cases.requests.remind(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nconst result = await didit.cases.requests.remind(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nclient := didit.NewClient()\n\nresult, err := client.Cases.Requests.Remind(ctx, \"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nvar result = client.cases().requests().remind(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "cases->requests->remind('11111111-2222-3333-4444-555555555555', '11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\nresult = didit.cases.requests.remind(\n \"11111111-2222-3333-4444-555555555555\",\n \"11111111-2222-3333-4444-555555555555\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nvar result = await client.Cases.Requests.RemindAsync(\"11111111-2222-3333-4444-555555555555\", \"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The reminder was recorded and its email sent.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "request", + "reminded_at", + "delivery_status", + "next_reminder_at" + ], + "properties": { + "request": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequest" + }, + "reminded_at": { + "type": "string", + "format": "date-time", + "description": "When this reminder was recorded." + }, + "delivery_status": { + "type": "string", + "enum": [ + "SENT", + "SIMULATED", + "UNKNOWN" + ], + "description": "How this reminder's delivery ended. SIMULATED: the email was not sent. UNKNOWN: the email might have been accepted; it is not sent again automatically." + }, + "next_reminder_at": { + "type": "string", + "format": "date-time", + "description": "When you can remind this request again: `reminded_at` plus 24 hours." + } + } + }, + "example": { + "request": { + "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "purpose": "confirm_details", + "language": "en", + "recipient": "customer@example.com", + "expires_at": "2026-10-20T09:00:00Z", + "actor_email": "reviewer@example.com", + "delivery_status": "SENT", + "delivered_at": "2026-10-13T09:00:04Z", + "cancelled_at": null, + "responded_at": null, + "response": null, + "has_attachment": false, + "created_at": "2026-10-13T09:00:00Z" + }, + "reminded_at": "2026-10-14T10:30:00Z", + "delivery_status": "SENT", + "next_reminder_at": "2026-10-15T10:30:00Z" + } + } + } + }, + "400": { + "description": "The case has a resolution pending approval. The body is a list of strings.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "example": [ + "Customer messages require an active case without a pending resolution." + ] + } + } + }, + "403": { + "description": "The caller lacks the required cases permission." + }, + "404": { + "description": "The request or case is unavailable for this caller." + }, + "409": { + "description": "The request no longer waits on the customer, so there is nothing to remind them of. `reason` says why: `answered` (the customer responded), `cancelled`, `expired`, `closed` (the case was resolved after the request went out, even if it was reopened since), `not_actionable` (a review-in-progress update asks nothing), `not_delivered` (the first email has not gone out), or `contact_unavailable` (the verified email is no longer available).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "case_request_not_waiting" + ] + }, + "reason": { + "type": "string", + "enum": [ + "answered", + "cancelled", + "expired", + "closed", + "not_actionable", + "not_delivered", + "contact_unavailable" + ] + } + } + }, + "example": { + "detail": "This request no longer waits on the customer, so there is nothing to remind them of.", + "code": "case_request_not_waiting", + "reason": "answered" + } + } + } + }, + "429": { + "description": "The request was already reminded in the last 24 hours. `retry_at` and the `Retry-After` header say when you can remind it again.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before you can remind this request again.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 3600 + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "case_request_reminder_too_soon" + ] + }, + "retry_at": { + "type": "string", + "format": "date-time" + } + } + }, + "example": { + "detail": "This request was already reminded in the last 24 hours.", + "code": "case_request_reminder_too_soon", + "retry_at": "2026-10-15T10:30:00Z" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-didit-sdk": { + "resource": "cases.requests", + "method": "remind", + "auth": "api_key", + "server": true, + "scope": "application" + } + } + }, + "/v3/session/{sessionId}/decision-email-preview/": { + "post": { + "summary": "Preview a session's decision email", + "description": "Render the email the person receives when you set a session to `Approved` or `Declined` with `send_email: true`, without changing the session or sending anything. The response shows the subject and HTML exactly as they are sent, the recipient, the sender, the language, and whether confirming the change sends the email at all (`will_be_sent`, `not_sent_reason`).\n\nThe recipient is the person's own address: the one they verified in an email verification step of the flow, else the one given in `contact_details` when the session was created. The sender is your application's verified sender domain when white label applies (`sender_kind: customer`), else Didit's sender (`sender_kind: didit`).\n\nTo send exactly what you previewed, call [Update Session Status](/sessions-api/update-status) with the same `new_status`, `send_email: true`, `email_address` set to `recipient`, `email_language` set to `language` and `email_preview_token` set to `preview_token`. The receipt expires after 15 minutes. When the recipient, language, session status, sender or email changed since the preview, the update returns `409` before anything changes; preview again and confirm with the new receipt.\n\nWorks for User Verification (KYC) and Business Verification (KYB) sessions in `Approved`, `Declined`, `In Review`, `Kyc Expired`, `Abandoned` or `Resubmitted` status. The status check runs before body validation. Requires the `write:sessions` privilege; a scoped API key needs write access to decisions. Responses use `Cache-Control: no-store`: treat them as sensitive verification content.", + "operationId": "post_v3_session_decision_email_preview", + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "sessionId", + "required": true, + "description": "UUID of the verification session. Accepts both user (KYC) and business (KYB) session IDs.", + "schema": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "new_status" + ], + "properties": { + "new_status": { + "type": "string", + "enum": [ + "Approved", + "Declined" + ], + "description": "The decision whose email you want to preview.", + "example": "Approved" + }, + "email_language": { + "type": "string", + "default": "en", + "description": "Language of the email, the same default as Update Session Status. A language that is not offered falls back to English; `language` in the response says which one is used.", + "example": "en" + } + } + }, + "examples": { + "Approved": { + "summary": "Preview the Approved email in English", + "value": { + "new_status": "Approved" + } + }, + "Declined in Spanish": { + "summary": "Preview the Declined email in Spanish", + "value": { + "new_status": "Declined", + "email_language": "es" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Preview only. The session is unchanged and no email is sent.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: the preview carries verification content and must not be cached.", + "schema": { + "type": "string", + "example": "no-store" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "recipient", + "recipient_source", + "sender", + "sender_name", + "sender_email", + "sender_kind", + "subject", + "html", + "text", + "language", + "will_be_sent", + "not_sent_reason", + "preview_token" + ], + "properties": { + "recipient": { + "type": "string", + "description": "The address the email goes to. An empty string when no address is known for the person: then no email can be sent and `preview_token` is empty too. Pass it as `email_address` when you confirm.", + "example": "person@example.com" + }, + "recipient_source": { + "type": "string", + "enum": [ + "email_verification", + "contact_details", + "" + ], + "description": "Where `recipient` comes from. `email_verification`: the address the person verified in an email verification step of the flow. `contact_details`: the address given in `contact_details` when the session was created. An empty string when there is no address.", + "example": "email_verification" + }, + "sender": { + "type": "string", + "description": "The `From` header the email goes out with.", + "example": "Example App " + }, + "sender_name": { + "type": "string", + "description": "The display name part of `sender`.", + "example": "Example App" + }, + "sender_email": { + "type": "string", + "format": "email", + "description": "The address part of `sender`.", + "example": "no-reply@mail.example.com" + }, + "sender_kind": { + "type": "string", + "enum": [ + "customer", + "didit" + ], + "description": "`customer`: the email goes out from your application's verified sender domain (white label). `didit`: it goes out from Didit's own sender.", + "example": "customer" + }, + "subject": { + "type": "string", + "description": "The subject line exactly as it is sent.", + "example": "Your verification has been approved in Example App" + }, + "html": { + "type": "string", + "description": "The email's HTML exactly as it is sent, with its logo embedded as a data URL. Render it in a sandboxed frame, never directly in your application DOM.", + "example": "..." + }, + "text": { + "type": "string", + "description": "The email's words as plain text, as the person's Messages thread keeps them, for a compact preview (the HTML stays the email the person receives).", + "example": "Verification Approved\n\nYour identity has been successfully verified with Acme Pay. No further action is needed on your part." + }, + "language": { + "type": "string", + "description": "The language the email is written in. English (`en`) when the `email_language` you asked for is not offered.", + "example": "en" + }, + "will_be_sent": { + "type": "boolean", + "description": "Whether confirming the change with `send_email: true` sends this email. `false` when `not_sent_reason` is set.", + "example": true + }, + "not_sent_reason": { + "type": "string", + "nullable": true, + "enum": [ + "status_unchanged", + "notification_emails_disabled", + "no_recipient", + "sandbox", + null + ], + "description": "Why confirming would send no email, or `null` when it will be sent. `status_unchanged`: the session already has this status, so the update changes nothing and sends nothing. `notification_emails_disabled`: the session was created without `contact_details.send_notification_emails: true`, so no decision email goes to the address given in `contact_details`. `no_recipient`: no address is known for the person. `sandbox`: a sandbox session whose recipient has not opted into sandbox email delivery, so the send is only simulated.", + "example": null + }, + "preview_token": { + "type": "string", + "description": "Signed receipt for this exact email, valid for 15 minutes. Pass it as `email_preview_token` to [Update Session Status](/sessions-api/update-status). An empty string when there is no recipient.", + "example": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + } + }, + "examples": { + "Will Be Sent": { + "summary": "The Approved email goes to the address verified in the flow", + "value": { + "recipient": "person@example.com", + "recipient_source": "email_verification", + "sender": "Example App ", + "sender_name": "Example App", + "sender_email": "no-reply@mail.example.com", + "sender_kind": "customer", + "subject": "Your verification has been approved in Example App", + "html": "...", + "language": "en", + "will_be_sent": true, + "not_sent_reason": null, + "preview_token": "eyJkaWdlc3QiOiI0ZjJhIn0:1uExAm:Pl3x4mpleS1gnatureValue000000000000000" + } + }, + "No Recipient": { + "summary": "No address is known for the person", + "value": { + "recipient": "", + "recipient_source": "", + "sender": "Example App ", + "sender_name": "Example App", + "sender_email": "no-reply@mail.example.com", + "sender_kind": "customer", + "subject": "Your verification has been declined in Example App", + "html": "...", + "language": "en", + "will_be_sent": false, + "not_sent_reason": "no_recipient", + "preview_token": "" + } + } + } + } + } + }, + "400": { + "description": "The session is not in a status this endpoint accepts (`{\"detail\": ...}`), or the body is invalid (`{\"\": [\"\"]}`).", + "content": { + "application/json": { + "examples": { + "Wrong Current Status": { + "summary": "Session is not in a status this endpoint accepts", + "value": { + "detail": "Only sessions with status Approved, Declined, In Review, Kyc Expired, Abandoned, Resubmitted can be updated. Current status: In Progress" + } + }, + "Invalid new_status": { + "summary": "new_status is not Approved or Declined", + "value": { + "new_status": [ + "\"Resubmitted\" is not a valid choice." + ] + } + }, + "Missing new_status": { + "summary": "Required field omitted", + "value": { + "new_status": [ + "This field is required." + ] + } + } + } + } + } + }, + "401": { + "description": "The scoped API key has expired.", + "content": { + "application/json": { + "examples": { + "Expired API Key": { + "summary": "Scoped API key past its expiry date", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Missing credentials, a caller without the `write:sessions` privilege, a scoped API key without the access this endpoint needs, or a `session_id` that belongs to another application.", + "content": { + "application/json": { + "examples": { + "Invalid Credentials": { + "summary": "Missing or invalid x-api-key", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Missing Privilege": { + "summary": "Caller lacks write:sessions", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped Key Without Access": { + "summary": "Scoped API key without this access", + "value": { + "detail": "This API key has no write access to decisions." + } + } + } + } + } + }, + "404": { + "description": "No KYC or KYB session with this `session_id` exists.", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Unknown session_id", + "value": { + "detail": "Not found." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST \\\n https://verification.didit.me/v3/session/3fa85f64-5717-4562-b3fc-2c963f66afa6/decision-email-preview/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"new_status\": \"Approved\", \"email_language\": \"en\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.preview_decision_email(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n new_status=\"Approved\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.previewDecisionEmail(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", {\n new_status: \"Approved\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.PreviewDecisionEmail(ctx, \"3fa85f64-5717-4562-b3fc-2c963f66afa6\", didit.SessionsPreviewDecisionEmailParams{\n\tNewStatus: \"Approved\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsPreviewDecisionEmailParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().previewDecisionEmail(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", SessionsPreviewDecisionEmailParams.builder()\n .newStatus(SessionsPreviewDecisionEmailParams.NewStatus.of(\"Approved\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->previewDecisionEmail('3fa85f64-5717-4562-b3fc-2c963f66afa6', [\n 'new_status' => 'Approved',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.preview_decision_email(\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n new_status: \"Approved\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.PreviewDecisionEmailAsync(\"3fa85f64-5717-4562-b3fc-2c963f66afa6\", new SessionsPreviewDecisionEmailParams\n{\n NewStatus = \"Approved\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "x-didit-sdk": { + "resource": "sessions", + "method": "preview_decision_email", + "auth": "api_key", + "server": true + } + } + }, + "/v3/travel-rule/join/{token}/": { + "get": { + "summary": "Get a free-join invitation", + "description": "Public, unauthenticated. Shows the invitation a join link is for while it is open: who asked, the address it went to, and the directory values that will be prefilled. The token is the authentication.", + "operationId": "getTravelRuleJoinInvitation", + "x-didit-sdk": { + "resource": "travel_rule.join", + "method": "get_invitation", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The invitation token from the join link." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/join/{token}/" + } + ], + "responses": { + "200": { + "description": "The invitation.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleJoinInvitation" + } + } + } + }, + "404": { + "description": "No invitation has this link." + }, + "410": { + "description": "The invitation is no longer open: expired, already used, or withdrawn." + } + } + }, + "post": { + "summary": "Register a free Travel Rule account", + "description": "Public, unauthenticated. Creates a free Travel Rule account for a counterparty VASP that received a request. The email must be the address the invitation went to. A 6-character code is emailed to it; send it to the verify endpoint to finish. Registration attempts are rate limited per address and per IP.", + "operationId": "registerTravelRuleJoin", + "x-didit-sdk": { + "resource": "travel_rule.join", + "method": "register", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The invitation token from the join link." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleJoinRegisterRequest" + }, + "example": { + "email": "compliance@example.com", + "password": "a-long-passphrase" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/join/{token}/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"email\": \"compliance@example.com\", \"password\": \"a-long-passphrase\"}'" + } + ], + "responses": { + "202": { + "description": "The account was created and a code was emailed.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Tells the counterparty to check their email for the verification code." + }, + "email": { + "type": "string", + "format": "email", + "description": "The address the code was sent to." + } + } + } + } + } + }, + "400": { + "description": "The email is not the invited address, or the password or address was refused." + }, + "404": { + "description": "No invitation has this link." + }, + "410": { + "description": "The invitation is no longer open: expired, already used, or withdrawn." + }, + "429": { + "description": "Too many attempts to join. The `X-RateLimit-*` headers name the budget that was spent." + } + } + } + }, + "/v3/travel-rule/join/{token}/verify/": { + "post": { + "summary": "Verify a free Travel Rule account", + "description": "Public, unauthenticated. Spends the emailed code, creates the free organization and answers with the credentials its application uses on the workspace endpoints. Receiving Travel Rule data is free.", + "operationId": "verifyTravelRuleJoin", + "x-didit-sdk": { + "resource": "travel_rule.join", + "method": "verify", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The invitation token from the join link." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleJoinVerifyRequest" + }, + "example": { + "email": "compliance@example.com", + "code": "123456" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/join/{token}/verify/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"email\": \"compliance@example.com\", \"code\": \"123456\"}'" + } + ], + "responses": { + "201": { + "description": "The organization and application credentials.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleJoinCredentials" + } + } + } + }, + "400": { + "description": "The code or email was refused." + }, + "404": { + "description": "No invitation has this link." + }, + "410": { + "description": "The invitation is no longer open: expired, already used, or withdrawn." + }, + "429": { + "description": "Too many attempts to join. The `X-RateLimit-*` headers name the budget that was spent." + } + } + } + }, + "/v3/travel-rule/workspace/": { + "get": { + "summary": "Get the free Travel Rule workspace", + "description": "Reads the free workspace of an application that joined free: directory claim, domain proof, billing and Didit network listing.", + "operationId": "getTravelRuleWorkspace", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "get", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/workspace/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.workspace.get()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.workspace.get();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.Workspace.Get(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().workspace().get();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->workspace->get();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.workspace.get\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Workspace.GetAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/claim/": { + "post": { + "summary": "Claim a directory entry", + "description": "Claims the directory entry this workspace is. When the work email is at a domain the entry lists, the claim is proved and applies at once: the VASP profile and Travel Rule settings are filled from the entry and Travel Rule is turned on, so the next transfer to it routes over the Didit network. Otherwise the claim waits for review (`pending_review`).", + "operationId": "claimTravelRuleWorkspace", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "claim", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspaceClaimRequest" + }, + "example": { + "vasp_id": "0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/claim/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"vasp_id\": \"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.workspace.claim(\n vasp_id=\"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.workspace.claim({\n vasp_id: \"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.Workspace.Claim(ctx, didit.TravelRuleWorkspaceClaimRequest{\n\tVASPID: \"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleWorkspaceClaimRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().workspace().claim(TravelRuleWorkspaceClaimRequest.builder()\n .vaspId(\"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->workspace->claim([\n 'vasp_id' => '0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.workspace.claim(vasp_id: \"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Workspace.ClaimAsync(new TravelRuleWorkspaceClaimRequest\n{\n VaspId = \"0b0c9d3e-5a53-4a3e-9d6e-1f2f6f6a1c11\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The workspace with its claim status.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "400": { + "description": "No directory entry has this id." + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/domain/code/": { + "post": { + "summary": "Email a domain verification code", + "description": "Emails a single-use code to the workspace's work address to prove it controls the email domain. Throttled, and a limited number of wrong attempts is accepted per code. Also returns the TXT record for the DNS alternative.", + "operationId": "sendTravelRuleWorkspaceDomainCode", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "send_domain_code", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/domain/code/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.workspace.send_domain_code()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.workspace.sendDomainCode();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.Workspace.SendDomainCode(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().workspace().sendDomainCode();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->workspace->sendDomainCode();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.workspace.send_domain_code\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Workspace.SendDomainCodeAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The domain, its status and the TXT record.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspaceDomainCodeResponse" + } + } + } + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/domain/confirm/": { + "post": { + "summary": "Confirm the domain verification code", + "description": "Spends the emailed code and marks the email domain verified.", + "operationId": "confirmTravelRuleWorkspaceDomain", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "confirm_domain", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspaceDomainConfirmRequest" + }, + "example": { + "code": "123456" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/domain/confirm/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"code\": \"123456\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.workspace.confirm_domain(\n code=\"123456\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.workspace.confirmDomain({\n code: \"123456\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.Workspace.ConfirmDomain(ctx, didit.TravelRuleWorkspaceDomainConfirmRequest{\n\tCode: \"123456\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleWorkspaceDomainConfirmRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().workspace().confirmDomain(TravelRuleWorkspaceDomainConfirmRequest.builder()\n .code(\"123456\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->workspace->confirmDomain([\n 'code' => '123456',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.workspace.confirm_domain(code: \"123456\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Workspace.ConfirmDomainAsync(new TravelRuleWorkspaceDomainConfirmRequest\n{\n Code = \"123456\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "400": { + "description": "The code is wrong, expired or already used." + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/domain/dns/": { + "post": { + "summary": "Verify the domain from its TXT record", + "description": "Checks for the `didit-tr-verify.` TXT record returned by the code endpoint and marks the domain verified when it matches. One of the two proofs, the emailed code or the TXT record, is enough.", + "operationId": "verifyTravelRuleWorkspaceDomainDns", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "verify_domain_dns", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/domain/dns/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.workspace.verify_domain_dns()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.workspace.verifyDomainDns();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.Workspace.VerifyDomainDNS(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().workspace().verifyDomainDns();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->workspace->verifyDomainDns();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.workspace.verify_domain_dns\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Workspace.VerifyDomainDnsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "400": { + "description": "The TXT record was not found or does not match." + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/travel-rule/workspace/billing/": { + "post": { + "summary": "Enable outbound billing", + "description": "Turns outbound Travel Rule billing on for the workspace. Receiving stays free. This cannot be turned back off through the API: contact Didit support.", + "operationId": "enableTravelRuleWorkspaceBilling", + "x-didit-sdk": { + "resource": "travel_rule.workspace", + "method": "enable_billing", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspaceBillingRequest" + }, + "example": { + "enabled": true + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/workspace/billing/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"enabled\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.workspace.enable_billing(\n enabled=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.workspace.enableBilling({\n enabled: true,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.Workspace.EnableBilling(ctx, didit.TravelRuleWorkspaceBillingRequest{\n\tEnabled: didit.Bool(true),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleWorkspaceBillingRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().workspace().enableBilling(TravelRuleWorkspaceBillingRequest.builder()\n .enabled(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->workspace->enableBilling([\n 'enabled' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.workspace.enable_billing(enabled: true)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Workspace.EnableBillingAsync(new TravelRuleWorkspaceBillingRequest\n{\n Enabled = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The workspace with `billing_enabled` true.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWorkspace" + } + } + } + }, + "400": { + "description": "`enabled` was false." + }, + "404": { + "description": "This application did not join free, so it has no free workspace." + } + } + } + }, + "/v3/investigator/summary/": { + "get": { + "operationId": "investigator_summary", + "summary": "Investigator summary", + "description": "Stage, queue counts, reports prepared and decided today, 30-day agreement, prices and the autonomy level of each queue. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "stage": { + "type": "string" + }, + "last_report_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "prepared_today": { + "type": "integer" + }, + "ready_to_accept": { + "type": "integer" + }, + "open": { + "type": "integer" + }, + "decided_today": { + "type": "integer" + }, + "automation_rate_month": { + "type": "number", + "nullable": true + }, + "preparing": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "timezone", + "schema": { + "type": "string" + }, + "description": "IANA time zone (for example `Europe/Madrid`) that \"today\" counts from. UTC by default." + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_summary", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.retrieve_summary()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.retrieveSummary();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.RetrieveSummary(ctx, didit.InvestigatorRetrieveSummaryParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().retrieveSummary();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->retrieveSummary();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.retrieve_summary\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.RetrieveSummaryAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/queue/": { + "get": { + "operationId": "investigator_queue", + "summary": "List the Investigator queue", + "description": "Open reports, **Ready to accept** first, then by deadline, risk and wait. `counts` covers every kind in scope. `count` is capped at 100. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "counts": { + "type": "object", + "additionalProperties": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/InvestigatorQueueRow" + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "kind", + "schema": { + "type": "string" + }, + "description": "`session`, `business_session`, `transaction` or `case`, or several separated by commas." + }, + { + "in": "query", + "name": "queue", + "schema": { + "type": "string", + "enum": [ + "kyc", + "kyb", + "aml", + "transactions", + "cases" + ] + } + }, + { + "in": "query", + "name": "recommendation", + "schema": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ] + } + }, + { + "in": "query", + "name": "confidence", + "schema": { + "type": "string", + "enum": [ + "high", + "moderate", + "low" + ] + } + }, + { + "in": "query", + "name": "ready", + "schema": { + "type": "boolean" + } + }, + { + "in": "query", + "name": "search", + "schema": { + "type": "string" + }, + "description": "Name, company, case title, number, transaction ID or item ID." + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "list_queue", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.investigator.list_queue():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.investigator.listQueue()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Investigator.ListQueueAutoPaging(ctx, didit.InvestigatorListQueueParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.investigator().listQueue()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->listQueue() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.investigator.list_queue.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Investigator.ListQueueAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/investigator/runs/{run_id}/": { + "get": { + "operationId": "investigator_run_retrieve", + "summary": "Get an Investigator report", + "description": "The full report of one run. 404 when the run has no completed report, or its item was deleted. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorRunDetail" + } + } + } + }, + "404": { + "description": "Not found, or no report." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "path", + "name": "run_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_run", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.retrieve_run(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.retrieveRun(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.RetrieveRun(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().retrieveRun(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->retrieveRun('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.retrieve_run(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.RetrieveRunAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/runs/{run_id}/accept/": { + "post": { + "operationId": "investigator_run_accept", + "summary": "Accept a recommendation", + "description": "Applies the recommended action through the item's normal path, so your usual webhooks fire. A `narrative` makes it edited and accepted. Restricted keys need the `decisions` scope (write); transaction alerts also need `transactions` (write). Escalating a verification or transaction opens a case.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorDecision" + } + } + } + }, + "409": { + "description": "Already decided, report not ready, or a guardrail holds the item." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "path", + "name": "run_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorAcceptRequest" + }, + "example": { + "send_email": false + } + } + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "accept_run", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.accept_run(\n \"11111111-2222-3333-4444-555555555555\",\n send_email=False,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.acceptRun(\"11111111-2222-3333-4444-555555555555\", {\n send_email: false,\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.AcceptRun(ctx, \"11111111-2222-3333-4444-555555555555\", didit.InvestigatorAcceptRequest{\n\tSendEmail: didit.Bool(false),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.InvestigatorAcceptRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().acceptRun(\"11111111-2222-3333-4444-555555555555\", InvestigatorAcceptRequest.builder()\n .sendEmail(false)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->acceptRun('11111111-2222-3333-4444-555555555555', [\n 'send_email' => false,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.accept_run(\n \"11111111-2222-3333-4444-555555555555\",\n send_email: false\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.AcceptRunAsync(\"11111111-2222-3333-4444-555555555555\", new InvestigatorAcceptRequest\n{\n SendEmail = false,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/runs/{run_id}/override/": { + "post": { + "operationId": "investigator_run_override", + "summary": "Override a recommendation", + "description": "Takes another action with a category and a reason. Also overrides a decision the Investigator took on its own while it still stands. Restricted keys need the `decisions` scope (write); transaction alerts also need `transactions` (write). Escalating a verification or transaction opens a case.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorDecision" + } + } + } + }, + "409": { + "description": "Already decided, or a guardrail holds the item." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "path", + "name": "run_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorOverrideRequest" + }, + "example": { + "action": "decline", + "category": "evidence_misread", + "reason": "The proof of address is older than three months." + } + } + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "override_run", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.override_run(\n \"11111111-2222-3333-4444-555555555555\",\n action=\"decline\",\n category=\"evidence_misread\",\n reason=\"The proof of address is older than three months.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.overrideRun(\"11111111-2222-3333-4444-555555555555\", {\n action: \"decline\",\n category: \"evidence_misread\",\n reason: \"The proof of address is older than three months.\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.OverrideRun(ctx, \"11111111-2222-3333-4444-555555555555\", didit.InvestigatorOverrideRequest{\n\tAction: \"decline\",\n\tCategory: \"evidence_misread\",\n\tReason: \"The proof of address is older than three months.\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.InvestigatorOverrideRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().overrideRun(\"11111111-2222-3333-4444-555555555555\", InvestigatorOverrideRequest.builder()\n .action(InvestigatorOverrideRequest.Action.of(\"decline\"))\n .category(InvestigatorOverrideRequest.Category.of(\"evidence_misread\"))\n .reason(\"The proof of address is older than three months.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->overrideRun('11111111-2222-3333-4444-555555555555', [\n 'action' => 'decline',\n 'category' => 'evidence_misread',\n 'reason' => 'The proof of address is older than three months.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.override_run(\n \"11111111-2222-3333-4444-555555555555\",\n action: \"decline\",\n category: \"evidence_misread\",\n reason: \"The proof of address is older than three months.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.OverrideRunAsync(\"11111111-2222-3333-4444-555555555555\", new InvestigatorOverrideRequest\n{\n Action = \"decline\",\n Category = \"evidence_misread\",\n Reason = \"The proof of address is older than three months.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/runs/batch-accept/": { + "post": { + "operationId": "investigator_batch_accept", + "summary": "Batch accept recommendations", + "description": "Accepts up to 50 reports marked Ready to accept. Each is its own decision: one that cannot be accepted is skipped with its reason. Restricted keys need the `decisions` scope (write); transaction alerts also need `transactions` (write). Escalating a verification or transaction opens a case.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorBatchAcceptResponse" + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorBatchAcceptRequest" + } + } + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "batch_accept_runs", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.batch_accept_runs(\n run_ids=[\"11111111-2222-3333-4444-555555555555\"],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.batchAcceptRuns({\n run_ids: [\"11111111-2222-3333-4444-555555555555\"],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.BatchAcceptRuns(ctx, didit.InvestigatorBatchAcceptRequest{\n\tRunIDs: []string{\"11111111-2222-3333-4444-555555555555\"},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.InvestigatorBatchAcceptRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().batchAcceptRuns(InvestigatorBatchAcceptRequest.builder()\n .runIds(List.of(\"11111111-2222-3333-4444-555555555555\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->batchAcceptRuns([\n 'run_ids' => ['11111111-2222-3333-4444-555555555555'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.batch_accept_runs(run_ids: [\"11111111-2222-3333-4444-555555555555\"])\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.BatchAcceptRunsAsync(new InvestigatorBatchAcceptRequest\n{\n RunIds = new string[] { \"11111111-2222-3333-4444-555555555555\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/transactions/{transaction_id}/": { + "get": { + "operationId": "investigator_transaction_retrieve", + "summary": "Get a transaction's Investigator review", + "description": "The transaction's latest review, or `investigator: null` when it never had one. Restricted keys need the `decisions` and `transactions` scopes (read).", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "transaction_id": { + "type": "string", + "format": "uuid" + }, + "investigator": { + "$ref": "#/components/schemas/InvestigatorBlock" + } + } + } + } + } + }, + "404": { + "description": "Not this application's transaction." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "path", + "name": "transaction_id", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_transaction", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.retrieve_transaction(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.retrieveTransaction(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.RetrieveTransaction(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().retrieveTransaction(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->retrieveTransaction('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.retrieve_transaction(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.RetrieveTransactionAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/guardrails/": { + "get": { + "operationId": "investigator_guardrails", + "summary": "List the locked guardrails", + "description": "The five guardrails that apply to every queue. They cannot be switched off. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "enum": [ + "sanctions_to_person", + "no_tipping_off", + "no_protected_characteristics", + "never_files_reports", + "every_decision_logged" + ] + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "locked": { + "type": "boolean" + } + } + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "list_guardrails", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.list_guardrails()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.listGuardrails();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.ListGuardrails(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().listGuardrails();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->listGuardrails();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.list_guardrails\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.ListGuardrailsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/dashboard/": { + "get": { + "operationId": "investigator_dashboard", + "summary": "Investigator dashboard", + "description": "The last 30 days: reviews, decisions, automation rate, hours saved, spend, overrides, quality samples since go-live, per-queue and weekly figures. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "stage": { + "type": "string" + }, + "window_days": { + "type": "integer" + }, + "reviews": { + "type": "integer" + }, + "decided": { + "type": "integer" + }, + "automated": { + "type": "integer" + }, + "automation_rate": { + "type": "number", + "nullable": true + }, + "hours_saved": { + "type": "number", + "nullable": true + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_dashboard", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.retrieve_dashboard()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.retrieveDashboard();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.RetrieveDashboard(ctx, didit.InvestigatorRetrieveDashboardParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().retrieveDashboard();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->retrieveDashboard();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.retrieve_dashboard\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.RetrieveDashboardAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/performance/": { + "get": { + "operationId": "investigator_performance", + "summary": "Investigator performance", + "description": "Agreement with your team, volume, time saved, confidence calibration, per-queue results, override reasons and disagreements for a period. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true, + "properties": { + "period": { + "type": "integer" + }, + "start": { + "type": "string", + "format": "date" + }, + "end": { + "type": "string", + "format": "date" + }, + "investigated": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "period", + "schema": { + "type": "string", + "enum": [ + "7", + "30", + "90" + ], + "default": "30" + }, + "description": "Days, ending today." + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_performance", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.retrieve_performance()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.retrievePerformance();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.RetrievePerformance(ctx, didit.InvestigatorRetrievePerformanceParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().retrievePerformance();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->retrievePerformance();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.retrieve_performance\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.RetrievePerformanceAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/investigator/activity/": { + "get": { + "operationId": "investigator_activity", + "summary": "List Investigator activity", + "description": "The append-only audit trail, newest first. A batch accept is one entry. Adds `counts` per group and `actors`. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "counts": { + "type": "object", + "additionalProperties": true + }, + "actors": { + "type": "array", + "items": {} + }, + "results": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true, + "description": "`id`, `at`, `kind`, `group`, `queue`, `run_id`, `item`, `actor`, `agent_version`, `data`, `report_version`, `steps`, `sources`, `batch`." + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "group", + "schema": { + "type": "string", + "enum": [ + "agent", + "decisions", + "overrides", + "autonomy" + ] + } + }, + { + "in": "query", + "name": "actor", + "schema": { + "type": "string" + }, + "description": "`agent` for the Investigator, or a person's email." + }, + { + "in": "query", + "name": "since", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "until", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "limit", + "schema": { + "type": "integer" + } + }, + { + "in": "query", + "name": "offset", + "schema": { + "type": "integer" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "list_activity", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.investigator.list_activity():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.investigator.listActivity()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.Investigator.ListActivityAutoPaging(ctx, didit.InvestigatorListActivityParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.investigator().listActivity()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->listActivity() as $item) {\n print_r($item);\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.investigator.list_activity.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.Investigator.ListActivityAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/investigator/activity/export/": { + "get": { + "operationId": "investigator_activity_export", + "summary": "Export Investigator activity", + "description": "The same entries as CSV, one row per decision, newest first, up to 10,000 entries. Restricted keys need the `decisions` scope (read); transaction alerts also need `transactions`.", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "text/csv": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "parameters": [ + { + "in": "query", + "name": "scope", + "schema": { + "type": "string", + "enum": [ + "all", + "identity", + "transactions" + ], + "default": "all" + }, + "description": "`identity` (sessions, and cases not opened by a transaction rule) or `transactions` (transaction alerts, and cases a transaction rule opened)." + }, + { + "in": "query", + "name": "group", + "schema": { + "type": "string", + "enum": [ + "agent", + "decisions", + "overrides", + "autonomy" + ] + } + }, + { + "in": "query", + "name": "actor", + "schema": { + "type": "string" + }, + "description": "`agent` for the Investigator, or a person's email." + }, + { + "in": "query", + "name": "since", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "in": "query", + "name": "until", + "schema": { + "type": "string", + "format": "date-time" + } + } + ], + "x-didit-sdk": { + "resource": "investigator", + "method": "export_activity", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\ncontent = client.investigator.export_activity()\nwith open(\"download.csv\", \"wb\") as file:\n file.write(content)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import { writeFile } from \"node:fs/promises\";\nimport Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst content = await didit.investigator.exportActivity();\nawait writeFile(\"download.csv\", Buffer.from(await content.arrayBuffer()));" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresp, err := client.Investigator.ExportActivity(ctx, didit.InvestigatorExportActivityParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer resp.Body.Close()\nfile, err := os.Create(\"download.csv\")\nif err != nil {\n\tlog.Fatal(err)\n}\ndefer file.Close()\nif _, err := io.Copy(file, resp.Body); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.investigator().exportActivity()\n .writeTo(Path.of(\"download.csv\"));" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->exportActivity();\n$content->saveTo('download.csv');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ncontent = didit.investigator.export_activity\nFile.binwrite(\"download.csv\", content)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar content = await client.Investigator.ExportActivityAsync();\nawait content.SaveToAsync(\"download.csv\");" + } + ] + } + }, + "/v3/investigator/settings/": { + "get": { + "operationId": "investigator_settings_retrieve", + "summary": "Get Investigator settings", + "description": "The stage (`none`, `shadow` or `live`) and the monthly spend limit with this month's spend. Restricted keys need the `applications` scope (read).", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorSettings" + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "retrieve_settings", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.retrieve_settings()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.retrieveSettings();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.RetrieveSettings(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().retrieveSettings();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->retrieveSettings();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.retrieve_settings\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.RetrieveSettingsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "patch": { + "operationId": "investigator_settings_update", + "summary": "Update Investigator settings", + "description": "Change the stage, the monthly spend limit, or both. Moving past `none` needs a plan that includes the Investigator (Growth or higher); `live` also needs a contract that prices it, if you have one. Restricted keys need the `applications` scope (write).", + "tags": [ + "Investigator" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorSettings" + } + } + } + }, + "400": { + "description": "Neither field sent, or an invalid value." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "The key lacks the scope, the item kind is not readable, or the plan does not include the Investigator." + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvestigatorSettings" + }, + "example": { + "stage": "shadow", + "monthly_spend_limit": "500.00" + } + } + } + }, + "x-didit-sdk": { + "resource": "investigator", + "method": "update_settings", + "auth": "api_key", + "server": true + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.investigator.update_settings(\n stage=\"shadow\",\n monthly_spend_limit=\"500.00\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.investigator.updateSettings({\n stage: \"shadow\",\n monthly_spend_limit: \"500.00\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Investigator.UpdateSettings(ctx, didit.InvestigatorSettingsParam{\n\tStage: didit.Some(didit.InvestigatorSettingsStage(\"shadow\")),\n\tMonthlySpendLimit: didit.String(\"500.00\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.InvestigatorSettings;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.investigator().updateSettings(InvestigatorSettings.builder()\n .stage(InvestigatorSettings.Stage.of(\"shadow\"))\n .monthlySpendLimit(\"500.00\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "investigator->updateSettings([\n 'stage' => 'shadow',\n 'monthly_spend_limit' => '500.00',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.investigator.update_settings(\n stage: \"shadow\",\n monthly_spend_limit: \"500.00\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Investigator.UpdateSettingsAsync(new InvestigatorSettings\n{\n Stage = new InvestigatorSettingsStage(\"shadow\"),\n MonthlySpendLimit = \"500.00\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/travel-rule/wallet-addresses/import/": { + "post": { + "summary": "Import wallet addresses in bulk", + "description": "Add up to 10,000 wallets to your Travel Rule address book in one call, as JSON or as a CSV file. A wallet can be sent by its address, by the SHA-256 of its lowercased address (`address_hash`) to keep the address private, or both. The import is idempotent: a wallet already in the book is reported as `existing` and left untouched, so the same file can be sent again. Each row is validated on its own, so one bad row never fails the others. A wallet imported by its hash only matches transfers to that address and can be proven with the wallet-ownership widget like any other entry.", + "operationId": "importTravelRuleWalletAddresses", + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-didit-sdk": { + "resource": "travel_rule.wallet_addresses", + "method": "bulk_import", + "auth": "api_key", + "server": true + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "required": [ + "entries" + ], + "properties": { + "entries": { + "type": "array", + "minItems": 1, + "maxItems": 10000, + "items": { + "$ref": "#/components/schemas/WalletAddressImportRow" + } + } + } + }, + { + "type": "array", + "minItems": 1, + "maxItems": 10000, + "items": { + "$ref": "#/components/schemas/WalletAddressImportRow" + } + } + ] + }, + "example": { + "entries": [ + { + "address": "0xBeneficiaryWallet01", + "chain": "ETH", + "holder_name": "Ana Diaz", + "holder_vendor_data": "user-042" + }, + { + "address_hash": "4f7d2e1c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e", + "chain": "bitcoin", + "holder_vendor_data": "user-043" + }, + { + "address": "rExchangeSharedAddress01", + "chain": "XRP", + "memo": "104729", + "holder_name": "Ana Diaz" + } + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "file" + ], + "properties": { + "file": { + "type": "string", + "format": "binary", + "description": "A UTF-8 CSV file of at most 5 MB with a header row naming its columns (the row fields or their aliases). Empty cells count as not sent; columns the import does not read are ignored." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl (JSON)", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/wallet-addresses/import/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"entries\": [{\"address\": \"0xBeneficiaryWallet01\", \"chain\": \"ETH\", \"holder_name\": \"Ana Diaz\"}, {\"address\": \"rExchangeSharedAddress01\", \"chain\": \"XRP\", \"memo\": \"104729\"}]}'" + }, + { + "lang": "curl", + "label": "curl (CSV)", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/wallet-addresses/import/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -F 'file=@wallets.csv;type=text/csv'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.wallet_addresses.bulk_import(\n body={\n \"entries\": [{\n \"address\": \"0xBeneficiaryWallet01\",\n \"chain\": \"ETH\",\n \"holder_name\": \"Ana Diaz\",\n \"holder_vendor_data\": \"user-042\",\n }, {\n \"address_hash\": \"4f7d2e1c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e\",\n \"chain\": \"bitcoin\",\n \"holder_vendor_data\": \"user-043\",\n }, {\n \"address\": \"rExchangeSharedAddress01\",\n \"chain\": \"XRP\",\n \"memo\": \"104729\",\n \"holder_name\": \"Ana Diaz\",\n }],\n },\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.walletAddresses.bulkImport({\n file: await fileFromPath(\"wallet-addresses.csv\"),\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.WalletAddresses.BulkImport(ctx, didit.TravelRuleWalletAddressesBulkImportParams{\n\tFile: didit.Some(didit.FileFromPath(\"wallet-addresses.csv\")),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport me.didit.Didit;\nimport me.didit.types.TravelRuleWalletAddressesBulkImportParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().walletAddresses().bulkImport(TravelRuleWalletAddressesBulkImportParams.builder()\n .file(Path.of(\"wallet-addresses.csv\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletAddresses->bulkImport([\n 'file' => File::fromPath('wallet-addresses.csv'),\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.wallet_addresses.bulk_import(\n body: {\n entries: [{\n address: \"0xBeneficiaryWallet01\",\n chain: \"ETH\",\n holder_name: \"Ana Diaz\",\n holder_vendor_data: \"user-042\",\n }, {\n address_hash: \"4f7d2e1c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e\",\n chain: \"bitcoin\",\n holder_vendor_data: \"user-043\",\n }, {\n address: \"rExchangeSharedAddress01\",\n chain: \"XRP\",\n memo: \"104729\",\n holder_name: \"Ana Diaz\",\n }],\n }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.WalletAddresses.BulkImportAsync(new TravelRuleWalletAddressesBulkImportParams\n{\n File = FileUpload.FromPath(\"wallet-addresses.csv\"),\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Every row was created, already in the book, or a repeat of an earlier row.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressImportReport" + }, + "example": { + "total": 3, + "created": 2, + "existing": 1, + "duplicate": 0, + "failed": 0, + "rows": [ + { + "row": 1, + "status": "existing", + "entry_id": "11111111-2222-3333-4444-555555555555", + "errors": {}, + "duplicate_of_row": null + }, + { + "row": 2, + "status": "created", + "entry_id": "22222222-3333-4444-5555-666666666666", + "errors": {}, + "duplicate_of_row": null + }, + { + "row": 3, + "status": "created", + "entry_id": "33333333-4444-5555-6666-777777777777", + "errors": {}, + "duplicate_of_row": null + } + ] + } + } + } + }, + "207": { + "description": "At least one row failed; the others were imported. Each failed row names its errors.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WalletAddressImportReport" + }, + "example": { + "total": 2, + "created": 1, + "existing": 0, + "duplicate": 0, + "failed": 1, + "rows": [ + { + "row": 1, + "status": "created", + "entry_id": "44444444-5555-6666-7777-888888888888", + "errors": {}, + "duplicate_of_row": null + }, + { + "row": 2, + "status": "failed", + "entry_id": null, + "errors": { + "address": [ + "Send address, address_hash or both." + ] + }, + "duplicate_of_row": null + } + ] + } + } + } + }, + "400": { + "description": "The request itself cannot be read: both `entries` and a `file`, or neither; more than 10,000 rows; a file over 5 MB, not UTF-8 CSV, without a header row or without rows.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "file": [ + "At most 10000 rows per import." + ] + } + } + } + } + } + } + }, + "/v3/travel-rule/counterparty-contacts/": { + "get": { + "summary": "List counterparty contacts", + "description": "The compliance addresses the EMAIL rail may write to for your application: Didit's platform contacts plus your application's own private ones, bounced ones included, newest first. Paginated with `limit` (default 50) and `offset`; `count` stops at 100, so 100 means 100 or more.", + "operationId": "listTravelRuleCounterpartyContacts", + "x-didit-sdk": { + "resource": "travel_rule.counterparty_contacts", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "counterparty", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Only the contacts of this directory entry. Must be a UUID; an empty value means no filter." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1 + }, + "description": "Page size, default 50." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + }, + "description": "Rows to skip." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/travel-rule/counterparty-contacts/?counterparty={vasp_id}' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.travel_rule.counterparty_contacts.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.travelRule.counterpartyContacts.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.TravelRule.CounterpartyContacts.ListAutoPaging(ctx, didit.TravelRuleCounterpartyContactsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.travelRule().counterpartyContacts().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->counterpartyContacts->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.counterparty_contacts.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.TravelRule.CounterpartyContacts.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "The contacts.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleCounterpartyContact" + } + } + } + }, + "example": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "uuid": "6c1f9b8e-2d4a-4f7e-8b3c-5a9d0e1f2a3b", + "counterparty": "0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91", + "counterparty_name": "Example Counterparty VASP", + "email": "compliance@counterparty.example", + "name": "Compliance desk", + "source": "application", + "scope": "application", + "created_by": "", + "verified_at": null, + "is_invalid": false, + "invalid_reason": "", + "invalid_detail": "", + "invalid_at": null, + "created_at": "2026-10-19T09:00:00Z" + } + ] + } + } + } + }, + "400": { + "description": "`counterparty` is not a UUID.", + "content": { + "application/json": { + "example": { + "counterparty": [ + "Must be a valid UUID." + ] + } + } + } + } + } + }, + "post": { + "summary": "Add a counterparty contact", + "description": "Adds a private compliance address for one directory entry. Only your application sees and uses it, and the EMAIL rail writes to it before any platform contact. Adding an address the entry already has among your own contacts returns that contact.", + "operationId": "createTravelRuleCounterpartyContact", + "x-didit-sdk": { + "resource": "travel_rule.counterparty_contacts", + "method": "create", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyContactCreate" + }, + "example": { + "counterparty": "0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91", + "email": "compliance@counterparty.example", + "name": "Compliance desk" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/counterparty-contacts/ \\\n -H 'x-api-key: YOUR_API_KEY' -H 'Content-Type: application/json' \\\n -d '{\"counterparty\": \"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\", \"email\": \"compliance@counterparty.example\", \"name\": \"Compliance desk\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.counterparty_contacts.create(\n counterparty=\"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\",\n email=\"compliance@counterparty.example\",\n name=\"Compliance desk\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.counterpartyContacts.create({\n counterparty: \"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\",\n email: \"compliance@counterparty.example\",\n name: \"Compliance desk\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.CounterpartyContacts.Create(ctx, didit.TravelRuleCounterpartyContactCreate{\n\tCounterparty: \"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\",\n\tEmail: \"compliance@counterparty.example\",\n\tName: didit.String(\"Compliance desk\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleCounterpartyContactCreate;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().counterpartyContacts().create(TravelRuleCounterpartyContactCreate.builder()\n .counterparty(\"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\")\n .email(\"compliance@counterparty.example\")\n .name(\"Compliance desk\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->counterpartyContacts->create([\n 'counterparty' => '0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91',\n 'email' => 'compliance@counterparty.example',\n 'name' => 'Compliance desk',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.counterparty_contacts.create(\n counterparty: \"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\",\n email: \"compliance@counterparty.example\",\n name: \"Compliance desk\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.CounterpartyContacts.CreateAsync(new TravelRuleCounterpartyContactCreate\n{\n Counterparty = \"0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91\",\n Email = \"compliance@counterparty.example\",\n Name = \"Compliance desk\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "201": { + "description": "The contact was added.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleCounterpartyContact" + }, + "example": { + "uuid": "6c1f9b8e-2d4a-4f7e-8b3c-5a9d0e1f2a3b", + "counterparty": "0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91", + "counterparty_name": "Example Counterparty VASP", + "email": "compliance@counterparty.example", + "name": "Compliance desk", + "source": "application", + "scope": "application", + "created_by": "", + "verified_at": null, + "is_invalid": false, + "invalid_reason": "", + "invalid_detail": "", + "invalid_at": null, + "created_at": "2026-10-19T09:00:00Z" + } + } + } + }, + "400": { + "description": "A field is missing or invalid, or no directory entry has this id.", + "content": { + "application/json": { + "example": { + "counterparty": [ + "No directory entry with this id." + ] + } + } + } + } + } + } + }, + "/v3/travel-rule/counterparty-contacts/{contact_uuid}/": { + "delete": { + "summary": "Delete a counterparty contact", + "description": "Deletes one of your application's own contacts. The EMAIL rail stops writing to it at once. Platform contacts cannot be deleted.", + "operationId": "deleteTravelRuleCounterpartyContact", + "x-didit-sdk": { + "resource": "travel_rule.counterparty_contacts", + "method": "delete", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "contact_uuid", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X DELETE https://verification.didit.me/v3/travel-rule/counterparty-contacts/{contact_uuid}/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.travel_rule.counterparty_contacts.delete(\n \"11111111-2222-3333-4444-555555555555\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.travelRule.counterpartyContacts.delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.TravelRule.CounterpartyContacts.Delete(ctx, \"11111111-2222-3333-4444-555555555555\"); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.travelRule().counterpartyContacts().delete(\"11111111-2222-3333-4444-555555555555\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->counterpartyContacts->delete('11111111-2222-3333-4444-555555555555');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.counterparty_contacts.delete(\"11111111-2222-3333-4444-555555555555\")" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.TravelRule.CounterpartyContacts.DeleteAsync(\"11111111-2222-3333-4444-555555555555\");" + } + ], + "responses": { + "204": { + "description": "Deleted." + }, + "404": { + "description": "No contact of your application has this id. A platform contact answers 404 too." + } + } + } + }, + "/v3/travel-rule/transfers/": { + "get": { + "summary": "List Travel Rule transfers", + "description": "Your application's Travel Rule transfers, newest first, paginated with `limit` (default 50) and `offset`. Rows carry VASP names, wallets and memos only, never person data. Comma-separated values select several. An unknown value answers 400 naming the parameter, for example `{\"direction\": [\"Unknown value(s): inbound.\"]}`.", + "operationId": "listTravelRuleTransfers", + "x-didit-sdk": { + "resource": "travel_rule.transfers", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "direction", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "INBOUND, OUTBOUND, or both comma-separated. Case-sensitive." + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "One or more transfer statuses, comma-separated." + }, + { + "name": "rail", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "One or more of INTERNAL, TRP, GTR, TRUST, VERIFYVASP, SYGNA, EMAIL, TEST_VASP, comma-separated." + }, + { + "name": "kind", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "withdrawal, deposit or message, comma-separated." + }, + { + "name": "currency", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "The transaction's currency, case-insensitive." + }, + { + "name": "start_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date" + }, + "description": "Transfers created on or after this day (UTC), YYYY-MM-DD." + }, + { + "name": "end_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date" + }, + "description": "Transfers created on or before this day (UTC), YYYY-MM-DD." + }, + { + "name": "counterparty", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The `id` of a directory entry or Didit network member from GET /v3/travel-rule/vasps/: your transfers with that counterparty. A value that is not a valid id answers 400." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1 + }, + "description": "Page size, default 50." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + }, + "description": "Rows to skip." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/travel-rule/transfers/?direction=OUTBOUND&status=AWAITING_COUNTERPARTY' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.travel_rule.transfers.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.travelRule.transfers.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.TravelRule.Transfers.ListAutoPaging(ctx, didit.TravelRuleTransfersListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.travelRule().transfers().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->transfers->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.transfers.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.TravelRule.Transfers.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "A page of transfers.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleTransferRow" + } + } + } + }, + "example": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "id": "5b0f6c2e-2a4d-4f0e-9a41-0c3f9a7e1d22", + "transaction_id": "a3c1e8f4-7b62-4d1a-8e0b-2f9d6c4b7a10", + "txn_id": "wd-2026-10-19-0042", + "txn_date": "2026-10-19T08:41:12Z", + "kind": "withdrawal", + "direction": "OUTBOUND", + "status": "AWAITING_COUNTERPARTY", + "rail": "TRP", + "asset": "ETH", + "amount": "1.25", + "originator_vasp": "Origin CASP SL", + "beneficiary_vasp": "Example Counterparty VASP", + "originator_wallet": "0x77ccff0cc624a53cd8b8d8703ced826710211ec7", + "beneficiary_wallet": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb", + "originator_memo": "", + "beneficiary_memo": "", + "originator_wallet_type": "vasp", + "beneficiary_wallet_type": "vasp", + "matched_transaction_id": null, + "deadline_at": "2026-10-21T08:41:13Z", + "created_at": "2026-10-19T08:41:13Z" + } + ] + } + } + } + }, + "400": { + "description": "A filter value is not valid.", + "content": { + "application/json": { + "example": { + "start_date": [ + "Use the YYYY-MM-DD format." + ] + } + } + } + } + } + } + }, + "/v3/session/imports/providers/": { + "get": { + "summary": "List migration providers", + "description": "The 15 providers you can migrate verifications from: what each needs to connect, whether its API can list records, the permissions its key needs and its default status mapping.", + "operationId": "get_v3_session_imports_providers_list", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "list_providers", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "200": { + "description": "Migration providers.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "providers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImportProvider" + } + }, + "mapping_targets": { + "type": "array", + "items": { + "type": "string" + } + }, + "skipped_statuses": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.list_providers()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.listProviders();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.ListProviders(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().listProviders();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->listProviders();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.list_providers\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.ListProvidersAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/providers/check/": { + "post": { + "summary": "Check a provider key", + "description": "Read the provider with your key before creating a migration. Nothing is stored.", + "operationId": "post_v3_session_imports_providers_check", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "check_provider", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "provider": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ], + "description": "Migration source, from `GET /v3/session/imports/providers/`. Required when `source` is `provider`." + }, + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "options": { + "type": "object", + "properties": { + "region": { + "type": "string", + "description": "The provider region, one of its `regions` (Onfido: `eu`, `us`, `ca`)." + } + } + }, + "record_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "For `api_ids` providers: the first record ID of your export." + } + }, + "required": [ + "provider", + "credentials" + ] + }, + "example": { + "provider": "sumsub", + "credentials": { + "app_token": "YOUR_PROVIDER_APP_TOKEN", + "secret_key": "YOUR_PROVIDER_SECRET_KEY" + } + } + } + } + }, + "responses": { + "200": { + "description": "The key can read records.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ok": { + "type": "boolean" + }, + "provider": { + "type": "string" + }, + "credential_hint": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "400": { + "description": "The provider refused the key: `code`, `detail`, `missing_permissions`, `suggested_region`." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.check_provider(\n provider=\"sumsub\",\n credentials={\"app_token\": \"YOUR_PROVIDER_APP_TOKEN\", \"secret_key\": \"YOUR_PROVIDER_SECRET_KEY\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.checkProvider({\n provider: \"sumsub\",\n credentials: { app_token: \"YOUR_PROVIDER_APP_TOKEN\", secret_key: \"YOUR_PROVIDER_SECRET_KEY\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.CheckProvider(ctx, didit.SessionsImportsCheckProviderParams{\n\tProvider: \"sumsub\",\n\tCredentials: map[string]string{\"app_token\": \"YOUR_PROVIDER_APP_TOKEN\", \"secret_key\": \"YOUR_PROVIDER_SECRET_KEY\"},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SessionsImportsCheckProviderParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().checkProvider(SessionsImportsCheckProviderParams.builder()\n .provider(SessionsImportsCheckProviderParams.Provider.of(\"sumsub\"))\n .credentials(Map.of(\"app_token\", \"YOUR_PROVIDER_APP_TOKEN\", \"secret_key\", \"YOUR_PROVIDER_SECRET_KEY\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->checkProvider([\n 'provider' => 'sumsub',\n 'credentials' => ['app_token' => 'YOUR_PROVIDER_APP_TOKEN', 'secret_key' => 'YOUR_PROVIDER_SECRET_KEY'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.check_provider(\n provider: \"sumsub\",\n credentials: { app_token: \"YOUR_PROVIDER_APP_TOKEN\", secret_key: \"YOUR_PROVIDER_SECRET_KEY\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.CheckProviderAsync(new SessionsImportsCheckProviderParams\n{\n Provider = \"sumsub\",\n Credentials = new Dictionary { [\"app_token\"] = \"YOUR_PROVIDER_APP_TOKEN\", [\"secret_key\"] = \"YOUR_PROVIDER_SECRET_KEY\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/{importId}/start/": { + "post": { + "summary": "Start a dry run", + "description": "Start a migration whose dry run finished (`READY`): the same job imports for real, with the status mapping and face templates choice as sent now.", + "operationId": "post_v3_session_imports_start", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "start", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + }, + "description": "Edits to the provider's default status mapping: source status to Didit status. `Not Finished` and `Abandoned` are skipped and counted, never imported." + }, + "face_templates": { + "type": "boolean", + "default": false, + "description": "Build face templates from the imported selfies and search them against the application's faces and blocklist. Matches are flagged for review only. Requires `lawful_basis_confirmed`." + }, + "lawful_basis_confirmed": { + "type": "boolean", + "default": false, + "description": "Confirms you have a lawful basis to share these selfies with Didit as processor." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.start(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.start(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Start(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsImportsStartParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().start(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->start('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.start(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.StartAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/{importId}/pause/": { + "post": { + "summary": "Pause an import job", + "description": "Pause a queued or running import.", + "operationId": "post_v3_session_imports_pause", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "pause", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.pause(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.pause(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Pause(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().pause(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->pause('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.pause(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.PauseAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/{importId}/resume/": { + "post": { + "summary": "Resume an import job", + "description": "Resume an import you paused. A job paused by the provider's rate limit resumes by itself.", + "operationId": "post_v3_session_imports_resume", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "resume", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.resume(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.resume(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Resume(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().resume(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->resume('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.resume(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.ResumeAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/{importId}/cancel/": { + "post": { + "summary": "Cancel an import job", + "description": "Cancel an import. What was imported stays; the uploaded file and the key are erased.", + "operationId": "post_v3_session_imports_cancel", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "cancel", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "The import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "The job is not in a state this action applies to." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.cancel(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.cancel(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Cancel(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().cancel(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->cancel('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.cancel(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.CancelAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/imports/{importId}/retry/": { + "post": { + "summary": "Retry a migration", + "description": "Run a migration that ended again as a new job, with a new key (the old one was erased). It keeps the provider, workflow, region, status mapping and face templates choice; everything already in Didit is skipped.", + "operationId": "post_v3_session_imports_retry", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "retry", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "importId", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": false, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "credentials": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The provider's read-only key, with the field names `credential_fields` lists. Encrypted at rest, never returned, and erased when the import ends. Send it as a JSON string in multipart." + }, + "file": { + "type": "string", + "format": "binary", + "description": "The record IDs or export file again, for `api_ids` and `export_file` providers." + } + } + }, + "example": { + "credentials": { + "app_token": "YOUR_PROVIDER_APP_TOKEN", + "secret_key": "YOUR_PROVIDER_SECRET_KEY" + } + } + } + } + }, + "responses": { + "201": { + "description": "The new import job.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionImportJob" + } + } + } + }, + "400": { + "description": "Not a migration, not ended, or the provider refused the key." + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.retry(\n \"11111111-2222-3333-4444-555555555555\",\n credentials={\"app_token\": \"YOUR_PROVIDER_APP_TOKEN\", \"secret_key\": \"YOUR_PROVIDER_SECRET_KEY\"},\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.retry(\"11111111-2222-3333-4444-555555555555\", {\n credentials: { app_token: \"YOUR_PROVIDER_APP_TOKEN\", secret_key: \"YOUR_PROVIDER_SECRET_KEY\" },\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.Retry(ctx, \"11111111-2222-3333-4444-555555555555\", didit.SessionsImportsRetryParams{\n\tCredentials: didit.Some(map[string]string{\"app_token\": \"YOUR_PROVIDER_APP_TOKEN\", \"secret_key\": \"YOUR_PROVIDER_SECRET_KEY\"}),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.Map;\nimport me.didit.Didit;\nimport me.didit.types.SessionsImportsRetryParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().retry(\"11111111-2222-3333-4444-555555555555\", SessionsImportsRetryParams.builder()\n .credentials(Map.of(\"app_token\", \"YOUR_PROVIDER_APP_TOKEN\", \"secret_key\", \"YOUR_PROVIDER_SECRET_KEY\"))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->retry('11111111-2222-3333-4444-555555555555', [\n 'credentials' => ['app_token' => 'YOUR_PROVIDER_APP_TOKEN', 'secret_key' => 'YOUR_PROVIDER_SECRET_KEY'],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.retry(\n \"11111111-2222-3333-4444-555555555555\",\n credentials: { app_token: \"YOUR_PROVIDER_APP_TOKEN\", secret_key: \"YOUR_PROVIDER_SECRET_KEY\" }\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Collections.Generic;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.RetryAsync(\"11111111-2222-3333-4444-555555555555\", new SessionsImportsRetryParams\n{\n Credentials = new Dictionary { [\"app_token\"] = \"YOUR_PROVIDER_APP_TOKEN\", [\"secret_key\"] = \"YOUR_PROVIDER_SECRET_KEY\" },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/import-jobs/": { + "get": { + "summary": "List import jobs", + "description": "Every import of the application in one shape: verification imports and migrations, people and businesses.", + "operationId": "get_v3_import_jobs_list", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "list_jobs", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "scope", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "all", + "sessions", + "users", + "businesses" + ], + "default": "all" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + } + } + ], + "responses": { + "200": { + "description": "Import jobs, newest first.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImportJobSummary" + } + }, + "counts": { + "type": "object", + "properties": { + "all": { + "type": "integer" + }, + "running_and_waiting": { + "type": "integer" + }, + "needs_attention": { + "type": "integer" + }, + "done": { + "type": "integer" + } + } + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.list_jobs()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.listJobs();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.ListJobs(ctx, didit.SessionsImportsListJobsParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().listJobs();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->listJobs();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.list_jobs\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.ListJobsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/import-jobs/active/": { + "get": { + "summary": "List active import jobs", + "description": "What is running or waiting, plus what just ended, for a progress indicator.", + "operationId": "get_v3_import_jobs_active", + "x-didit-sdk": { + "resource": "sessions.imports", + "method": "list_active_jobs", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "responses": { + "200": { + "description": "Active import jobs.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImportJobSummary" + } + }, + "running": { + "type": "integer" + } + } + } + } + } + }, + "403": { + "description": "Missing, invalid, or revoked API key, or the caller cannot import verifications for this application.", + "content": { + "application/json": { + "examples": { + "Forbidden": { + "value": { + "detail": "You do not have permission to perform this action." + } + } + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.imports.list_active_jobs()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.imports.listActiveJobs();\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.Imports.ListActiveJobs(ctx)\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().imports().listActiveJobs();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->imports->listActiveJobs();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.imports.list_active_jobs\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.Imports.ListActiveJobsAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/organization/{organization_id}/application/{application_id}/transactions/delete/": { + "delete": { + "summary": "Batch delete transactions", + "description": "Delete one or more transactions permanently. Select them with `transaction_uuids`, or delete every transaction in the application with `delete_all: true`.\n\nA deleted transaction goes with the records that are only its own: its parties, tags, notes, payment method links, documents and every stored version of their files, rule runs, alerts, provider results, network snapshot and Travel Rule data. Cases, people and companies stay; their link to the deleted transaction is cleared. Draft regulatory reports stop listing it and are rebuilt without it. Deletion is permanent and irreversible; there is no undo endpoint.\n\nIf any selected transaction is listed in a finalized regulatory report, the whole request is refused with `409` and nothing is deleted. The body uses `code: in_filed_report` and names each transaction and the reports that list it. Delete those reports, then repeat the request.", + "operationId": "batch_delete_transactions", + "x-didit-sdk": { + "resource": "transactions", + "method": "batch_delete", + "auth": "api_key", + "server": true, + "scope": "application" + }, + "tags": [ + "Transactions" + ], + "parameters": [ + { + "name": "organization_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Organization UUID." + }, + { + "name": "application_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "Application UUID." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "oneOf": [ + { + "title": "Selected transactions", + "required": [ + "transaction_uuids" + ], + "properties": { + "transaction_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Didit transaction UUIDs to delete. Required unless `delete_all` is true.", + "minItems": 1 + }, + "delete_all": { + "type": "boolean", + "enum": [ + false + ], + "default": false, + "description": "Leave out or send `false` when you select transactions with `transaction_uuids`." + } + } + }, + { + "title": "All transactions", + "required": [ + "delete_all" + ], + "properties": { + "delete_all": { + "type": "boolean", + "enum": [ + true + ], + "description": "Delete every transaction in the application. `transaction_uuids` is ignored." + }, + "transaction_uuids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Ignored when `delete_all` is true." + } + } + } + ] + }, + "examples": { + "Selected transactions": { + "value": { + "transaction_uuids": [ + "abcdef12-3456-7890-abcd-ef1234567890", + "12345678-90ab-cdef-1234-567890abcdef" + ] + } + }, + "All transactions": { + "value": { + "delete_all": true + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl (selected transactions)", + "source": "curl -X DELETE 'https://verification.didit.me/v3/organization/11111111-2222-3333-4444-555555555555/application/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/transactions/delete/' \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"transaction_uuids\": [\"abcdef12-3456-7890-abcd-ef1234567890\"]}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\nclient = Didit()\n\nclient.transactions.batch_delete(\n body={\n \"transaction_uuids\": [\"abcdef12-3456-7890-abcd-ef1234567890\", \"12345678-90ab-cdef-1234-567890abcdef\"],\n },\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nconst didit = new Didit();\n\nawait didit.transactions.batchDelete({\n transaction_uuids: [\"abcdef12-3456-7890-abcd-ef1234567890\", \"12345678-90ab-cdef-1234-567890abcdef\"],\n});" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.TransactionsBatchDeleteParams;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nDidit client = new Didit();\n\nclient.transactions().batchDelete(TransactionsBatchDeleteParams.builder()\n .putAdditionalProperty(\"transaction_uuids\", List.of(\"abcdef12-3456-7890-abcd-ef1234567890\", \"12345678-90ab-cdef-1234-567890abcdef\"))\n .build());" + }, + { + "lang": "php", + "label": "PHP", + "source": "transactions->batchDelete([\n 'transaction_uuids' => ['abcdef12-3456-7890-abcd-ef1234567890', '12345678-90ab-cdef-1234-567890abcdef'],\n]);" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n# and DIDIT_APPLICATION_ID.\ndidit = Didit::Client.new\n\ndidit.transactions.batch_delete(\n body: {\n transaction_uuids: [\"abcdef12-3456-7890-abcd-ef1234567890\", \"12345678-90ab-cdef-1234-567890abcdef\"],\n }\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY, DIDIT_ORGANIZATION_ID\n// and DIDIT_APPLICATION_ID.\nusing var client = new DiditClient();\n\nawait client.Transactions.BatchDeleteAsync(new TransactionsBatchDeleteParams\n{\n TransactionUuids = new string[] { \"abcdef12-3456-7890-abcd-ef1234567890\", \"12345678-90ab-cdef-1234-567890abcdef\" },\n});" + } + ], + "responses": { + "204": { + "description": "Selected transactions deleted." + }, + "400": { + "description": "No transaction selector was supplied.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "transaction_uuids": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "example": { + "transaction_uuids": [ + "This field is required unless delete_all is true." + ] + } + } + } + }, + "403": { + "description": "The API key cannot delete transactions. The endpoint requires `write:transactions`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + } + } + } + }, + "409": { + "description": "At least one selected transaction is listed in a finalized regulatory report. Nothing is deleted.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InFiledReportError" + } + } + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/travel-rule/networks/": { + "get": { + "summary": "Get Travel Rule networks", + "description": "Read, for this application, every network's true state: whether it sends through your own membership or through Didit's, and why it cannot yet. Also returns the due-diligence questionnaire and your participants. Reading creates nothing.", + "operationId": "getTravelRuleNetworks", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "retrieve", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/networks/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.networks.retrieve()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.networks.retrieve();\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().networks().retrieve();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->networks->retrieve();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.networks.retrieve\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Networks.RetrieveAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Network states, questionnaire and participants.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworksOverview" + }, + "examples": { + "Before the questionnaire": { + "value": { + "platform_networks_enabled": false, + "questionnaire": { + "status": "draft", + "answers": {}, + "submitted_at": null, + "reviewed_at": null, + "review_note": "", + "history": [], + "updated_at": null + }, + "participants": [], + "networks": { + "gtr": { + "rail": "GTR", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "trust": { + "rail": "TRUST", + "mode": "unavailable", + "state": "unavailable", + "reason": "own_membership_only", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "verifyvasp": { + "rail": "VERIFYVASP", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + }, + "sygna": { + "rail": "SYGNA", + "mode": "unavailable", + "state": "didit_onboarding", + "reason": "didit_membership_not_live", + "platform_available": false, + "connected": false, + "participant": null, + "vasps_reachable": 0, + "routed_30d": 0 + } + } + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + } + } + } + }, + "/v3/travel-rule/networks/questionnaire/": { + "get": { + "summary": "Get the network questionnaire", + "description": "Read Didit's network due-diligence questionnaire for this application, its status and Didit's review note. An application that never saved one reads an empty draft.", + "operationId": "getTravelRuleNetworkQuestionnaire", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "retrieve_questionnaire", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET https://verification.didit.me/v3/travel-rule/networks/questionnaire/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.networks.retrieve_questionnaire()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.networks.retrieveQuestionnaire();\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().networks().retrieveQuestionnaire();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->networks->retrieveQuestionnaire();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.networks.retrieve_questionnaire\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Networks.RetrieveQuestionnaireAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The questionnaire.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaire" + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + } + } + }, + "put": { + "summary": "Save network questionnaire answers", + "description": "Save answers to the questionnaire while it is draft or changes_requested. Saves are partial: answers you do not send are kept.", + "operationId": "updateTravelRuleNetworkQuestionnaire", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "update_questionnaire", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X PUT https://verification.didit.me/v3/travel-rule/networks/questionnaire/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"legal_name\": \"Example VASP Ltd\", \"incorporation_country\": \"ES\", \"aml_ctf_policy_attested\": true}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.networks.update_questionnaire(\n legal_name=\"Example VASP Ltd\",\n incorporation_country=\"ES\",\n aml_ctf_policy_attested=True,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.networks.updateQuestionnaire({\n legal_name: \"Example VASP Ltd\",\n incorporation_country: \"ES\",\n aml_ctf_policy_attested: true,\n});\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleNetworkQuestionnaireAnswers;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().networks().updateQuestionnaire(TravelRuleNetworkQuestionnaireAnswers.builder()\n .legalName(\"Example VASP Ltd\")\n .incorporationCountry(\"ES\")\n .amlCtfPolicyAttested(true)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->networks->updateQuestionnaire([\n 'legal_name' => 'Example VASP Ltd',\n 'incorporation_country' => 'ES',\n 'aml_ctf_policy_attested' => true,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.networks.update_questionnaire(\n legal_name: \"Example VASP Ltd\",\n incorporation_country: \"ES\",\n aml_ctf_policy_attested: true\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Networks.UpdateQuestionnaireAsync(new TravelRuleNetworkQuestionnaireAnswers\n{\n LegalName = \"Example VASP Ltd\",\n IncorporationCountry = \"ES\",\n AmlCtfPolicyAttested = true,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaireAnswers" + }, + "example": { + "legal_name": "Example VASP Ltd", + "incorporation_country": "ES", + "aml_ctf_policy_attested": true + } + } + } + }, + "responses": { + "200": { + "description": "The questionnaire with the merged answers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaire" + } + } + } + }, + "400": { + "description": "Validation error, keyed by field." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Network access through Didit's membership is not open yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_platform_networks_disabled" + ] + } + } + } + } + } + }, + "409": { + "description": "The questionnaire is with Didit or decided: it can only change while draft or changes_requested.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_questionnaire_not_editable" + ] + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/networks/questionnaire/submit/": { + "post": { + "summary": "Submit the network questionnaire", + "description": "Send the saved questionnaire to Didit's review. The saved answers must be complete: every field except lei and data_protection_contact_email, with the three attestations true. Didit approves it, asks for changes (edit and submit again) or rejects it. Networks you requested while it was pending are handed to their onboarding when it is approved.", + "operationId": "submitTravelRuleNetworkQuestionnaire", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "submit_questionnaire", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/networks/questionnaire/submit/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.networks.submit_questionnaire()\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.networks.submitQuestionnaire();\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().networks().submitQuestionnaire();\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->networks->submitQuestionnaire();\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.networks.submit_questionnaire\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Networks.SubmitQuestionnaireAsync();\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The submitted questionnaire.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaire" + } + } + } + }, + "400": { + "description": "The saved answers are incomplete or invalid, keyed by field." + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Network access through Didit's membership is not open yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_platform_networks_disabled" + ] + } + } + } + } + } + }, + "409": { + "description": "The questionnaire is with Didit or decided: it can only change while draft or changes_requested.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_questionnaire_not_editable" + ] + } + } + } + } + } + } + } + } + }, + "/v3/travel-rule/networks/{network}/request/": { + "post": { + "summary": "Request a Travel Rule network", + "description": "Ask for this application's own participant on a network Didit's membership carries (GTR or Sygna). Without an approved questionnaire the participant waits for it (questionnaire_pending); with one it goes to the network's onboarding (submitted_to_network) and becomes active once the network gives it a code. Idempotent: a pending, onboarding or live participant is returned unchanged. If the network refused the registration (last_error is set), requesting again retries. TRUST and VerifyVASP are reachable only through your own membership: TRUST admits members per VASP, and a VerifyVASP Enclave serves one VASP id.", + "operationId": "requestTravelRuleNetworkAccess", + "x-didit-sdk": { + "resource": "travel_rule.networks", + "method": "request_access", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "network", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "gtr", + "sygna" + ] + }, + "description": "The network to join through Didit's membership." + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/networks/gtr/request/ \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.networks.request_access(\n \"your-network\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.networks.requestAccess(\"your-network\");\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().networks().requestAccess(\"your-network\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->networks->requestAccess('your-network');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.networks.request_access(\"your-network\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.Networks.RequestAccessAsync(\"your-network\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "Your participant on the network.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleNetworkParticipant" + } + } + } + }, + "400": { + "description": "Unknown network, a network Didit does not carry (connect your own membership), or a sandbox application.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_network_own_membership_only", + "travel_rule_network_sandbox_application" + ] + } + } + } + } + } + }, + "401": { + "description": "Missing or invalid API key." + }, + "403": { + "description": "Network access through Didit's membership is not open yet.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "travel_rule_platform_networks_disabled" + ] + } + } + } + } + } + } + } + } + }, + "/v3/address-validation/": { + "post": { + "summary": "Validate an address", + "description": "Confirm that an address exists and get it back normalised: its delivery lines, components and coordinates. It confirms the address only; it never links the address to a person.\n\n**Request.** Send the address on one line (`address`) or as structured fields (`address_line_1` and, optionally, `address_line_2`, `city`, `region`, `postal_code`), never both, and always its `country` (ISO 3166-1 alpha-2 or alpha-3).\n\n**Result.** `address_validation.status` is `verified` (confirmed to the building), `partially_verified` (confirmed only to the street or above), `not_verified` (only the region or city matched, or nothing did) or `unavailable` (the validation could not run). HTTP is `200` for every completed request, `unavailable` included; retry an `unavailable` result later.\n\n**Billing.** A live call costs $0.10 per validated address. An `unavailable` result is not charged. Nothing is stored apart from the usage record: no session is created and the result is returned only in this response.\n\n**Sandbox.** Sandbox keys validate the request and always return a deterministic `verified` result that echoes the address sent, without consuming credits.\n\n**Authentication.** Send your application's API key in `x-api-key`. Missing or invalid credentials return `403` - this API never returns `401`.\n\n**Rate limit.** Shared write budget of 300 requests per minute per API key across POST/PATCH/DELETE endpoints (transaction creates and event ingestion have a separate budget of 600).", + "operationId": "post_v3_address_validation", + "x-didit-sdk": { + "resource": "address_validation", + "method": "validate", + "auth": "api_key", + "server": true + }, + "tags": [ + "Standalone APIs" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/address-validation/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"address_line_1\": \"1600 Amphitheatre Pkwy\",\n \"city\": \"Mountain View\",\n \"region\": \"CA\",\n \"postal_code\": \"94043\",\n \"country\": \"US\"\n }'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.address_validation.validate(\n address_line_1=\"1600 Amphitheatre Pkwy\",\n city=\"Mountain View\",\n region=\"CA\",\n postal_code=\"94043\",\n country=\"US\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.addressValidation.validate({\n address_line_1: \"1600 Amphitheatre Pkwy\",\n city: \"Mountain View\",\n region: \"CA\",\n postal_code: \"94043\",\n country: \"US\",\n});\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.AddressValidationRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.addressValidation().validate(AddressValidationRequest.builder()\n .addressLine1(\"1600 Amphitheatre Pkwy\")\n .city(\"Mountain View\")\n .region(\"CA\")\n .postalCode(\"94043\")\n .country(\"US\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "addressValidation->validate([\n 'address_line_1' => '1600 Amphitheatre Pkwy',\n 'city' => 'Mountain View',\n 'region' => 'CA',\n 'postal_code' => '94043',\n 'country' => 'US',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.address_validation.validate(\n address_line_1: \"1600 Amphitheatre Pkwy\",\n city: \"Mountain View\",\n region: \"CA\",\n postal_code: \"94043\",\n country: \"US\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.AddressValidation.ValidateAsync(new AddressValidationRequest\n{\n AddressLine1 = \"1600 Amphitheatre Pkwy\",\n City = \"Mountain View\",\n Region = \"CA\",\n PostalCode = \"94043\",\n Country = \"US\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddressValidationRequest" + }, + "examples": { + "Structured": { + "summary": "Structured address fields", + "value": { + "address_line_1": "1600 Amphitheatre Pkwy", + "city": "Mountain View", + "region": "CA", + "postal_code": "94043", + "country": "US" + } + }, + "One line": { + "summary": "The whole address on one line", + "value": { + "address": "Paseo de la Castellana 259, 28046 Madrid", + "country": "ES" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "The validation completed. Read `address_validation.status`; HTTP is always `200` for a completed request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddressValidationResponse" + }, + "examples": { + "Verified": { + "summary": "A real address, confirmed to the building", + "value": { + "request_id": "6f7d2c1e-1c5a-4a1e-9b8f-2a1f0f4b9c3d", + "address_validation": { + "status": "verified", + "verification_level": "premise", + "address": { + "address_line_1": "1600 Amphitheatre Pkwy", + "address_line_2": null, + "city": "Mountain View", + "region": "CA", + "postal_code": "94043-1351", + "country": "US" + }, + "formatted_address": "1600 Amphitheatre Pkwy, Mountain View CA 94043-1351", + "latitude": 37.422022, + "longitude": -122.08429 + }, + "created_at": "2026-10-27T11:20:00.000000+00:00" + } + }, + "Not verified": { + "summary": "An invented street: only the city matched", + "value": { + "request_id": "0b9e3f4a-6c2d-4e8b-a1f7-5d3c2b1a0e9f", + "address_validation": { + "status": "not_verified", + "verification_level": "locality", + "address": { + "address_line_1": "Calle Inventada Que No Existe 99999 28046", + "address_line_2": null, + "city": "Madrid", + "region": "Madrid", + "postal_code": null, + "country": "ES" + }, + "formatted_address": "Calle Inventada Que No Existe 99999 28046, Madrid, MADRID, SPAIN", + "latitude": 40.42603054, + "longitude": -3.68645331 + }, + "created_at": "2026-10-27T11:21:00.000000+00:00" + } + }, + "Unavailable": { + "summary": "The validation could not run; not charged", + "value": { + "request_id": "3c1b2a0f-9e8d-4c7b-b6a5-1f0e9d8c7b6a", + "address_validation": { + "status": "unavailable", + "verification_level": null, + "address": null, + "formatted_address": null, + "latitude": null, + "longitude": null + }, + "created_at": "2026-10-27T11:22:00.000000+00:00" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/AddressValidationValidationError" + }, + "403": { + "$ref": "#/components/responses/AddressValidationPermissionDenied" + }, + "429": { + "$ref": "#/components/responses/RateLimitedWrite" + } + } + } + }, + "/v3/travel-rule/wallet-proofs/": { + "get": { + "summary": "List screenshot wallet proofs to review", + "description": "Your review queue for screenshot wallet-ownership proofs. A screenshot is the one proof Didit cannot check itself: once the end user uploads it from the widget it waits PENDING until you approve or reject it with POST /v3/travel-rule/wallet-proofs/{proof_uuid}/decision/. Oldest first, so the queue is worked from the proof that has waited longest. Only uploaded screenshots appear, never a proof whose end user has not uploaded a file yet, and never one on a wallet you deleted from your address book (Didit closes those as OWNERSHIP_CHANGED).", + "operationId": "listTravelRuleWalletProofs", + "x-didit-sdk": { + "resource": "travel_rule.wallet_proofs", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Which proofs to list. Defaults to PENDING, the ones still to decide.", + "schema": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ], + "default": "PENDING" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "minimum": 1 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0, + "minimum": 0 + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl https://verification.didit.me/v3/travel-rule/wallet-proofs/?status=PENDING \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.travel_rule.wallet_proofs.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.travelRule.walletProofs.list()) {\n console.log(item);\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.travelRule().walletProofs().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletProofs->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.wallet_proofs.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.TravelRule.WalletProofs.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ], + "responses": { + "200": { + "description": "Paginated screenshot proofs. count is exact up to 100 and capped at 100 above it.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleWalletProofReview" + } + } + } + } + } + } + }, + "400": { + "description": "Unknown status." + }, + "401": { + "description": "Missing or invalid x-api-key." + }, + "403": { + "description": "The caller lacks the transactions permission." + } + } + } + }, + "/v3/travel-rule/wallet-proofs/{proof_uuid}/decision/": { + "post": { + "summary": "Approve or reject a screenshot wallet proof", + "description": "Decide a screenshot proof from your review queue. approve verifies the proof and the address book entry and applies the ownership to the transfer the widget session was minted for, exactly as a verified signature does. A screenshot never confirms a transfer that another Didit customer sent over the INTERNAL rail: that takes a signed message or a test deposit. reject closes the proof as REJECTED with failure_reason REVIEW_REJECTED; the end user can start a new proof from the widget. The note is kept with the proof.", + "operationId": "decideTravelRuleWalletProof", + "x-didit-sdk": { + "resource": "travel_rule.wallet_proofs", + "method": "decide", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "proof_uuid", + "in": "path", + "required": true, + "description": "proof_id from the review queue.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWalletProofDecision" + }, + "example": { + "decision": "approve", + "note": "Address matches the wallet app." + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/wallet-proofs/{proof_uuid}/decision/ \\\n -H 'x-api-key: YOUR_API_KEY' \\\n -H 'Content-Type: application/json' \\\n -d '{\"decision\": \"approve\", \"note\": \"Address matches the wallet app.\"}'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.wallet_proofs.decide(\n \"11111111-2222-3333-4444-555555555555\",\n decision=\"approve\",\n note=\"Address matches the wallet app.\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.walletProofs.decide(\"11111111-2222-3333-4444-555555555555\", {\n decision: \"approve\",\n note: \"Address matches the wallet app.\",\n});\nconsole.log(result);" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleWalletProofDecision;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().walletProofs().decide(\"11111111-2222-3333-4444-555555555555\", TravelRuleWalletProofDecision.builder()\n .decision(TravelRuleWalletProofDecision.Decision.of(\"approve\"))\n .note(\"Address matches the wallet app.\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->walletProofs->decide('11111111-2222-3333-4444-555555555555', [\n 'decision' => 'approve',\n 'note' => 'Address matches the wallet app.',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.wallet_proofs.decide(\n \"11111111-2222-3333-4444-555555555555\",\n decision: \"approve\",\n note: \"Address matches the wallet app.\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.WalletProofs.DecideAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleWalletProofDecision\n{\n Decision = \"approve\",\n Note = \"Address matches the wallet app.\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The decided proof.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWalletProofReview" + } + } + } + }, + "400": { + "description": "Invalid decision, or a note longer than 512 characters." + }, + "401": { + "description": "Missing or invalid x-api-key." + }, + "403": { + "description": "The caller lacks the transactions permission." + }, + "404": { + "description": "No submitted screenshot proof with this id in your application." + }, + "409": { + "description": "The proof is already decided (the body carries its status), or code OWNERSHIP_CHANGED: the wallet's holder changed or the wallet was deleted after the widget session was minted, so the proof is closed and the current holder needs a new one.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + } + } + } + }, + "/v3/travel-rule/widget/{token}/proofs/": { + "post": { + "summary": "Start a wallet-ownership proof", + "description": "Public endpoint keyed by the widget session token, no API key required: the hosted widget calls it when the end user picks a proof method. A proof exists, and counts in your proof funnel, only from this call. Picking the same method again returns the proof already open (200, same challenge or deposit amount) instead of starting a second one; picking another method closes the unfinished one as METHOD_SWITCHED, except a submitted screenshot or a test deposit still being checked. MESSAGE_SIGNING returns the challenge to sign; SATOSHI_TEST the deposit address, exact amount and window; SELF_DECLARATION is verified at once (status VERIFIED). Signature formats verified per chain family: EVM chains take a personal_sign (EIP-191) signature, hex encoded; Bitcoin takes a BIP-137 signmessage signature (P2PKH, P2SH-P2WPKH and P2WPKH addresses) or a BIP-322 simple signature (P2WPKH and P2TR addresses), base64 encoded; Solana takes an Ed25519 signature over the challenge, base58 encoded; Tron takes a signMessageV2 (TIP-191) signature, hex encoded. A BIP-322 full or proof-of-funds signature, or a multisig address, fails with SIGNATURE_UNSUPPORTED_SCHEME.", + "operationId": "createTravelRuleWidgetProof", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "create_widget_proof", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofCreate" + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/widget/{token}/proofs/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"method\": \"MESSAGE_SIGNING\"}'" + } + ], + "responses": { + "201": { + "description": "A new PENDING proof (VERIFIED for SELF_DECLARATION).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofSummary" + } + } + } + }, + "200": { + "description": "The proof already open for this method, resumed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofSummary" + } + } + } + }, + "400": { + "description": "Invalid body, a wallet that does not match the transfer, SCREENSHOT not allowed, or code CHAIN_UNSUPPORTED when the method is not available on the chain.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + }, + "403": { + "description": "SELF_DECLARATION is not allowed for this application." + }, + "404": { + "description": "Unknown token." + }, + "409": { + "description": "code OWNERSHIP_CHANGED: the wallet's holder changed or the wallet was deleted after the session was minted; mint a new session for the current holder. Also returned for SATOSHI_TEST when the session has no satoshi_deposit_address, and when the session has no attempts left (no code; the body carries max_attempts, attempts_used and attempts_remaining 0): mint a new session.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + }, + "410": { + "description": "The session expired." + } + } + } + }, + "/v3/travel-rule/widget/{token}/proofs/{proof_id}/verify/": { + "post": { + "summary": "Submit wallet-ownership proof evidence", + "description": "Public endpoint keyed by the widget session token, no API key required. Send the evidence the proof's method needs: signature for MESSAGE_SIGNING, txid for SATOSHI_TEST (JSON), or a file for SCREENSHOT (multipart). Every submission counts as an attempt. A signature that does not verify, or a deposit that does not match, answers 400 with a code and leaves the proof PENDING so the end user can try again. A deposit seen but not confirmed yet answers 200 with status PENDING and is rechecked automatically until the window closes. A screenshot answers 200 with status PENDING and waits in your review queue (GET /v3/travel-rule/wallet-proofs/). An EXPIRED or REJECTED proof never verifies later: start a new one. Signature formats verified per chain family: EVM chains take a personal_sign (EIP-191) signature, hex encoded; Bitcoin takes a BIP-137 signmessage signature (P2PKH, P2SH-P2WPKH and P2WPKH addresses) or a BIP-322 simple signature (P2WPKH and P2TR addresses), base64 encoded; Solana takes an Ed25519 signature over the challenge, base58 encoded; Tron takes a signMessageV2 (TIP-191) signature, hex encoded. A BIP-322 full or proof-of-funds signature, or a multisig address, fails with SIGNATURE_UNSUPPORTED_SCHEME.", + "operationId": "verifyTravelRuleWidgetProof", + "x-didit-sdk": { + "resource": "travel_rule", + "method": "verify_widget_proof", + "auth": "public", + "server": false + }, + "tags": [ + "Travel Rule" + ], + "security": [ + {} + ], + "parameters": [ + { + "name": "token", + "in": "path", + "required": true, + "description": "Opaque session token returned when the widget session was minted.", + "schema": { + "type": "string" + } + }, + { + "name": "proof_id", + "in": "path", + "required": true, + "description": "proof_id returned when the proof was started.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "signature": { + "type": "string", + "description": "MESSAGE_SIGNING: the wallet's signature over the proof's challenge." + }, + "txid": { + "type": "string", + "description": "SATOSHI_TEST: the transaction id of the test deposit." + } + } + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "file" + ], + "properties": { + "file": { + "type": "string", + "format": "binary", + "description": "SCREENSHOT: an image showing the wallet in the end user's wallet app." + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST https://verification.didit.me/v3/travel-rule/widget/{token}/proofs/{proof_id}/verify/ \\\n -H 'Content-Type: application/json' \\\n -d '{\"signature\": \"0x...\"}'" + } + ], + "responses": { + "200": { + "description": "VERIFIED, or still PENDING (a screenshot awaiting review, a deposit not indexed or not confirmed yet, with code DEPOSIT_NOT_FOUND or DEPOSIT_UNCONFIRMED).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofVerifyResult" + } + } + } + }, + "400": { + "description": "Missing evidence; a failed attempt that keeps the proof PENDING so the end user can try again (code SIGNATURE_INVALID, SIGNATURE_UNSUPPORTED_SCHEME or DEPOSIT_LOOKUP_FAILED); or a test deposit that rejects the proof (code DEPOSIT_MISMATCH, DEPOSIT_STALE or TXID_REUSED, status REJECTED, with the session's attempt counts): picking a method again starts a new proof while attempts_remaining is above 0.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + }, + "404": { + "description": "Unknown token, or no such proof on this session." + }, + "409": { + "description": "code OWNERSHIP_CHANGED (the proof is closed with it); a closed proof (REJECTED or EXPIRED), with its status, the code it closed with and the session's attempt counts: pick a method again to start a new proof; or a SELF_DECLARATION proof, which is verified when started.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + }, + "410": { + "description": "The session expired, or code WINDOW_EXPIRED when the satoshi test window closed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleWidgetProofError" + } + } + } + } + } + } + }, + "/v3/id-verification/wallets/verifications/{request_id}/response/": { + "post": { + "operationId": "submit_wallet_verification_response", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "submit_verification_response", + "auth": "api_key", + "server": true + }, + "description": "Digital Credentials API wallets (Google Wallet): send what the wallet returned to your page or app for a verification whose next_action is request_digital_credential. Didit decrypts it, checks the issuer's signature against the trusted issuer roots, every returned value against its signed digest, and the device signature over your origin and this request, then decides the verification: verified (billed once) or declined with the error code. Single use: a second submit returns the decided verification.", + "summary": "Submit a wallet response", + "parameters": [ + { + "in": "path", + "name": "request_id", + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The request_id the start endpoint returned.", + "required": true + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletDigitalCredentialResponseRequest" + }, + "example": { + "response": { + "protocol": "openid4vp-v1-signed", + "data": { + "response": "" + } + } + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletDigitalCredentialResponseRequest" + }, + "example": { + "response": { + "protocol": "openid4vp-v1-signed", + "data": { + "response": "" + } + } + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletDigitalCredentialResponseRequest" + }, + "example": { + "response": { + "protocol": "openid4vp-v1-signed", + "data": { + "response": "" + } + } + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "google_wallet", + "name": "Google Wallet", + "issuing_authority": "Google Wallet (ID pass) and US state issuers (mobile driving licence)" + }, + "country": "USA", + "status": "Approved", + "outcome": "verified", + "error": null, + "interaction": "digital_credentials", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": null, + "identity": { + "full_name": "Alex Example", + "first_name": "Alex", + "last_name": "Example", + "date_of_birth": "1990-01-31", + "nationality": "USA", + "issuing_state": "USA" + }, + "attributes": { + "document_number": "X0000000", + "document_expiry_date": "2030-01-31" + }, + "level_of_assurance": "substantial", + "verified_at": "2026-01-01T10:01:00Z", + "signature_valid": true, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + } + } + }, + "description": "Missing credentials (x-api-key or Authorization: Bearer)." + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error not_found: no wallet verification of this application has this request_id." + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]})." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error not_a_digital_credentials_wallet: this verification does not take a wallet response." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot verify the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/verifications/REQUEST_ID/response/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"response\": {\n \"protocol\": \"openid4vp-v1-signed\",\n \"data\": {\n \"response\": \"\"\n }\n }\n}'" + } + ] + } + }, + "/v3/id-verification/wallets/google-wallet/": { + "post": { + "operationId": "start_google_wallet_wallet_verification", + "x-didit-sdk": { + "resource": "id_wallets", + "method": "start_google_wallet", + "auth": "api_key", + "server": true + }, + "description": "Start a Google Wallet sign-in (Google Wallet (ID pass) and US state issuers (mobile driving licence); USA, GBR, BRA, SGP, TWN) without any workflow. Billed once, only when the identity is verified. The response carries next_action.type=request_digital_credential with a request Didit signed for your application, usable only from origin. In a user gesture there, pass next_action.digital_credentials.request to navigator.credentials.get({digital: ...}) (or to Android Credential Manager as GetDigitalCredentialOption requestJson), then POST what the wallet returns to /v3/id-verification/wallets/verifications/{request_id}/response/. The browser offers its own QR code when the wallet is on another device. Sandbox applications get a deterministic simulation (simulated: true) that never contacts the provider.", + "summary": "Start Google Wallet verification", + "parameters": [ + { + "in": "header", + "name": "Idempotency-Key", + "schema": { + "type": "string" + }, + "description": "Optional retry-safety key (max 255 characters). Retrying with the same key, wallet and body returns the original verification without starting a second sign-in; the same key with another wallet or body returns 409 idempotency_key_reused." + } + ], + "tags": [ + "Digital ID wallets" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGoogleWalletWalletVerificationStartRequest" + }, + "example": { + "country": "USA", + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "origin": "https://app.example.com" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGoogleWalletWalletVerificationStartRequest" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiGoogleWalletWalletVerificationStartRequest" + } + } + }, + "required": true + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "BearerAuth": [] + } + ], + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResponse" + }, + "example": { + "request_id": "00000000-0000-4000-8000-000000000001", + "wallet_verification": { + "wallet": { + "id": "google_wallet", + "name": "Google Wallet", + "issuing_authority": "Google Wallet (ID pass) and US state issuers (mobile driving licence)" + }, + "country": "USA", + "status": "Not Finished", + "outcome": "pending", + "error": null, + "interaction": "digital_credentials", + "simulated": false, + "expires_at": "2026-01-01T10:10:00Z", + "next_action": { + "type": "request_digital_credential", + "digital_credentials": { + "protocol": "openid4vp-v1-signed", + "request": { + "requests": [ + { + "protocol": "openid4vp-v1-signed", + "data": { + "request": "" + } + } + ] + } + } + }, + "identity": null, + "attributes": null, + "level_of_assurance": null, + "verified_at": null, + "signature_valid": null, + "credential_type": "person_identification" + }, + "vendor_data": "your-customer-reference", + "metadata": { + "reference": "example" + }, + "created_at": "2026-01-01T10:00:00Z" + } + } + }, + "description": "" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartBadRequest" + } + } + }, + "description": "Field validation errors ({field: [messages]}), or error return_url_invalid, return_url_not_allowed, origin_invalid, origin_not_allowed, invalid_personal_code, invalid_phone_number or idempotency_key_invalid." + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationStartForbidden" + } + } + }, + "description": "Missing credentials ({detail}), or error wallet_unavailable, insufficient_credits or contract_rate_missing." + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error idempotency_key_reused or idempotency_key_in_progress (with Retry-After)." + }, + "502": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_provider_unavailable: the provider could not start; nothing was created." + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + } + }, + "description": "error wallet_not_configured: this environment cannot reach the wallet." + } + }, + "x-codeSamples": [ + { + "lang": "curl", + "label": "cURL", + "source": "curl --request POST \\\n --url https://verification.didit.me/v3/id-verification/wallets/google-wallet/ \\\n --header 'x-api-key: YOUR_API_KEY' \\\n --header 'Idempotency-Key: your-unique-attempt-key' \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"country\": \"USA\",\n \"vendor_data\": \"your-customer-reference\",\n \"metadata\": {\n \"reference\": \"example\"\n },\n \"origin\": \"https://app.example.com\"\n}'" + } + ], + "x-didit-wallet-id": "google_wallet" + } + }, + "/v3/session/{sessionId}/kyb/documents/": { + "post": { + "summary": "Upload KYB documents to a session", + "description": "Upload one or more corporate documents (articles of incorporation, shareholder register, certificate of incorporation, ...) to the KYB Documents step of a Business Verification session, from your own server instead of the hosted page. Didit runs the hosted pipeline: compression, OCR, tampering and cross-document checks, document grouping, and advances the session when the required document groups are satisfied. The step costs one flat fee however many files you send. The session must be open and on the KYB Documents step, and the company chosen at the registry step must exist. Read the people the documents name with `GET /v3/session/{sessionId}/kyb/extracted-parties/`. Requires `write:businesses`.", + "operationId": "post_v3_session_kyb_documents", + "x-didit-sdk": { + "resource": "sessions", + "method": "upload_kyb_documents", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "required": [ + "documents" + ], + "properties": { + "documents": { + "type": "array", + "items": { + "type": "string", + "format": "binary" + }, + "description": "Files to upload: JPG, JPEG, PNG, TIFF, WEBP, PDF or ZIP archives of them. PDFs up to 50 MB, photos up to 6 MB before compression." + }, + "document_passwords": { + "type": "array", + "items": { + "type": "string" + }, + "description": "One entry per file in `documents`, in the same order: the password of a protected PDF, empty for any other file." + }, + "document_group": { + "type": "string", + "enum": [ + "LEGAL_PRESENCE", + "COMPANY_DETAILS", + "OWNERSHIP_STRUCTURE", + "REPRESENTATIVES_AUTHORIZATION" + ], + "description": "Required document group this upload answers." + }, + "locale": { + "type": "string", + "maxLength": 10, + "description": "Locale used to translate the response of the next step." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Documents processed. When every required group is satisfied the session advances and `next_step` names the next step; otherwise it stays on the Documents step and `document_groups` and `document_answer` say what is still missing.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "valid": { + "type": "boolean" + }, + "next_step": { + "type": "string", + "nullable": true, + "description": "Step the session is on after the upload." + }, + "kyb_status": { + "type": "string", + "description": "Status of the company check." + }, + "document_answer": { + "type": "object", + "additionalProperties": true, + "description": "Result per uploaded document and the uploads left." + } + }, + "additionalProperties": true + }, + "example": { + "valid": true, + "next_step": "KYB_KEY_PEOPLE", + "kyb_status": "In Progress" + } + } + } + }, + "400": { + "description": "A document was rejected (tampering, unreadable or unsupported) or the session has no KYB company yet. Retry with another file while attempts remain.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "message": "No KYB company found. Submit company data first." + } + } + } + }, + "402": { + "description": "The organization has no credit left for the documents step.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Insufficient credits." + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted, belongs to another application or is outside a scoped key's limits; no detail distinguishes them.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + }, + "409": { + "description": "The session is finished (`kyb_session_not_writable`) or is not on the KYB Documents step (`kyb_step_mismatch`, with `next_step` naming the step it is on).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string" + }, + "next_step": { + "type": "string" + } + } + }, + "example": { + "detail": "The session is not on the step this operation completes.", + "code": "kyb_step_mismatch", + "next_step": "KYB_REGISTRY" + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.upload_kyb_documents(\n \"33333333-4444-5555-6666-777777777777\",\n documents=[open(\"document.pdf\", \"rb\")],\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit, { fileFromPath } from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.uploadKybDocuments(\"33333333-4444-5555-6666-777777777777\", {\n documents: [await fileFromPath(\"document.pdf\")],\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UploadKYBDocuments(ctx, \"33333333-4444-5555-6666-777777777777\", didit.SessionsUploadKYBDocumentsParams{\n\tDocuments: []didit.File{didit.FileFromPath(\"document.pdf\")},\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.nio.file.Path;\nimport java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.SessionsUploadKybDocumentsParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().uploadKybDocuments(\"33333333-4444-5555-6666-777777777777\", SessionsUploadKybDocumentsParams.builder()\n .documents(List.of(Path.of(\"document.pdf\")))\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->uploadKybDocuments('33333333-4444-5555-6666-777777777777', [\n 'documents' => [File::fromPath('document.pdf')],\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.upload_kyb_documents(\n \"33333333-4444-5555-6666-777777777777\",\n documents: [Pathname(\"document.pdf\")]\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UploadKybDocumentsAsync(\"33333333-4444-5555-6666-777777777777\", new SessionsUploadKybDocumentsParams\n{\n Documents = new FileUpload[] { FileUpload.FromPath(\"document.pdf\") },\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/extracted-parties/": { + "get": { + "summary": "List the parties named by the uploaded documents", + "description": "Return the shareholders and directors that the documents uploaded to this KYB session name, merged per person and attributed to the documents that named them. Show them to your applicant to confirm, then declare them with the party draft endpoints. A range stays a range: `ownership_percent.text` keeps the verbatim figure and `min` / `max` the numbers read from it. Nobody is labelled a UBO here; that is decided when the parties are submitted, from the role settings and ownership thresholds of the workflow. The same list is in the session decision as `document_verifications[].extracted_parties`. Requires `read:businesses` or `read:sessions`.", + "operationId": "get_v3_session_kyb_extracted_parties", + "x-didit-sdk": { + "resource": "sessions", + "method": "list_kyb_extracted_parties", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Parties named by the current documents.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "parties", + "document_count" + ], + "properties": { + "parties": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ] + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "appointment_date": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "roles": { + "type": "array", + "items": { + "type": "object", + "properties": { + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "ownership_percent": { + "type": "object", + "properties": { + "text": { + "type": "string", + "nullable": true, + "description": "The ownership figure exactly as the document states it, such as `25-50%`, `>50%` or `NO EXHIBE`." + }, + "min": { + "type": "number", + "nullable": true, + "description": "Lower number read out of `text`; null when it states none." + }, + "max": { + "type": "number", + "nullable": true, + "description": "Upper number read out of `text`; null when it states none." + } + } + } + } + } + }, + "documents": { + "type": "array", + "description": "The uploaded documents that named this party.", + "items": { + "type": "object", + "properties": { + "document_uuid": { + "type": "string", + "format": "uuid" + }, + "document_type": { + "type": "string", + "nullable": true + }, + "document_subtype": { + "type": "string", + "nullable": true + }, + "original_filename": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "description": "The shareholders and directors the uploaded documents name, merged per person. Nobody is labelled a UBO here: that is decided when the parties are submitted." + }, + "document_count": { + "type": "integer", + "description": "Number of uploaded documents the list was read from." + }, + "node_id": { + "type": "string", + "nullable": true + } + } + }, + "example": { + "parties": [ + { + "name": "Ana Lopez", + "entity_type": "person", + "first_name": "Ana", + "last_name": "Lopez", + "appointment_date": null, + "designation": "Administrador unico", + "roles": [ + { + "role": "shareholder", + "ownership_percent": { + "text": "60%", + "min": 60, + "max": 60 + } + } + ], + "documents": [ + { + "document_uuid": "0a3f1e2c-7c2f-4a1b-9d55-2f8d2a5d6b41", + "document_type": "OWNERSHIP_STRUCTURE", + "document_subtype": "ARTICLES_OF_INCORPORATION", + "original_filename": "acta_constitutiva.pdf", + "status": "Approved" + } + ] + } + ], + "document_count": 1, + "node_id": "node_kyb_documents" + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted, belongs to another application or is outside a scoped key's limits; no detail distinguishes them.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.list_kyb_extracted_parties(\n \"33333333-4444-5555-6666-777777777777\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.listKybExtractedParties(\"33333333-4444-5555-6666-777777777777\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.ListKYBExtractedParties(ctx, \"33333333-4444-5555-6666-777777777777\", didit.SessionsListKYBExtractedPartiesParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().listKybExtractedParties(\"33333333-4444-5555-6666-777777777777\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->listKybExtractedParties('33333333-4444-5555-6666-777777777777');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.list_kyb_extracted_parties(\"33333333-4444-5555-6666-777777777777\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ListKybExtractedPartiesAsync(\"33333333-4444-5555-6666-777777777777\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/parties/drafts/": { + "get": { + "summary": "List the KYB party drafts", + "description": "List the parties you are preparing for this KYB session, oldest first. A draft is not a declared party: it does not appear in the decision or the console and creates no verification session until you submit it with `POST /v3/session/{sessionId}/kyb/parties/submit/`. Requires `write:businesses`.", + "operationId": "get_v3_session_kyb_party_drafts", + "x-didit-sdk": { + "resource": "sessions", + "method": "list_kyb_party_drafts", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The stored drafts.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "parties": { + "type": "array", + "items": { + "type": "object", + "required": [ + "draft_id", + "created_at", + "updated_at", + "party" + ], + "properties": { + "draft_id": { + "type": "string", + "format": "uuid" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "party": { + "type": "object", + "required": [ + "entity_type", + "nationality" + ], + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "full_name": { + "type": "string", + "maxLength": 255, + "description": "Full name of a person. Send it, or `first_name` and `last_name`." + }, + "first_name": { + "type": "string", + "maxLength": 128 + }, + "last_name": { + "type": "string", + "maxLength": 128 + }, + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Role the party holds. Interchangeable with `roles`; when both are sent they are merged." + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "description": "Every role the party holds. A person needs at least one role (here or in `role`)." + }, + "position": { + "type": "string", + "maxLength": 128, + "description": "Job title of a person." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Name of a company party. Required when `entity_type` is `company`." + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Registration number of a company party." + }, + "email": { + "type": "string", + "format": "email", + "description": "Where the verification link is sent. Required when the party has to complete verification, which depends on the role settings and ownership thresholds of the workflow." + }, + "ownership_percent": { + "type": "number", + "nullable": true, + "description": "Share of the company the party owns." + }, + "nationality": { + "type": "string", + "maxLength": 100, + "description": "Nationality of a person, or country of incorporation of a company. Required for every party." + }, + "phone_number": { + "type": "string", + "maxLength": 32 + }, + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined extra fields." + }, + "skip_verification": { + "type": "boolean", + "default": false, + "description": "Ask to skip this party's verification. Honoured only when the party's role allows skipping in the workflow's Key People settings." + } + } + } + } + } + }, + "node_id": { + "type": "string", + "nullable": true + } + } + }, + "example": { + "parties": [ + { + "draft_id": "5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d", + "created_at": "2026-10-31T10:00:00Z", + "updated_at": "2026-10-31T10:00:00Z", + "party": { + "entity_type": "person", + "first_name": "Ana", + "last_name": "Lopez", + "roles": [ + "ubo", + "director" + ], + "ownership_percent": 60, + "nationality": "MEX", + "email": "ana.lopez@example.com", + "date_of_birth": "1984-06-12" + } + } + ], + "node_id": "node_kyb_key_people" + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted, belongs to another application or is outside a scoped key's limits; no detail distinguishes them.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.list_kyb_party_drafts(\n \"33333333-4444-5555-6666-777777777777\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.listKybPartyDrafts(\"33333333-4444-5555-6666-777777777777\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.ListKYBPartyDrafts(ctx, \"33333333-4444-5555-6666-777777777777\", didit.SessionsListKYBPartyDraftsParams{})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().listKybPartyDrafts(\"33333333-4444-5555-6666-777777777777\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->listKybPartyDrafts('33333333-4444-5555-6666-777777777777');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.list_kyb_party_drafts(\"33333333-4444-5555-6666-777777777777\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ListKybPartyDraftsAsync(\"33333333-4444-5555-6666-777777777777\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "post": { + "summary": "Create a KYB party draft", + "description": "Add a party (a person or a company) to the list you are preparing for this KYB session. The same fields and rules as the hosted Key People screen apply: a person needs `full_name` or `first_name` and `last_name`, at least one role and a nationality; a company needs `company_name` and the country of incorporation in `nationality`. Nothing is declared or billed until `POST /v3/session/{sessionId}/kyb/parties/submit/`. Requires `write:businesses`.", + "operationId": "post_v3_session_kyb_party_drafts", + "x-didit-sdk": { + "resource": "sessions", + "method": "create_kyb_party_draft", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "entity_type", + "nationality" + ], + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "full_name": { + "type": "string", + "maxLength": 255, + "description": "Full name of a person. Send it, or `first_name` and `last_name`." + }, + "first_name": { + "type": "string", + "maxLength": 128 + }, + "last_name": { + "type": "string", + "maxLength": 128 + }, + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Role the party holds. Interchangeable with `roles`; when both are sent they are merged." + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "description": "Every role the party holds. A person needs at least one role (here or in `role`)." + }, + "position": { + "type": "string", + "maxLength": 128, + "description": "Job title of a person." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Name of a company party. Required when `entity_type` is `company`." + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Registration number of a company party." + }, + "email": { + "type": "string", + "format": "email", + "description": "Where the verification link is sent. Required when the party has to complete verification, which depends on the role settings and ownership thresholds of the workflow." + }, + "ownership_percent": { + "type": "number", + "nullable": true, + "description": "Share of the company the party owns." + }, + "nationality": { + "type": "string", + "maxLength": 100, + "description": "Nationality of a person, or country of incorporation of a company. Required for every party." + }, + "phone_number": { + "type": "string", + "maxLength": 32 + }, + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined extra fields." + }, + "skip_verification": { + "type": "boolean", + "default": false, + "description": "Ask to skip this party's verification. Honoured only when the party's role allows skipping in the workflow's Key People settings." + } + } + }, + "example": { + "entity_type": "person", + "first_name": "Ana", + "last_name": "Lopez", + "roles": [ + "ubo", + "director" + ], + "ownership_percent": 60, + "nationality": "MEX", + "email": "ana.lopez@example.com", + "date_of_birth": "1984-06-12" + } + } + } + }, + "responses": { + "201": { + "description": "Draft created.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "draft_id", + "created_at", + "updated_at", + "party" + ], + "properties": { + "draft_id": { + "type": "string", + "format": "uuid" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "party": { + "type": "object", + "required": [ + "entity_type", + "nationality" + ], + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "full_name": { + "type": "string", + "maxLength": 255, + "description": "Full name of a person. Send it, or `first_name` and `last_name`." + }, + "first_name": { + "type": "string", + "maxLength": 128 + }, + "last_name": { + "type": "string", + "maxLength": 128 + }, + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Role the party holds. Interchangeable with `roles`; when both are sent they are merged." + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "description": "Every role the party holds. A person needs at least one role (here or in `role`)." + }, + "position": { + "type": "string", + "maxLength": 128, + "description": "Job title of a person." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Name of a company party. Required when `entity_type` is `company`." + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Registration number of a company party." + }, + "email": { + "type": "string", + "format": "email", + "description": "Where the verification link is sent. Required when the party has to complete verification, which depends on the role settings and ownership thresholds of the workflow." + }, + "ownership_percent": { + "type": "number", + "nullable": true, + "description": "Share of the company the party owns." + }, + "nationality": { + "type": "string", + "maxLength": 100, + "description": "Nationality of a person, or country of incorporation of a company. Required for every party." + }, + "phone_number": { + "type": "string", + "maxLength": 32 + }, + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined extra fields." + }, + "skip_verification": { + "type": "boolean", + "default": false, + "description": "Ask to skip this party's verification. Honoured only when the party's role allows skipping in the workflow's Key People settings." + } + } + } + } + }, + "example": { + "draft_id": "5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d", + "created_at": "2026-10-31T10:00:00Z", + "updated_at": "2026-10-31T10:00:00Z", + "party": { + "entity_type": "person", + "first_name": "Ana", + "last_name": "Lopez", + "roles": [ + "ubo", + "director" + ], + "ownership_percent": 60, + "nationality": "MEX", + "email": "ana.lopez@example.com", + "date_of_birth": "1984-06-12" + } + } + } + } + }, + "400": { + "description": "A party field is invalid or a required one is missing.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "non_field_errors": [ + "Person parties require at least one role." + ] + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted, belongs to another application or is outside a scoped key's limits; no detail distinguishes them.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + }, + "409": { + "description": "The session is finished (`kyb_session_not_writable`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "This verification session is no longer accepting changes.", + "code": "kyb_session_not_writable" + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.create_kyb_party_draft(\n \"33333333-4444-5555-6666-777777777777\",\n entity_type=\"person\",\n first_name=\"Ana\",\n last_name=\"Lopez\",\n roles=[\"ubo\", \"director\"],\n date_of_birth=\"1984-06-12\",\n email=\"ana.lopez@example.com\",\n ownership_percent=60.0,\n nationality=\"MEX\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.createKybPartyDraft(\"33333333-4444-5555-6666-777777777777\", {\n entity_type: \"person\",\n first_name: \"Ana\",\n last_name: \"Lopez\",\n roles: [\"ubo\", \"director\"],\n date_of_birth: \"1984-06-12\",\n email: \"ana.lopez@example.com\",\n ownership_percent: 60.0,\n nationality: \"MEX\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.CreateKYBPartyDraft(ctx, \"33333333-4444-5555-6666-777777777777\", didit.SessionsCreateKYBPartyDraftParams{\n\tEntityType: \"person\",\n\tFirstName: didit.String(\"Ana\"),\n\tLastName: didit.String(\"Lopez\"),\n\tRoles: didit.Some([]didit.SessionsCreateKYBPartyDraftParamsRoles{\"ubo\", \"director\"}),\n\tDateOfBirth: didit.String(\"1984-06-12\"),\n\tEmail: didit.String(\"ana.lopez@example.com\"),\n\tOwnershipPercent: didit.Float(60.0),\n\tNationality: \"MEX\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import java.time.LocalDate;\nimport java.util.List;\nimport me.didit.Didit;\nimport me.didit.types.SessionsCreateKybPartyDraftParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().createKybPartyDraft(\"33333333-4444-5555-6666-777777777777\", SessionsCreateKybPartyDraftParams.builder()\n .entityType(SessionsCreateKybPartyDraftParams.EntityType.of(\"person\"))\n .firstName(\"Ana\")\n .lastName(\"Lopez\")\n .roles(List.of(SessionsCreateKybPartyDraftParams.Roles.of(\"ubo\"), SessionsCreateKybPartyDraftParams.Roles.of(\"director\")))\n .dateOfBirth(LocalDate.parse(\"1984-06-12\"))\n .email(\"ana.lopez@example.com\")\n .ownershipPercent(60.0)\n .nationality(\"MEX\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->createKybPartyDraft('33333333-4444-5555-6666-777777777777', [\n 'entity_type' => 'person',\n 'first_name' => 'Ana',\n 'last_name' => 'Lopez',\n 'roles' => ['ubo', 'director'],\n 'date_of_birth' => '1984-06-12',\n 'email' => 'ana.lopez@example.com',\n 'ownership_percent' => 60.0,\n 'nationality' => 'MEX',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.create_kyb_party_draft(\n \"33333333-4444-5555-6666-777777777777\",\n entity_type: \"person\",\n first_name: \"Ana\",\n last_name: \"Lopez\",\n roles: [\"ubo\", \"director\"],\n date_of_birth: \"1984-06-12\",\n email: \"ana.lopez@example.com\",\n ownership_percent: 60.0,\n nationality: \"MEX\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.CreateKybPartyDraftAsync(\"33333333-4444-5555-6666-777777777777\", new SessionsCreateKybPartyDraftParams\n{\n EntityType = \"person\",\n FirstName = \"Ana\",\n LastName = \"Lopez\",\n Roles = new SessionsCreateKybPartyDraftParamsRoles[] { \"ubo\", \"director\" },\n DateOfBirth = \"1984-06-12\",\n Email = \"ana.lopez@example.com\",\n OwnershipPercent = 60.0,\n Nationality = \"MEX\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/parties/drafts/{draftId}/": { + "patch": { + "summary": "Edit a KYB party draft", + "description": "Change fields of one party draft. The edit is merged into the stored draft and the merged party is validated as a whole, so an edit that would leave the party incomplete is rejected. Changing `first_name` or `last_name` replaces the stored full name. Requires `write:businesses`.", + "operationId": "patch_v3_session_kyb_party_draft", + "x-didit-sdk": { + "resource": "sessions", + "method": "update_kyb_party_draft", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "draftId", + "in": "path", + "required": true, + "description": "UUID of the party draft (`draft_id` returned when it was created).", + "schema": { + "type": "string", + "format": "uuid", + "example": "5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Any subset of the party fields. The edit is merged into the stored draft and the merged party is validated as a whole, so a draft that could be submitted before an edit can still be submitted after it.", + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "full_name": { + "type": "string", + "maxLength": 255, + "description": "Full name of a person. Send it, or `first_name` and `last_name`." + }, + "first_name": { + "type": "string", + "maxLength": 128 + }, + "last_name": { + "type": "string", + "maxLength": 128 + }, + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Role the party holds. Interchangeable with `roles`; when both are sent they are merged." + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "description": "Every role the party holds. A person needs at least one role (here or in `role`)." + }, + "position": { + "type": "string", + "maxLength": 128, + "description": "Job title of a person." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Name of a company party. Required when `entity_type` is `company`." + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Registration number of a company party." + }, + "email": { + "type": "string", + "format": "email", + "description": "Where the verification link is sent. Required when the party has to complete verification, which depends on the role settings and ownership thresholds of the workflow." + }, + "ownership_percent": { + "type": "number", + "nullable": true, + "description": "Share of the company the party owns." + }, + "nationality": { + "type": "string", + "maxLength": 100, + "description": "Nationality of a person, or country of incorporation of a company. Required for every party." + }, + "phone_number": { + "type": "string", + "maxLength": 32 + }, + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined extra fields." + }, + "skip_verification": { + "type": "boolean", + "default": false, + "description": "Ask to skip this party's verification. Honoured only when the party's role allows skipping in the workflow's Key People settings." + } + } + }, + "example": { + "ownership_percent": 55 + } + } + } + }, + "responses": { + "200": { + "description": "Draft updated.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "draft_id", + "created_at", + "updated_at", + "party" + ], + "properties": { + "draft_id": { + "type": "string", + "format": "uuid" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "party": { + "type": "object", + "required": [ + "entity_type", + "nationality" + ], + "properties": { + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "full_name": { + "type": "string", + "maxLength": 255, + "description": "Full name of a person. Send it, or `first_name` and `last_name`." + }, + "first_name": { + "type": "string", + "maxLength": 128 + }, + "last_name": { + "type": "string", + "maxLength": 128 + }, + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Role the party holds. Interchangeable with `roles`; when both are sent they are merged." + }, + "roles": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "description": "Every role the party holds. A person needs at least one role (here or in `role`)." + }, + "position": { + "type": "string", + "maxLength": 128, + "description": "Job title of a person." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "company_name": { + "type": "string", + "maxLength": 255, + "description": "Name of a company party. Required when `entity_type` is `company`." + }, + "registration_number": { + "type": "string", + "maxLength": 100, + "description": "Registration number of a company party." + }, + "email": { + "type": "string", + "format": "email", + "description": "Where the verification link is sent. Required when the party has to complete verification, which depends on the role settings and ownership thresholds of the workflow." + }, + "ownership_percent": { + "type": "number", + "nullable": true, + "description": "Share of the company the party owns." + }, + "nationality": { + "type": "string", + "maxLength": 100, + "description": "Nationality of a person, or country of incorporation of a company. Required for every party." + }, + "phone_number": { + "type": "string", + "maxLength": 32 + }, + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined extra fields." + }, + "skip_verification": { + "type": "boolean", + "default": false, + "description": "Ask to skip this party's verification. Honoured only when the party's role allows skipping in the workflow's Key People settings." + } + } + } + } + }, + "example": { + "draft_id": "5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d", + "created_at": "2026-10-31T10:00:00Z", + "updated_at": "2026-10-31T10:00:00Z", + "party": { + "entity_type": "person", + "first_name": "Ana", + "last_name": "Lopez", + "roles": [ + "ubo", + "director" + ], + "ownership_percent": 55, + "nationality": "MEX", + "email": "ana.lopez@example.com", + "date_of_birth": "1984-06-12" + } + } + } + } + }, + "400": { + "description": "The merged party is invalid.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "non_field_errors": [ + "Person parties require at least one role." + ] + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session or the draft does not exist, or the session is outside the key's application or a scoped key's limits.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + }, + "409": { + "description": "The session is finished (`kyb_session_not_writable`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "This verification session is no longer accepting changes.", + "code": "kyb_session_not_writable" + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.update_kyb_party_draft(\n \"33333333-4444-5555-6666-777777777777\",\n \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\",\n ownership_percent=55.0,\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.updateKybPartyDraft(\n \"33333333-4444-5555-6666-777777777777\",\n \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\",\n {\n ownership_percent: 55.0,\n },\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.UpdateKYBPartyDraft(ctx, \"33333333-4444-5555-6666-777777777777\", \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\", didit.SessionsUpdateKYBPartyDraftParams{\n\tOwnershipPercent: didit.Float(55.0),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsUpdateKybPartyDraftParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().updateKybPartyDraft(\"33333333-4444-5555-6666-777777777777\", \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\", SessionsUpdateKybPartyDraftParams.builder()\n .ownershipPercent(55.0)\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->updateKybPartyDraft('33333333-4444-5555-6666-777777777777', '5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d', [\n 'ownership_percent' => 55.0,\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.update_kyb_party_draft(\n \"33333333-4444-5555-6666-777777777777\",\n \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\",\n ownership_percent: 55.0\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.UpdateKybPartyDraftAsync(\"33333333-4444-5555-6666-777777777777\", \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\", new SessionsUpdateKybPartyDraftParams\n{\n OwnershipPercent = 55.0,\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + }, + "delete": { + "summary": "Delete a KYB party draft", + "description": "Drop one party draft. Declared parties are not affected. Requires `write:businesses`.", + "operationId": "delete_v3_session_kyb_party_draft", + "x-didit-sdk": { + "resource": "sessions", + "method": "delete_kyb_party_draft", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "draftId", + "in": "path", + "required": true, + "description": "UUID of the party draft (`draft_id` returned when it was created).", + "schema": { + "type": "string", + "format": "uuid", + "example": "5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Draft deleted. No body." + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session or the draft does not exist, or the session is outside the key's application or a scoped key's limits.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + }, + "409": { + "description": "The session is finished (`kyb_session_not_writable`).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string" + } + } + }, + "example": { + "detail": "This verification session is no longer accepting changes.", + "code": "kyb_session_not_writable" + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nclient.sessions.delete_kyb_party_draft(\n \"33333333-4444-5555-6666-777777777777\",\n \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\",\n)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nawait didit.sessions.deleteKybPartyDraft(\n \"33333333-4444-5555-6666-777777777777\",\n \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\",\n);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nif err := client.Sessions.DeleteKYBPartyDraft(ctx, \"33333333-4444-5555-6666-777777777777\", \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\", didit.SessionsDeleteKYBPartyDraftParams{}); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nclient.sessions().deleteKybPartyDraft(\"33333333-4444-5555-6666-777777777777\", \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\");" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->deleteKybPartyDraft('33333333-4444-5555-6666-777777777777', '5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d');" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.sessions.delete_kyb_party_draft(\n \"33333333-4444-5555-6666-777777777777\",\n \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\"\n)" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait client.Sessions.DeleteKybPartyDraftAsync(\"33333333-4444-5555-6666-777777777777\", \"5b0d7a52-9d3e-4f0c-8c6a-0e5f3a1b2c4d\");" + } + ] + } + }, + "/v3/session/{sessionId}/kyb/parties/submit/": { + "post": { + "summary": "Submit the KYB party drafts", + "description": "Declare the stored drafts as the company's key people. This is the hosted Key People step under your API key: Didit decides from the role settings and ownership thresholds of the workflow which parties must be verified, creates one individual KYC session (or a KYB sub-session for a corporate owner) for each of them, sends the emails the workflow asks for, and advances the session. Each child session is billed like in the hosted flow. The drafts are consumed. Repeating the call after a successful submit changes nothing and answers `200` with `already_submitted: true` and the parties that already exist. The session must be on the Key People step. Send each person's `kyc_session_url` to them, or embed it. Requires `write:businesses`.", + "operationId": "post_v3_session_kyb_parties_submit", + "x-didit-sdk": { + "resource": "sessions", + "method": "submit_kyb_parties", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the Business Verification (KYB) session. It must belong to the application of the API key.", + "schema": { + "type": "string", + "format": "uuid", + "example": "33333333-4444-5555-6666-777777777777" + } + }, + { + "name": "node_id", + "in": "query", + "required": false, + "description": "Workflow node of the KYB step. Only needed when the workflow has more than one KYB node; by default Didit uses the session's own node.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "locale": { + "type": "string", + "maxLength": 10, + "description": "Locale used to translate the response of the step the session advances to." + } + } + }, + "example": { + "locale": "es" + } + } + } + }, + "responses": { + "200": { + "description": "Parties declared (or already declared).", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "valid": { + "type": "boolean" + }, + "next_step": { + "type": "string", + "nullable": true, + "description": "Step the session is on." + }, + "status": { + "type": "string", + "description": "Status of the business session." + }, + "advanced": { + "type": "boolean", + "description": "`true` when this call declared the parties and advanced the session." + }, + "already_submitted": { + "type": "boolean", + "description": "`true` when the parties had been declared by an earlier call." + }, + "kyb_key_people_status": { + "type": "string" + }, + "parties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/KYBKeyPeopleParty" + } + } + } + }, + "example": { + "valid": true, + "next_step": "FINISHED", + "status": "In Progress", + "advanced": true, + "already_submitted": false, + "kyb_key_people_status": "In Progress", + "parties": [] + } + } + } + }, + "402": { + "description": "The organization has no credit left for the child verification sessions.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Insufficient credits." + } + } + } + }, + "401": { + "description": "The scoped API key has expired. This is the only case that returns `401`: a missing, invalid or revoked key returns `403`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Expired key": { + "summary": "The key's expiry date has passed", + "value": { + "detail": "This API key has expired." + } + } + } + } + } + }, + "403": { + "description": "Authentication or authorization failure. A missing key returns `403`, and so do a key of another application, a scoped key without the permission this endpoint needs, and a request from outside the key's IP allowlist; a wrong, revoked or expired key returns `401`. Distinguish them by `detail`.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Missing or invalid credentials": { + "summary": "No x-api-key header, or the key failed validation", + "value": { + "detail": "Authentication credentials were not provided or are invalid." + } + }, + "Other application": { + "summary": "A valid key that belongs to a different application than the session", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Scoped key without decisions write": { + "summary": "Scoped key lacks decisions: write", + "value": { + "detail": "This API key has no write access to decisions." + } + }, + "IP not allowed": { + "summary": "Request from outside the key's IP allowlist", + "value": { + "detail": "This API key can't be used from this IP address." + } + } + } + } + } + }, + "404": { + "description": "The session does not exist, was deleted, belongs to another application or is outside a scoped key's limits; no detail distinguishes them.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "example": { + "detail": "Session not found." + } + } + } + }, + "409": { + "description": "`kyb_no_party_drafts`: there is nothing to submit. `kyb_parties_already_submitted`: drafts were added after the parties were declared. `kyb_step_mismatch`: the session is not on the Key People step (`next_step` names its step). `kyb_session_not_writable`: the session is finished.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + }, + "code": { + "type": "string" + }, + "next_step": { + "type": "string" + } + } + }, + "example": { + "detail": "There are no draft parties to submit.", + "code": "kyb_no_party_drafts" + } + } + } + }, + "429": { + "description": "Rate limit exceeded: 300 write (POST, PATCH, DELETE) requests per minute per API key. Wait `Retry-After` seconds before retrying; the `X-RateLimit-*` headers show the budget.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable error message." + } + } + }, + "examples": { + "Rate limited": { + "summary": "Budget for the current minute exhausted", + "value": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "minimum": 1, + "example": 30 + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.submit_kyb_parties(\n \"33333333-4444-5555-6666-777777777777\",\n locale=\"es\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.submitKybParties(\"33333333-4444-5555-6666-777777777777\", {\n locale: \"es\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.SubmitKYBParties(ctx, \"33333333-4444-5555-6666-777777777777\", didit.SessionsSubmitKYBPartiesParams{\n\tLocale: didit.String(\"es\"),\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.SessionsSubmitKybPartiesParams;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().submitKybParties(\"33333333-4444-5555-6666-777777777777\", SessionsSubmitKybPartiesParams.builder()\n .locale(\"es\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->submitKybParties('33333333-4444-5555-6666-777777777777', [\n 'locale' => 'es',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.submit_kyb_parties(\n \"33333333-4444-5555-6666-777777777777\",\n locale: \"es\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.SubmitKybPartiesAsync(\"33333333-4444-5555-6666-777777777777\", new SessionsSubmitKybPartiesParams\n{\n Locale = \"es\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + }, + "/v3/session/{sessionId}/files-export/": { + "post": { + "summary": "Export every file of a session", + "description": "Start preparing one ZIP with every image, video and document of a User Verification (KYC) or Business Verification (KYB) session, in original quality: the files are stored in the ZIP as they were captured, never recompressed. Answers `202` with the export; read it with `GET /v3/session/{sessionId}/files-export/{exportId}/` until `status` is `READY`, then download `download_url`. While an export of the same session is still being prepared, that export is returned instead of a new one.\n\nThe ZIP is deleted 24 hours after it is ready, and at once when the session is deleted. A deleted session cannot be exported. Every export and every download link handed out is written to the session's audit log. A scoped API key needs `media: read` and `decisions: read`; a console user needs the privilege to read sessions.", + "operationId": "post_v3_session_files_export", + "x-didit-sdk": { + "resource": "sessions", + "method": "export_files", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs. A deleted session returns `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X POST 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/files-export/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.export_files(\n \"11111111-2222-3333-4444-555555555555\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.exportFiles(\"11111111-2222-3333-4444-555555555555\");\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.ExportFiles(ctx, \"11111111-2222-3333-4444-555555555555\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().exportFiles(\"11111111-2222-3333-4444-555555555555\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->exportFiles('11111111-2222-3333-4444-555555555555');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.export_files(\"11111111-2222-3333-4444-555555555555\")\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.ExportFilesAsync(\"11111111-2222-3333-4444-555555555555\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "202": { + "description": "The export, being prepared.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionFilesExport" + }, + "example": { + "export_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "session_id": "11111111-2222-3333-4444-555555555555", + "status": "PENDING", + "file_count": null, + "size_bytes": null, + "download_url": null, + "expires_at": null, + "error": null, + "created_at": "2026-10-29T12:00:03Z", + "completed_at": null + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/session/{sessionId}/files-export/{exportId}/": { + "get": { + "summary": "Get a session files export", + "description": "Progress of an export started with `POST /v3/session/{sessionId}/files-export/`. Once `status` is `READY`, `download_url` is a signed URL of the ZIP that stays valid for a short time; read the export again for a new one until `expires_at`. Each read that hands out a download link is written to the session's audit log. An export of a deleted session returns `404`.", + "operationId": "get_v3_session_files_export", + "x-didit-sdk": { + "resource": "sessions", + "method": "get_files_export", + "auth": "api_key", + "server": true + }, + "tags": [ + "Sessions" + ], + "parameters": [ + { + "name": "sessionId", + "in": "path", + "required": true, + "description": "UUID of the verification session (the `session_id` returned by `POST /v3/session/`). Accepts both User Verification (KYC) and Business Verification (KYB) session IDs. A deleted session returns `404`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "11111111-2222-3333-4444-555555555555" + } + }, + { + "name": "exportId", + "in": "path", + "required": true, + "description": "The `export_id` returned by `POST /v3/session/{sessionId}/files-export/`.", + "schema": { + "type": "string", + "format": "uuid", + "example": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" + } + } + ], + "x-codeSamples": [ + { + "lang": "curl", + "label": "curl", + "source": "curl -X GET 'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/files-export/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/' \\\n -H 'x-api-key: YOUR_API_KEY'" + }, + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.sessions.get_files_export(\n \"11111111-2222-3333-4444-555555555555\",\n \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.sessions.getFilesExport(\n \"11111111-2222-3333-4444-555555555555\",\n \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\n);\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.Sessions.GetFilesExport(ctx, \"11111111-2222-3333-4444-555555555555\", \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\")\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.sessions().getFilesExport(\"11111111-2222-3333-4444-555555555555\", \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\");\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "sessions->getFilesExport('11111111-2222-3333-4444-555555555555', 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee');\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.sessions.get_files_export(\n \"11111111-2222-3333-4444-555555555555\",\n \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.Sessions.GetFilesExportAsync(\"11111111-2222-3333-4444-555555555555\", \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\");\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ], + "responses": { + "200": { + "description": "The export.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionFilesExport" + }, + "example": { + "export_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + "session_id": "11111111-2222-3333-4444-555555555555", + "status": "READY", + "file_count": 9, + "size_bytes": 18734120, + "download_url": "https://storage.example.com/session/11111111-2222-3333-4444-555555555555/files-export/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/session-42-files.zip?X-Amz-Signature=...", + "expires_at": "2026-10-30T12:00:41Z", + "error": null, + "created_at": "2026-10-29T12:00:03Z", + "completed_at": "2026-10-29T12:00:41Z" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ] + } + }, + "/v3/travel-rule/unmatched/": { + "get": { + "summary": "List unmatched Travel Rule deposits", + "description": "Inbound deposits that are still waiting for the originating VASP's Travel Rule data: their transfer is `AWAITING_COUNTERPARTY`, `NOT_ENOUGH_COUNTERPARTY_DATA` or `COUNTERPARTY_VASP_NOT_FOUND`, no received message is paired with them, and nobody matched them by hand. The rows and the filters are the same as in `GET /v3/travel-rule/transfers/`, newest first, paginated with `limit` (default 50) and `offset`. Pair a deposit with the message the originating VASP sent with `POST /v3/travel-rule/unmatched/{transaction_uuid}/match/`.", + "operationId": "listUnmatchedTravelRuleDeposits", + "x-didit-sdk": { + "resource": "travel_rule.unmatched_deposits", + "method": "list", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "parameters": [ + { + "name": "direction", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "INBOUND, OUTBOUND, or both comma-separated. Case-sensitive." + }, + { + "name": "status", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "One or more transfer statuses, comma-separated." + }, + { + "name": "rail", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "One or more of INTERNAL, TRP, GTR, TRUST, VERIFYVASP, SYGNA, EMAIL, TEST_VASP, comma-separated." + }, + { + "name": "kind", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "withdrawal, deposit or message, comma-separated." + }, + { + "name": "currency", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "The transaction's currency, case-insensitive." + }, + { + "name": "start_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date" + }, + "description": "Transfers created on or after this day (UTC), YYYY-MM-DD." + }, + { + "name": "end_date", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "date" + }, + "description": "Transfers created on or before this day (UTC), YYYY-MM-DD." + }, + { + "name": "counterparty", + "in": "query", + "required": false, + "schema": { + "type": "string", + "format": "uuid" + }, + "description": "The `id` of a directory entry or Didit network member from GET /v3/travel-rule/vasps/: your transfers with that counterparty. A value that is not a valid id answers 400." + }, + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 1000000 + }, + "description": "Page size, default 50." + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 1000000 + }, + "description": "Rows to skip." + } + ], + "responses": { + "200": { + "description": "A page of unmatched deposits.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleTransferRow" + } + } + } + }, + "example": { + "count": 1, + "next": null, + "previous": null, + "results": [ + { + "id": "7d3a1c55-0b9e-4d52-8a3c-1f6e2b9d4a08", + "transaction_id": "c41f9b2e-5a8d-4e0c-9f17-3b6d8a2e5c91", + "txn_id": "dep-2026-10-20-0007", + "txn_date": "2026-10-19T08:41:12Z", + "kind": "deposit", + "direction": "INBOUND", + "status": "AWAITING_COUNTERPARTY", + "rail": "TRP", + "asset": "ETH", + "amount": "1.25", + "originator_vasp": "", + "beneficiary_vasp": "Example Counterparty VASP", + "originator_wallet": "0x77ccff0cc624a53cd8b8d8703ced826710211ec7", + "beneficiary_wallet": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb", + "originator_memo": "", + "beneficiary_memo": "", + "originator_wallet_type": "vasp", + "beneficiary_wallet_type": "vasp", + "matched_transaction_id": null, + "deadline_at": null, + "created_at": "2026-10-19T08:41:13Z" + } + ] + } + } + } + }, + "400": { + "description": "A filter value is not valid.", + "content": { + "application/json": { + "example": { + "start_date": [ + "Use the YYYY-MM-DD format." + ] + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nfor item in client.travel_rule.unmatched_deposits.list():\n print(item)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nfor await (const item of didit.travelRule.unmatchedDeposits.list()) {\n console.log(item);\n}" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\niter := client.TravelRule.UnmatchedDeposits.ListAutoPaging(ctx, didit.TravelRuleUnmatchedDepositsListParams{})\nfor iter.Next() {\n\tfmt.Printf(\"%+v\\n\", iter.Current())\n}\nif err := iter.Err(); err != nil {\n\tlog.Fatal(err)\n}" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nfor (var item : client.travelRule().unmatchedDeposits().list()) {\n System.out.println(item);\n}" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->unmatchedDeposits->list() as $item) {\n print_r($item->toArray());\n}" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\ndidit.travel_rule.unmatched_deposits.list.auto_paging_each do |item|\n puts item.to_h\nend" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nawait foreach (var item in await client.TravelRule.UnmatchedDeposits.ListAsync())\n{\n Console.WriteLine(JsonSerializer.Serialize(item));\n}" + } + ] + } + }, + "/v3/travel-rule/unmatched/{transaction_uuid}/match/": { + "post": { + "summary": "Match a Travel Rule deposit to a received message", + "operationId": "matchUnmatchedTravelRuleDeposit", + "x-didit-sdk": { + "resource": "travel_rule.unmatched_deposits", + "method": "match", + "auth": "api_key", + "server": true + }, + "tags": [ + "Travel Rule" + ], + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Pair an inbound deposit that is still waiting for originator data with the Travel Rule message the originating VASP sent for it. The deposit takes the message's originator data, counterparty VASP and rail, and both transfers record the match on their timelines. `transaction_uuid` is the deposit's `transaction_id` from `GET /v3/travel-rule/unmatched/`. `transaction_id` in the body is the received message's `transaction_id`, from a row of `GET /v3/travel-rule/transfers/?kind=message`; the transfer's own `id` is not found. The message must be for the same asset as the deposit. A deposit or message deleted from the Console answers `404`; one that has already moved on (matched, or no longer waiting) answers `409`.", + "parameters": [ + { + "name": "transaction_uuid", + "in": "path", + "required": true, + "description": "The `transaction_id` of the unmatched deposit.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleManualMatchRequest" + }, + "example": { + "transaction_id": "e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24" + } + } + } + }, + "responses": { + "200": { + "description": "Both transfers after the match.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleManualMatchResponse" + }, + "example": { + "deposit": { + "id": "7d3a1c55-0b9e-4d52-8a3c-1f6e2b9d4a08", + "transaction_id": "c41f9b2e-5a8d-4e0c-9f17-3b6d8a2e5c91", + "txn_id": "dep-2026-10-20-0007", + "txn_date": "2026-10-19T08:41:12Z", + "kind": "deposit", + "direction": "INBOUND", + "status": "AWAITING_COUNTERPARTY", + "rail": "TRP", + "asset": "ETH", + "amount": "1.25", + "originator_vasp": "Example Counterparty VASP", + "beneficiary_vasp": "Example Counterparty VASP", + "originator_wallet": "0x77ccff0cc624a53cd8b8d8703ced826710211ec7", + "beneficiary_wallet": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb", + "originator_memo": "", + "beneficiary_memo": "", + "originator_wallet_type": "vasp", + "beneficiary_wallet_type": "vasp", + "matched_transaction_id": "e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24", + "deadline_at": null, + "created_at": "2026-10-19T08:41:13Z" + }, + "message": { + "id": "2b8e6f10-9c3a-47d1-b5e2-0a4c7d9f3e66", + "transaction_id": "e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24", + "txn_id": "msg-2026-10-20-0003", + "txn_date": "2026-10-19T08:41:12Z", + "kind": "message", + "direction": "INBOUND", + "status": "COMPLETED", + "rail": "TRP", + "asset": "ETH", + "amount": "1.25", + "originator_vasp": "Origin CASP SL", + "beneficiary_vasp": "Example Counterparty VASP", + "originator_wallet": "0x77ccff0cc624a53cd8b8d8703ced826710211ec7", + "beneficiary_wallet": "0xede2d7e4f5b097e6391ab6bbcf2f47a53f4778cb", + "originator_memo": "", + "beneficiary_memo": "", + "originator_wallet_type": "vasp", + "beneficiary_wallet_type": "vasp", + "matched_transaction_id": null, + "deadline_at": "2026-10-21T08:41:13Z", + "created_at": "2026-10-19T08:41:13Z" + } + } + } + } + }, + "400": { + "description": "Validation error: `transaction_id` is missing, is the deposit itself, is not a received Travel Rule message, or is for a different asset than the deposit; or the transaction in the path is not an inbound deposit.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Error": { + "value": { + "transaction_id": [ + "The travel rule message is for a different asset than the deposit." + ] + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "description": "Unknown deposit or message, or one of them was deleted.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "No TravelRuleTransfer matches the given query." + } + } + } + }, + "409": { + "description": "The deposit is no longer waiting for originator data, or the message is already matched to a deposit.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "string" + } + } + }, + "examples": { + "Error": { + "value": { + "detail": "This deposit is no longer waiting for originator data." + } + } + } + } + } + } + }, + "x-codeSamples": [ + { + "lang": "python", + "label": "Python", + "source": "from didit import Didit\n\n# Reads DIDIT_API_KEY.\nclient = Didit()\n\nresult = client.travel_rule.unmatched_deposits.match(\n \"11111111-2222-3333-4444-555555555555\",\n transaction_id=\"e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24\",\n)\nprint(result)" + }, + { + "lang": "javascript", + "label": "Node.js", + "source": "import Didit from \"@didit-protocol/sdk-node\";\n\n// Reads DIDIT_API_KEY.\nconst didit = new Didit();\n\nconst result = await didit.travelRule.unmatchedDeposits.match(\"11111111-2222-3333-4444-555555555555\", {\n transaction_id: \"e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24\",\n});\nconsole.log(result);" + }, + { + "lang": "go", + "label": "Go", + "source": "ctx := context.Background()\n// Reads DIDIT_API_KEY.\nclient := didit.NewClient()\n\nresult, err := client.TravelRule.UnmatchedDeposits.Match(ctx, \"11111111-2222-3333-4444-555555555555\", didit.TravelRuleManualMatchRequest{\n\tTransactionID: \"e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)" + }, + { + "lang": "java", + "label": "Java", + "source": "import me.didit.Didit;\nimport me.didit.types.TravelRuleManualMatchRequest;\n\n// Reads DIDIT_API_KEY.\nDidit client = new Didit();\n\nvar result = client.travelRule().unmatchedDeposits().match(\"11111111-2222-3333-4444-555555555555\", TravelRuleManualMatchRequest.builder()\n .transactionId(\"e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24\")\n .build());\nSystem.out.println(result);" + }, + { + "lang": "php", + "label": "PHP", + "source": "travelRule->unmatchedDeposits->match('11111111-2222-3333-4444-555555555555', [\n 'transaction_id' => 'e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24',\n]);\nprint_r($result->toArray());" + }, + { + "lang": "ruby", + "label": "Ruby", + "source": "require \"didit\"\n\n# Reads DIDIT_API_KEY.\ndidit = Didit::Client.new\n\nresult = didit.travel_rule.unmatched_deposits.match(\n \"11111111-2222-3333-4444-555555555555\",\n transaction_id: \"e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24\"\n)\nputs result.to_h" + }, + { + "lang": "csharp", + "label": ".NET", + "source": "using System;\nusing System.Text.Json;\nusing Didit;\n\n// Reads DIDIT_API_KEY.\nusing var client = new DiditClient();\n\nvar result = await client.TravelRule.UnmatchedDeposits.MatchAsync(\"11111111-2222-3333-4444-555555555555\", new TravelRuleManualMatchRequest\n{\n TransactionId = \"e90a4d7b-1c2f-4b83-a6d5-8f0e3c1b7a24\",\n});\nConsole.WriteLine(JsonSerializer.Serialize(result));" + } + ] + } + } + }, + "components": { + "schemas": { + "DocumentAIDetectedCode": { + "type": "object", + "required": [ + "type", + "page_number", + "position", + "x", + "y", + "width", + "height", + "page_width", + "page_height", + "raw_payload", + "parsed_payload" + ], + "properties": { + "type": { + "type": "string", + "description": "Detected symbology, for example `QR_CODE`, `CODE_128`, `EAN_13`, `DATA_MATRIX`, or `PDF_417`." + }, + "page_number": { + "type": "integer", + "minimum": 1, + "description": "1-based page number." + }, + "position": { + "type": "array", + "nullable": true, + "description": "Detected polygon in rendered-page pixel coordinates. `null` when no reliable location is available.", + "items": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": { + "type": "integer" + } + } + }, + "x": { + "type": "integer", + "nullable": true, + "description": "Left edge of the bounding box in rendered-page pixels." + }, + "y": { + "type": "integer", + "nullable": true, + "description": "Top edge of the bounding box in rendered-page pixels." + }, + "width": { + "type": "integer", + "nullable": true, + "description": "Bounding-box width in rendered-page pixels." + }, + "height": { + "type": "integer", + "nullable": true, + "description": "Bounding-box height in rendered-page pixels." + }, + "page_width": { + "type": "integer", + "description": "Rendered page width in pixels." + }, + "page_height": { + "type": "integer", + "description": "Rendered page height in pixels." + }, + "raw_payload": { + "type": "string", + "description": "Raw decoder payload as a base64 string when available; otherwise an empty string." + }, + "parsed_payload": { + "nullable": true, + "description": "Decoded string or structured value. `null` when the code was located but could not be decoded." + } + } + }, + "SessionImportJob": { + "type": "object", + "required": [ + "uuid", + "status", + "import_type", + "source_format", + "source_filename", + "total_rows", + "processed_rows", + "created_count", + "updated_count", + "skipped_count", + "failed_count" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Import job id. Use this value to poll progress and fetch row errors." + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED", + "PAUSED", + "CANCELLED", + "READY" + ] + }, + "import_type": { + "type": "string", + "enum": [ + "user_verification", + "business_verification", + "status_rules", + "transactions" + ] + }, + "source_format": { + "type": "string", + "enum": [ + "csv", + "ndjson" + ] + }, + "source_filename": { + "type": "string" + }, + "total_rows": { + "type": "integer", + "format": "int64" + }, + "processed_rows": { + "type": "integer", + "format": "int64" + }, + "checkpoint_row": { + "type": "integer", + "format": "int64" + }, + "created_count": { + "type": "integer", + "format": "int64" + }, + "updated_count": { + "type": "integer", + "format": "int64" + }, + "skipped_count": { + "type": "integer", + "format": "int64" + }, + "failed_count": { + "type": "integer", + "format": "int64" + }, + "total_media_bytes": { + "type": "integer", + "format": "int64" + }, + "last_error": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "started_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "state": { + "type": "string", + "enum": [ + "ready", + "queued", + "running", + "paused", + "completed", + "errors", + "failed", + "cancelled" + ], + "description": "The job's state as the console shows it; `errors` is a completed job with rejected rows." + }, + "source": { + "type": "string", + "enum": [ + "file", + "api", + "provider" + ] + }, + "provider": { + "type": "string", + "description": "Migration source; empty for a file or API import." + }, + "provider_options": { + "type": "object" + }, + "credential_hint": { + "type": "string", + "description": "The first characters of the key the migration holds. The key itself is never returned." + }, + "credentials_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resumes_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When a job paused by the provider's rate limit resumes by itself." + }, + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "is_dry_run": { + "type": "boolean" + }, + "dry_run_summary": { + "type": "object", + "description": "What a dry run found: `total`, `by_status`, `already_in_didit`, `same_person`, `not_finished`, `failed`, `unmapped_statuses`." + }, + "face_templates_enabled": { + "type": "boolean" + }, + "lawful_basis_confirmed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "not_finished_count": { + "type": "integer", + "format": "int64" + }, + "same_person_count": { + "type": "integer", + "format": "int64" + }, + "face_templates_count": { + "type": "integer", + "format": "int64" + }, + "face_duplicates_count": { + "type": "integer", + "format": "int64" + }, + "face_blocklist_hits_count": { + "type": "integer", + "format": "int64" + }, + "created_by_name": { + "type": "string", + "nullable": true + }, + "created_by_email": { + "type": "string", + "nullable": true + } + } + }, + "SessionImportError": { + "type": "object", + "required": [ + "uuid", + "row_number", + "error", + "raw_row" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "row_number": { + "type": "integer", + "format": "int64" + }, + "external_id": { + "type": "string", + "nullable": true + }, + "error": { + "type": "string" + }, + "raw_row": { + "type": "object", + "additionalProperties": true + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BrandingCustomizationUpdate": { + "type": "object", + "description": "Partial branding update body. Provide only the fields you want to change. Hex colors accept `#RRGGBB` or `#RRGGBBAA`. Custom domain and white-label email are not accepted here.", + "properties": { + "color_primary": { + "type": "string", + "description": "Headers / primary text color." + }, + "color_secondary": { + "type": "string", + "description": "Paragraph / secondary text color." + }, + "color_background": { + "type": "string", + "description": "Page background color." + }, + "color_panel": { + "type": "string", + "description": "Panel / accent color." + }, + "color_panel_10": { + "type": "string", + "description": "Panel color at 10% opacity." + }, + "color_on_panel_1": { + "type": "string", + "description": "Support text color on panels." + }, + "color_on_panel_2": { + "type": "string", + "description": "Pill / badge color on panels." + }, + "color_on_background": { + "type": "string", + "description": "Text color on background." + }, + "color_button_1": { + "type": "string", + "description": "Primary button color." + }, + "color_button_2": { + "type": "string", + "description": "Secondary button color." + }, + "color_button_text_1": { + "type": "string", + "description": "Primary button text color." + }, + "color_button_text_2": { + "type": "string", + "description": "Secondary button text color." + }, + "color_pill_text": { + "type": "string", + "description": "Text color for pills, inputs and selectors." + }, + "border_radius_panel": { + "type": "integer", + "minimum": 0, + "description": "Panel corner radius in pixels." + }, + "border_radius_buttons": { + "type": "integer", + "minimum": 0, + "description": "Button corner radius in pixels." + }, + "font_family": { + "type": "string", + "description": "Google Font family name (e.g. `Inter`, `AR One Sans`)." + }, + "font_weight": { + "type": "string", + "description": "Font weight available for the selected family (e.g. `regular`, `500`, `700`)." + }, + "app_public_name": { + "type": "string", + "nullable": true, + "description": "Public name shown in the verification flow." + }, + "privacy_policy_url": { + "type": "string", + "nullable": true, + "description": "Privacy policy URL shown to the end user." + }, + "skip_welcome_screen": { + "type": "boolean", + "description": "Skip the initial welcome screen." + }, + "disable_login_with_didit": { + "type": "boolean", + "description": "Continue as guest instead of showing the login-with-Didit screen. Cannot be set back to `false` while a custom domain is configured (400)." + }, + "hide_progress_bar": { + "type": "boolean", + "description": "Hide the progress bar during the flow." + }, + "callback_seconds": { + "type": "integer", + "minimum": 0, + "description": "Delay (seconds) before redirecting to the callback URL." + }, + "delete_logo_square": { + "type": "boolean", + "description": "When `true`, removes the current square logo." + }, + "delete_logo_rectangular": { + "type": "boolean", + "description": "When `true`, removes the current rectangular logo." + }, + "delete_logo_rectangular_dark": { + "type": "boolean", + "description": "When `true`, removes the current dark-mode logo, and `logo_rectangular_dark` then reads `null`. If the same multipart request also uploads `image_rectangular_dark`, the upload wins." + }, + "delete_favicon": { + "type": "boolean", + "description": "When `true`, removes the current favicon." + } + } + }, + "BrandingCustomization": { + "type": "object", + "description": "White-label branding for an application: colors, fonts, radii, logo URLs and UI/UX options. Logo URLs are read-only (set them by uploading images). Custom domain and white-label email are not included.", + "properties": { + "color_primary": { + "type": "string" + }, + "color_secondary": { + "type": "string" + }, + "color_background": { + "type": "string" + }, + "color_panel": { + "type": "string" + }, + "color_panel_10": { + "type": "string" + }, + "color_on_panel_1": { + "type": "string" + }, + "color_on_panel_2": { + "type": "string" + }, + "color_on_background": { + "type": "string" + }, + "color_button_1": { + "type": "string" + }, + "color_button_2": { + "type": "string" + }, + "color_button_text_1": { + "type": "string" + }, + "color_button_text_2": { + "type": "string" + }, + "color_pill_text": { + "type": "string" + }, + "border_radius_panel": { + "type": "integer" + }, + "border_radius_buttons": { + "type": "integer" + }, + "font_family": { + "type": "string" + }, + "font_weight": { + "type": "string" + }, + "font_url": { + "type": "string", + "nullable": true, + "description": "Read-only. Resolved Google Fonts stylesheet URL for the selected family." + }, + "logo_square": { + "type": "string", + "nullable": true, + "description": "Read-only URL of the square logo. Upload via `image_square`." + }, + "logo_rectangular": { + "type": "string", + "nullable": true, + "description": "Read-only URL of the rectangular logo. Upload via `image_rectangular`." + }, + "logo_rectangular_dark": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Read-only. Temporary signed URL of the rectangular logo for dark mode, shown on a dark background. `null` when no dark logo is set or rectangular logos are not shown for your account. Upload it with `image_rectangular_dark`." + }, + "favicon": { + "type": "string", + "nullable": true, + "description": "Read-only URL of the favicon. Upload via `image_favicon`." + }, + "app_public_name": { + "type": "string", + "nullable": true + }, + "privacy_policy_url": { + "type": "string", + "nullable": true + }, + "skip_welcome_screen": { + "type": "boolean" + }, + "disable_login_with_didit": { + "type": "boolean" + }, + "hide_progress_bar": { + "type": "boolean" + }, + "callback_seconds": { + "type": "integer" + } + } + }, + "WebhookEvent": { + "type": "string", + "description": "Supported webhook event name. Use these exact strings in `subscribed_events`; unsupported values are rejected.", + "enum": [ + "status.updated", + "data.updated", + "user.status.updated", + "user.data.updated", + "business.status.updated", + "business.data.updated", + "activity.created", + "transaction.created", + "transaction.status.updated", + "travel_rule.status.updated", + "workflow.compliance_changed", + "transaction_rule_suggestions.created", + "auth.signup", + "auth.login", + "auth.login_failed", + "auth.password_reset_requested", + "auth.password_reset_completed", + "auth.mfa_changed", + "auth.new_device", + "auth.account_locked", + "investigator.report.ready", + "investigator.decision.automated", + "investigator.decision.overridden", + "investigator.decision.contested", + "investigator.question.raised", + "investigator.automation.paused", + "investigator.rfi.answered", + "investigator.rfi.overdue", + "investigator.drift.detected", + "investigator.sla.breached", + "investigator.qa.failed", + "investigator.external.report.ready" + ] + }, + "WebhookSubscribedEvents": { + "type": "array", + "description": "Event filter for this webhook destination. Didit delivers only webhooks whose event type exactly matches one of these values — there is no wildcard subscription. When sent, the list must contain at least one valid event (an explicit `[]` is rejected with 400); a destination whose list is empty (field omitted at create) receives nothing. On update the list is replaced wholesale, never merged.", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "example": [ + "status.updated", + "data.updated" + ] + }, + "PhoneVerificationSendResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the verification. A `Retry` send returns the same `request_id` as the original send. This id appears in the Business Console, is returned again by a finalized `POST /v3/phone/check/`, and can be passed to `GET /v3/session/{sessionId}/decision/`." + }, + "status": { + "type": "string", + "enum": [ + "Success", + "Retry", + "Blocked" + ], + "description": "`Success` — OTP sent to a new verification. `Retry` — OTP re-sent for the pending verification created by a previous send. `Blocked` — the anti-fraud layer refused to send; the verification is immediately finalized as `Declined` and nothing is billed." + }, + "reason": { + "type": "string", + "nullable": true, + "description": "Why a send was `Blocked` (e.g., `repeated_attempts`, `suspicious`, `spam`). `null` on `Success` and `Retry`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` stored on the session (from the first send)." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` stored on the session (from the first send)." + } + } + }, + "PhoneVerificationCheckResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "On `Approved`/`Declined`: the session id of the matched verification — identical to the `request_id` returned by `POST /v3/phone/send/`. On `Failed` and `Expired or Not Found`: a random one-off UUID that cannot be looked up later." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "Failed", + "Expired or Not Found" + ], + "description": "`Approved` — correct code, no declining risk. `Declined` — terminal: a declining risk matched or the attempt budget (3) was exhausted. `Failed` — wrong code, attempts remaining. `Expired or Not Found` — no pending verification for this number in the last 5 minutes." + }, + "message": { + "type": "string", + "description": "Human-readable explanation of the outcome, including the number of attempts remaining after a wrong code." + }, + "phone": { + "type": "object", + "nullable": true, + "description": "Full phone report. Present (non-null) only on finalized outcomes (`Approved`/`Declined`); `null` on `Failed` and absent on `Expired or Not Found`.", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Not Finished", + "Expired" + ], + "description": "Final status of the phone verification. Matches the top-level `status` for finalized checks." + }, + "phone_number_prefix": { + "type": "string", + "description": "Country calling code with leading `+` (e.g., `+1`).", + "example": "+1" + }, + "phone_number": { + "type": "string", + "description": "National number without the country calling code.", + "example": "4155552671" + }, + "full_number": { + "type": "string", + "description": "Complete number in E.164 format.", + "example": "+14155552671" + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of the number.", + "example": "US" + }, + "country_name": { + "type": "string", + "description": "Full country name derived from `country_code`.", + "example": "United States" + }, + "carrier": { + "type": "object", + "description": "Current network serving the number, from the carrier lookup run at finalization.", + "properties": { + "name": { + "type": "string", + "description": "Carrier name. Empty string when the lookup could not identify it." + }, + "type": { + "type": "string", + "description": "Line type reported by the lookup: `mobile`, `fixed_line`, `voip`, `isp`, `vpn`, `toll_free`, `premium_rate`, `pager`, `payphone`, `satellite`, `service`, `shared_cost`, `short_codes_commercial`, `calling_cards`, `local_rate`, `universal_access`, `voice_mail`, `other`, or `unknown`." + } + } + }, + "is_disposable": { + "type": "boolean", + "description": "`true` when the number belongs to a temporary/burner number service." + }, + "is_virtual": { + "type": "boolean", + "description": "`true` when the line type is virtual (`voip`, `isp`, or `vpn`)." + }, + "verification_method": { + "type": "string", + "description": "Channel that actually delivered the OTP once delivery is confirmed (e.g., `whatsapp`, `sms`); until then, the requested channel." + }, + "verification_attempts": { + "type": "integer", + "description": "Number of OTP sends for this verification (1, or 2 after a retry)." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the correct code was entered. `null` if the verification was never approved." + }, + "warnings": { + "type": "array", + "description": "Risk warnings recorded during the verification (duplicate, disposable, VoIP, attempts exceeded, blocklist, etc.). Empty array when there are none.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature that produced the warning. Always `PHONE` here." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk code." + }, + "additional_data": { + "type": "object", + "nullable": true, + "description": "Risk-specific context (e.g., the duplicated session id for duplicate risks). `null` when the risk carries no extra data." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` risks decline the verification, `warning` flags it for review, `information` is recorded without affecting the status." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "lifecycle": { + "type": "array", + "description": "Chronological audit trail of the verification. Event `type` is one of `PHONE_VERIFICATION_MESSAGE_SENT`, `PHONE_VERIFICATION_RETRY_MESSAGE_SENT`, `PHONE_VERIFICATION_BLOCKED`, `PHONE_DELIVERY_DELIVERED`, `PHONE_DELIVERY_UNDELIVERABLE`, `VALID_CODE_ENTERED`, `INVALID_CODE_ENTERED`, `PHONE_VERIFICATION_APPROVED`, `PHONE_VERIFICATION_DECLINED`, `PHONE_VERIFICATION_IN_REVIEW`, `PHONE_VERIFICATION_EXPIRED`.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Event type." + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the event happened." + }, + "details": { + "type": "object", + "nullable": true, + "description": "Event-specific payload. Send events: `{status, reason, channel, actual_channel}` (`actual_channel` is filled once delivery is confirmed). Delivery events: `{channel, status}`. Code-entry events: `{code_tried, status}`. Final-status events: `null`, or `{reason}` when declined or in review." + }, + "fee": { + "type": "number", + "description": "USD amount billed for this event. Non-zero only on send events (the delivery price); 0 for code entries, delivery confirmations, and status events." + } + } + } + }, + "matches": { + "type": "array", + "description": "Other sessions of your application (KYC, KYB, or API) where the same phone number was used by a different user (different `vendor_data`). Up to 5 entries, a synthetic list-entry blocklist match first when present (session-derived matches keep query order). Empty array when there are no matches.", + "items": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the other verification that used this phone number." + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of that session." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of that session." + }, + "verification_date": { + "type": "string", + "description": "When that session was created (`YYYY-MM-DDTHH:MM:SSZ`)." + }, + "phone_number": { + "type": "string", + "description": "The matched phone number." + }, + "status": { + "type": "string", + "description": "Status of that session (e.g., `Approved`, `Declined`, `In Progress`)." + }, + "is_blocklisted": { + "type": "boolean", + "description": "Whether the phone number is on your application's blocklist." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "For API-type sessions, the standalone API that created it (e.g., `PHONE_VERIFICATION`); `null` for regular verification sessions." + }, + "source": { + "type": "string", + "description": "Where the match was found. Usually `session`; a blocklist hit via an organization list entry prepends a synthetic match with `source: \"list_entry\"` and null `session_id`/`session_number`." + } + } + } + }, + "phone_social": { + "$ref": "#/components/schemas/PhoneSocialFootprint" + }, + "phone_intelligence": { + "allOf": [ + { + "$ref": "#/components/schemas/PhoneIntelligence" + } + ], + "description": "Phone intelligence signals. Present only when `enable_enrichment` was `true` on the check." + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of the matched verification's session. `null` on `Expired or Not Found`." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "`metadata` of the matched verification's session. `null` on `Expired or Not Found`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this check response." + } + } + }, + "EmailVerificationSendResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the verification. A `Retry` send returns the same `request_id` as the original send. This id appears in the Business Console, is returned again by a finalized `POST /v3/email/check/`, and can be passed to `GET /v3/session/{sessionId}/decision/`." + }, + "status": { + "type": "string", + "enum": [ + "Success", + "Retry", + "Undeliverable" + ], + "description": "`Success` — OTP emailed to a new verification (billed). `Retry` — fresh OTP emailed for the pending verification created by a previous send (free). `Undeliverable` — the address failed deliverability validation or the message could not be sent; the verification is immediately finalized as `Declined` and nothing is billed." + }, + "reason": { + "type": "string", + "nullable": true, + "enum": [ + "email_can_not_be_delivered", + null + ], + "description": "`email_can_not_be_delivered` when `status` is `Undeliverable`; `null` otherwise." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` stored on the session (from the first send)." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Echo of the `metadata` stored on the session (from the first send)." + } + } + }, + "EmailVerificationCheckResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "On `Approved`/`Declined`: the session id of the matched verification — identical to the `request_id` returned by `POST /v3/email/send/`. On `Failed` and `Expired or Not Found`: a random one-off UUID that cannot be looked up later." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "Failed", + "Expired or Not Found" + ], + "description": "`Approved` — correct code, no declining risk. `Declined` — terminal: a declining risk matched or the attempt budget (3) was exhausted. `Failed` — wrong code, attempts remaining. `Expired or Not Found` — no pending verification for this address in the last 5 minutes." + }, + "message": { + "type": "string", + "description": "Human-readable explanation of the outcome, including the number of attempts remaining after a wrong code." + }, + "email": { + "type": "object", + "nullable": true, + "description": "Full email report. Present (non-null) only on finalized outcomes (`Approved`/`Declined`); `null` on `Failed` and absent on `Expired or Not Found`.", + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Not Finished", + "Expired" + ], + "description": "Final status of the email verification. Matches the top-level `status` for finalized checks." + }, + "email": { + "type": "string", + "format": "email", + "description": "The verified email address.", + "example": "alice@example.com" + }, + "is_breached": { + "type": "boolean", + "description": "`true` when the address appears in known data breaches (breach intelligence lookup run at finalization)." + }, + "breaches": { + "type": "array", + "description": "Up to 5 most recent known breaches containing this address. Empty array (or omitted) when `is_breached` is `false`.", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Breach identifier." + }, + "domain": { + "type": "string", + "description": "Domain of the breached service." + }, + "breach_date": { + "type": "string", + "format": "date", + "description": "Date the breach occurred." + }, + "breach_emails_count": { + "type": "integer", + "description": "Total number of accounts exposed in the breach." + }, + "description": { + "type": "string", + "description": "Human-readable description of the breach (may contain HTML markup)." + }, + "logo_path": { + "type": "string", + "description": "URL of the breached service's logo." + }, + "data_classes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Categories of data exposed, in snake_case (e.g., `email_addresses`, `passwords`)." + }, + "is_verified": { + "type": "boolean", + "description": "Whether the breach has been verified as legitimate." + } + } + } + }, + "is_disposable": { + "type": "boolean", + "description": "`true` when the domain belongs to a known disposable/temporary-mail provider." + }, + "is_undeliverable": { + "type": "boolean", + "description": "`true` when the address cannot receive mail (failed syntax or DNS/MX validation, or the message bounced)." + }, + "verification_attempts": { + "type": "integer", + "description": "Number of OTP sends for this verification (1, or 2 after a retry)." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the correct code was entered. `null` if the verification was never approved." + }, + "warnings": { + "type": "array", + "description": "Risk warnings recorded during the verification (duplicate, breached, disposable, undeliverable, attempts exceeded, blocklist, etc.). Empty array when there are none.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature that produced the warning. Always `EMAIL` here." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk code." + }, + "additional_data": { + "type": "object", + "nullable": true, + "description": "Risk-specific context (e.g., the duplicated session id for duplicate risks). `null` when the risk carries no extra data." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity. `error` risks decline the verification, `warning` flags it for review, `information` is recorded without affecting the status." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary of the risk." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation of the risk." + } + } + } + }, + "lifecycle": { + "type": "array", + "description": "Chronological audit trail of the verification. Event `type` is one of `EMAIL_VERIFICATION_MESSAGE_SENT`, `EMAIL_VERIFICATION_RETRY_MESSAGE_SENT`, `VALID_CODE_ENTERED`, `INVALID_CODE_ENTERED`, `EMAIL_VERIFICATION_APPROVED`, `EMAIL_VERIFICATION_DECLINED`, `EMAIL_VERIFICATION_IN_REVIEW`, `EMAIL_VERIFICATION_EXPIRED`.", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Event type." + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the event happened." + }, + "details": { + "type": "object", + "nullable": true, + "description": "Event-specific payload. Send events: `{status, reason}`. Code-entry events: `{code_tried, status}`. Final-status events: `null`, or `{reason}` when declined or in review." + }, + "fee": { + "type": "number", + "description": "USD amount billed for this event. Non-zero only on the first successful send; 0 for retries, code entries, and status events." + } + } + } + }, + "matches": { + "type": "array", + "description": "Other sessions of your application (KYC, KYB, or API) where the same email address was used by a different user (different `vendor_data`). Up to 5 entries; a synthetic list-entry blocklist match is prepended when present (and only when no session-derived blocklisted email exists) — session-derived matches keep created_at order. Empty array when there are no matches.", + "items": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the other verification that used this email address." + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of that session." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of that session." + }, + "verification_date": { + "type": "string", + "description": "When that session was created (`YYYY-MM-DDTHH:MM:SSZ`)." + }, + "email": { + "type": "string", + "description": "The matched email address." + }, + "status": { + "type": "string", + "description": "Status of that session (e.g., `Approved`, `Declined`, `In Progress`)." + }, + "is_blocklisted": { + "type": "boolean", + "description": "Whether the email address is on your application's blocklist." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "For API-type sessions, the standalone API that created it (e.g., `PHONE_VERIFICATION`); `null` for regular verification sessions." + }, + "source": { + "type": "string", + "description": "Where the match was found. Usually `session`; a blocklist hit via an organization list entry prepends a synthetic match with `source: \"list_entry\"` and null `session_id`/`session_number`." + } + } + } + }, + "email_social": { + "$ref": "#/components/schemas/EmailSocialFootprint" + }, + "email_intelligence": { + "allOf": [ + { + "$ref": "#/components/schemas/EmailIntelligence" + } + ], + "description": "Email intelligence signals. Present only when `enable_enrichment` was `true` on the check." + } + } + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of the matched verification's session. `null` on `Expired or Not Found`." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "`metadata` of the matched verification's session. `null` on `Expired or Not Found`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this check response." + } + } + }, + "WorkflowListItem": { + "type": "object", + "description": "One workflow version in the list view.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Per-version UUID. Use as `{settings_uuid}` for get/update/delete, and as `workflow_id` when creating sessions." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable group ID shared by every version of the same workflow. Equals `uuid` for version 1." + }, + "workflow_label": { + "type": "string", + "description": "Display name for the workflow." + }, + "workflow_type": { + "type": "string", + "nullable": true, + "description": "Base type for simple workflows: `kyc`, `kyb`, `adaptive_age_verification`, `biometric_authentication`, etc. `null` for graph-based workflows without a KYB feature." + }, + "is_default": { + "type": "boolean", + "description": "Whether this is the default workflow for new sessions." + }, + "is_archived": { + "type": "boolean" + }, + "is_white_label_enabled": { + "type": "boolean", + "description": "Whether white-label customization applies to sessions created with this workflow." + }, + "total_price": { + "type": "number", + "description": "Fixed price per verification in USD (JSON number, excludes variable-priced features such as DATABASE_VALIDATION and PHONE_VERIFICATION)." + }, + "min_price": { + "type": "number", + "description": "Cheapest fixed price across workflow-graph paths, in USD." + }, + "max_price": { + "type": "number", + "description": "Most expensive fixed price across workflow-graph paths, in USD." + }, + "features": { + "type": "string", + "description": "Enabled features as a single `\" + \"`-joined string, e.g. `\"OCR + LIVENESS + FACE_MATCH\"`. Split on `\" + \"` to get a list." + }, + "is_simple_workflow": { + "type": "boolean", + "description": "True when the workflow has no custom workflow graph." + }, + "is_editable": { + "type": "boolean", + "description": "True when this version is a `draft` (drafts are editable in place; published versions are immutable in the Console)." + }, + "workflow_url": { + "type": "string", + "format": "uri", + "description": "Public shareable verification URL for this workflow (`https://verify.didit.me/u/`)." + }, + "max_retry_attempts": { + "type": "integer", + "description": "Maximum retry attempts allowed after a declined verification (0-10)." + }, + "retry_window_days": { + "type": "integer", + "nullable": true, + "description": "Rolling window in days used to count retries (1-365). `null` = all-time limit." + }, + "session_expiration_time": { + "type": "integer", + "description": "Seconds before an unfinished session expires (3600-2419200)." + }, + "version": { + "type": "integer", + "description": "Version number within the `workflow_id` group, starting at 1." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Version status. New sessions use the latest `published` version of a group." + }, + "has_draft": { + "type": "boolean", + "description": "True when any version in this `workflow_id` group is currently a draft." + }, + "created_by": { + "type": "object", + "nullable": true, + "description": "Who created this version: `{name, email, type}` where `type` is `user` (Console user, `email` set) or `api_key` (`name` is the client id, `email` is null). `null` when no creator was recorded.", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string", + "nullable": true + }, + "type": { + "type": "string", + "enum": [ + "user", + "api_key" + ] + } + } + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "SimpleWorkflowRequest": { + "type": "object", + "description": "Create a simple linear workflow. The API converts this feature list into a node-based workflow graph internally. STRICT field whitelist: any key not listed here (including `workflow_type`) is rejected with a 400.", + "required": [ + "features" + ], + "properties": { + "workflow_label": { + "type": "string", + "maxLength": 50, + "default": "ID Verification", + "description": "Display name for the workflow (max 50 characters). Defaults to `\"ID Verification\"` when omitted — always set it so workflows stay distinguishable.", + "example": "Standard KYC" + }, + "is_default": { + "type": "boolean", + "description": "Set this workflow as the default for new sessions." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Omit this field to create a published workflow ready for sessions. Use `draft` only when you want to save without publishing." + }, + "features": { + "type": "array", + "minItems": 1, + "description": "Verification features in execution order. The API links each item to the next one and adds one final status node automatically.", + "items": { + "$ref": "#/components/schemas/SimpleWorkflowFeature" + } + }, + "is_white_label_enabled": { + "type": "boolean", + "description": "Enable white-label customization for sessions created with this workflow.", + "default": false + }, + "is_desktop_allowed": { + "type": "boolean", + "description": "Allow the verification flow to run on desktop browsers.", + "default": false + }, + "max_retry_attempts": { + "type": "integer", + "minimum": 0, + "maximum": 10, + "default": 3, + "description": "Maximum retry attempts allowed after a declined verification. `0` blocks the user after the first decline." + }, + "retry_window_days": { + "type": "integer", + "minimum": 1, + "maximum": 365, + "nullable": true, + "default": 7, + "description": "Rolling window in days used to count retries. `null` enforces an all-time limit." + }, + "face_liveness_max_attempts": { + "type": "integer", + "minimum": 1, + "maximum": 3, + "default": 3, + "description": "Maximum liveness submissions per session. Default 3: one initial attempt plus two retries. This value is also copied into generated liveness and age-estimation workflow nodes unless the feature-level config overrides it." + }, + "face_match_max_attempts": { + "type": "integer", + "minimum": 1, + "maximum": 3, + "default": 3, + "description": "Maximum face-match submissions per session. Default 3: one initial attempt plus two retries. This value is also copied into generated face-match workflow nodes unless the feature-level config overrides it." + }, + "session_expiration_time": { + "type": "integer", + "minimum": 3600, + "maximum": 2419200, + "default": 604800, + "description": "Seconds before an unfinished session expires. Minimum 1 hour (3600), maximum 4 weeks (2419200)." + } + } + }, + "SimpleWorkflowUpdateRequest": { + "type": "object", + "description": "Update workflow metadata or replace the full linear feature list. STRICT field whitelist: any key not listed here is rejected with a 400.", + "properties": { + "workflow_label": { + "type": "string", + "maxLength": 50, + "description": "Display name for the workflow (max 50 characters)." + }, + "is_default": { + "type": "boolean", + "description": "Set this workflow as the default for new sessions." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Controls versioning. Omit to update the current version in place (drafts auto-publish). `published` on an already-published version creates and returns a NEW version. `draft` is only valid while the version is still a draft." + }, + "features": { + "type": "array", + "minItems": 1, + "description": "If provided, this replaces the full feature order and configuration.", + "items": { + "$ref": "#/components/schemas/SimpleWorkflowFeature" + } + }, + "is_white_label_enabled": { + "type": "boolean", + "description": "Enable white-label customization for sessions created with this workflow." + }, + "is_desktop_allowed": { + "type": "boolean", + "description": "Allow the verification flow to run on desktop browsers." + }, + "max_retry_attempts": { + "type": "integer", + "minimum": 0, + "maximum": 10, + "description": "Maximum retry attempts allowed after a declined verification. `0` blocks the user after the first decline." + }, + "retry_window_days": { + "type": "integer", + "minimum": 1, + "maximum": 365, + "nullable": true, + "description": "Rolling window in days used to count retries. `null` enforces an all-time limit." + }, + "face_liveness_max_attempts": { + "type": "integer", + "minimum": 1, + "maximum": 3, + "description": "Maximum liveness submissions per session. This value is also copied into generated liveness and age-estimation workflow nodes unless the feature-level config overrides it." + }, + "face_match_max_attempts": { + "type": "integer", + "minimum": 1, + "maximum": 3, + "description": "Maximum face-match submissions per session. This value is also copied into generated face-match workflow nodes unless the feature-level config overrides it." + }, + "session_expiration_time": { + "type": "integer", + "minimum": 3600, + "maximum": 2419200, + "description": "Seconds before an unfinished session expires. Minimum 1 hour (3600), maximum 4 weeks (2419200)." + } + } + }, + "SimpleWorkflowFeature": { + "type": "object", + "required": [ + "feature" + ], + "properties": { + "feature": { + "type": "string", + "description": "Feature to run. Use the uppercase values shown here.", + "enum": [ + "OCR", + "NFC", + "LIVENESS", + "FACE_MATCH", + "PROOF_OF_ADDRESS", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "DATABASE_VALIDATION", + "AML", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "SIGNATURE" + ] + }, + "config": { + "type": "object", + "description": "Feature-specific settings. For `QUESTIONNAIRE`, include `questionnaire_uuid` with the `questionnaire_id` returned by the questionnaire create endpoint. For `SIGNATURE`, a workflow that goes live needs a document to sign: set `signature_documents_source` to `session` or `both` so each session can bring its own, or send `status: \"draft\"` and add the documents before you publish. `signature_level: \"aes\"` is refused with `aes_not_available` while `signature_availability.aes` is `false` on the workflow. For `OCR`, `methods` selects which ID Verification methods each country may use (document capture, non-doc lookup against a government or other authoritative source, and digital identity wallets); omit it and every country is document capture only. For `SIGNATURE`, see [E-signature settings](/management-api/workflows/feature-configs#e-signature): `signature_level` accepts `simple` (the default, no ID verification, email or phone needed) and `aes` for now, and workflow documents and their fields are managed in the console, so through the API send documents and fields per session with `signature_documents_source: \"session\"`.", + "additionalProperties": true + }, + "label": { + "type": "string", + "description": "Optional internal label for this workflow node." + } + } + }, + "ReviewItem": { + "type": "object", + "description": "One entry in a session's review / activity feed. Status changes carry `previous_status`, `new_status`, `previous_value`, `new_value`, and `changed_fields`; comments carry `comment` and (for Console comments) `mentioned_emails`; the remaining fields are `null` or empty when they do not apply to the activity type.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of this feed entry.", + "example": "de0561bc-5583-4409-9171-1a31a4c8a5f4" + }, + "activity_type": { + "type": "string", + "enum": [ + "COMMENT", + "STATUS_UPDATED", + "KYC_DATA_UPDATED", + "POA_DATA_UPDATED", + "FILE_ADDED", + "FILE_REMOVED", + "AML_ONGOING_MATCH", + "AML_STATUS_UPDATED", + "AML_HIT_STATUS_UPDATED", + "KYB_DATA_UPDATED", + "KYB_DOCUMENT_UPLOADED", + "TAG_ADDED", + "TAG_REMOVED", + "FEATURE_OVERRIDE_PENDING", + "FEATURE_OVERRIDE_APPLIED", + "FEATURE_OVERRIDE_CANCELLED" + ], + "description": "What kind of activity this entry records. `STATUS_UPDATED` rows come from manual decisions (`PATCH /v3/session/{session_id}/update-status/`) and from entries added via `POST …/reviews/` (which default to this type even when no status is recorded). `COMMENT` rows come from Console reviewers; Console detail-page views are also stored as `COMMENT` rows flagged with `metadata.system_event = \"DETAIL_VIEWED\"`. The remaining types track data edits, file changes, tags, and AML monitoring activity. STATUS_UPDATED rows are also written by system actors (e.g. AML ongoing monitoring with actor_type SYSTEM, and automatic feature-status updates), not only by the update-status endpoint and manual review posts. `FEATURE_OVERRIDE_PENDING`, `FEATURE_OVERRIDE_APPLIED` and `FEATURE_OVERRIDE_CANCELLED` record an override of one check's status (`PATCH /v3/session/{session_id}/features/{node_id}/update-status/`): requested to wait for the flow to finish, applied, or cancelled. They carry the check's status in `previous_value` / `new_value` (`{\"status\": ...}`), leave `previous_status` and `new_status` `null`, and name the override in `metadata` (`feature_override_id`, `node_id`, `feature_model`, `forced`, `state`, `cancel_reason`). The entry for the request carries the reviewer's `comment`; an override applied when the flow finishes is recorded with `actor_type` `SYSTEM`.", + "example": "STATUS_UPDATED" + }, + "actor_display": { + "type": "string", + "description": "Human-readable author label, resolved in order: actor's full name → Console account identifier → local part of `actor_email` → `\"API Client\"` (for `API_KEY` actors) → `\"System\"` (for `SYSTEM` actors) → `\"Unknown\"`. Entries created via `POST …/reviews/` always display `\"Unknown\"` because that endpoint records no actor attribution.", + "example": "API Client" + }, + "actor_email": { + "type": "string", + "nullable": true, + "description": "Email of the Console user who performed the action. `null` for API-key and system actors.", + "example": null + }, + "actor_type": { + "type": "string", + "enum": [ + "API_KEY", + "CONSOLE_USER", + "SYSTEM" + ], + "description": "Who performed the action: an API key, a Console user, or Didit itself (`SYSTEM`, e.g. AML ongoing monitoring). Entries created via `POST …/reviews/` carry the default `CONSOLE_USER` regardless of the caller.", + "example": "API_KEY" + }, + "new_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Session status recorded by this entry (for status updates). `null` for comments and data-change entries.", + "example": "Approved" + }, + "previous_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Status the session had before the change. Populated on `STATUS_UPDATED` entries written by the update-status endpoint; `null` on entries added via `POST …/reviews/`. Also populated on system-written STATUS_UPDATED rows.", + "example": "In Review" + }, + "comment": { + "type": "string", + "nullable": true, + "description": "Free-text note attached to the entry, or `null`.", + "example": "Document re-checked manually; address matches." + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + }, + "description": "Console users `@email`-mentioned in the comment. Always empty for entries created via this API — mention processing is a Console-only feature.", + "example": [] + }, + "previous_value": { + "type": "object", + "nullable": true, + "description": "Snapshot of the changed data before the action, e.g. `{\"status\": \"In Review\"}` on a status change. `null` when nothing was changed.", + "example": { + "status": "In Review" + } + }, + "new_value": { + "type": "object", + "nullable": true, + "description": "Snapshot of the changed data after the action, e.g. `{\"status\": \"Approved\"}`. `null` when nothing was changed.", + "example": { + "status": "Approved" + } + }, + "changed_fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the fields modified by this activity (e.g. `[\"status\"]`). Empty for comments and for entries added via `POST …/reviews/`.", + "example": [ + "status" + ] + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Extra context for system-generated entries. Console detail-page views carry `{\"system_event\": \"DETAIL_VIEWED\", \"detail_type\": \"verification\"}` so they can be filtered out of notification logic. A `COMMENT` its author edited in the Console carries `edited_at`, the ISO 8601 time of the last edit (`comment` and `mentioned_emails` then hold the edited text and its mentions). `null` for ordinary entries.", + "example": null + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the entry was written, ISO 8601 UTC.", + "example": "2026-06-12T00:19:31.918735Z" + } + } + }, + "QuestionnaireListItem": { + "type": "object", + "description": "One questionnaire version in the list view.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Per-version UUID. Use as `{questionnaire_uuid}` for get/update/delete and as `questionnaire_uuid` in a workflow's QUESTIONNAIRE feature config." + }, + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, + "default_language": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "is_simple_questionnaire": { + "type": "boolean", + "description": "True for linear questionnaires created through this API; false for graph-based questionnaires built in the Console." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "question_types": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Uppercase element types of the answerable questions, in creation order (e.g. `SHORT_TEXT`, `MULTIPLE_CHOICE`, `FILE_UPLOAD`). Structural `SECTION_HEADER` and `SEPARATOR` elements are excluded. One entry per question, so values can repeat." + }, + "workflow_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of workflows using this questionnaire. Currently always `[]` on this endpoint (populated only in the Console listing)." + }, + "questionnaire_group_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier that groups all versions of the same questionnaire." + }, + "version": { + "type": "integer", + "description": "Version number within the group, starting at 1." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ] + }, + "published_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this version was published. `null` for drafts." + }, + "is_editable": { + "type": "boolean", + "description": "True when this version is a `draft`." + } + } + }, + "SimpleQuestionnaireRequest": { + "type": "object", + "description": "Create a simple linear questionnaire. The API converts `form_elements` into the internal questionnaire graph.", + "required": [ + "title", + "languages", + "form_elements" + ], + "properties": { + "title": { + "type": "string", + "description": "Internal name for the questionnaire." + }, + "description": { + "type": "string", + "nullable": true + }, + "languages": { + "type": "array", + "description": "Languages included in every label. Must include `en`.", + "items": { + "type": "string" + }, + "example": [ + "en" + ] + }, + "default_language": { + "type": "string", + "description": "Default language. Must be included in `languages`.", + "example": "en" + }, + "is_active": { + "type": "boolean", + "default": true + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Omit this field to create a published questionnaire ready for workflows." + }, + "form_elements": { + "type": "array", + "minItems": 1, + "description": "Questions in the exact order users should see them. Branching and conditional fields are not supported.", + "items": { + "$ref": "#/components/schemas/SimpleQuestionnaireElement" + } + } + } + }, + "SimpleQuestionnaireUpdateRequest": { + "type": "object", + "description": "Update questionnaire metadata or replace the full linear `form_elements` list.", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, + "default_language": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ] + }, + "form_elements": { + "type": "array", + "minItems": 1, + "description": "If provided, this replaces the full linear question list.", + "items": { + "$ref": "#/components/schemas/SimpleQuestionnaireElement" + } + } + } + }, + "SimpleQuestionnaireElement": { + "type": "object", + "required": [ + "element_type", + "label" + ], + "properties": { + "id": { + "type": "string", + "description": "Optional stable question id. If omitted, the API generates `q1`, `q2`, etc. Use lowercase letters, numbers, and underscores for readability.", + "example": "source_of_funds" + }, + "element_type": { + "type": "string", + "description": "Question type. The API accepts these lowercase values and their uppercase equivalents.", + "enum": [ + "short_text", + "long_text", + "dropdown", + "single_choice", + "multiple_choice", + "number", + "image", + "file_upload", + "time", + "email", + "address", + "phone", + "country", + "date_picker", + "consent", + "section_header", + "separator", + "heading", + "paragraph" + ] + }, + "label": { + "type": "object", + "description": "Question label by language code, for example `{ \"en\": \"Source of funds\" }`.", + "additionalProperties": { + "type": "string" + } + }, + "description": { + "type": "object", + "nullable": true, + "additionalProperties": { + "type": "string" + } + }, + "placeholder": { + "type": "object", + "nullable": true, + "additionalProperties": { + "type": "string" + } + }, + "is_required": { + "type": "boolean", + "default": false + }, + "options": { + "type": "array", + "description": "Use for `dropdown`, `single_choice`, and `multiple_choice`. If `value` is omitted, the API generates one from the option label.", + "items": { + "type": "object", + "required": [ + "label" + ], + "properties": { + "value": { + "type": "string" + }, + "label": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "max_files": { + "type": "integer", + "minimum": 1, + "maximum": 5, + "description": "Use with `file_upload` or `image`." + }, + "validation": { + "type": "object", + "nullable": true, + "description": "Optional format validation for `short_text`, `long_text`, or `number`. Enforced while the user fills the field (they cannot proceed until it passes) and re-checked by the API on submission.", + "properties": { + "pattern": { + "type": "string", + "description": "Regular expression (ECMA-262 subset) the value must fully match. For `short_text` and `long_text`. Do not add ^/$ anchors; the whole value is matched." + }, + "min_length": { + "type": "integer", + "minimum": 0, + "description": "Minimum number of characters. For `short_text` and `long_text`." + }, + "max_length": { + "type": "integer", + "minimum": 0, + "description": "Maximum number of characters. For `short_text` and `long_text`." + }, + "min": { + "type": "number", + "description": "Minimum numeric value. For `number`." + }, + "max": { + "type": "number", + "description": "Maximum numeric value. For `number`." + }, + "error_message": { + "type": "object", + "description": "Translated message shown when validation fails, keyed by language code.", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "QuestionnaireDetail": { + "type": "object", + "description": "Full questionnaire detail returned from GET, POST (create) and PATCH (update) endpoints.", + "properties": { + "questionnaire_id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of the questionnaire. Use this id when referencing the questionnaire from a workflow or in subsequent update/delete requests." + }, + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, + "default_language": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "is_simple_questionnaire": { + "type": "boolean" + }, + "graph": { + "type": "object", + "description": "Graph structure with start_node and nodes map." + }, + "sections": { + "type": "array", + "description": "Questionnaire content grouped into sections (derived from the graph).", + "items": { + "type": "object" + } + }, + "questionnaire_group_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier that groups all versions of the same questionnaire." + }, + "version": { + "type": "integer" + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ] + }, + "published_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "is_editable": { + "type": "boolean", + "description": "True when the questionnaire can still be edited in place (draft versions)." + } + } + }, + "UserListItem": { + "type": "object", + "description": "A verified user.", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit's stable internal UUID for this user." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your unique identifier for this user (passed when creating sessions). This can be null when no vendor identifier was supplied." + }, + "display_name": { + "type": "string", + "nullable": true, + "description": "Custom display name set by you" + }, + "full_name": { + "type": "string", + "nullable": true, + "description": "Full name extracted from verified documents" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "effective_name": { + "type": "string", + "nullable": true, + "description": "Best available name: display_name if set, otherwise full_name" + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "Lifecycle status of the user record (NOT a session status). `ACTIVE` is the default, `FLAGGED` marks the user for manual attention, `BLOCKED` prevents new sessions for this `vendor_data`." + }, + "portrait_image_url": { + "type": "string", + "nullable": true, + "description": "Presigned URL of the user's portrait photo (expires after a few hours)" + }, + "session_count": { + "type": "integer", + "description": "Total number of verification sessions for this user" + }, + "approved_count": { + "type": "integer", + "description": "Number of approved sessions" + }, + "declined_count": { + "type": "integer", + "description": "Number of declined sessions" + }, + "in_review_count": { + "type": "integer", + "description": "Number of sessions in review" + }, + "issuing_states": { + "type": "array", + "items": { + "type": "string" + }, + "description": "ISO 3166-1 alpha-3 codes of issuing countries seen on this user's approved ID documents, e.g. `[\"USA\", \"ESP\"]`. Empty array when none." + }, + "approved_emails": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Verified email addresses collected from this user's approved sessions, e.g. `[\"john@example.com\"]`." + }, + "approved_phones": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Verified phone numbers collected from this user's approved sessions, e.g. `[\"+14155551234\"]`." + }, + "features": { + "type": "object", + "description": "Aggregated per-feature status across all of this user's sessions. Possible keys: `ID_VERIFICATION`, `NFC`, `LIVENESS`, `FACE_MATCH`, `POA`, `QUESTIONNAIRE`, `EMAIL_VERIFICATION`, `PHONE`, `AML`, `IP_ANALYSIS`, `AGE_ESTIMATION`, `DATABASE_VALIDATION`. Possible values: `Approved`, `Declined`, `In Review`, `Not Finished`, `Resub Requested`." + }, + "features_list": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "description": "Same data as `features`, as an ordered array of `{feature, status}` objects." + }, + "last_session_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent session" + }, + "first_session_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the first session" + }, + "last_activity_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent activity on this user (session, transaction, status change, data edit, etc.)." + }, + "tags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + } + } + }, + "description": "Tags assigned to this user" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BlocklistItem": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email" + ] + }, + "session_id": { + "type": "string", + "format": "uuid" + }, + "session_number": { + "type": "integer" + }, + "blocklisted_at": { + "type": "string", + "format": "date-time" + }, + "face_image": { + "type": "string", + "nullable": true + }, + "document_type": { + "type": "string", + "nullable": true + }, + "document_number": { + "type": "string", + "nullable": true + }, + "phone_number": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + } + } + }, + "UserDetailItem": { + "type": "object", + "description": "Full user detail. Extends UserListItem with metadata, comments, and updated_at.", + "allOf": [ + { + "$ref": "#/components/schemas/UserListItem" + }, + { + "type": "object", + "properties": { + "metadata": { + "type": "object", + "description": "Custom metadata JSON you attached to this user. Defaults to `{}`." + }, + "tags": { + "type": "array", + "description": "Tag assignments. NOTE: on detail responses each entry is a tag *link* object (`{uuid, tag: {...}, added_by_email, added_by_name, created_at}`), unlike the flat `{uuid, name, color}` shape used on list responses.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the tag assignment (not the tag itself)." + }, + "tag": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ] + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "added_by_email": { + "type": "string", + "nullable": true + }, + "added_by_name": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + } + }, + "comments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "comment_type": { + "type": "string", + "enum": [ + "COMMENT", + "STATUS_CHANGED", + "METADATA_UPDATED", + "TAG_ADDED", + "TAG_REMOVED", + "PROFILE_UPDATED" + ] + }, + "comment": { + "type": "string", + "nullable": true + }, + "actor_email": { + "type": "string", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "previous_status": { + "type": "string", + "nullable": true, + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED", + null + ] + }, + "new_status": { + "type": "string", + "nullable": true, + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED", + null + ] + }, + "previous_value": { + "type": "object", + "nullable": true, + "description": "Previous field values for PROFILE_UPDATED entries." + }, + "new_value": { + "type": "object", + "nullable": true, + "description": "New field values for PROFILE_UPDATED entries." + }, + "changed_fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the fields changed by a PROFILE_UPDATED entry." + }, + "metadata": { + "type": "object", + "nullable": true + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string" + } + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "description": "Activity log and comments for this user (status changes, profile edits, manual notes)." + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + ] + }, + "BusinessListItem": { + "type": "object", + "description": "A verified business.", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit's stable internal UUID for this business." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your unique identifier for this business (passed when creating sessions). This can be null when no vendor identifier was supplied." + }, + "display_name": { + "type": "string", + "nullable": true, + "description": "Custom display name set by you" + }, + "legal_name": { + "type": "string", + "nullable": true, + "description": "Official legal name from registry or manual entry" + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Company registration or incorporation number" + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "Country of incorporation (ISO 3166-1 alpha-2, e.g. `GB`, `US`)." + }, + "effective_name": { + "type": "string", + "nullable": true, + "description": "Best available name: display_name if set, otherwise legal_name" + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "Lifecycle status of the business record (NOT a session status). `ACTIVE` is the default, `FLAGGED` marks it for manual attention, `BLOCKED` prevents new sessions for this `vendor_data`." + }, + "session_count": { + "type": "integer", + "description": "Total number of verification sessions for this business" + }, + "approved_count": { + "type": "integer", + "description": "Number of approved sessions" + }, + "declined_count": { + "type": "integer", + "description": "Number of declined sessions" + }, + "in_review_count": { + "type": "integer", + "description": "Number of sessions in review" + }, + "features": { + "type": "object", + "description": "Aggregated per-feature status across all of this business's KYB sessions. Currently the only key is `KYB` (the registry company check status). Possible values: `Approved`, `Declined`, `In Review`, `Not Finished`, `Resub Requested`." + }, + "features_list": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string" + }, + "status": { + "type": "string" + } + } + }, + "description": "Same data as `features`, as an ordered array of `{feature, status}` objects." + }, + "last_session_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent session" + }, + "first_session_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the first session" + }, + "last_activity_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the most recent activity (status change, session update, etc.)" + }, + "tags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + } + } + }, + "description": "Tags assigned to this business" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BusinessDetailItem": { + "type": "object", + "description": "Full business detail. Extends BusinessListItem with metadata and updated_at.", + "allOf": [ + { + "$ref": "#/components/schemas/BusinessListItem" + }, + { + "type": "object", + "properties": { + "metadata": { + "type": "object", + "description": "Custom metadata JSON you attached to this business. Defaults to `{}`." + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + ] + }, + "TransactionDetail": { + "type": "object", + "description": "Full monitoring record for one transaction, as returned by `GET /v3/transactions/{transaction_id}/` and `POST /v3/transactions/`.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit-stable transaction identifier. Use as `{transaction_id}` for follow-up calls." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Environment of the application that owns the transaction. A `sandbox` transaction is stored but never evaluated, screened, notified or billed, so its empty `rule_runs` means \"not evaluated\"." + }, + "transaction_number": { + "type": "integer", + "description": "Application-scoped sequential transaction number shown in Console (e.g. `4123`)." + }, + "txn_id": { + "type": "string", + "description": "The `transaction_id` you supplied at create time (max 128 chars). Unique per application." + }, + "txn_date": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred (from `transaction_at`; defaults to submission time)." + }, + "zone_id": { + "type": "string", + "nullable": true, + "description": "IANA time zone identifier provided at create time." + }, + "transaction_type": { + "type": "string", + "description": "Top-level category as stored: `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet`, `gamblingLimitChange`, `gamblingBonusChange`, `auditTrailEvent`. Note multi-word values are echoed back in camelCase even when submitted in snake_case." + }, + "action_type": { + "type": "string", + "description": "Sub-type within the category (e.g. `deposit`, `withdrawal`, `transfer`). Defaults to the category when not supplied." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject. Stored uppercase regardless of the casing submitted (`in`/`out`/`inbound`/`outbound` are accepted on input)." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Current monitoring verdict. Transactions are created `APPROVED`; rules may flip them to `IN_REVIEW`/`DECLINED` synchronously." + }, + "source": { + "type": "string", + "enum": [ + "LIVE", + "IMPORTED" + ], + "description": "How the record originated: `LIVE` for transactions submitted through the API or SDK, `IMPORTED` for transactions loaded through the console import." + }, + "amount": { + "type": "string", + "description": "Transaction amount as a decimal string with trailing zeros stripped (e.g. `\"1500\"`, `\"0.5\"`, `\"0.123456789012345678\"`). Up to 18 decimal places." + }, + "currency": { + "type": "string", + "description": "Currency code of `amount`. Any ISO 4217 alphabetic code is accepted (e.g. `EUR`, `USD`, `NGN`), as is a crypto asset symbol (e.g. `BTC`). Prefer a currently-circulating code. Not every accepted code has an exchange rate: when none is available for the code (for example the ISO accounting unit `XSU`, or a crypto symbol Didit does not price), automatic conversion fails and `preferred_currency_amount` is returned null unless the request supplied a pre-converted `amount_in_default_currency`." + }, + "currency_type": { + "type": "string", + "nullable": true, + "description": "`fiat` or `crypto`, as submitted in `currency_kind`." + }, + "amount_in_default_currency": { + "type": "string", + "nullable": true, + "description": "Pre-converted amount you supplied, as a decimal string with trailing zeros stripped." + }, + "default_currency_code": { + "type": "string", + "nullable": true + }, + "preferred_currency_amount": { + "type": "string", + "nullable": true, + "description": "`amount` converted to the application's preferred currency, when available." + }, + "preferred_currency_code": { + "type": "string", + "nullable": true + }, + "payment_details": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference or memo from the submission." + }, + "payment_txn_id": { + "type": "string", + "nullable": true, + "description": "External payment system reference (e.g. blockchain transaction hash) from `payment_reference_id`." + }, + "score": { + "type": "integer", + "description": "Risk score accumulated by rules (typically 0–100). Higher = riskier." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ], + "description": "Categorical risk severity set by rules/providers (`UNKNOWN`, `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`). `null` until something sets it." + }, + "decision_reason_code": { + "type": "string", + "nullable": true, + "description": "Machine-readable reason for the current `status`." + }, + "decision_reason_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label for `decision_reason_code`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Convenience copy of the applicant's `vendor_data`." + }, + "metadata": { + "type": "object", + "description": "Snapshot of the `subject` and `counterparty` payloads as submitted at create time." + }, + "props": { + "type": "object", + "description": "The `custom_properties` you supplied at create time. Each key is addressable in rule conditions as `custom_values.`." + }, + "tags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ], + "description": "Whether the tag is a Didit preset or a custom tag." + } + } + }, + "description": "Tags attached to the transaction (manually or by rules)." + }, + "parties": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "role": { + "type": "string", + "enum": [ + "APPLICANT", + "REMITTER", + "BENEFICIARY", + "COUNTERPARTY" + ], + "description": "Party role. The create endpoint assigns `APPLICANT` (the subject) and `COUNTERPARTY`." + }, + "entity_type": { + "type": "string", + "enum": [ + "individual", + "company", + "external", + "unhostedWallet" + ] + }, + "kind": { + "type": "string", + "enum": [ + "USER", + "BUSINESS", + "EXTERNAL" + ], + "description": "`USER`/`BUSINESS` parties link back to a Didit-owned entity via `vendor_data`. `EXTERNAL` parties have no Didit entity behind them." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your internal user/business identifier." + }, + "full_name": { + "type": "string", + "nullable": true + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "Country code from the submitted `address.country` (typically ISO 3166-1 alpha-3)." + }, + "dob": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth from the submitted `date_of_birth`." + }, + "address": { + "type": "object", + "description": "Address object as submitted. `{}` when omitted." + }, + "institution_info": { + "type": "object", + "description": "Institution details as submitted. `{}` when omitted." + }, + "device": { + "type": "object", + "description": "Normalized device context, including server-side IP enrichment under network_context (geolocation, VPN/data-center flags) when an IP was provided.", + "properties": { + "fingerprint": { + "type": "string", + "description": "Unique device fingerprint." + }, + "persistent_device_id": { + "type": "string", + "description": "Stable cross-session device identifier (v2 fingerprint schema)." + }, + "user_agent": { + "type": "string", + "description": "Browser or app user agent string." + }, + "browser_family": { + "type": "string", + "description": "Browser family (e.g. Chrome, Firefox, Safari)." + }, + "browser_version": { + "type": "string", + "description": "Browser version string." + }, + "platform": { + "type": "string", + "description": "Platform identifier (web, ios, android, macos, windows, linux)." + }, + "os_family": { + "type": "string", + "description": "Operating system family (e.g. iOS, Android, Windows)." + }, + "os_version": { + "type": "string", + "description": "Operating system version string." + }, + "accept_language": { + "type": "string", + "description": "Accept-Language header value." + }, + "session_id": { + "type": "string", + "description": "Session identifier (stored in metadata)." + }, + "session_age_ms": { + "type": "number", + "description": "Session age in milliseconds." + }, + "network_context": { + "type": "object", + "description": "Network context with IP address and geo-location.", + "properties": { + "ip_address": { + "type": "string", + "description": "Client IP address." + }, + "ip_country_code": { + "type": "string", + "description": "Geo-resolved country of the IP (ISO 3166-1 alpha-3)." + }, + "latitude": { + "type": "number", + "description": "Geographic latitude of the IP." + }, + "longitude": { + "type": "number", + "description": "Geographic longitude of the IP." + } + } + } + } + }, + "external_party_snapshot": { + "type": "object", + "nullable": true, + "description": "Frozen copy of the party data for `EXTERNAL` parties. `null` while the party is linked to a Didit user or business." + } + } + }, + "description": "Transaction parties (applicant, counterparty)." + }, + "payment_methods": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "payment_method_type": { + "type": "string", + "enum": [ + "bank_card", + "bank_account", + "e_wallet", + "crypto_wallet", + "unhosted_wallet", + "other" + ] + }, + "label": { + "type": "string", + "nullable": true + }, + "fingerprint": { + "type": "string", + "nullable": true, + "description": "SHA-256 over `type:account_id:issuing_country` — stable identifier to correlate reuse of the same instrument." + }, + "account_id": { + "type": "string", + "nullable": true, + "description": "Account identifier (IBAN, wallet address, card token, …)." + }, + "issuing_country": { + "type": "string", + "nullable": true + }, + "owner_kind": { + "type": "string", + "enum": [ + "USER", + "BUSINESS", + "EXTERNAL" + ] + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your identifier for the owning user/business." + }, + "owner_name": { + "type": "string", + "nullable": true + }, + "institution_info": { + "type": "object" + }, + "details": { + "type": "object", + "description": "Raw `payment_method` object as submitted." + }, + "external_owner_snapshot": { + "type": "object", + "nullable": true + }, + "role": { + "type": "string", + "enum": [ + "SOURCE", + "DESTINATION", + "FUNDING", + "BENEFICIARY" + ], + "description": "How this method was used in the transaction. The create endpoint assigns `SOURCE`/`DESTINATION` from the direction (outbound: subject = SOURCE; inbound: subject = DESTINATION)." + } + } + }, + "description": "Payment methods linked to the transaction." + }, + "documents": { + "type": "array", + "description": "Files attached to the transaction from the console. `[]` when none.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string", + "nullable": true + }, + "note": { + "type": "string", + "nullable": true + }, + "uploaded_by": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "files": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "file_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived signed download URL; fetch it when needed rather than storing it." + }, + "original_filename": { + "type": "string", + "nullable": true + }, + "file_size": { + "type": "integer", + "nullable": true + } + } + } + } + } + } + }, + "activities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "activity_type": { + "type": "string", + "description": "E.g. `TRANSACTION_CREATED`, `TRANSACTION_NOTE_ADDED`, `TRANSACTION_STATUS_CHANGED`." + }, + "source": { + "type": "string" + }, + "status": { + "type": "string", + "nullable": true + }, + "title": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object" + }, + "occurred_at": { + "type": "string", + "format": "date-time" + } + } + }, + "description": "Activity log entries (creation, notes, manual reviews, status changes)." + }, + "alerts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "severity": { + "type": "string", + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ] + }, + "status": { + "type": "string", + "enum": [ + "OPEN", + "INVESTIGATING", + "AWAITING_USER", + "PENDING_SAR", + "SAR_FILED", + "RESOLVED", + "DISMISSED" + ] + }, + "source": { + "type": "string", + "enum": [ + "RULE", + "PROVIDER", + "MANUAL" + ] + }, + "metadata": { + "type": "object" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "description": "Always empty today. An open alert is a transaction with status `IN_REVIEW` or `AWAITING_USER`." + }, + "rule_runs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "rule": { + "type": "string", + "format": "uuid", + "description": "UUID of the rule that ran." + }, + "rule_title": { + "type": "string" + }, + "rule_description": { + "type": "string", + "nullable": true + }, + "rule_category": { + "type": "string" + }, + "matched": { + "type": "boolean", + "description": "Whether the rule's conditions matched this transaction. A run is recorded only for matched rules, so live evaluations always carry `true`." + }, + "is_test": { + "type": "boolean", + "description": "True when the rule ran in TEST mode (no effect on status/score)." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ] + }, + "severity": { + "type": "string", + "nullable": true + }, + "score_delta": { + "type": "integer", + "description": "Score added by this rule's `add_score` actions." + }, + "status_target": { + "type": "string", + "nullable": true, + "description": "Status set by a `change_status` action, if any." + }, + "action_summary": { + "type": "string", + "nullable": true, + "description": "Summary of the actions the rule executed, joined with `; ` (e.g. `score +30; change_status:IN_REVIEW; add_tag:high_risk_country`). Parts: `score +N`, `change_status:`, `add_tag:`, `add_note`, `add_to_list`, `open_case`. `null` when the rule executed no action." + }, + "metadata": { + "type": "object", + "description": "Evaluation detail: `scope_match`, `conditions[]` and `aggregations[]`. Each condition entry carries `field`, `operator`, `expected_value`, the `current_value` read from the transaction, `matched`, `group_index` and `group_logic`. Each aggregation entry (velocity / count / sum) carries `metric`, `field`, `operator`, `expected_value`, the `computed_value` the aggregate evaluated to, `window`, `filters`, `matched` and `sample_references`; deferred async aggregations have `computed_value: null` and `deferred: true`. A nested condition group is one `conditions[]` entry with `operator` `any_of` or `all_of`, `field` null, and its members' results under its own `conditions`." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "description": "Per-rule execution results — which rule fired, with what score impact." + }, + "provider_results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "provider": { + "type": "string" + }, + "result_type": { + "type": "string", + "enum": [ + "FIAT_MONITORING", + "WALLET_SCREENING", + "TRANSACTION_SCREENING", + "TRAVEL_RULE", + "NETWORK_GRAPH", + "CUSTOM" + ] + }, + "status": { + "type": "string" + }, + "severity": { + "type": "string", + "nullable": true + }, + "score": { + "type": "integer" + }, + "summary": { + "type": "string", + "nullable": true + }, + "payload": { + "type": "object", + "description": "Raw provider response payload." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "description": "Raw provider payloads (AML screening, blockchain analytics, etc.)." + }, + "travel_rule": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TransactionTravelRule" + } + ], + "description": "Travel Rule transfer or check. Populated when `travel_rule_details` was submitted, and also when Travel Rule is enabled and the transaction was created with `transaction_category: travelRule` (Didit initiates the exchange itself, even without `travel_rule_details`). `null` when no transfer or check exists. On an inbound transfer, the counterparty's `originator_data` and `beneficiary_data` stay masked (`data_masked: true`) until wallet ownership is confirmed." + }, + "network_snapshot": { + "type": "object", + "nullable": true, + "description": "Graph snapshot of related transactions/parties used by the rules engine. Either the `network_snapshot` submitted with the transaction or the graph Didit builds from a successful AML wallet screening (which replaces a submitted one). `null` when neither exists.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "nodes": { + "type": "array", + "items": { + "type": "object" + } + }, + "edges": { + "type": "array", + "items": { + "type": "object" + } + }, + "metrics": { + "type": "object" + } + } + }, + "remediation": { + "type": "object", + "nullable": true, + "description": "Remediation session offered to the user when re-verification is required. Superseded by `action_required`, which is the canonical block; kept for backward compatibility.", + "properties": { + "session_id": { + "type": "string" + }, + "session_token": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "status": { + "type": "string" + } + } + }, + "action_required": { + "type": "object", + "nullable": true, + "description": "Pending end-user action required to complete this transaction, or null when there is none. The Didit SDKs auto-launch the action by default and refresh the transaction when it finishes. Two variants, discriminated by `type`: `verification_session` (a hosted verification session created by a rule action) and `wallet_ownership` (a wallet-ownership widget session auto-minted for a Travel Rule transfer that needs proof of wallet control, gated by the `auto_wallet_verification` Travel Rule setting). When both could apply, `wallet_ownership` takes precedence because it blocks the Travel Rule exchange.", + "oneOf": [ + { + "$ref": "#/components/schemas/TransactionVerificationSessionAction" + }, + { + "$ref": "#/components/schemas/TransactionWalletOwnershipAction" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "verification_session": "#/components/schemas/TransactionVerificationSessionAction", + "wallet_ownership": "#/components/schemas/TransactionWalletOwnershipAction" + } + } + }, + "cost_breakdown": { + "type": "object", + "nullable": true, + "description": "Cost of this transaction: `total_price` and `items`, one per charge, each with `usage_type`, `price`, `details` and `created_at`. A re-screen of the same transaction is a new item." + } + } + }, + "TravelRuleSettingsDetail": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean", + "description": "Whether the managed Travel Rule negotiation flow is active for outbound `travelRule` transactions on this application." + }, + "jurisdiction": { + "type": "string", + "maxLength": 8, + "description": "Your VASP's operating jurisdiction code. Default `\"EU\"`. Didit maps it to the regime it stamps on your Travel Rule transactions as `travel_rule_regime` (for example `EU` or an EU member code becomes `eu`), which transaction rules can key on. Other Didit customers see it as your jurisdiction in the VASP directory once the Didit network lists your profile." + }, + "name_matching_strictness": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "How strictly beneficiary names must match before a transfer can reach COMPLETED. `CONTAINS` accepts one name whose words all appear in the other." + }, + "confirmation_timeout_hours": { + "type": "integer", + "minimum": 1, + "description": "Hours an AWAITING_COUNTERPARTY transfer may remain unconfirmed before the periodic sweeper expires it. Default 48." + }, + "timeout_outcome": { + "type": "string", + "enum": [ + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "What happens to the underlying transaction when a transfer expires. Default HOLD. sunrise_policy and non_responsive_policy take precedence when they apply and are set." + }, + "threshold_amount": { + "type": "string", + "description": "Decimal string, in your application's default currency. Minimum transfer amount that triggers a managed exchange; transfers below it resolve straight to NOT_APPLICABLE. Didit compares the transaction amount converted to your default currency, or the `amount_in_default_currency` you send; when no converted amount is available, the threshold does not apply. Defaults to \"0.00\" (EU TFR applies no minimum amount)." + }, + "ownership_validation_mode": { + "type": "string", + "enum": [ + "FULL", + "SIMPLIFIED", + "AUTOMATED" + ], + "description": "How much proof a wallet's ownership takes. `FULL` always needs a proof (signed message, test deposit or reviewed screenshot), never the end user's word. `SIMPLIFIED` accepts a self-declaration below `simplified_validation_threshold` and a proof above it. `AUTOMATED` lets the address book and earlier proofs answer on their own, self-declarations included while `allow_self_declaration` is true. Default `AUTOMATED`." + }, + "simplified_validation_threshold": { + "type": "string", + "description": "Decimal string, default \"0.00\". In `SIMPLIFIED` mode a transfer below this amount (in the threshold currency) accepts a self-declaration. A transfer with no amount in that currency counts as above it." + }, + "data_sharing_fields": { + "$ref": "#/components/schemas/TravelRuleDataSharingFields" + }, + "matching_rules": { + "$ref": "#/components/schemas/TravelRuleMatchingRules" + }, + "legal_name": { + "type": "string", + "description": "Your VASP's legal name. Required (non-blank) to set is_enabled to true." + }, + "lei": { + "type": "string", + "description": "Your Legal Entity Identifier, surfaced in the VASP directory." + }, + "compliance_email": { + "type": "string", + "format": "email", + "description": "Compliance contact email for your VASP profile. The Didit network lists your profile only once this address is confirmed through an emailed link (POST /v3/travel-rule/settings/compliance-email/confirmation/); changing it needs a new confirmation." + }, + "is_discoverable": { + "type": "boolean", + "description": "Whether your VASP profile can be listed in the Didit network: found in GET /v3/travel-rule/vasps/ by other Didit customers and reached on the INTERNAL rail. Listing also needs a live application, Travel Rule on, a legal name and a confirmed compliance email; didit_network says which are missing. Default true." + }, + "inbound_auto_accept": { + "type": "boolean", + "description": "When true, an inbound TRP inquiry whose beneficiary wallet is a verified address-book entry with a matching name is approved automatically. When false, that inquiry waits in ON_HOLD for you to approve or deny it with the ownership endpoint. INTERNAL-rail and after-deposit transfers are not affected. Default true." + }, + "allow_self_declaration": { + "type": "boolean", + "description": "When true, the wallet-ownership widget offers self-declaration as a fallback proof method. Default true." + }, + "allow_screenshot_proof": { + "type": "boolean", + "description": "When true, the wallet-ownership widget offers screenshot upload as an ownership evidence method. Default true." + }, + "auto_wallet_verification": { + "type": "boolean", + "description": "When true, submitting an outbound Travel Rule transfer that needs end-user proof of wallet control auto-mints a wallet-ownership widget session bound to the transaction and exposes it in the transaction's action_required block. Default true." + }, + "vasp_attribution_enabled": { + "type": "boolean", + "description": "When true, an outbound Travel Rule transfer that no rail can route is attributed through blockchain analytics: the destination wallet is resolved to its owning VASP, the entity is recorded on the transfer and in the VASP directory, and the rails are re-run once with the attributed name. Default true." + }, + "counterparty_screening_enabled": { + "type": "boolean", + "description": "Opt-in sanctions and adverse-media screening of counterparty VASPs, billed per screening at the Company AML Screening price. When on, Didit screens the counterparty an outbound exchange reaches (reusing a screening younger than 30 days) and you can screen one with POST /v3/travel-rule/vasps/{vasp_id}/screening/. Default false: nothing is screened or billed." + }, + "sunrise_policy": { + "type": "string", + "enum": [ + "", + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "What an expired transfer does to its transaction when the counterparty is a directory entry in a jurisdiction on sunrise_jurisdictions, which does not enforce the Travel Rule yet: HOLD moves it to review, REJECT declines it, PROCEED leaves its status untouched. Empty (the default) follows timeout_outcome. Applies before non_responsive_policy." + }, + "sunrise_jurisdictions": { + "type": "array", + "items": { + "type": "string" + }, + "readOnly": true, + "description": "Read-only. The countries (ISO 3166 alpha-2) Didit treats as in a Travel Rule sunrise period, where sunrise_policy applies. Currently CO, KH and VN. Any other jurisdiction, and an empty or unknown one, follows timeout_outcome." + }, + "non_responsive_policy": { + "type": "string", + "enum": [ + "", + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "What an expired transfer does to its transaction when the counterparty is a directory entry whose responsiveness is UNRESPONSIVE: HOLD, REJECT or PROCEED, as for timeout_outcome. Empty (the default) follows timeout_outcome. sunrise_policy applies first when both fit." + }, + "travel_address": { + "type": "string", + "description": "Read-only. Your VASP's own travel address - the encoded inbound TRP inquiry endpoint other VASPs use to reach you. Empty while `is_enabled` is false." + }, + "disabled_networks": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA" + ] + }, + "description": "Networks opted out of platform membership. Does not disable your connected own memberships or provision unavailable networks.", + "default": [] + }, + "networks": { + "type": "object", + "readOnly": true, + "description": "Per network, how this application would send and why it cannot. No network, including GTR, is included by default: a network is reachable through your own connected membership, or through Didit's membership once Didit's membership of that network is live and your own participant on it is active. Not a live health or delivery check.", + "properties": { + "gtr": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "trust": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "verifyvasp": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "sygna": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + } + } + }, + "didit_network": { + "type": "object", + "readOnly": true, + "description": "Whether the Didit network lists your VASP profile, so other Didit customers find it in the directory and reach it on the INTERNAL rail, and each condition it still misses.", + "properties": { + "listed": { + "type": "boolean" + }, + "missing": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "travel_rule_off", + "sandbox_application", + "not_discoverable", + "legal_name_missing", + "compliance_email_missing", + "compliance_email_unconfirmed", + "excluded_by_staff", + "internal_organization" + ] + }, + "description": "Empty when listed. compliance_email_unconfirmed: send the confirmation with POST /v3/travel-rule/settings/compliance-email/confirmation/ and open the emailed link. excluded_by_staff: Didit marked the application as a test, demo or internal one." + }, + "compliance_email_confirmed": { + "type": "boolean", + "description": "Whether the current compliance_email was confirmed through the emailed link." + } + } + } + } + }, + "TravelRuleSettingsUpdate": { + "type": "object", + "properties": { + "is_enabled": { + "type": "boolean" + }, + "legal_name": { + "type": "string", + "maxLength": 255 + }, + "lei": { + "type": "string", + "maxLength": 20 + }, + "jurisdiction": { + "type": "string", + "maxLength": 8 + }, + "name_matching_strictness": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "`CONTAINS` accepts one name whose words all appear in the other." + }, + "confirmation_timeout_hours": { + "type": "integer", + "minimum": 1 + }, + "timeout_outcome": { + "type": "string", + "enum": [ + "HOLD", + "REJECT", + "PROCEED" + ] + }, + "compliance_email": { + "type": "string", + "format": "email", + "description": "Compliance contact email for your VASP profile. The Didit network lists your profile only once this address is confirmed through an emailed link (POST /v3/travel-rule/settings/compliance-email/confirmation/); changing it needs a new confirmation." + }, + "is_discoverable": { + "type": "boolean", + "description": "Whether your VASP profile can be listed in the Didit network: found in GET /v3/travel-rule/vasps/ by other Didit customers and reached on the INTERNAL rail. Listing also needs a live application, Travel Rule on, a legal name and a confirmed compliance email; didit_network says which are missing. Default true." + }, + "threshold_amount": { + "type": "string", + "description": "Decimal string, zero or greater, in your application's default currency. Minimum transfer amount that triggers a managed exchange." + }, + "ownership_validation_mode": { + "type": "string", + "enum": [ + "FULL", + "SIMPLIFIED", + "AUTOMATED" + ], + "description": "See the response field of the same name." + }, + "simplified_validation_threshold": { + "type": "string", + "description": "Decimal string, 0 or more." + }, + "data_sharing_fields": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleDataSharingFields" + } + ], + "description": "Merged per entity type: an entity type you leave out keeps its current choice. A choice that cannot meet the originator obligations of your `jurisdiction` regime (EU TFR or FATF Recommendation 16), an empty list included, is refused with `400`." + }, + "matching_rules": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleMatchingRules" + } + ], + "description": "Merged per entity type, and per field inside it. A field that entity type does not compare (for example `date_of_birth` for `company`) is refused with `400`." + }, + "inbound_auto_accept": { + "type": "boolean" + }, + "allow_self_declaration": { + "type": "boolean" + }, + "allow_screenshot_proof": { + "type": "boolean", + "description": "When true, the wallet-ownership widget offers screenshot upload as an ownership evidence method. Default true." + }, + "auto_wallet_verification": { + "type": "boolean" + }, + "vasp_attribution_enabled": { + "type": "boolean" + }, + "counterparty_screening_enabled": { + "type": "boolean", + "description": "Opt-in sanctions and adverse-media screening of counterparty VASPs, billed per screening at the Company AML Screening price. When on, Didit screens the counterparty an outbound exchange reaches (reusing a screening younger than 30 days) and you can screen one with POST /v3/travel-rule/vasps/{vasp_id}/screening/. Default false: nothing is screened or billed." + }, + "sunrise_policy": { + "type": "string", + "enum": [ + "", + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "HOLD, REJECT, PROCEED, or empty to follow timeout_outcome. See the response field of the same name." + }, + "non_responsive_policy": { + "type": "string", + "enum": [ + "", + "HOLD", + "REJECT", + "PROCEED" + ], + "description": "HOLD, REJECT, PROCEED, or empty to follow timeout_outcome. See the response field of the same name." + }, + "disabled_networks": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA" + ] + }, + "description": "Networks opted out of platform membership. Does not disable your connected own memberships or provision unavailable networks.", + "default": [] + } + }, + "description": "All fields optional; PUT applies a partial update." + }, + "WalletAddressEntry": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "address": { + "type": "string", + "description": "The wallet address; `\"\"` for an entry imported by its hash only. Unique per application, chain and memo, comparing the address without regard to case. After you delete an entry, you can register the same address again." + }, + "chain": { + "type": "string", + "description": "Chain token for the address, for example `ETH` or `BTC`. When Didit routes or registers a transfer, it matches address book entries on the wallet address and, when the transfer carries a chain, on this value too, ignoring case. A transfer's chain is `beneficiary_data.chain`, filled from the transaction's crypto chain or currency symbol (for example `ETH`) when you do not send it, or the `chain` of an after-deposit registration, so use the same token everywhere. An entry with an empty chain matches any chain. A Lightning payee (a node id or a Lightning Address) registered on bitcoin is stored lowercased on `LIGHTNING`, where Lightning transfers are matched. Known chains are stored under one canonical identifier whatever spelling you send: \"ethereum\", \"Ethereum\" and \"ERC20\" are all stored as ETH, \"BNB Smart Chain\" and \"BEP20\" as BNB, \"tron\" as TRX. An unknown chain name is kept as given." + }, + "holder_vendor_data": { + "type": "string", + "description": "Your internal identifier for the holder." + }, + "holder_name": { + "type": "string", + "description": "Name of the wallet holder, used for the beneficiary name match." + }, + "entity_type": { + "type": "string", + "description": "Free text, defaults to \"individual\"." + }, + "is_ownership_verified": { + "type": "boolean", + "description": "Read-only. Whether ownership of this address has been proven." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "travel_address": { + "type": "string", + "description": "The counterparty VASP's travel address for this wallet, if known. Lets Didit route this beneficiary's transfers over the TRP rail, for the assets TRP carries (BTC, ETH, and USDC on a named network)." + }, + "address_hash": { + "type": "string", + "description": "Read-only. SHA-256 of the lowercased address, in lowercase hex. An entry imported by its hash only is matched on it." + }, + "memo": { + "type": "string", + "description": "The destination tag or memo that is part of the wallet on a memo network (XRP, XLM, EOS, ATOM, HBAR); `\"\"` for the whole address. An entry with a memo matches only transfers naming that memo; one without matches the whole address." + }, + "receiving_travel_address": { + "type": "string", + "readOnly": true, + "description": "Your per-wallet travel address for this wallet: give it to the sending VASP so its TRP inquiry names this wallet. Empty for a wallet recorded as another VASP's, while Travel Rule is off, and before your VASP profile is saved." + } + } + }, + "WalletAddressEntryCreate": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string", + "maxLength": 128, + "description": "The wallet address. An XRP X-address is stored as its classic address and its tag, so it matches transfers that name either form." + }, + "chain": { + "type": "string", + "maxLength": 32, + "description": "Chain token for the address, for example `ETH` or `BTC`. When Didit routes or registers a transfer, it matches address book entries on the wallet address and, when the transfer carries a chain, on this value too, ignoring case. A transfer's chain is `beneficiary_data.chain`, filled from the transaction's crypto chain or currency symbol (for example `ETH`) when you do not send it, or the `chain` of an after-deposit registration, so use the same token everywhere. An entry with an empty chain matches any chain. Known chains are stored under one canonical identifier whatever spelling you send: \"ethereum\", \"Ethereum\" and \"ERC20\" are all stored as ETH, \"BNB Smart Chain\" and \"BEP20\" as BNB, \"tron\" as TRX. An unknown chain name is kept as given." + }, + "holder_vendor_data": { + "type": "string", + "maxLength": 255 + }, + "holder_name": { + "type": "string", + "maxLength": 255 + }, + "entity_type": { + "type": "string", + "default": "individual", + "maxLength": 16 + }, + "self_declared": { + "type": "boolean", + "default": false, + "description": "Write-only. When true, the entry is created already ownership-verified via a SELF_DECLARATION proof. Returns 400 when `allow_self_declaration` is off in your Travel Rule settings." + }, + "travel_address": { + "type": "string", + "description": "Optional. The counterparty VASP's travel address for this wallet, enabling TRP routing for the assets TRP carries (BTC, ETH, and USDC on a named network). An invalid travel address returns 400.", + "maxLength": 512 + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "Optional. The destination tag or memo that is part of the wallet. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`. Alias: `destinationTag` or `destination_tag`." + } + } + }, + "WalletAddressEntryUpdate": { + "type": "object", + "properties": { + "holder_name": { + "type": "string", + "maxLength": 255 + }, + "holder_vendor_data": { + "type": "string", + "maxLength": 255 + }, + "entity_type": { + "type": "string", + "maxLength": 16 + }, + "travel_address": { + "type": "string", + "maxLength": 512 + } + }, + "description": "PATCH is a partial update; the response echoes only holder_name, holder_vendor_data, entity_type, travel_address, receiving_travel_address, label, and owner_type." + }, + "TravelRuleTransferDetail": { + "type": "object", + "description": "Full transfer object returned by the ownership-confirmation and finish/cancel endpoints. While an inbound transfer's wallet ownership is unconfirmed, the counterparty's `originator_data` and `beneficiary_data` come back masked (the first letter of each word is kept; the wallet, chain and VASP routing stay readable) and `data_masked` is true.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "AWAITING_COUNTERPARTY", + "ON_HOLD", + "COMPLETED", + "FINISHED", + "CANCELLED", + "EXPIRED", + "COUNTERPARTY_MISMATCHED_DATA", + "COUNTERPARTY_VASP_NOT_FOUND", + "COUNTERPARTY_VASP_NOT_REACHABLE", + "UNCONFIRMED_OWNERSHIP", + "COUNTERPARTY_VASP_GENERAL_DECLINE", + "NOT_ENOUGH_COUNTERPARTY_DATA", + "NOT_APPLICABLE" + ] + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ] + }, + "rail": { + "type": "string", + "nullable": true, + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL", + "TEST_VASP", + null + ], + "description": "The rail the transfer was routed over, or null when no rail was selected (for example below the threshold, a self-hosted counterparty wallet, an after-deposit registration, or no reachable counterparty). INTERNAL means both sides are Didit applications; EMAIL means the counterparty was reached by an emailed pickup link. TEST_VASP means the Didit Test VASP answered the sandbox transfer. Over GTR, VERIFYVASP and SYGNA, Didit reads the counterparty's answer from the network's response or by polling it every 5 minutes. TRUST only sends, so a TRUST transfer stays AWAITING_COUNTERPARTY until it expires. A network rail is used only when you have connected your own membership for that network. It is chosen after the transaction create returns, so it is null in that response." + }, + "protocol": { + "type": "string", + "nullable": true + }, + "ivms_version": { + "type": "string", + "example": "ivms101.2023" + }, + "required": { + "type": "boolean" + }, + "ownership_confirmed": { + "type": "boolean", + "nullable": true + }, + "deadline_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "timeline": { + "type": "array", + "items": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "at": { + "type": "string", + "format": "date-time" + }, + "detail": { + "type": "string" + }, + "name_match": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleNameMatch" + } + ], + "nullable": true, + "description": "The name comparison this entry decided, next to `detail`. Absent on entries that compared no name." + }, + "reason": { + "type": "string", + "description": "Present only on the entry of a transition a failed delivery caused: `email_undeliverable` or `delivery_failed`, the value the transfer's `reason` reports." + } + } + } + }, + "counterparty_vasp": { + "type": "object", + "nullable": true, + "properties": { + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "dd_score": { + "type": "integer", + "description": "Due-diligence score from 0 to 100, where higher is better. Same scale as in the VASP directory." + }, + "dd_score_breakdown": { + "type": "object", + "description": "Per-component contributions to dd_score, one object per component as in the VASP directory (jurisdiction, verification, protocol, lei, history, essentials, responsiveness), each {points, max, reason, detail}. {} until the counterparty has been assessed.", + "additionalProperties": { + "type": "object", + "properties": { + "points": { + "type": "number", + "description": "Points awarded for this component." + }, + "max": { + "type": "integer", + "description": "Maximum points this component can award." + }, + "reason": { + "type": "string", + "description": "Machine-readable code of the component's verdict." + }, + "detail": { + "type": "string", + "description": "Human-readable reason for the awarded points." + } + } + } + }, + "dd_assessed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the due-diligence assessment last ran. Null until the counterparty has been assessed." + } + } + }, + "originator_data": { + "type": "object", + "description": "Originator party. For an inbound transfer the counterparty's values are masked until ownership is confirmed." + }, + "beneficiary_data": { + "type": "object", + "description": "Beneficiary party. For an inbound transfer the counterparty's values are masked until ownership is confirmed." + }, + "name_match": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleNameMatch" + } + ], + "nullable": true, + "description": "The name comparison that decided the transfer, or null until one ran. While the parties are masked, and on the other customer's copy of an INTERNAL exchange, `transfer_name`, `compared_name`, `matched_names` and `mismatched_names` are hidden. `score` and `matched` can disagree: `matched` follows the strictness, `score` is the similarity of the two full names." + }, + "data_match": { + "$ref": "#/components/schemas/TravelRuleDataMatch" + }, + "mismatched_fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Shortcut for `data_match.mismatched_fields`. Empty when nothing differs." + }, + "data_unmasked": { + "$ref": "#/components/schemas/TravelRuleDataUnmasked" + }, + "data_masked": { + "type": "boolean", + "description": "True while the counterparty's personal data is masked in this response." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "delivery": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleDelivery" + } + ], + "description": "Where the latest message this transfer owes the other side stands. Null when the transfer never owed one (for example below `threshold_amount`, a declared self-hosted wallet, or a sunrise registration)." + }, + "discovery": { + "type": "array", + "description": "How Didit looked for the counterparty, one entry per step that ran. Empty when no discovery ran, for example on an inbound transfer.", + "items": { + "$ref": "#/components/schemas/TravelRuleDiscoveryStep" + } + }, + "reason": { + "type": "string", + "description": "Why a `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE` transfer is stuck; `\"\"` for any other status. A failed delivery decides it first, on `COUNTERPARTY_VASP_NOT_REACHABLE` only: `email_undeliverable` (the mail provider refused the compliance email and no address is left to try: when it was sent, on a reminder, or as a permanent bounce reported later) or `delivery_failed` (the rail that was delivering failed for good: a network rail refused the message or ran out of retries, a `TRP` identity host did not answer, or the counterparty's `TRP` host did not accept the inquiry). Otherwise the `discovery` trace decides it: `awaiting_end_user` (no step named a counterparty Didit can reach), `directory_match_no_route` (a step named a directory VASP that has no route), `picked_member_without_proven_wallet` (the end user picked a Didit customer that has not proven control of the wallet), or `no_directory_match` when the trace gives no other reason. A resend moves the transfer on, so the reason clears. See [Why a transfer is stuck](/transaction-monitoring/travel-rule#why-a-transfer-is-stuck)." + }, + "next_actions": { + "type": "array", + "description": "What moves the transfer on; `[]` for any status but `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE`. `email_undeliverable` offers `update_compliance_email`, then `resend`. `delivery_failed` offers `resend`. A discovery reason offers `select_counterparty_vasp`, `declare_self_hosted`, `add_vasp_hint` and `resend`.", + "items": { + "$ref": "#/components/schemas/TravelRuleNextAction" + } + }, + "chain_details": { + "$ref": "#/components/schemas/TravelRuleChainDetails" + } + } + }, + "TravelRuleOwnershipConfirmRequest": { + "type": "object", + "required": [ + "confirmed" + ], + "properties": { + "confirmed": { + "type": "boolean", + "description": "true to confirm ownership, false to deny it." + }, + "wallet_address": { + "type": "string", + "default": "", + "description": "Inbound TRP transfers: the payment address Didit returns to the originating VASP when you approve. Defaults to the matched address book entry's address, then beneficiary_data.wallet_address. On a declared self-hosted wallet transfer that names no counterparty wallet, the wallet whose ownership you verified; without it the request is rejected with 400. Ignored for other transfers." + } + } + }, + "TravelRuleTransferPatchRequest": { + "type": "object", + "description": "Provide exactly one of: payment_txn_id (finish), action=cancel, action=resend, action=update_compliance_email with compliance_email, action=update_originator_wallet with wallet_address, or action=beneficiary_vasp with beneficiary_vasp.", + "properties": { + "payment_txn_id": { + "type": "string", + "description": "The on-chain transaction hash. Requires the transfer to be an outbound transfer in COMPLETED status." + }, + "action": { + "type": "string", + "enum": [ + "cancel", + "resend", + "update_compliance_email", + "update_originator_wallet", + "beneficiary_vasp" + ], + "description": "\"cancel\" cancels a non-terminal transfer. \"resend\" re-runs routing for an outbound transfer stuck in COUNTERPARTY_VASP_NOT_FOUND, COUNTERPARTY_VASP_NOT_REACHABLE, or NOT_ENOUGH_COUNTERPARTY_DATA. \"update_compliance_email\" replaces the compliance email the request goes to, on an outbound transfer in one of those three statuses; send \"resend\" afterwards. \"update_originator_wallet\" records the wallet the funds actually left from (`wallet_address`, `memo`). \"beneficiary_vasp\" names the counterparty VASP yourself (requires `beneficiary_vasp`) for an outbound transfer in one of those statuses: it is routed to that VASP and sent again." + }, + "compliance_email": { + "type": "string", + "format": "email", + "maxLength": 254, + "description": "Required with `action: update_compliance_email`. Kept as this transfer's `beneficiary_data.counterparty_vasp_email`: it belongs to this transfer only and joins no directory entry and no contact list." + }, + "wallet_address": { + "type": "string", + "maxLength": 128, + "description": "With `action: update_originator_wallet`: the wallet the funds left from. When the transfer's UTXO inputs are known it must be one of them." + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "With `action: update_originator_wallet`: the wallet's memo or tag on a memo network. Optional." + }, + "beneficiary_vasp": { + "type": "string", + "format": "uuid", + "description": "Required with action=beneficiary_vasp: the `id` of a directory VASP, from `GET /v3/travel-rule/vasps/`. Your choice outranks every automatic discovery step, which never overrides it." + } + } + }, + "VaspDirectoryEntry": { + "type": "object", + "description": "A VASP of the directory or a member of the Didit network, with the routes your application has to it. A customer is a member of the Didit network only while its application is live, its Travel Rule is on, its profile is discoverable, has a legal name and a confirmed compliance email, and Didit has not marked the application as a test, demo or internal one. Each row also carries the shared due-diligence assessment (dd_score, risk_level, responsiveness, essential_checks) and your application's own decision, entity screening and questionnaires, which no other customer sees.", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The entry's id, for GET /v3/travel-rule/vasps/{vasp_id}/." + }, + "kind": { + "type": "string", + "enum": [ + "directory", + "didit" + ], + "description": "directory for an entry of the shared directory; didit for a member of the Didit network." + }, + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "description": "ISO 3166 country code, as the source gives it." + }, + "domains": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The VASP's web domains. Always empty for Didit network members." + }, + "reachable": { + "type": "boolean", + "description": "Whether your application has an available route to this VASP, or to one of its entities for a brand. Always true for a Didit network member, except on a sandbox application, where every row is false." + }, + "aliases": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "trading_name", + "other_legal_name", + "network_name", + "attribution", + "merged", + "staff" + ], + "description": "trading_name, other_legal_name (from the global LEI register), network_name (the name on a Travel Rule network), attribution (the entity name blockchain analytics gives the VASP's wallets), merged (the name of a duplicate entry merged into this one) or staff." + } + } + }, + "description": "Other names the VASP is found by." + }, + "networks": { + "type": "array", + "items": { + "type": "object", + "description": "A current listing of this VASP in a Travel Rule network's member directory. A TRISA listing says who the VASP is on TRISA; Didit has no TRISA rail, so it is never a route.", + "properties": { + "network": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "TRISA" + ] + }, + "code": { + "type": "string", + "description": "The VASP's identifier on that network." + }, + "listed_name": { + "type": "string", + "description": "The name the network lists the VASP under." + }, + "country": { + "type": "string", + "description": "The country the network lists, when it gives one." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When Didit staff verified the listing. Null for listings read from a public directory." + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the source last showed the listing. A listing the source has not shown for three weeks is removed." + }, + "source": { + "type": "string", + "enum": [ + "trisa_directory", + "staff" + ], + "description": "Where the listing comes from: the public TRISA directory, or Didit staff." + } + } + } + }, + "routes": { + "type": "array", + "items": { + "type": "object", + "description": "One way your application can deliver Travel Rule data to this VASP, in the order dispatch tries them. A network route needs a membership on that network: your own (connected in the Marketplace) or Didit's once Didit holds one. Without it the route is listed as unavailable with the reason, never as a way to reach the VASP.", + "properties": { + "rail": { + "type": "string", + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL" + ] + }, + "status": { + "type": "string", + "enum": [ + "available", + "unavailable" + ] + }, + "reason": { + "type": "string", + "enum": [ + "", + "no_membership", + "network_switched_off", + "sandbox_application" + ], + "description": "Why an unavailable route cannot be used: no_membership (neither you nor Didit holds a membership on that network), network_switched_off (you turned that network off in your settings) or sandbox_application (a sandbox application sends only to the Didit Test VASP, so no route is available to it). Empty when the route is available." + }, + "via": { + "type": "string", + "enum": [ + "", + "own_membership", + "didit_membership", + "directory", + "contact", + "didit_network" + ], + "description": "How an available route is reached: own_membership or didit_membership for a network rail, directory for a TRP endpoint the directory verified, contact for the EMAIL rail, didit_network for a Didit network member. Empty when the route is unavailable." + } + } + } + }, + "umbrella": { + "type": "object", + "nullable": true, + "description": "The brand this entry is a regulated entity of. Null when it is not part of one.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + } + } + }, + "entities": { + "type": "array", + "items": { + "type": "object", + "description": "A regulated entity of a brand: the legal entity that receives a transfer.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "jurisdiction": { + "type": "string" + }, + "reachable": { + "type": "boolean", + "description": "Whether your application has an available route to it." + } + } + }, + "description": "For a brand, the regulated entities that receive its transfers. A transfer naming the brand goes to the reachable entity in the jurisdiction it states (beneficiary_data.counterparty_vasp_jurisdiction), else to the entity blockchain analytics attribution linked to the destination wallet, else to the only reachable entity when no jurisdiction is stated. Two entities that fit equally are never guessed between." + }, + "contacts": { + "type": "array", + "items": { + "type": "object", + "description": "A compliance contact the EMAIL rail can write to.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "email": { + "type": "string", + "format": "email" + }, + "name": { + "type": "string" + }, + "source": { + "type": "string", + "enum": [ + "staff", + "application", + "learned" + ], + "description": "staff: curated by Didit; application: added by your application; learned: an address that answered a Travel Rule request through its response link." + }, + "scope": { + "type": "string", + "enum": [ + "platform", + "application" + ], + "description": "platform contacts are used for every Didit customer's transfers; application contacts are private to your application, which sees only its own." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When someone at this address answered a request. Null when it has not yet." + } + } + }, + "description": "The platform's contacts for this VASP and your application's own." + }, + "dd_status": { + "type": "string", + "enum": [ + "NOT_STARTED", + "IN_PROGRESS", + "COMPLETED", + "REJECTED" + ], + "description": "Didit's verification status of the entry, scored by the verification component of dd_score. Always COMPLETED for a Didit network member." + }, + "dd_score": { + "type": "integer", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "The shared due-diligence score of a directory entry, 0 to 100, where higher is safer (see dd_score_semantic): the sum of the points in dd_score_breakdown. Null for a Didit network member, which is not scored. An entry not assessed yet (dd_assessed_at null) shows 0 with an empty risk_level, so read risk_level rather than the raw score." + }, + "reachable_rails": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL" + ] + }, + "description": "Rails recorded on the entry. Read routes for what your application can use today." + }, + "is_didit_customer": { + "type": "boolean", + "description": "true for a member of the Didit network (dd_status is always COMPLETED, dd_score null and risk_level empty for these) and for a directory entry linked to a Didit customer." + }, + "dd_score_breakdown": { + "type": "object", + "description": "One object per component of dd_score. Components, their weight (max) and their reason codes: jurisdiction, 25 (jurisdiction_not_high_risk, jurisdiction_high_risk, jurisdiction_unknown); verification, 15 (dd_status_completed, dd_status_in_progress, dd_status_not_started, dd_status_rejected); protocol, 15 (native_rail, email_only, no_rail); lei, 10 (lei_valid, lei_missing); history, 10 (history, no_history); essentials, 15 (essential_checks, no_essential_checks); responsiveness, 10 (responsive, unresponsive, responsiveness_unknown). Empty for a Didit network member.", + "additionalProperties": { + "type": "object", + "properties": { + "points": { + "type": "number", + "description": "Points the component contributes, rounded to one decimal." + }, + "max": { + "type": "integer", + "description": "The component's weight: the points it contributes at best." + }, + "reason": { + "type": "string", + "description": "Machine-readable code of the component's verdict." + }, + "detail": { + "type": "string", + "description": "One-line English explanation." + } + } + } + }, + "dd_assessed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When dd_score, risk_level and responsiveness were last computed: after an exchange with the entry completes, finishes, expires, mismatches or is declined, when Didit refreshes or curates the entry, and in the weekly directory refresh. Null for a Didit network member." + }, + "dd_score_semantic": { + "type": "string", + "enum": [ + "higher_is_safer" + ], + "description": "Which way dd_score points: always higher_is_safer." + }, + "risk_level": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH", + "" + ], + "description": "The band dd_score falls in: LOW (70 or more), MEDIUM (40 to 69) or HIGH (below 40). Empty for a Didit network member and for an entry not assessed yet. Filter on it with risk. Your own flagged screening never changes this shared value: it makes counterparty_risk_level HIGH in your rules instead." + }, + "responsiveness": { + "type": "object", + "description": "Whether the VASP answers Travel Rule requests: the share of the requests Didit customers started towards it in the last 90 days that it answered. Only requests sent over a network or protocol rail, or by email to a shared address (the entry's compliance address or a platform contact), count; a request sent to an address a customer typed, or to a customer's private contact, never does. An expired request is unanswered; a completed exchange, a decline or a data mismatch is an answer; a cancelled request or an unreachable rail is neither. Always UNKNOWN with zero counts for a Didit network member.", + "properties": { + "status": { + "type": "string", + "enum": [ + "RESPONSIVE", + "UNRESPONSIVE", + "UNKNOWN" + ], + "description": "UNKNOWN with fewer than 5 decided requests in the window; UNRESPONSIVE when less than 10 percent of them were answered; RESPONSIVE otherwise." + }, + "answered": { + "type": "integer", + "description": "Decided requests in the window that the VASP answered." + }, + "total": { + "type": "integer", + "description": "Decided requests in the window: answered plus expired." + }, + "computed_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "essential_checks": { + "type": "object", + "description": "The essential-checks report of a directory entry, built only from the platform's verified sources: registration (an official register lists the VASP: the EU's interim MiCA register, the public TRISA directory or the global LEI register), licence (a MiCA authorisation is on record and current), domain (the entry's domain matches its TRP endpoint or the website its register gives) and lei (its LEI record is active, issued and consistent with the entry's names and country). The essentials component of dd_score is the share of decided checks that pass. Empty until the weekly directory refresh first checks the entry, and always empty for a Didit network member.", + "properties": { + "level": { + "type": "string", + "enum": [ + "essential" + ] + }, + "checked_at": { + "type": "string", + "format": "date-time" + }, + "checks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "enum": [ + "registration", + "licence", + "domain", + "lei" + ] + }, + "status": { + "type": "string", + "enum": [ + "pass", + "fail", + "unknown" + ], + "description": "unknown when no source on record can decide the check." + }, + "reason": { + "type": "string", + "description": "Machine-readable code of the verdict." + }, + "detail": { + "type": "string", + "description": "One-line English explanation." + } + } + } + }, + "passed": { + "type": "integer" + }, + "failed": { + "type": "integer" + }, + "unknown": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, + "decision": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecision" + } + ], + "nullable": true, + "description": "Your application's decision on this VASP, or for an entity with no decision of its own its brand's (inherited_from names the brand). Null when there is none. Private to your application." + }, + "entity_screening": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreening" + } + ], + "nullable": true, + "description": "Your application's latest sanctions and adverse-media screening of this VASP. Null when you never screened it. Private to your application." + }, + "questionnaires": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaire" + }, + "description": "Your application's due-diligence questionnaires with this VASP, newest first. Private to your application." + } + }, + "example": { + "id": "0d6f3c2a-6a1e-4c55-9a39-1f2b0c7d8e91", + "kind": "directory", + "name": "Example Counterparty VASP", + "lei": "529900EXAMPLECPTY001", + "jurisdiction": "DE", + "domains": [ + "counterparty.example" + ], + "reachable": true, + "aliases": [ + { + "name": "Example Counterparty", + "kind": "trading_name" + } + ], + "networks": [ + { + "network": "GTR", + "code": "EXAMPLE01", + "listed_name": "Example Counterparty VASP", + "country": "DE", + "verified_at": "2026-10-12T09:30:00Z", + "last_seen_at": "2026-10-12T09:30:00Z", + "source": "staff" + } + ], + "routes": [ + { + "rail": "GTR", + "status": "unavailable", + "reason": "no_membership", + "via": "" + }, + { + "rail": "EMAIL", + "status": "available", + "reason": "", + "via": "contact" + } + ], + "umbrella": null, + "entities": [], + "contacts": [ + { + "id": "6c1f9b8e-2d4a-4f7e-8b3c-5a9d0e1f2a3b", + "email": "travelrule@counterparty.example", + "name": "Travel Rule desk", + "source": "staff", + "scope": "platform", + "verified_at": null + } + ], + "dd_status": "NOT_STARTED", + "dd_score": 62, + "dd_score_breakdown": { + "jurisdiction": { + "points": 25.0, + "max": 25, + "reason": "jurisdiction_not_high_risk", + "detail": "DEU is not on the high-risk jurisdiction list" + }, + "verification": { + "points": 3.8, + "max": 15, + "reason": "dd_status_not_started", + "detail": "due diligence status NOT_STARTED" + }, + "protocol": { + "points": 6.0, + "max": 15, + "reason": "email_only", + "detail": "reachable over the email fallback only" + }, + "lei": { + "points": 10.0, + "max": 10, + "reason": "lei_valid", + "detail": "valid LEI on record" + }, + "history": { + "points": 5.0, + "max": 10, + "reason": "no_history", + "detail": "no exchange history" + }, + "essentials": { + "points": 7.5, + "max": 15, + "reason": "no_essential_checks", + "detail": "no decided essential check yet" + }, + "responsiveness": { + "points": 5.0, + "max": 10, + "reason": "responsiveness_unknown", + "detail": "0 decided recent requests, too few to judge" + } + }, + "dd_assessed_at": "2026-10-12T09:30:00Z", + "dd_score_semantic": "higher_is_safer", + "risk_level": "MEDIUM", + "responsiveness": { + "status": "UNKNOWN", + "answered": 0, + "total": 0, + "computed_at": "2026-10-12T09:30:00Z" + }, + "essential_checks": {}, + "reachable_rails": [ + "GTR", + "EMAIL" + ], + "is_didit_customer": false, + "decision": { + "decision": "APPROVED", + "reason": "Licensed in DE, questionnaire reviewed", + "decided_by": "", + "updated_at": "2026-10-14T10:05:00Z", + "inherited_from": null + }, + "entity_screening": { + "id": "7a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d", + "status": "Approved", + "total_hits": 0, + "score": null, + "screened_at": "2026-10-14T09:58:00Z", + "fresh": true, + "flagged": false + }, + "questionnaires": [ + { + "id": "8b7a6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d", + "status": "ANSWERED", + "form_version": "1", + "sent_to": "travelrule@counterparty.example", + "sent_at": "2026-09-02T08:00:00Z", + "expires_at": "2026-10-02T08:00:00Z", + "answered_at": "2026-09-04T15:20:00Z", + "valid_until": "2027-09-04T15:20:00Z", + "current": true, + "answers": { + "legal_name": "Example Counterparty VASP, incorporated in the example jurisdiction.", + "licensing": "Crypto-asset service provider authorised under MiCA by its national regulator, licence active.", + "jurisdictions": "EU and EEA. We do not serve the United States.", + "aml_program": "Written AML/CFT policy, annual independent review, last completed in March 2026.", + "compliance_officer": "Head of Compliance, reachable at compliance@counterparty.example.", + "sanctions_screening": "Customers at onboarding and daily; counterparties on every transfer.", + "transaction_monitoring": "Rule-based monitoring with analyst review; suspicious activity reports filed with the FIU.", + "travel_rule": "TRP and email; we answer requests within one business day.", + "custody": "Segregated customer wallets held with a qualified custodian, insured.", + "ownership": "No shareholder holds 25 percent or more; parent company Example Holding Group.", + "record_keeping": "Five-year record-keeping for Travel Rule and transaction data, as our regulator requires.", + "data_protection": "Encrypted at rest and in transit, stored in the EU." + } + } + ] + } + }, + "TravelRuleCounterpartyDecision": { + "type": "object", + "description": "Your application's decision on a counterparty VASP. Private to your application: it never changes the shared directory entry and another customer never sees it. It acts only through your transaction rules (the counterparty_dd_status rule field and the travel-rule-counterparty-blocked and travel-rule-counterparty-restricted presets); it never changes how a transfer is routed.", + "properties": { + "decision": { + "type": "string", + "enum": [ + "APPROVED", + "RESTRICTED", + "BLOCKED" + ], + "description": "APPROVED: your team cleared the counterparty. RESTRICTED: transfers to it may go ahead after review. BLOCKED: your team refused it." + }, + "reason": { + "type": "string", + "maxLength": 255, + "description": "Free-text reason you gave, empty when none." + }, + "decided_by": { + "type": "string", + "description": "The console user who made or last changed the decision. Empty when an API key made it." + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "inherited_from": { + "type": "object", + "nullable": true, + "description": "The brand whose decision this entity follows because you made none on the entity itself. Null when the decision is the counterparty's own.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + } + } + } + } + }, + "TravelRuleCounterpartyDecisionResponse": { + "type": "object", + "properties": { + "decision": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleCounterpartyDecision" + } + ], + "nullable": true, + "description": "The decision that applies to the counterparty for your application: its own, else (for an entity of a brand) the brand's. Null when there is none." + } + } + }, + "TravelRuleCounterpartyDecisionRequest": { + "type": "object", + "required": [ + "decision" + ], + "properties": { + "decision": { + "type": "string", + "enum": [ + "APPROVED", + "RESTRICTED", + "BLOCKED" + ] + }, + "reason": { + "type": "string", + "maxLength": 255, + "description": "Optional. Replaces the previous reason; omit it to clear it." + } + } + }, + "TravelRuleCounterpartyScreening": { + "type": "object", + "description": "One sanctions and adverse-media screening of the counterparty entity, run for and billed to your application. Private to your application: another customer never sees it.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Approved: no possible match needs attention. In Review or Declined: a possible sanctions or adverse-media match." + }, + "total_hits": { + "type": "integer", + "description": "How many possible matches the screening returned." + }, + "score": { + "type": "number", + "nullable": true, + "description": "The highest risk score among the possible matches. Null when nothing matched." + }, + "screened_at": { + "type": "string", + "format": "date-time" + }, + "fresh": { + "type": "boolean", + "description": "Whether the screening is younger than fresh_days (30 days). A fresh screening is reused instead of billed again." + }, + "flagged": { + "type": "boolean", + "description": "true while the screening is fresh and its status is In Review or Declined. While your latest screening is flagged and counterparty screening stays on, counterparty_risk_level is HIGH in your rules." + } + } + }, + "TravelRuleCounterpartyScreeningStatus": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Your counterparty_screening_enabled Travel Rule setting. Off by default; nothing is screened or billed while it is off." + }, + "price": { + "type": "string", + "description": "Decimal string, in USD: what one screening costs your organization, the Company AML Screening price." + }, + "fresh_days": { + "type": "integer", + "description": "How many days a screening stays fresh and is reused." + }, + "screening": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreening" + } + ], + "nullable": true, + "description": "Your latest screening of the counterparty. Null when you never screened it." + } + } + }, + "TravelRuleCounterpartyScreeningRequest": { + "type": "object", + "properties": { + "refresh": { + "type": "boolean", + "default": false, + "description": "true screens again (and bills again) even when a fresh screening exists." + } + } + }, + "TravelRuleCounterpartyScreeningResult": { + "type": "object", + "properties": { + "outcome": { + "type": "string", + "enum": [ + "screened", + "reused" + ], + "description": "screened: a new screening ran and was billed (201). reused: a fresh screening was returned and nothing was billed (200)." + }, + "screening": { + "$ref": "#/components/schemas/TravelRuleCounterpartyScreening" + } + } + }, + "TravelRuleCounterpartyQuestionnaire": { + "type": "object", + "description": "One due-diligence questionnaire your application sent the counterparty: Didit's standard questionnaire, emailed to a compliance address on record with a single-use answer link. Private to your application.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "SENT", + "ANSWERED", + "EXPIRED" + ], + "description": "SENT: waiting for answers. ANSWERED: the counterparty answered every question. EXPIRED: the answer link closed unanswered (its deadline passed, or a newer questionnaire replaced it), or answers passed valid_until." + }, + "form_version": { + "type": "string", + "description": "Version of Didit's standard questionnaire the answers belong to. Currently 1." + }, + "sent_to": { + "type": "string", + "format": "email" + }, + "sent_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "The answer-by deadline, 30 days after sending. The link stops working after it." + }, + "answered_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "valid_until": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "How long the answers stay current: 365 days after answering. Null until answered." + }, + "current": { + "type": "boolean", + "description": "true while the questionnaire is ANSWERED and valid_until has not passed." + }, + "answers": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "The answers keyed by question id: legal_name, licensing, jurisdictions, aml_program, compliance_officer, sanctions_screening, transaction_monitoring, travel_rule, custody, ownership, record_keeping, data_protection. Each answer is up to 2,000 characters. Empty until answered." + } + } + }, + "TravelRuleCounterpartyQuestionnaireRequest": { + "type": "object", + "properties": { + "sent_to": { + "type": "string", + "format": "email", + "description": "Optional. A compliance address on record for the counterparty: for a directory entry its compliance address or a contact your application sees, for a Didit network member its confirmed compliance email. Default: the address a Travel Rule email to the counterparty goes to (your own contact first, then a platform contact, then the entry's compliance address)." + } + } + }, + "TravelRuleCounterpartyQuestionnaireList": { + "type": "object", + "properties": { + "questionnaires": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleCounterpartyQuestionnaire" + }, + "description": "Newest first." + } + } + }, + "TravelRulePickupInfo": { + "type": "object", + "description": "The pending exchange behind an emailed pickup link, as the counterparty's response page shows it.", + "properties": { + "reference": { + "type": "string", + "format": "uuid", + "description": "The transfer's uuid, as the sending VASP's console shows it." + }, + "requested_at": { + "type": "string", + "format": "date-time" + }, + "originator_vasp_name": { + "type": "string" + }, + "originator_vasp_lei": { + "type": "string" + }, + "recipient_vasp_name": { + "type": "string", + "description": "The counterparty VASP the request is addressed to." + }, + "language": { + "type": "string", + "description": "The language of the request email and the response page, from the counterparty's jurisdiction." + }, + "amount": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "fiat_amount": { + "type": "string", + "description": "The converted value the sender gave, or empty." + }, + "fiat_currency": { + "type": "string" + }, + "chain": { + "type": "string" + }, + "beneficiary_wallet": { + "type": "string" + }, + "status": { + "type": "string" + }, + "ivms_version": { + "type": "string" + }, + "deadline_at": { + "type": "string", + "format": "date-time", + "description": "When the link stops answering." + }, + "reminders_sent": { + "type": "integer" + }, + "contact": { + "type": "string", + "description": "The `?contact=` value of the link, echoed back; empty when the link has none." + }, + "decline_reasons": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "data_mismatch", + "not_our_customer", + "wallet_not_hosted", + "sanctions", + "risk_appetite", + "jurisdiction", + "insufficient_data", + "other" + ] + }, + "description": "The values `decline_reason` accepts." + }, + "join_invitation": { + "type": "object", + "description": "Whether the counterparty holds a live invitation to join Didit for free. The invitation link travels only in the email to the invited address, so `url` is empty here.", + "properties": { + "eligible": { + "type": "boolean" + }, + "url": { + "type": "string" + } + } + }, + "originator_data": { + "type": "object", + "description": "The originator fields the sending VASP shares (`data_sharing_fields`). The name and the wallet are always included." + }, + "beneficiary_data": { + "type": "object" + }, + "ivms_request": { + "type": "object", + "description": "The same request as an IVMS 101 object. Left out while `data_masked` is true." + }, + "data_masked": { + "type": "boolean", + "description": "Always false on this endpoint: an email request is outbound, and the sender's own user is never masked." + } + } + }, + "TravelRulePickupRespondRequest": { + "type": "object", + "required": [ + "decision" + ], + "properties": { + "decision": { + "type": "string", + "enum": [ + "accept", + "decline" + ] + }, + "beneficiary_name": { + "type": "string", + "description": "The beneficiary's name as your records hold it, run through the sending VASP's name rule. `ivms_response` takes precedence when it names the beneficiary.", + "maxLength": 255 + }, + "decline_reason": { + "type": "string", + "enum": [ + "", + "data_mismatch", + "not_our_customer", + "wallet_not_hosted", + "sanctions", + "risk_appetite", + "jurisdiction", + "insufficient_data", + "other" + ], + "description": "Why you decline. Recorded on the sending VASP's transfer timeline. Empty (the default) when you give no reason." + }, + "decline_detail": { + "type": "string", + "maxLength": 512, + "description": "Free text; required when `decision` is `decline` and `decline_reason` is `other`." + }, + "ivms_response": { + "type": "object", + "description": "Your full IVMS 101 response. Its beneficiary name wins over `beneficiary_name`." + }, + "contact": { + "type": "string", + "format": "uuid", + "description": "The contact the link was sent to, from the link's `?contact=` value. When the body leaves it out, the `contact` query parameter is used." + } + } + }, + "TravelRuleInboundRequest": { + "type": "object", + "required": [ + "chain", + "txid", + "wallet_address", + "amount", + "currency" + ], + "properties": { + "chain": { + "type": "string", + "description": "Chain the deposit settled on, for example `ETH`. Stored lowercase and compared case-insensitively with your wallet address book entries, so use the same chain token there.", + "maxLength": 32 + }, + "txid": { + "type": "string", + "description": "On-chain transaction hash. Deduped together with wallet_address.", + "maxLength": 128 + }, + "wallet_address": { + "type": "string", + "description": "The destination wallet you received the deposit on. An XRP X-address is split into its classic address and tag.", + "maxLength": 128 + }, + "amount": { + "type": "string", + "description": "Deposit amount as a decimal string, zero or greater, with up to 18 decimal places." + }, + "currency": { + "type": "string", + "description": "Deposit asset, e.g. \"USDC\".", + "maxLength": 16 + }, + "originator_data": { + "type": "object", + "description": "Originator details from the sending VASP, if known, as a flat object such as `{\"name\": \"...\"}`. Didit reads name (or full_name), first_name, last_name and vendor_data from it.", + "additionalProperties": true + }, + "beneficiary_data": { + "type": "object", + "description": "Beneficiary details (your customer), as a flat object such as `{\"name\": \"...\"}`. Didit matches name against the holder_name of the matched address book entry, and sets wallet_address and chain from the request.", + "additionalProperties": true + }, + "originating_vasp": { + "type": "object", + "description": "Optional description of the sending VASP. It stays on this transfer only (`metadata.originating_vasp`, and the transfer's originator VASP in GET /v3/travel-rule/transfers/); it never creates, edits or links a shared VASP directory entry.", + "properties": { + "name": { + "type": "string", + "maxLength": 255 + }, + "lei": { + "type": "string", + "maxLength": 20, + "description": "Optional. A valid 20-character ISO 17442 LEI, or empty." + }, + "travel_address": { + "type": "string", + "description": "The sender's travel address, recorded on this transfer.", + "maxLength": 512 + } + } + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "Optional. The deposit's destination tag or memo on a memo network: on a shared deposit address it is what tells your customers apart, and the address-book entry for that memo is matched. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`. Alias: `destinationTag` or `destination_tag`." + }, + "utxo": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleUtxo" + } + ], + "description": "Optional, UTXO chains only. The deposit transaction's inputs and outputs: the sender's wallet is the declared `originator_data.wallet_address`, else the largest input." + } + } + }, + "TravelRuleInboundResponse": { + "type": "object", + "properties": { + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Didit transaction UUID for the inbound deposit." + }, + "txn_id": { + "type": "string", + "description": "txn_id of the inbound transaction: Didit-generated when Didit created the transaction, or your own when an unclaimed deposit was reused." + }, + "created": { + "type": "boolean", + "description": "true when this call registered the transfer (HTTP 201); false when a transfer for the same txid and wallet_address already existed (HTTP 200)." + }, + "travel_rule": { + "$ref": "#/components/schemas/TravelRuleTransferDetail" + } + } + }, + "TravelRuleTestVaspInboundRequest": { + "type": "object", + "required": [ + "beneficiary_wallet_address" + ], + "properties": { + "beneficiary_wallet_address": { + "type": "string", + "description": "The destination wallet address. The Didit Test VASP sends to it, matched against your wallet address book on the chain of the asset. A wallet with no matching entry is accepted and the inbound transaction is booked ON_HOLD." + }, + "scenario": { + "type": "string", + "enum": [ + "accept", + "name_mismatch" + ], + "default": "accept", + "description": "What the Didit Test VASP declares the beneficiary to be. `accept` declares the holder's own name; `name_mismatch` declares a name no holder agrees with. The decision itself is always yours." + }, + "chain": { + "type": "string", + "default": "ETH", + "description": "Chain of the asset." + }, + "amount": { + "type": "string", + "default": "1", + "description": "Amount as a decimal string." + }, + "currency": { + "type": "string", + "default": "ETH", + "description": "Asset code." + }, + "reference": { + "type": "string", + "description": "Optional idempotency key. A repeat with the same value returns the transfer the first call booked." + } + } + }, + "TravelRuleTestVaspInboundResponse": { + "type": "object", + "properties": { + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Didit transaction UUID of the inbound transfer." + }, + "txn_id": { + "type": "string", + "description": "Didit-generated txn_id." + }, + "scenario": { + "type": "string", + "description": "The scenario that ran." + }, + "travel_rule_status": { + "type": "string", + "description": "The status your application's own decision resolved to." + }, + "detail": { + "type": "string", + "description": "Why the transfer resolved to that status." + }, + "test_vasp": { + "type": "boolean", + "description": "Always true: the sender was the Didit Test VASP." + }, + "travel_rule": { + "$ref": "#/components/schemas/TravelRuleTransferDetail" + } + } + }, + "TravelRuleWidgetSessionCreate": { + "type": "object", + "properties": { + "wallet_address": { + "type": "string", + "description": "The wallet address to verify. Optional; when empty, the address must be supplied when a proof is started.", + "maxLength": 128 + }, + "chain": { + "type": "string", + "description": "Chain token for the address, for example `ETH`, `BTC` or `SOL`. Use the same token as in your wallet address book. Message signing and the Satoshi test are offered only for a supported chain. The address book entry the proof verifies stores it under its canonical identifier (ETH, BTC, SOL, TRX).", + "maxLength": 32 + }, + "holder_name": { + "type": "string", + "description": "Name of the wallet holder, used for the beneficiary name match.", + "maxLength": 255 + }, + "vendor_data": { + "type": "string", + "description": "Your internal identifier for the holder, carried onto the created address-book entry.", + "maxLength": 255 + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Optional. Didit transaction UUID of a Travel Rule transfer to advance when the proof verifies.", + "nullable": true + }, + "satoshi_deposit_address": { + "type": "string", + "description": "Optional. A deposit address you control; supplying it enables the SATOSHI_TEST proof method.", + "maxLength": 128 + }, + "callback_url": { + "type": "string", + "description": "Optional. Absolute http(s) URL where the widget returns the customer after completion.", + "maxLength": 512 + }, + "expires_in_minutes": { + "type": "integer", + "description": "Optional. Lifetime of the minted link, in minutes. Automatically extended to cover the Satoshi test window when SATOSHI_TEST is offered.", + "default": 60, + "minimum": 5, + "maximum": 10080 + } + } + }, + "TravelRuleWidgetSessionResponse": { + "type": "object", + "properties": { + "widget_session_id": { + "type": "string", + "format": "uuid" + }, + "token": { + "type": "string", + "description": "Opaque session token embedded in the widget URL." + }, + "url": { + "type": "string", + "description": "The hosted wallet-ownership widget URL. Open it verbatim in the system browser (never inside a mobile SDK webview)." + }, + "expires_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleWidgetContext": { + "type": "object", + "properties": { + "vasp_name": { + "type": "string", + "description": "Legal name from your VASP profile, shown to the customer in the widget. Empty when no profile exists." + }, + "wallet_address": { + "type": "string" + }, + "chain": { + "type": "string", + "description": "The chain the session was minted with, as you sent it." + }, + "chain_family": { + "type": "string", + "nullable": true, + "enum": [ + "EVM", + "BTC", + "SOL", + "TRON", + null + ], + "description": "The key family behind chain: EVM (Ethereum, BNB Chain, Polygon, Arbitrum, Base, Optimism, Avalanche, Linea and the other EVM chains), BTC, SOL or TRON. One EVM key controls the same address on every EVM chain. Null when no signature can be verified on the chain." + }, + "holder_name": { + "type": "string" + }, + "allowed_methods": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SCREENSHOT", + "SATOSHI_TEST", + "SELF_DECLARATION" + ] + }, + "description": "Proof methods available for this session. MESSAGE_SIGNING requires a chain with a chain_family, SATOSHI_TEST requires a satoshi_deposit_address on the session and a chain whose deposits Didit can read back, SCREENSHOT requires the allow_screenshot_proof setting, and SELF_DECLARATION requires the allow_self_declaration setting." + }, + "transfer_status": { + "type": "string", + "nullable": true, + "description": "Status of the linked Travel Rule transfer. Null when the session was minted without a transaction_id." + }, + "callback_url": { + "type": "string", + "description": "Where the widget returns the customer after completion. Empty when not set." + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Non-null once a proof has been verified." + }, + "proofs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleWidgetProofSummary" + } + }, + "max_attempts": { + "type": "integer", + "description": "Rejected wallet proofs this session allows." + }, + "attempts_used": { + "type": "integer", + "description": "Rejected proofs so far." + }, + "attempts_remaining": { + "type": "integer", + "description": "When 0, starting another proof answers 409 and you mint a new widget session." + }, + "customization": { + "type": "object", + "nullable": true, + "description": "The application's whitelabel customization (colors, logo, font family) when white label is enabled - the widget renders with the same branding as the verification flow. Null otherwise." + } + } + }, + "TravelRuleWidgetProofSummary": { + "type": "object", + "properties": { + "proof_id": { + "type": "string", + "format": "uuid" + }, + "method": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SATOSHI_TEST", + "SCREENSHOT", + "SELF_DECLARATION" + ] + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ], + "description": "PENDING: the end user picked this method and the proof is not decided yet. A failed signature or a test deposit that is not readable or confirmed yet keeps the proof PENDING with a failure_reason, so the end user can try again; a submitted SATOSHI_TEST deposit is rechecked automatically until its window closes, and a submitted SCREENSHOT waits for your review. VERIFIED: ownership is proven. REJECTED: the test deposit is genuinely wrong (another sender, not the expected amount to the deposit address, older than the challenge, or a transaction that already proved another wallet) or your reviewer rejected the screenshot; failure_reason says which. A rejected proof is final and uses one of the session's attempts. EXPIRED: the proof can no longer be decided (its window closed, the end user switched method, the widget session lapsed, or the wallet's holder changed); failure_reason says which." + }, + "wallet_address": { + "type": "string" + }, + "chain": { + "type": "string", + "description": "Canonical chain identifier of the address book entry the proof is for, e.g. ETH, BTC, SOL, TRX." + }, + "deposit_address": { + "type": "string", + "nullable": true, + "description": "Deposit address the customer must send the Satoshi test amount to. Present only on a SATOSHI_TEST proof." + }, + "expected_amount": { + "type": "string", + "nullable": true, + "description": "Exact on-chain amount, in the deposit's native currency, the Satoshi test deposit must match. Present only on a SATOSHI_TEST proof." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Deadline for this proof's own Satoshi test window. Present only on a SATOSHI_TEST proof; the widget session's own expires_at is extended to cover it when SATOSHI_TEST is offered." + }, + "challenge": { + "type": "string", + "nullable": true, + "description": "Didit-issued message the customer signs with their wallet. Present only on a MESSAGE_SIGNING proof." + }, + "rejection_reason": { + "type": "string", + "description": "Why the proof was rejected, as an English sentence for the end user. Present only when `status` is REJECTED; empty when your reviewer rejected a screenshot (your note stays with you)." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "attempts": { + "type": "integer", + "description": "How many times evidence was submitted for this proof (signatures, transaction ids, uploads)." + }, + "failure_reason": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleWalletProofFailureReason" + } + ], + "description": "Why the last attempt fell short, or why the proof closed. Empty when nothing failed." + } + } + }, + "CaseSubject": { + "type": "object", + "nullable": true, + "description": "The single user or business the case is anchored to.", + "properties": { + "type": { + "type": "string", + "enum": [ + "user", + "business" + ] + }, + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "external_id": { + "type": "string", + "nullable": true, + "description": "The subject's vendor_data." + }, + "country": { + "type": "string", + "nullable": true, + "description": "Three-letter country code of the subject (ISO 3166-1 alpha-3), or null when it is not known. For a business, its country of incorporation. For a person, the issuing country of an identity document from their verifications; a person with documents from several countries shows one of them, and ICAO document codes such as `XXA` can appear.", + "example": "ESP" + }, + "portrait": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived signed URL of the person's portrait image. Always null for a business, and null for a person without a portrait. The URL expires, so download the image when you read the case instead of storing the URL." + } + } + }, + "CaseAssignee": { + "type": "object", + "nullable": true, + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "full_name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + } + } + }, + "CaseBlueprintSummary": { + "type": "object", + "nullable": true, + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "maxLength": 24 + } + } + }, + "CaseContentConfig": { + "type": "object", + "description": "The six togglable sections that drive which tabs and fields appear on the case page. Unknown sections or keys are rejected.", + "properties": { + "checklist": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "optional": { + "type": "boolean", + "description": "When true, a case can be resolved with incomplete checklist items." + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "maxLength": 255 + }, + "order": { + "type": "integer" + } + } + } + } + } + }, + "aml_control": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "financial": { + "type": "boolean" + }, + "identity": { + "type": "boolean" + } + } + }, + "identity": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "personal_information": { + "type": "boolean" + }, + "applicant_tags": { + "type": "boolean" + }, + "contact_information": { + "type": "boolean" + }, + "risk_overview": { + "type": "boolean" + }, + "poi_documents": { + "type": "boolean" + }, + "poa_documents": { + "type": "boolean" + } + } + }, + "verifications": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "summary": { + "type": "boolean" + } + } + }, + "reporting": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "fiu_reports": { + "type": "boolean" + } + } + }, + "financial": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "payment_methods": { + "type": "boolean" + }, + "transactions": { + "type": "boolean" + } + } + } + } + }, + "CaseNote": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "comment": { + "type": "string" + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + } + }, + "is_internal": { + "type": "boolean" + }, + "attachments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "s3_key": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "file_size": { + "type": "integer" + }, + "url": { + "type": "string", + "format": "uri" + } + } + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "CaseEvent": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "event_type": { + "type": "string", + "enum": [ + "CREATED", + "STATUS_CHANGED", + "ASSIGNED", + "UNASSIGNED", + "RESOLVED", + "REOPENED", + "LINK_ADDED", + "LINK_REMOVED", + "NOTE_ADDED", + "NOTE_PINNED", + "NOTE_UNPINNED", + "PRIORITY_CHANGED", + "DUE_DATE_CHANGED", + "TAG_ADDED", + "TAG_REMOVED", + "ESCALATED", + "TRANSFERRED", + "CHECKLIST_COMPLETED", + "REPORT_CREATED", + "REPORT_FINALIZED", + "SUBMITTED_FOR_APPROVAL", + "APPROVAL_GRANTED", + "APPROVAL_REJECTED", + "ENTITY_ACTION", + "APPROVAL_INVALIDATED", + "CUSTOMER_REQUEST", + "STAGE_CHANGED", + "TEAM_CHANGED", + "CHECKER_ASSIGNED", + "CHECKER_UNASSIGNED" + ] + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "previous_status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ], + "nullable": true + }, + "new_status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ], + "nullable": true + }, + "metadata": { + "type": "object" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "CaseLink": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "linked_entity_type": { + "type": "string", + "enum": [ + "session", + "business_session", + "transaction" + ], + "nullable": true + }, + "linked_entity_uuid": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "linked_entity_summary": { + "type": "object", + "nullable": true, + "description": "session_id/session_number/status for session and business_session links; uuid/txn_id/status for transaction links." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "CaseChecklistItem": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string", + "maxLength": 255 + }, + "is_completed": { + "type": "boolean" + }, + "completed_by": { + "type": "string", + "format": "email", + "nullable": true + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "order": { + "type": "integer" + } + } + }, + "CaseBlueprintListItem": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "maxLength": 24 + }, + "description": { + "type": "string", + "nullable": true + }, + "assignment_method": { + "type": "string", + "enum": [ + "MANUAL", + "AUTOMATIC" + ] + }, + "assignee_pool": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "max_active_cases": { + "type": "integer", + "nullable": true + }, + "escalation_enabled": { + "type": "boolean" + }, + "transfer_enabled": { + "type": "boolean" + }, + "require_resolution_note": { + "type": "boolean" + }, + "is_active": { + "type": "boolean" + }, + "preset_key": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "version": { + "type": "integer", + "readOnly": true + }, + "default_deadline_hours": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 8760 + } + } + }, + "CaseBlueprintDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "maxLength": 24 + }, + "description": { + "type": "string", + "nullable": true + }, + "assignment_method": { + "type": "string", + "enum": [ + "MANUAL", + "AUTOMATIC" + ] + }, + "assignee_pool": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "max_active_cases": { + "type": "integer", + "nullable": true + }, + "escalation_enabled": { + "type": "boolean" + }, + "transfer_enabled": { + "type": "boolean" + }, + "require_resolution_note": { + "type": "boolean" + }, + "is_active": { + "type": "boolean" + }, + "preset_key": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "escalation_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "escalation_blueprint_name": { + "type": "string", + "nullable": true + }, + "four_eyes_checker_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "four_eyes_checker_blueprint_name": { + "type": "string", + "nullable": true + }, + "content_config": { + "type": "object", + "description": "The six togglable sections that drive which tabs and fields appear on the case page. Unknown sections or keys are rejected.", + "properties": { + "checklist": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "optional": { + "type": "boolean", + "description": "When true, a case can be resolved with incomplete checklist items." + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string", + "maxLength": 255 + }, + "order": { + "type": "integer" + } + } + } + } + } + }, + "aml_control": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "financial": { + "type": "boolean" + }, + "identity": { + "type": "boolean" + } + } + }, + "identity": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "personal_information": { + "type": "boolean" + }, + "applicant_tags": { + "type": "boolean" + }, + "contact_information": { + "type": "boolean" + }, + "risk_overview": { + "type": "boolean" + }, + "poi_documents": { + "type": "boolean" + }, + "poa_documents": { + "type": "boolean" + } + } + }, + "verifications": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "summary": { + "type": "boolean" + } + } + }, + "reporting": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "fiu_reports": { + "type": "boolean" + } + } + }, + "financial": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "payment_methods": { + "type": "boolean" + }, + "transactions": { + "type": "boolean" + } + } + } + } + }, + "creation_sources": { + "type": "array", + "description": "Connectors currently routing new cases into this blueprint.", + "items": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "aml", + "rule", + "workflow" + ] + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + }, + "receives_escalation_from": { + "type": "array", + "description": "Other blueprints whose Escalation target points at this one.", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "version": { + "type": "integer", + "readOnly": true + }, + "default_deadline_hours": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 8760 + } + } + }, + "CaseListItem": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "case_number": { + "type": "string", + "nullable": true, + "description": "Auto-assigned, e.g. CS-0001." + }, + "title": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ] + }, + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ], + "nullable": true + }, + "pending_resolution": { + "type": "object", + "nullable": true, + "description": "Set only while a 4-eyes resolution is staged on the checker blueprint, awaiting approve or reject-approval.", + "properties": { + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ] + }, + "resolution_notes": { + "type": "string", + "nullable": true + }, + "maker_email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email of the officer who submitted the resolution. Null when an API key submitted it: that resolution has no recorded maker, so approve and reject-approval return 403 for every caller." + }, + "maker_name": { + "type": "string", + "nullable": true + }, + "maker_user_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Member user uuid of the officer who submitted the resolution. Null when an API key submitted it. That member cannot be named as `checker`." + }, + "maker_blueprint_id": { + "type": "string", + "format": "uuid" + }, + "staged_at": { + "type": "string", + "format": "date-time" + } + } + }, + "checker": { + "$ref": "#/components/schemas/CaseChecker" + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "source": { + "type": "string", + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ] + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "subject": { + "$ref": "#/components/schemas/CaseSubject" + }, + "assigned_to": { + "$ref": "#/components/schemas/CaseAssignee" + }, + "assigned_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "team": { + "$ref": "#/components/schemas/CaseTeamSummary" + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "created_by_email": { + "type": "string", + "nullable": true + }, + "created_by_name": { + "type": "string", + "nullable": true + }, + "blueprint": { + "$ref": "#/components/schemas/CaseBlueprintSummary" + }, + "links_count": { + "type": "integer" + }, + "notes_count": { + "type": "integer" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "application_id": { + "type": "string", + "format": "uuid" + }, + "application_name": { + "type": "string" + }, + "organization_id": { + "type": "string", + "format": "uuid" + }, + "organization_name": { + "type": "string" + } + } + }, + "CaseDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "case_number": { + "type": "string", + "nullable": true + }, + "title": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ] + }, + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ], + "nullable": true + }, + "resolution_notes": { + "type": "string", + "nullable": true + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ] + }, + "source": { + "type": "string", + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ] + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "subject": { + "$ref": "#/components/schemas/CaseSubject" + }, + "assigned_to": { + "$ref": "#/components/schemas/CaseAssignee" + }, + "assigned_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "team": { + "$ref": "#/components/schemas/CaseTeamSummary" + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "resolved_by_email": { + "type": "string", + "nullable": true + }, + "pending_resolution": { + "type": "object", + "nullable": true, + "description": "Set only while a 4-eyes resolution is staged on the checker blueprint, awaiting approve or reject-approval.", + "properties": { + "resolution": { + "type": "string", + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT", + "INCONCLUSIVE", + "REQUIREMENTS_NOT_MET", + "REQUIREMENTS_SATISFIED" + ] + }, + "resolution_notes": { + "type": "string", + "nullable": true + }, + "maker_email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email of the officer who submitted the resolution. Null when an API key submitted it: that resolution has no recorded maker, so approve and reject-approval return 403 for every caller." + }, + "maker_name": { + "type": "string", + "nullable": true + }, + "maker_user_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Member user uuid of the officer who submitted the resolution. Null when an API key submitted it. That member cannot be named as `checker`." + }, + "maker_blueprint_id": { + "type": "string", + "format": "uuid" + }, + "staged_at": { + "type": "string", + "format": "date-time" + } + } + }, + "checker": { + "$ref": "#/components/schemas/CaseChecker" + }, + "metadata": { + "type": "object" + }, + "blueprint": { + "$ref": "#/components/schemas/CaseBlueprintDetail" + }, + "notes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseNote" + }, + "description": "Newest 50 notes." + }, + "events": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseEvent" + }, + "description": "Newest 100 events." + }, + "links": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseLink" + } + }, + "links_count": { + "type": "integer" + }, + "notes_count": { + "type": "integer" + }, + "checklist_items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseChecklistItem" + } + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "ReportTemplateDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code." + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "report_type_display": { + "type": "string" + }, + "field_config": { + "type": "object", + "description": "Reusable goAML header defaults. Template-level obligatory fields: rentity_id, submission_code, and currency_code_local (3-letter). Optional: enabled_fields (which optional report blocks to include) plus reporting_person, location, reason, and action defaults. Per-filing header fields (action, submission_date, transactions, jurisdiction) are enforced when the report is validated or finalized, not on the template." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "RegulatoryReportDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "case_uuid": { + "type": "string", + "format": "uuid" + }, + "case_number": { + "type": "string", + "nullable": true + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ] + }, + "report_type_display": { + "type": "string" + }, + "report_code": { + "type": "string", + "description": "goAML report code derived from report_type, e.g. STR, CTR, BCR." + }, + "report_number": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "enum": [ + "DRAFT", + "VALIDATED", + "FINALIZED" + ] + }, + "status_display": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2 + }, + "rentity_id": { + "type": "string", + "nullable": true, + "description": "Reporting entity ID issued by the FIU." + }, + "rentity_branch": { + "type": "string", + "nullable": true + }, + "submission_code": { + "type": "string", + "enum": [ + "E", + "M" + ] + }, + "entity_reference": { + "type": "string", + "nullable": true + }, + "fiu_ref_number": { + "type": "string", + "nullable": true + }, + "currency_code_local": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "nullable": true + }, + "submission_date": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reporting_person": { + "type": "object" + }, + "location": { + "type": "object" + }, + "reason": { + "type": "string", + "nullable": true + }, + "action": { + "type": "string", + "nullable": true + }, + "activity": { + "type": "string", + "nullable": true + }, + "narrative": { + "type": "object", + "description": "Five Ws narrative sections.", + "properties": { + "who": { + "type": "string" + }, + "what": { + "type": "string" + }, + "when": { + "type": "string" + }, + "where": { + "type": "string" + }, + "why": { + "type": "string" + }, + "how": { + "type": "string" + } + } + }, + "report_indicators": { + "type": "array", + "items": { + "type": "string" + } + }, + "included_fields": { + "type": "array", + "items": { + "type": "string" + } + }, + "transactions": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "validation_errors": { + "type": "array", + "description": "Result of the most recent validation; each entry is a {field, message} object.", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + }, + "validated_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "finalized_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "created_by_email": { + "type": "string", + "nullable": true + }, + "created_by_name": { + "type": "string", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TransactionRuleCondition": { + "type": "object", + "required": [ + "field", + "operator" + ], + "properties": { + "field": { + "type": "string", + "description": "Transaction field path to evaluate. Core fields: amount, currency, direction, action_type, score, severity, transaction_type, payment_details, preferred_currency_amount (falls back to amount_in_default_currency), default_currency_code, subject_country, counterparty_country, subject_vendor_data, counterparty_vendor_data, subject_device_fingerprint, counterparty_device_fingerprint, subject_browser_family, subject_browser_version, subject_platform, subject_accept_language, subject_session_age_ms, subject_ip_country, counterparty_ip_country, subject_ip_address, counterparty_ip_address, subject_payment_method_type, subject_payment_method_country, subject_payment_method_fingerprint, subject_payment_method_account_id (and the counterparty_payment_method_* equivalents), travel_rule_status, travel_rule_required, travel_rule_obligations_count, counterparty_dd_status (your decision on the Travel Rule counterparty: APPROVED, RESTRICTED or BLOCKED, an entity following its brand's), counterparty_risk_level (LOW, MEDIUM or HIGH; HIGH while your own counterparty screening is flagged), counterparty_jurisdiction (the counterparty VASP's country), counterparty_responsiveness (RESPONSIVE, UNRESPONSIVE or UNKNOWN), subject_days_since_previous_transaction, tags, and custom_values. for any key submitted in custom_properties on transaction create. Country-code fields accept ISO-2 or ISO-3; both sides are normalized to ISO-3 before comparison." + }, + "operator": { + "type": "string", + "enum": [ + "eq", + "ne", + "gt", + "gte", + "lt", + "lte", + "in", + "not_in", + "contains", + "not_contains", + "contains_any", + "regex", + "fuzzy_match", + "exists", + "is_not_empty", + "is_not_null", + "not_exists", + "is_empty", + "is_null" + ], + "description": "Comparison operator. in/not_in test membership (value may be a list or a single scalar). contains/not_contains are case-insensitive substring checks against a string value. contains_any takes a list of strings and matches when any is a case-insensitive substring of the field value. regex matches the value pattern against the field. fuzzy_match compares against the value string with the required `score` threshold (0-100). gt/gte/lt/lte never match when either side is missing. exists/is_not_empty/is_not_null (aliases) and not_exists/is_empty/is_null (aliases) are presence checks that ignore `value`." + }, + "value": { + "description": "Comparison value. Its shape depends on the operator and value_type; omit it for the presence-check operators. Every other operator needs a value that is not `null` or an empty list, otherwise the rule is refused with `400`. `contains_any` needs a list of strings, and `contains`, `not_contains` and `regex` need a single string." + }, + "value_type": { + "type": "string", + "enum": [ + "list", + "field", + "relative_date" + ], + "description": "Optional value interpretation. `list`: value is a List UUID and the list's entries become the comparison value. `field`: value is another field path, compared field-to-field. `relative_date`: value is a relative-date object, e.g. {\"direction\": \"past\", \"unit\": \"days\", \"amount\": 30}." + }, + "score": { + "type": "number", + "minimum": 0, + "maximum": 100, + "description": "Required when operator is fuzzy_match: the 0-100 similarity threshold." + }, + "group_index": { + "type": "integer", + "description": "Optional grouped logic. When any condition carries group_index, conditions sharing an index form a group and grouped evaluation replaces evaluation_mode." + }, + "group_logic": { + "type": "string", + "enum": [ + "AND", + "OR" + ], + "default": "AND", + "description": "How conditions combine WITHIN a group." + }, + "groups_logic": { + "type": "string", + "enum": [ + "AND", + "OR" + ], + "default": "OR", + "description": "How group outcomes combine ACROSS groups. Read from the first condition." + } + }, + "description": "A condition reads only the transaction being evaluated - it never filters the aggregation window." + }, + "TransactionRuleAggregation": { + "type": "object", + "required": [ + "value" + ], + "properties": { + "metric": { + "type": "string", + "enum": [ + "count", + "sum", + "max", + "min", + "avg", + "distinct_count" + ], + "default": "count", + "description": "How the matching historical transactions are aggregated. unique_count is accepted as an alias of distinct_count." + }, + "field": { + "type": "string", + "default": "amount", + "description": "Field aggregated by sum/max/min/avg/distinct_count. Ignored for count." + }, + "operator": { + "type": "string", + "enum": [ + "eq", + "ne", + "gt", + "gte", + "lt", + "lte" + ], + "default": "eq", + "description": "Comparison applied to the computed metric." + }, + "value": { + "type": "number", + "description": "Threshold the computed metric is compared against." + }, + "window": { + "type": "string", + "pattern": "^\\d+(m|h|d)$", + "default": "1d", + "description": "Look-back window ending at the transaction's txn_date: m (MINUTES, not months), h (hours) or d (days), e.g. 30m, 24h, 7d, 30d." + }, + "filters": { + "type": "object", + "additionalProperties": true, + "description": "Narrows which historical transactions are aggregated. Keys are field paths from the condition field catalog; a value of \"__current__\" resolves to the same field's value on the transaction being evaluated (e.g. {\"subject_vendor_data\": \"__current__\"} scopes the window to the same subject), and a list value means membership. Records without the grouped value are never grouped together: when the transaction being evaluated has no value for a \"__current__\" field (no counterparty, no device fingerprint), the aggregation does not match, whatever its operator. Filters compare by equality or membership ONLY - there is no lt/gte filter, so a range such as \"historical transactions under 10,000\" cannot be expressed here. Put the range on the metric instead (metric max with operator lt keeps every transaction in the window below the threshold)." + } + }, + "description": "Velocity check over the application's historical transactions inside the window. Every aggregation entry must match for the rule to match, regardless of evaluation_mode. An aggregation constrains only the window; the transaction being evaluated is constrained by scope and conditions, so repeat direction, currency or transaction_type on both halves when the rule promises them." + }, + "TransactionRuleScope": { + "type": "object", + "properties": { + "transaction_types": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Restrict to transaction types, e.g. finance, kyc, travel_rule, user_event. Empty or missing = no restriction." + }, + "directions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ] + }, + "description": "Restrict to transaction directions (case-insensitive on write)." + }, + "action_types": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Restrict to your action_type values, e.g. withdrawal, deposit." + } + }, + "description": "Which transactions the rule applies to. Only these three keys are accepted." + }, + "TransactionRuleAction": { + "oneOf": [ + { + "$ref": "#/components/schemas/TransactionRuleActionAddScore" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionChangeStatus" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionAddTags" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionAddNote" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionAddToList" + }, + { + "$ref": "#/components/schemas/TransactionRuleActionOpenCase" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "add_score": "#/components/schemas/TransactionRuleActionAddScore", + "change_status": "#/components/schemas/TransactionRuleActionChangeStatus", + "add_tags": "#/components/schemas/TransactionRuleActionAddTags", + "add_note": "#/components/schemas/TransactionRuleActionAddNote", + "add_to_list": "#/components/schemas/TransactionRuleActionAddToList", + "open_case": "#/components/schemas/TransactionRuleActionOpenCase" + } + }, + "description": "One rule action. A rule can carry several; all are applied together when it matches. Rules in TEST mode record what they would have done without applying any action." + }, + "TransactionRuleCreateRequest": { + "type": "object", + "required": [ + "title", + "category", + "mode" + ], + "properties": { + "title": { + "type": "string", + "description": "Rule name. Unique per application among non-deleted rules; duplicates return 400." + }, + "description": { + "type": "string", + "nullable": true + }, + "category": { + "type": "string", + "description": "Risk theme used for grouping and reporting, e.g. finance, aml_ctf, anomaly_detection, fatf, device_intelligence, crypto_monitoring, travel_rule, responsible_gaming, e_commerce." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ], + "description": "ACTIVE evaluates live. TEST evaluates and records rule runs but never touches score or status - the recommended starting mode. DISABLED is skipped." + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ], + "default": "ALL", + "description": "Whether every condition must match (ALL) or at least one (ANY). Ignored when conditions use group_index." + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + } + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + } + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAction" + } + }, + "metadata": { + "type": "object", + "description": "Free-form metadata stored on the rule." + } + }, + "description": "Creates a CUSTOM rule. Rules in ACTIVE mode take effect immediately for new transactions - create in TEST mode and backtest first." + }, + "TransactionRuleUpdateRequest": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Rule name. Unique per application among non-deleted rules; duplicates return 400." + }, + "description": { + "type": "string", + "nullable": true + }, + "category": { + "type": "string", + "description": "Risk theme used for grouping and reporting, e.g. finance, aml_ctf, anomaly_detection, fatf, device_intelligence, crypto_monitoring, travel_rule, responsible_gaming, e_commerce." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ], + "description": "ACTIVE evaluates live. TEST evaluates and records rule runs but never touches score or status - the recommended starting mode. DISABLED is skipped." + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ], + "default": "ALL", + "description": "Whether every condition must match (ALL) or at least one (ANY). Ignored when conditions use group_index." + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + } + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + } + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAction" + } + }, + "metadata": { + "type": "object", + "description": "Free-form metadata stored on the rule." + } + }, + "description": "Partial update. Custom rules accept every field; library (PRESET) rules accept only mode - anything else returns 400." + }, + "TransactionRuleDetail": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "title": { + "type": "string", + "description": "Rule name. Unique per application among non-deleted rules; duplicates return 400." + }, + "description": { + "type": "string", + "nullable": true + }, + "category": { + "type": "string", + "description": "Risk theme used for grouping and reporting, e.g. finance, aml_ctf, anomaly_detection, fatf, device_intelligence, crypto_monitoring, travel_rule, responsible_gaming, e_commerce." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "DISABLED", + "TEST" + ], + "description": "ACTIVE evaluates live. TEST evaluates and records rule runs but never touches score or status - the recommended starting mode. DISABLED is skipped." + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ], + "default": "ALL", + "description": "Whether every condition must match (ALL) or at least one (ANY). Ignored when conditions use group_index." + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + } + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + } + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAction" + } + }, + "metadata": { + "type": "object", + "description": "Free-form metadata stored on the rule." + }, + "severity": { + "type": "string", + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ], + "nullable": true, + "description": "Informational severity carried by library rules; not settable on custom rules." + }, + "source": { + "type": "string", + "enum": [ + "PRESET", + "CUSTOM" + ], + "description": "PRESET rules come from the library (install/uninstall, mode-only edits); CUSTOM rules are fully yours." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Stable library identifier; null for custom rules." + }, + "latest_triggered_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "run_count": { + "type": "integer", + "description": "How many transactions matched this rule." + }, + "approved_count": { + "type": "integer" + }, + "reviewed_count": { + "type": "integer" + }, + "declined_count": { + "type": "integer" + }, + "awaiting_user_count": { + "type": "integer" + }, + "approved_pct": { + "type": "number" + }, + "reviewed_pct": { + "type": "number" + }, + "declined_pct": { + "type": "number" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TransactionRuleBacktestRequest": { + "type": "object", + "properties": { + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + }, + "default": [] + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + }, + "default": [] + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ], + "default": "ALL" + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "period_days": { + "type": "integer", + "minimum": 1, + "maximum": 365, + "default": 90 + } + }, + "description": "Hypothetical rule configuration to evaluate against recent transactions. Read-only and free: nothing is created or modified. Send the same conditions, aggregation, evaluation_mode AND scope as the rule you intend to activate. Omitted conditions, aggregation and scope add no constraints of their own; dropping scope can include directions or transaction types the saved rule skips. Omitted evaluation_mode defaults to ALL (which can reduce matches for an ANY rule), and omitted period_days defaults to 90 days, not unlimited history." + }, + "TransactionRuleBacktestResult": { + "type": "object", + "properties": { + "evaluated": { + "type": "integer", + "description": "Transactions evaluated (most recent first, capped)." + }, + "matched": { + "type": "integer", + "description": "Transactions the configuration would have matched." + }, + "affected_entities": { + "type": "integer", + "description": "Distinct subjects (by vendor_data) among the matches." + }, + "period_days": { + "type": "integer" + } + } + }, + "TransactionRuleLibraryEntry": { + "type": "object", + "properties": { + "library_key": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "category": { + "type": "string" + }, + "evaluation_mode": { + "type": "string", + "enum": [ + "ALL", + "ANY" + ] + }, + "scope": { + "$ref": "#/components/schemas/TransactionRuleScope" + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleCondition" + } + }, + "aggregation": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAggregation" + } + }, + "actions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransactionRuleAction" + } + }, + "metadata": { + "type": "object" + }, + "bundle": { + "type": "string", + "nullable": true, + "description": "Functional family, e.g. finance, aml_ctf, crypto_monitoring, travel_rule, responsible_gaming." + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "industries": { + "type": "array", + "items": { + "type": "string" + } + }, + "source": { + "type": "string" + }, + "is_installed": { + "type": "boolean", + "description": "Whether this preset is currently installed on the application." + } + } + }, + "TransactionRuleInstallRequest": { + "type": "object", + "properties": { + "library_keys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Specific presets to install or uninstall." + }, + "bundle": { + "type": "string", + "description": "Install or uninstall every preset in this bundle instead." + } + }, + "description": "Provide library_keys OR bundle." + }, + "TransactionRuleInstallResult": { + "type": "object", + "properties": { + "installed_library_keys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "POST (install) only: presets installed by this call." + }, + "installed_count": { + "type": "integer", + "description": "POST (install) only." + }, + "uninstalled_count": { + "type": "integer", + "description": "DELETE (uninstall) only: presets removed by this call." + } + } + }, + "WalletScreeningResult": { + "type": "object", + "properties": { + "provider": { + "type": "string", + "description": "Provider that performed the screening (e.g. `merklescience`, `crystal`)." + }, + "screening_type": { + "type": "string", + "enum": [ + "WALLET_SCREENING" + ], + "description": "Always `WALLET_SCREENING` for this endpoint." + }, + "risk_score": { + "type": "integer", + "description": "Normalised 0-100 risk score. Higher means greater exposure to risky entities. Merkle Science grades an address 0-5, which maps to 0, 15, 35, 55, 75 or 95." + }, + "severity": { + "type": "string", + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL" + ], + "description": "Risk bucket derived from `risk_score`: `0-9` UNKNOWN, `10-39` LOW, `40-69` MEDIUM, `70-89` HIGH, `90-100` CRITICAL. `UNKNOWN` is the lowest band, not a separate no-data state: `risk_score` 0 (the common clean-address case) means no adverse assessment, while a non-zero score in the 1-9 range is a real but sub-LOW signal - read the `risk_score`, not just the band. Never treat `UNKNOWN` as an affirmative low-risk or clear rating; do not display it as a pass." + }, + "status": { + "type": "string", + "enum": [ + "SCREENED", + "PENDING", + "ERROR" + ], + "description": "Screening outcome status." + }, + "summary": { + "type": "string", + "description": "Human-readable summary of the screening result." + }, + "wallet_address": { + "type": "string", + "description": "The screened address, echoed back." + }, + "transaction_hash": { + "type": "string", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "blockchain": { + "type": "string", + "description": "The blockchain that was screened." + }, + "block_timestamp": { + "type": "string", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "total_value_usd": { + "type": "number", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "fee_usd": { + "type": "number", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "sender_rows": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Transaction screening field, shared with the result shape. Always empty on a wallet screening result." + }, + "receiver_rows": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Transaction screening field, shared with the result shape. Always empty on a wallet screening result." + }, + "transaction_flows": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Transaction screening field, shared with the result shape. Always empty on a wallet screening result." + }, + "transaction_flows_status": { + "type": "string", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "transaction_flows_error": { + "type": "string", + "nullable": true, + "description": "Transaction screening field, shared with the result shape. Always `null` on a wallet screening result." + }, + "source_of_funds": { + "type": "array", + "description": "Where the address received funds from, attributed by entity. Each entry is an exposure breakdown.", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "description": "Normalised risk category, e.g. `exchange_licensed`, `dex`, `defi`, `payment_processor`, `mixer`, `darknet_market`, `sanctioned`, `stolen_funds`, `high_risk_exchange`." + }, + "percentage": { + "type": "number", + "description": "Share of the direction's total exposure, 0-100." + }, + "amount_usd": { + "type": "number", + "nullable": true, + "description": "USD value attributed to this entity." + }, + "entity_name": { + "type": "string", + "nullable": true, + "description": "Name of the attributed entity, for example an exchange, a mixer or a sanctioned group." + }, + "entity_type": { + "type": "string", + "nullable": true, + "description": "Provider entity type (e.g. `Exchange`, `DeFi`, `Sanctions`, `Theft`)." + }, + "entity_subtype": { + "type": "string", + "nullable": true, + "description": "More granular subtype (e.g. `Mandatory KYC and AML`, `OFAC`, `Yield Aggregator`)." + }, + "hops": { + "type": "integer", + "nullable": true, + "description": "Number of hops to the entity (1 for a direct counterparty), when the provider reports one. Always `null` for Merkle Science, which reports only whether exposure is direct or indirect: use `exposure_type`." + }, + "description": { + "type": "string", + "nullable": true, + "description": "`entity_type / entity_subtype`, as the provider labels the entity." + }, + "is_direct": { + "type": "boolean", + "description": "True when `exposure_type` is `direct`." + }, + "exposure_direction": { + "type": "string", + "enum": [ + "incoming", + "outgoing", + "connected" + ], + "description": "Whether the funds came from (`incoming`) or went to (`outgoing`) this entity." + }, + "exposure_type": { + "type": "string", + "enum": [ + "direct", + "indirect" + ], + "description": "`direct` (1 hop) or `indirect` (multi-hop) exposure." + }, + "provider_entity_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for the entity." + }, + "provider_entity_type_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for `entity_type`." + }, + "provider_entity_subtype_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for `entity_subtype`." + }, + "country": { + "type": "string", + "nullable": true, + "description": "Primary ISO 3166-1 alpha-2 country of the entity. Empty or `null` when the provider does not know it." + }, + "raw_amount": { + "type": "number", + "nullable": true, + "description": "Amount in the asset's own unit, when the provider reports it." + }, + "raw_currency": { + "type": "string", + "nullable": true, + "description": "Unit of `raw_amount`, when the provider reports it." + } + } + } + }, + "destination_of_funds": { + "type": "array", + "description": "Where the address sent funds to, attributed by entity. Same item shape as `source_of_funds` with `exposure_direction` = `outgoing`.", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "description": "Normalised risk category, e.g. `exchange_licensed`, `dex`, `defi`, `payment_processor`, `mixer`, `darknet_market`, `sanctioned`, `stolen_funds`, `high_risk_exchange`." + }, + "percentage": { + "type": "number", + "description": "Share of the direction's total exposure, 0-100." + }, + "amount_usd": { + "type": "number", + "nullable": true, + "description": "USD value attributed to this entity." + }, + "entity_name": { + "type": "string", + "nullable": true, + "description": "Name of the attributed entity, for example an exchange, a mixer or a sanctioned group." + }, + "entity_type": { + "type": "string", + "nullable": true, + "description": "Provider entity type (e.g. `Exchange`, `DeFi`, `Sanctions`, `Theft`)." + }, + "entity_subtype": { + "type": "string", + "nullable": true, + "description": "More granular subtype (e.g. `Mandatory KYC and AML`, `OFAC`, `Yield Aggregator`)." + }, + "hops": { + "type": "integer", + "nullable": true, + "description": "Number of hops to the entity (1 for a direct counterparty), when the provider reports one. Always `null` for Merkle Science, which reports only whether exposure is direct or indirect: use `exposure_type`." + }, + "description": { + "type": "string", + "nullable": true, + "description": "`entity_type / entity_subtype`, as the provider labels the entity." + }, + "is_direct": { + "type": "boolean", + "description": "True when `exposure_type` is `direct`." + }, + "exposure_direction": { + "type": "string", + "enum": [ + "incoming", + "outgoing", + "connected" + ], + "description": "Whether the funds came from (`incoming`) or went to (`outgoing`) this entity." + }, + "exposure_type": { + "type": "string", + "enum": [ + "direct", + "indirect" + ], + "description": "`direct` (1 hop) or `indirect` (multi-hop) exposure." + }, + "provider_entity_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for the entity." + }, + "provider_entity_type_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for `entity_type`." + }, + "provider_entity_subtype_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for `entity_subtype`." + }, + "country": { + "type": "string", + "nullable": true, + "description": "Primary ISO 3166-1 alpha-2 country of the entity. Empty or `null` when the provider does not know it." + }, + "raw_amount": { + "type": "number", + "nullable": true, + "description": "Amount in the asset's own unit, when the provider reports it." + }, + "raw_currency": { + "type": "string", + "nullable": true, + "description": "Unit of `raw_amount`, when the provider reports it." + } + } + } + }, + "counterparty_connections": { + "type": "array", + "description": "Entities the provider attributes the screened address to. For Merkle Science these come from the address's own owner and user tags, so the same entity can appear twice, and they carry no fund flows: `received_usd`, `sent_usd`, `received_hops` and `sent_hops` are `null` and `percentage` is 0. Do not route on `risk_level` or `is_direct` here: use `sanctions_hit`, `dominant_risk_category` and `risk_factors[].is_high_risk` for the verdict, and `exposure_type` in `source_of_funds`/`destination_of_funds` for directness.", + "items": { + "type": "object", + "properties": { + "entity_name": { + "type": "string", + "nullable": true, + "description": "Attributed entity name." + }, + "entity_type": { + "type": "string", + "nullable": true, + "description": "Provider entity type." + }, + "entity_subtype": { + "type": "string", + "nullable": true, + "description": "Provider entity subtype." + }, + "risk_level": { + "type": "string", + "description": "Lowercase and provider-dependent. For Merkle Science it is the address's own 0-5 risk grade (`no_risk`, `low`, `medium_low`, `medium`, `high`, `critical`), raised to `high` when the entity's category is high risk; it is not a separate grade of the entity." + }, + "categories": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Normalised risk categories of the entity." + }, + "received_usd": { + "type": "number", + "nullable": true, + "description": "USD received from the entity, when the provider reports it." + }, + "sent_usd": { + "type": "number", + "nullable": true, + "description": "USD sent to the entity, when the provider reports it." + }, + "received_hops": { + "type": "integer", + "nullable": true, + "description": "Hops to the entity on the receiving side, when the provider reports them." + }, + "sent_hops": { + "type": "integer", + "nullable": true, + "description": "Hops to the entity on the sending side, when the provider reports them." + }, + "percentage": { + "type": "number", + "description": "Share of the address's exposure, when the provider reports it; otherwise 0." + }, + "is_direct": { + "type": "boolean", + "description": "Always `true` for Merkle Science, whose connections are attributions rather than counterparties." + }, + "country": { + "type": "string", + "nullable": true, + "description": "Country of the entity, when known." + }, + "provider_entity_id": { + "type": "string", + "nullable": true, + "description": "The provider's own identifier for the entity." + } + } + } + }, + "network_nodes": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Exposure graph nodes for transaction screening. Empty on a wallet screening result." + }, + "network_edges": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Exposure graph edges for transaction screening. Empty on a wallet screening result." + }, + "sanctions_hit": { + "type": "boolean", + "description": "True if the address has direct or indirect sanctions exposure." + }, + "pep_counterparty": { + "type": "boolean", + "description": "Politically exposed person exposure. `null` when no PEP finding is available, which is every result today because no provider reports one; `false` only for an explicit negative screening result. Do not read `null` as a completed PEP check.", + "nullable": true + }, + "dominant_risk_category": { + "type": "string", + "nullable": true, + "description": "Highest-weighted high-risk category, or `null` when none is dominant (e.g. `sanctioned`, `mixer`, `stolen_funds`)." + }, + "risk_factors": { + "type": "array", + "description": "Ranked explanation of `risk_score`: what produced this verdict. Providers grade an address on their own model and return only a verdict (Merkle Science returns an integer 0-5), so the score alone is not actionable. Ordered high-risk first, then by the value that moved through each driver, capped at the five strongest; `source_of_funds` and `destination_of_funds` always carry the complete breakdown. A high `risk_score` with no `is_high_risk` entry means the provider reported no sanctions or attributed-entity match and graded the address purely on its own exposure model - `summary` states this explicitly.", + "items": { + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": [ + "attribution", + "exposure" + ], + "description": "`attribution` is what the provider says the address itself is (its own tag or cluster); `exposure` is where its funds actually moved." + }, + "category": { + "type": "string", + "description": "Normalised risk category, same vocabulary as the exposure breakdown." + }, + "label": { + "type": "string", + "description": "The provider's own wording for the driver (e.g. `Gambling / Crypto only`), which is what you would also see in the provider's console." + }, + "entity_name": { + "type": "string", + "nullable": true, + "description": "Attributed entity behind the driver, for example a gambling site or an exchange." + }, + "direction": { + "type": "string", + "enum": [ + "incoming", + "outgoing", + "connected", + "unknown", + "attribution" + ], + "description": "Direction of the fund flow, `attribution` when the driver is the address's own tag, or `unknown` when the provider did not report a direction." + }, + "exposure_type": { + "type": "string", + "enum": [ + "direct", + "indirect", + "unknown", + "attribution" + ], + "description": "`direct` (1 hop) or `indirect` (multi-hop), `attribution` for the address's own tag, or `unknown` when the provider did not state how many hops away the counterparty is. Treat `unknown` as unclassified, not as `direct`." + }, + "amount_usd": { + "type": "number", + "format": "float", + "nullable": true, + "description": "USD value attributed to this driver." + }, + "percentage": { + "type": "number", + "format": "float", + "description": "Share of the address's TOTAL screened volume, both directions combined - unlike `percentage` inside `source_of_funds`/`destination_of_funds`, which is normalised per direction. Comparable and summable across the whole list." + }, + "is_high_risk": { + "type": "boolean", + "description": "Whether the category is one Didit treats as high risk (`sanctioned`, `mixer`, `stolen_funds`, `ransomware`, `scam`, `darknet_market`, `terrorist_financing`, `child_exploitation`, `hacked_exchange`, `high_risk_exchange`, `seized_funds`)." + }, + "description": { + "type": "string", + "description": "One-line rendering of the driver, e.g. `9.01% indirect outgoing exposure to Gambling / Crypto only (Example Gambling)`." + } + } + } + }, + "raw_response": { + "type": "object", + "additionalProperties": true, + "description": "The provider's own payload. Its shape is provider-specific and can change without notice: read the normalised fields instead of parsing it." + }, + "report_signature": { + "type": "string", + "description": "Opaque HMAC-SHA256 signature over this exact result, scoped to your application. Not meaningful on its own — repost the full response body, unmodified and including this field, to `POST /v3/wallet-screening/pdf/` to render it as a PDF. The PDF endpoint rejects the request with `400` if `report_signature` is missing or if any signed field (e.g. `risk_score`, `severity`, `sanctions_hit`) was edited before repost." + } + }, + "required": [ + "provider", + "screening_type", + "risk_score", + "severity", + "status", + "summary", + "wallet_address", + "blockchain", + "sanctions_hit", + "dominant_risk_category", + "source_of_funds", + "destination_of_funds", + "counterparty_connections", + "report_signature" + ] + }, + "NetworkRiskDisplay": { + "type": "object", + "properties": { + "score": { + "type": "integer", + "description": "Risk score, 0-100." + }, + "band": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ], + "description": "Band the score renders as. high >= 70, medium >= 40, otherwise low." + }, + "bar": { + "type": "number", + "format": "float", + "description": "Normalized 0-1 value for rendering a risk bar." + } + } + }, + "NetworkNodeTag": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string", + "nullable": true, + "description": "Null for guard tags, whose color token the renderer owns." + }, + "source": { + "type": "string", + "enum": [ + "subject_tag", + "session_tag", + "transaction_tag", + "signal_guard" + ], + "description": "Where the tag came from: a tag on the user/business, a tag on a session in this network, a tag on a transaction a party belongs to, or a signal guard condition (e.g. hub, datacenter IP)." + }, + "category": { + "type": "string", + "enum": [ + "customer", + "quality" + ], + "nullable": true, + "description": "customer for customer-defined tags, quality for signal-quality guard tags. Null when not applicable." + } + } + }, + "NetworkListItem": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "network_id": { + "type": "string", + "description": "Human-readable per-organization sequential id, e.g. N-0001." + }, + "name": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "enum": [ + "active", + "in_review", + "resolved", + "dismissed" + ], + "description": "Network lifecycle status. Distinct from the per-applicant outcome_status inside a network (approved/in_review/declined/not_completed)." + }, + "risk": { + "$ref": "#/components/schemas/NetworkRiskDisplay" + }, + "pattern_types": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "exact_same_device", + "similar_device", + "same_ip_address", + "same_address", + "similar_selfie_backgrounds", + "similar_poa_documents", + "same_document_number", + "same_phone_number", + "same_email" + ] + }, + "description": "The clustering patterns detected in this network." + }, + "signal_types": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ip_address", + "device", + "face", + "document_number", + "phone", + "email", + "address" + ] + }, + "description": "The distinct signal types present in this network." + }, + "member_counts": { + "type": "object", + "description": "Counts of members by category (e.g. total, per outcome_status)." + }, + "member_status": { + "type": "object", + "description": "Breakdown of members by outcome_status." + }, + "signal_coverage": { + "type": "object", + "description": "Per signal_type coverage of this network's members." + }, + "first_activity_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_activity_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "NetworkDetail": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkListItem" + }, + { + "type": "object", + "properties": { + "stats": { + "type": "object", + "description": "Aggregate stats for the network (e.g. member totals)." + }, + "fraud_tags": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Fraud-pattern tags surfaced on the network." + }, + "risk_signals": { + "type": "object", + "description": "Risk-signal summaries: residential_proxy_reuse, email_intelligence, phone_intelligence." + }, + "reviewed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reviewed_by": { + "type": "string", + "nullable": true + }, + "review_reason": { + "type": "string", + "nullable": true + } + } + } + ], + "properties": { + "notes_count": { + "type": "integer", + "minimum": 0, + "readOnly": true, + "description": "Number of private analyst notes in this investigation." + } + } + }, + "NetworkGraphNode": { + "type": "object", + "description": "A subject node or a signal node, distinguished by kind. Subject nodes (kind: vendor_user | vendor_business | transaction_party) carry id, kind, subject, node_status (approved | in_review | declined | not_completed), and tags. Signal nodes (kind: signal) carry id, kind, uuid, signal_type, subject_count, coverage, is_hub, is_datacenter_ip, is_high_collision_device, node_status (signal_active | signal_guarded), tags, risk_evidence, first_seen_at, and last_seen_at.", + "properties": { + "id": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "vendor_user", + "vendor_business", + "transaction_party", + "signal" + ] + } + } + }, + "NetworkGraphEdge": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "source": { + "type": "string", + "description": "Subject node id." + }, + "target": { + "type": "string", + "description": "Signal node id." + }, + "subject_kind": { + "type": "string", + "enum": [ + "vendor_user", + "vendor_business", + "transaction_party" + ] + }, + "signal_type": { + "type": "string", + "enum": [ + "ip_address", + "device", + "face", + "document_number", + "phone", + "email", + "address" + ] + }, + "confidence": { + "type": "number", + "format": "float", + "description": "1.0 for an exact match." + }, + "observation_count": { + "type": "integer" + }, + "first_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "NetworkGraph": { + "type": "object", + "properties": { + "network": { + "$ref": "#/components/schemas/NetworkDetail" + }, + "depth": { + "type": "integer", + "nullable": true, + "description": "Effective depth applied, or null when unbounded (depth=all)." + }, + "truncated": { + "type": "boolean", + "description": "True if the neighborhood was cut off by depth." + }, + "nodes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkGraphNode" + } + }, + "edges": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkGraphEdge" + } + }, + "metrics": { + "type": "object", + "properties": { + "node_count": { + "type": "integer" + }, + "edge_count": { + "type": "integer" + }, + "hub_signal_count": { + "type": "integer" + } + } + } + } + }, + "NetworkMember": { + "type": "object", + "properties": { + "subject": { + "type": "object", + "description": "The vendor_user, vendor_business, or transaction_party this member represents." + }, + "outcome_status": { + "type": "string", + "description": "The subject's verification/decision outcome." + }, + "node_status": { + "type": "string", + "enum": [ + "approved", + "in_review", + "declined", + "not_completed", + "signal_active", + "signal_guarded" + ], + "description": "For a member row this is always one of the subject-outcome values: approved, in_review, declined, or not_completed." + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkNodeTag" + } + }, + "risk": { + "$ref": "#/components/schemas/NetworkRiskDisplay" + }, + "signal_coverage": { + "type": "object" + }, + "shared_infrastructure": { + "type": "object" + }, + "joined_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "provenance": { + "type": "object", + "properties": { + "session_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "business_session_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "transaction_ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "NetworkSignal": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "signal_type": { + "type": "string", + "enum": [ + "ip_address", + "device", + "face", + "document_number", + "phone", + "email", + "address" + ] + }, + "subject_count": { + "type": "integer" + }, + "coverage": { + "type": "number", + "format": "float" + }, + "is_hub": { + "type": "boolean", + "description": "True when this signal links an unusually large number of subjects." + }, + "is_datacenter_ip": { + "type": "boolean" + }, + "is_high_collision_device": { + "type": "boolean" + }, + "node_status": { + "type": "string", + "enum": [ + "signal_active", + "signal_guarded" + ] + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkNodeTag" + } + }, + "risk_evidence": { + "type": "object" + }, + "first_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "NetworkTimelineEvent": { + "type": "object", + "properties": { + "event_type": { + "type": "string", + "description": "e.g. network_detected, signal_observed, network_in_review, network_resolved, network_dismissed." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "label": { + "type": "string" + }, + "metadata": { + "type": "object" + } + } + }, + "NetworkMap": { + "type": "object", + "properties": { + "network": { + "$ref": "#/components/schemas/NetworkDetail" + }, + "convergence_count": { + "type": "integer", + "description": "Number of distinct address/location points members converge on." + }, + "countries": { + "type": "array", + "items": { + "type": "string" + } + }, + "points": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "country": { + "type": "string", + "nullable": true + }, + "country_code": { + "type": "string", + "nullable": true + }, + "city": { + "type": "string", + "nullable": true + }, + "latitude": { + "type": "number", + "format": "float", + "nullable": true + }, + "longitude": { + "type": "number", + "format": "float", + "nullable": true + }, + "observed_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + } + } + } + }, + "EntityNetworkMembership": { + "type": "object", + "properties": { + "network": { + "$ref": "#/components/schemas/NetworkListItem" + }, + "subject": { + "type": "object" + }, + "signal_types": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "ip_address", + "device", + "face", + "document_number", + "phone", + "email", + "address" + ] + } + }, + "signal_count": { + "type": "integer" + }, + "joined_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_seen_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "RiskSignals": { + "type": "object", + "description": "Optional device and network context observed by your application. All fields are optional. Passing them feeds cross-session match detection in Networks.", + "properties": { + "ip": { + "type": "string", + "format": "ip", + "description": "IPv4 or IPv6 address observed for the end user.", + "example": "192.0.2.1" + }, + "device_id": { + "type": "string", + "maxLength": 255, + "description": "Stable device identifier collected by your application (Android `ANDROID_ID`, iOS `identifierForVendor`)." + }, + "device_platform": { + "type": "string", + "enum": [ + "android", + "ios", + "ipados", + "tvos", + "web" + ], + "description": "Platform of the end user's device." + }, + "device_model": { + "type": "string", + "maxLength": 255, + "description": "Device model reported by your application." + }, + "os_version": { + "type": "string", + "maxLength": 64, + "description": "Operating-system version." + }, + "app_version": { + "type": "string", + "maxLength": 64, + "description": "Version of your application." + }, + "user_agent": { + "type": "string", + "maxLength": 512, + "description": "Browser or app user agent." + } + } + }, + "PhoneRiskRequest": { + "type": "object", + "required": [ + "phone_number" + ], + "properties": { + "phone_number": { + "type": "string", + "maxLength": 20, + "description": "Phone number to score. It is validated and normalized to E.164.", + "example": "+14155552671" + }, + "country_code": { + "type": "string", + "maxLength": 3, + "description": "Optional ISO 3166-1 alpha-2 or alpha-3 country hint for intelligence coverage.", + "example": "US" + }, + "signals": { + "$ref": "#/components/schemas/RiskSignals" + }, + "vendor_data": { + "type": "string", + "description": "Your stable reference for this request. Stored on the API session and echoed in the response." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Your non-sensitive JSON metadata. Stored on the API session and echoed in the response." + }, + "enable_social": { + "type": "boolean", + "default": false, + "description": "When true, also look up the social footprint of the number - the messaging, social, e-commerce and professional platforms it is registered on. Billed as one `phone_social` unit per request in addition to this endpoint's own price.", + "example": true + } + } + }, + "EmailRiskRequest": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "Email address to score.", + "example": "alice@example.com" + }, + "country_code": { + "type": "string", + "maxLength": 3, + "description": "Optional ISO 3166-1 alpha-2 or alpha-3 country hint for intelligence coverage.", + "example": "US" + }, + "signals": { + "$ref": "#/components/schemas/RiskSignals" + }, + "vendor_data": { + "type": "string", + "description": "Your stable reference for this request. Stored on the API session and echoed in the response." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Your non-sensitive JSON metadata. Stored on the API session and echoed in the response." + }, + "enable_social": { + "type": "boolean", + "default": false, + "description": "When true, also look up the social footprint of the address - the email, social, professional, e-commerce and entertainment platforms it is registered on. Billed as one `email_social` unit per request in addition to this endpoint's own price.", + "example": true + } + } + }, + "IPRiskRequest": { + "type": "object", + "required": [ + "ip_address" + ], + "properties": { + "ip_address": { + "type": "string", + "format": "ip", + "description": "IPv4 or IPv6 address to score.", + "example": "8.8.8.8" + }, + "claimed_country": { + "type": "string", + "maxLength": 3, + "description": "Optional ISO 3166-1 alpha-2 or alpha-3 country claimed by the user or session. Used to calculate `country_mismatch`.", + "example": "ESP" + }, + "user_agent": { + "type": "string", + "maxLength": 512, + "description": "User agent observed by your backend. Stored as request context; it does not provide device fingerprinting." + }, + "vendor_data": { + "type": "string", + "description": "Your stable reference for this request. Stored on the API session and echoed in the response." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Your non-sensitive JSON metadata. Stored on the API session and echoed in the response." + } + } + }, + "RiskWarning": { + "type": "object", + "required": [ + "feature", + "risk", + "log_type", + "short_description", + "long_description" + ], + "properties": { + "feature": { + "type": "string", + "description": "Feature that produced the warning." + }, + "risk": { + "type": "string", + "description": "Machine-readable risk code. New values can be added over time." + }, + "additional_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Risk-specific context, or `null`." + }, + "log_type": { + "type": "string", + "enum": [ + "error", + "warning", + "information" + ], + "description": "Severity used by the decision rules." + }, + "short_description": { + "type": "string", + "description": "Human-readable one-line summary." + }, + "long_description": { + "type": "string", + "description": "Human-readable explanation." + } + } + }, + "RiskLifecycleEvent": { + "type": "object", + "required": [ + "type", + "timestamp", + "details", + "fee" + ], + "properties": { + "type": { + "type": "string", + "description": "Machine-readable event type. New values can be added over time." + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When the event happened." + }, + "details": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Event-specific payload, or `null`." + }, + "fee": { + "type": "number", + "description": "USD amount billed for this event." + } + } + }, + "PhoneMatchItem": { + "type": "object", + "description": "Another session of your application that used the same phone number with a different `vendor_data`.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the other verification that used this phone number." + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of that session." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of that session." + }, + "verification_date": { + "type": "string", + "description": "When that session was created (`YYYY-MM-DDTHH:MM:SSZ`)." + }, + "phone_number": { + "type": "string", + "description": "The matched phone number." + }, + "status": { + "type": "string", + "description": "Status of that session (e.g., `Approved`, `Declined`, `In Progress`)." + }, + "is_blocklisted": { + "type": "boolean", + "description": "Whether the phone number is on your application's blocklist." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "For API-type sessions, the standalone API that created it; `null` for regular verification sessions." + }, + "source": { + "type": "string", + "description": "Where the match was found. Usually `session`; a blocklist hit via an organization list entry uses `list_entry`." + } + } + }, + "EmailMatchItem": { + "type": "object", + "description": "Another session of your application that used the same email address with a different `vendor_data`.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Session id of the other verification that used this email address." + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of that session." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "`vendor_data` of that session." + }, + "verification_date": { + "type": "string", + "description": "When that session was created (`YYYY-MM-DDTHH:MM:SSZ`)." + }, + "email": { + "type": "string", + "description": "The matched email address." + }, + "status": { + "type": "string", + "description": "Status of that session (e.g., `Approved`, `Declined`, `In Progress`)." + }, + "is_blocklisted": { + "type": "boolean", + "description": "Whether the email address is on your application's blocklist." + }, + "api_service": { + "type": "string", + "nullable": true, + "description": "For API-type sessions, the standalone API that created it; `null` for regular verification sessions." + }, + "source": { + "type": "string", + "description": "Where the match was found. Usually `session`; a blocklist hit via an organization list entry uses `list_entry`." + } + } + }, + "PhoneIntelligence": { + "type": "object", + "description": "Normalized phone-intelligence block. Present only when the intelligence add-on ran for this lookup; when present it is also exposed under the `enrichment` alias with identical values.", + "properties": { + "score": { + "type": "number", + "nullable": true, + "description": "Normalized 0-100 risk score. Higher is riskier." + }, + "phone_type_risk": { + "type": "string", + "nullable": true, + "description": "`Low`, `Medium` or `High`, derived from the score." + }, + "phone_trust_index": { + "type": "number", + "nullable": true, + "description": "Inverse of the score, for dashboards that prefer higher is better." + }, + "network_stability_index": { + "type": "number", + "nullable": true, + "description": "Stability of the line's network." + }, + "is_disposable": { + "type": "boolean", + "nullable": true, + "description": "`true` when the number looks disposable or virtual." + }, + "is_ported": { + "type": "boolean", + "nullable": true, + "description": "`true` when the number has moved carrier." + }, + "is_recently_ported": { + "type": "boolean", + "nullable": true, + "description": "`true` when the port happened recently." + }, + "carrier_type": { + "type": "string", + "nullable": true, + "description": "Line classification reported by the lookup (e.g., `mobile`)." + }, + "reason_codes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Stable Didit identifier of the signal, for example `IR-5AC67A32`. Opaque: group and filter on it, do not parse it." + }, + "value": { + "nullable": true, + "description": "For a risk flag (for example `voip`, `leaked`, `disposable`, `spam_trap`), its value: a boolean, a level or a date. Null for a scored reason code." + }, + "direction": { + "type": "string", + "enum": [ + "risk", + "trust" + ], + "description": "Whether the signal raised the risk or added trust." + }, + "message": { + "type": "string", + "description": "Human-readable explanation of the signal, in English." + }, + "impact_level": { + "type": "string", + "nullable": true, + "description": "How strongly the signal moved the score, for example `low`, `medium` or `high`." + } + } + }, + "description": "Signals that explain the score, each with a stable Didit code, a direction, a message and an impact level. New codes can appear over time, so do not reject unknown codes." + } + } + }, + "EmailIntelligence": { + "type": "object", + "description": "Normalized email-intelligence block. Present only when the intelligence add-on ran for this email; when present it is also exposed as the `enrichment` alias with identical values.", + "properties": { + "score": { + "type": "number", + "nullable": true, + "description": "Normalized 0-100 risk score. Higher is riskier." + }, + "deliverable": { + "type": "string", + "nullable": true, + "description": "Deliverability band (e.g., `high`)." + }, + "breach_risk_level": { + "type": "string", + "nullable": true, + "description": "`Low`, `Medium` or `High`." + }, + "breach_risk_index": { + "type": "number", + "nullable": true, + "description": "Numeric breach-risk index." + }, + "is_recent_breach": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address appears in a recent breach." + }, + "is_frequent_breached": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address appears in many breaches." + }, + "is_disposable": { + "type": "boolean", + "nullable": true, + "description": "`true` when the domain is a throwaway provider." + }, + "is_free_domain": { + "type": "boolean", + "nullable": true, + "description": "`true` when the domain is a free mail provider." + }, + "is_breached": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address appears in known breaches." + }, + "no_of_breaches": { + "type": "number", + "nullable": true, + "description": "Number of known breaches containing the address." + }, + "reason_codes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Stable Didit identifier of the signal, for example `IR-5AC67A32`. Opaque: group and filter on it, do not parse it." + }, + "value": { + "nullable": true, + "description": "For a risk flag (for example `voip`, `leaked`, `disposable`, `spam_trap`), its value: a boolean, a level or a date. Null for a scored reason code." + }, + "direction": { + "type": "string", + "enum": [ + "risk", + "trust" + ], + "description": "Whether the signal raised the risk or added trust." + }, + "message": { + "type": "string", + "description": "Human-readable explanation of the signal, in English." + }, + "impact_level": { + "type": "string", + "nullable": true, + "description": "How strongly the signal moved the score, for example `low`, `medium` or `high`." + } + } + }, + "description": "Signals that explain the score, each with a stable Didit code, a direction, a message and an impact level. New codes can appear over time, so do not reject unknown codes." + } + } + }, + "IPRiskResult": { + "type": "object", + "description": "IP risk result. Individual enrichment fields can be `null` when the lookup returned no usable data.", + "required": [ + "status", + "threat" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review" + ], + "description": "`In Review` when the resolved country conflicts with `claimed_country`, or when the address is associated with VPN, Tor, or data-center infrastructure. Otherwise `Approved`." + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "The scored address. `null` when no enrichment source returned usable data." + }, + "ip_country": { + "type": "string", + "nullable": true, + "description": "Resolved country name." + }, + "ip_country_code": { + "type": "string", + "nullable": true, + "description": "Resolved country code. Compare countries through `country_mismatch` instead of comparing raw country strings yourself." + }, + "ip_state": { + "type": "string", + "nullable": true, + "description": "Resolved state or region." + }, + "ip_city": { + "type": "string", + "nullable": true, + "description": "Resolved city." + }, + "latitude": { + "type": "number", + "nullable": true, + "description": "Approximate network geolocation, not a precise device location." + }, + "longitude": { + "type": "number", + "nullable": true, + "description": "Approximate network geolocation, not a precise device location." + }, + "isp": { + "type": "string", + "nullable": true, + "description": "Internet service provider serving the address." + }, + "organization": { + "type": "string", + "nullable": true, + "description": "Organization owning the address block." + }, + "is_vpn_or_tor": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address is associated with VPN or Tor infrastructure." + }, + "is_data_center": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address belongs to a hosting or data-center network." + }, + "time_zone": { + "type": "string", + "nullable": true, + "description": "Time zone of the resolved location." + }, + "time_zone_offset": { + "type": "string", + "nullable": true, + "description": "UTC offset of the resolved location." + }, + "asn_number": { + "type": "number", + "nullable": true, + "description": "Autonomous system number." + }, + "asn_organization": { + "type": "string", + "nullable": true, + "description": "Organization operating the autonomous system." + }, + "connection_type": { + "type": "string", + "nullable": true, + "description": "Connection classification (e.g., `cellular`, `hosting`)." + }, + "carrier": { + "type": "string", + "nullable": true, + "description": "Mobile carrier when the address is on a mobile network." + }, + "proxy_type": { + "type": "string", + "nullable": true, + "description": "Proxy classification when the address is behind a proxy." + }, + "claimed_country": { + "type": "string", + "nullable": true, + "description": "Echo of the `claimed_country` you sent." + }, + "country_mismatch": { + "type": "boolean", + "nullable": true, + "description": "`true` when the resolved country differs from `claimed_country`. `null` when no enrichment data or no claimed country is available." + }, + "threat": { + "type": "object", + "additionalProperties": true, + "description": "Free-form network-threat evidence from the enrichment source (e.g., `is_proxy`, `is_tor`). Empty object when no enrichment data is available. Treat as diagnostic detail, not a stable contract." + } + } + }, + "PhoneRiskResponse": { + "type": "object", + "required": [ + "request_id", + "status", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the API session created for this request. Store it with `vendor_data` if you need to correlate the result with later webhooks or console activity." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Final decision for this API session. Normally `Approved`; the risk evaluation can produce `In Review` or `Declined`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, `null` when you did not send one." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` you sent, `null` when you did not send one." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this response." + }, + "phone": { + "type": "object", + "description": "Normalized phone report, the same block used by Phone Verification responses.", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Decision for this phone number. Mirrors the top-level `status`." + }, + "phone_number_prefix": { + "type": "string", + "description": "Country calling code with leading `+` (e.g., `+1`).", + "example": "+1" + }, + "phone_number": { + "type": "string", + "description": "National number without the country calling code.", + "example": "4155552671" + }, + "full_number": { + "type": "string", + "description": "Complete number in E.164 format.", + "example": "+14155552671" + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of the number.", + "example": "US" + }, + "country_name": { + "type": "string", + "description": "Full country name derived from `country_code`.", + "example": "United States" + }, + "carrier": { + "type": "object", + "nullable": true, + "description": "Current network serving the number. `null` when the lookup could not resolve a carrier.", + "properties": { + "name": { + "type": "string", + "description": "Carrier name. Empty string when the lookup could not identify it." + }, + "type": { + "type": "string", + "description": "Line type reported by the lookup: `mobile`, `fixed_line`, `voip`, `isp`, `vpn`, `toll_free`, `premium_rate`, or similar." + } + } + }, + "is_disposable": { + "type": "boolean", + "description": "`true` when the number belongs to a temporary or burner number service." + }, + "is_virtual": { + "type": "boolean", + "description": "`true` when the line type is virtual (`voip`, `isp`, or `vpn`)." + }, + "verification_method": { + "type": "string", + "nullable": true, + "description": "OTP channel of the underlying verification. `null` for risk lookups, which never send a code." + }, + "verification_attempts": { + "type": "integer", + "description": "OTP sends for this number. `0` for risk lookups." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the number was last verified by an OTP. `null` for risk lookups." + }, + "warnings": { + "type": "array", + "description": "Risk warnings recorded for this request. Empty array when there are none.", + "items": { + "$ref": "#/components/schemas/RiskWarning" + } + }, + "lifecycle": { + "type": "array", + "description": "Chronological audit trail of the request. Empty array for risk lookups without send events.", + "items": { + "$ref": "#/components/schemas/RiskLifecycleEvent" + } + }, + "matches": { + "type": "array", + "description": "Other sessions of your application where the same phone number was used by a different user (different `vendor_data`). Up to 5 entries. Empty array when there are no matches.", + "items": { + "$ref": "#/components/schemas/PhoneMatchItem" + } + }, + "phone_intelligence": { + "$ref": "#/components/schemas/PhoneIntelligence" + }, + "enrichment": { + "$ref": "#/components/schemas/PhoneIntelligence" + }, + "phone_social": { + "$ref": "#/components/schemas/PhoneSocialFootprint" + } + } + } + } + }, + "EmailRiskResponse": { + "type": "object", + "required": [ + "request_id", + "status", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the API session created for this request. Store it with `vendor_data` if you need to correlate the result with later webhooks or console activity." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Final decision for this API session. Normally `Approved`; the risk evaluation can produce `In Review` or `Declined`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, `null` when you did not send one." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` you sent, `null` when you did not send one." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this response." + }, + "email": { + "type": "object", + "description": "Normalized email report, the same block used by Email Verification responses.", + "required": [ + "status", + "email", + "is_breached", + "breaches", + "is_disposable", + "is_undeliverable" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review", + "Declined" + ], + "description": "Decision for this email address. Mirrors the top-level `status`." + }, + "email": { + "type": "string", + "description": "The scored address.", + "example": "alice@example.com" + }, + "is_breached": { + "type": "boolean", + "description": "Whether the address appears in a known credential breach." + }, + "breaches": { + "type": "array", + "description": "Up to 5 most recent known breaches containing this address. Empty array when `is_breached` is `false`.", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Breach identifier." + }, + "domain": { + "type": "string", + "description": "Domain of the breached service." + }, + "breach_date": { + "type": "string", + "format": "date", + "description": "Date the breach occurred." + }, + "breach_emails_count": { + "type": "integer", + "description": "Total number of accounts exposed in the breach." + }, + "description": { + "type": "string", + "description": "Human-readable description of the breach (may contain HTML markup)." + }, + "logo_path": { + "type": "string", + "description": "URL of the breached service's logo." + }, + "data_classes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Categories of data exposed, in snake_case (e.g., `email_addresses`, `passwords`)." + }, + "is_verified": { + "type": "boolean", + "description": "Whether the breach has been verified as legitimate." + } + } + } + }, + "is_disposable": { + "type": "boolean", + "description": "Whether the domain belongs to a throwaway provider." + }, + "is_undeliverable": { + "type": "boolean", + "description": "Whether the address was found undeliverable." + }, + "verification_attempts": { + "type": "integer", + "description": "OTP sends for this address. `0` for risk lookups." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the address was last verified by an OTP. `null` for risk lookups." + }, + "warnings": { + "type": "array", + "description": "Risk warnings recorded for this request. Empty array when there are none.", + "items": { + "$ref": "#/components/schemas/RiskWarning" + } + }, + "lifecycle": { + "type": "array", + "description": "Chronological audit trail of the request. Empty array for risk lookups without sends.", + "items": { + "$ref": "#/components/schemas/RiskLifecycleEvent" + } + }, + "matches": { + "type": "array", + "description": "Other sessions of your application where the same email address was used by a different user (different `vendor_data`). Up to 5 entries. Empty array when there are no matches.", + "items": { + "$ref": "#/components/schemas/EmailMatchItem" + } + }, + "email_intelligence": { + "$ref": "#/components/schemas/EmailIntelligence" + }, + "enrichment": { + "$ref": "#/components/schemas/EmailIntelligence" + }, + "email_social": { + "$ref": "#/components/schemas/EmailSocialFootprint" + } + } + } + } + }, + "IPRiskResponse": { + "type": "object", + "required": [ + "request_id", + "status", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Id of the API session created for this request. Store it with `vendor_data` if you need to correlate the result with later webhooks or console activity." + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review" + ], + "description": "Decision for this API session: `In Review` on network or country-mismatch risk, otherwise `Approved`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent, `null` when you did not send one." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` you sent, `null` when you did not send one." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp of this response." + }, + "ip": { + "type": "object", + "description": "IP risk result. Individual enrichment fields can be `null` when the lookup returned no usable data.", + "required": [ + "status", + "threat" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "Approved", + "In Review" + ], + "description": "`In Review` when the resolved country conflicts with `claimed_country`, or when the address is associated with VPN, Tor, or data-center infrastructure. Otherwise `Approved`." + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "The scored address. `null` when no enrichment source returned usable data." + }, + "ip_country": { + "type": "string", + "nullable": true, + "description": "Resolved country name." + }, + "ip_country_code": { + "type": "string", + "nullable": true, + "description": "Resolved country code. Compare countries through `country_mismatch` instead of comparing raw country strings yourself." + }, + "ip_state": { + "type": "string", + "nullable": true, + "description": "Resolved state or region." + }, + "ip_city": { + "type": "string", + "nullable": true, + "description": "Resolved city." + }, + "latitude": { + "type": "number", + "nullable": true, + "description": "Approximate network geolocation, not a precise device location." + }, + "longitude": { + "type": "number", + "nullable": true, + "description": "Approximate network geolocation, not a precise device location." + }, + "isp": { + "type": "string", + "nullable": true, + "description": "Internet service provider serving the address." + }, + "organization": { + "type": "string", + "nullable": true, + "description": "Organization owning the address block." + }, + "is_vpn_or_tor": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address is associated with VPN or Tor infrastructure." + }, + "is_data_center": { + "type": "boolean", + "nullable": true, + "description": "`true` when the address belongs to a hosting or data-center network." + }, + "time_zone": { + "type": "string", + "nullable": true, + "description": "Time zone of the resolved location." + }, + "time_zone_offset": { + "type": "string", + "nullable": true, + "description": "UTC offset of the resolved location." + }, + "asn_number": { + "type": "number", + "nullable": true, + "description": "Autonomous system number." + }, + "asn_organization": { + "type": "string", + "nullable": true, + "description": "Organization operating the autonomous system." + }, + "connection_type": { + "type": "string", + "nullable": true, + "description": "Connection classification (e.g., `cellular`, `hosting`)." + }, + "carrier": { + "type": "string", + "nullable": true, + "description": "Mobile carrier when the address is on a mobile network." + }, + "proxy_type": { + "type": "string", + "nullable": true, + "description": "Proxy classification when the address is behind a proxy." + }, + "claimed_country": { + "type": "string", + "nullable": true, + "description": "Echo of the `claimed_country` you sent." + }, + "country_mismatch": { + "type": "boolean", + "nullable": true, + "description": "`true` when the resolved country differs from `claimed_country`. `null` when no enrichment data or no claimed country is available." + }, + "threat": { + "type": "object", + "additionalProperties": true, + "description": "Free-form network-threat evidence from the enrichment source (e.g., `is_proxy`, `is_tor`). Empty object when no enrichment data is available. Treat as diagnostic detail, not a stable contract." + } + } + } + } + }, + "SocialFootprintPlatform": { + "type": "object", + "properties": { + "platform": { + "type": "string", + "description": "Platform identifier.", + "example": "whatsapp" + }, + "category": { + "type": "string", + "description": "Platform category, e.g. MESSAGING, SOCIAL_MEDIA, ECOMMERCE, PROFESSIONAL, EMAIL_PROVIDER.", + "example": "MESSAGING" + }, + "scope": { + "type": "string", + "enum": [ + "global", + "local" + ], + "description": "`global` for worldwide platforms, `local` for region-specific ones.", + "example": "global" + } + } + }, + "SocialFootprintCategorySummary": { + "type": "object", + "properties": { + "registered": { + "type": "integer", + "nullable": true, + "description": "Platforms in this category where registration is confirmed.", + "example": 2 + }, + "ratio": { + "type": "number", + "format": "float", + "nullable": true, + "description": "Share of the category's checked platforms with a registration, 0.0-1.0.", + "example": 0.6667 + } + } + }, + "SocialFootprintProfile": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "description": "Profile name, when the platform exposes one." + }, + "photo": { + "type": "string", + "nullable": true, + "description": "Profile photo URL, when the platform exposes one." + }, + "about": { + "type": "string", + "nullable": true, + "description": "Profile about/bio text, when the platform exposes one." + }, + "privacy_status": { + "type": "string", + "nullable": true, + "enum": [ + "PUBLIC", + "PRIVATE", + null + ], + "description": "Profile visibility, when the platform exposes it." + }, + "business_account": { + "type": "boolean", + "nullable": true, + "description": "Whether the profile is a business account, when the platform exposes it." + } + } + }, + "PhoneSocialFootprint": { + "type": "object", + "nullable": true, + "description": "Social footprint of the phone number: the online platforms it is registered on. Present only when `enable_social` was set and the check completed. Every count may be null when no data was returned for it. `profiles_registered: 0` means the platforms were checked and the phone number was found on none of them - a valid, billable answer and a common synthetic-identity signal.", + "properties": { + "profiles_registered": { + "type": "integer", + "nullable": true, + "description": "Platforms where the phone number is confirmed registered.", + "example": 5 + }, + "profiles_checked": { + "type": "integer", + "nullable": true, + "description": "Platforms evaluated for registration.", + "example": 14 + }, + "names_found": { + "type": "integer", + "nullable": true, + "description": "Platforms that returned an associated name.", + "example": 0 + }, + "photos_found": { + "type": "integer", + "nullable": true, + "description": "Platforms that returned a profile photo.", + "example": 0 + }, + "communication_breadth": { + "type": "integer", + "nullable": true, + "description": "Communication platforms with detected presence.", + "example": 2 + }, + "digital_footprint_diversity": { + "type": "integer", + "nullable": true, + "description": "Distinct platform categories with detected presence.", + "example": 5 + }, + "digital_sophistication_index": { + "type": "integer", + "nullable": true, + "description": "Composite digital-maturity indicator, 0-10.", + "example": 5 + }, + "engagement_depth_index": { + "type": "integer", + "nullable": true, + "description": "Profile-richness indicator, 0-10.", + "example": 3 + }, + "subscription_affordability_index": { + "type": "integer", + "nullable": true, + "description": "Free vs paid platform mix, 0-10.", + "example": 2 + }, + "free_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on free platforms.", + "example": 4 + }, + "full_paid_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on fully paid platforms.", + "example": 0 + }, + "partial_paid_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on freemium platforms.", + "example": 1 + }, + "whatsapp_profile_completeness": { + "type": "string", + "nullable": true, + "enum": [ + "Basic", + "Partial", + "Complete", + "Not Present", + null + ], + "description": "Completeness of the WhatsApp profile. Phone only; null for email.", + "example": "Partial" + }, + "registered_platforms": { + "type": "array", + "description": "Platforms where registration is confirmed.", + "items": { + "$ref": "#/components/schemas/SocialFootprintPlatform" + } + }, + "not_registered_platforms": { + "type": "array", + "description": "Platforms checked with a negative result.", + "items": { + "$ref": "#/components/schemas/SocialFootprintPlatform" + } + }, + "categories": { + "type": "object", + "description": "Registered count and ratio per platform category.", + "additionalProperties": { + "$ref": "#/components/schemas/SocialFootprintCategorySummary" + } + }, + "profiles": { + "type": "object", + "description": "Per-platform profile details, keyed by platform, when the platform exposes them.", + "additionalProperties": { + "$ref": "#/components/schemas/SocialFootprintProfile" + } + } + } + }, + "EmailSocialFootprint": { + "type": "object", + "nullable": true, + "description": "Social footprint of the email address: the online platforms it is registered on. Present only when `enable_social` was set and the check completed. Every count may be null when no data was returned for it. `profiles_registered: 0` means the platforms were checked and the email address was found on none of them - a valid, billable answer and a common synthetic-identity signal.", + "properties": { + "profiles_registered": { + "type": "integer", + "nullable": true, + "description": "Platforms where the email address is confirmed registered.", + "example": 5 + }, + "profiles_checked": { + "type": "integer", + "nullable": true, + "description": "Platforms evaluated for registration.", + "example": 14 + }, + "names_found": { + "type": "integer", + "nullable": true, + "description": "Platforms that returned an associated name.", + "example": 0 + }, + "photos_found": { + "type": "integer", + "nullable": true, + "description": "Platforms that returned a profile photo.", + "example": 0 + }, + "communication_breadth": { + "type": "integer", + "nullable": true, + "description": "Communication platforms with detected presence.", + "example": 2 + }, + "digital_footprint_diversity": { + "type": "integer", + "nullable": true, + "description": "Distinct platform categories with detected presence.", + "example": 5 + }, + "digital_sophistication_index": { + "type": "integer", + "nullable": true, + "description": "Composite digital-maturity indicator, 0-10.", + "example": 5 + }, + "engagement_depth_index": { + "type": "integer", + "nullable": true, + "description": "Profile-richness indicator, 0-10.", + "example": 3 + }, + "subscription_affordability_index": { + "type": "integer", + "nullable": true, + "description": "Free vs paid platform mix, 0-10.", + "example": 2 + }, + "free_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on free platforms.", + "example": 4 + }, + "full_paid_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on fully paid platforms.", + "example": 0 + }, + "partial_paid_platforms": { + "type": "integer", + "nullable": true, + "description": "Registrations on freemium platforms.", + "example": 1 + }, + "whatsapp_profile_completeness": { + "type": "string", + "nullable": true, + "enum": [ + "Basic", + "Partial", + "Complete", + "Not Present", + null + ], + "description": "Completeness of the WhatsApp profile. Phone only; null for email.", + "example": "Partial" + }, + "registered_platforms": { + "type": "array", + "description": "Platforms where registration is confirmed.", + "items": { + "$ref": "#/components/schemas/SocialFootprintPlatform" + } + }, + "not_registered_platforms": { + "type": "array", + "description": "Platforms checked with a negative result.", + "items": { + "$ref": "#/components/schemas/SocialFootprintPlatform" + } + }, + "categories": { + "type": "object", + "description": "Registered count and ratio per platform category.", + "additionalProperties": { + "$ref": "#/components/schemas/SocialFootprintCategorySummary" + } + }, + "profiles": { + "type": "object", + "description": "Per-platform profile details, keyed by platform, when the platform exposes them.", + "additionalProperties": { + "$ref": "#/components/schemas/SocialFootprintProfile" + } + } + } + }, + "KYBRegistryAddress": { + "type": "object", + "description": "An address the registry states, in the same shape for every country and registry source. Every key is always present; a part the registry does not publish is null.", + "properties": { + "type": { + "type": "string", + "nullable": true, + "description": "Address type in lower case: `registered` for the registered office; other types as the registry names them (for example `postal`, `office`). `null` when the registry does not say what the address is." + }, + "address": { + "type": "string", + "description": "The full address as one line. Always present on every address." + }, + "street": { + "type": "string", + "nullable": true, + "description": "Street and number." + }, + "city": { + "type": "string", + "nullable": true, + "description": "City or locality." + }, + "postal_code": { + "type": "string", + "nullable": true, + "description": "Postal code." + }, + "region": { + "type": "string", + "nullable": true, + "description": "Region, state or province." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code." + }, + "description": { + "type": "string", + "nullable": true, + "description": "The registry's own label for the address, when it gives one." + } + }, + "required": [ + "type", + "address", + "street", + "city", + "postal_code", + "region", + "country_code", + "description" + ] + }, + "KYBRegistryIndustry": { + "type": "object", + "description": "An activity the registry classifies the company under, in the same shape for every country and registry source.", + "properties": { + "code": { + "type": "string", + "nullable": true, + "description": "Activity code as the registry prints it (for example `73110`)." + }, + "description": { + "type": "string", + "nullable": true, + "description": "Activity label, when the registry publishes one." + }, + "scheme": { + "type": "string", + "nullable": true, + "description": "Classification system, one name per system whichever source reported it: `UK SIC 2007`, `US SIC`, `NACE Rev. 2`, `NACE Rev. 2.1`, `NAF Rev. 2`, `CNAE`, `SBI`, `NOGA 2008`, `CAE Rev. 4`, `Canada SIC 1977`; other national systems keep the registry's own name. Null when the registry does not name the system." + }, + "is_primary": { + "type": "boolean", + "description": "True for the company's main activity. Exactly one entry is primary when the list is not empty." + } + }, + "required": [ + "code", + "description", + "scheme", + "is_primary" + ] + }, + "KYBRegistryShareholding": { + "type": "object", + "nullable": true, + "description": "What an owner holds, in the same shape for every country and registry source. Null when the registry states no holding. A band (for example a UK PSC's 25-50%) is not a stake: `percentage` is null and the band stays in `ownership_min_shares` / `ownership_max_shares`.", + "properties": { + "share_class": { + "type": "string", + "nullable": true, + "description": "Share class or type as the registry names it." + }, + "number_of_shares": { + "type": "number", + "nullable": true, + "description": "Number of shares held." + }, + "currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 currency of the values below." + }, + "nominal_value": { + "type": "number", + "nullable": true, + "description": "Nominal value per share." + }, + "total_value": { + "type": "number", + "nullable": true, + "description": "Total value of the holding." + }, + "percentage": { + "type": "number", + "nullable": true, + "description": "Exact stake in percent, to four decimal places." + } + }, + "required": [ + "share_class", + "number_of_shares", + "currency", + "nominal_value", + "total_value", + "percentage" + ] + }, + "KYBRegistryOwnership": { + "type": "object", + "description": "The outcome of the ownership (Shareholders / UBO) tier, in the same shape for every country and registry source.", + "properties": { + "status": { + "type": "string", + "nullable": true, + "enum": [ + "not_requested", + "pending", + "delivered", + "not_held", + "unavailable", + null + ], + "description": "`delivered` when the registry published ownership; `not_held` when the ownership registry answered and does not list the company at all (see `unavailable_reason`); `unavailable` when it did not serve the ownership data for any other reason (see `unavailable_reason`); `pending` while the registry is still fetching it; `not_requested` for the Basic tier." + }, + "tier_requested": { + "type": "string", + "nullable": true, + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "The tier the workflow ordered." + }, + "tier_delivered": { + "type": "string", + "nullable": true, + "enum": [ + "basic", + "shareholders", + "ubo", + null + ], + "description": "The tier the registry delivered (and that is billed). A UBO request answered with direct shareholders only is `shareholders`." + }, + "unavailable_reason": { + "type": "string", + "nullable": true, + "enum": [ + "no_owners_on_record", + "no_natural_person_owner", + "ownership_section_unavailable", + "company_not_found", + "ambiguous_company_match", + "missing_registration_number", + "entity_type_not_covered", + "provider_unavailable", + "other", + null + ], + "description": "Why a requested ownership tier was not delivered. Null unless `status` is `unavailable` or `pending`." + }, + "direct_ownership_complete": { + "type": "boolean", + "nullable": true, + "description": "True when the direct shareholders' stated stakes add up to 100% (within one percentage point of rounding); false when they do not; null when ownership was not delivered or a stake is not stated." + }, + "unresolved": { + "type": "array", + "description": "Parts of the ownership chain the registry could not resolve, for example no group structure on record above the direct owners.", + "items": { + "type": "object", + "properties": { + "reason": { + "type": "string" + }, + "section": { + "type": "string", + "nullable": true + } + } + } + } + }, + "required": [ + "status", + "tier_requested", + "tier_delivered", + "unavailable_reason", + "direct_ownership_complete", + "unresolved" + ] + }, + "KYBRegistryData": { + "type": "object", + "nullable": true, + "description": "Didit's normalized registry detail for the company: the ownership outcome (`ownership_error`, `section_errors`, `ubo_resolution`, `lite_enrichment`), the corrections made with update-data, and registry facts that have no field of their own. It never carries the registry source's own record. Every key is optional and present only when the company has a value for it; new keys may be added. Read the canonical fields (`addresses`, `industries`, `ownership`, `beneficial_owners[].shareholding`) for anything they cover.", + "properties": { + "company_name": { + "type": "string", + "description": "Company name as corrected in the console." + }, + "registration_number": { + "type": "string", + "description": "Registration number as the registry or a console correction states it." + }, + "country_code": { + "type": "string", + "description": "Country of the registration as the registry or a console correction states it." + }, + "region": { + "type": "string", + "description": "Region or state of the registration." + }, + "incorporation_date": { + "type": "string", + "description": "Incorporation date (ISO 8601)." + }, + "registration_date": { + "type": "string", + "description": "Registration date (ISO 8601) when the registry states one apart from the incorporation date." + }, + "dissolution_date": { + "type": "string", + "description": "Dissolution date (ISO 8601) when the registry states one." + }, + "tax_number": { + "type": "string", + "description": "Tax identification number." + }, + "vat_number": { + "type": "string", + "description": "VAT number." + }, + "legal_address": { + "type": "string", + "description": "Legal address as corrected in the console." + }, + "registered_address": { + "type": "string", + "description": "Registered address as the registry states it." + }, + "company_type": { + "type": "string", + "description": "Legal form." + }, + "registry_status": { + "type": "string", + "description": "Company status at the registry." + }, + "verification_status": { + "type": "string", + "description": "Verification status." + }, + "alternative_names": { + "type": "string", + "description": "Other names the company trades or traded under." + }, + "nature_of_business": { + "type": "string", + "description": "Activity description." + }, + "registered_capital": { + "type": "string", + "description": "Registered capital as printed." + }, + "registered_capital_amount": { + "type": "string", + "description": "Registered capital amount, as a decimal string." + }, + "registered_capital_currency": { + "type": "string", + "description": "Registered capital currency (ISO 4217)." + }, + "website": { + "type": "string", + "description": "Website." + }, + "email": { + "type": "string", + "description": "Contact email." + }, + "phone": { + "type": "string", + "description": "Contact phone." + }, + "legal_entity_identifier": { + "type": "string", + "description": "Legal Entity Identifier (LEI)." + }, + "location_of_registration": { + "type": "string", + "description": "Place of registration." + }, + "control_scheme": { + "type": "string", + "description": "Governance scheme when the registry exposes it." + }, + "ownership_error": { + "type": "string", + "enum": [ + "no_owners_on_record", + "no_natural_person_owner", + "ownership_section_unavailable", + "company_not_found", + "ambiguous_company_match", + "missing_registration_number", + "entity_type_not_covered", + "provider_unavailable", + "other" + ], + "description": "Why a requested ownership tier was not delivered, in the same codes as `ownership.unavailable_reason`. `no_owners_on_record` (the registry answered and names no owner) and `ownership_section_unavailable` (the registry did not return the ownership section) are different findings." + }, + "section_errors": { + "type": "object", + "description": "Sections the registry did not return, keyed by section (`officers`, `shareholders`, `group_structure`). A section listed here was never delivered: an empty list next to it says nothing about the company.", + "additionalProperties": { + "$ref": "#/components/schemas/KYBRegistrySectionError" + } + }, + "ubo_resolution": { + "$ref": "#/components/schemas/KYBRegistryUboResolution" + }, + "lite_enrichment": { + "$ref": "#/components/schemas/KYBRegistryLiteEnrichment" + }, + "registry_metadata": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ] + }, + "description": "The register's own fields for the company, keyed as the register names them, as plain values (legacy records). Only fields reviewed as registry facts are included." + }, + "registry_sources": { + "type": "array", + "description": "The official registers the record was read from (legacy records). Other sources are not named.", + "items": { + "$ref": "#/components/schemas/KYBRegistrySource" + } + }, + "previous_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names the company was registered under before (legacy records)." + }, + "parent_companies": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Parent companies the registry names (legacy records)." + }, + "documents_available": { + "type": "integer", + "description": "How many filings the registry offers for the company (legacy records)." + } + } + }, + "KYBRegistryGroupCompany": { + "type": "object", + "description": "A company of the corporate group, by its registry identity.", + "properties": { + "name": { + "type": "string", + "nullable": true, + "description": "Company name." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "Country of registration." + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Registration number." + } + }, + "required": [ + "name", + "country_code", + "registration_number" + ] + }, + "KYBRegistryUboResolution": { + "type": "object", + "description": "How the UBO tier walked the ownership chain above the company.", + "properties": { + "parent_chain": { + "type": "array", + "description": "Corporate parents from the ultimate parent down to the company.", + "items": { + "$ref": "#/components/schemas/KYBRegistryGroupCompany" + } + }, + "subsidiaries": { + "type": "array", + "description": "Companies directly below the company in its corporate group.", + "items": { + "$ref": "#/components/schemas/KYBRegistryGroupCompany" + } + }, + "unresolved": { + "type": "array", + "description": "Parts of the chain the registry could not resolve (the same entries as `ownership.unresolved`).", + "items": { + "type": "object", + "properties": { + "reason": { + "type": "string", + "description": "Why, for example `no_group_structure_on_record`, `selected_company_not_in_group_structure` or `section_error`." + }, + "section": { + "type": "string", + "nullable": true, + "description": "The section concerned, for example `group_structure`." + } + }, + "required": [ + "reason", + "section" + ] + } + } + }, + "required": [ + "parent_chain", + "subsidiaries", + "unresolved" + ] + }, + "KYBRegistrySectionError": { + "type": "object", + "description": "A section the registry did not return.", + "properties": { + "status_code": { + "type": "integer", + "nullable": true, + "description": "The status the registry answered for the section." + } + }, + "required": [ + "status_code" + ] + }, + "KYBRegistryLiteEnrichment": { + "type": "object", + "description": "Blank Lite profile fields another of the country's registry sources filled. The source is not named.", + "properties": { + "fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The company fields that were filled." + }, + "people_added": { + "type": "integer", + "description": "How many officers or owners were added." + } + }, + "required": [ + "fields", + "people_added" + ] + }, + "KYBRegistrySource": { + "type": "object", + "description": "An official register the record was read from. Only official government registers, regulators and the official identifier system are named; any other source is left out.", + "properties": { + "name": { + "type": "string", + "description": "Register name, for example `UK Companies House`." + }, + "source_type": { + "type": "string", + "enum": [ + "official" + ], + "description": "Always `official`." + } + }, + "required": [ + "name", + "source_type" + ] + }, + "KYBOwnershipStructure": { + "type": "object", + "nullable": true, + "description": "The cap table the registry proved, as it stood when the ownership tier was delivered: direct `shareholders` and `beneficial_owners`. Null when the registry proved no ownership. It never carries the registry source's own rows; read `company.beneficial_owners` and `company.ownership` for the current record.", + "properties": { + "shareholders": { + "type": "array", + "items": { + "$ref": "#/components/schemas/KYBOwnershipStructureOwner" + } + }, + "beneficial_owners": { + "type": "array", + "items": { + "$ref": "#/components/schemas/KYBOwnershipStructureOwner" + } + } + }, + "required": [ + "shareholders", + "beneficial_owners" + ] + }, + "KYBOwnershipStructureOwner": { + "type": "object", + "description": "One owner of the ownership snapshot: the same registry identity and stake as `company.beneficial_owners[]`, and its canonical `shareholding`.", + "properties": { + "name": { + "type": "string", + "nullable": true, + "description": "Owner name as the registry states it." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name, for a natural person, when the registry states it." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name, for a natural person, when the registry states it." + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Date of birth (ISO 8601) when the registry publishes it." + }, + "country_of_birth": { + "type": "string", + "nullable": true, + "description": "Country of birth when the registry publishes it." + }, + "company_name": { + "type": "string", + "nullable": true, + "description": "Company name, for a corporate owner." + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Registration number, for a corporate owner." + }, + "entity_type": { + "type": "string", + "nullable": true, + "enum": [ + "person", + "company", + null + ], + "description": "Whether the owner is a natural person or a company." + }, + "nationality": { + "type": "string", + "nullable": true, + "description": "Nationality when the registry states it." + }, + "designation": { + "type": "string", + "nullable": true, + "description": "The owner's title or role as the registry prints it." + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "`shareholder` for a direct holder, `ubo` for a beneficial owner." + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the stake, in percent (equal to the upper bound for an exact stake)." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the stake, in percent." + }, + "voting_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the voting rights, in percent." + }, + "voting_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the voting rights, in percent." + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the holding or appointment started (ISO 8601)." + }, + "is_active": { + "type": "boolean", + "nullable": true, + "description": "False for a former owner." + }, + "shareholding": { + "$ref": "#/components/schemas/KYBRegistryShareholding" + } + }, + "required": [ + "name", + "first_name", + "last_name", + "date_of_birth", + "country_of_birth", + "company_name", + "registration_number", + "entity_type", + "nationality", + "designation", + "roles", + "ownership_min_shares", + "ownership_max_shares", + "voting_min_shares", + "voting_max_shares", + "appointment_date", + "is_active", + "shareholding" + ] + }, + "DigitalIdWalletApiWalletVerificationError": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Stable machine-readable code, e.g. wallet_unavailable." + }, + "detail": { + "type": "string" + } + }, + "required": [ + "detail", + "error" + ] + }, + "DigitalIdWalletApiWalletVerificationStartForbidden": { + "oneOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiClientCredentialsForbiddenErrorMessage" + }, + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + } + ] + }, + "DigitalIdWalletApiClientCredentialsForbiddenErrorMessage": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "default": "Forbidden - User doesn't have permission" + } + } + }, + "DigitalIdWalletApiWalletVerificationStartBadRequest": { + "oneOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationError" + }, + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationFieldErrors" + } + ] + }, + "DigitalIdWalletApiWalletVerificationFieldErrors": { + "type": "object", + "description": "Request validation errors: each rejected field with its messages.", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "DigitalIdWalletApiWalletVerificationResponse": { + "type": "object", + "description": "One standalone wallet verification, as every wallet endpoint answers it.", + "properties": { + "request_id": { + "type": "string", + "format": "uuid" + }, + "wallet_verification": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResult" + }, + "vendor_data": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "The metadata object sent at start, or null." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "created_at", + "metadata", + "request_id", + "vendor_data", + "wallet_verification" + ] + }, + "DigitalIdWalletApiWalletVerificationResult": { + "type": "object", + "properties": { + "wallet": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWallet" + } + ], + "readOnly": true + }, + "country": { + "type": "string" + }, + "status": { + "type": "string", + "description": "Not Finished while pending, then Approved (verified) or Declined." + }, + "outcome": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletVerificationResultOutcomeEnum" + }, + "error": { + "type": "string", + "nullable": true, + "readOnly": true + }, + "interaction": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiInteractionEnum" + } + ], + "readOnly": true + }, + "simulated": { + "type": "boolean", + "readOnly": true + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "next_action": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletNextAction" + } + ], + "nullable": true, + "readOnly": true + }, + "identity": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletIdentity" + } + ], + "nullable": true, + "readOnly": true + }, + "attributes": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "readOnly": true + }, + "level_of_assurance": { + "type": "string", + "nullable": true + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "signature_valid": { + "type": "boolean", + "nullable": true, + "description": "True once the credential's signature was verified; null for a simulated one." + }, + "credential_type": { + "type": "string", + "nullable": true + } + }, + "required": [ + "attributes", + "country", + "credential_type", + "error", + "expires_at", + "identity", + "interaction", + "level_of_assurance", + "next_action", + "outcome", + "signature_valid", + "simulated", + "status", + "verified_at", + "wallet" + ] + }, + "DigitalIdWalletApiWalletIdentity": { + "type": "object", + "properties": { + "full_name": { + "type": "string", + "nullable": true + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true, + "description": "ISO3 nationality, only when the wallet asserts one (never inferred)." + }, + "issuing_state": { + "type": "string", + "description": "ISO3 country whose scheme issued the identity." + } + }, + "required": [ + "date_of_birth", + "first_name", + "full_name", + "issuing_state", + "last_name", + "nationality" + ] + }, + "DigitalIdWalletApiWalletNextAction": { + "type": "object", + "properties": { + "type": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletNextActionTypeEnum" + } + ], + "description": "redirect: send the user's browser to url. confirm_in_app: show verification_code to the user, who checks it matches the code in their wallet app and confirms there; then poll. request_digital_credential: in a user gesture on the origin you started with, ask the wallet with digital_credentials.request, then send what it returns to POST .../verifications/{request_id}/response/.\n\n* `redirect` - redirect\n* `confirm_in_app` - confirm_in_app\n* `request_digital_credential` - request_digital_credential" + }, + "url": { + "type": "string", + "format": "uri", + "description": "redirect only: where the user signs in with the wallet." + }, + "digital_credentials": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletDigitalCredentialsRequest" + } + ], + "description": "request_digital_credential only: the request to hand to the wallet." + }, + "verification_code": { + "type": "string", + "description": "confirm_in_app only: the 4-digit comparison code the wallet app shows too." + }, + "poll_interval_seconds": { + "type": "integer", + "description": "confirm_in_app only: how often to call POST .../verifications/{request_id}/poll/ for prompt feedback. Didit also polls pending sign-ins in the background, so the webhook reports the outcome even when you do not poll." + } + }, + "required": [ + "type" + ] + }, + "DigitalIdWalletApiWalletNextActionTypeEnum": { + "enum": [ + "redirect", + "confirm_in_app", + "request_digital_credential" + ], + "type": "string", + "description": "* `redirect` - redirect\n* `confirm_in_app` - confirm_in_app\n* `request_digital_credential` - request_digital_credential" + }, + "DigitalIdWalletApiInteractionEnum": { + "enum": [ + "redirect", + "in_app", + "digital_credentials" + ], + "type": "string", + "description": "* `redirect` - redirect\n* `in_app` - in_app\n* `digital_credentials` - digital_credentials" + }, + "DigitalIdWalletApiWalletVerificationResultOutcomeEnum": { + "enum": [ + "pending", + "verified", + "cancelled", + "timeout", + "failed" + ], + "type": "string", + "description": "* `pending` - Pending\n* `verified` - Verified\n* `cancelled` - Cancelled by the user\n* `timeout` - Timed out\n* `failed` - Failed" + }, + "DigitalIdWalletApiWallet": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Catalog id of the wallet, e.g. bankid_se." + }, + "name": { + "type": "string" + }, + "issuing_authority": { + "type": "string", + "nullable": true + } + }, + "required": [ + "id", + "issuing_authority", + "name" + ] + }, + "DigitalIdWalletApiEudiWalletVerificationStartRequest": { + "type": "object", + "description": "Start a EUDI Wallet verification (AUT, BEL, BGR, HRV, CYP, CZE, DNK, EST, FIN, FRA, DEU, GRC, HUN, IRL, ITA, LVA, LTU, LUX, MLT, NLD, POL, PRT, ROU, SVK, SVN, ESP, SWE, ISL, LIE, NOR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiEudiWalletVerificationStartCountryEnum" + } + ], + "description": "ISO 3166-1 alpha-3 country of the EUDI Wallet identity.\n\n* `AUT` - AUT\n* `BEL` - BEL\n* `BGR` - BGR\n* `HRV` - HRV\n* `CYP` - CYP\n* `CZE` - CZE\n* `DNK` - DNK\n* `EST` - EST\n* `FIN` - FIN\n* `FRA` - FRA\n* `DEU` - DEU\n* `GRC` - GRC\n* `HUN` - HUN\n* `IRL` - IRL\n* `ITA` - ITA\n* `LVA` - LVA\n* `LTU` - LTU\n* `LUX` - LUX\n* `MLT` - MLT\n* `NLD` - NLD\n* `POL` - POL\n* `PRT` - PRT\n* `ROU` - ROU\n* `SVK` - SVK\n* `SVN` - SVN\n* `ESP` - ESP\n* `SWE` - SWE\n* `ISL` - ISL\n* `LIE` - LIE\n* `NOR` - NOR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "country", + "return_url" + ] + }, + "DigitalIdWalletApiDeviceEnum": { + "enum": [ + "same_device", + "cross_device" + ], + "type": "string", + "description": "* `same_device` - same_device\n* `cross_device` - cross_device" + }, + "DigitalIdWalletApiEudiWalletVerificationStartCountryEnum": { + "enum": [ + "AUT", + "BEL", + "BGR", + "HRV", + "CYP", + "CZE", + "DNK", + "EST", + "FIN", + "FRA", + "DEU", + "GRC", + "HUN", + "IRL", + "ITA", + "LVA", + "LTU", + "LUX", + "MLT", + "NLD", + "POL", + "PRT", + "ROU", + "SVK", + "SVN", + "ESP", + "SWE", + "ISL", + "LIE", + "NOR" + ], + "type": "string", + "description": "* `AUT` - AUT\n* `BEL` - BEL\n* `BGR` - BGR\n* `HRV` - HRV\n* `CYP` - CYP\n* `CZE` - CZE\n* `DNK` - DNK\n* `EST` - EST\n* `FIN` - FIN\n* `FRA` - FRA\n* `DEU` - DEU\n* `GRC` - GRC\n* `HUN` - HUN\n* `IRL` - IRL\n* `ITA` - ITA\n* `LVA` - LVA\n* `LTU` - LTU\n* `LUX` - LUX\n* `MLT` - MLT\n* `NLD` - NLD\n* `POL` - POL\n* `PRT` - PRT\n* `ROU` - ROU\n* `SVK` - SVK\n* `SVN` - SVN\n* `ESP` - ESP\n* `SWE` - SWE\n* `ISL` - ISL\n* `LIE` - LIE\n* `NOR` - NOR" + }, + "DigitalIdWalletApiConnectidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a ConnectID verification (AUS).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiConnectidWalletVerificationStartCountryEnum" + } + ], + "default": "AUS", + "description": "ISO 3166-1 alpha-3 country of the ConnectID identity. Defaults to AUS, the only country it serves.\n\n* `AUS` - AUS" + }, + "authorisation_server_id": { + "type": "string", + "minLength": 1, + "description": "Authorisation server ID selected with ConnectID's bank selector. Sandbox defaults to Mock Bank A.", + "maxLength": 36 + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiConnectidWalletVerificationStartCountryEnum": { + "enum": [ + "AUS" + ], + "type": "string", + "description": "* `AUS` - AUS" + }, + "DigitalIdWalletApiAudkenniWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Auðkenni verification (ISL).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiAudkenniWalletVerificationStartCountryEnum" + } + ], + "default": "ISL", + "description": "ISO 3166-1 alpha-3 country of the Auðkenni identity. Defaults to ISL, the only country it serves.\n\n* `ISL` - ISL" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiAudkenniWalletVerificationStartCountryEnum": { + "enum": [ + "ISL" + ], + "type": "string", + "description": "* `ISL` - ISL" + }, + "DigitalIdWalletApiFranceconnectWalletVerificationStartRequest": { + "type": "object", + "description": "Start a FranceConnect verification (FRA).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiFranceconnectWalletVerificationStartCountryEnum" + } + ], + "default": "FRA", + "description": "ISO 3166-1 alpha-3 country of the FranceConnect identity. Defaults to FRA, the only country it serves.\n\n* `FRA` - FRA" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiFranceconnectWalletVerificationStartCountryEnum": { + "enum": [ + "FRA" + ], + "type": "string", + "description": "* `FRA` - FRA" + }, + "DigitalIdWalletApiDiiaWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Diia verification (UKR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDiiaWalletVerificationStartCountryEnum" + } + ], + "default": "UKR", + "description": "ISO 3166-1 alpha-3 country of the Diia identity. Defaults to UKR, the only country it serves.\n\n* `UKR` - UKR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiDiiaWalletVerificationStartCountryEnum": { + "enum": [ + "UKR" + ], + "type": "string", + "description": "* `UKR` - UKR" + }, + "DigitalIdWalletApiMojeidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a MojeID verification (CZE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryE2dEnum" + } + ], + "default": "CZE", + "description": "ISO 3166-1 alpha-3 country of the MojeID identity. Defaults to CZE, the only country it serves.\n\n* `CZE` - CZE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiCountryE2dEnum": { + "enum": [ + "CZE" + ], + "type": "string", + "description": "* `CZE` - CZE" + }, + "DigitalIdWalletApiBankidCzWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Bank iD verification (CZE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryE2dEnum" + } + ], + "default": "CZE", + "description": "ISO 3166-1 alpha-3 country of the Bank iD identity. Defaults to CZE, the only country it serves.\n\n* `CZE` - CZE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiEparakstsMobileWalletVerificationStartRequest": { + "type": "object", + "description": "Start a eParaksts mobile verification (LVA).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiEparakstsMobileWalletVerificationStartCountryEnum" + } + ], + "default": "LVA", + "description": "ISO 3166-1 alpha-3 country of the eParaksts mobile identity. Defaults to LVA, the only country it serves.\n\n* `LVA` - LVA" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiEparakstsMobileWalletVerificationStartCountryEnum": { + "enum": [ + "LVA" + ], + "type": "string", + "description": "* `LVA` - LVA" + }, + "DigitalIdWalletApiIdcardEeWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Estonian ID-card verification (EST).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiIdcardEeWalletVerificationStartCountryEnum" + } + ], + "default": "EST", + "description": "ISO 3166-1 alpha-3 country of the Estonian ID-card identity. Defaults to EST, the only country it serves.\n\n* `EST` - EST" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiIdcardEeWalletVerificationStartCountryEnum": { + "enum": [ + "EST" + ], + "type": "string", + "description": "* `EST` - EST" + }, + "DigitalIdWalletApiMobileIdWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Mobile-ID verification (EST, LTU).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiMobileIdWalletVerificationStartCountryEnum" + } + ], + "description": "ISO 3166-1 alpha-3 country of the Mobile-ID identity.\n\n* `EST` - EST\n* `LTU` - LTU" + }, + "personal_code": { + "type": "string", + "minLength": 1, + "description": "The user's national personal identification code. It addresses the user's Mobile-ID app; the user confirms the comparison code and enters their PIN in the app. Never collect the PIN.", + "pattern": "^[0-9-]{6,20}$" + }, + "phone_number": { + "type": "string", + "minLength": 1, + "description": "The Mobile-ID phone number in E.164 format (+372... or +370...).", + "maxLength": 16 + } + }, + "required": [ + "country", + "personal_code", + "phone_number" + ] + }, + "DigitalIdWalletApiMobileIdWalletVerificationStartCountryEnum": { + "enum": [ + "EST", + "LTU" + ], + "type": "string", + "description": "* `EST` - EST\n* `LTU` - LTU" + }, + "DigitalIdWalletApiSmartIdWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Smart-ID verification (EST, LVA, LTU, BEL).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiSmartIdWalletVerificationStartCountryEnum" + } + ], + "description": "ISO 3166-1 alpha-3 country of the Smart-ID identity.\n\n* `EST` - EST\n* `LVA` - LVA\n* `LTU` - LTU\n* `BEL` - BEL" + }, + "personal_code": { + "type": "string", + "minLength": 1, + "description": "The user's national personal identification code. It addresses the user's Smart-ID app; the user confirms the comparison code and enters their PIN in the app. Never collect the PIN.", + "pattern": "^[0-9-]{6,20}$" + } + }, + "required": [ + "country", + "personal_code" + ] + }, + "DigitalIdWalletApiSmartIdWalletVerificationStartCountryEnum": { + "enum": [ + "EST", + "LVA", + "LTU", + "BEL" + ], + "type": "string", + "description": "* `EST` - EST\n* `LVA` - LVA\n* `LTU` - LTU\n* `BEL` - BEL" + }, + "DigitalIdWalletApiGovukWalletWalletVerificationStartRequest": { + "type": "object", + "description": "Start a GOV.UK Wallet verification (GBR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountry520Enum" + } + ], + "default": "GBR", + "description": "ISO 3166-1 alpha-3 country of the GOV.UK Wallet identity. Defaults to GBR, the only country it serves.\n\n* `GBR` - GBR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiCountry520Enum": { + "enum": [ + "GBR" + ], + "type": "string", + "description": "* `GBR` - GBR" + }, + "DigitalIdWalletApiOneidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a OneID verification (GBR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountry520Enum" + } + ], + "default": "GBR", + "description": "ISO 3166-1 alpha-3 country of the OneID identity. Defaults to GBR, the only country it serves.\n\n* `GBR` - GBR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiGovbrWalletVerificationStartRequest": { + "type": "object", + "description": "Start a gov.br verification (BRA).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiGovbrWalletVerificationStartCountryEnum" + } + ], + "default": "BRA", + "description": "ISO 3166-1 alpha-3 country of the gov.br identity. Defaults to BRA, the only country it serves.\n\n* `BRA` - BRA" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiGovbrWalletVerificationStartCountryEnum": { + "enum": [ + "BRA" + ], + "type": "string", + "description": "* `BRA` - BRA" + }, + "DigitalIdWalletApiUaePassWalletVerificationStartRequest": { + "type": "object", + "description": "Start a UAE PASS verification (ARE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiUaePassWalletVerificationStartCountryEnum" + } + ], + "default": "ARE", + "description": "ISO 3166-1 alpha-3 country of the UAE PASS identity. Defaults to ARE, the only country it serves.\n\n* `ARE` - ARE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiUaePassWalletVerificationStartCountryEnum": { + "enum": [ + "ARE" + ], + "type": "string", + "description": "* `ARE` - ARE" + }, + "DigitalIdWalletApiFrejaidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Freja eID verification (SWE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryEe9Enum" + } + ], + "default": "SWE", + "description": "ISO 3166-1 alpha-3 country of the Freja eID identity. Defaults to SWE, the only country it serves.\n\n* `SWE` - SWE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiCountryEe9Enum": { + "enum": [ + "SWE" + ], + "type": "string", + "description": "* `SWE` - SWE" + }, + "DigitalIdWalletApiPersonalausweisWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Personalausweis verification (DEU).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiPersonalausweisWalletVerificationStartCountryEnum" + } + ], + "default": "DEU", + "description": "ISO 3166-1 alpha-3 country of the Personalausweis identity. Defaults to DEU, the only country it serves.\n\n* `DEU` - DEU" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiPersonalausweisWalletVerificationStartCountryEnum": { + "enum": [ + "DEU" + ], + "type": "string", + "description": "* `DEU` - DEU" + }, + "DigitalIdWalletApiFtnWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Finnish Trust Network verification (FIN).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiFtnWalletVerificationStartCountryEnum" + } + ], + "default": "FIN", + "description": "ISO 3166-1 alpha-3 country of the Finnish Trust Network identity. Defaults to FIN, the only country it serves.\n\n* `FIN` - FIN" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiFtnWalletVerificationStartCountryEnum": { + "enum": [ + "FIN" + ], + "type": "string", + "description": "* `FIN` - FIN" + }, + "DigitalIdWalletApiIdinWalletVerificationStartRequest": { + "type": "object", + "description": "Start a iDIN verification (NLD).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiIdinWalletVerificationStartCountryEnum" + } + ], + "default": "NLD", + "description": "ISO 3166-1 alpha-3 country of the iDIN identity. Defaults to NLD, the only country it serves.\n\n* `NLD` - NLD" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiIdinWalletVerificationStartCountryEnum": { + "enum": [ + "NLD" + ], + "type": "string", + "description": "* `NLD` - NLD" + }, + "DigitalIdWalletApiItsmeWalletVerificationStartRequest": { + "type": "object", + "description": "Start a itsme verification (BEL, LUX, NLD).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiItsmeWalletVerificationStartCountryEnum" + } + ], + "description": "ISO 3166-1 alpha-3 country of the itsme identity.\n\n* `BEL` - BEL\n* `LUX` - LUX\n* `NLD` - NLD" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "country", + "return_url" + ] + }, + "DigitalIdWalletApiItsmeWalletVerificationStartCountryEnum": { + "enum": [ + "BEL", + "LUX", + "NLD" + ], + "type": "string", + "description": "* `BEL` - BEL\n* `LUX` - LUX\n* `NLD` - NLD" + }, + "DigitalIdWalletApiBuypassWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Buypass ID verification (NOR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryAfcEnum" + } + ], + "default": "NOR", + "description": "ISO 3166-1 alpha-3 country of the Buypass ID identity. Defaults to NOR, the only country it serves.\n\n* `NOR` - NOR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiCountryAfcEnum": { + "enum": [ + "NOR" + ], + "type": "string", + "description": "* `NOR` - NOR" + }, + "DigitalIdWalletApiVippsWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Vipps verification (NOR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryAfcEnum" + } + ], + "default": "NOR", + "description": "ISO 3166-1 alpha-3 country of the Vipps identity. Defaults to NOR, the only country it serves.\n\n* `NOR` - NOR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiBankidNoWalletVerificationStartRequest": { + "type": "object", + "description": "Start a BankID verification (NOR).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryAfcEnum" + } + ], + "default": "NOR", + "description": "ISO 3166-1 alpha-3 country of the BankID identity. Defaults to NOR, the only country it serves.\n\n* `NOR` - NOR" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiBankidSeWalletVerificationStartRequest": { + "type": "object", + "description": "Start a BankID verification (SWE).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiCountryEe9Enum" + } + ], + "default": "SWE", + "description": "ISO 3166-1 alpha-3 country of the BankID identity. Defaults to SWE, the only country it serves.\n\n* `SWE` - SWE" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiMitidWalletVerificationStartRequest": { + "type": "object", + "description": "Start a MitID verification (DNK).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiMitidWalletVerificationStartCountryEnum" + } + ], + "default": "DNK", + "description": "ISO 3166-1 alpha-3 country of the MitID identity. Defaults to DNK, the only country it serves.\n\n* `DNK` - DNK" + }, + "return_url": { + "type": "string", + "format": "uri", + "minLength": 1, + "description": "https URL the user's browser returns to after the wallet, with request_id and result appended. Its host must be registered in the application's allowed_return_hosts (or be its white-label domain). Never trust result on its own: read the outcome with GET .../verifications/{request_id}/ or the webhook.", + "maxLength": 2048 + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiDeviceEnum" + } + ], + "default": "same_device", + "description": "same_device when the user signs in on the device showing your page, cross_device when they use another one (e.g. BankID's QR code).\n\n* `same_device` - same_device\n* `cross_device` - cross_device" + } + }, + "required": [ + "return_url" + ] + }, + "DigitalIdWalletApiMitidWalletVerificationStartCountryEnum": { + "enum": [ + "DNK" + ], + "type": "string", + "description": "* `DNK` - DNK" + }, + "DigitalIdWalletApiWalletCatalogResponse": { + "type": "object", + "properties": { + "wallets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DigitalIdWalletApiWalletCatalogEntry" + } + } + }, + "required": [ + "wallets" + ] + }, + "DigitalIdWalletApiWalletCatalogEntry": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string", + "description": "The wallet's own start endpoint." + }, + "countries": { + "type": "array", + "items": { + "type": "string" + } + }, + "issuing_authority": { + "type": "string" + }, + "level_of_assurance": { + "type": "string", + "nullable": true + }, + "interaction": { + "$ref": "#/components/schemas/DigitalIdWalletApiInteractionEnum" + }, + "required_fields": { + "type": "array", + "items": { + "type": "string" + } + }, + "available": { + "type": "boolean", + "description": "Whether this application can start it in this environment now. Listed does not mean live." + } + }, + "required": [ + "available", + "countries", + "id", + "interaction", + "issuing_authority", + "level_of_assurance", + "name", + "path", + "required_fields" + ] + }, + "CaseDuplicateCaseDuplicate": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "case_number": { + "type": "string", + "nullable": true, + "maxLength": 32 + }, + "title": { + "type": "string", + "maxLength": 255 + }, + "status": { + "$ref": "#/components/schemas/CaseDuplicateStatusEnum" + }, + "priority": { + "$ref": "#/components/schemas/CaseDuplicatePriorityEnum" + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "created_at", + "title", + "uuid" + ] + }, + "CaseDuplicateCaseDuplicatePage": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next_offset": { + "type": "integer", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseDuplicateCaseDuplicate" + } + } + }, + "required": [ + "count", + "next_offset", + "results" + ] + }, + "RoutingPublicationPolicyDraftResponse": { + "type": "object", + "properties": { + "draft": { + "allOf": [ + { + "$ref": "#/components/schemas/RoutingPublicationPolicyDraft" + } + ], + "nullable": true + } + }, + "required": [ + "draft" + ] + }, + "RoutingPublicationPolicyDraftWriteRequest": { + "type": "object", + "properties": { + "expected_version": { + "type": "integer", + "minimum": 0 + }, + "expected_revision": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "changes": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "changes", + "expected_revision", + "expected_version" + ] + }, + "CasePublicationPolicyDraft": { + "type": "object", + "properties": { + "revision": { + "type": "string", + "format": "uuid" + }, + "base_version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "changes": {}, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "base_version", + "updated_at" + ] + }, + "CasePublicationPolicyDraftPreconditionRequest": { + "type": "object", + "properties": { + "expected_version": { + "type": "integer", + "minimum": 0 + }, + "expected_revision": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "required": [ + "expected_revision", + "expected_version" + ] + }, + "CaseCollaborationCaseNote": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "comment": { + "type": "string" + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 255 + }, + "actor_name": { + "type": "string", + "nullable": true, + "maxLength": 255 + }, + "mentioned_emails": {}, + "is_internal": { + "type": "boolean" + }, + "attachments": {}, + "tags": {}, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "reply_to": { + "allOf": [ + { + "$ref": "#/components/schemas/CaseCollaborationCaseNoteReply" + } + ], + "nullable": true, + "readOnly": true + }, + "is_pinned": { + "type": "boolean" + }, + "client_reference": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "required": [ + "comment", + "created_at", + "reply_to", + "uuid" + ] + }, + "CustomerMessagingPaginatedCaseRequestList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=400&limit=100" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=200&limit=100" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequest" + } + } + } + }, + "FraudCheckResponse": { + "type": "object", + "properties": { + "check_id": { + "type": "string", + "format": "uuid" + }, + "request_id": { + "type": "string", + "format": "uuid", + "description": "Native verification session created for this check." + }, + "client_reference": { + "type": "string", + "nullable": true + }, + "processing_status": { + "$ref": "#/components/schemas/FraudProcessingStatusEnum" + }, + "profile_id": { + "type": "string" + }, + "profile_version": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "inputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckInput" + } + }, + "modules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckModuleReport" + } + }, + "attribute_evidence": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "compound_evidence": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "risk": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckRisk" + } + }, + "network": { + "type": "object", + "additionalProperties": {} + }, + "enrichment": { + "type": "object", + "additionalProperties": {} + }, + "identity_relationship": { + "allOf": [ + { + "$ref": "#/components/schemas/FraudCheckIdentityRelationship" + } + ], + "nullable": true + }, + "recommendation": { + "$ref": "#/components/schemas/FraudRecommendationEnum" + }, + "next_action": { + "$ref": "#/components/schemas/FraudNextActionEnum" + }, + "decision_reasons": { + "type": "array", + "items": { + "type": "string" + } + }, + "coverage": { + "type": "object", + "additionalProperties": {} + }, + "usage": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "vendor_data": { + "type": "string", + "nullable": true + }, + "metadata": { + "nullable": true + } + }, + "required": [ + "attribute_evidence", + "check_id", + "client_reference", + "completed_at", + "compound_evidence", + "coverage", + "created_at", + "decision_reasons", + "inputs", + "modules", + "network", + "next_action", + "processing_status", + "profile_id", + "profile_version", + "recommendation", + "risk", + "usage" + ] + }, + "CaseReviewTeam": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseReviewTeamMember" + }, + "maxItems": 250 + }, + "is_active": { + "type": "boolean" + }, + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + } + }, + "CaseActionCaseEntityActionRequest": { + "type": "object", + "properties": { + "client_reference": { + "type": "string", + "format": "uuid" + }, + "retry": { + "type": "boolean", + "default": false + }, + "action": { + "$ref": "#/components/schemas/CaseActionActionEnum" + }, + "status": { + "type": "string", + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "note": { + "type": "string", + "nullable": true + } + }, + "required": [ + "action" + ] + }, + "RoutingPublicationPaginatedPolicyRevisionList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=400&limit=100" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=200&limit=100" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RoutingPublicationPolicyRevision" + } + } + } + }, + "FraudRecommendationEnum": { + "enum": [ + "approve", + "review", + "reject" + ], + "type": "string", + "description": "* `approve` - Approve\n* `review` - Review\n* `reject` - Reject" + }, + "CustomerMessagingCaseRequestPreviewResponse": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid" + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "recipient": { + "type": "string", + "format": "email" + }, + "sender": { + "type": "string" + }, + "subject": { + "type": "string" + }, + "html": { + "type": "string" + }, + "requires_user_action": { + "type": "boolean" + }, + "preview_token": { + "type": "string" + } + }, + "required": [ + "expires_at", + "html", + "preview_token", + "recipient", + "request_id", + "requires_user_action", + "sender", + "subject" + ] + }, + "CustomerMessagingCaseCustomerResponseRequest": { + "type": "object", + "properties": { + "full_name": { + "type": "string", + "minLength": 1, + "maxLength": 512 + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "file": { + "type": "string", + "format": "binary", + "writeOnly": true + } + } + }, + "CaseCollaborationCaseNoteReply": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true + }, + "comment": { + "type": "string", + "maxLength": 300 + } + }, + "required": [ + "actor_email", + "actor_name", + "comment", + "uuid" + ] + }, + "FraudCheckModuleReport": { + "type": "object", + "properties": { + "module": { + "$ref": "#/components/schemas/FraudExcludeEnum" + }, + "status": { + "$ref": "#/components/schemas/FraudStatusEnum" + }, + "reason": { + "type": "string", + "nullable": true + }, + "latency_ms": { + "type": "integer" + }, + "freshness": { + "type": "string", + "nullable": true + }, + "billable_units": { + "type": "integer" + } + }, + "required": [ + "billable_units", + "freshness", + "latency_ms", + "module", + "reason", + "status" + ] + }, + "CaseDuplicateEntityTypeEnum": { + "enum": [ + "session", + "business_session", + "transaction" + ], + "type": "string", + "description": "* `session` - Session\n* `business_session` - Business Session\n* `transaction` - Transaction" + }, + "FraudClientCredentialsErrorMessage": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "default": "Authentication failed: Invalid client access token." + } + } + }, + "FraudAvailabilityEnum": { + "enum": [ + "available", + "not_requested", + "no_reference", + "insufficient_history", + "unsupported_region", + "source_unavailable" + ], + "type": "string", + "description": "* `available` - Available\n* `not_requested` - Not Requested\n* `no_reference` - No Reference\n* `insufficient_history` - Insufficient History\n* `unsupported_region` - Unsupported Region\n* `source_unavailable` - Source Unavailable" + }, + "CasePublicationPolicyDraftResponse": { + "type": "object", + "properties": { + "draft": { + "allOf": [ + { + "$ref": "#/components/schemas/CasePublicationPolicyDraft" + } + ], + "nullable": true + } + }, + "required": [ + "draft" + ] + }, + "FraudCheckAttributesRequest": { + "type": "object", + "description": "Corroborating attributes. All optional; they widen module coverage.", + "properties": { + "first_name": { + "type": "string", + "maxLength": 128 + }, + "last_name": { + "type": "string", + "maxLength": 128 + }, + "full_name": { + "type": "string", + "maxLength": 256 + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "nationality": { + "type": "string", + "maxLength": 3 + }, + "gender": { + "type": "string", + "maxLength": 16 + }, + "address": { + "type": "string", + "maxLength": 512 + }, + "city": { + "type": "string", + "maxLength": 128 + }, + "state": { + "type": "string", + "maxLength": 128 + }, + "postal_code": { + "type": "string", + "maxLength": 32 + } + } + }, + "NetworkNote": { + "type": "object", + "required": [ + "uuid", + "actor_email", + "comment", + "client_reference", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "actor_email": { + "type": "string", + "format": "email", + "readOnly": true, + "description": "Authenticated author, assigned by the server." + }, + "comment": { + "type": "string", + "minLength": 1, + "maxLength": 10000 + }, + "client_reference": { + "type": "string", + "format": "uuid" + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + } + }, + "CaseActionEntityTypeEnum": { + "enum": [ + "user", + "business" + ], + "type": "string", + "description": "* `user` - user\n* `business` - business" + }, + "CaseRoutingCondition": { + "type": "object", + "properties": { + "field": { + "type": "string", + "enum": [ + "source", + "priority", + "tags" + ] + }, + "operator": { + "type": "string", + "enum": [ + "equals", + "in", + "contains" + ] + }, + "value": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": [ + "field", + "operator", + "value" + ], + "description": "Source and priority support equals or in with supported values. Tags support contains with one non-empty tag." + }, + "RoutingPublicationPolicyRevision": { + "type": "object", + "properties": { + "version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "snapshot": {}, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "published_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "published_at", + "snapshot", + "version" + ] + }, + "CasePublicationPolicyRevision": { + "type": "object", + "properties": { + "version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "snapshot": {}, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "published_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "published_at", + "snapshot", + "version" + ] + }, + "FraudCheckIdentityRelationship": { + "type": "object", + "properties": { + "result": { + "$ref": "#/components/schemas/FraudResultEnum" + }, + "reference_type": { + "type": "string" + }, + "country": { + "type": "string" + }, + "availability": { + "$ref": "#/components/schemas/FraudAvailabilityEnum" + }, + "reason_codes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "availability", + "country", + "reason_codes", + "reference_type", + "result" + ] + }, + "CasePublicationPaginatedPolicyRevisionList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=400&limit=100" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=200&limit=100" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CasePublicationPolicyRevision" + } + } + } + }, + "CaseDuplicateBlankEnum": { + "enum": [ + "" + ] + }, + "FraudCheckPlan": { + "type": "object", + "properties": { + "profile_id": { + "type": "string" + }, + "profile_version": { + "type": "string" + }, + "modules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckPlannedModule" + } + }, + "estimated_price": { + "type": "string", + "nullable": true + }, + "currency": { + "type": "string", + "nullable": true + }, + "is_sandbox": { + "type": "boolean" + }, + "can_afford": { + "type": "boolean" + } + }, + "required": [ + "can_afford", + "currency", + "estimated_price", + "is_sandbox", + "modules", + "profile_id", + "profile_version" + ] + }, + "CaseReviewTeamUpdate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseReviewTeamMember" + }, + "maxItems": 250 + }, + "is_active": { + "type": "boolean" + } + } + }, + "RoutingPublicationPolicyDraft": { + "type": "object", + "properties": { + "revision": { + "type": "string", + "format": "uuid" + }, + "base_version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "changes": {}, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "base_version", + "updated_at" + ] + }, + "FraudCheckRequestRequest": { + "type": "object", + "properties": { + "client_reference": { + "type": "string", + "minLength": 1, + "description": "Your own reference for this check. Doubles as the idempotency key: reusing it with the same body returns the original check without running or billing the modules again; reusing it with a different body returns 409.", + "maxLength": 255 + }, + "profile": { + "type": "string", + "minLength": 1, + "default": "standard", + "description": "Orchestration profile id. Defaults to the Didit `standard` profile.", + "maxLength": 64 + }, + "profile_version": { + "type": "string", + "minLength": 1, + "description": "Pin a frozen profile version. Defaults to the current version of the profile.", + "maxLength": 32 + }, + "subject": { + "$ref": "#/components/schemas/FraudCheckSubjectRequest" + }, + "context": { + "$ref": "#/components/schemas/FraudCheckContextRequest" + }, + "checks": { + "$ref": "#/components/schemas/FraudCheckModuleSelectionRequest" + }, + "vendor_data": { + "type": "string", + "maxLength": 1000 + }, + "metadata": {} + }, + "required": [ + "subject" + ] + }, + "CaseDuplicateCaseSubjectInputRequest": { + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/CaseDuplicateTypeEnum" + }, + "uuid": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "uuid" + ] + }, + "CaseReviewTeamMember": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "format": "uuid" + }, + "availability": { + "type": "string", + "enum": [ + "available", + "unavailable" + ], + "default": "available" + }, + "capacity": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 10 + }, + "skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "available_from": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "available_until": { + "type": "string", + "format": "date-time", + "nullable": true + } + }, + "required": [ + "user_id" + ] + }, + "CaseRoutingPreviewRequest": { + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ] + }, + "priority": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ], + "default": "MEDIUM" + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 50 + }, + "rule_uuid": { + "type": "string", + "format": "uuid" + }, + "draft": { + "$ref": "#/components/schemas/CaseRoutingRuleCreate" + } + }, + "required": [ + "source" + ] + }, + "FraudExcludeEnum": { + "enum": [ + "normalization", + "email_risk", + "email_social", + "phone_social", + "phone_risk", + "ip_risk", + "database_validation", + "networks_same_org", + "networks_cross_org", + "biometric_identity" + ], + "type": "string", + "description": "* `normalization` - Normalization\n* `email_risk` - Email Risk\n* `email_social` - Email Social\n* `phone_social` - Phone Social\n* `phone_risk` - Phone Risk\n* `ip_risk` - Ip Risk\n* `database_validation` - Database Validation\n* `networks_same_org` - Networks Same Org\n* `networks_cross_org` - Networks Cross Org\n* `biometric_identity` - Biometric Identity" + }, + "CaseDuplicateCaseLinkCreateRequest": { + "type": "object", + "properties": { + "entity_type": { + "$ref": "#/components/schemas/CaseDuplicateEntityTypeEnum" + }, + "entity_uuid": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "entity_type", + "entity_uuid" + ] + }, + "CaseDuplicateStatusEnum": { + "enum": [ + "OPEN", + "AWAITING_USER", + "RESOLVED" + ], + "type": "string", + "description": "* `OPEN` - Open\n* `AWAITING_USER` - Awaiting user\n* `RESOLVED` - Resolved" + }, + "FraudBandEnum": { + "enum": [ + "low", + "medium", + "high", + "unknown" + ], + "type": "string", + "description": "* `low` - Low\n* `medium` - Medium\n* `high` - High\n* `unknown` - Unknown" + }, + "CaseDuplicatePriorityEnum": { + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ], + "type": "string", + "description": "* `LOW` - Low\n* `MEDIUM` - Medium\n* `HIGH` - High" + }, + "FraudStatusEnum": { + "enum": [ + "planned", + "completed", + "skipped", + "failed" + ], + "type": "string", + "description": "* `planned` - Planned\n* `completed` - Completed\n* `skipped` - Skipped\n* `failed` - Failed" + }, + "FraudCheckInput": { + "type": "object", + "properties": { + "reference": { + "type": "string", + "description": "Redacted handle for the submitted identifier." + }, + "type": { + "$ref": "#/components/schemas/FraudTypeEnum" + }, + "namespace": { + "type": "object", + "additionalProperties": {} + }, + "quality": { + "type": "string" + }, + "normalization": { + "type": "object", + "additionalProperties": {} + }, + "availability": { + "$ref": "#/components/schemas/FraudAvailabilityEnum" + } + }, + "required": [ + "availability", + "namespace", + "normalization", + "quality", + "reference", + "type" + ] + }, + "CustomerMessagingPurposesEnum": { + "enum": [ + "identity_document", + "proof_of_address", + "business_document", + "confirm_details", + "review_in_progress" + ], + "type": "string", + "description": "* `identity_document` - identity_document\n* `proof_of_address` - proof_of_address\n* `business_document` - business_document\n* `confirm_details` - confirm_details\n* `review_in_progress` - review_in_progress" + }, + "FraudTypeEnum": { + "enum": [ + "email", + "phone", + "national_id", + "tax_number", + "passport_number", + "driving_licence_number", + "residence_permit_number", + "voter_id", + "external_id" + ], + "type": "string", + "description": "* `email` - Email\n* `phone` - Phone\n* `national_id` - National Id\n* `tax_number` - Tax Number\n* `passport_number` - Passport Number\n* `driving_licence_number` - Driving Licence Number\n* `residence_permit_number` - Residence Permit Number\n* `voter_id` - Voter Id\n* `external_id` - External Id" + }, + "CaseDuplicateCaseSubjectInput": { + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/CaseDuplicateTypeEnum" + }, + "uuid": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "type", + "uuid" + ] + }, + "CaseReviewTeamCreate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "members": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseReviewTeamMember" + }, + "maxItems": 250 + }, + "is_active": { + "type": "boolean" + } + }, + "required": [ + "name", + "members" + ] + }, + "CaseDuplicateCaseDuplicateQueryRequest": { + "type": "object", + "properties": { + "subject": { + "$ref": "#/components/schemas/CaseDuplicateCaseSubjectInputRequest" + }, + "links": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseDuplicateCaseLinkCreateRequest" + } + }, + "offset": { + "type": "integer", + "maximum": 1000000, + "minimum": 0, + "default": 0 + } + } + }, + "CaseDuplicateResolutionEnum": { + "enum": [ + "FALSE_POSITIVE", + "VALID_THREAT" + ], + "type": "string", + "description": "* `FALSE_POSITIVE` - False positive\n* `VALID_THREAT` - Valid threat" + }, + "CasePublicationPolicyDraftWriteRequest": { + "type": "object", + "properties": { + "expected_version": { + "type": "integer", + "minimum": 0 + }, + "expected_revision": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "changes": { + "type": "object", + "additionalProperties": {} + } + }, + "required": [ + "changes", + "expected_revision", + "expected_version" + ] + }, + "FraudCheckSubjectRequest": { + "type": "object", + "properties": { + "selfie": { + "type": "string", + "minLength": 1, + "description": "Base64 JPEG, PNG or WebP selfie, up to 6 MB. No reference image or URL.", + "maxLength": 8388672 + }, + "identifiers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudCheckIdentifierRequest" + }, + "description": "One or more typed identifiers. One is enough.", + "maxItems": 10, + "minItems": 1 + }, + "attributes": { + "$ref": "#/components/schemas/FraudCheckAttributesRequest" + } + }, + "required": [ + "identifiers" + ] + }, + "RoutingPublicationRoutingCondition": { + "type": "object", + "properties": { + "field": { + "$ref": "#/components/schemas/RoutingPublicationFieldEnum" + }, + "operator": { + "$ref": "#/components/schemas/RoutingPublicationOperatorEnum" + }, + "value": {} + }, + "required": [ + "field", + "operator", + "value" + ] + }, + "CaseActionCaseActionReceipt": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "client_reference": { + "type": "string", + "format": "uuid" + }, + "action": { + "type": "string", + "maxLength": 32 + }, + "status": { + "$ref": "#/components/schemas/CaseActionStatusEnum" + }, + "result": {}, + "error": { + "type": "string", + "maxLength": 255 + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "maxLength": 254 + }, + "attempts": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "action", + "client_reference", + "created_at", + "status", + "updated_at", + "uuid" + ] + }, + "FraudCheckContextRequest": { + "type": "object", + "properties": { + "ip_address": { + "type": "string", + "minLength": 1 + }, + "user_agent": { + "type": "string", + "maxLength": 512 + }, + "country": { + "type": "string", + "description": "Country the subject claims to be in. Contextual only - never proof of identity ownership.", + "maxLength": 3 + }, + "purpose": { + "type": "string", + "maxLength": 128 + }, + "consent_obtained": { + "type": "boolean" + } + } + }, + "CustomerMessagingCaseCustomerResponseResult": { + "type": "object", + "properties": { + "responded_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "responded_at" + ] + }, + "CustomerMessagingCaseRequestContact": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "email": { + "type": "string", + "format": "email" + }, + "verified_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "email", + "uuid", + "verified_at" + ] + }, + "FraudClientCredentialsForbiddenErrorMessage": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "default": "Forbidden - User doesn't have permission" + } + } + }, + "CasePublicationAssignmentMethodEnum": { + "enum": [ + "MANUAL", + "AUTOMATIC" + ], + "type": "string", + "description": "* `MANUAL` - Manual\n* `AUTOMATIC` - Automatic" + }, + "CaseDuplicateNullEnum": { + "enum": [ + null + ] + }, + "CustomerMessagingLanguageEnum": { + "enum": [ + "en", + "es", + "pt" + ], + "type": "string", + "description": "* `en` - en\n* `es` - es\n* `pt` - pt" + }, + "CaseDuplicateCaseFraudSourceContextRequest": { + "type": "object", + "properties": { + "check_id": { + "type": "string", + "format": "uuid" + }, + "request_id": { + "type": "string", + "format": "uuid" + }, + "source_section": { + "type": "string", + "minLength": 1, + "pattern": "^[A-Z][A-Z0-9_:-]{0,63}$" + } + }, + "required": [ + "check_id", + "request_id", + "source_section" + ] + }, + "FraudCheckPlannedModule": { + "type": "object", + "properties": { + "module": { + "$ref": "#/components/schemas/FraudExcludeEnum" + }, + "available": { + "type": "boolean" + }, + "availability_reason": { + "type": "string", + "nullable": true + }, + "will_run": { + "type": "boolean" + }, + "selection_reason": { + "type": "string", + "nullable": true + }, + "estimated_price": { + "type": "string", + "nullable": true + } + }, + "required": [ + "availability_reason", + "available", + "estimated_price", + "module", + "selection_reason", + "will_run" + ] + }, + "CustomerMessagingCaseRequestOptions": { + "type": "object", + "properties": { + "contacts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerMessagingCaseRequestContact" + } + }, + "purposes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerMessagingPurposesEnum" + } + }, + "languages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerMessagingLanguagesEnum" + } + } + }, + "required": [ + "contacts", + "languages", + "purposes" + ] + }, + "CaseCollaborationPaginatedCaseNoteList": { + "type": "object", + "required": [ + "count", + "results" + ], + "properties": { + "count": { + "type": "integer", + "example": 123 + }, + "next": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=400&limit=100" + }, + "previous": { + "type": "string", + "nullable": true, + "format": "uri", + "example": "http://api.example.org/accounts/?offset=200&limit=100" + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseCollaborationCaseNote" + } + } + } + }, + "FraudProcessingStatusEnum": { + "enum": [ + "pending", + "running", + "completed", + "failed" + ], + "type": "string", + "description": "* `pending` - Pending\n* `running` - Running\n* `completed` - Completed\n* `failed` - Failed" + }, + "CasePublicationCaseBlueprint": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "name": { + "type": "string", + "maxLength": 24 + }, + "description": { + "type": "string", + "nullable": true + }, + "assignment_method": { + "$ref": "#/components/schemas/CasePublicationAssignmentMethodEnum" + }, + "assignee_pool": {}, + "max_active_cases": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true + }, + "escalation_enabled": { + "type": "boolean" + }, + "escalation_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "escalation_blueprint_name": { + "type": "string", + "readOnly": true + }, + "transfer_enabled": { + "type": "boolean" + }, + "four_eyes_checker_blueprint": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "four_eyes_checker_blueprint_name": { + "type": "string", + "readOnly": true + }, + "require_resolution_note": { + "type": "boolean" + }, + "content_config": {}, + "version": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "default_deadline_hours": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0, + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "preset_key": { + "type": "string", + "nullable": true, + "maxLength": 64 + }, + "creation_sources": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + }, + "readOnly": true + }, + "receives_escalation_from": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + }, + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "created_at", + "creation_sources", + "escalation_blueprint_name", + "four_eyes_checker_blueprint_name", + "name", + "receives_escalation_from", + "updated_at", + "uuid" + ] + }, + "CaseRoutingRuleUpdate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "priority": { + "type": "integer", + "minimum": 0 + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseRoutingCondition" + }, + "maxItems": 20 + }, + "required_skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "blueprint": { + "type": "string", + "format": "uuid" + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "expected_version": { + "type": "integer", + "minimum": 1, + "writeOnly": true, + "description": "Version opened by the editor. When supplied, stale writes return 409 without changing the rule." + } + } + }, + "CustomerMessagingCaseRequestPreviewRequest": { + "type": "object", + "properties": { + "contact_id": { + "type": "string", + "format": "uuid" + }, + "purpose": { + "$ref": "#/components/schemas/CustomerMessagingPurposeEnum" + }, + "language": { + "allOf": [ + { + "$ref": "#/components/schemas/CustomerMessagingLanguageEnum" + } + ], + "default": "en" + } + }, + "required": [ + "contact_id", + "purpose" + ] + }, + "CaseDuplicateCaseFraudSourceContext": { + "type": "object", + "properties": { + "check_id": { + "type": "string", + "format": "uuid" + }, + "request_id": { + "type": "string", + "format": "uuid" + }, + "source_section": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_:-]{0,63}$" + } + }, + "required": [ + "check_id", + "request_id", + "source_section" + ] + }, + "RoutingPublicationOperatorEnum": { + "enum": [ + "equals", + "in", + "contains" + ], + "type": "string", + "description": "* `equals` - equals\n* `in` - in\n* `contains` - contains" + }, + "CaseRoutingPreview": { + "type": "object", + "properties": { + "rule_uuid": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "rule_name": { + "type": "string", + "nullable": true + }, + "rule_version": { + "type": "integer", + "nullable": true + }, + "blueprint_uuid": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "team_uuid": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "assignee_user_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "reason": { + "type": "string" + }, + "candidates": { + "type": "array", + "items": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "format": "uuid" + }, + "eligible": { + "type": "boolean" + }, + "active_cases": { + "type": "integer", + "nullable": true + }, + "reason": { + "type": "string", + "nullable": true + } + } + } + }, + "matched_rules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "priority": { + "type": "integer" + } + } + } + } + } + }, + "FraudDimensionEnum": { + "enum": [ + "identity", + "contact", + "device_network", + "behavioral", + "fraud_network" + ], + "type": "string", + "description": "* `identity` - Identity\n* `contact` - Contact\n* `device_network` - Device Network\n* `behavioral` - Behavioral\n* `fraud_network` - Fraud Network" + }, + "FraudBadRequestErrorMessage": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "default": "Bad Request - Invalid request data" + } + } + }, + "FraudNextActionEnum": { + "enum": [ + "none", + "manual_review", + "step_up", + "resubmit_capture" + ], + "type": "string", + "description": "* `none` - None\n* `manual_review` - Manual Review\n* `step_up` - Step Up\n* `resubmit_capture` - Resubmit Capture" + }, + "RoutingPublicationPolicyDraftPreconditionRequest": { + "type": "object", + "properties": { + "expected_version": { + "type": "integer", + "minimum": 0 + }, + "expected_revision": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "required": [ + "expected_revision", + "expected_version" + ] + }, + "FraudCheckRisk": { + "type": "object", + "properties": { + "dimension": { + "$ref": "#/components/schemas/FraudDimensionEnum" + }, + "score": { + "type": "integer", + "nullable": true + }, + "band": { + "$ref": "#/components/schemas/FraudBandEnum" + }, + "availability": { + "$ref": "#/components/schemas/FraudAvailabilityEnum" + }, + "requested_but_unavailable": { + "type": "boolean", + "description": "True when the request supplied everything this dimension needed and the platform still could not answer it. Such a check is never recommended for approval on the strength of the dimensions that did answer." + }, + "reason_codes": { + "type": "array", + "items": { + "type": "string" + } + }, + "evidence_indexes": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "availability", + "band", + "dimension", + "evidence_indexes", + "reason_codes", + "requested_but_unavailable", + "score" + ] + }, + "CaseDuplicateSourceEnum": { + "enum": [ + "MANUAL", + "AML_SCREENING", + "TRANSACTION_RULE", + "WORKFLOW_BUILDER" + ], + "type": "string", + "description": "* `MANUAL` - Manual\n* `AML_SCREENING` - AML screening\n* `TRANSACTION_RULE` - Transaction rule\n* `WORKFLOW_BUILDER` - Workflow builder" + }, + "CaseCollaborationPatchedCaseNoteUpdateRequest": { + "type": "object", + "properties": { + "is_pinned": { + "type": "boolean" + } + } + }, + "CustomerMessagingPurposeEnum": { + "enum": [ + "identity_document", + "proof_of_address", + "business_document", + "confirm_details", + "review_in_progress" + ], + "type": "string", + "description": "* `identity_document` - identity_document\n* `proof_of_address` - proof_of_address\n* `business_document` - business_document\n* `confirm_details` - confirm_details\n* `review_in_progress` - review_in_progress" + }, + "CaseDuplicateTypeEnum": { + "enum": [ + "user", + "business" + ], + "type": "string", + "description": "* `user` - user\n* `business` - business" + }, + "CaseCollaborationCaseNoteCreateRequest": { + "type": "object", + "properties": { + "comment": { + "type": "string", + "minLength": 1, + "maxLength": 10000 + }, + "reply_to": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "client_reference": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email", + "minLength": 1 + } + }, + "is_internal": { + "type": "boolean", + "default": false + }, + "attachments": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "required": [ + "comment" + ] + }, + "CustomerMessagingLanguagesEnum": { + "enum": [ + "en", + "es", + "pt" + ], + "type": "string", + "description": "* `en` - en\n* `es` - es\n* `pt` - pt" + }, + "CustomerMessagingCaseRequestConfirmRequest": { + "type": "object", + "properties": { + "contact_id": { + "type": "string", + "format": "uuid" + }, + "purpose": { + "$ref": "#/components/schemas/CustomerMessagingPurposeEnum" + }, + "language": { + "allOf": [ + { + "$ref": "#/components/schemas/CustomerMessagingLanguageEnum" + } + ], + "default": "en" + }, + "request_id": { + "type": "string", + "format": "uuid" + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "preview_token": { + "type": "string", + "minLength": 1, + "maxLength": 512 + } + }, + "required": [ + "contact_id", + "expires_at", + "preview_token", + "purpose", + "request_id" + ] + }, + "CaseRoutingRule": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "priority": { + "type": "integer", + "minimum": 0 + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseRoutingCondition" + }, + "maxItems": 20 + }, + "required_skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "blueprint": { + "type": "string", + "format": "uuid" + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "version": { + "type": "integer", + "readOnly": true + }, + "blueprint_name": { + "type": "string", + "readOnly": true + }, + "team_name": { + "type": "string", + "nullable": true, + "readOnly": true + } + } + }, + "FraudResultEnum": { + "enum": [ + "match", + "mismatch", + "inconclusive", + "no_reference" + ], + "type": "string", + "description": "* `match` - Match\n* `mismatch` - Mismatch\n* `inconclusive` - Inconclusive\n* `no_reference` - No Reference" + }, + "CaseRoutingRuleCreate": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "priority": { + "type": "integer", + "minimum": 0 + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseRoutingCondition" + }, + "maxItems": 20 + }, + "required_skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "blueprint": { + "type": "string", + "format": "uuid" + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "publish": { + "type": "boolean", + "writeOnly": true, + "description": "Set false to save a never-published draft. The record remains inactive at version 0 until the draft is explicitly published. Omitting this field preserves immediate creation for existing integrations." + } + }, + "required": [ + "name", + "conditions", + "blueprint" + ] + }, + "FraudCheckIdentifierRequest": { + "type": "object", + "properties": { + "type": { + "allOf": [ + { + "$ref": "#/components/schemas/FraudTypeEnum" + } + ], + "description": "Identifier type. Determines which namespace fields are required.\n\n* `email` - Email\n* `phone` - Phone\n* `national_id` - National Id\n* `tax_number` - Tax Number\n* `passport_number` - Passport Number\n* `driving_licence_number` - Driving Licence Number\n* `residence_permit_number` - Residence Permit Number\n* `voter_id` - Voter Id\n* `external_id` - External Id" + }, + "value": { + "type": "string", + "minLength": 1, + "description": "The identifier value, in any customary format.", + "maxLength": 255 + }, + "country": { + "type": "string", + "description": "ISO-3166 alpha-2 or alpha-3 issuing country. Required for government identifier types: the same digits mean different people in different countries.", + "maxLength": 3 + }, + "issuer": { + "type": "string", + "description": "Optional issuing authority or sub-national issuer, when the country alone is not unique.", + "maxLength": 128 + } + }, + "required": [ + "type", + "value" + ] + }, + "CaseActionActionEnum": { + "enum": [ + "set_status", + "add_tags", + "add_note", + "add_to_blocklist" + ], + "type": "string", + "description": "* `set_status` - set_status\n* `add_tags` - add_tags\n* `add_note` - add_note\n* `add_to_blocklist` - add_to_blocklist" + }, + "CaseActionCaseActionReceiptPage": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "next_offset": { + "type": "integer", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaseActionCaseActionReceipt" + } + } + }, + "required": [ + "count", + "next_offset", + "results" + ] + }, + "CaseActionCaseEntityActionResult": { + "type": "object", + "properties": { + "receipt": { + "$ref": "#/components/schemas/CaseActionCaseActionReceipt" + }, + "entity_type": { + "$ref": "#/components/schemas/CaseActionEntityTypeEnum" + }, + "entity_uuid": { + "type": "string", + "format": "uuid" + }, + "action": { + "type": "string" + }, + "previous_status": { + "type": "string" + }, + "new_status": { + "type": "string" + }, + "tags_added": { + "type": "array", + "items": { + "type": "string" + } + }, + "comment_uuid": { + "type": "string", + "format": "uuid" + }, + "blocklisted": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "CustomerMessagingCaseRequest": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "purpose": { + "type": "string", + "readOnly": true + }, + "language": { + "type": "string", + "readOnly": true + }, + "recipient": { + "type": "string", + "format": "email", + "readOnly": true + }, + "expires_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "actor_email": { + "type": "string", + "format": "email", + "readOnly": true, + "nullable": true + }, + "delivery_status": { + "type": "string", + "readOnly": true + }, + "delivered_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "nullable": true + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "nullable": true + }, + "responded_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "nullable": true + }, + "response": { + "readOnly": true, + "nullable": true + }, + "has_attachment": { + "type": "boolean", + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "actor_email", + "cancelled_at", + "created_at", + "delivered_at", + "delivery_status", + "expires_at", + "has_attachment", + "language", + "purpose", + "recipient", + "responded_at", + "response", + "uuid" + ] + }, + "CustomerMessagingCaseRequestPublic": { + "type": "object", + "properties": { + "request_id": { + "type": "string", + "format": "uuid" + }, + "purpose": { + "$ref": "#/components/schemas/CustomerMessagingPurposeEnum" + }, + "language": { + "$ref": "#/components/schemas/CustomerMessagingLanguageEnum" + }, + "expires_at": { + "type": "string", + "format": "date-time" + }, + "responded_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "copy": { + "type": "object", + "additionalProperties": {} + }, + "organization_name": { + "type": "string" + }, + "is_white_label": { + "type": "boolean" + }, + "customization": { + "type": "object", + "additionalProperties": {}, + "nullable": true + } + }, + "required": [ + "copy", + "customization", + "expires_at", + "is_white_label", + "language", + "organization_name", + "purpose", + "request_id", + "responded_at" + ] + }, + "RoutingPublicationFieldEnum": { + "enum": [ + "source", + "priority", + "tags" + ], + "type": "string", + "description": "* `source` - source\n* `priority` - priority\n* `tags` - tags" + }, + "RoutingPublicationCaseRoutingRule": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "readOnly": true + }, + "name": { + "type": "string", + "maxLength": 100 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "priority": { + "type": "integer", + "maximum": 2147483647, + "minimum": 0 + }, + "conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RoutingPublicationRoutingCondition" + } + }, + "required_skills": { + "type": "array", + "items": { + "type": "string", + "maxLength": 64 + }, + "maxItems": 25 + }, + "blueprint": { + "type": "string", + "format": "uuid" + }, + "blueprint_name": { + "type": "string", + "readOnly": true + }, + "team": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "team_name": { + "type": "string", + "readOnly": true + }, + "is_active": { + "type": "boolean" + }, + "version": { + "type": "integer", + "readOnly": true + }, + "created_at": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "updated_at": { + "type": "string", + "format": "date-time", + "readOnly": true + } + }, + "required": [ + "blueprint", + "blueprint_name", + "conditions", + "created_at", + "name", + "team_name", + "updated_at", + "uuid", + "version" + ] + }, + "CaseActionStatusEnum": { + "enum": [ + "APPLIED", + "FAILED" + ], + "type": "string", + "description": "* `APPLIED` - Applied\n* `FAILED` - Failed" + }, + "FraudCheckModuleSelectionRequest": { + "type": "object", + "properties": { + "include": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudExcludeEnum" + } + }, + "exclude": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FraudExcludeEnum" + } + } + } + }, + "SignatureField": { + "type": "object", + "description": "A field to place on a session document: which signer fills it, how, and where. Give either a position (`page`, `x`, `y`, `width` and `height`) or an `anchor`. Positions are fractions of the page as it's displayed, from its top-left corner, rounded to 4 decimals, and the field must fit inside the page. A document takes up to 200 fields and one signer up to 500, and every role with fields on a document needs a `signature` or `initials` field there. See [Fields on the document](/core-technology/electronic-signature/overview#fields-on-the-document).", + "required": [ + "key", + "type" + ], + "properties": { + "key": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]{0,63}$", + "description": "Names the field in the results and in `sign.field_values`. Unique within the document." + }, + "type": { + "type": "string", + "enum": [ + "signature", + "initials", + "full_name", + "date_signed", + "text", + "checkbox", + "email", + "data" + ], + "description": "What the field holds. The signer applies their adopted `signature` and `initials`, types `text`, ticks a `checkbox`, and types an `email` when the session has no verified email. Didit fills `full_name` (the verified name), `date_signed` (the date of signing, in the signer's time zone), `data` (from `prefill`) and a verified `email`, and shows them read-only. There is no date picker type: use `text`." + }, + "role": { + "type": "string", + "enum": [ + "verified_person", + "legal_representative", + "key_people" + ], + "description": "Who fills the field, with the `signature_signer` values. A step fills and stamps only the fields of its own signer. Defaults to the step's signer." + }, + "page": { + "type": "integer", + "minimum": 1, + "description": "Page number, from 1 to the document's page count. For a Word document, a page of the converted PDF." + }, + "x": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Left edge, as a fraction of the page width." + }, + "y": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Top edge, as a fraction of the page height." + }, + "width": { + "type": "number", + "minimum": 0.02, + "maximum": 1, + "description": "Width, as a fraction of the page width. A signature is at least `0.12` wide." + }, + "height": { + "type": "number", + "minimum": 0.012, + "maximum": 1, + "description": "Height, as a fraction of the page height. A signature is at least `0.035` high." + }, + "anchor": { + "$ref": "#/components/schemas/SignatureFieldAnchor" + }, + "required": { + "type": "boolean", + "description": "`signature` and `initials` are required unless you set `false`. `full_name`, `date_signed` and `data` are always required. The signer can't finish a document until its required fields have a value, and a required checkbox must be ticked." + }, + "label": { + "type": "string", + "minLength": 1, + "maxLength": 80, + "description": "Shown to the signer and in the results. Defaults from the type." + }, + "placeholder": { + "type": "string", + "nullable": true, + "maxLength": 80, + "description": "`text` and `email` only." + }, + "validation": { + "$ref": "#/components/schemas/SignatureFieldValidation" + }, + "prefill": { + "type": "string", + "nullable": true, + "description": "Where the value comes from: `identity.full_name`, `identity.first_name`, `identity.last_name`, `identity.date_of_birth` (`DD/MM/YYYY`), `identity.nationality`, `identity.document_number`, `identity.document_type`, `identity.issuing_country`, `identity.address`, `contact.email`, `contact.phone`, `company.name`, `company.registration_number`, `company.address`, `company.country` (KYB), `session.vendor_data`, `metadata.` (a key of the session's `metadata`) or `api.` (a key of `sign.field_values`). Required for `data`. `full_name` always uses `identity.full_name` and `email` uses `contact.email`. The values of `data`, `full_name` and verified `email` fields are fixed when the signer starts signing." + }, + "editable_when_prefilled": { + "type": "boolean", + "default": false, + "description": "`text` and `email` only: the signer can change a pre-filled value." + }, + "checked_by_default": { + "type": "boolean", + "default": false, + "description": "`checkbox` only: the box starts ticked." + }, + "date_format": { + "type": "string", + "enum": [ + "DD/MM/YYYY", + "MM/DD/YYYY", + "YYYY-MM-DD", + "D MMMM YYYY" + ], + "default": "DD/MM/YYYY", + "description": "`date_signed` only." + } + } + }, + "SessionSignDocument": { + "type": "object", + "required": [ + "file_name", + "content_base64" + ], + "properties": { + "title": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "Title shown to the signer. Defaults to `file_name`." + }, + "file_name": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "File name, ending in `.pdf` or `.docx`." + }, + "content_base64": { + "type": "string", + "format": "byte", + "description": "The file, base64-encoded. A PDF can have up to 20 MB and 100 pages, without a password, scripts or attached files. A Word file (`.docx`) can have up to 20 MB and is converted to PDF while the request runs, in at most 25 seconds; the PDF then has the same limits. Up to 5 Word files per session. A Word file with macros, tracked changes, links to external content or embedded objects is refused with `signature_document_not_supported`, and one that can't be converted returns `signature_document_conversion_failed`, each with a `reason`." + }, + "fields": { + "type": "array", + "description": "Optional fields to place on this document, up to 200. Pages and positions refer to the PDF, or to the converted PDF for a Word file. Without fields, the signer reads the document and agrees to it.", + "items": { + "$ref": "#/components/schemas/SignatureField" + }, + "maxItems": 200 + } + } + }, + "SignatureResult": { + "type": "object", + "description": "One signer's result for one E-signature step. See the [E-signature report](/core-technology/electronic-signature/report-electronic-signature).", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Pass it as `signature_id` to download this signer's evidence or validate their signature." + }, + "node_id": { + "type": "string", + "description": "The workflow step that produced it." + }, + "status": { + "type": "string", + "enum": [ + "intro", + "review", + "code", + "signing", + "signed", + "declined" + ], + "description": "Where the signer is in the step. Only `signed` means the documents are signed." + }, + "verdict": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "In Review", + "Declined" + ], + "description": "The step's outcome. A warning sets it through its rule; custom status rules apply after." + }, + "level": { + "type": "string", + "enum": [ + "simple", + "aes", + "qes" + ], + "description": "`simple` (default) or `aes`. `qes` (Qualified Electronic Signature, QES) is an add-on that is not available yet." + }, + "format": { + "type": "string", + "enum": [ + "pdf", + "pades" + ], + "description": "`pdf` for a Simple signature, `pades` for Advanced (AES)." + }, + "profile": { + "type": "string", + "nullable": true, + "description": "`PAdES-B-LT` once an Advanced (AES) signature is signed, otherwise null." + }, + "signer": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "description": "The signer's name. Verified (from the approved ID verification or the session) except at the Simple level without a verified session name, where it is the name the signer typed and is not verified." + }, + "capacity": { + "type": "string", + "enum": [ + "self", + "company" + ] + }, + "company": { + "type": "string", + "nullable": true + }, + "role": { + "type": "string", + "nullable": true, + "description": "The key person's designation, as declared." + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The key person's structured roles, as declared for the company." + }, + "identity_session_id": { + "type": "string", + "format": "uuid", + "description": "The KYC session that verified the signer." + } + } + }, + "identity_used": { + "type": "object", + "description": "The verified identity the signature relies on. Empty for a Simple signature with no verified identity.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid" + }, + "kyc_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "verified": { + "type": "boolean" + }, + "expected_name": { + "type": "string", + "nullable": true, + "description": "The name the signer had to match." + } + } + }, + "certificate": { + "type": "object", + "nullable": true, + "description": "The certificate the documents were signed with. `null` until signed, and always `null` for a Simple signature.", + "properties": { + "issuer": { + "type": "string" + }, + "issued_to": { + "type": "string" + }, + "serial": { + "type": "string", + "description": "Serial number, hexadecimal." + }, + "valid_from": { + "type": "string", + "format": "date-time" + }, + "valid_until": { + "type": "string", + "format": "date-time" + } + } + }, + "confirmation": { + "type": "object", + "properties": { + "channel": { + "type": "string", + "nullable": true, + "enum": [ + "email", + "sms" + ], + "description": "`null` for a Simple signature, which has no code." + }, + "destination": { + "type": "string", + "nullable": true, + "description": "The masked contact the code went to." + }, + "confirmed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "attempts": { + "type": "integer", + "description": "Codes entered, the right one included." + } + } + }, + "timestamp": { + "type": "object", + "nullable": true, + "description": "The RFC 3161 timestamp in the signature, issued by Didit. `null` until signed, and always `null` for a Simple signature.", + "properties": { + "time": { + "type": "string", + "format": "date-time" + }, + "qualified": { + "type": "boolean", + "description": "Always `false` for AES." + }, + "valid": { + "type": "boolean" + } + } + }, + "documents": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The document on this signer's copy. Use it to download the signed file." + }, + "uuid": { + "type": "string", + "format": "uuid", + "description": "Same as `id`." + }, + "title": { + "type": "string" + }, + "file_name": { + "type": "string" + }, + "pages": { + "type": "integer", + "description": "Pages of the document signed. For Word, of the converted PDF." + }, + "size_bytes": { + "type": "integer" + }, + "sha256_original": { + "type": "string", + "description": "SHA-256 of the document as the signer was shown it." + }, + "sha256_signed": { + "type": "string", + "nullable": true, + "description": "SHA-256 of the signed PDF, once signed." + }, + "status": { + "type": "string", + "enum": [ + "signed", + "declined", + "not_signed", + "not_reached" + ], + "description": "`not_signed`: opened but not signed. `not_reached`: never opened." + }, + "opened_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "read_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the signer reached the end of a document without fields, when the step requires it." + }, + "read_to_end": { + "type": "boolean" + }, + "consented_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the signer agreed to a document without fields, or finished the fields of a document with fields." + }, + "consent": { + "type": "boolean" + }, + "signed_file": { + "type": "string", + "nullable": true, + "description": "Path of the signed PDF, once signed. See [Download a signed document](/sessions-api/electronic-signature/download-signed-document)." + }, + "mode": { + "type": "string", + "enum": [ + "fields", + "read_and_agree" + ], + "description": "`fields` for a document with fields, `read_and_agree` for a document without." + }, + "source_format": { + "type": "string", + "enum": [ + "pdf", + "docx" + ], + "description": "`pdf`, or `docx` for a Word document Didit converted to PDF." + }, + "conversion": { + "type": "object", + "nullable": true, + "description": "`null` for a PDF. For a Word document, the original file and the conversion.", + "properties": { + "from": { + "type": "string", + "enum": [ + "docx" + ] + }, + "file_name": { + "type": "string", + "description": "Your original Word file name." + }, + "sha256": { + "type": "string", + "description": "SHA-256 of your original Word file." + }, + "size_bytes": { + "type": "integer", + "description": "Size of your original Word file." + }, + "converter": { + "type": "string", + "description": "Version of the converter that produced the PDF." + } + } + }, + "fields": { + "type": "array", + "description": "Fields on the document, with their values. Empty for a document without fields.", + "items": { + "$ref": "#/components/schemas/SignatureFieldResult" + } + } + } + } + }, + "adopted_signature": { + "type": "object", + "nullable": true, + "description": "The signature and initials the signer adopted for signature and initials fields. `null` until the signer adopts one.", + "properties": { + "method": { + "type": "string", + "enum": [ + "typed", + "drawn" + ], + "description": "`typed`: the verified name in a handwriting style. `drawn`: drawn with a finger or a mouse." + }, + "font": { + "type": "string", + "nullable": true, + "description": "The handwriting style of a typed signature. `null` when drawn." + }, + "adopted_at": { + "type": "string", + "format": "date-time" + } + } + }, + "electronic_signature_consent_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the signer agreed, on the review screen, to sign the documents electronically. Set when any document has fields." + }, + "evidence_file": { + "type": "string", + "nullable": true, + "description": "Path of the evidence PDF, once signed. See [Download the signature evidence](/sessions-api/electronic-signature/download-evidence)." + }, + "warnings": { + "type": "array", + "description": "Warnings raised for this signer. See [E-signature warnings](/core-technology/electronic-signature/warnings-electronic-signature).", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": [ + "SIGNATURE_DECLINED", + "SIGNATURE_OTP_FAILED", + "SIGNATURE_NAME_MISMATCH", + "SIGNATURE_IDENTITY_NOT_VERIFIED", + "SIGNATURE_DOCUMENT_CHANGED", + "SIGNATURE_FIELD_DATA_MISSING" + ] + }, + "node_id": { + "type": "string" + } + } + } + }, + "validation": { + "type": "object", + "nullable": true, + "description": "The last cryptographic check of the signed documents. `null` until the documents are signed. If Didit can't confirm the outcome of signing, it's `{\"valid\": null, \"reason\": \"signature_provider_outcome_unknown\", \"checked_at\": ...}` and `status` stays `signing`: treat it as not signed.", + "properties": { + "valid": { + "type": "boolean", + "nullable": true, + "description": "`true` when every document is valid. `null` when the outcome of signing couldn't be confirmed." + }, + "reason": { + "type": "string", + "description": "Set when `valid` is `null`: `signature_provider_outcome_unknown`." + }, + "checked_at": { + "type": "string", + "format": "date-time" + }, + "documents": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "checked_at": { + "type": "string", + "format": "date-time" + }, + "valid": { + "type": "boolean", + "description": "`true` when the signature and the timestamp are both valid and trusted." + }, + "integrity": { + "type": "boolean", + "description": "`true` when the document is unchanged since signing." + }, + "trusted": { + "type": "boolean", + "description": "`true` when the signing certificate chains to a trusted root." + }, + "revoked": { + "type": "boolean" + }, + "trusted_list": { + "type": "object", + "description": "EU Trusted List check. For AES: `{\"status\": \"not_applicable\", \"reason\": \"aes_not_qualified\"}`.", + "properties": { + "status": { + "type": "string" + }, + "reason": { + "type": "string" + } + } + }, + "certificate": { + "type": "object", + "nullable": true, + "description": "The certificate the documents were signed with. `null` until signed, and always `null` for a Simple signature.", + "properties": { + "issuer": { + "type": "string" + }, + "issued_to": { + "type": "string" + }, + "serial": { + "type": "string", + "description": "Serial number, hexadecimal." + }, + "valid_from": { + "type": "string", + "format": "date-time" + }, + "valid_until": { + "type": "string", + "format": "date-time" + } + } + }, + "timestamp": { + "type": "object", + "nullable": true, + "description": "The RFC 3161 timestamp in the signature, issued by Didit. `null` until signed, and always `null` for a Simple signature.", + "properties": { + "time": { + "type": "string", + "format": "date-time" + }, + "qualified": { + "type": "boolean", + "description": "Always `false` for AES." + }, + "valid": { + "type": "boolean" + } + } + } + } + } + } + } + }, + "declined_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "declined_action": { + "type": "string", + "enum": [ + "DECLINE", + "REVIEW", + "NO_ACTION" + ] + }, + "signed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the signing link stops working." + }, + "email_sent_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "email_unavailable_reason": { + "type": "string", + "nullable": true + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "The signer's IP address when they asked for their code." + }, + "place": { + "type": "string", + "nullable": true, + "description": "Not collected: always `null`." + }, + "place_unavailable_reason": { + "type": "string", + "nullable": true, + "enum": [ + "not_collected" + ] + } + } + }, + "SignatureFieldValidation": { + "type": "object", + "nullable": true, + "description": "Rules for a `text` field's value. The console preview, the signing flow and the API apply the same rules.", + "properties": { + "kind": { + "type": "string", + "enum": [ + "any", + "letters", + "number", + "email", + "pattern" + ], + "description": "`letters`: letters in any script, spaces, hyphens and apostrophes. `number`: digits with an optional decimal separator. `pattern`: the value must match `pattern`." + }, + "min_length": { + "type": "integer", + "minimum": 0, + "maximum": 500, + "description": "From `0` up to `max_length`." + }, + "max_length": { + "type": "integer", + "minimum": 1, + "maximum": 500, + "default": 120 + }, + "pattern": { + "type": "string", + "nullable": true, + "minLength": 1, + "maxLength": 200, + "description": "Required with `kind: \"pattern\"`. A regular expression the whole value must match. Lookarounds and backreferences aren't supported." + }, + "message": { + "type": "string", + "nullable": true, + "maxLength": 120, + "description": "Shown to the signer instead of the default error." + } + } + }, + "SignatureFieldAnchor": { + "type": "object", + "description": "Places a field by a piece of text in the document instead of a position. Didit doesn't remove the text from the PDF, so write it in the same color as the page background. When the text isn't found, the call returns `400` with `field_anchor_not_found`. See [Place fields by anchor text](/sessions-api/create-session#place-fields-by-anchor-text).", + "required": [ + "text" + ], + "properties": { + "text": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "description": "Text searched in the document. For a Word file, in the converted PDF." + }, + "occurrence": { + "type": "string", + "enum": [ + "first", + "all" + ], + "default": "first", + "description": "`all` places one field per match; the keys after the first get `_2`, `_3` and so on." + }, + "offset_x": { + "type": "number", + "description": "Added to the left edge of the text, as a fraction of the page width." + }, + "offset_y": { + "type": "number", + "description": "Added to the top edge of the text, as a fraction of the page height." + }, + "width": { + "type": "number", + "minimum": 0.02, + "maximum": 1, + "description": "Width of the field, as a fraction of the page width." + }, + "height": { + "type": "number", + "minimum": 0.012, + "maximum": 1, + "description": "Height of the field, as a fraction of the page height." + } + } + }, + "SignatureFieldResult": { + "type": "object", + "description": "A field of a signed document, with its value on this signer's copy. See [Fields](/core-technology/electronic-signature/report-electronic-signature#fields).", + "properties": { + "key": { + "type": "string", + "description": "The field's key, as used in `sign.field_values`." + }, + "type": { + "type": "string", + "enum": [ + "signature", + "initials", + "full_name", + "date_signed", + "text", + "checkbox", + "email", + "data" + ] + }, + "role": { + "type": "string", + "enum": [ + "verified_person", + "legal_representative", + "key_people" + ] + }, + "label": { + "type": "string" + }, + "page": { + "type": "integer", + "description": "Page number, from 1. For a Word document, a page of the converted PDF." + }, + "x": { + "type": "number", + "description": "Left edge, as a fraction of the page width." + }, + "y": { + "type": "number", + "description": "Top edge, as a fraction of the page height." + }, + "width": { + "type": "number", + "description": "Width, as a fraction of the page width." + }, + "height": { + "type": "number", + "description": "Height, as a fraction of the page height." + }, + "required": { + "type": "boolean" + }, + "value": { + "nullable": true, + "description": "A string for `text`, `email`, `full_name`, `date_signed` and `data`, a boolean for a `checkbox`, and `true` for an applied signature or initials. `null` when empty." + }, + "display_value": { + "type": "string", + "nullable": true, + "description": "The value as stamped on the page: dates in the field's `date_format`, `Yes` or `No` for a checkbox, `Signed` for a signature or initials." + }, + "value_source": { + "type": "string", + "nullable": true, + "enum": [ + "signer", + "identity", + "contact", + "company", + "session", + "metadata", + "api", + "signing_time", + null + ], + "description": "Where the value came from: `signer` (typed, ticked or applied by the signer), `identity` (the verified ID), `contact` (the verified email or phone), `company` (the verified company), `session` (`vendor_data`), `metadata`, `api` (`sign.field_values`) or `signing_time` (the date signed)." + }, + "filled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "image_url": { + "type": "string", + "nullable": true, + "description": "Signature and initials only: the path of the applied image. See [Download a signature image](/sessions-api/electronic-signature/download-field-image)." + }, + "value_unavailable_reason": { + "type": "string", + "nullable": true, + "enum": [ + "source_empty", + null + ], + "description": "`source_empty` when a pre-filled field had no value in the session and `signature_field_data_missing_action` let the signer sign anyway." + } + } + }, + "VaspDirectoryWidgetEntry": { + "type": "object", + "description": "What the end user's VASP picker shows: who the VASP is and whether it can be reached.", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The entry's id, for GET /v3/travel-rule/vasps/{vasp_id}/." + }, + "kind": { + "type": "string", + "enum": [ + "directory", + "didit" + ], + "description": "directory for an entry of the shared directory; didit for a member of the Didit network." + }, + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "jurisdiction": { + "type": "string", + "description": "ISO 3166 country code, as the source gives it." + }, + "domains": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The VASP's web domains. Always empty for Didit network members." + }, + "reachable": { + "type": "boolean", + "description": "Whether your application has an available route to this VASP, or to one of its entities for a brand. Always true for a Didit network member." + } + } + }, + "TravelRuleDataSharingFields": { + "type": "object", + "description": "The fields Didit sends to the counterparty VASP about your own users, per entity type. The `name` and the `wallet_address` are always sent, whatever the list says. An entity type you never chose shares every field. An empty list is a choice too: it shares the name and the wallet only.", + "properties": { + "individual": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "name", + "wallet_address", + "address", + "national_id", + "customer_identification", + "date_of_birth", + "place_of_birth" + ] + }, + "description": "Fields shared about a natural person." + }, + "company": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "name", + "wallet_address", + "address", + "national_id", + "customer_identification" + ] + }, + "description": "Fields shared about a legal person." + } + } + }, + "TravelRuleMatchingRules": { + "type": "object", + "description": "How strictly each field of the counterparty's data is compared with what you hold, per entity type. A field you leave out keeps its default. `NONE` skips the comparison, `STRICT` needs exactly the same words, `CONTAINS` accepts one value whose words all appear in the other, `DEFAULT` is the standard subset match, and `FUZZY` accepts close spellings (and, for dates of birth, a swapped day and month).", + "properties": { + "individual": { + "type": "object", + "description": "Rules for a natural person.", + "properties": { + "name": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default: follows `name_matching_strictness`." + }, + "date_of_birth": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default `NONE`." + }, + "address": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default `NONE`." + }, + "account": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "The wallet the exchange was routed by. Default `STRICT`." + } + } + }, + "company": { + "type": "object", + "description": "Rules for a legal person.", + "properties": { + "name": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default: follows `name_matching_strictness`." + }, + "address": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "Default `NONE`." + }, + "account": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ], + "description": "The wallet the exchange was routed by. Default `STRICT`." + } + } + } + } + }, + "TravelRuleDataMatch": { + "type": "object", + "nullable": true, + "description": "Field-by-field comparison of the transfer's party data with what the other side knows about the wallet holder. It names fields and results, never values, so it stays readable while the counterparty's data is masked. Null until a comparison ran.", + "properties": { + "matched": { + "type": "boolean", + "description": "True when no compared field differs." + }, + "mismatched_fields": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "name", + "date_of_birth", + "address", + "account" + ] + }, + "description": "The fields that differ." + }, + "fields": { + "type": "object", + "description": "One entry per field compared for the party's entity type (`name`, `date_of_birth`, `address`, `account`; a company has no `date_of_birth`).", + "additionalProperties": { + "type": "object", + "properties": { + "result": { + "type": "string", + "enum": [ + "matched", + "mismatched", + "not_compared" + ], + "description": "`not_compared` when the rule is `NONE` or either side has no value for the field." + }, + "strictness": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ] + } + } + } + } + } + }, + "TravelRuleDataUnmasked": { + "type": "object", + "nullable": true, + "description": "Audit record of the moment the counterparty's data was unmasked. Null while the data is still masked, and for outbound transfers.", + "properties": { + "at": { + "type": "string", + "format": "date-time" + }, + "reason": { + "type": "string", + "enum": [ + "ownership_confirmed", + "wallet_proof", + "address_book_match", + "own_verified_wallet", + "inquiry_approved", + "known_payment_method", + "travel_address" + ], + "description": "What confirmed ownership: ownership_confirmed (your confirmation through the console or the API), wallet_proof (a wallet proof in the widget, or an INTERNAL inbound transfer, set when it is created), address_book_match (a verified address-book wallet), own_verified_wallet (your user's own verified wallet), inquiry_approved (your approval of an inbound TRP inquiry or a GTR or Sygna message), known_payment_method (a wallet your user already paid with) or travel_address (the per-wallet travel address named the wallet)." + }, + "actor": { + "type": "string", + "description": "Who confirmed it: the console user's email or the API key's label. Blank for an automatic decision." + } + } + }, + "TravelRuleMessageAttachment": { + "type": "object", + "nullable": true, + "description": "The document attached to a message, stored like any other transaction document.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "label": { + "type": "string", + "nullable": true + }, + "note": { + "type": "string", + "nullable": true + }, + "uploaded_by": { + "type": "string", + "nullable": true, + "description": "Who attached it. In the pickup portal, a file you sent shows your VASP name, never the person or API key behind it." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "files": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "file_url": { + "type": "string", + "nullable": true, + "description": "Time-limited download URL." + }, + "original_filename": { + "type": "string" + }, + "file_size": { + "type": "integer" + } + } + } + } + } + }, + "TravelRuleMessage": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "kind": { + "type": "string", + "enum": [ + "MESSAGE", + "DOCUMENT_REQUEST" + ] + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "`OUTBOUND` is what the reader's own side sent. In the pickup portal the direction is from the counterparty's side." + }, + "author": { + "type": "string", + "description": "Who wrote it on its own side: a console user's email or an API key label, or the other VASP's name." + }, + "body": { + "type": "string" + }, + "requested_documents": { + "type": "array", + "items": { + "type": "string" + }, + "description": "What a DOCUMENT_REQUEST asks for, in the requester's words." + }, + "reply_to": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "fulfilled_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Set on a DOCUMENT_REQUEST once a reply to it carries a document." + }, + "attachment": { + "$ref": "#/components/schemas/TravelRuleMessageAttachment" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleMessageCreateRequest": { + "type": "object", + "description": "Send as JSON, or as multipart/form-data with the attachments in `files`.", + "properties": { + "kind": { + "type": "string", + "enum": [ + "MESSAGE", + "DOCUMENT_REQUEST" + ], + "default": "MESSAGE" + }, + "body": { + "type": "string", + "maxLength": 5000, + "description": "Required for a MESSAGE unless a file is attached." + }, + "requested_documents": { + "type": "array", + "items": { + "type": "string", + "maxLength": 255 + }, + "maxItems": 10, + "description": "Required (at least one) for a DOCUMENT_REQUEST, and only allowed there." + }, + "reply_to": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "A message of the same transfer. A reply that carries a document fulfils the document request it answers." + }, + "files": { + "type": "array", + "items": { + "type": "string", + "format": "binary" + }, + "maxItems": 5, + "description": "Multipart only. Up to 5 files of 10 MB each." + } + } + }, + "SessionDeletionJob": { + "type": "object", + "description": "A bulk session deletion running in the background: progress and counts per outcome, never which sessions it deleted.", + "properties": { + "job_id": { + "type": "string", + "format": "uuid", + "description": "Poll `GET /v3/sessions/delete/{job_id}/` with this id." + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ], + "description": "`PENDING`: accepted, not started yet. `PROCESSING`: deleting; `processed` and `outcomes` are updating. `COMPLETED`: every selected session is accounted for. `FAILED`: the job stopped early; see `error`." + }, + "total": { + "type": "integer", + "description": "Sessions selected when the deletion was requested." + }, + "processed": { + "type": "integer", + "description": "Sessions the job has finished with so far." + }, + "outcomes": { + "type": "object", + "description": "How many sessions ended in each outcome. Every key is always present.", + "properties": { + "deleted": { + "type": "integer", + "description": "Sessions deleted together with their face embedding." + }, + "none": { + "type": "integer", + "description": "Sessions deleted that had no face embedding." + }, + "retained_with_user": { + "type": "integer", + "description": "Sessions deleted while one image-free biometric template was retained for their User, as the request or the application policy asked." + }, + "ineligible_no_vendor_user": { + "type": "integer", + "description": "Sessions deleted, with their face embedding, because retention was requested but they had no linked User." + }, + "failed_retryable": { + "type": "integer", + "description": "Sessions reported as not deleted. A job retries a group it could not delete by itself instead of counting it here." + }, + "already_deleted": { + "type": "integer", + "description": "Selected sessions that were already deleted, for example by another request, when the job reached them." + } + } + }, + "deleted_child_verifications": { + "type": "integer", + "description": "Verifications that the deleted business verifications created, deleted with them. Always `0` for a job started by `POST /v3/sessions/delete/`, which deletes KYC sessions only." + }, + "error": { + "type": "string", + "nullable": true, + "description": "`null` unless `status` is `FAILED`; then why the deletion stopped. The sessions the job had not reached are still in place: repeat the request to delete them." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the deletion was requested." + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the job finished; `null` while it runs." + } + } + }, + "EntityStatusUpdateRequest": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "FLAGGED", + "BLOCKED" + ], + "description": "New lifecycle status. `BLOCKED` also adds the `vendor_data` to the system blocklist." + } + } + }, + "AMLMonitoringToggleResult": { + "oneOf": [ + { + "type": "object", + "required": [ + "users_total", + "already_enabled", + "with_existing_aml", + "needing_screening", + "skipped_no_identity_data", + "skipped_blocked", + "estimated_screening_cost_usd", + "estimated_annual_monitoring_cost_usd", + "balance_sufficient", + "unmatched" + ], + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + }, + "unmatched": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Identifiers of the request that matched no profile in this application, exactly as sent." + } + } + }, + { + "type": "object", + "required": [ + "job_id", + "status", + "is_enabled", + "target_type", + "total", + "processed", + "outcomes", + "results", + "error", + "created_at", + "completed_at", + "estimate", + "unmatched" + ], + "properties": { + "job_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ] + }, + "is_enabled": { + "type": "boolean", + "description": "true when the job enables monitoring, false when it disables it." + }, + "target_type": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Which profile family the job toggles." + }, + "total": { + "type": "integer", + "description": "Profiles the job processes." + }, + "processed": { + "type": "integer", + "description": "Profiles processed so far." + }, + "outcomes": { + "type": "object", + "description": "Per-record outcome counters. Every selected profile resolves to exactly one counter; nothing is silently skipped.", + "properties": { + "enabled": { + "type": "integer", + "description": "Monitoring enabled on an existing AML screening." + }, + "screened_and_enabled": { + "type": "integer", + "description": "A new AML screening was created (billed) and monitoring enabled on it." + }, + "already_enabled": { + "type": "integer", + "description": "Monitoring was already active; nothing changed and nothing was billed." + }, + "disabled": { + "type": "integer", + "description": "Monitoring turned off across every AML screening of the profile." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Skipped: the profile lacks the full name, date of birth and country (users) or a name (businesses) needed to screen." + }, + "skipped_blocked": { + "type": "integer", + "description": "Skipped: the user is blocked." + }, + "skipped_sandbox": { + "type": "integer", + "description": "Skipped: sandbox application, no screening is created." + }, + "failed_provider": { + "type": "integer", + "description": "The screening could not be completed; retry later." + }, + "failed_insufficient_balance": { + "type": "integer", + "description": "Balance ran out while the job was running." + } + } + }, + "results": { + "type": "array", + "description": "One entry per processed profile, in processing order. Grows while the job runs; complete once status is COMPLETED. Recorded for jobs started through the API.", + "items": { + "type": "object", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit internal id of the profile." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the profile." + }, + "outcome": { + "type": "string", + "enum": [ + "enabled", + "screened_and_enabled", + "already_enabled", + "disabled", + "skipped_no_identity_data", + "skipped_blocked", + "skipped_sandbox", + "failed_provider", + "failed_insufficient_balance" + ] + } + } + } + }, + "error": { + "type": "string", + "nullable": true, + "description": "Generic failure message when status is FAILED; the detailed cause is kept on the platform side." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + } + } + }, + "unmatched": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Identifiers of the request that matched no profile in this application, exactly as sent. They are not part of the job." + } + } + } + ] + }, + "AMLMonitoringJob": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ] + }, + "is_enabled": { + "type": "boolean", + "description": "true when the job enables monitoring, false when it disables it." + }, + "target_type": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Which profile family the job toggles." + }, + "total": { + "type": "integer", + "description": "Profiles the job processes." + }, + "processed": { + "type": "integer", + "description": "Profiles processed so far." + }, + "outcomes": { + "type": "object", + "description": "Per-record outcome counters. Every selected profile resolves to exactly one counter; nothing is silently skipped.", + "properties": { + "enabled": { + "type": "integer", + "description": "Monitoring enabled on an existing AML screening." + }, + "screened_and_enabled": { + "type": "integer", + "description": "A new AML screening was created (billed) and monitoring enabled on it." + }, + "already_enabled": { + "type": "integer", + "description": "Monitoring was already active; nothing changed and nothing was billed." + }, + "disabled": { + "type": "integer", + "description": "Monitoring turned off across every AML screening of the profile." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Skipped: the profile lacks the full name, date of birth and country (users) or a name (businesses) needed to screen." + }, + "skipped_blocked": { + "type": "integer", + "description": "Skipped: the user is blocked." + }, + "skipped_sandbox": { + "type": "integer", + "description": "Skipped: sandbox application, no screening is created." + }, + "failed_provider": { + "type": "integer", + "description": "The screening could not be completed; retry later." + }, + "failed_insufficient_balance": { + "type": "integer", + "description": "Balance ran out while the job was running." + } + } + }, + "results": { + "type": "array", + "description": "One entry per processed profile, in processing order. Grows while the job runs; complete once status is COMPLETED. Recorded for jobs started through the API.", + "items": { + "type": "object", + "properties": { + "didit_internal_id": { + "type": "string", + "format": "uuid", + "description": "Didit internal id of the profile." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the profile." + }, + "outcome": { + "type": "string", + "enum": [ + "enabled", + "screened_and_enabled", + "already_enabled", + "disabled", + "skipped_no_identity_data", + "skipped_blocked", + "skipped_sandbox", + "failed_provider", + "failed_insufficient_balance" + ] + } + } + } + }, + "error": { + "type": "string", + "nullable": true, + "description": "Generic failure message when status is FAILED; the detailed cause is kept on the platform side." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (nothing is written).", + "properties": { + "users_total": { + "type": "integer", + "description": "Profiles matched by the selection." + }, + "already_enabled": { + "type": "integer", + "description": "Profiles already monitored (nothing to do when enabling; these are what disabling turns off)." + }, + "with_existing_aml": { + "type": "integer", + "description": "Profiles whose existing AML screening will be switched to monitored (no new screening)." + }, + "needing_screening": { + "type": "integer", + "description": "Profiles without any AML screening that will be screened first (billed as a regular AML screening)." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Profiles that cannot be screened because identity data is missing." + }, + "skipped_blocked": { + "type": "integer", + "description": "Blocked users that will be skipped (always 0 for businesses)." + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2, + "description": "One-time cost of the new screenings." + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07, + "description": "Yearly monitoring cost of the profiles that will become monitored." + }, + "balance_sufficient": { + "type": "boolean", + "description": "Whether the organization balance covers the screening plus the first year of monitoring." + } + } + }, + "unmatched": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Identifiers of the request that matched no profile in this application, exactly as sent. They are not part of the job." + } + } + }, + "OngoingMonitoringToggleRequest": { + "type": "object", + "required": [ + "is_enabled" + ], + "properties": { + "is_enabled": { + "type": "boolean", + "description": "true to enable monitoring, false to disable it." + }, + "estimate_only": { + "type": "boolean", + "default": false, + "description": "When true, returns the cost estimate and performs no writes." + }, + "update_all": { + "type": "boolean", + "default": false, + "description": "Apply to every entity of the application (scoped by the filter_* params when present)." + }, + "filter_search": { + "type": "string" + }, + "filter_status": { + "type": "array", + "items": { + "type": "string" + } + }, + "filter_country": { + "type": "string" + }, + "filter_date_from": { + "type": "string", + "format": "date" + }, + "filter_date_to": { + "type": "string", + "format": "date" + }, + "didit_internal_id_list": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Entities to toggle. Required unless update_all is true." + } + } + }, + "OngoingMonitoringJob": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "COMPLETED", + "FAILED" + ] + }, + "is_enabled": { + "type": "boolean" + }, + "total": { + "type": "integer" + }, + "processed": { + "type": "integer" + }, + "outcomes": { + "type": "object", + "description": "Per-entity outcome counters. Nothing is silently skipped: every requested entity resolves to exactly one counter.", + "properties": { + "enabled": { + "type": "integer", + "description": "Monitoring enabled on an existing AML screening." + }, + "screened_and_enabled": { + "type": "integer", + "description": "A new AML screening was created (billed) and monitoring enabled." + }, + "already_enabled": { + "type": "integer", + "description": "Monitoring was already active (legacy duplicate rows are consolidated)." + }, + "disabled": { + "type": "integer", + "description": "Monitoring disabled on every AML row of the entity." + }, + "skipped_no_identity_data": { + "type": "integer", + "description": "Skipped: no full name + date of birth + country available to screen." + }, + "skipped_blocked": { + "type": "integer", + "description": "Skipped: the user is blocked." + }, + "skipped_sandbox": { + "type": "integer", + "description": "Skipped: sandbox application." + }, + "failed_provider": { + "type": "integer", + "description": "The screening provider call failed." + }, + "failed_insufficient_balance": { + "type": "integer", + "description": "Balance ran out mid-job." + } + } + }, + "error": { + "type": "string", + "nullable": true + }, + "estimate": { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (no writes are performed).", + "properties": { + "users_total": { + "type": "integer", + "description": "Entities in the selection." + }, + "already_enabled": { + "type": "integer" + }, + "with_existing_aml": { + "type": "integer", + "description": "Entities whose existing AML screening will be switched to monitored." + }, + "needing_screening": { + "type": "integer", + "description": "Entities without any AML screening that will be screened first (billed)." + }, + "skipped_no_identity_data": { + "type": "integer" + }, + "skipped_blocked": { + "type": "integer" + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2 + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07 + }, + "balance_sufficient": { + "type": "boolean" + } + } + } + } + }, + "OngoingMonitoringToggleResult": { + "oneOf": [ + { + "type": "object", + "description": "Cost preview. Returned directly when estimate_only is true (no writes are performed).", + "properties": { + "users_total": { + "type": "integer", + "description": "Entities in the selection." + }, + "already_enabled": { + "type": "integer" + }, + "with_existing_aml": { + "type": "integer", + "description": "Entities whose existing AML screening will be switched to monitored." + }, + "needing_screening": { + "type": "integer", + "description": "Entities without any AML screening that will be screened first (billed)." + }, + "skipped_no_identity_data": { + "type": "integer" + }, + "skipped_blocked": { + "type": "integer" + }, + "estimated_screening_cost_usd": { + "type": "number", + "example": 0.2 + }, + "estimated_annual_monitoring_cost_usd": { + "type": "number", + "example": 0.07 + }, + "balance_sufficient": { + "type": "boolean" + } + } + }, + { + "$ref": "#/components/schemas/OngoingMonitoringJob" + } + ] + }, + "CaseBlueprintPresetInstallResult": { + "type": "object", + "properties": { + "created": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "CaseBulkUpdateResult": { + "type": "object", + "properties": { + "updated": { + "type": "integer" + }, + "failures": { + "type": "array", + "items": { + "type": "object", + "properties": { + "case_uuid": { + "type": "string", + "format": "uuid" + }, + "detail": { + "type": "string" + } + } + } + } + } + }, + "RegulatoryReportList": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Number of matching rows, capped at 100 for performance." + }, + "next": { + "type": "string", + "nullable": true + }, + "previous": { + "type": "string", + "nullable": true + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RegulatoryReportDetail" + } + } + } + }, + "EntityNetworkMembershipList": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EntityNetworkMembership" + } + } + } + }, + "SessionDecision": { + "type": "object", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The unique identifier of the session." + }, + "session_kind": { + "type": "string", + "enum": [ + "user", + "business" + ], + "description": "Discriminator indicating whether this is a User Verification (KYC) or Business Verification (KYB) session. When `user`, the KYC feature arrays (`id_verifications`, `nfc_verifications`, `liveness_checks`, `face_matches`, `poa_verifications`, `document_ai_documents`, `database_validations`, ...) apply and the business arrays are absent. When `business`, the payload instead carries `registry_checks`, `document_verifications`, `key_people_checks` plus the shared arrays (`aml_screenings`, `phone_verifications`, `email_verifications`, `ip_analyses`, `questionnaire_responses`). **Omitted when `include=events` is passed** — the events shape only exists for user sessions, so the discriminator is dropped from that variant." + }, + "shared_from_session": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "For a session cloned with `POST /v3/session/import-shared/`: the `session_id` of the original session in the source application. `null` for every other session. Omitted when `include=events` is passed." + }, + "shared_from_session_number": { + "type": "integer", + "nullable": true, + "description": "`session_number` of the source session when this session was imported from another application of your organization. `null` otherwise." + }, + "session_number": { + "type": "integer", + "description": "The number of the session.", + "nullable": true + }, + "session_url": { + "type": "string", + "format": "uri", + "description": "The URL of the session.", + "nullable": true + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Awaiting User", + "In Review", + "Approved", + "Declined", + "Resubmitted", + "Expired", + "Kyc Expired", + "Abandoned" + ], + "description": "Overall lifecycle status of the session — the rolled-up workflow result. Terminal values are `Approved`, `Declined`, `In Review`, `Expired`, `Kyc Expired`, and `Abandoned`; `Not Started`, `In Progress`, `Awaiting User`, and `Resubmitted` indicate the session is still moving through the workflow." + }, + "status_override": { + "type": "object", + "nullable": true, + "description": "Present when a reviewer set `status` by hand. `null` when the status is the automatic one. Use it to tell a manual approval from an automatic one.", + "properties": { + "overridden_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "automatic_status": { + "type": "string", + "nullable": true, + "description": "The status the platform's own checks conclude." + } + } + }, + "decision_trace": { + "type": "object", + "description": "How the status was reached: who set it and which warnings did.", + "properties": { + "status": { + "type": "string", + "description": "The session status." + }, + "decided_by": { + "type": "string", + "nullable": true, + "enum": [ + "manual", + "automatic", + "investigator", + null + ], + "description": "Who set the status. `null` while the session is not decided." + }, + "decided_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "automatic_status": { + "type": "string", + "nullable": true, + "description": "What the platform's own checks conclude, which differs from `status` after a manual decision." + }, + "terminal_node_id": { + "type": "string", + "nullable": true, + "description": "The workflow status node that ended the session, when one did." + }, + "actor": { + "type": "object", + "nullable": true, + "description": "The reviewer behind a manual decision.", + "properties": { + "user": { + "type": "string", + "nullable": true + }, + "actor_type": { + "type": "string", + "nullable": true + } + } + }, + "warning_counts": { + "type": "object", + "description": "Warnings per `log_type` (`error`, `warning`, `information`) and `total`.", + "additionalProperties": { + "type": "integer" + } + }, + "status_warnings": { + "type": "array", + "description": "The warnings that set a `Declined` or `In Review` status.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string" + }, + "feature": { + "type": "string", + "nullable": true + }, + "node_id": { + "type": "string", + "nullable": true + }, + "log_type": { + "type": "string", + "nullable": true + }, + "check_status": { + "type": "string", + "nullable": true + }, + "source": { + "type": "string", + "description": "The payload list the warning came from, for example `id_verifications`." + }, + "short_description": { + "type": "string", + "nullable": true + } + } + } + } + } + }, + "linked_records": { + "type": "object", + "nullable": true, + "description": "A summary of other records linked to the session. `null` when there is nothing to show, and for workflows that declare an age assurance standard.", + "properties": { + "same_face": { + "type": "object", + "nullable": true, + "description": "Other people whose face matched one of this session's face searches.", + "properties": { + "count": { + "type": "integer" + }, + "best_match": { + "type": "object", + "nullable": true, + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "session_number": { + "type": "integer", + "nullable": true + }, + "vendor_user_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "similarity_percentage": { + "type": "number", + "nullable": true + } + } + } + } + }, + "earlier_verifications": { + "type": "object", + "nullable": true, + "properties": { + "count": { + "type": "integer" + }, + "vendor_user_id": { + "type": "string", + "format": "uuid", + "nullable": true + } + } + }, + "networks": { + "type": "object", + "nullable": true, + "properties": { + "count": { + "type": "integer" + }, + "open": { + "type": "integer" + }, + "dismissed": { + "type": "integer" + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "network_id": { + "type": "string", + "format": "uuid" + }, + "display_number": { + "type": "integer", + "nullable": true + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "risk_score": { + "type": "number", + "nullable": true + } + } + } + } + } + } + } + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier of the workflow group that produced this session. Workflows are versioned — this value identifies the workflow itself, not the specific version.", + "nullable": true + }, + "features": { + "type": "array", + "items": { + "type": "string", + "description": "The feature of the session." + }, + "description": "List of verification features configured for this session, as display names: `ID_VERIFICATION`, `NFC`, `LIVENESS`, `FACE_MATCH`, `POA`, `PHONE`, `EMAIL_VERIFICATION`, `AML`, `IP_ANALYSIS`, `DATABASE_VALIDATION`, `QUESTIONNAIRE`, `AGE_ESTIMATION`, `FACE_SEARCH`, `GEOLOCATION` (Location verification) (Business Verification sessions use the KYB feature set: `KYB_REGISTRY`, `KYB_DOCUMENTS`, `KYB_KEY_PEOPLE`, `AML`, `PHONE`, `EMAIL_VERIFICATION`, `IP_ANALYSIS`, `QUESTIONNAIRE`). With `include=events` each entry becomes an object `{feature, node_id}` instead of a plain string, and the email feature is renamed: it appears as `EMAIL` in events mode instead of the `EMAIL_VERIFICATION` used here." + }, + "vendor_data": { + "type": "string", + "description": "The vendor data of the session.", + "nullable": true + }, + "metadata": { + "type": "object", + "description": "The metadata of the session.", + "nullable": true + }, + "expected_details": { + "type": "object", + "description": "Expected details supplied at session creation — used for cross-validation against extracted data (KYC) or to pre-fill the company registry search (KYB). Echoes back exactly the keys that were provided; `null` when none were supplied. KYC sessions carry the person fields; Business (KYB) sessions carry `company_name`, `registry_country` and `registration_number`.", + "properties": { + "first_name": { + "type": "string", + "description": "User's first name. For example, `John`." + }, + "last_name": { + "type": "string", + "description": "User's last name. For example, `Doe`." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "User's date of birth with format: YYYY-MM-DD. For example, `1990-05-15`." + }, + "gender": { + "type": "string", + "nullable": true, + "enum": [ + "M", + "F" + ], + "default": null, + "description": "User's gender. Must be either 'M', 'F', or null." + }, + "nationality": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the applicant's country of origin. For example, `USA`." + }, + "country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country code representing the country of the Proof of Address document, or the country of the applicant's ID document, which may differ from nationality. For example, `GBR`." + }, + "address": { + "type": "string", + "description": "The address in a human readable format, including as much information as possible. For example, `123 Main St, San Francisco, CA 94105, USA`." + }, + "identification_number": { + "type": "string", + "description": "The user's document number, personal number, or tax number. For example, `123456789`." + }, + "ip_address": { + "type": "string", + "format": "ipv4", + "description": "Expected IP address for the session. If the actual IP address differs from this value, a warning will be logged. For example, `192.168.1.100`." + }, + "expected_document_types": { + "type": "array", + "description": "Document types the ID verification step was restricted to at session creation. Empty/unset means no restriction. Codes: `P` (passport), `ID` (national ID), `DL` (driver's license), `RP` (residence permit), `HIC` (health insurance card), `TC` (tax card), `SSC` (social security card).", + "items": { + "type": "string", + "enum": [ + "P", + "ID", + "DL", + "RP", + "HIC", + "TC", + "SSC" + ] + } + }, + "id_country": { + "type": "string", + "nullable": true, + "description": "Expected ID-document issuing country (ISO 3166-1 alpha-3), when restricted at creation time.", + "example": "ESP" + }, + "poa_country": { + "type": "string", + "nullable": true, + "description": "Expected Proof of Address document country (ISO 3166-1 alpha-3), when restricted at creation time.", + "example": "ESP" + }, + "company_name": { + "type": "string", + "description": "Expected legal name of the business. Business (KYB) sessions only." + }, + "registry_country": { + "type": "string", + "description": "Expected registry country as `XX` or `XX-YY` (ISO 3166-1 alpha-2, optionally with an ISO 3166-2 subdivision for state-level registries, e.g. `US-CA`). Business (KYB) sessions only." + }, + "registration_number": { + "type": "string", + "description": "Expected company registration number. Business (KYB) sessions only." + } + }, + "nullable": true + }, + "contact_details": { + "type": "object", + "description": "Contact details supplied when the session was created (used for notification emails, prefilled phone/email verification, and communication language). `null` when neither an email nor a phone number was provided.", + "properties": { + "email": { + "type": "string", + "description": "Email address of the user (e.g., \"john.doe@example.com\") that will be used during the Email Verification step. If not provided, the user must provide it during the verification flow." + }, + "email_lang": { + "type": "string", + "description": "Language code (ISO 639-1) for email notifications. Controls the language of all email communications (e.g., \"en\", \"es\", \"fr\")." + }, + "send_notification_emails": { + "type": "boolean", + "default": false, + "description": "If true and an email is provided, Didit sends the initial \"Verify your identity\" email asynchronously when the User Verification (KYC) or Business Verification (KYB) session is created. Didit also sends verification status notifications for sessions requiring manual review to the provided email address." + }, + "phone": { + "type": "string", + "nullable": true, + "description": "Phone number of the user in E.164 format if available (e.g., +14155552671). `null` when only an email was supplied at session creation. Note: This field returns the original phone number provided during session creation. If the number was invalid, it may not be in E.164 format." + } + }, + "nullable": true + }, + "callback": { + "type": "string", + "format": "uri", + "description": "The callback URL of the session.", + "nullable": true + }, + "id_verifications": { + "type": "array", + "nullable": true, + "description": "**Array of ID verification (OCR) reports** produced by ID-document steps in the workflow graph. Always a JSON array — read `id_verifications[0]` and iterate if your workflow can run more than one ID check (for example a primary check plus a step-up). `null` until at least one ID step has produced data. Each item is the full report, including `node_id`, feature-level `status`, extracted document fields, image URLs, quality scores, MRZ, address parsing, cross-session matches, and `warnings[]`.", + "items": { + "type": "object", + "description": "One ID verification report - the full report for a single ID Verification step. `verification_method` says which of the three methods produced it (document capture, non-doc lookup or wallet sign-in) and the method-specific evidence block is populated accordingly.", + "properties": { + "node_id": { + "type": "string", + "description": "Workflow graph node id of the OCR step that produced this report." + }, + "verification_method": { + "type": "string", + "enum": [ + "document", + "id_lookup", + "wallet" + ], + "description": "Which ID Verification method produced this report. `document` - the user photographed a physical ID (the default, and the value on every session created before the other methods existed). `id_lookup` - the user typed their national ID number and it was checked against a government register or another authoritative source for that country (`id_lookup.source` names which). `wallet` - the user signed in with a government or bank digital identity." + }, + "assurance": { + "type": "string", + "enum": [ + "documentary", + "data_match", + "cryptographic" + ], + "description": "Assurance tier derived from `verification_method`: `documentary` for a document that was captured and checked, `data_match` for details that matched a record held by the source queried, `cryptographic` for attributes an identity provider authenticated and signed." + }, + "wallet_provider": { + "type": "string", + "nullable": true, + "description": "Catalog wallet id when `verification_method` is `wallet` (`mitid`, `bankid_se`, `itsme`, ...). `null` otherwise." + }, + "fallback_from": { + "type": "object", + "nullable": true, + "description": "Why a non-document method did not end the ID step. Set when a lookup or a wallet sign-in failed: on the document verification the user continued with when the country's fallback switch was `fallback_to_document` (`action` is `fallback_to_document`), or on the declined report when it was `decline`. `null` otherwise.", + "properties": { + "method": { + "type": "string", + "enum": [ + "id_lookup", + "wallet" + ], + "description": "The method that failed." + }, + "reason": { + "type": "string", + "description": "The outcome that ended it, e.g. `no_match`, `provider_error`, `cancelled`, `timeout`." + }, + "action": { + "type": "string", + "enum": [ + "fallback_to_document", + "decline" + ], + "description": "The configured action that was taken: `fallback_to_document` or `decline`." + } + } + }, + "id_lookup": { + "type": "object", + "nullable": true, + "description": "Registry evidence. Present only when `verification_method` is `id_lookup`; `null` otherwise.", + "properties": { + "source": { + "type": "string", + "description": "Customer-facing name of the source queried, e.g. `Department of Home Affairs` (a government register) or `UK credit bureau and financial services records` (not a government register). This is the only statement of provenance for the lookup; do not assume a government source." + }, + "checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the register answered." + }, + "attempts": { + "type": "integer", + "description": "1-based attempt number this report came from." + }, + "max_attempts": { + "type": "integer", + "description": "Attempt budget configured for the country (1 to 5, default 1)." + }, + "outcome": { + "type": "string", + "enum": [ + "match", + "partial_match", + "no_match", + "provider_error", + "format_rejected" + ], + "nullable": true, + "description": "What the register answered. Only `match`, `partial_match` and `no_match` count against the attempt budget and bill." + }, + "comparison": { + "type": "array", + "description": "One row per comparable field the source answered on, plus one row per source error. Comparison rows use the normalised field names below rather than the country's request field keys, because several request fields (first and last name, or any of the identifier aliases) collapse into a single comparison. A source that failed appends a row with `field: \"provider\"` and `result: \"error\"`, so read `result` before treating a row as a field verdict.", + "items": { + "type": "object", + "required": [ + "field", + "label", + "provided", + "record", + "result" + ], + "properties": { + "field": { + "type": "string", + "enum": [ + "identification_number", + "full_name", + "date_of_birth", + "address", + "provider" + ], + "description": "Normalised comparison field. `provider` is not a user field: it marks a source error row." + }, + "label": { + "type": "string", + "description": "Display label for the row, e.g. `ID number`, `Full name`, `Provider`." + }, + "provided": { + "type": "string", + "nullable": true, + "description": "What the user typed. `null` on a `provider` error row." + }, + "record": { + "type": "string", + "nullable": true, + "description": "What the source held. On a `provider` error row this carries the error message instead." + }, + "result": { + "type": "string", + "enum": [ + "match", + "partial", + "no_match", + "error" + ], + "description": "Per-field comparison result. `error` appears only on a `provider` row and means that source did not answer; treat it as evidence about the query, never as a field mismatch." + } + } + } + }, + "registry_portrait": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived presigned URL to the portrait the source returned. Only the four government registers that return one (South Africa, Nigeria, Argentina, Panama)." + }, + "face_match_score": { + "type": "number", + "nullable": true, + "description": "Selfie against the registry portrait, 0-100. Only where the register returns a portrait." + } + } + }, + "wallet_verification": { + "type": "object", + "nullable": true, + "description": "Credential evidence. Present only when `verification_method` is `wallet`; `null` otherwise.", + "properties": { + "provider": { + "type": "string", + "description": "Catalog wallet id, e.g. `mitid`." + }, + "provider_name": { + "type": "string", + "nullable": true, + "description": "Wallet name as the end user knows it, e.g. `MitID`." + }, + "issuing_authority": { + "type": "string", + "nullable": true, + "description": "The authority behind the wallet, e.g. `Danish Agency for Digital Government`." + }, + "issuing_country": { + "type": "string", + "description": "ISO 3166-1 alpha-3 country the wallet was offered in." + }, + "credential_type": { + "type": "string", + "nullable": true, + "description": "Credential the wallet presented, e.g. `person_identification`." + }, + "level_of_assurance": { + "type": "string", + "enum": [ + "low", + "substantial", + "high" + ], + "nullable": true, + "description": "The level of assurance the wallet asserted. A sign-in that asserts less than the level requested fails instead of downgrading." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "The wallet's authentication instant." + }, + "signature_valid": { + "type": "boolean", + "nullable": true, + "description": "Whether the signed assertion the wallet returned validated." + }, + "attributes": { + "type": "object", + "additionalProperties": true, + "description": "The attributes the wallet shared, keyed as the wallet's catalog entry lists them." + }, + "portrait": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived presigned URL to the portrait the wallet shared, where it shares one." + }, + "face_match_score": { + "type": "number", + "nullable": true, + "description": "Selfie against the wallet portrait, 0-100, where the wallet shares one." + } + } + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Expired" + ], + "description": "Feature-level status of this ID verification step. Independent of the top-level session `status` — a session can reach its overall decision while an individual feature stays `In Review`." + }, + "document_type": { + "type": "string", + "description": "Type of identity document" + }, + "document_subtype": { + "type": "string", + "nullable": true, + "description": "Accepted document subtype code from the workflow document settings, including the document region when applicable. Examples: `ID_CARD_GENERIC`, `QUEENSLAND_DRIVER_LICENSE_GENERIC`." + }, + "document_number": { + "type": "string", + "description": "Document identification number" + }, + "personal_number": { + "type": "string", + "description": "Personal identification number" + }, + "portrait_image": { + "type": "string", + "format": "uri", + "description": "URL of the portrait image" + }, + "front_image": { + "type": "string", + "format": "uri", + "description": "URL of the front document image" + }, + "front_video": { + "type": "string", + "format": "uri", + "description": "URL of the front document video" + }, + "back_image": { + "type": "string", + "format": "uri", + "description": "URL of the back document image" + }, + "back_video": { + "type": "string", + "format": "uri", + "description": "URL of the back document video" + }, + "full_front_image": { + "type": "string", + "format": "uri", + "description": "URL of the full front document image" + }, + "full_back_image": { + "type": "string", + "format": "uri", + "description": "URL of the full back document image" + }, + "front_image_camera_front": { + "type": "string", + "format": "uri", + "description": "URL of the front document image captured from the front-facing camera" + }, + "back_image_camera_front": { + "type": "string", + "format": "uri", + "description": "URL of the back document image captured from the front-facing camera" + }, + "date_of_birth": { + "type": "string", + "format": "date", + "description": "Date of birth from document" + }, + "age": { + "type": "integer", + "description": "Calculated age from date of birth" + }, + "expiration_date": { + "type": "string", + "format": "date", + "description": "Document expiration date" + }, + "date_of_issue": { + "type": "string", + "format": "date", + "description": "Document issue date" + }, + "issuing_state": { + "type": "string", + "description": "ISO code of issuing state" + }, + "issuing_state_name": { + "type": "string", + "description": "Full name of issuing state" + }, + "first_name": { + "type": "string", + "description": "First name from document" + }, + "last_name": { + "type": "string", + "description": "Last name from document" + }, + "full_name": { + "type": "string", + "description": "Full name from document" + }, + "gender": { + "type": "string", + "description": "Gender from document" + }, + "address": { + "type": "string", + "description": "Raw address from document" + }, + "formatted_address": { + "type": "string", + "description": "Formatted full address" + }, + "place_of_birth": { + "type": "string", + "description": "Place of birth from document" + }, + "marital_status": { + "type": "string", + "description": "Marital status from document" + }, + "nationality": { + "type": "string", + "description": "ISO nationality code" + }, + "extra_fields": { + "type": "object", + "description": "Additional document fields. Includes document-specific attributes (e.g. `dl_categories`, `blood_group`, `sponsor`, `employer`, `dl_class_code_b_from`, `dl_class_code_b_to`). Driver license class validity dates are returned as `dl_class_code__from` and `dl_class_code__to`, and per-class restriction/information codes (e.g. column 12 of UK licences) as `dl_class_code__notes`. When the document carries names, address, or place of birth in multiple scripts, this object also includes alternate-script versions of those fields. The workflow setting `preferred_characters` (`latin` or `non_latin`) controls which script populates the top-level fields (`first_name`, `last_name`, `full_name`, `address`, `place_of_birth`); the opposite script lands here as `first_name_non_latin` / `last_name_non_latin` / `full_name_non_latin` / `middle_name_non_latin` / `address_non_latin` / `place_of_birth_non_latin` (when `preferred_characters` is `latin`) or with a `_latin` suffix (when `preferred_characters` is `non_latin`). Keys appear only when the OCR actually extracts a real alternate-script value — a field whose alternate value is in the same script as the preferred set (for example, a Cyrillic-labelled field whose OCR value came back in Latin homoglyphs) is skipped. When `preferred_characters` is `latin` but the document only carries non-Latin text, Didit transliterates the extracted values and exposes `first_name_latin` / `last_name_latin` / etc. as a fallback.", + "additionalProperties": true, + "properties": { + "dl_categories": { + "type": "array", + "items": { + "type": "string" + } + }, + "blood_group": { + "type": "string", + "nullable": true + }, + "first_name_non_latin": { + "type": "string", + "description": "First name in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document (e.g. `Айна` for a Kyrgyz ID)." + }, + "last_name_non_latin": { + "type": "string", + "description": "Surname in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document." + }, + "middle_name_non_latin": { + "type": "string", + "description": "Middle name / patronymic in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document." + }, + "full_name_non_latin": { + "type": "string", + "description": "Composed full name in the non-Latin script. Reconstructed from the individual components when both first and last names are available." + }, + "address_non_latin": { + "type": "string", + "description": "Address in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document." + }, + "place_of_birth_non_latin": { + "type": "string", + "description": "Place of birth in the non-Latin script when `preferred_characters=latin` and a non-Latin version is present on the document." + }, + "first_name_latin": { + "type": "string", + "description": "First name in Latin script. Populated when `preferred_characters=non_latin` and a Latin version is present on the document, or — as a transliteration fallback — when `preferred_characters=latin` but the document only carries non-Latin text." + }, + "last_name_latin": { + "type": "string", + "description": "Surname in Latin script. Same rules as `first_name_latin`." + }, + "middle_name_latin": { + "type": "string", + "description": "Middle name / patronymic in Latin script. Same rules as `first_name_latin`." + }, + "full_name_latin": { + "type": "string", + "description": "Composed full name in Latin script. Always reconstructed from the individual components when both first and last names are available." + }, + "address_latin": { + "type": "string", + "description": "Address in Latin script. Same rules as `first_name_latin`." + }, + "place_of_birth_latin": { + "type": "string", + "description": "Place of birth in Latin script. Same rules as `first_name_latin`." + } + } + }, + "mrz": { + "type": "object", + "nullable": true, + "description": "Parsed Machine-Readable Zone (MRZ) fields extracted from the document. `null` if no MRZ was detected.", + "properties": { + "surname": { + "type": "string", + "example": "MARTINEZ_families` / `very_high_similarity_plus__families`. Present only when `match_source` is `recovered_high`." + } + } + } + }, + "browser_version": { + "type": "string", + "nullable": true + }, + "os_version": { + "type": "string", + "nullable": true + }, + "user_agent": { + "type": "string", + "nullable": true, + "description": "The browser's user agent string." + }, + "accept_language": { + "type": "string", + "nullable": true, + "description": "The `Accept-Language` header the browser sent." + }, + "session_identifier": { + "type": "string", + "nullable": true, + "description": "Identifier of the browsing session the verification ran in." + }, + "session_age_ms": { + "type": "integer", + "nullable": true, + "description": "Age of that browsing session in milliseconds when the step ran." + }, + "coords_accuracy": { + "type": "number", + "nullable": true, + "description": "Accuracy radius, in metres, of the coordinates the device reported." + }, + "raw_device_data": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Raw device signals collected for the step." + } + } + } + }, + "geolocation_checks": { + "type": "array", + "nullable": true, + "description": "**Array of Location verification results**, one per Location verification step: that step's accepted attempt. `null` until a Location verification step has produced data. Earlier attempts of the same step are in `geolocation_attempts`.", + "items": { + "$ref": "#/components/schemas/GeolocationCheck" + } + }, + "geolocation_attempts": { + "type": "array", + "nullable": true, + "description": "Every attempt of every Location verification step of this session, oldest first. Attempts that a resubmission replaced are left out. A workflow that withholds `geolocation_checks` through its response attributes withholds this list too.", + "items": { + "$ref": "#/components/schemas/GeolocationAttempt" + } + }, + "database_validations": { + "type": "array", + "nullable": true, + "description": "**Array of Database Validation reports** — always a JSON array, never a singular `database_validation` object. `null` until at least one DB validation step has produced data. Each item is the full report, including `node_id`, feature-level `status`, `screened_data`, the per-service `validations[]` (each with its registry `source_data`), and `warnings[]`. The `screened_data` and `source_data` fields vary by country:\n\n**ARG (Argentina)**: screened_data: {document_number, selfie, gender, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, date_of_death, tax_id, tax_id_type, face_match_score, sit_1_since, banks, last_position, highest_position, rejected_checks}. Uses RENAPER biometric face-match validation. Gender is auto-inferred from face analysis or given name when not present in the document (e.g., driver licenses)\n\n**BOL (Bolivia)**: screened_data: {document_number, date_of_birth, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, gender}\n\n**BRA (Brazil)**: screened_data: {tax_number, first_name, last_name, date_of_birth}. source_data always includes {identification_number, first_name, last_name, date_of_birth, lgpd_minor, minor_under_18, minor_under_16} for successful CPF matches. Successful adult lookups set those flags to false. The Receita Federal Consulta CPF service may return HTTP 422 (minor under 18) or 451 (under 16) with LGPD-withheld data; in those cases source_data sets the minor flags to true as appropriate, includes the upstream HTTP status, and field-level validation is no_match. See the Brazil section of the Database Validation Outcome Codes reference for the full code list.\n\n**CHL (Chile)**: screened_data: {personal_number, first_name, last_name, date_of_birth}. source_data: {identification_number, first_name, last_name, date_of_birth, gender}\n\n**COL (Colombia)**: screened_data: {document_number, document_type, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, document_type}\n\n**CRI (Costa Rica)**: screened_data: {personal_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**DOM (Dominican Republic)**: screened_data: {personal_number}. source_data: {identification_number} (returns valid/invalid only)\n\n**ECU (Ecuador)**: screened_data: {personal_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, street, formatted_address, gender, nationality, education, marital_status, spouse, mother_name, father_name, profession}\n\n**ESP (Spain)**: screened_data: {personal_number, document_type, expiration_date, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, document_type, expiration_date}\n\n**GTM (Guatemala)**: screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**HND (Honduras)**: screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**MEX (Mexico)**: screened_data: {personal_number (CURP), first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, date_of_birth, gender, nationality, curp_status, state_of_birth, doc_probatorio, registration_year, registration_state, registration_municipality, num_acta, crip, issuing_state_code, foreign_registry_number, folio_carta, folio_certificado}. Uses the RENAPO civil registry. A separate INE validity-check service is also available for MEX as an additional one-by-one validation.\n\n**PAN (Panama)**: screened_data: {personal_number, selfie}. source_data: {identification_number}. Uses SIB biometric validation\n\n**PER (Peru)**: screened_data: {personal_number (DNI), first_name, last_name}. source_data: {identification_number, first_name, last_name, paternal_name, maternal_name, verification_number, verification_letter}\n\n**PRY (Paraguay)**: screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**SLV (El Salvador)**: screened_data: {document_number, date_of_birth, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**URY (Uruguay)**: screened_data: {personal_number, date_of_birth, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name}\n\n**VEN (Venezuela)**: screened_data: {document_number, first_name, last_name}. source_data: {identification_number, first_name, last_name, full_name, gender, date_of_birth, document_type}", + "items": { + "type": "object", + "properties": { + "node_id": { + "type": "string", + "description": "Unique identifier for this database validation step in the workflow graph." + }, + "issuing_state": { + "type": "string", + "description": "ISO 3166-1 alpha-3 code of the country whose government/registry sources were queried (e.g. `BRA`, `MEX`, `ESP`, `NGA`, `IDN`). Database Validation covers 60+ countries across the Americas, Europe, Africa, the Middle East and Asia-Pacific, plus global multi-country sources — coverage expands regularly, so treat the value as an open set rather than a fixed enum." + }, + "validation_type": { + "type": "string", + "enum": [ + "one_by_one", + "two_by_two", + "not_enabled" + ], + "description": "How the result was corroborated: `one_by_one` (single-service outcome), `two_by_two` (two or more distinct services independently full-matched the identification number), or `not_enabled` (persisted when no service produced a usable match). The value is never `null`. Dedicated biometric 2x2 validation modes are currently available for `ECU` and `PER`." + }, + "screened_data": { + "type": "object", + "description": "The input data used for validation. Fields vary by country — see the Database Validation docs (`/core-technology/database-validation/overview`) for country-specific formats.", + "properties": { + "last_name": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "tax_number": { + "type": "string", + "description": "Brazil: CPF (11 digits)" + }, + "personal_number": { + "type": "string", + "description": "Chile: RUT, DOM: Cédula (11 digits), ECU: Cédula (10 digits), ESP: DNI/NIE, MEX: CURP (18 chars), PER: DNI (8 digits), URY: CI" + }, + "document_number": { + "type": "string", + "description": "ARG: DNI, BOL: CI, COL: Cédula, GTM: DPI, HND: DNI, PAN: Cédula, PRY: CI, SLV: DUI, VEN: Cédula" + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "document_type": { + "type": "string", + "description": "COL: CC/CE/TI/NIT, ESP: ID/PASSPORT" + }, + "expiration_date": { + "type": "string", + "format": "date", + "description": "Required for ESP" + } + } + }, + "validations": { + "type": "array", + "description": "One entry per registry service that returned a validation payload. Each entry identifies the service (`service_id`, `service_name`) and carries the per-field `validation` comparison, the service `outcome_code` / `outcome_detail` when reported, and the normalised registry record in `source_data`.", + "items": { + "type": "object", + "properties": { + "service_id": { + "type": "string", + "description": "Identifier of the registry service that produced this validation result.", + "example": "bra_cpf" + }, + "service_name": { + "type": "string", + "description": "Human-readable name of the registry service.", + "example": "Brazil - CPF status check" + }, + "validation": { + "type": "object", + "description": "The results of the field validations.", + "properties": { + "full_name": { + "type": "string", + "description": "Match result for full name (e.g., 'full_match', 'partial_match', 'no_match')." + }, + "date_of_birth": { + "type": "string", + "description": "Match result for date of birth." + }, + "identification_number": { + "type": "string", + "description": "Match result for identification number." + } + } + }, + "outcome_code": { + "type": "string", + "nullable": true, + "description": "Machine-readable outcome of the registry lookup (e.g. `MATCH`, `NO_MATCH`, `PARTIAL_MATCH`, `INCONCLUSIVE`, source-specific codes). Present when the service reports one." + }, + "outcome_detail": { + "type": "string", + "nullable": true, + "description": "Human-readable explanation of `outcome_code`, when provided by the service." + }, + "source_data": { + "type": "object", + "description": "The data returned from the government database. Common fields: identification_number, first_name, last_name, full_name, date_of_birth, gender. Additional fields by country: BRA always includes lgpd_minor, minor_under_18, and minor_under_16 on successful CPF matches; it also includes the upstream HTTP status when CPF data is withheld under LGPD (HTTP 422/451). ECU adds street, formatted_address, nationality, education, marital_status, spouse, mother_name, father_name, profession. PER adds paternal_name, maternal_name, verification_number, verification_letter. ESP adds document_type, expiration_date. VEN/COL add document_type.", + "additionalProperties": true + } + } + } + }, + "errors": { + "type": "array", + "description": "Errors returned by registry services while validating, each tagged with the `service_id` that produced it. **This key is omitted entirely when there are no errors.**", + "items": { + "type": "object", + "description": "The upstream service's error object spread as-is — its keys vary by service (commonly a message/code and any provider-specific details) — plus `service_id` identifying the service that errored.", + "properties": { + "service_id": { + "type": "string", + "nullable": true, + "description": "Identifier of the registry service that errored." + } + }, + "additionalProperties": true + } + }, + "match_type": { + "type": "string", + "enum": [ + "no_match", + "partial_match", + "full_match" + ], + "nullable": true, + "description": "The aggregate match outcome across the queried services. `null` until the validation has been evaluated." + }, + "status": { + "type": "string", + "description": "Feature-level status of this database validation. Independent of the top-level session `status` — a session can reach its overall decision while an individual feature stays `In Review`.", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ] + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "NFC", + "LIVENESS", + "FACEMATCH", + "PROOF_OF_ADDRESS", + "PHONE", + "EMAIL", + "AML", + "LOCATION", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "GEOLOCATION", + "SIGNATURE" + ], + "description": "The feature that generated this warning. The value identifies the feature block the warning belongs to. Each module's warnings array typically uses the value matching its module (e.g. `PHONE` inside `phone_verifications[].warnings`), but the enum above is authoritative across all feature warnings on the decision payload." + }, + "risk": { + "type": "string" + }, + "additional_data": { + "type": "object", + "nullable": true + }, + "log_type": { + "type": "string" + }, + "short_description": { + "type": "string" + }, + "long_description": { + "type": "string" + }, + "node_id": { + "type": "string", + "description": "The workflow graph node ID that generated this warning" + } + } + }, + "description": "Warnings related to the database validation." + } + } + } + }, + "database_validation_not_performed_reason": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Why a configured database validation produced no result, as `{code, message}` plus context fields. Present only when `database_validations` is `null` for a workflow that has the step, so you can tell \"the registry agreed\" from \"nothing ran\". `null` otherwise.", + "properties": { + "code": { + "type": "string", + "description": "Stable machine-readable reason." + }, + "message": { + "type": "string", + "description": "Human-readable explanation." + } + } + }, + "reviews": { + "type": "array", + "items": { + "type": "object", + "properties": { + "user": { + "type": "string", + "description": "Who performed the review. Usually the console reviewer's identifier (their email), but the value is `\"API Client\"` for status updates made with an API key and `\"Unknown\"` when no actor could be resolved — so do not parse this field as an email address." + }, + "new_status": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + } + }, + "assignee": { + "type": "object", + "nullable": true, + "description": "The Console reviewer the session is assigned to, or `null` when nobody holds it.", + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "assigned_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "waiting_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the session started waiting for review. `null` unless the session is `In Review` and your application sets a review deadline." + }, + "sla_due_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the review is due. `null` unless the session is `In Review` and your application sets a review deadline." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp at which the session was created (ISO 8601, UTC)." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp at which this session is scheduled to expire. Set on User Verification (KYC) sessions when an expiration was configured at creation time (or once `Approved` status triggers KYC expiry computation). `null` for sessions without a configured expiration, and for kinds that do not use expiry (e.g. Business Verification (KYB) sessions that never received an `expires_at`)." + }, + "registry_checks": { + "type": "array", + "nullable": true, + "description": "Company registry checks. Present only when `session_kind = business`. Each item wraps the full company-registry payload.", + "items": { + "type": "object", + "description": "One company registry check — the full company-registry payload enriched with ownership structure and warnings. Present only when `session_kind = business`.", + "properties": { + "status": { + "type": "string", + "description": "Feature lifecycle status." + }, + "node_id": { + "type": "string", + "nullable": true + }, + "data_resolved": { + "type": "boolean" + }, + "company": { + "type": "object", + "description": "Full registry payload for the company (registry record merged with any user-provided or console-edited data).", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "node_id": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string" + }, + "registry_status": { + "type": "string", + "nullable": true + }, + "data_resolved": { + "type": "boolean" + }, + "company_name": { + "type": "string" + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_code": { + "type": "string", + "description": "Country of incorporation (ISO 3166-1 alpha-2, e.g. `GB`, `DE`, `US`)." + }, + "region": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision code when applicable (e.g. `CA`, `NY` for US states)." + }, + "company_type": { + "type": "string", + "nullable": true + }, + "incorporation_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "registered_address": { + "type": "string", + "nullable": true + }, + "tax_number": { + "type": "string", + "nullable": true + }, + "risk_level": { + "type": "string", + "enum": [ + "LOW", + "MEDIUM", + "HIGH" + ], + "nullable": true + }, + "verification_status": { + "type": "string", + "nullable": true + }, + "is_from_registry": { + "type": "boolean" + }, + "fetch_status": { + "type": "string", + "nullable": true + }, + "alternative_names": { + "type": "string", + "nullable": true, + "description": "Alternative, trading, former or original-language company names as text, when reported." + }, + "nature_of_business": { + "type": "string", + "nullable": true + }, + "registered_capital": { + "type": "string", + "nullable": true + }, + "registered_capital_amount": { + "type": "string", + "format": "decimal", + "nullable": true, + "description": "Registered/share capital amount as a decimal string (e.g. `\"100.00\"`), parsed from the registry filing. `null` when not available." + }, + "registered_capital_currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 currency code of `registered_capital_amount` (e.g. `\"GBP\"`). `null` when not available." + }, + "website": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "format": "email", + "nullable": true + }, + "phone": { + "type": "string", + "nullable": true + }, + "legal_entity_identifier": { + "type": "string", + "nullable": true + }, + "location_of_registration": { + "type": "string", + "nullable": true + }, + "vat_number": { + "type": "string", + "nullable": true, + "description": "EU VAT number, when collected during the hosted registry step or edited afterwards." + }, + "vat_validation_status": { + "type": "string", + "enum": [ + "valid", + "invalid", + "format_valid", + "invalid_format", + "could_not_validate", + "not_applicable" + ], + "description": "Result of validating the VAT number or, when there is none, the `tax_number`, at registry submit. `valid` / `invalid`: decided by the official registry of the country (EU VIES, Norway, Switzerland, Brazil, Colombia, Costa Rica, Ecuador, Paraguay; Poland also by its domestic VAT register). `format_valid` / `invalid_format`: decided by the country's format and check-digit rules where no registry can be queried. `could_not_validate`: the registry was unavailable. `not_applicable`: no number, a country without registry or rules, or a sandbox session." + }, + "vat_validated_number": { + "type": "string", + "nullable": true, + "description": "The number that was checked, as the source knows it (for example the VAT number derived from a French SIREN or a Dutch RSIN). `null` when no check ran." + }, + "vat_validation_source": { + "type": "string", + "nullable": true, + "enum": [ + "eu_vies", + "pl_vat_white_list", + "no_brreg", + "ch_uid_register", + "br_receita_federal", + "co_rues", + "cr_hacienda", + "ec_sri", + "py_dnit", + "format_rules", + null + ], + "description": "The source that decided `vat_validation_status`: an official registry, or `format_rules`. `null` when no check ran." + }, + "vat_validated_name": { + "type": "string", + "nullable": true, + "description": "Name registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_validated_address": { + "type": "string", + "nullable": true, + "description": "Address registered with the source that validated the number, when it discloses one. Only set when the status is `valid`." + }, + "vat_checked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Timestamp of the check. `null` when no check ran." + }, + "financial_summary": { + "type": "object", + "nullable": true + }, + "officers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "designation": { + "type": "string", + "nullable": true + }, + "role": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "Pending" + ], + "nullable": true + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + } + }, + "beneficial_owners": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ] + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership percentage as reported by the registry. Free-form string (e.g. `\"25.00\"`, `\"50\"`); use `effective_ownership_percent` for a parsed numeric value." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership percentage as reported by the registry. Free-form string; use `effective_ownership_percent` for a parsed numeric value." + }, + "is_active": { + "type": "boolean" + }, + "kyc_status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "Pending" + ], + "nullable": true + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + }, + "shareholding": { + "$ref": "#/components/schemas/KYBRegistryShareholding" + } + } + } + }, + "addresses": { + "type": "array", + "nullable": true, + "description": "Every address the registry states, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryAddress" + } + }, + "industries": { + "type": "array", + "nullable": true, + "description": "The activities the registry classifies the company under, one canonical shape. Null when it states none.", + "items": { + "$ref": "#/components/schemas/KYBRegistryIndustry" + } + }, + "accounts": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Registry filing dates, when available. Annual returns and general meetings use last_annual_return_date and last_agm_date; they are distinct from financial accounts." + }, + "registry_data": { + "$ref": "#/components/schemas/KYBRegistryData" + }, + "user_provided_data": { + "type": "object", + "description": "Data confirmed or edited by the end user." + }, + "confirmed_by_user_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_console_edit_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "is_editable": { + "type": "boolean" + }, + "ownership": { + "$ref": "#/components/schemas/KYBRegistryOwnership" + }, + "registry_tier": { + "type": "string", + "nullable": true, + "description": "The registry tier the workflow ordered: `basic`, `shareholders` or `ubo`. Also in `ownership.tier_requested`." + }, + "registry_tier_delivered": { + "type": "string", + "nullable": true, + "description": "The tier the registry delivered and that is billed. Also in `ownership.tier_delivered`." + }, + "registry_ownership_status": { + "type": "string", + "nullable": true, + "description": "`not_requested`, `pending`, `delivered`, `not_held` or `unavailable`. Also in `ownership.status`." + }, + "registry_monitoring": { + "type": "object", + "nullable": true, + "description": "The latest registry monitor for the company, or `null` when none exists.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string" + }, + "unavailable_reason": { + "type": "string", + "nullable": true + }, + "enrolled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "next_bill_date": { + "type": "string", + "format": "date", + "nullable": true + }, + "last_event_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "events_count": { + "type": "integer" + }, + "watched_fields": { + "description": "The company fields the monitor watches." + } + } + } + } + }, + "ownership_structure": { + "$ref": "#/components/schemas/KYBOwnershipStructure" + }, + "warnings": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + }, + "key_people_checks": { + "type": "array", + "nullable": true, + "description": "Aggregate key-people checks. Present only when `session_kind = business`. Each item uses the two-bucket shape (`registry`, `submitted`, `ubo_kyc_summary`).", + "items": { + "type": "object", + "description": "Aggregate check over the company's officers + UBOs. Uses the `registry` / `submitted` shape. Present only when session_kind = business.", + "properties": { + "status": { + "type": "string" + }, + "node_id": { + "type": "string", + "nullable": true + }, + "registry": { + "type": "object", + "description": "Parties discovered via the registry check.", + "properties": { + "officers": { + "type": "array", + "description": "Officers discovered via the registry check, each enriched with the status of any child KYC session and AML screening spawned for them.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "description": "Officer's full name as recorded in the registry." + }, + "designation": { + "type": "string", + "nullable": true, + "description": "Free-text designation from the registry (e.g. `Director`, `Company Secretary`)." + }, + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Normalized officer role." + }, + "nationality": { + "type": "string", + "nullable": true + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Date the officer was appointed.", + "format": "date" + }, + "termination_date": { + "type": "string", + "nullable": true, + "description": "Date the appointment ended, when reported.", + "format": "date" + }, + "is_active": { + "type": "boolean" + }, + "address": { + "type": "string", + "nullable": true, + "description": "Officer address reported by the registry." + }, + "aml_status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "nullable": true, + "description": "Status of the AML screening run for this officer. `null` when no AML screening was run." + }, + "kyc_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "nullable": true, + "description": "Raw lifecycle status of the child KYC session spawned for this officer. This is the full session status enum (not a reduced Approved/Declined/Pending set). `null` when no child KYC session exists." + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Shareable URL of the child KYC session. `null` when no child session exists." + }, + "kyc_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Session id of the child KYC session. Pass it to this same decision endpoint to fetch the person's full KYC report. `null` when no child session exists." + }, + "verification_workflow_type": { + "type": "string", + "nullable": true, + "description": "Base workflow type of the child session's workflow (e.g. `kyc`). `null` for graph-based workflows or when no child session exists." + }, + "verification_workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Workflow id used by the child session. `null` when no child session exists." + }, + "verification_workflow_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label of the child session's workflow. `null` when unset." + }, + "verification_workflow_features": { + "type": "array", + "nullable": true, + "description": "Per-feature progress of the child verification session, e.g. `[{\"feature\": \"ID_VERIFICATION\", \"status\": \"Approved\"}]`. `null` when the child session has no resolvable feature list.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature display name (e.g. `ID_VERIFICATION`, `LIVENESS`, `AML`)." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Feature-level status inside the child session." + } + } + } + }, + "didit_internal_id": { + "type": "string", + "nullable": true, + "description": "Stable internal identifier of the verified individual behind the child session — useful for cross-session linking. `null` when no child session exists or the user has not been resolved yet." + }, + "kyc_document_type": { + "type": "string", + "nullable": true, + "description": "Document type presented in the child KYC session (e.g. `P`, `ID`, `DL`). `null` until the child session has an ID verification result." + }, + "kyc_nationality": { + "type": "string", + "nullable": true, + "description": "Nationality extracted from the document in the child KYC session. `null` until available." + }, + "kyc_liveness_passed": { + "type": "boolean", + "nullable": true, + "description": "Whether the child KYC session's liveness check passed. `null` when no liveness check has run." + }, + "kyc_verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the child KYC session reached `Approved`. `null` while the child session has any other status." + }, + "kyc_aml_categories": { + "type": "object", + "nullable": true, + "description": "Per-category summary derived from the person's AML hits. `null` when there are no hits.", + "properties": { + "sanctions": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "pep": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "adverse_media": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "warnings": { + "type": "string", + "enum": [ + "match", + "clear" + ] + } + } + }, + "kyc_notification_sent_at": { + "type": "string", + "nullable": true, + "description": "ISO 8601 timestamp of the last verification-invite notification sent for this person's child session. `null` when none was sent." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "Given name when explicitly reported by the registry; not inferred from the full name." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Family name when explicitly reported by the registry." + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete registry-reported birth date. Null when only a birth year or month is known.", + "format": "date" + }, + "date_of_birth_year": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 9999, + "description": "Registry-reported birth year, including when the full date is unavailable." + }, + "date_of_birth_month": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 12, + "description": "Registry-reported birth month. Null when only the year is known." + } + } + } + }, + "beneficial_owners": { + "type": "array", + "description": "Beneficial owners discovered via the registry check, each enriched with the status of any child KYC session and AML screening spawned for them.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "description": "Beneficial owner's full name (or company name for corporate UBOs)." + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "nullable": true, + "description": "Complete birth date when reported.", + "format": "date" + }, + "country_of_birth": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ] + }, + "nationality": { + "type": "string", + "nullable": true + }, + "designation": { + "type": "string", + "nullable": true + }, + "roles": { + "type": "array", + "description": "Role names held by this owner, e.g. `[\"ubo\", \"shareholder\"]`.", + "items": { + "type": "string" + } + }, + "ownership_min_shares": { + "type": "string", + "nullable": true, + "description": "Lower bound of the ownership percentage as reported by the registry. Free-form string (e.g. `\"25\"`, `\"25-50%\"`); use `effective_ownership_percent` for a parsed numeric value." + }, + "ownership_max_shares": { + "type": "string", + "nullable": true, + "description": "Upper bound of the ownership percentage as reported by the registry. Free-form string; use `effective_ownership_percent` for a parsed numeric value." + }, + "is_active": { + "type": "boolean" + }, + "aml_status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "nullable": true, + "description": "Status of the AML screening run for this beneficial owner. `null` when no AML screening was run." + }, + "kyc_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "nullable": true, + "description": "Raw lifecycle status of the child KYC session spawned for this beneficial owner. This is the full session status enum (not a reduced Approved/Declined/Pending set). `null` when no child KYC session exists." + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Shareable URL of the child KYC session. `null` when no child session exists." + }, + "kyc_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Session id of the child KYC session. Pass it to this same decision endpoint to fetch the person's full KYC report. `null` when no child session exists." + }, + "verification_workflow_type": { + "type": "string", + "nullable": true, + "description": "Base workflow type of the child session's workflow (e.g. `kyc`). `null` for graph-based workflows or when no child session exists." + }, + "verification_workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Workflow id used by the child session. `null` when no child session exists." + }, + "verification_workflow_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label of the child session's workflow. `null` when unset." + }, + "verification_workflow_features": { + "type": "array", + "nullable": true, + "description": "Per-feature progress of the child verification session, e.g. `[{\"feature\": \"ID_VERIFICATION\", \"status\": \"Approved\"}]`. `null` when the child session has no resolvable feature list.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature display name (e.g. `ID_VERIFICATION`, `LIVENESS`, `AML`)." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Feature-level status inside the child session." + } + } + } + }, + "didit_internal_id": { + "type": "string", + "nullable": true, + "description": "Stable internal identifier of the verified individual behind the child session — useful for cross-session linking. `null` when no child session exists or the user has not been resolved yet." + }, + "kyc_document_type": { + "type": "string", + "nullable": true, + "description": "Document type presented in the child KYC session (e.g. `P`, `ID`, `DL`). `null` until the child session has an ID verification result." + }, + "kyc_nationality": { + "type": "string", + "nullable": true, + "description": "Nationality extracted from the document in the child KYC session. `null` until available." + }, + "kyc_liveness_passed": { + "type": "boolean", + "nullable": true, + "description": "Whether the child KYC session's liveness check passed. `null` when no liveness check has run." + }, + "kyc_verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the child KYC session reached `Approved`. `null` while the child session has any other status." + }, + "kyc_aml_categories": { + "type": "object", + "nullable": true, + "description": "Per-category summary derived from the person's AML hits. `null` when there are no hits.", + "properties": { + "sanctions": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "pep": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "adverse_media": { + "type": "string", + "enum": [ + "match", + "clear" + ] + }, + "warnings": { + "type": "string", + "enum": [ + "match", + "clear" + ] + } + } + }, + "kyc_notification_sent_at": { + "type": "string", + "nullable": true, + "description": "ISO 8601 timestamp of the last verification-invite notification sent for this person's child session. `null` when none was sent." + }, + "child_beneficial_owners": { + "type": "array", + "description": "For corporate UBOs: the owning entity's own beneficial owners, as raw objects extracted from the registry. Empty array when not applicable.", + "items": { + "type": "object" + } + }, + "effective_ownership_percent": { + "type": "number", + "nullable": true, + "description": "Parsed numeric ownership percentage — the max bound when available, otherwise the min bound. `null` when neither bound could be parsed." + }, + "voting_min_shares": { + "type": "string", + "nullable": true + }, + "voting_max_shares": { + "type": "string", + "nullable": true + }, + "appointment_date": { + "type": "string", + "nullable": true, + "description": "Appointment date when reported.", + "format": "date" + } + } + } + } + } + }, + "submitted": { + "type": "object", + "description": "Parties submitted by the business admin during the Key People flow (BusinessParty rows where source=USER).", + "properties": { + "parties": { + "type": "array", + "items": { + "type": "object", + "description": "Party submitted by the business admin via the Key People flow (source=USER).", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ] + }, + "source": { + "type": "string", + "enum": [ + "USER" + ], + "description": "Always `USER` here — this bucket only contains parties entered or confirmed by the end user in the Key People flow (registry-discovered people appear under `registry`)." + }, + "name": { + "type": "string" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "company_name": { + "type": "string", + "nullable": true + }, + "registration_number": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "format": "email", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true, + "description": "Nationality for persons; country of incorporation for company parties." + }, + "phone_number": { + "type": "string", + "nullable": true + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true + }, + "position": { + "type": "string", + "nullable": true, + "description": "Free-text job title / position (persons only)." + }, + "custom_fields": { + "type": "object", + "description": "Workflow-defined custom fields collected for this party; keys are defined by the workflow's key-people field descriptors." + }, + "is_active": { + "type": "boolean" + }, + "is_skipped": { + "type": "boolean", + "description": "True when the end user skipped verification for this party (only possible when the role allows skipping)." + }, + "requires_verification": { + "type": "boolean", + "description": "True when a child KYC / KYB sub-session is required for this party based on role settings and ownership thresholds." + }, + "roles": { + "type": "array", + "description": "All roles held by this party, each with its own ownership and voting percentages. There is no scalar `role`/`ownership_percent` on the party itself.", + "items": { + "type": "object", + "properties": { + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ] + }, + "ownership_percent": { + "type": "number", + "nullable": true + }, + "voting_percent": { + "type": "number", + "nullable": true + } + } + } + }, + "kyc_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Child KYC session id for person parties requiring verification. `null` otherwise." + }, + "kyc_session_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "nullable": true, + "description": "Lifecycle status of the child KYC session. `null` when no child KYC session exists." + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true + }, + "kyb_sub_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Child KYB sub-session id for corporate parties requiring verification. `null` otherwise." + }, + "kyb_sub_session_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "nullable": true, + "description": "Lifecycle status of the child KYB sub-session. `null` when no sub-session exists." + }, + "kyb_sub_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Shareable URL of the child KYB sub-session. `null` when no sub-session exists." + }, + "verification_workflow_type": { + "type": "string", + "nullable": true, + "description": "Base workflow type of the child session's workflow (KYC session preferred, falling back to the KYB sub-session). `null` for graph-based workflows or when no child session exists." + }, + "verification_workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Workflow id used by the child session. `null` when no child session exists." + }, + "verification_workflow_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label of the child session's workflow. `null` when unset." + }, + "verification_workflow_features": { + "type": "array", + "nullable": true, + "description": "Per-feature progress of the child verification session, e.g. `[{\"feature\": \"ID_VERIFICATION\", \"status\": \"Approved\"}]`. `null` when the child session has no resolvable feature list.", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "description": "Feature display name (e.g. `ID_VERIFICATION`, `LIVENESS`, `AML`)." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Feature-level status inside the child session." + } + } + } + }, + "didit_internal_id": { + "type": "string", + "nullable": true, + "description": "Stable internal identifier of the verified individual behind the child session. `null` when not resolved." + } + } + } + } + } + }, + "ubo_kyc_summary": { + "type": "object", + "nullable": true, + "description": "Aggregate UBO KYC progress, or null when no UBOs have linked KYC sessions.", + "properties": { + "total": { + "type": "integer" + }, + "approved": { + "type": "integer" + }, + "flagged": { + "type": "integer", + "description": "UBOs whose KYC is Declined or In Review." + }, + "pending": { + "type": "integer" + } + } + }, + "warnings": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + }, + "document_verifications": { + "type": "array", + "nullable": true, + "description": "Document verification checks grouped by workflow node. Present only when `session_kind = business`.", + "items": { + "type": "object", + "description": "Documents grouped by workflow node. Present only when session_kind = business.", + "properties": { + "status": { + "type": "string" + }, + "node_id": { + "type": "string", + "nullable": true + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "document_type": { + "type": "string", + "enum": [ + "LEGAL_PRESENCE", + "COMPANY_DETAILS", + "OWNERSHIP_STRUCTURE", + "REPRESENTATIVES_AUTHORIZATION", + "OTHER" + ], + "nullable": true, + "description": "High-level KYB document group the file was classified into. `null` while unclassified." + }, + "document_subtype": { + "type": "string", + "nullable": true, + "description": "Specific OCR-detected subtype (e.g. `CERTIFICATE_OF_INCORPORATION`, `SHAREHOLDER_REGISTRY`); `UNKNOWN` when the OCR could not confidently classify the document." + }, + "document_group": { + "type": "string", + "enum": [ + "LEGAL_PRESENCE", + "COMPANY_DETAILS", + "OWNERSHIP_STRUCTURE", + "REPRESENTATIVES_AUTHORIZATION", + "OTHER" + ], + "nullable": true, + "description": "Alias of `document_type` — the group name used by `groups` and `required_groups`." + }, + "file_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived presigned link to the uploaded file. `null` for analyst-created document request placeholders (`is_requested = true`) that have not been fulfilled yet." + }, + "original_filename": { + "type": "string", + "nullable": true, + "description": "Filename as uploaded by the user or analyst." + }, + "file_size": { + "type": "integer", + "nullable": true, + "description": "File size in bytes." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Per-document verification status." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "cross_check_result": { + "type": "object", + "nullable": true, + "description": "Cross-reference of the OCR-extracted document fields against the registry company profile. An object with `critical`, `non_critical` and `people` sections, each mapping a field name (e.g. `company_name`, `registration_number`, `incorporation_date`, `registered_address`) to a comparison record like `{\"document\": \"...\", \"registry\": \"...\", \"match\": true, \"score\": 0.97}`. `null` when the document had no OCR data or there was no registry company to compare against." + }, + "document_metadata": { + "type": "object", + "nullable": true, + "description": "Metadata extracted from the uploaded corporate document. When metadata extraction succeeded, all 16 keys are always present (individual values are `null` when not applicable to the file type); the whole object is `null` when nothing could be extracted. For PDFs, `overlay_manipulation` carries forensic overlay-text analysis.", + "properties": { + "file_size": { + "type": "integer", + "nullable": true, + "description": "Size of the document." + }, + "content_type": { + "type": "string", + "nullable": true, + "description": "Content type of the document." + }, + "creation_date": { + "type": "string", + "nullable": true, + "description": "Date when the document was created." + }, + "modified_date": { + "type": "string", + "nullable": true, + "description": "Date when the document was modified." + }, + "creator": { + "type": "string", + "nullable": true, + "description": "Authoring tool/user recorded in the file metadata (PDF `Creator`)." + }, + "producer": { + "type": "string", + "nullable": true, + "description": "Software that produced the file (PDF `Producer`)." + }, + "software": { + "type": "string", + "nullable": true, + "description": "Editing software recorded in the file metadata." + }, + "encryption": { + "type": "string", + "nullable": true, + "description": "Encryption scheme of the file, when encrypted." + }, + "is_signed": { + "type": "boolean", + "nullable": true, + "description": "Whether the document carries a digital signature." + }, + "is_tampered": { + "type": "boolean", + "nullable": true, + "description": "Whether the digital signature failed validation (post-signing modification)." + }, + "signature_info": { + "type": "object", + "nullable": true, + "description": "Details of the digital signature(s), when present." + }, + "exif_original_date": { + "type": "string", + "nullable": true, + "description": "EXIF original capture date, for image uploads." + }, + "exif_digitized_date": { + "type": "string", + "nullable": true, + "description": "EXIF digitization date, for image uploads." + }, + "processed_by_known_editor": { + "type": "boolean", + "nullable": true, + "description": "Whether metadata indicates the file passed through a known image/PDF editor." + }, + "has_different_creation_mod_date": { + "type": "boolean", + "nullable": true, + "description": "Whether creation and modification dates differ — a possible (but not conclusive) editing signal." + }, + "overlay_manipulation": { + "type": "object", + "nullable": true, + "description": "PDF forensic analysis for suspected overlay-text manipulation. Null when no overlay manipulation evidence was found or the document was not a PDF.", + "properties": { + "detected": { + "type": "boolean", + "description": "Whether overlay-text manipulation evidence was detected." + }, + "analyzed": { + "type": "boolean", + "description": "Whether the PDF could be analyzed." + }, + "signals": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Forensic signals that fired, such as duplicate_font_subset or glyph_fragmentation." + }, + "manipulated_regions": { + "type": "array", + "description": "Suspected manipulated areas in PDF page coordinates.", + "items": { + "type": "object", + "properties": { + "page": { + "type": "integer" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + }, + "width": { + "type": "number" + }, + "height": { + "type": "number" + }, + "page_width": { + "type": "number" + }, + "page_height": { + "type": "number" + } + } + } + } + } + } + } + }, + "description": { + "type": "string", + "nullable": true, + "description": "Analyst-provided label or reason for a document request. `null` for regular uploads." + }, + "is_requested": { + "type": "boolean", + "description": "True for analyst-created document request placeholders awaiting an upload." + }, + "ocr_data": { + "type": "object", + "nullable": true + }, + "is_console_upload": { + "type": "boolean", + "description": "True for documents uploaded from the console rather than the end-user flow. Console uploads never change the feature-level aggregate status." + }, + "is_confirmed": { + "type": "boolean", + "description": "Console uploads start `false` and flip `true` once the admin confirms them; end-user uploads are always `true`." + } + } + } + }, + "groups": { + "type": "object", + "description": "Per-group progress counters keyed by group name (e.g. legal_presence, ownership_structure).", + "additionalProperties": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "approved": { + "type": "integer" + }, + "pending": { + "type": "integer" + }, + "declined": { + "type": "integer" + }, + "in_review": { + "type": "integer" + }, + "other": { + "type": "integer" + }, + "missing": { + "type": "integer", + "description": "Requested placeholders with no file uploaded yet." + } + } + } + }, + "required_groups": { + "type": "array", + "items": { + "type": "string" + } + }, + "warnings": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "Whether the session was created by a live application (`live`) or a sandbox application (`sandbox`). Sandbox sessions are free, fully simulated, and never bill credits." + }, + "sandbox_scenario": { + "type": "string", + "nullable": true, + "description": "The sandbox scenario the session was created with. `null` for live sessions and for sandbox sessions created without one." + }, + "resubmissions": { + "type": "array", + "description": "Returned only with `include=resubmissions`, for user and business sessions. One entry per resubmission request, oldest first. Each entry carries the results the resubmitted steps returned before the request, so earlier submissions stay readable. Deleting the session deletes every submission.", + "items": { + "type": "object", + "properties": { + "number": { + "type": "integer", + "description": "1 for the first resubmission of the session, 2 for the second, and so on." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the resubmission was requested." + }, + "previous_status": { + "type": "string", + "description": "The session status just before the request." + }, + "nodes": { + "type": "array", + "description": "The workflow steps sent back to the user.", + "items": { + "type": "object", + "properties": { + "node_id": { + "type": "string" + }, + "feature": { + "type": "string" + } + } + } + }, + "reasons": { + "type": "object", + "nullable": true, + "additionalProperties": { + "type": "string" + }, + "description": "The reason given for each step, keyed by `node_id`." + }, + "requested_by": { + "type": "object", + "nullable": true, + "description": "Who requested the resubmission.", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "results": { + "type": "object", + "additionalProperties": true, + "description": "What the resubmitted steps returned before the request, under the same keys and in the same shape as the current results of this response (`id_verifications`, `nfc_verifications`, `liveness_checks`, `face_matches`, `poa_verifications`, `phone_verifications`, `email_verifications`, `aml_screenings`, `ip_analyses`, `database_validations`, `questionnaire_responses`, `document_ai_documents`, `geolocation_checks`; for business sessions also `document_verifications`). Only the keys of the steps that were sent back appear." + } + } + } + }, + "biometric_consent": { + "type": "object", + "nullable": true, + "description": "Whether the session required explicit biometric consent before a face capture, why, and the evidence of the answer. User Verification (KYC) sessions only; the same object is in the `decision` of session webhooks. `null` when the session was never evaluated, for example a workflow without a face capture. Fields with no recorded value are `null`. A native SDK below 4.10.0, or one that does not report its version, cannot show the consent step: its face captures run as before, and a session that required consent then reports `status: \"pending\"` with `reason: \"BIOMETRIC_CONSENT_UNSUPPORTED_SDK\"`. See [Biometric consent](/getting-started/biometric-consent).", + "properties": { + "required": { + "type": "boolean", + "description": "Whether the session must collect explicit biometric consent. Never goes back to `false`." + }, + "trigger": { + "type": "string", + "nullable": true, + "enum": [ + "declared_state", + "ip_state", + "ip_unknown", + "document_issuer", + "forced", + "customer_collected" + ], + "description": "Why consent is required, or how it was satisfied. `declared_state`: you declared a covered state (`user_state`, or a US address in `expected_details`). `ip_state`: the person's IP is in a covered state (Illinois, Texas, Colorado). `ip_unknown`: the evidence places the person in the US but the state cannot be told (the IP is in the US with no state, or cannot be located while your details or the document place the person in the US). `document_issuer`: the identity document was issued by a covered state. `forced`: the application's mode is Always. `customer_collected`: you sent `biometric_consent` when creating the session. `null` while not required." + }, + "status": { + "type": "string", + "enum": [ + "not_required", + "pending", + "given", + "declined" + ], + "description": "`not_required`; `pending` (required and not given: the person has not answered yet, or `reason` says why it was not collected); `given`; `declined` (a decline was recorded and the session ended as `Declined`; no Didit screen offers a decline today)." + }, + "given_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When consent was given. For `customer_collected`, the `given_at` you sent." + }, + "declined_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When a decline was recorded." + }, + "text_version": { + "type": "string", + "nullable": true, + "description": "Version id of the consent text the person answered, or the one you sent." + }, + "text_hash": { + "type": "string", + "nullable": true, + "description": "SHA-256 (hex) of the exact consent text shown, in the language it was shown in." + }, + "locale": { + "type": "string", + "nullable": true, + "description": "Language the consent text was shown in." + }, + "policy_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The biometric data policy the consent step linked to." + }, + "step": { + "type": "string", + "nullable": true, + "description": "Workflow step the session was on when the person answered, for example `LIVENESS`." + }, + "node_id": { + "type": "string", + "nullable": true, + "description": "Workflow graph node the session was on when the person answered." + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "IP the answer came from." + }, + "ip_country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of the answer's IP." + }, + "ip_state": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision of the answer's IP, for example `US-IL`." + }, + "ip_city": { + "type": "string", + "nullable": true, + "description": "City of the answer's IP." + }, + "user_agent": { + "type": "string", + "nullable": true, + "description": "User agent of the answer's request." + }, + "detected_ip_address": { + "type": "string", + "nullable": true, + "description": "The person's IP the requirement was last evaluated from. Can differ from `ip_address` when the person answered on another device." + }, + "detected_country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country of the detected IP." + }, + "detected_state": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision of the detected IP." + }, + "detected_city": { + "type": "string", + "nullable": true, + "description": "City of the detected IP." + }, + "declared_state": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision you declared: `user_state`, or the state of a US address in `expected_details`." + }, + "document_state": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision that issued the identity document, read at ID capture." + }, + "jurisdictions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "US states (USPS codes, for example `IL`) whose biometric law covers the person. `US` when the evidence places the person in the US but no state is known. Empty when none applies." + }, + "retention_months": { + "type": "integer", + "nullable": true, + "description": "The shortest biometric retention limit, in months, among `jurisdictions`. `null` when none applies." + }, + "reason": { + "type": "string", + "nullable": true, + "enum": [ + "BIOMETRIC_CONSENT_UNSUPPORTED_SDK", + "BIOMETRIC_CONSENT_DECLINED" + ], + "description": "`BIOMETRIC_CONSENT_UNSUPPORTED_SDK`: consent was required and a native SDK below 4.10.0, or one that did not report its version, captured a face without the consent step; `status` stays `pending`, or `given` if the person later agreed in the hosted flow. `BIOMETRIC_CONSENT_DECLINED`: a decline was recorded (it replaces the SDK reason). `null` otherwise, and always `null` when the session did not require consent." + }, + "decline_reason": { + "type": "string", + "nullable": true, + "enum": [ + "BIOMETRIC_CONSENT_DECLINED" + ], + "description": "`BIOMETRIC_CONSENT_DECLINED` when a recorded decline ended the session, otherwise `null`." + }, + "face_data_deleted_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the decline deleted the session's face images, videos and face templates. `null` unless a decline was recorded." + } + } + }, + "investigator": { + "$ref": "#/components/schemas/InvestigatorBlock" + }, + "biometric_retention": { + "$ref": "#/components/schemas/BiometricRetention" + }, + "fraud_check": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "The result of a Fraud Check API call. `null` for every other session." + }, + "full_session_video": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Expiring URL of the full-session recording. Returned only when your organization records full-session video." + }, + "full_session_video_recorded_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the full-session recording was made. Returned only when your organization records full-session video." + } + } + }, + "WorkflowDetail": { + "type": "object", + "description": "The fields below are typed; the object also carries every other column of the workflow configuration (feature toggles, thresholds, warning actions, allowed documents, callbacks) as additional properties. Fields that do not apply to the `workflow_type` are `null`.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Per-version identifier (`settings_uuid`). Every version of a workflow has its own." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "Stable identifier shared by every version of the workflow. Sessions created with it use the latest published version." + }, + "workflow_label": { + "type": "string", + "description": "Display name of the workflow." + }, + "workflow_type": { + "type": "string", + "nullable": true, + "enum": [ + "kyc", + "kyb", + "adaptive_age_verification", + "biometric_authentication", + "address_verification", + "questionnaire_verification", + "email_verification", + "phone_verification", + null + ], + "description": "Workflow template. `null` for graph workflows created without a template." + }, + "is_default": { + "type": "boolean", + "description": "Whether this is the application's default workflow." + }, + "is_archived": { + "type": "boolean", + "description": "Archived workflows cannot start new sessions." + }, + "version": { + "type": "integer", + "description": "Version number within the workflow." + }, + "status": { + "type": "string", + "enum": [ + "draft", + "published" + ], + "description": "Draft versions are editable; published versions are immutable and used for new sessions." + }, + "published_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this version was published. `null` for drafts." + }, + "is_simple_workflow": { + "type": "boolean", + "description": "`true` when the workflow has no custom `workflow_graph`." + }, + "is_editable": { + "type": "boolean", + "description": "`true` only for `draft` versions." + }, + "workflow_url": { + "type": "string", + "format": "uri", + "description": "Public URL that starts a session with this workflow." + }, + "workflow_graph": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Graph structure of the workflow (nodes and edges). `null` for simple workflows." + }, + "features": { + "type": "string", + "description": "Enabled features joined with `\" + \"`, for example `\"OCR + LIVENESS\"`." + }, + "total_price": { + "type": "number", + "nullable": true, + "description": "Price per session; for graph workflows the most expensive path." + }, + "min_price": { + "type": "number", + "nullable": true, + "description": "Price of the cheapest path through the workflow." + }, + "max_price": { + "type": "number", + "nullable": true, + "description": "Price of the most expensive path through the workflow." + }, + "special_add_on_prices": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Organization-specific add-on prices, or `null` when none are set." + }, + "max_retry_attempts": { + "type": "integer", + "description": "Retries allowed after a declined verification." + }, + "retry_window_days": { + "type": "integer", + "nullable": true, + "description": "Days during which the retries are allowed." + }, + "session_expiration_time": { + "type": "integer", + "description": "Seconds before an unfinished session expires." + }, + "questionnaire": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "The questionnaire attached to the workflow, or `null`." + }, + "kyb_role_settings": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true + }, + "description": "Per-role verification settings for KYB key people." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "signature_availability": { + "type": "object", + "description": "Which E-signature levels can sign right now. Check it before you set `signature_level`. Returned on `kyc`, `kyb` and graph workflows.", + "properties": { + "simple": { + "type": "boolean", + "description": "Simple is always available." + }, + "aes": { + "type": "boolean", + "description": "Whether Advanced (AES) can sign now. While it is `false`, saving a new step at `signature_level: \"aes\"` is refused with `aes_not_available`." + }, + "qes": { + "type": "boolean", + "description": "Whether Qualified (QES) can sign now. It is `false` until a qualified provider is connected." + } + } + }, + "is_bank_verification_enabled": { + "type": "boolean", + "description": "Whether a simple KYC or KYB workflow runs Bank verification. It runs after the contact steps (and Location verification) and before database validation, AML and IP analysis. Bank verification is not generally available yet: turning it on is refused wherever it is not enabled." + }, + "bank_verification_config": { + "type": "object", + "additionalProperties": true, + "description": "Bank verification settings of a simple workflow, with the same keys a `BANK_VERIFICATION` node takes (for example `bank_countries` and `bank_method_order`). Unknown keys are dropped. Every key is optional and an absent key keeps its default, so `{}` means all defaults. See [Bank verification settings](/management-api/workflows/feature-configs#bank-verification).", + "example": {} + }, + "is_geolocation_enabled": { + "type": "boolean", + "description": "Whether a simple KYC workflow runs Location verification. It runs after the contact steps and before Bank verification and database validation. Business (KYB) workflows cannot run it." + }, + "geolocation_config": { + "type": "object", + "additionalProperties": true, + "description": "Location verification settings of a simple workflow, with the same keys a `GEOLOCATION` node takes (for example `is_geolocation_geofencing_enabled` and `geolocation_max_attempts`). Unknown keys are dropped. Every key is optional and an absent key keeps its default, so `{}` means all defaults.", + "example": {} + } + }, + "additionalProperties": true + }, + "UserSessionListItem": { + "title": "KYC session row (session_kind: user)", + "type": "object", + "description": "Summary row for a User Verification (KYC) session. Identity fields (`full_name`, `country`, `document_type`, `portrait_image`, contact fields) are populated progressively as the user completes verification steps, and are `null` until the relevant step finishes. Fields restricted by the workflow's response-attributes configuration are also returned as `null`.", + "required": [ + "session_id", + "session_kind", + "session_number", + "session_url", + "portrait_image", + "document_type", + "full_name", + "country", + "status", + "vendor_data", + "didit_internal_id", + "created_at", + "features", + "phone_number", + "email_address", + "additional_details" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of the verification session. Use it with `GET /v3/session/{sessionId}/decision/` to fetch the full decision payload.", + "example": "5b3720ed-d429-42ef-b67f-37ea805f48ee" + }, + "session_kind": { + "type": "string", + "enum": [ + "user" + ], + "description": "Row discriminator. Always `\"user\"` for KYC rows; Business Verification (KYB) rows have `\"business\"` and a different shape.", + "example": "user" + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of the session within your application. Also matched by the `search` parameter.", + "example": 720 + }, + "session_url": { + "type": "string", + "nullable": true, + "description": "Hosted verification URL the end user opens to complete the flow. `null` when the session has no hosted URL.", + "example": "https://verify.didit.me/session/CuXoBYCYVO5Y" + }, + "portrait_image": { + "type": "string", + "nullable": true, + "description": "Expiring (presigned) URL of the best available portrait: the NFC chip photo when available, otherwise the ID document portrait, otherwise the liveness reference image. `null` until one of those exists. Download promptly; the link expires.", + "example": "https:///portraits/5b3720ed.jpg?X-Amz-Expires=3600&X-Amz-Signature=..." + }, + "document_type": { + "type": "string", + "nullable": true, + "enum": [ + "Passport", + "Identity Card", + "Driver's License", + "Residence Permit", + "Health Insurance Card", + "Tax Card" + ], + "description": "Human-readable type of the verified ID document. `null` before document verification completes, and also `null` after completion when the verified code has no display mapping (`SSC`, `WP`, `VISA`, `PSC`, `BC`, `OTHER`). Note: the `document_type` query parameter filters by short codes (`P`, `ID`, `DL`, `RP`, ...), not by these display values.", + "example": "Passport" + }, + "full_name": { + "type": "string", + "nullable": true, + "description": "Best available full name for the session, resolved in order: ID document OCR, AML screened data, database-validation screened data, proof-of-address document, then the expected details provided at session creation. `null` when none is available yet.", + "example": "Jane Marie Doe" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-3 country associated with the session, resolved in order: ID document issuing state, database-validation issuing state, proof-of-address issuing state, expected details, then the verified phone's country. May differ from nationality.", + "example": "ESP" + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Overall session status. See the [Verification Statuses](/integration/verification-statuses) page for the lifecycle.", + "example": "Approved" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the end user, echoed back exactly as sent at session creation.", + "example": "your-user-id-123" + }, + "didit_internal_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Didit's internal identifier for the verified user profile behind this session. Stable across multiple sessions of the same user; filterable via the `didit_internal_id` query parameter.", + "example": "d9e78474-6c8d-4f2a-bb90-6f5c2f3a1147" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "UTC timestamp when the session was created.", + "example": "2026-06-09T12:26:54.328364Z" + }, + "features": { + "type": "array", + "description": "Per-feature status for every verification step configured in the session's workflow, in workflow order.", + "items": { + "type": "object", + "required": [ + "feature", + "status" + ], + "properties": { + "feature": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "NFC", + "LIVENESS", + "FACE_MATCH", + "POA", + "QUESTIONNAIRE", + "EMAIL_VERIFICATION", + "PHONE", + "AML", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "DATABASE_VALIDATION", + "GEOLOCATION" + ], + "description": "Verification feature identifier.", + "example": "ID_VERIFICATION" + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Status of this feature. `Not Finished` until the step completes.", + "example": "Approved" + } + } + } + }, + "phone_number": { + "type": "object", + "nullable": true, + "description": "Phone associated with the session: the verified phone when phone verification ran, otherwise the contact phone provided at session creation. `null` when neither exists.", + "required": [ + "number", + "is_verified" + ], + "properties": { + "number": { + "type": "string", + "description": "Phone number in E.164 format.", + "example": "+14155552671" + }, + "is_verified": { + "type": "boolean", + "description": "`true` only when the phone-verification feature approved this number.", + "example": true + } + } + }, + "email_address": { + "type": "object", + "nullable": true, + "description": "Email associated with the session: the verified email when email verification ran, otherwise the contact email provided at session creation. `null` when neither exists.", + "required": [ + "email", + "is_verified" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "example": "jane@example.com" + }, + "is_verified": { + "type": "boolean", + "description": "`true` only when the email-verification feature approved this address.", + "example": true + } + } + }, + "additional_details": { + "type": "object", + "nullable": true, + "description": "Compact per-feature extras. Only features that produced data appear as keys; `null` when no feature produced extras. Fields excluded by the workflow's response-attributes configuration are omitted.", + "properties": { + "aml": { + "type": "object", + "description": "Present when AML screening ran.", + "properties": { + "total_hits": { + "type": "integer", + "description": "Number of AML screening hits.", + "example": 0 + }, + "entity_type": { + "type": "string", + "description": "Screened entity type.", + "example": "person" + }, + "is_adverse_media_fetched": { + "type": "boolean", + "example": false + }, + "screened_data": { + "type": "object", + "description": "Subset of the data that was screened. Only non-empty keys are included.", + "properties": { + "nationality": { + "type": "string", + "example": "ESP" + }, + "document_number": { + "type": "string", + "example": "XX1234567" + }, + "date_of_birth": { + "type": "string", + "example": "1990-05-21" + } + } + } + } + }, + "database_validation": { + "type": "object", + "description": "Present when database validation ran.", + "properties": { + "validation_type": { + "type": "string", + "example": "DOCUMENT_VALIDATION" + }, + "match_type": { + "type": "string", + "example": "FULL_MATCH" + }, + "identification_number": { + "type": "string", + "description": "Tax, personal, or document number that was validated (first available).", + "example": "12345678Z" + } + } + }, + "poa": { + "type": "object", + "description": "Present when proof of address ran.", + "properties": { + "document_type": { + "type": "string", + "example": "UTILITY_BILL" + }, + "issuer": { + "type": "string", + "example": "Energy Co" + }, + "address": { + "type": "string", + "example": "Calle Mayor 1, 28013 Madrid" + }, + "name_on_document": { + "type": "string", + "example": "Jane Marie Doe" + } + } + }, + "liveness": { + "type": "object", + "description": "Present when a liveness check ran.", + "properties": { + "method": { + "type": "string", + "example": "PASSIVE" + }, + "score": { + "type": "number", + "description": "Liveness confidence score, rounded to 2 decimals.", + "example": 0.98 + } + } + }, + "face_match": { + "type": "object", + "description": "Present when face match ran.", + "properties": { + "score": { + "type": "number", + "description": "Face-match similarity score, rounded to 2 decimals.", + "example": 0.95 + }, + "source_image": { + "type": "string", + "description": "Expiring (presigned) URL of the source face image.", + "example": "https:///faces/source.jpg?X-Amz-Expires=3600&..." + }, + "target_image": { + "type": "string", + "description": "Expiring (presigned) URL of the target face image.", + "example": "https:///faces/target.jpg?X-Amz-Expires=3600&..." + } + } + }, + "phone_verification": { + "type": "object", + "description": "Present when phone verification ran.", + "properties": { + "country_code": { + "type": "string", + "description": "ISO 3166-1 alpha-2 country of the phone number.", + "example": "US" + }, + "is_temporary": { + "type": "boolean", + "example": false + }, + "is_ported": { + "type": "boolean", + "example": false + }, + "is_blocked": { + "type": "boolean", + "example": false + } + } + }, + "email_verification": { + "type": "object", + "description": "Present when email verification ran.", + "properties": { + "is_breached": { + "type": "boolean", + "example": false + }, + "is_disposable": { + "type": "boolean", + "example": false + }, + "is_undeliverable": { + "type": "boolean", + "example": false + }, + "is_blocklisted": { + "type": "boolean", + "example": false + } + } + } + } + }, + "assignee": { + "type": "object", + "nullable": true, + "description": "The Console reviewer the session is assigned to, or `null` when nobody holds it.", + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "assigned_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "waiting_since": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the session started waiting for review. `null` unless the session is `In Review`." + }, + "sla_due_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the review is due, from the review deadline set for your application in the Console. `null` unless the session is `In Review` and a deadline is set." + }, + "top_warnings": { + "type": "array", + "description": "Up to a few of the session's most important warnings, for a quick triage view. The full list is in the decision.", + "items": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "description": "Warning code, for example `FACE_MATCH_LOW_SIMILARITY`." + }, + "feature": { + "type": "string", + "description": "The feature that raised it." + }, + "log_type": { + "type": "string", + "nullable": true, + "description": "`error` or `warning`; `null` for a legacy entry." + }, + "short_description": { + "type": "string" + } + } + } + }, + "warnings_count": { + "type": "integer", + "description": "Number of warnings on the session." + } + } + }, + "BusinessSessionListItem": { + "title": "KYB session row (session_kind: business)", + "type": "object", + "description": "Summary row for a Business Verification (KYB) session. Returned only when `session_kind` is `business` or `all`.", + "required": [ + "session_id", + "session_kind", + "session_number", + "status", + "vendor_data", + "created_at", + "workflow_id", + "workflow_type", + "workflow_label", + "company_name", + "registration_number", + "country" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "Unique identifier of the business verification session.", + "example": "8a1f63c2-1b9e-4f3a-9c41-2f6a1f0b7d52" + }, + "session_kind": { + "type": "string", + "enum": [ + "business" + ], + "description": "Row discriminator. Always `\"business\"` for KYB rows.", + "example": "business" + }, + "session_number": { + "type": "integer", + "description": "Human-friendly sequential number of the business session within your application. Numbered independently from KYC sessions.", + "example": 42 + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Overall session status. See the [Verification Statuses](/integration/verification-statuses) page.", + "example": "Approved" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own identifier for the business, echoed back exactly as sent at session creation.", + "example": "your-company-id-77" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "UTC timestamp when the session was created.", + "example": "2026-06-08T09:14:02.118450Z" + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Identifier of the workflow this session was created with.", + "example": "9f9b1234-aaaa-bbbb-cccc-1234567890ab" + }, + "workflow_type": { + "type": "string", + "nullable": true, + "description": "Workflow type. `kyb` for business workflows.", + "example": "kyb" + }, + "workflow_label": { + "type": "string", + "nullable": true, + "description": "Display name of the workflow.", + "example": "Standard KYB" + }, + "company_name": { + "type": "string", + "nullable": true, + "description": "Name of the company being verified, from the registry company linked to the session. `null` until a company is selected.", + "example": "Acme Holdings S.L." + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Company registration number from the registry company linked to the session.", + "example": "B12345678" + }, + "country": { + "type": "string", + "nullable": true, + "description": "Country code of the registry company linked to the session.", + "example": "ES" + } + } + }, + "TransactionVerificationSessionAction": { + "title": "Verification session", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "verification_session" + ] + }, + "url": { + "type": "string", + "format": "uri", + "description": "Hosted verification URL to open for the end user." + }, + "session_id": { + "type": "string", + "format": "uuid", + "description": "Verification session id." + }, + "session_token": { + "type": "string", + "description": "Session token, used by the mobile SDKs to launch the flow natively." + }, + "status": { + "type": "string", + "description": "Verification session status." + } + }, + "required": [ + "type", + "url" + ] + }, + "TransactionWalletOwnershipAction": { + "title": "Wallet ownership", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "wallet_ownership" + ] + }, + "url": { + "type": "string", + "format": "uri", + "description": "Hosted wallet-ownership widget URL. Open it in a real browser surface - on mobile use SFSafariViewController or a Chrome Custom Tab, never an embedded webview." + }, + "widget_session_id": { + "type": "string", + "format": "uuid", + "description": "Wallet-ownership widget session id." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the widget session expires." + } + }, + "required": [ + "type", + "url" + ] + }, + "TransactionRuleActionAddScore": { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "add_score" + ] + }, + "value": { + "type": "integer", + "description": "Points added to the transaction's risk score. May be negative." + } + }, + "description": "Add points to the transaction's cumulative risk score; the total decides the status via the application's review/decline thresholds." + }, + "TransactionRuleActionChangeStatus": { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "change_status" + ] + }, + "value": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Status to set immediately, regardless of score. Alias key: status." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "REQUIRED when value is AWAITING_USER: the verification workflow the subject is asked to complete. Alias key: remediation_workflow_id. Didit creates a hosted remediation session for the subject when the rule fires." + } + }, + "description": "Directly set the transaction status. DECLINED always wins across matched rules." + }, + "TransactionRuleActionAddTags": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "add_tags" + ] + }, + "tag_uuid": { + "type": "string", + "format": "uuid", + "description": "Existing application tag to attach." + }, + "tag_name": { + "type": "string", + "description": "Tag name to attach; created on first use (stored uppercased). Provide tag_uuid or tag_name." + }, + "tag_color": { + "type": "string", + "description": "Hex color used when tag_name creates a new tag, e.g. #007AFE." + } + }, + "description": "Attach an application tag to the matched transaction." + }, + "TransactionRuleActionAddNote": { + "type": "object", + "required": [ + "type", + "note" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "add_note" + ] + }, + "note": { + "type": "string", + "description": "Note text attached to the transaction (author: Rule Engine). Alias key: value." + } + }, + "description": "Attach an automated analyst note to the matched transaction." + }, + "TransactionRuleActionAddToList": { + "type": "object", + "required": [ + "type", + "list_id" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "add_to_list" + ] + }, + "list_id": { + "type": "string", + "format": "uuid", + "description": "List to add the subject's vendor_data to." + } + }, + "description": "Add the subject's vendor_data to a blocklist, allowlist or custom list." + }, + "TransactionRuleActionOpenCase": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "open_case" + ] + }, + "blueprint": { + "type": "string", + "format": "uuid", + "description": "Case blueprint UUID; defaults to the application's default blueprint." + }, + "grouping": { + "type": "string", + "enum": [ + "by_applicant", + "by_rule_and_applicant" + ], + "description": "Whether repeat matches join one case per applicant or one per rule+applicant." + }, + "attach_matched_transaction": { + "type": "boolean", + "description": "Attach the matched transaction to the case." + } + }, + "description": "Open (or add to) an investigation case. Only the first open_case action on a rule is applied." + }, + "KYBRegistrySearchResults": { + "type": "object", + "properties": { + "companies": { + "type": "array", + "items": { + "type": "object", + "properties": { + "kyb_response_id": { + "type": "string", + "nullable": true, + "description": "Handle for this candidate — pass it to `POST /v3/kyb/select/` to retrieve the full profile. Ephemeral and scoped to this search: do not store it long-term; re-search to get a fresh one.", + "example": "69aeeb95febb0f1704042259" + }, + "name": { + "type": "string", + "nullable": true, + "description": "Registered company name.", + "example": "Tesco PLC" + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Registry registration number.", + "example": "00445790" + }, + "status": { + "type": "string", + "nullable": true, + "description": "Normalized registry status of the company (e.g. `active`, `dissolved`, `inactive`, `struck off`).", + "example": "active" + }, + "type": { + "type": "string", + "nullable": true, + "description": "Legal form as reported by the registry.", + "example": "Public Limited Company" + }, + "registered_address": { + "type": "string", + "nullable": true, + "description": "Registered address of the candidate, when the registry returns one. Use it, with the registration number, to tell same-named companies apart: in the United States a candidate can be listed under the state where the company operates rather than the state you searched.", + "example": "Tesco House, Shire Park, Kestrel Way, Welwyn Garden City, AL7 1GA" + }, + "risk_level": { + "type": "string", + "nullable": true, + "description": "Registry-reported risk level, when available. Usually `null` at search time." + }, + "fetch_status": { + "type": "string", + "nullable": true, + "description": "Always `pending` for search candidates — the full company profile is only fetched (and billed) when you select the candidate.", + "example": "pending" + } + } + }, + "description": "Deduplicated candidate list. Empty while `search_resolved=false` (webhook mode) or when nothing matched." + }, + "pagination": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "description": "Number of candidates returned." + }, + "page": { + "type": "integer" + }, + "per_page": { + "type": "integer", + "example": 25 + } + } + }, + "search_status": { + "type": "string", + "nullable": true, + "enum": [ + "pending", + "resolved" + ], + "description": "Registry search lifecycle status." + }, + "search_resolved": { + "type": "boolean", + "description": "`true` once the candidate list is final." + } + } + }, + "KYBRegistryCandidateRequest": { + "type": "object", + "required": [ + "kyb_response_id" + ], + "properties": { + "kyb_response_id": { + "type": "string", + "maxLength": 1024, + "description": "Candidate handle from `POST /v3/kyb/search/` (`kyb_registry.companies[].kyb_response_id`). Ephemeral: use it shortly after the search that produced it. Handles can be long, so store and send the value exactly as returned, without trimming or assuming a length.", + "example": "69aeeb95febb0f1704042259" + }, + "vendor_data": { + "type": "string", + "description": "Your identifier for this company; echoed back and stored with the business session." + }, + "metadata": { + "type": "object", + "nullable": true, + "description": "Free-form JSON stored with the business session and echoed back." + }, + "save_api_request": { + "type": "boolean", + "default": true, + "description": "Must be `true` (the default). KYB registry selects are always persisted so billing and retrieval can be tracked; `false` returns `400`." + } + } + }, + "WebhookEventEnvelope": { + "type": "object", + "description": "Fields Didit adds to every signed webhook delivery, whatever the event type. Read `webhook_type` to know which payload you received.", + "required": [ + "event_id", + "webhook_type", + "timestamp", + "created_at", + "application_id", + "environment" + ], + "properties": { + "event_id": { + "type": "string", + "format": "uuid", + "description": "Identifier of the event. Every destination receives the same value and a redelivery keeps it, so use it to deduplicate." + }, + "webhook_type": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "timestamp": { + "type": "integer", + "description": "Unix time in seconds at which this delivery was signed. Equal to the `X-Timestamp` header; a redelivery carries a new value." + }, + "created_at": { + "type": "integer", + "description": "Unix time in seconds of the change that produced the event." + }, + "application_id": { + "type": "string", + "format": "uuid", + "description": "The application the event belongs to." + }, + "environment": { + "type": "string", + "enum": [ + "live", + "sandbox" + ], + "description": "`sandbox` for sandbox sessions and sandbox applications, otherwise `live`." + } + } + }, + "SessionWebhookEventData": { + "type": "object", + "description": "Session fields of `status.updated` and `data.updated`. Optional fields are omitted, never sent as `null`, when they do not apply. Business (KYB) sessions add `business_session_id`, `session_kind` and `vendor_business_id`.", + "required": [ + "session_id", + "status", + "sandbox_scenario" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The verification session." + }, + "business_session_id": { + "type": "string", + "format": "uuid", + "description": "Business (KYB) sessions only: the same value as `session_id`." + }, + "session_kind": { + "type": "string", + "enum": [ + "business" + ], + "description": "Business (KYB) sessions only. User (KYC) session events carry no top-level `session_kind`; read `decision.session_kind` instead." + }, + "status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "Session status after the change." + }, + "trigger": { + "type": "string", + "description": "Present when the event was not produced by the verification flow itself, for example `manual_review`, `manual_step_update`, `ongoing_monitoring`, `webhook_reconciliation` (a `status.updated` resent because the first terminal webhook was never created, so you may receive a second event for a decision you already got: deduplicate on `session_id` and `status`), `manual_webhook_recovery`, `kyb_registry_change`, `biometric_retention` (a `data.updated` sent when the session's live biometric data is deleted on schedule) or a `console_*` edit such as `console_registry_edit`, `console_key_people_skip` or `console_key_people_regenerate`." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "The session's workflow. Omitted when the session has none." + }, + "workflow_version": { + "type": "integer", + "description": "Version of the workflow the session ran. Sent together with `workflow_id`." + }, + "vendor_data": { + "type": "string", + "description": "Your `vendor_data` for the session. Omitted when empty." + }, + "vendor_business_id": { + "type": "string", + "description": "Business (KYB) sessions only: the business the session belongs to. Omitted when none." + }, + "metadata": { + "type": "object", + "additionalProperties": true, + "description": "Your session `metadata`. Omitted when empty." + }, + "decision": { + "$ref": "#/components/schemas/SessionDecision" + }, + "resubmit_info": { + "type": "object", + "description": "Only when `status` is `Resubmitted`: the steps the user must redo and why.", + "required": [ + "nodes_to_resubmit", + "reasons" + ], + "properties": { + "nodes_to_resubmit": { + "type": "array", + "items": { + "type": "object", + "required": [ + "node_id", + "feature" + ], + "properties": { + "node_id": { + "type": "string", + "description": "Node identifier as it appears in the session's workflow definition (for example `feature_ocr`, `feature_liveness`).", + "example": "feature_poa" + }, + "feature": { + "type": "string", + "description": "Feature type of the node, for example `OCR`, `LIVENESS` or `PROOF_OF_ADDRESS`.", + "example": "PROOF_OF_ADDRESS" + } + } + }, + "description": "Workflow steps the user must redo. The same `{node_id, feature}` entries as `nodes_to_resubmit` in [Update Status](#patch-/v3/session/-sessionId-/update-status/)." + }, + "reasons": { + "type": "object", + "additionalProperties": true, + "description": "Reason per `node_id` in `nodes_to_resubmit`." + } + } + }, + "sandbox_scenario": { + "type": "string", + "nullable": true, + "description": "The simulated outcome armed on a sandbox session, or `null`. Always `null` for live sessions." + } + } + }, + "SessionStatusUpdatedEvent": { + "description": "A verification session changed status. `decision` is sent only for `Approved`, `Declined`, `In Review` and `Abandoned`; it has the shape of `GET /v3/session/{sessionId}/decision/` for destinations on webhook version 3 (the default), and the older reduced shape for version 1 or 2 destinations. Business sessions always receive the version 3 business decision.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "$ref": "#/components/schemas/SessionWebhookEventData" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "status.updated" + ], + "description": "Always `status.updated`." + } + }, + "required": [ + "webhook_type" + ] + } + ] + }, + "SessionDataUpdatedEvent": { + "description": "Data of a verification session changed without a status change, for example after a manual review edit, a step override or an ongoing-monitoring rescreen. `decision` is sent only for `Approved`, `Declined`, `In Review` and `Abandoned`; it has the shape of `GET /v3/session/{sessionId}/decision/` for destinations on webhook version 3 (the default), and the older reduced shape for version 1 or 2 destinations. Business sessions always receive the version 3 business decision.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "$ref": "#/components/schemas/SessionWebhookEventData" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "data.updated" + ], + "description": "Always `data.updated`." + } + }, + "required": [ + "webhook_type" + ] + } + ] + }, + "UserStatusUpdatedEvent": { + "description": "A user changed status. Not sent when the user is created.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "user.status.updated" + ], + "description": "Always `user.status.updated`." + }, + "vendor_user_id": { + "type": "string", + "format": "uuid", + "description": "The user the event is about." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the user, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "User status after the change." + }, + "previous_status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "Status before the change." + } + }, + "required": [ + "webhook_type", + "vendor_user_id", + "vendor_data", + "status", + "previous_status" + ] + } + ] + }, + "UserDataUpdatedEvent": { + "description": "Fields of a user changed. Not sent when the user is created. One save can produce both a status and a data event.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "user.data.updated" + ], + "description": "Always `user.data.updated`." + }, + "vendor_user_id": { + "type": "string", + "format": "uuid", + "description": "The user the event is about." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the user, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "User status after the change." + }, + "changed_fields": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "vendor_data", + "display_name", + "full_name", + "date_of_birth", + "metadata", + "portrait_image", + "issuing_states", + "approved_emails", + "approved_phones", + "features", + "session_count", + "approved_count", + "declined_count", + "in_review_count", + "first_session_at", + "last_session_at" + ] + }, + "description": "Names of the fields that changed." + }, + "changes": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "previous": { + "nullable": true, + "description": "Value before the change. Dates are ISO 8601 strings." + }, + "current": { + "nullable": true, + "description": "Value after the change. Dates are ISO 8601 strings." + } + } + }, + "description": "Previous and current value of each changed field, keyed by field name. Values can be personal data (names, dates of birth, portrait links, emails, phone numbers); handle them accordingly." + } + }, + "required": [ + "webhook_type", + "vendor_user_id", + "vendor_data", + "status", + "changed_fields", + "changes" + ] + } + ] + }, + "BusinessStatusUpdatedEvent": { + "description": "A business changed status. Not sent when the business is created.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "business.status.updated" + ], + "description": "Always `business.status.updated`." + }, + "vendor_business_id": { + "type": "string", + "format": "uuid", + "description": "The business the event is about." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the business, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "Business status after the change." + }, + "previous_status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "Status before the change." + } + }, + "required": [ + "webhook_type", + "vendor_business_id", + "vendor_data", + "status", + "previous_status" + ] + } + ] + }, + "BusinessDataUpdatedEvent": { + "description": "Fields of a business changed. Not sent when the business is created. One save can produce both a status and a data event.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "business.data.updated" + ], + "description": "Always `business.data.updated`." + }, + "vendor_business_id": { + "type": "string", + "format": "uuid", + "description": "The business the event is about." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the business, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "ACTIVE", + "IN_REVIEW", + "FLAGGED", + "BLOCKED" + ], + "description": "Business status after the change." + }, + "changed_fields": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "vendor_data", + "display_name", + "metadata", + "legal_name", + "registration_number", + "country_code", + "session_count", + "approved_count", + "declined_count", + "in_review_count", + "features", + "first_session_at", + "last_session_at" + ] + }, + "description": "Names of the fields that changed." + }, + "changes": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "previous": { + "nullable": true, + "description": "Value before the change. Dates are ISO 8601 strings." + }, + "current": { + "nullable": true, + "description": "Value after the change. Dates are ISO 8601 strings." + } + } + }, + "description": "Previous and current value of each changed field, keyed by field name." + } + }, + "required": [ + "webhook_type", + "vendor_business_id", + "vendor_data", + "status", + "changed_fields", + "changes" + ] + } + ] + }, + "TransactionCreatedEvent": { + "description": "A transaction was created and screened. Sent for live applications only.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "transaction.created" + ], + "description": "Always `transaction.created`." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Didit's identifier of the transaction." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "The `transaction_id` you supplied at create time, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Monitoring status after the change." + }, + "score": { + "type": "integer", + "description": "Risk score accumulated by rules." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL", + null + ], + "description": "Risk severity, or `null` until a rule or provider sets one." + }, + "amount": { + "type": "string", + "description": "Transaction amount as a decimal string. Unlike the API it is not normalized, so it can carry trailing zeros (up to 18 decimal places)." + }, + "currency": { + "type": "string", + "description": "Currency code or crypto asset symbol of `amount`." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject." + }, + "action_required": { + "type": "object", + "nullable": true, + "oneOf": [ + { + "$ref": "#/components/schemas/TransactionVerificationSessionAction" + }, + { + "$ref": "#/components/schemas/TransactionWalletOwnershipAction" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "verification_session": "#/components/schemas/TransactionVerificationSessionAction", + "wallet_ownership": "#/components/schemas/TransactionWalletOwnershipAction" + } + }, + "description": "Pending end-user action, or `null`. Same object as `action_required` on the transaction; `session_token` is a credential, so never log it." + } + }, + "required": [ + "webhook_type", + "transaction_id", + "txn_id", + "status", + "score", + "severity", + "amount", + "currency", + "direction", + "action_required" + ] + } + ] + }, + "TransactionStatusUpdatedEvent": { + "description": "A transaction changed status, from a rule, a review or an end-user action. Sent for live applications only.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "transaction.status.updated" + ], + "description": "Always `transaction.status.updated`." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "Didit's identifier of the transaction." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "The `transaction_id` you supplied at create time, or `null`." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Monitoring status after the change." + }, + "score": { + "type": "integer", + "description": "Risk score accumulated by rules." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL", + null + ], + "description": "Risk severity, or `null` until a rule or provider sets one." + }, + "amount": { + "type": "string", + "description": "Transaction amount as a decimal string. Unlike the API it is not normalized, so it can carry trailing zeros (up to 18 decimal places)." + }, + "currency": { + "type": "string", + "description": "Currency code or crypto asset symbol of `amount`." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject." + }, + "action_required": { + "type": "object", + "nullable": true, + "oneOf": [ + { + "$ref": "#/components/schemas/TransactionVerificationSessionAction" + }, + { + "$ref": "#/components/schemas/TransactionWalletOwnershipAction" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "verification_session": "#/components/schemas/TransactionVerificationSessionAction", + "wallet_ownership": "#/components/schemas/TransactionWalletOwnershipAction" + } + }, + "description": "Pending end-user action, or `null`. Same object as `action_required` on the transaction; `session_token` is a credential, so never log it." + } + }, + "required": [ + "webhook_type", + "transaction_id", + "txn_id", + "status", + "score", + "severity", + "amount", + "currency", + "direction", + "action_required" + ] + } + ] + }, + "TravelRuleStatusUpdatedEvent": { + "description": "A Travel Rule exchange changed status. Sent on every transition up to and including `FINISHED`, `CANCELLED` or `EXPIRED`, never after. Sent for live applications and, for Travel Rule exchanges, for sandbox applications too (`environment: sandbox`). There is no top-level `status`.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "travel_rule.status.updated" + ], + "description": "Always `travel_rule.status.updated`." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "The transaction the exchange belongs to." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "The `transaction_id` you supplied at create time, or `null`." + }, + "travel_rule_status": { + "type": "string", + "enum": [ + "AWAITING_COUNTERPARTY", + "ON_HOLD", + "COMPLETED", + "FINISHED", + "CANCELLED", + "EXPIRED", + "COUNTERPARTY_MISMATCHED_DATA", + "COUNTERPARTY_VASP_NOT_FOUND", + "COUNTERPARTY_VASP_NOT_REACHABLE", + "UNCONFIRMED_OWNERSHIP", + "COUNTERPARTY_VASP_GENERAL_DECLINE", + "NOT_ENOUGH_COUNTERPARTY_DATA", + "NOT_APPLICABLE" + ], + "description": "Exchange status after the change." + }, + "rail": { + "type": "string", + "enum": [ + "", + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL", + "TEST_VASP" + ], + "description": "Network the exchange runs on, or an empty string when none could be resolved. `TEST_VASP` is a sandbox transfer the Didit Test VASP answered." + }, + "test_vasp": { + "type": "boolean", + "description": "Present and `true` only on a sandbox transfer the Didit Test VASP answered." + }, + "test_vasp_scenario": { + "type": "string", + "description": "Present only with `test_vasp`: the sandbox scenario that decided the outcome, or an empty string." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction of the transfer." + } + }, + "required": [ + "webhook_type", + "transaction_id", + "txn_id", + "travel_rule_status", + "rail", + "direction" + ] + } + ] + }, + "WorkflowComplianceChangedEvent": { + "description": "The compliance status of a published workflow changed after a re-evaluation. One event per workflow. There is no top-level `status`.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "workflow.compliance_changed" + ], + "description": "Always `workflow.compliance_changed`." + }, + "workflow_id": { + "type": "string", + "format": "uuid", + "description": "The workflow that changed." + }, + "workflow_version": { + "type": "integer", + "description": "Version that was evaluated." + }, + "workflow_label": { + "type": "string", + "nullable": true, + "description": "Display name of the workflow." + }, + "transition": { + "type": "object", + "required": [ + "from", + "to" + ], + "properties": { + "from": { + "type": "string", + "enum": [ + "compliant", + "gaps_detected", + "pending_change", + "unknown_coverage", + "not_evaluated" + ], + "description": "Status before the re-evaluation." + }, + "to": { + "type": "string", + "enum": [ + "compliant", + "gaps_detected", + "pending_change", + "unknown_coverage", + "not_evaluated" + ], + "description": "Status after the re-evaluation." + } + } + }, + "severity": { + "type": "string", + "enum": [ + "actionable", + "indicative", + "informative" + ], + "description": "`actionable` when gaps were detected, `indicative` for a pending change, `informative` when gaps were resolved." + }, + "kb_version": { + "type": "string", + "description": "Version of the regulatory knowledge base used." + } + }, + "required": [ + "webhook_type", + "workflow_id", + "workflow_version", + "workflow_label", + "transition", + "severity", + "kb_version" + ] + } + ] + }, + "WebhookEventPayload": { + "description": "Body of a signed webhook delivery, discriminated by `webhook_type`. `activity.created` can be subscribed to but is not sent today. Treat any other `webhook_type` as a new event type and keep the raw body.", + "oneOf": [ + { + "$ref": "#/components/schemas/SessionStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/SessionDataUpdatedEvent" + }, + { + "$ref": "#/components/schemas/UserStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/UserDataUpdatedEvent" + }, + { + "$ref": "#/components/schemas/BusinessStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/BusinessDataUpdatedEvent" + }, + { + "$ref": "#/components/schemas/TransactionCreatedEvent" + }, + { + "$ref": "#/components/schemas/TransactionStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/TravelRuleStatusUpdatedEvent" + }, + { + "$ref": "#/components/schemas/WorkflowComplianceChangedEvent" + }, + { + "$ref": "#/components/schemas/TransactionRuleSuggestionsCreatedEvent" + } + ], + "discriminator": { + "propertyName": "webhook_type", + "mapping": { + "status.updated": "#/components/schemas/SessionStatusUpdatedEvent", + "data.updated": "#/components/schemas/SessionDataUpdatedEvent", + "user.status.updated": "#/components/schemas/UserStatusUpdatedEvent", + "user.data.updated": "#/components/schemas/UserDataUpdatedEvent", + "business.status.updated": "#/components/schemas/BusinessStatusUpdatedEvent", + "business.data.updated": "#/components/schemas/BusinessDataUpdatedEvent", + "transaction.created": "#/components/schemas/TransactionCreatedEvent", + "transaction.status.updated": "#/components/schemas/TransactionStatusUpdatedEvent", + "travel_rule.status.updated": "#/components/schemas/TravelRuleStatusUpdatedEvent", + "workflow.compliance_changed": "#/components/schemas/WorkflowComplianceChangedEvent", + "transaction_rule_suggestions.created": "#/components/schemas/TransactionRuleSuggestionsCreatedEvent" + } + } + }, + "KYBRegistrySearchResolvedCallback": { + "type": "object", + "description": "Body of the callback sent to the `webhook_url` of `POST /v3/kyb/search/` once the registry search resolves. It is not signed: the request carries `X-Didit-Unsigned-Callback: true` and none of the signature headers, so confirm it by matching `request_id` against a search you started. Uses `event_type`, not `webhook_type`.", + "required": [ + "event_id", + "event_type", + "request_id", + "vendor_data", + "metadata", + "search_status", + "search_resolved", + "kyb_registry", + "timestamp", + "created_at" + ], + "properties": { + "event_id": { + "type": "string", + "format": "uuid", + "description": "Identifier of the callback." + }, + "event_type": { + "type": "string", + "enum": [ + "kyb.registry_search.resolved" + ], + "description": "Always `kyb.registry_search.resolved`." + }, + "request_id": { + "type": "string", + "format": "uuid", + "description": "The `request_id` returned by `POST /v3/kyb/search/`." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Echo of the `vendor_data` you sent." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Echo of the `metadata` you sent." + }, + "search_status": { + "type": "string", + "description": "Always `resolved` today." + }, + "search_resolved": { + "type": "boolean", + "description": "`true` when `search_status` is `resolved`." + }, + "kyb_registry": { + "$ref": "#/components/schemas/KYBRegistrySearchResults" + }, + "timestamp": { + "type": "integer", + "description": "Unix time in seconds at which the callback was queued." + }, + "created_at": { + "type": "integer", + "description": "Unix time in seconds at which the callback was queued." + } + } + }, + "AMLScreening": { + "type": "object", + "properties": { + "node_id": { + "type": "string", + "description": "Unique identifier for this AML step in the workflow graph." + }, + "status": { + "type": "string", + "description": "Feature-level status of this AML screening. Independent of the top-level session `status` — a session can reach its overall decision while an individual feature stays `In Review`.", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ] + }, + "total_hits": { + "type": "integer", + "description": "Total number of matches found" + }, + "entity_type": { + "type": "string", + "description": "Type of entity screened: person or company" + }, + "hits": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Didit's stable identifier for the hit: `hit_` followed by 24 hexadecimal characters. The same matched entity keeps the same `id` when the subject is screened again. Pass it as `hit_id` to the AML hit review endpoints." + }, + "url": { + "type": "string", + "format": "uri", + "description": "Source URL for the match" + }, + "match": { + "type": "boolean", + "description": "Whether this is a confirmed match" + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 1, + "description": "Deprecated weighted match score in the 0-1 range (e.g. `0.73`). Use `match_score` (0-100) instead.", + "deprecated": true + }, + "target": { + "type": "boolean", + "nullable": true, + "description": "Whether the matched entity is the direct target of the listing (as opposed to a related party). `null` when the source does not specify it." + }, + "caption": { + "type": "string", + "description": "Caption or title of the match" + }, + "datasets": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "PEP", + "PEP Level 1", + "PEP Level 2", + "PEP Level 3", + "PEP Level 4", + "Sanctions", + "Adverse Media", + "Warnings and Regulatory Enforcement", + "Fitness and Probity", + "SIP", + "SIE", + "Insolvency", + "Other Lists" + ] + }, + "description": "List of dataset categories where the match was found. Indicates whether the entity appeared in PEP lists (with level), sanctions databases, adverse media, warnings/regulatory enforcement, or other compliance datasets. `Other Lists` when the entity's source states none of these categories." + }, + "features": { + "type": "object", + "nullable": true, + "description": "Additional features of the match" + }, + "rca_name": { + "type": "string", + "description": "RCA name if applicable" + }, + "last_seen": { + "type": "string", + "format": "date-time", + "description": "Date when match was last seen" + }, + "risk_view": { + "type": "object", + "properties": { + "crimes": { + "type": "object", + "properties": { + "score": { + "type": "integer" + }, + "weightage": { + "type": "integer" + }, + "risk_level": { + "type": "string" + }, + "risk_scores": { + "type": "object" + } + } + }, + "countries": { + "type": "object", + "properties": { + "score": { + "type": "integer" + }, + "weightage": { + "type": "integer" + }, + "risk_level": { + "type": "string" + }, + "risk_scores": { + "type": "object" + } + } + }, + "categories": { + "type": "object", + "properties": { + "score": { + "type": "integer" + }, + "weightage": { + "type": "integer" + }, + "risk_level": { + "type": "string" + }, + "risk_scores": { + "type": "object" + } + } + }, + "custom_list": { + "type": "object" + } + } + }, + "first_seen": { + "type": "string", + "format": "date-time", + "description": "Date when match was first seen" + }, + "properties": { + "type": "object", + "properties": { + "name": { + "type": "array", + "items": { + "type": "string" + } + }, + "alias": { + "type": "array", + "items": { + "type": "string" + } + }, + "notes": { + "type": "array", + "items": { + "type": "string" + } + }, + "title": { + "type": "string", + "nullable": true + }, + "gender": { + "type": "array", + "items": { + "type": "string" + } + }, + "height": { + "type": "string", + "nullable": true + }, + "topics": { + "type": "string", + "nullable": true + }, + "weight": { + "type": "string", + "nullable": true + }, + "address": { + "type": "string", + "nullable": true + }, + "country": { + "type": "string", + "nullable": true + }, + "website": { + "type": "string", + "nullable": true + }, + "eyeColor": { + "type": "string", + "nullable": true + }, + "keywords": { + "type": "string", + "nullable": true + }, + "lastName": { + "type": "array", + "items": { + "type": "string" + } + }, + "position": { + "type": "string", + "nullable": true + }, + "religion": { + "type": "string", + "nullable": true + }, + "birthDate": { + "type": "array", + "items": { + "type": "string" + } + }, + "education": { + "type": "array", + "items": { + "type": "string" + } + }, + "ethnicity": { + "type": "string", + "nullable": true + }, + "firstName": { + "type": "array", + "items": { + "type": "string" + } + }, + "hairColor": { + "type": "string", + "nullable": true + }, + "weakAlias": { + "type": "string", + "nullable": true + }, + "birthPlace": { + "type": "array", + "items": { + "type": "string" + } + }, + "modifiedAt": { + "type": "string", + "nullable": true + }, + "wikidataId": { + "type": "string", + "nullable": true + }, + "citizenship": { + "type": "string", + "nullable": true + }, + "nationality": { + "type": "string", + "nullable": true + } + } + }, + "match_score": { + "type": "number", + "description": "Weighted identity confidence score (0-100) calculated from name similarity, date of birth, and country matching. Used to determine if hit is a False Positive or Possible Match.", + "format": "float", + "nullable": true + }, + "risk_score": { + "type": "number", + "description": "Entity risk score (0-100). Used to determine final AML status (Approved/In Review/Declined) based on the highest risk_score among non-False-Positive hits." + }, + "review_status": { + "type": "string", + "description": "Review status for this hit. Can be 'Unreviewed', 'Confirmed Match', 'False Positive', or 'Inconclusive'.", + "enum": [ + "Unreviewed", + "Confirmed Match", + "False Positive", + "Inconclusive" + ] + }, + "score_breakdown": { + "type": "object", + "description": "Detailed breakdown of how the match_score was calculated.", + "properties": { + "name_score": { + "type": "integer", + "description": "Raw name similarity percentage (0-100)." + }, + "name_weight": { + "type": "integer", + "description": "Original weight for name from config (0-100)." + }, + "name_weight_normalized": { + "type": "number", + "description": "Normalized weight for name after re-weighting for missing data (0-100)." + }, + "name_contribution": { + "type": "number", + "description": "Points contributed by name to the total score." + }, + "dob_score": { + "type": "integer", + "description": "DOB match score (-100 to 100)." + }, + "dob_weight": { + "type": "integer", + "description": "Original weight for DOB from config (0-100)." + }, + "dob_weight_normalized": { + "type": "number", + "description": "Normalized weight for DOB after re-weighting for missing data (0-100)." + }, + "dob_contribution": { + "type": "number", + "description": "Points contributed by DOB to the total score." + }, + "country_score": { + "type": "integer", + "description": "Country match score (-100 to 100)." + }, + "country_weight": { + "type": "integer", + "description": "Original weight for country from config (0-100)." + }, + "country_weight_normalized": { + "type": "number", + "description": "Normalized weight for country after re-weighting for missing data (0-100)." + }, + "country_contribution": { + "type": "number", + "description": "Points contributed by country to the total score." + }, + "document_number_match_type": { + "type": "string", + "description": "Result of document number 'Golden Key' evaluation.", + "enum": [ + "MATCH", + "NEUTRAL", + "HARD_MISMATCH" + ] + }, + "document_number_effect": { + "type": "string", + "description": "Human-readable description of the document number effect on the score." + }, + "total_score": { + "type": "integer", + "description": "Final weighted match score (0-100)." + } + } + }, + "pep_matches": { + "type": "array", + "items": { + "type": "object", + "properties": { + "aliases": { + "type": "array", + "items": { + "type": "string" + } + }, + "education": { + "type": "array", + "items": { + "type": "string" + } + }, + "list_name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "source_url": { + "type": "string", + "format": "uri" + }, + "description": { + "type": "string" + }, + "matched_name": { + "type": "string" + }, + "pep_position": { + "type": "string" + }, + "date_of_birth": { + "type": "string" + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + } + }, + "place_of_birth": { + "type": "string" + } + } + } + }, + "linked_entities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "array", + "items": { + "type": "string" + } + }, + "active": { + "type": "array", + "items": { + "type": "string" + } + }, + "status": { + "type": "array", + "items": { + "type": "string" + } + }, + "details": { + "type": "array", + "items": { + "type": "string" + } + }, + "relation": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "warning_matches": { + "type": "array", + "description": "List of warning and regulatory enforcement matches. Includes entries from categories: Warning, SIP, Warnings and Regulatory Enforcement, Fitness and Probity, SIE, and Insolvency.", + "items": { + "type": "object", + "properties": { + "list_name": { + "type": "string", + "description": "Name of the warning list publisher" + }, + "matched_name": { + "type": "string", + "description": "Name matched in the warning list" + }, + "description": { + "type": "string", + "description": "Description of the warning entry" + }, + "publisher": { + "type": "string", + "description": "Publisher or source of the warning" + }, + "source_url": { + "type": "string", + "format": "uri", + "description": "URL to the source of the warning" + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Additional source URLs" + }, + "countries": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Countries associated with the warning" + }, + "created_at": { + "type": "string", + "nullable": true, + "description": "Date the warning entry was created" + }, + "updated_at": { + "type": "string", + "nullable": true, + "description": "Date the warning entry was last updated" + }, + "additional_data": { + "type": "object", + "description": "Additional data from the warning source. May contain a `data` object with `case_details` array.", + "properties": { + "data": { + "type": "object", + "properties": { + "case_details": { + "type": "array", + "description": "Details of warning cases associated with this match", + "items": { + "type": "object", + "properties": { + "case_type": { + "type": "string", + "description": "Type of the warning case", + "enum": [ + "Accusation", + "Arrest", + "Bribery", + "Criminal", + "Fraud", + "Illegal Fundraising", + "Non-conviction Terror", + "Proclaimed Offender", + "Regulatory Enforcement", + "Terrorism", + "Terrorism-related", + "Terrorism-related listing" + ] + } + } + } + } + } + } + } + } + } + } + }, + "sanction_matches": { + "type": "array", + "description": "List of sanction list matches found for this entity.", + "items": { + "type": "object", + "properties": { + "list_name": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Name(s) of the sanction list(s)" + }, + "matched_name": { + "type": "string", + "description": "Name matched in the sanction list" + }, + "description": { + "type": "string", + "description": "Description of the sanction entry" + }, + "reason": { + "type": "string", + "nullable": true, + "description": "Free-text reason why the entity was sanctioned. Joined from the sanction list's reason entries. Examples: 'Executive Order 13224 (Terrorism)', 'Support for actions undermining Ukraine's sovereignty', 'Listed due to national security concerns.'" + }, + "source_url": { + "type": "string", + "format": "uri", + "description": "URL to the source of the sanction" + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Additional source URLs" + }, + "details": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Additional details about the sanction" + }, + "legal_basis": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Legal basis for the sanction" + }, + "listed_on": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Date(s) when the entity was listed" + }, + "sanction_list": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "description": "Sanction list identifier(s)" + }, + "sanction_program": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Sanction program(s) the entity is listed under" + }, + "sanctioning_authority": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Authority/authorities that imposed the sanction" + }, + "updated_on": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Date(s) when the sanction entry was last updated" + }, + "additional_data": { + "type": "object", + "description": "Additional data from the sanction source not captured in the main fields" + } + } + } + }, + "adverse_media_details": { + "type": "object", + "properties": { + "sentiment": { + "type": "string" + }, + "entity_type": { + "type": "string" + }, + "sentiment_score": { + "type": "integer" + }, + "adverse_keywords": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + } + }, + "adverse_media_matches": { + "type": "array", + "items": { + "type": "object", + "properties": { + "country": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "headline": { + "type": "string" + }, + "sentiment": { + "type": "string" + }, + "thumbnail": { + "type": "string", + "format": "uri" + }, + "source_url": { + "type": "string", + "format": "uri" + }, + "author_name": { + "type": "string", + "nullable": true + }, + "other_sources": { + "type": "array", + "items": { + "type": "string" + } + }, + "adverse_keywords": { + "type": "array", + "items": { + "type": "string" + } + }, + "sentiment_score": { + "type": "integer" + }, + "publication_date": { + "type": "string", + "format": "date-time" + } + } + } + }, + "additional_information": { + "type": "object" + } + } + } + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "Overall AML screening score — the highest risk score across all hits (float, 0-100). `null` until computed." + }, + "screened_data": { + "type": "object", + "properties": { + "full_name": { + "type": "string" + }, + "nationality": { + "type": "string" + }, + "date_of_birth": { + "type": "string", + "format": "date" + }, + "document_number": { + "type": "string", + "nullable": true + } + } + }, + "is_ongoing_monitoring_enabled": { + "type": "boolean", + "description": "Whether AML ongoing monitoring is enabled for this screening. When `true`, the entity is re-screened periodically and any new hits update the report." + }, + "next_ongoing_monitoring_bill_date": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date (YYYY-MM-DD) when this ongoing-monitoring subscription will next be billed. Computed as the date of the last ongoing-monitoring billing plus 365 days when `is_ongoing_monitoring_enabled = true`; otherwise `null`. This is a billing date, not a screening date - use `last_ongoing_screening_at` for that." + }, + "last_ongoing_screening_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When ongoing monitoring last completed a re-screening of this record, whether or not the result changed. `null` when the record has never been re-screened; it is never back-filled from the enablement or billing date." + }, + "last_ongoing_screening_datasets_updated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "The most recent AML dataset update date the provider reported at the time of that screening. `null` when the provider reported no dataset dates; never estimated." + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "NFC", + "LIVENESS", + "FACEMATCH", + "PROOF_OF_ADDRESS", + "PHONE", + "EMAIL", + "AML", + "LOCATION", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "GEOLOCATION", + "SIGNATURE" + ], + "description": "The feature that generated this warning. The value identifies the feature block the warning belongs to. Within an AML screening this is typically `AML`, but the enum above is authoritative across all feature warnings on the decision payload." + }, + "risk": { + "type": "string" + }, + "additional_data": { + "type": "object", + "nullable": true + }, + "log_type": { + "type": "string" + }, + "short_description": { + "type": "string" + }, + "long_description": { + "type": "string" + }, + "node_id": { + "type": "string", + "description": "The workflow graph node ID that generated this warning" + } + } + } + } + } + }, + "MonitoringEvent": { + "type": "object", + "description": "One stored monitoring event as returned by the list and subject-timeline endpoints. Restricted evidence (payload, device context, rule evidence) is only returned by `GET /v3/events/{event_uuid}/`.", + "required": [ + "event_uuid", + "event_id", + "event_category", + "action_type", + "occurred_at", + "received_at", + "status", + "score", + "subject_vendor_data", + "subject_entity_type", + "subject_uuid", + "counterparty_vendor_data", + "session_id", + "device_fingerprint", + "ip_address", + "ip_country", + "amount", + "currency", + "game_id", + "round_id", + "bonus_id", + "affiliate_code", + "payment_reference_id", + "retention_class", + "rules_matched_count", + "correlation_pending", + "is_droppable" + ], + "properties": { + "event_uuid": { + "type": "string", + "format": "uuid", + "description": "Didit identifier of the event. Use it as `{event_uuid}` in the retrieve and delete calls." + }, + "event_id": { + "type": "string", + "maxLength": 128, + "description": "Your own identifier, the idempotency key you sent." + }, + "event_category": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ], + "description": "Event category." + }, + "action_type": { + "type": "string", + "maxLength": 128, + "description": "What happened within the category, as you sent it." + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "description": "When the event happened on your side." + }, + "received_at": { + "type": "string", + "format": "date-time", + "description": "When Didit accepted the event." + }, + "status": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ], + "description": "`ACCEPTED`: stored, no matched rule changed its standing (the normal answer for most events, and always the answer in a sandbox application, where no rules run). `APPROVED`: a rule matched and the score stayed under your review threshold. `IN_REVIEW`: a rule pushed the score over the review threshold or set the status. `DECLINED`: a rule declined it or the score crossed your decline threshold." + }, + "score": { + "type": "integer", + "description": "Accumulated rule score. Starts at 0; rule `add_score` actions move it (negative deltas are allowed)." + }, + "subject_vendor_data": { + "type": "string", + "maxLength": 255, + "description": "`subject.vendor_data` of the event: your id for the user or company." + }, + "subject_entity_type": { + "type": "string", + "description": "`individual` or `company`: which kind of subject `subject_vendor_data` names.", + "example": "individual" + }, + "subject_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "UUID of the user (individual) or company the event resolved to, or null when that record no longer exists." + }, + "counterparty_vendor_data": { + "type": "string", + "nullable": true, + "description": "The other player of a P2P event (promoted from `payload.counterparty_vendor_data`).", + "maxLength": 255 + }, + "session_id": { + "type": "string", + "nullable": true, + "description": "`session.session_id` sent with the event.", + "maxLength": 255 + }, + "device_fingerprint": { + "type": "string", + "nullable": true, + "description": "`device_context.fingerprint` sent with the event.", + "maxLength": 255 + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "IP address of the event. On the server lane this is `device_context.network_context.ip_address` as you sent it; on the device lane it is the IP of the request.", + "maxLength": 64 + }, + "ip_country": { + "type": "string", + "nullable": true, + "description": "Country code of `ip_address` from IP enrichment, or null when the address was not enriched (missing, private, or low-value telemetry whose address was not already known).", + "example": "ES" + }, + "amount": { + "type": "string", + "nullable": true, + "description": "Amount as a decimal string with 18 decimal places, from `transaction_context.amount`, `payload.amount` or `payload.stake_amount`, in that order.", + "example": "250.000000000000000000" + }, + "currency": { + "type": "string", + "nullable": true, + "description": "Currency from `transaction_context.currency` or `payload.currency`.", + "maxLength": 10 + }, + "game_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.game_id`.", + "maxLength": 128 + }, + "round_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.round_id`.", + "maxLength": 128 + }, + "bonus_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.bonus_id`.", + "maxLength": 128 + }, + "affiliate_code": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.affiliate_code`.", + "maxLength": 128 + }, + "payment_reference_id": { + "type": "string", + "nullable": true, + "description": "Payment reference from `transaction_context.payment_reference_id` or `payload.payment_reference_id`.", + "maxLength": 255 + }, + "retention_class": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ], + "description": "Retention class. Payment and dispute events are `monetary_or_compliance`; security and responsible-gambling events are `risk_relevant`; `session.page_view`, `session.sdk_heartbeat`, `session.sdk_loaded`, `session.device_seen`, `session.bot_signal_observed`, `bonus.promotion_viewed` and `affiliate.referral_clicked` are `low_value`; everything else is `standard`. An event an active rule matched is promoted to `risk_relevant` (never demoted)." + }, + "rules_matched_count": { + "type": "integer", + "minimum": 0, + "description": "Number of rules that matched the event, including rules in `TEST` mode (which record evidence but apply no actions)." + }, + "correlation_pending": { + "type": "boolean", + "description": "`true` while an asynchronous rule (cross-subject correlation, or a sequence longer than 24 hours) still has to run for this event. It resolves within minutes and `status`, `score` and `rules_matched_count` update in place." + }, + "is_droppable": { + "type": "boolean", + "description": "Whether an SDK under backpressure may drop events of this kind. `false` for every `security`, `payment`, `dispute` and `responsible_gambling` event and for `gameplay.bet_placed`, `gameplay.bet_settled` and `gameplay.p2p_settlement`." + } + } + }, + "MonitoringEventDetail": { + "description": "One monitoring event with its full envelope, enrichment and rule evidence. Every field of `MonitoringEvent` plus the fields below.", + "allOf": [ + { + "$ref": "#/components/schemas/MonitoringEvent" + }, + { + "type": "object", + "required": [ + "payload", + "custom_values", + "device_context", + "field_sources", + "session_sequence", + "table_id", + "hand_id", + "match_id", + "market_id", + "side", + "role", + "net_result_amount", + "payment_fingerprint", + "instrument_reference_hash", + "withdrawal_address_hash", + "dispute_id", + "chargeback_id", + "changed_field", + "identity_match_ref", + "schema_version", + "expires_at", + "rules_evaluated_count", + "rule_runs", + "ip_enrichment", + "subject_profile", + "tags" + ], + "properties": { + "payload": { + "type": "object", + "additionalProperties": true, + "description": "The category fields you sent in `payload`." + }, + "custom_values": { + "type": "object", + "additionalProperties": true, + "description": "The scalar `custom_values` you sent. Didit adds `vpn_or_proxy_detected: true` or `is_data_center: true` when IP enrichment flags the address and you did not set them." + }, + "device_context": { + "type": "object", + "additionalProperties": true, + "description": "The device context as stored: your `device_context` (including `fingerprint`, `persistent_device_id` and `bot_score` when sent) with `network_context` completed by IP enrichment (`ip_country`, `ip_country_code`, `ip_city`, `asn_number`, `asn_organization`, `is_vpn_or_tor`, `is_data_center`, `proxy_type`, `connection_type`) for keys you left empty." + }, + "field_sources": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "customer", + "sdk", + "server" + ] + }, + "description": "Field path to provenance: `customer` (sent by your backend), `sdk` (sent through the device lane) or `server` (derived by Didit, such as `received_at` and enriched network fields). Check it before acting on a match." + }, + "session_sequence": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "`session.sequence` sent with the event." + }, + "table_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.table_id`.", + "maxLength": 128 + }, + "hand_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.hand_id`.", + "maxLength": 128 + }, + "match_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.match_id`.", + "maxLength": 128 + }, + "market_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.market_id`.", + "maxLength": 128 + }, + "side": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.side`.", + "maxLength": 64 + }, + "role": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.role`.", + "maxLength": 64 + }, + "net_result_amount": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.net_result_amount`, as a decimal string with 18 decimal places.", + "example": "-120.000000000000000000" + }, + "payment_fingerprint": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.payment_fingerprint`.", + "maxLength": 255 + }, + "instrument_reference_hash": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.instrument_reference_hash`.", + "maxLength": 255 + }, + "withdrawal_address_hash": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.withdrawal_address_hash` (or `payload.destination_reference_hash`).", + "maxLength": 255 + }, + "dispute_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.dispute_id`.", + "maxLength": 255 + }, + "chargeback_id": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.chargeback_id`.", + "maxLength": 255 + }, + "changed_field": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.changed_field` (security change events).", + "maxLength": 128 + }, + "identity_match_ref": { + "type": "string", + "nullable": true, + "description": "Promoted from `payload.identity_match_ref` (responsible-gambling registry matches).", + "maxLength": 255 + }, + "schema_version": { + "type": "integer", + "description": "Version of the event envelope schema. Currently `1`.", + "example": 1 + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the event expires under its retention class and is deleted: `occurred_at` plus the class period (14, 30 or 90 days by default, adjustable per application). Null for `monetary_or_compliance` events, which follow transaction retention." + }, + "rules_evaluated_count": { + "type": "integer", + "minimum": 0, + "description": "Number of event rules evaluated for this event." + }, + "rule_runs": { + "type": "array", + "description": "Every rule that matched the event, newest first.", + "items": { + "$ref": "#/components/schemas/MonitoringEventRuleRun" + } + }, + "ip_enrichment": { + "type": "object", + "nullable": true, + "description": "Enrichment of the event's IP address, or null when the address was not enriched. See [IP enrichment](https://docs.didit.me/transaction-monitoring/ip-enrichment).", + "properties": { + "ip_country_code": { + "type": "string", + "nullable": true, + "description": "Country code of the address." + }, + "ip_city": { + "type": "string", + "nullable": true, + "description": "City of the address." + }, + "asn_number": { + "type": "integer", + "nullable": true, + "description": "Autonomous system number." + }, + "asn_organization": { + "type": "string", + "nullable": true, + "description": "Organization that operates the autonomous system." + }, + "is_vpn_or_tor": { + "type": "boolean", + "description": "Whether the address is a known VPN or Tor exit." + }, + "is_data_center": { + "type": "boolean", + "description": "Whether the address belongs to a hosting or data-centre network." + }, + "proxy_type": { + "type": "string", + "nullable": true, + "description": "Proxy classification of the address as an opaque string, or null." + }, + "last_enriched_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the address was last enriched." + } + }, + "required": [ + "ip_country_code", + "ip_city", + "asn_number", + "asn_organization", + "is_vpn_or_tor", + "is_data_center", + "proxy_type", + "last_enriched_at" + ] + }, + "subject_profile": { + "type": "object", + "nullable": true, + "description": "Cached email and phone risk signals of the subject, or null when no profile exists for the subject.", + "properties": { + "email_risk_score": { + "type": "integer", + "nullable": true, + "description": "Email risk score." + }, + "email_deliverability": { + "type": "string", + "nullable": true, + "description": "Email deliverability verdict." + }, + "email_is_disposable": { + "type": "boolean", + "nullable": true, + "description": "Whether the email address is disposable." + }, + "email_is_breached": { + "type": "boolean", + "nullable": true, + "description": "Whether the email address appears in known data breaches." + }, + "email_social_count": { + "type": "integer", + "nullable": true, + "description": "Number of online accounts linked to the email address." + }, + "email_social_providers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the online services where an account linked to the email address was found." + }, + "phone_risk_score": { + "type": "integer", + "nullable": true, + "description": "Phone risk score." + }, + "phone_line_type": { + "type": "string", + "nullable": true, + "description": "Phone line type." + }, + "phone_carrier": { + "type": "string", + "nullable": true, + "description": "Phone carrier name." + }, + "phone_social_count": { + "type": "integer", + "nullable": true, + "description": "Number of online accounts linked to the phone number." + }, + "phone_social_providers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the online services where an account linked to the phone number was found." + }, + "source_provider": { + "type": "string", + "nullable": true, + "description": "Opaque identifier of the source of the profile." + }, + "last_enriched_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the profile was last refreshed." + } + }, + "required": [ + "email_risk_score", + "email_deliverability", + "email_is_disposable", + "email_is_breached", + "email_social_count", + "email_social_providers", + "phone_risk_score", + "phone_line_type", + "phone_carrier", + "phone_social_count", + "phone_social_providers", + "source_provider", + "last_enriched_at" + ] + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Names of the tags attached to the event (rule `add_tags` actions)." + } + } + } + ] + }, + "MonitoringEventRuleRun": { + "type": "object", + "properties": { + "rule_uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of the transaction monitoring rule." + }, + "rule_title": { + "type": "string", + "description": "Title of the rule." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Library key when the rule was installed from the rules library, otherwise null." + }, + "matched": { + "type": "boolean", + "description": "Always `true`: only matching runs are recorded." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "TEST" + ], + "description": "Mode of the rule when it ran. A `TEST` run records evidence but applied no actions." + }, + "is_async": { + "type": "boolean", + "description": "`true` when the run came from the asynchronous correlation pass rather than the ingest request." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL", + null + ], + "description": "Severity of the rule. Null for `TEST` runs and for rules without a severity." + }, + "score_delta": { + "type": "integer", + "description": "How much this run changed the event's score (0 for `TEST` runs)." + }, + "action_summary": { + "type": "string", + "nullable": true, + "description": "Short summary of the actions applied, for example `score +40; add_tag:VPN_LOGIN`. Null when nothing was applied." + }, + "matched_payload": { + "type": "array", + "description": "The conditions and aggregations that matched. Condition entries carry `field`, `operator`, `expected_value`, `current_value`, `matched`, `group_index`, `group_logic` and `field_source` (`server`, `sdk` or `customer`); aggregation entries carry `metric`, `field`, `operator`, `expected_value`, `computed_value`, `window`, `filters`, `matched` and `sample_references`.", + "items": { + "type": "object", + "additionalProperties": true + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the rule run was recorded." + } + }, + "required": [ + "rule_uuid", + "rule_title", + "library_key", + "matched", + "mode", + "is_async", + "severity", + "score_delta", + "action_summary", + "matched_payload", + "created_at" + ] + }, + "MonitoringEventPage": { + "type": "object", + "description": "One page of monitoring events, newest `occurred_at` first.", + "required": [ + "count", + "next", + "previous", + "results" + ], + "properties": { + "count": { + "type": "integer", + "description": "Number of matching events, exact up to 100 and capped at 100 above that. Do not compute page counts from it; follow `next` until it is null." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the next page, or null on the last page." + }, + "previous": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL of the previous page, or null on the first page." + }, + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MonitoringEvent" + } + } + } + }, + "MonitoringEventCreateRequest": { + "type": "object", + "description": "The event envelope, the same for every category. camelCase aliases are accepted: `eventId`, `eventCategory` (or `category`), `actionType`, `occurredAt`, `timeZone` (or `zoneId`), `deviceContext` (or `device`), `transactionContext`, `customValues` (or `props`); inside `subject`: `vendorData`, `entityType`, `fullName`; inside `session`: `sessionId`, `pageUrl`; inside `transaction_context`: `paymentReferenceId`.", + "required": [ + "event_id", + "event_category", + "action_type", + "occurred_at", + "subject" + ], + "properties": { + "event_id": { + "type": "string", + "maxLength": 128, + "description": "Your identifier for the event, unique per application. It is the idempotency key: resending the same `event_category`, `action_type`, `occurred_at`, `subject.vendor_data` and `payload` under it is a replay, anything different is a conflict.", + "example": "login-2026-10-17-000001" + }, + "event_category": { + "type": "string", + "enum": [ + "auth", + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "security", + "responsible_gambling", + "custom" + ], + "description": "Event category.", + "example": "auth" + }, + "action_type": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "What happened within the category. Open vocabulary; the managed actions listed in [Event ingestion](https://docs.didit.me/transaction-monitoring/events) (for example `login`, `deposit_completed`, `chargeback_opened`) unlock typed validation and managed rule fields. Leading and trailing whitespace is trimmed.", + "example": "login" + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "description": "When the event happened on your side, ISO 8601 (a value without an offset is read as UTC). Must be no more than 7 days in the past and no more than 5 minutes in the future.", + "example": "2026-10-17T09:15:02.481Z" + }, + "time_zone": { + "type": "string", + "nullable": true, + "maxLength": 64, + "description": "Time zone of the occurrence on your side, for example `Europe/Madrid`. Stored with the event; not returned by the read endpoints." + }, + "subject": { + "type": "object", + "description": "The user or company the event belongs to. It resolves to the user (or company) with this `vendor_data` in your application; one is created when none exists.", + "required": [ + "vendor_data" + ], + "properties": { + "vendor_data": { + "type": "string", + "minLength": 1, + "maxLength": 255, + "description": "Your id for the subject.", + "example": "user-42" + }, + "entity_type": { + "type": "string", + "maxLength": 32, + "default": "individual", + "description": "`company` resolves the subject to a company; any other value (default `individual`) resolves it to an individual. The same `vendor_data` can name one of each.", + "example": "individual" + }, + "full_name": { + "type": "string", + "nullable": true, + "maxLength": 512, + "description": "Display name, used only when this event creates the user or company." + }, + "email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Validated as an email address; not stored on the event." + }, + "phone": { + "type": "string", + "nullable": true, + "maxLength": 32, + "description": "Validated; not stored on the event." + }, + "country": { + "type": "string", + "nullable": true, + "maxLength": 3, + "description": "Validated; not stored on the event." + } + } + }, + "business": { + "type": "object", + "description": "The business the event belongs to, when it differs from the subject. Only `vendor_data` is read.", + "additionalProperties": true, + "properties": { + "vendor_data": { + "type": "string", + "maxLength": 255, + "description": "Your id for the business. Filterable as `business_vendor_data`.", + "example": "merchant-7" + } + } + }, + "session": { + "type": "object", + "description": "Your session context for the event.", + "properties": { + "session_id": { + "type": "string", + "nullable": true, + "maxLength": 255, + "description": "Your session identifier. Filterable and returned as `session_id`." + }, + "sequence": { + "type": "integer", + "nullable": true, + "description": "Position of the event within the session. Returned as `session_sequence`." + }, + "page_url": { + "type": "string", + "nullable": true, + "maxLength": 2048, + "description": "Validated; not stored on the event." + }, + "referrer": { + "type": "string", + "nullable": true, + "maxLength": 2048, + "description": "Validated; not stored on the event." + } + } + }, + "device_context": { + "type": "object", + "additionalProperties": true, + "description": "Device and network context, stored as sent and returned by `GET /v3/events/{event_uuid}/`. On this server lane Didit does not see the end user's connection, so the IP enriched and used by rules is `network_context.ip_address` as you send it.", + "properties": { + "fingerprint": { + "type": "string", + "maxLength": 255, + "description": "Device fingerprint. Filterable as `device_fingerprint`." + }, + "persistent_device_id": { + "type": "string", + "maxLength": 255, + "description": "Your persistent device identifier." + }, + "bot_score": { + "type": "integer", + "description": "Bot score from your own detection, as an integer." + }, + "network_context": { + "type": "object", + "additionalProperties": true, + "properties": { + "ip_address": { + "type": "string", + "maxLength": 64, + "description": "End user's IP address. Public addresses are enriched once per unique address.", + "example": "203.0.113.42" + } + } + } + } + }, + "transaction_context": { + "type": "object", + "description": "Money context. Non-empty values here also satisfy the required payload keys of the action.", + "properties": { + "amount": { + "type": "string", + "nullable": true, + "description": "Amount as a decimal string (a number is also accepted).", + "example": "1000.00" + }, + "currency": { + "type": "string", + "nullable": true, + "maxLength": 10, + "description": "Currency code." + }, + "direction": { + "type": "string", + "nullable": true, + "maxLength": 16, + "description": "Direction of the money movement, for example `inbound` or `outbound`." + }, + "payment_reference_id": { + "type": "string", + "nullable": true, + "maxLength": 255, + "description": "Your payment reference. Filterable as `payment_reference_id`." + } + } + }, + "payload": { + "type": "object", + "additionalProperties": true, + "description": "The category's domain fields, at most 16384 bytes once JSON-encoded. Some actions require keys (in `payload` or `transaction_context`): `gameplay.p2p_settlement` needs `game_id`, `counterparty_vendor_data`, `stake_amount`, `net_result_amount`, `role` and one of `hand_id`, `round_id`, `match_id`; `gameplay.bet_placed` and `gameplay.bet_settled` need `game_id`; `payment.deposit_initiated`, `payment.deposit_completed`, `payment.withdrawal_requested` and `payment.withdrawal_completed` need `payment_reference_id`, `amount`, `currency`, `payment_method`, `status`; `payment.withdrawal_address_changed` needs `withdrawal_address_hash`; `dispute.chargeback_opened`, `dispute.chargeback_won` and `dispute.chargeback_lost` need `payment_reference_id` and one of `dispute_id`, `chargeback_id`; `session.bot_signal_observed` needs `telemetry_schema_version` and one of `bot_score`, `actions_per_minute`, `timing_variance_ms`, `pointer_entropy`, `keystroke_entropy`, `reaction_time_ms_p50`; `responsible_gambling.self_exclusion_match` needs `registry_source`, `exclusion_status`, `identity_match_ref`. Every `security` action ending in `_changed` needs `changed_field`, plus `previous_reference_hash` or `new_reference_hash` when the field is `password`, `mfa_method`, `mfa`, `email`, `phone`, `withdrawal_address`, `payout_method` or `api_key`. Send hashes and references, never a card number, a full withdrawal address, a password hash or an MFA secret." + }, + "custom_values": { + "type": "object", + "description": "Up to 100 keys of your own, addressable from rules as `custom_values.`. Values must be scalars (string, number, boolean or null); nested objects and arrays are rejected.", + "additionalProperties": { + "nullable": true, + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ] + } + } + }, + "example": { + "event_id": "login-2026-10-17-000001", + "event_category": "auth", + "action_type": "login", + "occurred_at": "2026-10-17T09:15:02.481Z", + "time_zone": "Europe/Madrid", + "subject": { + "vendor_data": "user-42", + "entity_type": "individual" + }, + "session": { + "session_id": "web-session-abc", + "sequence": 142 + }, + "device_context": { + "fingerprint": "fp-a8c1", + "network_context": { + "ip_address": "203.0.113.42" + } + }, + "payload": { + "method": "password" + }, + "custom_values": { + "vip_tier": "gold" + } + } + }, + "MonitoringEventIngestResult": { + "type": "object", + "description": "Outcome of ingesting one event. The same shape is returned for a new event and for an idempotent replay.", + "required": [ + "event_uuid", + "event_id", + "status", + "score", + "rules_evaluated_count", + "rules_matched_count", + "correlation_pending", + "retention_class", + "expires_at", + "idempotent_replay", + "rule_matches" + ], + "properties": { + "event_uuid": { + "type": "string", + "format": "uuid", + "description": "Didit identifier of the stored event." + }, + "event_id": { + "type": "string", + "description": "Your `event_id`." + }, + "status": { + "type": "string", + "enum": [ + "ACCEPTED", + "APPROVED", + "IN_REVIEW", + "DECLINED" + ], + "description": "Status after the real-time rules ran. See `MonitoringEvent.status`." + }, + "score": { + "type": "integer", + "description": "Score after the real-time rules ran." + }, + "rules_evaluated_count": { + "type": "integer", + "minimum": 0, + "description": "Number of event rules evaluated in the request. 0 in a sandbox application, where no rules run." + }, + "rules_matched_count": { + "type": "integer", + "minimum": 0, + "description": "Number of rules that matched, including `TEST` mode rules." + }, + "correlation_pending": { + "type": "boolean", + "description": "`true` when an asynchronous rule still has to run. Read `GET /v3/events/{event_uuid}/` a few minutes later for the final `status` and `score`." + }, + "retention_class": { + "type": "string", + "enum": [ + "low_value", + "standard", + "risk_relevant", + "monetary_or_compliance" + ], + "description": "Retention class, after any promotion by a matched rule." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the event expires under its retention class and is deleted. Null for `monetary_or_compliance` (payment and dispute) events." + }, + "idempotent_replay": { + "type": "boolean", + "description": "`true` when an event with this `event_id` and the same content was already stored; the values are those of the stored event and no rule ran again." + }, + "rule_matches": { + "type": "array", + "description": "Rules that matched the event.", + "items": { + "type": "object", + "required": [ + "rule_id", + "library_key", + "mode", + "is_async" + ], + "properties": { + "rule_id": { + "type": "string", + "format": "uuid", + "description": "UUID of the rule." + }, + "library_key": { + "type": "string", + "nullable": true, + "description": "Library key when the rule was installed from the rules library, otherwise null." + }, + "mode": { + "type": "string", + "enum": [ + "ACTIVE", + "TEST" + ], + "description": "`TEST` rules record the match but apply no actions." + }, + "is_async": { + "type": "boolean", + "description": "`true` when the match came from the asynchronous pass." + } + } + } + } + } + }, + "ComplianceList": { + "type": "object", + "description": "A blocklist, allowlist or custom list of one application.", + "required": [ + "uuid", + "name", + "description", + "list_type", + "entry_type", + "is_system", + "entry_count", + "last_entry_added_at", + "created_at", + "updated_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "List identifier. Use it as `list_uuid` in the list and entry endpoints, and as the value of a workflow or transaction rule condition with `value_type: \"list\"`." + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "List name, unique among the application's lists." + }, + "description": { + "type": "string", + "description": "Free-text description. Empty string when not set." + }, + "list_type": { + "type": "string", + "enum": [ + "blocklist", + "allowlist", + "custom" + ], + "description": "`blocklist`: a match declines the session or transaction (system lists only, one per entry type). `allowlist`: matching items are trusted, for example a face allowlist skips the duplicated-face action; never overrides a blocklist match. `custom`: used in workflow and transaction rule conditions (`value_type: \"list\"`)." + }, + "entry_type": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email", + "ip_address", + "device_fingerprint", + "wallet_address", + "bank_account", + "country", + "user", + "business", + "key" + ], + "description": "What the list holds: `face`, `document` (document number), `phone`, `email`, `ip_address` (IP address or CIDR range), `device_fingerprint`, `wallet_address`, `bank_account`, `country` (ISO 3166-1 alpha-3), `user` or `business` (your `vendor_data`), or `key` (any string). It decides how entry values are validated and matched." + }, + "is_system": { + "type": "boolean", + "description": "`true` for the lists Didit provisions: one blocklist per entry type (`face`, `document`, `phone`, `email`, `ip_address`, `device_fingerprint`, `wallet_address`, `bank_account`, `user`, `business`), the **High Risk Countries** custom list, and system allowlists. System lists cannot be renamed, edited or deleted; you can add and delete their entries." + }, + "entry_count": { + "type": "integer", + "minimum": 0, + "description": "Number of entries currently in the list." + }, + "last_entry_added_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the newest current entry was added. `null` for an empty list." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the list was created." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When the list was last updated." + } + } + }, + "ComplianceListEntry": { + "type": "object", + "description": "One value in a list.", + "required": [ + "uuid", + "value", + "display_label", + "comment", + "added_by", + "reference_session_id", + "reference_session_number", + "reference_session_type", + "reference_object_uuid", + "metadata", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Entry identifier. Use it as `entry_uuid` in Delete a list entry." + }, + "value": { + "type": "string", + "description": "The listed value: a phone number in E.164, an email address, a document number, an IP address or CIDR range, a device fingerprint, a wallet address, a bank account, an ISO 3166-1 alpha-3 country code, a `vendor_data`, any string on a `key` list, or the UUID of the stored face on a face list. Values you send are stored in lower case, except phone numbers and country codes. Matching is case-insensitive." + }, + "display_label": { + "type": "string", + "nullable": true, + "maxLength": 512, + "description": "Human-readable label. Didit fills it for entries taken from a session (for example the holder's name and document number) when you send none." + }, + "comment": { + "type": "string", + "description": "Free-text note. Empty string when not set." + }, + "added_by": { + "type": "string", + "nullable": true, + "description": "Who added the entry: `api` (an API key), a console user's email address or `console`, `system` or `migration` (entries Didit created), or `rule` (a transaction or monitoring rule action)." + }, + "reference_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Verification session the entry was taken from. `null` when the entry was added by value." + }, + "reference_session_number": { + "type": "integer", + "nullable": true, + "description": "Application-scoped number of that session, as shown in the console." + }, + "reference_session_type": { + "type": "string", + "nullable": true, + "enum": [ + "kyc", + "kyb", + null + ], + "description": "`kyc` for a person verification session, `kyb` for a business verification session, `null` when there is no session." + }, + "reference_object_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The record the entry stands for: the face, document, phone or email record read from a session, the face created by Upload a face to a list, or the UUID you sent (for example a transaction UUID or a user's `didit_internal_id`)." + }, + "metadata": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Extra context. Holds the keys Didit sets (listed here) merged with any `metadata` you sent when creating the entry, returned as stored. `null` when there is none.", + "properties": { + "reference_image_url": { + "type": "string", + "description": "Face entries: short-lived signed URL, issued again on every call; `null` for a scoped API key without `media: read`. Absent when no image belongs to the entry." + }, + "front_image_url": { + "type": "string", + "description": "Document entries that carry a document front image: short-lived signed URL, issued again on every call; `null` for a scoped API key without `media: read`." + }, + "full_name": { + "type": "string", + "description": "Document entries taken from a session: holder's full name." + }, + "issuing_state": { + "type": "string", + "description": "Document entries taken from a session: ISO 3166-1 alpha-3 issuing country." + }, + "document_type": { + "type": "string", + "description": "Document entries taken from a session: document type code, for example `P`, `ID` or `DL`." + }, + "region": { + "type": "string", + "description": "Document entries taken from a session: issuing state or province of a regionally issued document, empty when not applicable. Not set by Add session items to the blocklist." + }, + "document_number": { + "type": "string", + "description": "Document entries taken from a session: the document number." + }, + "face_image_source": { + "type": "string", + "enum": [ + "front_image_camera_front", + "back_image_camera_front" + ], + "description": "Face entries taken from a document-capture selfie: which selfie. Absent for liveness faces." + }, + "reference_node_id": { + "type": "string", + "nullable": true, + "description": "Face entries taken from a document-capture selfie: the ID verification node the selfie came from." + }, + "fingerprint_tier": { + "type": "string", + "nullable": true, + "enum": [ + "persistent_id", + "composite_hash", + "legacy_fp", + "unknown", + null + ], + "description": "Device fingerprint entries taken from a session: how the fingerprint was derived. `persistent_id` identifies one device; `composite_hash` and `legacy_fp` can be shared by devices of the same model." + }, + "sandbox": { + "type": "boolean", + "description": "`true` on a face entry uploaded in a sandbox application; such an entry matches nothing." + }, + "reference_type": { + "type": "string", + "enum": [ + "transaction", + "vendor_user", + "vendor_business" + ], + "description": "Set by you: what `reference_object_uuid` points to, so the console links back to it." + }, + "reference_id": { + "type": "string", + "description": "Set by you: identifier of the source record." + } + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the entry was added." + } + } + }, + "BlocklistedItem": { + "type": "object", + "description": "One blocklisted face, identity document, phone number or email address of a verification session. Fields that do not apply to the item's `type` are `null`.", + "required": [ + "type", + "session_id", + "session_number", + "blocklisted_at", + "face_image", + "document_type", + "document_number", + "front_image", + "phone_number", + "country_code", + "email" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "face", + "document", + "phone", + "email" + ], + "description": "What was blocklisted." + }, + "session_id": { + "type": "string", + "format": "uuid", + "description": "Verification session the item belongs to." + }, + "session_number": { + "type": "integer", + "nullable": true, + "description": "Application-scoped number of that session, as shown in the console." + }, + "blocklisted_at": { + "type": "string", + "format": "date-time", + "description": "When the item was last blocklisted." + }, + "face_image": { + "type": "string", + "nullable": true, + "description": "Face items: short-lived signed URL, issued again on every call; `null` for a scoped API key without `media: read`. Also `null` when no reference image is stored." + }, + "document_type": { + "type": "string", + "nullable": true, + "description": "Document items: document type code, for example `P` (passport), `ID` (national ID), `DL` (driver's license) or `RP` (residence permit)." + }, + "document_number": { + "type": "string", + "nullable": true, + "description": "Document items: the document number, or the personal number when the document has none." + }, + "front_image": { + "type": "string", + "nullable": true, + "description": "Document items: front image of the document, short-lived signed URL, issued again on every call; `null` for a scoped API key without `media: read`." + }, + "phone_number": { + "type": "string", + "nullable": true, + "description": "Phone items: the phone number in E.164." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "Phone items: ISO 3166-1 alpha-2 country of the number." + }, + "email": { + "type": "string", + "nullable": true, + "description": "Email items: the email address." + } + } + }, + "TransactionNote": { + "type": "object", + "description": "One note on a monitored transaction's review timeline.", + "required": [ + "uuid", + "note", + "actor_email", + "actor_name", + "metadata", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Note identifier." + }, + "note": { + "type": "string", + "description": "Plain-text note body." + }, + "actor_email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email of the Console user who wrote the note or changed the status. `null` for notes created with an API key and for notes Didit adds automatically." + }, + "actor_name": { + "type": "string", + "nullable": true, + "description": "Display name of the Console user (their email when they have no name). `Rule Engine` on notes Didit adds automatically. `null` for notes created with an API key." + }, + "metadata": { + "type": "object", + "description": "Tells the note types apart. Notes written through the API or the Console carry `mentioned_emails`. A status change made in the Console carries `type: status_change` with `previous_status`, `new_status`, `reason_code` and `reason_label`. Notes Didit adds carry `source`: `rule_action` (with `rule_uuid`) for a rule's `add_note` action, or `remediation_session` for a problem creating the user-action verification session.", + "properties": { + "mentioned_emails": { + "type": "array", + "items": { + "type": "string", + "format": "email" + }, + "description": "Mentioned organization members, lower-cased and de-duplicated. Empty when nobody was mentioned." + }, + "type": { + "type": "string", + "enum": [ + "status_change" + ], + "description": "Present on notes recording a status change made in the Console." + }, + "previous_status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Status before the change." + }, + "new_status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Status after the change." + }, + "reason_code": { + "type": "string", + "nullable": true, + "description": "Reason code given with the change." + }, + "reason_label": { + "type": "string", + "nullable": true, + "description": "Reason label given with the change." + }, + "source": { + "type": "string", + "enum": [ + "rule_action", + "remediation_session" + ], + "description": "Present on notes Didit adds automatically." + }, + "rule_uuid": { + "type": "string", + "format": "uuid", + "description": "Rule whose `add_note` action wrote the note (with `source: rule_action`)." + } + } + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the note was stored." + } + } + }, + "TransactionSummary": { + "type": "object", + "description": "Summary of a monitored transaction, the shape `GET /v3/transactions/` lists. Call `GET /v3/transactions/{transaction_id}/` with `uuid` for parties, alerts and rule runs.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit transaction UUID. Use it as `{transaction_id}` on the transaction endpoints." + }, + "transaction_number": { + "type": "integer", + "nullable": true, + "description": "Application-scoped sequential number shown in the Console." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "Your own identifier from the create call. Unique per application." + }, + "transaction_type": { + "type": "string", + "description": "Category as stored: `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet`, `gamblingLimitChange`, `gamblingBonusChange` or `auditTrailEvent`." + }, + "action_type": { + "type": "string", + "description": "Sub-type within the category, for example `withdrawal`." + }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "IN_REVIEW", + "DECLINED", + "AWAITING_USER" + ], + "description": "Current monitoring verdict." + }, + "source": { + "type": "string", + "enum": [ + "LIVE", + "IMPORTED" + ], + "description": "`LIVE` for transactions submitted for monitoring, `IMPORTED` for historical imports." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Direction relative to the subject." + }, + "amount": { + "type": "string", + "description": "Amount as a decimal string with trailing zeros removed (up to 18 decimal places)." + }, + "currency": { + "type": "string", + "description": "Currency code or crypto asset symbol of `amount`." + }, + "currency_type": { + "type": "string", + "nullable": true, + "description": "`fiat` or `crypto` when known." + }, + "preferred_currency_amount": { + "type": "string", + "nullable": true, + "description": "`amount` converted to the application's preferred currency, when a rate was available." + }, + "preferred_currency_code": { + "type": "string", + "nullable": true, + "description": "Currency of `preferred_currency_amount`." + }, + "payment_details": { + "type": "string", + "nullable": true, + "description": "Free-text payment reference." + }, + "txn_date": { + "type": "string", + "format": "date-time", + "description": "When the transaction occurred." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When Didit recorded it." + }, + "score": { + "type": "integer", + "description": "Risk score. Higher is riskier." + }, + "severity": { + "type": "string", + "nullable": true, + "enum": [ + "UNKNOWN", + "LOW", + "MEDIUM", + "HIGH", + "CRITICAL", + null + ], + "description": "Risk severity, `null` until something sets it." + }, + "decision_reason_code": { + "type": "string", + "nullable": true, + "description": "Machine-readable reason for the current `status`." + }, + "decision_reason_label": { + "type": "string", + "nullable": true, + "description": "Human-readable label for `decision_reason_code`." + }, + "tags": { + "type": "array", + "description": "Tags on the transaction.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "color": { + "type": "string", + "description": "Hex color, for example `#F59E0B`." + }, + "description": { + "type": "string", + "nullable": true + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ], + "description": "Didit preset or custom tag." + } + } + } + }, + "subject_name": { + "type": "string", + "nullable": true, + "description": "Full name of the `APPLICANT` party." + }, + "subject_vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the applicant." + }, + "subject_type": { + "type": "string", + "nullable": true, + "enum": [ + "user", + "business", + null + ], + "description": "Whether the applicant is linked to a Didit user or business; `null` when it is linked to neither." + }, + "subject_uuid": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "UUID of the Didit user or business the applicant is linked to." + }, + "subject_country": { + "type": "string", + "nullable": true, + "description": "Country code of the applicant." + }, + "counterparty_name": { + "type": "string", + "nullable": true, + "description": "Full name of the `COUNTERPARTY` party, if any." + }, + "counterparty_vendor_data": { + "type": "string", + "nullable": true, + "description": "Your `vendor_data` for the counterparty." + }, + "counterparty_country": { + "type": "string", + "nullable": true, + "description": "Country code of the counterparty." + }, + "assigned_to_name": { + "type": "string", + "nullable": true, + "description": "Name of the Console reviewer assigned to the transaction." + }, + "travel_rule_status": { + "type": "string", + "nullable": true, + "description": "Status of the transaction's Travel Rule transfer, or `null` when it has none." + }, + "application_id": { + "type": "string", + "format": "uuid", + "description": "Application that owns the transaction." + }, + "application_name": { + "type": "string", + "nullable": true, + "description": "Name of that application." + }, + "organization_id": { + "type": "string", + "format": "uuid", + "description": "Organization that owns the application." + }, + "organization_name": { + "type": "string", + "nullable": true, + "description": "Name of that organization." + } + } + }, + "ApplicationAnalyticsDataPoint": { + "type": "object", + "description": "Session counts for one day, by creation date (UTC). Each count reflects the session's current status. Points of the previous period carry at least the required fields.", + "required": [ + "rounded_date", + "total", + "approved", + "declined", + "canceled", + "pending" + ], + "properties": { + "rounded_date": { + "type": "string", + "format": "date", + "description": "The day, `YYYY-MM-DD`." + }, + "total": { + "type": "integer", + "description": "Sessions created that day." + }, + "approved": { + "type": "integer", + "description": "Of those, sessions `Approved` or `Kyc Expired`." + }, + "declined": { + "type": "integer", + "description": "Sessions `Declined`." + }, + "canceled": { + "type": "integer", + "description": "Sessions `Expired` or `Abandoned`." + }, + "pending": { + "type": "integer", + "description": "Sessions `Not Started`, `In Progress` or `In Review`." + }, + "in_review": { + "type": "integer", + "description": "Sessions `In Review`." + }, + "expired": { + "type": "integer", + "description": "Sessions `Expired`." + }, + "abandoned": { + "type": "integer", + "description": "Sessions `Abandoned`." + }, + "in_progress": { + "type": "integer", + "description": "Sessions `In Progress`." + }, + "not_started": { + "type": "integer", + "description": "Sessions `Not Started`." + }, + "manually_approved": { + "type": "integer", + "description": "Sessions a reviewer approved after the workflow gave another verdict." + }, + "manually_declined": { + "type": "integer", + "description": "Sessions a reviewer declined after the workflow gave another verdict." + } + } + }, + "ApplicationAnalyticsRequestBreakdown": { + "type": "object", + "description": "Status totals for the range. The previous-period breakdown carries at least the required fields.", + "required": [ + "total_approved", + "total_in_review", + "total_declined", + "total_canceled", + "total_pending" + ], + "properties": { + "total_approved": { + "type": "integer", + "description": "Sessions `Approved` or `Kyc Expired`." + }, + "total_in_review": { + "type": "integer", + "description": "Sessions `In Review`." + }, + "total_declined": { + "type": "integer", + "description": "Sessions `Declined`." + }, + "total_canceled": { + "type": "integer", + "description": "Sessions `Expired` or `Abandoned`." + }, + "total_pending": { + "type": "integer", + "description": "Sessions `Not Started`, `In Progress` or `In Review` (includes `total_in_review`)." + }, + "total_expired": { + "type": "integer", + "description": "Sessions `Expired`." + }, + "total_abandoned": { + "type": "integer", + "description": "Sessions `Abandoned`." + }, + "total_in_progress": { + "type": "integer", + "description": "Sessions `In Progress`." + }, + "total_not_started": { + "type": "integer", + "description": "Sessions `Not Started`." + }, + "total_manually_approved": { + "type": "integer", + "description": "Approved by a reviewer." + }, + "total_manually_declined": { + "type": "integer", + "description": "Declined by a reviewer." + } + } + }, + "ApplicationAnalyticsCountryCount": { + "type": "object", + "properties": { + "country": { + "type": "string", + "description": "Country name." + }, + "country_code": { + "type": "string", + "description": "Two-letter country code." + }, + "session_count": { + "type": "integer", + "description": "Sessions from that country." + } + } + }, + "ApplicationAnalyticsLocationStats": { + "type": "object", + "properties": { + "ip_countries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsCountryCount" + }, + "description": "Countries of the users' IP addresses, most sessions first." + }, + "countries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsCountryCount" + }, + "description": "Same list as `ip_countries`." + }, + "id_countries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsCountryCount" + }, + "description": "Issuing countries of the verified ID documents, most sessions first." + }, + "conversion_by_country": { + "type": "array", + "description": "Per document issuing country: finished sessions (approved, declined or in review) and how many were approved. Most finished first.", + "items": { + "type": "object", + "properties": { + "country": { + "type": "string" + }, + "country_code": { + "type": "string" + }, + "finished": { + "type": "integer" + }, + "approved": { + "type": "integer" + } + } + } + }, + "cities": { + "type": "array", + "description": "Up to 100 IP cities, most sessions first.", + "items": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "`City, Country`." + }, + "session_count": { + "type": "integer" + }, + "latitude": { + "type": "number", + "nullable": true + }, + "longitude": { + "type": "number", + "nullable": true + } + } + } + } + } + }, + "ApplicationAnalyticsDemographicStats": { + "type": "object", + "properties": { + "age_distribution": { + "type": "object", + "description": "Sessions per age band from the ID document. Empty when there is no data.", + "properties": { + "under_18": { + "type": "integer" + }, + "18_24": { + "type": "integer" + }, + "25_34": { + "type": "integer" + }, + "35_44": { + "type": "integer" + }, + "45_64": { + "type": "integer" + }, + "65_plus": { + "type": "integer" + } + }, + "additionalProperties": { + "type": "integer" + } + }, + "gender_distribution": { + "type": "object", + "description": "Sessions per document gender code: `M`, `F` or `U` (unknown).", + "additionalProperties": { + "type": "integer" + } + } + } + }, + "ApplicationAnalyticsDeviceDetails": { + "type": "object", + "description": "Top five of each, most sessions first.", + "properties": { + "device_models": { + "type": "array", + "description": "Device brands: `Apple`, `Android`, `LG`, everything else grouped as `Other`.", + "items": { + "type": "object", + "properties": { + "device": { + "type": "string" + }, + "count": { + "type": "integer" + } + } + } + }, + "browsers": { + "type": "array", + "description": "Browsers: `Chrome`, `Safari`, `Didit App`, `Firefox`, everything else grouped as `Other`.", + "items": { + "type": "object", + "properties": { + "browser": { + "type": "string" + }, + "count": { + "type": "integer" + } + } + } + }, + "operating_systems": { + "type": "array", + "description": "Operating systems.", + "items": { + "type": "object", + "properties": { + "os": { + "type": "string" + }, + "count": { + "type": "integer" + } + } + } + } + } + }, + "ApplicationAnalyticsWorkflowFunnel": { + "type": "object", + "description": "Keyed by workflow ID. `total` is the sessions started on that workflow; `steps` counts the sessions that reached each step (`OCR`, `LIVENESS`, `FACE_MATCH`, `AML`, `POA`, `PHONE`, `EMAIL`, `NFC`, `DATABASE_VALIDATION`, `QUESTIONNAIRE`, `IP_ANALYSIS`, `AGE_ESTIMATION`, `KYB_REGISTRY`, `KYB_DOCUMENTS`, `KYB_KEY_PEOPLE`). Reaching a step is not passing it.", + "additionalProperties": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "steps": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "reached": { + "type": "integer" + } + } + } + } + } + } + }, + "ApplicationAnalyticsVerificationTimeStats": { + "type": "object", + "description": "Active time users spent verifying, ignoring idle gaps. An empty object when there is no data.", + "properties": { + "overall_avg_seconds": { + "type": "number", + "description": "Average per session, one decimal." + }, + "overall_count": { + "type": "integer", + "description": "Sessions measured." + }, + "per_feature": { + "type": "object", + "description": "Keyed by feature, for example `OCR` or `LIVENESS`.", + "additionalProperties": { + "type": "object", + "properties": { + "avg_seconds": { + "type": "number" + }, + "total_seconds": { + "type": "number" + }, + "count": { + "type": "integer" + } + } + } + }, + "per_sub_step": { + "type": "object", + "description": "Keyed by feature, then by sub-step, for example `front_id_photo`.", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "avg_seconds": { + "type": "number" + }, + "total_seconds": { + "type": "number" + }, + "count": { + "type": "integer" + } + } + } + } + } + } + }, + "ApplicationAnalyticsResubmissionStats": { + "type": "object", + "properties": { + "first_pass": { + "type": "integer", + "description": "Finished sessions with no resubmission." + }, + "second": { + "type": "integer", + "description": "Finished sessions resubmitted once." + }, + "third_plus": { + "type": "integer", + "description": "Finished sessions resubmitted two or more times." + }, + "abandoned": { + "type": "integer", + "description": "Abandoned sessions." + } + } + }, + "ApplicationAnalyticsOriginalStatusStats": { + "type": "object", + "description": "The workflow's automatic verdict, before any reviewer changed it.", + "properties": { + "approved": { + "type": "integer" + }, + "in_review": { + "type": "integer" + }, + "declined": { + "type": "integer" + } + } + }, + "ApplicationAnalyticsWarningStats": { + "type": "object", + "description": "Keyed by feature (`ID_VERIFICATION`, `LIVENESS`, `FACEMATCH`, `AML`, `PROOF_OF_ADDRESS`, `PHONE`, `EMAIL`, `NFC`, `LOCATION`, `DATABASE_VALIDATION`, `QUESTIONNAIRE` and the KYB features). `total` is the sessions that reached the feature; `risks` counts sessions per warning code and level.", + "additionalProperties": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "risks": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "error": { + "type": "integer" + }, + "warning": { + "type": "integer" + }, + "information": { + "type": "integer" + } + } + } + } + } + } + }, + "ApplicationAnalyticsDemographicPoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "age_distribution": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + }, + "gender_distribution": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + } + }, + "ApplicationAnalyticsDevicePoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "devices": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + }, + "browsers": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + }, + "operating_systems": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + } + }, + "ApplicationAnalyticsVerificationTimePoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "overall_avg": { + "type": "number", + "description": "Average active seconds per session." + }, + "per_feature": { + "type": "object", + "description": "Average active seconds per feature.", + "additionalProperties": { + "type": "number" + } + } + } + }, + "ApplicationAnalyticsResubmissionPoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "first_pass": { + "type": "integer" + }, + "second": { + "type": "integer" + }, + "third_plus": { + "type": "integer" + }, + "abandoned": { + "type": "integer" + } + } + }, + "ApplicationAnalyticsOriginalStatusPoint": { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "Period key: `YYYY-MM-DD` for a day, the Monday of a week, or `YYYY-MM` for a month, following `grouping_mode`." + }, + "approved": { + "type": "integer" + }, + "in_review": { + "type": "integer" + }, + "declined": { + "type": "integer" + } + } + }, + "ApplicationAnalyticsSummary": { + "type": "object", + "description": "User verification (KYC) summary, returned when neither `scope` nor `widget_keys` is sent.", + "required": [ + "data_points", + "request_breakdown", + "previous_data_points", + "previous_request_breakdown", + "conversion_rate", + "location_stats", + "demographic_stats", + "device_details", + "workflow_funnel_stats", + "verification_time_stats", + "resubmission_stats", + "warning_stats", + "original_status_stats", + "grouping_mode", + "demographic_time_series", + "device_time_series", + "verification_time_time_series", + "resubmission_time_series", + "original_status_time_series" + ], + "properties": { + "data_points": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDataPoint" + }, + "description": "One point per day, from the first day with sessions in the range to `end_date`. Always daily, whatever `grouping_mode` says." + }, + "request_breakdown": { + "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" + }, + "previous_data_points": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDataPoint" + }, + "description": "Daily points for the period of the same length right before `start_date`." + }, + "previous_request_breakdown": { + "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" + }, + "conversion_rate": { + "type": "number", + "nullable": true, + "description": "`total_approved / (total_approved + total_declined + total_in_review) x 100`, one decimal. `null` when nothing finished." + }, + "location_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsLocationStats" + }, + "demographic_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsDemographicStats" + }, + "device_details": { + "$ref": "#/components/schemas/ApplicationAnalyticsDeviceDetails" + }, + "workflow_funnel_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsWorkflowFunnel" + }, + "verification_time_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimeStats" + }, + "resubmission_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionStats" + }, + "warning_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsWarningStats" + }, + "original_status_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusStats" + }, + "grouping_mode": { + "type": "string", + "enum": [ + "day", + "week", + "month" + ], + "description": "How the `*_time_series` lists are grouped: `day` for up to 14 days, `week` up to 90, `month` beyond. Measured from the first day with sessions in the range." + }, + "demographic_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDemographicPoint" + } + }, + "device_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDevicePoint" + } + }, + "verification_time_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimePoint" + } + }, + "resubmission_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionPoint" + } + }, + "original_status_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusPoint" + } + } + } + }, + "ApplicationAnalyticsWidget": { + "type": "object", + "description": "One dashboard widget. Every widget has `kind` and `title`; the other fields depend on `kind`. The session widgets reuse the summary objects (`data_points`, `request_breakdown`, `workflow_funnel_stats` and so on); list widgets put their entries in `items`; transaction metrics use `value`.", + "required": [ + "kind", + "title" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "sessions_volume", + "workflow_funnel", + "average_verification_time", + "country_distribution", + "warning_insights", + "conversion_rate", + "conversion_by_country", + "resubmission_insights", + "original_status_distribution", + "database_validation_coverage", + "database_validation_latency", + "demographics", + "device_details", + "horizontal_bar", + "kyb_registry_coverage", + "kyb_registry_data_completeness", + "metric", + "time_series", + "breakdown", + "status_time_series", + "leaderboard", + "histogram" + ], + "description": "Widget type." + }, + "title": { + "type": "string", + "description": "Display title, for example `Verification volume`." + }, + "grouping_mode": { + "type": "string", + "enum": [ + "day", + "week", + "month" + ], + "description": "How the `*_time_series` lists are grouped: `day` for up to 14 days, `week` up to 90, `month` beyond. Measured from the first day with sessions in the range." + }, + "data_points": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDataPoint" + } + }, + "request_breakdown": { + "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" + }, + "previous_data_points": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDataPoint" + } + }, + "previous_request_breakdown": { + "$ref": "#/components/schemas/ApplicationAnalyticsRequestBreakdown" + }, + "conversion_rate": { + "type": "number", + "nullable": true + }, + "workflow_funnel_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsWorkflowFunnel" + }, + "verification_time_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimeStats" + }, + "verification_time_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsVerificationTimePoint" + } + }, + "warning_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsWarningStats" + }, + "resubmission_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionStats" + }, + "resubmission_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsResubmissionPoint" + } + }, + "original_status_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusStats" + }, + "original_status_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsOriginalStatusPoint" + } + }, + "demographic_stats": { + "$ref": "#/components/schemas/ApplicationAnalyticsDemographicStats" + }, + "demographic_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDemographicPoint" + } + }, + "device_details": { + "$ref": "#/components/schemas/ApplicationAnalyticsDeviceDetails" + }, + "device_time_series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplicationAnalyticsDevicePoint" + } + }, + "items": { + "type": "array", + "description": "Entries of a list widget. `country_distribution` (KYC): `country`, `country_code`, `session_count`. `conversion_by_country`: `country`, `country_code`, `finished`, `approved`. `time_series`: `date`, `value`. `status_time_series`: `date`, `approved`, `declined`, `flagged`. `breakdown`: `transaction_type` or `action_type` with `count` and `volume`, or `tag_id`, `tag_name`, `count`. `leaderboard`: `rule_title`, `trigger_count` or `assignee`, `handled_count`, `average_processing_seconds`, each with `approved_percentage`, `declined_percentage`, `flagged_percentage`. `histogram`: `score`, `count`.", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "previous_items": { + "type": "array", + "description": "Transaction time series for the period of the same length right before `start_date`.", + "items": { + "type": "object", + "additionalProperties": {} + } + }, + "value": { + "type": "number", + "description": "`metric` widgets: the figure for the range." + }, + "currency": { + "type": "string", + "nullable": true, + "description": "Transaction volume and amount widgets: the application's default transaction currency." + }, + "thresholds": { + "type": "object", + "description": "`histogram` widget: the application's review and decline score thresholds.", + "properties": { + "review": { + "type": "integer" + }, + "decline": { + "type": "integer" + } + } + }, + "search": { + "type": "object", + "description": "`kyb_registry_coverage` widget: registry search totals.", + "properties": { + "searches_total": { + "type": "integer" + }, + "searches_with_no_candidates": { + "type": "integer" + } + } + } + } + }, + "ApplicationAnalyticsWidgetResponse": { + "type": "object", + "description": "Dashboard widgets, returned when `scope` or `widget_keys` is sent.", + "required": [ + "scope", + "available_widget_keys", + "requested_widget_keys", + "widgets" + ], + "properties": { + "scope": { + "type": "string", + "enum": [ + "home", + "kyc", + "kyb", + "transactions" + ], + "description": "Dashboard tab." + }, + "available_widget_keys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Every widget key this scope accepts." + }, + "requested_widget_keys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The widget keys answered, in order: your valid `widget_keys`, or the scope's defaults." + }, + "widgets": { + "type": "object", + "description": "Keyed by widget key, in the order of `requested_widget_keys`.", + "additionalProperties": { + "$ref": "#/components/schemas/ApplicationAnalyticsWidget" + } + } + } + }, + "ApplicationAnalyticsToday": { + "type": "object", + "description": "Today's verifications by local hour against the same weekday last week.", + "required": [ + "time_zone", + "date", + "current_hour", + "previous_date", + "hours", + "previous_hours", + "total", + "previous_total_so_far", + "outcomes" + ], + "properties": { + "time_zone": { + "type": "string", + "description": "The IANA time zone the day and its hours are in.", + "example": "Europe/Madrid" + }, + "date": { + "type": "string", + "format": "date", + "description": "Today in that time zone.", + "example": "2026-10-30" + }, + "current_hour": { + "type": "integer", + "minimum": 0, + "maximum": 23, + "description": "The local hour it is now.", + "example": 14 + }, + "previous_date": { + "type": "string", + "format": "date", + "description": "The comparison day: the same weekday one week before.", + "example": "2026-10-23" + }, + "hours": { + "type": "array", + "items": { + "type": "integer", + "minimum": 0 + }, + "minItems": 24, + "maxItems": 24, + "description": "Verifications created today, per local hour from 00:00. The hours still to come are 0." + }, + "previous_hours": { + "type": "array", + "items": { + "type": "integer", + "minimum": 0 + }, + "minItems": 24, + "maxItems": 24, + "description": "Verifications created on the comparison day, per local hour from 00:00." + }, + "total": { + "type": "integer", + "minimum": 0, + "description": "Verifications created today so far.", + "example": 165 + }, + "previous_total_so_far": { + "type": "integer", + "minimum": 0, + "description": "Verifications created on the comparison day up to the same local time.", + "example": 151 + }, + "outcomes": { + "type": "object", + "description": "Of today's verifications, how many are approved, waiting in review and declined now. The rest are not finished yet.", + "required": [ + "approved", + "in_review", + "declined" + ], + "properties": { + "approved": { + "type": "integer", + "minimum": 0 + }, + "in_review": { + "type": "integer", + "minimum": 0 + }, + "declined": { + "type": "integer", + "minimum": 0 + } + } + } + } + }, + "WebhookConfigDestination": { + "type": "object", + "description": "A webhook destination as listed in the webhook configuration, without delivery metrics. `GET /v3/webhook/destinations/{destination_uuid}/` adds its delivery summary.", + "required": [ + "uuid", + "label", + "url", + "enabled", + "webhook_version", + "secret_shared_key", + "subscribed_events", + "created_at", + "updated_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Destination ID." + }, + "label": { + "type": "string", + "maxLength": 255, + "description": "Human-readable name." + }, + "url": { + "type": "string", + "format": "uri", + "maxLength": 2000, + "description": "HTTPS endpoint Didit posts events to." + }, + "enabled": { + "type": "boolean", + "description": "`false` pauses deliveries to this destination." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload version sent to this destination." + }, + "secret_shared_key": { + "type": "string", + "description": "This destination's signing secret (43-character URL-safe token). Verify each webhook with the secret of the destination it was sent to. API-key callers always receive it in full; Console users without `read:webhooks` receive an empty string." + }, + "subscribed_events": { + "type": "array", + "uniqueItems": true, + "description": "Event types delivered to this destination. Empty when the destination was created without events, in which case it receives nothing.", + "items": { + "$ref": "#/components/schemas/WebhookEvent" + } + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "WebhookConfig": { + "type": "object", + "description": "Application-level webhook configuration, retention policy and Digital ID wallet return hosts, with every webhook destination.", + "required": [ + "webhook_url", + "webhook_version", + "secret_shared_key", + "capture_method", + "data_retention_months", + "face_retention_policy", + "face_retention_days", + "available_events", + "allowed_return_hosts", + "endpoints" + ], + "properties": { + "webhook_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Application-level (legacy) webhook URL. Didit sends to it only while the application has no enabled destination, and then only `status.updated` and `data.updated`. Every create, update or delete of a destination sets it to the URL of the oldest enabled destination, or `null` when none is enabled." + }, + "webhook_version": { + "type": "string", + "enum": [ + "v1", + "v2", + "v3" + ], + "description": "Payload version of the legacy `webhook_url`. Each destination has its own `webhook_version`." + }, + "secret_shared_key": { + "type": "string", + "description": "Application-level signing secret (43-character URL-safe token). It signs only deliveries to the legacy `webhook_url`; each destination signs with its own `secret_shared_key`. API-key callers always receive it in full; Console users without `read:webhooks` receive an empty string." + }, + "capture_method": { + "type": "string", + "enum": [ + "MOBILE", + "MOBILE_AND_DESKTOP" + ], + "description": "Legacy application setting. The verification flow does not read it: capture options are configured per workflow." + }, + "data_retention_months": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 120, + "description": "Automatic deletion window. User verification sessions created more than this many months ago (a month counts as 30 days) are deleted automatically, and that deletion is irreversible. `null` means sessions are never deleted automatically. See [Data retention](https://docs.didit.me/console/data-retention)." + }, + "face_retention_policy": { + "type": "string", + "enum": [ + "delete_with_session", + "retain_until_user_deleted" + ], + "description": "What happens to a session's face embedding when the session is deleted. `delete_with_session` (default) deletes it with the session. `retain_until_user_deleted` is the opt-in [biometric template retention](https://docs.didit.me/console/data-retention#biometric-template-retention): an image-free biometric template linked to the User is managed separately until its retention period ends, the User is deleted, or you purge it." + }, + "face_retention_days": { + "type": "integer", + "nullable": true, + "minimum": 1, + "maximum": 3650, + "description": "Retention period, in days, of biometric templates under `retain_until_user_deleted`. Required when that policy is set." + }, + "biometric_consent_mode": { + "type": "string", + "enum": [ + "auto", + "always", + "never" + ], + "description": "When the hosted flow and the native SDKs ask the person for explicit biometric consent before a face capture. `auto` (default) asks only where a trigger applies: a declared state, the person's IP, or the identity document's issuer is in Illinois, Texas or Colorado, or the person looks to be in the US without a known state. `always` asks every person whose workflow captures a face. `never` never shows the step, and you collect consent yourself before the selfie. Each change is recorded in the audit log. See [Biometric consent](https://docs.didit.me/getting-started/biometric-consent)." + }, + "available_events": { + "type": "array", + "description": "Every event type a destination can subscribe to, with a display label.", + "items": { + "type": "object", + "required": [ + "value", + "label" + ], + "properties": { + "value": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "label": { + "type": "string", + "description": "Display label, for example `Session status updated`." + } + } + } + }, + "allowed_return_hosts": { + "type": "array", + "maxItems": 20, + "description": "Hostnames (exact match, HTTPS only) a standalone Digital ID wallet verification may send the user back to with its `return_url`. Your white-label domain is always allowed as well. See [Register your return destination](https://docs.didit.me/standalone-apis/digital-id-wallets#register-your-return-destination).", + "items": { + "type": "string", + "maxLength": 253 + } + }, + "endpoints": { + "type": "array", + "description": "Every webhook destination of the application, oldest first, each with its signing secret. Read only here: manage destinations with the `/v3/webhook/destinations/` endpoints.", + "items": { + "$ref": "#/components/schemas/WebhookConfigDestination" + } + }, + "allowed_domains": { + "type": "array", + "maxItems": 50, + "items": { + "type": "string", + "maxLength": 300 + }, + "description": "Origins (for example `https://app.example.com`; `http` only for `localhost`) the hosted verification flow may be embedded in and may send the user back to with a session callback. Server-to-server calls are not affected. An empty list means no restriction. Didit stores each origin in lower case without a default port. An invalid entry fails with `400` on `allowed_domains`.", + "example": [ + "https://app.example.com" + ] + } + } + }, + "WebhookDestinationDelivery": { + "type": "object", + "description": "One delivery attempt to a webhook destination. The first send of an event, each automatic retry and each resend are separate deliveries.", + "required": [ + "uuid", + "session_id", + "request_url", + "response_status", + "response_time_ms", + "status", + "webhook_type", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Delivery ID, for the detail and resend calls." + }, + "session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "User verification session the event is about. `null` for business session, user, business, transaction, Travel Rule and workflow events." + }, + "request_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL the delivery was sent to: the destination's URL when the event was generated." + }, + "response_status": { + "type": "integer", + "nullable": true, + "description": "HTTP status your endpoint returned. `null` while the delivery is queued and not yet attempted. When no HTTP answer was received Didit records a status of its own: `504` when your endpoint did not answer within 5 seconds, `503` for a connection failure, `400` when the URL was refused as unsafe (for example a private or internal address), and `404` for any other request error. `400` or above counts as a failed delivery." + }, + "response_time_ms": { + "type": "integer", + "nullable": true, + "minimum": 0, + "description": "Time the attempt took, in milliseconds. `null` while the delivery is queued." + }, + "status": { + "type": "string", + "nullable": true, + "description": "Status the event reported when it was generated: the session status for session events (for example `Approved` or `In Review`), the person's or company's status for user and business events, and the transaction status for transaction events. `null` for events that carry no status." + }, + "webhook_type": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the delivery was created." + } + } + }, + "WebhookDestinationDeliveryDetail": { + "type": "object", + "description": "One delivery attempt to a webhook destination, with the exact request headers and body Didit sent.", + "required": [ + "uuid", + "session_id", + "request_url", + "response_status", + "response_time_ms", + "status", + "webhook_type", + "created_at", + "request_header", + "request_body" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Delivery ID, for the detail and resend calls." + }, + "session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "User verification session the event is about. `null` for business session, user, business, transaction, Travel Rule and workflow events." + }, + "request_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL the delivery was sent to: the destination's URL when the event was generated." + }, + "response_status": { + "type": "integer", + "nullable": true, + "description": "HTTP status your endpoint returned. `null` while the delivery is queued and not yet attempted. When no HTTP answer was received Didit records a status of its own: `504` when your endpoint did not answer within 5 seconds, `503` for a connection failure, `400` when the URL was refused as unsafe (for example a private or internal address), and `404` for any other request error. `400` or above counts as a failed delivery." + }, + "response_time_ms": { + "type": "integer", + "nullable": true, + "minimum": 0, + "description": "Time the attempt took, in milliseconds. `null` while the delivery is queued." + }, + "status": { + "type": "string", + "nullable": true, + "description": "Status the event reported when it was generated: the session status for session events (for example `Approved` or `In Review`), the person's or company's status for user and business events, and the transaction status for transaction events. `null` for events that carry no status." + }, + "webhook_type": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the delivery was created." + }, + "request_header": { + "type": "string", + "description": "JSON text (a string) of the request headers Didit sent: `Content-Type`, `X-Signature`, `X-Signature-V2`, `X-Signature-Simple`, `X-Timestamp` and `User-Agent`. Parse it with a JSON parser. It holds signatures, never the signing secret." + }, + "request_body": { + "type": "string", + "description": "JSON text (a string) of the exact payload Didit sent, keys sorted, including `event_id` and `timestamp`. Parse it with a JSON parser; see [Payload shape](https://docs.didit.me/integration/webhooks#payload-shape). For a scoped key without `media: read`, media URLs inside it are `null`; without `sessions: write`, session tokens and hosted verification links are `null`. A body changed that way no longer matches its signatures. Didit does not record the response body your endpoint returned." + } + } + }, + "SessionWebhookDelivery": { + "type": "object", + "description": "One webhook delivery attempt recorded for a verification session, with the exact request headers and body Didit sent.", + "required": [ + "uuid", + "session_id", + "request_url", + "response_status", + "status", + "webhook_type", + "request_header", + "request_body", + "created_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Delivery ID. Use it as `{webhook_uuid}` to get or resend this delivery." + }, + "session_id": { + "type": "string", + "format": "uuid", + "description": "The session the delivery belongs to (the session in the path)." + }, + "request_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "URL the delivery was sent to." + }, + "response_status": { + "type": "integer", + "nullable": true, + "description": "HTTP status your endpoint returned. `null` while the delivery is queued and not yet attempted. When no HTTP answer was received Didit records a status of its own: `504` when your endpoint did not answer within 5 seconds, `503` for a connection failure, `400` when the URL was refused as unsafe (for example a private or internal address), and `404` for any other request error. `400` or above counts as a failed delivery." + }, + "status": { + "type": "string", + "nullable": true, + "description": "Session status when the event was generated, for example `In Progress`, `Approved` or `Declined`." + }, + "webhook_type": { + "$ref": "#/components/schemas/WebhookEvent" + }, + "request_header": { + "type": "string", + "description": "JSON text (a string) of the request headers Didit sent: `Content-Type`, `X-Signature`, `X-Signature-V2`, `X-Signature-Simple`, `X-Timestamp` and `User-Agent`. Parse it with a JSON parser. It holds signatures, never the signing secret." + }, + "request_body": { + "type": "string", + "description": "JSON text (a string) of the exact payload Didit sent, keys sorted, including `event_id` and `timestamp`. Parse it with a JSON parser; see [Payload shape](https://docs.didit.me/integration/webhooks#payload-shape). For a scoped key without `media: read`, media URLs inside it are `null`; without `sessions: write`, session tokens and hosted verification links are `null`. A body changed that way no longer matches its signatures. Didit does not record the response body your endpoint returned." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the delivery was created." + } + } + }, + "SessionEvent": { + "type": "object", + "description": "One entry of a verification session's event timeline.", + "required": [ + "uuid", + "event_type", + "created_at", + "component", + "feature_step", + "step", + "details", + "ip_address", + "user_agent", + "device_info", + "file_url", + "sent_at" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Event identifier." + }, + "event_type": { + "type": "string", + "enum": [ + "SESSION_CREATED", + "SESSION_COMPLETED", + "SESSION_STATUS_CHANGED", + "LINK_OPENED", + "FORM_SUBMITTED", + "BUTTON_CLICKED", + "FILE_UPLOADED", + "ATTEMPT_FAILED", + "FEATURE_STEP_CHANGED", + "STEP_RENDERED", + "NOTE_ADDED", + "MANUAL_REVIEW_STARTED", + "WORKFLOW_BRANCH_EVALUATED", + "WORKFLOW_STATUS_NODE_REACHED", + "LOG" + ], + "description": "What happened. Reported by the device during the hosted flow or SDK: `LINK_OPENED`, `STEP_RENDERED`, `BUTTON_CLICKED`, `FORM_SUBMITTED`, `FILE_UPLOADED`, `ATTEMPT_FAILED` and `LOG` (debugging). Recorded by the server: `SESSION_CREATED`, `SESSION_STATUS_CHANGED` (`details.old_status`, `details.current_status`), `FEATURE_STEP_CHANGED` (`details.old_feature_step`, `details.current_feature_step`), `WORKFLOW_BRANCH_EVALUATED` and `WORKFLOW_STATUS_NODE_REACHED` (the workflow routing trace). `SESSION_COMPLETED`, `NOTE_ADDED` and `MANUAL_REVIEW_STARTED` can appear on older sessions." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When Didit stored the event. Events are stored asynchronously, a few seconds after `sent_at`." + }, + "component": { + "type": "string", + "nullable": true, + "description": "UI component that reported the event (device events only)." + }, + "feature_step": { + "type": "string", + "nullable": true, + "enum": [ + "OCR", + "OCR_BACK", + "NFC", + "AML", + "FACE", + "LIVENESS", + "FACE_MATCH", + "IP_ANALYSIS", + "AGE_ESTIMATION", + "PROOF_OF_ADDRESS", + "PHONE_VERIFICATION", + "EMAIL_VERIFICATION", + "FACE_SEARCH", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "DOCUMENT_AI", + "GEOLOCATION", + null + ], + "description": "Verification feature the session was on when the event happened." + }, + "step": { + "type": "string", + "nullable": true, + "description": "Screen within the feature, for example `DOCUMENT_CAPTURE_FRONT` or `FACE_PASSIVE_LIVENESS`." + }, + "details": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Event-specific data. The keys depend on `event_type`." + }, + "ip_address": { + "type": "string", + "nullable": true, + "description": "IP address the device reported the event from. `null` for server-recorded events." + }, + "user_agent": { + "type": "string", + "nullable": true, + "description": "User agent of the device. `null` for server-recorded events." + }, + "device_info": { + "type": "object", + "nullable": true, + "additionalProperties": true, + "description": "Device details the SDK or browser reported." + }, + "file_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Short-lived presigned link to a file attached to the event (for example a captured image). `null` when there is none, and always `null` for a scoped API key without `media: read`." + }, + "sent_at": { + "type": "integer", + "format": "int64", + "description": "When the event happened, as a Unix timestamp in milliseconds. Results are ordered by it." + } + } + }, + "AMLDatasetSourceFreshness": { + "type": "object", + "description": "Freshness of one family of AML datasets.", + "required": [ + "last_updated_at", + "oldest_updated_at", + "dataset_count", + "dated_dataset_count" + ], + "properties": { + "last_updated_at": { + "type": "string", + "format": "date-time", + "description": "Most recent update of any dataset in the family." + }, + "oldest_updated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Staleness bound: every dataset in the family is at least this current. `null` when at least one dataset in the family reported no update date, because the bound is then unknown." + }, + "dataset_count": { + "type": "integer", + "description": "Number of datasets in the family." + }, + "dated_dataset_count": { + "type": "integer", + "description": "Number of those datasets that reported an update date." + } + } + }, + "AMLDatasetFreshness": { + "type": "object", + "description": "How current the AML datasets were when a screening ran. Dates are never estimated.", + "required": [ + "captured_at", + "provider", + "catalog_version", + "sources" + ], + "properties": { + "captured_at": { + "type": "string", + "format": "date-time", + "description": "When Didit read the dataset catalog this freshness comes from." + }, + "provider": { + "type": "string", + "description": "Opaque identifier of the AML data source. Treat it as an opaque string; do not branch on it.", + "example": "aml_data_source" + }, + "catalog_version": { + "type": "string", + "nullable": true, + "description": "Version identifier of the dataset catalog, when the source reports one." + }, + "sources": { + "type": "object", + "description": "Freshness per dataset family. A family whose datasets reported no update date is absent rather than filled with `null`.", + "properties": { + "sanctions": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" + }, + "pep": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" + }, + "warnings": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" + }, + "adverse_media": { + "$ref": "#/components/schemas/AMLDatasetSourceFreshness" + } + } + } + } + }, + "AMLScreeningRun": { + "type": "object", + "description": "One completed ongoing-monitoring re-screen of an AML check.", + "required": [ + "uuid", + "run_id", + "completed_at", + "started_at", + "outcome", + "trigger", + "provider", + "status", + "score", + "total_hits", + "new_hits", + "entities_screened", + "dataset_freshness" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Identifier of this screening run." + }, + "run_id": { + "type": "string", + "format": "uuid", + "description": "The monitoring run this screening belongs to. Every verification screened in the same run shares it." + }, + "completed_at": { + "type": "string", + "format": "date-time", + "description": "When this subject's re-screen finished. Results are ordered by it, newest first." + }, + "started_at": { + "type": "string", + "format": "date-time", + "description": "When the monitoring batch this re-screen belongs to started." + }, + "outcome": { + "type": "string", + "enum": [ + "NO_CHANGE", + "CHANGED", + "FAILED" + ], + "description": "`NO_CHANGE`: completed and the result did not move. `CHANGED`: completed and the score, status, hit count or hits changed. `FAILED`: no usable answer for this subject, so the check was left as it was." + }, + "trigger": { + "type": "string", + "enum": [ + "ONGOING_MONITORING" + ], + "description": "What started the screening. Always `ONGOING_MONITORING` today." + }, + "provider": { + "type": "string", + "description": "Opaque identifier of the AML data source that answered. Treat it as an opaque string; do not branch on it.", + "example": "aml_data_source" + }, + "status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested", + null + ], + "description": "AML check status this run left the check in." + }, + "score": { + "type": "number", + "format": "float", + "nullable": true, + "minimum": 0, + "maximum": 100, + "description": "AML score this run left the check in." + }, + "total_hits": { + "type": "integer", + "nullable": true, + "description": "Number of hits this run left the check in." + }, + "new_hits": { + "type": "integer", + "nullable": true, + "description": "Number of hits this run found that the check did not have before it. `0` when nothing new was found; `null` when the run failed or was recorded before this count existed." + }, + "entities_screened": { + "type": "integer", + "minimum": 0, + "maximum": 1, + "description": "Number of entities this run screened for the verification: `1` when the subject was screened, `0` when the run failed." + }, + "dataset_freshness": { + "allOf": [ + { + "$ref": "#/components/schemas/AMLDatasetFreshness" + } + ], + "nullable": true, + "description": "Dataset freshness behind this run, or `null` when no dates were available." + } + } + }, + "KYBKeyPeopleParty": { + "type": "object", + "description": "A Key People party of a Business Verification (KYB) session, with its child verification. The same party appears in `key_people_checks[].submitted.parties[]` of the business session decision.", + "required": [ + "uuid", + "entity_type", + "source", + "name", + "is_active", + "is_skipped", + "requires_verification", + "roles", + "kyc_session_deleted", + "kyb_sub_session_deleted" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Party identifier." + }, + "entity_type": { + "type": "string", + "enum": [ + "person", + "company" + ], + "description": "Whether the party is an individual or a company." + }, + "source": { + "type": "string", + "enum": [ + "REGISTRY", + "USER" + ], + "description": "`REGISTRY`: taken from the company registry. `USER`: entered in the Key People screen." + }, + "name": { + "type": "string", + "description": "Display name of the party." + }, + "first_name": { + "type": "string", + "nullable": true, + "description": "First name (persons)." + }, + "last_name": { + "type": "string", + "nullable": true, + "description": "Last name (persons)." + }, + "company_name": { + "type": "string", + "nullable": true, + "description": "Company name (company parties)." + }, + "registration_number": { + "type": "string", + "nullable": true, + "description": "Company registration number (company parties)." + }, + "email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email the verification link is sent to." + }, + "nationality": { + "type": "string", + "nullable": true, + "description": "Nationality for persons, country of incorporation for companies." + }, + "phone_number": { + "type": "string", + "nullable": true, + "description": "Phone number collected for the party." + }, + "date_of_birth": { + "type": "string", + "format": "date", + "nullable": true, + "description": "Date of birth (persons)." + }, + "position": { + "type": "string", + "nullable": true, + "description": "Free-text job title (persons)." + }, + "custom_fields": { + "type": "object", + "additionalProperties": true, + "description": "Workflow-defined custom fields collected for the party, keyed by field key." + }, + "is_active": { + "type": "boolean", + "description": "Whether the party is active." + }, + "is_skipped": { + "type": "boolean", + "description": "Whether the party is exempt from verification. Regenerating clears it." + }, + "requires_verification": { + "type": "boolean", + "description": "Whether the role and ownership rules require a verification for this party." + }, + "roles": { + "type": "array", + "description": "Roles the party holds in the company.", + "items": { + "type": "object", + "required": [ + "role", + "ownership_percent", + "voting_percent" + ], + "properties": { + "role": { + "type": "string", + "enum": [ + "director", + "non_executive_director", + "secretary", + "chairman", + "ubo", + "shareholder", + "representative", + "founder", + "legal_advisor", + "authorized_signatory", + "trustee", + "beneficiary", + "settlor", + "protector", + "company_officer", + "investor", + "other" + ], + "description": "Role code." + }, + "ownership_percent": { + "type": "number", + "nullable": true, + "description": "Ownership percentage for this role, when known." + }, + "voting_percent": { + "type": "number", + "nullable": true, + "description": "Voting-rights percentage for this role, when known." + } + } + } + }, + "kyc_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Child individual KYC session (persons)." + }, + "kyc_session_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User", + null + ], + "description": "Status of the child KYC session. A deleted session keeps the status it had when it was deleted." + }, + "kyc_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Hosted link of the child KYC session. `null` when there is none or it was deleted, and for a scoped API key without `sessions: write`." + }, + "kyc_session_deleted": { + "type": "boolean", + "description": "The child KYC session was deleted, so it can never settle." + }, + "kyb_sub_session_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Child KYB session (company parties)." + }, + "kyb_sub_session_status": { + "type": "string", + "nullable": true, + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User", + null + ], + "description": "Status of the child KYB session." + }, + "kyb_sub_session_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Hosted link of the child KYB session, with the same `null` rules as `kyc_session_url`." + }, + "kyb_sub_session_deleted": { + "type": "boolean", + "description": "Company-party counterpart of `kyc_session_deleted`." + }, + "verification_workflow_type": { + "type": "string", + "nullable": true, + "enum": [ + "kyc", + "kyb", + "adaptive_age_verification", + "biometric_authentication", + "address_verification", + "questionnaire_verification", + "email_verification", + "phone_verification", + null + ], + "description": "Type of the workflow the child session runs." + }, + "verification_workflow_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Workflow the child session runs." + }, + "verification_workflow_label": { + "type": "string", + "nullable": true, + "description": "Label of that workflow." + }, + "verification_workflow_features": { + "type": "array", + "nullable": true, + "description": "Features of the child session's workflow with their current status.", + "items": { + "type": "object", + "required": [ + "feature", + "status" + ], + "properties": { + "feature": { + "type": "string", + "description": "Feature code, for example `ID_VERIFICATION` or `LIVENESS`." + }, + "status": { + "type": "string", + "description": "Feature status, `Not Finished` until it runs." + } + } + } + }, + "didit_internal_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "Didit's identifier of the verified person, once known." + } + } + }, + "TravelRuleDelivery": { + "type": "object", + "description": "Where the latest message the transfer owes the other side stands. Didit contacts the counterparty after the request that created or changed the transfer has returned, so this object says whether that message got through: queued, being sent, waiting to be retried after an error, delivered, or failed. A failure the network may still recover from (no answer in time, a failed connection, HTTP `408`, `429` or `5xx`) is tried again after 30 seconds, 2 minutes, 10 minutes, 30 minutes and 1 hour. When a first message (`kind: SEND`) fails for good, because the network refused it or the sixth attempt failed too, the transfer moves to `COUNTERPARTY_VASP_NOT_REACHABLE` with `last_error` in its `timeline`; any other message that fails for good is noted in the `timeline`. The transfer's `status` stays the source of truth for the exchange itself.", + "required": [ + "kind", + "status", + "rail", + "attempts", + "last_error", + "next_attempt_at", + "delivered_at" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "SEND", + "ANSWER", + "TXID", + "CANCEL", + "POLL" + ], + "description": "What the message carries. `SEND`: the transfer's first message to the counterparty (outbound; the rail is chosen when it is sent). `ANSWER`: your accept or decline, owed to the network that asked (inbound). `TXID`: the on-chain transaction hash, after you finish the transfer. `CANCEL`: the cancellation, after you cancel it. `POLL`: a status query to a network that offers one, repeated every 5 minutes while the transfer waits on the counterparty." + }, + "status": { + "type": "string", + "enum": [ + "QUEUED", + "SENDING", + "RETRYING", + "DELIVERED", + "FAILED" + ], + "description": "`QUEUED`: waiting to be sent. `SENDING`: an attempt is running. `RETRYING`: an attempt failed and the next one runs at `next_attempt_at`. `DELIVERED`: the other side took the message. `FAILED`: the other side refused it, or the last attempt failed." + }, + "rail": { + "type": "string", + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL", + "" + ], + "description": "The rail that carries the message. Empty until Didit has chosen one for a first message." + }, + "attempts": { + "type": "integer", + "minimum": 0, + "description": "Attempts made so far for this message. A message that is not a status poll is attempted at most 6 times." + }, + "last_error": { + "type": "string", + "description": "The kind of failure the last attempt hit, `\"\"` when none did: for example `email undeliverable`, `trp identity endpoint unreachable` or `trp transfer inquiry delivery failed`. It never carries the counterparty's own answer, which stays on the transfer's `timeline`.\nWhen GTR answers with an error, its own status code and message are included. Response bodies are never included.", + "example": "email undeliverable" + }, + "next_attempt_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the next attempt runs while the message is `QUEUED` or `RETRYING`." + }, + "delivered_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the other side took the message." + } + } + }, + "TravelRuleDiscoveryStep": { + "type": "object", + "description": "One step of the counterparty discovery Didit ran for an outbound transfer, in the order it ran them. The first step that names a VASP with a route ends the chain.", + "properties": { + "step": { + "type": "string", + "enum": [ + "internal_address_book", + "wallet_attribution", + "network_lookup", + "customer_hint", + "end_user" + ], + "description": "`internal_address_book`: another Didit customer that proved control of the destination wallet. `wallet_attribution`: the wallet's blockchain analytics attribution. `network_lookup`: a network that can say which VASP holds the address. `customer_hint`: the VASP name, LEI or jurisdiction you sent in `beneficiary_data`. `end_user`: the counterparty the end user (or you) picked, or the request to pick one." + }, + "outcome": { + "type": "string", + "enum": [ + "match", + "miss", + "skipped" + ], + "description": "`match`: the step named a counterparty. `miss`: it ran and found none. `skipped`: it could not run, and `reason` says why." + }, + "reason": { + "type": "string", + "description": "Machine-readable reason, `\"\"` on a match: `no_proven_internal_wallet`, `attribution_disabled`, `already_attributed`, `no_attributed_entity`, `directory_match_no_route`, `gtr_address_lookup_runs_on_dispatch`, `no_membership`, `network_switched_off`, `no_network_address_lookup`, `no_directory_match`, `awaiting_end_user` or `picked_member_without_proven_wallet`." + }, + "vasp": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The directory VASP the step named, when it named one." + }, + "rail": { + "type": "string", + "nullable": true, + "description": "The rail that reaches that counterparty, when the step found one." + }, + "at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleNextAction": { + "type": "object", + "description": "One thing that moves a `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE` transfer on. See [Why a transfer is stuck](/transaction-monitoring/travel-rule#why-a-transfer-is-stuck).", + "properties": { + "action": { + "type": "string", + "enum": [ + "select_counterparty_vasp", + "declare_self_hosted", + "add_vasp_hint", + "update_compliance_email", + "resend" + ], + "description": "`select_counterparty_vasp`: the end user picks the destination VASP in the wallet-ownership widget, or you PATCH `action=beneficiary_vasp`. `declare_self_hosted`: the end user declares the wallet self-hosted in the widget. `add_vasp_hint`: tell Didit who the counterparty is, with a VASP name, LEI or travel address (for example a `travel_address` on the destination wallet's address-book entry), then resend. `update_compliance_email` and `resend` are `action` values of `PATCH /v3/transactions/{transaction_id}/travel-rule/`: give the counterparty's compliance email again, and re-run routing." + }, + "url": { + "type": "string", + "format": "uri", + "description": "The wallet-ownership widget link, on `select_counterparty_vasp` and `declare_self_hosted` while the transfer has an open widget session." + } + }, + "required": [ + "action" + ] + }, + "TransactionTravelRule": { + "type": "object", + "description": "A transaction's Travel Rule transfer, as `GET /v3/transactions/{transaction_id}/`, the `201` of `POST /v3/transactions/` and each row of `GET /v3/transactions/` return it. `direction`, `rail`, `counterparty_vasp`, `delivery`, `discovery`, `reason` and `next_actions` have the same shape as on `PATCH /v3/transactions/{transaction_id}/travel-rule/`, so one read of a transaction says why its transfer is stuck and what moves it on.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string" + }, + "protocol": { + "type": "string", + "nullable": true + }, + "required": { + "type": "boolean" + }, + "obligations_count": { + "type": "integer" + }, + "originator_data": { + "type": "object", + "description": "Originator party. For an inbound transfer the counterparty's values are masked until ownership is confirmed." + }, + "beneficiary_data": { + "type": "object", + "description": "Beneficiary party. For an inbound transfer the counterparty's values are masked until ownership is confirmed." + }, + "name_match": { + "type": "object", + "nullable": true, + "description": "The name comparison that decided the transfer (score and strictness). Null until one ran. The compared names are masked together with the parties." + }, + "data_match": { + "$ref": "#/components/schemas/TravelRuleDataMatch" + }, + "mismatched_fields": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Shortcut for `data_match.mismatched_fields`. Empty when nothing differs." + }, + "data_unmasked": { + "$ref": "#/components/schemas/TravelRuleDataUnmasked" + }, + "data_masked": { + "type": "boolean", + "description": "True while the counterparty's personal data is masked in this response." + }, + "metadata": { + "type": "object" + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ], + "description": "Which side of the exchange this transfer is." + }, + "rail": { + "type": "string", + "nullable": true, + "description": "The rail the transfer went over: `INTERNAL`, `TRP`, `GTR`, `TRUST`, `VERIFYVASP`, `SYGNA` or `EMAIL` (`TEST_VASP` on a sandbox application, when the Didit Test VASP answered), or `null` until a rail is chosen." + }, + "counterparty_vasp": { + "type": "object", + "nullable": true, + "properties": { + "name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "dd_score": { + "type": "integer", + "description": "Due-diligence score from 0 to 100, where higher is better. Same scale as in the VASP directory." + }, + "dd_score_breakdown": { + "type": "object", + "description": "Per-component contributions to dd_score, one object per component as in the VASP directory (jurisdiction, verification, protocol, lei, history, essentials, responsiveness), each {points, max, reason, detail}. {} until the counterparty has been assessed.", + "additionalProperties": { + "type": "object", + "properties": { + "points": { + "type": "number", + "description": "Points awarded for this component." + }, + "max": { + "type": "integer", + "description": "Maximum points this component can award." + }, + "reason": { + "type": "string", + "description": "Machine-readable code of the component's verdict." + }, + "detail": { + "type": "string", + "description": "Human-readable reason for the awarded points." + } + } + } + }, + "dd_assessed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the due-diligence assessment last ran. Null until the counterparty has been assessed." + } + }, + "description": "The counterparty's directory entry, or `null` when the transfer links none." + }, + "delivery": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleDelivery" + } + ], + "description": "The newest message the transfer owes another party. `null` when it has owed none, for example below `threshold_amount` or for a declared self-hosted wallet." + }, + "discovery": { + "type": "array", + "description": "How Didit looked for the counterparty, one entry per step that ran. Empty when no discovery ran, for example on an inbound transfer.", + "items": { + "$ref": "#/components/schemas/TravelRuleDiscoveryStep" + } + }, + "reason": { + "type": "string", + "description": "Why a `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE` transfer is stuck; `\"\"` for any other status. A failed delivery decides it first, on `COUNTERPARTY_VASP_NOT_REACHABLE` only: `email_undeliverable` (the mail provider refused the compliance email and no address is left to try: when it was sent, on a reminder, or as a permanent bounce reported later) or `delivery_failed` (the rail that was delivering failed for good: a network rail refused the message or ran out of retries, a `TRP` identity host did not answer, or the counterparty's `TRP` host did not accept the inquiry). Otherwise the `discovery` trace decides it: `awaiting_end_user` (no step named a counterparty Didit can reach), `directory_match_no_route` (a step named a directory VASP that has no route), `picked_member_without_proven_wallet` (the end user picked a Didit customer that has not proven control of the wallet), or `no_directory_match` when the trace gives no other reason. A resend moves the transfer on, so the reason clears. See [Why a transfer is stuck](/transaction-monitoring/travel-rule#why-a-transfer-is-stuck)." + }, + "next_actions": { + "type": "array", + "description": "What moves the transfer on; `[]` for any status but `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE`. `email_undeliverable` offers `update_compliance_email`, then `resend`. `delivery_failed` offers `resend`. A discovery reason offers `select_counterparty_vasp`, `declare_self_hosted`, `add_vasp_hint` and `resend`.", + "items": { + "$ref": "#/components/schemas/TravelRuleNextAction" + } + }, + "chain_details": { + "$ref": "#/components/schemas/TravelRuleChainDetails" + } + } + }, + "NetworkListCounts": { + "type": "object", + "description": "Network counts for the list's views and filter options, each network counted once. Returned only when `summary` is not `false`. `scopes` count every network visible to the application. `views` count the visible networks inside the selected `scope`. `facets` count the networks in the selected `scope` and `status` view (dismissed networks are left out unless `status` includes them). The other list filters do not change these counts.", + "properties": { + "scopes": { + "type": "object", + "description": "Visible networks by product.", + "properties": { + "all": { + "type": "integer" + }, + "identity": { + "type": "integer", + "description": "Every other network." + }, + "transactions": { + "type": "integer", + "description": "Networks whose members are all transaction parties." + } + } + }, + "views": { + "type": "object", + "description": "Visible networks inside the selected `scope`, by status, plus two saved views.", + "properties": { + "all": { + "type": "integer" + }, + "active": { + "type": "integer" + }, + "in_review": { + "type": "integer" + }, + "resolved": { + "type": "integer" + }, + "dismissed": { + "type": "integer" + }, + "mine": { + "type": "integer", + "description": "Networks assigned to the calling console member. Always 0 for an API key." + }, + "high_risk": { + "type": "integer", + "description": "Networks with a risk score of 80 or more that are not dismissed." + } + } + }, + "facets": { + "type": "object", + "description": "Counts for each filter option, inside the selected `scope` and status view.", + "properties": { + "total": { + "type": "integer", + "description": "Networks in the selected `scope` and status view." + }, + "last_activity_days": { + "type": "object", + "description": "Networks with activity in the last 7, 30 and 90 days, keyed by the number of days.", + "properties": { + "7": { + "type": "integer" + }, + "30": { + "type": "integer" + }, + "90": { + "type": "integer" + } + } + }, + "risk_min": { + "type": "object", + "description": "Networks with a risk score of at least 50 and at least 80, keyed by the threshold.", + "properties": { + "50": { + "type": "integer" + }, + "80": { + "type": "integer" + } + } + }, + "signal_types": { + "type": "object", + "description": "Networks with at least one signal of each type.", + "properties": { + "ip_address": { + "type": "integer" + }, + "device": { + "type": "integer" + }, + "face": { + "type": "integer" + }, + "document_number": { + "type": "integer" + }, + "phone": { + "type": "integer" + }, + "email": { + "type": "integer" + }, + "address": { + "type": "integer" + } + } + }, + "priority": { + "type": "object", + "description": "Networks by priority. `none` counts networks without a priority.", + "properties": { + "low": { + "type": "integer" + }, + "medium": { + "type": "integer" + }, + "high": { + "type": "integer" + }, + "none": { + "type": "integer" + } + } + }, + "assignee": { + "type": "object", + "description": "Networks by assignee.", + "properties": { + "me": { + "type": "integer", + "description": "Assigned to the calling console member. Always 0 for an API key." + }, + "unassigned": { + "type": "integer", + "description": "Assigned to nobody." + }, + "users": { + "type": "array", + "description": "One entry per assignee, most networks first.", + "items": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "full_name": { + "type": "string", + "description": "The assignee's name, or their email when no name is set." + }, + "email": { + "type": "string" + }, + "count": { + "type": "integer" + } + } + } + } + } + }, + "has_case": { + "type": "integer", + "description": "Networks this application opened a case for." + }, + "min_size": { + "type": "object", + "description": "Networks with at least 10 members, keyed by the minimum size.", + "properties": { + "10": { + "type": "integer" + } + } + } + } + } + } + }, + "NetworkTag": { + "type": "object", + "description": "One of your application's tags on the network.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "The tag's UUID, the same value you pass as `tag_uuid`." + }, + "name": { + "type": "string" + }, + "color": { + "type": "string", + "description": "Hex color, for example `#6366F1`." + }, + "source": { + "type": "string", + "enum": [ + "didit", + "custom" + ], + "description": "`didit` for a Didit preset tag, `custom` for a tag your team created." + }, + "added_by": { + "type": "string", + "nullable": true, + "description": "Email of the console member who added the tag. Null when an API key added it." + }, + "added_at": { + "type": "string", + "format": "date-time" + } + } + }, + "NetworkOutcomes": { + "type": "object", + "description": "Where the network's people and businesses stand. Each member you may see counts once, by the status of their most recent verification that reached a decision. Transaction parties are not counted.", + "properties": { + "approved": { + "type": "integer", + "description": "Members whose most recent decided verification was approved." + }, + "declined": { + "type": "integer", + "description": "Members whose most recent decided verification was declined." + }, + "in_review": { + "type": "integer", + "description": "Members whose most recent decided verification is in review." + }, + "total": { + "type": "integer", + "description": "Members with at least one verification, decided or not. A member whose verifications are all undecided (not started, in progress, expired or abandoned) counts here and in no other key." + } + } + }, + "NetworkPatternScore": { + "type": "object", + "description": "How alike one kind of the members' data is across the whole network. A pattern is not a signal: no shared value links the members.", + "properties": { + "type": { + "type": "string", + "enum": [ + "background" + ], + "description": "`background`: the mean similarity of every pair of member selfie backgrounds, with the person masked out. It reads the newest selfie of each person, at most the 50 newest, and leaves out age assurance sessions. It covers the whole network, not only the members of your applications." + }, + "score": { + "type": "number", + "nullable": true, + "minimum": 0, + "maximum": 1, + "description": "From 0 to 1, rounded to 4 decimals. Null when `reason` is set." + }, + "members": { + "type": "integer", + "nullable": true, + "description": "How many members' selfies the score compared. A selfie that cannot be read, or shows too little background, is not compared. Null while `computing`; 0 or 1 with `not_enough_selfies`." + }, + "reason": { + "type": "string", + "enum": [ + "computing", + "not_enough_selfies" + ], + "nullable": true, + "description": "Why there is no score. `computing`: the score for the current members is being computed; read the network again later. `not_enough_selfies`: fewer than two members have a usable selfie. Null when there is a score." + }, + "computed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the score was computed. Null while `computing`." + } + } + }, + "NetworkDetailResponse": { + "description": "The network detail, plus the organization's sharing mode, whether cross-organization signals apply to it, your application's tags on the network, member outcomes and pattern scores.", + "allOf": [ + { + "$ref": "#/components/schemas/NetworkDetail" + }, + { + "type": "object", + "properties": { + "sharing_mode": { + "type": "string", + "enum": [ + "off", + "private", + "global" + ], + "description": "The organization's cross-organization sharing mode. Only `global` compares networks across organizations; `private` and `off` keep them to the organization's own history.", + "example": "global" + }, + "cross_org_available": { + "type": "boolean", + "description": "Whether cross-organization signals apply to this organization. They need both the Enterprise plan and the `global` sharing mode.", + "example": true + }, + "cross_org_unavailable_reason": { + "type": "string", + "enum": [ + "plan", + "sharing_mode" + ], + "nullable": true, + "description": "Why cross-organization signals do not apply, checked in this order. `plan`: the organization's plan does not include the cross-organization fraud network (it needs the Enterprise plan), whatever the sharing mode. `sharing_mode`: the plan includes it, but the sharing mode is `private` or `off`. Null when `cross_org_available` is `true`.", + "example": null + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkTag" + }, + "description": "Your application's tags on the network, oldest first. Each application sees only its own tags. `fraud_tags` is separate and unchanged: the tags the network's members carry." + }, + "outcomes": { + "$ref": "#/components/schemas/NetworkOutcomes" + }, + "patterns": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkPatternScore" + }, + "description": "Pattern scores over the whole network. Today one item, `background`." + } + } + } + ] + }, + "NetworkTagRequest": { + "type": "object", + "required": [ + "tag_uuid" + ], + "properties": { + "tag_uuid": { + "type": "string", + "format": "uuid", + "description": "UUID of one of your application's tags." + } + }, + "example": { + "tag_uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7" + } + }, + "NetworkTimeline": { + "title": "Default timeline (no contract)", + "type": "object", + "description": "Without `contract`: the network's own events, oldest first, not paginated.", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkTimelineEvent" + } + } + } + }, + "NetworkTimelinePage": { + "title": "Typed timeline page (contract=2)", + "type": "object", + "description": "With `contract=2`: one page of typed events, newest first.", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkTypedTimelineEvent" + } + }, + "next_cursor": { + "type": "string", + "nullable": true, + "description": "Pass as `cursor` to read the next page. Null on the last page." + } + } + }, + "NetworkTypedTimelineEvent": { + "type": "object", + "description": "One typed timeline event: the four default fields, plus its id, when Didit stored it, the member account and record it belongs to, and the detail block of its event type. The other detail blocks are null.", + "properties": { + "id": { + "type": "string", + "description": "`:`, unique within the timeline.", + "example": "session:3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "event_type": { + "type": "string", + "enum": [ + "network_detected", + "signal_observed", + "network_active", + "network_in_review", + "network_resolved", + "network_dismissed", + "verification_attempt", + "transaction", + "login", + "rule_hit", + "case_linked", + "note_added" + ] + }, + "occurred_at": { + "type": "string", + "format": "date-time", + "description": "When it happened." + }, + "ingested_at": { + "type": "string", + "format": "date-time", + "description": "When Didit stored it." + }, + "label": { + "type": "string", + "description": "A short English label, for example `Verification attempt 2 of 3`." + }, + "metadata": { + "type": "object", + "description": "For the network's own events, the same metadata as the default timeline. Empty otherwise." + }, + "account": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineAccount" + } + ], + "nullable": true, + "description": "The member account. Null for the network's own events, `case_linked` and `note_added`." + }, + "source_ref": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineSourceRef" + } + ], + "nullable": true, + "description": "The source record. Null for the network's own events and `note_added`." + }, + "verification_attempt": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineAttempt" + } + ], + "nullable": true, + "description": "Set on `verification_attempt` events." + }, + "transaction": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineTransaction" + } + ], + "nullable": true, + "description": "Set on `transaction` events." + }, + "login": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineLogin" + } + ], + "nullable": true, + "description": "Set on `login` events." + }, + "rule_hit": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineRuleHit" + } + ], + "nullable": true, + "description": "Set on `rule_hit` events." + }, + "case_linked": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineCaseLinked" + } + ], + "nullable": true, + "description": "Set on `case_linked` events." + }, + "note_added": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineNoteAdded" + } + ], + "nullable": true, + "description": "Set on `note_added` events. Never returned to an API key." + } + } + }, + "NetworkTimelineAccount": { + "type": "object", + "description": "The member account the event belongs to.", + "properties": { + "node_id": { + "type": "string", + "description": "`:`, where kind is `vendor_user`, `vendor_business` or `transaction_party`.", + "example": "vendor_user:3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "node_type": { + "type": "string", + "enum": [ + "account", + "business_account", + "transaction_party" + ] + }, + "uuid": { + "type": "string", + "format": "uuid" + }, + "vendor_data": { + "type": "string", + "nullable": true + }, + "display_name": { + "type": "string", + "nullable": true + }, + "ownership": { + "type": "string", + "enum": [ + "owned", + "organization" + ], + "description": "`owned` when the record belongs to the application you called (for a console member, to an application they were granted). `organization` when it belongs to another application of your organization." + }, + "route": { + "$ref": "#/components/schemas/NetworkTimelineRoute" + } + } + }, + "NetworkTimelineSourceRef": { + "type": "object", + "description": "The record the event was read from.", + "properties": { + "kind": { + "type": "string", + "enum": [ + "session", + "business_session", + "transaction", + "monitoring_event", + "case" + ] + }, + "id": { + "type": "string", + "format": "uuid" + }, + "ownership": { + "type": "string", + "enum": [ + "owned", + "organization" + ], + "description": "`owned` when the record belongs to the application you called (for a console member, to an application they were granted). `organization` when it belongs to another application of your organization." + }, + "route": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineRoute" + } + ], + "description": "Absent for `monitoring_event` and `case`." + } + } + }, + "NetworkTimelineRoute": { + "type": "object", + "description": "Where the record opens: its kind, its UUID and the application it belongs to.", + "properties": { + "kind": { + "type": "string", + "description": "`user`, `business` or `transaction_party` for an account; `session`, `business_session` or `transaction` for a source record." + }, + "id": { + "type": "string", + "format": "uuid" + }, + "application_id": { + "type": "string", + "format": "uuid" + } + } + }, + "NetworkTimelineAttempt": { + "type": "object", + "description": "One verification session of the account.", + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "description": "The session UUID, or the business session UUID for a company." + }, + "session_number": { + "type": "integer", + "nullable": true + }, + "attempt": { + "type": "integer", + "description": "This session's position among the account's sessions, in creation order." + }, + "attempt_count": { + "type": "integer", + "description": "How many sessions the account has." + }, + "status": { + "type": "string", + "description": "The session status, for example `Approved` or `Declined`." + }, + "status_changed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "decline_reasons": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NetworkTimelineDeclineReason" + }, + "description": "For a declined session only: its warnings, oldest first, once per warning. Empty otherwise." + } + } + }, + "NetworkTimelineDeclineReason": { + "type": "object", + "properties": { + "risk": { + "type": "string", + "description": "The warning code." + }, + "feature": { + "type": "string", + "description": "The feature that raised the warning." + }, + "short_description": { + "type": "string" + } + } + }, + "NetworkTimelineTransaction": { + "type": "object", + "description": "A transaction a member account took part in, dated by its transaction date.", + "properties": { + "transaction_id": { + "type": "string", + "format": "uuid" + }, + "transaction_number": { + "type": "integer", + "nullable": true + }, + "transaction_type": { + "type": "string" + }, + "action_type": { + "type": "string" + }, + "amount": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineAmount" + } + ], + "nullable": true, + "description": "Null when the transaction has no amount." + }, + "status": { + "type": "string" + }, + "decision_reason_code": { + "type": "string", + "nullable": true + }, + "decision_reason_label": { + "type": "string", + "nullable": true + }, + "party_role": { + "type": "string", + "description": "The account's role in the transaction: `APPLICANT`, `REMITTER`, `BENEFICIARY` or `COUNTERPARTY`." + } + } + }, + "NetworkTimelineAmount": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Decimal amount as a string." + }, + "currency": { + "type": "string" + }, + "direction": { + "type": "string", + "nullable": true + } + } + }, + "NetworkTimelineLogin": { + "type": "object", + "description": "A sign-in event you posted for a person through the events API.", + "properties": { + "action": { + "type": "string", + "description": "`.`: `auth.login` or `security.login_succeeded` for a sign-in, `auth.login_failed` or `security.login_failed` for a failed one." + }, + "succeeded": { + "type": "boolean" + }, + "status": { + "type": "string", + "description": "The event's status." + }, + "ip": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineIP" + } + ], + "nullable": true, + "description": "Null when the event has no IP address." + }, + "device": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTimelineDevice" + } + ], + "nullable": true, + "description": "Null when the event has no device fingerprint or context." + }, + "is_first_login": { + "type": "boolean", + "description": "The account's first successful sign-in on record." + }, + "is_new_device": { + "type": "boolean", + "description": "A successful sign-in from a device none of the account's earlier successful sign-ins came from. Never true on the first sign-in." + } + } + }, + "NetworkTimelineIP": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "country_code": { + "type": "string", + "nullable": true + }, + "city": { + "type": "string", + "nullable": true + }, + "connection_type": { + "type": "string", + "nullable": true, + "description": "The connection type label for the address, as free text." + }, + "is_vpn_or_tor": { + "type": "boolean" + }, + "is_data_center": { + "type": "boolean" + } + } + }, + "NetworkTimelineDevice": { + "type": "object", + "properties": { + "fingerprint": { + "type": "string", + "nullable": true + }, + "label": { + "type": "string", + "nullable": true, + "description": "A readable device label built from the event's device context." + } + } + }, + "NetworkTimelineRuleHit": { + "type": "object", + "description": "A live rule that matched a member's transaction or event. Test-mode matches are not included.", + "properties": { + "rule_id": { + "type": "string", + "format": "uuid" + }, + "rule_title": { + "type": "string" + }, + "rule_category": { + "type": "string" + }, + "rule_source": { + "type": "string" + }, + "severity": { + "type": "string", + "nullable": true + }, + "score_delta": { + "type": "integer" + }, + "status_target": { + "type": "string", + "nullable": true + }, + "action_summary": { + "type": "string", + "nullable": true + } + } + }, + "NetworkTimelineCaseLinked": { + "type": "object", + "description": "A case opened for the network in your organization. `source_ref.ownership` says whether it belongs to the application you called.", + "properties": { + "case_id": { + "type": "string", + "format": "uuid" + }, + "case_number": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string" + }, + "author_email": { + "type": "string", + "nullable": true + }, + "author_name": { + "type": "string", + "nullable": true + } + } + }, + "NetworkTimelineNoteAdded": { + "type": "object", + "description": "A note left on the network. The note text is not included.", + "properties": { + "note_id": { + "type": "string", + "format": "uuid" + }, + "author_email": { + "type": "string" + } + } + }, + "FeatureStatusOverride": { + "type": "object", + "description": "One override of a check (feature) status made with `PATCH /v3/session/{sessionId}/features/{nodeId}/update-status/`: what it changes, whether it applied or still waits for the person to finish the flow, who asked and why.", + "required": [ + "id", + "node_id", + "feature_type", + "feature_model", + "previous_status", + "new_status", + "state", + "forced", + "reason", + "requested_by", + "requested_at", + "applied_at", + "cancelled_at", + "cancel_reason" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Identifier of the override. Pass it to the cancel endpoint.", + "example": "7c9e6679-7425-40de-944b-e07fc1f90ae7" + }, + "node_id": { + "type": "string", + "description": "The workflow node the request named (`default` for a step without a node).", + "example": "feature_ocr_1" + }, + "feature_type": { + "type": "string", + "description": "The `feature_type` the request passed, or an empty string when it passed none.", + "example": "" + }, + "feature_model": { + "type": "string", + "description": "The kind of check that was overridden, for example `KYC`, `Face`, `AML` or `GeolocationCheck`.", + "example": "KYC" + }, + "previous_status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "The check's status when the override was requested.", + "example": "In Review" + }, + "new_status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review" + ], + "description": "The status the override sets.", + "example": "Approved" + }, + "state": { + "type": "string", + "enum": [ + "applied", + "pending_until_flow_finishes", + "cancelled" + ], + "description": "`applied`: the check has the new status. `pending_until_flow_finishes`: the override waits and applies when the person finishes the flow. `cancelled`: it waited and never applied (see `cancel_reason`).", + "example": "applied" + }, + "forced": { + "type": "boolean", + "description": "`true` when the override applied at once while the person was still in the flow.", + "example": false + }, + "reason": { + "type": "string", + "nullable": true, + "description": "The `comment` sent with the request, or `null`.", + "example": "Document manually verified by compliance." + }, + "requested_by": { + "type": "object", + "nullable": true, + "description": "Who requested the override, or `null` when that is not known.", + "properties": { + "email": { + "type": "string", + "format": "email", + "nullable": true, + "description": "Email of the console user, or `null` for API keys and the system.", + "example": "reviewer@example.com" + }, + "name": { + "type": "string", + "nullable": true, + "description": "Display name of the requester, or `null`.", + "example": "Alex Reviewer" + }, + "type": { + "type": "string", + "enum": [ + "CONSOLE_USER", + "API_KEY", + "SYSTEM" + ], + "description": "Who made the request: a console user, an API key, or Didit itself.", + "example": "CONSOLE_USER" + } + } + }, + "requested_at": { + "type": "string", + "format": "date-time", + "description": "When the override was requested.", + "example": "2026-10-13T09:15:00Z" + }, + "applied_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the check took the new status, or `null` while it waits or once it is cancelled.", + "example": "2026-10-13T09:15:00Z" + }, + "cancelled_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When a waiting override was cancelled, or `null`.", + "example": null + }, + "cancel_reason": { + "type": "string", + "nullable": true, + "enum": [ + "cancelled_by_reviewer", + "replaced", + "check_changed", + "session_closed", + null + ], + "description": "Why a waiting override never applied, or `null`. `cancelled_by_reviewer`: cancelled with the cancel endpoint. `replaced`: a newer override on the same check replaced it. `check_changed`: the person redid the check before finishing the flow. `session_closed`: the session left the flow before the person finished it (abandoned, expired, or decided mid-flow).", + "example": null + } + } + }, + "SandboxSimulateRequest": { + "type": "object", + "description": "Force a sandbox session's status. Every field is optional, but at least one of `new_status`, `scenario` or `risk` is required.", + "properties": { + "new_status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned" + ], + "description": "The status to set. When you leave it out, it defaults to the default status of the risk case (`risk`, or the first risk the scenario's magic values carry), else to the scenario's default status.", + "example": "Declined" + }, + "comment": { + "type": "string", + "maxLength": 500, + "description": "Note added to the activity entry the status change records.", + "example": "Testing the decline path" + }, + "scenario": { + "type": "string", + "maxLength": 100, + "description": "A scenario slug from the catalog (`GET /v1/sandbox/scenarios/`). It supplies the default status and, without `risk`, the warning its magic values carry. An unknown slug returns `400`.", + "example": "decline_aml_hit" + }, + "risk": { + "type": "string", + "maxLength": 128, + "description": "A risk case from the catalog. Its warning is added to the session, with a log type that follows `new_status`. An unknown value returns `400`.", + "example": "POSSIBLE_MATCH_FOUND" + }, + "node_id": { + "type": "string", + "maxLength": 128, + "description": "Workflow node the risk warning is attached to. Defaults to the session's current node.", + "example": "feature_aml_1" + }, + "send_webhooks": { + "type": "boolean", + "default": true, + "description": "`true` sends exactly the webhooks a real status change sends to the sandbox application's destinations: `status.updated`, plus the `user.status.updated` / `user.data.updated` (or `business.status.updated` / `business.data.updated`) events the change causes, for destinations subscribed to them. `false` changes the status without logging or sending any webhook: the missed-webhook recovery never re-sends that decision, the console still updates live, and the next status change of the session sends webhooks as usual.", + "example": true + } + } + }, + "SandboxSimulateResponse": { + "type": "object", + "required": [ + "session_id", + "previous_status", + "status", + "scenario", + "risk", + "send_webhooks" + ], + "properties": { + "session_id": { + "type": "string", + "format": "uuid", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "previous_status": { + "type": "string", + "enum": [ + "Not Started", + "In Progress", + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned", + "Kyc Expired", + "Resubmitted", + "Awaiting User" + ], + "description": "The session's status before the simulation.", + "example": "In Progress" + }, + "status": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Abandoned" + ], + "description": "The status the simulation set.", + "example": "Declined" + }, + "scenario": { + "type": "string", + "nullable": true, + "description": "The scenario slug you passed, or `null`.", + "example": "decline_aml_hit" + }, + "risk": { + "type": "string", + "nullable": true, + "description": "The risk case applied (`risk`, or the one taken from the scenario), or `null` when none.", + "example": "POSSIBLE_MATCH_FOUND" + }, + "send_webhooks": { + "type": "boolean", + "description": "The `send_webhooks` value the simulation applied.", + "example": false + } + } + }, + "CaseTeamSummary": { + "type": "object", + "nullable": true, + "description": "The review team that owns the case, or null when no team owns it. Routing sets it when a routing rule places the case with one of its teams. Change or clear it with `PATCH .../cases/{case_uuid}/` (`team`).", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + } + } + }, + "CaseChecker": { + "type": "object", + "nullable": true, + "description": "The member named to approve or reject the pending resolution. Set only while a four-eyes resolution waits on a named checker. Null when nothing is pending, while the pending resolution waits on the checker queue, and once the resolution is approved, rejected, or returned for a new review because the evidence changed.", + "properties": { + "member_uuid": { + "type": "string", + "format": "uuid", + "description": "The member's user uuid: the value you pass as `checker` when you resolve the case." + }, + "name": { + "type": "string", + "nullable": true, + "description": "The member's full name, or null when it is not known." + }, + "email": { + "type": "string", + "format": "email" + } + } + }, + "ServiceStatusFeed": { + "type": "object", + "description": "Public service status: the state of each Didit service, the overall state, and the incidents that are ongoing or were resolved in the last 7 days.", + "required": [ + "status", + "updated_at", + "services", + "incidents" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "operational", + "degraded", + "down" + ], + "description": "The worst state of any service right now, decided by the ongoing incidents only.", + "example": "degraded" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "When Didit built this response (ISO 8601, UTC). Responses are cached for 30 seconds, so it can be up to 30 seconds old.", + "example": "2026-10-13T09:41:07.512345Z" + }, + "services": { + "type": "array", + "description": "One entry per service, every service always listed.", + "items": { + "$ref": "#/components/schemas/ServiceStatusService" + } + }, + "incidents": { + "type": "array", + "description": "Newest `started_at` first. Every ongoing incident, plus the incidents resolved in the last 7 days (at most 50 resolved incidents). Empty when nothing happened in that time.", + "items": { + "$ref": "#/components/schemas/ServiceStatusIncident" + } + } + } + }, + "ServiceStatusService": { + "type": "object", + "required": [ + "key", + "name", + "status" + ], + "properties": { + "key": { + "type": "string", + "enum": [ + "service_id_verification", + "service_liveness_face_match", + "service_aml_screening", + "service_kyb_registries", + "service_database_validation", + "service_phone_delivery", + "service_email_delivery", + "service_transaction_monitoring" + ], + "description": "Stable service key. Match on this, never on the display name. `service_id_verification`: ID verification, `service_liveness_face_match`: Liveness and face match, `service_aml_screening`: AML screening, `service_kyb_registries`: KYB registries, `service_database_validation`: Database validation, `service_phone_delivery`: Phone verification delivery, `service_email_delivery`: Email verification delivery, `service_transaction_monitoring`: Transaction monitoring.", + "example": "service_database_validation" + }, + "name": { + "type": "string", + "description": "English display name of the service.", + "example": "Database validation" + }, + "status": { + "type": "string", + "enum": [ + "operational", + "degraded", + "down" + ], + "description": "`operational` when no incident is ongoing for the service, otherwise the worst state of its ongoing incidents.", + "example": "degraded" + } + } + }, + "ServiceStatusIncident": { + "type": "object", + "required": [ + "id", + "service", + "service_name", + "state", + "status", + "title", + "body", + "affected", + "country_name", + "started_at", + "resolved_at", + "duration_minutes" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Incident ID.", + "example": "3fa85f64-5717-4562-b3fc-2c963f66afa6" + }, + "service": { + "type": "string", + "enum": [ + "service_id_verification", + "service_liveness_face_match", + "service_aml_screening", + "service_kyb_registries", + "service_database_validation", + "service_phone_delivery", + "service_email_delivery", + "service_transaction_monitoring" + ], + "description": "Key of the affected service (see `services[].key`).", + "example": "service_database_validation" + }, + "service_name": { + "type": "string", + "description": "English display name of the affected service.", + "example": "Database validation" + }, + "state": { + "type": "string", + "enum": [ + "degraded", + "down" + ], + "description": "How the service is affected. A resolved incident keeps the last state it had.", + "example": "degraded" + }, + "status": { + "type": "string", + "enum": [ + "ongoing", + "resolved" + ], + "description": "`ongoing` until the service recovers, then `resolved`.", + "example": "ongoing" + }, + "title": { + "type": "string", + "description": "English headline. To translate it, build your own text from `service`, `state` and `country_name`.", + "example": "Database validation is degraded in Brazil." + }, + "body": { + "type": "string", + "description": "English detail line: since when checks may be slow or fail, or when it recovered.", + "example": "Checks against Brazil (CPF) may be slow or fail since 08:55 UTC." + }, + "affected": { + "type": "string", + "description": "Countries or registries affected, for example `Brazil (CPF)`. Empty when the incident is not tied to a country or registry.", + "example": "Brazil (CPF)" + }, + "country_name": { + "type": "string", + "description": "Country affected, in English. Empty when the incident is not tied to a country.", + "example": "Brazil" + }, + "started_at": { + "type": "string", + "format": "date-time", + "description": "When the incident started (ISO 8601, UTC).", + "example": "2026-10-13T08:55:00Z" + }, + "resolved_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the service recovered (ISO 8601, UTC). `null` while the incident is ongoing." + }, + "duration_minutes": { + "type": "integer", + "nullable": true, + "description": "Whole minutes from `started_at` to `resolved_at`. `null` while the incident is ongoing." + } + } + }, + "TravelRuleJoinInvitation": { + "type": "object", + "description": "What the join page shows before anything is typed.", + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "The address the invitation went to." + }, + "vasp_name": { + "type": "string" + }, + "source": { + "type": "string", + "description": "Where the invitation came from, for example `pickup`." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "prefill": { + "type": "object", + "properties": { + "vasp_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "legal_name": { + "type": "string" + }, + "lei": { + "type": "string" + }, + "jurisdiction": { + "type": "string" + }, + "domains": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "TravelRuleJoinRegisterRequest": { + "type": "object", + "required": [ + "email", + "password" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "maxLength": 254, + "description": "Must be the address the invitation went to." + }, + "password": { + "type": "string", + "minLength": 8, + "maxLength": 128 + } + } + }, + "TravelRuleJoinVerifyRequest": { + "type": "object", + "required": [ + "email", + "code" + ], + "properties": { + "email": { + "type": "string", + "format": "email", + "maxLength": 254 + }, + "code": { + "type": "string", + "minLength": 6, + "maxLength": 6, + "description": "The 6-character code emailed after registering." + } + } + }, + "TravelRuleJoinCredentials": { + "type": "object", + "description": "The new free organization and the credentials its application calls the API with.", + "properties": { + "organization": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + } + } + }, + "application": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + } + } + }, + "client_id": { + "type": "string" + }, + "api_key": { + "type": "string", + "description": "Send as the `x-api-key` header on the workspace endpoints." + } + } + }, + "TravelRuleWorkspaceClaimRequest": { + "type": "object", + "required": [ + "vasp_id" + ], + "properties": { + "vasp_id": { + "type": "string", + "format": "uuid", + "description": "The directory entry this workspace is. Read ids from `GET /v3/travel-rule/vasps/`." + } + } + }, + "TravelRuleWorkspaceDomainConfirmRequest": { + "type": "object", + "required": [ + "code" + ], + "properties": { + "code": { + "type": "string", + "minLength": 6, + "maxLength": 6 + } + } + }, + "TravelRuleWorkspaceBillingRequest": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": true, + "description": "Only `true` is accepted: outbound billing cannot be turned back off through the API." + } + } + }, + "TravelRuleWorkspaceDomainCodeResponse": { + "type": "object", + "properties": { + "domain": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "unverified", + "code_sent", + "verified" + ] + }, + "txt": { + "$ref": "#/components/schemas/TravelRuleWorkspaceTxtRecord" + } + } + }, + "TravelRuleWorkspaceTxtRecord": { + "type": "object", + "description": "The TXT record to publish for the DNS proof. Empty until a proof has been started.", + "properties": { + "host": { + "type": "string", + "example": "didit-tr-verify.example.com" + }, + "value": { + "type": "string" + } + } + }, + "TravelRuleWorkspace": { + "type": "object", + "description": "The free Travel Rule workspace of an application that joined free.", + "properties": { + "joined_free": { + "type": "boolean" + }, + "work_email": { + "type": "string", + "format": "email" + }, + "joined_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "claim": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "unclaimed", + "pending_review", + "claimed", + "rejected" + ] + }, + "proved_by_domain": { + "type": "boolean" + }, + "claimed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reviewed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "review_note": { + "type": "string" + }, + "vasp_id": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "vasp_name": { + "type": "string" + } + } + }, + "prefill": { + "type": "object", + "description": "Profile values taken from the directory entry: `vasp_id`, `legal_name`, `lei`, `jurisdiction`, `compliance_email`, `domains`. Nothing is written until the claim is made." + }, + "domain": { + "type": "object", + "properties": { + "domain": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "unverified", + "code_sent", + "verified" + ] + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "txt": { + "$ref": "#/components/schemas/TravelRuleWorkspaceTxtRecord" + } + } + }, + "billing_enabled": { + "type": "boolean", + "description": "Receiving Travel Rule data is free. Sending is billed only after billing is enabled." + }, + "billing_enabled_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "didit_network": { + "type": "object", + "description": "Whether the Didit network lists this VASP and, when not, each condition it still misses." + }, + "settings": { + "$ref": "#/components/schemas/TravelRuleSettingsDetail" + } + } + }, + "InvestigatorDecision": { + "type": "object", + "description": "How the item left the Investigator queue. `decided_by`, `original_outcome` and `overrides` are returned by the accept and override endpoints and the report; the `investigator` block of a session decision carries the other fields.", + "properties": { + "outcome": { + "type": "string", + "enum": [ + "accepted", + "edited", + "overridden", + "decided_elsewhere", + "automated" + ], + "description": "`accepted` or `edited` took the recommendation (`edited` with a rewritten narrative), `overridden` took another action, `decided_elsewhere` is a decision taken outside the Investigator while the report was open, `automated` is a decision the Investigator took on its own." + }, + "action": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ] + }, + "actor_email": { + "type": "string", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "override_category": { + "type": "string", + "enum": [ + "missing_context", + "needs_information", + "policy_differs", + "evidence_misread", + "other", + "" + ], + "nullable": true + }, + "override_reason": { + "type": "string", + "nullable": true + }, + "final_status": { + "type": "string", + "description": "The item's status after the decision." + }, + "agreed": { + "type": "boolean", + "description": "Whether the decision matched the recommendation.", + "nullable": true + }, + "decided_at": { + "type": "string", + "format": "date-time" + }, + "billed": { + "type": "boolean", + "description": "The review was charged." + }, + "decided_by": { + "type": "string", + "enum": [ + "investigator", + "member" + ] + }, + "original_outcome": { + "type": "object", + "description": "The decision the Investigator took on its own, unchanged by later overrides. Null for a decision the team took.", + "properties": { + "action": { + "type": "string" + }, + "final_status": { + "type": "string" + }, + "decided_at": { + "type": "string", + "format": "date-time" + } + }, + "nullable": true + }, + "overrides": { + "type": "array", + "description": "Every override of a decision the Investigator took on its own, oldest first.", + "items": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "actor_email": { + "type": "string", + "nullable": true + }, + "actor_name": { + "type": "string", + "nullable": true + }, + "override_category": { + "type": "string" + }, + "override_reason": { + "type": "string" + }, + "previous_status": { + "type": "string" + }, + "final_status": { + "type": "string" + }, + "agreed": { + "type": "boolean", + "nullable": true + }, + "overridden_at": { + "type": "string", + "format": "date-time" + } + } + } + } + } + }, + "InvestigatorBlock": { + "type": "object", + "nullable": true, + "description": "The item's latest Investigator review, or null when it never had one. Carried by the V3 session and business session decision (and so by `status.updated` webhooks).", + "properties": { + "run_id": { + "type": "string", + "format": "uuid" + }, + "status": { + "type": "string", + "enum": [ + "running", + "completed", + "failed" + ] + }, + "stage": { + "type": "string", + "enum": [ + "shadow", + "live" + ] + }, + "queue": { + "type": "string", + "enum": [ + "kyc", + "kyb", + "aml", + "transactions", + "cases" + ] + }, + "is_open": { + "type": "boolean" + }, + "recommendation": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ], + "nullable": true + }, + "recommendation_detail": { + "type": "string", + "nullable": true + }, + "confidence": { + "type": "string", + "enum": [ + "high", + "moderate", + "low" + ], + "nullable": true + }, + "summary": { + "type": "string", + "nullable": true + }, + "ready_to_accept": { + "type": "boolean" + }, + "prepared_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "automated": { + "type": "boolean", + "description": "The Investigator decided on its own, even if the decision was later overridden." + }, + "decided_by": { + "type": "string", + "enum": [ + "investigator", + "member" + ], + "nullable": true + }, + "decision": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/InvestigatorDecision" + } + ], + "type": "object" + } + } + }, + "InvestigatorQueueRow": { + "type": "object", + "properties": { + "run_id": { + "type": "string", + "format": "uuid" + }, + "kind": { + "type": "string", + "enum": [ + "session", + "business_session", + "transaction", + "case" + ] + }, + "queue": { + "type": "string", + "enum": [ + "kyc", + "kyb", + "aml", + "transactions", + "cases" + ] + }, + "stage": { + "type": "string", + "enum": [ + "shadow", + "live" + ] + }, + "subject": { + "type": "object", + "description": "The item under review: `id`, `number`, `status`, `name` and, depending on the kind, `country`, `workflow`, `vendor_data`, `txn_id`, `amount`, `currency`, `direction`, `title` or `priority`.", + "additionalProperties": true + }, + "recommendation": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ] + }, + "recommendation_detail": { + "type": "string" + }, + "confidence": { + "type": "string", + "enum": [ + "high", + "moderate", + "low" + ] + }, + "summary": { + "type": "string" + }, + "sources_count": { + "type": "integer" + }, + "ready_to_accept": { + "type": "boolean" + }, + "guardrails": { + "description": "The locked guardrails that hold this item, if any." + }, + "waiting_since": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "due_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "prepared_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "InvestigatorRunDetail": { + "allOf": [ + { + "$ref": "#/components/schemas/InvestigatorQueueRow" + }, + { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "is_open": { + "type": "boolean" + }, + "lookups": { + "type": "integer", + "description": "How many sections and linked records it read." + }, + "duration_ms": { + "type": "integer", + "nullable": true + }, + "agent_version": { + "type": "string" + }, + "prepared_before_opened": { + "type": "boolean", + "nullable": true + }, + "report": { + "type": "object", + "additionalProperties": true, + "description": "The report: reasoning, evidence points with their verdict (`ok`, `risk`, `look`, `explained`, `context`) and cited sources, screening hit verdicts (`same`, `not_same`, `unclear`) and suggested next steps." + }, + "trail": { + "description": "What I checked: the overview, sections and records it read, in order." + }, + "decision": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/InvestigatorDecision" + } + ], + "type": "object" + }, + "autonomy": { + "type": "object", + "additionalProperties": true, + "description": "What autonomy made of the report (`result`, `reason`, `tier`, `policy_version`, `rule`, `agreement`, `decisions`, `threshold`, `at`). Null when the queue is not automated.", + "nullable": true + } + } + } + ] + }, + "InvestigatorAcceptRequest": { + "type": "object", + "properties": { + "narrative": { + "type": "string", + "maxLength": 4000, + "description": "A rewritten narrative. Sending one records the decision as edited and accepted." + }, + "send_email": { + "type": "boolean", + "default": false, + "description": "Email the person about the decision." + }, + "email_address": { + "type": "string", + "format": "email", + "description": "Required when `send_email` is true." + }, + "email_language": { + "type": "string", + "default": "en", + "maxLength": 16 + } + } + }, + "InvestigatorOverrideRequest": { + "type": "object", + "required": [ + "action", + "category", + "reason" + ], + "properties": { + "action": { + "type": "string", + "enum": [ + "approve", + "decline", + "escalate", + "request" + ] + }, + "category": { + "type": "string", + "enum": [ + "missing_context", + "needs_information", + "policy_differs", + "evidence_misread", + "other" + ] + }, + "reason": { + "type": "string", + "minLength": 10, + "maxLength": 500 + }, + "send_email": { + "type": "boolean", + "default": false, + "description": "Email the person about the decision." + }, + "email_address": { + "type": "string", + "format": "email", + "description": "Required when `send_email` is true." + }, + "email_language": { + "type": "string", + "default": "en", + "maxLength": 16 + } + } + }, + "InvestigatorBatchAcceptRequest": { + "type": "object", + "required": [ + "run_ids" + ], + "properties": { + "run_ids": { + "type": "array", + "minItems": 1, + "maxItems": 50, + "items": { + "type": "string", + "format": "uuid" + } + } + } + }, + "InvestigatorBatchAcceptResponse": { + "type": "object", + "properties": { + "accepted": { + "type": "integer" + }, + "skipped": { + "type": "integer" + }, + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "run_id": { + "type": "string", + "format": "uuid" + }, + "result": { + "type": "string", + "enum": [ + "accepted", + "skipped" + ] + }, + "reason": { + "type": "string", + "enum": [ + "not_found", + "forbidden", + "already_decided", + "not_ready", + "guardrail", + "invalid" + ] + }, + "detail": {}, + "action": { + "type": "string" + }, + "final_status": { + "type": "string" + } + } + } + } + } + }, + "InvestigatorSettings": { + "type": "object", + "properties": { + "stage": { + "type": "string", + "enum": [ + "none", + "shadow", + "live" + ] + }, + "stage_changed_at": { + "type": "string", + "format": "date-time", + "readOnly": true, + "nullable": true + }, + "stage_changed_by": { + "type": "string", + "readOnly": true + }, + "monthly_spend_limit": { + "type": "string", + "format": "decimal", + "description": "Monthly limit in your billing currency, 0.01 to 1,000,000. Null turns it off.", + "nullable": true + }, + "spend_limit": { + "type": "object", + "readOnly": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "limit": { + "type": "string", + "format": "decimal", + "nullable": true + }, + "spent": { + "type": "string", + "format": "decimal" + }, + "reviews": { + "type": "integer" + }, + "reserved": { + "type": "string", + "format": "decimal", + "description": "The price of every open live report, held back because accepting it charges it." + }, + "currency": { + "type": "string" + }, + "paused": { + "type": "boolean", + "description": "At the limit: no new review starts." + } + } + } + } + }, + "TravelRuleUtxoEntry": { + "type": "object", + "description": "One input or output of a UTXO transaction, as the chain recorded it.", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string", + "maxLength": 128 + }, + "amount": { + "type": "string", + "nullable": true, + "description": "Decimal string in the coin's own unit (BTC, not satoshis). Optional.", + "example": "0.0125" + }, + "txid": { + "type": "string", + "maxLength": 128, + "description": "Optional. The transaction an input spends." + }, + "vout": { + "type": "integer", + "nullable": true, + "minimum": 0, + "description": "Optional. The output index an input spends." + } + } + }, + "TravelRuleUtxo": { + "type": "object", + "description": "The inputs and outputs of a UTXO transfer (BTC, LTC, BCH, DOGE). Only accepted on those chains: on any other chain it is a `400`. From them Didit infers the originator wallet (the declared one, else the input address that contributed the most) and the beneficiary output (the declared one, else the only output that does not pay an input address back as change, else the one output paying the transfer amount). The result is on the transfer's `chain_details`.", + "required": [ + "outputs" + ], + "properties": { + "inputs": { + "type": "array", + "maxItems": 1000, + "items": { + "$ref": "#/components/schemas/TravelRuleUtxoEntry" + }, + "description": "Optional. Without inputs no originator is inferred and no change output is recognised." + }, + "outputs": { + "type": "array", + "minItems": 1, + "maxItems": 1000, + "items": { + "$ref": "#/components/schemas/TravelRuleUtxoEntry" + } + } + } + }, + "TravelRuleChainSide": { + "type": "object", + "description": "One side's wallet as Didit resolved it from the chain data.", + "properties": { + "address": { + "type": "string", + "description": "The wallet. On a memo network the classic address (an XRP X-address is split into its classic address and its tag); on Lightning the payee node id or Lightning Address." + }, + "memo": { + "type": "string", + "description": "The destination tag or memo that is part of the wallet; `\"\"` for none." + }, + "source": { + "type": "string", + "enum": [ + "declared", + "x_address", + "largest_input", + "only_non_change_output", + "amount_match", + "invoice_payee", + "updated" + ], + "description": "Where the wallet came from: `declared` (your request), `x_address` (split from an XRP X-address), `largest_input` (the UTXO input address that contributed the most), `only_non_change_output` (the only UTXO output that is not change), `amount_match` (the one UTXO output paying the transfer amount), `invoice_payee` (the payee a Lightning payment request names), `updated` (set later with `action: update_originator_wallet`)." + } + } + }, + "TravelRuleChainDetails": { + "type": "object", + "description": "How the transfer's chain names its two wallets, when that is more than one address paying another. Empty (`{}`) for an account-chain transfer (EVM, Solana, Tron), where the two addresses are the wallets. Matching, routing and the address book read the wallets from here first.", + "properties": { + "chain": { + "type": "string", + "description": "Canonical chain, e.g. `BTC`, `XRP`, or `LIGHTNING` for a Lightning transfer." + }, + "shape": { + "type": "string", + "enum": [ + "utxo", + "memo", + "lightning" + ] + }, + "originator": { + "$ref": "#/components/schemas/TravelRuleChainSide" + }, + "beneficiary": { + "$ref": "#/components/schemas/TravelRuleChainSide" + }, + "utxo": { + "type": "object", + "description": "On a UTXO transfer: the inputs and outputs as stored (amounts as decimal strings) and which outputs Didit read as what.", + "properties": { + "inputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleUtxoEntry" + } + }, + "outputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleUtxoEntry" + } + }, + "change_output_indexes": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "Outputs that pay an input address back." + }, + "beneficiary_output_index": { + "type": "integer", + "nullable": true, + "description": "The output that pays the beneficiary; null when none could be told apart." + } + } + }, + "lightning": { + "type": "object", + "description": "On a Lightning transfer: what the payment request says. Always `kind` (`bolt11`, `bolt12_offer`, `bolt12_invoice`, `bolt12_invoice_request`, `lnurl`, `lightning_address`, `node_id` or `unknown`); a BOLT11 invoice adds `network`, `payee_node_id`, `payment_hash`, `amount_msat`, `created_at` and `expires_at`; a BOLT12 string `blinded_paths` and `payee_node_id`; an LNURL or a Lightning Address its `domain`. Nothing is resolved over the network: the request is decoded only.", + "additionalProperties": true + }, + "warnings": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "declared_originator_not_an_input", + "declared_beneficiary_not_an_output", + "beneficiary_output_ambiguous", + "beneficiary_output_amount_differs" + ] + }, + "description": "UTXO inconsistencies worth a look: the declared originator is not an input, the declared beneficiary is not an output, no single beneficiary output could be told apart, or the beneficiary output pays a different amount than the transaction states." + }, + "not_applicable_reason": { + "type": "string", + "enum": [ + "LIGHTNING_NO_PAYEE_IDENTIFIER", + "LIGHTNING_BOLT12_BLINDED_PAYEE", + "LIGHTNING_LNURL_NAMES_NO_PAYEE" + ], + "description": "On an outbound Lightning transfer whose payment request names no payee: why the transfer is `NOT_APPLICABLE`. `LIGHTNING_NO_PAYEE_IDENTIFIER` (the request names no payee node, or could not be decoded), `LIGHTNING_BOLT12_BLINDED_PAYEE` (the offer hides its payee behind blinded paths), `LIGHTNING_LNURL_NAMES_NO_PAYEE` (an LNURL names a service, not a payee). The same code opens the transfer's last `timeline` entry." + } + } + }, + "WalletAddressImportRow": { + "type": "object", + "description": "One wallet to import: by its `address`, its `address_hash`, or both. The camelCase forms of these fields are accepted too, as are `walletAddress`, `walletAddressHash`, `destinationTag` and `externalUserId`.", + "properties": { + "address": { + "type": "string", + "maxLength": 128, + "description": "The wallet address. Alias: `walletAddress`." + }, + "address_hash": { + "type": "string", + "description": "SHA-256 of the lowercased address, as 64 hex characters or 44 base64 characters. Send it alone to register a wallet without disclosing its address; sent with `address`, it must be that address's hash. Alias: `walletAddressHash`." + }, + "chain": { + "type": "string", + "maxLength": 32 + }, + "memo": { + "type": "string", + "maxLength": 256, + "description": "The destination tag or memo. XRP: a destination tag, a whole number from 0 to 4294967295 (a JSON number is accepted). XLM: a text memo of up to 28 bytes, a 64-bit id or a 32-byte hash in hex. EOS: up to 256 bytes. ATOM: up to 256 characters. HBAR: up to 100 bytes. Bitcoin, EVM, Solana, Tron, Litecoin, Bitcoin Cash, Dogecoin and Lightning wallets take no memo: one there is a `400`." + }, + "holder_name": { + "type": "string", + "maxLength": 255 + }, + "holder_vendor_data": { + "type": "string", + "maxLength": 255, + "description": "Your internal identifier for the holder. Alias: `externalUserId`." + }, + "entity_type": { + "type": "string", + "enum": [ + "individual", + "company" + ], + "default": "individual" + }, + "label": { + "type": "string", + "maxLength": 255 + }, + "owner_type": { + "type": "string", + "enum": [ + "customer", + "internal", + "vasp" + ], + "default": "customer" + }, + "travel_address": { + "type": "string", + "maxLength": 512 + }, + "self_declared": { + "type": "boolean", + "default": false, + "description": "Create the entry already verified through a self-declaration, when your settings allow it." + } + } + }, + "WalletAddressImportReport": { + "type": "object", + "description": "What happened to each row of an import, in the order sent.", + "properties": { + "total": { + "type": "integer" + }, + "created": { + "type": "integer" + }, + "existing": { + "type": "integer" + }, + "duplicate": { + "type": "integer" + }, + "failed": { + "type": "integer" + }, + "rows": { + "type": "array", + "items": { + "type": "object", + "properties": { + "row": { + "type": "integer", + "description": "The row's number, from 1." + }, + "status": { + "type": "string", + "enum": [ + "created", + "existing", + "duplicate", + "failed" + ], + "description": "`created`: added to the book. `existing`: the wallet is already in the book and was left untouched. `duplicate`: repeats an earlier row of this import. `failed`: see `errors`." + }, + "entry_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The address-book entry the row created or found." + }, + "errors": { + "type": "object", + "description": "Field errors of a `failed` row; empty otherwise." + }, + "duplicate_of_row": { + "type": "integer", + "nullable": true, + "description": "On a `duplicate` row, the row it repeats." + } + } + } + } + } + }, + "TravelRuleCounterpartyContact": { + "type": "object", + "description": "A compliance address the EMAIL rail writes to for one directory entry.", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "counterparty": { + "type": "string", + "format": "uuid", + "description": "The directory entry's `id`, as GET /v3/travel-rule/vasps/ shows it." + }, + "counterparty_name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "name": { + "type": "string", + "description": "Optional label, for example the team name." + }, + "source": { + "type": "string", + "enum": [ + "staff", + "application", + "learned" + ], + "description": "staff: curated by Didit. learned: an address at one of the VASP's own domains that answered a request through its response link. application: added by your application." + }, + "scope": { + "type": "string", + "enum": [ + "platform", + "application" + ], + "description": "platform: curated by Didit or learned from a verified answer, used for every Didit customer, read-only for you. application: private to your application." + }, + "created_by": { + "type": "string", + "description": "Empty for platform contacts and for contacts an API key added." + }, + "verified_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When this address last answered a request through its response link." + }, + "is_invalid": { + "type": "boolean", + "description": "true once the address bounced or complained; the EMAIL rail skips it." + }, + "invalid_reason": { + "type": "string", + "enum": [ + "", + "hard_bounce", + "soft_bounce", + "spam_complaint", + "suppressed" + ] + }, + "invalid_detail": { + "type": "string", + "description": "The receiving mail server's reason, when it gave one." + }, + "invalid_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleCounterpartyContactCreate": { + "type": "object", + "required": [ + "counterparty", + "email" + ], + "properties": { + "counterparty": { + "type": "string", + "format": "uuid", + "description": "The directory entry's `id`." + }, + "email": { + "type": "string", + "format": "email", + "maxLength": 254 + }, + "name": { + "type": "string", + "description": "Optional label.", + "maxLength": 255 + } + } + }, + "TravelRuleNameMatch": { + "type": "object", + "nullable": true, + "description": "The name comparison that decided the transfer, or null until one ran. While the parties are masked, and on the other customer's copy of an INTERNAL exchange, `transfer_name`, `compared_name`, `matched_names` and `mismatched_names` are hidden. `score` and `matched` can disagree: `matched` follows the strictness, `score` is the similarity of the two full names.", + "properties": { + "score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Similarity of the two full names, 0 to 100. 0 when either name is blank." + }, + "matched": { + "type": "boolean", + "description": "Whether the names satisfy `strictness`. The transfer's outcome follows `data_match`, which compares every field, so a transfer can be COUNTERPARTY_MISMATCHED_DATA while this is true." + }, + "method": { + "type": "string", + "enum": [ + "none", + "exact_token", + "token_subset", + "token_containment", + "sequence_ratio" + ], + "description": "How `matched` was decided: none (`NONE`), exact_token (`STRICT`), token_subset (`DEFAULT`), token_containment (`CONTAINS`) or sequence_ratio (`FUZZY`)." + }, + "strictness": { + "type": "string", + "enum": [ + "NONE", + "STRICT", + "CONTAINS", + "DEFAULT", + "FUZZY" + ] + }, + "compared_with": { + "type": "string", + "enum": [ + "wallet_holder", + "counterparty_vasp" + ], + "description": "wallet_holder: the holder recorded for the wallet. counterparty_vasp: the name the counterparty answered on the email response page, or the Didit Test VASP's answer in sandbox." + }, + "transfer_name": { + "type": "string", + "description": "The name on the transfer." + }, + "compared_name": { + "type": "string", + "description": "The name it was compared with." + }, + "matched_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Words of `transfer_name` found in `compared_name`." + }, + "mismatched_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Words of `transfer_name` missing from `compared_name`, then words of `compared_name` missing from `transfer_name`." + } + } + }, + "TravelRuleTransferRow": { + "type": "object", + "description": "One Travel Rule transfer as the transfers list shows it: VASP names, wallets and memos only, never the originator or beneficiary person data.", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "The transfer's id." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "The Didit transaction the transfer belongs to." + }, + "txn_id": { + "type": "string", + "nullable": true, + "description": "Your own id for the transaction. Transfers Didit books for you, such as a Didit Test VASP inbound transfer, carry a generated one." + }, + "txn_date": { + "type": "string", + "format": "date-time" + }, + "kind": { + "type": "string", + "enum": [ + "withdrawal", + "deposit", + "message" + ], + "description": "withdrawal: outbound. message: an inbound transfer a rail created from a counterparty's message (a TRP inquiry, the INTERNAL inbound leg, or a GTR, VerifyVASP or Sygna Bridge message). deposit: any other inbound transfer." + }, + "direction": { + "type": "string", + "enum": [ + "INBOUND", + "OUTBOUND" + ] + }, + "status": { + "type": "string", + "enum": [ + "AWAITING_COUNTERPARTY", + "ON_HOLD", + "COMPLETED", + "FINISHED", + "CANCELLED", + "EXPIRED", + "COUNTERPARTY_MISMATCHED_DATA", + "COUNTERPARTY_VASP_NOT_FOUND", + "COUNTERPARTY_VASP_NOT_REACHABLE", + "UNCONFIRMED_OWNERSHIP", + "COUNTERPARTY_VASP_GENERAL_DECLINE", + "NOT_ENOUGH_COUNTERPARTY_DATA", + "NOT_APPLICABLE" + ] + }, + "reason": { + "type": "string", + "description": "Why a `COUNTERPARTY_VASP_NOT_FOUND` or `COUNTERPARTY_VASP_NOT_REACHABLE` transfer is stuck, the same value as the transfer's own `reason`, so the list tells a bounced compliance email from a counterparty with no route without opening each transfer. `\"\"` for any other status. See [Why a transfer is stuck](/transaction-monitoring/travel-rule#why-a-transfer-is-stuck)." + }, + "rail": { + "type": "string", + "nullable": true, + "enum": [ + "INTERNAL", + "TRP", + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA", + "EMAIL", + "TEST_VASP", + null + ] + }, + "asset": { + "type": "string", + "description": "The transaction's currency." + }, + "amount": { + "type": "string", + "description": "The amount as a plain decimal, for example \"1.25\"." + }, + "originator_vasp": { + "type": "string", + "description": "Outbound: your VASP's legal name. Inbound: the counterparty's name, from its directory entry, the Didit network member, the TRP sender, the Didit Test VASP, or the name the transfer declared. Empty when none is known." + }, + "beneficiary_vasp": { + "type": "string", + "description": "Outbound: the counterparty's name, worked out the same way. Inbound: your VASP's legal name." + }, + "originator_wallet": { + "type": "string" + }, + "beneficiary_wallet": { + "type": "string" + }, + "originator_memo": { + "type": "string", + "description": "Memo or destination tag, empty on chains without one." + }, + "beneficiary_memo": { + "type": "string" + }, + "originator_wallet_type": { + "type": "string", + "enum": [ + "vasp", + "self_hosted", + "own" + ], + "description": "vasp, self_hosted (declared unhosted) or own (a wallet your address book records as yours). Your own side is always vasp." + }, + "beneficiary_wallet_type": { + "type": "string", + "enum": [ + "vasp", + "self_hosted", + "own" + ] + }, + "matched_transaction_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The other side of a deposit you matched to an announced transfer by hand." + }, + "deadline_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the transfer expires if it is still waiting on its counterparty. Set on every transfer you create with `POST /v3/transactions/` that needs an exchange, outbound or inbound. Null below the threshold, on a Lightning payment the Travel Rule does not apply to, and on inbound transfers Didit creates when a counterparty reaches you (an inbound TRP inquiry, a network message or the INTERNAL inbound leg)." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BankAccountCheckHolder": { + "description": "The name the account is checked against. Send exactly one of `full_name` and `business_name`.", + "oneOf": [ + { + "$ref": "#/components/schemas/BankAccountCheckPersonHolder" + }, + { + "$ref": "#/components/schemas/BankAccountCheckBusinessHolder" + } + ] + }, + "BankAccountCheckRequest": { + "type": "object", + "required": [ + "country", + "account", + "holder" + ], + "properties": { + "country": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "description": "The account's country, as an ISO 3166-1 alpha-3 code, for example `GBR`. Lowercase is accepted.", + "example": "GBR" + }, + "account": { + "type": "object", + "description": "The account the way its country writes it: an `iban`, or an `account_number` with the bank or branch code the country needs (`sort_code`, `routing_number`, `transit_institution`, `bsb`, `ifsc`, `bank_code` or `bic`). Mexico takes a `clabe` and Argentina a `cbu_cvu`, which already name the bank. A business account adds a `registration_id` where the country asks for one. Spaces, dashes and dots are ignored. Keys the country's check does not use are ignored; a key it needs and does not get is a `400` that names it.", + "properties": { + "iban": { + "type": "string", + "maxLength": 64, + "description": "The IBAN, in a country that uses IBANs. It must belong to `country`." + }, + "account_number": { + "type": "string", + "maxLength": 64, + "description": "The account number, in a country that writes the account with a separate bank or branch code." + }, + "sort_code": { + "type": "string", + "maxLength": 64, + "description": "United Kingdom: the 6-digit sort code." + }, + "routing_number": { + "type": "string", + "maxLength": 64, + "description": "United States: the 9-digit ABA routing number." + }, + "transit_institution": { + "type": "string", + "maxLength": 64, + "description": "Canada: the 8-digit transit and institution number." + }, + "bsb": { + "type": "string", + "maxLength": 64, + "description": "Australia: the 6-digit BSB." + }, + "ifsc": { + "type": "string", + "maxLength": 64, + "description": "India: the 11-character IFSC." + }, + "bank_code": { + "type": "string", + "maxLength": 64, + "description": "The bank's code, where the country needs one, for example in Nigeria." + }, + "bic": { + "type": "string", + "maxLength": 64, + "description": "The bank's BIC, where the country's check asks for one." + }, + "clabe": { + "type": "string", + "maxLength": 64, + "description": "Mexico: the 18-digit CLABE. It already names the bank." + }, + "cbu_cvu": { + "type": "string", + "maxLength": 64, + "description": "Argentina: the 22-digit CBU or CVU. It already names the bank." + }, + "registration_id": { + "type": "string", + "maxLength": 64, + "description": "A business account's registration number, where the country asks for one." + } + }, + "additionalProperties": { + "type": "string", + "maxLength": 64 + }, + "example": { + "sort_code": "20-45-67", + "account_number": "31926819" + } + }, + "holder": { + "$ref": "#/components/schemas/BankAccountCheckHolder" + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this check, returned unchanged.", + "example": "payout_20481" + }, + "consent": { + "type": "boolean", + "default": false, + "description": "Set to `true` when the account holder has explicitly consented to this check. Required in the countries whose account check needs that consent: without it the check is refused with a `400` whose `field` is `consent` and whose `reason` is `required`, and nothing is billed. Elsewhere it is optional, and Didit records it with the check's charge when you send it.", + "example": true + } + } + }, + "BankAccountCheckResponse": { + "type": "object", + "required": [ + "request_id", + "result", + "holder_name", + "account", + "vendor_data", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "The ID of this check." + }, + "result": { + "type": "string", + "enum": [ + "match", + "close_match", + "no_match", + "cannot_verify" + ], + "description": "`match`: the bank holds the account under the name you sent. `close_match`: under a close name, for example a missing middle name or a typo. `no_match`: under another name, or there is no such account. `cannot_verify`: the bank answered without comparing the name, for example for a blocked or closed account." + }, + "holder_name": { + "type": "string", + "nullable": true, + "description": "Only on a `close_match`, and only when the bank returned it: the name on the account, as the bank holds it. `null` otherwise." + }, + "account": { + "type": "string", + "description": "The account, masked to its last 4 characters." + }, + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference, as you sent it." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the check ran, in UTC." + } + } + }, + "BankAccountCheckError": { + "type": "object", + "required": [ + "detail" + ], + "properties": { + "detail": { + "type": "string", + "description": "What went wrong, in English." + }, + "code": { + "type": "string", + "description": "A stable code to branch on. Absent on `401` and `403`." + }, + "field": { + "type": "string", + "description": "`400` only: the body field to correct, as a dotted path, for example `account.sort_code`." + }, + "reason": { + "type": "string", + "enum": [ + "required", + "length", + "format", + "checksum", + "country_mismatch", + "rejected" + ], + "description": "`400` only: why the field was refused. `required`, `length`, `format`, `checksum` and `country_mismatch` (an IBAN of another country) are Didit's own checks; `rejected` means the bank refused the account as sent." + } + } + }, + "BankVerificationResult": { + "type": "object", + "description": "One Bank Verification step's result, for User and Business Verification. `method` says how the account was verified, and `none` that the person continued without a bank before any attempt was evaluated. `continued_without_bank` says the person continued without a bank, also after an evaluated attempt. See [Bank verification methods](/core-technology/bank-verification/verification-methods) and [When the person cannot verify their bank](/core-technology/bank-verification/fallbacks).", + "properties": { + "schema_version": { + "type": "integer", + "description": "Version of this result shape. `1` today." + }, + "bank_verification_id": { + "type": "string", + "format": "uuid", + "description": "The ID of this result." + }, + "node_id": { + "type": "string", + "description": "The workflow node this result belongs to." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Expired" + ], + "description": "The step's verdict, after its actions and status rules." + }, + "original_status": { + "type": "string", + "nullable": true, + "description": "The first verdict the step reached, before any later change such as a manual review." + }, + "method": { + "type": "string", + "nullable": true, + "enum": [ + "open_banking", + "account_lookup", + "microdeposits", + "statement", + "none" + ], + "description": "How the account was verified: `open_banking` (bank sign-in: the person signed in at their bank), `account_lookup` (account check: the person typed the account details and the bank confirmed the name), `microdeposits` or `statement` (a statement upload). `none` when the person continued without a bank before any attempt was evaluated, so no account was verified. After an evaluated attempt, it stays the method of that attempt. `null` until an account is evaluated, and when the workflow narrows the data it returns. Workflow rules read the same value as `bank.method`." + }, + "provider": { + "type": "string", + "nullable": true, + "description": "The key of the route that ran the connection. Read `method`, not this key, to tell how the account was verified." + }, + "credential_source": { + "type": "string", + "nullable": true, + "enum": [ + "native", + "byok" + ], + "description": "`native`: Didit's own route, billed to your Didit balance. `byok`: a marketplace provider you connected with your own credentials." + }, + "route": { + "type": "object", + "nullable": true, + "description": "The route chosen before consent. It never changes afterwards.", + "properties": { + "provider_key": { + "type": "string", + "nullable": true, + "description": "The route's key." + }, + "credential_source": { + "type": "string", + "enum": [ + "native", + "byok" + ] + }, + "is_fallback": { + "type": "boolean", + "description": "Whether Didit's own route stood in for a marketplace provider that could not connect." + }, + "provider_connection_id": { + "type": "string", + "nullable": true, + "description": "The marketplace connection used, for `byok`." + }, + "requested_scopes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The data the connection asked for, for example `ownership` or `balances`." + } + } + }, + "connection": { + "type": "object", + "nullable": true, + "description": "The latest attempt's connection. `null` before the first attempt.", + "properties": { + "status": { + "type": "string", + "enum": [ + "pending", + "connected", + "cancelled", + "timeout", + "failed", + "unsupported", + "revoked", + "expired" + ] + }, + "institution_id": { + "type": "string", + "nullable": true, + "description": "The bank's ID. `null` for an account check, where no bank is picked." + }, + "institution_name": { + "type": "string", + "nullable": true, + "description": "The bank's name. `null` for an account check." + }, + "institution_country": { + "type": "string", + "nullable": true, + "description": "The account's country, ISO 3166-1 alpha-3." + }, + "oauth": { + "type": "boolean", + "nullable": true, + "description": "Whether the bank sign-in ran over OAuth or app-to-app. `null` for an account check." + }, + "connected_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the connection succeeded." + }, + "consent_expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the bank consent ends, for bank sign-in." + }, + "last_refreshed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the financial data was last read again." + }, + "attempts": { + "type": "integer", + "description": "The attempts the person spent on this step, every method together." + }, + "reason_code": { + "type": "string", + "nullable": true, + "description": "Why the connection is not simply `connected`, for example `USER_CANCELLED`, `INSTITUTION_DOWN`, `ACCOUNT_NOT_FOUND`, `REFERENCE_IDENTITY_MISSING` or `VERIFICATION_COMPLETED`." + } + } + }, + "account": { + "type": "object", + "nullable": true, + "description": "The evaluated account. `null` until an account is evaluated.", + "properties": { + "account_id_masked": { + "type": "string", + "nullable": true, + "description": "The account number, masked." + }, + "iban_masked": { + "type": "string", + "nullable": true, + "description": "The IBAN, masked." + }, + "sort_code": { + "type": "string", + "nullable": true, + "description": "The sort code, in the United Kingdom." + }, + "routing_number_masked": { + "type": "string", + "nullable": true, + "description": "The routing number, masked." + }, + "type": { + "type": "string", + "nullable": true, + "enum": [ + "checking", + "current", + "savings", + "business", + "other" + ] + }, + "subtype": { + "type": "string", + "nullable": true, + "description": "The bank's own account subtype." + }, + "currency": { + "type": "string", + "nullable": true, + "description": "ISO 4217 currency of the account." + }, + "holder_type": { + "type": "string", + "nullable": true, + "enum": [ + "person", + "business", + "unknown" + ] + }, + "holders_count": { + "type": "integer", + "nullable": true + }, + "is_joint": { + "type": "boolean", + "nullable": true + }, + "is_business": { + "type": "boolean", + "nullable": true + } + } + }, + "ownership": { + "type": "object", + "nullable": true, + "description": "How the account holder compared with the verified identity. `null` until an account is evaluated.", + "properties": { + "result": { + "type": "string", + "enum": [ + "match", + "partial_match", + "no_match", + "unknown", + "not_verifiable" + ], + "description": "`partial_match` is a close match. `not_verifiable`: the bank answered with no holder to compare. `unknown`: the evidence could not be read, or there was no identity to compare." + }, + "reference_identity_source": { + "type": "string", + "nullable": true, + "description": "The step whose identity the holder was compared against: `kyc`, `database_validation`, `expected_details`, `questionnaire`, `document_ai` or `kyb`." + }, + "name_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "Didit's holder name score." + }, + "provider_name_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The bank's own holder name score, when it returned one." + }, + "address_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The holder address score, when address matching ran." + }, + "email_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The holder email score, when email matching ran." + }, + "phone_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The holder phone score, when phone matching ran." + }, + "dob_score": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "nullable": true, + "description": "The holder date of birth score, when the bank shared one." + }, + "close_match": { + "type": "boolean", + "nullable": true, + "description": "Whether the name landed in the close-match band." + }, + "holder_confirmation": { + "type": "string", + "nullable": true, + "enum": [ + "pending", + "confirmed", + "rejected" + ], + "description": "The person's answer when asked to confirm a close-match name. `null` when never asked." + }, + "method": { + "type": "string", + "nullable": true, + "enum": [ + "exact", + "token_set", + "nickname", + "transliteration", + "fuzzy", + "provider_only", + "microdeposits" + ], + "description": "Which name comparison produced the score. This is not the verification method: read the top-level `method` for that." + }, + "matched_holder_index": { + "type": "integer", + "nullable": true + }, + "holders": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Each holder the bank listed, with its scores." + }, + "owner_identity_available": { + "type": "boolean", + "nullable": true + }, + "evidence_freshness": { + "type": "string", + "nullable": true, + "enum": [ + "live", + "cached", + "refreshed" + ] + }, + "evaluated_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the verdict was computed." + } + } + }, + "financial_data": { + "type": "object", + "description": "The financial data the step read. Only bank sign-in reads it.", + "properties": { + "balances": { + "type": "object", + "nullable": true, + "description": "With the `balances` scope." + }, + "transactions": { + "type": "object", + "nullable": true, + "description": "With the `transactions` scope." + }, + "income": { + "type": "object", + "nullable": true, + "description": "With the `income` scope." + }, + "categorization": { + "type": "object", + "nullable": true, + "description": "Didit's categorization of the transactions read. `null` without them." + }, + "not_collected": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "balances", + "income", + "transactions" + ] + }, + "description": "The data the step asked for that the method which verified the account does not collect. Balances, transactions and income come from bank sign-in only, so an account verified by account check lists them here. Never a failure. Empty otherwise." + } + } + }, + "billing": { + "type": "object", + "nullable": true, + "properties": { + "route": { + "type": "string", + "enum": [ + "native", + "byok" + ] + }, + "billed": { + "type": "boolean" + }, + "marketplace_fee": { + "type": "string", + "nullable": true, + "description": "The marketplace platform fee charged, for `byok`." + }, + "usage_type": { + "type": "string", + "nullable": true, + "description": "The usage line the charge was booked on: `bank_verification` for a bank sign-in or an account check, `bank_statement_upload` for a statement upload with no charged bank attempt before it, and `bank_byok_fee` for the marketplace fee on your own provider. `null` when nothing was billed." + } + } + }, + "reference_identity": { + "type": "object", + "nullable": true, + "description": "The identity the holder was compared against, with its source." + }, + "signals": { + "type": "object", + "nullable": true, + "description": "Account signals read from the transaction history, when the step read one." + }, + "connection_abandoned": { + "type": "boolean", + "nullable": true, + "description": "Whether the step ended without a match after the person left at their bank." + }, + "not_covered": { + "type": "boolean", + "nullable": true, + "description": "Whether the person's bank or country could not be reached on this step. Also `true` when the person continued without a bank because no method could verify their country or bank." + }, + "continued_without_bank": { + "type": "boolean", + "description": "Whether the person left the step with **Continue without your bank**. `continuation` then says why. `method` is `none` when no attempt was evaluated before they continued. After an evaluated attempt, such as a holder that does not match, `method` stays the method of that attempt and its verdict and reason codes stand. Returned even when the workflow narrows the data it returns." + }, + "continuation": { + "type": "object", + "nullable": true, + "description": "How the person continued without a bank. `null` when they did not. When the workflow narrows the data it returns, `reason` is still returned and every other key is `null`.", + "properties": { + "reason": { + "type": "string", + "enum": [ + "no_route", + "institution_unsupported", + "attempts_exhausted", + "person_choice" + ], + "description": "Why, as Didit worked it out when the person continued: `no_route` (no bank sign-in or account check serves their country), `institution_unsupported` (the bank they picked cannot be used on this step), `attempts_exhausted` (every attempt was already spent and none was in progress), or `person_choice` (a way to verify was there and they chose to continue, also when their bank could not be reached right now). `no_route` and `institution_unsupported` follow `bank_unsupported_action`, `attempts_exhausted` follows `bank_attempts_exhausted_action`, and `person_choice` follows `bank_skipped_action`, unless an evaluated attempt already decided the step." + }, + "country": { + "type": "string", + "nullable": true, + "description": "The country the person was verifying in, ISO 3166-1 alpha-3. `null` when they continued before picking one." + }, + "offered": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "statement", + "microdeposits" + ] + }, + "nullable": true, + "description": "The other ways to verify that country offered the person when they continued, in the order it offered them: `statement` (a statement upload), then `microdeposits`. Empty when it offered none." + }, + "institution_id": { + "type": "string", + "nullable": true, + "description": "For `institution_unsupported`, the ID of the bank the person could not use: the bank they picked, or the bank their last attempt ended on. Didit takes it from its own list of banks, never from the person's device, so a bank that list does not have is `null`. `null` for every other reason." + }, + "institution_name": { + "type": "string", + "nullable": true, + "description": "For `institution_unsupported`, the name of that bank, from the same list as `institution_id`. `null` when that list does not have the bank, and for every other reason." + }, + "continued_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the person continued." + } + } + }, + "evidence_purged_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the bank evidence was deleted under `bank_evidence_retention_days`. `null` otherwise." + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Informational codes, for example `holder_declared`. A warning never changes the status on its own." + }, + "reason_codes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Why the verdict came out the way it did, for example `OWNER_NAME_PARTIAL`. Several may apply. `NOT_COVERED`: the person continued without a bank because no method could verify their country or bank. `SKIPPED`: the person chose to continue without a bank while a way to verify was there. Neither is added when an evaluated attempt already decided the step." + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BankAccountCheckPersonHolder": { + "type": "object", + "description": "The person who should hold the account.", + "required": [ + "full_name" + ], + "properties": { + "full_name": { + "type": "string", + "maxLength": 255, + "description": "The full name of the person who should hold the account.", + "example": "Maria Oliveira" + } + } + }, + "BankAccountCheckBusinessHolder": { + "type": "object", + "description": "The company that should hold the account. Checks a business account.", + "required": [ + "business_name" + ], + "properties": { + "business_name": { + "type": "string", + "maxLength": 255, + "description": "The name of the company that should hold the account. Checks a business account." + } + } + }, + "BankVerificationPricing": { + "type": "object", + "description": "Public Bank Verification list prices: the statement upload, the own provider key fee and each country's methods.", + "required": [ + "currency", + "statement_upload", + "own_provider_fee_usd", + "countries" + ], + "properties": { + "currency": { + "type": "string", + "enum": [ + "USD" + ], + "description": "Currency of every price in the response.", + "example": "USD" + }, + "statement_upload": { + "$ref": "#/components/schemas/BankStatementUploadPricing" + }, + "own_provider_fee_usd": { + "type": "string", + "description": "Fee for each billable ownership check on a route that uses your own provider contract instead of Didit's, charged whatever the result. A cancelled, timed out or abandoned connection is not billed. Decimal string with 4 decimals, in USD.", + "example": "0.0200" + }, + "countries": { + "type": "object", + "description": "Every country, keyed by ISO 3166-1 alpha-3 code.", + "additionalProperties": { + "$ref": "#/components/schemas/BankVerificationPricingCountry" + } + } + } + }, + "BankStatementUploadPricing": { + "type": "object", + "description": "How the bank statement upload is priced.", + "required": [ + "price_usd", + "included_after_charged_bank_attempt", + "standalone_available" + ], + "properties": { + "price_usd": { + "type": "string", + "description": "Price of a statement upload after a charged bank attempt. Decimal string with 4 decimals, in USD.", + "example": "0.0000" + }, + "included_after_charged_bank_attempt": { + "type": "boolean", + "description": "`true` when the upload is included at no extra charge after a charged bank attempt in the same session.", + "example": true + }, + "standalone_available": { + "type": "boolean", + "description": "`true` when a statement upload can run on its own, with no bank attempt first. `false` in production; sandbox sessions can test it without billing.", + "example": false + } + } + }, + "BankVerificationPricingCountry": { + "type": "object", + "description": "One country's Bank Verification methods and their list prices.", + "required": [ + "name", + "methods" + ], + "properties": { + "name": { + "type": "string", + "description": "Country name in English.", + "example": "United Kingdom" + }, + "methods": { + "$ref": "#/components/schemas/BankVerificationPricingMethods" + } + } + }, + "BankVerificationPricingMethods": { + "type": "object", + "description": "The two Bank Verification methods in a country.", + "required": [ + "open_banking", + "account_lookup" + ], + "properties": { + "open_banking": { + "$ref": "#/components/schemas/BankSignInPricing" + }, + "account_lookup": { + "$ref": "#/components/schemas/BankAccountCheckPricing" + } + } + }, + "BankSignInPricing": { + "type": "object", + "description": "Bank sign-in: the person signs in to their bank. Priced by market, the same for every bank in it.", + "required": [ + "available", + "market", + "price_usd", + "add_ons" + ], + "properties": { + "available": { + "type": "boolean", + "description": "`true` only where Didit serves bank sign-in in this country in production.", + "example": true + }, + "market": { + "type": "string", + "nullable": true, + "description": "The market whose price applies. `null` when not available.", + "example": "UK and EEA" + }, + "price_usd": { + "type": "string", + "nullable": true, + "description": "List price of one bank sign-in. `null` when not available. Decimal string with 4 decimals, in USD.", + "example": "0.7900" + }, + "add_ons": { + "type": "object", + "description": "Add-ons priced in this country, each a decimal string with 4 decimals in USD, added to the bank sign-in price. Only the add-ons the country offers are listed; empty when bank sign-in is not available.", + "properties": { + "balance": { + "type": "string", + "description": "Account balance add-on.", + "example": "0.3500" + }, + "transactions": { + "type": "string", + "description": "Transactions add-on.", + "example": "1.3000" + } + } + } + } + }, + "BankAccountCheckPricing": { + "type": "object", + "description": "Account check: the person types their account number and Didit checks it with the bank. Priced per country.", + "required": [ + "available", + "price_usd" + ], + "properties": { + "available": { + "type": "boolean", + "description": "`true` only where Didit serves the account check in this country in production.", + "example": true + }, + "price_usd": { + "type": "string", + "nullable": true, + "description": "List price of one account check the bank answers. `null` when not available. Decimal string with 4 decimals, in USD.", + "example": "0.4000" + } + } + }, + "ImportProvider": { + "type": "object", + "properties": { + "key": { + "type": "string", + "enum": [ + "sumsub", + "jumio", + "veriff", + "onfido", + "persona", + "metamap", + "socure", + "idnow", + "shufti", + "idenfy", + "ondato", + "trulioo", + "au10tix", + "complycube", + "other" + ] + }, + "name": { + "type": "string" + }, + "mode": { + "type": "string", + "enum": [ + "api_list", + "api_ids", + "export_file" + ], + "description": "`api_list`: Didit lists records with the key. `api_ids`: upload the record IDs from an export. `export_file`: upload an export in Didit's format." + }, + "record_noun": { + "type": "string" + }, + "credential_fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "label": { + "type": "string" + }, + "secret": { + "type": "boolean" + } + } + } + }, + "regions": { + "type": "array", + "items": { + "type": "string" + } + }, + "required_permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "docs_url": { + "type": "string", + "nullable": true + }, + "status_mapping": { + "type": "object", + "additionalProperties": { + "type": "string", + "enum": [ + "Approved", + "Declined", + "In Review", + "Expired", + "Not Finished", + "Abandoned" + ] + } + } + } + }, + "ImportJobSummary": { + "type": "object", + "description": "One import job of any kind, in the shape the console's Import jobs drawer reads.", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "kind": { + "type": "string", + "enum": [ + "sessions", + "businesses", + "users", + "status_rules", + "transactions" + ] + }, + "source": { + "type": "string", + "enum": [ + "file", + "api", + "provider" + ] + }, + "provider": { + "type": "string", + "nullable": true + }, + "provider_name": { + "type": "string", + "nullable": true + }, + "filename": { + "type": "string", + "nullable": true + }, + "state": { + "type": "string", + "enum": [ + "ready", + "queued", + "running", + "paused", + "completed", + "errors", + "failed", + "cancelled" + ], + "description": "The job's state as the console shows it; `errors` is a completed job with rejected rows." + }, + "status": { + "type": "string" + }, + "is_dry_run": { + "type": "boolean" + }, + "credential_hint": { + "type": "string", + "nullable": true + }, + "resumes_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_error": { + "type": "string", + "nullable": true + }, + "started_by": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "nullable": true + } + } + }, + "progress": { + "type": "object", + "properties": { + "total": { + "type": "integer", + "format": "int64" + }, + "processed": { + "type": "integer", + "format": "int64" + }, + "imported": { + "type": "integer", + "format": "int64" + }, + "updated": { + "type": "integer", + "format": "int64" + }, + "already_in_didit": { + "type": "integer", + "format": "int64" + }, + "failed": { + "type": "integer", + "format": "int64" + }, + "not_finished": { + "type": "integer", + "format": "int64" + }, + "same_person": { + "type": "integer", + "format": "int64" + } + } + }, + "face_templates": { + "type": "object", + "nullable": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "built": { + "type": "integer", + "format": "int64" + }, + "duplicates": { + "type": "integer", + "format": "int64" + }, + "blocklisted": { + "type": "integer", + "format": "int64" + } + } + }, + "eta_seconds": { + "type": "integer", + "nullable": true + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "started_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "FiledRegulatoryReport": { + "type": "object", + "description": "A finalized regulatory report that prevents the requested deletion.", + "required": [ + "uuid", + "report_number", + "report_type", + "case_uuid" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Report UUID." + }, + "report_number": { + "type": "string", + "description": "Report number shown in the Console (for example, SAR-0001)." + }, + "report_type": { + "type": "string", + "enum": [ + "SAR", + "STR", + "CTR", + "TTR", + "UTR", + "IFT", + "CBR", + "TFR" + ], + "description": "Regulatory report type." + }, + "case_uuid": { + "type": "string", + "format": "uuid", + "description": "Case the report belongs to." + } + } + }, + "FiledReportTransaction": { + "type": "object", + "description": "A transaction that cannot be deleted because a finalized regulatory report lists it.", + "required": [ + "uuid", + "transaction_id", + "reports" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "Didit transaction UUID." + }, + "transaction_id": { + "type": "string", + "description": "The transaction identifier supplied when the transaction was created." + }, + "reports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FiledRegulatoryReport" + }, + "description": "Finalized reports that list this transaction." + } + } + }, + "InFiledReportError": { + "type": "object", + "description": "The deletion is refused because the selected data is in a finalized regulatory report. Nothing is deleted. Delete the report, then repeat the request.", + "required": [ + "detail", + "code" + ], + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "in_filed_report" + ], + "description": "Stable error code for a deletion blocked by a finalized regulatory report." + }, + "reports": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FiledRegulatoryReport" + }, + "description": "Reports that block a person, company, case or session deletion." + }, + "transactions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FiledReportTransaction" + }, + "description": "Transactions that block a transaction deletion, each with the reports that list it." + } + } + }, + "SubjectDeletionInProgressError": { + "type": "object", + "description": "Another request is still deleting this person or company. This request deleted nothing and can be repeated once that deletion has finished; a repeat then deletes whatever is still selected.", + "required": [ + "detail", + "code" + ], + "properties": { + "detail": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "subject_deletion_in_progress" + ], + "description": "Stable error code for a person or company another request is still deleting." + } + } + }, + "TravelRuleNetworkParticipantSummary": { + "type": "object", + "nullable": true, + "description": "This application's own participant on the network, or null when it has none.", + "properties": { + "mode": { + "type": "string", + "enum": [ + "platform", + "own" + ], + "description": "platform: carried by Didit's membership; own: onboarded under the application's own membership." + }, + "status": { + "type": "string", + "enum": [ + "not_requested", + "questionnaire_pending", + "submitted_to_network", + "active", + "suspended", + "rejected" + ], + "description": "not_requested: never requested, or the network refused the first request (see last_error); questionnaire_pending: waiting for the due-diligence questionnaire to be approved; submitted_to_network: handed to the network's onboarding; active: the network gave this application its own participant code; suspended / rejected: Didit suspended or rejected it." + }, + "participant_code": { + "type": "string", + "description": "The code the network gave this application (for GTR, its VASP code). Outbound messages carry this code, never Didit's own." + }, + "requested_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "activated_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_error": { + "type": "string", + "description": "The network's answer when it refused the registration; empty otherwise." + } + } + }, + "TravelRuleNetworkState": { + "type": "object", + "description": "How Travel Rule messages on one network would be sent for this application, and why they cannot be. Configuration status, not a live health or delivery check.", + "properties": { + "rail": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA" + ] + }, + "mode": { + "type": "string", + "enum": [ + "own", + "didit", + "off", + "unavailable" + ], + "description": "own: the application's connected membership is used; didit: Didit's membership carries the application as its own active participant; off: platform routing disabled in disabled_networks with no own membership; unavailable: nothing can send on this network yet (see state and reason)." + }, + "state": { + "type": "string", + "enum": [ + "own_membership", + "live_through_didit", + "off", + "didit_onboarding", + "questionnaire_needed", + "questionnaire_in_review", + "request_access", + "request_sent", + "unavailable", + "error" + ], + "description": "own_membership: your connected membership sends; live_through_didit: your own participant on Didit's membership sends; off: you switched platform routing off; didit_onboarding: Didit's own membership of this network is not live yet, so it cannot carry anyone; questionnaire_needed / questionnaire_in_review: the due-diligence questionnaire must be submitted or is with Didit; request_access: the questionnaire is approved, request the network; request_sent: your participant is being onboarded; unavailable: see reason; error: see reason." + }, + "reason": { + "type": "string", + "enum": [ + "own_membership_connected", + "platform_network_switched_off", + "own_membership_only", + "sandbox_application", + "didit_membership_not_live", + "participant_suspended", + "participant_rejected", + "questionnaire_not_submitted", + "questionnaire_submitted", + "questionnaire_changes_requested", + "questionnaire_rejected", + "participant_not_requested", + "participant_active", + "participant_key_missing", + "participant_key_unresolved", + "provisioning_failed", + "questionnaire_pending", + "submitted_to_network" + ], + "description": "Machine-readable detail of state. own_membership_only: Didit does not carry applications on this network (TRUST), connect your own membership; sandbox_application: sandbox transfers never reach a network; provisioning_failed: the network refused the registration, the participant's last_error says why and requesting the network again retries." + }, + "platform_available": { + "type": "boolean", + "description": "Whether Didit's own membership of this network is configured; not a live health check." + }, + "connected": { + "type": "boolean", + "description": "Whether a connected customer membership exists. Credentials are validated locally; network validation occurs on a live exchange." + }, + "participant": { + "$ref": "#/components/schemas/TravelRuleNetworkParticipantSummary" + }, + "vasps_reachable": { + "type": "integer", + "description": "Directory VASPs reachable on this network." + }, + "routed_30d": { + "type": "integer", + "description": "This application's transfers carried on this network in the last 30 days." + } + } + }, + "TravelRuleNetworkQuestionnaireAnswers": { + "type": "object", + "description": "Didit's network due-diligence questionnaire. Saves are partial: a save keeps every answer it does not name. Submitting requires every field except lei and data_protection_contact_email, and the three attestations true.", + "properties": { + "legal_name": { + "type": "string", + "maxLength": 255 + }, + "registration_number": { + "type": "string", + "maxLength": 255 + }, + "incorporation_country": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code." + }, + "registered_address": { + "type": "string", + "maxLength": 1000 + }, + "website": { + "type": "string", + "format": "uri", + "maxLength": 500 + }, + "lei": { + "type": "string", + "maxLength": 20, + "description": "Optional. A valid ISO 17442 LEI when given." + }, + "licences": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "regulator", + "jurisdiction", + "licence_type", + "licence_number" + ], + "properties": { + "regulator": { + "type": "string", + "maxLength": 255 + }, + "jurisdiction": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code." + }, + "licence_type": { + "type": "string", + "maxLength": 255 + }, + "licence_number": { + "type": "string", + "maxLength": 255 + } + } + }, + "description": "Every licence or registration the VASP holds." + }, + "operating_jurisdictions": { + "type": "array", + "minItems": 1, + "maxItems": 250, + "items": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "ISO 3166-1 alpha-2 country code." + } + }, + "compliance_officer_name": { + "type": "string", + "maxLength": 255 + }, + "compliance_officer_email": { + "type": "string", + "format": "email" + }, + "aml_ctf_policy_attested": { + "type": "boolean", + "description": "Must be true to submit." + }, + "sanctions_screening_attested": { + "type": "boolean", + "description": "Must be true to submit." + }, + "data_protection_attested": { + "type": "boolean", + "description": "Must be true to submit." + }, + "data_protection_contact_email": { + "type": "string", + "format": "email", + "description": "Optional." + }, + "expected_monthly_transfers": { + "type": "integer", + "minimum": 0 + }, + "expected_monthly_volume_usd": { + "type": "string", + "format": "decimal", + "description": "Expected monthly volume in USD, up to 2 decimal places. Returned as a string." + }, + "assets": { + "type": "array", + "minItems": 1, + "maxItems": 200, + "items": { + "type": "string", + "maxLength": 32 + }, + "description": "Asset tickers, stored upper-case, sorted and de-duplicated." + } + } + }, + "TravelRuleNetworkQuestionnaire": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "draft", + "submitted", + "changes_requested", + "approved", + "rejected" + ], + "description": "Answers can be saved while draft or changes_requested; submitted, approved and rejected are Didit's to move." + }, + "answers": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaireAnswers" + }, + "submitted_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "reviewed_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "review_note": { + "type": "string", + "description": "Didit's note on its last decision, for example what to change." + }, + "history": { + "type": "array", + "readOnly": true, + "description": "Every status change, oldest first.", + "items": { + "type": "object", + "properties": { + "at": { + "type": "string", + "format": "date-time" + }, + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "note": { + "type": "string", + "description": "Review note or the network's answer, when there is one." + } + } + } + }, + "updated_at": { + "type": "string", + "format": "date-time", + "nullable": true + } + } + }, + "TravelRuleNetworkParticipant": { + "type": "object", + "description": "This application's own participant on one network.", + "properties": { + "network": { + "type": "string", + "enum": [ + "gtr", + "trust", + "verifyvasp", + "sygna" + ] + }, + "rail": { + "type": "string", + "enum": [ + "GTR", + "TRUST", + "VERIFYVASP", + "SYGNA" + ] + }, + "mode": { + "type": "string", + "enum": [ + "platform", + "own" + ] + }, + "status": { + "type": "string", + "enum": [ + "not_requested", + "questionnaire_pending", + "submitted_to_network", + "active", + "suspended", + "rejected" + ], + "description": "not_requested: never requested, or the network refused the first request (see last_error); questionnaire_pending: waiting for the due-diligence questionnaire to be approved; submitted_to_network: handed to the network's onboarding; active: the network gave this application its own participant code; suspended / rejected: Didit suspended or rejected it." + }, + "participant_code": { + "type": "string" + }, + "requested_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "submitted_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "activated_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "suspended_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "last_error": { + "type": "string", + "description": "The network's answer when it refused the registration; empty otherwise." + }, + "history": { + "type": "array", + "readOnly": true, + "description": "Every status change, oldest first.", + "items": { + "type": "object", + "properties": { + "at": { + "type": "string", + "format": "date-time" + }, + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "note": { + "type": "string", + "description": "Review note or the network's answer, when there is one." + } + } + } + } + } + }, + "TravelRuleNetworksOverview": { + "type": "object", + "properties": { + "platform_networks_enabled": { + "type": "boolean", + "description": "Whether network access through Didit's membership is open. While false, questionnaire saves, submissions and network requests answer 403." + }, + "questionnaire": { + "$ref": "#/components/schemas/TravelRuleNetworkQuestionnaire" + }, + "participants": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TravelRuleNetworkParticipant" + } + }, + "networks": { + "type": "object", + "readOnly": true, + "description": "Per network, how this application would send and why it cannot. No network, including GTR, is included by default: a network is reachable through your own connected membership, or through Didit's membership once Didit's membership of that network is live and your own participant on it is active. Not a live health or delivery check.", + "properties": { + "gtr": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "trust": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "verifyvasp": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + }, + "sygna": { + "$ref": "#/components/schemas/TravelRuleNetworkState" + } + } + } + } + }, + "AddressValidationResult": { + "type": "object", + "description": "The result of one Address Validation: whether the address exists, and its normalised form. It confirms the address only; it never links the address to a person.", + "required": [ + "status", + "verification_level", + "address", + "formatted_address", + "latitude", + "longitude" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "verified", + "partially_verified", + "not_verified", + "unavailable" + ], + "description": "`verified`: the address was confirmed down to the building (or the flat or PO box). `partially_verified`: it was confirmed only to the street, or to the most precise level the postal reference data holds for that place. `not_verified`: only the region or the city matched, or nothing did. `unavailable`: the validation could not run (for example the reference service did not answer); it is not charged and never affects a verification." + }, + "verification_level": { + "type": "string", + "nullable": true, + "enum": [ + "administrative_area", + "locality", + "thoroughfare", + "premise", + "delivery_point", + null + ], + "description": "The most precise level the address was matched to. `null` when nothing matched or `status` is `unavailable`." + }, + "address": { + "type": "object", + "nullable": true, + "description": "The normalised address. `null` when `status` is `unavailable`.", + "properties": { + "address_line_1": { + "type": "string", + "nullable": true, + "description": "First delivery line, written the way the country writes it.", + "example": "1600 Amphitheatre Pkwy" + }, + "address_line_2": { + "type": "string", + "nullable": true, + "description": "Second delivery line (a flat, building or neighbourhood), when the address has one." + }, + "city": { + "type": "string", + "nullable": true, + "example": "Mountain View" + }, + "region": { + "type": "string", + "nullable": true, + "description": "State, province or region.", + "example": "CA" + }, + "postal_code": { + "type": "string", + "nullable": true, + "example": "94043-1351" + }, + "country": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country code.", + "example": "US" + } + } + }, + "formatted_address": { + "type": "string", + "nullable": true, + "description": "The normalised address on one line.", + "example": "1600 Amphitheatre Pkwy, Mountain View CA 94043-1351" + }, + "latitude": { + "type": "number", + "nullable": true, + "example": 37.422022 + }, + "longitude": { + "type": "number", + "nullable": true, + "example": -122.08429 + } + } + }, + "AddressValidationRequest": { + "type": "object", + "required": [ + "country" + ], + "description": "One address: on one line (`address`) or as structured fields with at least `address_line_1`, never both.", + "properties": { + "address": { + "type": "string", + "maxLength": 500, + "description": "The whole address on one line.", + "example": "Paseo de la Castellana 259, 28046 Madrid" + }, + "address_line_1": { + "type": "string", + "maxLength": 255, + "description": "Street and number.", + "example": "1600 Amphitheatre Pkwy" + }, + "address_line_2": { + "type": "string", + "maxLength": 255, + "description": "Apartment, suite, floor or building." + }, + "city": { + "type": "string", + "maxLength": 128, + "description": "City or locality.", + "example": "Mountain View" + }, + "region": { + "type": "string", + "maxLength": 128, + "description": "State, province or region.", + "example": "CA" + }, + "postal_code": { + "type": "string", + "maxLength": 32, + "example": "94043" + }, + "country": { + "type": "string", + "maxLength": 3, + "description": "ISO 3166-1 alpha-2 or alpha-3 code of the address's country.", + "example": "US" + } + } + }, + "AddressValidationResponse": { + "type": "object", + "required": [ + "request_id", + "address_validation", + "created_at" + ], + "properties": { + "request_id": { + "type": "string", + "format": "uuid", + "description": "Identifier of this request; quote it to support. No session is created." + }, + "address_validation": { + "$ref": "#/components/schemas/AddressValidationResult" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleWalletProofFailureReason": { + "type": "string", + "enum": [ + "", + "SIGNATURE_INVALID", + "SIGNATURE_UNSUPPORTED_SCHEME", + "DEPOSIT_NOT_FOUND", + "DEPOSIT_UNCONFIRMED", + "DEPOSIT_LOOKUP_FAILED", + "DEPOSIT_MISMATCH", + "DEPOSIT_STALE", + "TXID_REUSED", + "CHAIN_UNSUPPORTED", + "WINDOW_EXPIRED", + "OWNERSHIP_CHANGED", + "METHOD_SWITCHED", + "REVIEW_REJECTED", + "ABANDONED" + ], + "description": "Why a wallet-ownership proof, or its last attempt, fell short of VERIFIED. Empty when nothing failed. A PENDING proof can carry one: the last attempt failed and the end user can try again. DEPOSIT_MISMATCH, DEPOSIT_STALE, TXID_REUSED and REVIEW_REJECTED close the proof as REJECTED. SIGNATURE_INVALID: the signature does not match the wallet and challenge. SIGNATURE_UNSUPPORTED_SCHEME: the wallet signed in a format Didit does not verify. DEPOSIT_NOT_FOUND, DEPOSIT_UNCONFIRMED, DEPOSIT_LOOKUP_FAILED: the test deposit is not readable or not confirmed yet; Didit keeps checking it until the test window closes. DEPOSIT_MISMATCH: the transaction does not send the expected amount from the wallet to the deposit address. DEPOSIT_STALE: the transaction predates the challenge. TXID_REUSED: the transaction already proved another wallet. CHAIN_UNSUPPORTED: the method is not available on this chain. WINDOW_EXPIRED: the test window closed. OWNERSHIP_CHANGED: the wallet's holder changed, or the wallet was deleted from your address book, after the widget session was minted. METHOD_SWITCHED: the end user picked another method before finishing this one. REVIEW_REJECTED: your reviewer rejected the screenshot. ABANDONED: the widget session lapsed before the end user finished." + }, + "TravelRuleWidgetProofError": { + "type": "object", + "properties": { + "detail": { + "type": "string", + "description": "Human-readable message in English." + }, + "code": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleWalletProofFailureReason" + } + ], + "description": "Machine-readable reason, so a client can show its own translated message." + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ], + "description": "Present when the proof is closed: REJECTED by the deposit just submitted (400), or REJECTED or EXPIRED when evidence is sent to a proof that is already closed (409)." + }, + "proof_id": { + "type": "string", + "format": "uuid", + "description": "Present with status: the closed proof." + }, + "method": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SATOSHI_TEST", + "SCREENSHOT", + "SELF_DECLARATION" + ], + "description": "Present with status: the closed proof's method." + }, + "max_attempts": { + "type": "integer", + "description": "Present with status: rejected wallet proofs this session allows." + }, + "attempts_used": { + "type": "integer", + "description": "Present with status: rejected proofs so far." + }, + "attempts_remaining": { + "type": "integer", + "description": "Present with status: when 0, starting another proof answers 409 and you mint a new widget session." + } + } + }, + "TravelRuleWidgetProofCreate": { + "type": "object", + "required": [ + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SATOSHI_TEST", + "SCREENSHOT", + "SELF_DECLARATION" + ], + "description": "The proof method the end user picked. It must be one of the context's allowed_methods." + }, + "wallet_address": { + "type": "string", + "maxLength": 128, + "description": "Defaults to the session's wallet_address. Must match the transfer's wallet when the session is linked to a transfer." + }, + "chain": { + "type": "string", + "maxLength": 32, + "description": "Defaults to the session's chain." + } + } + }, + "TravelRuleWidgetProofVerifyResult": { + "type": "object", + "properties": { + "proof_id": { + "type": "string", + "format": "uuid" + }, + "method": { + "type": "string", + "enum": [ + "MESSAGE_SIGNING", + "SATOSHI_TEST", + "SCREENSHOT", + "SELF_DECLARATION" + ] + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ] + }, + "is_ownership_verified": { + "type": "boolean", + "description": "Present when the proof is VERIFIED: the address book entry is now ownership-verified." + }, + "transfer_status": { + "type": "string", + "nullable": true, + "description": "Present when the proof is VERIFIED: the linked transfer's status, null without one." + }, + "detail": { + "type": "string", + "description": "Present while the proof stays PENDING: a submitted screenshot awaiting review, or a test deposit that is not confirmed yet." + }, + "code": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleWalletProofFailureReason" + } + ], + "description": "Present on a pending test deposit: why it is not confirmed yet." + } + } + }, + "TravelRuleWalletProofReview": { + "type": "object", + "properties": { + "proof_id": { + "type": "string", + "format": "uuid" + }, + "method": { + "type": "string", + "enum": [ + "SCREENSHOT" + ] + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "VERIFIED", + "REJECTED", + "EXPIRED" + ] + }, + "wallet_address": { + "type": "string" + }, + "chain": { + "type": "string", + "description": "Canonical chain identifier, e.g. ETH, BTC, SOL, TRX." + }, + "holder_name": { + "type": "string" + }, + "is_ownership_verified": { + "type": "boolean", + "description": "Whether the address book entry is ownership-verified, by this proof or another one." + }, + "transaction_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The transaction whose Travel Rule transfer the widget session was minted for. Null for a session minted without a transaction_id." + }, + "file_name": { + "type": "string", + "nullable": true + }, + "content_type": { + "type": "string", + "nullable": true + }, + "screenshot_url": { + "type": "string", + "nullable": true, + "description": "Time-limited link to the uploaded screenshot. List the queue again for a fresh link." + }, + "attempts": { + "type": "integer", + "description": "How many files the end user uploaded for this proof." + }, + "failure_reason": { + "allOf": [ + { + "$ref": "#/components/schemas/TravelRuleWalletProofFailureReason" + } + ], + "description": "REVIEW_REJECTED after a rejection, OWNERSHIP_CHANGED when the wallet changed holder or was deleted before a decision. Empty otherwise." + }, + "method_chosen_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the end user picked the screenshot method." + }, + "decided_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "decided_by": { + "type": "string", + "description": "Who closed the proof: review: for a console reviewer, review for an API-key decision, widget or sweeper when Didit closed it automatically. Empty while PENDING." + }, + "review_note": { + "type": "string", + "description": "The note sent with the decision. Empty when none." + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "TravelRuleWalletProofDecision": { + "type": "object", + "required": [ + "decision" + ], + "properties": { + "decision": { + "type": "string", + "enum": [ + "approve", + "reject" + ] + }, + "note": { + "type": "string", + "maxLength": 512, + "description": "Optional. Kept with the proof and returned as review_note." + } + } + }, + "DigitalIdWalletApiWalletDigitalCredentialsRequest": { + "type": "object", + "properties": { + "protocol": { + "type": "string", + "description": "openid4vp-v1-signed." + }, + "request": { + "type": "object", + "additionalProperties": {}, + "description": "Pass as is: navigator.credentials.get({digital: request}) in the browser, or JSON.stringify(request) as GetDigitalCredentialOption's requestJson on Android. Signed by Didit for your application." + } + }, + "required": [ + "protocol", + "request" + ] + }, + "DigitalIdWalletApiGoogleWalletWalletVerificationStartRequest": { + "type": "object", + "description": "Start a Google Wallet verification (USA, GBR, BRA, SGP, TWN).", + "properties": { + "vendor_data": { + "type": "string", + "nullable": true, + "description": "Your own reference for this verification (e.g. your user id). Returned in results and webhooks." + }, + "metadata": { + "type": "object", + "additionalProperties": {}, + "nullable": true, + "description": "Optional JSON object stored with the verification and returned in results and webhooks." + }, + "sandbox_scenario": { + "type": "string", + "minLength": 1, + "description": "Sandbox applications only: the simulated outcome - 'wallet_cancelled', 'wallet_timeout' or 'wallet_provider_error'; any other scenario (or none) verifies. Rejected on live applications. The full catalog is available at GET /v1/sandbox/scenarios/." + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/DigitalIdWalletApiGoogleWalletWalletVerificationStartCountryEnum" + } + ], + "description": "ISO 3166-1 alpha-3 country of the Google Wallet identity.\n\n* `USA` - USA\n* `GBR` - GBR\n* `BRA` - BRA\n* `SGP` - SGP\n* `TWN` - TWN" + }, + "origin": { + "type": "string", + "minLength": 1, + "description": "Where your page or app will ask the wallet: the https origin of your page (its host must be in the application's allowed_return_hosts, or be its white-label domain), e.g. https://app.example.com, or android:apk-key-hash:. The wallet refuses the request anywhere else.", + "maxLength": 256 + } + }, + "required": [ + "country", + "origin" + ] + }, + "DigitalIdWalletApiGoogleWalletWalletVerificationStartCountryEnum": { + "enum": [ + "USA", + "GBR", + "BRA", + "SGP", + "TWN" + ], + "type": "string", + "description": "* `USA` - USA\n* `GBR` - GBR\n* `BRA` - BRA\n* `SGP` - SGP\n* `TWN` - TWN" + }, + "DigitalIdWalletApiWalletDigitalCredentialResponseRequest": { + "type": "object", + "description": "What the wallet answered your page or app, for a ``request_digital_credential`` verification.", + "properties": { + "response": { + "type": "object", + "additionalProperties": {}, + "description": "Exactly what the wallet returned: {protocol, data} of the DigitalCredential from navigator.credentials.get, or the parsed credentialJson of Android's DigitalCredential. Encrypted to Didit; do not alter it." + } + }, + "required": [ + "response" + ] + }, + "SessionFilesExport": { + "type": "object", + "description": "One ZIP with every file of a session, in original quality.", + "required": [ + "export_id", + "session_id", + "status", + "file_count", + "size_bytes", + "download_url", + "expires_at", + "error", + "created_at", + "completed_at" + ], + "properties": { + "export_id": { + "type": "string", + "format": "uuid", + "description": "ID of the export. Pass it to `GET /v3/session/{sessionId}/files-export/{exportId}/`." + }, + "session_id": { + "type": "string", + "format": "uuid", + "description": "The session the files are from." + }, + "status": { + "type": "string", + "enum": [ + "PENDING", + "PROCESSING", + "READY", + "FAILED", + "EXPIRED" + ], + "description": "`PENDING` and `PROCESSING` while the ZIP is prepared, `READY` once it can be downloaded, `FAILED` when it could not be prepared (request a new export), `EXPIRED` once the ZIP was deleted." + }, + "file_count": { + "type": "integer", + "nullable": true, + "description": "Files in the ZIP. Set only while `status` is `READY`." + }, + "size_bytes": { + "type": "integer", + "format": "int64", + "nullable": true, + "description": "Size of the ZIP in bytes. Set only while `status` is `READY`." + }, + "download_url": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "Signed URL of the ZIP, valid for a short time. Set only while `status` is `READY`; read the export again for a new one." + }, + "expires_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the ZIP is deleted, 24 hours after it was ready. Set only while `status` is `READY`." + }, + "error": { + "type": "string", + "nullable": true, + "description": "Why the export failed, when `status` is `FAILED`." + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When the export was requested." + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "When the ZIP was ready or the export failed." + } + } + }, + "TransactionRuleSuggestionsCreatedEvent": { + "description": "A transaction rule suggestion run proposed new rule changes for your application. Sent when the rule-suggestion settings have webhook notifications on. There is no top-level `status`.", + "allOf": [ + { + "$ref": "#/components/schemas/WebhookEventEnvelope" + }, + { + "type": "object", + "properties": { + "webhook_type": { + "type": "string", + "enum": [ + "transaction_rule_suggestions.created" + ], + "description": "Always `transaction_rule_suggestions.created`." + }, + "run_id": { + "type": "string", + "format": "uuid", + "description": "The suggestion run that produced the new suggestions." + }, + "open_count": { + "type": "integer", + "description": "Number of suggestions that are still open after this run, new ones included." + }, + "suggestions": { + "type": "array", + "description": "The suggestions this run created.", + "items": { + "type": "object", + "required": [ + "suggestion_id", + "kind", + "title", + "rule_id", + "expected_impact" + ], + "properties": { + "suggestion_id": { + "type": "string", + "format": "uuid" + }, + "kind": { + "type": "string", + "enum": [ + "ADD", + "TWEAK", + "DISABLE" + ], + "description": "`ADD` proposes a new rule, `TWEAK` changes an existing one, `DISABLE` turns one off." + }, + "title": { + "type": "string" + }, + "rule_id": { + "type": "string", + "format": "uuid", + "nullable": true, + "description": "The rule a `TWEAK` or `DISABLE` suggestion applies to; `null` for `ADD`." + }, + "expected_impact": { + "type": "object", + "additionalProperties": true, + "description": "What the change would do, as the suggestion shows it in the Console." + } + } + } + } + }, + "required": [ + "webhook_type", + "run_id", + "open_count", + "suggestions" + ] + } + ] + }, + "GeolocationCheck": { + "type": "object", + "description": "One Location verification result, the accepted attempt of one workflow node. Measurement, uncertainty and authenticity are separate keys, so a precise unattested fix reads differently from an imprecise attested one.", + "properties": { + "node_id": { + "type": "string", + "nullable": true, + "description": "Workflow node that produced this result, so a workflow with two Location verification steps stays readable." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Feature-level status of this Location verification step, resolved from its warnings. Independent of the session `status`." + }, + "outcome": { + "type": "string", + "nullable": true, + "description": "What happened on this attempt.", + "enum": [ + "verified", + "low_accuracy", + "accuracy_undetermined", + "stale", + "approximate", + "denied", + "restricted", + "unavailable", + "timeout", + "mock_detected", + "outside_geofence", + "in_blocked_area", + "anonymizer_detected", + "skipped", + "replay_rejected", + "unsupported_client", + null + ] + }, + "permission": { + "type": "string", + "nullable": true, + "description": "What the operating system or browser granted. `not_requested` means the step uses the connection's IP location only, so the person is never asked for a permission.", + "enum": [ + "granted_precise", + "granted_approximate", + "denied", + "restricted", + "unavailable", + "timeout", + "dismissed", + "not_requested", + null + ] + }, + "latitude": { + "type": "number", + "nullable": true, + "description": "WGS84 latitude of the fix. `null` when there is no fix.", + "format": "double" + }, + "longitude": { + "type": "number", + "nullable": true, + "description": "WGS84 longitude of the fix. `null` when there is no fix.", + "format": "double" + }, + "accuracy_m": { + "type": "number", + "nullable": true, + "description": "Radius of uncertainty in metres, at the confidence level in `accuracy_confidence`.", + "format": "double" + }, + "accuracy_confidence": { + "type": "string", + "nullable": true, + "description": "Confidence level the platform attaches to `accuracy_m`: `p95` (browsers), `p68` (Android) or `unspecified` (iOS).", + "enum": [ + "p95", + "p68", + "unspecified", + null + ] + }, + "measured_at": { + "type": "string", + "nullable": true, + "description": "When the device took the fix, by its own clock.", + "format": "date-time" + }, + "age_seconds": { + "type": "integer", + "nullable": true, + "description": "Seconds between the device taking the fix and Didit receiving it." + }, + "source": { + "type": "string", + "nullable": true, + "description": "Where the fix came from, when the platform says. `ip` means the position is the connection's IP location.", + "enum": [ + "gps", + "fused", + "network", + "wifi", + "cell", + "browser", + "unknown", + "ip", + null + ] + }, + "platform": { + "type": "string", + "nullable": true, + "description": "The client that produced the fix.", + "enum": [ + "web", + "ios", + "android", + "react_native", + "flutter", + null + ] + }, + "sdk_version": { + "type": "string", + "nullable": true, + "description": "Version of the SDK that produced the fix." + }, + "is_mock": { + "type": "boolean", + "nullable": true, + "description": "`true` when the device says the fix came from a mock location provider or simulation software. `null` when the platform does not report it (browsers cannot tell); `null` is not `false`." + }, + "produced_by_accessory": { + "type": "boolean", + "nullable": true, + "description": "iOS only: `true` when the fix came from an external accessory. `null` elsewhere." + }, + "integrity": { + "type": "string", + "enum": [ + "attested", + "not_attested", + "failed", + "unsupported", + "not_determined" + ], + "description": "Whether device attestation backs the fix. `unsupported` is what browsers report, because the web has no attestation mechanism; it is not a failure." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country the fix is in." + }, + "region_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision the fix is in." + }, + "city": { + "type": "string", + "nullable": true, + "description": "Nearest populated place, a label for reviewers." + }, + "geofence": { + "type": "object", + "description": "Result of the country and area rules of the step.", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether the step applies any country or area rule." + }, + "result": { + "type": "string", + "nullable": true, + "description": "Where the position sits relative to the rules. `undetermined` means the accuracy circle straddles the border or the position resolved to no country; it is not evidence of being outside.", + "enum": [ + "inside", + "outside", + "undetermined", + null + ] + } + } + }, + "inside_geofence": { + "type": "boolean", + "nullable": true, + "description": "`true` when `geofence.result` is `inside`, `false` when `outside`, `null` otherwise." + }, + "geofence_basis": { + "type": "string", + "nullable": true, + "description": "Which position the area result was judged from: the `device` fix, or the connection's `ip` location.", + "enum": [ + "device", + "ip", + null + ] + }, + "inside_areas": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Ids of the configured allowed areas the position is inside." + }, + "blocked_areas": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Ids of the configured blocked areas the position is in." + }, + "position_source": { + "type": "string", + "nullable": true, + "description": "Where the step takes the position from, as configured: the device checked against the IP location, the device only, or the IP location only.", + "enum": [ + "device_and_ip", + "device_only", + "ip_only", + null + ] + }, + "anonymizers": { + "type": "object", + "nullable": true, + "description": "What the connection and device look like, each `true`, `false` or `null` when the source cannot tell.", + "properties": { + "vpn": { + "type": "boolean", + "nullable": true + }, + "proxy": { + "type": "boolean", + "nullable": true + }, + "tor": { + "type": "boolean", + "nullable": true + }, + "spoofing": { + "type": "boolean", + "nullable": true + }, + "emulator": { + "type": "boolean", + "nullable": true + } + } + }, + "ip_distance_km": { + "type": "number", + "nullable": true, + "description": "Distance to the session's IP location after both uncertainty radii are subtracted; `0` when they overlap.", + "format": "double" + }, + "ip_distance_uncertainty_km": { + "type": "number", + "nullable": true, + "description": "Combined radius that was subtracted: the IP location's radius plus this fix's accuracy.", + "format": "double" + }, + "ip_consistent": { + "type": "boolean", + "nullable": true, + "description": "Whether `ip_distance_km` is within the distance configured on the step. `null` when either side is unknown." + }, + "document_distance_km": { + "type": "number", + "nullable": true, + "description": "Distance to the geocoded address on the ID document or proof of address. `null` when no address is on file.", + "format": "double" + }, + "travel_excused": { + "type": "boolean", + "nullable": true, + "description": "Whether the step's travel tolerance excused the distance from the address. `null` when tolerance is off." + }, + "consecutive_mismatches": { + "type": "integer", + "nullable": true, + "description": "On the final attempt: this check plus the same person's earlier mismatches in a row." + }, + "attempts": { + "type": "integer", + "description": "Number of this attempt within the node, starting at 1." + }, + "consent": { + "type": "object", + "nullable": true, + "description": "The consent the person gave before the system prompt, recorded with the wording version and language they saw.", + "properties": { + "accepted_at": { + "type": "string", + "format": "date-time" + }, + "version": { + "type": "string", + "nullable": true + }, + "locale": { + "type": "string", + "nullable": true + } + } + }, + "warnings": { + "type": "array", + "items": { + "type": "object", + "properties": { + "feature": { + "type": "string", + "enum": [ + "ID_VERIFICATION", + "NFC", + "LIVENESS", + "FACEMATCH", + "PROOF_OF_ADDRESS", + "PHONE", + "EMAIL", + "AML", + "LOCATION", + "DATABASE_VALIDATION", + "QUESTIONNAIRE", + "DOCUMENT_AI", + "KYB_REGISTRY", + "KYB_DOCUMENTS", + "KYB_KEY_PEOPLE", + "GEOLOCATION", + "SIGNATURE" + ], + "description": "The feature that generated this warning. The value identifies the feature block the warning belongs to. Each module's warnings array typically uses the value matching its module (e.g. `PHONE` inside `phone_verifications[].warnings`), but the enum above is authoritative across all feature warnings on the decision payload." + }, + "risk": { + "type": "string" + }, + "additional_data": { + "type": "object", + "nullable": true + }, + "log_type": { + "type": "string" + }, + "short_description": { + "type": "string" + }, + "long_description": { + "type": "string" + }, + "node_id": { + "type": "string", + "description": "The workflow graph node ID that generated this warning" + } + } + }, + "description": "Warnings this Location verification step raised, with `feature` set to `GEOLOCATION`. Warning codes start with `LOCATION_`." + } + } + }, + "GeolocationAttempt": { + "type": "object", + "description": "One attempt of a Location verification step: the same keys as `geolocation_checks[]` except `warnings`, plus when Didit received it.", + "properties": { + "node_id": { + "type": "string", + "nullable": true, + "description": "Workflow node that produced this result, so a workflow with two Location verification steps stays readable." + }, + "status": { + "type": "string", + "enum": [ + "Not Finished", + "Approved", + "Declined", + "In Review", + "Resub Requested" + ], + "description": "Feature-level status of this Location verification step, resolved from its warnings. Independent of the session `status`." + }, + "outcome": { + "type": "string", + "nullable": true, + "description": "What happened on this attempt.", + "enum": [ + "verified", + "low_accuracy", + "accuracy_undetermined", + "stale", + "approximate", + "denied", + "restricted", + "unavailable", + "timeout", + "mock_detected", + "outside_geofence", + "in_blocked_area", + "anonymizer_detected", + "skipped", + "replay_rejected", + "unsupported_client", + null + ] + }, + "permission": { + "type": "string", + "nullable": true, + "description": "What the operating system or browser granted. `not_requested` means the step uses the connection's IP location only, so the person is never asked for a permission.", + "enum": [ + "granted_precise", + "granted_approximate", + "denied", + "restricted", + "unavailable", + "timeout", + "dismissed", + "not_requested", + null + ] + }, + "latitude": { + "type": "number", + "nullable": true, + "description": "WGS84 latitude of the fix. `null` when there is no fix.", + "format": "double" + }, + "longitude": { + "type": "number", + "nullable": true, + "description": "WGS84 longitude of the fix. `null` when there is no fix.", + "format": "double" + }, + "accuracy_m": { + "type": "number", + "nullable": true, + "description": "Radius of uncertainty in metres, at the confidence level in `accuracy_confidence`.", + "format": "double" + }, + "accuracy_confidence": { + "type": "string", + "nullable": true, + "description": "Confidence level the platform attaches to `accuracy_m`: `p95` (browsers), `p68` (Android) or `unspecified` (iOS).", + "enum": [ + "p95", + "p68", + "unspecified", + null + ] + }, + "measured_at": { + "type": "string", + "nullable": true, + "description": "When the device took the fix, by its own clock.", + "format": "date-time" + }, + "age_seconds": { + "type": "integer", + "nullable": true, + "description": "Seconds between the device taking the fix and Didit receiving it." + }, + "source": { + "type": "string", + "nullable": true, + "description": "Where the fix came from, when the platform says. `ip` means the position is the connection's IP location.", + "enum": [ + "gps", + "fused", + "network", + "wifi", + "cell", + "browser", + "unknown", + "ip", + null + ] + }, + "platform": { + "type": "string", + "nullable": true, + "description": "The client that produced the fix.", + "enum": [ + "web", + "ios", + "android", + "react_native", + "flutter", + null + ] + }, + "sdk_version": { + "type": "string", + "nullable": true, + "description": "Version of the SDK that produced the fix." + }, + "is_mock": { + "type": "boolean", + "nullable": true, + "description": "`true` when the device says the fix came from a mock location provider or simulation software. `null` when the platform does not report it (browsers cannot tell); `null` is not `false`." + }, + "produced_by_accessory": { + "type": "boolean", + "nullable": true, + "description": "iOS only: `true` when the fix came from an external accessory. `null` elsewhere." + }, + "integrity": { + "type": "string", + "enum": [ + "attested", + "not_attested", + "failed", + "unsupported", + "not_determined" + ], + "description": "Whether device attestation backs the fix. `unsupported` is what browsers report, because the web has no attestation mechanism; it is not a failure." + }, + "country_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-1 alpha-2 country the fix is in." + }, + "region_code": { + "type": "string", + "nullable": true, + "description": "ISO 3166-2 subdivision the fix is in." + }, + "city": { + "type": "string", + "nullable": true, + "description": "Nearest populated place, a label for reviewers." + }, + "geofence": { + "type": "object", + "description": "Result of the country and area rules of the step.", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether the step applies any country or area rule." + }, + "result": { + "type": "string", + "nullable": true, + "description": "Where the position sits relative to the rules. `undetermined` means the accuracy circle straddles the border or the position resolved to no country; it is not evidence of being outside.", + "enum": [ + "inside", + "outside", + "undetermined", + null + ] + } + } + }, + "inside_geofence": { + "type": "boolean", + "nullable": true, + "description": "`true` when `geofence.result` is `inside`, `false` when `outside`, `null` otherwise." + }, + "geofence_basis": { + "type": "string", + "nullable": true, + "description": "Which position the area result was judged from: the `device` fix, or the connection's `ip` location.", + "enum": [ + "device", + "ip", + null + ] + }, + "inside_areas": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Ids of the configured allowed areas the position is inside." + }, + "blocked_areas": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Ids of the configured blocked areas the position is in." + }, + "position_source": { + "type": "string", + "nullable": true, + "description": "Where the step takes the position from, as configured: the device checked against the IP location, the device only, or the IP location only.", + "enum": [ + "device_and_ip", + "device_only", + "ip_only", + null + ] + }, + "anonymizers": { + "type": "object", + "nullable": true, + "description": "What the connection and device look like, each `true`, `false` or `null` when the source cannot tell.", + "properties": { + "vpn": { + "type": "boolean", + "nullable": true + }, + "proxy": { + "type": "boolean", + "nullable": true + }, + "tor": { + "type": "boolean", + "nullable": true + }, + "spoofing": { + "type": "boolean", + "nullable": true + }, + "emulator": { + "type": "boolean", + "nullable": true + } + } + }, + "ip_distance_km": { + "type": "number", + "nullable": true, + "description": "Distance to the session's IP location after both uncertainty radii are subtracted; `0` when they overlap.", + "format": "double" + }, + "ip_distance_uncertainty_km": { + "type": "number", + "nullable": true, + "description": "Combined radius that was subtracted: the IP location's radius plus this fix's accuracy.", + "format": "double" + }, + "ip_consistent": { + "type": "boolean", + "nullable": true, + "description": "Whether `ip_distance_km` is within the distance configured on the step. `null` when either side is unknown." + }, + "document_distance_km": { + "type": "number", + "nullable": true, + "description": "Distance to the geocoded address on the ID document or proof of address. `null` when no address is on file.", + "format": "double" + }, + "travel_excused": { + "type": "boolean", + "nullable": true, + "description": "Whether the step's travel tolerance excused the distance from the address. `null` when tolerance is off." + }, + "consecutive_mismatches": { + "type": "integer", + "nullable": true, + "description": "On the final attempt: this check plus the same person's earlier mismatches in a row." + }, + "attempts": { + "type": "integer", + "description": "Number of this attempt within the node, starting at 1." + }, + "consent": { + "type": "object", + "nullable": true, + "description": "The consent the person gave before the system prompt, recorded with the wording version and language they saw.", + "properties": { + "accepted_at": { + "type": "string", + "format": "date-time" + }, + "version": { + "type": "string", + "nullable": true + }, + "locale": { + "type": "string", + "nullable": true + } + } + }, + "received_at": { + "type": "string", + "nullable": true, + "description": "When Didit received the attempt.", + "format": "date-time" + }, + "created_at": { + "type": "string", + "format": "date-time" + } + } + }, + "BiometricRetention": { + "type": "object", + "description": "When the live biometric data of this session is deleted.", + "required": [ + "jurisdictions", + "deletes_at" + ], + "properties": { + "jurisdictions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Codes of the US states whose biometric rules set a deletion limit for this person (`US` when the state is unknown). Empty when none applies." + }, + "deletes_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "The date the live biometric data (face images and templates) of this session is deleted, from your application's retention setting and, where one applies, the legal limit for the person's jurisdiction. `null` when no date applies yet." + } + } + }, + "TravelRuleManualMatchRequest": { + "type": "object", + "required": [ + "transaction_id" + ], + "properties": { + "transaction_id": { + "type": "string", + "format": "uuid", + "description": "The `transaction_id` of the received Travel Rule message (a row with `kind: message`)." + } + } + }, + "TravelRuleManualMatchResponse": { + "type": "object", + "required": [ + "deposit", + "message" + ], + "properties": { + "deposit": { + "$ref": "#/components/schemas/TravelRuleTransferRow" + }, + "message": { + "$ref": "#/components/schemas/TravelRuleTransferRow" + } + } + } + }, + "securitySchemes": { + "ApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "x-api-key", + "description": "Your application's API key, from Developers -> API keys in the Business Console. The primary key has full access. A named key can be scoped: none, read or write per resource, limited to some workflows or to approved sessions, to a list of IP addresses, and to an expiry date. 401 means the key is wrong, revoked or expired; 403 means no key was sent, the key has no access to this resource or action, or the request came from an address outside its IP list; 404 on a session route means the session is outside the key's workflows or statuses. A key without media access receives image, video and PDF URLs as null, and a key without sessions write receives session links and tokens as null. See https://docs.didit.me/console/api-keys." + }, + "TransactionTokenAuth": { + "type": "apiKey", + "in": "header", + "name": "X-Transaction-Token", + "description": "Short-lived scoped token minted by your backend via POST /v3/transactions/sdk-token/. Used by the Didit SDKs on the device-facing /v1/transactions/ endpoints." + }, + "SessionTokenAuth": { + "type": "apiKey", + "in": "header", + "name": "Session-Token", + "description": "Short-lived token returned in the `session_token` field of POST /v3/session/. Used by the hosted verification flow (and custom sandbox UIs) to call session-scoped endpoints on behalf of the verifying user, without your server-side API key." + }, + "BearerAuth": { + "type": "http", + "scheme": "bearer", + "description": "The application's client-credentials access token." + }, + "CaseRequestToken": { + "type": "apiKey", + "in": "header", + "name": "X-Case-Request-Token", + "description": "Request-specific credential from the secure link. Grants access only to this request." + } + }, + "responses": { + "Unauthorized": { + "description": "Unauthorized", + "content": { + "application/json": { + "examples": { + "Invalid API Key": { + "summary": "Invalid API Key", + "value": { + "detail": "Invalid API Key." + } + } + } + } + } + }, + "Forbidden": { + "description": "Forbidden", + "content": { + "application/json": { + "examples": { + "No Permission": { + "summary": "No Permission", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not Enough Credits": { + "summary": "Not Enough Credits", + "value": { + "error": "You don't have enough credits to perform this request. Please top up at https://business.didit.me" + } + } + } + } + } + }, + "NotFound": { + "description": "Not Found", + "content": { + "application/json": { + "examples": { + "Not Found": { + "summary": "Not Found", + "value": { + "detail": "Resource not found." + } + } + } + } + } + }, + "PhoneRiskValidationError": { + "description": "Request validation failed.", + "content": { + "application/json": { + "examples": { + "Missing phone number": { + "summary": "Missing phone number", + "value": { + "phone_number": [ + "This field is required." + ] + } + }, + "Invalid phone number": { + "summary": "Invalid phone number", + "value": { + "phone_number": [ + "Invalid phone number provided." + ] + } + }, + "Invalid signal IP": { + "summary": "Invalid signal IP", + "value": { + "signals": { + "ip": [ + "Enter a valid IPv4 or IPv6 address." + ] + } + } + } + } + } + } + }, + "EmailRiskValidationError": { + "description": "Request validation failed.", + "content": { + "application/json": { + "examples": { + "Missing email": { + "summary": "Missing email", + "value": { + "email": [ + "This field is required." + ] + } + }, + "Invalid email": { + "summary": "Invalid email", + "value": { + "email": [ + "Enter a valid email address." + ] + } + }, + "Invalid country hint": { + "summary": "Invalid country hint", + "value": { + "country_code": [ + "Ensure this field has no more than 3 characters." + ] + } + } + } + } + } + }, + "IPRiskValidationError": { + "description": "Request validation failed.", + "content": { + "application/json": { + "examples": { + "Missing IP address": { + "summary": "Missing IP address", + "value": { + "ip_address": [ + "This field is required." + ] + } + }, + "Invalid IP address": { + "summary": "Invalid IP address", + "value": { + "ip_address": [ + "Enter a valid IPv4 or IPv6 address." + ] + } + }, + "Country hint too long": { + "summary": "Country hint too long", + "value": { + "claimed_country": [ + "Ensure this field has no more than 3 characters." + ] + } + } + } + } + } + }, + "RiskPermissionDenied": { + "description": "The API key is missing or invalid, or the organization cannot cover the live lookup. This API never returns `401`.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "summary": "No `x-api-key` header, or the key is invalid or revoked", + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "summary": "Organization balance cannot cover the lookup price", + "value": { + "error": "You don't have enough credits to perform this request." + } + } + } + } + } + }, + "RateLimitedWrite": { + "description": "The shared write limit of 300 requests per minute per API key was exceeded. Honor `Retry-After` before retrying.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "detail" + ], + "properties": { + "detail": { + "type": "string" + } + } + }, + "example": { + "detail": "Write request rate limit exceeded. You can make up to 300 requests per minute." + } + } + } + }, + "AddressValidationValidationError": { + "description": "The request body is invalid: a missing or unknown `country`, no address, or both `address` and the structured fields.", + "content": { + "application/json": { + "examples": { + "Unknown country": { + "value": { + "country": [ + "Use an ISO 3166-1 alpha-2 or alpha-3 country code, e.g. 'US' or 'USA'." + ] + } + }, + "No address": { + "value": { + "address": [ + "Send the address: 'address' on one line, or the structured fields with 'address_line_1'." + ] + } + }, + "Both shapes": { + "value": { + "address": [ + "Send either 'address' or the structured fields (address_line_1, ...), not both." + ] + } + } + } + } + } + }, + "AddressValidationPermissionDenied": { + "description": "The API key is missing or invalid, a scoped key lacks `sessions` write access, or the organization cannot cover the validation. This API never returns `401`.", + "content": { + "application/json": { + "examples": { + "Missing or invalid API key": { + "value": { + "detail": "You do not have permission to perform this action." + } + }, + "Not enough credits": { + "value": { + "detail": "You don't have enough credits to perform this request. Please top up at https://business.didit.me", + "code": "insufficient_credits" + } + } + } + } + } + } + }, + "parameters": { + "WebhookSignature": { + "name": "X-Signature", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Lowercase hex HMAC-SHA256, keyed with the destination's secret, of the exact raw request body." + }, + "WebhookSignatureV2": { + "name": "X-Signature-V2", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Lowercase hex HMAC-SHA256, keyed with the destination's secret, of the canonical JSON of the body (keys sorted, no whitespace, non-ASCII characters unescaped). Recommended, because it survives middleware that re-encodes the body." + }, + "WebhookSignatureSimple": { + "name": "X-Signature-Simple", + "in": "header", + "required": true, + "deprecated": true, + "schema": { + "type": "string" + }, + "description": "Lowercase hex HMAC-SHA256, keyed with the destination's secret, of `{timestamp}:{session_id}:{status}:{webhook_type}` (missing fields as empty strings). Covers the envelope only, never the payload." + }, + "WebhookTimestamp": { + "name": "X-Timestamp", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "description": "Unix time in seconds, equal to the body's `timestamp`. Reject deliveries older than a few minutes." + } + } + }, + "x-webhooks": { + "status.updated": { + "post": { + "summary": "Session status changed", + "description": "A verification session changed status. `decision` is sent only for `Approved`, `Declined`, `In Review` and `Abandoned`; it has the shape of `GET /v3/session/{sessionId}/decision/` for destinations on webhook version 3 (the default), and the older reduced shape for version 1 or 2 destinations. Business sessions always receive the version 3 business decision.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "data.updated": { + "post": { + "summary": "Session data changed", + "description": "Data of a verification session changed without a status change, for example after a manual review edit, a step override or an ongoing-monitoring rescreen. `decision` is sent only for `Approved`, `Declined`, `In Review` and `Abandoned`; it has the shape of `GET /v3/session/{sessionId}/decision/` for destinations on webhook version 3 (the default), and the older reduced shape for version 1 or 2 destinations. Business sessions always receive the version 3 business decision.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionDataUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "user.status.updated": { + "post": { + "summary": "User status changed", + "description": "A user changed status. Not sent when the user is created.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "user.data.updated": { + "post": { + "summary": "User data changed", + "description": "Fields of a user changed. Not sent when the user is created. One save can produce both a status and a data event.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDataUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "business.status.updated": { + "post": { + "summary": "Business status changed", + "description": "A business changed status. Not sent when the business is created.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "business.data.updated": { + "post": { + "summary": "Business data changed", + "description": "Fields of a business changed. Not sent when the business is created. One save can produce both a status and a data event.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BusinessDataUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "transaction.created": { + "post": { + "summary": "Transaction created", + "description": "A transaction was created and screened. Sent for live applications only.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionCreatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "transaction.status.updated": { + "post": { + "summary": "Transaction status changed", + "description": "A transaction changed status, from a rule, a review or an end-user action. Sent for live applications only.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "travel_rule.status.updated": { + "post": { + "summary": "Travel Rule status changed", + "description": "A Travel Rule exchange changed status. Sent on every transition up to and including `FINISHED`, `CANCELLED` or `EXPIRED`, never after. Sent for live applications and, for Travel Rule exchanges, for sandbox applications too (`environment: sandbox`). There is no top-level `status`.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TravelRuleStatusUpdatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "workflow.compliance_changed": { + "post": { + "summary": "Workflow compliance changed", + "description": "The compliance status of a published workflow changed after a re-evaluation. One event per workflow. There is no top-level `status`.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WorkflowComplianceChangedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "kyb.registry_search.resolved": { + "post": { + "summary": "Company search resolved (unsigned callback)", + "description": "Body of the callback sent to the `webhook_url` of `POST /v3/kyb/search/` once the registry search resolves. It is not signed: the request carries `X-Didit-Unsigned-Callback: true` and none of the signature headers, so confirm it by matching `request_id` against a search you started. Uses `event_type`, not `webhook_type`.", + "tags": [ + "Webhook" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KYBRegistrySearchResolvedCallback" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + }, + "transaction_rule_suggestions.created": { + "post": { + "summary": "Transaction rule suggestions created", + "description": "A transaction rule suggestion run proposed new rule changes for your application. Sent when the rule-suggestion settings have webhook notifications on. There is no top-level `status`.", + "tags": [ + "Webhook" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WebhookSignature" + }, + { + "$ref": "#/components/parameters/WebhookSignatureV2" + }, + { + "$ref": "#/components/parameters/WebhookSignatureSimple" + }, + { + "$ref": "#/components/parameters/WebhookTimestamp" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRuleSuggestionsCreatedEvent" + } + } + } + }, + "responses": { + "2XX": { + "description": "Any 2xx response acknowledges the delivery. Other responses and timeouts are retried; see https://docs.didit.me/integration/webhooks." + } + } + } + } + } +} diff --git a/codegen/tests/test_codegen.py b/codegen/tests/test_codegen.py index 57b36c9..e917741 100644 --- a/codegen/tests/test_codegen.py +++ b/codegen/tests/test_codegen.py @@ -284,14 +284,17 @@ def test_removed_operation_stays_as_a_deprecated_method(openapi: pathlib.Path) - del spec["paths"]["/v3/billing/balance/"] (upstream / "verification.json").write_text(json.dumps(spec), "utf-8") shutil.copy(openapi / "account.json", upstream / "account.json") + shutil.copy(openapi / "authentication.json", upstream / "authentication.json") downloads = {name: (upstream / name).read_bytes() for name in spec_sync.UPSTREAM} - changed, summary = spec_sync.sync( - openapi, downloads, today=NOW.date(), now=NOW, version="0.4.2" + result = spec_sync.sync(openapi, downloads, today=NOW.date(), now=NOW, version="0.4.2") + assert result.changed + assert result.commit_type == "feat" + assert result.summary.startswith("## Breaking changes\n") + assert _breaking(result.summary).strip() == ( + "- `GET /v3/billing/balance/`: operation removed; `billing.retrieve_balance` stays as " + "a deprecated method until version 0.5.0" ) - assert changed - assert "billing.retrieve_balance` was removed upstream" in summary - assert "## Breaking changes" in summary deprecated = _read(openapi / "deprecated-operations.json")["methods"] entry = deprecated["billing.retrieve_balance"] assert entry["reason"] == "removed" @@ -319,13 +322,14 @@ def test_renamed_method_stays_as_a_deprecated_alias(openapi: pathlib.Path) -> No "verification.json": json.dumps(spec).encode(), "account.json": (openapi / "account.json").read_bytes(), } - changed, summary = spec_sync.sync( - openapi, downloads, today=NOW.date(), now=NOW, version="0.1.0" - ) - assert changed - breaking = summary.split("## Breaking changes", 1)[1].split("##", 1)[0] - assert "`billing.retrieve_balance` was renamed upstream to `billing.get_balance`" in breaking - assert "until version 0.2.0" in breaking + result = spec_sync.sync(openapi, downloads, today=NOW.date(), now=NOW, version="0.1.0") + assert result.changed + # A rename keeps the old name, so nothing breaks yet; the new name is a feature. + assert result.commit_type == "feat" + assert _breaking(result.summary).strip() == "None detected." + renamed = _section(result.summary, "Renamed methods") + assert "`billing.retrieve_balance` renamed to `billing.get_balance`" in renamed + assert "until version 0.2.0" in renamed entry = _read(openapi / "deprecated-operations.json")["methods"]["billing.retrieve_balance"] assert entry["reason"] == "renamed" assert entry["replacement"] == "billing.get_balance" @@ -344,7 +348,7 @@ def test_renamed_method_stays_as_a_deprecated_alias(openapi: pathlib.Path) -> No # interim name it shipped under becomes the deprecated alias in turn. spec["paths"]["/v3/billing/balance/"]["get"]["x-didit-sdk"]["method"] = "retrieve_balance" downloads["verification.json"] = json.dumps(spec).encode() - _, summary = spec_sync.sync(openapi, downloads, today=NOW.date(), now=NOW, version="0.1.1") + summary = spec_sync.sync(openapi, downloads, today=NOW.date(), now=NOW, version="0.1.1").summary assert "`billing.retrieve_balance` is provided again" in summary methods = _read(openapi / "deprecated-operations.json")["methods"] assert list(methods) == ["billing.get_balance"] @@ -357,12 +361,15 @@ def _sync(openapi: pathlib.Path, spec: Any) -> str: "verification.json": json.dumps(spec).encode(), "account.json": (openapi / "account.json").read_bytes(), } - _, summary = spec_sync.sync(openapi, downloads, today=NOW.date(), now=NOW, version="0.1.0") - return summary + return spec_sync.sync(openapi, downloads, today=NOW.date(), now=NOW, version="0.1.0").summary + + +def _section(summary: str, title: str) -> str: + return summary.split(f"## {title}", 1)[1].split("\n## ", 1)[0] def _breaking(summary: str) -> str: - return summary.split("## Breaking changes", 1)[1].split("\n## ", 1)[0] + return _section(summary, "Breaking changes") def _name_op(openapi: pathlib.Path, spec: Any, path: str, http_method: str, **naming: Any) -> None: @@ -370,17 +377,17 @@ def _name_op(openapi: pathlib.Path, spec: Any, path: str, http_method: str, **na spec["paths"][path][http_method]["x-didit-sdk"] = {**_naming(openapi, key), **naming} -def test_swapped_names_are_breaking(openapi: pathlib.Path) -> None: +def test_swapped_names_are_called_out_for_review(openapi: pathlib.Path) -> None: spec = _read(openapi / "verification.json") _name_op(openapi, spec, "/v3/billing/balance/", "get", method="top_up") _name_op(openapi, spec, "/v3/billing/top-up/", "post", method="retrieve_balance") - breaking = _breaking(_sync(openapi, spec)) - assert "`billing.top_up` now calls `GET /v3/billing/balance/` instead of" in breaking - assert "`billing.retrieve_balance` now calls `POST /v3/billing/top-up/`" in breaking + moved = _section(_sync(openapi, spec), "Moved or reassigned methods") + assert "`billing.top_up` **(review)** now calls `GET /v3/billing/balance/` instead of" in moved + assert "`billing.retrieve_balance` **(review)** now calls `POST /v3/billing/top-up/`" in moved assert _read(openapi / "deprecated-operations.json")["methods"] == {} -def test_a_name_reused_by_another_operation_is_breaking(openapi: pathlib.Path) -> None: +def test_a_name_reused_by_another_operation_is_called_out(openapi: pathlib.Path) -> None: spec = _read(openapi / "verification.json") _name_op(openapi, spec, "/v3/billing/balance/", "get", method="current_balance") spec["paths"]["/v3/billing/balance-v2/"] = { @@ -389,10 +396,10 @@ def test_a_name_reused_by_another_operation_is_breaking(openapi: pathlib.Path) - "x-didit-sdk": {**_naming(openapi, "GET /v3/billing/balance/")}, } } - breaking = _breaking(_sync(openapi, spec)) + moved = _section(_sync(openapi, spec), "Moved or reassigned methods") assert ( - "`billing.retrieve_balance` now calls `GET /v3/billing/balance-v2/` instead of " - "`GET /v3/billing/balance/`" in breaking + "`billing.retrieve_balance` **(review)** now calls `GET /v3/billing/balance-v2/` " + "instead of `GET /v3/billing/balance/`" in moved ) @@ -409,10 +416,13 @@ def test_a_moved_operation_reports_what_changed(openapi: pathlib.Path) -> None: } spec["paths"]["/v4/billing/balance/"] = item summary = _sync(openapi, spec) - breaking = _breaking(summary) - assert "`billing.retrieve_balance` moved from `GET /v3/billing/balance/`" in breaking - assert "parameter added: currency (query, required)" in breaking - assert "response: type object -> array" in breaking + assert "`billing.retrieve_balance` **(breaking)** moved from `GET /v3/billing/balance/`" in ( + _section(summary, "Moved or reassigned methods") + ) + assert _breaking(summary).strip().splitlines() == [ + "- `GET /v4/billing/balance/` parameter `currency` (query): added (required)", + "- `GET /v4/billing/balance/` response: type changed from object to array", + ] assert _read(openapi / "deprecated-operations.json")["methods"] == {} @@ -422,7 +432,10 @@ def test_an_operation_that_stops_being_a_server_operation_is_removed( spec = _read(openapi / "verification.json") _name_op(openapi, spec, "/v3/billing/balance/", "get", auth="device_token", server=False) breaking = _breaking(_sync(openapi, spec)) - assert "`billing.retrieve_balance` (GET /v3/billing/balance/) is removed" in breaking + assert breaking.strip() == ( + "- `GET /v3/billing/balance/`: operation removed from server SDKs: it now needs " + "device_token, and `billing.retrieve_balance` is removed" + ) # Never kept: server SDKs do not expose device-token operations (DESIGN 4). assert _read(openapi / "deprecated-operations.json")["methods"] == {} files = generate(openapi) @@ -441,10 +454,9 @@ def test_an_operation_that_loses_its_naming_is_called_out(openapi: pathlib.Path) spec = _read(openapi / "verification.json") del spec["paths"]["/v3/billing/balance/"]["get"]["x-didit-sdk"] summary = _sync(openapi, spec) - assert "lost its SDK naming" in summary - assert "`billing.retrieve_balance` (GET /v3/billing/balance/) has no SDK naming" in _breaking( - summary - ) + # A sync failure for a person to fix, not an API change. + assert "`GET /v3/billing/balance/` lost its SDK naming" in _section(summary, "Renamed methods") + assert _breaking(summary).strip() == "None detected." assert _read(openapi / "deprecated-operations.json")["methods"] == {} with pytest.raises(CodegenError, match="no SDK naming"): load(openapi) @@ -464,45 +476,11 @@ def test_nested_type_and_enum_changes_are_breaking(openapi: pathlib.Path) -> Non "verification.json": json.dumps(spec).encode(), "account.json": (openapi / "account.json").read_bytes(), } - _, summary = spec_sync.sync(openapi, downloads, today=NOW.date(), now=NOW, version="0.1.0") - breaking = summary.split("## Breaking changes", 1)[1].split("## ", 1)[0] - assert f".{prop}: type string -> integer (breaking)" in breaking - assert "schema `LegacyUnusedThing` removed" in breaking - - -def test_schema_diff_rules() -> None: - def diff(old: Any, new: Any, *, request: bool) -> list[tuple[str, bool]]: - notes: list[tuple[str, bool]] = [] - spec_sync.diff_schema(old, new, "x", request=request, notes=notes) - return notes - - status = {"type": "string", "enum": ["a", "b"]} - assert diff(status, {**status, "enum": ["a"]}, request=True) == [ - ('x: values removed ["b"]', True) - ] - assert diff(status, {**status, "enum": ["a"]}, request=False) == [ - ('x: values removed ["b"]', False) - ] - obj = {"type": "object", "properties": {"a": {"type": "string"}}, "required": ["a"]} - assert diff(obj, {**obj, "required": []}, request=False) == [ - ("x.a: no longer always present", True) - ] - assert diff({**obj, "required": []}, obj, request=True) == [("x.a: now required", True)] - assert diff({"type": "integer"}, {"type": "number"}, request=True) == [ - ("x: type integer -> number", False) - ] - assert diff({"type": "string"}, {"type": "string", "nullable": True}, request=False) == [ - ("x: can now be null", True) - ] - nested = {"type": "array", "items": obj} - assert diff( - nested, {"type": "array", "items": {"type": "object", "properties": {}}}, request=False - ) == [("x[].a: removed", True)] - assert diff({"oneOf": [obj, status]}, {"oneOf": [obj]}, request=False) == [ - ("x: 1 oneOf variant(s) removed", True) - ] - documented = {**obj, "description": "now documented", "example": {"a": "z"}} - assert diff(obj, documented, request=False) == [] + summary = spec_sync.sync(openapi, downloads, today=NOW.date(), now=NOW, version="0.1.0").summary + breaking = _breaking(summary) + assert f".{prop}`: type changed from string to integer" in breaking + assert "LegacyUnusedThing" not in breaking + assert "verification.json removed: `LegacyUnusedThing`" in _section(summary, "Schemas") @pytest.mark.parametrize( @@ -556,9 +534,10 @@ def test_moved_operation_is_not_deprecated(openapi: pathlib.Path) -> None: "verification.json": json.dumps(spec).encode(), "account.json": (openapi / "account.json").read_bytes(), } - changed, summary = spec_sync.sync(openapi, downloads, today=NOW.date(), now=NOW) - assert changed - assert "moved to `GET /v3/billing/balance-v2/`" in summary + result = spec_sync.sync(openapi, downloads, today=NOW.date(), now=NOW) + assert result.changed + assert "moved to `GET /v3/billing/balance-v2/`" in result.summary + assert _breaking(result.summary).strip() == "None detected." assert _read(openapi / "deprecated-operations.json")["methods"] == {} load(openapi) @@ -583,11 +562,16 @@ def test_sync_reports_changes_and_drops_overlay_entries(openapi: pathlib.Path) - "verification.json": json.dumps(spec).encode(), "account.json": (openapi / "account.json").read_bytes(), } - changed, summary = spec_sync.sync(openapi, downloads, today=NOW.date(), now=NOW) - assert changed + result = spec_sync.sync(openapi, downloads, today=NOW.date(), now=NOW) + summary = result.summary + assert result.changed assert "`GET /v3/new-thing/`: **no SDK naming**" in summary - assert "parameter added: dry_run (query, required)" in summary - assert "**(breaking)**" in summary + assert "`POST /v3/session/` **(breaking)**: parameter `dry_run` (query): added (required)" in ( + summary + ) + assert _breaking(summary).strip() == ( + "- `POST /v3/session/` parameter `dry_run` (query): added (required)" + ) assert "Dropped 1 overlay entries" in summary assert "POST /v3/session/" not in _read(openapi / "overlay.json")["operations"] source = _read(openapi / "SOURCE.json") @@ -599,9 +583,9 @@ def test_sync_reports_changes_and_drops_overlay_entries(openapi: pathlib.Path) - def test_sync_without_changes_touches_nothing(openapi: pathlib.Path) -> None: before = {p.name: p.read_bytes() for p in openapi.iterdir()} downloads = {name: (openapi / name).read_bytes() for name in spec_sync.UPSTREAM} - changed, summary = spec_sync.sync(openapi, downloads, today=NOW.date(), now=NOW) - assert not changed - assert summary == "No upstream change.\n" + result = spec_sync.sync(openapi, downloads, today=NOW.date(), now=NOW) + assert not result.changed + assert result.summary == "No upstream change.\n" assert {p.name: p.read_bytes() for p in openapi.iterdir()} == before diff --git a/codegen/tests/test_spec_sync.py b/codegen/tests/test_spec_sync.py new file mode 100644 index 0000000..96124ef --- /dev/null +++ b/codegen/tests/test_spec_sync.py @@ -0,0 +1,528 @@ +"""The spec sync's commit type, breaking-change detection and release notes +(docs/DESIGN.md sections 3.1 and 13). They never touch the network. + +The fixtures are ``openapi/verification.json`` as this repository committed it at the +commit in each file name: public, pinned copies, so that all seven SDKs test the same +upstream changes and report the same results. +""" + +from __future__ import annotations + +import datetime as dt +import hashlib +import json +import pathlib +import re +import shutil +import sys +from typing import Any + +import pytest + +CODEGEN = pathlib.Path(__file__).resolve().parents[1] +REPO = CODEGEN.parent +FIXTURES = CODEGEN / "tests" / "fixtures" +sys.path.insert(0, str(CODEGEN)) + +import spec_sync # noqa: E402 + +NOW = dt.datetime(2026, 11, 6, 6, 0, tzinfo=dt.timezone.utc) +# Commit -> the start of the SHA-256 of its openapi/verification.json. +PINNED = { + "9fe57fc69299601e59cc7a64c1db370fea5c7461": "12b2fdb05572755d", # v1.0.1 + "e002a815773bbd1599256e2774046ee76520c6c8": "d1783bd862848198", + "fe9f57a1dfb6374bfd391750d2044fc7a206e4ea": "9f8cdf8be8157588", + "ccc799410f82eee1c25c7ce5abc6caa53846be6b": "1b0c7bdd8d7957bd", +} +NULL = "can now be null" +OBJECT = "type changed from string to object" +# The API change of 2026-11-04: the list sdk-python PR #14 printed for the same pair. +BREAKING_2026_11_04 = [ + ("GET /v3/session/{sessionId}/decision/", "registry_checks[].company.registry_data", NULL), + ("GET /v3/session/{sessionId}/decision/", "registry_checks[].ownership_structure", NULL), + ("GET /v3/session/{sessionId}/decision/", "signatures[].confirmation.channel", NULL), + ("POST /v3/email/risk/", "email.email_intelligence.reason_codes[]", OBJECT), + ("POST /v3/email/risk/", "email.enrichment.reason_codes[]", OBJECT), + ("POST /v3/kyb/select/", "kyb_registry.registry_data", NULL), + ("POST /v3/kyb/shareholders/", "kyb_registry.registry_data", NULL), + ("POST /v3/kyb/ubo/", "kyb_registry.registry_data", NULL), + ("POST /v3/phone/risk/", "phone.enrichment.reason_codes[]", OBJECT), + ("POST /v3/phone/risk/", "phone.phone_intelligence.reason_codes[]", OBJECT), + ("POST /v3/session/import-shared/", "registry_checks[].company.registry_data", NULL), + ("POST /v3/session/import-shared/", "registry_checks[].ownership_structure", NULL), + ("POST /v3/session/import-shared/", "signatures[].confirmation.channel", NULL), + ("POST /v3/session/{sessionId}/signatures/{nodeId}/validate/", "signatures[].confirmation.channel", NULL), +] # fmt: skip +API_SECTION = "API changes that can break typed code" + + +@pytest.fixture +def openapi(tmp_path: pathlib.Path) -> pathlib.Path: + target = tmp_path / "openapi" + shutil.copytree(REPO / "openapi", target) + return target + + +def _fixture(commit: str) -> bytes: + return (FIXTURES / f"verification-{commit}.json").read_bytes() + + +def _sync(openapi: pathlib.Path, downloads: dict[str, bytes]) -> spec_sync.SyncResult: + return spec_sync.sync(openapi, downloads, today=NOW.date(), now=NOW, version="1.1.0") + + +def _sync_pair(openapi: pathlib.Path, before: str, after: str) -> spec_sync.SyncResult: + (openapi / "verification.json").write_bytes(_fixture(before)) + return _sync(openapi, {"verification.json": _fixture(after)}) + + +def _section(summary: str, title: str) -> str: + """The text under the heading that starts with ``title`` (some headings add a count).""" + after = f"\n{summary}".split(f"\n## {title}", 1)[1] + return after.split("\n", 1)[1].split("\n## ", 1)[0] + + +def _nested(message: str) -> list[str]: + return re.findall(r"BEGIN_NESTED_COMMIT\n(.*?)\nEND_NESTED_COMMIT", message, re.S) + + +# The pinned upstream changes ------------------------------------------------------------ + + +def test_fixtures_are_the_pinned_upstream_documents() -> None: + for commit, digest in PINNED.items(): + assert hashlib.sha256(_fixture(commit)).hexdigest().startswith(digest), commit + + +def test_api_change_of_2026_11_04_is_a_minor_release_listing_every_breaking_change( + openapi: pathlib.Path, +) -> None: + result = _sync_pair( + openapi, "9fe57fc69299601e59cc7a64c1db370fea5c7461", "e002a815773bbd1599256e2774046ee76520c6c8" + ) # fmt: skip + assert result.commit_type == "feat" + assert [(b.operation, b.path, b.kind) for b in result.breaking] == BREAKING_2026_11_04 + assert {b.where for b in result.breaking} == {"response"} + + # The pull request body opens with them, one line each. + lines = result.summary.splitlines() + assert lines[0] == "## Breaking changes" + assert lines[1 : 1 + len(BREAKING_2026_11_04)] == [f"- {b.describe()}" for b in result.breaking] + assert lines[1] == ( + "- `GET /v3/session/{sessionId}/decision/` response " + "`registry_checks[].company.registry_data`: can now be null" + ) + assert "a minor version, never a major one" in _section(result.summary, "Release") + + # The release notes repeat them; never "!" or a BREAKING CHANGE footer (a major). + message = result.commit_message() + assert message.splitlines()[0] == "feat(spec): sync API spec 2026-11-06" + assert "BREAKING" not in message + assert _nested(message) == [f"api(spec): {b.describe()}" for b in result.breaking] + assert not any(c in line for line in _nested(message) for c in "<>") + + +def test_documentation_only_change_of_2026_11_06_releases_nothing(openapi: pathlib.Path) -> None: + result = _sync_pair( + openapi, "fe9f57a1dfb6374bfd391750d2044fc7a206e4ea", "ccc799410f82eee1c25c7ce5abc6caa53846be6b" + ) # fmt: skip + assert result.changed + assert result.commit_type == "chore" + assert result.breaking == [] + assert _section(result.summary, "Breaking changes").strip() == "None detected." + assert "releases nothing on its own" in _section(result.summary, "Release") + assert "`POST /v3/poa/`" in _section(result.summary, "Changed operations") + assert result.commit_message() == "chore(spec): sync API spec 2026-11-06\n" + + +# Release notes ---------------------------------------------------------------------------- + + +def _release(message: str) -> tuple[str | None, dict[str, list[str]]]: + """What release-please makes of a squash-merged sync whose BEGIN_COMMIT_OVERRIDE block + holds ``message``, read with release-please-config.json: the version bump and the + release notes by section. The first line and each nested block are one commit; only + types with a visible section reach the notes, and without one nothing is released.""" + config = json.loads((REPO / "release-please-config.json").read_text("utf-8")) + sections = {s["type"]: s for s in config["packages"]["."]["changelog-sections"]} + commits = [message.splitlines()[0], *_nested(message)] + notes: dict[str, list[str]] = {} + bump = "patch" + for commit in commits: + match = re.fullmatch(r"(\w+)(?:\((\w+)\))?(!?): (.+)", commit) + assert match, commit + kind, _, bang, text = match.groups() + if bang or "BREAKING CHANGE" in message: + bump = "major" + elif kind == "feat" and bump != "major": + bump = "minor" + section = sections.get(kind) + if section and not section.get("hidden"): + notes.setdefault(section["section"], []).append(text) + return (bump if notes else None), notes + + +def test_release_notes_have_a_section_for_api_changes_that_can_break_typed_code( + openapi: pathlib.Path, +) -> None: + result = _sync_pair( + openapi, "9fe57fc69299601e59cc7a64c1db370fea5c7461", "e002a815773bbd1599256e2774046ee76520c6c8" + ) # fmt: skip + bump, notes = _release(result.commit_message()) + assert bump == "minor" + assert notes == { + "Features": ["sync API spec 2026-11-06"], + API_SECTION: [b.describe() for b in result.breaking], + } + # A documentation-only sync (chore, a hidden section) proposes no release on its own. + assert _release("chore(spec): sync API spec 2026-11-06\n") == (None, {}) + + +def test_release_please_config_keeps_chores_ci_and_tests_hidden() -> None: + config = json.loads((REPO / "release-please-config.json").read_text("utf-8")) + sections = {s["type"]: s for s in config["packages"]["."]["changelog-sections"]} + assert sections["api"] == {"type": "api", "section": API_SECTION} + assert not sections["feat"].get("hidden") + for kind in ("chore", "ci", "test", "build", "refactor", "style"): + assert sections[kind]["hidden"] is True, kind + + +def test_release_notes_are_capped(openapi: pathlib.Path) -> None: + many = [ + spec_sync.BreakingChange(f"GET /v3/thing-{i}/", "response", "a", "removed") + for i in range(spec_sync.MAX_RELEASE_NOTES + 3) + ] + message = spec_sync.SyncResult(True, "", "2026-11-06", "feat", many).commit_message() + nested = _nested(message) + assert len(nested) == spec_sync.MAX_RELEASE_NOTES + 1 + assert nested[-1] == "api(spec): 3 more, listed in the spec sync pull request" + + +# Detection rules (DESIGN 3.1) ------------------------------------------------------------- + +OBJ: dict[str, Any] = {"type": "object", "properties": {"a": {"type": "string"}}, "required": ["a"]} +STATUS: dict[str, Any] = {"type": "string", "enum": ["a", "b"]} + + +def _diff(old: Any, new: Any, *, request: bool = False) -> list[tuple[str, str, bool]]: + out: list[spec_sync.Change] = [] + spec_sync.diff_schema(old, new, "body", request=request, out=out) + return [(c.path, c.kind, c.breaking) for c in out] + + +@pytest.mark.parametrize("request_side", [True, False], ids=["request", "response"]) +def test_counted_in_requests_and_responses(request_side: bool) -> None: + def diff(old: Any, new: Any) -> list[tuple[str, str, bool]]: + return _diff(old, new, request=request_side) + + # A type change, in either direction. + assert diff({"type": "integer"}, {"type": "number"}) == [ + ("", "type changed from integer to number", True) + ] + assert diff({"type": "number"}, {"type": "integer"}) == [ + ("", "type changed from number to integer", True) + ] + assert diff({"type": "string"}, {"type": "object", "properties": {}}) == [("", OBJECT, True)] + # A value that can now be null, however the document says it. + for nullable in ( + {"type": "string", "nullable": True}, + {"type": ["string", "null"]}, + {"anyOf": [{"type": "string"}, {"type": "null"}]}, + {"oneOf": [{"type": "string"}, {"enum": [None]}]}, + {"allOf": [{"type": "string"}], "nullable": True}, + ): + assert diff({"type": "string"}, nullable) == [("", NULL, True)], nullable + assert diff(STATUS, {**STATUS, "enum": ["a", "b", None]}) == [("", NULL, True)] + # A removed field or enum value, nested in objects and array items. + assert diff(OBJ, {"type": "object", "properties": {}}) == [("a", "removed", True)] + assert diff(STATUS, {**STATUS, "enum": ["a"]}) == [("", 'enum value "b" removed', True)] + nested = {"type": "array", "items": {"type": "object", "properties": {"s": STATUS}}} + narrowed = { + "type": "array", + "items": {"type": "object", "properties": {"s": {**STATUS, "enum": ["b"]}}}, + } + assert diff(nested, narrowed) == [("[].s", 'enum value "a" removed', True)] # fmt: skip + # A union that loses a variant. + two = {"oneOf": [OBJ, {**OBJ, "properties": {"b": {"type": "string"}}}]} + assert diff(two, {"oneOf": [OBJ]}) == [("", "type changed: 1 oneOf variant(s) removed", True)] + assert diff(two, OBJ) == [("", "type changed: 1 oneOf variant(s) removed", True)] + assert diff({"oneOf": [{"type": "string"}, {"type": "integer"}]}, {"type": "string"}) == [ + ("", "type changed from integer or string to string", True) + ] + + +def test_newly_required_request_fields_count_and_response_requiredness_does_not() -> None: + optional = {**OBJ, "required": []} + assert _diff(optional, OBJ, request=True) == [("a", "now required", True)] + added = {"type": "object", "properties": {**OBJ["properties"], "b": {"type": "string"}}} + assert _diff(optional, {**added, "required": ["b"]}, request=True) == [ + ("b", "added (required)", True) + ] + assert _diff(optional, added, request=True) == [("b", "added", False)] + assert _diff(optional, OBJ) == [("a", "now always present", False)] + assert _diff(OBJ, optional) == [("a", "no longer always present", False)] + assert _diff(OBJ, optional, request=True) == [("a", "no longer required", False)] + + +def test_listed_but_not_counted() -> None: + assert _diff(STATUS, {**STATUS, "enum": ["a", "b", "c"]}) == [ + ("", 'enum values added ["c"]', False) + ] + assert _diff({"type": "string"}, STATUS) == [("", 'now limited to ["a", "b"]', False)] + assert _diff(STATUS, {"type": "string"}) == [("", "no longer limited to fixed values", False)] + assert _diff({"type": "string", "nullable": True}, {"type": "string"}, request=True) == [ + ("", "no longer accepts null", False) + ] + assert _diff({"type": "string"}, {"type": "string", "format": "date-time"}) == [ + ("", "other constraints changed (format, pattern, limits or default)", False) + ] + assert _diff({"oneOf": [OBJ]}, {"oneOf": [OBJ, {**OBJ, "title": "B", "properties": {}}]}) == [ + ("", "1 oneOf variant(s) added", False) + ] + + +def test_an_optional_value_reads_as_nullable_not_as_its_fields_removed() -> None: + assert _diff(OBJ, {"anyOf": [OBJ, {"type": "null"}]}) == [("", NULL, True)] + assert _diff(OBJ, {"oneOf": [OBJ, {"enum": [None]}], "description": "or null"}) == [ + ("", NULL, True) + ] + assert _diff({"anyOf": [OBJ, {"type": "null"}]}, OBJ) == [("", "can no longer be null", False)] + + +def test_an_enum_behind_all_of_is_compared() -> None: + assert _diff({"allOf": [STATUS]}, {"allOf": [{**STATUS, "enum": ["a"]}]}) == [ + ("", 'enum value "b" removed', True) + ] + + +def test_documentation_never_counts_but_a_field_named_like_it_does() -> None: + documented = { + **OBJ, + "description": "now documented", + "title": "Thing", + "example": {"a": "z"}, + "x-codeSamples": [], + } + assert _diff(OBJ, documented) == [] + fields = {"type": "object", "properties": {"description": {"type": "string"}, "tags": {}}} + assert _diff(fields, {"type": "object", "properties": {"tags": {}}}) == [ + ("description", "removed", True) + ] + assert _diff( + {"type": "object", "properties": {"title": {"type": "string"}}}, + {"type": "object", "properties": {"title": {"type": "integer"}}}, + ) == [("title", "type changed from string to integer", True)] + + +def _op(*parameters: Any, responses: Any = None, body: Any = None) -> dict[str, Any]: + op: dict[str, Any] = {"parameters": list(parameters), "responses": responses or {}} + if body is not None: + op["requestBody"] = {"content": {"application/json": {"schema": body}}} + return op + + +def _json(schema: Any) -> dict[str, Any]: + return {"description": "ok", "content": {"application/json": {"schema": schema}}} + + +def test_parameters() -> None: + limit = {"name": "limit", "in": "query", "schema": {"type": "integer"}} + where = "parameter `limit` (query)" + + def changes(old: Any, new: Any) -> list[tuple[str, str, bool]]: + found = spec_sync.describe_change({}, old, {}, new) + return [(c.where, c.kind, c.breaking) for c in found] + + required_null = {**limit, "required": True, "schema": {"type": "integer", "nullable": True}} + assert changes(_op(limit), _op(required_null)) == [ + (where, "now required", True), + (where, NULL, True), + ] + assert changes(_op(limit), _op({**limit, "schema": {"type": "string"}})) == [ + (where, "type changed from integer to string", True) + ] + assert changes(_op(limit), _op({**limit, "name": "page"})) == [ + (where, "removed", False), + ("parameter `page` (query)", "added", False), + ] + assert changes(_op(), _op({**limit, "required": True})) == [(where, "added (required)", True)] + + +def test_every_success_response_is_compared_and_reported_once() -> None: + gone = {"type": "object", "properties": {}} + old = _op(responses={"200": _json(OBJ), "202": _json(OBJ), "400": _json(OBJ)}) + both = _op(responses={"200": _json(gone), "202": _json(gone), "400": _json(gone)}) + found = spec_sync.describe_change({}, old, {}, both) + assert [(c.where, c.path, c.kind) for c in found] == [("response", "a", "removed")] + only_202 = _op(responses={"200": _json(OBJ), "202": _json(gone)}) + found = spec_sync.describe_change({}, old, {}, only_202) + assert [(c.path, c.kind) for c in found] == [("a", "removed")] + + +def test_references_are_followed() -> None: + doc = {"components": {"schemas": {"Obj": OBJ}}} + new_doc = {"components": {"schemas": {"Obj": {**OBJ, "properties": {}}}}} + ref = {"$ref": "#/components/schemas/Obj"} + op = _op(responses={"200": _json({"type": "array", "items": ref})}, body=ref) + found = spec_sync.describe_change(doc, op, new_doc, op) + assert [(c.where, c.path, c.kind, c.breaking) for c in found] == [ + ("request body", "a", "removed", True), + ("response", "[].a", "removed", True), + ] + + +# Commit type ------------------------------------------------------------------------------ + +OP = "/programmatic/resend-otp/" + + +def _account_sync(openapi: pathlib.Path, change: Any) -> spec_sync.SyncResult: + spec = json.loads((openapi / "account.json").read_text("utf-8")) + change(spec) + return _sync(openapi, {"account.json": json.dumps(spec).encode()}) + + +def _response(spec: Any) -> Any: + return spec["paths"][OP]["post"]["responses"]["200"]["content"]["application/json"]["schema"] + + +@pytest.mark.parametrize( + ("change", "commit_type"), + [ + (lambda s: s["paths"][OP]["post"].update(description="Reworded."), "chore"), + (lambda s: s["paths"][OP]["post"].update({"x-codeSamples": []}), "chore"), + (lambda s: s["info"].update(version="9.9.9"), "chore"), + (lambda s: s["tags"].append({"name": "New tag"}), "chore"), + (lambda s: _response(s).update(description="Reworded."), "chore"), + (lambda s: _response(s)["properties"].update(description={"type": "string"}), "feat"), + (lambda s: _response(s)["properties"].update(extra={"type": "string"}), "feat"), + (lambda s: s["paths"][OP]["post"].update(deprecated=True), "feat"), + (lambda s: s["paths"][OP]["post"]["x-didit-sdk"].update(method="resend"), "feat"), + ], + ids=[ + "description", + "code samples", + "info", + "tags", + "schema description", + "field named description", + "new field", + "deprecated", + "rename", + ], +) +def test_feat_when_a_caller_can_observe_it_and_chore_for_documentation( + openapi: pathlib.Path, change: Any, commit_type: str +) -> None: + result = _account_sync(openapi, change) + assert result.changed + assert result.commit_type == commit_type + assert result.breaking == [] + assert result.commit_message() == f"{commit_type}(spec): sync API spec 2026-11-06\n" + + +def test_a_new_webhook_event_is_feat(openapi: pathlib.Path) -> None: + spec = json.loads((openapi / "verification.json").read_text("utf-8")) + spec["x-webhooks"]["synthetic.event"] = spec["x-webhooks"]["status.updated"] + result = _sync(openapi, {"verification.json": json.dumps(spec).encode()}) + assert result.commit_type == "feat" + + +def test_upstream_adopting_an_overlay_entry_unchanged_is_chore(openapi: pathlib.Path) -> None: + key = f"account POST {OP}" + spec = json.loads((openapi / "account.json").read_text("utf-8")) + naming = spec["paths"][OP]["post"].pop("x-didit-sdk") + (openapi / "account.json").write_text(json.dumps(spec), "utf-8") + overlay = json.loads((openapi / "overlay.json").read_text("utf-8")) + overlay["operations"][key] = naming + (openapi / "overlay.json").write_text(json.dumps(overlay, indent=1), "utf-8") + spec["paths"][OP]["post"]["x-didit-sdk"] = naming + result = _sync(openapi, {"account.json": json.dumps(spec).encode()}) + assert result.commit_type == "chore" + assert key not in json.loads((openapi / "overlay.json").read_text("utf-8"))["operations"] + + +# The Authentication document --------------------------------------------------------------- + + +def _authentication(openapi: pathlib.Path) -> Any: + return json.loads((openapi / "authentication.json").read_text("utf-8")) + + +def test_a_removed_authentication_operation_is_breaking_and_never_deprecated( + openapi: pathlib.Path, +) -> None: + before = (openapi / "deprecated-operations.json").read_bytes() + spec = _authentication(openapi) + del spec["paths"]["/session/"]["delete"] + result = _sync(openapi, {"authentication.json": json.dumps(spec).encode()}) + assert result.commit_type == "feat" + assert [b.describe() for b in result.breaking] == [ + "`authentication DELETE /session/`: operation removed (`authentication.revoke_session`)" + ] + assert "- `authentication.revoke_session` was removed: remove it" in _section( + result.summary, "Authentication module" + ) + # The module is hand-written: nothing is generated or kept as deprecated for it. + assert (openapi / "deprecated-operations.json").read_bytes() == before + + +def test_a_renamed_authentication_method_names_the_module_change(openapi: pathlib.Path) -> None: + spec = _authentication(openapi) + naming = spec["paths"]["/token/"]["post"]["x-didit-sdk"] + naming["variants"][1]["method"] = "refresh_tokens" + result = _sync(openapi, {"authentication.json": json.dumps(spec).encode()}) + assert result.commit_type == "feat" + assert result.breaking == [] + module = _section(result.summary, "Authentication module") + assert "- `authentication.refresh_tokens` is new: add it" in module + assert "- `authentication.refresh` was removed: remove it" in module + assert "## Renamed methods (0)" in result.summary + + +def test_authentication_documentation_change_is_chore(openapi: pathlib.Path) -> None: + spec = _authentication(openapi) + spec["paths"]["/session/"]["get"]["description"] = "Reworded." + result = _sync(openapi, {"authentication.json": json.dumps(spec).encode()}) + assert result.commit_type == "chore" + assert "## Authentication module" not in result.summary + + +def test_the_sync_downloads_the_authentication_document() -> None: + assert spec_sync.UPSTREAM["authentication.json"] == ( + "https://docs.didit.me/openapi-authentication.json" + ) + source = json.loads((REPO / "openapi" / "SOURCE.json").read_text("utf-8")) + data = (REPO / "openapi" / "authentication.json").read_bytes() + assert source["authentication.json"]["url"] == spec_sync.UPSTREAM["authentication.json"] + assert source["authentication.json"]["sha256"] == hashlib.sha256(data).hexdigest() + + +# Command line ---------------------------------------------------------------------------------- + + +def test_cli_writes_the_type_and_the_commit_message( + monkeypatch: pytest.MonkeyPatch, openapi: pathlib.Path, tmp_path: pathlib.Path +) -> None: + upstream = tmp_path / "upstream" + shutil.copytree(openapi, upstream) + spec = json.loads((upstream / "verification.json").read_text("utf-8")) + del spec["paths"]["/v3/billing/balance/"] + (upstream / "verification.json").write_text(json.dumps(spec), "utf-8") + output, message = tmp_path / "github_output", tmp_path / "message.txt" + version = openapi.parent / "src" / "didit" / "_version.py" + version.parent.mkdir(parents=True) + version.write_text('__version__ = "1.1.0"\n', "utf-8") + monkeypatch.setattr(spec_sync, "REPO", openapi.parent) + monkeypatch.setenv("GITHUB_OUTPUT", str(output)) + args = ["--from-dir", str(upstream), "--commit-message", str(message)] + assert spec_sync.main(args) == 0 + outputs = dict(line.split("=", 1) for line in output.read_text("utf-8").splitlines()) + assert outputs["changed"] == "true" + assert outputs["type"] == "feat" + assert message.read_text("utf-8") == ( + f"feat(spec): sync API spec {outputs['date']}\n\nBEGIN_NESTED_COMMIT\n" + "api(spec): `GET /v3/billing/balance/`: operation removed; `billing.retrieve_balance` " + "stays as a deprecated method until version 1.2.0\n" + "END_NESTED_COMMIT\n" + ) diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 32996e0..22e9dd7 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -44,6 +44,7 @@ Every SDK is generated from Didit's published OpenAPI documents and checked agai |---|---|---| | `verification.json` | `https://docs.didit.me/openapi-25.json` (mintlify-docs `openapi-25.json`, branch `main`) | `https://verification.didit.me` | | `account.json` | `https://docs.didit.me/openapi-auth.json` (mintlify-docs `openapi-auth.json`) | `https://apx.didit.me/auth/v2` | +| `authentication.json` | `https://docs.didit.me/openapi-authentication.json` (mintlify-docs `openapi-authentication.json`) | the application's issuer (section 10) | | `overlay.json` | this repository | - | | `webhook-vectors.json` | generated from `service-didit-verification/src/sessions/utils/webhooks.py` | - | | `SOURCE.json` | URL, SHA-256 and fetch time of each upstream file | - | @@ -51,13 +52,18 @@ Every SDK is generated from Didit's published OpenAPI documents and checked agai The upstream files are copied byte for byte and never edited by hand. `overlay.json` holds the SDK naming (section 4) for operations whose upstream definition does not carry it yet. When an operation carries `x-didit-sdk` upstream, the upstream value wins and the overlay entry is deleted. -The Authentication runtime (section 10) has no OpenAPI document yet; mintlify-docs will publish `openapi-authentication.json`, and until then that module is hand-written from section 10. +`authentication.json` describes the Authentication runtime (section 10). The `authentication` module stays hand-written, because token verification needs code a generator cannot produce; a contract test (section 11) keeps it aligned with the document. ### 3.1 Spec sync Every repository has `.github/workflows/spec-sync.yml`, run daily and on `workflow_dispatch`. -It downloads the upstream files, and when any of them changed it regenerates the code, runs the full test suite and opens a pull request titled `chore(spec): sync API spec YYYY-MM-DD`. -The pull request body lists added, removed and changed operations and schemas, and calls out anything breaking. +It downloads the upstream files, and when any of them changed it regenerates the code, runs the full test suite and opens a pull request titled `(spec): sync API spec YYYY-MM-DD`. +The type follows section 13 and is the same in every repository: `feat(spec)` when the sync adds or changes anything a caller can observe (an operation, parameter, field, enum value or response shape), a minor release; `chore(spec)` when only descriptions, examples or other documentation changed, which releases nothing on its own. +The commit never carries `!` or a `BREAKING CHANGE:` footer: a change that mirrors the API is never a major version (section 13). +The pull request body opens with a "Breaking changes" section, then lists added, removed and changed operations and schemas. +"Breaking changes" lists every change that can break typed code, or says "None detected": a field or parameter whose type changed, a field that can now be null, a removed operation, field or enum value, and a newly required parameter or request field. +Every repository detects exactly that list, so the seven pull requests for one upstream change report the same breaking changes. +The release notes repeat that list under "API changes that can break typed code". A new server operation without SDK naming fails the generator with a message that names the operation; the fix is an entry in `overlay.json` (or upstream). The sync never deletes a public SDK method silently: a removed operation is marked deprecated for one minor version first, and the pull request says so. @@ -87,7 +93,7 @@ Convert names to the language's idiom and nothing else: | Ruby | `didit.sessions.imports.create(...)` | | C# | `await client.Sessions.Imports.CreateAsync(...)` | -The full resource tree (version 1, 248 server operations in 47 resources) is in `overlay.json`. +The full resource tree is generated from `openapi/*.json`: the upstream `x-didit-sdk` naming plus the `overlay.json` entries upstream does not carry yet. Highlights: `sessions`, `sessions.imports`, `sessions.reviews`, the standalone APIs (`id_verification.verify`, `face_match.compare`, `face_search.search`, `passive_liveness.check`, `age_estimation.estimate`, `proof_of_address.verify`, `document_ai.analyze`, `aml.screen`, `database_validation.validate`, `phone.send_code`, `email.check_code`, `ip.assess_risk`, `kyb.search`), `id_wallets`, `fraud_checks`, `users`, `businesses`, `transactions`, `transactions.rules`, `transactions.travel_rule`, `wallet_screening`, `travel_rule`, `travel_rule.wallet_addresses`, `workflows`, `questionnaires`, `webhooks.destinations`, `billing`, `biometric_templates`, `customization`, `cases` (with `notes`, `links`, `checklist`, `requests`, `teams`, `routing_rules`, `blueprints`, `regulatory_reports`), `regulatory_reports`, `report_templates`, `networks`, and `account` (`account.organizations`, `account.applications`). ## 5. Architecture @@ -224,7 +230,7 @@ Tests use a locally generated ES256 key pair and a stubbed issuer; never call a - Unit tests for the core: configuration, headers, errors per status, retry policy (including the no-retry cases for `POST`), `Retry-After`, timeouts, pagination, multipart, binary responses, origin check, redaction. - Generated tests: one test per server operation from the spec's examples, with a mocked HTTP layer, asserting method, path (ids filled, application scope expanded), query, body encoding and response parsing. -- Contract test: every `server: true` operation in `openapi/` has exactly one SDK method, and nothing else exists. +- Contract test: every `server: true` operation in `openapi/` has exactly one SDK method, and nothing else exists. For `authentication.json` the methods are the hand-written ones of section 10, so a spec sync that adds, removes or renames an Authentication operation fails until the module follows. - Webhook vectors (9.3) and Authentication token tests (10). - Optional live smoke tests against a sandbox application, run only when `DIDIT_SANDBOX_API_KEY` is set; never in default CI; they create nothing that costs money beyond sandbox sessions. - Strict static checks: Python `mypy --strict` and `pyright`, ruff; TypeScript `strict` with no `any` in public types, eslint; Go `go vet`, `staticcheck`, `golangci-lint`; Java `-Werror` with errorprone and nullness annotations; PHP PHPStan level max and PHP-CS-Fixer; Ruby RBS signatures checked with Steep, RuboCop; C# nullable enabled with warnings as errors. @@ -244,7 +250,9 @@ Ruby: `net/http` from the standard library, `jwt` only for Authentication. ## 13. Versioning and release -- Semantic versioning; a breaking change to the public SDK surface is a major version. +- Semantic versioning with one rule for API changes (Alex, 2026-11-06): a change that mirrors the Didit API is a minor version, including one that breaks typed code (a type change, a field that can now be null, a removed field, operation or enum value). The API already behaves that way for every caller, so an older SDK version is not safer, and a major would keep users on it. The release notes list such changes under "API changes that can break typed code". +- A major version is only for a change to the SDK's own design: renaming or removing an SDK method or resource the API still has, changing client configuration, errors, retries, pagination or webhook verification behaviour, or dropping a supported runtime version. An API change never needs a new Go `/vN` module path. +- Each language keeps its own version number: Node, PHP and Ruby moved to 2.x on 2026-11-05 under the earlier rule and continue from there. - Releases are cut by release-please from conventional commits; `CHANGELOG.md` is generated and never edited by hand. - Release workflows publish with trusted publishing where the registry supports it (PyPI, npm with provenance, RubyGems and NuGet), Maven Central through the Central Portal with signed artifacts (token and signing key in the `maven-central` environment), Packagist through its GitHub hook, and Go module tags announced to proxy.golang.org. - 1.0.0 shipped on 2026-10-29 with Alex's approval, and the repositories are public. diff --git a/openapi/SOURCE.json b/openapi/SOURCE.json index ec749db..9b7cb2f 100644 --- a/openapi/SOURCE.json +++ b/openapi/SOURCE.json @@ -12,5 +12,10 @@ "webhook-vectors.json": { "generator": "product-automations scripts/server-sdks/make_vectors.py (seeded 2026-10-17)", "source": "service-didit-verification@9773c66e0a src/sessions/utils/webhooks.py" + }, + "authentication.json": { + "url": "https://docs.didit.me/openapi-authentication.json", + "sha256": "3987984647bfcaa4b5462c8d60cd3c53894bf7585a38109e0cc813292b2416fe", + "fetched_at": "2026-11-06T12:17:36Z" } } \ No newline at end of file diff --git a/openapi/authentication.json b/openapi/authentication.json new file mode 100644 index 0000000..9e16db8 --- /dev/null +++ b/openapi/authentication.json @@ -0,0 +1,2634 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Didit Authentication API", + "version": "1.0.0", + "description": "The server-facing endpoints of Didit Authentication, the sign-in product for your application's end users. Authentication is **new** and reaches production in the 2026-11-01 release.\n\n## Base URL\n\nEvery application is its own OpenID Connect issuer, and every endpoint below is served at the root of that issuer:\n\n- `https://.auth.didit.me` for a live application, or `https://.sandbox.auth.didit.me` for a sandbox application.\n- Your own custom domain, once it is active. It then replaces the default host as the issuer: new tokens name it in `iss`, discovery points at it and the publishable key changes. Access tokens issued under the default host fail `/session/`, `/step-up/` and `/monitoring-token/` until they are refreshed.\n\nLive and sandbox are separate applications, so they are separate issuers with separate keys. The publishable key is `pk_live_` (live) or `pk_test_` (sandbox) followed by the unpadded base64url encoding of the issuer, without a trailing slash: decode it to find the issuer. It is a public identifier, not a credential.\n\nThe same JSON endpoints are also served under `https://apx.didit.me/auth/v2/authentication/{application_id}/`. Tokens always name the issuer above, whichever address issued them.\n\n## Credentials\n\nThese endpoints never take your Didit API key or a console token.\n\n- Discovery, the JWKS and `POST /token/` take no credential. Clients are public: there is no client secret, and an authorization code is bound to its PKCE verifier (`S256`). `client_id` is your application ID.\n- `/session/`, `/step-up/` and `/monitoring-token/` take the person's access token: `Authorization: Bearer `. Each successful call counts as activity on the session, so it restarts the session's idle timeout.\n\n## Request bodies\n\nEvery endpoint that takes a body accepts JSON, form-encoded and multipart bodies. This document lists JSON, plus form-encoded for `POST /token/` as OAuth clients send it. A field the endpoint does not accept gets `400 {\"\": \"unknown_field\"}`.\n\n## Tokens\n\n- **Access token**: an ES256 JWT with header `typ: at+jwt`, valid for at most 5 minutes and never longer than its session. Claims: `AccessTokenClaims`.\n- **ID token**: an ES256 JWT with header `typ: JWT`. Claims: `IDTokenClaims`. It is never accepted as an access token.\n- **Refresh token**: a random string that rotates on every use. Sending a used refresh token again revokes the session.\n\nVerify a token with the keys from `GET /.well-known/jwks.json`: the algorithm must be `ES256`, `typ` must match the token type, `iss` must equal the issuer you configured and `aud` your application ID. Offline verification sees a revoked session only once its access token expires; `GET /session/` is the instant check.\n\n## Errors\n\n| Body | When |\n| --- | --- |\n| `{\"error\": \"\"}` | Errors from `POST /token/`, all with status `400`, in the body shape of [RFC 6749 section 5.2](https://www.rfc-editor.org/rfc/rfc6749#section-5.2): `invalid_grant`, `invalid_client` and Didit's `mfa_required`. Service errors: `temporarily_unavailable`, `monitoring_unavailable`, `monitoring_disabled`. |\n| `{\"\": [\"\"]}` or `{\"\": \"\"}` | A request field failed validation. `non_field_errors` names a rule across fields, and `unknown_field` a field the endpoint does not accept. |\n| `{\"detail\": \"\"}` | `401` (`invalid_token`), `403` (`origin_not_allowed`), `404` (unknown application or step-up) and `429` (rate limit). Also `400` for a body that cannot be parsed (`JSON parse error - ...`), `405` for a method the endpoint does not serve and `415` for an unsupported `Content-Type`. |\n\nA `401` also carries `WWW-Authenticate: Bearer`, and a `429` carries `Retry-After` in seconds.\n\nTwo refusals are not JSON. An address that is not an issuer is refused before it reaches these endpoints, for example with an empty `404` for an unused `*.auth.didit.me` name. A `step_up_id` that is not a UUID gets a `404` page.\n\n## Rate limits\n\nA request has to pass two layers.\n\n**Endpoint limits**, counted per client IP address, per application and per endpoint:\n\n- Discovery and the JWKS: 1,200 requests a minute and 12,000 an hour.\n- Every other endpoint: 30 requests a minute and 300 an hour. `GET` and `DELETE /session/` share one count.\n\nOver a limit: `429 {\"detail\": \"Too many requests from this IP address. Please wait before trying again. Expected available in 60 seconds.\"}`, or `3600 seconds` for the hourly limit.\n\n**Bot protection**, on every `POST` and `DELETE`. Its buckets are shared by all of the application's `POST` and `DELETE` requests, the hosted sign-in steps included, and refill continuously:\n\n- 60 requests a minute per client IP address.\n- 30 requests a minute per device, for requests that identify their device, as the browser and native SDKs do.\n- 3,000 requests a minute for the whole application, across every IP address. Token refreshes (`POST /token/` with `grant_type=refresh_token`) draw on a bucket of their own instead: 6,000 a minute for the whole application.\n\nOver a bucket: `429 {\"detail\": \"rate_limited Expected available in N seconds.\"}` (`1 second.` when N is 1). The per-IP bucket spans endpoints, so one address that mixes token, step-up and sign-out calls can empty it before it reaches any endpoint limit. The application bucket counts every client of the application, browsers included.\n\nBoth layers answer before the endpoint runs, so a request that got `429` did not run: retry it after `Retry-After`, a refresh included.\n\n## Browser requests\n\nA request that carries an `Origin` header must come from one of the application's own origins or its allowed origins, or it gets `403 origin_not_allowed`. Server-to-server requests send no `Origin`.\n\n## Not covered here\n\nThese routes serve the hosted pages, the browser and native SDKs, and AI agents. They are not part of the server contract, and server SDKs do not call them:\n\n- Sign-in steps: `POST /authorize/`, `POST /password/`, `/email/*`, `/phone/*`, `/password-reset/*`, `/passkeys/*`, `/mfa/*`, `/social/*` and `/protection/complete/`.\n- Hosted pages and callbacks: `/sign-in`, `/magic`, `/invite`, `/reset`, `/verify/`, `/sso/*`, `/wallet/callback/`, `/logo/` and the native app association files under `/.well-known/`.\n- Agent access: `/agents/*` and `/grants/`, including `GET /agents/session/`, the live check for an AI agent's access token. Agent access is not part of this version of the contract.\n- The console management API for Authentication, which takes console sessions.\n\nAuthentication events are not delivered as webhooks yet, so this document defines no webhook." + }, + "servers": [ + { + "url": "https://{issuer}", + "description": "Your application's issuer: its sign-in origin.", + "variables": { + "issuer": { + "default": "your-app.auth.didit.me", + "description": "The issuer's host: `.auth.didit.me` (live), `.sandbox.auth.didit.me` (sandbox) or your active custom domain." + } + } + } + ], + "tags": [ + { + "name": "Issuer", + "description": "Discovery and signing keys, to verify tokens." + }, + { + "name": "Tokens", + "description": "The authorization code and refresh grants." + }, + { + "name": "Sessions", + "description": "Check and end the session behind an access token." + }, + { + "name": "Step-ups", + "description": "Verification before a sensitive action." + }, + { + "name": "Monitoring", + "description": "Device credentials for transaction monitoring events." + } + ], + "paths": { + "/.well-known/openid-configuration": { + "get": { + "tags": [ + "Issuer" + ], + "summary": "Get the OpenID configuration", + "operationId": "authentication_get_openid_configuration", + "description": "The issuer's discovery document: its endpoints, grants, signing algorithm and claims. Fetch it from the issuer you configured, never from a URL found in a token. Cache it for up to 5 minutes, as `Cache-Control` says.", + "security": [ + {} + ], + "x-mint": { + "metadata": { + "authMethod": "none" + } + }, + "x-didit-sdk": { + "resource": "authentication", + "method": "get_openid_configuration", + "auth": "none", + "server": true + }, + "responses": { + "200": { + "description": "The discovery document.", + "headers": { + "Cache-Control": { + "description": "`public, max-age=300`: you and shared caches may keep a successful response for 5 minutes, one access token lifetime.", + "schema": { + "type": "string", + "example": "public, max-age=300" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OpenIDConfiguration" + }, + "examples": { + "Discovery": { + "summary": "A live application", + "value": { + "issuer": "https://your-app.auth.didit.me", + "authorization_endpoint": "https://your-app.auth.didit.me/sign-in", + "token_endpoint": "https://your-app.auth.didit.me/token/", + "jwks_uri": "https://your-app.auth.didit.me/.well-known/jwks.json", + "response_types_supported": [ + "code" + ], + "response_modes_supported": [ + "query" + ], + "grant_types_supported": [ + "authorization_code", + "refresh_token" + ], + "subject_types_supported": [ + "public" + ], + "id_token_signing_alg_values_supported": [ + "ES256" + ], + "token_endpoint_auth_methods_supported": [ + "none" + ], + "code_challenge_methods_supported": [ + "S256" + ], + "scopes_supported": [ + "openid", + "email", + "phone", + "profile", + "offline_access" + ], + "claims_supported": [ + "iss", + "aud", + "sub", + "sid", + "auth_time", + "nonce", + "vendor_data", + "amr", + "org", + "verified", + "act", + "email", + "email_verified", + "name", + "phone_number" + ] + } + } + } + } + } + }, + "403": { + "$ref": "#/components/responses/OriginNotAllowed" + }, + "404": { + "$ref": "#/components/responses/ApplicationNotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "503": { + "$ref": "#/components/responses/TemporarilyUnavailable" + } + } + } + }, + "/.well-known/jwks.json": { + "get": { + "tags": [ + "Issuer" + ], + "summary": "Get the signing keys", + "operationId": "authentication_get_jwks", + "description": "The application's public ES256 keys. Every token names its key in the `kid` header.\n\nKeys rotate every 30 days. The next key is published a day before it starts signing, and the previous key stays published for a day after it stops, so a JWKS cached for up to 5 minutes keeps verifying across a rotation. Cache keys by `kid` and fetch the JWKS again when a token names a `kid` you do not have, at most once a minute. When Didit revokes a compromised key, the key leaves this document at once and `GET /session/` rejects every token it signed. A replacement key starts signing immediately, without the day of advance publication, so a JWKS cached up to 5 minutes earlier still accepts the revoked key and does not know the replacement yet.", + "security": [ + {} + ], + "x-mint": { + "metadata": { + "authMethod": "none" + } + }, + "x-didit-sdk": { + "resource": "authentication", + "method": "get_jwks", + "auth": "none", + "server": true + }, + "responses": { + "200": { + "description": "The published keys.", + "headers": { + "Cache-Control": { + "description": "`public, max-age=300`: you and shared caches may keep a successful response for 5 minutes, one access token lifetime.", + "schema": { + "type": "string", + "example": "public, max-age=300" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JSONWebKeySet" + }, + "examples": { + "Keys": { + "summary": "One signing key", + "value": { + "keys": [ + { + "kty": "EC", + "crv": "P-256", + "x": "ZDN2unLMultuZwNY_ZKMvsKLRrlC_usPFZyK77cnDO8", + "y": "CfLKoaJNvhFpDWqG56H8dD_j0f1c93RdR63bhIdTh3I", + "kid": "k_8sJ2dQvX0nLr5TfYb3HkPw", + "use": "sig", + "alg": "ES256" + } + ] + } + } + } + } + } + }, + "403": { + "$ref": "#/components/responses/OriginNotAllowed" + }, + "404": { + "$ref": "#/components/responses/ApplicationNotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "503": { + "$ref": "#/components/responses/TemporarilyUnavailable" + } + } + } + }, + "/token/": { + "post": { + "tags": [ + "Tokens" + ], + "summary": "Exchange a code or refresh tokens", + "operationId": "authentication_token", + "description": "The token endpoint, for both grants.\n\n- `authorization_code` exchanges the code your `redirect_uri` received for an access, ID and refresh token. The code works once, within 60 seconds, with the PKCE verifier and the exact `redirect_uri` of its sign-in. When the application allows one session per person, the exchange ends the person's other sessions.\n- `refresh_token` returns a new access, ID and refresh token, and the refresh token you sent stops working. **Sending a used refresh token again revokes the session** and returns `invalid_grant`, so run one refresh at a time for a session and replace all three tokens together. After a refresh fails with `400`, or gets no answer, start sign-in again rather than retrying: a retry after a refresh that did succeed is a reuse. A `429` is answered before the refresh runs, so it is safe to retry after `Retry-After`. A `503` means the refresh did not happen and the refresh token still works: retry it with backoff.\n\nThe body can be JSON or form-encoded.", + "security": [ + {} + ], + "x-mint": { + "metadata": { + "authMethod": "none" + } + }, + "x-didit-sdk": { + "resource": "authentication", + "method": "exchange_code", + "auth": "none", + "server": true, + "variants": [ + { + "method": "exchange_code", + "discriminator": "authorization_code" + }, + { + "method": "refresh", + "discriminator": "refresh_token" + } + ] + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TokenRequest" + }, + "examples": { + "AuthorizationCode": { + "summary": "Exchange the authorization code", + "value": { + "grant_type": "authorization_code", + "code": "Vb0bVt4CLlr8ZL0kE5mAxv8gS6R0rW3hYqC1pN7dJ2s", + "code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk", + "redirect_uri": "https://app.example.com/auth/callback" + } + }, + "RefreshToken": { + "summary": "Refresh the tokens", + "value": { + "grant_type": "refresh_token", + "refresh_token": "N4yZTl6y24YKYq77X_SOrBn1q2Lit81WBQMOay7dq_IVfTnxyY8ZGcJk-6-fcmWW" + } + } + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/TokenRequest" + } + } + } + }, + "responses": { + "200": { + "description": "New tokens for the session.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: never cache this response.", + "schema": { + "type": "string", + "example": "no-store" + } + }, + "Pragma": { + "description": "Always `no-cache`.", + "schema": { + "type": "string", + "example": "no-cache" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TokenResponse" + }, + "examples": { + "Tokens": { + "summary": "Tokens for a new session", + "value": { + "access_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImtfOHNKMmRRdlgwbkxyNVRmWWIzSGtQdyIsInR5cCI6ImF0K2p3dCJ9.eyJpc3MiOiJodHRwczovL3lvdXItYXBwLmF1dGguZGlkaXQubWUiLCJhdWQiOiI3YjBhNmMyZS0zZjQxLTRjOWEtOGQ1NS0xZTJmM2E0YjVjNmQiLCJzdWIiOiIxYzlkOGI4OC0xYmZmLTQzZDEtYWQ3YS04OGIyYmMwMDI0ZTkiLCJzaWQiOiJjOWFiZjZjYy0yZDY0LTRiZjItYjMxZS1hNGQwOWEwNzBhYjEiLCJqdGkiOiJSZ3F1NzE3OWUtbGhKMGJ2VmVELVlnIiwiaWF0IjoxNzkyMjMzNjc0LCJleHAiOjE3OTIyMzM5NzQsInZlbmRvcl9kYXRhIjoidXNlci00MiIsIm9yZyI6bnVsbCwidmVyaWZpZWQiOnt9LCJhbXIiOlsicGFzc3dvcmQiXSwiYWN0IjpudWxsLCJjbGllbnRfaWQiOiI3YjBhNmMyZS0zZjQxLTRjOWEtOGQ1NS0xZTJmM2E0YjVjNmQifQ.AxrqN8Pm-Op-3OayZk8HcvlFQZhozRiOvQ0SgQsGgrz4UNeJlUEeSlq9B8WcYUuM8naRaY5kQt_TUofnsYcyYQ", + "id_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImtfOHNKMmRRdlgwbkxyNVRmWWIzSGtQdyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3lvdXItYXBwLmF1dGguZGlkaXQubWUiLCJhdWQiOiI3YjBhNmMyZS0zZjQxLTRjOWEtOGQ1NS0xZTJmM2E0YjVjNmQiLCJzdWIiOiIxYzlkOGI4OC0xYmZmLTQzZDEtYWQ3YS04OGIyYmMwMDI0ZTkiLCJzaWQiOiJjOWFiZjZjYy0yZDY0LTRiZjItYjMxZS1hNGQwOWEwNzBhYjEiLCJqdGkiOiJNTHRPNHM4SHItQnJrcDd2NTlPV0Z3IiwiaWF0IjoxNzkyMjMzNjc0LCJleHAiOjE3OTIyMzM5NzQsInZlbmRvcl9kYXRhIjoidXNlci00MiIsIm9yZyI6bnVsbCwidmVyaWZpZWQiOnt9LCJhbXIiOlsicGFzc3dvcmQiXSwiYWN0IjpudWxsLCJhdXRoX3RpbWUiOjE3OTIyMzM2NzQsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJlbWFpbCI6InBlcnNvbkBleGFtcGxlLmNvbSIsIm5vbmNlIjoibi0wUzZfV3pBMk1qIn0.avhfDFp_aUV5_1ssYzwDcIA7H6Waf19P0bn5GCg25FhLIZBGIlwc8HiUPGj1j26r21OkP4FQnS_jM1Ho3dHtKQ", + "refresh_token": "N4yZTl6y24YKYq77X_SOrBn1q2Lit81WBQMOay7dq_IVfTnxyY8ZGcJk-6-fcmWW", + "token_type": "Bearer", + "expires_in": 299, + "session_id": "c9abf6cc-2d64-4bf2-b31e-a4d09a070ab1" + } + } + } + } + } + }, + "400": { + "description": "The grant failed. `invalid_grant`: the code or refresh token is unknown, used or expired; the PKCE verifier or `redirect_uri` does not match, or the `redirect_uri` is no longer registered; the person was blocked or their email is no longer verified; the session ended or no longer meets your MFA policy; or, for a code, you turned off the sign-in method it came from. Turning a method off does not stop the refresh of sessions it already started. `invalid_client`: `client_id` is not your application ID. `mfa_required`: the sign-in did not meet your MFA policy. Field errors name a missing or invalid field. A body that cannot be parsed gets a `detail` message.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: never cache this response.", + "schema": { + "type": "string", + "example": "no-store" + } + }, + "Pragma": { + "description": "Always `no-cache`.", + "schema": { + "type": "string", + "example": "no-cache" + } + } + }, + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/OAuthError" + }, + { + "$ref": "#/components/schemas/FieldErrors" + }, + { + "$ref": "#/components/schemas/Detail" + } + ] + }, + "examples": { + "InvalidGrant": { + "summary": "Code or refresh token rejected", + "value": { + "error": "invalid_grant" + } + }, + "InvalidClient": { + "summary": "Wrong client_id", + "value": { + "error": "invalid_client" + } + }, + "MfaRequired": { + "summary": "MFA policy not met", + "value": { + "error": "mfa_required" + } + }, + "MissingFields": { + "summary": "A grant without its required fields", + "value": { + "non_field_errors": [ + "invalid_request" + ] + } + }, + "UnknownGrant": { + "summary": "Unsupported grant_type", + "value": { + "grant_type": [ + "\"password\" is not a valid choice." + ] + } + }, + "UnknownField": { + "summary": "A field the endpoint does not accept", + "value": { + "foo": "unknown_field" + } + }, + "MalformedBody": { + "summary": "A JSON body that cannot be parsed", + "value": { + "detail": "JSON parse error - Expecting property name enclosed in double quotes: line 1 column 2 (char 1)" + } + } + } + } + } + }, + "403": { + "$ref": "#/components/responses/OriginNotAllowed" + }, + "404": { + "$ref": "#/components/responses/ApplicationNotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "503": { + "$ref": "#/components/responses/TemporarilyUnavailable" + } + } + } + }, + "/session/": { + "get": { + "tags": [ + "Sessions" + ], + "summary": "Get the session", + "operationId": "authentication_get_session", + "description": "Checks the access token against the live session and returns it. Stricter than verifying the JWT yourself: it also fails at once when the session was revoked, went idle or passed its lifetime, when the person was blocked or their email is no longer verified, when the session no longer meets your MFA policy (for example after the person added an authenticator app or became an organization admin), or when the token was replaced by a refresh. Use it before a sensitive action, for example a payout. A successful check counts as activity, so it restarts the session's idle timeout.", + "security": [ + { + "AccessToken": [] + } + ], + "x-didit-sdk": { + "resource": "authentication", + "method": "get_session", + "auth": "access_token", + "server": true + }, + "responses": { + "200": { + "description": "The session is live.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: never cache this response.", + "schema": { + "type": "string", + "example": "no-store" + } + }, + "Pragma": { + "description": "Always `no-cache`.", + "schema": { + "type": "string", + "example": "no-cache" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Session" + }, + "examples": { + "Session": { + "summary": "A password session", + "value": { + "sub": "1c9d8b88-1bff-43d1-ad7a-88b2bc0024e9", + "sid": "c9abf6cc-2d64-4bf2-b31e-a4d09a070ab1", + "application_id": "7b0a6c2e-3f41-4c9a-8d55-1e2f3a4b5c6d", + "vendor_data": "user-42", + "amr": [ + "password" + ], + "mfa_verified": false, + "email": "person@example.com", + "email_verified": true, + "phone": null, + "verified_identity": null, + "verified": null, + "expires_at": "2026-11-16T10:41:14.347557Z", + "post_logout_redirect_uri": "", + "organizations": [] + } + }, + "VerifiedMember": { + "summary": "A verified organization member", + "value": { + "sub": "1c9d8b88-1bff-43d1-ad7a-88b2bc0024e9", + "sid": "c9abf6cc-2d64-4bf2-b31e-a4d09a070ab1", + "application_id": "7b0a6c2e-3f41-4c9a-8d55-1e2f3a4b5c6d", + "vendor_data": "user-42", + "amr": [ + "passkey" + ], + "mfa_verified": true, + "email": "person@example.com", + "email_verified": true, + "phone": "+34600000000", + "verified_identity": null, + "verified": { + "level": "full_id", + "over_18": true, + "given_name": "Alex", + "country": "ES" + }, + "expires_at": "2026-11-16T10:41:14.347557Z", + "post_logout_redirect_uri": "https://app.example.com/", + "organizations": [ + { + "organization_id": "3e9a1f0b-5c2d-4e7f-8a6b-9c0d1e2f3a4b", + "organization__name": "Northwind", + "role": "admin", + "custom_role": null + } + ] + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/InvalidToken" + }, + "403": { + "$ref": "#/components/responses/OriginNotAllowed" + }, + "404": { + "$ref": "#/components/responses/ApplicationNotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "503": { + "$ref": "#/components/responses/TemporarilyUnavailable" + } + } + }, + "delete": { + "tags": [ + "Sessions" + ], + "summary": "Revoke the session", + "operationId": "authentication_revoke_session", + "description": "Signs the person out: ends the session behind the access token. Its access tokens fail `GET /session/` at once, and its refresh token stops working.\n\nSend the current access token: one that has not expired and has not been replaced by a refresh. Any other token gets `401` and the session does not end, so refresh and sign out again with the new access token. A monitoring token issued earlier is not tied to the session and stays usable until it expires, within 15 minutes. Tokens you verify offline stay valid until they expire, within 5 minutes. Then send the person to your `post_logout_redirect_uri`, if you set one.", + "security": [ + { + "AccessToken": [] + } + ], + "x-didit-sdk": { + "resource": "authentication", + "method": "revoke_session", + "auth": "access_token", + "server": true + }, + "responses": { + "204": { + "description": "The session ended.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: never cache this response.", + "schema": { + "type": "string", + "example": "no-store" + } + }, + "Pragma": { + "description": "Always `no-cache`.", + "schema": { + "type": "string", + "example": "no-cache" + } + } + } + }, + "401": { + "$ref": "#/components/responses/InvalidToken" + }, + "403": { + "$ref": "#/components/responses/OriginNotAllowed" + }, + "404": { + "$ref": "#/components/responses/ApplicationNotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "503": { + "$ref": "#/components/responses/TemporarilyUnavailable" + } + } + } + }, + "/step-up/": { + "post": { + "tags": [ + "Step-ups" + ], + "summary": "Create a step-up", + "operationId": "authentication_create_step_up", + "description": "Asks whether the person has to verify before a sensitive action, using the step-up rules you set for the application.\n\n- `200 {\"required\": false}`: no rule applies, go ahead.\n- `201`: send the person to `verification_url`. They come back to `return_to` with `step_up=&status=approved`, but that query proves nothing: read the step-up with `GET /step-up/{step_up_id}/` and act only on `approved`.\n\nA step-up never fails open: when verification is unavailable, the action stays blocked.", + "security": [ + { + "AccessToken": [] + } + ], + "x-didit-sdk": { + "resource": "authentication", + "method": "create_step_up", + "auth": "access_token", + "server": true + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StepUpRequest" + }, + "examples": { + "Payout": { + "summary": "Before a payout", + "value": { + "action": "payout", + "amount": "900.00", + "currency": "USD", + "return_to": "https://app.example.com/payouts/confirm" + } + }, + "Bank": { + "summary": "Before changing a bank account", + "value": { + "action": "bank", + "return_to": "https://app.example.com/settings/bank" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "No rule applies.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: never cache this response.", + "schema": { + "type": "string", + "example": "no-store" + } + }, + "Pragma": { + "description": "Always `no-cache`.", + "schema": { + "type": "string", + "example": "no-cache" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StepUpNotRequired" + }, + "examples": { + "NotRequired": { + "summary": "Go ahead", + "value": { + "required": false + } + } + } + } + } + }, + "201": { + "description": "The person has to verify first.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: never cache this response.", + "schema": { + "type": "string", + "example": "no-store" + } + }, + "Pragma": { + "description": "Always `no-cache`.", + "schema": { + "type": "string", + "example": "no-cache" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StepUpRequired" + }, + "examples": { + "Required": { + "summary": "A selfie before a payout", + "value": { + "id": "94efc2c3-ca84-42fe-900b-6fb61669a7a1", + "rule": "payout", + "method": "selfie", + "status": "pending", + "level": null, + "amount": "900.00", + "currency": "USD", + "created_at": "2026-10-17T10:41:14.894632Z", + "completed_at": null, + "expires_at": "2026-10-17T11:11:14.894581Z", + "required": true, + "verification_url": "https://your-app.auth.didit.me/verify/?verification=1nSX7HJCXAP9XPA9AeJfQxIuPk2GkG0dGSOzCA2Zpww", + "expires_in": 1800 + } + } + } + } + } + }, + "400": { + "description": "A field is missing or invalid. `return_to: invalid_redirect_uri` means a rule applies and `return_to` is not one of the application's redirect URIs.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: never cache this response.", + "schema": { + "type": "string", + "example": "no-store" + } + }, + "Pragma": { + "description": "Always `no-cache`.", + "schema": { + "type": "string", + "example": "no-cache" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FieldErrors" + }, + "examples": { + "MissingAmount": { + "summary": "A payout without amount", + "value": { + "amount": [ + "required" + ] + } + }, + "InvalidReturnTo": { + "summary": "Unregistered return_to", + "value": { + "return_to": "invalid_redirect_uri" + } + }, + "UnknownAction": { + "summary": "Unsupported action", + "value": { + "action": [ + "\"withdraw\" is not a valid choice." + ] + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/InvalidToken" + }, + "403": { + "$ref": "#/components/responses/OriginNotAllowed" + }, + "404": { + "$ref": "#/components/responses/ApplicationNotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "503": { + "$ref": "#/components/responses/TemporarilyUnavailable" + } + } + } + }, + "/step-up/{step_up_id}/": { + "get": { + "tags": [ + "Step-ups" + ], + "summary": "Get a step-up", + "operationId": "authentication_retrieve_step_up", + "description": "The step-up's current status. Call it with an access token of the same session that created the step-up, and act only when `status` is `approved`.", + "security": [ + { + "AccessToken": [] + } + ], + "x-didit-sdk": { + "resource": "authentication", + "method": "retrieve_step_up", + "auth": "access_token", + "server": true + }, + "parameters": [ + { + "name": "step_up_id", + "in": "path", + "required": true, + "description": "The `id` from `POST /step-up/`, also the `step_up` query parameter on your `return_to`. A value that is not a UUID gets a `404` page, not JSON.", + "schema": { + "type": "string", + "format": "uuid" + }, + "example": "94efc2c3-ca84-42fe-900b-6fb61669a7a1" + } + ], + "responses": { + "200": { + "description": "The step-up.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: never cache this response.", + "schema": { + "type": "string", + "example": "no-store" + } + }, + "Pragma": { + "description": "Always `no-cache`.", + "schema": { + "type": "string", + "example": "no-cache" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StepUp" + }, + "examples": { + "Approved": { + "summary": "Approved: go ahead", + "value": { + "id": "94efc2c3-ca84-42fe-900b-6fb61669a7a1", + "rule": "payout", + "method": "selfie", + "status": "approved", + "level": "liveness_face_match", + "amount": "900.00", + "currency": "USD", + "created_at": "2026-10-17T10:41:14.894632Z", + "completed_at": "2026-10-17T10:43:02.118204Z", + "expires_at": "2026-10-17T11:11:14.894581Z" + } + }, + "Pending": { + "summary": "Not finished yet", + "value": { + "id": "94efc2c3-ca84-42fe-900b-6fb61669a7a1", + "rule": "payout", + "method": "selfie", + "status": "pending", + "level": null, + "amount": "900.00", + "currency": "USD", + "created_at": "2026-10-17T10:41:14.894632Z", + "completed_at": null, + "expires_at": "2026-10-17T11:11:14.894581Z" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/InvalidToken" + }, + "403": { + "$ref": "#/components/responses/OriginNotAllowed" + }, + "404": { + "description": "No step-up with this ID belongs to this session, or the application is unknown.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: never cache this response.", + "schema": { + "type": "string", + "example": "no-store" + } + }, + "Pragma": { + "description": "Always `no-cache`.", + "schema": { + "type": "string", + "example": "no-cache" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Detail" + }, + "examples": { + "StepUpNotFound": { + "summary": "Unknown step-up", + "value": { + "detail": "No VerificationCheck matches the given query." + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "503": { + "$ref": "#/components/responses/TemporarilyUnavailable" + } + } + } + }, + "/monitoring-token/": { + "post": { + "tags": [ + "Monitoring" + ], + "summary": "Create a monitoring token", + "operationId": "authentication_create_monitoring_token", + "description": "Swaps the person's access token for a short-lived device credential, so the person's browser or app can send activity events to transaction monitoring without your backend. The credential is bound to the person's `vendor_data` and to the request's `Origin`.\n\nThe browser and native SDKs call this endpoint; server SDKs do not. It is off until Didit turns it on, and answers `503 monitoring_unavailable` until then.", + "security": [ + { + "AccessToken": [] + } + ], + "x-didit-sdk": { + "resource": "authentication", + "method": "create_monitoring_token", + "auth": "access_token", + "server": false + }, + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringTokenRequest" + }, + "example": {} + } + } + }, + "responses": { + "201": { + "description": "A device credential for the person.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: never cache this response.", + "schema": { + "type": "string", + "example": "no-store" + } + }, + "Pragma": { + "description": "Always `no-cache`.", + "schema": { + "type": "string", + "example": "no-cache" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MonitoringToken" + }, + "examples": { + "MonitoringToken": { + "summary": "A credential for the person's browser", + "value": { + "monitoring_token": "mt_2f8c1d9e7b6a5f4e3d2c1b0a", + "expires_at": "2026-10-17T10:56:14Z", + "allowed_event_categories": [ + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "responsible_gambling", + "custom" + ], + "max_uses": 1000, + "bind_to_origin": "https://app.example.com", + "vendor_data": "user-42" + } + } + } + } + } + }, + "400": { + "description": "The body carried a field. Send an empty object.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: never cache this response.", + "schema": { + "type": "string", + "example": "no-store" + } + }, + "Pragma": { + "description": "Always `no-cache`.", + "schema": { + "type": "string", + "example": "no-cache" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FieldErrors" + }, + "examples": { + "UnknownField": { + "summary": "A field in the body", + "value": { + "vendor_data": "unknown_field" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/InvalidToken" + }, + "403": { + "$ref": "#/components/responses/OriginNotAllowed" + }, + "404": { + "$ref": "#/components/responses/ApplicationNotFound" + }, + "409": { + "description": "Event monitoring is turned off for the application. Turn on event ingestion in the transaction monitoring settings.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: never cache this response.", + "schema": { + "type": "string", + "example": "no-store" + } + }, + "Pragma": { + "description": "Always `no-cache`.", + "schema": { + "type": "string", + "example": "no-cache" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuthError" + }, + "examples": { + "MonitoringDisabled": { + "summary": "Event ingestion off", + "value": { + "error": "monitoring_disabled" + } + } + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "503": { + "description": "Monitoring tokens are not enabled, or monitoring could not issue one (`monitoring_unavailable`), or a service the request depends on is briefly unavailable (`temporarily_unavailable`). Retry later.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: never cache this response.", + "schema": { + "type": "string", + "example": "no-store" + } + }, + "Pragma": { + "description": "Always `no-cache`.", + "schema": { + "type": "string", + "example": "no-cache" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuthError" + }, + "examples": { + "MonitoringUnavailable": { + "summary": "Not available", + "value": { + "error": "monitoring_unavailable" + } + }, + "TemporarilyUnavailable": { + "summary": "Retry later", + "value": { + "error": "temporarily_unavailable" + } + } + } + } + } + } + } + } + } + }, + "components": { + "securitySchemes": { + "AccessToken": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "The person's access token from `POST /token/`: an ES256 JWT with header `typ: at+jwt`, valid for at most 5 minutes. Not your Didit API key." + } + }, + "responses": { + "InvalidToken": { + "description": "The access token is missing, malformed, expired, signed by another issuer or key, an ID token, an agent token, or replaced by a refresh; or its session was revoked, idled out, passed its lifetime or no longer meets your MFA policy, or its person was blocked or their email is no longer verified. Send the person through sign-in again.", + "headers": { + "WWW-Authenticate": { + "description": "`Bearer`.", + "schema": { + "type": "string", + "example": "Bearer" + } + }, + "Cache-Control": { + "description": "Always `no-store`: never cache this response.", + "schema": { + "type": "string", + "example": "no-store" + } + }, + "Pragma": { + "description": "Always `no-cache`.", + "schema": { + "type": "string", + "example": "no-cache" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Detail" + }, + "examples": { + "InvalidToken": { + "summary": "Rejected access token", + "value": { + "detail": "invalid_token" + } + } + } + } + } + }, + "OriginNotAllowed": { + "description": "The request carried an `Origin` that is neither the application's own nor in its allowed origins. Server requests send no `Origin`.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: never cache this response.", + "schema": { + "type": "string", + "example": "no-store" + } + }, + "Pragma": { + "description": "Always `no-cache`.", + "schema": { + "type": "string", + "example": "no-cache" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Detail" + }, + "examples": { + "OriginNotAllowed": { + "summary": "Origin not allowed", + "value": { + "detail": "origin_not_allowed" + } + } + } + } + } + }, + "ApplicationNotFound": { + "description": "The address belongs to an application, but Authentication is not active for it; on the `apx.didit.me` address, the application ID is unknown. An address that belongs to no application is refused before it reaches the endpoint, and that response is not JSON.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: never cache this response.", + "schema": { + "type": "string", + "example": "no-store" + } + }, + "Pragma": { + "description": "Always `no-cache`.", + "schema": { + "type": "string", + "example": "no-cache" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Detail" + }, + "examples": { + "ApplicationNotFound": { + "summary": "Unknown application", + "value": { + "detail": "No Configuration matches the given query." + } + } + } + } + } + }, + "RateLimited": { + "description": "Too many requests from this IP address for this endpoint, or, on a `POST` or `DELETE`, a bot protection bucket is empty. Nothing ran: wait `Retry-After` seconds, then retry.", + "headers": { + "Retry-After": { + "description": "Seconds to wait before retrying.", + "schema": { + "type": "integer", + "example": 60 + } + }, + "Cache-Control": { + "description": "Always `no-store`: never cache this response.", + "schema": { + "type": "string", + "example": "no-store" + } + }, + "Pragma": { + "description": "Always `no-cache`.", + "schema": { + "type": "string", + "example": "no-cache" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Detail" + }, + "examples": { + "RateLimited": { + "summary": "Endpoint limit", + "value": { + "detail": "Too many requests from this IP address. Please wait before trying again. Expected available in 60 seconds." + } + }, + "BotProtection": { + "summary": "Bot protection limit (POST and DELETE)", + "value": { + "detail": "rate_limited Expected available in 2 seconds." + } + } + } + } + } + }, + "TemporarilyUnavailable": { + "description": "A service the request depends on is briefly unavailable. Retry with backoff.", + "headers": { + "Cache-Control": { + "description": "Always `no-store`: never cache this response.", + "schema": { + "type": "string", + "example": "no-store" + } + }, + "Pragma": { + "description": "Always `no-cache`.", + "schema": { + "type": "string", + "example": "no-cache" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuthError" + }, + "examples": { + "TemporarilyUnavailable": { + "summary": "Retry later", + "value": { + "error": "temporarily_unavailable" + } + } + } + } + } + } + }, + "schemas": { + "OpenIDConfiguration": { + "type": "object", + "description": "The issuer's OpenID Connect discovery document. Read endpoints from here rather than building them.", + "required": [ + "issuer", + "authorization_endpoint", + "token_endpoint", + "jwks_uri", + "response_types_supported", + "response_modes_supported", + "grant_types_supported", + "subject_types_supported", + "id_token_signing_alg_values_supported", + "token_endpoint_auth_methods_supported", + "code_challenge_methods_supported", + "scopes_supported", + "claims_supported" + ], + "properties": { + "issuer": { + "type": "string", + "format": "uri", + "description": "The issuer: the application's sign-in origin, without a trailing slash. Every token's `iss` equals it.", + "example": "https://your-app.auth.didit.me" + }, + "authorization_endpoint": { + "type": "string", + "format": "uri", + "description": "The hosted sign-in page (`/sign-in`).", + "example": "https://your-app.auth.didit.me/sign-in" + }, + "token_endpoint": { + "type": "string", + "format": "uri", + "example": "https://your-app.auth.didit.me/token/" + }, + "jwks_uri": { + "type": "string", + "format": "uri", + "example": "https://your-app.auth.didit.me/.well-known/jwks.json" + }, + "response_types_supported": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "code" + ] + }, + "response_modes_supported": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "query" + ] + }, + "grant_types_supported": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "authorization_code", + "refresh_token" + ] + }, + "subject_types_supported": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "public" + ] + }, + "id_token_signing_alg_values_supported": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "ES256" + ] + }, + "token_endpoint_auth_methods_supported": { + "type": "array", + "items": { + "type": "string" + }, + "description": "`none`: clients are public and send no secret.", + "example": [ + "none" + ] + }, + "code_challenge_methods_supported": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "S256" + ] + }, + "scopes_supported": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "openid", + "email", + "phone", + "profile", + "offline_access" + ] + }, + "claims_supported": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "iss", + "aud", + "sub", + "sid", + "auth_time", + "nonce", + "vendor_data", + "amr", + "org", + "verified", + "act", + "email", + "email_verified", + "name", + "phone_number" + ] + } + } + }, + "JSONWebKeySet": { + "type": "object", + "description": "The application's published public signing keys. Usually one key; two around a rotation, when the next key is published a day before it signs and the previous key stays a day after it stops.", + "required": [ + "keys" + ], + "properties": { + "keys": { + "type": "array", + "items": { + "$ref": "#/components/schemas/JSONWebKey" + } + } + } + }, + "JSONWebKey": { + "type": "object", + "description": "A P-256 public key in JWK form (RFC 7517).", + "required": [ + "kty", + "crv", + "x", + "y", + "kid", + "use", + "alg" + ], + "properties": { + "kty": { + "type": "string", + "enum": [ + "EC" + ] + }, + "crv": { + "type": "string", + "enum": [ + "P-256" + ] + }, + "x": { + "type": "string", + "description": "The public point's x coordinate, base64url.", + "example": "ZDN2unLMultuZwNY_ZKMvsKLRrlC_usPFZyK77cnDO8" + }, + "y": { + "type": "string", + "description": "The public point's y coordinate, base64url.", + "example": "CfLKoaJNvhFpDWqG56H8dD_j0f1c93RdR63bhIdTh3I" + }, + "kid": { + "type": "string", + "description": "Key ID, the `kid` in the header of every token this key signs. New key IDs start with `k_`.", + "example": "k_8sJ2dQvX0nLr5TfYb3HkPw" + }, + "use": { + "type": "string", + "enum": [ + "sig" + ] + }, + "alg": { + "type": "string", + "enum": [ + "ES256" + ] + } + } + }, + "TokenRequest": { + "description": "One of the two grants, chosen by `grant_type`. The endpoint accepts the fields of both grants and validates every field it receives, so a refresh that also carries a malformed `code_verifier` gets a field error. Send only your grant's fields; any other field gets `unknown_field`.", + "oneOf": [ + { + "$ref": "#/components/schemas/AuthorizationCodeGrantRequest" + }, + { + "$ref": "#/components/schemas/RefreshTokenGrantRequest" + } + ], + "discriminator": { + "propertyName": "grant_type", + "mapping": { + "authorization_code": "#/components/schemas/AuthorizationCodeGrantRequest", + "refresh_token": "#/components/schemas/RefreshTokenGrantRequest" + } + } + }, + "AuthorizationCodeGrantRequest": { + "title": "Authorization code grant", + "type": "object", + "description": "Exchange the authorization code from your redirect for tokens. The code works once, for 60 seconds.", + "required": [ + "grant_type", + "code", + "code_verifier", + "redirect_uri" + ], + "properties": { + "grant_type": { + "type": "string", + "enum": [ + "authorization_code" + ] + }, + "code": { + "type": "string", + "maxLength": 128, + "description": "The `code` query parameter your `redirect_uri` received. Check the callback's `state` before you exchange it.", + "example": "Vb0bVt4CLlr8ZL0kE5mAxv8gS6R0rW3hYqC1pN7dJ2s" + }, + "code_verifier": { + "type": "string", + "minLength": 43, + "maxLength": 128, + "pattern": "^[A-Za-z0-9._~-]{43,128}$", + "description": "The PKCE verifier whose S256 hash was the sign-in's `code_challenge`.", + "example": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" + }, + "redirect_uri": { + "type": "string", + "maxLength": 2048, + "description": "The exact `redirect_uri` the sign-in started with. It must still be one of the application's redirect URIs.", + "example": "https://app.example.com/auth/callback" + }, + "client_id": { + "type": "string", + "maxLength": 64, + "description": "Optional. When sent, it must equal your application ID, or the request fails with `invalid_client`.", + "example": "7b0a6c2e-3f41-4c9a-8d55-1e2f3a4b5c6d" + }, + "scope": { + "type": "string", + "maxLength": 512, + "description": "Optional and accepted from standard OpenID Connect clients. It never widens what the sign-in granted.", + "example": "openid email" + } + } + }, + "RefreshTokenGrantRequest": { + "title": "Refresh token grant", + "type": "object", + "description": "Swap a refresh token for a new access, ID and refresh token. The refresh token works once: sending it again revokes the session, so run one refresh at a time for a session.", + "required": [ + "grant_type", + "refresh_token" + ], + "properties": { + "grant_type": { + "type": "string", + "enum": [ + "refresh_token" + ] + }, + "refresh_token": { + "type": "string", + "maxLength": 128, + "description": "The `refresh_token` from the previous token response.", + "example": "N4yZTl6y24YKYq77X_SOrBn1q2Lit81WBQMOay7dq_IVfTnxyY8ZGcJk-6-fcmWW" + }, + "client_id": { + "type": "string", + "maxLength": 64, + "description": "Optional. When sent, it must equal your application ID, or the request fails with `invalid_client`.", + "example": "7b0a6c2e-3f41-4c9a-8d55-1e2f3a4b5c6d" + }, + "scope": { + "type": "string", + "maxLength": 512, + "description": "Optional and accepted from standard OpenID Connect clients. A refresh never widens what the session holds.", + "example": "openid email" + } + } + }, + "TokenResponse": { + "type": "object", + "description": "Tokens for one session. Replace all three together after a refresh.", + "required": [ + "access_token", + "id_token", + "refresh_token", + "token_type", + "expires_in", + "session_id" + ], + "properties": { + "access_token": { + "type": "string", + "description": "ES256 JWT, header `typ: at+jwt`. Send it as `Authorization: Bearer `. Claims: `AccessTokenClaims`.", + "example": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImtfOHNKMmRRdlgwbkxyNVRmWWIzSGtQdyIsInR5cCI6ImF0K2p3dCJ9.eyJpc3MiOiJodHRwczovL3lvdXItYXBwLmF1dGguZGlkaXQubWUiLCJhdWQiOiI3YjBhNmMyZS0zZjQxLTRjOWEtOGQ1NS0xZTJmM2E0YjVjNmQiLCJzdWIiOiIxYzlkOGI4OC0xYmZmLTQzZDEtYWQ3YS04OGIyYmMwMDI0ZTkiLCJzaWQiOiJjOWFiZjZjYy0yZDY0LTRiZjItYjMxZS1hNGQwOWEwNzBhYjEiLCJqdGkiOiJSZ3F1NzE3OWUtbGhKMGJ2VmVELVlnIiwiaWF0IjoxNzkyMjMzNjc0LCJleHAiOjE3OTIyMzM5NzQsInZlbmRvcl9kYXRhIjoidXNlci00MiIsIm9yZyI6bnVsbCwidmVyaWZpZWQiOnt9LCJhbXIiOlsicGFzc3dvcmQiXSwiYWN0IjpudWxsLCJjbGllbnRfaWQiOiI3YjBhNmMyZS0zZjQxLTRjOWEtOGQ1NS0xZTJmM2E0YjVjNmQifQ.AxrqN8Pm-Op-3OayZk8HcvlFQZhozRiOvQ0SgQsGgrz4UNeJlUEeSlq9B8WcYUuM8naRaY5kQt_TUofnsYcyYQ", + "x-didit-jwt": { + "header": "#/components/schemas/AccessTokenHeader", + "claims": "#/components/schemas/AccessTokenClaims" + } + }, + "id_token": { + "type": "string", + "description": "ES256 JWT, header `typ: JWT`, for your application to read who signed in. Never send it as an access token. Claims: `IDTokenClaims`.", + "example": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImtfOHNKMmRRdlgwbkxyNVRmWWIzSGtQdyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3lvdXItYXBwLmF1dGguZGlkaXQubWUiLCJhdWQiOiI3YjBhNmMyZS0zZjQxLTRjOWEtOGQ1NS0xZTJmM2E0YjVjNmQiLCJzdWIiOiIxYzlkOGI4OC0xYmZmLTQzZDEtYWQ3YS04OGIyYmMwMDI0ZTkiLCJzaWQiOiJjOWFiZjZjYy0yZDY0LTRiZjItYjMxZS1hNGQwOWEwNzBhYjEiLCJqdGkiOiJNTHRPNHM4SHItQnJrcDd2NTlPV0Z3IiwiaWF0IjoxNzkyMjMzNjc0LCJleHAiOjE3OTIyMzM5NzQsInZlbmRvcl9kYXRhIjoidXNlci00MiIsIm9yZyI6bnVsbCwidmVyaWZpZWQiOnt9LCJhbXIiOlsicGFzc3dvcmQiXSwiYWN0IjpudWxsLCJhdXRoX3RpbWUiOjE3OTIyMzM2NzQsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJlbWFpbCI6InBlcnNvbkBleGFtcGxlLmNvbSIsIm5vbmNlIjoibi0wUzZfV3pBMk1qIn0.avhfDFp_aUV5_1ssYzwDcIA7H6Waf19P0bn5GCg25FhLIZBGIlwc8HiUPGj1j26r21OkP4FQnS_jM1Ho3dHtKQ", + "x-didit-jwt": { + "header": "#/components/schemas/IDTokenHeader", + "claims": "#/components/schemas/IDTokenClaims" + } + }, + "refresh_token": { + "type": "string", + "description": "Single-use. It is valid until the session ends, and each refresh returns a new one.", + "example": "N4yZTl6y24YKYq77X_SOrBn1q2Lit81WBQMOay7dq_IVfTnxyY8ZGcJk-6-fcmWW" + }, + "token_type": { + "type": "string", + "enum": [ + "Bearer" + ] + }, + "expires_in": { + "type": "integer", + "minimum": 0, + "maximum": 300, + "description": "Seconds until the access token expires: at most 300, less when the session ends sooner.", + "example": 299 + }, + "session_id": { + "type": "string", + "format": "uuid", + "description": "The session these tokens belong to, the same value as the `sid` claim.", + "example": "c9abf6cc-2d64-4bf2-b31e-a4d09a070ab1" + } + } + }, + "AccessTokenHeader": { + "type": "object", + "description": "The JOSE header of an access token.", + "required": [ + "alg", + "kid", + "typ" + ], + "properties": { + "alg": { + "type": "string", + "enum": [ + "ES256" + ] + }, + "kid": { + "type": "string", + "description": "The signing key, one of the `kid` values in the JWKS.", + "example": "k_8sJ2dQvX0nLr5TfYb3HkPw" + }, + "typ": { + "type": "string", + "enum": [ + "at+jwt" + ] + } + } + }, + "IDTokenHeader": { + "type": "object", + "description": "The JOSE header of an ID token.", + "required": [ + "alg", + "kid", + "typ" + ], + "properties": { + "alg": { + "type": "string", + "enum": [ + "ES256" + ] + }, + "kid": { + "type": "string", + "description": "The signing key, one of the `kid` values in the JWKS.", + "example": "k_8sJ2dQvX0nLr5TfYb3HkPw" + }, + "typ": { + "type": "string", + "enum": [ + "JWT" + ] + } + } + }, + "AccessTokenClaims": { + "type": "object", + "description": "The payload of an access token. Accept it only after checking the signature, `alg`, `typ`, `iss`, `aud` and `exp`.", + "required": [ + "iss", + "aud", + "client_id", + "sub", + "sid", + "jti", + "iat", + "exp", + "vendor_data", + "amr", + "org", + "verified", + "act" + ], + "properties": { + "iss": { + "type": "string", + "format": "uri", + "description": "The issuer. Compare it with the issuer you configured, never with a value read from the token.", + "example": "https://your-app.auth.didit.me" + }, + "aud": { + "type": "string", + "description": "Your application ID.", + "example": "7b0a6c2e-3f41-4c9a-8d55-1e2f3a4b5c6d" + }, + "client_id": { + "type": "string", + "description": "Your application ID.", + "example": "7b0a6c2e-3f41-4c9a-8d55-1e2f3a4b5c6d" + }, + "sub": { + "type": "string", + "format": "uuid", + "description": "The person's identity ID in this application.", + "example": "1c9d8b88-1bff-43d1-ad7a-88b2bc0024e9" + }, + "sid": { + "type": "string", + "format": "uuid", + "description": "The session ID. For an agent token (`act` is not `null`), the agent's grant ID.", + "example": "c9abf6cc-2d64-4bf2-b31e-a4d09a070ab1" + }, + "jti": { + "type": "string", + "description": "This token's unique ID.", + "example": "Rgqu7179e-lhJ0bvVeD-Yg" + }, + "iat": { + "type": "integer", + "description": "Issue time, in seconds since the Unix epoch.", + "example": 1792233674 + }, + "exp": { + "type": "integer", + "description": "Expiry, in seconds since the Unix epoch: at most 5 minutes after `iat`.", + "example": 1792233974 + }, + "vendor_data": { + "type": "string", + "description": "The person's reference across Didit products: the same value links their verification sessions and transactions. Your external ID when you imported the person, otherwise the identity ID.", + "example": "user-42" + }, + "amr": { + "$ref": "#/components/schemas/AuthenticationMethods" + }, + "org": { + "$ref": "#/components/schemas/OrganizationClaim" + }, + "verified": { + "$ref": "#/components/schemas/VerifiedClaims" + }, + "act": { + "$ref": "#/components/schemas/ActorClaim" + } + } + }, + "IDTokenClaims": { + "type": "object", + "description": "The payload of an ID token: the access token's claims except `client_id`, plus when and who signed in. Unset `email`, `name` and `phone_number` are left out, and so is `nonce` after a refresh.", + "required": [ + "iss", + "aud", + "sub", + "sid", + "jti", + "iat", + "exp", + "vendor_data", + "amr", + "org", + "verified", + "act", + "auth_time", + "email_verified" + ], + "properties": { + "iss": { + "type": "string", + "format": "uri", + "example": "https://your-app.auth.didit.me" + }, + "aud": { + "type": "string", + "description": "Your application ID.", + "example": "7b0a6c2e-3f41-4c9a-8d55-1e2f3a4b5c6d" + }, + "sub": { + "type": "string", + "format": "uuid", + "example": "1c9d8b88-1bff-43d1-ad7a-88b2bc0024e9" + }, + "sid": { + "type": "string", + "format": "uuid", + "example": "c9abf6cc-2d64-4bf2-b31e-a4d09a070ab1" + }, + "jti": { + "type": "string", + "description": "This token's unique ID, different from the access token's.", + "example": "MLtO4s8Hr-Brkp7v59OWFw" + }, + "iat": { + "type": "integer", + "example": 1792233674 + }, + "exp": { + "type": "integer", + "example": 1792233974 + }, + "vendor_data": { + "type": "string", + "example": "user-42" + }, + "amr": { + "$ref": "#/components/schemas/AuthenticationMethods" + }, + "org": { + "$ref": "#/components/schemas/OrganizationClaim" + }, + "verified": { + "$ref": "#/components/schemas/VerifiedClaims" + }, + "act": { + "type": "object", + "nullable": true, + "description": "Always `null` in an ID token.", + "example": null + }, + "auth_time": { + "type": "integer", + "description": "When the session started, in seconds since the Unix epoch.", + "example": 1792233674 + }, + "nonce": { + "type": "string", + "description": "The `nonce` the sign-in started with. Only the ID token from the code exchange carries it, when the sign-in had one: check that it matches there. ID tokens from a refresh never carry it.", + "example": "n-0S6_WzA2Mj" + }, + "email": { + "type": "string", + "format": "email", + "example": "person@example.com" + }, + "email_verified": { + "type": "boolean", + "description": "Whether the person proved the email address. Present even when `false`.", + "example": true + }, + "name": { + "type": "string", + "example": "Alex Doe" + }, + "phone_number": { + "type": "string", + "description": "E.164 phone number, present once the person proved it.", + "example": "+34600000000" + } + } + }, + "AuthenticationMethods": { + "type": "array", + "description": "How the session signed in, as one value: for example `password`, `code` (email code), `magic` (magic link), `passkey`, `phone`, `sso`, `wallet` (Didit ID Wallet) or the name of a social sign-in method. Empty for an agent token.", + "items": { + "type": "string" + }, + "example": [ + "password" + ] + }, + "OrganizationClaim": { + "type": "object", + "description": "The person's earliest membership in one of your customer organizations, when organizations are on. `null` otherwise.", + "nullable": true, + "required": [ + "id", + "role" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid", + "example": "3e9a1f0b-5c2d-4e7f-8a6b-9c0d1e2f3a4b" + }, + "role": { + "type": "string", + "enum": [ + "admin", + "member", + "billing" + ], + "example": "member" + } + } + }, + "ActorClaim": { + "type": "object", + "description": "Set when an AI agent acts for the person under a grant the person approved. `null` for the person's own sessions.", + "nullable": true, + "required": [ + "sub", + "agent", + "scopes" + ], + "properties": { + "sub": { + "type": "string", + "description": "The agent's ID.", + "example": "0f6e5d4c-3b2a-4190-8f7e-6d5c4b3a2918" + }, + "agent": { + "type": "string", + "description": "The agent's name.", + "example": "Expense assistant" + }, + "scopes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "What the person allowed the agent to do.", + "example": [ + "read" + ] + } + } + }, + "VerifiedClaims": { + "type": "object", + "description": "Identity verification the person passed, with the claims your application selected. In tokens it is `{}` when there is none; `GET /session/` returns `null` instead. A check approved during a session reaches the tokens at the next refresh.", + "properties": { + "level": { + "type": "string", + "enum": [ + "liveness", + "liveness_face_match", + "full_id" + ], + "description": "The highest level passed. From weakest to strongest: `liveness`, `liveness_face_match`, `full_id`.", + "example": "full_id" + }, + "over_18": { + "type": "boolean", + "example": true + }, + "over_21": { + "type": "boolean", + "example": true + }, + "given_name": { + "type": "string", + "example": "Alex" + }, + "family_name": { + "type": "string", + "example": "Doe" + }, + "birthdate": { + "type": "string", + "description": "Date of birth from the document, `YYYY-MM-DD`.", + "example": "1990-04-12" + }, + "nationality": { + "type": "string", + "description": "ISO 3166-1 alpha-2 country code.", + "example": "ES" + }, + "country": { + "type": "string", + "description": "ISO 3166-1 alpha-2 code of the document's country.", + "example": "ES" + }, + "document_verified": { + "type": "boolean", + "example": true + }, + "liveness": { + "type": "boolean", + "example": true + } + } + }, + "Session": { + "type": "object", + "description": "The live session behind an access token.", + "required": [ + "sub", + "sid", + "application_id", + "vendor_data", + "amr", + "mfa_verified", + "email", + "email_verified", + "phone", + "verified_identity", + "verified", + "expires_at", + "post_logout_redirect_uri", + "organizations" + ], + "properties": { + "sub": { + "type": "string", + "format": "uuid", + "description": "The identity ID.", + "example": "1c9d8b88-1bff-43d1-ad7a-88b2bc0024e9" + }, + "sid": { + "type": "string", + "format": "uuid", + "description": "The session ID.", + "example": "c9abf6cc-2d64-4bf2-b31e-a4d09a070ab1" + }, + "application_id": { + "type": "string", + "format": "uuid", + "example": "7b0a6c2e-3f41-4c9a-8d55-1e2f3a4b5c6d" + }, + "vendor_data": { + "type": "string", + "description": "The person's reference across Didit products, as in the tokens.", + "example": "user-42" + }, + "amr": { + "$ref": "#/components/schemas/AuthenticationMethods" + }, + "mfa_verified": { + "type": "boolean", + "description": "Whether the session passed a second factor. Always `true` for a passkey sign-in.", + "example": false + }, + "email": { + "type": "string", + "format": "email", + "nullable": true, + "example": "person@example.com" + }, + "email_verified": { + "type": "boolean", + "example": true + }, + "phone": { + "type": "string", + "nullable": true, + "description": "E.164 phone number, or `null`.", + "example": null + }, + "verified_identity": { + "$ref": "#/components/schemas/WalletVerification" + }, + "verified": { + "type": "object", + "nullable": true, + "description": "Verification level and selected claims, the same object the tokens carry as `verified`, or `null` when there is none. Read this, not `email_verified`, to know whether the person verified their identity.", + "properties": { + "level": { + "type": "string", + "enum": [ + "liveness", + "liveness_face_match", + "full_id" + ], + "description": "The highest level passed. From weakest to strongest: `liveness`, `liveness_face_match`, `full_id`.", + "example": "full_id" + }, + "over_18": { + "type": "boolean", + "example": true + }, + "over_21": { + "type": "boolean", + "example": true + }, + "given_name": { + "type": "string", + "example": "Alex" + }, + "family_name": { + "type": "string", + "example": "Doe" + }, + "birthdate": { + "type": "string", + "description": "Date of birth from the document, `YYYY-MM-DD`.", + "example": "1990-04-12" + }, + "nationality": { + "type": "string", + "description": "ISO 3166-1 alpha-2 country code.", + "example": "ES" + }, + "country": { + "type": "string", + "description": "ISO 3166-1 alpha-2 code of the document's country.", + "example": "ES" + }, + "document_verified": { + "type": "boolean", + "example": true + }, + "liveness": { + "type": "boolean", + "example": true + } + } + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the session ends at the latest. Idle timeout can end it sooner.", + "example": "2026-11-16T10:41:14.347557Z" + }, + "post_logout_redirect_uri": { + "type": "string", + "description": "Where your application sends the person after sign-out. Empty string when not set.", + "example": "https://app.example.com/" + }, + "organizations": { + "type": "array", + "description": "Every customer organization the person belongs to, when organizations are on. Empty otherwise.", + "items": { + "$ref": "#/components/schemas/SessionOrganization" + } + } + } + }, + "SessionOrganization": { + "type": "object", + "required": [ + "organization_id", + "organization__name", + "role", + "custom_role" + ], + "properties": { + "organization_id": { + "type": "string", + "format": "uuid", + "example": "3e9a1f0b-5c2d-4e7f-8a6b-9c0d1e2f3a4b" + }, + "organization__name": { + "type": "string", + "description": "The organization's name.", + "example": "Northwind" + }, + "role": { + "type": "string", + "enum": [ + "admin", + "member", + "billing" + ], + "example": "admin" + }, + "custom_role": { + "type": "object", + "nullable": true, + "description": "The custom role assigned to the member, or `null`.", + "required": [ + "key", + "permissions" + ], + "properties": { + "key": { + "type": "string", + "example": "approver" + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "payouts.approve" + ] + } + } + } + } + }, + "WalletVerification": { + "type": "object", + "description": "What the Didit ID Wallet shared at sign-in. `null` when the person signed in another way.", + "nullable": true, + "required": [ + "issuer", + "subject" + ], + "properties": { + "issuer": { + "type": "string", + "description": "The wallet's issuer.", + "example": "https://apx.didit.me/auth/idv" + }, + "subject": { + "type": "string", + "description": "The person's ID at the wallet.", + "example": "8b7f5c1e-2d3a-4b5c-9e8f-7a6b5c4d3e2f" + }, + "shared_session": { + "type": "object", + "description": "A verification the person chose to share, which you redeem with your own API key.", + "properties": { + "share_token": { + "type": "string" + }, + "session_id": { + "type": "string" + }, + "from_application_id": { + "type": "string" + } + } + }, + "claims": { + "type": "object", + "description": "The verified facts the person agreed to share, limited to what your application asked for.", + "additionalProperties": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "string" + } + ] + }, + "example": { + "over_18": true, + "given_name": "Alex" + } + } + } + }, + "StepUpRequest": { + "type": "object", + "description": "The sensitive action the person is about to take.", + "required": [ + "action", + "return_to" + ], + "additionalProperties": false, + "properties": { + "action": { + "type": "string", + "enum": [ + "payout", + "bank", + "contact" + ], + "description": "`payout` (a withdrawal or transfer), `bank` (adding or changing a bank account) or `contact` (changing an email or phone number). A payout also applies the new-device rule." + }, + "amount": { + "type": "string", + "pattern": "^[0-9]{1,16}(\\.[0-9]{1,2})?$", + "description": "Required for `payout`: the amount, up to two decimals. A JSON number is accepted too.", + "example": "900.00" + }, + "currency": { + "type": "string", + "pattern": "^[A-Z]{3}$", + "description": "ISO 4217 code of `amount`. Defaults to the payout rule's currency; a payout in another currency is always checked.", + "example": "USD" + }, + "return_to": { + "type": "string", + "maxLength": 2048, + "description": "Where the person returns after verifying, with `step_up=&status=approved` added. When a rule applies, it must be one of the application's redirect URIs; when none applies, it is not checked.", + "example": "https://app.example.com/payouts/confirm" + } + } + }, + "StepUpNotRequired": { + "type": "object", + "description": "No rule applies: go ahead with the action.", + "required": [ + "required" + ], + "properties": { + "required": { + "type": "boolean", + "enum": [ + false + ] + } + } + }, + "StepUp": { + "type": "object", + "description": "A verification the person has to pass before the action.", + "required": [ + "id", + "rule", + "method", + "status", + "level", + "amount", + "currency", + "created_at", + "completed_at", + "expires_at" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid", + "example": "94efc2c3-ca84-42fe-900b-6fb61669a7a1" + }, + "rule": { + "type": "string", + "enum": [ + "payout", + "device", + "bank", + "contact" + ], + "description": "The rule that asked for it. When several apply, the one with the strongest method.", + "example": "payout" + }, + "method": { + "type": "string", + "enum": [ + "liveness", + "selfie", + "full_id" + ], + "description": "What the person does: `liveness`, `selfie` (liveness matched to the face Didit already holds) or `full_id` (document and selfie). When Didit holds no face for the person yet, `selfie` becomes `full_id` once they open `verification_url`.", + "example": "selfie" + }, + "status": { + "type": "string", + "enum": [ + "pending", + "review", + "approved", + "declined", + "expired", + "superseded" + ], + "description": "Act only on `approved`. `review` waits for your team's review. `declined` is not final while `verification_url` works: the person can try again, and the step-up returns to `pending`. `superseded` means another check took over its verification, or the result was not for this step-up: create a new one.", + "example": "pending" + }, + "level": { + "type": "string", + "nullable": true, + "enum": [ + "liveness", + "liveness_face_match", + "full_id", + null + ], + "description": "The level the verification reached, once approved.", + "example": null + }, + "amount": { + "type": "string", + "nullable": true, + "description": "The payout amount, two decimals.", + "example": "900.00" + }, + "currency": { + "type": "string", + "nullable": true, + "example": "USD" + }, + "created_at": { + "type": "string", + "format": "date-time", + "example": "2026-10-17T10:41:14.894632Z" + }, + "completed_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "example": null + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "30 minutes after creation. Reading a step-up first collects any result its verification reached, so it can still read `approved`, `review` or `declined` after this time; one still `pending` reads `expired`.", + "example": "2026-10-17T11:11:14.894581Z" + } + } + }, + "StepUpRequired": { + "description": "A new step-up. Send the person to `verification_url`.", + "allOf": [ + { + "$ref": "#/components/schemas/StepUp" + }, + { + "type": "object", + "required": [ + "required", + "verification_url", + "expires_in" + ], + "properties": { + "required": { + "type": "boolean", + "enum": [ + true + ] + }, + "verification_url": { + "type": "string", + "format": "uri", + "description": "The hosted page where the person verifies; open it in the person's browser. It works until the step-up is approved or expires, so a person who was declined can try again from it.", + "example": "https://your-app.auth.didit.me/verify/?verification=1nSX7HJCXAP9XPA9AeJfQxIuPk2GkG0dGSOzCA2Zpww" + }, + "expires_in": { + "type": "integer", + "description": "Seconds the step-up stays open: 1800.", + "example": 1800 + } + } + } + ] + }, + "MonitoringTokenRequest": { + "type": "object", + "description": "Empty: the access token names the person.", + "additionalProperties": false, + "properties": {} + }, + "MonitoringToken": { + "type": "object", + "description": "A short-lived device credential for sending the person's activity events to transaction monitoring from their device. It is not tied to the session: it stays usable until `expires_at`, even after sign-out.", + "required": [ + "monitoring_token", + "expires_at", + "allowed_event_categories", + "max_uses", + "bind_to_origin", + "vendor_data" + ], + "properties": { + "monitoring_token": { + "type": "string", + "description": "The device credential. Treat it as a secret.", + "example": "mt_2f8c1d9e7b6a5f4e3d2c1b0a" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "At most 15 minutes after it was issued.", + "example": "2026-10-17T10:56:14Z" + }, + "allowed_event_categories": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The event categories it may send. Authentication and security events always come from Didit, never from the device.", + "example": [ + "session", + "gameplay", + "bonus", + "affiliate", + "payment", + "dispute", + "responsible_gambling", + "custom" + ] + }, + "max_uses": { + "type": "integer", + "example": 1000 + }, + "bind_to_origin": { + "type": "string", + "nullable": true, + "description": "The browser origin the credential works from: the request's `Origin`, or `null` when there was none.", + "example": "https://app.example.com" + }, + "vendor_data": { + "type": "string", + "description": "The person every event is recorded for, whatever the device sends.", + "example": "user-42" + } + } + }, + "OAuthError": { + "title": "OAuth error", + "type": "object", + "description": "An OAuth error (RFC 6749 section 5.2) or a service error.", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string", + "example": "invalid_grant" + } + } + }, + "FieldErrors": { + "title": "Field errors", + "type": "object", + "description": "Validation errors by field. A value is a list of messages, or a single code (`unknown_field`, `invalid_redirect_uri`). `non_field_errors` holds rules across fields.", + "additionalProperties": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "Detail": { + "type": "object", + "required": [ + "detail" + ], + "properties": { + "detail": { + "type": "string", + "example": "invalid_token" + } + } + } + } + } +} diff --git a/pyproject.toml b/pyproject.toml index 0bc7226..858ae79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,6 +77,8 @@ include = [ "/LICENSE", "/CHANGELOG.md", ] +# Pinned copies of earlier upstream documents (23 MB) for the spec sync tests in the repository. +exclude = ["/codegen/tests/fixtures"] [tool.pytest.ini_options] testpaths = ["tests", "codegen/tests"] diff --git a/release-please-config.json b/release-please-config.json index 7d082e6..98f9697 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -8,6 +8,21 @@ "bump-minor-pre-major": true, "include-component-in-tag": false, "include-v-in-tag": true, + "changelog-sections": [ + { "type": "feat", "section": "Features" }, + { "type": "api", "section": "API changes that can break typed code" }, + { "type": "fix", "section": "Bug Fixes" }, + { "type": "perf", "section": "Performance Improvements" }, + { "type": "deps", "section": "Dependencies" }, + { "type": "revert", "section": "Reverts" }, + { "type": "docs", "section": "Documentation" }, + { "type": "style", "section": "Styles", "hidden": true }, + { "type": "chore", "section": "Miscellaneous Chores", "hidden": true }, + { "type": "refactor", "section": "Code Refactoring", "hidden": true }, + { "type": "test", "section": "Tests", "hidden": true }, + { "type": "build", "section": "Build System", "hidden": true }, + { "type": "ci", "section": "Continuous Integration", "hidden": true } + ], "extra-files": [ "src/didit/_version.py", { diff --git a/tests/test_authentication_contract.py b/tests/test_authentication_contract.py new file mode 100644 index 0000000..190ffb1 --- /dev/null +++ b/tests/test_authentication_contract.py @@ -0,0 +1,224 @@ +"""Contract test (DESIGN 11) for the hand-written Authentication module (DESIGN 10). + +``openapi/authentication.json`` is synced from docs.didit.me like the other documents, +but nothing is generated from it. Every ``server: true`` operation in it (its +``x-didit-sdk`` method, or each of its variants) must be a method of both clients, and +each one is sent to a stub issuer (tests/authentication_helpers.py) to check its HTTP +method, path, content type and grant against the document. A spec sync that adds, +removes or renames an Authentication operation fails here until the module follows. +""" + +from __future__ import annotations + +import asyncio +import inspect +import json +import pathlib +import re +import urllib.parse +from collections.abc import Callable +from typing import Any + +import httpx +import pytest + +from didit.authentication import AsyncAuthentication, Authentication +from tests.authentication_helpers import ( + ACCESS_TOKEN, + CODE, + CODE_VERIFIER, + ISSUER, + REDIRECT_URI, + REFRESH_TOKEN, + STEP_UP_ID, + FakeClock, + FakeIssuer, + SigningKey, + access_claims, +) +from tests.test_authentication import ( + SESSION_RESPONSE, + STEP_UP_REQUIRED, + TOKEN_RESPONSE, + configure, +) + +DOCUMENT = json.loads( + (pathlib.Path(__file__).resolve().parents[1] / "openapi" / "authentication.json").read_text( + "utf-8" + ) +) +HTTP_METHODS = ("get", "put", "post", "delete", "patch") +# Public methods that are not API operations. +HELPERS = {"close", "create_nonce", "create_pkce", "create_state", "from_publishable_key"} +# Token verification fetches the discovery document and the keys itself (DESIGN 10): +# these operations have no method of their own, the verify methods stand for them. +FETCHED_BY_VERIFICATION = {"get_openid_configuration", "get_jwks"} +VERIFIERS = {"verify_access_token", "verify_id_token"} +# What the stub issuer answers per operation (the body of its first 2xx response). +REPLIES: dict[str, object] = { + "exchange_code": TOKEN_RESPONSE, + "refresh": TOKEN_RESPONSE, + "get_session": SESSION_RESPONSE, + "create_step_up": STEP_UP_REQUIRED, + "retrieve_step_up": STEP_UP_REQUIRED, +} + + +def _schema(op: dict[str, Any]) -> dict[str, Any]: + """The request body schema of ``op`` (its first media type), references followed.""" + for media in ((op.get("requestBody") or {}).get("content") or {}).values(): + schema: dict[str, Any] = media.get("schema") or {} + while "$ref" in schema: + target: Any = DOCUMENT + for part in schema["$ref"].removeprefix("#/").split("/"): + target = target[part] + schema = target + return schema + return {} + + +def _operations() -> dict[str, dict[str, Any]]: + """Document method -> HTTP method, path pattern, request content types and grant.""" + found: dict[str, dict[str, Any]] = {} + for path, item in DOCUMENT["paths"].items(): + for http_method in HTTP_METHODS: + op = item.get(http_method) + if op is None: + continue + naming = op["x-didit-sdk"] + assert naming["resource"] == "authentication", (http_method, path) + if not naming["server"]: + continue + content = sorted((op.get("requestBody") or {}).get("content") or {}) + discriminator = (_schema(op).get("discriminator") or {}).get("propertyName") + successes = sorted(s for s in op["responses"] if s.startswith("2")) + for variant in naming.get("variants") or [{"method": naming["method"]}]: + assert variant["method"] not in found, variant + found[variant["method"]] = { + "http": http_method.upper(), + "path": re.compile("^" + re.sub(r"\{[^}]+\}", "[^/]+", path) + "$"), + "raw_path": path, + "content": content, + "grant": (discriminator, variant.get("discriminator")), + "status": int(successes[0]), + } + return found + + +OPERATIONS = _operations() + + +def _hidden() -> set[str]: + return { + op["x-didit-sdk"]["method"] + for item in DOCUMENT["paths"].values() + for http_method in HTTP_METHODS + if (op := item.get(http_method)) is not None and not op["x-didit-sdk"]["server"] + } + + +def _public(cls: type) -> set[str]: + return {n for n, value in inspect.getmembers(cls) if not n.startswith("_") and callable(value)} + + +@pytest.mark.parametrize("cls", [Authentication, AsyncAuthentication]) +def test_document_operations_equal_module_methods(cls: type) -> None: + documented = set(OPERATIONS) + expected = documented - FETCHED_BY_VERIFICATION + if documented >= FETCHED_BY_VERIFICATION: + expected |= VERIFIERS + methods = _public(cls) - HELPERS + assert not expected - methods, f"operations without a method: {sorted(expected - methods)}" + assert not methods - expected, f"methods without an operation: {sorted(methods - expected)}" + # Device and end-user token operations are never in a server SDK (DESIGN 4). + assert _hidden() + assert not _hidden() & _public(cls) + + +def _calls(key: SigningKey) -> dict[str, Callable[[Any], Any]]: + """How the module performs each document operation.""" + token = key.sign(access_claims()) + return { + "get_openid_configuration": lambda auth: auth.verify_access_token(token), + "get_jwks": lambda auth: auth.verify_access_token(token), + "exchange_code": lambda auth: auth.exchange_code( + CODE, code_verifier=CODE_VERIFIER, redirect_uri=REDIRECT_URI + ), + "refresh": lambda auth: auth.refresh(REFRESH_TOKEN), + "get_session": lambda auth: auth.get_session(ACCESS_TOKEN), + "revoke_session": lambda auth: auth.revoke_session(ACCESS_TOKEN), + "create_step_up": lambda auth: auth.create_step_up( + ACCESS_TOKEN, action="payout", amount="1500.00", currency="EUR", return_to=REDIRECT_URI + ), + "retrieve_step_up": lambda auth: auth.retrieve_step_up(ACCESS_TOKEN, STEP_UP_ID), + } + + +def _body(request: httpx.Request) -> dict[str, Any]: + media = request.headers.get("content-type", "").split(";")[0] + if media == "application/x-www-form-urlencoded": + return dict(urllib.parse.parse_qsl(request.content.decode("ascii"))) + body: dict[str, Any] = json.loads(request.content) + return body + + +def _sent(issuer: FakeIssuer, method: str) -> httpx.Request: + """The request that performed the document operation ``method``.""" + operation = OPERATIONS[method] + matching = [ + r + for r in issuer.requests + if r.method == operation["http"] and operation["path"].match(r.url.path) + ] + assert matching, f"{method}: no {operation['http']} {operation['raw_path']} was sent" + request = matching[-1] + assert str(request.url).startswith(f"{ISSUER}/") + if operation["content"]: + assert request.headers["content-type"].split(";")[0] in operation["content"], method + prop, value = operation["grant"] + if prop is not None: + assert _body(request)[prop] == value, method + return request + + +@pytest.fixture +def key() -> SigningKey: + return SigningKey.generate("k_synthetic_contract") + + +@pytest.fixture +def issuer(key: SigningKey) -> FakeIssuer: + issuer = FakeIssuer([key]) + for method, operation in OPERATIONS.items(): + if method not in FETCHED_BY_VERIFICATION: + issuer.reply( + operation["http"], + operation["raw_path"].replace("{step_up_id}", STEP_UP_ID), + operation["status"], + REPLIES.get(method), + ) + return issuer + + +@pytest.mark.parametrize("method", sorted(OPERATIONS)) +def test_each_operation_is_sent_as_documented( + method: str, issuer: FakeIssuer, key: SigningKey +) -> None: + with issuer.client() as http: + auth = configure(Authentication(ISSUER, http_client=http), FakeClock()) + _calls(key)[method](auth) + _sent(issuer, method) + + +@pytest.mark.parametrize("method", sorted(OPERATIONS)) +def test_each_operation_is_sent_as_documented_async( + method: str, issuer: FakeIssuer, key: SigningKey +) -> None: + async def run() -> None: + async with issuer.async_client() as http: + auth = configure(AsyncAuthentication(ISSUER, http_client=http), FakeClock()) + await _calls(key)[method](auth) + + asyncio.run(run()) + _sent(issuer, method) From 142f89d2567f68bf2a6d3780e97e8c38591916ba Mon Sep 17 00:00:00 2001 From: Alejandro Rosas Date: Fri, 6 Nov 2026 12:52:21 +0000 Subject: [PATCH 2/4] DID-10140: [VER-PY] sdk-python: API changes release as minor versions, the shared breaking-change detection, Authentication spec synced --- .gitattributes | 4 + .github/workflows/spec-sync.yml | 13 +++ .gitleaks.toml | 7 ++ CONTRIBUTING.md | 2 +- codegen/spec_sync.py | 140 +++++++++++++++++++++++++------- codegen/tests/test_spec_sync.py | 74 +++++++++++++++-- 6 files changed, 203 insertions(+), 37 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..218befc --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# The upstream copies and the pinned fixtures are compared by SHA-256: check them out byte +# for byte, without line-ending conversion (Windows runners convert text to CRLF). +/openapi/*.json -text +/codegen/tests/fixtures/*.json -text diff --git a/.github/workflows/spec-sync.yml b/.github/workflows/spec-sync.yml index d9987e5..06432ac 100644 --- a/.github/workflows/spec-sync.yml +++ b/.github/workflows/spec-sync.yml @@ -156,6 +156,19 @@ jobs: # A pull request description holds 65536 characters: shorten the report, never the # checks or the block release-please reads. The full report is in the sync step's log. touch "$CHECKS" + # Hundreds of breaking changes do not fit at all. Then the description leaves the + # block out and release-please reads the squashed commit, whose message (the sync + # commit's) holds every line. + if [ "$(($(wc -c < "$override") + $(wc -c < "$CHECKS")))" -gt 60000 ]; then + count="$(grep -c '^BEGIN_NESTED_COMMIT$' "$COMMIT_MESSAGE")" + { + echo + echo "## Release notes" + echo + echo "The ${count} lines for \"API changes that can break typed code\" do not fit this description, so it has no override block and release-please reads the merged commit instead. Merge with **Squash and merge** and use the message of this branch's sync commit as the commit message: it holds every line, each as its own nested commit." + } > "$override" + echo "::warning::${count} breaking changes do not fit the pull request description; squash with the sync commit's message so that the release notes list them." + fi limit=$((65000 - $(wc -c < "$override") - $(wc -c < "$CHECKS"))) if [ "$(wc -c < "$SUMMARY")" -gt "$limit" ]; then { diff --git a/.gitleaks.toml b/.gitleaks.toml index b07ea4d..1efa822 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -20,6 +20,13 @@ paths = [ '''^codegen/tests/fixtures/verification-[0-9a-f]{40}\.json$''', ] +[[allowlists]] +# Exact values only: a path in a global allowlist makes gitleaks skip the whole file. +description = "The public sdk-python commits those fixtures come from, named in the classification tests" +regexes = [ + '''^(9fe57fc69299601e59cc7a64c1db370fea5c7461|e002a815773bbd1599256e2774046ee76520c6c8|fe9f57a1dfb6374bfd391750d2044fc7a206e4ea|ccc799410f82eee1c25c7ce5abc6caa53846be6b)$''', +] + [[allowlists]] description = "Synthetic webhook secrets used by the test suite" regexes = [ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c74cf90..db51488 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,7 +34,7 @@ CI runs `uv run python codegen/generate.py --check`, which regenerates into a te The spec sync runs daily. When upstream changed, it regenerates the code, runs the checks and opens a pull request titled `(spec): sync API spec YYYY-MM-DD`. The type is `feat(spec)` when a caller can observe the change (a minor release, even when it can break typed code) and `chore(spec)` when only descriptions, examples or other documentation changed (no release on its own); never `!` or a `BREAKING CHANGE:` footer (DESIGN sections 3.1 and 13). A download that is not an OpenAPI 3 document with paths is refused and nothing is written. -The body opens with "Breaking changes", or "None detected": every field or parameter whose type changed or that can now be null, removed operation, field or enum value, and newly required parameter or request field, in parameters, request bodies and 2xx responses, following `$ref`s and array items. Then it lists added, removed, renamed and changed operations and schemas. It ends with a `BEGIN_COMMIT_OVERRIDE` block that release-please reads instead of the merged commit: the sync line plus one nested `api(spec):` commit per breaking change, which `release-please-config.json` lists under "API changes that can break typed code". Merge sync pull requests with **Squash and merge** and keep that block: after a merge commit release-please reads it once per commit and every line appears twice. +The body opens with "Breaking changes", or "None detected": every field or parameter whose type changed or that can now be null, removed operation, field or enum value, and newly required parameter or request field, in parameters, request bodies and 2xx responses, following `$ref`s and array items. Then it lists added, removed, renamed and changed operations and schemas. It ends with a `BEGIN_COMMIT_OVERRIDE` block that release-please reads instead of the merged commit: the sync line plus one nested `api(spec):` commit per breaking change, which `release-please-config.json` lists under "API changes that can break typed code". Merge sync pull requests with **Squash and merge** and keep that block: after a merge commit release-please reads it once per commit and every line appears twice. When the lines do not fit in a pull request description (hundreds of breaking changes), the description says so and leaves the block out; then squash with the sync commit's message, which holds every line. `openapi/authentication.json` is synced like the others, but the `didit.authentication` module stays hand-written (DESIGN section 10). When a sync adds, removes or renames one of its server operations, the body lists it under "Authentication module" and `tests/test_authentication_contract.py` fails until the module follows on the sync branch. diff --git a/codegen/spec_sync.py b/codegen/spec_sync.py index 4ff7f36..a9a82d1 100644 --- a/codegen/spec_sync.py +++ b/codegen/spec_sync.py @@ -90,10 +90,11 @@ # The extension a caller can observe (the webhook events). Every other x- key documents; # x-didit-sdk is compared as the effective naming, together with overlay.json. OBSERVABLE_EXTENSIONS = frozenset({"x-webhooks"}) +# String formats with their own type in the generated code (datetime, date, bytes or a +# file): adding, removing or changing one changes the field's type. +TYPED_FORMATS = frozenset({"date-time", "date", "binary"}) MAX_NOTES = 25 MAX_DEPTH = 32 -# Lines a sync adds to the release notes; the pull request lists every breaking change. -MAX_RELEASE_NOTES = 100 NO_DIFFERENCE = "descriptions, examples or other details changed" Json = Any @@ -327,6 +328,15 @@ def _kinds(schema: Json, depth: int = 0) -> frozenset[str]: return frozenset(kinds) +def _type_names(schema: Json, kinds: frozenset[str]) -> str: + """``kinds`` as the summary names them; a string with a typed format is its own type.""" + fmt = schema.get("format") + return " or ".join( + f"string ({fmt})" if kind == "string" and fmt in TYPED_FORMATS else kind + for kind in sorted(kinds) + ) + + def _enum(schema: Json) -> list[Json] | None: values = schema.get("enum") return [v for v in values if v is not None] if isinstance(values, list) else None @@ -381,16 +391,13 @@ def note(kind: str, breaking: bool = False, at: str = path) -> None: note("can now be null", True) elif _nullable(old) and not _nullable(new): note("no longer accepts null" if request else "can no longer be null") - # A plain schema is a union of one variant: [X, null] against X loses no variant. - sizes = { - k: (len(_variants(old, k)) or 1, len(_variants(new, k)) or 1) for k in ("oneOf", "anyOf") - } - raw_old, raw_new = old, new old, new = _single(old), _single(new) old_kinds, new_kinds = _kinds(old), _kinds(new) - if old_kinds and new_kinds and old_kinds != new_kinds: - before, after = " or ".join(sorted(old_kinds)), " or ".join(sorted(new_kinds)) + before, after = _type_names(old, old_kinds), _type_names(new, new_kinds) + if old_kinds and new_kinds and before != after: note(f"type changed from {before} to {after}", True) + # A union's variants can change too; the type change already covers those removed. + _diff_variants(old, new, where, path, request=request, out=out, depth=depth, retyped=True) return old_enum, new_enum = _enum(old), _enum(new) @@ -433,23 +440,99 @@ def note(kind: str, breaking: bool = False, at: str = path) -> None: new_sub if isinstance(new_sub, dict) else {}, where, path + suffix, request=request, out=out, depth=depth + 1, ) # fmt: skip - for keyword, (old_size, new_size) in sizes.items(): - if new_size < old_size: - note(f"type changed: {old_size - new_size} {keyword} variant(s) removed", True) - elif new_size > old_size: - note(f"{new_size - old_size} {keyword} variant(s) added") - elif old_size > 1: - # One variant each was compared as the schema itself, above. - old_vars, new_vars = _variants(raw_old, keyword), _variants(raw_new, keyword) - for index, (a, b) in enumerate(zip(old_vars, new_vars, strict=True)): - diff_schema( - a, b, where, f"{path}({keyword} {index + 1})", - request=request, out=out, depth=depth + 1, - ) # fmt: skip + _diff_variants(old, new, where, path, request=request, out=out, depth=depth, retyped=False) if len(out) == start: note("other constraints changed (format, pattern, limits or default)") +def _union_keyword(schema: Json) -> str | None: + return next((k for k in ("oneOf", "anyOf") if _variants(schema, k)), None) + + +def _compatible(old: Json, new: Json) -> bool: + old_kinds, new_kinds = _kinds(old), _kinds(new) + return not old_kinds or not new_kinds or old_kinds == new_kinds + + +def _field_names(schema: Json) -> set[str]: + return set(_object_props(_single(schema))[0]) if isinstance(schema, dict) else set() + + +def _pair_variants( + old: list[Json], new: list[Json] +) -> tuple[list[tuple[int, int]], list[int], list[int]]: + """Pair each old union variant with the new one it became: an unchanged one first, + then one with the same title, then one of the same type with the most fields in common. + Returns the pairs (old and new position) and the positions left over on each side + (variants removed and added).""" + left_old, left_new = list(range(len(old))), list(range(len(new))) + pairs: list[tuple[int, int]] = [] + + def pair(i: int, j: int) -> None: + pairs.append((i, j)) + left_old.remove(i) + left_new.remove(j) + + def title(schema: Json) -> Json: + return schema.get("title") if isinstance(schema, dict) else None + + old_shapes, new_shapes = [_shape(v) for v in old], [_shape(v) for v in new] + + def same_shape(i: int, j: int) -> bool: + return bool(old_shapes[i] == new_shapes[j]) + + def same_title(i: int, j: int) -> bool: + return title(old[i]) is not None and title(old[i]) == title(new[j]) + + for same in (same_shape, same_title): + for i in list(left_old): + j = next((j for j in left_new if same(i, j)), None) + if j is not None: + pair(i, j) + candidates = sorted( + (-len(_field_names(old[i]) & _field_names(new[j])), i, j) + for i in left_old + for j in left_new + if _compatible(old[i], new[j]) + ) + for _, i, j in candidates: + if i in left_old and j in left_new: + pair(i, j) + return sorted(pairs, key=lambda p: p[1]), left_old, left_new + + +def _diff_variants( + old: Json, + new: Json, + where: str, + path: str, + *, + request: bool, + out: list[Change], + depth: int, + retyped: bool, +) -> None: + """Compare two unions variant by variant; a plain schema is a union of one, so that + ``[X, null]`` against X loses no variant and X against ``[X', Y]`` compares X with X'. + A variant added or removed never hides a change inside the others.""" + old_vars, new_vars = _variants(old) or [old], _variants(new) or [new] + if len(old_vars) == 1 and len(new_vars) == 1: + return # compared as the schema itself + keyword = _union_keyword(new) or _union_keyword(old) + pairs, removed, added = _pair_variants(old_vars, new_vars) + for i, j in pairs: + at = f"{path}({keyword} {j + 1})" if len(new_vars) > 1 else path + diff_schema(old_vars[i], new_vars[j], where, at, request=request, out=out, depth=depth + 1) + if removed and not retyped: + kind = f"type changed: {len(removed)} {keyword} variant(s) removed" + out.append(Change(where, path, kind, True)) + if added: + out.append(Change(where, path, f"{len(added)} {keyword} variant(s) added")) + order = [j for _, j in sorted(pairs)] + if order != sorted(order): + out.append(Change(where, path, f"{keyword} variants reordered")) + + def _parameter_where(key: tuple[str, str]) -> str: return f"parameter `{key[1]}` ({key[0]})" @@ -594,16 +677,13 @@ class SyncResult: def commit_message(self) -> str: """What release-please reads (the pull request's ``BEGIN_COMMIT_OVERRIDE`` block): - the sync, then one nested ``api(spec):`` commit per breaking change. A type - outside release-please's own list only splits off in a nested block.""" - lines = [f"api(spec): {c.describe()}" for c in self.breaking[:MAX_RELEASE_NOTES]] - if len(self.breaking) > MAX_RELEASE_NOTES: - more = len(self.breaking) - MAX_RELEASE_NOTES - lines.append(f"api(spec): {more} more, listed in the spec sync pull request") + the sync, then one nested ``api(spec):`` commit per breaking change, every one of + them. A type outside release-please's own list only splits off in a nested block.""" message = f"{self.commit_type}(spec): sync API spec {self.date}\n" - if lines: + if self.breaking: message += "\n" + "".join( - f"BEGIN_NESTED_COMMIT\n{line}\nEND_NESTED_COMMIT\n" for line in lines + f"BEGIN_NESTED_COMMIT\napi(spec): {c.describe()}\nEND_NESTED_COMMIT\n" + for c in self.breaking ) return message diff --git a/codegen/tests/test_spec_sync.py b/codegen/tests/test_spec_sync.py index 96124ef..7827aac 100644 --- a/codegen/tests/test_spec_sync.py +++ b/codegen/tests/test_spec_sync.py @@ -187,15 +187,13 @@ def test_release_please_config_keeps_chores_ci_and_tests_hidden() -> None: assert sections[kind]["hidden"] is True, kind -def test_release_notes_are_capped(openapi: pathlib.Path) -> None: +def test_release_notes_list_every_breaking_change() -> None: many = [ spec_sync.BreakingChange(f"GET /v3/thing-{i}/", "response", "a", "removed") - for i in range(spec_sync.MAX_RELEASE_NOTES + 3) + for i in range(503) ] message = spec_sync.SyncResult(True, "", "2026-11-06", "feat", many).commit_message() - nested = _nested(message) - assert len(nested) == spec_sync.MAX_RELEASE_NOTES + 1 - assert nested[-1] == "api(spec): 3 more, listed in the spec sync pull request" + assert _nested(message) == [f"api(spec): {b.describe()}" for b in many] # Detection rules (DESIGN 3.1) ------------------------------------------------------------- @@ -251,6 +249,70 @@ def diff(old: Any, new: Any) -> list[tuple[str, str, bool]]: ] +@pytest.mark.parametrize("request_side", [True, False], ids=["request", "response"]) +def test_a_string_format_with_its_own_type_is_a_type_change(request_side: bool) -> None: + # The generated code types these strings as datetime, date, and bytes or a file. + plain = {"type": "string"} + for fmt in ("date-time", "date", "binary"): + typed = {"type": "string", "format": fmt} + assert _diff(plain, typed, request=request_side) == [ + ("", f"type changed from string to string ({fmt})", True) + ] + assert _diff(typed, plain, request=request_side) == [ + ("", f"type changed from string ({fmt}) to string", True) + ] + date = {"type": "string", "format": "date"} + stamp = {"type": ["string", "null"], "format": "date-time"} + assert _diff(date, stamp, request=request_side) == [ + ("", NULL, True), + ("", "type changed from string (date) to string (date-time)", True), + ] + listed = {"type": "array", "items": plain} + assert _diff(listed, {**listed, "items": stamp}, request=request_side) == [ + ("[]", NULL, True), + ("[]", "type changed from string to string (date-time)", True), + ] + + +def test_union_variants_are_compared_with_the_variant_they_became() -> None: + a: dict[str, Any] = {"type": "object", "title": "A", "properties": {"kind": STATUS}} + b: dict[str, Any] = { + "type": "object", + "properties": {"code": {"type": "string"}, "score": {"type": "number"}}, + } + c: dict[str, Any] = {"type": "object", "properties": {"url": {"type": "string"}}} + b2 = {**b, "properties": {**b["properties"], "code": {"type": "integer"}}} + retyped = "type changed from string to integer" + added = ("", "1 oneOf variant(s) added", False) + # A variant added, wherever it goes, or removed hides no change in the others. + assert _diff({"oneOf": [a, b]}, {"oneOf": [a, b2, c]}) == [ + ("(oneOf 2).code", retyped, True), + added, + ] + assert _diff({"oneOf": [a, b]}, {"oneOf": [c, a, b2]}) == [ + ("(oneOf 3).code", retyped, True), + added, + ] + assert _diff({"oneOf": [a, b, c]}, {"oneOf": [a, b2]}) == [ + ("(oneOf 2).code", retyped, True), + ("", "type changed: 1 oneOf variant(s) removed", True), + ] + # A schema that becomes a union is compared with the variant it became. + assert _diff(b, {"oneOf": [b2, c]}) == [("(oneOf 1).code", retyped, True), added] + assert _diff({"anyOf": [b, c]}, b2) == [ + ("code", retyped, True), + ("", "type changed: 1 anyOf variant(s) removed", True), + ] + # A variant of a new type changes the union's type and still compares the others. + assert _diff({"oneOf": [a, b]}, {"oneOf": [a, b2, {"type": "string"}]}) == [ + ("", "type changed from object to object or string", True), + ("(oneOf 2).code", retyped, True), + added, + ] + # Variants in another order are the same union. + assert _diff({"oneOf": [a, b]}, {"oneOf": [b, a]}) == [("", "oneOf variants reordered", False)] + + def test_newly_required_request_fields_count_and_response_requiredness_does_not() -> None: optional = {**OBJ, "required": []} assert _diff(optional, OBJ, request=True) == [("a", "now required", True)] @@ -273,7 +335,7 @@ def test_listed_but_not_counted() -> None: assert _diff({"type": "string", "nullable": True}, {"type": "string"}, request=True) == [ ("", "no longer accepts null", False) ] - assert _diff({"type": "string"}, {"type": "string", "format": "date-time"}) == [ + assert _diff({"type": "string"}, {"type": "string", "format": "uuid"}) == [ ("", "other constraints changed (format, pattern, limits or default)", False) ] assert _diff({"oneOf": [OBJ]}, {"oneOf": [OBJ, {**OBJ, "title": "B", "properties": {}}]}) == [ From 014cbc7f52c3bfd80bc8b9ff47623c705150a4c9 Mon Sep 17 00:00:00 2001 From: Alejandro Rosas Date: Fri, 6 Nov 2026 12:56:18 +0000 Subject: [PATCH 3/4] DID-10140: [VER-PY] sdk-python: API changes release as minor versions, the shared breaking-change detection, Authentication spec synced --- codegen/spec_sync.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/codegen/spec_sync.py b/codegen/spec_sync.py index a9a82d1..0fdc772 100644 --- a/codegen/spec_sync.py +++ b/codegen/spec_sync.py @@ -1040,9 +1040,14 @@ def removed_operation(key: str, detail: str) -> None: "fetched_at": now.strftime("%Y-%m-%dT%H:%M:%SZ"), } # Same layout as the seeded files (one-space indent, no trailing newline), so diffs stay small. - source_path.write_text(json.dumps(source, indent=1, ensure_ascii=False), "utf-8") - overlay_path.write_text(json.dumps(overlay, indent=1, ensure_ascii=False), "utf-8") - deprecated_path.write_text(json.dumps(deprecated, indent=1, ensure_ascii=False) + "\n", "utf-8") + # LF on every platform: .gitattributes checks openapi/*.json out byte for byte. + source_path.write_text(json.dumps(source, indent=1, ensure_ascii=False), "utf-8", newline="\n") + overlay_path.write_text( + json.dumps(overlay, indent=1, ensure_ascii=False), "utf-8", newline="\n" + ) + deprecated_path.write_text( + json.dumps(deprecated, indent=1, ensure_ascii=False) + "\n", "utf-8", newline="\n" + ) summary = "\n".join(lines).rstrip() + "\n" return SyncResult(True, summary, today.isoformat(), commit_type, breaking) From 24dffae1d45740e53cbc82ba9770cf52b6b875d7 Mon Sep 17 00:00:00 2001 From: Alejandro Rosas Date: Fri, 6 Nov 2026 13:21:07 +0000 Subject: [PATCH 4/4] DID-10140: [VER-PY] sdk-python: API changes release as minor versions, the shared breaking-change detection, Authentication spec synced --- codegen/spec_sync.py | 118 +++++++++++++++++++++++++++----- codegen/tests/test_spec_sync.py | 101 +++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 18 deletions(-) diff --git a/codegen/spec_sync.py b/codegen/spec_sync.py index 0fdc772..057a389 100644 --- a/codegen/spec_sync.py +++ b/codegen/spec_sync.py @@ -50,6 +50,8 @@ import urllib.request from typing import Any +from didit_codegen.naming import pascal + REPO = pathlib.Path(__file__).resolve().parents[1] UPSTREAM = { "verification.json": "https://docs.didit.me/openapi-25.json", @@ -97,6 +99,9 @@ MAX_DEPTH = 32 NO_DIFFERENCE = "descriptions, examples or other details changed" Json = Any +# Where a request body or response has no content at all: nothing to compare with, unlike +# ``{}``, which allows any value. Compared by identity. +NO_SCHEMA: dict[str, Json] = {} class SpecSyncError(Exception): @@ -137,8 +142,12 @@ def _pointer(doc: Json, ref: str) -> Json: return cur -def dereference(doc: Json, node: Json, depth: int = 0) -> Json: - """``node`` with every local ``$ref`` inlined (self-contained for deprecated operations).""" +def dereference(doc: Json, node: Json, depth: int = 0, *, ref_titles: bool = True) -> Json: + """``node`` with every local ``$ref`` inlined (self-contained for deprecated operations). + + ``ref_titles=False`` drops the title of every referenced schema: the generated code + names it after its component, so only an inline union variant's title names a type. + """ if depth > 64: return {} if isinstance(node, dict): @@ -148,12 +157,17 @@ def dereference(doc: Json, node: Json, depth: int = 0) -> Json: except (KeyError, IndexError, TypeError): # A dangling reference upstream: keep it visible; the generator reports it. return {"x-unresolved-ref": node["$ref"]} - target = dereference(doc, found, depth + 1) - extra = {k: v for k, v in node.items() if k != "$ref"} - return {**target, **extra} if isinstance(target, dict) else target - return {k: dereference(doc, v, depth + 1) for k, v in node.items()} + target = dereference(doc, found, depth + 1, ref_titles=ref_titles) + dropped = ("$ref",) if ref_titles else ("$ref", "title") + extra = {k: v for k, v in node.items() if k not in dropped} + if not isinstance(target, dict): + return target + if not ref_titles: + target = {k: v for k, v in target.items() if k != "title"} + return {**target, **extra} + return {k: dereference(doc, v, depth + 1, ref_titles=ref_titles) for k, v in node.items()} if isinstance(node, list): - return [dereference(doc, v, depth + 1) for v in node] + return [dereference(doc, v, depth + 1, ref_titles=ref_titles) for v in node] return node @@ -172,21 +186,38 @@ def operations(doc: Json, prefix: str) -> dict[str, Json]: # What a caller can observe ---------------------------------------------------------------- -def _shape(node: Json, names: bool = False) -> Json: +def _variant_type_name(schema: Json) -> str | None: + """What an inline oneOf/anyOf variant's title adds to its generated type name + (``typegen._variant_name``); None without one, or for a ``$ref`` (named after its + component).""" + if not isinstance(schema, dict) or "$ref" in schema: + return None + title = schema.get("title") + return pascal(title) if isinstance(title, str) and title.strip() else None + + +def _shape(node: Json, names: bool = False, variant: bool = False) -> Json: """``node`` without documentation, so that only what is sent or received compares. ``names`` marks a map whose keys are names (properties, paths, status codes...), - which all stay. ``required`` and ``type`` lists compare in any order. + which all stay. ``required`` and ``type`` lists compare in any order. ``variant`` + marks a oneOf/anyOf variant, whose title names its generated type, so it stays. """ if isinstance(node, dict): out: dict[str, Json] = {} for k, v in node.items(): + if variant and k == "title": + if (name := _variant_type_name(node)) is not None: + out[k] = name + continue if not names and ( k in DOC_KEYS or (str(k).startswith("x-") and k not in OBSERVABLE_EXTENSIONS) ): continue if not names and k in ("required", "type") and isinstance(v, list): out[k] = sorted(v, key=str) + elif not names and k in ("oneOf", "anyOf") and isinstance(v, list): + out[k] = [_shape(member, variant=True) for member in v] else: out[k] = _shape(v, not names and k in NAME_MAPS) return out @@ -329,7 +360,10 @@ def _kinds(schema: Json, depth: int = 0) -> frozenset[str]: def _type_names(schema: Json, kinds: frozenset[str]) -> str: - """``kinds`` as the summary names them; a string with a typed format is its own type.""" + """``kinds`` as the summary names them; a string with a typed format is its own type, + and a schema that does not say allows any value.""" + if not kinds: + return "any" fmt = schema.get("format") return " or ".join( f"string ({fmt})" if kind == "string" and fmt in TYPED_FORMATS else kind @@ -380,6 +414,8 @@ def diff_schema( if depth > MAX_DEPTH or _shape(old) == _shape(new): return start = len(out) + # A body that appears or disappears is compared field by field, not as a type change. + present = old is not NO_SCHEMA and new is not NO_SCHEMA def note(kind: str, breaking: bool = False, at: str = path) -> None: out.append(Change(where, at, kind, breaking)) @@ -394,10 +430,14 @@ def note(kind: str, breaking: bool = False, at: str = path) -> None: old, new = _single(old), _single(new) old_kinds, new_kinds = _kinds(old), _kinds(new) before, after = _type_names(old, old_kinds), _type_names(new, new_kinds) - if old_kinds and new_kinds and before != after: + # "any" (a schema that does not say, typed ``object``) against a type counts too. + if present and (old_kinds or new_kinds) and before != after: note(f"type changed from {before} to {after}", True) - # A union's variants can change too; the type change already covers those removed. - _diff_variants(old, new, where, path, request=request, out=out, depth=depth, retyped=True) + if old_kinds and new_kinds: + # A union's variants can change too; the type change already covers those removed. + _diff_variants( + old, new, where, path, request=request, out=out, depth=depth, retyped=True + ) return old_enum, new_enum = _enum(old), _enum(new) @@ -523,6 +563,12 @@ def _diff_variants( for i, j in pairs: at = f"{path}({keyword} {j + 1})" if len(new_vars) > 1 else path diff_schema(old_vars[i], new_vars[j], where, at, request=request, out=out, depth=depth + 1) + before, after = ( + _variant_type_name(v) or "a numbered name" for v in (old_vars[i], new_vars[j]) + ) + if before != after: + # The variant's title names its generated type, which gets another name. + out.append(Change(where, at, f"variant type renamed from {before} to {after}")) if removed and not retyped: kind = f"type changed: {len(removed)} {keyword} variant(s) removed" out.append(Change(where, path, kind, True)) @@ -539,7 +585,7 @@ def _parameter_where(key: tuple[str, str]) -> str: def _parameters(doc: Json, op: Json) -> dict[tuple[str, str], Json]: found: dict[tuple[str, str], Json] = {} - for parameter in dereference(doc, op.get("parameters") or []): + for parameter in dereference(doc, op.get("parameters") or [], ref_titles=False): if isinstance(parameter, dict): found[(str(parameter.get("in", "")), str(parameter.get("name", "")))] = parameter return found @@ -547,7 +593,7 @@ def _parameters(doc: Json, op: Json) -> dict[tuple[str, str], Json]: def _content(doc: Json, node: Json) -> dict[str, Json]: """The schema of each media type of a request body or response.""" - node = dereference(doc, node) + node = dereference(doc, node, ref_titles=False) content = node.get("content") if isinstance(node, dict) else None if not isinstance(content, dict): return {} @@ -578,13 +624,47 @@ def _diff_content( ) pairs = [(m, m) for m in sorted(set(old) & set(new))] if not pairs and (old or new): - old = old or {"": {}} - new = new or {"": {}} + old = old or {"": NO_SCHEMA} + new = new or {"": NO_SCHEMA} pairs = [(min(old), min(new))] for before, after in pairs: diff_schema(old[before], new[after], where, request=request, out=out) +def _diff_statuses( + old: dict[str, dict[str, Json]], new: dict[str, dict[str, Json]] +) -> list[Change]: + """The success statuses only one side has. The method returns a union of every 2xx + body, nullable when one has none: a status without a body makes it nullable, and a + removed status whose body no other one returns removes a variant of it (DESIGN 3.1).""" + found: list[Change] = [] + + def bodies(responses: dict[str, dict[str, Json]]) -> list[Json]: + return [_shape(content, names=True) for content in responses.values() if content] + + def returns_null(responses: dict[str, dict[str, Json]]) -> bool: + return any(not content for content in responses.values()) + + for status in sorted(set(new) - set(old)): + if not new[status] and bodies(old) and not returns_null(old): + found.append( + Change("response", "", f"can now be null (status {status} has no body)", True) + ) + elif new[status] and _shape(new[status], names=True) not in bodies(old): + found.append(Change("response", "", f"status {status} added with another body")) + else: + found.append(Change("response", "", f"status {status} added")) + for status in sorted(set(old) - set(new)): + if not old[status] and bodies(new) and not returns_null(new): + found.append(Change("response", "", f"can no longer be null (status {status} removed)")) + elif old[status] and _shape(old[status], names=True) not in bodies(new): + kind = f"type changed: status {status} removed, and no other status returns its body" + found.append(Change("response", "", kind, True)) + else: + found.append(Change("response", "", f"status {status} removed")) + return found + + def describe_change(old_doc: Json, old: Json, new_doc: Json, new: Json) -> list[Change]: """Every difference of one operation (or of a method that moved to another one).""" found: list[Change] = [] @@ -616,12 +696,14 @@ def describe_change(old_doc: Json, old: Json, new_doc: Json, new: Json) -> list[ ) # fmt: skip # Every 2xx response both have; when the only success status changed (200 to 201), - # the old body against the new one. + # the old body against the new one; otherwise the statuses only one side has. old_responses, new_responses = _successes(old_doc, old), _successes(new_doc, new) statuses = [(s, s) for s in old_responses if s in new_responses] if not statuses and len(old_responses) == 1 and len(new_responses) == 1: statuses = [(next(iter(old_responses)), next(iter(new_responses)))] found.append(Change("response", "", f"status {statuses[0][0]} -> {statuses[0][1]}")) + else: + found.extend(_diff_statuses(old_responses, new_responses)) for before, after in statuses: _diff_content( old_responses[before], new_responses[after], "response", request=False, out=found diff --git a/codegen/tests/test_spec_sync.py b/codegen/tests/test_spec_sync.py index 7827aac..33735d1 100644 --- a/codegen/tests/test_spec_sync.py +++ b/codegen/tests/test_spec_sync.py @@ -274,6 +274,26 @@ def test_a_string_format_with_its_own_type_is_a_type_change(request_side: bool) ] +@pytest.mark.parametrize("request_side", [True, False], ids=["request", "response"]) +def test_a_schema_that_allows_any_value_is_a_type_change(request_side: bool) -> None: + # Without a type the generated code types a value as ``object``. + uuid = {"type": "string", "format": "uuid"} + assert _diff(uuid, {"format": "uuid"}, request=request_side) == [ + ("", "type changed from string to any", True) + ] + assert _diff({}, {"type": "integer"}, request=request_side) == [ + ("", "type changed from any to integer", True) + ] + listed = {"type": "array", "items": uuid} + assert _diff(listed, {"type": "array"}, request=request_side) == [ + ("[]", "type changed from string to any", True) + ] + either = {"oneOf": [{"type": "string"}, {"type": "integer"}]} + assert _diff(either, {"oneOf": [{"type": "string"}, {}]}, request=request_side) == [ + ("", "type changed from integer or string to any", True) + ] + + def test_union_variants_are_compared_with_the_variant_they_became() -> None: a: dict[str, Any] = {"type": "object", "title": "A", "properties": {"kind": STATUS}} b: dict[str, Any] = { @@ -313,6 +333,20 @@ def test_union_variants_are_compared_with_the_variant_they_became() -> None: assert _diff({"oneOf": [a, b]}, {"oneOf": [b, a]}) == [("", "oneOf variants reordered", False)] +def test_a_variant_title_names_its_generated_type() -> None: + a: dict[str, Any] = {"type": "object", "title": "Selected items", "properties": {"s": STATUS}} + b: dict[str, Any] = {"type": "object", "title": "All items", "properties": {"all": {}}} + assert _diff({"oneOf": [a, b]}, {"oneOf": [{**a, "title": "Chosen items"}, b]}) == [ + ("(oneOf 1)", "variant type renamed from SelectedItems to ChosenItems", False) + ] + assert _diff({"oneOf": [a, b]}, {"oneOf": [{**a, "title": None}, b]}) == [ + ("(oneOf 1)", "variant type renamed from SelectedItems to a numbered name", False) + ] + # The same generated name, or a title outside a union, is documentation. + assert _diff({"oneOf": [a, b]}, {"oneOf": [{**a, "title": "Selected Items"}, b]}) == [] + assert _diff(a, {**a, "title": "Chosen items"}) == [] + + def test_newly_required_request_fields_count_and_response_requiredness_does_not() -> None: optional = {**OBJ, "required": []} assert _diff(optional, OBJ, request=True) == [("a", "now required", True)] @@ -421,6 +455,38 @@ def test_every_success_response_is_compared_and_reported_once() -> None: assert [(c.path, c.kind) for c in found] == [("a", "removed")] +def test_a_success_status_only_one_side_has_changes_the_result() -> None: + # The method returns every 2xx body, and None when a 2xx response has no body. + job = {"type": "object", "properties": {"job_id": {"type": "string"}}} + one = {"200": _json(OBJ)} + no_body = {"204": {"description": "No content"}} + + def changes(old: Any, new: Any) -> list[tuple[str, bool]]: + found = spec_sync.describe_change({}, _op(responses=old), {}, _op(responses=new)) + return [(c.kind, c.breaking) for c in found] + + assert changes(one, {**one, **no_body}) == [("can now be null (status 204 has no body)", True)] + assert changes({**one, **no_body}, one) == [ + ("can no longer be null (status 204 removed)", False) + ] + two = {**one, "202": _json(job)} + assert changes(one, two) == [("status 202 added with another body", False)] + removed = "type changed: status 202 removed, and no other status returns its body" + assert changes(two, one) == [(removed, True)] + # The same body under one more or one less status changes no type. + same = {**one, "202": _json(OBJ)} + assert changes(one, same) == [("status 202 added", False)] + assert changes(same, one) == [("status 202 removed", False)] + + +def test_a_body_that_appears_is_compared_field_by_field() -> None: + optional = {**OBJ, "required": []} + found = spec_sync.describe_change({}, _op(), {}, _op(body=optional)) + assert [(c.where, c.path, c.kind, c.breaking) for c in found] == [ + ("request body", "a", "added", False) + ] + + def test_references_are_followed() -> None: doc = {"components": {"schemas": {"Obj": OBJ}}} new_doc = {"components": {"schemas": {"Obj": {**OBJ, "properties": {}}}}} @@ -483,6 +549,41 @@ def test_feat_when_a_caller_can_observe_it_and_chore_for_documentation( assert result.commit_message() == f"{commit_type}(spec): sync API spec 2026-11-06\n" +def _batch_delete_variant(spec: Any) -> Any: + """The inline oneOf variant named "Selected transactions" (its type is named after it).""" + path = "/v3/organization/{organization_id}/application/{application_id}/transactions/delete/" + body = spec["paths"][path]["delete"]["requestBody"]["content"]["application/json"] + variant = body["schema"]["oneOf"][0] + assert variant["title"] == "Selected transactions" + return variant + + +@pytest.mark.parametrize( + ("change", "commit_type"), + [ + (lambda s: _batch_delete_variant(s).update(title="Selected entries"), "feat"), + (lambda s: _batch_delete_variant(s).update(title="Selected Transactions"), "chore"), + ( + lambda s: s["components"]["schemas"]["TransactionVerificationSessionAction"].update( + title="Renamed" + ), + "chore", + ), + ], + ids=["renames the type", "same type name", "component title"], +) +def test_a_variant_title_that_names_a_type_is_feat( + openapi: pathlib.Path, change: Any, commit_type: str +) -> None: + spec = json.loads((openapi / "verification.json").read_text("utf-8")) + change(spec) + result = _sync(openapi, {"verification.json": json.dumps(spec).encode()}) + assert result.commit_type == commit_type + assert result.breaking == [] + renamed = "variant type renamed from SelectedTransactions to SelectedEntries" + assert (renamed in result.summary) == (commit_type == "feat") + + def test_a_new_webhook_event_is_feat(openapi: pathlib.Path) -> None: spec = json.loads((openapi / "verification.json").read_text("utf-8")) spec["x-webhooks"]["synthetic.event"] = spec["x-webhooks"]["status.updated"]